Class: Anzu::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/anzu/client.rb,
lib/anzu/endpoints/media_v1.rb,
lib/anzu/endpoints/manage_tweets_v2.rb

Constant Summary collapse

UPLAOD_MEDIA_SEGMENT_SIZE =
5_000_000

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(access_token: nil, access_token_secret: nil, consumer_key: nil, consumer_secret: nil, bearer_token: nil) {|_self| ... } ⇒ Client

Returns a new instance of Client.

Yields:

  • (_self)

Yield Parameters:

  • _self (Anzu::Client)

    the object that the method was called on



14
15
16
17
18
19
20
21
22
# File 'lib/anzu/client.rb', line 14

def initialize(access_token: nil, access_token_secret: nil, consumer_key: nil, consumer_secret: nil, bearer_token: nil)
  @access_token = access_token
  @access_token_secret = access_token_secret
  @consumer_key = consumer_key
  @consumer_secret = consumer_secret
  @bearer_token = bearer_token

  yield(self) if block_given?
end

Instance Attribute Details

#access_tokenObject

Returns the value of attribute access_token.



12
13
14
# File 'lib/anzu/client.rb', line 12

def access_token
  @access_token
end

#access_token_secretObject

Returns the value of attribute access_token_secret.



12
13
14
# File 'lib/anzu/client.rb', line 12

def access_token_secret
  @access_token_secret
end

#bearer_tokenObject

Returns the value of attribute bearer_token.



12
13
14
# File 'lib/anzu/client.rb', line 12

def bearer_token
  @bearer_token
end

#consumer_keyObject

Returns the value of attribute consumer_key.



12
13
14
# File 'lib/anzu/client.rb', line 12

def consumer_key
  @consumer_key
end

#consumer_secretObject

Returns the value of attribute consumer_secret.



12
13
14
# File 'lib/anzu/client.rb', line 12

def consumer_secret
  @consumer_secret
end

Instance Method Details

#create_tweet_v2(text, direct_message_deep_link: nil, for_super_followers_only: nil, geo_place_id: nil, media_ids: nil, media_tagged_user_ids: nil, poll_duration_minutes: nil, poll_options: nil, quote_tweet_id: nil, exclude_reply_user_ids: nil, in_reply_to_tweet_id: nil, reply_settings: nil) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/anzu/endpoints/manage_tweets_v2.rb', line 8

def create_tweet_v2(
  text,
  direct_message_deep_link: nil,
  for_super_followers_only: nil,
  geo_place_id: nil,
  media_ids: nil,
  media_tagged_user_ids: nil,
  poll_duration_minutes: nil,
  poll_options: nil,
  quote_tweet_id: nil,
  exclude_reply_user_ids: nil,
  in_reply_to_tweet_id: nil,
  reply_settings: nil
)
  options = {
    direct_message_deep_link: direct_message_deep_link,
    for_super_followers_only: for_super_followers_only,
    geo: geo_place_id && { place_id: geo_place_id }.compact,
    media: media_ids && { media_ids: media_ids.map(&:to_s), tagged_user_ids: media_tagged_user_ids&.map(&:to_s) }.compact,
    poll: poll_options && { duration_minutes: poll_duration_minutes, options: poll_options }.compact,
    quote_tweet_id: quote_tweet_id,
    reply: in_reply_to_tweet_id && { in_reply_to_tweet_id: in_reply_to_tweet_id.to_s, exclude_reply_user_ids: exclude_reply_user_ids&.map(&:to_s) }.compact,
    reply_settings: reply_settings,
    text: text
  }
  options.compact!

  res = req_oauth1(:post, 'https://api.twitter.com/2/tweets') do |req|
    req.content_type = 'application/json; charset=UTF-8'
    req.body = options.to_json
  end
  Error.raise_from_response(res)

  json = JSON.parse(res.body)
  { id: json['data']['id'], text: json['data']['text'] }
end

#delete_tweet_v2(id) ⇒ Object



45
46
47
48
49
50
51
# File 'lib/anzu/endpoints/manage_tweets_v2.rb', line 45

def delete_tweet_v2(id)
  res = req_oauth1(:delete, "https://api.twitter.com/2/tweets/#{URI.encode_www_form_component(id.to_s)}")
  Error.raise_from_response(res)

  json = JSON.parse(res.body)
  { deleted: json['data']['deleted'] }
end

#req_bearer(method, url) {|req| ... } ⇒ Object

Yields:

  • (req)


24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/anzu/client.rb', line 24

def req_bearer(method, url)
  uri = URI.parse(url)

  req_klass = Net::HTTP.const_get(method.capitalize)
  req = req_klass.new(uri)

  req['authorization'] = "Bearer #{@bearer_token}"
  yield(req) if block_given?

  Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
    http.request(req)
  end
end

#req_oauth1(method, url) {|req| ... } ⇒ Object

Yields:

  • (req)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/anzu/client.rb', line 38

def req_oauth1(method, url)
  uri = URI.parse(url)

  req_klass = Net::HTTP.const_get(method.to_s.capitalize)
  req = req_klass.new(uri)

  req['authorization'] =
    SimpleOAuth::Header.new(method, url, {}, consumer_key: @consumer_key, consumer_secret: @consumer_secret, token: @access_token, token_secret: @access_token_secret).to_s
  yield(req) if block_given?

  Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
    http.request(req)
  end
end

#upload_media_v1(bin, mime_type = nil, media_category: nil, additional_owners: nil) ⇒ Object

Parameters:

  • bin (String | File)
  • mime_type (String | nil) (defaults to: nil)
  • media_category (String | nil) (defaults to: nil)

    Must be nil, tweet_image, tweet_video, tweet_gif, dm_image, dm_video, dm_gif, or subtitles

  • additional_owners (Array<String>) (defaults to: nil)


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/anzu/endpoints/media_v1.rb', line 17

def upload_media_v1(bin, mime_type = nil, media_category: nil, additional_owners: nil)
  unless bin.is_a?(String)
    mime_type ||= MIME::Types.type_for(bin.path).find {|e| e.media_type == 'image' || e.media_type == 'video'}&.content_type
    bin.binmode
    bin.rewind
    bin = bin.read
  end

  unless media_category
    if mime_type == 'image/gif'
      media_category = 'tweet_gif'
    elsif mime_type == 'application/x-subrip'
      media_category = 'subtitles'
    elsif mime_type.start_with?('image/')
      media_category = 'tweet_image'
    elsif mime_type.start_with?('video/')
      media_category = 'tweet_video'
    end
  end

  bytesize = bin.bytesize

  init_res = req_oauth1(:post, 'https://upload.twitter.com/1.1/media/upload.json') do |req|
    req.set_form(
      [
        %w[command INIT],
        ['total_bytes', bytesize.to_s],
        ['media_type', mime_type],
        media_category && ['media_category', media_category],
        additional_owners && ['additional_owners', additional_owners.join(',')]
      ].compact,
      'multipart/form-data'
    )
    puts req.body
  end
  Error.raise_from_response(init_res)
  init_json = JSON.parse(init_res.body)

  count = (bytesize + UPLAOD_MEDIA_SEGMENT_SIZE - 1) / UPLAOD_MEDIA_SEGMENT_SIZE
  count.times do |i|
    append_res = req_oauth1(:post, 'https://upload.twitter.com/1.1/media/upload.json') do |req|
      req.set_form(
        [
          %w[command APPEND],
          ['media_id', init_json['media_id_string']],
          ['media', bin.byteslice(i * UPLAOD_MEDIA_SEGMENT_SIZE, UPLAOD_MEDIA_SEGMENT_SIZE), { content_type: 'application/octet-stream' }],
          ['segment_index', i.to_s]
        ],
        'multipart/form-data'
      )
    end
    Error.raise_from_response(append_res)
  end

  finalize_res = req_oauth1(:post, 'https://upload.twitter.com/1.1/media/upload.json') do |req|
    req.set_form([%w[command FINALIZE], ['media_id', init_json['media_id_string']]], 'multipart/form-data')
  end
  Error.raise_from_response(finalize_res)

  json = JSON.parse(finalize_res.body)

  if json['processing_info'] && json['processing_info']['state'] == 'pending'
    sleep json['processing_info']['check_after_secs']

    loop do
      status_res = req_oauth1(:get, "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=#{URI.encode_www_form_component(json['media_id_string'])}")
      Error.raise_from_response(status_res)
      status_json = JSON.parse(status_res.body)

      case status_json['processing_info']['state']
      when 'succeeded'
        break
      when 'failed'
        raise MediaUploadError.new(status_json['processing_info']['error']['code'], status_json['processing_info']['error']['name'],
                                   status_json['processing_info']['error']['message'])
      when 'pending'
        sleep status_json['processing_info']['check_after_secs']
      end
    end
  end

  {
    media_id: json['media_id_string'],
    expires_after_secs: json['expires_after_secs'],
    size: json['size']
  }
end