From 3df8f65db60b83778b46384e1bc26c2c0ef2067f Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 14 Jan 2022 14:46:00 +0700 Subject: [PATCH 1/4] implement cors for browser extension --- tubearchivist/api/views.py | 1 + tubearchivist/config/settings.py | 11 +++++++++++ tubearchivist/requirements.txt | 1 + 3 files changed, 13 insertions(+) diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index ccbc22f..745a5eb 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -178,6 +178,7 @@ class DownloadApiListView(ApiBaseView): @staticmethod def post(request): """add list of videos to download queue""" + print(f"request meta data: {request.META}") data = request.data try: to_add = data["data"] diff --git a/tubearchivist/config/settings.py b/tubearchivist/config/settings.py index cb12ad6..9999b7a 100644 --- a/tubearchivist/config/settings.py +++ b/tubearchivist/config/settings.py @@ -14,6 +14,7 @@ import hashlib from os import environ, path from pathlib import Path +from corsheaders.defaults import default_headers from home.src.config import AppConfig # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -41,6 +42,7 @@ INSTALLED_APPS = [ "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", + "corsheaders", "whitenoise.runserver_nostatic", "django.contrib.staticfiles", "django.contrib.humanize", @@ -52,6 +54,7 @@ INSTALLED_APPS = [ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -140,3 +143,11 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" LOGIN_URL = "/login/" LOGOUT_REDIRECT_URL = "/login/" + +# Cors needed for browser extension +# background.js makes the request so HTTP_ORIGIN will be from extension +CORS_ALLOWED_ORIGIN_REGEXES = [r"moz-extension://*"] + +CORS_ALLOW_HEADERS = list(default_headers) + [ + "mode", +] diff --git a/tubearchivist/requirements.txt b/tubearchivist/requirements.txt index 3ebbf26..2bf1310 100644 --- a/tubearchivist/requirements.txt +++ b/tubearchivist/requirements.txt @@ -1,5 +1,6 @@ beautifulsoup4==4.10.0 celery==5.2.3 +django-cors-headers==3.11.0 Django==4.0.1 djangorestframework==3.13.1 Pillow==9.0.0 From fec62379075d2e4bfe56790352143a1e2428626e Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 15 Jan 2022 12:27:36 +0700 Subject: [PATCH 2/4] add chrome-extension to CORS_ALLOWED_ORIGIN --- tubearchivist/config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tubearchivist/config/settings.py b/tubearchivist/config/settings.py index 9999b7a..c1eb432 100644 --- a/tubearchivist/config/settings.py +++ b/tubearchivist/config/settings.py @@ -146,7 +146,7 @@ LOGOUT_REDIRECT_URL = "/login/" # Cors needed for browser extension # background.js makes the request so HTTP_ORIGIN will be from extension -CORS_ALLOWED_ORIGIN_REGEXES = [r"moz-extension://*"] +CORS_ALLOWED_ORIGIN_REGEXES = [r"moz-extension://*", r"chrome-extension://*"] CORS_ALLOW_HEADERS = list(default_headers) + [ "mode", From e1cdd79b45cccb9cd15042f1f80d7f7419dd09ec Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 15 Jan 2022 13:03:44 +0700 Subject: [PATCH 3/4] added browser extension proof of concept --- browser_extension/README.md | 26 +++++ browser_extension/extension/background.js | 94 ++++++++++++++++ browser_extension/extension/images/icon.png | Bin 0 -> 6981 bytes .../extension/images/icon128.png | Bin 0 -> 22039 bytes browser_extension/extension/images/icon16.png | Bin 0 -> 3238 bytes browser_extension/extension/images/logo.svg | 102 ++++++++++++++++++ .../extension/images/question.svg | 16 +++ .../extension/images/social/discord.svg | 24 +++++ .../extension/images/social/github.svg | 23 ++++ .../extension/images/social/reddit.svg | 35 ++++++ browser_extension/extension/index.html | 93 ++++++++++++++++ browser_extension/extension/manifest.json | 25 +++++ browser_extension/extension/popup.js | 63 +++++++++++ browser_extension/extension/script.js | 72 +++++++++++++ 14 files changed, 573 insertions(+) create mode 100644 browser_extension/README.md create mode 100644 browser_extension/extension/background.js create mode 100644 browser_extension/extension/images/icon.png create mode 100644 browser_extension/extension/images/icon128.png create mode 100644 browser_extension/extension/images/icon16.png create mode 100644 browser_extension/extension/images/logo.svg create mode 100644 browser_extension/extension/images/question.svg create mode 100644 browser_extension/extension/images/social/discord.svg create mode 100644 browser_extension/extension/images/social/github.svg create mode 100644 browser_extension/extension/images/social/reddit.svg create mode 100644 browser_extension/extension/index.html create mode 100644 browser_extension/extension/manifest.json create mode 100644 browser_extension/extension/popup.js create mode 100644 browser_extension/extension/script.js diff --git a/browser_extension/README.md b/browser_extension/README.md new file mode 100644 index 0000000..9737e6e --- /dev/null +++ b/browser_extension/README.md @@ -0,0 +1,26 @@ +# Tube Archivist Companion +A browser extension to directly add videos from YouTube to Tube Archivist. + +## MVP or better *bearly viable product* +This is a proof of concept with the following functionality: +- Add your Tube Archivist connection details in the addon popup +- Inject a download button into youtube search results page +- Clicking the button will automatically add the video to the your download queue + +## Test this extension +- Firefox + - Open `about:debugging#/runtime/this-firefox` + - Click on *Load Temporary Add-on* + - Select the *manifest.json* file to load the addon. +- Chrome / Chromium + - Open `chrome://extensions/` + - Toggle *Developer mode* on top right + - Click on *Load unpacked* + - Open the folder containing the *manifest.json* file. + +## Help needed +This is only minimally useful in this state. Join us on our Discord and please help us improve that. + +## Note: +- For mysterious reasons sometimes the download buttons will only load when refreshing the YouTube search page and not on first load... Hence: Help needed! +- For your testing environment only for now: Point the extension to the newest *unstable* build. diff --git a/browser_extension/extension/background.js b/browser_extension/extension/background.js new file mode 100644 index 0000000..9e73291 --- /dev/null +++ b/browser_extension/extension/background.js @@ -0,0 +1,94 @@ +/* +extension background script listening for events +*/ + +console.log("running background.js"); + +let browserType = getBrowser(); + + +// boilerplate to dedect browser type api +function getBrowser() { + if (typeof chrome !== "undefined") { + if (typeof browser !== "undefined") { + console.log("detected firefox"); + return browser; + } else { + console.log("detected chrome"); + return chrome; + } + } else { + console.log("failed to dedect browser"); + throw "browser detection error" + }; +} + + +// send post request to API backend +async function sendPayload(url, token, payload) { + + const rawResponse = await fetch(url, { + method: "POST", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": token, + "mode": "no-cors" + }, + body: JSON.stringify(payload) + }); + + const content = await rawResponse.json(); + return content; +} + + +// read access storage and send +function forwardRequest(payload) { + + console.log("running forwardRequest"); + + function onGot(item) { + console.log(item.access); + + const url = `${item.access.url}:${item.access.port}/api/download/`; + console.log(`sending to ${url}`); + const token = `Token ${item.access.apiKey}`; + + sendPayload(url, token, payload).then(content => { + console.log(content); + }) + + }; + + function onError(error) { + console.local("failed to get access details"); + console.log(`Error: ${error}`); + }; + + browserType.storage.local.get("access", function(result) { + onGot(result) + }); + +} + + +// listen for messages +browserType.runtime.onMessage.addListener( + function(request, sender, sendResponse) { + console.log("responding from background.js listener"); + console.log(JSON.stringify(request)); + if (request.download) { + console.log("found new download task"); + let payload = { + "data": [ + { + "youtube_id": request.download["videoId"], + "status": "pending", + } + ] + } + forwardRequest(payload); + }; + } +); diff --git a/browser_extension/extension/images/icon.png b/browser_extension/extension/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..33c0c5bd2d703fbe7a2867f5a70098cef61ede33 GIT binary patch literal 6981 zcmV-L8@l9)P)StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@KaetdT zeSggCNQeas*L^irQ?L8$_x<|oFZK5bTDtI6i2wo&M3^>DrQvpwkXIN6Y*0!;h#(MO z`K2{t@P)rB6leyr1(C@LN*XWhhR10Qr3E3-43u<{FQTY{QAiAwd{scwNx=}Hl-jz9 zbPklz#(;PB>7LdmS2Pk`dw$=O8%x`Fv|N9+cj0N3feDc*mHS6?J%i?03Y;SxAWm6u z{{IJv5$KjZnt(@bSTfHuzthvv=4cKxw+hWDXYyLM2w2XhP^f#3Hlsx$Oz1{oB2_*z zQrz8>{9qrCrw|Q6cHksneJLOU5Q=yfrXBj)g78IW2j+Bw60KM^az&mmLY7<(KOqDl z0>aXCE0r%;woQZ_^oGx05c%4ffqh5PZ|+RK^D*oKq9F+J<@PlNAR-_LKnV;B6LDpF z^NbrVaxI%{TXuOO#RN#uBR5AxS>yS#BjlIdDnP_o2E!@NAp5)ymy;JN_DD)6+&yc0 zc;3wLqIpA)y;43hjK(ljnVMVxVk!e8pZ78fFhb2j8HGa2%0d=5Ue_!eU`! znAV};Oz%)({}B}R)@xS8KRS?nX}jd}NG^qXRo3A1ojT3m+2-hKqq3wYGO)Qb7DhoE zed4vT7q`OaA^|T<`(yzTi6U7LrqOZ76%AJ`)iQZKQ*g&35F-2T$BFHq77q0DYypfx zae#nH4&hR`Wk54wTF?#HT=x51r?odOo)hVwZ!na{lak-#2?gRWZ9DS&=is0S`e09C zQlA6FD6%DJTE};n@OIwQ3V-Vhiu|=G5k0y+40kC^CE=V)GL$>a%gE8cTA;Q}4O9Y1QI< z-!M@eN@HkZ;CrHz+;sfa%o1hEe$3P}S1G{=_>UIi1ePS*L53VZ<}u zGdkslK=$pAK`C`e;Pq_)#~U>P1d)&!z(w8Z^Ew81@1YXoWnujEA?)2-V;M_NAsaw= z8oXYZCIB!7!^HR?YNDusqyNDd>}0O_o0nC1t}rmxcyYHeFfqD*BX>9Bv)eg#WQ0;N zGB|6=qQ86Kho&G_x$IC~mENJ`TRSDckGLGi+_f67Cs#2-9(n57(Y~YbdH}$X0sZ{t z=P3>czzj=l+Oh>D4TVB<&G~J&U8be;lu>8E+xw$O(mM~JrQ!JaB-LezvJP%6|IWI* zPhWTp@SJX}Sv$2>j-z)z*fqN6L*n%aA|Y}3td*C%`PAd}*%en_`_^-RCQV)A&%L1r ze6j1ko*5jG{hruOD>Da=igE>tv(7we4TK_F5TK+OjJf)<6kyIgo z6Mdpas^SDJ03sFlo;v@wn{KGD0C3IatHQIp5g&(Fg%EvzdhR(PpB(AG-}`>?qn^~x12o_ffSFCKT7A{U%NKzFglvER{U_Hy zCHWi3q=qRWhx!KKj|j@3r>?y88vqdi0jU}r0oGo&@^^dR18+tsp!JUx_8j)FSfJ$! ztSdJ6H-EqP?)&pSg9v&7NCXfi!c|6q<%$2o)1x-@NLIs(Py4%pn-at=Fvi-GBT76&-@Jpw}^bI`g}o$y|2x zN7iu4G)$t%5D)~CEm4>jEI#z7=Z5}wOK9O-Ns&Mm+R_C9PJofa(Z)bsv_BLCC}O94 zCK>^-EF^|H7hbq@@e%+8WMFUvgtWIc132f5xlMEDP3+l8T3fl!bDQ_1Ui&cp{$68f z5Gcdd%%VZAn*byf%1Sgm?WRl0|_rJSM5JsKy@bhn@tRdhIeSL9w(Y(ag-Pvt>5%EDlf?>)s z^Dmtn_PQg>&n)K)*UX_PB73J^=u+#s~lg3ZSVm1lT8Ph*IQ+&tAH?t+ly2FgiA#JJM&39zE38TOA1o zg5ArPpfD^Lh0|f=^180;x>4*Mas<2xdtuuEB-pm(@puA3+qPx5ntX9<-*28KUYB6B zrVikhCpJjgoUkQE?N_#JuALYmlYU(-ZS23jVND(3A0Ar|GY#Okk3RM5d%s^hwVQ8# z@7X`Ywjnc-$?N?SuDKnCVZh~(6sJ|zAb}vz>THa3eeXu%aw-Au;FGV9KmH1)MTsql z0Iz7Sxm~=;qs0^85@0)Jea(#^#Bh zU3H+LS!ZRA4x%B5iOh^|tO{MVRL_-oF5mO>C(Tp=ZbgDEB!^5T2Ac~ZxM6}yu!R7S zEvTJsv;O%;raG;X4sZ&}R41{l1OJrC@9#s{2LVtDJ$e0_JL+iO-TdxgVz}$K|00=W z|2_9@di|{nEhM63FyHO&hF0ti6}sYh|)kVUCt z^iPz=lZnCM)X;c*V7M@mDvl+MVi{6RJPZqPtXq3UT@4REwgFD~nj2g#A^6-6Kl*fi z>elP824&Dq*rcjpK_DQAsM-M}P-5zaFm39PElPw(IcBO}mzK&Myzep3^kxdf(7A(2 zMT+7?)=Z~~-vc2C2#*hTeeLTD&pW5ONH_H-p5B1Y7z$;W9FfNL8=iUOpb0BvV6YMJ@_T&KW6&#v91DHb6jF0?BI>C`fCK#Q8?UW%KL6Sqh5r2j zBa9J%QwoU!ta$X3SKi!GXIr;s6}p-wpGP-MiBNMS1>3I1JPH7QPkP@GZ8RadRIZt> z1)XrK)l~PTM3GLoJ7!(O zNv~afxkGhUH8wFc{MOdDTkpAf=6!btX11H@f|)A@=XA{a>0Pb&+_H7s)`9-v+ETac z>P3s=qX}OinECXu);mbuuG%h1t!_bb*Fj+zAe0D%R-6fk4Bec>G*zLfTL4{m_2qSj zm$trV4fZB}`6LN>TW()1iWO4STJO994wnAU^&&Cy(%Z*;ZBrt-r{psDJ?Hfbrcs*44-R28MTTd=;S> zS|Wu#JqPZ6Sn;?CV?F=+Q0dShX=^|r{OP9G4(-}sUvkULbFAmLLNlPs^?T6qS80K< zbjLkwLzgYg_YJ#Z4QAiip&vYG=5ieiyE|Hckf{YyZ{FTM$-*T=bMI=eSa^R)+q8t6f4x_T=wH{&pf?T&lkPzO@pt0@cj3F zfmp-wci-axQG!g7Dbi^_xyu=As0_!Iu2ya5;e$VU%+8HZ7Cv)&gz{RO>9l6B$rSB7TuA_-Dxb4REj-j#%h~Zr^8{4MM=@(R%tT$z0B; zLk1%Rgb0Av4vmV*!q&2o$|F})o5C&ET`*(aW%>O>x%c0pk;c~5%Q72x3_bEZnj&D7 z2ypu4fB-NGRVnQ`B+5pqXK4JH7d*2Uw0-AlVOtsqN|royZK_z{7wWtu9k-Mjsv+*`c})o8&g_|Y zUS*WZye*8f!IOE@K_!pNsVc&QVr30wP3Bo8)txUy=R~ XRn)t+=fFbo00000NkvXXu0mjfy!A!# literal 0 HcmV?d00001 diff --git a/browser_extension/extension/images/icon128.png b/browser_extension/extension/images/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..2c08551a21c05b68c95d611a2c905da40d2d07e1 GIT binary patch literal 22039 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 Y|G#O&VPC}!Qvd(}07*qoM6N<$f-sv6WB>pF literal 0 HcmV?d00001 diff --git a/browser_extension/extension/images/logo.svg b/browser_extension/extension/images/logo.svg new file mode 100644 index 0000000..3f900d4 --- /dev/null +++ b/browser_extension/extension/images/logo.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser_extension/extension/images/question.svg b/browser_extension/extension/images/question.svg new file mode 100644 index 0000000..872deef --- /dev/null +++ b/browser_extension/extension/images/question.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/browser_extension/extension/images/social/discord.svg b/browser_extension/extension/images/social/discord.svg new file mode 100644 index 0000000..dd07931 --- /dev/null +++ b/browser_extension/extension/images/social/discord.svg @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/browser_extension/extension/images/social/github.svg b/browser_extension/extension/images/social/github.svg new file mode 100644 index 0000000..c693abf --- /dev/null +++ b/browser_extension/extension/images/social/github.svg @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/browser_extension/extension/images/social/reddit.svg b/browser_extension/extension/images/social/reddit.svg new file mode 100644 index 0000000..e6c615c --- /dev/null +++ b/browser_extension/extension/images/social/reddit.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/browser_extension/extension/index.html b/browser_extension/extension/index.html new file mode 100644 index 0000000..f8c254a --- /dev/null +++ b/browser_extension/extension/index.html @@ -0,0 +1,93 @@ + + + + + + + TubeArchivist Companion + + + + +
+ ta-logo +
+ +
+ +
+
+ +
+ + question-icon + +
+
+
+ + + \ No newline at end of file diff --git a/browser_extension/extension/manifest.json b/browser_extension/extension/manifest.json new file mode 100644 index 0000000..a260861 --- /dev/null +++ b/browser_extension/extension/manifest.json @@ -0,0 +1,25 @@ +{ + "manifest_version": 2, + "name": "TubeArchivist Companion", + "description": "Interact with your selhosted TA server.", + "version": "0.0.1", + "icons": { + "128": "/images/icon128.png" + }, + "browser_action": { + "default_icon": "/images/icon.png", + "default_popup": "index.html" + }, + "permissions": [ + "storage" + ], + "content_scripts": [ + { + "matches": ["https://www.youtube.com/results*"], + "js": ["script.js"] + } + ], + "background": { + "scripts": ["background.js"] + } +} diff --git a/browser_extension/extension/popup.js b/browser_extension/extension/popup.js new file mode 100644 index 0000000..84c21da --- /dev/null +++ b/browser_extension/extension/popup.js @@ -0,0 +1,63 @@ +/* +Loaded into popup index.html +*/ + +let browserType = getBrowser(); + +// boilerplate to dedect browser type api +function getBrowser() { + if (typeof chrome !== "undefined") { + if (typeof browser !== "undefined") { + console.log("detected firefox"); + return browser; + } else { + console.log("detected chrome"); + return chrome; + } + } else { + console.log("failed to dedect browser"); + throw "browser detection error" + }; +} + +// store access details +document.getElementById("save-login").addEventListener("click", function () { + console.log("save form"); + let toStore = { + "access": { + "url": document.getElementById("url").value, + "port": document.getElementById("port").value, + "apiKey": document.getElementById("api-key").value + } + }; + console.log(toStore); + browserType.storage.local.set(toStore, function() { + console.log("Stored connection details: " + JSON.stringify(toStore)); + }); +}) + +// fill in form +document.addEventListener("DOMContentLoaded", async () => { + + console.log("executing dom loader"); + + function onGot(item) { + if (!item.access) { + console.log("no access details found"); + return + } + console.log(item.access); + document.getElementById("url").value = item.access.url; + document.getElementById("port").value = item.access.port; + document.getElementById("api-key").value = item.access.apiKey; + }; + + function onError(error) { + console.log(`Error: ${error}`); + }; + + browserType.storage.local.get("access", function(result) { + onGot(result) + }); + +}) diff --git a/browser_extension/extension/script.js b/browser_extension/extension/script.js new file mode 100644 index 0000000..803e094 --- /dev/null +++ b/browser_extension/extension/script.js @@ -0,0 +1,72 @@ +/* +content script running on youtube.com +*/ + +console.log("running script.js"); + +let browserType = getBrowser(); + +setTimeout(function(){ + console.log("running setimeout") + linkFinder(); + return false; +}, 2000); + + +// boilerplate to dedect browser type api +function getBrowser() { + if (typeof chrome !== "undefined") { + if (typeof browser !== "undefined") { + console.log("detected firefox"); + return browser; + } else { + console.log("detected chrome"); + return chrome; + } + } else { + console.log("failed to dedect browser"); + throw "browser detection error" + }; +} + + +// event handler for download task +function addToDownload(videoId) { + + console.log(`downloading ${videoId}`); + let payload = { + "download": { + "videoId": videoId + } + }; + + browserType.runtime.sendMessage(payload); + +} + + +// find relevant links to add a button to +function linkFinder() { + + console.log("running link finder"); + + var allLinks = document.links; + for (let i = 0; i < allLinks.length; i++) { + + const linkItem = allLinks[i]; + const linkDest = linkItem.getAttribute("href"); + + if (linkDest.startsWith("/watch?v=") && linkItem.id == "video-title") { + var dlButton = document.createElement("button"); + dlButton.innerText = "download"; + var videoId = linkDest.split("=")[1]; + dlButton.setAttribute("data-id", videoId); + dlButton.setAttribute("id", "ta-dl-" + videoId); + dlButton.onclick = function(event) { + var videoId = this.getAttribute("data-id"); + addToDownload(videoId); + }; + linkItem.parentElement.appendChild(dlButton); + } + } +} From 79b0a989be70ebc3c1fa4cf0448dc713039ddb9c Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 15 Jan 2022 14:14:18 +0700 Subject: [PATCH 4/4] new api view to return video player --- tubearchivist/api/README.md | 4 ++++ tubearchivist/api/urls.py | 6 ++++++ tubearchivist/api/views.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/tubearchivist/api/README.md b/tubearchivist/api/README.md index 73dd4fc..0e74a11 100644 --- a/tubearchivist/api/README.md +++ b/tubearchivist/api/README.md @@ -23,6 +23,10 @@ response = requests.get(url, headers=headers) ## Video Item View /api/video/\/ +## Video Player View +returns all relevant information to create video player +/api/video/\/player + ## Channel List View /api/channel/ diff --git a/tubearchivist/api/urls.py b/tubearchivist/api/urls.py index d39dc30..a6c6801 100644 --- a/tubearchivist/api/urls.py +++ b/tubearchivist/api/urls.py @@ -6,6 +6,7 @@ from api.views import ( DownloadApiListView, DownloadApiView, PlaylistApiView, + VideoApiPlayerView, VideoApiView, ) from django.urls import path @@ -16,6 +17,11 @@ urlpatterns = [ VideoApiView.as_view(), name="api-video", ), + path( + "video//player/", + VideoApiPlayerView.as_view(), + name="api-video-player", + ), path( "channel/", ChannelApiListView.as_view(), diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index 745a5eb..165ca60 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -3,6 +3,7 @@ import requests from home.src.config import AppConfig from home.src.helper import UrlListParser +from home.src.thumbnails import ThumbManager from home.tasks import extrac_dl, subscribe_to from rest_framework.authentication import ( SessionAuthentication, @@ -77,6 +78,38 @@ class VideoApiView(ApiBaseView): return Response(self.response, status=self.status_code) +class VideoApiPlayerView(ApiBaseView): + """resolves to /api/video//player + GET: returns dict of video to build player + """ + + search_base = "/ta_video/_doc/" + + def get(self, request, video_id): + # pylint: disable=unused-argument + """get request""" + self.config_builder() + self.get_document(video_id) + player = self.process_response() + return Response(player, status=self.status_code) + + def process_response(self): + """build all needed vars for player""" + vid_data = self.response["data"] + youtube_id = vid_data["youtube_id"] + vid_thumb_url = ThumbManager().vid_thumb_path(youtube_id) + player = { + "youtube_id": youtube_id, + "media_url": "/media/" + vid_data["media_url"], + "vid_thumb_url": "/cache/" + vid_thumb_url, + "title": vid_data["title"], + "channel_name": vid_data["channel"]["channel_name"], + "channel_id": vid_data["channel"]["channel_id"], + "is_watched": vid_data["player"]["watched"], + } + return player + + class ChannelApiView(ApiBaseView): """resolves to /api/channel// GET: returns metadata dict of channel