diff --git a/README.md b/README.md
index 8be021c9..20f509aa 100644
--- a/README.md
+++ b/README.md
@@ -34,8 +34,17 @@ Onion links:
 
 [Alternative Invidious instances](https://github.com/omarroth/invidious/wiki/Invidious-Instances)
 
+## Screenshots
+
+| Player                                                                                                                  | Preferences                                                                                                             | Subscriptions                                                                                                               |
+| ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| [ #{child["contentHtml"]}
](screenshots/01_player.png?raw=true)           | [
](screenshots/02_preferences.png?raw=true) | [
](screenshots/03_subscriptions.png?raw=true) |
+| [
](screenshots/04_description.png?raw=true) | [
](screenshots/05_preferences.png?raw=true) | [
](screenshots/06_subscriptions.png?raw=true) |
+
 ## Installation
 
+See [Invidious-Updater](https://github.com/tmiland/Invidious-Updater) for a self-contained script that can automatically install and update Invidious.
+
 ### Docker:
 
 #### Build and start cluster:
@@ -98,6 +107,7 @@ $ psql invidious < /home/invidious/invidious/config/sql/channels.sql
 $ psql invidious < /home/invidious/invidious/config/sql/videos.sql
 $ psql invidious < /home/invidious/invidious/config/sql/channel_videos.sql
 $ psql invidious < /home/invidious/invidious/config/sql/users.sql
+$ psql invidious < /home/invidious/invidious/config/sql/session_ids.sql
 $ psql invidious < /home/invidious/invidious/config/sql/nonces.sql
 $ exit
 ```
@@ -107,7 +117,7 @@ $ exit
 ```bash
 $ sudo -i -u invidious
 $ cd invidious
-$ shards
+$ shards update && shards install
 $ crystal build src/invidious.cr --release
 # test compiled binary
 $ ./invidious # stop with ctrl c
@@ -115,6 +125,7 @@ $ exit
 ```
 
 #### systemd service
+
 ```bash
 $ sudo cp /home/invidious/invidious/invidious.service /etc/systemd/system/invidious.service
 $ sudo systemctl enable invidious.service
@@ -138,15 +149,17 @@ $ psql invidious < config/sql/channels.sql
 $ psql invidious < config/sql/videos.sql
 $ psql invidious < config/sql/channel_videos.sql
 $ psql invidious < config/sql/users.sql
+$ psql invidious < config/sql/session_ids.sql
 $ psql invidious < config/sql/nonces.sql
 
 # Setup Invidious
-$ shards
+$ shards update && shards install
 $ crystal build src/invidious.cr --release
 ```
 
 ## Update Invidious
-You can find information about how to update in the wiki: [Updating](https://github.com/omarroth/invidious/wiki/Updating).
+
+You can see how to update Invidious [here](https://github.com/omarroth/invidious/wiki/Updating).
 
 ## Usage:
 
@@ -178,16 +191,19 @@ $ ./sentry
 ```
 
 ## Documentation
+
 [Documentation](https://github.com/omarroth/invidious/wiki) can be found in the wiki.
 
 ## Extensions
-Extensions for Invidious and for integrating Invidious into other projects [are in the wiki](https://github.com/omarroth/invidious/wiki/Extensions)
+
+[Extensions](https://github.com/omarroth/invidious/wiki/Extensions) can be found in the wiki, as well as documentation for integrating it into other projects.
 
 ## Made with Invidious
 
 - [FreeTube](https://github.com/FreeTubeApp/FreeTube): An Open Source YouTube app for privacy.
 - [CloudTube](https://github.com/cloudrac3r/cadencegq): Website featuring pastebin, image host, and YouTube player
 - [PeerTubeify](https://gitlab.com/Ealhad/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists.
+- [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A materialistic music player that streams music from YouTube.
 
 ## Contributing
 
diff --git a/config/config.yml b/config/config.yml
index f981a398..c6f8420a 100644
--- a/config/config.yml
+++ b/config/config.yml
@@ -10,4 +10,4 @@ db:
   dbname: invidious
 full_refresh: false
 https_only: false
-domain: invidio.us
+domain:
diff --git a/config/migrate-scripts/migrate-db-30e6d29.sh b/config/migrate-scripts/migrate-db-30e6d29.sh
deleted file mode 100755
index 259862df..00000000
--- a/config/migrate-scripts/migrate-db-30e6d29.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-psql invidious -c "ALTER TABLE channels ADD COLUMN deleted bool;"
-psql invidious -c "UPDATE channels SET deleted = false;"
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 0197db94..e2d434db 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -9,7 +9,7 @@ ADD . /invidious
 WORKDIR /invidious
 
 RUN sed -i 's/host: localhost/host: postgres/' config/config.yml && \
-    shards && \
+    shards update && shards install && \
     crystal build src/invidious.cr
 
 CMD [ "/invidious/invidious" ]
diff --git a/docker/entrypoint.postgres.sh b/docker/entrypoint.postgres.sh
index 9a258dd6..8f987201 100755
--- a/docker/entrypoint.postgres.sh
+++ b/docker/entrypoint.postgres.sh
@@ -16,6 +16,7 @@ if [ ! -f /var/lib/postgresql/data/setupFinished ]; then
     su postgres -c 'psql invidious < config/sql/videos.sql'
     su postgres -c 'psql invidious < config/sql/channel_videos.sql'
     su postgres -c 'psql invidious < config/sql/users.sql'
+    su postgres -c 'psql invidious < config/sql/session_ids.sql'
     su postgres -c 'psql invidious < config/sql/nonces.sql'
     touch /var/lib/postgresql/data/setupFinished
     echo "### invidious database setup finished"
diff --git a/locales/ar.json b/locales/ar.json
index da7125f3..e8b59c46 100644
--- a/locales/ar.json
+++ b/locales/ar.json
@@ -82,6 +82,14 @@
   "Manage subscriptions": "إدارة المشتركين",
   "Watch history": "سجل المشاهدة",
   "Delete account": "حذف الحساب",
+  "Administrator preferences": "",
+  "Default homepage: ": "",
+  "Feed menu: ": "",
+  "Top enabled? ": "",
+  "CAPTCHA enabled? ": "",
+  "Login enabled? ": "",
+  "Registration enabled? ": "",
+  "Report statistics? ": "",
   "Save preferences": "حفظ التفضيلات",
   "Subscription manager": "مدير الإشتراكات",
   "`x` subscriptions": "`x` مشتركين",
@@ -280,5 +288,7 @@
   "%A %B %-d, %Y": "",
   "(edited)": "",
   "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "`x` marked it with a ❤": "",
+  "Audio mode": "",
+  "Video mode": ""
 }
diff --git a/locales/de.json b/locales/de.json
index a23631a0..e40c0b31 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -82,6 +82,14 @@
   "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? ": "",
+  "Report statistics? ": "",
   "Save preferences": "Einstellungen speichern",
   "Subscription manager": "Abonnementverwaltung",
   "`x` subscriptions": "`x` Abonnements",
@@ -264,9 +272,9 @@
   "`x` hours": "`x` Stunden",
   "`x` minutes": "`x` Minuten",
   "`x` seconds": "`x` Sekunden",
-  "Fallback comments: ": "",
+  "Fallback comments: ": "Alternative Kommentare: ",
   "Popular": "Populär",
-  "Top": "",
+  "Top": "Top",
   "About": "Über",
   "Rating: ": "Bewertung: ",
   "Language: ": "Sprache: ",
@@ -280,5 +288,7 @@
   "%A %B %-d, %Y": "",
   "(edited)": "",
   "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "`x` marked it with a ❤": "",
+  "Audio mode": "",
+  "Video mode": ""
 }
diff --git a/locales/en-US.json b/locales/en-US.json
index 20fc019e..1848ff20 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -80,6 +80,14 @@
   "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? ",
+  "Report statistics? ": "Report statistics? ",
   "Save preferences": "Save preferences",
   "Subscription manager": "Subscription manager",
   "`x` subscriptions": "`x` subscriptions",
@@ -274,5 +282,7 @@
   "%A %B %-d, %Y": "%A %B %-d, %Y",
   "(edited)": "(edited)",
   "Youtube permalink of the comment": "Youtube permalink of the comment",
-  "`x` marked it with a ❤": "`x` marked it with a ❤"
+  "`x` marked it with a ❤": "`x` marked it with a ❤",
+  "Audio mode": "Audio mode",
+  "Video mode": "Video mode"
 }
diff --git a/locales/eu.json b/locales/eu.json
index b0378ea8..8f887f71 100644
--- a/locales/eu.json
+++ b/locales/eu.json
@@ -1,11 +1,11 @@
 {
-  "`x` subscribers": "",
-  "`x` videos": "",
-  "LIVE": "",
-  "Shared `x` ago": "",
-  "Unsubscribe": "",
+  "`x` subscribers": "`x` harpidedun",
+  "`x` videos": "`x` bideo",
+  "LIVE": "ZUZENEAN",
+  "Shared `x` ago": "Duela `x` partekatua",
+  "Unsubscribe": "Harpidetza kendu",
   "Subscribe": "Harpidetu",
-  "Login to subscribe to `x`": "",
+  "Login to subscribe to `x`": "Saioa hasi `x`(e)ra harpidetzeko",
   "View channel on YouTube": "Ikusi kanala YouTuben",
   "newest": "berrienak",
   "oldest": "zaharrenak",
@@ -24,22 +24,22 @@
   "Import NewPipe data (.zip)": "NewPipeko datuak inportatu (.zip)",
   "Export": "Esportatu",
   "Export subscriptions as OPML": "Esportatu harpidetzak OPML bezala",
-  "Export subscriptions as OPML (for NewPipe & FreeTube)": "",
-  "Export data as JSON": "",
+  "Export subscriptions as OPML (for NewPipe & FreeTube)": "Harpidetzak OPML bezala esportatu (NewPipe eta FreeTuberako)",
+  "Export data as JSON": "Datuak JSON bezala esportatu",
   "Delete account?": "Kontua ezabatu?",
   "History": "Historia",
   "Previous page": "Aurreko orria",
-  "An alternative front-end to YouTube": "",
-  "JavaScript license information": "",
-  "source": "",
-  "Login": "",
-  "Login/Register": "",
-  "Login to Google": "",
-  "User ID:": "",
-  "Password:": "",
-  "Time (h:mm:ss):": "",
-  "Text CAPTCHA": "",
-  "Image CAPTCHA": "",
+  "An alternative front-end to YouTube": "YouTuberako interfaze alternatibo bat",
+  "JavaScript license information": "JavaScript lizentzia informazioa",
+  "source": "iturburua",
+  "Login": "Saioa hasi",
+  "Login/Register": "Saioa hasi/Izena eman",
+  "Login to Google": "Googlekin hasi saioa",
+  "User ID:": "Erabiltzaile IDa:",
+  "Password:": "Pasahitza:",
+  "Time (h:mm:ss):": "Denbora (o:mm:ss):",
+  "Text CAPTCHA": "Testu CAPTCHA",
+  "Image CAPTCHA": "Irudi CAPTCHA",
   "Sign In": "",
   "Register": "",
   "Email:": "",
@@ -80,6 +80,14 @@
   "Manage subscriptions": "",
   "Watch history": "",
   "Delete account": "",
+  "Administrator preferences": "",
+  "Default homepage: ": "",
+  "Feed menu: ": "",
+  "Top enabled? ": "",
+  "CAPTCHA enabled? ": "",
+  "Login enabled? ": "",
+  "Registration enabled? ": "",
+  "Report statistics? ": "",
   "Save preferences": "",
   "Subscription manager": "",
   "`x` subscriptions": "",
@@ -274,5 +282,7 @@
   "%A %B %-d, %Y": "",
   "(edited)": "",
   "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "`x` marked it with a ❤": "",
+  "Audio mode": "",
+  "Video mode": ""
 }
diff --git a/locales/fr.json b/locales/fr.json
index e4bb5111..f5c34a6e 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -1,152 +1,159 @@
 {
-  "`x` subscribers": "`x` souscripteurs",
+  "`x` subscribers": "`x` abonnés",
   "`x` videos": "`x` vidéos",
-  "LIVE": "LIVE",
+  "LIVE": "EN DIRECT",
   "Shared `x` ago": "Partagé il y a `x`",
   "Unsubscribe": "Se désabonner",
   "Subscribe": "S'abonner",
-  "Login to subscribe to `x`": "Se connecter pour s'abonner à `x`",
+  "Login to subscribe to `x`": "Vous devez vous connecter pour vous abonner à `x`",
   "View channel on YouTube": "Voir la chaîne sur YouTube",
-  "newest": "récent",
-  "oldest": "aînée",
-  "popular": "appréciés",
-  "Preview page": "Page de prévisualisation",
+  "newest": "Date d'ajout (la plus récente)",
+  "oldest": "Date d'ajout (la plus ancienne)",
+  "popular": "Les plus populaires",
   "Next page": "Page suivante",
-  "Clear watch history?": "L'histoire de la montre est claire?",
+  "Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?",
   "Yes": "Oui",
-  "No": "Aucun",
-  "Import and Export Data": "Importation et exportation de données",
-  "Import": "Importation",
-  "Import Invidious data": "Importation de données invalides",
+  "No": "Non",
+  "Import and Export Data": "Importer et Exporter les Données",
+  "Import": "Importer",
+  "Import Invidious data": "Importer des données Invidious",
   "Import YouTube subscriptions": "Importer des abonnements YouTube",
   "Import FreeTube subscriptions (.db)": "Importer des abonnements FreeTube (.db)",
   "Import NewPipe subscriptions (.json)": "Importer des abonnements NewPipe (.json)",
   "Import NewPipe data (.zip)": "Importer des données NewPipe (.zip)",
   "Export": "Exporter",
-  "Export subscriptions as OPML": "Exporter les abonnements comme OPML",
-  "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporter les abonnements comme OPML (pour NewPipe & FreeTube)",
+  "Export subscriptions as OPML": "Exporter les abonnements en OPML",
+  "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporter les abonnements en OPML (pour NewPipe & FreeTube)",
   "Export data as JSON": "Exporter les données au format JSON",
-  "Delete account?": "Supprimer un compte ?",
-  "History": "Histoire",
+  "Delete account?": "Supprimer votre compte ?",
+  "History": "Historique",
   "Previous page": "Page précédente",
-  "An alternative front-end to YouTube": "Un frontal alternatif à YouTube",
-  "JavaScript license information": "Informations sur la licence JavaScript",
-  "source": "origine",
+  "An alternative front-end to YouTube": "Un front-end alternatif à YouTube",
+  "JavaScript license information": "Informations sur les licences JavaScript",
+  "source": "source",
   "Login": "Connexion",
   "Login/Register": "Connexion/S'inscrire",
   "Login to Google": "Se connecter à Google",
-  "User ID:": "ID utilisateur:",
-  "Password:": "Mot de passe:",
-  "Time (h:mm:ss):": "Temps (h:mm:ss):",
-  "Text CAPTCHA": "Texte CAPTCHA",
-  "Image CAPTCHA": "Image CAPTCHA",
+  "User ID:": "ID utilisateur :",
+  "Password:": "Mot de passe :",
+  "Time (h:mm:ss):": "Heure (h:mm:ss) :",
+  "Text CAPTCHA": "CAPTCHA Texte",
+  "Image CAPTCHA": "CAPTCHA Image",
   "Sign In": "S'identifier",
   "Register": "S'inscrire",
-  "Email:": "Courriel:",
-  "Google verification code:": "Code de vérification Google:",
+  "Email:": "Email :",
+  "Google verification code:": "Code de vérification Google :",
   "Preferences": "Préférences",
-  "Player preferences": "Joueur préférences",
-  "Always loop: ": "Toujours en boucle: ",
-  "Autoplay: ": "Autoplay: ",
-  "Autoplay next video: ": "Lecture automatique de la vidéo suivante: ",
-  "Listen by default: ": "Écouter par défaut: ",
-  "Default speed: ": "Vitesse par défaut: ",
-  "Preferred video quality: ": "Qualité vidéo préférée: ",
-  "Player volume: ": "Volume de lecteur: ",
-  "Default comments: ": "Commentaires par défaut: ",
-  "Default captions: ": "Légendes par défaut: ",
-  "Fallback captions: ": "Légendes de repli: ",
-  "Show related videos? ": "Voir les vidéos liées à ce sujet? ",
+  "Player preferences": "Préférences du Lecteur",
+  "Always loop: ": "Lire en boucle : ",
+  "Autoplay: ": "Lire Automatiquement : ",
+  "Autoplay next video: ": "Lire automatiquement la vidéo suivante : ",
+  "Listen by default: ": "Audio Uniquement par défaut : ",
+  "Default speed: ": "Vitesse par défaut : ",
+  "Preferred video quality: ": "Qualité vidéo souhaitée : ",
+  "Player volume: ": "Volume du lecteur : ",
+  "Default comments: ": "Source des Commentaires : ",
+  "Default captions: ": "Sous-titres principal : ",
+  "Fallback captions: ": "Sous-titres secondaire : ",
+  "Show related videos? ": "Voir les vidéos liées à ce sujet ? ",
   "Visual preferences": "Préférences visuelles",
-  "Dark mode: ": "Mode sombre: ",
-  "Thin mode: ": "Mode Thin: ",
-  "Subscription preferences": "Préférences d'abonnement",
-  "Redirect homepage to feed: ": "Rediriger la page d'accueil vers le flux: ",
-  "Number of videos shown in feed: ": "Nombre de vidéos montrées dans le flux: ",
-  "Sort videos by: ": "Trier les vidéos par: ",
-  "published": "publié",
-  "published - reverse": "publié - reverse",
+  "Dark mode: ": "Mode Sombre : ",
+  "Thin mode: ": "Mode Simplifié : ",
+  "Subscription preferences": "Préférences de la page d'abonnements",
+  "Redirect homepage to feed: ": "Rediriger la page d'accueil vers la page d'abonnements : ",
+  "Number of videos shown in feed: ": "Nombre de vidéos montrées dans la page d'abonnements : ",
+  "Sort videos by: ": "Trier les vidéos par : ",
+  "published": "publication",
+  "published - reverse": "publication - inversé",
   "alphabetically": "alphabétiquement",
-  "alphabetically - reverse": "alphabétiquement - contraire",
-  "channel name": "nom du canal",
-  "channel name - reverse": "nom du canal - contraire",
-  "Only show latest video from channel: ": "Afficher uniquement les dernières vidéos de la chaîne: ",
-  "Only show latest unwatched video from channel: ": "Afficher uniquement les dernières vidéos non regardées de la chaîne: ",
-  "Only show unwatched: ": "Afficher uniquement les images non surveillées: ",
-  "Only show notifications (if there are any): ": "Afficher uniquement les notifications (s'il y en a): ",
-  "Data preferences": "Préférences de données",
-  "Clear watch history": "Historique clair de la montre",
-  "Import/Export data": "Données d'importation/exportation",
+  "alphabetically - reverse": "alphabétiquement - inversé",
+  "channel name": "nom de la chaîne",
+  "channel name - reverse": "nom de la chaîne - inversé",
+  "Only show latest video from channel: ": "Afficher uniquement la dernière vidéo de la chaîne : ",
+  "Only show latest unwatched video from channel: ": "Afficher uniquement la dernière vidéo de la chaîne non regardée : ",
+  "Only show unwatched: ": "Afficher uniquement les vidéos non regardées : ",
+  "Only show notifications (if there are any): ": "Afficher uniquement les notifications (s'il y en a) : ",
+  "Data preferences": "Préférences liées aux données",
+  "Clear watch history": "Supprimer l'historique des vidéos regardées",
+  "Import/Export data": "Importer/exporter les données",
   "Manage subscriptions": "Gérer les abonnements",
-  "Watch history": "Historique des montres",
-  "Delete account": "Supprimer un compte",
+  "Watch history": "Historique de visionnage",
+  "Delete account": "Supprimer votre compte",
+  "Administrator preferences": "",
+  "Default homepage: ": "",
+  "Feed menu: ": "",
+  "Top enabled? ": "",
+  "CAPTCHA enabled? ": "",
+  "Login enabled? ": "",
+  "Registration enabled? ": "",
+  "Report statistics? ": "",
   "Save preferences": "Enregistrer les préférences",
   "Subscription manager": "Gestionnaire d'abonnement",
   "`x` subscriptions": "`x` abonnements",
   "Import/Export": "Importer/Exporter",
   "unsubscribe": "se désabonner",
   "Subscriptions": "Abonnements",
-  "`x` unseen notifications": "`x` notifications invisibles",
-  "search": "perquisition",
+  "`x` unseen notifications": "`x` notifications non vues",
+  "search": "Rechercher",
   "Sign out": "Déconnexion",
-  "Released under the AGPLv3 by Omar Roth.": "Publié sous l'AGPLv3 par Omar Roth.",
-  "Source available here.": "Source disponible ici.",
-  "View JavaScript license information.": "Voir les informations de licence JavaScript.",
+  "Released under the AGPLv3 by Omar Roth.": "Publié sous licence AGPLv3 par Omar Roth.",
+  "Source available here.": "Code Source.",
+  "View JavaScript license information.": "Voir les informations des licences JavaScript.",
   "Trending": "Tendances",
   "Watch video on Youtube": "Voir la vidéo sur Youtube",
-  "Genre: ": "Genre: ",
-  "License: ": "Licence: ",
-  "Family friendly? ": "Convivialité familiale? ",
-  "Wilson score: ": "Wilson marque: ",
-  "Engagement: ": "Fiançailles: ",
-  "Whitelisted regions: ": "Régions en liste blanche: ",
-  "Blacklisted regions: ": "Régions sur liste noire: ",
+  "Genre: ": "Genre : ",
+  "License: ": "Licence : ",
+  "Family friendly? ": "Tout Public ? ",
+  "Wilson score: ": "Score de Wilson : ",
+  "Engagement: ": "Poucentage de spectateur aillant aimé Liker ou Disliker la vidéo : ",
+  "Whitelisted regions: ": "Régions en liste blanche : ",
+  "Blacklisted regions: ": "Régions sur liste noire : ",
   "Shared `x`": "Partagée `x`",
-  "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hi! On dirait que vous avez désactivé JavaScript. Cliquez ici pour voir les commentaires, gardez à l'esprit que le chargement peut prendre un peu plus de temps.",
-  "View YouTube comments": "Voir les commentaires sur YouTube",
+  "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript sois désactivé. Cliquez ici pour voir les commentaires. Gardez à l'esprit que le chargement peut prendre plus de temps.",
+  "View YouTube comments": "Voir les commentaires YouTube",
   "View more comments on Reddit": "Voir plus de commentaires sur Reddit",
   "View `x` comments": "Voir `x` commentaires",
-  "View Reddit comments": "Voir Reddit commentaires",
+  "View Reddit comments": "Voir les commentaires Reddit",
   "Hide replies": "Masquer les réponses",
   "Show replies": "Afficher les réponses",
   "Incorrect password": "Mot de passe incorrect",
-  "Quota exceeded, try again in a few hours": "Quota dépassé, réessayez dans quelques heures",
+  "Quota exceeded, try again in a few hours": "Nombre de tentative de connexion dépassé, réessayez dans quelques heures",
   "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.",
-  "Invalid TFA code": "Code TFA invalide",
+  "Invalid TFA code": "Code d'authentification à deux facteurs invalide",
   "Login failed. This may be because two-factor authentication is not enabled on your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.",
   "Invalid answer": "Réponse non valide",
   "Invalid CAPTCHA": "CAPTCHA invalide",
-  "CAPTCHA is a required field": "CAPTCHA est un champ obligatoire",
-  "User ID is a required field": "Utilisateur ID est un champ obligatoire",
-  "Password is a required field": "Mot de passe est un champ obligatoire",
+  "CAPTCHA is a required field": "Veuillez rentrez un CAPTCHA",
+  "User ID is a required field": "Veuillez rentrez un Identifiant Utilisateur",
+  "Password is a required field": "Veuillez rentrez un Mot de passe",
   "Invalid username or password": "Nom d'utilisateur ou mot de passe invalide",
-  "Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant 'S'identifier avec Google'",
+  "Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"S'identifier avec Google\"",
   "Password cannot be empty": "Le mot de passe ne peut pas être vide",
-  "Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères.",
-  "Please sign in": "Veuillez ouvrir une session",
-  "Invidious Private Feed for `x`": "Flux privé Invidious pour `x`",
-  "channel:`x`": "chenal:`x`",
-  "Deleted or invalid channel": "Canal supprimé ou non valide",
-  "This channel does not exist.": "Ce canal n'existe pas.",
-  "Could not get channel info.": "Impossible d'obtenir des informations sur les chaînes.",
-  "Could not fetch comments": "Impossible d'aller chercher les commentaires",
+  "Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères",
+  "Please sign in": "Veuillez vous connecter",
+  "Invidious Private Feed for `x`": "Flux RSS privé pour `x`",
+  "channel:`x`": "chaîne:`x`",
+  "Deleted or invalid channel": "Chaîne supprimée ou invalide",
+  "This channel does not exist.": "Cette chaine n'existe pas.",
+  "Could not get channel info.": "Impossible de charger les informations de cette chaîne.",
+  "Could not fetch comments": "Impossible de charger les commentaires",
   "View `x` replies": "Voir `x` réponses",
   "`x` ago": "il y a `x`",
   "Load more": "Charger plus",
   "`x` points": "`x` points",
-  "Could not create mix.": "Impossible de créer du mixage.",
+  "Could not create mix.": "Impossible de charger cette liste de lecture.",
   "Playlist is empty": "La liste de lecture est vide",
   "Invalid playlist.": "Liste de lecture invalide.",
   "Playlist does not exist.": "La liste de lecture n'existe pas.",
-  "Could not pull trending pages.": "Impossible de tirer les pages de tendances.",
-  "Hidden field \"challenge\" is a required field": "Champ caché \"contestation\" est un champ obligatoire",
-  "Hidden field \"token\" is a required field": "Champ caché \"jeton\" est un champ obligatoire",
-  "Invalid challenge": "Contestation non valide",
-  "Invalid token": "Jeton non valide",
-  "Invalid user": "Iutilisateur non valide",
-  "Token is expired, please try again": "Le jeton est expiré, veuillez réessayer",
+  "Could not pull trending pages.": "Impossible de charger les pages de tendances.",
+  "Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field",
+  "Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field",
+  "Invalid challenge": "Invalid challenge",
+  "Invalid token": "Invalid token",
+  "Invalid user": "Invalid user",
+  "Token is expired, please try again": "Token is expired, please try again",
   "English": "Anglais",
-  "English (auto-generated)": "Anglais (auto-généré)",
+  "English (auto-generated)": "Anglais (générés automatiquement)",
   "Afrikaans": "Afrikaans",
   "Albanian": "Albanais",
   "Amharic": "Amharique",
@@ -258,21 +265,23 @@
   "`x` hours": "`x` heures",
   "`x` minutes": "`x` minutes",
   "`x` seconds": "`x` secondes",
-  "Fallback comments: ": "Commentaires de repli: ",
+  "Fallback comments: ": "Commentaires secondaires : ",
   "Popular": "Populaire",
-  "Top": "Haut",
-  "About": "Sur",
-  "Rating: ": "Évaluation: ",
-  "Language: ": "Langue: ",
-  "Default": "",
-  "Music": "",
-  "Gaming": "",
-  "News": "",
-  "Movies": "",
-  "Download": "",
-  "Download as: ": "",
-  "%A %B %-d, %Y": "",
-  "(edited)": "",
-  "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "Top": "Top",
+  "About": "A Propos",
+  "Rating: ": "Évaluation : ",
+  "Language: ": "Langue : ",
+  "Default": "Défaut",
+  "Music": "Musique",
+  "Gaming": "Jeux Vidéo",
+  "News": "Actualités",
+  "Movies": "Films",
+  "Download": "Télécharger",
+  "Download as: ": "Télécharger en : ",
+  "%A %B %-d, %Y": "%A %-d %B %Y",
+  "(edited)": "(modifié)",
+  "Youtube permalink of the comment": "Lien YouTube permanent vers le commentaire",
+  "`x` marked it with a ❤": "`x` l'a marqué d'un ❤",
+  "Audio mode": "Mode Audio",
+  "Video mode": "Mode Vidéo"
 }
diff --git a/locales/it.json b/locales/it.json
new file mode 100644
index 00000000..eeae6ed3
--- /dev/null
+++ b/locales/it.json
@@ -0,0 +1,287 @@
+{
+  "`x` subscribers": "`x` iscritti",
+  "`x` videos": "`x` video",
+  "LIVE": "IN DIRETTA",
+  "Shared `x` ago": "Condiviso `x` fa",
+  "Unsubscribe": "Disiscriviti",
+  "Subscribe": "Iscriviti",
+  "Login to subscribe to `x`": "Accedi per iscriverti a `x`",
+  "View channel on YouTube": "Vedi canale su YouTube",
+  "newest": "Data di aggiunta (più recente)",
+  "oldest": "Data di aggiunta (più vecchia)",
+  "popular": "Tendenze",
+  "Next page": "Pagina successiva",
+  "Clear watch history?": "Sei sicuro di voler cancellare la cronologia dei video guardati?",
+  "Yes": "Si",
+  "No": "No",
+  "Import and Export Data": "Importazione ed esportazione dati",
+  "Import": "Importa",
+  "Import Invidious data": "Importa dati Invidious",
+  "Import YouTube subscriptions": "Importa le iscrizioni da YouTube",
+  "Import FreeTube subscriptions (.db)": "Importa le iscrizioni da FreeTube (.db)",
+  "Import NewPipe subscriptions (.json)": "Importa le iscrizioni da NewPipe (.json)",
+  "Import NewPipe data (.zip)": "Importa i dati di NewPipe (.zip)",
+  "Export": "Esporta",
+  "Export subscriptions as OPML": "Esporta gli abbonamenti come OPML",
+  "Export subscriptions as OPML (for NewPipe & FreeTube)": "Esporta gli abbonamenti come OPML (per NewPipe e FreeTube)",
+  "Export data as JSON": "Esporta i dati in formato JSON",
+  "Delete account?": "Sei sicuro di voler cancellare l'account?",
+  "History": "Cronologia",
+  "Previous page": "Pagina precedente",
+  "An alternative front-end to YouTube": "Un'interfaccia alternativa per YouTube",
+  "JavaScript license information": "Info licenze JavaScript",
+  "source": "sorgente",
+  "Login": "Entra",
+  "Login/Register": "Entra/Registrati",
+  "Login to Google": "Entra con Google",
+  "User ID:": "ID utente:",
+  "Password:": "Password:",
+  "Time (h:mm:ss):": "Orario (h:mm:ss):",
+  "Text CAPTCHA": "Testo del CAPTCHA",
+  "Image CAPTCHA": "Immagine CAPTCHA",
+  "Sign In": "Entra",
+  "Register": "Registrati",
+  "Email:": "Email:",
+  "Google verification code:": "Codice di verifica Google:",
+  "Preferences": "Preferenze",
+  "Player preferences": "Preferenze del riproduttore",
+  "Always loop: ": "Ripeti sempre: ",
+  "Autoplay: ": "Riproduzione automatica: ",
+  "Autoplay next video: ": "Riproduci automaticamente il prossimo video: ",
+  "Listen by default: ": "Modalità solo audio come predefinita: ",
+  "Default speed: ": "Velocità di riproduzione predefinita: ",
+  "Preferred video quality: ": "Preferenza sulla qualità video: ",
+  "Player volume: ": "Volume di riproduzione: ",
+  "Default comments: ": "Origine dei commenti: ",
+  "Default captions: ": "Sottotitoli predefiniti: ",
+  "Fallback captions: ": "Sottotitoli alternativi: ",
+  "Show related videos? ": "Mostra video correlati? ",
+  "Visual preferences": "Preferenze grafiche",
+  "Dark mode: ": "Tema scuro: ",
+  "Thin mode: ": "Modalità per connessioni lente: ",
+  "Subscription preferences": "Preferenze iscrizioni",
+  "Redirect homepage to feed: ": "Reindirizza la pagina principale a quella delle iscrizioni: ",
+  "Number of videos shown in feed: ": "Numero di video da mostrare nelle iscrizioni: ",
+  "Sort videos by: ": "Ordinare i video per: ",
+  "published": "data di pubblicazione",
+  "published - reverse": "data di pubblicazione - decrescente",
+  "alphabetically": "ordine alfabetico",
+  "alphabetically - reverse": "ordine alfabetico - decrescente",
+  "channel name": "nome del canale",
+  "channel name - reverse": "nome del canale - decrescente",
+  "Only show latest video from channel: ": "Mostra solo il video più recente del canale: ",
+  "Only show latest unwatched video from channel: ": "Mostra solo il video più recente non guardato del canale: ",
+  "Only show unwatched: ": "Mostra solo i video non guardati: ",
+  "Only show notifications (if there are any): ": "Mostra solo le notifiche (se presenti): ",
+  "Data preferences": "Preferenze dati",
+  "Clear watch history": "Cancella la cronologia dei video guardati",
+  "Import/Export data": "Importazione/esportazione dati",
+  "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? ": "",
+  "Report statistics? ": "",
+  "Save preferences": "Salva le preferenze",
+  "Subscription manager": "Gestisci le iscrizioni",
+  "`x` subscriptions": "`x` iscrizioni",
+  "Import/Export": "Importa/esporta",
+  "unsubscribe": "disiscriviti",
+  "Subscriptions": "Iscrizioni",
+  "`x` unseen notifications": "`x` notifiche non visualizzate",
+  "search": "Cerca",
+  "Sign out": "Esci",
+  "Released under the AGPLv3 by Omar Roth.": "Pubblicato con licenza AGPLv3 da Omar Roth.",
+  "Source available here.": "Codice sorgente.",
+  "View JavaScript license information.": "Guarda le informazioni di licenza del codice JavaScript.",
+  "Trending": "Tendenze",
+  "Watch video on Youtube": "Guarda il video su YouTube",
+  "Genre: ": "Genere: ",
+  "License: ": "Licenza: ",
+  "Family friendly? ": "Per tutti? ",
+  "Wilson score: ": "Punteggio di Wilson: ",
+  "Engagement: ": "Tasso di coinvolgimento: ",
+  "Whitelisted regions: ": "Regioni nella lista bianca: ",
+  "Blacklisted regions: ": "Regioni nella lista nera: ",
+  "Shared `x`": "Condiviso `x`",
+  "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Ciao! Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti. Considera che potrebbe volerci più tempo.",
+  "View YouTube comments": "Visualizza i commenti da YouTube",
+  "View more comments on Reddit": "Visualizza più commenti su Reddit",
+  "View `x` comments": "Visualizza `x` commenti",
+  "View Reddit comments": "Visualizza i commenti da Reddit",
+  "Hide replies": "Nascondi le risposte",
+  "Show replies": "Mostra le risposte",
+  "Incorrect password": "Password sbagliata",
+  "Quota exceeded, try again in a few hours": "Limite superato, prova di nuovo fra qualche ora",
+  "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Impossibile autenticarsi, controlla che l'autenticazione in due passaggi (Authenticator o SMS) sia attiva.",
+  "Invalid TFA code": "Codice di autenticazione a due fattori non valido",
+  "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login fallito. L'errore potrebbe essere causato dal fatto che la verifica in due passaggi non è attiva sul tuo account.",
+  "Invalid answer": "Risposta errata",
+  "Invalid CAPTCHA": "CAPTCHA errato",
+  "CAPTCHA is a required field": "Il CAPTCHA è un campo obbligatorio",
+  "User ID is a required field": "L'ID utente è obbligatorio",
+  "Password is a required field": "La password è un campo obbligatorio",
+  "Invalid username or password": "Nome utente o password errati",
+  "Please sign in using 'Sign in with Google'": "Per favore accedi con \"Entra con Google\"",
+  "Password cannot be empty": "La password non può essere vuota",
+  "Password cannot be longer than 55 characters": "La password non può contenere più di 55 caratteri",
+  "Please sign in": "Per favore, entra",
+  "Invidious Private Feed for `x`": "Feed privato Invidious per `x`",
+  "channel:`x`": "canale:`x`",
+  "Deleted or invalid channel": "Canale cancellato o invalido",
+  "This channel does not exist.": "Canale inesistente.",
+  "Could not get channel info.": "Impossibile ottenere le informazioni del canale.",
+  "Could not fetch comments": "Impossibile recuperare i commenti",
+  "View `x` replies": "Visualizza `x` risposte",
+  "`x` ago": "`x` fa",
+  "Load more": "Carica altro",
+  "`x` points": "`x` punti",
+  "Could not create mix.": "Impossibile creare il mix.",
+  "Playlist is empty": "Playlist vuota",
+  "Invalid playlist.": "Playlist invalida.",
+  "Playlist does not exist.": "Playlist inesistente.",
+  "Could not pull trending pages.": "Impossibile recuperare le tendenze.",
+  "Hidden field \"challenge\" is a required field": "Il campo nascosto \"challenge\" è obbligatorio",
+  "Hidden field \"token\" is a required field": "Il campo nascosto \"token\" è obbligatorio",
+  "Invalid challenge": "Campo \"challenge\" invalido",
+  "Invalid token": "Campo \"token\" invalido",
+  "Invalid user": "Utente invalido",
+  "Token is expired, please try again": "Token scaduto, riprova",
+  "English": "Inglese",
+  "English (auto-generated)": "Inglese (generati automaticamente)",
+  "Afrikaans": "Afrikaans",
+  "Albanian": "Albanese",
+  "Amharic": "Amarico",
+  "Arabic": "Arabo",
+  "Armenian": "Armeno",
+  "Azerbaijani": "Azero",
+  "Bangla": "Bengalese",
+  "Basque": "Basco",
+  "Belarusian": "Biellorusso",
+  "Bosnian": "Bosniaco",
+  "Bulgarian": "Bulgaro",
+  "Burmese": "Birmano",
+  "Catalan": "Catalano",
+  "Cebuano": "Sugbuanon",
+  "Chinese (Simplified)": "Cinese semplifiato",
+  "Chinese (Traditional)": "Cinese tradizionale",
+  "Corsican": "Corso",
+  "Croatian": "Croato",
+  "Czech": "Ceco",
+  "Danish": "Danese",
+  "Dutch": "Olandese",
+  "Esperanto": "Esperanto",
+  "Estonian": "Estone",
+  "Filipino": "Filippino",
+  "Finnish": "Finlandese",
+  "French": "Francese",
+  "Galician": "Galiziano",
+  "Georgian": "Georgiano",
+  "German": "Tedesco",
+  "Greek": "Greco",
+  "Gujarati": "Gujarati",
+  "Haitian Creole": "Creolo haitiano",
+  "Hausa": "Lingua hausa",
+  "Hawaiian": "Hawaiano",
+  "Hebrew": "Ebreo",
+  "Hindi": "Hindi",
+  "Hmong": "Hmong",
+  "Hungarian": "Ungarese",
+  "Icelandic": "Islandese",
+  "Igbo": "Igbo",
+  "Indonesian": "Indonesiano",
+  "Irish": "Irlandese",
+  "Italian": "Italiano",
+  "Japanese": "Giapponese",
+  "Javanese": "Giavanese",
+  "Kannada": "Kannada",
+  "Kazakh": "Kazaco",
+  "Khmer": "Khmer",
+  "Korean": "Coreano",
+  "Kurdish": "Curdo",
+  "Kyrgyz": "Kirghize",
+  "Lao": "Lao",
+  "Latin": "Latino",
+  "Latvian": "Lettone",
+  "Lithuanian": "Lituano",
+  "Luxembourgish": "Lussemburghese",
+  "Macedonian": "Macedone",
+  "Malagasy": "Malgascio",
+  "Malay": "Malese",
+  "Malayalam": "Lingua malayalam",
+  "Maltese": "Maltese",
+  "Maori": "Maori",
+  "Marathi": "Marathi",
+  "Mongolian": "Mongolo",
+  "Nepali": "Nepalese",
+  "Norwegian": "Norvegese",
+  "Nyanja": "Nyanja",
+  "Pashto": "Lingua pashtu",
+  "Persian": "Persiano",
+  "Polish": "Polacco",
+  "Portuguese": "Portoghese",
+  "Punjabi": "Punjabi",
+  "Romanian": "Rumeno",
+  "Russian": "Russo",
+  "Samoan": "Samoan",
+  "Scottish Gaelic": "Gaelico scozzese",
+  "Serbian": "Serbo",
+  "Shona": "Shona",
+  "Sindhi": "Sindhi",
+  "Sinhala": "Cingalese",
+  "Slovak": "Slovacco",
+  "Slovenian": "Sloveno",
+  "Somali": "Somalo",
+  "Southern Sotho": "Sotho del Sud",
+  "Spanish": "Spagnolo",
+  "Spanish (Latin America)": "Spagnolo (America latina)",
+  "Sundanese": "Sudanese",
+  "Swahili": "Swahili",
+  "Swedish": "Svedese",
+  "Tajik": "Tajik",
+  "Tamil": "Tamil",
+  "Telugu": "Telugu",
+  "Thai": "Thaï",
+  "Turkish": "Turco",
+  "Ukrainian": "Ucraino",
+  "Urdu": "Urdu",
+  "Uzbek": "Uzbeco",
+  "Vietnamese": "Vietnamese",
+  "Welsh": "Gallese",
+  "Western Frisian": "Frisone occidentale",
+  "Xhosa": "Xhosa",
+  "Yiddish": "Yiddish",
+  "Yoruba": "Yoruba",
+  "Zulu": "Zulu",
+  "`x` years": "`x` anni",
+  "`x` months": "`x` mesi",
+  "`x` weeks": "`x` settimane",
+  "`x` days": "`x` giorni",
+  "`x` hours": "`x` ore",
+  "`x` minutes": "`x` minuti",
+  "`x` seconds": "`x` secondi",
+  "Fallback comments: ": "Commenti alternativi: ",
+  "Popular": "Popolare",
+  "Top": "Top",
+  "About": "A proposito",
+  "Rating: ": "Punteggio: ",
+  "Language: ": "Lingua: ",
+  "Default": "Predefinito",
+  "Music": "Musica",
+  "Gaming": "Videogiochi",
+  "News": "Notizie",
+  "Movies": "Film",
+  "Download": "Scarica",
+  "Download as: ": "Scarica come: ",
+  "%A %B %-d, %Y": "%A %-d %B %Y",
+  "(edited)": "(modificato)",
+  "Youtube permalink of the comment": "Link permanente al commento di YouTube",
+  "`x` marked it with a ❤": "`x` l'ha contrassegnato con un ❤",
+  "Audio mode": "Modalità audio",
+  "Video mode": "Modalità video"
+}
diff --git a/locales/nb_NO.json b/locales/nb_NO.json
index dd575416..d299da2f 100644
--- a/locales/nb_NO.json
+++ b/locales/nb_NO.json
@@ -80,6 +80,14 @@
   "Manage subscriptions": "Behandle abonnementer",
   "Watch history": "Visningshistorikk",
   "Delete account": "Slett konto",
+  "Administrator preferences": "Administratorinnstillinger",
+  "Default homepage: ": "Forvalgt hjemmeside: ",
+  "Feed menu: ": "Flyt-meny: ",
+  "Top enabled? ": "",
+  "CAPTCHA enabled? ": "CAPTCHA påskrudd? ",
+  "Login enabled? ": "Innlogging påskrudd? ",
+  "Registration enabled? ": "Registrering påskrudd? ",
+  "Report statistics? ": "",
   "Save preferences": "Lagre innstillinger",
   "Subscription manager": "Abonnementsbehandler",
   "`x` subscriptions": "`x` abonnementer",
@@ -264,15 +272,17 @@
   "About": "Om",
   "Rating: ": "Vurdering: ",
   "Language: ": "Språk: ",
-  "Default": "",
-  "Music": "",
-  "Gaming": "",
-  "News": "",
-  "Movies": "",
-  "Download": "",
-  "Download as: ": "",
+  "Default": "Forvalg",
+  "Music": "Musikk",
+  "Gaming": "Spill",
+  "News": "Nyheter",
+  "Movies": "Filmer",
+  "Download": "Last ned",
+  "Download as: ": "Last ned som: ",
   "%A %B %-d, %Y": "",
-  "(edited)": "",
-  "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "(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 0969b4cc..2224d326 100644
--- a/locales/nl.json
+++ b/locales/nl.json
@@ -80,6 +80,14 @@
   "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? ": "",
+  "Report statistics? ": "",
   "Save preferences": "Opslaan voorkeuren",
   "Subscription manager": "Abonnees beheerder",
   "`x` subscriptions": "`x` abonnees",
@@ -274,5 +282,7 @@
   "%A %B %-d, %Y": "",
   "(edited)": "",
   "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "`x` marked it with a ❤": "",
+  "Audio mode": "",
+  "Video mode": ""
 }
diff --git a/locales/pl.json b/locales/pl.json
index 3b46559b..d9a21cf1 100644
--- a/locales/pl.json
+++ b/locales/pl.json
@@ -29,7 +29,7 @@
   "Delete account?": "Usunąć konto?",
   "History": "Historia",
   "Previous page": "Poprzednia strona",
-  "An alternative front-end to YouTube": "",
+  "An alternative front-end to YouTube": "Alternatywny front-end dla YouTube",
   "JavaScript license information": "Informacja o licencji JavaScript",
   "source": "źródło",
   "Login": "Zaloguj",
@@ -80,6 +80,14 @@
   "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? ": "",
+  "Report statistics? ": "",
   "Save preferences": "Zapisz preferencje",
   "Subscription manager": "Manager subskrybcji",
   "`x` subscriptions": "`x` subskrybcji",
@@ -145,107 +153,107 @@
   "Invalid token": "Niepoprawny token",
   "Invalid user": "Niepoprawny użytkownik",
   "Token is expired, please try again": "Token wygasł, spróbuj ponownie",
-  "English": "",
-  "English (auto-generated)": "",
+  "English": "angielski",
+  "English (auto-generated)": "angielski (automatycznie generowane)",
   "Afrikaans": "",
-  "Albanian": "",
+  "Albanian": "albański",
   "Amharic": "",
-  "Arabic": "",
+  "Arabic": "arabski",
   "Armenian": "",
   "Azerbaijani": "",
   "Bangla": "",
   "Basque": "",
-  "Belarusian": "",
-  "Bosnian": "",
-  "Bulgarian": "",
-  "Burmese": "",
-  "Catalan": "",
+  "Belarusian": "białoruski",
+  "Bosnian": "bośniacki",
+  "Bulgarian": "bułgarski",
+  "Burmese": "birmański",
+  "Catalan": "kataloński",
   "Cebuano": "",
-  "Chinese (Simplified)": "",
-  "Chinese (Traditional)": "",
-  "Corsican": "",
-  "Croatian": "",
-  "Czech": "",
-  "Danish": "",
-  "Dutch": "",
-  "Esperanto": "",
-  "Estonian": "",
-  "Filipino": "",
-  "Finnish": "",
-  "French": "",
-  "Galician": "",
-  "Georgian": "",
-  "German": "",
-  "Greek": "",
+  "Chinese (Simplified)": "chiński (uproszczony)",
+  "Chinese (Traditional)": "chiński (tradycyjny)",
+  "Corsican": "korsykański",
+  "Croatian": "chorwacki",
+  "Czech": "czeski",
+  "Danish": "duński",
+  "Dutch": "holenderski",
+  "Esperanto": "esperanto",
+  "Estonian": "estoński",
+  "Filipino": "filipiński",
+  "Finnish": "fiński",
+  "French": "francuski",
+  "Galician": "galicyjski",
+  "Georgian": "gruziński",
+  "German": "niemiecki",
+  "Greek": "grecki",
   "Gujarati": "",
   "Haitian Creole": "",
   "Hausa": "",
-  "Hawaiian": "",
-  "Hebrew": "",
-  "Hindi": "",
+  "Hawaiian": "hawajski",
+  "Hebrew": "hebrajski",
+  "Hindi": "hindi",
   "Hmong": "",
-  "Hungarian": "",
-  "Icelandic": "",
+  "Hungarian": "węgierski",
+  "Icelandic": "islandzki",
   "Igbo": "",
-  "Indonesian": "",
-  "Irish": "",
-  "Italian": "",
-  "Japanese": "",
-  "Javanese": "",
+  "Indonesian": "indonezyjski",
+  "Irish": "irlandzki",
+  "Italian": "włoski",
+  "Japanese": "japoński",
+  "Javanese": "jawajski",
   "Kannada": "",
-  "Kazakh": "",
+  "Kazakh": "kazachski",
   "Khmer": "",
-  "Korean": "",
-  "Kurdish": "",
-  "Kyrgyz": "",
+  "Korean": "koreański",
+  "Kurdish": "kurdyjski",
+  "Kyrgyz": "kirgiski",
   "Lao": "",
-  "Latin": "",
-  "Latvian": "",
-  "Lithuanian": "",
-  "Luxembourgish": "",
-  "Macedonian": "",
-  "Malagasy": "",
-  "Malay": "",
+  "Latin": "łaciński",
+  "Latvian": "łotewski",
+  "Lithuanian": "litewski",
+  "Luxembourgish": "luksemburski",
+  "Macedonian": "macedoński",
+  "Malagasy": "malgaski",
+  "Malay": "malajski",
   "Malayalam": "",
-  "Maltese": "",
+  "Maltese": "maltański",
   "Maori": "",
   "Marathi": "",
-  "Mongolian": "",
-  "Nepali": "",
-  "Norwegian": "",
+  "Mongolian": "mongolski",
+  "Nepali": "nepalski",
+  "Norwegian": "norweski",
   "Nyanja": "",
   "Pashto": "",
-  "Persian": "",
-  "Polish": "",
-  "Portuguese": "",
+  "Persian": "perski",
+  "Polish": "polski",
+  "Portuguese": "portugalski",
   "Punjabi": "",
-  "Romanian": "",
-  "Russian": "",
+  "Romanian": "rumuński",
+  "Russian": "rosyjski",
   "Samoan": "",
   "Scottish Gaelic": "",
-  "Serbian": "",
+  "Serbian": "serbski",
   "Shona": "",
   "Sindhi": "",
   "Sinhala": "",
-  "Slovak": "",
-  "Slovenian": "",
-  "Somali": "",
+  "Slovak": "słowacki",
+  "Slovenian": "słoweński",
+  "Somali": "somalijski",
   "Southern Sotho": "",
-  "Spanish": "",
-  "Spanish (Latin America)": "",
+  "Spanish": "hiszpański",
+  "Spanish (Latin America)": "hiszpański (ameryka łacińska)",
   "Sundanese": "",
   "Swahili": "",
-  "Swedish": "",
+  "Swedish": "szwedzki",
   "Tajik": "",
   "Tamil": "",
   "Telugu": "",
-  "Thai": "",
-  "Turkish": "",
-  "Ukrainian": "",
+  "Thai": "tajski",
+  "Turkish": "turecki",
+  "Ukrainian": "ukraiński",
   "Urdu": "",
-  "Uzbek": "",
-  "Vietnamese": "",
-  "Welsh": "",
+  "Uzbek": "uzbecki",
+  "Vietnamese": "wietnamski",
+  "Welsh": "walijski",
   "Western Frisian": "",
   "Xhosa": "",
   "Yiddish": "",
@@ -258,21 +266,23 @@
   "`x` hours": "`x` godzin",
   "`x` minutes": "`x` minut",
   "`x` seconds": "`x` sekund",
-  "Fallback comments: ": "",
-  "Popular": "",
-  "Top": "",
-  "About": "",
-  "Rating: ": "",
-  "Language: ": "",
+  "Fallback comments: ": "Zastępcze komentarze: ",
+  "Popular": "Popularne",
+  "Top": "Na czasie",
+  "About": "Informacje",
+  "Rating: ": "Ocena: ",
+  "Language: ": "Język: ",
   "Default": "",
-  "Music": "",
-  "Gaming": "",
-  "News": "",
-  "Movies": "",
-  "Download": "",
-  "Download as: ": "",
+  "Music": "Muzyka",
+  "Gaming": "Gry",
+  "News": "Wiadomości",
+  "Movies": "Filmy",
+  "Download": "Pobierz",
+  "Download as: ": "Pobierz jako: ",
   "%A %B %-d, %Y": "",
-  "(edited)": "",
-  "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "(edited)": "(edytowany)",
+  "Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube",
+  "`x` marked it with a ❤": "",
+  "Audio mode": "Tryb audio",
+  "Video mode": "Tryb wideo"
 }
diff --git a/locales/ru.json b/locales/ru.json
index ec62cadb..a840a869 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -82,6 +82,14 @@
   "Manage subscriptions": "Управление подписками",
   "Watch history": "История просмотров",
   "Delete account": "Удалить аккаунт",
+  "Administrator preferences": "",
+  "Default homepage: ": "",
+  "Feed menu: ": "",
+  "Top enabled? ": "",
+  "CAPTCHA enabled? ": "",
+  "Login enabled? ": "",
+  "Registration enabled? ": "",
+  "Report statistics? ": "",
   "Save preferences": "Сохранить настройки",
   "Subscription manager": "Менеджер подписок",
   "`x` subscriptions": "`x` подписок",
@@ -159,103 +167,103 @@
   "Arabic": "Арабский",
   "Armenian": "Армянский",
   "Azerbaijani": "Азербайджанский",
-  "Bangla": "",
-  "Basque": "",
-  "Belarusian": "",
-  "Bosnian": "",
-  "Bulgarian": "",
-  "Burmese": "",
-  "Catalan": "",
-  "Cebuano": "",
-  "Chinese (Simplified)": "",
-  "Chinese (Traditional)": "",
-  "Corsican": "",
-  "Croatian": "",
-  "Czech": "",
-  "Danish": "",
-  "Dutch": "",
-  "Esperanto": "",
-  "Estonian": "",
-  "Filipino": "",
-  "Finnish": "",
-  "French": "",
-  "Galician": "",
-  "Georgian": "",
-  "German": "",
-  "Greek": "",
-  "Gujarati": "",
-  "Haitian Creole": "",
-  "Hausa": "",
-  "Hawaiian": "",
-  "Hebrew": "",
-  "Hindi": "",
-  "Hmong": "",
-  "Hungarian": "",
-  "Icelandic": "",
-  "Igbo": "",
-  "Indonesian": "",
-  "Irish": "",
-  "Italian": "",
-  "Japanese": "",
-  "Javanese": "",
-  "Kannada": "",
-  "Kazakh": "",
-  "Khmer": "",
-  "Korean": "",
-  "Kurdish": "",
-  "Kyrgyz": "",
-  "Lao": "",
-  "Latin": "",
-  "Latvian": "",
-  "Lithuanian": "",
-  "Luxembourgish": "",
-  "Macedonian": "",
-  "Malagasy": "",
-  "Malay": "",
-  "Malayalam": "",
-  "Maltese": "",
-  "Maori": "",
-  "Marathi": "",
-  "Mongolian": "",
-  "Nepali": "",
-  "Norwegian": "",
-  "Nyanja": "",
-  "Pashto": "",
-  "Persian": "",
-  "Polish": "",
-  "Portuguese": "",
-  "Punjabi": "",
-  "Romanian": "",
-  "Russian": "",
-  "Samoan": "",
-  "Scottish Gaelic": "",
-  "Serbian": "",
-  "Shona": "",
-  "Sindhi": "",
-  "Sinhala": "",
-  "Slovak": "",
-  "Slovenian": "",
-  "Somali": "",
-  "Southern Sotho": "",
-  "Spanish": "",
-  "Spanish (Latin America)": "",
-  "Sundanese": "",
-  "Swahili": "",
-  "Swedish": "",
-  "Tajik": "",
-  "Tamil": "",
-  "Telugu": "",
-  "Thai": "",
-  "Turkish": "",
-  "Ukrainian": "",
-  "Urdu": "",
-  "Uzbek": "",
-  "Vietnamese": "",
-  "Welsh": "",
-  "Western Frisian": "",
-  "Xhosa": "",
-  "Yiddish": "",
-  "Yoruba": "",
+  "Bangla": "Бенгальский",
+  "Basque": "Баскский",
+  "Belarusian": "Белорусский",
+  "Bosnian": "Боснийский",
+  "Bulgarian": "Болгарский",
+  "Burmese": "Бирманский",
+  "Catalan": "Каталонский",
+  "Cebuano": "Себуанский",
+  "Chinese (Simplified)": "Китайский (упрощенный)",
+  "Chinese (Traditional)": "Китайский (традиционный)",
+  "Corsican": "Корсиканский",
+  "Croatian": "Хорватский",
+  "Czech": "Чешский",
+  "Danish": "Датский",
+  "Dutch": "Нидерландский",
+  "Esperanto": "Эсперанто",
+  "Estonian": "Эстонский",
+  "Filipino": "Филиппинский",
+  "Finnish": "Финский",
+  "French": "Французский",
+  "Galician": "Галисийский",
+  "Georgian": "Грузинский",
+  "German": "Немецкий",
+  "Greek": "Греческий",
+  "Gujarati": "Гуджаратский",
+  "Haitian Creole": "Гаит. креольский",
+  "Hausa": "Хауса",
+  "Hawaiian": "Гавайский",
+  "Hebrew": "Иврит",
+  "Hindi": "Хинди",
+  "Hmong": "Хмонг (мяо)",
+  "Hungarian": "Венгерский",
+  "Icelandic": "Исландский",
+  "Igbo": "Игбо",
+  "Indonesian": "Индонезийский",
+  "Irish": "Ирландский",
+  "Italian": "Итальянский",
+  "Japanese": "Японский",
+  "Javanese": "Яванский",
+  "Kannada": "Каннада",
+  "Kazakh": "Казахский",
+  "Khmer": "Кхмерский",
+  "Korean": "Корейский",
+  "Kurdish": "Курдский",
+  "Kyrgyz": "Киргизский",
+  "Lao": "Лаосский",
+  "Latin": "Латинский",
+  "Latvian": "Латышский",
+  "Lithuanian": "Литовский",
+  "Luxembourgish": "Люксембургский",
+  "Macedonian": "Македонский",
+  "Malagasy": "Малагасийский",
+  "Malay": "Малайский",
+  "Malayalam": "Малаялам",
+  "Maltese": "Мальтийский",
+  "Maori": "Маори",
+  "Marathi": "Маратхи",
+  "Mongolian": "Монгольская",
+  "Nepali": "Непальский",
+  "Norwegian": "Норвежский",
+  "Nyanja": "Ньянджа",
+  "Pashto": "Пушту",
+  "Persian": "Персидский",
+  "Polish": "Польский",
+  "Portuguese": "Португальский",
+  "Punjabi": "Панджаби",
+  "Romanian": "Румынский",
+  "Russian": "Русский",
+  "Samoan": "Самоанский",
+  "Scottish Gaelic": "Шотландский (гэльский)",
+  "Serbian": "Сербский",
+  "Shona": "Шона",
+  "Sindhi": "Синдхи",
+  "Sinhala": "Сингальский",
+  "Slovak": "Словацкий",
+  "Slovenian": "Словенский",
+  "Somali": "Сомалийский",
+  "Southern Sotho": "Сесото (южный сото)",
+  "Spanish": "Испанский",
+  "Spanish (Latin America)": "Испанский (Латинская Америка)",
+  "Sundanese": "Сунданский",
+  "Swahili": "Суахили",
+  "Swedish": "Шведский",
+  "Tajik": "Таджикский",
+  "Tamil": "Тамильский",
+  "Telugu": "Телугу",
+  "Thai": "Тайский",
+  "Turkish": "Турецкий",
+  "Ukrainian": "Украинский",
+  "Urdu": "Урду",
+  "Uzbek": "Узбекский",
+  "Vietnamese": "Вьетнамский",
+  "Welsh": "Валлийский",
+  "Western Frisian": "Западнофризский",
+  "Xhosa": "Коса",
+  "Yiddish": "Идиш",
+  "Yoruba": "Йоруба",
   "Zulu": "Зулусский",
   "`x` years": "`x` лет",
   "`x` months": "`x` месяцев",
@@ -277,8 +285,10 @@
   "Movies": "Фильмы",
   "Download": "Скачать",
   "Download as: ": "Скачать как: ",
-  "%A %B %-d, %Y": "",
-  "(edited)": "",
-  "Youtube permalink of the comment": "",
-  "`x` marked it with a ❤": ""
+  "%A %B %-d, %Y": "%-d %B %Y, %A",
+  "(edited)": "(изменено)",
+  "Youtube permalink of the comment": "Прямая ссылка на YouTube",
+  "`x` marked it with a ❤": "❤ от автора канала \"`x`\"",
+  "Audio mode": "Аудио режим",
+  "Video mode": "Видео режим"
 }
diff --git a/screenshots/01_player.png b/screenshots/01_player.png
new file mode 100644
index 00000000..63e6dbba
Binary files /dev/null and b/screenshots/01_player.png differ
diff --git a/screenshots/02_preferences.png b/screenshots/02_preferences.png
new file mode 100644
index 00000000..1cd29add
Binary files /dev/null and b/screenshots/02_preferences.png differ
diff --git a/screenshots/03_subscriptions.png b/screenshots/03_subscriptions.png
new file mode 100644
index 00000000..f5cfa8c1
Binary files /dev/null and b/screenshots/03_subscriptions.png differ
diff --git a/screenshots/04_description.png b/screenshots/04_description.png
new file mode 100644
index 00000000..f8ec2564
Binary files /dev/null and b/screenshots/04_description.png differ
diff --git a/screenshots/05_preferences.png b/screenshots/05_preferences.png
new file mode 100644
index 00000000..dc6d4a42
Binary files /dev/null and b/screenshots/05_preferences.png differ
diff --git a/screenshots/06_subscriptions.png b/screenshots/06_subscriptions.png
new file mode 100644
index 00000000..0da82f55
Binary files /dev/null and b/screenshots/06_subscriptions.png differ
diff --git a/shard.yml b/shard.yml
index e0fea2ee..7edab354 100644
--- a/shard.yml
+++ b/shard.yml
@@ -1,5 +1,5 @@
 name: invidious
-version: 0.14.0
+version: 0.14.1
 
 authors:
   - Omar Roth 
                   
#{recode_length_seconds(video["lengthSeconds"].as_i)}
+#{video["title"]}
#{video["author"]} diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 220a0ef7..9f844ce6 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -161,117 +161,6 @@ def produce_playlist_url(id, index) return url end -def produce_channel_playlists_url(ucid, cursor, sort = "newest") - cursor = Base64.urlsafe_encode(cursor, false) - - meta = IO::Memory.new - meta.write(Bytes[0x12, 0x09]) - meta.print("playlists") - - # TODO: Look at 0x01, 0x00 - case sort - when "oldest", "oldest_created" - meta.write(Bytes[0x18, 0x02]) - when "newest", "newest_created" - meta.write(Bytes[0x18, 0x03]) - when "last", "last_added" - meta.write(Bytes[0x18, 0x04]) - end - - meta.write(Bytes[0x20, 0x01]) - meta.write(Bytes[0x30, 0x02]) - meta.write(Bytes[0x38, 0x01]) - meta.write(Bytes[0x60, 0x01]) - meta.write(Bytes[0x6a, 0x00]) - - meta.write(Bytes[0x7a, cursor.size]) - meta.print(cursor) - - meta.write(Bytes[0xb8, 0x01, 0x00]) - - meta.rewind - meta = Base64.urlsafe_encode(meta.to_slice) - meta = URI.escape(meta) - - continuation = IO::Memory.new - continuation.write(Bytes[0x12, ucid.size]) - continuation.print(ucid) - - continuation.write(Bytes[0x1a]) - continuation.write(write_var_int(meta.size)) - continuation.print(meta) - - continuation.rewind - continuation = continuation.gets_to_end - - wrapper = IO::Memory.new - wrapper.write(Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02]) - wrapper.write(write_var_int(continuation.size)) - wrapper.print(continuation) - wrapper.rewind - - wrapper = Base64.urlsafe_encode(wrapper.to_slice) - wrapper = URI.escape(wrapper) - - url = "/browse_ajax?continuation=#{wrapper}&gl=US&hl=en" - - return url -end - -def extract_channel_playlists_cursor(url) - wrapper = HTTP::Params.parse(URI.parse(url).query.not_nil!)["continuation"] - - wrapper = URI.unescape(wrapper) - wrapper = Base64.decode(wrapper) - - # 0xe2 0xa9 0x85 0xb2 0x02 - wrapper += 5 - - continuation_size = read_var_int(wrapper[0, 4]) - wrapper += write_var_int(continuation_size).size - continuation = wrapper[0, continuation_size] - - # 0x12 - continuation += 1 - ucid_size = continuation[0] - continuation += 1 - ucid = continuation[0, ucid_size] - continuation += ucid_size - - # 0x1a - continuation += 1 - meta_size = read_var_int(continuation[0, 4]) - continuation += write_var_int(meta_size).size - meta = continuation[0, meta_size] - continuation += meta_size - - meta = String.new(meta) - meta = URI.unescape(meta) - meta = Base64.decode(meta) - - # 0x12 0x09 playlists - meta += 11 - - until meta[0] == 0x7a - tag = read_var_int(meta[0, 4]) - meta += write_var_int(tag).size - value = meta[0] - meta += 1 - end - - # 0x7a - meta += 1 - cursor_size = meta[0] - meta += 1 - cursor = meta[0, cursor_size] - - cursor = String.new(cursor) - cursor = URI.unescape(cursor) - cursor = Base64.decode_string(cursor) - - return cursor -end - def fetch_playlist(plid, locale) client = make_client(YT_URL) @@ -345,7 +234,10 @@ def template_playlist(playlist) html += <<-END_HTML
#{recode_length_seconds(video["lengthSeconds"].as_i)}
+#{video["title"]}
               #{video["author"]}
diff --git a/src/invidious/search.cr b/src/invidious/search.cr
index ce29abf2..ec97cf85 100644
--- a/src/invidious/search.cr
+++ b/src/invidious/search.cr
@@ -188,7 +188,7 @@ def produce_search_params(sort : String = "relevance", date : String = "", conte
             end
   end
 
-  if body.size > 0
+  if !body.empty?
     token = head + "\x12" + body.size.unsafe_chr + body
   else
     token = head
diff --git a/src/invidious/signatures.cr b/src/invidious/signatures.cr
index b2ed89d2..8b760398 100644
--- a/src/invidious/signatures.cr
+++ b/src/invidious/signatures.cr
@@ -39,7 +39,12 @@ def fetch_decrypt_function(id = "CvFH_6DNRCY")
   return decrypt_function
 end
 
-def decrypt_signature(a, code)
+def decrypt_signature(fmt, code)
+  if !fmt["s"]?
+    return ""
+  end
+
+  a = fmt["s"]
   a = a.split("")
 
   code.each do |item|
@@ -53,7 +58,8 @@ def decrypt_signature(a, code)
     end
   end
 
-  return a.join("")
+  signature = a.join("")
+  return "{fmt["sp"]?}=#{signature}"
 end
 
 def splice(a, b)
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index d45c5af4..42468228 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -12,7 +12,6 @@ class User
   end
 
   add_mapping({
-    id:            Array(String),
     updated:       Time,
     notifications: Array(String),
     subscriptions: Array(String),
@@ -126,49 +125,55 @@ class Preferences
 end
 
 def get_user(sid, headers, db, refresh = true)
-  if db.query_one?("SELECT EXISTS (SELECT true FROM users WHERE $1 = ANY(id))", sid, as: Bool)
-    user = db.query_one("SELECT * FROM users WHERE $1 = ANY(id)", sid, as: User)
+  if email = db.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String)
+    user = db.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
 
     if refresh && Time.now - user.updated > 1.minute
-      user = fetch_user(sid, headers, db)
+      user, sid = fetch_user(sid, headers, db)
       user_array = user.to_a
 
-      user_array[5] = user_array[5].to_json
+      user_array[4] = user_array[4].to_json
       args = arg_array(user_array)
 
       db.exec("INSERT INTO users VALUES (#{args}) \
-      ON CONFLICT (email) DO UPDATE SET id = users.id || $1, updated = $2, subscriptions = $4", user_array)
+      ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", user_array)
+
+      db.exec("INSERT INTO session_ids VALUES ($1,$2,$3) \
+      ON CONFLICT (id) DO NOTHING", sid, user.email, Time.now)
 
       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 = '#{user.email}')::text[]) \
+        ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \
         ORDER BY published DESC;")
       rescue ex
       end
     end
   else
-    user = fetch_user(sid, headers, db)
+    user, sid = fetch_user(sid, headers, db)
     user_array = user.to_a
 
-    user_array[5] = user_array[5].to_json
+    user_array[4] = user_array[4].to_json
     args = arg_array(user.to_a)
 
     db.exec("INSERT INTO users VALUES (#{args}) \
-    ON CONFLICT (email) DO UPDATE SET id = users.id || $1, updated = $2, subscriptions = $4", user_array)
+    ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", user_array)
+
+    db.exec("INSERT INTO session_ids VALUES ($1,$2,$3) \
+    ON CONFLICT (id) DO NOTHING", sid, user.email, Time.now)
 
     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 = '#{user.email}')::text[]) \
+      ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \
       ORDER BY published DESC;")
     rescue ex
     end
   end
 
-  return user
+  return user, sid
 end
 
 def fetch_user(sid, headers, db)
@@ -196,17 +201,17 @@ def fetch_user(sid, headers, db)
 
   token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
 
-  user = User.new([sid], Time.now, [] of String, channels, email, DEFAULT_USER_PREFERENCES, nil, token, [] of String)
-  return user
+  user = User.new(Time.now, [] of String, channels, email, DEFAULT_USER_PREFERENCES, nil, token, [] of String)
+  return user, sid
 end
 
 def create_user(sid, email, password)
   password = Crypto::Bcrypt::Password.create(password, cost: 10)
   token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
 
-  user = User.new([sid], Time.now, [] of String, [] of String, email, DEFAULT_USER_PREFERENCES, password.to_s, token, [] of String)
+  user = User.new(Time.now, [] of String, [] of String, email, DEFAULT_USER_PREFERENCES, password.to_s, token, [] of String)
 
-  return user
+  return user, sid
 end
 
 def create_response(user_id, operation, key, db, expire = 6.hours)
@@ -242,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/videos.cr b/src/invidious/videos.cr
index 2045dd2c..1e4679fc 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -263,7 +263,7 @@ class Video
   end
 
   def keywords
-    keywords = self.player_response["videoDetails"]["keywords"]?.try &.as_a
+    keywords = self.player_response["videoDetails"]?.try &.["keywords"]?.try &.as_a
     keywords ||= [] of String
 
     return keywords
@@ -271,9 +271,51 @@ class Video
 
   def fmt_stream(decrypt_function)
     streams = [] of HTTP::Params
-    self.info["url_encoded_fmt_stream_map"].split(",") do |string|
-      if !string.empty?
-        streams << HTTP::Params.parse(string)
+
+    if fmt_streams = self.player_response["streamingData"]?.try &.["formats"]?
+      fmt_streams.as_a.each do |fmt_stream|
+        if !fmt_stream.as_h?
+          next
+        end
+
+        fmt = {} of String => String
+
+        fmt["lmt"] = fmt_stream["lastModified"]?.try &.as_s || "0"
+        fmt["projection_type"] = "1"
+        fmt["type"] = fmt_stream["mimeType"].as_s
+        fmt["clen"] = fmt_stream["contentLength"]?.try &.as_s || "0"
+        fmt["bitrate"] = fmt_stream["bitrate"]?.try &.as_i.to_s || "0"
+        fmt["itag"] = fmt_stream["itag"].as_i.to_s
+        fmt["url"] = fmt_stream["url"].as_s
+        fmt["quality"] = fmt_stream["quality"].as_s
+
+        if fmt_stream["width"]?
+          fmt["size"] = "#{fmt_stream["width"]}x#{fmt_stream["height"]}"
+          fmt["height"] = fmt_stream["height"].as_i.to_s
+        end
+
+        if fmt_stream["fps"]?
+          fmt["fps"] = fmt_stream["fps"].as_i.to_s
+        end
+
+        if fmt_stream["qualityLabel"]?
+          fmt["quality_label"] = fmt_stream["qualityLabel"].as_s
+        end
+
+        params = HTTP::Params.new
+        fmt.each do |key, value|
+          params[key] = value
+        end
+
+        streams << params
+      end
+
+      streams.sort_by! { |stream| stream["height"].to_i }.reverse!
+    elsif fmt_stream = self.info["url_encoded_fmt_stream_map"]?
+      fmt_stream.split(",").each do |string|
+        if !string.empty?
+          streams << HTTP::Params.parse(string)
+        end
       end
     end
 
@@ -286,10 +328,8 @@ class Video
       end
     end
 
-    if streams[0]? && streams[0]["s"]?
-      streams.each do |fmt|
-        fmt["url"] += "&signature=" + decrypt_signature(fmt["s"], decrypt_function)
-      end
+    streams.each do |fmt|
+      fmt["url"] += decrypt_signature(fmt, decrypt_function)
     end
 
     return streams
@@ -298,80 +338,54 @@ class Video
   def adaptive_fmts(decrypt_function)
     adaptive_fmts = [] of HTTP::Params
 
-    if self.info.has_key?("adaptive_fmts")
-      self.info["adaptive_fmts"].split(",") do |string|
-        adaptive_fmts << HTTP::Params.parse(string)
-      end
-    elsif self.info.has_key?("dashmpd")
-      client = make_client(YT_URL)
-      response = client.get(self.info["dashmpd"])
-      document = XML.parse_html(response.body)
-
-      document.xpath_nodes(%q(//adaptationset)).each do |adaptation_set|
-        mime_type = adaptation_set["mimetype"]
-
-        document.xpath_nodes(%q(.//representation)).each do |representation|
-          codecs = representation["codecs"]
-          itag = representation["id"]
-          bandwidth = representation["bandwidth"]
-          url = representation.xpath_node(%q(.//baseurl)).not_nil!.content
-
-          clen = url.match(/clen\/(?