friends_controller.rb 3.31 KB
Newer Older
Eugen Rochko's avatar
Eugen Rochko committed
1 2
# frozen_string_literal: true

Eugen Rochko's avatar
Eugen Rochko committed
3 4 5 6
require 'twitter'

class FriendsController < ApplicationController
  before_action :authenticate_user!
Eugen Rochko's avatar
Eugen Rochko committed
7 8 9 10 11
  before_action :set_page
  before_action :set_friends
  before_action :set_top_instances
  before_action :set_next_page
  before_action :paginate_friends
12
  before_action :set_relationships, if: -> { current_user&.mastodon }
Eugen Rochko's avatar
Eugen Rochko committed
13

Eugen Rochko's avatar
Eugen Rochko committed
14 15 16 17
  rescue_from Twitter::Error do |e|
    redirect_to root_path, alert: "Twitter error: #{e}"
  end

Eugen Rochko's avatar
Eugen Rochko committed
18 19 20 21
  PER_PAGE_FRIENDS = 20
  MAX_INSTANCES    = 20
  MIN_INSTANCES    = 4

Eugen Rochko's avatar
Eugen Rochko committed
22
  def index; end
23

Eugen Rochko's avatar
Eugen Rochko committed
24 25
  private

Eugen Rochko's avatar
Eugen Rochko committed
26 27 28 29 30 31 32 33
  def set_page
    @page = (params['page'] || 1).to_i
  end

  def set_next_page
    @next_page = @friends.size > (@page * PER_PAGE_FRIENDS) ? @page + 1 : nil
  end

Eugen Rochko's avatar
Eugen Rochko committed
34 35
  def set_friends
    @friends = User.where(id: Authorization.where(provider: :twitter, uid: twitter_friend_ids).map(&:user_id))
36
                   .includes(:twitter, :mastodon)
Eugen Rochko's avatar
Eugen Rochko committed
37
                   .reject { |user| user.mastodon.nil? }
Eugen Rochko's avatar
Eugen Rochko committed
38 39
  end

Eugen Rochko's avatar
Eugen Rochko committed
40
  def set_top_instances
41 42 43
    @top_instances = friends_domains.map { |k, _| fetch_instance_info(k) }.compact
  end

Eugen Rochko's avatar
Eugen Rochko committed
44 45 46 47 48
  def paginate_friends
    @friends = @friends.slice([(@page - 1) * PER_PAGE_FRIENDS, @friends.size].min, PER_PAGE_FRIENDS)
                       .map { |user| fetch_account_id(user) }
  end

49
  def friends_domains
Eugen Rochko's avatar
Eugen Rochko committed
50
    return default_domains.sample(MIN_INSTANCES) if @friends.empty?
51 52 53 54 55 56

    @friends.collect { |user| user&.mastodon&.uid }
            .compact
            .map { |uid| uid.split('@').last }
            .inject(Hash.new(0)) { |h, k| h[k] += 1; h }
            .sort_by { |k, v| -v }
Eugen Rochko's avatar
Eugen Rochko committed
57
            .take(MAX_INSTANCES)
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
  end

  def default_domains
    %w(
      octodon.social
      mastodon.art
      niu.moe
      todon.nl
      soc.ialis.me
      scifi.fyi
      hostux.social
      mstdn.maud.io
      mastodon.sdf.org
      x0r.be
      toot.cafe
    )
74 75
  end

Eugen Rochko's avatar
Eugen Rochko committed
76
  def twitter_friend_ids
77
    Rails.cache.fetch("#{current_user.id}/twitter-friends", expires_in: 15.minutes) { current_user.twitter_client.friend_ids.to_a }
Eugen Rochko's avatar
Eugen Rochko committed
78 79
  end

80 81 82 83 84
  def fetch_instance_info(host)
    Rails.cache.fetch("instance:#{host}", expires_in: 1.week) { Oj.load(HTTP.get("https://#{host}/api/v1/instance").to_s, mode: :strict) }
  rescue HTTP::Error, OpenSSL::SSL::SSLError, Oj::ParseError
    nil
  end
Eugen Rochko's avatar
Eugen Rochko committed
85

86 87
  def fetch_account_id(user)
    user.tap do |user|
88 89
      next if current_user.mastodon.nil?

90 91 92 93 94 95 96
      begin
        user.relative_account_id = Rails.cache.fetch("#{current_user.id}/#{current_user.mastodon.domain}/#{user.mastodon.uid}", expires_in: 1.week) do
          account, _ = current_user.mastodon_client.perform_request(:get, '/api/v1/accounts/search', q: user.mastodon.uid, resolve: 'true', limit: 1)
          next if account.nil?
          account['id']
        end
      rescue Mastodon::Error, HTTP::Error, OpenSSL::SSL::SSLError
97
        next
98
      end
Eugen Rochko's avatar
Eugen Rochko committed
99 100
    end
  end
101

102 103 104 105
  def set_relationships
    account_map = @friends.map { |user| [user.relative_account_id, user] }.to_h
    account_ids = @friends.collect { |user| user.relative_account_id }.compact
    param_str   = account_ids.map { |id| "id[]=#{id}" }.join('&')
106

107 108 109 110
    current_user.mastodon_client.perform_request(:get, "/api/v1/accounts/relationships?#{param_str}").each do |relationship|
      account_map[relationship['id']].following = relationship['following']
    end
  rescue Mastodon::Error, HTTP::Error, OpenSSL::SSL::SSLError
111 112
    nil
  end
Eugen Rochko's avatar
Eugen Rochko committed
113
end