FEATURE: Basic support for threads.net onebox (#22471)

This commit is contained in:
Rafael dos Santos Silva 2023-07-06 16:02:49 -03:00 committed by GitHub
parent dc46acb851
commit 3fd327c458
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 551 additions and 0 deletions

View File

@ -1010,3 +1010,81 @@ aside.onebox.preview-error .site-icon {
height: 16px;
margin-right: 0.5em;
}
.onebox.threadsstatus .onebox-body {
.thumbnail,
.thumbnail.onebox-avatar {
width: 36px;
height: 36px;
margin-right: 12px;
}
.threads-screen-name {
font-size: var(--font-down-1);
}
h4 {
margin-bottom: 0;
}
.thread-contents .thread-description {
white-space: pre-line;
}
p,
.thread-contents {
clear: left;
padding: 1em 0;
.quoted {
border: 1px solid var(--primary-low);
padding: 0.5em 1em;
margin-top: 1em;
white-space: normal;
.quoted-link {
color: inherit;
}
.quoted-title {
font-weight: bold;
margin: 0.5em 0;
padding: 0;
span {
font-weight: lighter;
color: var(--primary-medium);
}
}
div {
margin-bottom: 5px;
}
}
}
.date {
display: flex;
line-height: var(--line-height-small);
.timestamp {
color: var(--primary-medium);
}
}
.like,
.replies {
align-items: center;
color: var(--primary-medium);
display: flex;
margin-left: 0.75em;
svg {
fill: currentColor;
margin-right: 0.25em;
}
}
.is-reply {
color: var(--primary-medium);
margin-right: 0.25em;
}
}

View File

@ -165,6 +165,7 @@ require_relative "engine/google_play_app_onebox"
require_relative "engine/image_onebox"
require_relative "engine/video_onebox"
require_relative "engine/audio_onebox"
require_relative "engine/threads_status_onebox"
require_relative "engine/stack_exchange_onebox"
require_relative "engine/twitter_status_onebox"
require_relative "engine/wikimedia_onebox"

View File

@ -0,0 +1,91 @@
# frozen_string_literal: true
module Onebox
module Engine
class ThreadsStatusOnebox
include Engine
include LayoutSupport
include HTML
matches_regexp(%r{^https?://www\.threads\.net/t/(?<id>[\d\w_-]+)/?.*?$})
always_https
def self.priority
1
end
private
def link
raw.css("link[rel='canonical']").first["href"]
end
def likes
@og[:description].split(" ").first
end
def replies
@og[:description].split(", ").drop(1).join(", ").split(" repl").first
end
def description
text = @og[:description].split(". ").drop(1).join(". ")
linkify_mentions(text)
end
def title
@og[:title].split(" (@").first
end
def screen_name
@og[:title].split(" (@").drop(1).join(" (@").split(") on Threads")[0]
end
def avatar
poster_response =
begin
Onebox::Helpers.fetch_response("https://www.threads.net/@#{screen_name}")
rescue StandardError
return nil
end
poster_html = Nokogiri.HTML(poster_response)
poster_data = ::Onebox::OpenGraph.new(poster_html).data
poster_data[:image]
end
def image
@og[:image]
end
def favicon
raw.css("link[rel='icon']").first["href"]
end
def linkify_mentions(text)
text.gsub(/@([\w\d]+)/, "<a href='https://www.threads.net/@\\1'>@\\1</a>")
end
def data
@og = ::Onebox::OpenGraph.new(raw).data
@data ||= {
favicon: favicon,
link: link,
description: description,
image: image,
title: title,
screen_name: screen_name,
avatar: avatar,
likes: likes,
replies: replies,
}
# if the image is the same as the avatar, don't show it
# means it's a thread with no image
@data[:image] = nil if @data[:image].split("?").first == @data[:avatar].split("?").first
@data
end
end
end
end

View File

@ -0,0 +1,30 @@
{{#avatar}}<img src="{{avatar}}" class="thumbnail onebox-avatar">{{/avatar}}
<h4><a href="{{link}}" target="_blank" rel="noopener">{{title}}</a></h4>
<div class="threads-screen-name"><a href="{{link}}" target="_blank" rel="noopener">@{{screen_name}}</a></div>
<div class="thread-contents">
<span class="thread-description">{{{description}}}</span>
{{#image}}
<div class="scale-images"><img src="{{image}}"></div>
{{/image}}
</div>
<div class="date">
{{#likes}}
<span class="like">
<svg viewBox="0 0 512 512" width="14px" height="16px" aria-hidden="true">
<path d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z"></path>
</svg>
{{likes}}
</span>
{{/likes}}
{{#replies}}
<span class="replies">
<svg viewBox="0 0 640 512" width="14px" height="16px" aria-hidden="true">
<path d="M629.657 343.598L528.971 444.284c-9.373 9.372-24.568 9.372-33.941 0L394.343 343.598c-9.373-9.373-9.373-24.569 0-33.941l10.823-10.823c9.562-9.562 25.133-9.34 34.419.492L480 342.118V160H292.451a24.005 24.005 0 0 1-16.971-7.029l-16-16C244.361 121.851 255.069 96 276.451 96H520c13.255 0 24 10.745 24 24v222.118l40.416-42.792c9.285-9.831 24.856-10.054 34.419-.492l10.823 10.823c9.372 9.372 9.372 24.569-.001 33.941zm-265.138 15.431A23.999 23.999 0 0 0 347.548 352H160V169.881l40.416 42.792c9.286 9.831 24.856 10.054 34.419.491l10.822-10.822c9.373-9.373 9.373-24.569 0-33.941L144.971 67.716c-9.373-9.373-24.569-9.373-33.941 0L10.343 168.402c-9.373 9.373-9.373 24.569 0 33.941l10.822 10.822c9.562 9.562 25.133 9.34 34.419-.491L96 169.881V392c0 13.255 10.745 24 24 24h243.549c21.382 0 32.09-25.851 16.971-40.971l-16.001-16z"></path>
</svg>
{{replies}}
</span>
{{/replies}}
</div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,94 @@
# frozen_string_literal: true
RSpec.describe Onebox::Engine::ThreadsStatusOnebox do
context "with a thread with only text" do
let(:link) { "https://www.threads.net/t/CuVvRcttG57" }
let(:html) { described_class.new(link).to_html }
before do
stub_request(:get, link).to_return(
status: 200,
body: onebox_response("threadsstatus_without_image"),
)
stub_request(:get, "https://www.threads.net/@rafael_falco").to_return(
status: 200,
body: onebox_response("threadsstatus_without_image"),
)
end
it "includes threads content" do
expect(html).to include("trazer a lista de follows")
end
it "includes name" do
expect(html).to include("Rafael Silva")
end
it "includes username" do
expect(html).to include("@rafael_falco")
end
it "includes user avatar" do
expect(html).to include(
"https://scontent.cdninstagram.com/v/t51.2885-19/358195671_1485179698889636_5420020496346583344_n.jpg?stp=dst-jpg_s150x150&amp;_nc_ht=scontent.cdninstagram.com&amp;_nc_cat=108&amp;_nc_ohc=UbFgg6blcOUAX8XVrUj&amp;edm=APs17CUBAAAA&amp;ccb=7-5&amp;oh=00_AfDTSDE1W16bDEOUCofc_RLwOXbwfwL83BafmR_f4_ou6g&amp;oe=64AB848C&amp;_nc_sid=10d13b",
)
end
it "includes twitter link" do
expect(html).to include("https://www.threads.net/t/CuVvRcttG57")
end
it "includes twitter likes" do
expect(html).to include("3")
end
it "includes twitter retweets" do
expect(html).to include("1")
end
end
context "with a thread containing an image" do
let(:link) { "https://www.threads.net/t/CuWRRrQuql9" }
let(:html) { described_class.new(link).to_html }
before do
stub_request(:get, link).to_return(
status: 200,
body: onebox_response("threadsstatus_featured_image"),
)
stub_request(:get, "https://www.threads.net/@joyqiuu").to_return(
status: 200,
body: onebox_response("threadsstatus_profile"),
)
end
it "includes threads content" do
expect(html).to include("10M users later")
end
it "includes name" do
expect(html).to include("Joy Qiu")
end
it "includes username" do
expect(html).to include("@joyqiuu")
end
it "includes user avatar" do
expect(html).to include(
"https://scontent.cdninstagram.com/v/t51.2885-19/358167674_306426985144380_6235341132840289293_n.jpg?stp=dst-jpg_s640x640&amp;_nc_ht=scontent.cdninstagram.com&amp;_nc_cat=1&amp;_nc_ohc=KqFQdmSjeMsAX-OWNHA&amp;edm=APs17CUBAAAA&amp;ccb=7-5&amp;oh=00_AfDrfi6q0GGPALemTc0YzaE-Bnxm0GJ3QTrswCox095yRA&amp;oe=64AC85F1&amp;_nc_sid=10d13b",
)
end
it "includes twitter link" do
expect(html).to include("https://www.threads.net/t/CuWRRrQuql9")
end
it "includes twitter likes" do
expect(html).to include("5.8K")
end
it "includes twitter retweets" do
expect(html).to include("449")
end
end
end