diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 11168aea..a945da58 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -23,4 +23,4 @@ jobs: stale-pr-label: "stale" ascending: true # Never mark feature requests/enhancements as stale - exempt-issue-labels: "feature-request,enhancement" + exempt-issue-labels: "feature-request,enhancement,exempt-stale" diff --git a/README.md b/README.md index 8d668a29..0744ac50 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ Weblate also allows you to log-in with major SSO providers like Github, Gitlab, - [Yattee](https://github.com/yattee/yattee): Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV. - [TubiTui](https://codeberg.org/777/TubiTui): A lightweight, libre, TUI-based YouTube client. - [Ytfzf](https://github.com/pystardust/ytfzf): A posix script to find and watch youtube videos from the terminal. (Without API) +- [Playlet](https://github.com/iBicha/playlet): Unofficial Youtube client for Roku TV ## Liability diff --git a/assets/css/default.css b/assets/css/default.css index 9788e9f7..3deaebe1 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -145,6 +145,24 @@ img.thumbnail { object-fit: cover; } +div.watched-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255,255,255,.4); +} + +div.watched-indicator { + position: absolute; + left: 0; + bottom: 0; + height: 4px; + width: 100%; + background-color: red; +} + .length { z-index: 100; position: absolute; diff --git a/assets/js/watched_indicator.js b/assets/js/watched_indicator.js new file mode 100644 index 00000000..e971cd80 --- /dev/null +++ b/assets/js/watched_indicator.js @@ -0,0 +1,24 @@ +'use strict'; +var save_player_pos_key = 'save_player_pos'; + +function get_all_video_times() { + return helpers.storage.get(save_player_pos_key) || {}; +} + +document.querySelectorAll('.watched-indicator').forEach(function (indicator) { + var watched_part = get_all_video_times()[indicator.dataset.id]; + var total = parseInt(indicator.dataset.length, 10); + if (watched_part === undefined) { + watched_part = total; + } + var percentage = Math.round((watched_part / total) * 100); + + if (percentage < 5) { + percentage = 5; + } + if (percentage > 90) { + percentage = 100; + } + + indicator.style.width = percentage + '%'; +}); diff --git a/locales/de.json b/locales/de.json index 55c40905..c2941d6d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -472,5 +472,12 @@ "search_filters_duration_option_none": "Beliebige Länge", "search_filters_date_label": "Upload-Datum", "search_filters_date_option_none": "Beliebiges Datum", - "error_video_not_in_playlist": "Das angeforderte Video existiert nicht in dieser Wiedergabeliste. Klicken Sie hier, um zur Startseite der Wiedergabeliste zu gelangen." + "error_video_not_in_playlist": "Das angeforderte Video existiert nicht in dieser Wiedergabeliste. Klicken Sie hier, um zur Startseite der Wiedergabeliste zu gelangen.", + "channel_tab_shorts_label": "Shorts", + "channel_tab_streams_label": "Livestreams", + "Music in this video": "Musik in diesem Video", + "Artist: ": "Künstler: ", + "Album: ": "Album: ", + "channel_tab_playlists_label": "Wiedergabelisten", + "channel_tab_channels_label": "Kanäle" } diff --git a/locales/en-US.json b/locales/en-US.json index a5c16fd7..86b83a23 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -454,7 +454,7 @@ "footer_documentation": "Documentation", "footer_source_code": "Source code", "footer_original_source_code": "Original source code", - "footer_modfied_source_code": "Modified Source code", + "footer_modfied_source_code": "Modified source code", "adminprefs_modified_source_code_url_label": "URL to modified source code repository", "none": "none", "videoinfo_started_streaming_x_ago": "Started streaming `x` ago", diff --git a/locales/hi.json b/locales/hi.json index e576080f..41335266 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -470,5 +470,14 @@ "crash_page_switch_instance": "किसी दूसरे उदाहरण का इस्तेमाल करें", "crash_page_read_the_faq": "अक्सर पूछे जाने वाले प्रश्न (FAQ) पढ़ें", "crash_page_refresh": "पृष्ठ को एक बार साफ़ करें", - "crash_page_search_issue": "GitHub पर मौजूदा मुद्दे ढूँढ़ें" + "crash_page_search_issue": "GitHub पर मौजूदा मुद्दे ढूँढ़ें", + "Popular enabled: ": "लोकप्रिय सक्षम: ", + "Artist: ": "कलाकार: ", + "Music in this video": "इस वीडियो में संगीत", + "Album: ": "एल्बम: ", + "error_video_not_in_playlist": "अनुरोधित वीडियो इस प्लेलिस्ट में मौजूद नहीं है। प्लेलिस्ट के मुखपृष्ठ पर जाने के लिए यहाँ क्लिक करें।", + "channel_tab_shorts_label": "शॉर्ट्स", + "channel_tab_streams_label": "लाइवस्ट्रीम्स", + "channel_tab_playlists_label": "प्लेलिस्ट्स", + "channel_tab_channels_label": "चैनल्स" } diff --git a/locales/hr.json b/locales/hr.json index 72cd6a8e..c626ed28 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -359,13 +359,13 @@ "next_steps_error_message_refresh": "Aktualiziraj stranicu", "next_steps_error_message_go_to_youtube": "Idi na YouTube", "footer_donate_page": "Doniraj", - "adminprefs_modified_source_code_url_label": "URL do repozitorija izmijenjenog izvornog koda", + "adminprefs_modified_source_code_url_label": "URL do repozitorija prilagođenog izvornog koda", "search_filters_duration_option_short": "Kratko (< 4 minute)", "search_filters_duration_option_long": "Dugo (> 20 minute)", "footer_source_code": "Izvorni kod", - "footer_modfied_source_code": "Izmijenjeni izvorni kod", + "footer_modfied_source_code": "Prilagođen izvorni kod", "footer_documentation": "Dokumentacija", - "footer_original_source_code": "Izvoran izvorni kod", + "footer_original_source_code": "Prvobitan izvorni kod", "preferences_region_label": "Zemlja sadržaja: ", "preferences_quality_dash_label": "Preferirana DASH videokvaliteta: ", "preferences_quality_option_dash": "DASH (adaptativna kvaliteta)", diff --git a/locales/ja.json b/locales/ja.json index 3ad4b494..d08413ea 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -5,7 +5,7 @@ "generic_subscribers_count_0": "{{count}} 人の登録者", "generic_subscriptions_count_0": "{{count}} 個の登録チャンネル", "LIVE": "ライブ", - "Shared `x` ago": "`x`前に共有", + "Shared `x` ago": "`x`前に公開", "Unsubscribe": "登録解除", "Subscribe": "登録", "View channel on YouTube": "YouTube でチャンネルを見る", @@ -56,17 +56,17 @@ "preferences_category_player": "プレイヤーの設定", "preferences_video_loop_label": "常にループ: ", "preferences_autoplay_label": "自動再生: ", - "preferences_continue_label": "デフォルトで次を再生: ", + "preferences_continue_label": "次の動画を再生: ", "preferences_continue_autoplay_label": "次の動画を自動再生: ", "preferences_listen_label": "デフォルトで音声モードを使用: ", - "preferences_local_label": "動画をプロキシーに通す: ", - "preferences_speed_label": "デフォルトの再生速度: ", + "preferences_local_label": "動画視聴にプロキシーを経由: ", + "preferences_speed_label": "標準の再生速度: ", "preferences_quality_label": "優先する画質: ", "preferences_volume_label": "プレイヤーの音量: ", "preferences_comments_label": "デフォルトのコメント: ", "youtube": "YouTube", "reddit": "Reddit", - "preferences_captions_label": "デフォルトの字幕: ", + "preferences_captions_label": "優先する字幕: ", "Fallback captions: ": "フォールバック時の字幕: ", "preferences_related_videos_label": "関連動画を表示: ", "preferences_annotations_label": "デフォルトでアノテーションを表示: ", @@ -108,7 +108,7 @@ "Watch history": "再生履歴", "Delete account": "アカウントを削除", "preferences_category_admin": "管理者設定", - "preferences_default_home_label": "デフォルトのホーム: ", + "preferences_default_home_label": "ホームに表示するページ: ", "preferences_feed_menu_label": "フィードメニュー: ", "preferences_show_nick_label": "ニックネームを一番上に表示する: ", "Top enabled: ": "トップページを有効化: ", @@ -157,7 +157,7 @@ "Engagement: ": "エンゲージメント: ", "Whitelisted regions: ": "ホワイトリストの地域: ", "Blacklisted regions: ": "ブラックリストの地域: ", - "Shared `x`": "`x`に共有", + "Shared `x`": "公開日 `x`", "Premieres in `x`": "`x`後にプレミア公開", "Premieres `x`": "`x`にプレミア公開", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "やあ!君は JavaScript を無効にしているのかな?ここをクリックしてコメントを見れるけど、読み込みには少し時間がかかることがあるのを覚えておいてね。", @@ -191,9 +191,9 @@ "This channel does not exist.": "このチャンネルは存在しません。", "Could not get channel info.": "チャンネル情報を取得できませんでした。", "Could not fetch comments": "コメントを取得できませんでした", - "comments_view_x_replies_0": "{{count}} 件の返信を見る", + "comments_view_x_replies_0": "{{count}}件の返信を表示", "`x` ago": "`x`前", - "Load more": "もっと読み込む", + "Load more": "もっと見る", "comments_points_count_0": "{{count}}点", "Could not create mix.": "ミックスを作成できませんでした。", "Empty playlist": "空の再生リスト", @@ -377,8 +377,8 @@ "search_filters_duration_option_short": "4 分未満", "footer_documentation": "文書", "footer_source_code": "ソースコード", - "footer_original_source_code": "ソースコード (元)", - "footer_modfied_source_code": "ソースコード (改変)", + "footer_original_source_code": "元のソースコード", + "footer_modfied_source_code": "改変して使用", "adminprefs_modified_source_code_url_label": "改変されたソースコードのレポジトリのURL", "search_filters_duration_option_long": "20 分以上", "preferences_region_label": "地域: ", diff --git a/locales/pt-BR.json b/locales/pt-BR.json index afd31ede..079c4ea1 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -476,5 +476,8 @@ "channel_tab_channels_label": "Canais", "channel_tab_playlists_label": "Listas de reprodução", "channel_tab_shorts_label": "Curtos", - "channel_tab_streams_label": "Ao Vivo" + "channel_tab_streams_label": "Ao Vivo", + "Music in this video": "Música neste vídeo", + "Artist: ": "Artista: ", + "Album: ": "Álbum: " } diff --git a/locales/pt-PT.json b/locales/pt-PT.json index 1788deb1..43834d70 100644 --- a/locales/pt-PT.json +++ b/locales/pt-PT.json @@ -472,5 +472,12 @@ "search_message_change_filters_or_query": "Tente alargar os termos genéricos da pesquisa e/ou alterar os filtros.", "crash_page_refresh": "tentou recarregar a página", "crash_page_switch_instance": "tentou usar outra instância", - "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. Clique aqui para a página inicial da lista de reprodução." + "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. Clique aqui para a página inicial da lista de reprodução.", + "Artist: ": "Artista: ", + "Album: ": "Álbum: ", + "channel_tab_streams_label": "Diretos", + "channel_tab_playlists_label": "Listas de reprodução", + "channel_tab_channels_label": "Canais", + "Music in this video": "Música neste vídeo", + "channel_tab_shorts_label": "Curtos" } diff --git a/locales/ru.json b/locales/ru.json index 733e0be1..7ca5cf1f 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -69,11 +69,11 @@ "preferences_vr_mode_label": "Интерактивные 360-градусные видео (необходим WebGL): ", "preferences_category_visual": "Настройки сайта", "preferences_player_style_label": "Стиль проигрывателя: ", - "Dark mode: ": "Тёмное оформление: ", + "Dark mode: ": "Темное оформление: ", "preferences_dark_mode_label": "Тема: ", - "dark": "тёмная", + "dark": "темная", "light": "светлая", - "preferences_thin_mode_label": "Облегчённое оформление: ", + "preferences_thin_mode_label": "Облегченное оформление: ", "preferences_category_misc": "Прочие настройки", "preferences_automatic_instance_redirect_label": "Автоматическая смена зеркала (переход на redirect.invidious.io): ", "preferences_category_subscription": "Настройки подписок", @@ -88,7 +88,7 @@ "channel name": "по названию канала", "channel name - reverse": "по названию канала в обратном порядке", "Only show latest video from channel: ": "Показывать только последние видео с каналов: ", - "Only show latest unwatched video from channel: ": "Показывать только непросмотренные видео с каналов: ", + "Only show latest unwatched video from channel: ": "Показывать только последние непросмотренные видео с канала: ", "preferences_unseen_only_label": "Показывать только непросмотренные видео: ", "preferences_notifications_only_label": "Показывать только оповещения, если они есть: ", "Enable web notifications": "Включить уведомления в браузере", @@ -147,13 +147,13 @@ "License: ": "Лицензия: ", "Family friendly? ": "Семейный просмотр: ", "Wilson score: ": "Оценка Уилсона: ", - "Engagement: ": "Вовлечённость: ", + "Engagement: ": "Вовлеченность: ", "Whitelisted regions: ": "Доступно в регионах: ", "Blacklisted regions: ": "Недоступно в регионах: ", "Shared `x`": "Опубликовано `x`", "Premieres in `x`": "Премьера через `x`", "Premieres `x`": "Премьера `x`", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Похоже, у вас отключён JavaScript. Нажмите сюда, чтобы увидеть комментарии. Но учтите: они могут загружаться немного медленнее.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Похоже, у вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии. Но учтите: они могут загружаться немного медленнее.", "View YouTube comments": "Показать комментарии с YouTube", "View more comments on Reddit": "Посмотреть больше комментариев на Reddit", "View `x` comments": { @@ -180,23 +180,23 @@ "Please log in": "Пожалуйста, войдите", "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удалён или не найден", + "Deleted or invalid channel": "Канал удален или не найден", "This channel does not exist.": "Такого канала не существует.", - "Could not get channel info.": "Не удаётся получить информацию об этом канале.", - "Could not fetch comments": "Не удаётся загрузить комментарии", + "Could not get channel info.": "Не удается получить информацию об этом канале.", + "Could not fetch comments": "Не удается загрузить комментарии", "`x` ago": "`x` назад", - "Load more": "Загрузить ещё", + "Load more": "Загрузить еще", "Could not create mix.": "Не удалось создать микс.", "Empty playlist": "Плейлист пуст", - "Not a playlist.": "Некорректный плейлист.", + "Not a playlist.": "Это не плейлист.", "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Не удаётся загрузить страницы «в тренде».", + "Could not pull trending pages.": "Не удается загрузить страницы «в тренде».", "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле «challenge»", "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле «токен»", "Erroneous challenge": "Неправильный ответ в «challenge»", "Erroneous token": "Неправильный токен", "No such user": "Пользователь не найден", - "Token is expired, please try again": "Срок действия токена истёк, попробуйте позже", + "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", "English": "Английский", "English (auto-generated)": "Английский (созданы автоматически)", "Afrikaans": "Африкаанс", @@ -453,8 +453,8 @@ "Portuguese (Brazil)": "Португальский (Бразилия)", "footer_source_code": "Исходный код", "footer_original_source_code": "Оригинальный исходный код", - "footer_modfied_source_code": "Изменённый исходный код", - "user_saved_playlists": "`x` сохранённых плейлистов", + "footer_modfied_source_code": "Измененный исходный код", + "user_saved_playlists": "`x` сохраненных плейлистов", "crash_page_search_issue": "поискали похожую проблему на GitHub", "comments_points_count_0": "{{count}} плюс", "comments_points_count_1": "{{count}} плюса", diff --git a/locales/sq.json b/locales/sq.json index 15025750..7f29a035 100644 --- a/locales/sq.json +++ b/locales/sq.json @@ -286,7 +286,7 @@ "search_filters_type_option_show": "Shfaqe", "search_filters_duration_option_short": "E shkurtër (< 4 minuta)", "search_filters_features_option_purchased": "Të blera", - "footer_modfied_source_code": "Kod Burim i ndryshuar", + "footer_modfied_source_code": "Kod burim i ndryshuar", "adminprefs_modified_source_code_url_label": "URL e depos së ndryshuar të kodit burim", "none": "asnjë", "videoinfo_started_streaming_x_ago": "Filloi transmetimin `x` më parë", @@ -468,5 +468,7 @@ "Artist: ": "Artist: ", "Album: ": "Album: ", "channel_tab_channels_label": "Kanale", - "Music in this video": "Muzikë në këtë video" + "Music in this video": "Muzikë në këtë video", + "channel_tab_shorts_label": "Të shkurtra", + "channel_tab_streams_label": "Transmetime të drejtpërdrejta" } diff --git a/locales/tr.json b/locales/tr.json index d98e2038..b7cb3958 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -363,7 +363,7 @@ "footer_documentation": "Belgelendirme", "footer_source_code": "Kaynak Kodları", "footer_original_source_code": "Orijinal Kaynak Kodları", - "footer_modfied_source_code": "Değiştirilmiş Kaynak Kodları", + "footer_modfied_source_code": "Değiştirilmiş kaynak kodları", "adminprefs_modified_source_code_url_label": "Değiştirilmiş Kaynak Kodları Deposunun URL'si", "footer_donate_page": "Bağış Yap", "preferences_region_label": "İçerik Ülkesi: ", diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr index e6a70ed2..5aa4452c 100644 --- a/src/invidious/routes/account.cr +++ b/src/invidious/routes/account.cr @@ -262,6 +262,7 @@ module Invidious::Routes::Account end query["token"] = access_token + query["username"] = URI.encode_path_segment(user.email) url.query = query.to_s env.redirect url.to_s diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr index 421355bb..6b935312 100644 --- a/src/invidious/routes/api/v1/authenticated.cr +++ b/src/invidious/routes/api/v1/authenticated.cr @@ -31,6 +31,29 @@ module Invidious::Routes::API::V1::Authenticated env.response.status_code = 204 end + def self.export_invidious(env) + env.response.content_type = "application/json" + user = env.get("user").as(User) + + return Invidious::User::Export.to_invidious(user) + end + + def self.import_invidious(env) + user = env.get("user").as(User) + + begin + if body = env.request.body + body = env.request.body.not_nil!.gets_to_end + else + body = "{}" + end + Invidious::User::Import.from_invidious(user, body) + rescue + end + + env.response.status_code = 204 + end + def self.feed(env) env.response.content_type = "application/json" diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr index 43d360e6..e499f4d6 100644 --- a/src/invidious/routes/api/v1/misc.cr +++ b/src/invidious/routes/api/v1/misc.cr @@ -150,4 +150,31 @@ module Invidious::Routes::API::V1::Misc response end + + # resolve channel and clip urls, return the UCID + def self.resolve_url(env) + env.response.content_type = "application/json" + url = env.params.query["url"]? + + return error_json(400, "Missing URL to resolve") if !url + + begin + resolved_url = YoutubeAPI.resolve_url(url.as(String)) + endpoint = resolved_url["endpoint"] + pageType = endpoint.dig?("commandMetadata", "webCommandMetadata", "webPageType").try &.as_s || "" + if resolved_ucid = endpoint.dig?("watchEndpoint", "videoId") + elsif resolved_ucid = endpoint.dig?("browseEndpoint", "browseId") + elsif pageType == "WEB_PAGE_TYPE_UNKNOWN" + return error_json(400, "Unknown url") + end + rescue ex + return error_json(500, ex) + end + JSON.build do |json| + json.object do + json.field "ucid", resolved_ucid.try &.as_s || "" + json.field "pageType", pageType + end + end + end end diff --git a/src/invidious/routes/subscriptions.cr b/src/invidious/routes/subscriptions.cr index 7b1fa876..0704c05e 100644 --- a/src/invidious/routes/subscriptions.cr +++ b/src/invidious/routes/subscriptions.cr @@ -104,33 +104,8 @@ module Invidious::Routes::Subscriptions if format == "json" env.response.content_type = "application/json" env.response.headers["content-disposition"] = "attachment" - playlists = Invidious::Database::Playlists.select_like_iv(user.email) - return JSON.build do |json| - json.object do - json.field "subscriptions", user.subscriptions - json.field "watch_history", user.watched - json.field "preferences", user.preferences - json.field "playlists" do - json.array do - playlists.each do |playlist| - json.object do - json.field "title", playlist.title - json.field "description", html_to_content(playlist.description_html) - json.field "privacy", playlist.privacy.to_s - json.field "videos" do - json.array do - Invidious::Database::PlaylistVideos.select_ids(playlist.id, playlist.index, limit: 500).each do |video_id| - json.string video_id - end - end - end - end - end - end - end - end - end + return Invidious::User::Export.to_invidious(user) else env.response.content_type = "application/xml" env.response.headers["content-disposition"] = "attachment" diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index 157e6de7..dca2f117 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -254,6 +254,9 @@ module Invidious::Routing get "/api/v1/auth/preferences", {{namespace}}::Authenticated, :get_preferences post "/api/v1/auth/preferences", {{namespace}}::Authenticated, :set_preferences + get "/api/v1/auth/export/invidious", {{namespace}}::Authenticated, :export_invidious + post "/api/v1/auth/import/invidious", {{namespace}}::Authenticated, :import_invidious + get "/api/v1/auth/feed", {{namespace}}::Authenticated, :feed get "/api/v1/auth/subscriptions", {{namespace}}::Authenticated, :get_subscriptions @@ -281,6 +284,7 @@ module Invidious::Routing get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes + get "/api/v1/resolveurl", {{namespace}}::Misc, :resolve_url {% end %} end end diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr index 1f957081..134eb437 100644 --- a/src/invidious/trending.cr +++ b/src/invidious/trending.cr @@ -4,11 +4,12 @@ def fetch_trending(trending_type, region, locale) plid = nil - if trending_type == "Music" + case trending_type.try &.downcase + when "music" params = "4gINGgt5dG1hX2NoYXJ0cw%3D%3D" - elsif trending_type == "Gaming" + when "gaming" params = "4gIcGhpnYW1pbmdfY29ycHVzX21vc3RfcG9wdWxhcg%3D%3D" - elsif trending_type == "Movies" + when "movies" params = "4gIKGgh0cmFpbGVycw%3D%3D" else # Default params = "" diff --git a/src/invidious/user/exports.cr b/src/invidious/user/exports.cr new file mode 100644 index 00000000..b52503c9 --- /dev/null +++ b/src/invidious/user/exports.cr @@ -0,0 +1,35 @@ +struct Invidious::User + module Export + extend self + + def to_invidious(user : User) + playlists = Invidious::Database::Playlists.select_like_iv(user.email) + + return JSON.build do |json| + json.object do + json.field "subscriptions", user.subscriptions + json.field "watch_history", user.watched + json.field "preferences", user.preferences + json.field "playlists" do + json.array do + playlists.each do |playlist| + json.object do + json.field "title", playlist.title + json.field "description", html_to_content(playlist.description_html) + json.field "privacy", playlist.privacy.to_s + json.field "videos" do + json.array do + Invidious::Database::PlaylistVideos.select_ids(playlist.id, playlist.index, limit: CONFIG.playlist_length_limit).each do |video_id| + json.string video_id + end + end + end + end + end + end + end + end + end + end + end # module +end diff --git a/src/invidious/views/add_playlist_items.ecr b/src/invidious/views/add_playlist_items.ecr index 22870317..bcba74cf 100644 --- a/src/invidious/views/add_playlist_items.ecr +++ b/src/invidious/views/add_playlist_items.ecr @@ -39,6 +39,8 @@ <% end %> + + <% if query %> <%- query_encoded = URI.encode_www_form(query.text, space_to_plus: true) -%>
diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr index a29315ef..6e62a471 100644 --- a/src/invidious/views/channel.ecr +++ b/src/invidious/views/channel.ecr @@ -49,6 +49,8 @@ <% end %>
+ +
diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 0e959ff2..fa12374f 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -1,3 +1,5 @@ +<% item_watched = !item.is_a?(SearchChannel | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil %> +
<% case item when %> @@ -40,6 +42,11 @@ <% if item.length_seconds != 0 %>

<%= recode_length_seconds(item.length_seconds) %>

<% end %> + + <% if item_watched %> +
+
+ <% end %>
<% end %>

<%= HTML.escape(item.title) %>

@@ -67,6 +74,11 @@ <% elsif item.length_seconds != 0 %>

<%= recode_length_seconds(item.length_seconds) %>

<% end %> + + <% if item_watched %> +
+
+ <% end %>
<% end %>

<%= HTML.escape(item.title) %>

@@ -124,6 +136,11 @@ <% elsif item.length_seconds != 0 %>

<%= recode_length_seconds(item.length_seconds) %>

<% end %> + + <% if item_watched %> +
+
+ <% end %>
<% end %>

<%= HTML.escape(item.title) %>

diff --git a/src/invidious/views/edit_playlist.ecr b/src/invidious/views/edit_playlist.ecr index 89819ef0..548104c8 100644 --- a/src/invidious/views/edit_playlist.ecr +++ b/src/invidious/views/edit_playlist.ecr @@ -62,6 +62,8 @@ <% end %>
+ +
<% if page > 1 %> diff --git a/src/invidious/views/feeds/playlists.ecr b/src/invidious/views/feeds/playlists.ecr index a59344c4..e52a7707 100644 --- a/src/invidious/views/feeds/playlists.ecr +++ b/src/invidious/views/feeds/playlists.ecr @@ -32,3 +32,5 @@ <%= rendered "components/item" %> <% end %>
+ + diff --git a/src/invidious/views/feeds/popular.ecr b/src/invidious/views/feeds/popular.ecr index e77f35b9..5fbe539c 100644 --- a/src/invidious/views/feeds/popular.ecr +++ b/src/invidious/views/feeds/popular.ecr @@ -16,3 +16,5 @@ <%= rendered "components/item" %> <% end %>
+ + diff --git a/src/invidious/views/feeds/subscriptions.ecr b/src/invidious/views/feeds/subscriptions.ecr index 76f2f2bd..9c69c5b0 100644 --- a/src/invidious/views/feeds/subscriptions.ecr +++ b/src/invidious/views/feeds/subscriptions.ecr @@ -62,6 +62,8 @@ <% end %> + +
<% if page > 1 %> diff --git a/src/invidious/views/feeds/trending.ecr b/src/invidious/views/feeds/trending.ecr index a35c4ee3..7dc416c6 100644 --- a/src/invidious/views/feeds/trending.ecr +++ b/src/invidious/views/feeds/trending.ecr @@ -45,3 +45,5 @@ <%= rendered "components/item" %> <% end %>
+ + diff --git a/src/invidious/views/hashtag.ecr b/src/invidious/views/hashtag.ecr index 0ecfe832..3351c21c 100644 --- a/src/invidious/views/hashtag.ecr +++ b/src/invidious/views/hashtag.ecr @@ -24,6 +24,8 @@ <%- end -%>
+ +
<%- if page > 1 -%> diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index df3112db..a04acf4c 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -106,6 +106,8 @@ <% end %>
+ +
<% if page > 1 %> diff --git a/src/invidious/views/search.ecr b/src/invidious/views/search.ecr index 254449a1..a7469e36 100644 --- a/src/invidious/views/search.ecr +++ b/src/invidious/views/search.ecr @@ -37,6 +37,8 @@
<%- end -%> + +
<%- if query.page > 1 -%>