diff options
| -rw-r--r-- | assets/css/default.css | 2 | ||||
| -rw-r--r-- | locales/hi.json | 474 | ||||
| -rw-r--r-- | locales/pt-BR.json | 17 | ||||
| -rw-r--r-- | locales/ru.json | 8 | ||||
| -rw-r--r-- | src/invidious/channels/about.cr | 7 | ||||
| -rw-r--r-- | src/invidious/comments.cr | 8 | ||||
| -rw-r--r-- | src/invidious/helpers/errors.cr | 2 | ||||
| -rw-r--r-- | src/invidious/helpers/i18n.cr | 1 | ||||
| -rw-r--r-- | src/invidious/helpers/serialized_yt_data.cr | 7 | ||||
| -rw-r--r-- | src/invidious/routes/feeds.cr | 1 | ||||
| -rw-r--r-- | src/invidious/videos.cr | 19 | ||||
| -rw-r--r-- | src/invidious/views/channel.ecr | 2 | ||||
| -rw-r--r-- | src/invidious/views/community.ecr | 2 | ||||
| -rw-r--r-- | src/invidious/views/components/item.ecr | 6 | ||||
| -rw-r--r-- | src/invidious/views/playlists.ecr | 2 | ||||
| -rw-r--r-- | src/invidious/views/watch.ecr | 6 | ||||
| -rw-r--r-- | src/invidious/yt_backend/extractors.cr | 48 |
17 files changed, 574 insertions, 38 deletions
diff --git a/assets/css/default.css b/assets/css/default.css index 49069c92..61b7819f 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -291,7 +291,7 @@ input[type="search"]::-webkit-search-cancel-button { .flexible { display: flex; } .flex-left { flex: 1 1 100%; flex-wrap: wrap; } -.flex-right { flex: 1 0 max-content; flex-wrap: nowrap; } +.flex-right { flex: 1 0 auto; flex-wrap: nowrap; } p.channel-name { margin: 0; } p.video-data { margin: 0; font-weight: bold; font-size: 80%; } diff --git a/locales/hi.json b/locales/hi.json new file mode 100644 index 00000000..0fc35b25 --- /dev/null +++ b/locales/hi.json @@ -0,0 +1,474 @@ +{ + "last": "आखिरी", + "Yes": "हाँ", + "No": "नहीं", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML के रूप में सदस्यताएँ निर्यात करें (NewPipe और FreeTube के लिए)", + "Log in/register": "लॉग-इन/पंजीकृत करें", + "Log in with Google": "Google के साथ लॉग-इन करें", + "preferences_autoplay_label": "अपने आप चलाने की सुविधा: ", + "preferences_dark_mode_label": "थीम: ", + "preferences_default_home_label": "डिफ़ॉल्ट मुखपृष्ठ: ", + "Could not fetch comments": "टिप्पणियाँ प्राप्त न की जा सकीं", + "comments_points_count": "{{count}} पॉइंट", + "comments_points_count_plural": "{{count}} पॉइंट्स", + "Subscription manager": "सदस्यता प्रबंधन", + "License: ": "लाइसेंस: ", + "Wilson score: ": "Wilson स्कोर: ", + "Wrong answer": "गलत जवाब", + "Erroneous CAPTCHA": "गलत CAPTCHA", + "Please log in": "कृपया लॉग-इन करें", + "Bosnian": "बोस्नियाई", + "Bulgarian": "बुल्गारियाई", + "Burmese": "बर्मी", + "Chinese (Traditional)": "चीनी (पारंपरिक)", + "Kurdish": "कुर्द", + "Punjabi": "पंजाबी", + "Sinhala": "सिंहली", + "Slovak": "स्लोवाक", + "generic_count_days": "{{count}} दिन", + "generic_count_days_plural": "{{count}} दिन", + "generic_count_hours": "{{count}} घंटे", + "generic_count_hours_plural": "{{count}} घंटे", + "generic_count_minutes": "{{count}} मिनट", + "generic_count_minutes_plural": "{{count}} मिनट", + "generic_count_seconds": "{{count}} सेकंड", + "generic_count_seconds_plural": "{{count}} सेकंड", + "generic_playlists_count": "{{count}} प्लेलिस्ट", + "generic_playlists_count_plural": "{{count}} प्लेलिस्ट्स", + "crash_page_report_issue": "अगर इनमें से कुछ भी काम नहीं करता, कृपया <a href=\"`x`\">GitHub पर एक नया मुद्दा खोल दें</a> (अंग्रेज़ी में) और अपने संदेश में यह टेक्स्ट दर्ज करें (इसे अनुवादित न करें!):", + "generic_views_count": "{{count}} बार देखा गया", + "generic_views_count_plural": "{{count}} बार देखा गया", + "generic_videos_count": "{{count}} वीडियो", + "generic_videos_count_plural": "{{count}} वीडियो", + "generic_subscribers_count": "{{count}} सदस्य", + "generic_subscribers_count_plural": "{{count}} सदस्य", + "generic_subscriptions_count": "{{count}} सदस्यता", + "generic_subscriptions_count_plural": "{{count}} सदस्यताएँ", + "LIVE": "लाइव", + "Shared `x` ago": "`x` पहले बाँटा गया", + "Unsubscribe": "सदस्यता छोड़ें", + "Subscribe": "सदस्यता लें", + "View channel on YouTube": "चैनल YouTube पर देखें", + "View playlist on YouTube": "प्लेलिस्ट YouTube पर देखें", + "newest": "सबसे नया", + "oldest": "सबसे पुराना", + "popular": "सर्वाधिक लोकप्रिय", + "Next page": "अगला पृष्ठ", + "Previous page": "पिछला पृष्ठ", + "Clear watch history?": "देखने का इतिहास मिटाएँ?", + "New password": "नया पासवर्ड", + "New passwords must match": "पासवर्ड्स को मेल खाना होगा", + "Cannot change password for Google accounts": "Google खातों के लिए पासवर्ड नहीं बदल सकते", + "Authorize token?": "टोकन को प्रमाणित करें?", + "Authorize token for `x`?": "`x` के लिए टोकन को प्रमाणित करें?", + "Import and Export Data": "डेटा को आयात और निर्यात करें", + "Import": "आयात करें", + "Import Invidious data": "Invidious JSON डेटा आयात करें", + "Import YouTube subscriptions": "YouTube/OPML सदस्यताएँ आयात करें", + "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 data as JSON": "Invidious डेटा को JSON के रूप में निर्यात करें", + "Delete account?": "खाता हटाएँ?", + "History": "देखे गए वीडियो", + "An alternative front-end to YouTube": "YouTube का एक वैकल्पिक फ्रंट-एंड", + "JavaScript license information": "जावास्क्रिप्ट लाइसेंस की जानकारी", + "source": "स्रोत", + "Log in": "लॉग-इन करें", + "User ID": "सदस्य ID", + "Password": "पासवर्ड", + "Register": "पंजीकृत करें", + "E-mail": "ईमेल", + "Google verification code": "Google प्रमाणीकरण कोड", + "Time (h:mm:ss):": "समय (घं:मिमि:सेसे):", + "Text CAPTCHA": "टेक्स्ट CAPTCHA", + "Image CAPTCHA": "चित्र CAPTCHA", + "Sign In": "साइन इन करें", + "Preferences": "प्राथमिकताएँ", + "preferences_category_player": "प्लेयर की प्राथमिकताएँ", + "preferences_video_loop_label": "हमेशा लूप करें: ", + "preferences_continue_label": "डिफ़ॉल्ट से अगला चलाएँ: ", + "preferences_continue_autoplay_label": "अगला वीडियो अपने आप चलाएँ: ", + "preferences_listen_label": "डिफ़ॉल्ट से सुनें: ", + "preferences_local_label": "प्रॉक्सी वीडियो: ", + "preferences_watch_history_label": "देखने का इतिहास सक्षम करें: ", + "preferences_speed_label": "वीडियो चलाने की डिफ़ॉल्ट रफ़्तार: ", + "preferences_quality_label": "वीडियो की प्राथमिक क्वालिटी: ", + "preferences_quality_option_dash": "DASH (अनुकूली गुणवत्ता)", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_medium": "मध्यम", + "preferences_quality_option_small": "छोटा", + "preferences_quality_dash_label": "प्राथमिक DASH वीडियो क्वालिटी: ", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_auto": "अपने-आप", + "preferences_quality_dash_option_best": "सबसे अच्छा", + "preferences_quality_dash_option_worst": "सबसे खराब", + "preferences_quality_dash_option_4320p": "4320p", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_480p": "480p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_144p": "144p", + "preferences_comments_label": "डिफ़ॉल्ट टिप्पणियाँ: ", + "preferences_volume_label": "प्लेयर का वॉल्यूम: ", + "youtube": "YouTube", + "reddit": "Reddit", + "invidious": "Invidious", + "preferences_captions_label": "डिफ़ॉल्ट कैप्शन: ", + "Fallback captions: ": "वैकल्पिक कैप्शन: ", + "preferences_related_videos_label": "संबंधित वीडियो दिखाएँ: ", + "preferences_annotations_label": "डिफ़ॉल्ट से टिप्पणियाँ दिखाएँ: ", + "preferences_extend_desc_label": "अपने आप वीडियो के विवरण का विस्तार करें: ", + "preferences_vr_mode_label": "उत्तरदायी 360 डिग्री वीडियो (WebGL की ज़रूरत है): ", + "preferences_category_visual": "यथादृश्य प्राथमिकताएँ", + "preferences_region_label": "सामग्री का राष्ट्र: ", + "preferences_player_style_label": "प्लेयर का स्टाइल: ", + "Dark mode: ": "डार्क मोड: ", + "dark": "डार्क", + "light": "लाइट", + "preferences_thin_mode_label": "हल्का मोड: ", + "preferences_category_misc": "विविध प्राथमिकताएँ", + "preferences_automatic_instance_redirect_label": "अपने आप अनुप्रेषित करें (redirect.invidious.io पर फ़ॉलबैक करें): ", + "preferences_category_subscription": "सदस्यताओं की प्राथमिकताएँ", + "preferences_annotations_subscribed_label": "सदस्यता लिए गए चैनलों पर डिफ़ॉल्ट से टिप्पणियाँ दिखाएँ? ", + "Redirect homepage to feed: ": "फ़ीड पर मुखपृष्ठ को अनुप्रेषित करें: ", + "preferences_max_results_label": "फ़ीड में दिखाए जाने वाले वीडियों की संख्या: ", + "preferences_sort_label": "वीडियों को इस मानदंड पर छाँटें: ", + "published": "प्रकाशित", + "published - reverse": "प्रकाशित - उल्टा", + "Only show latest video from channel: ": "चैनल से सिर्फ नवीनतम वीडियो ही दिखाएँ: ", + "alphabetically": "वर्णक्रमानुसार", + "Only show latest unwatched video from channel: ": "चैनल से सिर्फ न देखा गया नवीनतम वीडियो ही दिखाएँ: ", + "alphabetically - reverse": "वर्णक्रमानुसार - उल्टा", + "channel name": "चैनल का नाम", + "channel name - reverse": "चैनल का नाम - उल्टा", + "preferences_unseen_only_label": "सिर्फ न देखे गए वीडियो ही दिखाएँ: ", + "preferences_notifications_only_label": "सिर्फ सूचनाएँ दिखाएँ (अगर हो तो): ", + "Enable web notifications": "वेब सूचनाएँ सक्षम करें", + "`x` uploaded a video": "`x` ने वीडियो अपलोड किया", + "`x` is live": "`x` लाइव हैं", + "preferences_category_data": "डेटा की प्राथमिकताएँ", + "Clear watch history": "देखने का इतिहास साफ़ करें", + "Import/export data": "डेटा को आयात/निर्यात करें", + "Change password": "पासवर्ड बदलें", + "Manage subscriptions": "सदस्यताएँ प्रबंधित करें", + "Manage tokens": "टोकन प्रबंधित करें", + "Watch history": "देखने का इतिहास", + "Delete account": "खाता हटाएँ", + "preferences_category_admin": "प्रबंधक प्राथमिकताएँ", + "preferences_feed_menu_label": "फ़ीड मेन्यू: ", + "preferences_show_nick_label": "ऊपर उपनाम दिखाएँ: ", + "Top enabled: ": "ऊपर का हिस्सा सक्षम है: ", + "CAPTCHA enabled: ": "CAPTCHA सक्षम है: ", + "Login enabled: ": "लॉग-इन सक्षम है: ", + "Registration enabled: ": "पंजीकरण सक्षम है: ", + "Report statistics: ": "सांख्यिकी रिपोर्ट करें: ", + "Released under the AGPLv3 on Github.": "GitHub पर AGPLv3 के अंतर्गत प्रकाशित।", + "Save preferences": "प्राथमिकताएँ सहेजें", + "Token manager": "टोकन प्रबंधन", + "Token": "टोकन", + "tokens_count": "{{count}} टोकन", + "tokens_count_plural": "{{count}} टोकन", + "Import/export": "आयात/निर्यात करें", + "unsubscribe": "सदस्यता छोड़ें", + "revoke": "हटाएँ", + "Subscriptions": "सदस्यताएँ", + "subscriptions_unseen_notifs_count": "{{count}} अपठित सूचना", + "subscriptions_unseen_notifs_count_plural": "{{count}} अपठित सूचना", + "search": "खोजें", + "Log out": "लॉग-आउट करें", + "Source available here.": "स्रोत यहाँ उपलब्ध है।", + "View JavaScript license information.": "जावास्क्रिप्ट लाइसेंस की जानकारी देखें।", + "View privacy policy.": "निजता नीति देखें।", + "Trending": "रुझान में", + "Public": "सार्वजनिक", + "Unlisted": "सबके लिए उपलब्ध नहीं", + "Private": "निजी", + "View all playlists": "सभी प्लेलिस्ट देखें", + "Create playlist": "प्लेलिस्ट बनाएँ", + "Updated `x` ago": "`x` पहले अपडेट किया गया", + "Delete playlist `x`?": "प्लेलिस्ट `x` हटाएँ?", + "Delete playlist": "प्लेलिस्ट हटाएँ", + "Title": "शीर्षक", + "Playlist privacy": "प्लेलिस्ट की निजता", + "Editing playlist `x`": "प्लेलिस्ट `x` को संपादित किया जा रहा है", + "Show more": "अधिक देखें", + "Show less": "कम देखें", + "Watch on YouTube": "YouTube पर देखें", + "Switch Invidious Instance": "Invidious उदाहरण बदलें", + "search_message_no_results": "कोई परिणाम नहीं मिला।", + "search_message_change_filters_or_query": "अपने खोज क्वेरी को और चौड़ा करें और/या फ़िल्टर बदलें।", + "search_message_use_another_instance": " आप <a href=\"`x`\">दूसरे उदाहरण पर भी खोज सकते हैं</a>।", + "Hide annotations": "टिप्पणियाँ छिपाएँ", + "Show annotations": "टिप्पणियाँ दिखाएँ", + "Genre: ": "श्रेणी: ", + "Family friendly? ": "परिवार के लिए ठीक है? ", + "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.": "नमस्कार! ऐसा लगता है कि आपका जावास्क्रिप्ट अक्षम है। टिप्पणियाँ देखने के लिए यहाँ क्लिक करें, लेकिन याद रखें कि इन्हें लोड होने में थोड़ा ज़्यादा समय लग सकता है।", + "View YouTube comments": "YouTube टिप्पणियाँ देखें", + "View more comments on Reddit": "Reddit पर अधिक टिप्पणियाँ देखें", + "View `x` comments": { + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` टिप्पणी देखें", + "": "`x` टिप्पणियाँ देखें" + }, + "View Reddit comments": "Reddit पर टिप्पणियाँ", + "Hide replies": "जवाब छिपाएँ", + "Show replies": "जवाब दिखाएँ", + "Incorrect password": "गलत पासवर्ड", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "लॉग-इन नहीं किया जा सका, सुनिश्चित करें कि दो-कारक प्रमाणीकरण (Authenticator या SMS) सक्षम है।", + "Invalid TFA code": "अमान्य TFA कोड", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "लॉग-इन नाकाम रहा। ऐसा इसलिए हो सकता है कि दो-कारक प्रमाणीकरण आपके खाते पर सक्षम नहीं है।", + "Quota exceeded, try again in a few hours": "कोटा पार हो चुका है, कृपया कुछ घंटों में फिर कोशिश करें", + "CAPTCHA is a required field": "CAPTCHA एक ज़रूरी फ़ील्ड है", + "User ID is a required field": "सदस्य ID एक ज़रूरी फ़ील्ड है", + "Password is a required field": "पासवर्ड एक ज़रूरी फ़ील्ड है", + "Wrong username or password": "गलत सदस्यनाम या पासवर्ड", + "Please sign in using 'Log in with Google'": "कृपया 'Google के साथ लॉग-इन करें' के साथ साइन-इन करें", + "Password cannot be empty": "पासवर्ड खाली नहीं हो सकता", + "Password cannot be longer than 55 characters": "पासवर्ड में अधिकतम 55 अक्षर हो सकते हैं", + "Invidious Private Feed for `x`": "`x` के लिए Invidious निजी फ़ीड", + "channel:`x`": "चैनल:`x`", + "Deleted or invalid channel": "हटाया गया या अमान्य चैनल", + "This channel does not exist.": "यह चैनल मौजूद नहीं है।", + "Could not get channel info.": "चैनल की जानकारी प्राप्त न की जा सकी।", + "comments_view_x_replies": "{{count}} टिप्पणी देखें", + "comments_view_x_replies_plural": "{{count}} टिप्पणियाँ देखें", + "`x` ago": "`x` पहले", + "Load more": "अधिक लोड करें", + "Could not create mix.": "मिक्स न बनाया जा सका।", + "Empty playlist": "खाली प्लेलिस्ट", + "Not a playlist.": "यह प्लेलिस्ट नहीं है।", + "Playlist does not exist.": "प्लेलिस्ट मौजूद नहीं है।", + "Could not pull trending pages.": "रुझान के पृष्ठ प्राप्त न किए जा सके।", + "Hidden field \"challenge\" is a required field": "छिपाया गया फ़ील्ड \"चुनौती\" एक आवश्यक फ़ील्ड है", + "Hidden field \"token\" is a required field": "छिपाया गया फ़ील्ड \"टोकन\" एक आवश्यक फ़ील्ड है", + "Erroneous challenge": "त्रुटिपूर्ण चुनौती", + "Erroneous token": "त्रुटिपूर्ण टोकन", + "No such user": "यह सदस्य मौजूद नहीं हैं", + "Token is expired, please try again": "टोकन की समय-सीमा समाप्त हो चुकी है, कृपया दोबारा कोशिश करें", + "English": "अंग्रेज़ी", + "English (United Kingdom)": "अंग्रेज़ी (यूनाइटेड किंग्डम)", + "English (United States)": "अंग्रेज़ी (संयुक्त राष्ट्र)", + "English (auto-generated)": "अंग्रेज़ी (अपने-आप जनरेट हुआ)", + "Afrikaans": "अफ़्रीकी", + "Albanian": "अल्बानियाई", + "Amharic": "अम्हेरी", + "Arabic": "अरबी", + "Armenian": "आर्मेनियाई", + "Belarusian": "बेलारूसी", + "Azerbaijani": "अज़रबैजानी", + "Bangla": "बंगाली", + "Basque": "बास्क", + "Cantonese (Hong Kong)": "कैंटोनीज़ (हाँग काँग)", + "Catalan": "कातालान", + "Cebuano": "सेबुआनो", + "Chinese": "चीनी", + "Chinese (China)": "चीनी (चीन)", + "Chinese (Hong Kong)": "चीनी (हाँग काँग)", + "Chinese (Simplified)": "चीनी (सरलीकृत)", + "Chinese (Taiwan)": "चीनी (ताइवान)", + "Corsican": "कोर्सिकन", + "Croatian": "क्रोएशियाई", + "Czech": "चेक", + "Danish": "डेनिश", + "Dutch": "डच", + "Dutch (auto-generated)": "डच (अपने-आप जनरेट हुआ)", + "Esperanto": "एस्पेरांतो", + "Estonian": "एस्टोनियाई", + "Filipino": "फ़िलिपीनो", + "Finnish": "फ़िनिश", + "French": "फ़्रेंच", + "French (auto-generated)": "फ़्रेंच (अपने-आप जनरेट हुआ)", + "Galician": "गैलिशियन", + "Georgian": "जॉर्जियाई", + "German": "जर्मन", + "German (auto-generated)": "जर्मन (अपने-आप जनरेट हुआ)", + "Greek": "यूनानी", + "Gujarati": "गुजराती", + "Haitian Creole": "हैती क्रियोल", + "Hausa": "हौसा", + "Hawaiian": "हवाई", + "Hebrew": "हीब्रू", + "Hindi": "हिन्दी", + "Hmong": "हमोंग", + "Hungarian": "हंगेरी", + "Icelandic": "आइसलैंडिक", + "Igbo": "इग्बो", + "Indonesian": "इंडोनेशियाई", + "Indonesian (auto-generated)": "इंडोनेशियाई (अपने-आप जनरेट हुआ)", + "Interlingue": "इंटरलिंगुआ", + "Irish": "आयरिश", + "Italian": "इतालवी", + "Italian (auto-generated)": "इतालवी (अपने-आप जनरेट हुआ)", + "Japanese": "जापानी", + "Japanese (auto-generated)": "जापानी (अपने-आप जनरेट हुआ)", + "Javanese": "जावानीज़", + "Kannada": "कन्नड़", + "Kazakh": "कज़ाख़", + "Khmer": "खमेर", + "Korean": "कोरियाई", + "Korean (auto-generated)": "कोरियाई (अपने-आप जनरेट हुआ)", + "Kyrgyz": "किर्गीज़", + "Lao": "लाओ", + "Latin": "लैटिन", + "Latvian": "लातवियाई", + "Lithuanian": "लिथुएनियाई", + "Luxembourgish": "लग्ज़मबर्गी", + "Macedonian": "मकादूनियाई", + "Malagasy": "मालागासी", + "Malay": "मलय", + "Malayalam": "मलयालम", + "Maltese": "माल्टीज़", + "Maori": "माओरी", + "Marathi": "मराठी", + "Mongolian": "मंगोलियाई", + "Nepali": "नेपाली", + "Norwegian Bokmål": "नॉर्वेजियाई", + "Nyanja": "न्यानजा", + "Pashto": "पश्तो", + "Persian": "फ़ारसी", + "Polish": "पोलिश", + "Portuguese": "पुर्तगाली", + "Portuguese (auto-generated)": "पुर्तगाली (अपने-आप जनरेट हुआ)", + "Portuguese (Brazil)": "पुर्तगाली (ब्राज़ील)", + "Romanian": "रोमेनियाई", + "Russian": "रूसी", + "Russian (auto-generated)": "रूसी (अपने-आप जनरेट हुआ)", + "Samoan": "सामोन", + "Scottish Gaelic": "स्कॉटिश गाएलिक", + "Serbian": "सर्बियाई", + "Shona": "शोणा", + "Sindhi": "सिंधी", + "Slovenian": "स्लोवेनियाई", + "Somali": "सोमाली", + "Southern Sotho": "दक्षिणी सोथो", + "Spanish": "स्पेनी", + "Spanish (auto-generated)": "स्पेनी (अपने-आप जनरेट हुआ)", + "Spanish (Latin America)": "स्पेनी (लातिन अमेरिकी)", + "Spanish (Mexico)": "स्पेनी (मेक्सिको)", + "Spanish (Spain)": "स्पेनी (स्पेन)", + "Sundanese": "सुंडानी", + "Swahili": "स्वाहिली", + "Swedish": "स्वीडिश", + "Tajik": "ताजीक", + "Tamil": "तमिल", + "Telugu": "तेलुगु", + "Thai": "थाई", + "Turkish": "तुर्की", + "Turkish (auto-generated)": "तुर्की (अपने-आप जनरेट हुआ)", + "Ukrainian": "यूक्रेनी", + "Urdu": "उर्दू", + "Uzbek": "उज़्बेक", + "Vietnamese": "वियतनामी", + "Vietnamese (auto-generated)": "वियतनामी (अपने-आप जनरेट हुआ)", + "Welsh": "Welsh", + "Western Frisian": "पश्चिमी फ़्रिसियाई", + "Xhosa": "खोसा", + "Yiddish": "यहूदी", + "generic_count_years": "{{count}} वर्ष", + "generic_count_years_plural": "{{count}} वर्ष", + "Yoruba": "योरुबा", + "generic_count_months": "{{count}} महीने", + "generic_count_months_plural": "{{count}} महीने", + "Zulu": "ज़ूलू", + "generic_count_weeks": "{{count}} हफ़्ते", + "generic_count_weeks_plural": "{{count}} हफ़्ते", + "Fallback comments: ": "फ़ॉलबैक टिप्पणियाँ: ", + "Popular": "प्रसिद्ध", + "Search": "खोजें", + "Top": "ऊपर", + "About": "जानकारी", + "Rating: ": "रेटिंग: ", + "preferences_locale_label": "भाषा: ", + "View as playlist": "प्लेलिस्ट के रूप में देखें", + "Default": "डिफ़ॉल्ट", + "Download": "डाउनलोड करें", + "Download as: ": "इस रूप में डाउनलोड करें: ", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "Music": "संगीत", + "Gaming": "गेमिंग", + "News": "समाचार", + "Movies": "फ़िल्में", + "(edited)": "(संपादित)", + "YouTube comment permalink": "YouTube पर टिप्पणी की स्थायी कड़ी", + "permalink": "स्थायी कड़ी", + "Videos": "वीडियो", + "`x` marked it with a ❤": "`x` ने इसे एक ❤ से चिह्नित किया", + "Audio mode": "ऑडियो मोड", + "Playlists": "प्लेलिस्ट्स", + "Video mode": "वीडियो मोड", + "Community": "समुदाय", + "search_filters_title": "फ़िल्टर", + "search_filters_date_label": "अपलोड करने का समय", + "search_filters_date_option_none": "कोई भी समय", + "search_filters_date_option_week": "इस हफ़्ते", + "search_filters_date_option_month": "इस महीने", + "search_filters_date_option_hour": "पिछला घंटा", + "search_filters_date_option_today": "आज", + "search_filters_date_option_year": "इस साल", + "search_filters_type_label": "प्रकार", + "search_filters_type_option_all": "कोई भी प्रकार", + "search_filters_type_option_video": "वीडियो", + "search_filters_type_option_channel": "चैनल", + "search_filters_sort_option_relevance": "प्रासंगिकता", + "search_filters_type_option_playlist": "प्लेलिस्ट", + "search_filters_type_option_movie": "फ़िल्म", + "search_filters_type_option_show": "शो", + "search_filters_duration_label": "अवधि", + "search_filters_duration_option_none": "कोई भी अवधि", + "search_filters_duration_option_short": "4 मिनट से कम", + "search_filters_duration_option_medium": "4 से 20 मिनट तक", + "search_filters_duration_option_long": "20 मिनट से ज़्यादा", + "search_filters_features_label": "सुविधाएँ", + "search_filters_features_option_live": "लाइव", + "search_filters_sort_option_rating": "रेटिंग", + "search_filters_features_option_four_k": "4K", + "search_filters_features_option_hd": "HD", + "search_filters_features_option_subtitles": "उपशीर्षक/कैप्शन", + "search_filters_features_option_c_commons": "क्रिएटिव कॉमन्स", + "search_filters_features_option_three_sixty": "360°", + "search_filters_features_option_vr180": "VR180", + "search_filters_features_option_three_d": "3D", + "search_filters_features_option_hdr": "HDR", + "search_filters_features_option_location": "जगह", + "search_filters_features_option_purchased": "खरीदा गया", + "search_filters_sort_label": "इस क्रम से लगाएँ", + "search_filters_sort_option_date": "अपलोड की ताऱीख", + "search_filters_sort_option_views": "देखे जाने की संख्या", + "search_filters_apply_button": "चयनित फ़िल्टर लागू करें", + "footer_documentation": "प्रलेख", + "footer_source_code": "स्रोत कोड", + "footer_original_source_code": "मूल स्रोत कोड", + "footer_modfied_source_code": "बदला गया स्रोत कोड", + "Current version: ": "वर्तमान संस्करण: ", + "next_steps_error_message": "इसके बाद आपके ये आज़माने चाहिए: ", + "next_steps_error_message_refresh": "साफ़ करें", + "next_steps_error_message_go_to_youtube": "YouTube पर जाएँ", + "footer_donate_page": "दान करें", + "adminprefs_modified_source_code_url_label": "बदले गए स्रोत कोड के रिपॉज़िटरी का URL", + "none": "कुछ नहीं", + "videoinfo_started_streaming_x_ago": "`x` पहले स्ट्रीम करना शुरू किया", + "videoinfo_watch_on_youTube": "YouTube पर देखें", + "Video unavailable": "वीडियो उपलब्ध नहीं है", + "preferences_save_player_pos_label": "यहाँ से चलाना शुरू करें: ", + "crash_page_you_found_a_bug": "शायद आपको Invidious में कोई बग नज़र आ गया है!", + "videoinfo_youTube_embed_link": "एम्बेड करें", + "videoinfo_invidious_embed_link": "एम्बोड करने की कड़ी", + "download_subtitles": "उपशीर्षक - `x` (.vtt)", + "user_created_playlists": "बनाए गए `x` प्लेलिस्ट्स", + "user_saved_playlists": "सहेजे गए `x` प्लेलिस्ट्स", + "crash_page_before_reporting": "बग रिपोर्ट करने से पहले:", + "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> ढूँढ़ें" +} diff --git a/locales/pt-BR.json b/locales/pt-BR.json index f3a05a1a..2ee65272 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -21,15 +21,15 @@ "No": "Não", "Import and Export Data": "Importar e Exportar Dados", "Import": "Importar", - "Import Invidious data": "Importar dados do Invidious", - "Import YouTube subscriptions": "Importar inscrições do YouTube", + "Import Invidious data": "Importar dados em JSON do Invidious", + "Import YouTube subscriptions": "Importar inscrições do YouTube/OPML", "Import FreeTube subscriptions (.db)": "Importar inscrições do FreeTube (.db)", "Import NewPipe subscriptions (.json)": "Importar inscrições do NewPipe (.json)", "Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)", "Export": "Exportar", "Export subscriptions as OPML": "Exportar inscrições como OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar inscrições como OPML (para NewPipe e FreeTube)", - "Export data as JSON": "Exportar dados como JSON", + "Export data as JSON": "Exportar dados Invidious como JSON", "Delete account?": "Excluir conta?", "History": "Histórico", "An alternative front-end to YouTube": "Uma interface alternativa para o YouTube", @@ -66,7 +66,7 @@ "preferences_related_videos_label": "Mostrar vídeos relacionados: ", "preferences_annotations_label": "Sempre mostrar anotações: ", "preferences_extend_desc_label": "Estenda automaticamente a descrição do vídeo: ", - "preferences_vr_mode_label": "Vídeos interativos de 360 graus: ", + "preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ", "preferences_category_visual": "Preferências visuais", "preferences_player_style_label": "Estilo do tocador: ", "Dark mode: ": "Modo escuro: ", @@ -410,7 +410,7 @@ "crash_page_read_the_faq": "leu as <a href=\"`x`\">Perguntas Frequentes (FAQ)</a>", "generic_views_count": "{{count}} visualização", "generic_views_count_plural": "{{count}} visualizações", - "preferences_quality_option_dash": "DASH (qualidade adaptiva)", + "preferences_quality_option_dash": "DASH (qualidade adaptável)", "preferences_quality_option_hd720": "HD720", "preferences_quality_option_small": "Pequeno", "preferences_quality_dash_option_auto": "Auto", @@ -436,5 +436,10 @@ "user_saved_playlists": "`x` listas de reprodução salvas", "Video unavailable": "Vídeo indisponível", "videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`", - "search_filters_title": "Filtro" + "search_filters_title": "Filtro", + "preferences_watch_history_label": "Ative o histórico de exibição: ", + "search_message_no_results": "Nenhum resultado encontrado.", + "search_message_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.", + "English (United Kingdom)": "Inglês (Reino Unido)", + "English (United States)": "Inglês (Estados Unidos)" } diff --git a/locales/ru.json b/locales/ru.json index a10bb050..0199f61f 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -5,8 +5,8 @@ "Subscribe": "Подписаться", "View channel on YouTube": "Смотреть канал на YouTube", "View playlist on YouTube": "Посмотреть плейлист на YouTube", - "newest": "самые свежие", - "oldest": "самые старые", + "newest": "сначала новые", + "oldest": "сначала старые", "popular": "популярные", "last": "недавние", "Next page": "Следующая страница", @@ -74,8 +74,8 @@ "dark": "темная", "light": "светлая", "preferences_thin_mode_label": "Облегчённое оформление: ", - "preferences_category_misc": "Прочие предпочтения", - "preferences_automatic_instance_redirect_label": "Автоматическое перенаправление на зеркало сайта (резервный вариант redirect.invidious.io): ", + "preferences_category_misc": "Прочие настройки", + "preferences_automatic_instance_redirect_label": "Автоматическое перенаправление на зеркало сайта (переход на redirect.invidious.io): ", "preferences_category_subscription": "Настройки подписок", "preferences_annotations_subscribed_label": "Всегда показывать аннотации в видео каналов, на которые вы подписаны? ", "Redirect homepage to feed: ": "Отображать видео с каналов, на которые вы подписаны, как главную страницу: ", diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr index 4f82a0f1..d48fd1fb 100644 --- a/src/invidious/channels/about.cr +++ b/src/invidious/channels/about.cr @@ -12,7 +12,8 @@ record AboutChannel, joined : Time, is_family_friendly : Bool, allowed_regions : Array(String), - tabs : Array(String) + tabs : Array(String), + verified : Bool record AboutRelatedChannel, ucid : String, @@ -70,6 +71,9 @@ def get_about_info(ucid, locale) : AboutChannel # if banner.includes? "channels/c4/default_banner" # banner = nil # end + # author_verified_badges = initdata["header"]?.try &.["c4TabbedHeaderRenderer"]?.try &.["badges"]? + author_verified_badge = initdata["header"].dig?("c4TabbedHeaderRenderer", "badges", 0, "metadataBadgeRenderer", "tooltip") + author_verified = (author_verified_badge && author_verified_badge == "Verified") description = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?.try &.as_s? || "" description_html = HTML.escape(description) @@ -128,6 +132,7 @@ def get_about_info(ucid, locale) : AboutChannel is_family_friendly: is_family_friendly, allowed_regions: allowed_regions, tabs: tabs, + verified: author_verified || false, ) end diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 91ea8607..1f8de657 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -146,6 +146,8 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b content_html = node_comment["contentText"]?.try { |t| parse_content(t, id) } || "" author = node_comment["authorText"]?.try &.["simpleText"]? || "" + json.field "verified", (node_comment["authorCommentBadge"]? != nil) + json.field "author", author json.field "authorThumbnails" do json.array do @@ -329,7 +331,11 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) end author_name = HTML.escape(child["author"].as_s) - + if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool + author_name += " <i class=\"icon ion ion-md-checkmark-circle\"></i>" + elsif child["verified"]?.try &.as_bool + author_name += " <i class=\"icon ion ion-md-checkmark\"></i>" + end html << <<-END_HTML <div class="pure-g" style="width:100%"> <div class="channel-profile pure-u-4-24 pure-u-md-2-24"> diff --git a/src/invidious/helpers/errors.cr b/src/invidious/helpers/errors.cr index 2eab6263..b80dcdaf 100644 --- a/src/invidious/helpers/errors.cr +++ b/src/invidious/helpers/errors.cr @@ -46,7 +46,7 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce TEXT - issue_template += github_details("Backtrace", HTML.escape(exception.inspect_with_backtrace)) + issue_template += github_details("Backtrace", exception.inspect_with_backtrace) # URLs for the error message below url_faq = "https://github.com/iv-org/documentation/blob/master/docs/faq.md" diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr index 982b97d8..3f987b4d 100644 --- a/src/invidious/helpers/i18n.cr +++ b/src/invidious/helpers/i18n.cr @@ -14,6 +14,7 @@ LOCALES_LIST = { "fi" => "Suomi", # Finnish "fr" => "Français", # French "he" => "עברית", # Hebrew + "hi" => "हिन्दी", # Hindi "hr" => "Hrvatski", # Croatian "hu-HU" => "Magyar Nyelv", # Hungarian "id" => "Bahasa Indonesia", # Indonesian diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index bfbc237c..3918bd13 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -12,6 +12,7 @@ struct SearchVideo property live_now : Bool property premium : Bool property premiere_timestamp : Time? + property author_verified : Bool def to_xml(auto_generated, query_params, xml : XML::Builder) query_params["v"] = self.id @@ -129,6 +130,7 @@ struct SearchPlaylist property video_count : Int32 property videos : Array(SearchPlaylistVideo) property thumbnail : String? + property author_verified : Bool def to_json(locale : String?, json : JSON::Builder) json.object do @@ -141,6 +143,8 @@ struct SearchPlaylist json.field "authorId", self.ucid json.field "authorUrl", "/channel/#{self.ucid}" + json.field "authorVerified", self.author_verified + json.field "videoCount", self.video_count json.field "videos" do json.array do @@ -182,6 +186,7 @@ struct SearchChannel property video_count : Int32 property description_html : String property auto_generated : Bool + property author_verified : Bool def to_json(locale : String?, json : JSON::Builder) json.object do @@ -189,7 +194,7 @@ struct SearchChannel json.field "author", self.author json.field "authorId", self.ucid json.field "authorUrl", "/channel/#{self.ucid}" - + json.field "authorVerified", self.author_verified json.field "authorThumbnails" do json.array do qualities = {32, 48, 76, 100, 176, 512} diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr index f7f7b426..b5b58399 100644 --- a/src/invidious/routes/feeds.cr +++ b/src/invidious/routes/feeds.cr @@ -182,6 +182,7 @@ module Invidious::Routes::Feeds paid: false, premium: false, premiere_timestamp: nil, + author_verified: false, # ¯\_(ツ)_/¯ }) end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index c007a07b..f65b05bb 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -413,6 +413,10 @@ struct Video end end + # Livestream chunk infos + json.field "targetDurationSec", fmt["targetDurationSec"].as_i if fmt.has_key?("targetDurationSec") + json.field "maxDvrDurationSec", fmt["maxDvrDurationSec"].as_i if fmt.has_key?("maxDvrDurationSec") + # Audio-related data json.field "audioQuality", fmt["audioQuality"] if fmt.has_key?("audioQuality") json.field "audioSampleRate", fmt["audioSampleRate"].as_s.to_i if fmt.has_key?("audioSampleRate") @@ -609,6 +613,10 @@ struct Video info["authorThumbnail"]?.try &.as_s || "" end + def author_verified : Bool + info["authorVerified"]?.try &.as_bool || false + end + def sub_count_text : String info["subCountText"]?.try &.as_s || "-" end @@ -860,6 +868,12 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)? .try &.dig?("runs", 0) author = channel_info.try &.dig?("text") + author_verified_badge = related["ownerBadges"]?.try do |badges_array| + badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified") + end + + author_verified = (author_verified_badge && author_verified_badge.size > 0).to_s + ucid = channel_info.try { |ci| HelperExtractors.get_browse_id(ci) } # "4,088,033 views", only available on compact renderer @@ -883,6 +897,7 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)? "length_seconds" => JSON::Any.new(length || "0"), "view_count" => JSON::Any.new(view_count || "0"), "short_view_count" => JSON::Any.new(short_view_count || "0"), + "author_verified" => JSON::Any.new(author_verified), } end @@ -1077,6 +1092,10 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_ author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer") author_thumbnail = author_info.try &.dig?("thumbnail", "thumbnails", 0, "url") + author_verified_badge = author_info.try &.dig?("badges", 0, "metadataBadgeRenderer", "tooltip") + author_verified = (!author_verified_badge.nil? && author_verified_badge == "Verified") + params["authorVerified"] = JSON::Any.new(author_verified) + params["authorThumbnail"] = JSON::Any.new(author_thumbnail.try &.as_s || "") params["subCountText"] = JSON::Any.new(author_info.try &.["subscriberCountText"]? diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr index 40b553a9..92f81ee4 100644 --- a/src/invidious/views/channel.ecr +++ b/src/invidious/views/channel.ecr @@ -20,7 +20,7 @@ <div class="pure-u-2-3"> <div class="channel-profile"> <img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>"> - <span><%= author %></span> + <span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %> </div> </div> <div class="pure-u-1-3"> diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr index f0add06b..3bc29e55 100644 --- a/src/invidious/views/community.ecr +++ b/src/invidious/views/community.ecr @@ -19,7 +19,7 @@ <div class="pure-u-2-3"> <div class="channel-profile"> <img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>"> - <span><%= author %></span> + <span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %> </div> </div> <div class="pure-u-1-3" style="text-align:right"> diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index ce7af783..fb7ad1dc 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -8,7 +8,7 @@ <img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>"/> </center> <% end %> - <p dir="auto"><%= HTML.escape(item.author) %></p> + <p dir="auto"><%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></p> </a> <p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p> <% if !item.auto_generated %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %> @@ -30,7 +30,7 @@ <p dir="auto"><%= HTML.escape(item.title) %></p> </a> <a href="/channel/<%= item.ucid %>"> - <p dir="auto"><b><%= HTML.escape(item.author) %></b></p> + <p dir="auto"><b><%= HTML.escape(item.author) %><% if !item.is_a?(InvidiousPlaylist) && !item.author_verified.nil? && item.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></b></p> </a> <% when MixVideo %> <a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>"> @@ -142,7 +142,7 @@ <div class="video-card-row flexible"> <div class="flex-left"><a href="/channel/<%= item.ucid %>"> - <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %></p> + <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %><% if !item.is_a?(ChannelVideo) && !item.author_verified.nil? && item.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></p> </a></div> <% endpoint_params = "?v=#{item.id}" %> diff --git a/src/invidious/views/playlists.ecr b/src/invidious/views/playlists.ecr index 12dba088..c8718e7b 100644 --- a/src/invidious/views/playlists.ecr +++ b/src/invidious/views/playlists.ecr @@ -19,7 +19,7 @@ <div class="pure-u-2-3"> <div class="channel-profile"> <img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>"> - <span><%= author %></span> + <span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %> </div> </div> <div class="pure-u-1-3" style="text-align:right"> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 2e493f4c..8b6eb903 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -207,7 +207,7 @@ we're going to need to do it here in order to allow for translations. <% if !video.author_thumbnail.empty? %> <img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>"> <% end %> - <span id="channel-name"><%= author %></span> + <span id="channel-name"><%= author %><% if !video.author_verified.nil? && video.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></span> </div> </a> @@ -281,9 +281,9 @@ we're going to need to do it here in order to allow for translations. <h5 class="pure-g"> <div class="pure-u-14-24"> <% if rv["ucid"]? %> - <b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %></a></b> + <b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b> <% else %> - <b style="width:100%"><%= rv["author"]? %></b> + <b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></b> <% end %> </div> diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index f6229a9b..a2ec7d59 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -102,7 +102,11 @@ private module Parsers premium = false premiere_timestamp = item_contents.dig?("upcomingEventData", "startTime").try { |t| Time.unix(t.as_s.to_i64) } + author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array| + badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified") + end + author_verified = (author_verified_badge && author_verified_badge.size > 0) item_contents["badges"]?.try &.as_a.each do |badge| b = badge["metadataBadgeRenderer"] case b["label"].as_s @@ -129,6 +133,7 @@ private module Parsers live_now: live_now, premium: premium, premiere_timestamp: premiere_timestamp, + author_verified: author_verified || false, }) end @@ -156,7 +161,11 @@ private module Parsers private def self.parse(item_contents, author_fallback) author = extract_text(item_contents["title"]) || author_fallback.name author_id = item_contents["channelId"]?.try &.as_s || author_fallback.id + author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array| + badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified") + end + author_verified = (author_verified_badge && author_verified_badge.size > 0) author_thumbnail = HelperExtractors.get_thumbnails(item_contents) # When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube. # Always simpleText @@ -179,6 +188,7 @@ private module Parsers video_count: video_count, description_html: description_html, auto_generated: auto_generated, + author_verified: author_verified || false, }) end @@ -206,18 +216,23 @@ private module Parsers private def self.parse(item_contents, author_fallback) title = extract_text(item_contents["title"]) || "" plid = item_contents["playlistId"]?.try &.as_s || "" + author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array| + badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified") + end + author_verified = (author_verified_badge && author_verified_badge.size > 0) video_count = HelperExtractors.get_video_count(item_contents) playlist_thumbnail = HelperExtractors.get_thumbnails(item_contents) SearchPlaylist.new({ - title: title, - id: plid, - author: author_fallback.name, - ucid: author_fallback.id, - video_count: video_count, - videos: [] of SearchPlaylistVideo, - thumbnail: playlist_thumbnail, + title: title, + id: plid, + author: author_fallback.name, + ucid: author_fallback.id, + video_count: video_count, + videos: [] of SearchPlaylistVideo, + thumbnail: playlist_thumbnail, + author_verified: author_verified || false, }) end @@ -251,7 +266,11 @@ private module Parsers author_info = item_contents.dig?("shortBylineText", "runs", 0) author = author_info.try &.["text"].as_s || author_fallback.name author_id = author_info.try { |x| HelperExtractors.get_browse_id(x) } || author_fallback.id + author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array| + badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified") + end + author_verified = (author_verified_badge && author_verified_badge.size > 0) videos = item_contents["videos"]?.try &.as_a.map do |v| v = v["childVideoRenderer"] v_title = v.dig?("title", "simpleText").try &.as_s || "" @@ -267,13 +286,14 @@ private module Parsers # TODO: item_contents["publishedTimeText"]? SearchPlaylist.new({ - title: title, - id: plid, - author: author, - ucid: author_id, - video_count: video_count, - videos: videos, - thumbnail: playlist_thumbnail, + title: title, + id: plid, + author: author, + ucid: author_id, + video_count: video_count, + videos: videos, + thumbnail: playlist_thumbnail, + author_verified: author_verified || false, }) end |
