discourse/lib/onebox/engine/twitter_status_onebox.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

243 lines
6.4 KiB
Ruby
Raw Permalink Normal View History

# frozen_string_literal: true
module Onebox
module Engine
class TwitterStatusOnebox
include Engine
include LayoutSupport
include HTML
include ActionView::Helpers::NumberHelper
matches_regexp(
%r{^https?://(mobile\.|www\.)?(twitter\.com|x\.com)/.+?/status(es)?/\d+(/(video|photo)/\d?+)?+(/?\?.*)?/?$},
)
always_https
def http_params
{ "User-Agent" => "DiscourseBot/1.0" }
end
def to_html
raw.present? ? super : ""
end
private
def get_twitter_data
response =
begin
# We need to allow cross domain cookies to prevent an
# infinite redirect loop between twitter.com and x.com
Onebox::Helpers.fetch_response(
url,
headers: http_params,
allow_cross_domain_cookies: true,
)
rescue StandardError
return nil
end
html = Nokogiri.HTML(response)
twitter_data = {}
html
.css("meta")
.each do |m|
if m.attribute("property") && m.attribute("property").to_s.match(/^og:/i)
m_content = m.attribute("content").to_s.strip
m_property = m.attribute("property").to_s.gsub("og:", "").gsub(":", "_")
twitter_data[m_property.to_sym] = m_content
end
end
twitter_data
end
def match
@match ||= @url.match(%r{(twitter\.com|x\.com)/.+?/status(es)?/(?<id>\d+)})
end
def twitter_data
@twitter_data ||= get_twitter_data
end
def guess_tweet_index
usernames = meta_tags_data("additionalName").compact
usernames.each_with_index do |username, index|
return index if twitter_data[:url].to_s.include?(username)
end
end
def tweet_index
@tweet_index ||= guess_tweet_index
end
def client
Onebox.options.twitter_client
end
def twitter_api_credentials_present?
client && !client.twitter_credentials_missing?
end
def symbolize_keys(obj)
case obj
when Array
obj.map { |item| symbolize_keys(item) }
when Hash
obj.each_with_object({}) do |(key, value), result|
result[key.to_sym] = symbolize_keys(value)
end
else
obj
end
end
def raw
if twitter_api_credentials_present?
@raw ||= symbolize_keys(client.status(match[:id]))
else
super
end
end
def tweet
if twitter_api_credentials_present?
client.prettify_tweet(raw)&.strip
else
twitter_data[:description].gsub(/“(.+?)”/im) { $1 } if twitter_data[:description]
end
end
def timestamp
if twitter_api_credentials_present? && (created_at = raw.dig(:data, :created_at))
date = DateTime.strptime(created_at, "%Y-%m-%dT%H:%M:%S.%L%z")
date.strftime("%-l:%M %p - %-d %b %Y")
end
end
def title
if twitter_api_credentials_present?
raw.dig(:includes, :users)&.first&.dig(:name)
else
twitter_data[:title]
end
end
def screen_name
if twitter_api_credentials_present?
raw.dig(:includes, :users)&.first&.dig(:username)
else
twitter_data[:title][/\(@([^\)\(]*)\) on X/, 1] if twitter_data[:title].present?
end
end
def avatar
if twitter_api_credentials_present?
raw.dig(:includes, :users)&.first&.dig(:profile_image_url)
else
twitter_data[:image] if twitter_data[:image]&.include?("profile_images")
end
end
def likes
if twitter_api_credentials_present?
prettify_number(raw.dig(:data, :public_metrics, :like_count).to_i)
end
end
def retweets
if twitter_api_credentials_present?
prettify_number(raw.dig(:data, :public_metrics, :retweet_count).to_i)
end
end
def is_reply
if twitter_api_credentials_present?
raw.dig(:data, :referenced_tweets)&.any? { |tweet| tweet.dig(:type) == "replied_to" }
end
end
def quoted_full_name
if twitter_api_credentials_present? && quoted_tweet_author.present?
quoted_tweet_author[:name]
end
end
def quoted_screen_name
if twitter_api_credentials_present? && quoted_tweet_author.present?
quoted_tweet_author[:username]
end
end
def quoted_text
quoted_tweet[:text] if twitter_api_credentials_present? && quoted_tweet.present?
end
def quoted_link
if twitter_api_credentials_present?
"https://twitter.com/#{quoted_screen_name}/status/#{quoted_status_id}"
end
end
def quoted_status_id
raw.dig(:data, :referenced_tweets)&.find { |ref| ref[:type] == "quoted" }&.dig(:id)
end
def quoted_tweet
raw.dig(:includes, :tweets)&.find { |tweet| tweet[:id] == quoted_status_id }
end
def quoted_tweet_author
raw.dig(:includes, :users)&.find { |user| user[:id] == quoted_tweet&.dig(:author_id) }
end
def prettify_number(count)
if count > 0
number_to_human(
count,
format: "%n%u",
precision: 2,
units: {
thousand: "K",
million: "M",
billion: "B",
},
)
end
end
def attr_at_css(css_property, attribute_name)
raw.at_css(css_property)&.attr(attribute_name)
end
def meta_tags_data(attribute_name)
data = []
raw
.css("meta")
.each do |m|
if m.attribute("itemprop") && m.attribute("itemprop").to_s.strip == attribute_name
data.push(m.attribute("content").to_s.strip)
end
end
data
end
def data
@data ||= {
link: link,
tweet: tweet,
timestamp: timestamp,
title: title,
screen_name: screen_name,
avatar: avatar,
likes: likes,
retweets: retweets,
is_reply: is_reply,
quoted_text: quoted_text,
quoted_full_name: quoted_full_name,
quoted_screen_name: quoted_screen_name,
quoted_link: quoted_link,
}
end
end
end
end