mirror of
				https://github.com/iv-org/invidious.git
				synced 2025-11-04 06:31:57 +00:00 
			
		
		
		
	
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								assets/js/watched_indicator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								assets/js/watched_indicator.js
									
									
									
									
									
										Normal file
									
								
							@@ -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 + '%';
 | 
			
		||||
});
 | 
			
		||||
@@ -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. <a href=\"`x`\">Klicken Sie hier, um zur Startseite der Wiedergabeliste zu gelangen.</a>"
 | 
			
		||||
    "error_video_not_in_playlist": "Das angeforderte Video existiert nicht in dieser Wiedergabeliste. <a href=\"`x`\">Klicken Sie hier, um zur Startseite der Wiedergabeliste zu gelangen.</a>",
 | 
			
		||||
    "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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -470,5 +470,14 @@
 | 
			
		||||
    "crash_page_switch_instance": "<a href=\"`x`\">किसी दूसरे उदाहरण का इस्तेमाल करें</a>",
 | 
			
		||||
    "crash_page_read_the_faq": "<a href=\"`x`\">अक्सर पूछे जाने वाले प्रश्न (FAQ)</a> पढ़ें",
 | 
			
		||||
    "crash_page_refresh": "<a href=\"`x`\">पृष्ठ को एक बार साफ़ करें</a>",
 | 
			
		||||
    "crash_page_search_issue": "<a href=\"`x`\">GitHub पर मौजूदा मुद्दे</a> ढूँढ़ें"
 | 
			
		||||
    "crash_page_search_issue": "<a href=\"`x`\">GitHub पर मौजूदा मुद्दे</a> ढूँढ़ें",
 | 
			
		||||
    "Popular enabled: ": "लोकप्रिय सक्षम: ",
 | 
			
		||||
    "Artist: ": "कलाकार: ",
 | 
			
		||||
    "Music in this video": "इस वीडियो में संगीत",
 | 
			
		||||
    "Album: ": "एल्बम: ",
 | 
			
		||||
    "error_video_not_in_playlist": "अनुरोधित वीडियो इस प्लेलिस्ट में मौजूद नहीं है। <a href=\"`x`\">प्लेलिस्ट के मुखपृष्ठ पर जाने के लिए यहाँ क्लिक करें।</a>",
 | 
			
		||||
    "channel_tab_shorts_label": "शॉर्ट्स",
 | 
			
		||||
    "channel_tab_streams_label": "लाइवस्ट्रीम्स",
 | 
			
		||||
    "channel_tab_playlists_label": "प्लेलिस्ट्स",
 | 
			
		||||
    "channel_tab_channels_label": "चैनल्स"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)",
 | 
			
		||||
 
 | 
			
		||||
@@ -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": "地域: ",
 | 
			
		||||
 
 | 
			
		||||
@@ -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: "
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <a href=\"`x`\">recarregar a página</a>",
 | 
			
		||||
    "crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
 | 
			
		||||
    "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>"
 | 
			
		||||
    "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>",
 | 
			
		||||
    "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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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": "поискали <a href=\"`x`\">похожую проблему на GitHub</a>",
 | 
			
		||||
    "comments_points_count_0": "{{count}} плюс",
 | 
			
		||||
    "comments_points_count_1": "{{count}} плюса",
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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: ",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = ""
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								src/invidious/user/exports.cr
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/invidious/user/exports.cr
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
@@ -39,6 +39,8 @@
 | 
			
		||||
    <% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 | 
			
		||||
<% if query %>
 | 
			
		||||
    <%- query_encoded = URI.encode_www_form(query.text, space_to_plus: true) -%>
 | 
			
		||||
    <div class="pure-g h-box">
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,8 @@
 | 
			
		||||
<% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 | 
			
		||||
<div class="pure-g h-box">
 | 
			
		||||
    <div class="pure-u-1 pure-u-md-4-5"></div>
 | 
			
		||||
    <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
<% item_watched = !item.is_a?(SearchChannel | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil %>
 | 
			
		||||
 | 
			
		||||
<div class="pure-u-1 pure-u-md-1-4">
 | 
			
		||||
    <div class="h-box">
 | 
			
		||||
        <% case item when %>
 | 
			
		||||
@@ -40,6 +42,11 @@
 | 
			
		||||
                        <% if item.length_seconds != 0 %>
 | 
			
		||||
                            <p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
 | 
			
		||||
                        <% end %>
 | 
			
		||||
 | 
			
		||||
                        <% if item_watched %>
 | 
			
		||||
                            <div class="watched-overlay"></div>
 | 
			
		||||
                            <div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
 | 
			
		||||
                        <% end %>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <% end %>
 | 
			
		||||
                <p dir="auto"><%= HTML.escape(item.title) %></p>
 | 
			
		||||
@@ -67,6 +74,11 @@
 | 
			
		||||
                        <% elsif item.length_seconds != 0 %>
 | 
			
		||||
                            <p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
 | 
			
		||||
                        <% end %>
 | 
			
		||||
 | 
			
		||||
                        <% if item_watched %>
 | 
			
		||||
                            <div class="watched-overlay"></div>
 | 
			
		||||
                            <div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
 | 
			
		||||
                        <% end %>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <% end %>
 | 
			
		||||
                <p dir="auto"><%= HTML.escape(item.title) %></p>
 | 
			
		||||
@@ -124,6 +136,11 @@
 | 
			
		||||
                        <% elsif item.length_seconds != 0 %>
 | 
			
		||||
                            <p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
 | 
			
		||||
                        <% end %>
 | 
			
		||||
 | 
			
		||||
                        <% if item_watched %>
 | 
			
		||||
                            <div class="watched-overlay"></div>
 | 
			
		||||
                            <div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
 | 
			
		||||
                        <% end %>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <% end %>
 | 
			
		||||
                <p dir="auto"><%= HTML.escape(item.title) %></p>
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,8 @@
 | 
			
		||||
<% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 | 
			
		||||
<div class="pure-g h-box">
 | 
			
		||||
    <div class="pure-u-1 pure-u-lg-1-5">
 | 
			
		||||
        <% if page > 1 %>
 | 
			
		||||
 
 | 
			
		||||
@@ -32,3 +32,5 @@
 | 
			
		||||
    <%= rendered "components/item" %>
 | 
			
		||||
<% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,3 +16,5 @@
 | 
			
		||||
    <%= rendered "components/item" %>
 | 
			
		||||
<% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,8 @@
 | 
			
		||||
<% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 | 
			
		||||
<div class="pure-g h-box">
 | 
			
		||||
    <div class="pure-u-1 pure-u-lg-1-5">
 | 
			
		||||
        <% if page > 1 %>
 | 
			
		||||
 
 | 
			
		||||
@@ -45,3 +45,5 @@
 | 
			
		||||
    <%= rendered "components/item" %>
 | 
			
		||||
<% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@
 | 
			
		||||
    <%- end -%>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 | 
			
		||||
<div class="pure-g h-box">
 | 
			
		||||
    <div class="pure-u-1 pure-u-lg-1-5">
 | 
			
		||||
        <%- if page > 1 -%>
 | 
			
		||||
 
 | 
			
		||||
@@ -106,6 +106,8 @@
 | 
			
		||||
<% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 | 
			
		||||
<div class="pure-g h-box">
 | 
			
		||||
    <div class="pure-u-1 pure-u-lg-1-5">
 | 
			
		||||
        <% if page > 1 %>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,8 @@
 | 
			
		||||
</div>
 | 
			
		||||
<%- end -%>
 | 
			
		||||
 | 
			
		||||
<script src="/js/watched_indicator.js"></script>
 | 
			
		||||
 | 
			
		||||
<div class="pure-g h-box">
 | 
			
		||||
    <div class="pure-u-1 pure-u-lg-1-5">
 | 
			
		||||
        <%- if query.page > 1 -%>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user