Implement TwitchTV Unified Embed (#15968)
* still building * complete twitch unifiedembed implementation * quick youtube regexp fix * add some documentation
This commit is contained in:
parent
9fb1899e7e
commit
46e6e44076
5 changed files with 94 additions and 25 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}&parent=localhost&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}&parent=localhost&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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue