diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79c8c1264..a14a1bf2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,3 @@ -# API Keys - -When you are testing and working on FreeTube, PLEASE use your own API Key. The keys included in the project are in use by the userbase and testing can cause these keys to max out. Please do not risk degrading the experience for other users and use your own key if at all possible. Thank you for your cooperation. - # Code Contributions Please follow these guidlines before sending your pull request and making contributions. diff --git a/README.md b/README.md index 77c8a9ba7..e17fd0df0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

FreeTube is an open source desktop YouTube player built with privacy in mind. -Use YouTube without advertisements and prevent Google tracking from you with their cookies and JavaScript. +Use YouTube without advertisements and prevent Google from tracking you with their cookies and JavaScript. Available for Windows, Mac & Linux thanks to Electron. Please note that FreeTube is currently in Beta. While it should work well for @@ -32,7 +32,7 @@ to hide your IP while using FreeTube. ## Features * Watch videos without ads * Use YouTube without Google tracking you using cookies and JavaScript -* Make API calls through the Tor network +* Tor / Proxy Support * Subscribe to channels without an account * Local subscriptions, history, and saved videos * Export & import subscriptions diff --git a/locales/de.json b/locales/de.json index ad867fa04..3915e0941 100644 --- a/locales/de.json +++ b/locales/de.json @@ -42,9 +42,9 @@ "Latest Subscriptions": "Neueste Abonnements", "Save Video": "Video Speichern", "Remove Saved Video": "Gespeichertes Video entfernen", - "Open in YouTube": "Öffnen in YouTube", + "Open in YouTube": "In YouTube öffnen", "Copy YouTube Link": "YouTube Link kopieren", - "Open in HookTube": "Öffnen in HookTube", + "Open in HookTube": "In HookTube öffnen", "Copy HookTube Link": "HookTube Link kopieren", "URL has been copied to the clipboard": "Die URL wurde in die Zwischenablage kopiert", "Found valid URL for 480p, but returned a 404. Video type might be available in the future.": "Es wurde eine gültige URL für 480p gefunden, aber eine 404 zurückgegeben. Der Videotyp könnte in Zukunft verfügbar sein.", @@ -53,7 +53,7 @@ "View": "Wiedergabe", "Views": "Wiedergaben", "Subscribe": "Abonnieren", - "Unsubscribe": "Abmelden", + "Unsubscribe": "Abo beenden", "Published on": "Veröffentlicht am", "Jan": "Jan", "Feb": "Feb", @@ -72,7 +72,7 @@ "Recommendations": "Empfehlungen", "Latest Subscriptions": "Neueste Abonnements", "Getting Subscriptions. Please wait...": "Rufe Abonnements ab. Bitte warten...", - "Your Subscription list is currently empty. Start adding subscriptions to see them here.": "Ihre Abonnementsliste ist momentan lehr. Füge Abonnements hinzu, um sie hier zu sehen.", + "Your Subscription list is currently empty. Start adding subscriptions to see them here.": "Deine Abonnementsliste ist momentan leer. Füge Abonnements hinzu, um sie hier zu sehen.", "Saved Videos": "Gespeichterte Videos", "Watch History": "Wiedergabeverlauf", "API Key": "API Schlüssel", @@ -85,7 +85,7 @@ "Clear Saved Videos": "Gespeicherte Videos löschen", "Are you sure you want to remove all saved videos?": "Möchtest du deine gespeicherten Videos wirklich löschen?", "Clear Subscriptions": "Abonnements löschen", - "Are you sure you want to remove all subscriptions?": "Möchten Sie Ihrer Abonnements wirklich löschen?", + "Are you sure you want to remove all subscriptions?": "Möchtest du deine Abonnements wirklich löschen?", "Save Settings": "Einstellungen Speichern", "Yes": "Ja", "No": "Nein", diff --git a/locales/pt-PT.json b/locales/pt-PT.json new file mode 100644 index 000000000..d0e74962d --- /dev/null +++ b/locales/pt-PT.json @@ -0,0 +1,95 @@ +{ + "File": "Ficheiro", + "Quit": "Sair", + "Edit": "Editar", + "Undo": "Desfazer", + "Redo": "Refazer", + "Cut": "Cortar", + "Copy": "Copiar", + "Paste": "Colar", + "Delete": "Apagar", + "Select all": "Selecionar tudo", + "View": "Ver", + "Reload": "Recarregar", + "Force Reload": "Forçar a recarregar", + "Toggle Developer Tools": "Inspecionar", + "Actual size": "100% zoom", + "Zoom in": "Aumentar zoom", + "Zoom out": "Diminuir zoom", + "Toggle fullscreen": "Ecrã inteiro", + "Window": "Janela", + "Minimize": "Minimizar", + "Close": "Fechar", + "FreeTube": "FreeTube", + "Subscriptions": "Subscrições", + "Featured": "Em destaque", + "Most Popular": "Popular", + "Saved": "Guardado", + "Playlists": "Listas", + "History": "Histórico", + "Settings": "Definições", + "About": "Sobre", + "Search / Go to URL": "Pesquisar / Ir para URL", + "Search Results": "Resultados", + "Subscriber": "Subscritor", + "Subscribers": "Subscritores", + "Video": "Vídeo", + "Videos": "Vídeos", + "View Full Playlist": "Ver Lista Completa", + "Live Now": "Ao Vivo", + "Fetch more results": "Buscar mais resultados", + "Fetching results. Please wait": "A buscar mais resultados. Por favor espere", + "Latest Subscriptions": "Subscrições recentes", + "Save Video": "Guardar vídeo", + "Remove Saved Video": "Remover vídeo guardado", + "Open in YouTube": "Abrir no Youtube", + "Copy YouTube Link": "Copiar link para Youtube", + "Open in HookTube": "Abrir no HookTube", + "Copy HookTube Link": "Copiar link para HookTube", + "URL has been copied to the clipboard": "O URL foi copiado", + "Found valid URL for 480p, but returned a 404. Video type might be available in the future.": "Foi encontrado um URL válido para 480p, mas devolveu um erro 404. Este tipo de vídeo pode estar disponível no futuro.", + "Save": "Guardar", + "Mini Player": "Mini Janela", + "View": "Ver", + "Views": "Visualizacões", + "Subscribe": "Subscrever", + "Unsubscribe": "Cancelar subscrição", + "Published on": "Publicado em", + "Jan": "Jan", + "Feb": "Fev", + "Mar": "Mar", + "Apr": "Abr", + "May": "Mai", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Ago", + "Sep": "Set", + "Oct": "Out", + "Nov": "Nov", + "Dec": "Dez", + "Show Comments": "Mostrar comentários", + "Max of 100": "Máximo de 100", + "Recommendations": "Recomendado", + "Latest Subscriptions": "Subscrições recentes", + "Getting Subscriptions. Please wait...": "A buscar subscrições. Por favor espere...", + "Your Subscription list is currently empty. Start adding subscriptions to see them here.": "De momento não tens subscrições. Adiciona algumas para as ver aqui.", + "Saved Videos": "Vídeos guardados", + "Watch History": "Ver histórico", + "API Key": "Chave API", + "Set API Key: Leave blank to use default": "Definir chave API: Deixe em branco para usar padrão", + "Use Dark Theme": "Usar Tema Escuro", + "Import Subscriptions": "Importar Subscrições", + "Export Subscriptions": "Exportar Subcrições", + "Clear History": "Apagar Histórico", + "Are you sure you want to delete your history?": "Tens certeza que queres apagar o teu histórico?", + "Clear Saved Videos": "Apagar vídeos guardados", + "Are you sure you want to remove all saved videos?": "Tens a certeza que queres apagar todos os teus vídeos guardados?", + "Clear Subscriptions": "Cancelar Subscrições", + "Are you sure you want to remove all subscriptions?": "Tens a certeza que queres cancelar todas as tuas subscrições?", + "Save Settings": "Guardar definições", + "Yes": "Sim", + "No": "Não", + "Beta": "Beta", + "This software is FOSS and released under the GNU Public License v3+.": "Este software é FOSS e lançado sobre a GPLv3+.", + "Found a bug? Want to suggest a feature? Want to help out? Check out our GitHub page. Pull requests are welcome.": "Encontraste um defeito? Queres fazer uma sugestão? Queres ajudar? Vê a nossa página no GitHub. Contribuições são agradecidas." +} diff --git a/package-lock.json b/package-lock.json index 06250c9a4..b4459f57d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "FreeTube", - "version": "0.4.1", + "version": "0.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -125,6 +125,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "dev": true, "requires": { "es6-promisify": "^5.0.0" } @@ -4441,12 +4442,14 @@ "es6-promise": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, "requires": { "es6-promise": "^4.0.3" } @@ -4623,11 +4626,6 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -5402,8 +5400,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/imul/-/imul-1.0.1.tgz", "integrity": "sha1-nVhnFh6LPelsLDjV3HyxAvNeKsk=", - "dev": true, - "optional": true + "dev": true }, "indent-string": { "version": "2.1.0", @@ -5546,11 +5543,6 @@ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, "is-absolute-url": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", @@ -5647,8 +5639,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true, - "optional": true + "dev": true }, "is-relative-path": { "version": "1.0.2", @@ -6782,8 +6773,7 @@ "version": "2.11.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", - "dev": true, - "optional": true + "dev": true }, "natives": { "version": "1.1.6", @@ -8445,11 +8435,6 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, - "smart-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", - "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==" - }, "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", @@ -8460,24 +8445,6 @@ "hoek": "2.x.x" } }, - "socks": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz", - "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==", - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.0.1" - } - }, - "socks-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", - "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" - } - }, "sorcery": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz", @@ -9144,134 +9111,6 @@ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", "dev": true }, - "tor-request": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tor-request/-/tor-request-2.3.0.tgz", - "integrity": "sha512-uFsCDUYaJn1S55bPyIbi7TrpQyEB6F2WdU7LxgCqKN+VrfRladNaE99V9zCD5TrCPvizmLL+sTH2JHWFIpgv3A==", - "requires": { - "request": "~2.88.0", - "socks-proxy-agent": "~4.0.1" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, - "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" - }, - "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", - "requires": { - "mime-db": "~1.36.0" - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } - } - }, "touch": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", diff --git a/package.json b/package.json index abc7d4312..c431f53ea 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "FreeTube", "productName": "FreeTube", - "version": "0.4.1", + "version": "0.5.0", "description": "An Open Source YouTube app for privacy.", "main": "src/js/init.js", "scripts": { @@ -100,8 +100,7 @@ "mustache": "^2.3.0", "nedb": "^1.8.0", "opml-to-json": "0.0.3", - "tor-request": "^2.3.0", "vue": "^2.5.17", - "ytdl-core": "^0.20.4" + "ytdl-core": "^0.29.1" } } diff --git a/src/index.html b/src/index.html index 18abc9563..65aab2155 100644 --- a/src/index.html +++ b/src/index.html @@ -13,7 +13,7 @@ - Freetube Player + FreeTube Player @@ -32,12 +32,14 @@ -
+
+   @@ -63,6 +65,34 @@
+
+

Search Filters

+ + + + +

Your Subscription list is currently empty. Start adding subscriptions diff --git a/src/js/events.js b/src/js/events.js index 66b1d6b81..4d5e3ce35 100644 --- a/src/js/events.js +++ b/src/js/events.js @@ -73,7 +73,6 @@ let playPauseVideo = function (event) { * Handle keyboard shortcut commands. */ let videoShortcutHandler = function (event) { - if (event.which == 68 && event.altKey === true) { $('#search').focus(); } diff --git a/src/js/history.js b/src/js/history.js index 01bc5df45..4df612414 100644 --- a/src/js/history.js +++ b/src/js/history.js @@ -25,12 +25,30 @@ along with FreeTube. If not, see . * * @return {Void} */ -function addToHistory(videoId){ - const data = { - videoId: videoId, - timeWatched: new Date().getTime(), - }; - historyDb.insert(data, (err, newDoc) => {}); +function addToHistory(data){ + historyDb.findOne({ videoId: data.videoId }, function (err, doc) { + if(doc === null) { + historyDb.insert(data, (err, newDoc) => {}); + } else { + historyDb.update( + { videoId: data.videoId }, + { + videoId: data.videoId, + author: data.author, + authorId: data.authorId, + published: data.published, + publishedText: data.publishedText, + description: data.description, + viewCount: data.viewCount, + title: data.title, + description: data.description, + lengthSeconds: data.lengthSeconds, + videoThumbnails: data.videoThumbnails, + type: 'video', + timeWatched: data.timeWatched, + }, {}, (err, newDoc) => {}); + } + }); } /** @@ -62,23 +80,38 @@ function showHistory(){ historyDb.find({}).sort({ timeWatched: -1 }).exec((err, docs) => { - - if (docs.length < 100) { - for (let i = 0; i < docs.length; i++) { - invidiousAPI('videos', docs[i].videoId, {}, (data) => { - data.position = i; - displayVideo(data, 'history'); - }); - } - } - else{ - for (let i = 0; i < 100; i++) { - invidiousAPI('videos', docs[i].videoId, {}, (data) => { - data.position = i; - displayVideo(data, 'history'); - }); - } - } + docs.forEach((video, index) => { + if (video.authorId === undefined) { + // History data is from old version of FreeTube, update data for future calls. + invidiousAPI('videos', video.videoId, {}, (data) => { + let publishedText = new Date(data.published * 1000); + publishedText = dateFormat(publishedText, "mmm dS, yyyy"); + let videoData = { + videoId: video.videoId, + published: data.published, + publishedText: publishedText, + description: data.description, + viewCount: data.viewCount, + title: data.title, + lengthSeconds: data.lengthSeconds, + videoThumbnails: data.videoThumbnails[4].url, + author: data.author, + authorId: data.authorId, + liveNow: false, + paid: false, + type: 'video', + timeWatched: video.timeWatched, + }; + addToHistory(videoData); + videoData.position = index; + displayVideo(videoData, 'history'); + }); + } + else{ + video.position = index; + displayVideo(video, 'history'); + } + }); loadingView.seen = false; }); diff --git a/src/js/init.js b/src/js/init.js index 927d86a47..059d43a70 100644 --- a/src/js/init.js +++ b/src/js/init.js @@ -24,7 +24,8 @@ const { app, BrowserWindow, dialog, - protocol + protocol, + ipcMain } = require('electron'); const path = require('path'); const url = require('url'); @@ -163,6 +164,17 @@ let init = function () { const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); + + /** + * Sets proxy when setProxy event is sent from renderer + * + * example data "SOCKS5://127.0.0.1:9050" + */ + ipcMain.on("setProxy", (_e, data) => { + win.webContents.session.setProxy({ proxyRules: data }, function () { + win.webContents.send("proxyAvailable") + }); + }); }; /** diff --git a/src/js/layout.js b/src/js/layout.js index 11deecaeb..eff376204 100644 --- a/src/js/layout.js +++ b/src/js/layout.js @@ -38,18 +38,6 @@ const shell = electron.shell; // Used to open external links into the user's nat const clipboard = electron.clipboard; const getOpml = require('opml-to-json'); // Gets the file type for imported files. const fs = require('fs'); // Used to read files. Specifically in the settings page. -const tor = require('tor-request'); - -// User Defaults -let currentTheme = ''; -let useTor = false; -let rememberHistory = true; -let autoplay = true; -let enableSubtitles = false; -let checkForUpdates = true; -let currentVolume = 1; -let defaultQuality = 720; -let defaultPlaybackRate = '1'; let dialog = electron.remote.dialog; // Used for opening file browser to export / import subscriptions. let toastTimeout; // Timeout for toast notifications. @@ -69,6 +57,11 @@ electron.ipcRenderer.on('ping', function(event, message) { ft.log(message); }); +// Listens for proxy to be set in main process +electron.ipcRenderer.on('proxyAvailable', function(event, message) { + proxyAvailable = true; +}); + $(document).ready(() => { const searchBar = document.getElementById('search'); const jumpToInput = document.getElementById('jumpToInput'); @@ -208,4 +201,29 @@ function showVideoOptions(element) { } else { element.nextElementSibling.style.display = 'none' } -} \ No newline at end of file +} + +/** + * Wrapper around AJAX calls to wait for proxy to become available + * @return {Void} + */ +function proxyRequest(callback) { + let proxyCheckingInterval; + let counter = 0; + + // Wait for proxy to become available + proxyCheckingInterval = setInterval(function() { + if(proxyAvailable) { + clearInterval(proxyCheckingInterval) + + callback(); + + } else { + if(counter > 10) { + clearInterval(proxyCheckingInterval); + showToast('Unable to connect to the Tor network. Check the help page if you\'re having trouble setting up your node.'); + } + counter++; + } + }, 100); +} diff --git a/src/js/player.js b/src/js/player.js index 04d489c7b..84b34b8a4 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -38,6 +38,7 @@ function playVideo(videoId, playlistId = '') { playerView.valid480p = true; playerView.video720p = undefined; playerView.valid720p = true; + playerView.videoUrl = ''; playerView.embededHtml = ""; let videoHtml = ''; @@ -54,8 +55,82 @@ function playVideo(videoId, playlistId = '') { playerView.savedIconType = 'fas saved'; } }); - //"kpkXPy_jXmU" - invidiousAPI('videos', videoId, {}, function (data) { + + youtubedlGetInfo(videoId, (data) => { + console.log(data); + + let videoUrls = data.formats; + let formatUrls = data.player_response.streamingData.adaptiveFormats; + + // Search through the returned object to get the 480p and 720p video URLs (If available) + Object.keys(videoUrls).forEach((key) => { + switch (videoUrls[key]['itag']) { + case '18': + playerView.video480p = decodeURIComponent(videoUrls[key]['url']); + // console.log(playerView.video480p); + break; + case '22': + playerView.video720p = decodeURIComponent(videoUrls[key]['url']); + // console.log(playerView.video720p); + break; + } + }); + + // Last adaptive format will be best the quality audio stream (migrate fully to adaptive formats later) + playerView.videoAudio = decodeURIComponent(formatUrls[formatUrls.length - 1]['url']); + + if (typeof(playerView.videoAudio) === 'undefined') { + console.log(playerView.videoAudio); + playerView.validAudio = false; + } + + let useEmbedPlayer = false; + + // Default to the embeded player if the URLs cannot be found. + if (typeof (playerView.video720p) === 'undefined' && typeof (playerView.video480p) === 'undefined') { + //useEmbedPlayer = true; + playerView.currentQuality = 'EMBED'; + playerView.playerSeen = false; + //useEmbedPlayer = true; + showToast('Unable to get video file. Reverting to embeded player.'); + } + else if (typeof (playerView.video720p) === 'undefined' && typeof (playerView.video480p) !== 'undefined') { + // Default to the 480p video if the 720p URL cannot be found. + console.log('Found'); + playerView.videoUrl = playerView.video480p; + playerView.currentQuality = '480p'; + } else { + // Default to the 720p video. + playerView.videoUrl = playerView.video720p; + playerView.currentQuality = '720p'; + //playerView.videoUrl = playerView.liveManifest; + } + + if (!useEmbedPlayer && + typeof(data.player_response.captions) !== 'undefined' && + typeof(data.player_response.captions.playerCaptionsTracklistRenderer) !== 'undefined' && + typeof(data.player_response.captions.playerCaptionsTracklistRenderer.captionTracks) !== 'undefined') { + data.player_response.captions.playerCaptionsTracklistRenderer.captionTracks.forEach((caption) => { + let subtitleUrl = invidiousInstance + '/api/v1/captions/' + videoId + '?label=' + caption.name.simpleText; + + videoHtml = videoHtml + ''; + }); + + playerView.subtitleHtml = videoHtml; + } + + loadingView.seen = false; + + if (subscriptionView.seen === false && aboutView.seen === false && headerView.seen === false && searchView.seen === false && settingsView.seen === false && popularView.seen === false && savedView.seen === false && historyView.seen === false && channelView.seen === false && channelVideosView.seen === false) { + playerView.seen = true; + } else { + return; + } + + window.setTimeout(checkVideoUrls, 5000, playerView.video480p, playerView.video720p, playerView.videoAudio); + }); + + invidiousAPI('videos', videoId, {}, (data) => { console.log(data); @@ -68,9 +143,6 @@ function playVideo(videoId, playlistId = '') { playerView.channelName = data.author; playerView.channelId = data.authorId; playerView.channelIcon = data.authorThumbnails[2].url; - - let videoUrls = data.formatStreams; - let formatUrls = data.adaptiveFormats; // Add commas to the video view count. playerView.videoViews = data.viewCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); @@ -84,70 +156,13 @@ function playVideo(videoId, playlistId = '') { playerView.description = parseDescription(data.descriptionHtml); - // Search through the returned object to get the 480p and 720p video URLs (If available) - Object.keys(videoUrls).forEach((key) => { - switch (videoUrls[key]['itag']) { - case '18': - playerView.video480p = decodeURIComponent(videoUrls[key]['url']); - //console.log(video480p); - break; - case '22': - playerView.video720p = decodeURIComponent(videoUrls[key]['url']); - //console.log(video720p); - break; - } - }); - - // Last adaptive format will be best the quality audio stream (migrate fully to adaptive formats later) - playerView.videoAudio = decodeURIComponent(formatUrls[formatUrls.length - 1]['url']); - - if (typeof(playerView.videoAudio) === 'undefined') { - playerView.validAudio = false; - } - - let useEmbedPlayer = false; - - // Default to the embeded player if the URLs cannot be found. - if (typeof (playerView.video720p) === 'undefined' && typeof (playerView.video480p) === 'undefined') { - //useEmbedPlayer = true; - playerView.currentQuality = 'EMBED'; - playerView.playerSeen = false; - //useEmbedPlayer = true; - showToast('Unable to get video file. Reverting to embeded player.'); - } - else if (typeof (playerView.video720p) === 'undefined' && typeof (playerView.video480p) !== 'undefined') { - // Default to the 480p video if the 720p URL cannot be found. - console.log('Found'); - playerView.videoUrl = playerView.video480p; - playerView.currentQuality = '480p'; - } else { - // Default to the 720p video. - playerView.videoUrl = playerView.video720p; - playerView.currentQuality = '720p'; - //playerView.videoUrl = playerView.liveManifest; - } - - if (!useEmbedPlayer) { - data.captions.forEach((caption) => { - let subtitleUrl = 'https://www.invidio.us/api/v1/captions/' + videoId + '?label=' + caption.label; - - videoHtml = videoHtml + ''; - }); - - playerView.subtitleHtml = videoHtml; - } - const checkSubscription = isSubscribed(playerView.channelId); checkSubscription.then((results) => { if (results === false) { - if (subscribeButton != null) { - playerView.subscribedText = 'SUBSCRIBE'; - } + playerView.subscribedText = 'SUBSCRIBE'; } else { - if (subscribeButton != null) { - playerView.subscribedText = 'UNSUBSCRIBE'; - } + playerView.subscribedText = 'UNSUBSCRIBE'; } }); @@ -227,20 +242,27 @@ function playVideo(videoId, playlistId = '') { playerView.playlistId = ''; } - loadingView.seen = false; - - if (subscriptionView.seen === false && aboutView.seen === false && headerView.seen === false && searchView.seen === false && settingsView.seen === false && popularView.seen === false && savedView.seen === false && historyView.seen === false && channelView.seen === false && channelVideosView.seen === false) { - playerView.seen = true; - } else { - return; - } - if (rememberHistory === true){ - addToHistory(videoId); + let historyData = { + videoId: videoId, + published: data.published, + publishedText: playerView.publishedDate, + description: data.description, + viewCount: data.viewCount, + title: playerView.videoTitle, + lengthSeconds: data.lengthSeconds, + videoThumbnails: playerView.videoThumbnail, + author: playerView.channelName, + authorId: playerView.channelId, + liveNow: false, + paid: false, + type: 'video', + timeWatched: new Date().getTime(), + }; + + console.log(historyData); + addToHistory(historyData); } - - window.setTimeout(checkVideoUrls, 5000, playerView.video480p, playerView.video720p, playerView.videoAudio); - }); } diff --git a/src/js/savedVideos.js b/src/js/savedVideos.js index d264d3f3c..4661d519b 100644 --- a/src/js/savedVideos.js +++ b/src/js/savedVideos.js @@ -33,14 +33,30 @@ function addSavedVideo(videoId) { checkIfSaved.then((saved) => { if (saved === false) { - let data = { + invidiousAPI('videos', videoId, {}, (data) => { + let publishedText = new Date(data.published * 1000); + publishedText = dateFormat(publishedText, "mmm dS, yyyy"); + let videoData = { videoId: videoId, + published: data.published, + publishedText: publishedText, + description: data.description, + viewCount: data.viewCount, + title: data.title, + lengthSeconds: data.lengthSeconds, + videoThumbnails: data.videoThumbnails[4].url, + author: data.author, + authorId: data.authorId, + liveNow: false, + paid: false, + type: 'video', timeSaved: new Date().getTime(), - }; + }; - savedVidsDb.insert(data, (err, newDoc) => { - showToast('The video has been favorited!'); - }); + savedVidsDb.insert(videoData, (err, newDoc) => { + showToast('The video has been favorited!'); + }); + }); } else { showToast('The video has already been favorited!') } @@ -122,22 +138,40 @@ function showSavedVideos() { savedVidsDb.find({}).sort({ timeSaved: -1 }).exec((err, docs) => { - if (docs.length < 100) { - for (let i = 0; i < docs.length; i++) { - invidiousAPI('videos', docs[i].videoId, {}, (data) => { - data.position = i; - displayVideo(data, 'saved'); - }); - } - } - else{ - for (let i = 0; i < 100; i++) { - invidiousAPI('videos', docs[i].videoId, {}, (data) => { - data.position = i; - displayVideo(data, 'saved'); - }); - } - } + docs.forEach((video, index) => { + if (typeof(video.author) === 'undefined'){ + invidiousAPI('videos', video.videoId, {}, (data) => { + let publishedText = new Date(data.published * 1000); + publishedText = dateFormat(publishedText, "mmm dS, yyyy"); + let videoData = { + videoId: video.videoId, + published: data.published, + publishedText: publishedText, + description: data.description, + viewCount: data.viewCount, + title: data.title, + lengthSeconds: data.lengthSeconds, + videoThumbnails: data.videoThumbnails[4].url, + author: data.author, + authorId: data.authorId, + liveNow: false, + paid: false, + type: 'video', + }; + + savedVidsDb.update( + { videoId: data.videoId }, + videoData, {}, (err, newDoc) => {}); + + videoData.position = index; + displayVideo(videoData, 'saved'); + }); + } + else { + video.position = index; + displayVideo(video, 'saved'); + } + }); loadingView.seen = false; }); diff --git a/src/js/settings.js b/src/js/settings.js index f2cc04ae5..3ab76aa93 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -18,6 +18,23 @@ along with FreeTube. If not, see . * A file for functions used for settings. */ + // User Defaults + let currentTheme = ''; + let useTor = false; + let rememberHistory = true; + let autoplay = true; + let enableSubtitles = false; + let checkForUpdates = true; + let currentVolume = 1; + let defaultQuality = 720; + let defaultPlaybackRate = '1'; + // Proxy address variable + let defaultProxy = false; + // This variable is to make sure that proxy was set before making any API calls + let proxyAvailable = false; + let invidiousInstance = 'https://invidio.us'; + let checkedSettings = false; // Used to prevent data leak when using self-hosted Invidious Instance + /** * Display the settings screen to the user. * @@ -77,6 +94,12 @@ function updateSettingsView() { document.getElementById('qualitySelect').value = defaultQuality; document.getElementById('rateSelect').value = defaultPlaybackRate; + + if(defaultProxy) { + settingsView.proxyAddress = defaultProxy; + } else { + settingsView.proxyAddress = "SOCKS5://127.0.0.1:9050"; + } }); } @@ -98,6 +121,8 @@ function checkDefaultSettings() { 'updates': true, 'quality': '720', 'rate': '1', + 'invidious': 'https://invidio.us', + 'proxy': "SOCKS5://127.0.0.1:9050" // This is default value for tor client }; console.log(settingDefaults); @@ -151,6 +176,17 @@ function checkDefaultSettings() { case 'rate': defaultPlaybackRate = docs[0]['value']; break; + case 'proxy': + defaultProxy = docs[0]['value']; + + if(useTor && defaultProxy) { + electron.ipcRenderer.send("setProxy", defaultProxy); + } + break; + case 'invidious': + settingsView.invidiousInstance = docs[0]['value']; + invidiousInstance = docs[0]['value']; + break; default: break; } @@ -173,6 +209,8 @@ function updateSettings() { let updatesSwitch = document.getElementById('updatesSwitch').checked; let qualitySelect = document.getElementById('qualitySelect').value; let rateSelect = document.getElementById('rateSelect').value; + let proxyAddress = document.getElementById('proxyAddress').value; + let invidious = document.getElementById('invidiousInstance').value; let theme = 'light'; settingsView.useTor = torSwitch; @@ -180,6 +218,7 @@ function updateSettings() { settingsView.autoplay = autoplaySwitch; settingsView.subtitles = subtitlesSwitch; settingsView.updates = updatesSwitch; + settingsView.proxyAddress = proxyAddress; rememberHistory = historySwitch; defaultQuality = qualitySelect; defaultPlaybackRate = rateSelect; @@ -209,6 +248,29 @@ function updateSettings() { useTor = torSwitch; }); + // Update proxy address + settingsDb.update({ + _id: 'proxy' + }, { + value: proxyAddress + }, {}, function(err, numReplaced) { + console.log(err); + console.log(numReplaced); + defaultProxy = proxyAddress; + }); + + // Update Invidious Instance + settingsDb.update({ + _id: 'invidious' + }, { + value: invidious + }, {}, function(err, numReplaced) { + console.log(err); + console.log(numReplaced); + settingsView.invidiousInstance = invidious; + invidiousInstance = invidious; + }); + // Update history settingsDb.update({ _id: 'history' @@ -275,6 +337,13 @@ function updateSettings() { defaultPlaybackRate = rateSelect; }); + // set proxy in electron based on new values + if(torSwitch) { + electron.ipcRenderer.send("setProxy", proxyAddress); + } else { + electron.ipcRenderer.send("setProxy", {}); + } + showToast('Settings have been saved.'); } @@ -351,14 +420,34 @@ function importOpmlSubs(json){ return; } - json.forEach((channel) => { + showToast('Importing susbcriptions, please wait.'); + + progressView.seen = true; + progressView.width = 0; + + let counter = 0; + json.forEach((channel, index) => { let channelId = channel['xmlurl'].replace('https://www.youtube.com/feeds/videos.xml?channel_id=', ''); - addSubscription(channelId, false); + invidiousAPI('channels', channelId, {}, (data) => { + let subscription = { + channelId: data.authorId, + channelName: data.author, + channelThumbnail: data.authorThumbnails[2].url + }; + + addSubscription(subscription, false); + counter++; + progressView.progressWidth = (counter / json.length) * 100; + + if ((counter + 1) == json.length) { + showToast('Subscriptions have been imported!'); + progressView.seen = false; + progressView.seen = 0; + return; + } + }); }); - window.setTimeout(displaySubs, 1000); - showToast('Subscriptions have been imported!'); - return; } /** diff --git a/src/js/subscriptions.js b/src/js/subscriptions.js index 5b73903ab..aeeccdddd 100644 --- a/src/js/subscriptions.js +++ b/src/js/subscriptions.js @@ -33,26 +33,15 @@ let forceSubs = true; * * @return {Void} */ -function addSubscription(channelId, useToast = true) { - ft.log('Channel ID: ', channelId); +function addSubscription(data, useToast = true) { + ft.log('Channel Data: ', data); - invidiousAPI('channels', channelId, {}, (data) => { - const channelName = data.author; - const thumbnail = data.authorThumbnails[3].url; - - const channel = { - channelId: data.authorId, - channelName: channelName, - channelThumbnail: thumbnail, - }; - - // Refresh the list of subscriptions on the side navigation bar. - subDb.insert(channel, (err, newDoc) => { - if (useToast) { - showToast('Added ' + channelName + ' to subscriptions.'); - displaySubs(); - } - }); + // Refresh the list of subscriptions on the side navigation bar. + subDb.insert(data, (err, newDoc) => { + if (useToast) { + showToast('Added ' + data.channelName + ' to subscriptions.'); + } + displaySubs(); }); } @@ -103,8 +92,10 @@ function loadSubscriptions() { for (let i = 0; i < results.length; i++) { channelId = results[i]['channelId']; - invidiousAPI('channels/videos', channelId, {}, (data) => { - console.log(data); + invidiousAPI('channels/latest', channelId, {}, (data) => { + data.forEach((video, index) => { + data[index].author = results[i]['channelName']; + }); videoList = videoList.concat(data); counter = counter + 1; progressView.progressWidth = (counter / results.length) * 100; @@ -131,12 +122,15 @@ function loadSubscriptions() { } function addSubsToView(videoList) { + videoList = videoList.filter(a => { + return !a.premium; + }); + videoList.sort((a, b) => { return b.published - a.published; }); subscriptionView.videoList = []; - console.log(videoList); if (videoList.length > 100) { for (let i = 0; i < 100; i++) { @@ -229,24 +223,21 @@ function displaySubs() { * * @return {Void} */ -function toggleSubscription(channelId) { +function toggleSubscription(data) { event.stopPropagation(); - const checkIfSubscribed = isSubscribed(channelId); - const subscribeButton = document.getElementById('subscribeButton'); + const checkIfSubscribed = isSubscribed(data.channelId); checkIfSubscribed.then((results) => { if (results === false) { - if (subscribeButton != null) { - subscribeButton.innerHTML = 'UNSUBSCRIBE'; - } - addSubscription(channelId); + playerView.subscribedText = 'UNSUBSCRIBE'; + channelView.subButtonText = 'UNSUBSCRIBE'; + addSubscription(data); } else { - if (subscribeButton != null) { - subscribeButton.innerHTML = 'SUBSCRIBE'; - } - removeSubscription(channelId); + playerView.subscribedText = 'SUBSCRIBE'; + channelView.subButtonText = 'SUBSCRIBE'; + removeSubscription(data.channelId); } }); } diff --git a/src/js/templates.js b/src/js/templates.js index 29fba6a2e..8677384a8 100644 --- a/src/js/templates.js +++ b/src/js/templates.js @@ -50,6 +50,13 @@ let loadingView = new Vue({ } }); +let searchFilter = new Vue({ + el: '#searchFilter', + data: { + seen: false + } +}); + let noSubscriptions = new Vue({ el: '#noSubscriptions', data: { @@ -165,7 +172,7 @@ let subscriptionView = new Vue({ goToChannel(channelId); }, toggleSave: (videoId) => { - toggleSavedVideo(videoId); + addSavedVideo(videoId); }, copy: (site, videoId) => { const url = 'https://' + site + '/watch?v=' + videoId; @@ -255,7 +262,7 @@ let savedView = new Vue({ goToChannel(channelId); }, toggleSave: (videoId) => { - addSavedVideo(videoId); + toggleSavedVideo(videoId); }, copy: (site, videoId) => { const url = 'https://' + site + '/watch?v=' + videoId; @@ -318,9 +325,13 @@ let playlistView = new Vue({ play: (videoId) => { loadingView.seen = true; playVideo(videoId, playlistView.playlistId); + + backButtonView.lastView = playlistView }, channel: (channelId) => { goToChannel(channelId); + + backButtonView.lastView = playlistView }, toggleSave: (videoId) => { addSavedVideo(videoId); @@ -357,6 +368,40 @@ let settingsView = new Vue({ autoplay: true, subtitles: false, updates: true, + proxyAddress: false, + invidiousInstance: 'https://invidio.us', + checkProxyResult: false, + proxyTestLoading: false + }, + methods: { + checkProxy() { + this.checkProxyResult = false; + this.proxyTestLoading = true; + electron.ipcRenderer.send("setProxy", this.proxyAddress) + + proxyRequest(() => { + $.ajax({ + url: "https://ifconfig.co/json", + dataType: 'json', + timeout: 3000 // 3 second timeout + }).done(response => { + this.checkProxyResult = response; + }) + .fail((xhr, textStatus, error) => { + console.log(xhr); + console.log(textStatus); + showToast('Proxy test failed'); + }).always(() =>{ + this.proxyTestLoading = false; + electron.ipcRenderer.send("setProxy", {}); + }); + }) + } + }, + computed: { + proxyTestButtonText() { + return this.proxyTestLoading ? "LOADING..." : "TEST PROXY" + } }, template: settingsTemplate }); @@ -373,9 +418,13 @@ let searchView = new Vue({ play: (videoId) => { loadingView.seen = true; playVideo(videoId); + + backButtonView.lastView = searchView }, channel: (channelId) => { goToChannel(channelId); + + backButtonView.lastView = searchView }, toggleSave: (videoId) => { addSavedVideo(videoId); @@ -391,6 +440,8 @@ let searchView = new Vue({ }, playlist: (playlistId) => { showPlaylist(playlistId); + + backButtonView.lastView = searchView }, }, template: videoListTemplate @@ -410,7 +461,12 @@ let channelView = new Vue({ }, methods: { subscription: (channelId) => { - toggleSubscription(channelId); + let channelData = { + channelId: channelView.id, + channelName: channelView.name, + channelThumbnail: channelView.icon + }; + toggleSubscription(channelData); }, }, template: channelTemplate @@ -498,8 +554,13 @@ let playerView = new Vue({ channel: (channelId) => { goToChannel(channelId); }, - subscription: (videoId) => { - toggleSubscription(videoId); + subscription: () => { + let channelData = { + channelId: playerView.channelId, + channelName: playerView.channelName, + channelThumbnail: playerView.channelIcon + }; + toggleSubscription(channelData); }, quality: (url, qualityText) => { console.log(url); @@ -573,6 +634,47 @@ let playerView = new Vue({ template: playerTemplate }); +let backButtonView = new Vue({ + el: '#backButton', + data: { + lastView: false + }, + methods: { + back: function() { + // variable here because this.lastView gets reset in hideViews() + const isSearch = this.lastView.$options.el === "#searchView"; + + hideViews(); + loadingView.seen = false; + + // Check if lastView was search + if(isSearch) { + // Change back to searchView + headerView.seen = true; + headerView.title = 'Search Results'; + searchView.seen = true; + + // reset this.lastView + this.lastView = false; + } else { + // if not search then this.lastView has to be playlistView + + // Change back to playlistView + playlistView.seen = true; + + // Check if searchView has videos if it does set this.lastView as searchView + this.lastView = searchView.videoList.length > 0 ? searchView : false; + } + } + }, + computed: { + canShowBackButton: function() { + // this.lastView can be either searchView or playlistView + return !!this.lastView && !this.lastView.seen && this.lastView.videoList.length > 0; + } + }, +}); + function hideViews(){ subscriptionView.seen = false; noSubscriptions.seen = false; @@ -588,4 +690,6 @@ function hideViews(){ playerView.seen = false; channelView.seen = false; channelVideosView.seen = false; + + backButtonView.lastView = false; } diff --git a/src/js/videos.js b/src/js/videos.js index add044e1f..c9bfd736f 100644 --- a/src/js/videos.js +++ b/src/js/videos.js @@ -33,6 +33,12 @@ const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", */ function search(page = 1) { const query = document.getElementById('search').value; + const searchSortby = document.getElementById('searchSortby').value; + const searchType = document.getElementById('searchType').value; + const searchDate = document.getElementById('searchDate').value; + const searchDuration = document.getElementById('searchDuration').value; + + searchFilter.seen = false; if (query === '') { return; @@ -52,7 +58,10 @@ function search(page = 1) { invidiousAPI('search', '', { q: query, page: page, - type: 'all', + sort_by: searchSortby, + date: searchDate, + duration: searchDuration, + type: searchType, }, function (data) { console.log(data); @@ -161,8 +170,6 @@ function displayVideo(videoData, listType = '') { } } - //const searchMenu = $('#videoListContainer').html(); - // Include a remove icon in the list if the application is displaying the history list or saved videos. video.deleteHtml = () => { switch (listType) { @@ -175,12 +182,18 @@ function displayVideo(videoData, listType = '') { video.youtubeUrl = 'https://youtube.com/watch?v=' + video.id; video.invidiousUrl = 'https://invidio.us/watch?v=' + video.id; - video.thumbnail = videoData.videoThumbnails[4].url; + if (typeof(videoData.videoThumbnails) === 'string'){ + video.thumbnail = videoData.videoThumbnails; + } + else { + video.thumbnail = videoData.videoThumbnails[4].url; + } video.title = videoData.title; video.channelName = videoData.author; video.channelId = videoData.authorId; video.description = videoData.description; video.isVideo = true; + video.premium = videoData.premium; switch (listType) { case 'subscriptions': @@ -219,7 +232,7 @@ function displayChannel(channel) { let channelData = {}; channelData.channelId = channel.authorId; - channelData.thumbnail = channel.authorThumbnails[4].url; + channelData.thumbnail = "https:" + channel.authorThumbnails[4].url; channelData.channelName = channel.author; channelData.description = channel.description; channelData.subscriberCount = channel.subCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); diff --git a/src/js/youtubeApi.js b/src/js/youtubeApi.js index 37bb661bc..61e032758 100644 --- a/src/js/youtubeApi.js +++ b/src/js/youtubeApi.js @@ -29,20 +29,22 @@ function invidiousAPI(resource, id, params, success, fail = function(){ showToast('There was an error calling the Invidious API.'); loadingView.seen = false; }) { - let requestUrl = 'https://www.invidio.us/api/v1/' + resource + '/' + id + '?' + $.param(params); + let requestUrl = invidiousInstance + '/api/v1/' + resource + '/' + id + '?' + $.param(params); if (useTor) { - tor.request(requestUrl, (err, res, body) => { - if (!err && res.statusCode == 200) { - success(JSON.parse(body)); - } else { - showToast('Unable to connect to the Tor network. Check the help page if you\'re having trouble setting up your node.'); - console.log(err); - console.log(res); - console.log(body); - loadingView.seen = false; - } - }); + + proxyRequest(() => { + $.getJSON( + requestUrl, + success + ).fail((xhr, textStatus, error) => { + fail(xhr); + console.log(xhr); + console.log(textStatus); + console.log(requestUrl); + }); + }) + } else { $.getJSON( requestUrl, diff --git a/src/style/darkTheme.css b/src/style/darkTheme.css index 299392e7d..af72d1924 100644 --- a/src/style/darkTheme.css +++ b/src/style/darkTheme.css @@ -85,6 +85,14 @@ input[type=text] { color: #E0E0E0; } +.filterButton { + color: #E0E0E0; +} + +#searchFilter { + background-color: #424242; +} + .jumpToInput { border-bottom: 1px solid #E0E0E0; } diff --git a/src/style/lightTheme.css b/src/style/lightTheme.css index 287a6b4a9..bc2090c74 100644 --- a/src/style/lightTheme.css +++ b/src/style/lightTheme.css @@ -72,6 +72,14 @@ body { color: black; } +.filterButton { + color: black; +} + +#searchFilter { + background-color: white; +} + .jumpToInput { border-bottom: 1px solid #757575; } diff --git a/src/style/main.css b/src/style/main.css index 0e2034fb0..2846302e5 100644 --- a/src/style/main.css +++ b/src/style/main.css @@ -91,6 +91,15 @@ a { top: 3px; } +#backButton { + cursor: pointer; + font-weight: bold; + font-size: 20px; + margin-left: 20px; + position: relative; + top: 3px; +} + #menuIcon { width: 25px; position: relative; @@ -109,17 +118,31 @@ a { .searchBar { position: absolute; - right: 135px; + right: 100px; top: 0; - width: 50%; + width: 600px; } .searchBar input { - width: 90%; + width: 60%; +} + +#searchFilter { + width: 90%; + padding: 10px; + margin-bottom: 20px; + -webkit-box-shadow: 4px -2px 51px -6px rgba(0, 0, 0, 0.75); } .searchButton { text-decoration: none; + cursor: pointer; +} + +.filterButton { + text-decoration: none; + margin-right: 10px; + cursor: pointer; } #sideNav { @@ -219,6 +242,19 @@ a { border-bottom: 1px solid #616161; } +.input-text-settings input { + width: auto; + border-bottom: 1px solid #616161; + width: 50%; + margin: 0 auto; + display: block; +} + +#ipInfoSettings { + border: 1px solid; + margin: 10px 0; +} + .settingsButton { padding: 10px; display: inline-block; diff --git a/src/templates/settings.html b/src/templates/settings.html index aa91807dc..a75d48b4d 100644 --- a/src/templates/settings.html +++ b/src/templates/settings.html @@ -3,8 +3,6 @@
- -
@@ -43,6 +41,36 @@

+
+
+ + +
+
+
+ + +
+ + +
+ +
+

Your Info:

+

ip: {{checkProxyResult.ip}}

+

ip_decimal: {{checkProxyResult.ip_decimal}}

+

country: {{checkProxyResult.country}}

+

city: {{checkProxyResult.city || "N/A"}}

+

hostname: {{checkProxyResult.hostname || "N/A"}}

+
+ +

Clicking "TEST PROXY" button will send a request to https://ifconfig.co/json

+
+ {{proxyTestButtonText}} +
+
+
+
IMPORT SUBSCRIPTIONS diff --git a/src/templates/videoTemplate.html b/src/templates/videoTemplate.html index 92238a641..b20a91cee 100644 --- a/src/templates/videoTemplate.html +++ b/src/templates/videoTemplate.html @@ -29,7 +29,7 @@

{{video.title}}

{{video.channelName}}

-

{{video.views}} {{video.viewText}} - {{video.publishedDate}}

+

{{video.views}} {{video.viewText}} - {{video.publishedDate}}

{{video.description}}

{{video.liveText}}