twitter_concern.rb 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. module TwitterConcern
  2. extend ActiveSupport::Concern
  3. included do
  4. include Oauthable
  5. validate :validate_twitter_options
  6. valid_oauth_providers :twitter
  7. gem_dependency_check {
  8. defined?(Twitter) &&
  9. Devise.omniauth_providers.include?(:twitter) &&
  10. ENV['TWITTER_OAUTH_KEY'].present? &&
  11. ENV['TWITTER_OAUTH_SECRET'].present?
  12. }
  13. end
  14. def validate_twitter_options
  15. unless twitter_consumer_key.present? &&
  16. twitter_consumer_secret.present? &&
  17. twitter_oauth_token.present? &&
  18. twitter_oauth_token_secret.present?
  19. errors.add(
  20. :base,
  21. "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_'."
  22. )
  23. end
  24. end
  25. def twitter_consumer_key
  26. (config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_key
  27. end
  28. def twitter_consumer_secret
  29. (config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_secret
  30. end
  31. def twitter_oauth_token
  32. service && service.token
  33. end
  34. def twitter_oauth_token_secret
  35. service && service.secret
  36. end
  37. def twitter
  38. @twitter ||= Twitter::REST::Client.new do |config|
  39. config.consumer_key = twitter_consumer_key
  40. config.consumer_secret = twitter_consumer_secret
  41. config.access_token = twitter_oauth_token
  42. config.access_token_secret = twitter_oauth_token_secret
  43. end
  44. end
  45. HTML_ENTITIES = {
  46. '&' => '&',
  47. '&lt;' => '<',
  48. '&gt;' => '>',
  49. }
  50. RE_HTML_ENTITIES = Regexp.union(HTML_ENTITIES.keys)
  51. def format_tweet(tweet)
  52. attrs =
  53. case tweet
  54. when Twitter::Tweet
  55. tweet.attrs
  56. when Hash
  57. if tweet.key?(:id)
  58. tweet
  59. else
  60. tweet.deep_symbolize_keys
  61. end
  62. else
  63. raise TypeError, "Unexpected tweet type: #{tweet.class}"
  64. end
  65. text = (attrs[:full_text] || attrs[:text])&.dup or return attrs
  66. expanded_text = text.dup.tap { |text|
  67. attrs.dig(:entities, :urls)&.reverse_each do |entity|
  68. from, to = entity[:indices]
  69. text[from...to] = entity[:expanded_url]
  70. end
  71. }
  72. text.gsub!(RE_HTML_ENTITIES, HTML_ENTITIES)
  73. expanded_text.gsub!(RE_HTML_ENTITIES, HTML_ENTITIES)
  74. attrs[:text] &&= text
  75. attrs[:full_text] &&= text
  76. attrs.update(expanded_text:)
  77. end
  78. module_function :format_tweet
  79. module ClassMethods
  80. def twitter_dependencies_missing
  81. if ENV['TWITTER_OAUTH_KEY'].blank? || ENV['TWITTER_OAUTH_SECRET'].blank?
  82. "## Set TWITTER_OAUTH_KEY and TWITTER_OAUTH_SECRET in your environment to use Twitter Agents."
  83. elsif !defined?(Twitter) || !Devise.omniauth_providers.include?(:twitter)
  84. "## Include the `twitter`, `omniauth-twitter`, and `cantino-twitter-stream` gems in your Gemfile to use Twitter Agents."
  85. end
  86. end
  87. def tweet_event_description(text_key, extra_fields = nil)
  88. <<~MD.indent(4)
  89. {
  90. #{extra_fields&.indent(2)}// ... every Tweet field, including ...
  91. // Huginn automatically decodes "&lt;", "&gt;", and "&amp;" to "<", ">", and "&".
  92. "#{text_key}": "something https://t.co/XXXX",
  93. "user": {
  94. "name": "Mr. Someone",
  95. "screen_name": "Someone",
  96. "location": "Vancouver BC Canada",
  97. "description": "...",
  98. "followers_count": 486,
  99. "friends_count": 1983,
  100. "created_at": "Mon Aug 29 23:38:14 +0000 2011",
  101. "time_zone": "Pacific Time (US & Canada)",
  102. "statuses_count": 3807,
  103. "lang": "en"
  104. },
  105. "retweet_count": 0,
  106. "entities": ...
  107. "lang": "en",
  108. // Huginn adds this field, expanding all shortened t.co URLs in "#{text_key}".
  109. "expanded_text": "something https://example.org/foo/bar"
  110. }
  111. MD
  112. end
  113. end
  114. end
  115. class Twitter::Error
  116. remove_const :FORBIDDEN_MESSAGES
  117. FORBIDDEN_MESSAGES = proc do |message|
  118. case message
  119. when /(?=.*status).*duplicate/i
  120. # - "Status is a duplicate."
  121. Twitter::Error::DuplicateStatus
  122. when /already favorited/i
  123. # - "You have already favorited this status."
  124. Twitter::Error::AlreadyFavorited
  125. when /already retweeted|Share validations failed/i
  126. # - "You have already retweeted this Tweet." (Nov 2017-)
  127. # - "You have already retweeted this tweet." (?-Nov 2017)
  128. # - "sharing is not permissible for this status (Share validations failed)" (-? 2017)
  129. Twitter::Error::AlreadyRetweeted
  130. end
  131. end
  132. end