Merge branch 'development'

This commit is contained in:
Preston 2019-03-03 11:27:02 -05:00
commit f91fffd370
23 changed files with 743 additions and 387 deletions

View File

@ -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.

View File

@ -3,7 +3,7 @@
</p>
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

View File

@ -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",

95
locales/pt-PT.json Normal file
View File

@ -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."
}

177
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

View File

@ -13,7 +13,7 @@
<link rel="stylesheet" href="style/select.css">
<link rel="stylesheet" href="style/fa-solid.min.css">
<link rel="stylesheet" href="style/fontawesome-all.min.css">
<title>Freetube Player</title>
<title>FreeTube Player</title>
</head>
<body>
@ -32,12 +32,14 @@
<span id='toastMessage'></span>
<i onclick='hideToast()' class="closeToast fas fa-times"></i>
</div>
<div class="topNav">
<div id="topNav" class="topNav">
<i onclick='toggleSideNavigation()' class="fas fa-bars" id='menuButton'></i>
<i onclick='forceSubscriptions()' class="fas fa-sync" id='reloadButton' title='Force Subscription Reload'></i>
<i v-on:click='back' v-if="canShowBackButton" class="fas fa-arrow-left" id="backButton"></i>
<div class="searchBar">
<input id='search' class="search" type="text" placeholder="Search / Go to URL">
<i onclick='parseSearchText()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
<span class='filterButton' onclick='searchFilter.seen = !searchFilter.seen'><i class="fas fa-filter" style='margin-right: -10px; cursor: pointer'></i>&nbsp;&nbsp;&nbsp;&nbsp;Filter</span>
<span class='searchButton' onclick='parseSearchText()'><i class="fas fa-search" style='margin-right: -10px; cursor: pointer'></i>&nbsp;&nbsp;&nbsp;&nbsp;Search</span>
</div>
<img src='icons/iconBlackSmall.png' id='menuIcon' /> &nbsp;
<img src='icons/textBlackSmall.png' id='menuText' />
@ -63,6 +65,34 @@
</div>
</div>
<div id="main">
<div id='searchFilter' class='center' v-show='seen'>
<h2>Search Filters</h2>
<select id='searchSortby' class='select-text'>
<option value="relevance">Most Relevant</option>
<option value="rating">Rating</option>
<option value="upload_date">Upload Date</option>
<option value="view_count">View Count</option>
</select>
<select id='searchDate' class='select-text'>
<option value="">Any Time</option>
<option value="hour">Last Hour</option>
<option value="today">Today</option>
<option value="week">This Week</option>
<option value="month">This Month</option>
<option value="year">This Year</option>
</select>
<select id='searchType' class='select-text'>
<option value="all">All Types</option>
<option value="video">Videos</option>
<option value="channel">Channels</option>
<option value="playlist">Playlists</option>
</select>
<select id='searchDuration' class='select-text'>
<option value="">All Durations</option>
<option value="short">Short (< 4 minutes)</option>
<option value="long">Long (> 20 minutes)</option>
</select>
</div>
<div id='noSubscriptions' v-if='seen'>
<h2 class="message">
Your Subscription list is currently empty. Start adding subscriptions

View File

@ -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();
}

View File

@ -25,12 +25,30 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*
* @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;
});

View File

@ -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")
});
});
};
/**

View File

@ -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'
}
}
}
/**
* 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);
}

View File

@ -38,6 +38,7 @@ function playVideo(videoId, playlistId = '') {
playerView.valid480p = true;
playerView.video720p = undefined;
playerView.valid720p = true;
playerView.videoUrl = '';
playerView.embededHtml = "<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/" + videoId + "?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>";
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 + '<track kind="subtitles" src="' + subtitleUrl + '" srclang="' + caption.languageCode + '" label="' + caption.name.simpleText + '">';
});
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 + '<track kind="subtitles" src="' + subtitleUrl + '" srclang="' + caption.languageCode + '" label="' + caption.label + '">';
});
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);
});
}

View File

@ -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;
});

View File

@ -18,6 +18,23 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
* 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;
}
/**

View File

@ -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);
}
});
}

View File

@ -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;
}

View File

@ -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, ",");

View File

@ -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,

View File

@ -85,6 +85,14 @@ input[type=text] {
color: #E0E0E0;
}
.filterButton {
color: #E0E0E0;
}
#searchFilter {
background-color: #424242;
}
.jumpToInput {
border-bottom: 1px solid #E0E0E0;
}

View File

@ -72,6 +72,14 @@ body {
color: black;
}
.filterButton {
color: black;
}
#searchFilter {
background-color: white;
}
.jumpToInput {
border-bottom: 1px solid #757575;
}

View File

@ -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;

View File

@ -3,8 +3,6 @@
<div class='center'>
<input type="checkbox" id="themeSwitch" name="set-name" class="switch-input" onchange='toggleTheme(this)' :checked='useTheme'>
<label for="themeSwitch" class="switch-label">Use Dark Theme</label>
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" :checked='useTor'>
<label for="torSwitch" class="switch-label">Use Tor for API calls</label>
<input type="checkbox" id="updatesSwitch" name="set-name" class="switch-input" :checked='updates'>
<label for="updatesSwitch" class="switch-label">Check for Updates</label>
<br />
@ -43,6 +41,36 @@
</div>
<br />
</div>
<div class='center'>
<div class="input-text-settings">
<label for="invidiousInstance">Current Invidious Instance (Defaults to https://invidio.us)</label>
<input type="text" id="invidiousInstance" name="set-name" v-model="invidiousInstance">
</div>
</div>
<div class="center">
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" :checked='useTor'>
<label for="torSwitch" class="switch-label">Use Tor / Proxy for API calls</label>
<div class="input-text-settings">
<label for="proxyAddress">Proxy Address (Example: SOCKS5://127.0.0.1:9050 ): </label>
<input type="text" id="proxyAddress" name="set-name" v-model="proxyAddress">
</div>
<div id="ipInfoSettings" v-if="checkProxyResult">
<h4>Your Info:</h4>
<p>ip: {{checkProxyResult.ip}}</p>
<p>ip_decimal: {{checkProxyResult.ip_decimal}}</p>
<p>country: {{checkProxyResult.country}}</p>
<p>city: {{checkProxyResult.city || "N/A"}}</p>
<p>hostname: {{checkProxyResult.hostname || "N/A"}}</p>
</div>
<p>Clicking "TEST PROXY" button will send a request to https://ifconfig.co/json</p>
<div v-on:click='checkProxy' class='center settingsButton'>
{{proxyTestButtonText}}
</div>
</div>
<br />
<br />
<div class='center'>
<div onclick='importSubscriptions()' class='settingsButton'>
IMPORT SUBSCRIPTIONS

View File

@ -29,7 +29,7 @@
</div>
<p v-on:click='play(video.id)' class='videoTitle'>{{video.title}}</p>
<p v-on:click='channel(video.channelId)' class='channelName'>{{video.channelName}}</p>
<p v-on:click='play(video.id)' class='videoViews'>{{video.views}} {{video.viewText}} - {{video.publishedDate}}</p>
<p v-if='!video.premium' v-on:click='play(video.id)' class='videoViews'>{{video.views}} {{video.viewText}} - {{video.publishedDate}}</p>
<p v-on:click='play(video.id)' class='videoDescription'>{{video.description}}</p>
<p v-on:click='play(video.id)' class='live'>{{video.liveText}}</p>
</div>