app/concerns/twitter_concern.rb
module TwitterConcern
extend ActiveSupport::Concern
included do
include Oauthable
validate :validate_twitter_options
valid_oauth_providers :twitter
gem_dependency_check {
defined?(Twitter) &&
Devise.omniauth_providers.include?(:twitter) &&
ENV['TWITTER_OAUTH_KEY'].present? &&
ENV['TWITTER_OAUTH_SECRET'].present?
}
end
def validate_twitter_options
unless twitter_consumer_key.present? &&
twitter_consumer_secret.present? &&
twitter_oauth_token.present? &&
twitter_oauth_token_secret.present?
errors.add(
:base,
"Twitter consumer_key, consumer_secret, oauth_token, and oauth_token_secret are required to authenticate with the Twitter API. You can provide these as options to this Agent, or as Credentials with the same names, but starting with 'twitter_'."
)
end
end
def twitter_consumer_key
(config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_key
end
def twitter_consumer_secret
(config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_secret
end
def twitter_oauth_token
service && service.token
end
def twitter_oauth_token_secret
service && service.secret
end
def twitter
@twitter ||= Twitter::REST::Client.new do |config|
config.consumer_key = twitter_consumer_key
config.consumer_secret = twitter_consumer_secret
config.access_token = twitter_oauth_token
config.access_token_secret = twitter_oauth_token_secret
end
end
HTML_ENTITIES = {
'&' => '&',
'<' => '<',
'>' => '>',
}
RE_HTML_ENTITIES = Regexp.union(HTML_ENTITIES.keys)
def format_tweet(tweet)
attrs =
case tweet
when Twitter::Tweet
tweet.attrs
when Hash
if tweet.key?(:id)
tweet
else
tweet.deep_symbolize_keys
end
else
raise TypeError, "Unexpected tweet type: #{tweet.class}"
end
text = (attrs[:full_text] || attrs[:text])&.dup or return attrs
expanded_text = text.dup.tap { |text|
attrs.dig(:entities, :urls)&.reverse_each do |entity|
from, to = entity[:indices]
text[from...to] = entity[:expanded_url]
end
}
text.gsub!(RE_HTML_ENTITIES, HTML_ENTITIES)
expanded_text.gsub!(RE_HTML_ENTITIES, HTML_ENTITIES)
attrs[:text] &&= text
attrs[:full_text] &&= text
attrs.update(expanded_text:)
end
module_function :format_tweet
module ClassMethods
def twitter_dependencies_missing
if ENV['TWITTER_OAUTH_KEY'].blank? || ENV['TWITTER_OAUTH_SECRET'].blank?
"## Set TWITTER_OAUTH_KEY and TWITTER_OAUTH_SECRET in your environment to use Twitter Agents."
elsif !defined?(Twitter) || !Devise.omniauth_providers.include?(:twitter)
"## Include the `twitter`, `omniauth-twitter`, and `cantino-twitter-stream` gems in your Gemfile to use Twitter Agents."
end
end
def tweet_event_description(text_key, extra_fields = nil)
<<~MD.indent(4)
{
#{extra_fields&.indent(2)}// ... every Tweet field, including ...
// Huginn automatically decodes "<", ">", and "&" to "<", ">", and "&".
"#{text_key}": "something https://t.co/XXXX",
"user": {
"name": "Mr. Someone",
"screen_name": "Someone",
"location": "Vancouver BC Canada",
"description": "...",
"followers_count": 486,
"friends_count": 1983,
"created_at": "Mon Aug 29 23:38:14 +0000 2011",
"time_zone": "Pacific Time (US & Canada)",
"statuses_count": 3807,
"lang": "en"
},
"retweet_count": 0,
"entities": ...
"lang": "en",
// Huginn adds this field, expanding all shortened t.co URLs in "#{text_key}".
"expanded_text": "something https://example.org/foo/bar"
}
MD
end
end
end
class Twitter::Error
remove_const :FORBIDDEN_MESSAGES
FORBIDDEN_MESSAGES = proc do |message|
case message
when /(?=.*status).*duplicate/i
# - "Status is a duplicate."
Twitter::Error::DuplicateStatus
when /already favorited/i
# - "You have already favorited this status."
Twitter::Error::AlreadyFavorited
when /already retweeted|Share validations failed/i
# - "You have already retweeted this Tweet." (Nov 2017-)
# - "You have already retweeted this tweet." (?-Nov 2017)
# - "sharing is not permissible for this status (Share validations failed)" (-? 2017)
Twitter::Error::AlreadyRetweeted
end
end
end