friends_controller.rb 2.84 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 7
require 'twitter'

class FriendsController < ApplicationController
  before_action :authenticate_user!

8 9 10 11
  MAX_INSTANCES = 20
  MIN_INSTANCES = 4

  helper_method :default_domains
12

Eugen Rochko's avatar
Eugen Rochko committed
13
  def index
14
    session[:job_id] = FindFriendsWorker.perform_async(current_user.id) unless job_exists?
Eugen Rochko's avatar
Eugen Rochko committed
15 16
  end

Eugen Rochko's avatar
Eugen Rochko committed
17
  def results
18 19 20 21 22
    render json: friends, each_serializer: UserSerializer
  end

  def domains
    render json: friends_domains
Eugen Rochko's avatar
Eugen Rochko committed
23 24
  end

Eugen Rochko's avatar
Eugen Rochko committed
25
  def status
Yamagishi Kazutoshi's avatar
Yamagishi Kazutoshi committed
26
    render json: Sidekiq::Status.get_all(job_id), serializer: StatusSerializer
Eugen Rochko's avatar
Eugen Rochko committed
27 28
  end

29
  def follow_all
Yamagishi Kazutoshi's avatar
Yamagishi Kazutoshi committed
30
    if Sidekiq::Status.complete?(job_id)
Eugen Rochko's avatar
Eugen Rochko committed
31
      loaded_friends = friends
32

Eugen Rochko's avatar
Eugen Rochko committed
33
      loaded_friends.each do |friend|
34 35
        next if friend.relative_account_id.blank? || friend.following

Eugen Rochko's avatar
Eugen Rochko committed
36 37 38
        begin
          relationship     = current_user.mastodon_client.perform_request(:post, "/api/v1/accounts/#{friend.relative_account_id}/follow")
          friend.following = relationship['following'] || relationship['requested']
39
        rescue HTTP::Error, OpenSSL::SSL::SSLError, Oj::ParseError, Mastodon::Error
Eugen Rochko's avatar
Eugen Rochko committed
40 41
          next
        end
42 43
      end

Eugen Rochko's avatar
Eugen Rochko committed
44
      Rails.cache.write("#{current_user.id}/friends", loaded_friends.map { |f| [f.id, f.relative_account_id, f.following] })
45 46 47 48 49
    end

    redirect_to friends_path
  end

Eugen Rochko's avatar
Eugen Rochko committed
50
  private
51

Eugen Rochko's avatar
Eugen Rochko committed
52 53
  def job_id
    session[:job_id]
Eugen Rochko's avatar
Eugen Rochko committed
54 55
  end

56
  def job_exists?
Yamagishi Kazutoshi's avatar
Yamagishi Kazutoshi committed
57
    job_id.present? && Sidekiq::Status.get_all(job_id).key?('status')
58 59
  end

Eugen Rochko's avatar
Eugen Rochko committed
60
  def friends
Yamagishi Kazutoshi's avatar
Yamagishi Kazutoshi committed
61
    return unless Sidekiq::Status.complete?(job_id)
62

63 64 65 66 67 68 69 70 71 72 73 74 75 76
    data_map = Rails.cache.fetch("#{current_user.id}/friends") { [] }.map { |d| [d.first, d] }.to_h

    return [] if data_map.empty?

    User.where(id: data_map.keys).includes(:mastodon, :twitter).map do |user|
      user.relative_account_id = data_map[user.id][1]
      user.following           = data_map[user.id][2]
      user
    end
  end

  def friends_domains
    data = Rails.cache.fetch("#{current_user.id}/friends")

77
    return default_domains if data.empty?
78 79 80 81

    Authorization.where(provider: 'mastodon', user_id: data.map(&:first))
                 .map(&:uid)
                 .map { |uid| uid.split('@').last }
Yamagishi Kazutoshi's avatar
Yamagishi Kazutoshi committed
82 83
                 .each_with_object(Hash.new(0)) { |k, h| h[k] += 1 }
                 .sort_by { |_k, v| -v }
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
                 .take(MAX_INSTANCES)
                 .map { |k, _| fetch_instance_info(k) }
                 .compact
  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
102
    ).sample(MIN_INSTANCES).map { |k| fetch_instance_info(k) }.compact
103 104
  end

105 106 107 108
  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
109
  end
Eugen Rochko's avatar
Eugen Rochko committed
110
end