Skip to content

Instantly share code, notes, and snippets.

@jkotchoff
Last active July 3, 2023 10:54
Show Gist options
  • Save jkotchoff/e2f5e5fa431f090ab2fb62613287dfbb to your computer and use it in GitHub Desktop.
Save jkotchoff/e2f5e5fa431f090ab2fb62613287dfbb to your computer and use it in GitHub Desktop.
Twitter v2 Oauth2 send tweet from Ruby on Rails

These instructions set up a way to publish tweets to Twitter's OAuth2 API from a Ruby on Rails app.

  1. Set up a twitter account via the Twitter account setup instructions here: https://github.com/jkotchoff/twitter_oauth2#twitter-account-setup

  2. Add the twitter_oauth2 gem and something to issue HTTP requests (like Typhoeus) to your Gemfile:

gem 'twitter_oauth2'
gem 'typhoeus'
  1. Introduce a rails migration to persist your oauth2 token in a single database record in a table (and update it when it needs refreshing)
class CreateTwitterTokens < ActiveRecord::Migration[7.0]
  def change
    create_table :twitter_tokens do |t|
      t.string :marshaled_client, null: false
      t.string :marshaled_token, null: false

      t.timestamps
    end
  end
end
  1. Retrieve and persist your token (refer one-off.rb in this gist)

  2. Use the token to write tweets to the twitter API, refreshing and persisting the token when it expires (refer app/models/twitter.rb in this gist)

# Redirect uri doesn't matter, it will redirect your browser to this path and you can then get the code from that redirect
client = TwitterOAuth2::Client.new(
identifier: "YOUR-TWITTER-DEVELOPER-OAUTH2-TOKEN-ID",
secret: "YOUR-TWITTER-DEVELOPER-OAUTH2-TOKEN-SECRET",
redirect_uri: "https://stocklight.com/api/twitter/callback",
)
ARGV.clear
authorization_uri = client.authorization_uri(
scope: [
:'users.read',
:'tweet.read',
:'tweet.write',
:'offline.access'
]
)
code_verifier = client.code_verifier
state = client.state
puts authorization_uri
# Get the code from the callback URL
print 'code: ' and STDOUT.flush
code = gets.chop
client.authorization_code = code
token_response = client.access_token! code_verifier
Twitter.first_or_initialize.tap do |twitter_token|
twitter_token.update(
marshaled_client: Marshal.dump(client),
marshaled_token: Marshal.dump(token_response),
)
end
class Twitter < ApplicationRecord
self.table_name = "twitter_tokens"
validates_presence_of :marshaled_client, :marshaled_token
class RetryableTwitterApiError < StandardError; end
# For testing
def self.user_lookup
poll_api(
url: "https://api.twitter.com/2/users/me",
method: :get,
successful_response_code: 200,
params: {}
)
end
def self.tweet(tweet)
poll_api(
url: "https://api.twitter.com/2/tweets",
method: :post,
successful_response_code: 201,
params: {
body: {
text: tweet,
}.to_json
}
)
end
private_class_method def self.poll_api(url:, method:, successful_response_code:, params:)
attempts ||= 0
attempts += 1
instance = self.first
options = {
method: method,
headers: {
"Content-Type" => "application/json",
Authorization: "Bearer #{instance.access_token}"
},
}.merge(params)
request = Typhoeus::Request.new(url, options)
response = request.run
if response.code == 401 && attempts < 2
raise RetryableTwitterApiError
end
raise "#{response.code} response code received polling #{url}" if response.code != successful_response_code
JSON.parse(response.body)
rescue RetryableTwitterApiError
instance.refresh_token!
retry
end
delegate :access_token, to: :token
def refresh_token!
client.refresh_token = token.refresh_token
new_token = client.access_token!
update(
marshaled_client: Marshal.dump(client),
marshaled_token: Marshal.dump(new_token),
)
end
private
#TODO: don't use Marshal.load here, it violates a security concern according to rubocop
def client
@client ||= Marshal.load(marshaled_client)
end
def token
@token ||= Marshal.load(marshaled_token)
end
end
@nezirz
Copy link

nezirz commented May 20, 2023

worked fine, thank you @jkotchoff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment