Skip to content

Instantly share code, notes, and snippets.

@jkotchoff
Last active July 3, 2023 10:54
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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 12, 2023

hey @jkotchoff I am trying to use this code for authorization and I have a sample like you put here. Just inside controller index action I put all the code from [2-one-off.rb] file and now I come to code and I stuck there I also put my real callback of my site live there
image
SO I suppose I would need to see somewhere this code and put here in console but I dont see it, also I dont see any request to my callback. What am I doing wrong here?

@jkotchoff
Copy link
Author

jkotchoff commented May 12, 2023

Hey @nezirz, you’ll want to run the one off code in a console. On line 22, you’ll see a url which will be printed to output which you can open in your browser manually. You’ll want that browser to be already authenticated into your twitter developer account.

That url will prompt you for permission and then redirect your local browser to the callback url (ie. the stocklight url) which probably won’t resolve properly however it will contain a ‘code’ query string parameter which you can then take off that redirected url and use in the rest of the one-off script.

@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