summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docker/Dockerfile.arm644
-rw-r--r--locales/hu-HU.json76
-rw-r--r--locales/it.json13
-rw-r--r--locales/pt.json27
-rw-r--r--shard.lock4
-rw-r--r--shard.yml4
-rw-r--r--spec/helpers_spec.cr112
-rw-r--r--spec/invidious/helpers_spec.cr100
-rw-r--r--spec/spec_helper.cr18
-rw-r--r--src/invidious/channels/about.cr139
-rw-r--r--src/invidious/jobs/refresh_channels_job.cr4
-rw-r--r--src/invidious/routes/api/v1/channels.cr2
12 files changed, 268 insertions, 235 deletions
diff --git a/docker/Dockerfile.arm64 b/docker/Dockerfile.arm64
index 4d5d46bf..d2955c7c 100644
--- a/docker/Dockerfile.arm64
+++ b/docker/Dockerfile.arm64
@@ -1,4 +1,4 @@
-FROM alpine:edge AS builder
+FROM alpine:3.15 AS builder
RUN apk add --no-cache 'crystal=1.2.2-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev
ARG release
@@ -29,7 +29,7 @@ RUN if [ ${release} == 1 ] ; then \
--link-flags "-lxml2 -llzma"; \
fi
-FROM alpine:edge
+FROM alpine:3.15
RUN apk add --no-cache librsvg ttf-opensans
WORKDIR /invidious
RUN addgroup -g 1000 -S invidious && \
diff --git a/locales/hu-HU.json b/locales/hu-HU.json
index 14248e87..3b16c8e4 100644
--- a/locales/hu-HU.json
+++ b/locales/hu-HU.json
@@ -12,7 +12,7 @@
"([^.,0-9]|^)1([^.,0-9]|$)": "`x` lejátszási lista"
},
"LIVE": "ÉLŐ",
- "Shared `x` ago": "`x` ezelőtt lett megosztva",
+ "Shared `x` ago": "`x` ezelőtt megosztva",
"Unsubscribe": "Leiratkozás",
"Subscribe": "Feliratkozás",
"View channel on YouTube": "Csatorna megnézése YouTube-on",
@@ -23,9 +23,9 @@
"last": "utolsó",
"Next page": "Következő oldal",
"Previous page": "Előző oldal",
- "Clear watch history?": "Törölve legyen a megnézett videók listája?",
+ "Clear watch history?": "Törölve legyen a megnézett videók naplója?",
"New password": "Új jelszó",
- "New passwords must match": "Az új jelszavaknak egyezniük kell!",
+ "New passwords must match": "Az új jelszavaknak egyezniük kell.",
"Cannot change password for Google accounts": "A Google-fiók jelszavát nem lehet megváltoztatni.",
"Authorize token?": "Engedélyezve legyen a token?",
"Authorize token for `x`?": "Engedélyezve legyen a token erre? „`x`”",
@@ -43,12 +43,12 @@
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Feliratkozások exportálása OPML-ként (NewPipe-hoz és FreeTube-hoz)",
"Export data as JSON": "Adat exportálása JSON-ként",
"Delete account?": "Törlésre kerüljön a fiók?",
- "History": "Megnézések naplója",
- "An alternative front-end to YouTube": "Alternatív YouTube front-end",
+ "History": "Megnézett videók naplója",
+ "An alternative front-end to YouTube": "Ez az oldal egyike a YouTube alternatív kezelőfelületeinek",
"JavaScript license information": "A JavaScript licencinformációja",
"source": "forrás",
"Log in": "Bejelentkezés",
- "Log in/register": "Bejelentkezés/Regisztráció",
+ "Log in/register": "Bejelentkezés/Regisztrálás",
"Log in with Google": "Bejelentkezés Google-fiókkal",
"User ID": "Felhasználói azonosító",
"Password": "Jelszó",
@@ -56,7 +56,7 @@
"Text CAPTCHA": "Szöveges CAPTCHA kérése",
"Image CAPTCHA": "Kép CAPTCHA kérése",
"Sign In": "Bejelentkezés",
- "Register": "Regisztráció",
+ "Register": "Regisztrálás",
"E-mail": "E-mail-cím",
"Google verification code": "A Google ellenőrző kódja",
"Preferences": "Beállítások",
@@ -76,7 +76,7 @@
"preferences_captions_label": "Felirat nyelvének sorrendje: ",
"Fallback captions: ": "Másodlagos feliratok: ",
"preferences_related_videos_label": "Hasonló videók ajánlása: ",
- "preferences_annotations_label": "Szövegmagyarázatok alapértelmezett mutatása: ",
+ "preferences_annotations_label": "Szövegmagyarázat alapértelmezett mutatása: ",
"preferences_extend_desc_label": "A videó leírása automatikusan látható: ",
"preferences_vr_mode_label": "Interaktív, 360°-os videók ",
"preferences_category_visual": "Kinézet, elrendezés és régió beállításai",
@@ -87,7 +87,7 @@
"light": "világos",
"preferences_thin_mode_label": "Vékony mód: ",
"preferences_category_subscription": "Feliratkozott tartalmak beállításai",
- "preferences_annotations_subscribed_label": "A feliratkozott csatornák szövegmagyarázatainak alapértelmezett mutatása: ",
+ "preferences_annotations_subscribed_label": "A feliratkozott csatornák szövegmagyarázatának alapértelmezett mutatása: ",
"Redirect homepage to feed: ": "Kezdőoldal átirányitása a feedre: ",
"preferences_max_results_label": "Feedben mutatott videók száma: ",
"preferences_sort_label": "Videók rendezése: ",
@@ -105,12 +105,12 @@
"`x` uploaded a video": "`x` feltöltött egy videót",
"`x` is live": "`x` élőben közvetít",
"preferences_category_data": "Fiók beállításai és egyéb lehetőségek",
- "Clear watch history": "Megnézett videók listájának törlése",
+ "Clear watch history": "Megnézett videók naplójának törlése",
"Import/export data": "Adatok importálása vagy exportálása",
"Change password": "Jelszó megváltoztatása",
"Manage subscriptions": "Feliratkozások kezelése",
"Manage tokens": "Tokenek kezelése",
- "Watch history": "Megnézett videók",
+ "Watch history": "Megnézett videók naplója",
"Delete account": "Fiók törlése",
"preferences_category_admin": "Adminisztrátorok beállításai",
"preferences_default_home_label": "Kezdőoldal: ",
@@ -118,7 +118,7 @@
"Top enabled: ": "Toplista engedélyezve: ",
"CAPTCHA enabled: ": "CAPTCHA engedélyezve: ",
"Login enabled: ": "Bejelentkezés engedélyezve: ",
- "Registration enabled: ": "Regisztráció engedélyezve: ",
+ "Registration enabled: ": "Regisztrálás engedélyezve: ",
"Report statistics: ": "Statisztika jelentése: ",
"Save preferences": "Beállítások mentése",
"Subscription manager": "Feliratkozások kezelője",
@@ -150,7 +150,7 @@
"Unlisted": "nem nyilvános",
"Private": "magán",
"View all playlists": "Összes lejátszási lista megnézése",
- "Updated `x` ago": "`x` ezelőtt lett frissítve",
+ "Updated `x` ago": "`x` ezelőtt frissítve",
"Delete playlist `x`?": "Törlésre kerüljön ez a lejátszási lista? „`x`”",
"Delete playlist": "Lejátszási lista törlése",
"Create playlist": "Lejátszási lista létrehozása",
@@ -159,7 +159,7 @@
"Editing playlist `x`": "„`x`” lejátszási lista szerkesztése",
"Show more": "Többi szöveg mutatása",
"Show less": "Kevesebb szöveg mutatása",
- "Watch on YouTube": "Megnézés a YouTube-on",
+ "Watch on YouTube": "YouTube-on megnézni",
"Hide annotations": "Megjegyzések elrejtése",
"Show annotations": "Megjegyzések mutatása",
"Genre: ": "Műfaj: ",
@@ -169,14 +169,14 @@
"Engagement: ": "Visszajelzési mutató: ",
"Whitelisted regions: ": "Engedélyezett régiók: ",
"Blacklisted regions: ": "Tiltott régiók: ",
- "Shared `x`": "`x` osztották meg",
+ "Shared `x`": "`x` napon osztották meg",
"`x` views": {
- "": "`x` alkalommal nézték meg",
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` alkalommal nézték meg"
+ "": "`x` már látta",
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` már látta"
},
"Premieres in `x`": "`x` később lesz a premierje",
"Premieres `x`": "`x` lesz a premierje",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Helló! Úgy tűnik a JavaScript ki van kapcsolva a böngészőben. Ide kattintva lehet olvasni a hozzászólásokat, de a betöltésük így kicsit több időbe fog telni.",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Helló! Úgy tűnik a JavaScript ki van kapcsolva a böngészőben. Ide kattintva lehet olvasni a hozzászólásokat, de a betöltésük így kicsit több időbe telik.",
"View YouTube comments": "YouTube-on lévő hozzászólások olvasása",
"View more comments on Reddit": "A többi hozzászólás olvasása Redditen",
"View `x` comments": {
@@ -193,13 +193,13 @@
"Wrong answer": "Nem jól válaszoltál.",
"Erroneous CAPTCHA": "A CAPTCHA hibás.",
"CAPTCHA is a required field": "A CAPTCHA-mezőt ki kell tölteni.",
- "User ID is a required field": "A felhasználói azonosítót meg kell adni!",
+ "User ID is a required field": "A felhasználói azonosítót meg kell adni.",
"Password is a required field": "Meg kell adni egy jelszót.",
"Wrong username or password": "Vagy a felhasználói név, vagy pedig a jelszó nem megfelelő.",
- "Please sign in using 'Log in with Google'": "A „Bejelentkezés Google-el” gombbal jelentkezz be!",
+ "Please sign in using 'Log in with Google'": "A „Bejelentkezés Google-el” gombbal jelentkezz be.",
"Password cannot be empty": "A jelszót nem lehet kihagyni.",
"Password cannot be longer than 55 characters": "A jelszó nem lehet hosszabb 55 karakternél.",
- "Please log in": "Kérjük, jelentkezz be!",
+ "Please log in": "Kérjük, jelentkezz be.",
"Invidious Private Feed for `x`": "„`x`” Invidious magán feedje",
"channel:`x`": "`x` csatornája",
"Deleted or invalid channel": "A csatorna érvénytelen, vagy pedig törölve lett.",
@@ -226,7 +226,7 @@
"Erroneous challenge": "Hibás challenge",
"Erroneous token": "Hibás token",
"No such user": "Nincs ilyen felhasználó",
- "Token is expired, please try again": "A token lejárt. Kérjük, próbáld meg újból!",
+ "Token is expired, please try again": "A token lejárt. Kérjük, próbáld meg újból.",
"English": "angol",
"English (auto-generated)": "angol (automatikusan létrehozott)",
"Afrikaans": "afrikaans",
@@ -336,7 +336,7 @@
"([^.,0-9]|^)1([^.,0-9]|$)": "`x` évvel"
},
"`x` months": {
- "": "x` hónappal",
+ "": "`x` hónappal",
"([^.,0-9]|^)1([^.,0-9]|$)": "`x` hónappal"
},
"`x` weeks": {
@@ -375,13 +375,13 @@
"Download": "Letöltés",
"Download as: ": "Letöltés másként: ",
"(edited)": "(szerkesztve)",
- "YouTube comment permalink": "YouTube-hozzászólás permalinkje",
- "permalink": "permalink",
- "`x` marked it with a ❤": "`x` egy ❤ jellel jelölte meg",
+ "YouTube comment permalink": "YouTube-hozzászólás idehivatkozása",
+ "permalink": "idehivatkozás",
+ "`x` marked it with a ❤": "`x` ❤ jelet adott a hozzászóláshoz",
"Audio mode": "Csak hanggal",
"Video mode": "Hanggal és képpel",
- "Videos": "Videók",
- "Playlists": "Lejátszási listák",
+ "Videos": "Videói",
+ "Playlists": "Lejátszási listái",
"Community": "Közösség",
"Current version: ": "Jelenlegi verzió: ",
"preferences_quality_option_medium": "Közepes",
@@ -397,13 +397,13 @@
"preferences_quality_dash_option_144p": "144p",
"invidious": "Invidious",
"videoinfo_started_streaming_x_ago": "`x` ezelőtt kezdte streamelni",
- "views": "Megnézések száma szerint",
+ "views": "Mennyien látták",
"purchased": "Megvásárolva",
"360": "360°-os",
"footer_original_source_code": "Eredeti forráskód",
"none": "egyik sem",
- "videoinfo_watch_on_youTube": "Megnézés a YouTube-on",
- "videoinfo_youTube_embed_link": "Beágyazás",
+ "videoinfo_watch_on_youTube": "YouTube-on megnézni",
+ "videoinfo_youTube_embed_link": "beágyazva",
"videoinfo_invidious_embed_link": "Beágyazás linkje",
"download_subtitles": "Felirat – `x` (.vtt)",
"user_created_playlists": "`x` létrehozott lejátszási lista",
@@ -414,11 +414,11 @@
"preferences_quality_dash_option_1440p": "1440p",
"preferences_quality_dash_label": "DASH-videó minősége: ",
"preferences_quality_option_small": "Rossz",
- "date": "Feltöltés dátuma szerint",
+ "date": "Feltöltés dátuma",
"Video unavailable": "A videó nem érhető el",
"preferences_save_player_pos_label": "A videó folytatása onnan, ahol félbe lett hagyva: ",
"preferences_show_nick_label": "Becenév mutatása felül: ",
- "Released under the AGPLv3 on Github.": "Az AGPLv3 licenc alapján, a GitHubon elérhető",
+ "Released under the AGPLv3 on Github.": "AGPLv3 licenc alapján a GitHubon",
"3d": "3D-ben",
"live": "Élőben",
"filter": "Szűrők",
@@ -429,8 +429,8 @@
"adminprefs_modified_source_code_url_label": "A módosított forráskód repositoryjának URL-je:",
"preferences_automatic_instance_redirect_label": "Váltáskor másik Invidious oldal automatikus betöltése (redirect.invidious.io töltődik, ha nem működne): ",
"preferences_region_label": "Ország tartalmainak mutatása: ",
- "relevance": "Relevancia szerint",
- "rating": "Besorolás szerint",
+ "relevance": "Relevancia",
+ "rating": "Besorolás",
"content_type": "Típus",
"today": "Mai napon",
"channel": "Csatorna",
@@ -441,12 +441,12 @@
"sort": "Rendezés módja",
"preferences_category_misc": "További beállítások",
"%A %B %-d, %Y": "%Y. %B %-d %A",
- "long": "Hosszú (több, mint 20 perces)",
+ "long": "Hosszú (20 percnél hosszabb)",
"year": "Ebben az évben",
"hour": "Az elmúlt órában",
"movie": "Film",
"hdr": "HDR",
- "Broken? Try another Invidious Instance": "Nem működik? Próbáld meg egy másik Invidious oldallal!",
+ "Broken? Try another Invidious Instance": "Nem működik? Próbáld meg egy másik Invidious oldallal.",
"duration": "Játékidő",
"next_steps_error_message": "Az alábbi lehetőségek állnak rendelkezésre: ",
"Xhosa": "xhosza",
@@ -459,7 +459,7 @@
"next_steps_error_message_go_to_youtube": "Ugrás a YouTube-ra",
"show": "Műsor",
"4k": "4K",
- "short": "Rövid (kevesebb, mint 4 perces)",
+ "short": "Rövid (4 percnél nem több)",
"month": "Ebben a hónapban",
"subtitles": "Felirattal",
"location": "Közelben"
diff --git a/locales/it.json b/locales/it.json
index b90a1e93..a43e1a49 100644
--- a/locales/it.json
+++ b/locales/it.json
@@ -409,5 +409,16 @@
"location": "Posizione",
"hdr": "HDR",
"filter": "Filtra",
- "Current version: ": "Versione attuale: "
+ "Current version: ": "Versione attuale: ",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_dash_option_480p": "480p",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "360": "360°",
+ "preferences_quality_dash_option_144p": "144p",
+ "Released under the AGPLv3 on Github.": "Rilasciato su Github con licenza AGPLv3."
}
diff --git a/locales/pt.json b/locales/pt.json
index 3665d0b5..1976fe38 100644
--- a/locales/pt.json
+++ b/locales/pt.json
@@ -433,5 +433,30 @@
"footer_modfied_source_code": "Código-fonte alterado",
"footer_donate_page": "Doar",
"preferences_region_label": "País do conteúdo: ",
- "preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: "
+ "preferences_quality_dash_label": "Qualidade de vídeo DASH preferida: ",
+ "preferences_quality_option_small": "Baixa",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_dash_option_auto": "Auto",
+ "preferences_quality_dash_option_best": "Melhor",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_144p": "144p",
+ "purchased": "Adquirido",
+ "360": "360°",
+ "videoinfo_invidious_embed_link": "Incorporar hiperligação",
+ "Video unavailable": "Vídeo não disponível",
+ "invidious": "Invidious",
+ "preferences_quality_option_medium": "Médio",
+ "preferences_quality_option_dash": "DASH (qualidade adaptativa)",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_480p": "480p",
+ "videoinfo_watch_on_youTube": "Ver no YouTube",
+ "preferences_quality_dash_option_worst": "Pior",
+ "none": "nenhum",
+ "videoinfo_youTube_embed_link": "Incorporar",
+ "preferences_save_player_pos_label": "Guardar o tempo atual do vídeo: "
}
diff --git a/shard.lock b/shard.lock
index 21ea9919..0ef10281 100644
--- a/shard.lock
+++ b/shard.lock
@@ -40,6 +40,10 @@ shards:
git: https://github.com/luislavena/radix.git
version: 0.4.1
+ spectator:
+ git: https://github.com/icy-arctic-fox/spectator.git
+ version: 0.10.3
+
sqlite3:
git: https://github.com/crystal-lang/crystal-sqlite3.git
version: 0.18.0
diff --git a/shard.yml b/shard.yml
index b32054e6..25d9e7de 100644
--- a/shard.yml
+++ b/shard.yml
@@ -28,6 +28,10 @@ dependencies:
athena-negotiation:
github: athena-framework/negotiation
version: ~> 0.1.1
+development_dependencies:
+ spectator:
+ github: icy-arctic-fox/spectator
+ version: ~> 0.10.3
crystal: ">= 1.0.0, < 2.0.0"
diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr
deleted file mode 100644
index 3319252f..00000000
--- a/spec/helpers_spec.cr
+++ /dev/null
@@ -1,112 +0,0 @@
-require "kemal"
-require "openssl/hmac"
-require "pg"
-require "protodec/utils"
-require "spec"
-require "yaml"
-require "../src/invidious/helpers/*"
-require "../src/invidious/channels/*"
-require "../src/invidious/videos"
-require "../src/invidious/comments"
-require "../src/invidious/playlists"
-require "../src/invidious/search"
-require "../src/invidious/trending"
-
-CONFIG = Config.from_yaml(File.open("config/config.example.yml"))
-
-describe "Helper" do
- describe "#produce_channel_videos_url" do
- it "correctly produces url for requesting page `x` of a channel's videos" do
- produce_channel_videos_url(ucid: "UCXuqSBlHAE6Xw-yeJA0Tunw").should eq("/browse_ajax?continuation=4qmFsgI8EhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaIEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0V4&gl=US&hl=en")
-
- produce_channel_videos_url(ucid: "UCXuqSBlHAE6Xw-yeJA0Tunw", sort_by: "popular").should eq("/browse_ajax?continuation=4qmFsgJAEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaJEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0V4R0FFPQ%3D%3D&gl=US&hl=en")
-
- produce_channel_videos_url(ucid: "UCXuqSBlHAE6Xw-yeJA0Tunw", page: 20).should eq("/browse_ajax?continuation=4qmFsgJAEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaJEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0l5TUE9PQ%3D%3D&gl=US&hl=en")
-
- produce_channel_videos_url(ucid: "UC-9-kyTW8ZkZNDHQJ6FgpwQ", page: 20, sort_by: "popular").should eq("/browse_ajax?continuation=4qmFsgJAEhhVQy05LWt5VFc4WmtaTkRIUUo2Rmdwd1EaJEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0l5TUJnQg%3D%3D&gl=US&hl=en")
- end
- end
-
- describe "#produce_channel_search_continuation" do
- it "correctly produces token for searching a specific channel" do
- produce_channel_search_continuation("UCXuqSBlHAE6Xw-yeJA0Tunw", "", 100).should eq("4qmFsgJqEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaIEVnWnpaV0Z5WTJnd0FUZ0JZQUY2QkVkS2IxaTRBUUE9WgCaAilicm93c2UtZmVlZFVDWHVxU0JsSEFFNlh3LXllSkEwVHVud3NlYXJjaA%3D%3D")
-
- produce_channel_search_continuation("UCXuqSBlHAE6Xw-yeJA0Tunw", "По ожиशुपतिरपि子而時ஸ்றீனி", 0).should eq("4qmFsgKoARIYVUNYdXFTQmxIQUU2WHcteWVKQTBUdW53GiBFZ1p6WldGeVkyZ3dBVGdCWUFGNkJFZEJRVDI0QVFBPVo-0J_QviDQvtC20LjgpLbgpYHgpKrgpKTgpL_gpLDgpKrgpL_lrZDogIzmmYLgrrjgr43grrHgr4Dgrqngrr-aAilicm93c2UtZmVlZFVDWHVxU0JsSEFFNlh3LXllSkEwVHVud3NlYXJjaA%3D%3D")
- end
- end
-
- describe "#produce_channel_playlists_url" do
- it "correctly produces a /browse_ajax URL with the given UCID and cursor" do
- produce_channel_playlists_url("UCCj956IF62FbT7Gouszaj9w", "AIOkY9EQpi_gyn1_QrFuZ1reN81_MMmI1YmlBblw8j7JHItEFG5h7qcJTNd4W9x5Quk_CVZ028gW").should eq("/browse_ajax?continuation=4qmFsgLNARIYVUNDajk1NklGNjJGYlQ3R291c3phajl3GrABRWdsd2JHRjViR2x6ZEhNd0FqZ0JZQUZxQUxnQkFIcG1VVlZzVUdFeGF6VlNWa1ozWVZZNWJtVlhOSGhZTVVaNVVtNVdZVTFZU214VWFtZDRXREF4VG1KVmEzaFhWekZ6VVcxS2MyUjZhSEZPTUhCSlUxaFNSbEpyWXpGaFJHUjRXVEJ3VlZSdFVUQldlbXcwVGxaR01XRXhPVVJXYkc5M1RXcG9ibFozSUFFWUF3PT0%3D&gl=US&hl=en")
- end
- end
-
- describe "#produce_search_params" do
- it "correctly produces token for searching with specified filters" do
- produce_search_params.should eq("CAASAhABSAA%3D")
-
- produce_search_params(sort: "upload_date", content_type: "video").should eq("CAISAhABSAA%3D")
-
- produce_search_params(content_type: "playlist").should eq("CAASAhADSAA%3D")
-
- produce_search_params(sort: "date", content_type: "video", features: ["hd", "cc", "purchased", "hdr"]).should eq("CAISCxABIAEwAUgByAEBSAA%3D")
-
- produce_search_params(content_type: "channel").should eq("CAASAhACSAA%3D")
- end
- end
-
- describe "#produce_comment_continuation" do
- it "correctly produces a continuation token for comments" do
- produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA").should eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyjAMK9gJBRFNKX2kycXZKZUZ0TDBodG1TNV9LNUN0ajNlR0ZWQk1XTDlXZDQybzNrbVVMNl9tQXpkTHA4NS1saVFaTDBtWXJfMTZCaGFnZ1VxWDY1MlN2OUpxVjZWWGluU2hTUC1aVDZyTDROb2xQQmFQWFZ0SnNPNV9yQV9xRTNHdWJBdUxGdzl1eklJWFUyLUhucFhiZGdQTFdURmF2ZlgyMDZocVdtbXBId1VPcm14UVZfT1g2dFlrTTN1eDNyUEFLQ0RyVDhlV0w3TVUzYkxpTmNuYmdrVzhvMGg4S1lMTF84QlBhOExjSGJUdjhwQW9Oa2plcmxYMXg3SzRwcXhhWFBveXo4OXFObG5oNnJSeDZBWGdBenpvSEgxZG1jeVE4Q0lCZU9IZy1tNGk4WnhkWDRkUDg4WFdySUZnLWpKR2hwR1A4SlVNRGdaZ2F2eFZ4MjI1aFVFWVpNeXJMR2xlcjVlbTRGZ2JHNjJZV0M1MW1vTERMZVlFQSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D")
-
- produce_comment_continuation("_cE8xSu6swE", "ADSJ_i1yz21HI4xrtsYXVC-2_kfZ6kx1yjYQumXAAxqH3CAd7ZxKxfLdZS1__fqhCtOASRbbpSBGH_tH1J96Dxux-Qfjk-lUbupMqv08Q3aHzGu7p70VoUMHhI2-GoJpnbpmcOxkGzeIuenRS_ym2Y8fkDowhqLPFgsS0n4djnZ2UmC17F3Ch3N1S1UYf1ZVOc991qOC1iW9kJDzyvRQTWCPsJUPneSaAKW-Rr97pdesOkR4i8cNvHZRnQKe2HEfsvlJOb2C3lF1dJBfJeNfnQYeh5hv6_fZN7bt3-JL1Xk3Qc9NXNxmmbDpwAC_yFR8dthFfUJdyIO9Nu1D79MLYeR-H5HxqUJokkJiGIz4lTE_CXXbhAI").should eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyiQMK8wJBRFNKX2kxeXoyMUhJNHhydHNZWFZDLTJfa2ZaNmt4MXlqWVF1bVhBQXhxSDNDQWQ3WnhLeGZMZFpTMV9fZnFoQ3RPQVNSYmJwU0JHSF90SDFKOTZEeHV4LVFmamstbFVidXBNcXYwOFEzYUh6R3U3cDcwVm9VTUhoSTItR29KcG5icG1jT3hrR3plSXVlblJTX3ltMlk4ZmtEb3docUxQRmdzUzBuNGRqbloyVW1DMTdGM0NoM04xUzFVWWYxWlZPYzk5MXFPQzFpVzlrSkR6eXZSUVRXQ1BzSlVQbmVTYUFLVy1Scjk3cGRlc09rUjRpOGNOdkhaUm5RS2UySEVmc3ZsSk9iMkMzbEYxZEpCZkplTmZuUVllaDVodjZfZlpON2J0My1KTDFYazNRYzlOWE54bW1iRHB3QUNfeUZSOGR0aEZmVUpkeUlPOU51MUQ3OU1MWWVSLUg1SHhxVUpva2tKaUdJejRsVEVfQ1hYYmhBSSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D")
-
- produce_comment_continuation("29-q7YnyUmY", "").should eq("EkMSCzI5LXE3WW55VW1ZyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyFQoAIg8iCzI5LXE3WW55VW1ZMAAoFA%3D%3D")
-
- produce_comment_continuation("CvFH_6DNRCY", "").should eq("EkMSC0N2RkhfNkROUkNZyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyFQoAIg8iC0N2RkhfNkROUkNZMAAoFA%3D%3D")
- end
- end
-
- describe "#produce_channel_community_continuation" do
- it "correctly produces a continuation token for a channel community" do
- produce_channel_community_continuation("UCCj956IF62FbT7Gouszaj9w", "Egljb21tdW5pdHm4").should eq("4qmFsgIsEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaEEVnbGpiMjF0ZFc1cGRIbTQ%3D")
- produce_channel_community_continuation("UCCj956IF62FbT7Gouszaj9w", "Egljb21tdW5pdHm4AQCqAyQaIBIaVWd3cE9NQmVwWEdjclhsUHg2WjRBYUFCQ1FIZGgDKAA%3D").should eq("4qmFsgJmEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaSkVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2QzY0U5TlFtVndXRWRqY2xoc1VIZzJXalJCWVVGQ1ExRklaR2dES0FBJTNE")
-
- produce_channel_community_continuation("UC-lHJZR3Gqxm24_Vd_AJ5Yw", "Egljb21tdW5pdHm4AQCqAyQaIBIaVWd5RTI2NW1rUkk2cE9uS21nbDRBYUFCQ1FIZGgDKAA%3D").should eq("4qmFsgJmEhhVQy1sSEpaUjNHcXhtMjRfVmRfQUo1WXcaSkVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2Q1UlRJMk5XMXJVa2syY0U5dVMyMW5iRFJCWVVGQ1ExRklaR2dES0FBJTNE")
- produce_channel_community_continuation("UC-lHJZR3Gqxm24_Vd_AJ5Yw", "Egljb21tdW5pdHm4AQCqA-cOCsAOUVVSVFNsOXBNWEYxYlVablFXaGFiWFJNTW5WM1ZHSXdPVU5EWTNoeFJWWlVjRWRGVTBOa1prTktjVUoyWjBZemNEZHRPV2cwV1hWbVJtaFVPWFJwVjJaUU4xTXlNRWRaYlZwSVFUa3dlak5pTUV0dll6QkRVMlpsWHpoVFdUbHFSR0o1YkRkM1kydEhMVTVwWDFCdFdXOUhjR0Z6ZEMxbldVcEhUMjkzUm1saGRXSkViVmR6ZFhwd1QxTnpOME54TW5KUloxQlBkME5QU1VWVWMybHlNbFZvUVV0NlVIZFVhMVV5UzNWUmJHRldkRmszU1dKd1pVUllVMkZFVG1aV1ZsRnVUMGhsZFd0T01sVndTbGd3TkhweVdDMVBTRUphV25GNk5Yb3dYMWRCVTFnMlltODBPRmhIV205WlQwNW1YMjV1UlVKTWNucHNNSGR5Y1hKaFltUkVkblJYZG1Kc1FVaHFUV3BwTkc5R1pUQkVlbGw2ZHpSM2FISlBTSFJoYjJGbVMwNTBiV1pxV2pCSVNWWnZTalpRT0RoclVGVmhia1p5VFhsaWFTMVBjREZZV1dSTFdERkZjSHB0ZUhseWFtRXdNR1JmTkhOWmFEVlZTbVZ1ZUVkRU1XRlFhbU4xVERabk4wdDVSSGxHU2xsT1VEQlJXR1ZLTUhGM1UwWkJTSE5oWkRWQ2NXZHNaMFpqYW1ST1YxZFlhMDVOVUZSSFZWVktRekZSYVhodlUxTm1SV1EwTUdsdWNEWXlPV1YwUjNkcGFVcEVTM040YUZadmRXbHJhblkyZFdFelNHWXpUV3hMYURCa2JIRTFSblJ4Wms4NU1XbGtOM0pHYjBGeU4xZFJNMU5qYkZCd05rZE9jV1JqT1hGRGIyNU5Xak5TUlhkemFsUXRObGt4UWxkUE16ZGFaRTlxVGtaZlIweEhRbXRNWXpCWE9GUjNOMHBsYVhwS2RtSlZkMmxGTVhCbVNIWkdkVTFJY0MxbFdYSkVZM0V0ZFROWWRtVlFlV3hhYlVKMmVreGZUMGxOU2xaSlRFTlBZMVpEUjFwd1RHZFhZMmhIYVVKakxUSmFabXd0U1RNeFJEWkhlSGhYTkhOMU1GZGhOMjFCVlVnNGNFTlJXSGx2WW5ScWNUaHZXWGxKT1d0TVRXc3lRMWc0Um5wU2JEVjBlRGxpTW5vMVRYaEtkelExY201S1JHSmZkamhmTlhOWmRGYzRjak5FVVdkMlpXVnNRWEJyZW5OdFpHcEljVGhWYzFsZkxWa3dRVTkyTVZVMmIyMTNVeTFLVEUxeFIwUldRbmc0VEdsTlpGVktjVmxzTkZGa1UwazFabE0wZUhsRk5WZ3lWR0ZaYzJadlYyaHRPRFpzTjNCT1dHRnBiMHhUVDBkMmRuZFVOMlptVm05dWIwRTFZVkZuYldKNmIwMUNaMng2VGkxSk56bHhXV3BJVGt4RFYwVllUM05pTVcwemRHc3lUVWN6TVVKcVRHdElNVWg1YmtKQmVrbFNVMnczZEVKUlJGOUlNVWRyZERsbFJraHVYekJXZUhGbE1rTTBlVE40YVU1T1pFcGpVMkpFZFMxWVdITjNTMnhWVjJwYVgzVXRXbGcwZG5OSE1qUXpYMlJHTVhSV1kxWkZRMlZwU25OdVlXTkdVek5wVUd4b2FUbDVSRVp4YVhsbFRqbG1aRWxYVFZCMVFWbG9OMEl3TW5KV1JUVjRkREJLZG5obmJGZHhSVlY1ZWpjMFIyeGlZemRIVmkxeFpESmlaMnhFZGxkcVRuSjZNVEZWUkRWamVIQlFkRk5DVmtSU2RITlRaSGhWZG05WE9VUkNhWEYwTm1kSFRtb3RNV1pNYlhSeVJWTnJhRWhIVDB0SU0yVkxUbFZ2V1VGNlJTMDJialJZYkRKdFFUVnJhRVJ4WmpjeFptcERNR001UmpkM2QwNW1VRXd5YUZCZlEwWjFSbEUzY0doRk5ISkZZMWxTTWs5d2RXRnhiRzFrYjBVMmIxWkJaRzkyU2xneFZWOXNiMDVWWkUxRFJ6QjBjWGhpVjBVMldYY3pTUzF4UVcxa1RuZEJRVGRvWVZFNGNsSTBaVUl0UmxacVdETnJXazVLY21aRk9HVndRbWxqUjB0blRFZEZVR3N6YzJOclkwSTNlVlZZVEdkcE1YQkdiMHAyZVU1aGRVZFdVblJQYVhaQlZtdHZSa0UzTFU1Sk1XaFJRMUpMV2kxSWJ6WkxjWEkxZGtSTWJsOVdUa0ZFVmpKZmMwUlFWV3gwUTJ0TFRsbDJaM2gxZFVOSVkzbEVORUpRZVUxMVREQnpOMVowWDI1MWRrVmlUMU54TkRkUk5rVjViMEpRTUZGNmR6RlJSR2RxY1U1eVgwNTBjMDkxWm14R2NUVjBlRkJGT1dGVmFXeFJTMEZYYldwQlVVbHNOVmgwZERZdGFFRlViMWxmUjFWc1EycG1WVkJQV0hkcFVRPT0aIBIaVWd5RTI2NW1rUkk2cE9uS21nbDRBYUFCQ1FIZGgDKGM%3D").should eq("4qmFsgKXFBIYVUMtbEhKWlIzR3F4bTI0X1ZkX0FKNVl3GvoTRWdsamIyMXRkVzVwZEhtNEFRQ3FBLWNPQ3NBT1VWVlNWRk5zT1hCTldFWXhZbFZhYmxGWGFHRmlXRkpOVFc1V00xWkhTWGRQVlU1RVdUTm9lRkpXV2xWalJXUkdWVEJPYTFwclRrdGpWVW95V2pCWmVtTkVaSFJQVjJjd1YxaFdiVkp0YUZWUFdGSndWakphVVU0eFRYbE5SV1JhWWxad1NWRlVhM2RsYWs1cFRVVjBkbGw2UWtSVk1scHNXSHBvVkZkVWJIRlNSMG8xWWtSa00xa3lkRWhNVlRWd1dERkNkRmRYT1VoalIwWjZaRU14YmxkVmNFaFVNamt6VW0xc2FHUlhTa1ZpVm1SNlpGaHdkMVF4VG5wT01FNTRUVzVLVWxveFFsQmtNRTVRVTFWV1ZXTXliSGxOYkZadlVWVjBObFZJWkZWaE1WVjVVek5XVW1KSFJsZGtSbXN6VTFkS2QxcFZVbGxWTWtaRlZHMWFWMVpzUm5WVU1HaHNaRmQwVDAxc1ZuZFRiR2QzVGtod2VWZERNVkJUUlVwaFYyNUdOazVZYjNkWU1XUkNWVEZuTWxsdE9EQlBSbWhJVjIwNVdsUXdOVzFZTWpWMVVsVktUV051Y0hOTlNHUjVZMWhLYUZsdFVrVmtibEpZWkcxS2MxRlZhSEZVVjNCd1RrYzVSMXBVUWtWbGJHdzJaSHBTTTJGSVNsQlRTRkpvWWpKR2JWTXdOVEJpVjFweFYycENTVk5XV25aVGFscFJUMFJvY2xWR1ZtaGlhMXA1VkZoc2FXRlRNVkJqUkVaWlYxZFNURmRFUmtaalNIQjBaVWhzZVdGdFJYZE5SMUptVGtoT1dtRkVWbFpUYlZaMVpVVmtSVTFYUmxGaGJVNHhWRVJhYms0d2REVlNTR3hIVTJ4c1QxVkVRbEpYUjFaTFRVaEdNMVV3V2tKVFNFNW9Xa1JXUTJOWFpITmFNRnBxWVcxU1QxWXhaRmxoTURWT1ZVWlNTRlpXVmt0UmVrWlNZVmhvZGxVeFRtMVNWMUV3VFVkc2RXTkVXWGxQVjFZd1VqTmtjR0ZWY0VWVE0wNDBZVVphZG1SWGJISmhibGt5WkZkRmVsTkhXWHBVVjNoTVlVUkNhMkpJUlRGU2JsSjRXbXM0TlUxWGJHdE9NMHBIWWpCR2VVNHhaRkpOTVU1cVlrWkNkMDVyWkU5alYxSnFUMWhHUkdJeU5VNVhhazVUVWxoa2VtRnNVWFJPYkd0NFVXeGtVRTE2WkdGYVJUbHhWR3RhWmxJd2VFaFJiWFJOV1hwQ1dFOUdVak5PTUhCc1lWaHdTMlJ0U2xaa01teEdUVmhDYlZOSVdrZGtWVEZKWTBNeGJGZFlTa1ZaTTBWMFpGUk9XV1J0VmxGbFYzaGhZbFZLTW1WcmVHWlVNR3hPVTJ4YVNsUkZUbEJaTVZwRVVqRndkMVJIWkZoWk1taElZVlZLYWt4VVNtRmFiWGQwVTFSTmVGSkVXa2hsU0doWVRraE9NVTFHWkdoT01qRkNWbFZuTkdORlRsSlhTR3gyV1c1U2NXTlVhSFpYV0d4S1QxZDBUVlJYYzNsUk1XYzBVbTV3VTJKRVZqQmxSR3hwVFc1dk1WUllhRXRrZWxFeFkyMDFTMUpIU21aa2FtaG1UbGhPV21SR1l6UmphazVGVlZka01scFhWbk5SV0VKeVpXNU9kRnBIY0VsalZHaFdZekZzWmt4V2EzZFJWVGt5VFZaVk1tSXlNVE5WZVRGTFZFVXhlRkl3VWxkUmJtYzBWRWRzVGxwR1ZrdGpWbXh6VGtaR2ExVXdhekZhYkUwd1pVaHNSazVXWjNsV1IwWmFZekphZGxZeWFIUlBSRnB6VGpOQ1QxZEhSbkJpTUhoVVZEQmtNbVJ1WkZWT01scHRWbTA1ZFdJd1JURlpWa1p1WWxkS05tSXdNVU5hTW5nMlZHa3hTazU2YkhoWFYzQkpWR3Q0UkZZd1ZsbFVNMDVwVFZjd2VtUkhjM2xVVldONlRWVktjVlJIZEVsTlZXZzFZbXRLUW1WcmJGTlZNbmN6WkVWS1VsSkdPVWxOVldSeVpFUnNiRkpyYUhWWWVrSlhaVWhHYkUxclRUQmxWRTQwWVZVMVQxcEZjR3BWTWtwRlpGTXhXVmRJVGpOVE1uaFdWakp3WVZnelZYUlhiR2N3Wkc1T1NFMXFVWHBZTWxKSFRWaFNWMWt4V2taUk1sWndVMjVPZFZsWFRrZFZlazV3VlVkNGIyRlViRFZTUlZwNFlWaHNiRlJxYkcxYVJXeFlWRlpDTVZGV2JHOU9NRWwzVFc1S1YxSlVWalJrUkVKTFpHNW9ibUpHWkhoU1ZsWTFaV3BqTUZJeWVHbFplbVJJVm1reGVGcEVTbWxhTW5oRlpHeGtjVlJ1U2paTlZFWldVa1JXYW1WSVFsRmtSazVEVm10U1UyUklUbFJhU0doV1pHMDVXRTlWVWtOaFdFWXdUbTFrU0ZSdGIzUk5WMXBOWWxoU2VWSldUbkpoUldoSVZEQjBTVTB5Vmt4VWJGWjJWMVZHTmxKVE1ESmlhbEpaWWtSS2RGRlVWbkpoUlZKNFdtcGplRnB0Y0VSTlIwMDFVbXBrTTJRd05XMVZSWGQ1WVVaQ1psRXdXakZTYkVVelkwZG9SazVJU2taWk1XeFRUV3M1ZDJSWFJuaGlSekZyWWpCVk1tSXhXa0phUnpreVUyeG5lRlpXT1hOaU1EVldXa1V4UkZKNlFqQmpXR2hwVmpCVk1sZFlZM3BUVXpGNFVWY3hhMVJ1WkVKUlZHUnZXVlpGTkdOc1NUQmFWVWwwVW14YWNWZEVUbkpYYXpWTFkyMWFSazlIVm5kUmJXeHFVakIwYmxSRlpFWlZSM042WXpKT2Nsa3dTVE5sVmxaWlZFZGtjRTFZUWtkaU1IQXlaVlUxYUdSVlpGZFZibEpRWVZoYVFsWnRkSFpTYTBVelRGVTFTazFYYUZKUk1VcE1WMmt4U1dKNldreGpXRWt4Wkd0U1RXSnNPVmRVYTBaRlZtcEtabU13VWxGV1YzZ3dVVEowVEZSc2JESmFNMmd4WkZWT1NWa3piRVZPUlVwUlpWVXhNVlJFUW5wT01Wb3dXREkxTVdSclZtbFVNVTU0VGtSa1VrNXJWalZpTUVwUlRVWkdObVI2UmxKU1IyUnhZMVUxZVZnd05UQmpNRGt4V20xNFIyTlVWakJsUmtKR1QxZEdWbUZYZUZKVE1FWllZbGR3UWxWVmJITk9WbWd3WkVSWmRHRkZSbFZpTVd4bVVqRldjMUV5Y0cxV1ZrSlFWMGhrY0ZWUlBUMGFJQklhVldkNVJUSTJOVzFyVWtrMmNFOXVTMjFuYkRSQllVRkNRMUZJWkdnREtHTSUzRA%3D%3D")
- end
- end
-
- describe "#extract_channel_community_cursor" do
- it "correctly extracts a community cursor from a given continuation" do
- extract_channel_community_cursor("4qmFsgIsEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaEEVnbGpiMjF0ZFc1cGRIbTQ%3D").should eq("Egljb21tdW5pdHk=")
- extract_channel_community_cursor("4qmFsgJoEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaTEVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2QzY0U5TlFtVndXRWRqY2xoc1VIZzJXalJCWVVGQ1ExRklaR2dES0FBJTI1M0Q%3D").should eq("Egljb21tdW5pdHm4AQCqAyQaIEhkaAMSGlVnd3BPTUJlcFhHY3JYbFB4Nlo0QWFBQkNRKAA=")
-
- extract_channel_community_cursor("4qmFsgJoEhhVQy1sSEpaUjNHcXhtMjRfVmRfQUo1WXcaTEVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2Q1UlRJMk5XMXJVa2syY0U5dVMyMW5iRFJCWVVGQ1ExRklaR2dES0FBJTI1M0Q%3D").should eq("Egljb21tdW5pdHm4AQCqAyQaIEhkaAMSGlVneUUyNjVta1JJNnBPbkttZ2w0QWFBQkNRKAA=")
- extract_channel_community_cursor("4qmFsgKZFBIYVUMtbEhKWlIzR3F4bTI0X1ZkX0FKNVl3GvwTRWdsamIyMXRkVzVwZEhtNEFRQ3FBLWNPQ3NBT1VWVlNWRk5zT1hCTldFWXhZbFZhYmxGWGFHRmlXRkpOVFc1V00xWkhTWGRQVlU1RVdUTm9lRkpXV2xWalJXUkdWVEJPYTFwclRrdGpWVW95V2pCWmVtTkVaSFJQVjJjd1YxaFdiVkp0YUZWUFdGSndWakphVVU0eFRYbE5SV1JhWWxad1NWRlVhM2RsYWs1cFRVVjBkbGw2UWtSVk1scHNXSHBvVkZkVWJIRlNSMG8xWWtSa00xa3lkRWhNVlRWd1dERkNkRmRYT1VoalIwWjZaRU14YmxkVmNFaFVNamt6VW0xc2FHUlhTa1ZpVm1SNlpGaHdkMVF4VG5wT01FNTRUVzVLVWxveFFsQmtNRTVRVTFWV1ZXTXliSGxOYkZadlVWVjBObFZJWkZWaE1WVjVVek5XVW1KSFJsZGtSbXN6VTFkS2QxcFZVbGxWTWtaRlZHMWFWMVpzUm5WVU1HaHNaRmQwVDAxc1ZuZFRiR2QzVGtod2VWZERNVkJUUlVwaFYyNUdOazVZYjNkWU1XUkNWVEZuTWxsdE9EQlBSbWhJVjIwNVdsUXdOVzFZTWpWMVVsVktUV051Y0hOTlNHUjVZMWhLYUZsdFVrVmtibEpZWkcxS2MxRlZhSEZVVjNCd1RrYzVSMXBVUWtWbGJHdzJaSHBTTTJGSVNsQlRTRkpvWWpKR2JWTXdOVEJpVjFweFYycENTVk5XV25aVGFscFJUMFJvY2xWR1ZtaGlhMXA1VkZoc2FXRlRNVkJqUkVaWlYxZFNURmRFUmtaalNIQjBaVWhzZVdGdFJYZE5SMUptVGtoT1dtRkVWbFpUYlZaMVpVVmtSVTFYUmxGaGJVNHhWRVJhYms0d2REVlNTR3hIVTJ4c1QxVkVRbEpYUjFaTFRVaEdNMVV3V2tKVFNFNW9Xa1JXUTJOWFpITmFNRnBxWVcxU1QxWXhaRmxoTURWT1ZVWlNTRlpXVmt0UmVrWlNZVmhvZGxVeFRtMVNWMUV3VFVkc2RXTkVXWGxQVjFZd1VqTmtjR0ZWY0VWVE0wNDBZVVphZG1SWGJISmhibGt5WkZkRmVsTkhXWHBVVjNoTVlVUkNhMkpJUlRGU2JsSjRXbXM0TlUxWGJHdE9NMHBIWWpCR2VVNHhaRkpOTVU1cVlrWkNkMDVyWkU5alYxSnFUMWhHUkdJeU5VNVhhazVUVWxoa2VtRnNVWFJPYkd0NFVXeGtVRTE2WkdGYVJUbHhWR3RhWmxJd2VFaFJiWFJOV1hwQ1dFOUdVak5PTUhCc1lWaHdTMlJ0U2xaa01teEdUVmhDYlZOSVdrZGtWVEZKWTBNeGJGZFlTa1ZaTTBWMFpGUk9XV1J0VmxGbFYzaGhZbFZLTW1WcmVHWlVNR3hPVTJ4YVNsUkZUbEJaTVZwRVVqRndkMVJIWkZoWk1taElZVlZLYWt4VVNtRmFiWGQwVTFSTmVGSkVXa2hsU0doWVRraE9NVTFHWkdoT01qRkNWbFZuTkdORlRsSlhTR3gyV1c1U2NXTlVhSFpYV0d4S1QxZDBUVlJYYzNsUk1XYzBVbTV3VTJKRVZqQmxSR3hwVFc1dk1WUllhRXRrZWxFeFkyMDFTMUpIU21aa2FtaG1UbGhPV21SR1l6UmphazVGVlZka01scFhWbk5SV0VKeVpXNU9kRnBIY0VsalZHaFdZekZzWmt4V2EzZFJWVGt5VFZaVk1tSXlNVE5WZVRGTFZFVXhlRkl3VWxkUmJtYzBWRWRzVGxwR1ZrdGpWbXh6VGtaR2ExVXdhekZhYkUwd1pVaHNSazVXWjNsV1IwWmFZekphZGxZeWFIUlBSRnB6VGpOQ1QxZEhSbkJpTUhoVVZEQmtNbVJ1WkZWT01scHRWbTA1ZFdJd1JURlpWa1p1WWxkS05tSXdNVU5hTW5nMlZHa3hTazU2YkhoWFYzQkpWR3Q0UkZZd1ZsbFVNMDVwVFZjd2VtUkhjM2xVVldONlRWVktjVlJIZEVsTlZXZzFZbXRLUW1WcmJGTlZNbmN6WkVWS1VsSkdPVWxOVldSeVpFUnNiRkpyYUhWWWVrSlhaVWhHYkUxclRUQmxWRTQwWVZVMVQxcEZjR3BWTWtwRlpGTXhXVmRJVGpOVE1uaFdWakp3WVZnelZYUlhiR2N3Wkc1T1NFMXFVWHBZTWxKSFRWaFNWMWt4V2taUk1sWndVMjVPZFZsWFRrZFZlazV3VlVkNGIyRlViRFZTUlZwNFlWaHNiRlJxYkcxYVJXeFlWRlpDTVZGV2JHOU9NRWwzVFc1S1YxSlVWalJrUkVKTFpHNW9ibUpHWkhoU1ZsWTFaV3BqTUZJeWVHbFplbVJJVm1reGVGcEVTbWxhTW5oRlpHeGtjVlJ1U2paTlZFWldVa1JXYW1WSVFsRmtSazVEVm10U1UyUklUbFJhU0doV1pHMDVXRTlWVWtOaFdFWXdUbTFrU0ZSdGIzUk5WMXBOWWxoU2VWSldUbkpoUldoSVZEQjBTVTB5Vmt4VWJGWjJWMVZHTmxKVE1ESmlhbEpaWWtSS2RGRlVWbkpoUlZKNFdtcGplRnB0Y0VSTlIwMDFVbXBrTTJRd05XMVZSWGQ1WVVaQ1psRXdXakZTYkVVelkwZG9SazVJU2taWk1XeFRUV3M1ZDJSWFJuaGlSekZyWWpCVk1tSXhXa0phUnpreVUyeG5lRlpXT1hOaU1EVldXa1V4UkZKNlFqQmpXR2hwVmpCVk1sZFlZM3BUVXpGNFVWY3hhMVJ1WkVKUlZHUnZXVlpGTkdOc1NUQmFWVWwwVW14YWNWZEVUbkpYYXpWTFkyMWFSazlIVm5kUmJXeHFVakIwYmxSRlpFWlZSM042WXpKT2Nsa3dTVE5sVmxaWlZFZGtjRTFZUWtkaU1IQXlaVlUxYUdSVlpGZFZibEpRWVZoYVFsWnRkSFpTYTBVelRGVTFTazFYYUZKUk1VcE1WMmt4U1dKNldreGpXRWt4Wkd0U1RXSnNPVmRVYTBaRlZtcEtabU13VWxGV1YzZ3dVVEowVEZSc2JESmFNMmd4WkZWT1NWa3piRVZPUlVwUlpWVXhNVlJFUW5wT01Wb3dXREkxTVdSclZtbFVNVTU0VGtSa1VrNXJWalZpTUVwUlRVWkdObVI2UmxKU1IyUnhZMVUxZVZnd05UQmpNRGt4V20xNFIyTlVWakJsUmtKR1QxZEdWbUZYZUZKVE1FWllZbGR3UWxWVmJITk9WbWd3WkVSWmRHRkZSbFZpTVd4bVVqRldjMUV5Y0cxV1ZrSlFWMGhrY0ZWUlBUMGFJQklhVldkNVJUSTJOVzFyVWtrMmNFOXVTMjFuYkRSQllVRkNRMUZJWkdnREtHTSUyNTNE").should eq("Egljb21tdW5pdHm4AQCqA-kOCsAOUVVSVFNsOXBNWEYxYlVablFXaGFiWFJNTW5WM1ZHSXdPVU5EWTNoeFJWWlVjRWRGVTBOa1prTktjVUoyWjBZemNEZHRPV2cwV1hWbVJtaFVPWFJwVjJaUU4xTXlNRWRaYlZwSVFUa3dlak5pTUV0dll6QkRVMlpsWHpoVFdUbHFSR0o1YkRkM1kydEhMVTVwWDFCdFdXOUhjR0Z6ZEMxbldVcEhUMjkzUm1saGRXSkViVmR6ZFhwd1QxTnpOME54TW5KUloxQlBkME5QU1VWVWMybHlNbFZvUVV0NlVIZFVhMVV5UzNWUmJHRldkRmszU1dKd1pVUllVMkZFVG1aV1ZsRnVUMGhsZFd0T01sVndTbGd3TkhweVdDMVBTRUphV25GNk5Yb3dYMWRCVTFnMlltODBPRmhIV205WlQwNW1YMjV1UlVKTWNucHNNSGR5Y1hKaFltUkVkblJYZG1Kc1FVaHFUV3BwTkc5R1pUQkVlbGw2ZHpSM2FISlBTSFJoYjJGbVMwNTBiV1pxV2pCSVNWWnZTalpRT0RoclVGVmhia1p5VFhsaWFTMVBjREZZV1dSTFdERkZjSHB0ZUhseWFtRXdNR1JmTkhOWmFEVlZTbVZ1ZUVkRU1XRlFhbU4xVERabk4wdDVSSGxHU2xsT1VEQlJXR1ZLTUhGM1UwWkJTSE5oWkRWQ2NXZHNaMFpqYW1ST1YxZFlhMDVOVUZSSFZWVktRekZSYVhodlUxTm1SV1EwTUdsdWNEWXlPV1YwUjNkcGFVcEVTM040YUZadmRXbHJhblkyZFdFelNHWXpUV3hMYURCa2JIRTFSblJ4Wms4NU1XbGtOM0pHYjBGeU4xZFJNMU5qYkZCd05rZE9jV1JqT1hGRGIyNU5Xak5TUlhkemFsUXRObGt4UWxkUE16ZGFaRTlxVGtaZlIweEhRbXRNWXpCWE9GUjNOMHBsYVhwS2RtSlZkMmxGTVhCbVNIWkdkVTFJY0MxbFdYSkVZM0V0ZFROWWRtVlFlV3hhYlVKMmVreGZUMGxOU2xaSlRFTlBZMVpEUjFwd1RHZFhZMmhIYVVKakxUSmFabXd0U1RNeFJEWkhlSGhYTkhOMU1GZGhOMjFCVlVnNGNFTlJXSGx2WW5ScWNUaHZXWGxKT1d0TVRXc3lRMWc0Um5wU2JEVjBlRGxpTW5vMVRYaEtkelExY201S1JHSmZkamhmTlhOWmRGYzRjak5FVVdkMlpXVnNRWEJyZW5OdFpHcEljVGhWYzFsZkxWa3dRVTkyTVZVMmIyMTNVeTFLVEUxeFIwUldRbmc0VEdsTlpGVktjVmxzTkZGa1UwazFabE0wZUhsRk5WZ3lWR0ZaYzJadlYyaHRPRFpzTjNCT1dHRnBiMHhUVDBkMmRuZFVOMlptVm05dWIwRTFZVkZuYldKNmIwMUNaMng2VGkxSk56bHhXV3BJVGt4RFYwVllUM05pTVcwemRHc3lUVWN6TVVKcVRHdElNVWg1YmtKQmVrbFNVMnczZEVKUlJGOUlNVWRyZERsbFJraHVYekJXZUhGbE1rTTBlVE40YVU1T1pFcGpVMkpFZFMxWVdITjNTMnhWVjJwYVgzVXRXbGcwZG5OSE1qUXpYMlJHTVhSV1kxWkZRMlZwU25OdVlXTkdVek5wVUd4b2FUbDVSRVp4YVhsbFRqbG1aRWxYVFZCMVFWbG9OMEl3TW5KV1JUVjRkREJLZG5obmJGZHhSVlY1ZWpjMFIyeGlZemRIVmkxeFpESmlaMnhFZGxkcVRuSjZNVEZWUkRWamVIQlFkRk5DVmtSU2RITlRaSGhWZG05WE9VUkNhWEYwTm1kSFRtb3RNV1pNYlhSeVJWTnJhRWhIVDB0SU0yVkxUbFZ2V1VGNlJTMDJialJZYkRKdFFUVnJhRVJ4WmpjeFptcERNR001UmpkM2QwNW1VRXd5YUZCZlEwWjFSbEUzY0doRk5ISkZZMWxTTWs5d2RXRnhiRzFrYjBVMmIxWkJaRzkyU2xneFZWOXNiMDVWWkUxRFJ6QjBjWGhpVjBVMldYY3pTUzF4UVcxa1RuZEJRVGRvWVZFNGNsSTBaVUl0UmxacVdETnJXazVLY21aRk9HVndRbWxqUjB0blRFZEZVR3N6YzJOclkwSTNlVlZZVEdkcE1YQkdiMHAyZVU1aGRVZFdVblJQYVhaQlZtdHZSa0UzTFU1Sk1XaFJRMUpMV2kxSWJ6WkxjWEkxZGtSTWJsOVdUa0ZFVmpKZmMwUlFWV3gwUTJ0TFRsbDJaM2gxZFVOSVkzbEVORUpRZVUxMVREQnpOMVowWDI1MWRrVmlUMU54TkRkUk5rVjViMEpRTUZGNmR6RlJSR2RxY1U1eVgwNTBjMDkxWm14R2NUVjBlRkJGT1dGVmFXeFJTMEZYYldwQlVVbHNOVmgwZERZdGFFRlViMWxmUjFWc1EycG1WVkJQV0hkcFVRPT0aIhIcVWd5RTI2NW1rUkk2cE9uS21nbDRBYUFCQ1E9PUhkaAMoYw==")
- end
- end
-
- describe "#sign_token" do
- it "correctly signs a given hash" do
- token = {
- "session" => "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
- "expires" => 1554680038,
- "scopes" => [
- ":notifications",
- ":subscriptions/*",
- "GET:tokens*",
- ],
- "signature" => "f__2hS20th8pALF305PJFK-D2aVtvefNnQheILHD2vU=",
- }
- sign_token("SECRET_KEY", token).should eq(token["signature"])
-
- token = {
- "session" => "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
- "scopes" => [":notifications", "POST:subscriptions/*"],
- "signature" => "fNvXoT0MRAL9eE6lTE33CEg8HitYJDOL9a22rSN2Ihg=",
- }
- sign_token("SECRET_KEY", token).should eq(token["signature"])
- end
- end
-end
diff --git a/spec/invidious/helpers_spec.cr b/spec/invidious/helpers_spec.cr
new file mode 100644
index 00000000..b2436989
--- /dev/null
+++ b/spec/invidious/helpers_spec.cr
@@ -0,0 +1,100 @@
+require "../spec_helper"
+
+CONFIG = Config.from_yaml(File.open("config/config.example.yml"))
+
+Spectator.describe "Helper" do
+ describe "#produce_channel_videos_url" do
+ it "correctly produces url for requesting page `x` of a channel's videos" do
+ expect(produce_channel_videos_url(ucid: "UCXuqSBlHAE6Xw-yeJA0Tunw")).to eq("/browse_ajax?continuation=4qmFsgI8EhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaIEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0V4&gl=US&hl=en")
+
+ expect(produce_channel_videos_url(ucid: "UCXuqSBlHAE6Xw-yeJA0Tunw", sort_by: "popular")).to eq("/browse_ajax?continuation=4qmFsgJAEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaJEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0V4R0FFPQ%3D%3D&gl=US&hl=en")
+
+ expect(produce_channel_videos_url(ucid: "UCXuqSBlHAE6Xw-yeJA0Tunw", page: 20)).to eq("/browse_ajax?continuation=4qmFsgJAEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaJEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0l5TUE9PQ%3D%3D&gl=US&hl=en")
+
+ expect(produce_channel_videos_url(ucid: "UC-9-kyTW8ZkZNDHQJ6FgpwQ", page: 20, sort_by: "popular")).to eq("/browse_ajax?continuation=4qmFsgJAEhhVQy05LWt5VFc4WmtaTkRIUUo2Rmdwd1EaJEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0l5TUJnQg%3D%3D&gl=US&hl=en")
+ end
+ end
+
+ describe "#produce_channel_search_continuation" do
+ it "correctly produces token for searching a specific channel" do
+ expect(produce_channel_search_continuation("UCXuqSBlHAE6Xw-yeJA0Tunw", "", 100)).to eq("4qmFsgJqEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaIEVnWnpaV0Z5WTJnd0FUZ0JZQUY2QkVkS2IxaTRBUUE9WgCaAilicm93c2UtZmVlZFVDWHVxU0JsSEFFNlh3LXllSkEwVHVud3NlYXJjaA%3D%3D")
+
+ expect(produce_channel_search_continuation("UCXuqSBlHAE6Xw-yeJA0Tunw", "По ожиशुपतिरपि子而時ஸ்றீனி", 0)).to eq("4qmFsgKoARIYVUNYdXFTQmxIQUU2WHcteWVKQTBUdW53GiBFZ1p6WldGeVkyZ3dBVGdCWUFGNkJFZEJRVDI0QVFBPVo-0J_QviDQvtC20LjgpLbgpYHgpKrgpKTgpL_gpLDgpKrgpL_lrZDogIzmmYLgrrjgr43grrHgr4Dgrqngrr-aAilicm93c2UtZmVlZFVDWHVxU0JsSEFFNlh3LXllSkEwVHVud3NlYXJjaA%3D%3D")
+ end
+ end
+
+ describe "#produce_channel_playlists_url" do
+ it "correctly produces a /browse_ajax URL with the given UCID and cursor" do
+ expect(produce_channel_playlists_url("UCCj956IF62FbT7Gouszaj9w", "AIOkY9EQpi_gyn1_QrFuZ1reN81_MMmI1YmlBblw8j7JHItEFG5h7qcJTNd4W9x5Quk_CVZ028gW")).to eq("/browse_ajax?continuation=4qmFsgLNARIYVUNDajk1NklGNjJGYlQ3R291c3phajl3GrABRWdsd2JHRjViR2x6ZEhNd0FqZ0JZQUZxQUxnQkFIcG1VVlZzVUdFeGF6VlNWa1ozWVZZNWJtVlhOSGhZTVVaNVVtNVdZVTFZU214VWFtZDRXREF4VG1KVmEzaFhWekZ6VVcxS2MyUjZhSEZPTUhCSlUxaFNSbEpyWXpGaFJHUjRXVEJ3VlZSdFVUQldlbXcwVGxaR01XRXhPVVJXYkc5M1RXcG9ibFozSUFFWUF3PT0%3D&gl=US&hl=en")
+ end
+ end
+
+ describe "#produce_search_params" do
+ it "correctly produces token for searching with specified filters" do
+ expect(produce_search_params).to eq("CAASAhABSAA%3D")
+
+ expect(produce_search_params(sort: "upload_date", content_type: "video")).to eq("CAISAhABSAA%3D")
+
+ expect(produce_search_params(content_type: "playlist")).to eq("CAASAhADSAA%3D")
+
+ expect(produce_search_params(sort: "date", content_type: "video", features: ["hd", "cc", "purchased", "hdr"])).to eq("CAISCxABIAEwAUgByAEBSAA%3D")
+
+ expect(produce_search_params(content_type: "channel")).to eq("CAASAhACSAA%3D")
+ end
+ end
+
+ describe "#produce_comment_continuation" do
+ it "correctly produces a continuation token for comments" do
+ expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyjAMK9gJBRFNKX2kycXZKZUZ0TDBodG1TNV9LNUN0ajNlR0ZWQk1XTDlXZDQybzNrbVVMNl9tQXpkTHA4NS1saVFaTDBtWXJfMTZCaGFnZ1VxWDY1MlN2OUpxVjZWWGluU2hTUC1aVDZyTDROb2xQQmFQWFZ0SnNPNV9yQV9xRTNHdWJBdUxGdzl1eklJWFUyLUhucFhiZGdQTFdURmF2ZlgyMDZocVdtbXBId1VPcm14UVZfT1g2dFlrTTN1eDNyUEFLQ0RyVDhlV0w3TVUzYkxpTmNuYmdrVzhvMGg4S1lMTF84QlBhOExjSGJUdjhwQW9Oa2plcmxYMXg3SzRwcXhhWFBveXo4OXFObG5oNnJSeDZBWGdBenpvSEgxZG1jeVE4Q0lCZU9IZy1tNGk4WnhkWDRkUDg4WFdySUZnLWpKR2hwR1A4SlVNRGdaZ2F2eFZ4MjI1aFVFWVpNeXJMR2xlcjVlbTRGZ2JHNjJZV0M1MW1vTERMZVlFQSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D")
+
+ expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i1yz21HI4xrtsYXVC-2_kfZ6kx1yjYQumXAAxqH3CAd7ZxKxfLdZS1__fqhCtOASRbbpSBGH_tH1J96Dxux-Qfjk-lUbupMqv08Q3aHzGu7p70VoUMHhI2-GoJpnbpmcOxkGzeIuenRS_ym2Y8fkDowhqLPFgsS0n4djnZ2UmC17F3Ch3N1S1UYf1ZVOc991qOC1iW9kJDzyvRQTWCPsJUPneSaAKW-Rr97pdesOkR4i8cNvHZRnQKe2HEfsvlJOb2C3lF1dJBfJeNfnQYeh5hv6_fZN7bt3-JL1Xk3Qc9NXNxmmbDpwAC_yFR8dthFfUJdyIO9Nu1D79MLYeR-H5HxqUJokkJiGIz4lTE_CXXbhAI")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyiQMK8wJBRFNKX2kxeXoyMUhJNHhydHNZWFZDLTJfa2ZaNmt4MXlqWVF1bVhBQXhxSDNDQWQ3WnhLeGZMZFpTMV9fZnFoQ3RPQVNSYmJwU0JHSF90SDFKOTZEeHV4LVFmamstbFVidXBNcXYwOFEzYUh6R3U3cDcwVm9VTUhoSTItR29KcG5icG1jT3hrR3plSXVlblJTX3ltMlk4ZmtEb3docUxQRmdzUzBuNGRqbloyVW1DMTdGM0NoM04xUzFVWWYxWlZPYzk5MXFPQzFpVzlrSkR6eXZSUVRXQ1BzSlVQbmVTYUFLVy1Scjk3cGRlc09rUjRpOGNOdkhaUm5RS2UySEVmc3ZsSk9iMkMzbEYxZEpCZkplTmZuUVllaDVodjZfZlpON2J0My1KTDFYazNRYzlOWE54bW1iRHB3QUNfeUZSOGR0aEZmVUpkeUlPOU51MUQ3OU1MWWVSLUg1SHhxVUpva2tKaUdJejRsVEVfQ1hYYmhBSSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D")
+
+ expect(produce_comment_continuation("29-q7YnyUmY", "")).to eq("EkMSCzI5LXE3WW55VW1ZyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyFQoAIg8iCzI5LXE3WW55VW1ZMAAoFA%3D%3D")
+
+ expect(produce_comment_continuation("CvFH_6DNRCY", "")).to eq("EkMSC0N2RkhfNkROUkNZyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyFQoAIg8iC0N2RkhfNkROUkNZMAAoFA%3D%3D")
+ end
+ end
+
+ describe "#produce_channel_community_continuation" do
+ it "correctly produces a continuation token for a channel community" do
+ expect(produce_channel_community_continuation("UCCj956IF62FbT7Gouszaj9w", "Egljb21tdW5pdHm4")).to eq("4qmFsgIsEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaEEVnbGpiMjF0ZFc1cGRIbTQ%3D")
+ expect(produce_channel_community_continuation("UCCj956IF62FbT7Gouszaj9w", "Egljb21tdW5pdHm4AQCqAyQaIBIaVWd3cE9NQmVwWEdjclhsUHg2WjRBYUFCQ1FIZGgDKAA%3D")).to eq("4qmFsgJmEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaSkVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2QzY0U5TlFtVndXRWRqY2xoc1VIZzJXalJCWVVGQ1ExRklaR2dES0FBJTNE")
+
+ expect(produce_channel_community_continuation("UC-lHJZR3Gqxm24_Vd_AJ5Yw", "Egljb21tdW5pdHm4AQCqAyQaIBIaVWd5RTI2NW1rUkk2cE9uS21nbDRBYUFCQ1FIZGgDKAA%3D")).to eq("4qmFsgJmEhhVQy1sSEpaUjNHcXhtMjRfVmRfQUo1WXcaSkVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2Q1UlRJMk5XMXJVa2syY0U5dVMyMW5iRFJCWVVGQ1ExRklaR2dES0FBJTNE")
+ expect(produce_channel_community_continuation("UC-lHJZR3Gqxm24_Vd_AJ5Yw", "Egljb21tdW5pdHm4AQCqA-cOCsAOUVVSVFNsOXBNWEYxYlVablFXaGFiWFJNTW5WM1ZHSXdPVU5EWTNoeFJWWlVjRWRGVTBOa1prTktjVUoyWjBZemNEZHRPV2cwV1hWbVJtaFVPWFJwVjJaUU4xTXlNRWRaYlZwSVFUa3dlak5pTUV0dll6QkRVMlpsWHpoVFdUbHFSR0o1YkRkM1kydEhMVTVwWDFCdFdXOUhjR0Z6ZEMxbldVcEhUMjkzUm1saGRXSkViVmR6ZFhwd1QxTnpOME54TW5KUloxQlBkME5QU1VWVWMybHlNbFZvUVV0NlVIZFVhMVV5UzNWUmJHRldkRmszU1dKd1pVUllVMkZFVG1aV1ZsRnVUMGhsZFd0T01sVndTbGd3TkhweVdDMVBTRUphV25GNk5Yb3dYMWRCVTFnMlltODBPRmhIV205WlQwNW1YMjV1UlVKTWNucHNNSGR5Y1hKaFltUkVkblJYZG1Kc1FVaHFUV3BwTkc5R1pUQkVlbGw2ZHpSM2FISlBTSFJoYjJGbVMwNTBiV1pxV2pCSVNWWnZTalpRT0RoclVGVmhia1p5VFhsaWFTMVBjREZZV1dSTFdERkZjSHB0ZUhseWFtRXdNR1JmTkhOWmFEVlZTbVZ1ZUVkRU1XRlFhbU4xVERabk4wdDVSSGxHU2xsT1VEQlJXR1ZLTUhGM1UwWkJTSE5oWkRWQ2NXZHNaMFpqYW1ST1YxZFlhMDVOVUZSSFZWVktRekZSYVhodlUxTm1SV1EwTUdsdWNEWXlPV1YwUjNkcGFVcEVTM040YUZadmRXbHJhblkyZFdFelNHWXpUV3hMYURCa2JIRTFSblJ4Wms4NU1XbGtOM0pHYjBGeU4xZFJNMU5qYkZCd05rZE9jV1JqT1hGRGIyNU5Xak5TUlhkemFsUXRObGt4UWxkUE16ZGFaRTlxVGtaZlIweEhRbXRNWXpCWE9GUjNOMHBsYVhwS2RtSlZkMmxGTVhCbVNIWkdkVTFJY0MxbFdYSkVZM0V0ZFROWWRtVlFlV3hhYlVKMmVreGZUMGxOU2xaSlRFTlBZMVpEUjFwd1RHZFhZMmhIYVVKakxUSmFabXd0U1RNeFJEWkhlSGhYTkhOMU1GZGhOMjFCVlVnNGNFTlJXSGx2WW5ScWNUaHZXWGxKT1d0TVRXc3lRMWc0Um5wU2JEVjBlRGxpTW5vMVRYaEtkelExY201S1JHSmZkamhmTlhOWmRGYzRjak5FVVdkMlpXVnNRWEJyZW5OdFpHcEljVGhWYzFsZkxWa3dRVTkyTVZVMmIyMTNVeTFLVEUxeFIwUldRbmc0VEdsTlpGVktjVmxzTkZGa1UwazFabE0wZUhsRk5WZ3lWR0ZaYzJadlYyaHRPRFpzTjNCT1dHRnBiMHhUVDBkMmRuZFVOMlptVm05dWIwRTFZVkZuYldKNmIwMUNaMng2VGkxSk56bHhXV3BJVGt4RFYwVllUM05pTVcwemRHc3lUVWN6TVVKcVRHdElNVWg1YmtKQmVrbFNVMnczZEVKUlJGOUlNVWRyZERsbFJraHVYekJXZUhGbE1rTTBlVE40YVU1T1pFcGpVMkpFZFMxWVdITjNTMnhWVjJwYVgzVXRXbGcwZG5OSE1qUXpYMlJHTVhSV1kxWkZRMlZwU25OdVlXTkdVek5wVUd4b2FUbDVSRVp4YVhsbFRqbG1aRWxYVFZCMVFWbG9OMEl3TW5KV1JUVjRkREJLZG5obmJGZHhSVlY1ZWpjMFIyeGlZemRIVmkxeFpESmlaMnhFZGxkcVRuSjZNVEZWUkRWamVIQlFkRk5DVmtSU2RITlRaSGhWZG05WE9VUkNhWEYwTm1kSFRtb3RNV1pNYlhSeVJWTnJhRWhIVDB0SU0yVkxUbFZ2V1VGNlJTMDJialJZYkRKdFFUVnJhRVJ4WmpjeFptcERNR001UmpkM2QwNW1VRXd5YUZCZlEwWjFSbEUzY0doRk5ISkZZMWxTTWs5d2RXRnhiRzFrYjBVMmIxWkJaRzkyU2xneFZWOXNiMDVWWkUxRFJ6QjBjWGhpVjBVMldYY3pTUzF4UVcxa1RuZEJRVGRvWVZFNGNsSTBaVUl0UmxacVdETnJXazVLY21aRk9HVndRbWxqUjB0blRFZEZVR3N6YzJOclkwSTNlVlZZVEdkcE1YQkdiMHAyZVU1aGRVZFdVblJQYVhaQlZtdHZSa0UzTFU1Sk1XaFJRMUpMV2kxSWJ6WkxjWEkxZGtSTWJsOVdUa0ZFVmpKZmMwUlFWV3gwUTJ0TFRsbDJaM2gxZFVOSVkzbEVORUpRZVUxMVREQnpOMVowWDI1MWRrVmlUMU54TkRkUk5rVjViMEpRTUZGNmR6RlJSR2RxY1U1eVgwNTBjMDkxWm14R2NUVjBlRkJGT1dGVmFXeFJTMEZYYldwQlVVbHNOVmgwZERZdGFFRlViMWxmUjFWc1EycG1WVkJQV0hkcFVRPT0aIBIaVWd5RTI2NW1rUkk2cE9uS21nbDRBYUFCQ1FIZGgDKGM%3D")).to eq("4qmFsgKXFBIYVUMtbEhKWlIzR3F4bTI0X1ZkX0FKNVl3GvoTRWdsamIyMXRkVzVwZEhtNEFRQ3FBLWNPQ3NBT1VWVlNWRk5zT1hCTldFWXhZbFZhYmxGWGFHRmlXRkpOVFc1V00xWkhTWGRQVlU1RVdUTm9lRkpXV2xWalJXUkdWVEJPYTFwclRrdGpWVW95V2pCWmVtTkVaSFJQVjJjd1YxaFdiVkp0YUZWUFdGSndWakphVVU0eFRYbE5SV1JhWWxad1NWRlVhM2RsYWs1cFRVVjBkbGw2UWtSVk1scHNXSHBvVkZkVWJIRlNSMG8xWWtSa00xa3lkRWhNVlRWd1dERkNkRmRYT1VoalIwWjZaRU14YmxkVmNFaFVNamt6VW0xc2FHUlhTa1ZpVm1SNlpGaHdkMVF4VG5wT01FNTRUVzVLVWxveFFsQmtNRTVRVTFWV1ZXTXliSGxOYkZadlVWVjBObFZJWkZWaE1WVjVVek5XVW1KSFJsZGtSbXN6VTFkS2QxcFZVbGxWTWtaRlZHMWFWMVpzUm5WVU1HaHNaRmQwVDAxc1ZuZFRiR2QzVGtod2VWZERNVkJUUlVwaFYyNUdOazVZYjNkWU1XUkNWVEZuTWxsdE9EQlBSbWhJVjIwNVdsUXdOVzFZTWpWMVVsVktUV051Y0hOTlNHUjVZMWhLYUZsdFVrVmtibEpZWkcxS2MxRlZhSEZVVjNCd1RrYzVSMXBVUWtWbGJHdzJaSHBTTTJGSVNsQlRTRkpvWWpKR2JWTXdOVEJpVjFweFYycENTVk5XV25aVGFscFJUMFJvY2xWR1ZtaGlhMXA1VkZoc2FXRlRNVkJqUkVaWlYxZFNURmRFUmtaalNIQjBaVWhzZVdGdFJYZE5SMUptVGtoT1dtRkVWbFpUYlZaMVpVVmtSVTFYUmxGaGJVNHhWRVJhYms0d2REVlNTR3hIVTJ4c1QxVkVRbEpYUjFaTFRVaEdNMVV3V2tKVFNFNW9Xa1JXUTJOWFpITmFNRnBxWVcxU1QxWXhaRmxoTURWT1ZVWlNTRlpXVmt0UmVrWlNZVmhvZGxVeFRtMVNWMUV3VFVkc2RXTkVXWGxQVjFZd1VqTmtjR0ZWY0VWVE0wNDBZVVphZG1SWGJISmhibGt5WkZkRmVsTkhXWHBVVjNoTVlVUkNhMkpJUlRGU2JsSjRXbXM0TlUxWGJHdE9NMHBIWWpCR2VVNHhaRkpOTVU1cVlrWkNkMDVyWkU5alYxSnFUMWhHUkdJeU5VNVhhazVUVWxoa2VtRnNVWFJPYkd0NFVXeGtVRTE2WkdGYVJUbHhWR3RhWmxJd2VFaFJiWFJOV1hwQ1dFOUdVak5PTUhCc1lWaHdTMlJ0U2xaa01teEdUVmhDYlZOSVdrZGtWVEZKWTBNeGJGZFlTa1ZaTTBWMFpGUk9XV1J0VmxGbFYzaGhZbFZLTW1WcmVHWlVNR3hPVTJ4YVNsUkZUbEJaTVZwRVVqRndkMVJIWkZoWk1taElZVlZLYWt4VVNtRmFiWGQwVTFSTmVGSkVXa2hsU0doWVRraE9NVTFHWkdoT01qRkNWbFZuTkdORlRsSlhTR3gyV1c1U2NXTlVhSFpYV0d4S1QxZDBUVlJYYzNsUk1XYzBVbTV3VTJKRVZqQmxSR3hwVFc1dk1WUllhRXRrZWxFeFkyMDFTMUpIU21aa2FtaG1UbGhPV21SR1l6UmphazVGVlZka01scFhWbk5SV0VKeVpXNU9kRnBIY0VsalZHaFdZekZzWmt4V2EzZFJWVGt5VFZaVk1tSXlNVE5WZVRGTFZFVXhlRkl3VWxkUmJtYzBWRWRzVGxwR1ZrdGpWbXh6VGtaR2ExVXdhekZhYkUwd1pVaHNSazVXWjNsV1IwWmFZekphZGxZeWFIUlBSRnB6VGpOQ1QxZEhSbkJpTUhoVVZEQmtNbVJ1WkZWT01scHRWbTA1ZFdJd1JURlpWa1p1WWxkS05tSXdNVU5hTW5nMlZHa3hTazU2YkhoWFYzQkpWR3Q0UkZZd1ZsbFVNMDVwVFZjd2VtUkhjM2xVVldONlRWVktjVlJIZEVsTlZXZzFZbXRLUW1WcmJGTlZNbmN6WkVWS1VsSkdPVWxOVldSeVpFUnNiRkpyYUhWWWVrSlhaVWhHYkUxclRUQmxWRTQwWVZVMVQxcEZjR3BWTWtwRlpGTXhXVmRJVGpOVE1uaFdWakp3WVZnelZYUlhiR2N3Wkc1T1NFMXFVWHBZTWxKSFRWaFNWMWt4V2taUk1sWndVMjVPZFZsWFRrZFZlazV3VlVkNGIyRlViRFZTUlZwNFlWaHNiRlJxYkcxYVJXeFlWRlpDTVZGV2JHOU9NRWwzVFc1S1YxSlVWalJrUkVKTFpHNW9ibUpHWkhoU1ZsWTFaV3BqTUZJeWVHbFplbVJJVm1reGVGcEVTbWxhTW5oRlpHeGtjVlJ1U2paTlZFWldVa1JXYW1WSVFsRmtSazVEVm10U1UyUklUbFJhU0doV1pHMDVXRTlWVWtOaFdFWXdUbTFrU0ZSdGIzUk5WMXBOWWxoU2VWSldUbkpoUldoSVZEQjBTVTB5Vmt4VWJGWjJWMVZHTmxKVE1ESmlhbEpaWWtSS2RGRlVWbkpoUlZKNFdtcGplRnB0Y0VSTlIwMDFVbXBrTTJRd05XMVZSWGQ1WVVaQ1psRXdXakZTYkVVelkwZG9SazVJU2taWk1XeFRUV3M1ZDJSWFJuaGlSekZyWWpCVk1tSXhXa0phUnpreVUyeG5lRlpXT1hOaU1EVldXa1V4UkZKNlFqQmpXR2hwVmpCVk1sZFlZM3BUVXpGNFVWY3hhMVJ1WkVKUlZHUnZXVlpGTkdOc1NUQmFWVWwwVW14YWNWZEVUbkpYYXpWTFkyMWFSazlIVm5kUmJXeHFVakIwYmxSRlpFWlZSM042WXpKT2Nsa3dTVE5sVmxaWlZFZGtjRTFZUWtkaU1IQXlaVlUxYUdSVlpGZFZibEpRWVZoYVFsWnRkSFpTYTBVelRGVTFTazFYYUZKUk1VcE1WMmt4U1dKNldreGpXRWt4Wkd0U1RXSnNPVmRVYTBaRlZtcEtabU13VWxGV1YzZ3dVVEowVEZSc2JESmFNMmd4WkZWT1NWa3piRVZPUlVwUlpWVXhNVlJFUW5wT01Wb3dXREkxTVdSclZtbFVNVTU0VGtSa1VrNXJWalZpTUVwUlRVWkdObVI2UmxKU1IyUnhZMVUxZVZnd05UQmpNRGt4V20xNFIyTlVWakJsUmtKR1QxZEdWbUZYZUZKVE1FWllZbGR3UWxWVmJITk9WbWd3WkVSWmRHRkZSbFZpTVd4bVVqRldjMUV5Y0cxV1ZrSlFWMGhrY0ZWUlBUMGFJQklhVldkNVJUSTJOVzFyVWtrMmNFOXVTMjFuYkRSQllVRkNRMUZJWkdnREtHTSUzRA%3D%3D")
+ end
+ end
+
+ describe "#extract_channel_community_cursor" do
+ it "correctly extracts a community cursor from a given continuation" do
+ expect(extract_channel_community_cursor("4qmFsgIsEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaEEVnbGpiMjF0ZFc1cGRIbTQ%3D")).to eq("Egljb21tdW5pdHk=")
+ expect(extract_channel_community_cursor("4qmFsgJoEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaTEVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2QzY0U5TlFtVndXRWRqY2xoc1VIZzJXalJCWVVGQ1ExRklaR2dES0FBJTI1M0Q%3D")).to eq("Egljb21tdW5pdHm4AQCqAyQaIEhkaAMSGlVnd3BPTUJlcFhHY3JYbFB4Nlo0QWFBQkNRKAA=")
+
+ expect(extract_channel_community_cursor("4qmFsgJoEhhVQy1sSEpaUjNHcXhtMjRfVmRfQUo1WXcaTEVnbGpiMjF0ZFc1cGRIbTRBUUNxQXlRYUlCSWFWV2Q1UlRJMk5XMXJVa2syY0U5dVMyMW5iRFJCWVVGQ1ExRklaR2dES0FBJTI1M0Q%3D")).to eq("Egljb21tdW5pdHm4AQCqAyQaIEhkaAMSGlVneUUyNjVta1JJNnBPbkttZ2w0QWFBQkNRKAA=")
+ expect(extract_channel_community_cursor("4qmFsgKZFBIYVUMtbEhKWlIzR3F4bTI0X1ZkX0FKNVl3GvwTRWdsamIyMXRkVzVwZEhtNEFRQ3FBLWNPQ3NBT1VWVlNWRk5zT1hCTldFWXhZbFZhYmxGWGFHRmlXRkpOVFc1V00xWkhTWGRQVlU1RVdUTm9lRkpXV2xWalJXUkdWVEJPYTFwclRrdGpWVW95V2pCWmVtTkVaSFJQVjJjd1YxaFdiVkp0YUZWUFdGSndWakphVVU0eFRYbE5SV1JhWWxad1NWRlVhM2RsYWs1cFRVVjBkbGw2UWtSVk1scHNXSHBvVkZkVWJIRlNSMG8xWWtSa00xa3lkRWhNVlRWd1dERkNkRmRYT1VoalIwWjZaRU14YmxkVmNFaFVNamt6VW0xc2FHUlhTa1ZpVm1SNlpGaHdkMVF4VG5wT01FNTRUVzVLVWxveFFsQmtNRTVRVTFWV1ZXTXliSGxOYkZadlVWVjBObFZJWkZWaE1WVjVVek5XVW1KSFJsZGtSbXN6VTFkS2QxcFZVbGxWTWtaRlZHMWFWMVpzUm5WVU1HaHNaRmQwVDAxc1ZuZFRiR2QzVGtod2VWZERNVkJUUlVwaFYyNUdOazVZYjNkWU1XUkNWVEZuTWxsdE9EQlBSbWhJVjIwNVdsUXdOVzFZTWpWMVVsVktUV051Y0hOTlNHUjVZMWhLYUZsdFVrVmtibEpZWkcxS2MxRlZhSEZVVjNCd1RrYzVSMXBVUWtWbGJHdzJaSHBTTTJGSVNsQlRTRkpvWWpKR2JWTXdOVEJpVjFweFYycENTVk5XV25aVGFscFJUMFJvY2xWR1ZtaGlhMXA1VkZoc2FXRlRNVkJqUkVaWlYxZFNURmRFUmtaalNIQjBaVWhzZVdGdFJYZE5SMUptVGtoT1dtRkVWbFpUYlZaMVpVVmtSVTFYUmxGaGJVNHhWRVJhYms0d2REVlNTR3hIVTJ4c1QxVkVRbEpYUjFaTFRVaEdNMVV3V2tKVFNFNW9Xa1JXUTJOWFpITmFNRnBxWVcxU1QxWXhaRmxoTURWT1ZVWlNTRlpXVmt0UmVrWlNZVmhvZGxVeFRtMVNWMUV3VFVkc2RXTkVXWGxQVjFZd1VqTmtjR0ZWY0VWVE0wNDBZVVphZG1SWGJISmhibGt5WkZkRmVsTkhXWHBVVjNoTVlVUkNhMkpJUlRGU2JsSjRXbXM0TlUxWGJHdE9NMHBIWWpCR2VVNHhaRkpOTVU1cVlrWkNkMDVyWkU5alYxSnFUMWhHUkdJeU5VNVhhazVUVWxoa2VtRnNVWFJPYkd0NFVXeGtVRTE2WkdGYVJUbHhWR3RhWmxJd2VFaFJiWFJOV1hwQ1dFOUdVak5PTUhCc1lWaHdTMlJ0U2xaa01teEdUVmhDYlZOSVdrZGtWVEZKWTBNeGJGZFlTa1ZaTTBWMFpGUk9XV1J0VmxGbFYzaGhZbFZLTW1WcmVHWlVNR3hPVTJ4YVNsUkZUbEJaTVZwRVVqRndkMVJIWkZoWk1taElZVlZLYWt4VVNtRmFiWGQwVTFSTmVGSkVXa2hsU0doWVRraE9NVTFHWkdoT01qRkNWbFZuTkdORlRsSlhTR3gyV1c1U2NXTlVhSFpYV0d4S1QxZDBUVlJYYzNsUk1XYzBVbTV3VTJKRVZqQmxSR3hwVFc1dk1WUllhRXRrZWxFeFkyMDFTMUpIU21aa2FtaG1UbGhPV21SR1l6UmphazVGVlZka01scFhWbk5SV0VKeVpXNU9kRnBIY0VsalZHaFdZekZzWmt4V2EzZFJWVGt5VFZaVk1tSXlNVE5WZVRGTFZFVXhlRkl3VWxkUmJtYzBWRWRzVGxwR1ZrdGpWbXh6VGtaR2ExVXdhekZhYkUwd1pVaHNSazVXWjNsV1IwWmFZekphZGxZeWFIUlBSRnB6VGpOQ1QxZEhSbkJpTUhoVVZEQmtNbVJ1WkZWT01scHRWbTA1ZFdJd1JURlpWa1p1WWxkS05tSXdNVU5hTW5nMlZHa3hTazU2YkhoWFYzQkpWR3Q0UkZZd1ZsbFVNMDVwVFZjd2VtUkhjM2xVVldONlRWVktjVlJIZEVsTlZXZzFZbXRLUW1WcmJGTlZNbmN6WkVWS1VsSkdPVWxOVldSeVpFUnNiRkpyYUhWWWVrSlhaVWhHYkUxclRUQmxWRTQwWVZVMVQxcEZjR3BWTWtwRlpGTXhXVmRJVGpOVE1uaFdWakp3WVZnelZYUlhiR2N3Wkc1T1NFMXFVWHBZTWxKSFRWaFNWMWt4V2taUk1sWndVMjVPZFZsWFRrZFZlazV3VlVkNGIyRlViRFZTUlZwNFlWaHNiRlJxYkcxYVJXeFlWRlpDTVZGV2JHOU9NRWwzVFc1S1YxSlVWalJrUkVKTFpHNW9ibUpHWkhoU1ZsWTFaV3BqTUZJeWVHbFplbVJJVm1reGVGcEVTbWxhTW5oRlpHeGtjVlJ1U2paTlZFWldVa1JXYW1WSVFsRmtSazVEVm10U1UyUklUbFJhU0doV1pHMDVXRTlWVWtOaFdFWXdUbTFrU0ZSdGIzUk5WMXBOWWxoU2VWSldUbkpoUldoSVZEQjBTVTB5Vmt4VWJGWjJWMVZHTmxKVE1ESmlhbEpaWWtSS2RGRlVWbkpoUlZKNFdtcGplRnB0Y0VSTlIwMDFVbXBrTTJRd05XMVZSWGQ1WVVaQ1psRXdXakZTYkVVelkwZG9SazVJU2taWk1XeFRUV3M1ZDJSWFJuaGlSekZyWWpCVk1tSXhXa0phUnpreVUyeG5lRlpXT1hOaU1EVldXa1V4UkZKNlFqQmpXR2hwVmpCVk1sZFlZM3BUVXpGNFVWY3hhMVJ1WkVKUlZHUnZXVlpGTkdOc1NUQmFWVWwwVW14YWNWZEVUbkpYYXpWTFkyMWFSazlIVm5kUmJXeHFVakIwYmxSRlpFWlZSM042WXpKT2Nsa3dTVE5sVmxaWlZFZGtjRTFZUWtkaU1IQXlaVlUxYUdSVlpGZFZibEpRWVZoYVFsWnRkSFpTYTBVelRGVTFTazFYYUZKUk1VcE1WMmt4U1dKNldreGpXRWt4Wkd0U1RXSnNPVmRVYTBaRlZtcEtabU13VWxGV1YzZ3dVVEowVEZSc2JESmFNMmd4WkZWT1NWa3piRVZPUlVwUlpWVXhNVlJFUW5wT01Wb3dXREkxTVdSclZtbFVNVTU0VGtSa1VrNXJWalZpTUVwUlRVWkdObVI2UmxKU1IyUnhZMVUxZVZnd05UQmpNRGt4V20xNFIyTlVWakJsUmtKR1QxZEdWbUZYZUZKVE1FWllZbGR3UWxWVmJITk9WbWd3WkVSWmRHRkZSbFZpTVd4bVVqRldjMUV5Y0cxV1ZrSlFWMGhrY0ZWUlBUMGFJQklhVldkNVJUSTJOVzFyVWtrMmNFOXVTMjFuYkRSQllVRkNRMUZJWkdnREtHTSUyNTNE")).to eq("Egljb21tdW5pdHm4AQCqA-kOCsAOUVVSVFNsOXBNWEYxYlVablFXaGFiWFJNTW5WM1ZHSXdPVU5EWTNoeFJWWlVjRWRGVTBOa1prTktjVUoyWjBZemNEZHRPV2cwV1hWbVJtaFVPWFJwVjJaUU4xTXlNRWRaYlZwSVFUa3dlak5pTUV0dll6QkRVMlpsWHpoVFdUbHFSR0o1YkRkM1kydEhMVTVwWDFCdFdXOUhjR0Z6ZEMxbldVcEhUMjkzUm1saGRXSkViVmR6ZFhwd1QxTnpOME54TW5KUloxQlBkME5QU1VWVWMybHlNbFZvUVV0NlVIZFVhMVV5UzNWUmJHRldkRmszU1dKd1pVUllVMkZFVG1aV1ZsRnVUMGhsZFd0T01sVndTbGd3TkhweVdDMVBTRUphV25GNk5Yb3dYMWRCVTFnMlltODBPRmhIV205WlQwNW1YMjV1UlVKTWNucHNNSGR5Y1hKaFltUkVkblJYZG1Kc1FVaHFUV3BwTkc5R1pUQkVlbGw2ZHpSM2FISlBTSFJoYjJGbVMwNTBiV1pxV2pCSVNWWnZTalpRT0RoclVGVmhia1p5VFhsaWFTMVBjREZZV1dSTFdERkZjSHB0ZUhseWFtRXdNR1JmTkhOWmFEVlZTbVZ1ZUVkRU1XRlFhbU4xVERabk4wdDVSSGxHU2xsT1VEQlJXR1ZLTUhGM1UwWkJTSE5oWkRWQ2NXZHNaMFpqYW1ST1YxZFlhMDVOVUZSSFZWVktRekZSYVhodlUxTm1SV1EwTUdsdWNEWXlPV1YwUjNkcGFVcEVTM040YUZadmRXbHJhblkyZFdFelNHWXpUV3hMYURCa2JIRTFSblJ4Wms4NU1XbGtOM0pHYjBGeU4xZFJNMU5qYkZCd05rZE9jV1JqT1hGRGIyNU5Xak5TUlhkemFsUXRObGt4UWxkUE16ZGFaRTlxVGtaZlIweEhRbXRNWXpCWE9GUjNOMHBsYVhwS2RtSlZkMmxGTVhCbVNIWkdkVTFJY0MxbFdYSkVZM0V0ZFROWWRtVlFlV3hhYlVKMmVreGZUMGxOU2xaSlRFTlBZMVpEUjFwd1RHZFhZMmhIYVVKakxUSmFabXd0U1RNeFJEWkhlSGhYTkhOMU1GZGhOMjFCVlVnNGNFTlJXSGx2WW5ScWNUaHZXWGxKT1d0TVRXc3lRMWc0Um5wU2JEVjBlRGxpTW5vMVRYaEtkelExY201S1JHSmZkamhmTlhOWmRGYzRjak5FVVdkMlpXVnNRWEJyZW5OdFpHcEljVGhWYzFsZkxWa3dRVTkyTVZVMmIyMTNVeTFLVEUxeFIwUldRbmc0VEdsTlpGVktjVmxzTkZGa1UwazFabE0wZUhsRk5WZ3lWR0ZaYzJadlYyaHRPRFpzTjNCT1dHRnBiMHhUVDBkMmRuZFVOMlptVm05dWIwRTFZVkZuYldKNmIwMUNaMng2VGkxSk56bHhXV3BJVGt4RFYwVllUM05pTVcwemRHc3lUVWN6TVVKcVRHdElNVWg1YmtKQmVrbFNVMnczZEVKUlJGOUlNVWRyZERsbFJraHVYekJXZUhGbE1rTTBlVE40YVU1T1pFcGpVMkpFZFMxWVdITjNTMnhWVjJwYVgzVXRXbGcwZG5OSE1qUXpYMlJHTVhSV1kxWkZRMlZwU25OdVlXTkdVek5wVUd4b2FUbDVSRVp4YVhsbFRqbG1aRWxYVFZCMVFWbG9OMEl3TW5KV1JUVjRkREJLZG5obmJGZHhSVlY1ZWpjMFIyeGlZemRIVmkxeFpESmlaMnhFZGxkcVRuSjZNVEZWUkRWamVIQlFkRk5DVmtSU2RITlRaSGhWZG05WE9VUkNhWEYwTm1kSFRtb3RNV1pNYlhSeVJWTnJhRWhIVDB0SU0yVkxUbFZ2V1VGNlJTMDJialJZYkRKdFFUVnJhRVJ4WmpjeFptcERNR001UmpkM2QwNW1VRXd5YUZCZlEwWjFSbEUzY0doRk5ISkZZMWxTTWs5d2RXRnhiRzFrYjBVMmIxWkJaRzkyU2xneFZWOXNiMDVWWkUxRFJ6QjBjWGhpVjBVMldYY3pTUzF4UVcxa1RuZEJRVGRvWVZFNGNsSTBaVUl0UmxacVdETnJXazVLY21aRk9HVndRbWxqUjB0blRFZEZVR3N6YzJOclkwSTNlVlZZVEdkcE1YQkdiMHAyZVU1aGRVZFdVblJQYVhaQlZtdHZSa0UzTFU1Sk1XaFJRMUpMV2kxSWJ6WkxjWEkxZGtSTWJsOVdUa0ZFVmpKZmMwUlFWV3gwUTJ0TFRsbDJaM2gxZFVOSVkzbEVORUpRZVUxMVREQnpOMVowWDI1MWRrVmlUMU54TkRkUk5rVjViMEpRTUZGNmR6RlJSR2RxY1U1eVgwNTBjMDkxWm14R2NUVjBlRkJGT1dGVmFXeFJTMEZYYldwQlVVbHNOVmgwZERZdGFFRlViMWxmUjFWc1EycG1WVkJQV0hkcFVRPT0aIhIcVWd5RTI2NW1rUkk2cE9uS21nbDRBYUFCQ1E9PUhkaAMoYw==")
+ end
+ end
+
+ describe "#sign_token" do
+ it "correctly signs a given hash" do
+ token = {
+ "session" => "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+ "expires" => 1554680038,
+ "scopes" => [
+ ":notifications",
+ ":subscriptions/*",
+ "GET:tokens*",
+ ],
+ "signature" => "f__2hS20th8pALF305PJFK-D2aVtvefNnQheILHD2vU=",
+ }
+ expect(sign_token("SECRET_KEY", token)).to eq(token["signature"])
+
+ token = {
+ "session" => "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+ "scopes" => [":notifications", "POST:subscriptions/*"],
+ "signature" => "fNvXoT0MRAL9eE6lTE33CEg8HitYJDOL9a22rSN2Ihg=",
+ }
+ expect(sign_token("SECRET_KEY", token)).to eq(token["signature"])
+ end
+ end
+end
diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr
new file mode 100644
index 00000000..09320750
--- /dev/null
+++ b/spec/spec_helper.cr
@@ -0,0 +1,18 @@
+require "kemal"
+require "openssl/hmac"
+require "pg"
+require "protodec/utils"
+require "yaml"
+require "../src/invidious/helpers/*"
+require "../src/invidious/channels/*"
+require "../src/invidious/videos"
+require "../src/invidious/comments"
+require "../src/invidious/playlists"
+require "../src/invidious/search"
+require "../src/invidious/trending"
+require "spectator"
+
+Spectator.configure do |config|
+ config.fail_blank
+ config.randomize
+end
diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr
index d93ee681..8cae7ae2 100644
--- a/src/invidious/channels/about.cr
+++ b/src/invidious/channels/about.cr
@@ -1,33 +1,26 @@
# TODO: Refactor into either SearchChannel or InvidiousChannel
-struct AboutChannel
- include DB::Serializable
-
- property ucid : String
- property author : String
- property auto_generated : Bool
- property author_url : String
- property author_thumbnail : String
- property banner : String?
- property description_html : String
- property total_views : Int64
- property sub_count : Int32
- property joined : Time
- property is_family_friendly : Bool
- property allowed_regions : Array(String)
- property related_channels : Array(AboutRelatedChannel)
- property tabs : Array(String)
-end
-
-struct AboutRelatedChannel
- include DB::Serializable
-
- property ucid : String
- property author : String
- property author_url : String
- property author_thumbnail : String
-end
-
-def get_about_info(ucid, locale)
+record AboutChannel,
+ ucid : String,
+ author : String,
+ auto_generated : Bool,
+ author_url : String,
+ author_thumbnail : String,
+ banner : String?,
+ description_html : String,
+ total_views : Int64,
+ sub_count : Int32,
+ joined : Time,
+ is_family_friendly : Bool,
+ allowed_regions : Array(String),
+ tabs : Array(String)
+
+record AboutRelatedChannel,
+ ucid : String,
+ author : String,
+ author_url : String,
+ author_thumbnail : String
+
+def get_about_info(ucid, locale) : AboutChannel
begin
# "EgVhYm91dA==" is the base64-encoded protobuf object {"2:string":"about"}
initdata = YoutubeAPI.browse(browse_id: ucid, params: "EgVhYm91dA==")
@@ -63,8 +56,6 @@ def get_about_info(ucid, locale)
is_family_friendly = initdata["microformat"]["microformatDataRenderer"]["familySafe"].as_bool
allowed_regions = initdata["microformat"]["microformatDataRenderer"]["availableCountries"].as_a.map(&.as_s)
-
- related_channels = [] of AboutRelatedChannel
else
author = initdata["metadata"]["channelMetadataRenderer"]["title"].as_s
author_url = initdata["metadata"]["channelMetadataRenderer"]["channelUrl"].as_s
@@ -85,38 +76,6 @@ def get_about_info(ucid, locale)
is_family_friendly = initdata["microformat"]["microformatDataRenderer"]["familySafe"].as_bool
allowed_regions = initdata["microformat"]["microformatDataRenderer"]["availableCountries"].as_a.map(&.as_s)
-
- related_channels = initdata["contents"]["twoColumnBrowseResultsRenderer"]
- .["secondaryContents"]?.try &.["browseSecondaryContentsRenderer"]["contents"][0]?
- .try &.["verticalChannelSectionRenderer"]?.try &.["items"]?.try &.as_a.map do |node|
- renderer = node["miniChannelRenderer"]?
- related_id = renderer.try &.["channelId"]?.try &.as_s?
- related_id ||= ""
-
- related_title = renderer.try &.["title"]?.try &.["simpleText"]?.try &.as_s?
- related_title ||= ""
-
- related_author_url = renderer.try &.["navigationEndpoint"]?.try &.["commandMetadata"]?.try &.["webCommandMetadata"]?
- .try &.["url"]?.try &.as_s?
- related_author_url ||= ""
-
- related_author_thumbnails = renderer.try &.["thumbnail"]?.try &.["thumbnails"]?.try &.as_a?
- related_author_thumbnails ||= [] of JSON::Any
-
- related_author_thumbnail = ""
- if related_author_thumbnails.size > 0
- related_author_thumbnail = related_author_thumbnails[-1]["url"]?.try &.as_s?
- related_author_thumbnail ||= ""
- end
-
- AboutRelatedChannel.new({
- ucid: related_id,
- author: related_title,
- author_url: related_author_url,
- author_thumbnail: related_author_thumbnail,
- })
- end
- related_channels ||= [] of AboutRelatedChannel
end
total_views = 0_i64
@@ -155,20 +114,44 @@ def get_about_info(ucid, locale)
sub_count = initdata["header"]["c4TabbedHeaderRenderer"]?.try &.["subscriberCountText"]?.try &.["simpleText"]?.try &.as_s?
.try { |text| short_text_to_number(text.split(" ")[0]) } || 0
- AboutChannel.new({
- ucid: ucid,
- author: author,
- auto_generated: auto_generated,
- author_url: author_url,
- author_thumbnail: author_thumbnail,
- banner: banner,
- description_html: description_html,
- total_views: total_views,
- sub_count: sub_count,
- joined: joined,
+ AboutChannel.new(
+ ucid: ucid,
+ author: author,
+ auto_generated: auto_generated,
+ author_url: author_url,
+ author_thumbnail: author_thumbnail,
+ banner: banner,
+ description_html: description_html,
+ total_views: total_views,
+ sub_count: sub_count,
+ joined: joined,
is_family_friendly: is_family_friendly,
- allowed_regions: allowed_regions,
- related_channels: related_channels,
- tabs: tabs,
- })
+ allowed_regions: allowed_regions,
+ tabs: tabs,
+ )
+end
+
+def fetch_related_channels(about_channel : AboutChannel) : Array(AboutRelatedChannel)
+ # params is {"2:string":"channels"} encoded
+ channels = YoutubeAPI.browse(browse_id: about_channel.ucid, params: "EghjaGFubmVscw%3D%3D")
+
+ tabs = channels.dig?("contents", "twoColumnBrowseResultsRenderer", "tabs").try(&.as_a?) || [] of JSON::Any
+ tab = tabs.find { |tab| tab.dig?("tabRenderer", "title").try(&.as_s?) == "Channels" }
+ return [] of AboutRelatedChannel if tab.nil?
+
+ items = tab.dig?("tabRenderer", "content", "sectionListRenderer", "contents", 0, "itemSectionRenderer", "contents", 0, "gridRenderer", "items").try(&.as_a?) || [] of JSON::Any
+
+ items.map do |item|
+ related_id = item.dig("gridChannelRenderer", "channelId").as_s
+ related_title = item.dig("gridChannelRenderer", "title", "simpleText").as_s
+ related_author_url = item.dig("gridChannelRenderer", "navigationEndpoint", "browseEndpoint", "canonicalBaseUrl").as_s
+ related_author_thumbnail = item.dig("gridChannelRenderer", "thumbnail", "thumbnails", -1, "url").as_s
+
+ AboutRelatedChannel.new(
+ ucid: related_id,
+ author: related_title,
+ author_url: related_author_url,
+ author_thumbnail: related_author_thumbnail,
+ )
+ end
end
diff --git a/src/invidious/jobs/refresh_channels_job.cr b/src/invidious/jobs/refresh_channels_job.cr
index c224c745..941089c1 100644
--- a/src/invidious/jobs/refresh_channels_job.cr
+++ b/src/invidious/jobs/refresh_channels_job.cr
@@ -13,7 +13,7 @@ class Invidious::Jobs::RefreshChannelsJob < Invidious::Jobs::BaseJob
loop do
LOGGER.debug("RefreshChannelsJob: Refreshing all channels")
- db.query("SELECT id FROM channels ORDER BY updated") do |rs|
+ PG_DB.query("SELECT id FROM channels ORDER BY updated") do |rs|
rs.each do
id = rs.read(String)
@@ -30,7 +30,7 @@ class Invidious::Jobs::RefreshChannelsJob < Invidious::Jobs::BaseJob
spawn do
begin
LOGGER.trace("RefreshChannelsJob: #{id} fiber : Fetching channel")
- channel = fetch_channel(id, db, CONFIG.full_refresh)
+ channel = fetch_channel(id, CONFIG.full_refresh)
lim_fibers = max_fibers
diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr
index 8b6df3fd..322ac42e 100644
--- a/src/invidious/routes/api/v1/channels.cr
+++ b/src/invidious/routes/api/v1/channels.cr
@@ -96,7 +96,7 @@ module Invidious::Routes::API::V1::Channels
json.field "relatedChannels" do
json.array do
- channel.related_channels.each do |related_channel|
+ fetch_related_channels(channel).each do |related_channel|
json.object do
json.field "author", related_channel.author
json.field "authorId", related_channel.ucid