Implement TwitchTV Unified Embed (#15968)

* still building

* complete twitch unifiedembed implementation

* quick youtube regexp fix

* add some documentation
This commit is contained in:
Arit Amana 2022-01-05 18:27:46 -05:00 committed by GitHub
parent 9fb1899e7e
commit 46e6e44076
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 25 deletions

View file

@ -1,10 +1,13 @@
class TwitchTag < LiquidTagBase
PARTIAL = "liquids/twitch".freeze
REGISTRY_REGEXP = %r{https://(?:clips|player|www).twitch.tv/(?:(?:embed\?clip=|\w+/clip/)|(?:\?video=|videos/))(?<id>[a-zA-Z0-9-]{,100})(?:&[^$]+)?}
VALID_VIDEO_REGEXP = /\A(?<video_id>\d+)\Z/
VALID_CLIP_REGEXP = /\A(?<clip_slug>[a-zA-Z0-9-]{,100})\Z/
REGEXP_OPTIONS = [VALID_VIDEO_REGEXP, VALID_CLIP_REGEXP, REGISTRY_REGEXP].freeze
def initialize(_tag_name, slug, _parse_context)
def initialize(_tag_name, input, _parse_context)
super
@url = parsed_url(Settings::General.app_domain)
@slug = parsed_slug(slug)
@url = parsed_input(strip_tags(input))
@width = 710
@height = 399
end
@ -14,7 +17,6 @@ class TwitchTag < LiquidTagBase
partial: PARTIAL,
locals: {
url: @url,
slug: @slug,
width: @width,
height: @height
},
@ -23,15 +25,50 @@ class TwitchTag < LiquidTagBase
private
# Strip out port number because it confuses Twitch
def parsed_url(url)
url.split(":")[0]
# The `parsed_input` method handles two Twitch Liquid Tag use-cases:
# `{% twitch <video_id or clip_slug> %}`
# `{% embed <url> %}`
#
# The iframe src for a video is different from that for a clip.
#
# In the case of a video_id or clip_slug, this method validates the input
# and then returns the appropriate src.
#
# In the case of a url, this method validates the input, then determines
# whether the id contained in the url is a video_id or clip_slug.
#
# Seeing as the clip_slug regexp would match a video_id, the check
# against the video_id regexp occurs first 😅
def parsed_input(input)
input = input.split("&")[0] # prevent param injection
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid Twitch ID, Slug or URL" unless match
return player_url(match[:video_id]) if match.names.include?("video_id")
return clip_url(match[:clip_slug]) if match.names.include?("clip_slug")
return player_or_clip_url(match) if match.names.include?("id")
end
# prevent param injection
def parsed_slug(slug)
slug.strip.split("&")[0]
def clip_url(id)
"https://clips.twitch.tv/embed?clip=#{id}&parent=#{parent_url}&autoplay=false"
end
def player_url(id)
"https://player.twitch.tv/?video=#{id}&parent=#{parent_url}&autoplay=false"
end
def player_or_clip_url(match)
return player_url(match[:id]) if match[:id].match?(VALID_VIDEO_REGEXP)
return clip_url(match[:id]) if match[:id].match?(VALID_CLIP_REGEXP)
end
def parent_url
Settings::General.app_domain.split(":")[0]
end
end
Liquid::Template.register_tag("twitch", TwitchTag)
UnifiedEmbed.register(TwitchTag, regexp: TwitchTag::REGISTRY_REGEXP)

View file

@ -1,8 +1,8 @@
class YoutubeTag < LiquidTagBase
PARTIAL = "liquids/youtube".freeze
# rubocop:disable Layout/LineLength
REGISTRY_REGEXP = %r{https?://(?:www\.)?(?:youtube\.com|youtu\.be)/(?:embed/|watch\?v=)?(?<video_id>[a-zA-Z0-9_-]{11})(?:\?|&)?(?:t=|start=)?(?<time_parameter>(?:\d{1,}h?)?(?:\d{1,2}m)?(?:\d{1,2}s)?{5,11})?}
VALID_ID_REGEXP = /\A(?<video_id>[a-zA-Z0-9_-]{11})(?:(?:&|\?)(?:t=|start=)(?<time_parameter>(?:\d{1,}h?)?(?:\d{1,2}m)?(?:\d{1,2}s)?))?\Z/
REGISTRY_REGEXP = %r{https?://(?:www\.)?(?:youtube\.com|youtu\.be)/(?:embed/|watch\?v=)?(?<video_id>[a-zA-Z0-9_-]{11})(?:(?:&|\?)(?:t=|start=)(?<time_parameter>(?:\d{1,}h)?(?:\d{1,2}m)?(?:\d{1,2}s)?))?}
VALID_ID_REGEXP = /\A(?<video_id>[a-zA-Z0-9_-]{11})(?:(?:&|\?)(?:t=|start=)(?<time_parameter>(?:\d{1,}h)?(?:\d{1,2}m)?(?:\d{1,2}s)?))?\Z/
# rubocop:enable Layout/LineLength
REGEXP_OPTIONS = [REGISTRY_REGEXP, VALID_ID_REGEXP].freeze

View file

@ -1,6 +1,8 @@
<iframe
src="https://clips.twitch.tv/embed?autoplay=false&clip=<%= slug %>&parent=<%= url %>"
src="<%= url %>"
height="<%= height %>"
width="<%= width %>"
allowfullscreen="true">
allowfullscreen="true"
frameborder="0"
scrolling="no">
</iframe>

View file

@ -2,26 +2,41 @@ require "rails_helper"
require "nokogiri"
RSpec.describe TwitchTag, type: :liquid_tag do
let(:slug) { "CuteSpicyNostrilDoritosChip" }
let(:clip_slug) { "CuteSpicyNostrilDoritosChip" }
let(:video_id) { "1196406756" }
def assert_parses(slug, token)
def assert_parses_clip(slug, token)
liquid = Liquid::Template.parse("{% twitch #{token} %}").render
expect(liquid).to include "https://clips.twitch.tv/embed?autoplay=false&clip=#{slug}&parent=localhost"
expect(liquid).to include "https://clips.twitch.tv/embed?clip=#{slug}&amp;parent=localhost&amp;autoplay=false"
end
it "accepts twitch clip slug" do
assert_parses slug, slug
def assert_parses_video(id, token)
liquid = Liquid::Template.parse("{% twitch #{token} %}").render
expect(liquid).to include "https://player.twitch.tv/?video=#{id}&amp;parent=localhost&amp;autoplay=false"
end
it "accepts twitch clip slug with wonky whitespace" do
assert_parses slug, " #{slug} \t"
context "when twitch clip slug passed in" do
it "accepts slug" do
assert_parses_clip clip_slug, clip_slug
end
it "accepts slug with wonky whitespace" do
assert_parses_clip clip_slug, " #{clip_slug} \t"
end
end
it "forbids inserting autoplay option" do
assert_parses slug, "#{slug}&autoplay=true"
context "when twitch video id passed in" do
it "accepts id" do
assert_parses_video video_id, video_id
end
it "accepts id with wonky whitespace" do
assert_parses_video video_id, " #{video_id} \t"
end
end
it "forbids inserting mute option" do
assert_parses slug, "#{slug}&muted=true"
it "prevents param injection" do
assert_parses_clip clip_slug, "#{clip_slug}&autoplay=true"
assert_parses_video video_id, "#{video_id}&muted=true"
end
end

View file

@ -25,6 +25,14 @@ RSpec.describe UnifiedEmbed::Registry do
"instagram.com/p/CXgzXWXroHK/",
]
valid_twitch_url_formats = [
"https://clips.twitch.tv/embed?clip=SpeedyVivaciousDolphinKappaRoss-IQl5YslMAGKbMOGM&parent=www.example.com",
"https://player.twitch.tv/?video=1222841752&parent=www.example.com",
"https://player.twitch.tv/?video=1222841752",
"https://www.twitch.tv/videos/1250164963",
"https://www.twitch.tv/monchi_tv/clip/CrepuscularSparklingGalagoBudBlast-ij3jvc4r437D4L4L",
]
valid_vimeo_url_formats = [
"https://player.vimeo.com/video/652446985?h=a68f6ed1f5",
"https://vimeo.com/ondemand/withchude/647355334",
@ -98,6 +106,13 @@ RSpec.describe UnifiedEmbed::Registry do
.to eq(SoundcloudTag)
end
it "returns Twitch for a valid twitch url" do
valid_twitch_url_formats.each do |url|
expect(described_class.find_liquid_tag_for(link: url))
.to eq(TwitchTag)
end
end
it "returns TwitterTimelineTag for a twitter timeline url" do
expect(described_class.find_liquid_tag_for(link: "https://twitter.com/FreyaHolmer/timelines/1215413954505297922"))
.to eq(TwitterTimelineTag)