Class: ChatSDK::X::ApiClient

Inherits:
ApiClient::Base
  • Object
show all
Defined in:
lib/chat_sdk/x/api_client.rb

Constant Summary collapse

BASE_URL =
"https://api.x.com"
TOKEN_URL =
"https://api.x.com/2/oauth2/token"
TOKEN_EXPIRY_MARGIN =

seconds before expiry to trigger proactive refresh

60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(access_token: nil, client_id: nil, client_secret: nil, refresh_token: nil, state: nil) ⇒ ApiClient

Returns a new instance of ApiClient.



12
13
14
15
16
17
18
19
20
21
# File 'lib/chat_sdk/x/api_client.rb', line 12

def initialize(access_token: nil, client_id: nil, client_secret: nil,
  refresh_token: nil, state: nil)
  @access_token = access_token
  @client_id = client_id
  @client_secret = client_secret
  @refresh_token = refresh_token
  @state = state
  @token_expires_at = nil
  @refresh_mutex = Mutex.new
end

Instance Attribute Details

#state=(value) ⇒ Object (writeonly)

Allow post-init state injection (called by adapter's set_state)



24
25
26
# File 'lib/chat_sdk/x/api_client.rb', line 24

def state=(value)
  @state = value
end

Instance Method Details

#create_tweet(text:, reply_to: nil, media_ids: nil) ⇒ Object



26
27
28
29
30
31
# File 'lib/chat_sdk/x/api_client.rb', line 26

def create_tweet(text:, reply_to: nil, media_ids: nil)
  body = {"text" => text}
  body["reply"] = {"in_reply_to_tweet_id" => reply_to} if reply_to
  body["media"] = {"media_ids" => media_ids} if media_ids&.any?
  request(:post, "/2/tweets", body)
end

#delete_tweet(tweet_id:) ⇒ Object



74
75
76
# File 'lib/chat_sdk/x/api_client.rb', line 74

def delete_tweet(tweet_id:)
  request(:delete, "/2/tweets/#{tweet_id}")
end

#get_dm_events(participant_id:, cursor: nil, limit: 50) ⇒ Object



82
83
84
85
86
# File 'lib/chat_sdk/x/api_client.rb', line 82

def get_dm_events(participant_id:, cursor: nil, limit: 50)
  query = "max_results=#{[limit, 100].min}&dm_event.fields=id,text,sender_id,created_at"
  query += "&pagination_token=#{cursor}" if cursor
  request(:get, "/2/dm_conversations/with/#{participant_id}/dm_events?#{query}")
end

#get_user(user_id) ⇒ Object



78
79
80
# File 'lib/chat_sdk/x/api_client.rb', line 78

def get_user(user_id)
  request(:get, "/2/users/#{user_id}?user.fields=name,username")
end

#like_tweet(user_id:, tweet_id:) ⇒ Object



66
67
68
# File 'lib/chat_sdk/x/api_client.rb', line 66

def like_tweet(user_id:, tweet_id:)
  request(:post, "/2/users/#{user_id}/likes", {"tweet_id" => tweet_id})
end

#load_stored_tokenObject

Load stored token data from state store (survives restarts)



89
90
91
92
93
94
95
96
97
98
99
# File 'lib/chat_sdk/x/api_client.rb', line 89

def load_stored_token
  return unless @state && @client_id

  stored = @state.get(token_state_key)
  return unless stored.is_a?(Hash)

  @access_token = stored["access_token"] if stored["access_token"]
  @refresh_token = stored["refresh_token"] if stored["refresh_token"]
  @token_expires_at = Time.at(stored["expires_at"]) if stored["expires_at"]
  @connection = nil # rebuild connection with loaded token
end

#send_dm(participant_id:, text:) ⇒ Object



62
63
64
# File 'lib/chat_sdk/x/api_client.rb', line 62

def send_dm(participant_id:, text:)
  request(:post, "/2/dm_conversations/with/#{participant_id}/messages", {"text" => text})
end

#unlike_tweet(user_id:, tweet_id:) ⇒ Object



70
71
72
# File 'lib/chat_sdk/x/api_client.rb', line 70

def unlike_tweet(user_id:, tweet_id:)
  request(:delete, "/2/users/#{user_id}/likes/#{tweet_id}")
end

#upload_media(io:, content_type:, total_bytes:) ⇒ Object



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
# File 'lib/chat_sdk/x/api_client.rb', line 33

def upload_media(io:, content_type:, total_bytes:)
  # INIT
  init_result = request(:post, "/2/media/upload", {
    command: "INIT",
    total_bytes: total_bytes,
    media_type: content_type
  })
  media_id = init_result.dig("data", "id") || init_result["media_id_string"]

  # APPEND (single chunk, works for images <5MB)
  response = upload_connection.post("/2/media/upload") do |req|
    req.body = {
      command: "APPEND",
      media_id: media_id,
      segment_index: 0,
      media_data: Base64.strict_encode64(io.read)
    }
  end
  handle_response(response)

  # FINALIZE
  request(:post, "/2/media/upload", {
    command: "FINALIZE",
    media_id: media_id
  })

  media_id
end