diff --git a/fixture/vcr_cassettes/tiktok_invalid.json b/fixture/vcr_cassettes/tiktok_invalid.json new file mode 100644 index 0000000..ef5ef23 --- /dev/null +++ b/fixture/vcr_cassettes/tiktok_invalid.json @@ -0,0 +1,25 @@ +[ + { + "request": { + "body": "", + "headers": [], + "method": "get", + "options": { + "follow_redirect": "true", + "ssl_options": { + "versions": { + "tlsv1.2": true + } + } + }, + "request_body": "", + "url": "https://www.tiktok.com/@scout2015/video/invalid_url" + }, + "response": { + "binary": false, + "message": "Something went wrong", + "status_code": 400, + "type": "ok" + } + } +] diff --git a/fixture/vcr_cassettes/tiktok_valid.json b/fixture/vcr_cassettes/tiktok_valid.json new file mode 100644 index 0000000..621ddae --- /dev/null +++ b/fixture/vcr_cassettes/tiktok_valid.json @@ -0,0 +1,50 @@ +[ + { + "request": { + "body": "", + "headers": [], + "method": "get", + "options": { + "follow_redirect": "true", + "ssl_options": { + "versions": { + "tlsv1.2": true + } + } + }, + "request_body": "", + "url": "https://www.tiktok.com/oembed?url=https%3A%2F%2Fwww.tiktok.com%2F%40scout2015%2Fvideo%2F6718335390845095173" + }, + "response": { + "binary": false, + "body": "{\"version\":\"1.0\",\"type\":\"video\",\"title\":\"Scramble up ur name & I’ll try to guess it😍❤️ #foryoupage #petsoftiktok #aesthetic\",\"author_url\":\"https://www.tiktok.com/@scout2015\",\"author_name\":\"Scout, Suki & Stella\",\"width\":\"100%\",\"height\":\"100%\",\"html\":\"
@scout2015

Scramble up ur name & I’ll try to guess it😍❤️ #foryoupage #petsoftiktok #aesthetic

♬ original sound - tiff
\",\"thumbnail_width\":720,\"thumbnail_height\":1280,\"thumbnail_url\":\"https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/2367c7d45cf54a1397abd0e72bf22eac?x-expires=1665003600&x-signature=Fpv8iDJCk%2Bt8PO%2F8%2FjE1MNLY4Lc%3D\",\"provider_url\":\"https://www.tiktok.com\",\"provider_name\":\"TikTok\"}", + "headers": { + "Server": "nginx", + "Content-Type": "application/json; charset=utf-8", + "X-Tt-Logid": "20221005154816F14388128A319830E5F4", + "X-Xss-Protection": "1; mode=block", + "X-Frame-Options": "SAMEORIGIN", + "X-Content-Type-Options": "nosniff", + "X-Download-Options": "noopen", + "x-tt-trace-host": "0140a3cbc36dbd2901a952d68819c5a5cbe21382bcb1cf4df83480642d2901718f581ee66fc93afe022de4bdec10ba453b9895718999ec854d41ac681115f19628fda5204bc135e5cd882aae960fc06ba6eb7736d92dcf9c9abb3c3f30b7634ecb", + "Access-Control-Allow-Origin": "*", + "X-Origin-Response-Time": "64,23.45.233.7", + "X-Akamai-Request-ID": "1b69d3c8.7a5f8eb", + "Expires": "Wed, 05 Oct 2022 15:48:16 GMT", + "Cache-Control": "max-age=0, no-cache, no-store", + "Pragma": "no-cache", + "Date": "Wed, 05 Oct 2022 15:48:16 GMT", + "Content-Length": "1563", + "X-Cache": "TCP_MISS from a23-45-12-173.deploy.akamaitechnologies.com (AkamaiGHost/10.9.5-44379351) (-)", + "Connection": "keep-alive", + "X-Cache-Remote": "TCP_MISS from a23-45-233-7.deploy.akamaitechnologies.com (AkamaiGHost/10.9.5-44379351) (-)", + "x-tt-trace-tag": "id=16;cdn-cache=miss;type=dyn", + "Server-Timing": "cdn-cache; desc=MISS, edge; dur=97, origin; dur=64", + "X-Parent-Response-Time": "161,23.45.12.173", + "Set-Cookie": "_abck=BD0C68FA825306B5EA8113BA8184691B~-1~YAAQrQwtF94iPp+DAQAAylLVqAj8iBkIcUKELOZskzFbnO2pr8sYgc7UJCSSj+NWtQrYR49yiwQKnodkVSFDk7QckimIYrRRaFb9hxSciev1yfK9+CG+TncxsggVkloUI7OoLKr0718kCZjX0AncP8n87LiO/UGbqPE/Bi6P2L5oJGT6bgXQec5XsokrJxHCc3DXGYtjztay1OS3TEZBBogksGKtw/ilawqe2TEiemyt0wT1hHECaOXEaZSaKwm44HGggaFUju3J+mK4igrWYW/Zij1nQkW5+GWu3C/7HCjyY22yYmMKwJMzFgYUp068IBbtzBgq5LXA2anRiizh2P/40KS1Qottmq0hFeB9Sm67E4c8tw8aubg=~-1~-1~-1; Domain=.tiktok.com; Path=/; Expires=Thu, 05 Oct 2023 15:48:16 GMT; Max-Age=31536000; Secure" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/twitter_status_invalid.json b/fixture/vcr_cassettes/twitter_status_invalid.json new file mode 100644 index 0000000..36fb91e --- /dev/null +++ b/fixture/vcr_cassettes/twitter_status_invalid.json @@ -0,0 +1,45 @@ +[ + { + "request": { + "body": "", + "headers": [], + "method": "get", + "options": { + "follow_redirect": "true", + "ssl_options": { + "versions": { + "tlsv1.2": true + } + } + }, + "request_body": "", + "url": "https://www.twitter.com/jack/status/invalid_url/" + }, + "response": { + "binary": false, + "body": "\n\n\n\n\n\n\n\n\n
Something went wrong, but don’t fret — let’s give it another shot.
", + "headers": { + "date": "Fri, 09 Jul 2021 02:36:34 GMT", + "expiry": "Tue, 31 Mar 1981 05:00:00 GMT", + "pragma": "no-cache", + "server": "tsa_a", + "set-cookie": "personalization_id=\"v1_5d1/CU/p/G2H28GB5lnkOw==\"; Max-Age=63072000; Expires=Sun, 09 Jul 2023 02:36:34 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None", + "content-type": "text/html; charset=utf-8", + "x-powered-by": "Express", + "cache-control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0", + "last-modified": "Fri, 09 Jul 2021 02:36:34 GMT", + "x-frame-options": "DENY", + "x-xss-protection": "0", + "x-content-type-options": "nosniff", + "content-security-policy": "connect-src 'self' blob: https://*.giphy.com https://*.pscp.tv https://*.video.pscp.tv https://*.twimg.com https://api.twitter.com https://api-stream.twitter.com https://ads-api.twitter.com https://aa.twitter.com https://caps.twitter.com https://media.riffsy.com https://pay.twitter.com https://sentry.io https://ton.twitter.com https://twitter.com https://upload.twitter.com https://www.google-analytics.com https://app.link https://api2.branch.io https://bnc.lt wss://*.pscp.tv https://vmap.snappytv.com https://vmapstage.snappytv.com https://vmaprel.snappytv.com https://vmap.grabyo.com https://dhdsnappytv-vh.akamaihd.net https://pdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://dwo3ckksxlb0v.cloudfront.net ; default-src 'self'; form-action 'self' https://twitter.com https://*.twitter.com; font-src 'self' https://*.twimg.com; frame-src 'self' https://twitter.com https://mobile.twitter.com https://pay.twitter.com https://cards-frame.twitter.com https://accounts.google.com/; img-src 'self' blob: data: https://*.cdn.twitter.com https://ton.twitter.com https://*.twimg.com https://analytics.twitter.com https://cm.g.doubleclick.net https://www.google-analytics.com https://www.periscope.tv https://www.pscp.tv https://media.riffsy.com https://*.giphy.com https://*.pscp.tv https://*.periscope.tv https://prod-periscope-profile.s3-us-west-2.amazonaws.com https://platform-lookaside.fbsbx.com https://scontent.xx.fbcdn.net https://scontent-sea1-1.xx.fbcdn.net https://*.googleusercontent.com https://imgix.revue.co; manifest-src 'self'; media-src 'self' blob: https://twitter.com https://*.twimg.com https://*.vine.co https://*.pscp.tv https://*.video.pscp.tv https://*.giphy.com https://media.riffsy.com https://dhdsnappytv-vh.akamaihd.net https://pdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://dwo3ckksxlb0v.cloudfront.net; object-src 'none'; script-src 'self' 'unsafe-inline' https://*.twimg.com https://www.google-analytics.com https://twitter.com https://app.link https://apis.google.com/js/platform.js https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js 'nonce-MzkyMGVmYzctODQyYS00ODY5LTgzMmMtN2UzNGE1NTJjNjU4'; style-src 'self' 'unsafe-inline' https://*.twimg.com; worker-src 'self' blob:; report-uri https://twitter.com/i/csp_report?a=O5RXE%3D%3D%3D&ro=false", + "strict-transport-security": "max-age=631138519", + "cross-origin-opener-policy": "same-origin-allow-popups", + "cross-origin-embedder-policy": "unsafe-none", + "x-connection-hash": "1ec5111a334e8a88337de721bbf717d812108f1a6559f4569e4d2c3aeca7ec3b", + "transfer-encoding": "chunked" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/twitter_status_valid.json b/fixture/vcr_cassettes/twitter_status_valid.json new file mode 100644 index 0000000..5de1972 --- /dev/null +++ b/fixture/vcr_cassettes/twitter_status_valid.json @@ -0,0 +1,41 @@ +[ + { + "request": { + "body": "", + "headers": [], + "method": "get", + "options": { + "follow_redirect": "true", + "ssl_options": { + "versions": { + "tlsv1.2": true + } + } + }, + "request_body": "", + "url": "https://publish.twitter.com/oembed?url=https%3A%2F%2Ftwitter.com%2Fjack%2Fstatus%2F20" + }, + "response": { + "binary": false, + "body": "{\"url\":\"https:\\/\\/twitter.com\\/jack\\/status\\/20\",\"author_name\":\"jack\",\"author_url\":\"https:\\/\\/twitter.com\\/jack\",\"html\":\"\\u003Cblockquote class=\\\"twitter-tweet\\\"\\u003E\\u003Cp lang=\\\"en\\\" dir=\\\"ltr\\\"\\u003Ejust setting up my twttr\\u003C\\/p\\u003E— jack (@jack) \\u003Ca href=\\\"https:\\/\\/twitter.com\\/jack\\/status\\/20?ref_src=twsrc%5Etfw\\\"\\u003EMarch 21, 2006\\u003C\\/a\\u003E\\u003C\\/blockquote\\u003E\\n\\u003Cscript async src=\\\"https:\\/\\/platform.twitter.com\\/widgets.js\\\" charset=\\\"utf-8\\\"\\u003E\\u003C\\/script\\u003E\\n\",\"width\":550,\"height\":null,\"type\":\"rich\",\"cache_age\":\"3153600000\",\"provider_name\":\"Twitter\",\"provider_url\":\"https:\\/\\/twitter.com\",\"version\":\"1.0\"}", + "headers": { + "date": "Fri, 09 Jul 2021 02:36:28 GMT", + "server": "tsa_a", + "expires": "Sun, 15 Jun 2121 02:36:29 GMT", + "set-cookie": "personalization_id=\"v1_1VCHb8EcTSSour3kTA1gmg==\"; Max-Age=63072000; Expires=Sun, 09 Jul 2023 02:36:29 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None", + "content-type": "application/json; charset=utf-8", + "cache-control": "must-revalidate, max-age=3153600000", + "last-modified": "Fri, 09 Jul 2021 02:36:29 GMT", + "content-length": "664", + "x-frame-options": "SAMEORIGIN", + "x-xss-protection": "0", + "content-disposition": "attachment; filename=json.json", + "x-content-type-options": "nosniff", + "strict-transport-security": "max-age=631138519", + "x-connection-hash": "9a68eee7d5f5b51f5057f60e2c65b1d36fcec3d6f702658b8fa7d58c29000dc9" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/oembed.ex b/lib/oembed.ex index d37d9e1..48e8db8 100644 --- a/lib/oembed.ex +++ b/lib/oembed.ex @@ -7,7 +7,9 @@ defmodule OEmbed do OEmbed.InstagramProvider, OEmbed.PinterestProvider, OEmbed.VimeoProvider, - OEmbed.YoutubeProvider + OEmbed.YoutubeProvider, + OEmbed.TwitterProvider, + OEmbed.TikTokProvider ] @fallback_providers [OEmbed.DiscoverableProvider] diff --git a/lib/oembed/providers/tiktok_provider.ex b/lib/oembed/providers/tiktok_provider.ex new file mode 100644 index 0000000..4dbd2f4 --- /dev/null +++ b/lib/oembed/providers/tiktok_provider.ex @@ -0,0 +1,25 @@ +defmodule OEmbed.TikTokProvider do + @moduledoc """ + oEmbed provider for Tiktok URLs. + """ + use OEmbed.Provider + + @oembed_endpoint "https://www.tiktok.com/oembed?url=" + + @doc """ + Check if this provider supports given URL. + """ + def provides?(url) do + Regex.match?( + ~r/(^|[^'"])(https?:\/\/(www\.)?tiktok\.com\/\@?(\w+)\/video?\/(\d+))/i, + url + ) + end + + @doc """ + Get oEmbed result for given URL. + """ + def get(url) do + get_oembed(@oembed_endpoint <> URI.encode(url, &URI.char_unreserved?/1)) + end +end diff --git a/lib/oembed/providers/twitter_provider.ex b/lib/oembed/providers/twitter_provider.ex new file mode 100644 index 0000000..1c26965 --- /dev/null +++ b/lib/oembed/providers/twitter_provider.ex @@ -0,0 +1,25 @@ +defmodule OEmbed.TwitterProvider do + @moduledoc """ + oEmbed provider for Twitter URLs. + """ + use OEmbed.Provider + + @oembed_endpoint "https://publish.twitter.com/oembed?url=" + + @doc """ + Check if this provider supports given URL. + """ + def provides?(url) do + Regex.match?( + ~r/(^|[^'"])(https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+))/i, + url + ) + end + + @doc """ + Get oEmbed result for given URL. + """ + def get(url) do + get_oembed(@oembed_endpoint <> URI.encode(url, &URI.char_unreserved?/1)) + end +end diff --git a/test/oembed_test.exs b/test/oembed_test.exs index eb6a75d..1ae1500 100644 --- a/test/oembed_test.exs +++ b/test/oembed_test.exs @@ -87,6 +87,19 @@ defmodule OEmbedTest do end end + test "gets rich oembed for valid twitter status url" do + use_cassette "twitter_status_valid" do + {:ok, %Rich{} = oembed} = OEmbed.for("https://twitter.com/jack/status/20") + assert oembed.html =~ "just setting up my twttr" + end + end + + test "gets error response for invalid twitter url" do + use_cassette "twitter_status_invalid" do + {:error, _} = OEmbed.for("https://www.twitter.com/jack/status/invalid_url/") + end + end + test "gets rich oembed for valid soundcloud url" do use_cassette "soundcloud_valid" do {:ok, %Rich{} = oembed} = OEmbed.for("https://soundcloud.com/forss/flickermood") @@ -129,4 +142,19 @@ defmodule OEmbedTest do assert oembed.html =~ "vimeo" end end + + test "gets video oembed for valid tiktok video url" do + use_cassette "tiktok_valid" do + {:ok, %Video{} = oembed} = + OEmbed.for("https://www.tiktok.com/@scout2015/video/6718335390845095173") + + assert oembed.html =~ "Scramble up ur name" + end + end + + test "gets error response for invalid tiktok url" do + use_cassette "tiktok_invalid" do + {:error, _} = OEmbed.for("https://www.tiktok.com/@scout2015/video/invalid_url") + end + end end