From 695b836852a25ea31a5b20f09d35fa2826c02230 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 28 Feb 2020 21:21:17 -0500 Subject: [PATCH 1/2] added url params on home page to auto download content created chrome extension to facilitate this feature --- .gitignore | 1 + chrome-extension.crx | Bin 0 -> 1124 bytes chrome-extension.pem | 28 ++++++++++++++++++++ chrome-extension/background.js | 20 ++++++++++++++ chrome-extension/favicon.png | Bin 0 -> 3870 bytes chrome-extension/manifest.json | 20 ++++++++++++++ chrome-extension/options.html | 29 +++++++++++++++++++++ chrome-extension/options.js | 31 ++++++++++++++++++++++ src/app/main/main.component.ts | 46 +++++++++++++++++++++------------ 9 files changed, 158 insertions(+), 17 deletions(-) create mode 100644 chrome-extension.crx create mode 100644 chrome-extension.pem create mode 100644 chrome-extension/background.js create mode 100644 chrome-extension/favicon.png create mode 100644 chrome-extension/manifest.json create mode 100644 chrome-extension/options.html create mode 100644 chrome-extension/options.js diff --git a/.gitignore b/.gitignore index 477e0c8..12784b3 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ Thumbs.db node_modules/* backend/node_modules/* +backend/public/* YoutubeDL-Material/node_modules/* backend/video/* backend/audio/* diff --git a/chrome-extension.crx b/chrome-extension.crx new file mode 100644 index 0000000000000000000000000000000000000000..0e8360b2148a2b181e3c596cc7ce296900af6d1a GIT binary patch literal 1124 zcmZ=RGBROiU|?`%Vqg$j!@{+U$)Jf*$$*!QQ>)FR?K>|cBO@yVa}y&!15licsfm%1 z;Y|G1B~O(t3)*kI6z__!nVlTi@xgp^*S1$PZ(@5k zE8$Y0b+;$G#GJS5jUBx1Hx&Kv{-PcB)oJOc>LaUPy|!5V196@lQZvCiM+Y}>ZXk0+!ARGi{cMa*I%_T%rV&2bZ}>O z@+7-sEswf3ysBI==ZWX3nJevQWSuCpsr`D;Khxou;pI0kZ{#j$tWLRLe}1+6s>3WV zl0$BXsQcS4JaR#Q(OU=SCk1gv8Z*}xykVbyxL`csz42(hzOp2vhukAc;{ks)+$7gz{L%niPSd0D@;R!1&*_f@_ z=fC@NSKQ>a*R1m?IxOr0-VYATpIKvk^N06|<2wby8U*BL8FsWP7Yi@DU(WXT?t)wY zMdnuQ{(T|u(L0~(dAC~TJz5!_((s@BS~Yc0{gZ(dKRJ1McNTIax|!#pj@PO165ld2*fOgXskbWSRR zZG745kEgUP94~x7{CLOrKCAp~n$P~vzQ10XW9QKnp2j3U-9dlxRWMitZ(l a9*EYDKo*Jz1H4(;K+;S=SO%oq!Ri2Ox!!01 literal 0 HcmV?d00001 diff --git a/chrome-extension.pem b/chrome-extension.pem new file mode 100644 index 0000000..19f4917 --- /dev/null +++ b/chrome-extension.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMX9Wk5SM5cIfY +6ReKX3ybY1rsbNbOzG8ceN7yyeXB0mor8pVsX1MOna2HewOyBuaaYNJRO4tJBxic +7a8zQErfgHL/i/QrVvVCpfJ7xKvq6zij5NYoqd/FBUwawqjeH5/voIcAp9z5Vmsr +kL0sxJUKy6b4IWNp3noU7Nvq2RwxnXQbKDhz8FrX6oQAnDC6gsG5a2OSPsaE4oqw +6nmonORJypmpP5hqyHY8ffXBT2lAxjHT7OnYbaCBe2TQP8+rH6rDBOhjVNtUJ089 +ocTQL6LtQEPkcF4yKJmtcOwHl8OPGZs5l9i8xb4j9RuSPkm2lbzZX8sOsdGGoqJZ +q68nYhsHAgMBAAECggEAXmtKEzfPObq88B/kAcgSk+FngMHZzcmR7bgD3GwdSxnQ +dkRI9zvk7eQ35tcUwntAr4Lat6/ILjFqlBmVLxrdXHuF5Xz9jcZLYgKzz61xdYM9 +dC6FKF0u5eGIIvbauGAo7jaeGFX1F3Zu5b4lP9kEOGwU1B7sxF0FzsQM5+dtCJgv +We/hWQeF+9gtoVnkCSS/Mq2p0UomXXHW0Bz4+HuHlTR9aiYbviYnotABiLUhZyzt +v5yUaktb9qniBfdLpRlq8cp06xYlTEA9gJpa4Pnok8OWUsbAiW6EiXUSaZ/cchVa +AnO8WWYvVOnnt6WHI3+QdFTnqVjE5TBX4N/7bVhHGQKBgQD0dtbFqp7vZK/jVYvE +z0WPdySOg2ZDmoSfk5ZlR1+Y9zWToHv0qu8zqoOjL8Ubxrh9fGlOow+cCVdkEuaa +jWC2AWetuRvW0Z5A3XMXr0/N/1fgOkTqtp3WNrUPjVJahEg3lN+90opgFoT8swSi +s1oxW0oLcVIlrjhGBXAPCfsAuQKBgQDWBLRhHsRAvGcK5wGuVnxVApTIyBOermsW +3bJt+7+UI+4sYrBAwkWdQG93IG0cQtn48TEPBgmR2fjRF5IFT9M4/u+QOeeByT7I +we7nVtHgSY5ByC9N0mjWbcmSg8fktz/LonjldNC4kWdOFb75fxGf8kOGS5rUaMA4 +zHucfB6ZvwKBgQCPHJrysMXGY21MaqIeHzEboaX3ABl37hdBzAa5V6UxSVdGCydF +vmO2HVZey/JaJmWOoKyNaowSzq0oWqBBTg6VvhDR9JHFmoVId9uOvAS+FYN+Mt5x +gWK5KuGoLxVNBC+6yh6JY526TrSfsrU+Aj0Es+qO9FIg2PL8muZVB4S3kQKBgH/5 +CDMaxpc/EQ5/2413wZjDllwI51J3USm3Hz6Mzp2ybnSz/lh60k2Zfg1polTH1Lb6 +4i7tmUNRZ2sAARyUAuWN64n+VeRRhe1dqZFDZPQMh7fmEAMk0fOGaoXlrt2ghdEq +Mchi9Xun1nHmpu9hgBR4NNBU3RwuFuLfwvprbZDZAoGAWa62QJChE86xQGP1MrL2 +SbIzw3cfeP5xdQ3MKldJiy5IkbMR7Z13WZ7FwvPTy0g/onLHD1rqlm1kUMsGRHpD +5vH06PNpKXQ6x8BYaRGtE6P39jLycO/X+WK/lYTrWo1bR+mGCebDh4B5XrwT3gI6 +x4Gvz134pZCTyQCf5JCwbQs= +-----END PRIVATE KEY----- diff --git a/chrome-extension/background.js b/chrome-extension/background.js new file mode 100644 index 0000000..4785a23 --- /dev/null +++ b/chrome-extension/background.js @@ -0,0 +1,20 @@ +// background.js + +// Called when the user clicks on the browser action. +chrome.browserAction.onClicked.addListener(function(tab) { + // get the frontend_url + chrome.storage.sync.get({ + frontend_url: 'http://localhost', + audio_only: false + }, function(items) { + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + var activeTab = tabs[0]; + var url = activeTab.url; + if (url.includes('youtube.com')) { + var new_url = items.frontend_url + '/#/home;url=' + encodeURIComponent(url) + ';audioOnly=' + items.audio_only; + chrome.tabs.create({ url: new_url }); + } + }); + }); + +}); \ No newline at end of file diff --git a/chrome-extension/favicon.png b/chrome-extension/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c363a1e6e3ef40fa5cf39dc7e930d00bed8ffd5f GIT binary patch literal 3870 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 + +YoutubeDL-Material Extension Options + + +

Settings

+ +
+

Frontend URL

+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/chrome-extension/options.js b/chrome-extension/options.js new file mode 100644 index 0000000..78597b0 --- /dev/null +++ b/chrome-extension/options.js @@ -0,0 +1,31 @@ +// Saves options to chrome.storage +function save_options() { + var frontend_url = document.getElementById('frontend_url').value; + var audio_only = document.getElementById('audio_only').checked; + chrome.storage.sync.set({ + frontend_url: frontend_url, + audio_only: audio_only + }, function() { + // Update status to let user know options were saved. + var status = document.getElementById('status'); + status.textContent = 'Options saved.'; + setTimeout(function() { + status.textContent = ''; + }, 750); + }); + } + + // Restores select box and checkbox state using the preferences + // stored in chrome.storage. + function restore_options() { + chrome.storage.sync.get({ + frontend_url: 'http://localhost', + audio_only: false + }, function(items) { + document.getElementById('frontend_url').value = items.frontend_url; + document.getElementById('audio_only').checked = items.audio_only; + }); + } + document.addEventListener('DOMContentLoaded', restore_options); + document.getElementById('save').addEventListener('click', + save_options); \ No newline at end of file diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 64fa517..c71233c 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -15,7 +15,7 @@ import 'rxjs/add/operator/debounceTime' import 'rxjs/add/operator/do' import 'rxjs/add/operator/switch' import { YoutubeSearchService, Result } from '../youtube-search.service'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component'; import { Platform } from '@angular/cdk/platform'; import { v4 as uuid } from 'uuid'; @@ -56,6 +56,7 @@ export class MainComponent implements OnInit { url = ''; exists = ''; percentDownloaded: number; + autoStartDownload = false; // settings fileManagerEnabled = false; @@ -198,7 +199,7 @@ export class MainComponent implements OnInit { }; constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar, - private router: Router, public dialog: MatDialog, private platform: Platform) { + private router: Router, public dialog: MatDialog, private platform: Platform, private route: ActivatedRoute) { this.audioOnly = false; // loading config @@ -241,12 +242,38 @@ export class MainComponent implements OnInit { } } + if (this.autoStartDownload) { + this.downloadClicked(); + } + }, error => { console.log(error); }); } + // app initialization. + ngOnInit() { + this.iOS = this.platform.IOS; + + if (localStorage.getItem('audioOnly') !== null) { + this.audioOnly = localStorage.getItem('audioOnly') === 'true'; + } + + if (localStorage.getItem('multiDownloadMode') !== null) { + this.multiDownloadMode = localStorage.getItem('multiDownloadMode') === 'true'; + } + + // check if params exist + if (this.route.snapshot.paramMap.get('url')) { + this.url = decodeURIComponent(this.route.snapshot.paramMap.get('url')); + this.audioOnly = this.route.snapshot.paramMap.get('audioOnly') === 'true'; + + // set auto start flag to true + this.autoStartDownload = true; + } + } + // file manager stuff getMp3s() { @@ -372,21 +399,6 @@ export class MainComponent implements OnInit { }); } - - // app initialization. - ngOnInit() { - // this.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window['MSStream']; - this.iOS = this.platform.IOS; - - if (localStorage.getItem('audioOnly') !== null) { - this.audioOnly = localStorage.getItem('audioOnly') === 'true'; - } - - if (localStorage.getItem('multiDownloadMode') !== null) { - this.multiDownloadMode = localStorage.getItem('multiDownloadMode') === 'true'; - } - } - // download helpers downloadHelperMp3(name, is_playlist = false, forceView = false, new_download = null) { From 8a5202018692dfd0bd8ea64c232663a7ed9740dd Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Fri, 28 Feb 2020 22:05:16 -0500 Subject: [PATCH 2/2] resized favicon --- chrome-extension/favicon.png | Bin 3870 -> 2543 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/chrome-extension/favicon.png b/chrome-extension/favicon.png index c363a1e6e3ef40fa5cf39dc7e930d00bed8ffd5f..81d69e73bda8a913ca22c2efcb01ae2a40f34597 100644 GIT binary patch literal 2543 zcmb_d2UinX621uu#wdb$kxmR!j0#8>FvJi+iXuc&iXi3Dq(}gzha?i^p&^J!^4KR5 zP!U9mD7b`^y=*+Ye_;3QoOAEoJ9B34oO|Z`CdKijwG>hj2>_6? zwXr-U*7RQsAt~Np6TDjh*jj08Y2g%eemeVHh?1*9Ls~%nb5piTyt`@5eY=XC47lZ1 zY(-%7q@ya*#yj1n(owxx>BgIFsFWzVTs(z*{PdIRD>h|*Pp4(?OQvX`+*HqM^cIgv zaHVc?9>5|lyw|*pYIrA#Nw9AC*r;dgj}ONWo91|ayEB(Px4HQ)nlJJTIr`nl4WiUh zlw79<=;4T81%|`rJqf=5%VDF$fM}vDtEs68tG{gMyZo7Aa#&9(f2uHT7ZT3@ zKyT-7ASueu6cgpJsX@VJuz`WWW`PFD9(gvKXj;8@BdxEx};e;9gfYAV6^ z!i5V=nI9r#Sp8O2hdoSW5ZMUQJ}|KHxvsA6+;RfnW%_p(jccP$NRhECgwrRb$h9eY ziRS4x^X-Ak4Sq*++eKTXF>7S)DowDKxVao?xFTgH-!?byrMr|nGBUESLijmu7>&3} znmy<0x_nxP8`)k!$eCSz;u1m(mHY?z9)k}fz2&L@y{TE;ecn?AGg$NURG7P!GP-g4iW-5o@pgX z0kAErFkl~`W4g3Dw}7&nS#caF7$dimsQ;zw)pl^cbn#CtB29&mu=Jp*sp+!0kLwd@ z192@URhj3bO_Ucc$qD>>_l8k)$F=*MEC-1j;C8egTsSOxSsQoEOO}q5k+meRSp)}9 zKIrLj9c4?7LJDBQ>8K;LuZ=tNgJ7DFA(I`Z%4|O?GO6oo_Uq3sJnYi?2G^w#aRNF- zF==ng*t@$rc5bv9ZKd$mSKs6Dm0B(E@MQJ`X7_H|YJ2X{85gZ6H}Y-953b7r?=tbD z7VMkqpUb{5&3X&ehtutRnKT*IPLE;!~$>`%0n@I^_M%v zFsoD$j$h*AY~QD~QVhi|N#1_7`MnuU!eV`B+uRKuZVN>Q(9NO0%)gPMU<}zC+qP{B zj>l(jDq%q2%`fv*ajF-;Ar?J!oW)(JjsV0YfJ-(_T>IzhOnB!0C#vxjEHL_J+rahaJUT9XzZTOCXolKr2{yd$(UpTyJWe+3Nd;k*O>e^N_Le zU0clz^R70BQ4_X_GP_7yTl;mo&DR{W*MSp~An;ZpA+@=_VOSJ!y3olar!6dw64kWi zTyuW*YO?fh0#o(_!j5C4S33lX@+IL^OO_Sq&gS7`G96#Q2Rg7cW zWXj(K1V@QZ-m^?rvKp^ajm}lycLEWslYR0C*`qJ>G}T~gDQG}dV(KQO`7qxXUp(=m zd9*X;c`sGrcYMf#le7^r)Cly8%$YX>qCbkbNy4G;YilnH+hQty+o4aqN_wE;X`EQ3 zBKRCtT?&IT@(*Ex=&eRzc5XQiZPjzJK`xc6KK`n&uaEVqBq)FKaR6PwVI7o$G_%*kwe=B9!uQ`l0v?1)>Zy*lTUnz4mSBkT7YB zvPhSuY3ZZMG>1zLR&p=)Ly&Uw3xsGMttgx1H_XOWZEhANraLsbAvY#FYN560{ z-G4pe+B@mr*P=;{@u9}Oqt@Ir_5=BTdU-g$X3LL&z;k4Dz{=|CZWR@kGtcI)8mE$n zrh1;11b3J8r~546)2`2v+nfgZKbo>?8^AXnRRV$3w)d&Rc&4#HpqAA6VNt8D*%N$yUTn%Ys_1kjh!otF0+q-u>f@@JAsPr`g`LmQR?MG%U2Mi1I5^?MBzFM7IyImk4*CCurEY?uc)^55ky-h#-n6wkX<0(@ Gy7Dh;HGi`J literal 3870 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