2019-05-02 17:17:27 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-10-10 19:46:20 -05:00
|
|
|
# lightweight Twitter api calls
|
|
|
|
class TwitterApi
|
|
|
|
class << self
|
2018-07-17 02:29:40 -05:00
|
|
|
include ActionView::Helpers::NumberHelper
|
2013-10-10 19:46:20 -05:00
|
|
|
|
2022-08-21 12:26:24 -05:00
|
|
|
BASE_URL = 'https://api.twitter.com'
|
|
|
|
|
2013-10-10 19:46:20 -05:00
|
|
|
def prettify_tweet(tweet)
|
2017-03-17 15:36:53 -05:00
|
|
|
text = tweet["full_text"].dup
|
2017-07-27 20:20:09 -05:00
|
|
|
if (entities = tweet["entities"]) && (urls = entities["urls"])
|
2013-10-10 19:46:20 -05:00
|
|
|
urls.each do |url|
|
|
|
|
text.gsub!(url["url"], "<a target='_blank' href='#{url["expanded_url"]}'>#{url["display_url"]}</a>")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
text = link_hashtags_in link_handles_in text
|
|
|
|
|
2016-05-11 15:11:26 -05:00
|
|
|
result = Rinku.auto_link(text, :all, 'target="_blank"').to_s
|
|
|
|
|
|
|
|
if tweet['extended_entities'] && media = tweet['extended_entities']['media']
|
|
|
|
media.each do |m|
|
|
|
|
if m['type'] == 'photo'
|
|
|
|
if large = m['sizes']['large']
|
2018-03-23 07:05:17 -05:00
|
|
|
result << "<div class='tweet-images'><img class='tweet-image' src='#{m['media_url_https']}' width='#{large['w']}' height='#{large['h']}'></div>"
|
2016-05-11 15:11:26 -05:00
|
|
|
end
|
2020-08-17 14:37:36 -05:00
|
|
|
elsif m['type'] == 'video' || m['type'] == 'animated_gif'
|
|
|
|
video_to_display = m['video_info']['variants']
|
|
|
|
.select { |v| v['content_type'] == 'video/mp4' }
|
|
|
|
.sort { |v| v['bitrate'] }.last # choose highest bitrate
|
|
|
|
|
2020-08-17 14:53:14 -05:00
|
|
|
if video_to_display && url = video_to_display['url']
|
2020-08-17 14:37:36 -05:00
|
|
|
width = m['sizes']['large']['w']
|
|
|
|
height = m['sizes']['large']['h']
|
|
|
|
|
2020-08-17 17:56:41 -05:00
|
|
|
attributes =
|
|
|
|
if m['type'] == 'animated_gif'
|
|
|
|
%w{
|
2020-08-17 18:26:51 -05:00
|
|
|
playsinline
|
2020-08-17 17:56:41 -05:00
|
|
|
loop
|
|
|
|
muted
|
|
|
|
autoplay
|
|
|
|
disableRemotePlayback
|
|
|
|
disablePictureInPicture
|
|
|
|
}
|
|
|
|
else
|
|
|
|
%w{
|
|
|
|
controls
|
|
|
|
playsinline
|
|
|
|
}
|
|
|
|
end.join(' ')
|
|
|
|
|
2020-08-17 14:37:36 -05:00
|
|
|
result << <<~HTML
|
|
|
|
<div class='tweet-images'>
|
|
|
|
<div class='aspect-image-full-size' style='--aspect-ratio:#{width}/#{height};'>
|
2020-08-17 17:56:41 -05:00
|
|
|
<video class='tweet-video' #{attributes}
|
2020-10-30 09:04:29 -05:00
|
|
|
width='#{width}'
|
2020-08-17 14:37:36 -05:00
|
|
|
height='#{height}'
|
2020-08-17 17:56:41 -05:00
|
|
|
poster='#{m['media_url_https']}'>
|
2020-08-17 14:37:36 -05:00
|
|
|
<source src='#{url}' type="video/mp4">
|
|
|
|
</video>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
HTML
|
2017-03-17 15:49:29 -05:00
|
|
|
end
|
2016-05-11 15:11:26 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
result
|
2013-10-10 19:46:20 -05:00
|
|
|
end
|
|
|
|
|
2018-07-17 02:29:40 -05:00
|
|
|
def prettify_number(count)
|
2018-07-17 02:45:44 -05:00
|
|
|
number_to_human(count, format: '%n%u', precision: 2, units: { thousand: 'K', million: 'M', billion: 'B' })
|
2018-07-17 02:29:40 -05:00
|
|
|
end
|
|
|
|
|
2013-10-10 19:46:20 -05:00
|
|
|
def tweet_for(id)
|
2022-08-21 12:26:24 -05:00
|
|
|
JSON.parse(twitter_get(tweet_uri_for(id)))
|
2013-10-10 19:46:20 -05:00
|
|
|
end
|
2014-01-28 14:51:29 -06:00
|
|
|
alias_method :status, :tweet_for
|
|
|
|
|
2013-10-10 19:46:20 -05:00
|
|
|
def twitter_credentials_missing?
|
|
|
|
consumer_key.blank? || consumer_secret.blank?
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
def link_handles_in(text)
|
2022-02-09 05:54:02 -06:00
|
|
|
text.gsub(/(?:^|\s)@\w+/) do |match|
|
2022-06-10 00:20:26 -05:00
|
|
|
whitespace = match[0] == " " ? " " : ""
|
|
|
|
handle = match.strip[1..]
|
|
|
|
"#{whitespace}<a href='https://twitter.com/#{handle}' target='_blank'>@#{handle}</a>"
|
2022-02-09 05:54:02 -06:00
|
|
|
end.strip
|
2013-10-10 19:46:20 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def link_hashtags_in(text)
|
2022-02-09 05:54:02 -06:00
|
|
|
text.gsub(/(?:^|\s)#\w+/) do |match|
|
2022-06-10 00:20:26 -05:00
|
|
|
whitespace = match[0] == " " ? " " : ""
|
|
|
|
hashtag = match.strip[1..]
|
|
|
|
"#{whitespace}<a href='https://twitter.com/search?q=%23#{hashtag}' target='_blank'>##{hashtag}</a>"
|
2022-02-09 05:54:02 -06:00
|
|
|
end.strip
|
2013-10-10 19:46:20 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def tweet_uri_for(id)
|
2017-03-17 15:36:53 -05:00
|
|
|
URI.parse "#{BASE_URL}/1.1/statuses/show.json?id=#{id}&tweet_mode=extended"
|
2013-10-10 19:46:20 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def twitter_get(uri)
|
|
|
|
request = Net::HTTP::Get.new(uri)
|
|
|
|
request.add_field 'Authorization', "Bearer #{bearer_token}"
|
2022-08-17 10:32:48 -05:00
|
|
|
response = http(uri).request(request)
|
|
|
|
|
|
|
|
if response.kind_of?(Net::HTTPTooManyRequests)
|
|
|
|
Rails.logger.warn("Twitter API rate limit has been reached")
|
|
|
|
end
|
|
|
|
|
|
|
|
response.body
|
2013-10-10 19:46:20 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def authorization
|
|
|
|
request = Net::HTTP::Post.new(auth_uri)
|
|
|
|
|
|
|
|
request.add_field 'Authorization',
|
|
|
|
"Basic #{bearer_token_credentials}"
|
|
|
|
request.add_field 'Content-Type',
|
|
|
|
'application/x-www-form-urlencoded;charset=UTF-8'
|
|
|
|
|
|
|
|
request.set_form_data 'grant_type' => 'client_credentials'
|
|
|
|
|
|
|
|
http(auth_uri).request(request).body
|
|
|
|
end
|
|
|
|
|
|
|
|
def bearer_token
|
|
|
|
@access_token ||= JSON.parse(authorization).fetch('access_token')
|
|
|
|
end
|
|
|
|
|
|
|
|
def bearer_token_credentials
|
|
|
|
Base64.strict_encode64(
|
2019-12-11 22:54:26 -06:00
|
|
|
"#{UrlHelper.encode_component(consumer_key)}:#{UrlHelper.encode_component(consumer_secret)}"
|
2013-10-10 19:46:20 -05:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def auth_uri
|
|
|
|
URI.parse "#{BASE_URL}/oauth2/token"
|
|
|
|
end
|
|
|
|
|
|
|
|
def http(uri)
|
|
|
|
Net::HTTP.new(uri.host, uri.port).tap { |http| http.use_ssl = true }
|
|
|
|
end
|
|
|
|
|
|
|
|
def consumer_key
|
|
|
|
SiteSetting.twitter_consumer_key
|
|
|
|
end
|
|
|
|
|
|
|
|
def consumer_secret
|
|
|
|
SiteSetting.twitter_consumer_secret
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|