Skip to content

Instantly share code, notes, and snippets.

@jkotchoff
Last active July 3, 2023 10:54
Show Gist options
  • Select an option

  • Save jkotchoff/e2f5e5fa431f090ab2fb62613287dfbb to your computer and use it in GitHub Desktop.

Select an option

Save jkotchoff/e2f5e5fa431f090ab2fb62613287dfbb to your computer and use it in GitHub Desktop.

Revisions

  1. jkotchoff revised this gist Apr 27, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 1-oauth2_tweet_from_ruby.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    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:
    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](https://github.com/nov/twitter_oauth2) and something to issue HTTP requests (like [Typhoeus](https://github.com/typhoeus/typhoeus)) to your Gemfile:
  2. jkotchoff revised this gist Apr 27, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 1-oauth2_tweet_from_ruby.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    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 these instructions:
    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](https://github.com/nov/twitter_oauth2) and something to issue HTTP requests (like [Typhoeus](https://github.com/typhoeus/typhoeus)) to your Gemfile:
  3. jkotchoff revised this gist Apr 27, 2023. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion 1-oauth2_tweet_from_ruby.md
    Original file line number Diff line number Diff line change
    @@ -3,9 +3,10 @@ These instructions set up a way to publish tweets to Twitter's OAuth2 API from a
    1. Set up a twitter account via these instructions:
    https://github.com/jkotchoff/twitter_oauth2#twitter-account-setup

    2. Add the [twitter_oauth2 gem](https://github.com/nov/twitter_oauth2) to your Gemfile:
    2. Add the [twitter_oauth2 gem](https://github.com/nov/twitter_oauth2) and something to issue HTTP requests (like [Typhoeus](https://github.com/typhoeus/typhoeus)) to your Gemfile:
    ```ruby
    gem 'twitter_oauth2'
    gem 'typhoeus'
    ```

    3. Introduce a rails migration to persist your oauth2 token in a single database record in a table (and update it when it needs refreshing)
  4. jkotchoff revised this gist Apr 27, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 1-oauth2_tweet_from_ruby.md
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@ https://github.com/jkotchoff/twitter_oauth2#twitter-account-setup

    2. Add the [twitter_oauth2 gem](https://github.com/nov/twitter_oauth2) to your Gemfile:
    ```ruby
    gem 'oauth'
    gem 'twitter_oauth2'
    ```

    3. Introduce a rails migration to persist your oauth2 token in a single database record in a table (and update it when it needs refreshing)
  5. jkotchoff revised this gist Apr 27, 2023. No changes.
  6. jkotchoff revised this gist Apr 25, 2023. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions 3-app-models-twitter.rb
    Original file line number Diff line number Diff line change
    @@ -69,6 +69,7 @@ def refresh_token!

    private

    #TODO: don't use Marshal.load here, it violates a security concern according to rubocop
    def client
    @client ||= Marshal.load(marshaled_client)
    end
  7. jkotchoff revised this gist Apr 25, 2023. 3 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  8. jkotchoff renamed this gist Apr 25, 2023. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  9. jkotchoff created this gist Apr 25, 2023.
    79 changes: 79 additions & 0 deletions app-models-twitter.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,79 @@
    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

    def client
    @client ||= Marshal.load(marshaled_client)
    end

    def token
    @token ||= Marshal.load(marshaled_token)
    end
    end
    29 changes: 29 additions & 0 deletions oauth2_tweet_from_ruby
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,29 @@
    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 these instructions:
    https://github.com/jkotchoff/twitter_oauth2#twitter-account-setup

    2. Add the [twitter_oauth2 gem](https://github.com/nov/twitter_oauth2) to your Gemfile:
    ```ruby
    gem 'oauth'
    ```

    3. Introduce a rails migration to persist your oauth2 token in a single database record in a table (and update it when it needs refreshing)
    ```ruby
    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
    ```

    4. Retrieve and persist your token
    (refer *one-off.rb* in this gist)

    5. 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)
    37 changes: 37 additions & 0 deletions one-off.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    # 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