diff --git a/locales/ar.json b/locales/ar.json index 7331b022..f82683cf 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -10,7 +10,7 @@ "newest": "الأجدد", "oldest": "الأقدم", "popular": "الاكثر شعبية", - "last": "", + "last": "اخر الفيديوهات المعدلة", "Next page": "الصفحة الثانية", "Previous page": "الصفحة السابقة", "Clear watch history?": "مسح السجل ؟", @@ -50,7 +50,7 @@ "Autoplay: ": "تشغيل تلقائى: ", "Autoplay next video: ": "شغل الفيديو التالى تلقائى: ", "Listen by default: ": "تشغيل النسخة السمعية تلقائى: ", - "Proxy videos? ": "", + "Proxy videos? ": "عرض الفيديوهات عن طريق الوكيل(proxy) ؟", "Default speed: ": "السرعة الإفتراضية: ", "Preferred video quality: ": "الجودة المفضلة للفيديوهات: ", "Player volume: ": "صوت المشغل: ", @@ -83,14 +83,14 @@ "Manage subscriptions": "إدارة المشتركين", "Watch history": "سجل المشاهدة", "Delete account": "حذف الحساب", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", + "Administrator preferences": "إعدادات المدير", + "Default homepage: ": "الصفحة الرئيسية الافتراضية ", + "Feed menu: ": "قائمة التغذية", + "Top enabled? ": "تفعيل 'الأفضل' ؟ ", + "CAPTCHA enabled? ": "تفعيل الكابتشا ؟", + "Login enabled? ": "تفعيل تسجيل الدخول ؟", + "Registration enabled? ": "تفعيل التسجيل ؟", + "Report statistics? ": "إبلاغ الإحصائيات", "Save preferences": "حفظ التفضيلات", "Subscription manager": "مدير الإشتراكات", "`x` subscriptions": "`x` مشتركين", @@ -103,8 +103,9 @@ "Released under the AGPLv3 by Omar Roth.": "تم الإنشاء تحت AGPLv3 بواسطة عمر روث.", "Source available here.": "الأكواد متوفرة هنا.", "View JavaScript license information.": "مشاهدة معلومات حول تراخيص الجافاسكريبت.", - "View privacy policy.": "", + "View privacy policy.": "عرض سياسة الخصوصية", "Trending": "الشائع", + "Unlisted": "غير مصنف", "Watch video on Youtube": "مشاهدة الفيديو على اليوتيوب", "Genre: ": "النوع: ", "License: ": "التراخيص: ", @@ -114,6 +115,7 @@ "Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ", "Blacklisted regions: ": "الدول الحظور فيها هذا الفيديو: ", "Shared `x`": "شارك منذ `x`", + "Premieres in `x`": "يعرض فى 'x'", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.", "View YouTube comments": "عرض تعليقات اليوتيوب", "View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع Reddit", diff --git a/locales/de.json b/locales/de.json index 89bc09ea..2fcffd75 100644 --- a/locales/de.json +++ b/locales/de.json @@ -105,6 +105,7 @@ "View JavaScript license information.": "Javascript Lizenzinformationen anzeigen.", "View privacy policy.": "", "Trending": "Trending", + "Unlisted": "", "Watch video on Youtube": "Video auf YouTube ansehen", "Genre: ": "Genre: ", "License: ": "Lizenz: ", @@ -114,6 +115,7 @@ "Whitelisted regions: ": "Erlaubte Regionen: ", "Blacklisted regions: ": "Unerlaubte Regionen: ", "Shared `x`": "Geteilt `x`", + "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.", "View YouTube comments": "YouTube Kommentare anzeigen", "View more comments on Reddit": "Mehr Kommentare auf Reddit anzeigen", diff --git a/locales/en-US.json b/locales/en-US.json index 68204a04..174ffb4f 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -103,6 +103,7 @@ "View JavaScript license information.": "View JavaScript license information.", "View privacy policy.": "View privacy policy.", "Trending": "Trending", + "Unlisted": "", "Watch video on Youtube": "Watch video on Youtube", "Genre: ": "Genre: ", "License: ": "License: ", @@ -112,6 +113,7 @@ "Whitelisted regions: ": "Whitelisted regions: ", "Blacklisted regions: ": "Blacklisted regions: ", "Shared `x`": "Shared `x`", + "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.", "View YouTube comments": "View YouTube comments", "View more comments on Reddit": "View more comments on Reddit", diff --git a/locales/eu.json b/locales/eu.json index b71a163e..4a7342a1 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -102,6 +102,7 @@ "Source available here.": "", "View JavaScript license information.": "", "View privacy policy.": "", + "Unlisted": "", "Trending": "", "Watch video on Youtube": "", "Genre: ": "", @@ -112,6 +113,7 @@ "Whitelisted regions: ": "", "Blacklisted regions: ": "", "Shared `x`": "", + "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", "View YouTube comments": "", "View more comments on Reddit": "", diff --git a/locales/fr.json b/locales/fr.json index 6ce60575..12dc9f2c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -2,7 +2,7 @@ "`x` subscribers": "`x` abonnés", "`x` videos": "`x` vidéos", "LIVE": "EN DIRECT", - "Shared `x` ago": "Partagé il y a `x`", + "Shared `x` ago": "Ajoutée il y a `x`", "Unsubscribe": "Se désabonner", "Subscribe": "S'abonner", "Login to subscribe to `x`": "Vous devez vous connecter pour vous abonner à `x`", @@ -16,7 +16,7 @@ "Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?", "Yes": "Oui", "No": "Non", - "Import and Export Data": "Importer et Exporter les Données", + "Import and Export Data": "Importer et exporter des données", "Import": "Importer", "Import Invidious data": "Importer des données Invidious", "Import YouTube subscriptions": "Importer des abonnements YouTube", @@ -32,32 +32,32 @@ "An alternative front-end to YouTube": "Un front-end alternatif à YouTube", "JavaScript license information": "Informations sur les licences JavaScript", "source": "source", - "Login": "Connexion", - "Login/Register": "Connexion/S'inscrire", - "Login to Google": "Se connecter à Google", + "Login": "Se connecter", + "Login/Register": "Se connecter/Créer un compte", + "Login to Google": "Se connecter avec Google", "User ID:": "Identifiant utilisateur :", "Password:": "Mot de passe :", "Time (h:mm:ss):": "Heure (h:mm:ss) :", "Text CAPTCHA": "CAPTCHA Texte", "Image CAPTCHA": "CAPTCHA Image", - "Sign In": "S'identifier", + "Sign In": "Se connecter", "Register": "S'inscrire", "Email:": "E-mail :", "Google verification code:": "Code de vérification Google :", "Preferences": "Préférences", - "Player preferences": "Préférences du Lecteur", + "Player preferences": "Préférences du lecteur", "Always loop: ": "Lire en boucle : ", - "Autoplay: ": "Lire Automatiquement : ", + "Autoplay: ": "Lire automatiquement : ", "Autoplay next video: ": "Lire automatiquement la vidéo suivante : ", - "Listen by default: ": "Audio Uniquement par défaut : ", - "Proxy videos? ": "Souhaitez vous charger les vidéos à travers un proxy ?", + "Listen by default: ": "Audio uniquement : ", + "Proxy videos? ": "Charger les vidéos à travers un proxy ? ", "Default speed: ": "Vitesse par défaut : ", "Preferred video quality: ": "Qualité vidéo souhaitée : ", "Player volume: ": "Volume du lecteur : ", - "Default comments: ": "Source des Commentaires : ", - "Default captions: ": "Sous-titres principal : ", - "Fallback captions: ": "Sous-titres secondaire : ", - "Show related videos? ": "Voir les vidéos liées à ce sujet ? ", + "Default comments: ": "Source des commentaires : ", + "Default captions: ": "Sous-titres par défaut : ", + "Fallback captions: ": "Fallback captions: ", + "Show related videos? ": "Voir les vidéos liées ? ", "Visual preferences": "Préférences du site", "Dark mode: ": "Mode Sombre : ", "Thin mode: ": "Mode Simplifié : ", @@ -82,13 +82,13 @@ "Watch history": "Historique de visionnage", "Delete account": "Supprimer votre compte", "Administrator preferences": "Préferences d'Administrateur", - "Default homepage: ": "Page d'accueil par defaut :", - "Feed menu: ": "Menu des Flux :", - "Top enabled? ": "Top activé ?", - "CAPTCHA enabled? ": "CAPTCHA activé ?", - "Login enabled? ": "Connexion activé ?", - "Registration enabled? ": "Inscription activé ?", - "Report statistics? ": "Telemetrie activé ?", + "Default homepage: ": "Page d'accueil par défaut : ", + "Feed menu: ": "Menu des Flux : ", + "Top enabled? ": "Top activé ? ", + "CAPTCHA enabled? ": "CAPTCHA activé ? ", + "Login enabled? ": "Connexion activé ? ", + "Registration enabled? ": "Inscription activée ? ", + "Report statistics? ": "Télémétrie activé ? ", "Save preferences": "Enregistrer les préférences", "Subscription manager": "Gestionnaire d'abonnement", "`x` subscriptions": "`x` abonnements", @@ -103,16 +103,18 @@ "View JavaScript license information.": "Voir les informations des licences JavaScript.", "View privacy policy.": "Politique de confidentialité", "Trending": "Tendances", + "Unlisted": "Non répertoriée", "Watch video on Youtube": "Voir la vidéo sur Youtube", "Genre: ": "Genre : ", "License: ": "Licence : ", "Family friendly? ": "Tout Public ? ", "Wilson score: ": "Score de Wilson : ", - "Engagement: ": "Poucentage de spectateur aillant aimé Liker ou Disliker la vidéo : ", + "Engagement: ": "Poucentage de spectateur aillant aimé Like ou Dislike la vidéo : ", "Whitelisted regions: ": "Régions en liste blanche : ", "Blacklisted regions: ": "Régions sur liste noire : ", - "Shared `x`": "Partagée `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript sois désactivé. Cliquez ici pour voir les commentaires. Gardez à l'esprit que le chargement peut prendre plus de temps.", + "Shared `x`": "Ajoutée le `x`", + "Premieres in `x`": "Première dans `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript soit désactivé. Cliquez ici pour voir les commentaires sans. Gardez à l'esprit que le chargement peut prendre plus de temps.", "View YouTube comments": "Voir les commentaires YouTube", "View more comments on Reddit": "Voir plus de commentaires sur Reddit", "View `x` comments": "Voir `x` commentaires", @@ -124,13 +126,13 @@ "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.", "Invalid TFA code": "Code d'authentification à deux facteurs invalide", "Login failed. This may be because two-factor authentication is not enabled on your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.", - "Invalid answer": "Réponse non valide", + "Invalid answer": "Réponse invalide", "Invalid CAPTCHA": "CAPTCHA invalide", - "CAPTCHA is a required field": "Veuillez rentrez un CAPTCHA", - "User ID is a required field": "Veuillez rentrez un Identifiant Utilisateur", - "Password is a required field": "Veuillez rentrez un Mot de passe", + "CAPTCHA is a required field": "Veuillez entrer un CAPTCHA", + "User ID is a required field": "Veuillez entrer un Identifiant Utilisateur", + "Password is a required field": "Veuillez entrer un Mot de passe", "Invalid username or password": "Nom d'utilisateur ou mot de passe invalide", - "Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"S'identifier avec Google\"", + "Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"Se connecter avec Google\"", "Password cannot be empty": "Le mot de passe ne peut pas être vide", "Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères", "Please sign in": "Veuillez vous connecter", @@ -268,7 +270,7 @@ "`x` hours": "`x` heures", "`x` minutes": "`x` minutes", "`x` seconds": "`x` secondes", - "Fallback comments: ": "Commentaires secondaires : ", + "Fallback comments: ": "Fallback comments: ", "Popular": "Populaire", "Top": "Top", "About": "A Propos", @@ -289,5 +291,5 @@ "Video mode": "Mode Vidéo", "Videos": "Vidéos", "Playlists": "Liste de lecture", - "Current version: ": "Version actuelle :" + "Current version: ": "Version :" } diff --git a/locales/it.json b/locales/it.json index 6fae1259..76bc15bd 100644 --- a/locales/it.json +++ b/locales/it.json @@ -103,6 +103,7 @@ "View JavaScript license information.": "Guarda le informazioni di licenza del codice JavaScript.", "View privacy policy.": "", "Trending": "Tendenze", + "Unlisted": "", "Watch video on Youtube": "Guarda il video su YouTube", "Genre: ": "Genere: ", "License: ": "Licenza: ", @@ -112,6 +113,7 @@ "Whitelisted regions: ": "Regioni nella lista bianca: ", "Blacklisted regions: ": "Regioni nella lista nera: ", "Shared `x`": "Condiviso `x`", + "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Ciao! Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti. Considera che potrebbe volerci più tempo.", "View YouTube comments": "Visualizza i commenti da YouTube", "View more comments on Reddit": "Visualizza più commenti su Reddit", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 92d43ca0..e0fbfd77 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -103,6 +103,7 @@ "View JavaScript license information.": "Vis JavaScript-lisensinfo.", "View privacy policy.": "", "Trending": "Trendsettende", + "Unlisted": "", "Watch video on Youtube": "Vis video på YouTube", "Genre: ": "Sjanger: ", "License: ": "Lisens: ", @@ -112,6 +113,7 @@ "Whitelisted regions: ": "Hvitlistede regioner: ", "Blacklisted regions: ": "Svartelistede regioner: ", "Shared `x`": "Delt `x`", + "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", "View YouTube comments": "Vis YouTube-kommentarer", "View more comments on Reddit": "Vis flere kommenterer på Reddit", diff --git a/locales/nl.json b/locales/nl.json index 3bfe0ac4..68294dc6 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -103,6 +103,7 @@ "View JavaScript license information.": "Bekijk JavaScript licentie informatie.", "View privacy policy.": "", "Trending": "Trending", + "Unlisted": "", "Watch video on Youtube": "Bekijk video op Youtube", "Genre: ": "Genre: ", "License: ": "Licentie: ", @@ -112,6 +113,7 @@ "Whitelisted regions: ": "Toegestane regio's: ", "Blacklisted regions: ": "Geblokkeerde regio's: ", "Shared `x`": "`x` gedeeld", + "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hoi! Het lijkt erop dat je JavaScript uit hebt staan. Klik hier om de reacties te bekijken, hou er rekening mee dat het wat langer duurt om te laden.", "View YouTube comments": "Bekijk YouTube reacties", "View more comments on Reddit": "Bekijk meer reacties op Reddit", diff --git a/locales/pl.json b/locales/pl.json index 44db3d65..37410974 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -10,7 +10,7 @@ "newest": "najnowsze", "oldest": "najstarsze", "popular": "popularne", - "last": "", + "last": "ostatnie", "Next page": "Następna strona", "Previous page": "Poprzednia strona", "Clear watch history?": "Wyczyścić historię?", @@ -50,7 +50,7 @@ "Autoplay: ": "Autoodtwarzanie: ", "Autoplay next video: ": "Odtwórz następny film: ", "Listen by default: ": "Tryb dźwiękowy: ", - "Proxy videos? ": "", + "Proxy videos? ": "Filmy przez proxy? ", "Default speed: ": "Domyślna prędkość: ", "Preferred video quality: ": "Preferowana jakość filmów: ", "Player volume: ": "Głośność odtwarzacza: ", @@ -101,8 +101,9 @@ "Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.", "Source available here.": "Kod źródłowy dostępny tutaj.", "View JavaScript license information.": "Wyświetl informację o licencji JavaScript.", - "View privacy policy.": "", + "View privacy policy.": "Polityka prywatności.", "Trending": "Na czasie", + "Unlisted": "", "Watch video on Youtube": "Zobacz film na YouTube", "Genre: ": "Gatunek: ", "License: ": "Licencja: ", @@ -112,6 +113,7 @@ "Whitelisted regions: ": "Dostępny na obszarach: ", "Blacklisted regions: ": "Niedostępny na obszarach: ", "Shared `x`": "Udostępniono `x`", + "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", "View YouTube comments": "Wyświetl komentarze z YouTube", "View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie", @@ -270,7 +272,7 @@ "`x` seconds": "`x` sekund", "Fallback comments: ": "Zastępcze komentarze: ", "Popular": "Popularne", - "Top": "Na czasie", + "Top": "Najczęściej oglądane", "About": "Informacje", "Rating: ": "Ocena: ", "Language: ": "Język: ", diff --git a/locales/ru.json b/locales/ru.json index c0d31313..a2ad7333 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,295 +1,297 @@ { - "`x` subscribers": "`x` подписчиков", - "`x` videos": "`x` видео", - "LIVE": "ПРЯМОЙ ЭФИР", - "Shared `x` ago": "Опубликовано `x` назад", - "Unsubscribe": "Отписаться", - "Subscribe": "Подписаться", - "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", - "View channel on YouTube": "Канал на YouTube", - "newest": "новые", - "oldest": "старые", - "popular": "популярные", - "last": "недавно обновленные", - "Next page": "Следующая страница", - "Previous page": "Предыдущая страница", - "Clear watch history?": "Очистить историю просмотров?", - "Yes": "Да", - "No": "Нет", - "Import and Export Data": "Импорт и экспорт данных", - "Import": "Импорт", - "Import Invidious data": "Импортировать данные Invidious", - "Import YouTube subscriptions": "Импортировать YouTube подписки", - "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", - "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", - "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", - "Export": "Экспорт", - "Export subscriptions as OPML": "Экспортировать подписки в OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", - "Export data as JSON": "Экспортировать данные в JSON", - "Delete account?": "Удалить аккаунт?", - "History": "История", - "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", - "JavaScript license information": "Лицензии JavaScript", - "source": "источник", - "Login": "Войти", - "Login/Register": "Войти/Регистрация", - "Login to Google": "Войти через Google", - "User ID:": "ID пользователя:", - "Password:": "Пароль:", - "Time (h:mm:ss):": "Время (ч:мм:сс):", - "Text CAPTCHA": "Текст капчи", - "Image CAPTCHA": "Изображение капчи", - "Sign In": "Войти", - "Register": "Регистрация", - "Email:": "Эл. почта:", - "Google verification code:": "Код подтверждения Google:", - "Preferences": "Настройки", - "Player preferences": "Настройки проигрывателя", - "Always loop: ": "Всегда повторять: ", - "Autoplay: ": "Автовоспроизведение: ", - "Autoplay next video: ": "Автовоспроизведение следующего видео: ", - "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", - "Proxy videos? ": "Проксировать видео? ", - "Default speed: ": "Скорость по-умолчанию: ", - "Preferred video quality: ": "Предпочтительное качество видео: ", - "Player volume: ": "Громкость воспроизведения: ", - "Default comments: ": "Источник комментариев: ", - "youtube": "YouTube", - "reddit": "Reddit", - "Default captions: ": "Субтитры по-умолчанию: ", - "Fallback captions: ": "Резервные субтитры: ", - "Show related videos? ": "Показывать похожие видео? ", - "Visual preferences": "Визуальные настройки", - "Dark mode: ": "Темная тема: ", - "Thin mode: ": "Облегченный режим: ", - "Subscription preferences": "Настройки подписок", - "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", - "Number of videos shown in feed: ": "Число видео в ленте: ", - "Sort videos by: ": "Сортировать видео по: ", - "published": "дате публикации", - "published - reverse": "дате - обратный порядок", - "alphabetically": "алфавиту", - "alphabetically - reverse": "алфавиту - обратный порядок", - "channel name": "имени канала", - "channel name - reverse": "имени канала - обратный порядок", - "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", - "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", - "Only show unwatched: ": "Отображать только непросмотренные видео: ", - "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", - "Data preferences": "Настройки данных", - "Clear watch history": "Очистить историю просмотра", - "Import/Export data": "Импорт/Экспорт данных", - "Manage subscriptions": "Управление подписками", - "Watch history": "История просмотров", - "Delete account": "Удалить аккаунт", - "Administrator preferences": "Настройки администратора", - "Default homepage: ": "Главная страница по умолчанию: ", - "Feed menu: ": "Меню ленты: ", - "Top enabled? ": "Включить ТОП? ", - "CAPTCHA enabled? ": "Включить капчу? ", - "Login enabled? ": "Включить логин? ", - "Registration enabled? ": "Включить регистрацию? ", - "Report statistics? ": "Отображать статистику? ", - "Save preferences": "Сохранить настройки", - "Subscription manager": "Менеджер подписок", - "`x` subscriptions": "`x` подписок", - "Import/Export": "Импорт/Экспорт", - "unsubscribe": "отписаться", - "Subscriptions": "Подписки", - "`x` unseen notifications": "`x` новых оповещений", - "search": "поиск", - "Sign out": "Выйти", - "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", - "Source available here.": "Исходный код доступен здесь.", - "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", - "View privacy policy.": "См. политику конфиденциальности.", - "Trending": "В тренде", - "Watch video on Youtube": "Смотреть на YouTube", - "Genre: ": "Жанр: ", - "License: ": "Лицензия: ", - "Family friendly? ": "Семейный просмотр: ", - "Wilson score: ": "Рейтинг Вильсона: ", - "Engagement: ": "Вовлеченность: ", - "Whitelisted regions: ": "Доступно для: ", - "Blacklisted regions: ": "Недоступно для: ", - "Shared `x`": "Опубликовано `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", - "View YouTube comments": "Смотреть комментарии с YouTube", - "View more comments on Reddit": "Больше комментариев на Reddit", - "View `x` comments": "Показать `x` комментариев", - "View Reddit comments": "Смотреть комментарии с Reddit", - "Hide replies": "Скрыть ответы", - "Show replies": "Показать ответы", - "Incorrect password": "Неправильный пароль", - "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", - "Invalid TFA code": "Неправильный TFA код", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", - "Invalid answer": "Неверный ответ", - "Invalid CAPTCHA": "Неверная капча", - "CAPTCHA is a required field": "Необходимо ввести капчу", - "User ID is a required field": "Необходимо ввести идентификатор пользователя", - "Password is a required field": "Необходимо ввести пароль", - "Invalid username or password": "Недопустимый пароль или имя пользователя", - "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", - "Password cannot be empty": "Пароль не может быть пустым", - "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", - "Please sign in": "Пожалуйста, войдите", - "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", - "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удален или не найден", - "This channel does not exist.": "Такой канал не существует.", - "Could not get channel info.": "Невозможно получить информацию о канале.", - "Could not fetch comments": "Невозможно получить комментарии", - "View `x` replies": "Показать `x` ответов", - "`x` ago": "`x` назад", - "Load more": "Загрузить больше", - "`x` points": "`x` очков", - "Could not create mix.": "Невозможно создать \"микс\".", - "Playlist is empty": "Плейлист пуст", - "Invalid playlist.": "Некорректный плейлист.", - "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", - "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", - "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", - "Invalid challenge": "Неправильный ответ в \"challenge\"", - "Invalid token": "Неправильный токен", - "Invalid user": "Недопустимое имя пользователя", - "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", - "English": "Английский", - "English (auto-generated)": "Английский (созданы автоматически)", - "Afrikaans": "Африкаанс", - "Albanian": "Албанский", - "Amharic": "Амхарский", - "Arabic": "Арабский", - "Armenian": "Армянский", - "Azerbaijani": "Азербайджанский", - "Bangla": "Бенгальский", - "Basque": "Баскский", - "Belarusian": "Белорусский", - "Bosnian": "Боснийский", - "Bulgarian": "Болгарский", - "Burmese": "Бирманский", - "Catalan": "Каталонский", - "Cebuano": "Себуанский", - "Chinese (Simplified)": "Китайский (упрощенный)", - "Chinese (Traditional)": "Китайский (традиционный)", - "Corsican": "Корсиканский", - "Croatian": "Хорватский", - "Czech": "Чешский", - "Danish": "Датский", - "Dutch": "Нидерландский", - "Esperanto": "Эсперанто", - "Estonian": "Эстонский", - "Filipino": "Филиппинский", - "Finnish": "Финский", - "French": "Французский", - "Galician": "Галисийский", - "Georgian": "Грузинский", - "German": "Немецкий", - "Greek": "Греческий", - "Gujarati": "Гуджаратский", - "Haitian Creole": "Гаит. креольский", - "Hausa": "Хауса", - "Hawaiian": "Гавайский", - "Hebrew": "Иврит", - "Hindi": "Хинди", - "Hmong": "Хмонг (мяо)", - "Hungarian": "Венгерский", - "Icelandic": "Исландский", - "Igbo": "Игбо", - "Indonesian": "Индонезийский", - "Irish": "Ирландский", - "Italian": "Итальянский", - "Japanese": "Японский", - "Javanese": "Яванский", - "Kannada": "Каннада", - "Kazakh": "Казахский", - "Khmer": "Кхмерский", - "Korean": "Корейский", - "Kurdish": "Курдский", - "Kyrgyz": "Киргизский", - "Lao": "Лаосский", - "Latin": "Латинский", - "Latvian": "Латышский", - "Lithuanian": "Литовский", - "Luxembourgish": "Люксембургский", - "Macedonian": "Македонский", - "Malagasy": "Малагасийский", - "Malay": "Малайский", - "Malayalam": "Малаялам", - "Maltese": "Мальтийский", - "Maori": "Маори", - "Marathi": "Маратхи", - "Mongolian": "Монгольская", - "Nepali": "Непальский", - "Norwegian": "Норвежский", - "Nyanja": "Ньянджа", - "Pashto": "Пушту", - "Persian": "Персидский", - "Polish": "Польский", - "Portuguese": "Португальский", - "Punjabi": "Панджаби", - "Romanian": "Румынский", - "Russian": "Русский", - "Samoan": "Самоанский", - "Scottish Gaelic": "Шотландский (гэльский)", - "Serbian": "Сербский", - "Shona": "Шона", - "Sindhi": "Синдхи", - "Sinhala": "Сингальский", - "Slovak": "Словацкий", - "Slovenian": "Словенский", - "Somali": "Сомалийский", - "Southern Sotho": "Сесото (южный сото)", - "Spanish": "Испанский", - "Spanish (Latin America)": "Испанский (Латинская Америка)", - "Sundanese": "Сунданский", - "Swahili": "Суахили", - "Swedish": "Шведский", - "Tajik": "Таджикский", - "Tamil": "Тамильский", - "Telugu": "Телугу", - "Thai": "Тайский", - "Turkish": "Турецкий", - "Ukrainian": "Украинский", - "Urdu": "Урду", - "Uzbek": "Узбекский", - "Vietnamese": "Вьетнамский", - "Welsh": "Валлийский", - "Western Frisian": "Западнофризский", - "Xhosa": "Коса", - "Yiddish": "Идиш", - "Yoruba": "Йоруба", - "Zulu": "Зулусский", - "`x` years": "`x` лет", - "`x` months": "`x` месяцев", - "`x` weeks": "`x` недель", - "`x` days": "`x` дней", - "`x` hours": "`x` часов", - "`x` minutes": "`x` минут", - "`x` seconds": "`x` секунд", - "Fallback comments: ": "Резервные комментарии: ", - "Popular": "Популярное", - "Top": "Топ", - "About": "О сайте", - "Rating: ": "Рейтинг: ", - "Language: ": "Язык: ", - "Default": "По-умолчанию", - "Music": "Музыка", - "Gaming": "Игры", - "News": "Новости", - "Movies": "Фильмы", - "Download": "Скачать", - "Download as: ": "Скачать как: ", - "%A %B %-d, %Y": "%-d %B %Y, %A", - "(edited)": "(изменено)", - "Youtube permalink of the comment": "Прямая ссылка на YouTube", - "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", - "Audio mode": "Аудио режим", - "Video mode": "Видео режим", - "Videos": "Видео", - "Playlists": "Плейлисты", - "Current version: ": "Текущая версия: " + "`x` subscribers": "`x` подписчиков", + "`x` videos": "`x` видео", + "LIVE": "ПРЯМОЙ ЭФИР", + "Shared `x` ago": "Опубликовано `x` назад", + "Unsubscribe": "Отписаться", + "Subscribe": "Подписаться", + "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", + "View channel on YouTube": "Канал на YouTube", + "newest": "новые", + "oldest": "старые", + "popular": "популярные", + "last": "недавно обновленные", + "Next page": "Следующая страница", + "Previous page": "Предыдущая страница", + "Clear watch history?": "Очистить историю просмотров?", + "Yes": "Да", + "No": "Нет", + "Import and Export Data": "Импорт и экспорт данных", + "Import": "Импорт", + "Import Invidious data": "Импортировать данные Invidious", + "Import YouTube subscriptions": "Импортировать YouTube подписки", + "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", + "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", + "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", + "Export": "Экспорт", + "Export subscriptions as OPML": "Экспортировать подписки в OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", + "Export data as JSON": "Экспортировать данные в JSON", + "Delete account?": "Удалить аккаунт?", + "History": "История", + "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", + "JavaScript license information": "Лицензии JavaScript", + "source": "источник", + "Login": "Войти", + "Login/Register": "Войти/Регистрация", + "Login to Google": "Войти через Google", + "User ID:": "ID пользователя:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Время (ч:мм:сс):", + "Text CAPTCHA": "Текст капчи", + "Image CAPTCHA": "Изображение капчи", + "Sign In": "Войти", + "Register": "Регистрация", + "Email:": "Эл. почта:", + "Google verification code:": "Код подтверждения Google:", + "Preferences": "Настройки", + "Player preferences": "Настройки проигрывателя", + "Always loop: ": "Всегда повторять: ", + "Autoplay: ": "Автовоспроизведение: ", + "Autoplay next video: ": "Автовоспроизведение следующего видео: ", + "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", + "Proxy videos? ": "Проксировать видео? ", + "Default speed: ": "Скорость по-умолчанию: ", + "Preferred video quality: ": "Предпочтительное качество видео: ", + "Player volume: ": "Громкость воспроизведения: ", + "Default comments: ": "Источник комментариев: ", + "youtube": "YouTube", + "reddit": "Reddit", + "Default captions: ": "Субтитры по-умолчанию: ", + "Fallback captions: ": "Резервные субтитры: ", + "Show related videos? ": "Показывать похожие видео? ", + "Visual preferences": "Визуальные настройки", + "Dark mode: ": "Темная тема: ", + "Thin mode: ": "Облегченный режим: ", + "Subscription preferences": "Настройки подписок", + "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", + "Number of videos shown in feed: ": "Число видео в ленте: ", + "Sort videos by: ": "Сортировать видео по: ", + "published": "дате публикации", + "published - reverse": "дате - обратный порядок", + "alphabetically": "алфавиту", + "alphabetically - reverse": "алфавиту - обратный порядок", + "channel name": "имени канала", + "channel name - reverse": "имени канала - обратный порядок", + "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", + "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", + "Only show unwatched: ": "Отображать только непросмотренные видео: ", + "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", + "Data preferences": "Настройки данных", + "Clear watch history": "Очистить историю просмотра", + "Import/Export data": "Импорт/Экспорт данных", + "Manage subscriptions": "Управление подписками", + "Watch history": "История просмотров", + "Delete account": "Удалить аккаунт", + "Administrator preferences": "Настройки администратора", + "Default homepage: ": "Главная страница по умолчанию: ", + "Feed menu: ": "Меню ленты: ", + "Top enabled? ": "Включить ТОП? ", + "CAPTCHA enabled? ": "Включить капчу? ", + "Login enabled? ": "Включить логин? ", + "Registration enabled? ": "Включить регистрацию? ", + "Report statistics? ": "Отображать статистику? ", + "Save preferences": "Сохранить настройки", + "Subscription manager": "Менеджер подписок", + "`x` subscriptions": "`x` подписок", + "Import/Export": "Импорт/Экспорт", + "unsubscribe": "отписаться", + "Subscriptions": "Подписки", + "`x` unseen notifications": "`x` новых оповещений", + "search": "поиск", + "Sign out": "Выйти", + "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", + "Source available here.": "Исходный код доступен здесь.", + "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", + "View privacy policy.": "См. политику конфиденциальности.", + "Trending": "В тренде", + "Unlisted": "", + "Watch video on Youtube": "Смотреть на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Лицензия: ", + "Family friendly? ": "Семейный просмотр: ", + "Wilson score: ": "Рейтинг Вильсона: ", + "Engagement: ": "Вовлеченность: ", + "Whitelisted regions: ": "Доступно для: ", + "Blacklisted regions: ": "Недоступно для: ", + "Shared `x`": "Опубликовано `x`", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", + "View YouTube comments": "Смотреть комментарии с YouTube", + "View more comments on Reddit": "Больше комментариев на Reddit", + "View `x` comments": "Показать `x` комментариев", + "View Reddit comments": "Смотреть комментарии с Reddit", + "Hide replies": "Скрыть ответы", + "Show replies": "Показать ответы", + "Incorrect password": "Неправильный пароль", + "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", + "Invalid TFA code": "Неправильный TFA код", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", + "Invalid answer": "Неверный ответ", + "Invalid CAPTCHA": "Неверная капча", + "CAPTCHA is a required field": "Необходимо ввести капчу", + "User ID is a required field": "Необходимо ввести идентификатор пользователя", + "Password is a required field": "Необходимо ввести пароль", + "Invalid username or password": "Недопустимый пароль или имя пользователя", + "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", + "Password cannot be empty": "Пароль не может быть пустым", + "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", + "Please sign in": "Пожалуйста, войдите", + "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал удален или не найден", + "This channel does not exist.": "Такой канал не существует.", + "Could not get channel info.": "Невозможно получить информацию о канале.", + "Could not fetch comments": "Невозможно получить комментарии", + "View `x` replies": "Показать `x` ответов", + "`x` ago": "`x` назад", + "Load more": "Загрузить больше", + "`x` points": "`x` очков", + "Could not create mix.": "Невозможно создать \"микс\".", + "Playlist is empty": "Плейлист пуст", + "Invalid playlist.": "Некорректный плейлист.", + "Playlist does not exist.": "Плейлист не существует.", + "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", + "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", + "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", + "Invalid challenge": "Неправильный ответ в \"challenge\"", + "Invalid token": "Неправильный токен", + "Invalid user": "Недопустимое имя пользователя", + "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", + "English": "Английский", + "English (auto-generated)": "Английский (созданы автоматически)", + "Afrikaans": "Африкаанс", + "Albanian": "Албанский", + "Amharic": "Амхарский", + "Arabic": "Арабский", + "Armenian": "Армянский", + "Azerbaijani": "Азербайджанский", + "Bangla": "Бенгальский", + "Basque": "Баскский", + "Belarusian": "Белорусский", + "Bosnian": "Боснийский", + "Bulgarian": "Болгарский", + "Burmese": "Бирманский", + "Catalan": "Каталонский", + "Cebuano": "Себуанский", + "Chinese (Simplified)": "Китайский (упрощенный)", + "Chinese (Traditional)": "Китайский (традиционный)", + "Corsican": "Корсиканский", + "Croatian": "Хорватский", + "Czech": "Чешский", + "Danish": "Датский", + "Dutch": "Нидерландский", + "Esperanto": "Эсперанто", + "Estonian": "Эстонский", + "Filipino": "Филиппинский", + "Finnish": "Финский", + "French": "Французский", + "Galician": "Галисийский", + "Georgian": "Грузинский", + "German": "Немецкий", + "Greek": "Греческий", + "Gujarati": "Гуджаратский", + "Haitian Creole": "Гаит. креольский", + "Hausa": "Хауса", + "Hawaiian": "Гавайский", + "Hebrew": "Иврит", + "Hindi": "Хинди", + "Hmong": "Хмонг (мяо)", + "Hungarian": "Венгерский", + "Icelandic": "Исландский", + "Igbo": "Игбо", + "Indonesian": "Индонезийский", + "Irish": "Ирландский", + "Italian": "Итальянский", + "Japanese": "Японский", + "Javanese": "Яванский", + "Kannada": "Каннада", + "Kazakh": "Казахский", + "Khmer": "Кхмерский", + "Korean": "Корейский", + "Kurdish": "Курдский", + "Kyrgyz": "Киргизский", + "Lao": "Лаосский", + "Latin": "Латинский", + "Latvian": "Латышский", + "Lithuanian": "Литовский", + "Luxembourgish": "Люксембургский", + "Macedonian": "Македонский", + "Malagasy": "Малагасийский", + "Malay": "Малайский", + "Malayalam": "Малаялам", + "Maltese": "Мальтийский", + "Maori": "Маори", + "Marathi": "Маратхи", + "Mongolian": "Монгольская", + "Nepali": "Непальский", + "Norwegian": "Норвежский", + "Nyanja": "Ньянджа", + "Pashto": "Пушту", + "Persian": "Персидский", + "Polish": "Польский", + "Portuguese": "Португальский", + "Punjabi": "Панджаби", + "Romanian": "Румынский", + "Russian": "Русский", + "Samoan": "Самоанский", + "Scottish Gaelic": "Шотландский (гэльский)", + "Serbian": "Сербский", + "Shona": "Шона", + "Sindhi": "Синдхи", + "Sinhala": "Сингальский", + "Slovak": "Словацкий", + "Slovenian": "Словенский", + "Somali": "Сомалийский", + "Southern Sotho": "Сесото (южный сото)", + "Spanish": "Испанский", + "Spanish (Latin America)": "Испанский (Латинская Америка)", + "Sundanese": "Сунданский", + "Swahili": "Суахили", + "Swedish": "Шведский", + "Tajik": "Таджикский", + "Tamil": "Тамильский", + "Telugu": "Телугу", + "Thai": "Тайский", + "Turkish": "Турецкий", + "Ukrainian": "Украинский", + "Urdu": "Урду", + "Uzbek": "Узбекский", + "Vietnamese": "Вьетнамский", + "Welsh": "Валлийский", + "Western Frisian": "Западнофризский", + "Xhosa": "Коса", + "Yiddish": "Идиш", + "Yoruba": "Йоруба", + "Zulu": "Зулусский", + "`x` years": "`x` лет", + "`x` months": "`x` месяцев", + "`x` weeks": "`x` недель", + "`x` days": "`x` дней", + "`x` hours": "`x` часов", + "`x` minutes": "`x` минут", + "`x` seconds": "`x` секунд", + "Fallback comments: ": "Резервные комментарии: ", + "Popular": "Популярное", + "Top": "Топ", + "About": "О сайте", + "Rating: ": "Рейтинг: ", + "Language: ": "Язык: ", + "Default": "По-умолчанию", + "Music": "Музыка", + "Gaming": "Игры", + "News": "Новости", + "Movies": "Фильмы", + "Download": "Скачать", + "Download as: ": "Скачать как: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "(edited)": "(изменено)", + "Youtube permalink of the comment": "Прямая ссылка на YouTube", + "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", + "Audio mode": "Аудио режим", + "Video mode": "Видео режим", + "Videos": "Видео", + "Playlists": "Плейлисты", + "Current version: ": "Текущая версия: " } diff --git a/src/invidious.cr b/src/invidious.cr index 6377e309..5783ec79 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -242,6 +242,9 @@ get "/api/v1/comments/:id" do |env| source = env.params.query["source"]? source ||= "youtube" + thin_mode = env.params.query["thin_mode"]? + thin_mode = thin_mode == "true" + format = env.params.query["format"]? format ||= "json" @@ -249,7 +252,7 @@ get "/api/v1/comments/:id" do |env| if source == "youtube" begin - comments = fetch_youtube_comments(id, continuation, proxies, format, locale, region) + comments = fetch_youtube_comments(id, continuation, proxies, format, locale, thin_mode, region) rescue ex error_message = {"error" => ex.message}.to_json env.response.status_code = 500 @@ -1688,6 +1691,11 @@ get "/videoplayback" do |env| fvip = query_params["fvip"]? || "3" mns = query_params["mn"].split(",") + if query_params["region"]? + region = query_params["region"] + query_params.delete("region") + end + if query_params["host"]? && !query_params["host"].empty? host = "https://#{query_params["host"]}" query_params.delete("host") @@ -1704,8 +1712,6 @@ get "/videoplayback" do |env| end end - region = query_params["region"]? - response = HTTP::Client::Response.new(403) 5.times do begin @@ -1725,9 +1731,12 @@ get "/videoplayback" do |env| if response.headers["Location"]? url = URI.parse(response.headers["Location"]) + host = url.host env.response.headers["Access-Control-Allow-Origin"] = "*" url = url.full_path + url += "&host=#{host}" + if region url += "®ion=#{region}" end @@ -1744,15 +1753,30 @@ get "/videoplayback" do |env| client.get(url, headers) do |response| env.response.status_code = response.status_code - if title = env.params.query["title"]? - # https://blog.fastmail.com/2011/06/24/download-non-english-filenames/ - env.response.headers["Content-Disposition"] = "attachment; filename=\"#{URI.escape(title)}\"; filename*=UTF-8''#{URI.escape(title)}" - end - response.headers.each do |key, value| env.response.headers[key] = value end + if response.headers["Location"]? + url = URI.parse(response.headers["Location"]) + host = url.host + env.response.headers["Access-Control-Allow-Origin"] = "*" + + url = url.full_path + url += "&host=#{host}" + + if region + url += "®ion=#{region}" + end + + next env.redirect url + end + + if title = query_params["title"]? + # https://blog.fastmail.com/2011/06/24/download-non-english-filenames/ + env.response.headers["Content-Disposition"] = "attachment; filename=\"#{URI.escape(title)}\"; filename*=UTF-8''#{URI.escape(title)}" + end + env.response.headers["Access-Control-Allow-Origin"] = "*" begin diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index 126cc2b8..b24159db 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -1,4 +1,4 @@ -class InvidiousChannel +struct InvidiousChannel add_mapping({ id: String, author: String, @@ -8,7 +8,7 @@ class InvidiousChannel }) end -class ChannelVideo +struct ChannelVideo add_mapping({ id: String, title: String, diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 98a497a6..1ba1bd40 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -29,7 +29,7 @@ class RedditComment }) end -class RedditLink +struct RedditLink JSON.mapping({ author: String, score: Int32, @@ -41,7 +41,7 @@ class RedditLink }) end -class RedditMore +struct RedditMore JSON.mapping({ children: Array(String), count: Int32, @@ -56,7 +56,7 @@ class RedditListing }) end -def fetch_youtube_comments(id, continuation, proxies, format, locale, region) +def fetch_youtube_comments(id, continuation, proxies, format, locale, thin_mode, region) video = fetch_video(id, proxies, region: region) session_token = video.info["session_token"]? @@ -232,7 +232,7 @@ def fetch_youtube_comments(id, continuation, proxies, format, locale, region) if format == "html" comments = JSON.parse(comments) - content_html = template_youtube_comments(comments, locale) + content_html = template_youtube_comments(comments, locale, thin_mode) comments = JSON.build do |json| json.object do @@ -278,7 +278,7 @@ def fetch_reddit_comments(id) return comments, thread end -def template_youtube_comments(comments, locale) +def template_youtube_comments(comments, locale, thin_mode) html = "" root = comments["comments"].as_a @@ -297,7 +297,11 @@ def template_youtube_comments(comments, locale) END_HTML end - author_thumbnail = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).full_path}" + if !thin_mode + author_thumbnail = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).full_path}" + else + author_thumbnail = "" + end html += <<-END_HTML
@@ -318,7 +322,12 @@ def template_youtube_comments(comments, locale) END_HTML if child["creatorHeart"]? - creator_thumbnail = "/ggpht#{URI.parse(child["creatorHeart"]["creatorThumbnail"].as_s).full_path}" + if !thin_mode + creator_thumbnail = "/ggpht#{URI.parse(child["creatorHeart"]["creatorThumbnail"].as_s).full_path}" + else + creator_thumbnail = "" + end + html += <<-END_HTML
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 10ea7dcb..ad744c74 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -1,4 +1,4 @@ -class Config +struct Config YAML.mapping({ channel_threads: Int32, # Number of threads to use for crawling videos from channels (for updating subscriptions) feed_threads: Int32, # Number of threads to use for updating feeds diff --git a/src/invidious/helpers/macros.cr b/src/invidious/helpers/macros.cr index 977b1fbc..5991faaf 100644 --- a/src/invidious/helpers/macros.cr +++ b/src/invidious/helpers/macros.cr @@ -9,6 +9,17 @@ macro add_mapping(mapping) DB.mapping({{mapping}}) end +macro json_mapping(mapping) + def initialize({{*mapping.keys.map { |id| "@#{id}".id }}}) + end + + def to_a + return [{{*mapping.keys.map { |id| "@#{id}".id }}}] + end + + JSON.mapping({{mapping}}) +end + macro templated(filename, template = "template") render "src/invidious/views/#{{{filename}}}.ecr", "src/invidious/views/#{{{template}}}.ecr" end diff --git a/src/invidious/jobs.cr b/src/invidious/jobs.cr index 54749f87..a217c1af 100644 --- a/src/invidious/jobs.cr +++ b/src/invidious/jobs.cr @@ -106,7 +106,7 @@ def subscribe_to_feeds(db, logger, key, config) if config.use_pubsub_feeds spawn do loop do - db.query_all("SELECT id FROM channels WHERE CURRENT_TIMESTAMP - subscribed > '4 days'") do |rs| + db.query_all("SELECT id FROM channels WHERE CURRENT_TIMESTAMP - subscribed > '4 days' OR subscribed IS NULL") do |rs| rs.each do ucid = rs.read(String) response = subscribe_pubsub(ucid, key, config) diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index 011c5722..d290ac14 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -1,4 +1,4 @@ -class MixVideo +struct MixVideo add_mapping({ title: String, id: String, @@ -10,7 +10,7 @@ class MixVideo }) end -class Mix +struct Mix add_mapping({ title: String, id: String, diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 88308686..027bcae5 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -1,4 +1,4 @@ -class PlaylistVideo +struct PlaylistVideo add_mapping({ title: String, id: String, @@ -12,7 +12,7 @@ class PlaylistVideo }) end -class Playlist +struct Playlist add_mapping({ title: String, id: String, diff --git a/src/invidious/search.cr b/src/invidious/search.cr index 6805f119..04eb63a2 100644 --- a/src/invidious/search.cr +++ b/src/invidious/search.cr @@ -1,4 +1,4 @@ -class SearchVideo +struct SearchVideo add_mapping({ title: String, id: String, @@ -16,7 +16,7 @@ class SearchVideo }) end -class SearchPlaylistVideo +struct SearchPlaylistVideo add_mapping({ title: String, id: String, @@ -24,7 +24,7 @@ class SearchPlaylistVideo }) end -class SearchPlaylist +struct SearchPlaylist add_mapping({ title: String, id: String, @@ -36,7 +36,7 @@ class SearchPlaylist }) end -class SearchChannel +struct SearchChannel add_mapping({ author: String, ucid: String, diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 747b72d8..9140378e 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -1,12 +1,12 @@ require "crypto/bcrypt/password" -class User +struct User module PreferencesConverter def self.from_rs(rs) begin Preferences.from_json(rs.read(String)) rescue ex - DEFAULT_USER_PREFERENCES + Preferences.from_json("{}") end end end @@ -18,7 +18,6 @@ class User email: String, preferences: { type: Preferences, - default: DEFAULT_USER_PREFERENCES, converter: PreferencesConverter, }, password: String?, @@ -27,30 +26,30 @@ class User }) end -DEFAULT_USER_PREFERENCES = Preferences.from_json({ - "video_loop" => false, - "autoplay" => false, - "continue" => false, - "local" => false, - "listen" => false, - "speed" => 1.0, - "quality" => "hd720", - "volume" => 100, - "comments" => ["youtube", ""], - "captions" => ["", "", ""], - "related_videos" => true, - "redirect_feed" => false, - "locale" => "en-US", - "dark_mode" => false, - "thin_mode" => false, - "max_results" => 40, - "sort" => "published", - "latest_only" => false, - "unseen_only" => false, - "notifications_only" => false, -}.to_json) +DEFAULT_USER_PREFERENCES = Preferences.new( + video_loop: false, + autoplay: false, + continue: false, + local: false, + listen: false, + speed: 1.0_f32, + quality: "hd720", + volume: 100, + comments: ["youtube", ""], + captions: ["", "", ""], + related_videos: true, + redirect_feed: false, + locale: "en-US", + dark_mode: false, + thin_mode: false, + max_results: 40, + sort: "published", + latest_only: false, + unseen_only: false, + notifications_only: false, +) -class Preferences +struct Preferences module StringToArray def self.to_json(value : Array(String), json : JSON::Builder) json.array do @@ -74,58 +73,27 @@ class Preferences end end - JSON.mapping({ - video_loop: Bool, - autoplay: Bool, - continue: { - type: Bool, - default: DEFAULT_USER_PREFERENCES.continue, - }, - local: { - type: Bool, - default: DEFAULT_USER_PREFERENCES.local, - }, - listen: { - type: Bool, - default: DEFAULT_USER_PREFERENCES.listen, - }, - speed: Float32, - quality: String, - volume: Int32, - comments: { - type: Array(String), - default: DEFAULT_USER_PREFERENCES.comments, - converter: StringToArray, - }, - captions: { - type: Array(String), - default: DEFAULT_USER_PREFERENCES.captions, - }, - redirect_feed: { - type: Bool, - default: DEFAULT_USER_PREFERENCES.redirect_feed, - }, - related_videos: { - type: Bool, - default: DEFAULT_USER_PREFERENCES.related_videos, - }, - dark_mode: Bool, - thin_mode: { - type: Bool, - default: DEFAULT_USER_PREFERENCES.thin_mode, - }, - max_results: Int32, - sort: String, - latest_only: Bool, - unseen_only: Bool, - notifications_only: { - type: Bool, - default: DEFAULT_USER_PREFERENCES.notifications_only, - }, - locale: { - type: String, - default: DEFAULT_USER_PREFERENCES.locale, - }, + json_mapping({ + video_loop: {type: Bool, default: DEFAULT_USER_PREFERENCES.video_loop}, + autoplay: {type: Bool, default: DEFAULT_USER_PREFERENCES.autoplay}, + continue: {type: Bool, default: DEFAULT_USER_PREFERENCES.continue}, + local: {type: Bool, default: DEFAULT_USER_PREFERENCES.local}, + listen: {type: Bool, default: DEFAULT_USER_PREFERENCES.listen}, + speed: {type: Float32, default: DEFAULT_USER_PREFERENCES.speed}, + quality: {type: String, default: DEFAULT_USER_PREFERENCES.quality}, + volume: {type: Int32, default: DEFAULT_USER_PREFERENCES.volume}, + comments: {type: Array(String), default: DEFAULT_USER_PREFERENCES.comments, converter: StringToArray}, + captions: {type: Array(String), default: DEFAULT_USER_PREFERENCES.captions, converter: StringToArray}, + redirect_feed: {type: Bool, default: DEFAULT_USER_PREFERENCES.redirect_feed}, + related_videos: {type: Bool, default: DEFAULT_USER_PREFERENCES.related_videos}, + dark_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.dark_mode}, + thin_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.thin_mode}, + max_results: {type: Int32, default: DEFAULT_USER_PREFERENCES.max_results}, + sort: {type: String, default: DEFAULT_USER_PREFERENCES.sort}, + latest_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.latest_only}, + unseen_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.unseen_only}, + notifications_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.notifications_only}, + locale: {type: String, default: DEFAULT_USER_PREFERENCES.locale}, }) end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index e9b190dd..9d6d9ecd 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -241,7 +241,7 @@ VIDEO_FORMATS = { "251" => {"ext" => "webm", "format" => "DASH audio", "acodec" => "opus", "abr" => 160}, } -class Video +struct Video property player_json : JSON::Any? module HTTPParamConverter @@ -251,7 +251,7 @@ class Video end def allow_ratings - allow_ratings = player_response["videoDetails"].try &.["allowRatings"]?.try &.as_bool + allow_ratings = player_response["videoDetails"]?.try &.["allowRatings"]?.try &.as_bool if allow_ratings.nil? return true @@ -271,7 +271,7 @@ class Video end def is_listed - is_listed = player_response["videoDetails"].try &.["isCrawlable"]?.try &.as_bool + is_listed = player_response["videoDetails"]?.try &.["isCrawlable"]?.try &.as_bool if is_listed.nil? return true @@ -281,7 +281,7 @@ class Video end def is_upcoming - is_upcoming = player_response["videoDetails"].try &.["isUpcoming"]?.try &.as_bool + is_upcoming = player_response["videoDetails"]?.try &.["isUpcoming"]?.try &.as_bool if is_upcoming.nil? return false @@ -297,7 +297,7 @@ class Video .try &.["liveStreamabilityRenderer"]? .try &.["offlineSlate"]? .try &.["liveStreamOfflineSlateRenderer"]? - .try &.["scheduledStartTime"].as_s.to_i64 + .try &.["scheduledStartTime"]?.try &.as_s.to_i64 end if premiere_timestamp @@ -549,7 +549,7 @@ class Video }) end -class Caption +struct Caption JSON.mapping( name: CaptionName, baseUrl: String, @@ -557,7 +557,7 @@ class Caption ) end -class CaptionName +struct CaptionName JSON.mapping( simpleText: String, )