diff --git a/locales/ar.json b/locales/ar.json index b2339cf0..adf85080 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -82,6 +82,13 @@ "Manage subscriptions": "إدارة المشتركين", "Watch history": "سجل المشاهدة", "Delete account": "حذف الحساب", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "حفظ التفضيلات", "Subscription manager": "مدير الإشتراكات", "`x` subscriptions": "`x` مشتركين", diff --git a/locales/de.json b/locales/de.json index d4ab6a52..1e2647e3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -82,6 +82,13 @@ "Manage subscriptions": "Abonnements verwalten", "Watch history": "Verlauf", "Delete account": "Account löschen", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Einstellungen speichern", "Subscription manager": "Abonnementverwaltung", "`x` subscriptions": "`x` Abonnements", diff --git a/locales/en-US.json b/locales/en-US.json index b985908e..d460238e 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -80,6 +80,13 @@ "Manage subscriptions": "Manage subscriptions", "Watch history": "Watch history", "Delete account": "Delete account", + "Administrator preferences": "Administrator preferences", + "Default homepage: ": "Default homepage: ", + "Feed menu: ": "Feed menu: ", + "Top enabled? ": "Top enabled? ", + "CAPTCHA enabled? ": "CAPTCHA enabled? ", + "Login enabled? ": "Login enabled? ", + "Registration enabled? ": "Registration enabled? ", "Save preferences": "Save preferences", "Subscription manager": "Subscription manager", "`x` subscriptions": "`x` subscriptions", diff --git a/locales/eu.json b/locales/eu.json index 4fa24bd7..67a34aa9 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -80,6 +80,13 @@ "Manage subscriptions": "", "Watch history": "", "Delete account": "", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "", "Subscription manager": "", "`x` subscriptions": "", diff --git a/locales/fr.json b/locales/fr.json index 71935f36..ec6508b9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -79,6 +79,13 @@ "Manage subscriptions": "Gérer les abonnements", "Watch history": "Historique de visionnage", "Delete account": "Supprimer votre compte", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Enregistrer les préférences", "Subscription manager": "Gestionnaire d'abonnement", "`x` subscriptions": "`x` abonnements", diff --git a/locales/it.json b/locales/it.json index 403c018c..8b47c026 100644 --- a/locales/it.json +++ b/locales/it.json @@ -79,6 +79,13 @@ "Manage subscriptions": "Gestisci le iscrizioni", "Watch history": "Cronologia dei video", "Delete account": "Elimina l'account", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Salva le preferenze", "Subscription manager": "Gestisci le iscrizioni", "`x` subscriptions": "`x` iscrizioni", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 5dbd3e8f..6a277741 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1,280 +1,287 @@ { - "`x` subscribers": "`x` abonnenter", - "`x` videos": "`x` videoer", - "LIVE": "SANNTIDSVISNING", - "Shared `x` ago": "Delt for `x` siden", - "Unsubscribe": "Opphev abonnement", - "Subscribe": "Abonner", - "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", - "View channel on YouTube": "Vis kanal på YouTube", - "newest": "nyeste", - "oldest": "eldste", - "popular": "populært", - "Preview page": "Forhåndsvis side", - "Next page": "Neste side", - "Clear watch history?": "Tøm visningshistorikk?", - "Yes": "Ja", - "No": "Nei", - "Import and Export Data": "Importer- og eksporter data", - "Import": "Importer", - "Import Invidious data": "Importer Invidious-data", - "Import YouTube subscriptions": "Importer YouTube-abonnenter", - "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", - "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", - "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", - "Export": "Eksporter", - "Export subscriptions as OPML": "Eksporter abonnenter som OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", - "Export data as JSON": "Eksporter data som JSON", - "Delete account?": "Slett konto?", - "History": "Historikk", - "Previous page": "Forrige side", - "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", - "JavaScript license information": "JavaScript-lisensinformasjon", - "source": "kilde", - "Login": "Logg inn", - "Login/Register": "Logg inn/registrer", - "Login to Google": "Logg inn med Google", - "User ID:": "Bruker-ID:", - "Password:": "Passord:", - "Time (h:mm:ss):": "Tid (h:mm:ss):", - "Text CAPTCHA": "Tekst-CAPTCHA", - "Image CAPTCHA": "Bilde-CAPTCHA", - "Sign In": "Innlogging", - "Register": "Registrer", - "Email:": "E-post:", - "Google verification code:": "Google-bekreftelseskode:", - "Preferences": "Innstillinger", - "Player preferences": "Avspillerinnstillinger", - "Always loop: ": "Alltid gjenta: ", - "Autoplay: ": "Autoavspilling: ", - "Autoplay next video: ": "Autospill neste video: ", - "Listen by default: ": "Lytt som forvalg: ", - "Default speed: ": "Forvalgt hastighet: ", - "Preferred video quality: ": "Foretrukket videokvalitet: ", - "Player volume: ": "Avspillerlydstyrke: ", - "Default comments: ": "Forvalgte kommentarer: ", - "Default captions: ": "Forvalgte undertitler: ", - "Fallback captions: ": "Tilbakefallsundertitler: ", - "Show related videos? ": "Vis relaterte videoer? ", - "Visual preferences": "Visuelle innstillinger", - "Dark mode: ": "Mørk drakt: ", - "Thin mode: ": "Tynt modus: ", - "Subscription preferences": "Abonnementsinnstillinger", - "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", - "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", - "Sort videos by: ": "Sorter videoer etter: ", - "published": "publisert", - "published - reverse": "publisert - motsatt", - "alphabetically": "alfabetisk", - "alphabetically - reverse": "alfabetisk - motsatt", - "channel name": "kanalnavn", - "channel name - reverse": "kanalnavn - motsatt", - "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", - "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", - "Only show unwatched: ": "Kun vis usette: ", - "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", - "Data preferences": "Datainnstillinger", - "Clear watch history": "Tøm visningshistorikk", - "Import/Export data": "Importer/eksporter data", - "Manage subscriptions": "Behandle abonnementer", - "Watch history": "Visningshistorikk", - "Delete account": "Slett konto", - "Save preferences": "Lagre innstillinger", - "Subscription manager": "Abonnementsbehandler", - "`x` subscriptions": "`x` abonnementer", - "Import/Export": "Importer/eksporter", - "unsubscribe": "opphev abonnement", - "Subscriptions": "Abonnement", - "`x` unseen notifications": "`x` usette merknader", - "search": "søk", - "Sign out": "Logg ut", - "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", - "Source available here.": "Kildekode tilgjengelig her.", - "View JavaScript license information.": "Vis JavaScript-lisensinfo.", - "Trending": "Trendsettende", - "Watch video on Youtube": "Vis video på YouTube", - "Genre: ": "Sjanger: ", - "License: ": "Lisens: ", - "Family friendly? ": "Familievennlig? ", - "Wilson score: ": "Wilson-poengsum: ", - "Engagement: ": "Engasjement: ", - "Whitelisted regions: ": "Hvitlistede regioner: ", - "Blacklisted regions: ": "Svartelistede regioner: ", - "Shared `x`": "Delt `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", - "View `x` comments": "Vis `x` kommentarer", - "View Reddit comments": "Vis Reddit-kommentarer", - "Hide replies": "Skjul svar", - "Show replies": "Vis svar", - "Incorrect password": "Feil passord", - "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", - "Invalid TFA code": "Ugyldig tofaktorkode", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", - "Invalid answer": "Ugyldig svar", - "Invalid CAPTCHA": "Ugyldig CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", - "User ID is a required field": "Bruker-ID er et påkrevd felt", - "Password is a required field": "Passord er et påkrevd felt", - "Invalid username or password": "Ugyldig brukernavn eller passord", - "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", - "Password cannot be empty": "Passordet kan ikke være tomt", - "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", - "Please sign in": "Logg inn", - "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", - "channel:`x`": "kanal `x`", - "Deleted or invalid channel": "Slettet eller ugyldig kanal", - "This channel does not exist.": "Denne kanalen finnes ikke.", - "Could not get channel info.": "Kunne ikke innhente kanalinfo.", - "Could not fetch comments": "Kunne ikke hente kommentarer", - "View `x` replies": "Vis `x` svar", - "`x` ago": "`x` siden", - "Load more": "Last inn flere", - "`x` points": "`x` poeng", - "Could not create mix.": "Kunne ikke opprette miks.", - "Playlist is empty": "Spillelisten er tom", - "Invalid playlist.": "Ugyldig spilleliste.", - "Playlist does not exist.": "Spillelisten finnes ikke.", - "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", - "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", - "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", - "Invalid challenge": "Ugyldig utfordring", - "Invalid token": "Ugyldig symbol", - "Invalid user": "Ugyldig bruker", - "Token is expired, please try again": "Symbol utløpt, prøv igjen", - "English": "Engelsk", - "English (auto-generated)": "Engelsk (auto-generert)", - "Afrikaans": "", - "Albanian": "Albansk", - "Amharic": "", - "Arabic": "Arabisk", - "Armenian": "Armensk", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "Hviterussisk", - "Bosnian": "Bosnisk", - "Bulgarian": "Bulgarsk", - "Burmese": "Burmesisk", - "Catalan": "Katalansk", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "Tsjekkisk", - "Danish": "Dansk", - "Dutch": "", - "Esperanto": "Esperanto", - "Estonian": "", - "Filipino": "", - "Finnish": "Finsk", - "French": "Fransk", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "Ungarsk", - "Icelandic": "Islandsk", - "Igbo": "", - "Indonesian": "Indonesisk", - "Irish": "Irsk", - "Italian": "Italiensk", - "Japanese": "Japansk", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "Norsk bokmål", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "Russisk", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "Serbisk", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "Slovakisk", - "Slovenian": "Slovensk", - "Somali": "Somali", - "Southern Sotho": "", - "Spanish": "Spansk", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "Svensk", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "Tyrkisk", - "Ukrainian": "Ukrainsk", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "Vietnamesisk", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` år", - "`x` months": "`x` måneder", - "`x` weeks": "`x` uker", - "`x` days": "`x` dager", - "`x` hours": "`x` timer", - "`x` minutes": "`x` minutter", - "`x` seconds": "`x` sekunder", - "Fallback comments: ": "Tilbakefallskommentarer: ", - "Popular": "Pupulært", - "Top": "Topp", - "About": "Om", - "Rating: ": "Vurdering: ", - "Language: ": "Språk: ", - "Default": "Forvalg", - "Music": "Musikk", - "Gaming": "Spill", - "News": "Nyheter", - "Movies": "Filmer", - "Download": "Last ned", - "Download as: ": "Last ned som: ", - "%A %B %-d, %Y": "", - "(edited)": "(redigert)", - "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", - "`x` marked it with a ❤": "`x` levnet et ❤", - "Audio mode": "Lydmodus", - "Video mode": "Video-modus" + "`x` subscribers": "`x` abonnenter", + "`x` videos": "`x` videoer", + "LIVE": "SANNTIDSVISNING", + "Shared `x` ago": "Delt for `x` siden", + "Unsubscribe": "Opphev abonnement", + "Subscribe": "Abonner", + "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", + "View channel on YouTube": "Vis kanal på YouTube", + "newest": "nyeste", + "oldest": "eldste", + "popular": "populært", + "Preview page": "Forhåndsvis side", + "Next page": "Neste side", + "Clear watch history?": "Tøm visningshistorikk?", + "Yes": "Ja", + "No": "Nei", + "Import and Export Data": "Importer- og eksporter data", + "Import": "Importer", + "Import Invidious data": "Importer Invidious-data", + "Import YouTube subscriptions": "Importer YouTube-abonnenter", + "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", + "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", + "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", + "Export": "Eksporter", + "Export subscriptions as OPML": "Eksporter abonnenter som OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", + "Export data as JSON": "Eksporter data som JSON", + "Delete account?": "Slett konto?", + "History": "Historikk", + "Previous page": "Forrige side", + "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", + "JavaScript license information": "JavaScript-lisensinformasjon", + "source": "kilde", + "Login": "Logg inn", + "Login/Register": "Logg inn/registrer", + "Login to Google": "Logg inn med Google", + "User ID:": "Bruker-ID:", + "Password:": "Passord:", + "Time (h:mm:ss):": "Tid (h:mm:ss):", + "Text CAPTCHA": "Tekst-CAPTCHA", + "Image CAPTCHA": "Bilde-CAPTCHA", + "Sign In": "Innlogging", + "Register": "Registrer", + "Email:": "E-post:", + "Google verification code:": "Google-bekreftelseskode:", + "Preferences": "Innstillinger", + "Player preferences": "Avspillerinnstillinger", + "Always loop: ": "Alltid gjenta: ", + "Autoplay: ": "Autoavspilling: ", + "Autoplay next video: ": "Autospill neste video: ", + "Listen by default: ": "Lytt som forvalg: ", + "Default speed: ": "Forvalgt hastighet: ", + "Preferred video quality: ": "Foretrukket videokvalitet: ", + "Player volume: ": "Avspillerlydstyrke: ", + "Default comments: ": "Forvalgte kommentarer: ", + "Default captions: ": "Forvalgte undertitler: ", + "Fallback captions: ": "Tilbakefallsundertitler: ", + "Show related videos? ": "Vis relaterte videoer? ", + "Visual preferences": "Visuelle innstillinger", + "Dark mode: ": "Mørk drakt: ", + "Thin mode: ": "Tynt modus: ", + "Subscription preferences": "Abonnementsinnstillinger", + "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", + "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", + "Sort videos by: ": "Sorter videoer etter: ", + "published": "publisert", + "published - reverse": "publisert - motsatt", + "alphabetically": "alfabetisk", + "alphabetically - reverse": "alfabetisk - motsatt", + "channel name": "kanalnavn", + "channel name - reverse": "kanalnavn - motsatt", + "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", + "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", + "Only show unwatched: ": "Kun vis usette: ", + "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", + "Data preferences": "Datainnstillinger", + "Clear watch history": "Tøm visningshistorikk", + "Import/Export data": "Importer/eksporter data", + "Manage subscriptions": "Behandle abonnementer", + "Watch history": "Visningshistorikk", + "Delete account": "Slett konto", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Save preferences": "Lagre innstillinger", + "Subscription manager": "Abonnementsbehandler", + "`x` subscriptions": "`x` abonnementer", + "Import/Export": "Importer/eksporter", + "unsubscribe": "opphev abonnement", + "Subscriptions": "Abonnement", + "`x` unseen notifications": "`x` usette merknader", + "search": "søk", + "Sign out": "Logg ut", + "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", + "Source available here.": "Kildekode tilgjengelig her.", + "View JavaScript license information.": "Vis JavaScript-lisensinfo.", + "Trending": "Trendsettende", + "Watch video on Youtube": "Vis video på YouTube", + "Genre: ": "Sjanger: ", + "License: ": "Lisens: ", + "Family friendly? ": "Familievennlig? ", + "Wilson score: ": "Wilson-poengsum: ", + "Engagement: ": "Engasjement: ", + "Whitelisted regions: ": "Hvitlistede regioner: ", + "Blacklisted regions: ": "Svartelistede regioner: ", + "Shared `x`": "Delt `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", + "View `x` comments": "Vis `x` kommentarer", + "View Reddit comments": "Vis Reddit-kommentarer", + "Hide replies": "Skjul svar", + "Show replies": "Vis svar", + "Incorrect password": "Feil passord", + "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", + "Invalid TFA code": "Ugyldig tofaktorkode", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", + "Invalid answer": "Ugyldig svar", + "Invalid CAPTCHA": "Ugyldig CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", + "User ID is a required field": "Bruker-ID er et påkrevd felt", + "Password is a required field": "Passord er et påkrevd felt", + "Invalid username or password": "Ugyldig brukernavn eller passord", + "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", + "Password cannot be empty": "Passordet kan ikke være tomt", + "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", + "Please sign in": "Logg inn", + "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", + "channel:`x`": "kanal `x`", + "Deleted or invalid channel": "Slettet eller ugyldig kanal", + "This channel does not exist.": "Denne kanalen finnes ikke.", + "Could not get channel info.": "Kunne ikke innhente kanalinfo.", + "Could not fetch comments": "Kunne ikke hente kommentarer", + "View `x` replies": "Vis `x` svar", + "`x` ago": "`x` siden", + "Load more": "Last inn flere", + "`x` points": "`x` poeng", + "Could not create mix.": "Kunne ikke opprette miks.", + "Playlist is empty": "Spillelisten er tom", + "Invalid playlist.": "Ugyldig spilleliste.", + "Playlist does not exist.": "Spillelisten finnes ikke.", + "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", + "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", + "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", + "Invalid challenge": "Ugyldig utfordring", + "Invalid token": "Ugyldig symbol", + "Invalid user": "Ugyldig bruker", + "Token is expired, please try again": "Symbol utløpt, prøv igjen", + "English": "Engelsk", + "English (auto-generated)": "Engelsk (auto-generert)", + "Afrikaans": "", + "Albanian": "Albansk", + "Amharic": "", + "Arabic": "Arabisk", + "Armenian": "Armensk", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "Hviterussisk", + "Bosnian": "Bosnisk", + "Bulgarian": "Bulgarsk", + "Burmese": "Burmesisk", + "Catalan": "Katalansk", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "Tsjekkisk", + "Danish": "Dansk", + "Dutch": "", + "Esperanto": "Esperanto", + "Estonian": "", + "Filipino": "", + "Finnish": "Finsk", + "French": "Fransk", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "Ungarsk", + "Icelandic": "Islandsk", + "Igbo": "", + "Indonesian": "Indonesisk", + "Irish": "Irsk", + "Italian": "Italiensk", + "Japanese": "Japansk", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "Norsk bokmål", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "Russisk", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "Serbisk", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "Slovakisk", + "Slovenian": "Slovensk", + "Somali": "Somali", + "Southern Sotho": "", + "Spanish": "Spansk", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "Svensk", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "Tyrkisk", + "Ukrainian": "Ukrainsk", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "Vietnamesisk", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` år", + "`x` months": "`x` måneder", + "`x` weeks": "`x` uker", + "`x` days": "`x` dager", + "`x` hours": "`x` timer", + "`x` minutes": "`x` minutter", + "`x` seconds": "`x` sekunder", + "Fallback comments: ": "Tilbakefallskommentarer: ", + "Popular": "Pupulært", + "Top": "Topp", + "About": "Om", + "Rating: ": "Vurdering: ", + "Language: ": "Språk: ", + "Default": "Forvalg", + "Music": "Musikk", + "Gaming": "Spill", + "News": "Nyheter", + "Movies": "Filmer", + "Download": "Last ned", + "Download as: ": "Last ned som: ", + "%A %B %-d, %Y": "", + "(edited)": "(redigert)", + "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", + "`x` marked it with a ❤": "`x` levnet et ❤", + "Audio mode": "Lydmodus", + "Video mode": "Video-modus" } diff --git a/locales/nl.json b/locales/nl.json index 1f2e3930..04937de9 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -80,6 +80,13 @@ "Manage subscriptions": "Abonnees beheren", "Watch history": "Kijkgeschiedenis", "Delete account": "Account verwijderen", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Opslaan voorkeuren", "Subscription manager": "Abonnees beheerder", "`x` subscriptions": "`x` abonnees", diff --git a/locales/pl.json b/locales/pl.json index 755d0567..8aa29343 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -80,6 +80,13 @@ "Manage subscriptions": "Organizuj subskrybcje", "Watch history": "Historia", "Delete account": "Usuń konto", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Zapisz preferencje", "Subscription manager": "Manager subskrybcji", "`x` subscriptions": "`x` subskrybcji", diff --git a/locales/ru.json b/locales/ru.json index 1495bc9c..4c958b4c 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -82,6 +82,13 @@ "Manage subscriptions": "Управление подписками", "Watch history": "История просмотров", "Delete account": "Удалить аккаунт", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", "Save preferences": "Сохранить настройки", "Subscription manager": "Менеджер подписок", "`x` subscriptions": "`x` подписок", diff --git a/src/invidious.cr b/src/invidious.cr index e6e8340b..36450676 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -31,42 +31,38 @@ require "./invidious/*" CONFIG = Config.from_yaml(File.read("config/config.yml")) HMAC_KEY = CONFIG.hmac_key || Random::Secure.random_bytes(32) -crawl_threads = CONFIG.crawl_threads -channel_threads = CONFIG.channel_threads -feed_threads = CONFIG.feed_threads -video_threads = CONFIG.video_threads - +config = CONFIG logger = Invidious::LogHandler.new Kemal.config.extra_options do |parser| parser.banner = "Usage: invidious [arguments]" - parser.on("-t THREADS", "--crawl-threads=THREADS", "Number of threads for crawling YouTube (default: #{crawl_threads})") do |number| + parser.on("-t THREADS", "--crawl-threads=THREADS", "Number of threads for crawling YouTube (default: #{config.crawl_threads})") do |number| begin - crawl_threads = number.to_i + config.crawl_threads = number.to_i rescue ex puts "THREADS must be integer" exit end end - parser.on("-c THREADS", "--channel-threads=THREADS", "Number of threads for refreshing channels (default: #{channel_threads})") do |number| + parser.on("-c THREADS", "--channel-threads=THREADS", "Number of threads for refreshing channels (default: #{config.channel_threads})") do |number| begin - channel_threads = number.to_i + config.channel_threads = number.to_i rescue ex puts "THREADS must be integer" exit end end - parser.on("-f THREADS", "--feed-threads=THREADS", "Number of threads for refreshing feeds (default: #{feed_threads})") do |number| + parser.on("-f THREADS", "--feed-threads=THREADS", "Number of threads for refreshing feeds (default: #{config.feed_threads})") do |number| begin - feed_threads = number.to_i + config.feed_threads = number.to_i rescue ex puts "THREADS must be integer" exit end end - parser.on("-v THREADS", "--video-threads=THREADS", "Number of threads for refreshing videos (default: #{video_threads})") do |number| + parser.on("-v THREADS", "--video-threads=THREADS", "Number of threads for refreshing videos (default: #{config.video_threads})") do |number| begin - video_threads = number.to_i + config.video_threads = number.to_i rescue ex puts "THREADS must be integer" exit @@ -107,28 +103,30 @@ LOCALES = { "ru" => load_locale("ru"), } -crawl_threads.times do +config.crawl_threads.times do spawn do crawl_videos(PG_DB, logger) end end -refresh_channels(PG_DB, logger, channel_threads, CONFIG.full_refresh) +refresh_channels(PG_DB, logger, config.channel_threads, config.full_refresh) -refresh_feeds(PG_DB, logger, feed_threads) +refresh_feeds(PG_DB, logger, config.feed_threads) -video_threads.times do |i| +config.video_threads.times do |i| spawn do refresh_videos(PG_DB, logger) end end top_videos = [] of Video -spawn do - pull_top_videos(CONFIG, PG_DB) do |videos| - top_videos = videos - sleep 1.minutes - Fiber.yield +if config.top_enabled + spawn do + pull_top_videos(config, PG_DB) do |videos| + top_videos = videos + sleep 1.minutes + Fiber.yield + end end end @@ -231,7 +229,20 @@ get "/" do |env| end end - templated "index" + case config.default_home + when "Popular" + templated "popular" + when "Top" + templated "top" + when "Trending" + env.redirect "/feed/trending" + when "Subscriptions" + if user + env.redirect "/feed/subscriptions" + else + templated "popular" + end + end end get "/licenses" do |env| @@ -367,7 +378,7 @@ get "/watch" do |env| video.description = replace_links(video.description) description = video.short_description - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) host_params = env.request.query_params host_params.delete_all("v") @@ -467,7 +478,7 @@ get "/embed/:id" do |env| video.description = replace_links(video.description) description = video.short_description - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) host_params = env.request.query_params host_params.delete_all("v") @@ -553,7 +564,7 @@ get "/opensearch.xml" do |env| locale = LOCALES[env.get("locale").as(String)]? env.response.content_type = "application/opensearchdescription+xml" - host = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host = make_host_url(Kemal.config.ssl || config.https_only, config.domain) XML.build(indent: " ", encoding: "UTF-8") do |xml| xml.element("OpenSearchDescription", xmlns: "http://a9.com/-/spec/opensearch/1.1/") do @@ -678,6 +689,11 @@ get "/login" do |env| next env.redirect "/feed/subscriptions" end + if !config.login_enabled + error_message = "Login has been disabled by administrator." + next templated "error" + end + referer = get_referer(env, "/feed/subscriptions") account_type = env.params.query["type"]? @@ -716,6 +732,11 @@ post "/login" do |env| referer = get_referer(env, "/feed/subscriptions") + if !config.login_enabled + error_message = "Login has been disabled by administrator." + next templated "error" + end + email = env.params.body["email"]? password = env.params.body["password"]? @@ -876,14 +897,14 @@ post "/login" do |env| host = URI.parse(env.request.headers["Host"]).host - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only secure = true else secure = false end login.cookies.each do |cookie| - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only cookie.secure = secure else cookie.secure = secure @@ -912,54 +933,56 @@ post "/login" do |env| answer = env.params.body["answer"]? text_answer = env.params.body["text_answer"]? - if answer - answer = answer.lstrip('0') - answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer) + if config.captcha_enabled + if answer + answer = answer.lstrip('0') + answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer) - challenge = env.params.body["challenge"]? - token = env.params.body["token"]? + challenge = env.params.body["challenge"]? + token = env.params.body["token"]? - begin - validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale) - rescue ex - if ex.message == translate(locale, "Invalid user") - error_message = translate(locale, "Invalid answer") - else - error_message = ex.message - end - - next templated "error" - end - elsif text_answer - text_answer = Digest::MD5.hexdigest(text_answer.downcase.strip) - - challenges = env.params.body.select { |k, v| k.match(/text_challenge\d+/) } - tokens = env.params.body.select { |k, v| k.match(/text_token\d+/) } - - found_valid_captcha = false - - error_message = translate(locale, "Invalid CAPTCHA") - challenges.each_with_index do |challenge, i| begin - challenge = challenge[1] - token = tokens[i][1] - validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB, locale) - found_valid_captcha = true + validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale) rescue ex if ex.message == translate(locale, "Invalid user") error_message = translate(locale, "Invalid answer") else error_message = ex.message end - end - end - if !found_valid_captcha + next templated "error" + end + elsif text_answer + text_answer = Digest::MD5.hexdigest(text_answer.downcase.strip) + + challenges = env.params.body.select { |k, v| k.match(/text_challenge\d+/) } + tokens = env.params.body.select { |k, v| k.match(/text_token\d+/) } + + found_valid_captcha = false + + error_message = translate(locale, "Invalid CAPTCHA") + challenges.each_with_index do |challenge, i| + begin + challenge = challenge[1] + token = tokens[i][1] + validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB, locale) + found_valid_captcha = true + rescue ex + if ex.message == translate(locale, "Invalid user") + error_message = translate(locale, "Invalid answer") + else + error_message = ex.message + end + end + end + + if !found_valid_captcha + next templated "error" + end + else + error_message = translate(locale, "CAPTCHA is a required field") next templated "error" end - else - error_message = translate(locale, "CAPTCHA is a required field") - next templated "error" end action = env.params.body["action"]? @@ -992,14 +1015,14 @@ post "/login" do |env| sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.now) - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only secure = true else secure = false end - if CONFIG.domain - env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{CONFIG.domain}", value: sid, expires: Time.now + 2.years, + if config.domain + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{config.domain}", value: sid, expires: Time.now + 2.years, secure: secure, http_only: true) else env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, @@ -1016,6 +1039,11 @@ post "/login" do |env| secure: secure, http_only: true) end elsif action == "register" + if !config.registration_enabled + error_message = "Registration has been disabled by administrator." + next templated "error" + end + if password.empty? error_message = translate(locale, "Password cannot be empty") next templated "error" @@ -1049,14 +1077,14 @@ post "/login" do |env| ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") - if Kemal.config.ssl || CONFIG.https_only + if Kemal.config.ssl || config.https_only secure = true else secure = false end - if CONFIG.domain - env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{CONFIG.domain}", value: sid, expires: Time.now + 2.years, + if config.domain + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: ".#{config.domain}", value: sid, expires: Time.now + 2.years, secure: secure, http_only: true) else env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, @@ -1153,14 +1181,15 @@ post "/preferences" do |env| volume = env.params.body["volume"]?.try &.as(String).to_i? volume ||= DEFAULT_USER_PREFERENCES.volume - comments_0 = env.params.body["comments_0"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[0] - comments_1 = env.params.body["comments_1"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[1] - comments = [comments_0, comments_1] + comments = [] of String + 2.times do |i| + comments << (env.params.body["comments[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[i]) + end - captions_0 = env.params.body["captions_0"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[0] - captions_1 = env.params.body["captions_1"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[1] - captions_2 = env.params.body["captions_2"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[2] - captions = [captions_0, captions_1, captions_2] + captions = [] of String + 3.times do |i| + captions << (env.params.body["captions[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[i]) + end related_videos = env.params.body["related_videos"]?.try &.as(String) related_videos ||= "off" @@ -1224,6 +1253,37 @@ post "/preferences" do |env| if user = env.get? "user" user = user.as(User) PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email) + + if config.admins.includes? user.email + config.default_home = env.params.body["default_home"]?.try &.as(String) || config.default_home + + feed_menu = [] of String + 4.times do |index| + option = env.params.body["feed_menu[#{index}]"]?.try &.as(String) || "" + if !option.empty? + feed_menu << option + end + end + config.feed_menu = feed_menu + + top_enabled = env.params.body["top_enabled"]?.try &.as(String) + top_enabled ||= "off" + config.top_enabled = top_enabled == "on" + + captcha_enabled = env.params.body["captcha_enabled"]?.try &.as(String) + captcha_enabled ||= "off" + config.captcha_enabled = captcha_enabled == "on" + + login_enabled = env.params.body["login_enabled"]?.try &.as(String) + login_enabled ||= "off" + config.login_enabled = login_enabled == "on" + + registration_enabled = env.params.body["registration_enabled"]?.try &.as(String) + registration_enabled ||= "off" + config.registration_enabled = registration_enabled == "on" + + File.write("config/config.yml", config.to_yaml) + end else env.response.cookies["PREFS"] = preferences end @@ -1397,7 +1457,7 @@ get "/subscription_manager" do |env| subscriptions.sort_by! { |channel| channel.author.downcase } if action_takeout - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) if format == "json" env.response.content_type = "application/json" @@ -1741,7 +1801,11 @@ end get "/feed/top" do |env| locale = LOCALES[env.get("locale").as(String)]? - templated "top" + if config.top_enabled + templated "top" + else + env.redirect "/" + end end get "/feed/popular" do |env| @@ -1984,7 +2048,7 @@ get "/feed/channel/:ucid" do |env| ) end - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) path = env.request.path feed = XML.build(indent: " ", encoding: "UTF-8") do |xml| @@ -2118,7 +2182,7 @@ get "/feed/private" do |env| videos = videos[0..max_results] end - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) path = env.request.path query = env.request.query.not_nil! @@ -2173,7 +2237,7 @@ get "/feed/playlist/:plid" do |env| plid = env.params.url["plid"] - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) path = env.request.path client = make_client(YT_URL) @@ -2487,7 +2551,7 @@ get "/api/v1/insights/:id" do |env| env.response.content_type = "application/json" error_message = {"error" => "YouTube has removed publicly-available analytics."}.to_json - halt env, status_code: 503, response: error_message + halt env, status_code: 410, response: error_message client = make_client(YT_URL) headers = HTTP::Headers.new @@ -2653,7 +2717,7 @@ get "/api/v1/videos/:id" do |env| end if video.player_response["streamingData"]?.try &.["hlsManifestUrl"]? - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) host_params = env.request.query_params host_params.delete_all("v") @@ -2871,6 +2935,11 @@ get "/api/v1/top" do |env| env.response.content_type = "application/json" + if !config.top_enabled + error_message = {"error" => "Administrator has disabled this endpoint."}.to_json + halt env, status_code: 400, response: error_message + end + videos = JSON.build do |json| json.array do top_videos.each do |video| @@ -3842,7 +3911,7 @@ get "/api/manifest/hls_variant/*" do |env| env.response.content_type = "application/x-mpegURL" env.response.headers.add("Access-Control-Allow-Origin", "*") - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) manifest = manifest.body manifest.gsub("https://www.youtube.com", host_url) @@ -3856,7 +3925,7 @@ get "/api/manifest/hls_playlist/*" do |env| halt env, status_code: manifest.status_code end - host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, CONFIG.domain) + host_url = make_host_url(Kemal.config.ssl || config.https_only, config.domain) manifest = manifest.body.gsub("https://www.youtube.com", host_url) manifest = manifest.gsub(/https:\/\/r\d---.{11}\.c\.youtube\.com/, host_url) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 45ebc4dd..26b6244c 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -1,9 +1,9 @@ class Config YAML.mapping({ + video_threads: Int32, # Number of threads to use for updating videos in cache (mostly non-functional) crawl_threads: Int32, # Number of threads to use for finding new videos from YouTube (used to populate "top" page) 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 - video_threads: Int32, # Number of threads to use for updating videos in cache (mostly non-functional) db: NamedTuple( # Database configuration user: String, password: String, @@ -11,11 +11,18 @@ user: String, port: Int32, dbname: String, ), - dl_api_key: String?, # DetectLanguage API Key (used to filter non-English results from "top" page), mostly non-functional - https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// - hmac_key: String?, # HMAC signing key for CSRF tokens - full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel - domain: String, # Domain to be used for links to resources on the site where an absolute URL is required + full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel + https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// + hmac_key: String?, # HMAC signing key for CSRF tokens + domain: String, # Domain to be used for links to resources on the site where an absolute URL is required + dl_api_key: String?, # DetectLanguage API Key (used to filter non-English results from "top" page), mostly non-functional + default_home: {type: String, default: "Top"}, + feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending"]}, + top_enabled: {type: Bool, default: true}, + captcha_enabled: {type: Bool, default: true}, + login_enabled: {type: Bool, default: true}, + registration_enabled: {type: Bool, default: true}, + admins: {type: Array(String), default: [] of String}, }) end diff --git a/src/invidious/jobs.cr b/src/invidious/jobs.cr index e000540a..cc5f85d1 100644 --- a/src/invidious/jobs.cr +++ b/src/invidious/jobs.cr @@ -133,7 +133,7 @@ def refresh_feeds(db, logger, max_threads = 1) rescue ex # Create view if it doesn't exist if ex.message.try &.ends_with? "does not exist" - PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ + db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") @@ -193,11 +193,11 @@ end def pull_popular_videos(db) loop do - subscriptions = PG_DB.query_all("SELECT channel FROM \ + subscriptions = db.query_all("SELECT channel FROM \ (SELECT UNNEST(subscriptions) AS channel FROM users) AS d \ GROUP BY channel ORDER BY COUNT(channel) DESC LIMIT 40", as: String) - videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM \ + videos = db.query_all("SELECT DISTINCT ON (ucid) * FROM \ channel_videos WHERE ucid IN (#{arg_array(subscriptions)}) \ ORDER BY ucid, published DESC", subscriptions, as: ChannelVideo).sort_by { |video| video.published }.reverse diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 48d8008f..42468228 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -143,7 +143,7 @@ def get_user(sid, headers, db, refresh = true) begin view_name = "subscriptions_#{sha256(user.email)[0..7]}" - PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ + db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") @@ -165,7 +165,7 @@ def get_user(sid, headers, db, refresh = true) begin view_name = "subscriptions_#{sha256(user.email)[0..7]}" - PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ + db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") @@ -247,7 +247,7 @@ def validate_response(challenge, token, user_id, operation, key, db, locale) raise translate(locale, "Invalid challenge") end - challenge = OpenSSL::HMAC.digest(:sha256, HMAC_KEY, challenge) + challenge = OpenSSL::HMAC.digest(:sha256, key, challenge) challenge = Base64.urlsafe_encode(challenge) if db.query_one?("SELECT EXISTS (SELECT true FROM nonces WHERE nonce = $1)", nonce, as: Bool) diff --git a/src/invidious/views/components/feed_menu.ecr b/src/invidious/views/components/feed_menu.ecr index 5c19bafc..378601a1 100644 --- a/src/invidious/views/components/feed_menu.ecr +++ b/src/invidious/views/components/feed_menu.ecr @@ -2,12 +2,12 @@
- <% feeds = ["Popular", "Top", "Trending"] %> - <% if env.get? "user" %> - <% feeds << "Subscriptions" %> + <% feed_menu = config.feed_menu.dup %> + <% if !env.get?("user") %> + <% feed_menu.reject! {|feed| feed == "Subscriptions"} %> <% end %> - <% feeds.each do |feed| %> -
+ <% feed_menu.each do |feed| %> +
<%= translate(locale, feed) %> diff --git a/src/invidious/views/index.ecr b/src/invidious/views/index.ecr deleted file mode 100644 index 6cc978e5..00000000 --- a/src/invidious/views/index.ecr +++ /dev/null @@ -1,14 +0,0 @@ -<% content_for "header" do %> -"> -Invidious -<% end %> - -<%= rendered "components/feed_menu" %> - -
-<% top_videos.each_slice(4) do |slice| %> - <% slice.each do |item| %> - <%= rendered "components/item" %> - <% end %> -<% end %> -
diff --git a/src/invidious/views/login.ecr b/src/invidious/views/login.ecr index f4b0ce96..20363b2c 100644 --- a/src/invidious/views/login.ecr +++ b/src/invidious/views/login.ecr @@ -27,36 +27,40 @@ - - <% if captcha_type == "image" %> - - - - - - - - <% else %> - <% text_captcha.not_nil![:tokens].each_with_index do |token, i| %> - - - <% end %> - - - + <% if config.captcha_enabled %> + <% if captcha_type == "image" %> + + + + + + + + <% else %> + <% text_captcha.not_nil![:tokens].each_with_index do |token, i| %> + + + <% end %> + + + + + <% end %> <% end %> + <% if config.registration_enabled %> + <% end %> <% elsif account_type == "google" %> @@ -67,7 +71,7 @@ - + <% if tfa %> diff --git a/src/invidious/views/popular.ecr b/src/invidious/views/popular.ecr index f235aad8..d60ae557 100644 --- a/src/invidious/views/popular.ecr +++ b/src/invidious/views/popular.ecr @@ -1,6 +1,6 @@ <% content_for "header" do %> "> -<%= translate(locale, "Popular") %> - Invidious +<% if config.default_home != "Popular" %><%= translate(locale, "Popular") %> - <% end %>Invidious <% end %> <%= rendered "components/feed_menu" %> diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr index 7435c91e..ea6a84c8 100644 --- a/src/invidious/views/preferences.ecr +++ b/src/invidious/views/preferences.ecr @@ -58,45 +58,25 @@ function update_value(element) {
- - <% {"", "youtube", "reddit"}.each do |option| %> - + <% end %> + <% end %>
- - -
- -
- - <% CAPTION_LANGUAGES.each do |option| %> - + <% end %> -
- -
- - - -
@@ -167,13 +147,57 @@ function update_value(element) {
<% end %> + <% if env.get?("user") && config.admins.includes? env.get?("user").as(User).email %> + <%= translate(locale, "Administrator preferences") %> + +
+ + +
+ +
+ + <% 4.times do |index| %> + + <% end %> +
+ +
+ + checked<% end %>> +
+ +
+ + checked<% end %>> +
+ +
+ + checked<% end %>> +
+ +
+ + checked<% end %>> +
+ <% end %> + <% if env.get? "user" %> <%= translate(locale, "Data preferences") %> - + diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index b58135d7..0ca794ca 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -89,12 +89,14 @@
+ <% if config.login_enabled %> <% end %> + <% end %>
<%= content %> diff --git a/src/invidious/views/top.ecr b/src/invidious/views/top.ecr index acf122a6..44faf706 100644 --- a/src/invidious/views/top.ecr +++ b/src/invidious/views/top.ecr @@ -1,6 +1,6 @@ <% content_for "header" do %> "> -<%= translate(locale, "Top") %> - Invidious +<% if config.default_home != "Top" %><%= translate(locale, "Top") %> - <% end %>Invidious <% end %> <%= rendered "components/feed_menu" %> diff --git a/src/invidious/views/trending.ecr b/src/invidious/views/trending.ecr index 617a9a58..efd9999a 100644 --- a/src/invidious/views/trending.ecr +++ b/src/invidious/views/trending.ecr @@ -1,6 +1,6 @@ <% content_for "header" do %> "> -<%= translate(locale, "Trending") %> - Invidious +<% if config.default_home != "Trending" %><%= translate(locale, "Trending") %> - <% end %>Invidious <% end %> <%= rendered "components/feed_menu" %>