view app/models/tweet.rb @ 206:a3c140f01707

Update gems
author nanaya <me@nanaya.pro>
date Mon, 19 Oct 2020 04:10:15 +0900
parents 3176bd0d3c2c
children 17461f7682a2
line wrap: on
line source

class Tweet
  TIMELINE_OPTIONS = {
    :count => 100,
    :exclude_replies => false,
    :include_rts => true,
    :tweet_mode => :extended,
  }

  def self.cache_expires_time
    (15 + rand(15)).minutes
  end

  def self.client_options(id)
    {
      :timeouts => {
        :connect => 5,
        :read => 5,
        :write => 5,
      },
    }.merge $cfg[:twitter][id]
  end

  def initialize(twitter_id)
    @clients = {}
    @twitter_id = twitter_id
  end

  def id
    user.id
  end

  def timeline
    if @timeline.nil?
      cache_key = "timeline:v2:#{id}/#{Base64.urlsafe_encode64 id.to_s}"
      raw = Rails.cache.fetch(cache_key, :expires_in => self.class.cache_expires_time) do
        client_try(:user_timeline, id, TIMELINE_OPTIONS).tap do |data|
          if data[:result] == :ok
            if data[:data].any? && data[:data].first.user.id != id
              wrong_user = data[:data].first.user
              throw "Wrong timeline data. Requested: #{id}, got: #{wrong_user.id} (#{wrong_user.screen_name.printable})"
            end

            data[:data] = data[:data].select do |tweet|
              tweet.retweeted_status.nil? || tweet.user.id != tweet.retweeted_status.user.id
            end.map { |tweet| tweet.to_h }
          end
        end
      end

      raise Twitter::Error::NotFound if raw[:result] == :not_found

      @timeline = raw[:data].map { |tweet_hash| Twitter::Tweet.new(tweet_hash) }
    end

    @timeline
  end

  def user
    if @user.nil?
      cache_key = "user:v1:#{@twitter_id.is_a?(Integer) ? 'id' : 'lookup'}:#{@twitter_id}"
      raw = Rails.cache.fetch(cache_key, :expires_in => self.class.cache_expires_time) do
        client_try(:user, @twitter_id).tap do |data|
          if data[:result] == :ok
            user = data[:data]

            if user.id != @twitter_id && user.screen_name.downcase != @twitter_id.try(:downcase)
              throw "Wrong user data. Requested: #{@twitter_id}, got: #{user.id} (#{user.screen_name.printable})"
            end
          end
        end
      end

      raise Twitter::Error::NotFound if raw[:result] == :not_found

      @user = raw[:data]
    end

    @user
  end

  def client
    @clients[client_config_id] ||= Twitter::REST::Client.new(self.class.client_options client_config_id)
  end

  def client_try(method, *args)
    initial_config_id = client_config_id

    begin
      data = client.public_send method, *args
    rescue Twitter::Error::TooManyRequests
      @client_config_id += 1

      if initial_config_id == client_config_id
        raise
      else
        retry
      end
    rescue Twitter::Error::NotFound
      return { :result => :not_found }
    end

    { :result => :ok, :data => data }
  end

  def client_config_id
    @client_config_count ||= $cfg[:twitter].size
    @client_config_id ||= rand(@client_config_count)

    @client_config_id %= @client_config_count
  end
end