twitter.rb 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class ExternalCredential::Twitter
  3. def self.app_verify(params)
  4. register_webhook(params)
  5. end
  6. def self.request_account_to_link(credentials = {}, app_required = true)
  7. external_credential = ExternalCredential.find_by(name: 'twitter')
  8. raise Exceptions::UnprocessableEntity, __('There is no Twitter app configured.') if !external_credential && app_required
  9. if external_credential
  10. if credentials[:consumer_key].blank?
  11. credentials[:consumer_key] = external_credential.credentials['consumer_key']
  12. end
  13. if credentials[:consumer_secret].blank?
  14. credentials[:consumer_secret] = external_credential.credentials['consumer_secret']
  15. end
  16. end
  17. raise Exceptions::UnprocessableEntity, __("The required parameter 'consumer_key' is missing.") if credentials[:consumer_key].blank?
  18. raise Exceptions::UnprocessableEntity, __("The required parameter 'consumer_secret' is missing.") if credentials[:consumer_secret].blank?
  19. consumer = OAuth::Consumer.new(
  20. credentials[:consumer_key],
  21. credentials[:consumer_secret], {
  22. site: 'https://api.twitter.com'
  23. }
  24. )
  25. begin
  26. request_token = consumer.get_request_token(oauth_callback: ExternalCredential.callback_url('twitter'))
  27. rescue => e
  28. case e.message
  29. when '401 Unauthorized'
  30. raise "#{e.message} (Invalid credentials may be to blame.)"
  31. when '403 Forbidden'
  32. raise "#{e.message} (Your app's callback URL configuration on developer.twitter.com may be to blame.)"
  33. else
  34. raise
  35. end
  36. end
  37. {
  38. request_token: request_token,
  39. authorize_url: request_token.authorize_url,
  40. }
  41. end
  42. def self.link_account(request_token, params)
  43. external_credential = ExternalCredential.find_by(name: 'twitter')
  44. raise Exceptions::UnprocessableEntity, __('There is no Twitter app configured.') if !external_credential
  45. raise Exceptions::UnprocessableEntity, __("The required parameter 'request_token' is missing.") if !request_token
  46. raise Exceptions::UnprocessableEntity, __("The provided 'oauth_token' is invalid.") if request_token.params[:oauth_token] != params[:oauth_token]
  47. access_token = request_token.get_access_token(oauth_verifier: params[:oauth_verifier])
  48. client = TwitterSync.new(
  49. consumer_key: external_credential.credentials[:consumer_key],
  50. consumer_secret: external_credential.credentials[:consumer_secret],
  51. access_token: access_token.token,
  52. access_token_secret: access_token.secret,
  53. )
  54. client_user = client.who_am_i
  55. # check if account already exists
  56. Channel.where(area: 'Twitter::Account').each do |channel|
  57. next if !channel.options
  58. next if !channel.options['user']
  59. next if !channel.options['user']['id']
  60. next if channel.options['user']['id'].to_s != client_user.id.to_s && channel.options['user']['screen_name'] != client_user.screen_name
  61. channel.options['user']['id'] = client_user.id.to_s
  62. channel.options['user']['screen_name'] = client_user.screen_name
  63. channel.options['user']['name'] = client_user.name
  64. # update access_token
  65. channel.options['auth']['external_credential_id'] = external_credential.id
  66. channel.options['auth']['oauth_token'] = access_token.token
  67. channel.options['auth']['oauth_token_secret'] = access_token.secret
  68. channel.save!
  69. subscribe_webhook(
  70. channel: channel,
  71. client: client,
  72. external_credential: external_credential,
  73. )
  74. return channel
  75. end
  76. # create channel
  77. channel = Channel.create!(
  78. area: 'Twitter::Account',
  79. options: {
  80. adapter: 'twitter',
  81. user: {
  82. id: client_user.id.to_s,
  83. screen_name: client_user.screen_name,
  84. name: client_user.name,
  85. },
  86. auth: {
  87. external_credential_id: external_credential.id,
  88. oauth_token: access_token.token,
  89. oauth_token_secret: access_token.secret,
  90. },
  91. sync: {
  92. limit: 20,
  93. search: [],
  94. mentions: {},
  95. direct_messages: {},
  96. track_retweets: false
  97. }
  98. },
  99. active: true,
  100. created_by_id: 1,
  101. updated_by_id: 1,
  102. )
  103. subscribe_webhook(
  104. channel: channel,
  105. client: client,
  106. external_credential: external_credential,
  107. )
  108. channel
  109. end
  110. def self.webhook_url
  111. "#{Setting.get('http_type')}://#{Setting.get('fqdn')}#{Rails.configuration.api_path}/channels_twitter_webhook"
  112. end
  113. def self.register_webhook(params)
  114. request_account_to_link(params, false)
  115. raise Exceptions::UnprocessableEntity, __("The required parameter 'consumer_key' is missing.") if params[:consumer_key].blank?
  116. raise Exceptions::UnprocessableEntity, __("The required parameter 'consumer_secret' is missing.") if params[:consumer_secret].blank?
  117. raise Exceptions::UnprocessableEntity, __("The required parameter 'oauth_token' is missing.") if params[:oauth_token].blank?
  118. raise Exceptions::UnprocessableEntity, __("The required parameter 'oauth_token_secret' is missing.") if params[:oauth_token_secret].blank?
  119. return if params[:env].blank?
  120. env_name = params[:env]
  121. client = TwitterSync.new(
  122. consumer_key: params[:consumer_key],
  123. consumer_secret: params[:consumer_secret],
  124. access_token: params[:oauth_token],
  125. access_token_secret: params[:oauth_token_secret],
  126. )
  127. # needed for verify callback
  128. Rails.cache.write('external_credential_twitter', {
  129. consumer_key: params[:consumer_key],
  130. consumer_secret: params[:consumer_secret],
  131. access_token: params[:oauth_token],
  132. access_token_secret: params[:oauth_token_secret],
  133. })
  134. # verify if webhook is already registered
  135. begin
  136. webhooks = client.webhooks_by_env_name(env_name)
  137. rescue
  138. begin
  139. webhooks = client.webhooks
  140. raise "Dev Environment Label invalid. Please use an existing one #{webhooks[:environments].pluck(:environment_name)}, or create a new one."
  141. rescue Twitter::Error => e
  142. raise "#{e.message} Are you sure you created a development environment on developer.twitter.com?"
  143. end
  144. end
  145. webhook_id = nil
  146. webhook_valid = nil
  147. webhooks.each do |webhook|
  148. next if webhook[:url] != webhook_url
  149. webhook_id = webhook[:id]
  150. webhook_valid = webhook[:valid]
  151. end
  152. # if webhook is already registered
  153. # - in case if webhook is invalid, just send a new verification request
  154. # - in case if webhook is valid return
  155. if webhook_id
  156. if webhook_valid == false
  157. client.webhook_request_verification(webhook_id, env_name, webhook_url)
  158. end
  159. params[:webhook_id] = webhook_id
  160. return params
  161. end
  162. # delete already registered webhooks
  163. webhooks.each do |webhook|
  164. client.webhook_delete(webhook[:id], env_name)
  165. end
  166. # register new webhook
  167. response = client.webhook_register(env_name, webhook_url)
  168. params[:webhook_id] = response[:id]
  169. params
  170. end
  171. def self.subscribe_webhook(channel:, client:, external_credential:)
  172. env_name = external_credential.credentials[:env]
  173. webhook_id = external_credential.credentials[:webhook_id]
  174. Rails.logger.debug { "Starting Twitter subscription for webhook_id #{webhook_id} and Channel #{channel.id}" }
  175. client.webhook_subscribe(env_name)
  176. channel.options['subscribed_to_webhook_id'] = webhook_id
  177. channel.save!
  178. true
  179. end
  180. end