friends_controller.rb 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. # frozen_string_literal: true
  2. require 'twitter'
  3. class FriendsController < ApplicationController
  4. before_action :authenticate_user!
  5. before_action :set_page
  6. before_action :set_friends
  7. before_action :set_top_instances
  8. before_action :set_next_page
  9. before_action :paginate_friends
  10. before_action :set_relationships, if: -> { current_user&.mastodon }
  11. rescue_from Twitter::Error do |e|
  12. redirect_to root_path, alert: "Twitter error: #{e}"
  13. end
  14. PER_PAGE_FRIENDS = 20
  15. MAX_INSTANCES = 20
  16. MIN_INSTANCES = 4
  17. def index; end
  18. private
  19. def set_page
  20. @page = (params['page'] || 1).to_i
  21. end
  22. def set_next_page
  23. @next_page = @friends.size > (@page * PER_PAGE_FRIENDS) ? @page + 1 : nil
  24. end
  25. def set_friends
  26. @friends = User.where(id: Authorization.where(provider: :twitter, uid: twitter_friend_ids).map(&:user_id))
  27. .includes(:twitter, :mastodon)
  28. .reject { |user| user.mastodon.nil? }
  29. end
  30. def set_top_instances
  31. @top_instances = friends_domains.map { |k, _| fetch_instance_info(k) }.compact
  32. end
  33. def paginate_friends
  34. @friends = @friends.slice([(@page - 1) * PER_PAGE_FRIENDS, @friends.size].min, PER_PAGE_FRIENDS)
  35. .map { |user| fetch_account_id(user) }
  36. end
  37. def friends_domains
  38. return default_domains.sample(MIN_INSTANCES) if @friends.empty?
  39. @friends.collect { |user| user&.mastodon&.uid }
  40. .compact
  41. .map { |uid| uid.split('@').last }
  42. .inject(Hash.new(0)) { |h, k| h[k] += 1; h }
  43. .sort_by { |k, v| -v }
  44. .take(MAX_INSTANCES)
  45. end
  46. def default_domains
  47. %w(
  48. octodon.social
  49. mastodon.art
  50. niu.moe
  51. todon.nl
  52. soc.ialis.me
  53. scifi.fyi
  54. hostux.social
  55. mstdn.maud.io
  56. mastodon.sdf.org
  57. x0r.be
  58. toot.cafe
  59. )
  60. end
  61. def twitter_friend_ids
  62. Rails.cache.fetch("#{current_user.id}/twitter-friends", expires_in: 15.minutes) { current_user.twitter_client.friend_ids.to_a }
  63. end
  64. def fetch_instance_info(host)
  65. Rails.cache.fetch("instance:#{host}", expires_in: 1.week) { Oj.load(HTTP.get("https://#{host}/api/v1/instance").to_s, mode: :strict) }
  66. rescue HTTP::Error, OpenSSL::SSL::SSLError, Oj::ParseError
  67. nil
  68. end
  69. def fetch_account_id(user)
  70. user.tap do |user|
  71. next if current_user.mastodon.nil?
  72. begin
  73. user.relative_account_id = Rails.cache.fetch("#{current_user.id}/#{current_user.mastodon.domain}/#{user.mastodon.uid}", expires_in: 1.week) do
  74. account, _ = current_user.mastodon_client.perform_request(:get, '/api/v1/accounts/search', q: user.mastodon.uid, resolve: 'true', limit: 1)
  75. next if account.nil?
  76. account['id']
  77. end
  78. rescue Mastodon::Error, HTTP::Error, OpenSSL::SSL::SSLError
  79. next
  80. end
  81. end
  82. end
  83. def set_relationships
  84. account_map = @friends.map { |user| [user.relative_account_id, user] }.to_h
  85. account_ids = @friends.collect { |user| user.relative_account_id }.compact
  86. param_str = account_ids.map { |id| "id[]=#{id}" }.join('&')
  87. current_user.mastodon_client.perform_request(:get, "/api/v1/accounts/relationships?#{param_str}").each do |relationship|
  88. account_map[relationship['id']].following = relationship['following']
  89. end
  90. rescue Mastodon::Error, HTTP::Error, OpenSSL::SSL::SSLError
  91. nil
  92. end
  93. end