From 8eb2831bc672cddd3b4e3bab2dd81389c53b77fe Mon Sep 17 00:00:00 2001 From: Marco <35533749+marcopeocchi@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:46:24 +0200 Subject: [PATCH] 49 feat add cookies (#98) * build client side validation and submission * enabled cookies submission, bug fixes --- .dockerignore | 16 +- .gitignore | 1 + frontend/src/atoms/settings.ts | 9 ++ frontend/src/components/CookiesTextField.tsx | 161 +++++++++++++++++++ frontend/src/components/Downloads.tsx | 4 +- frontend/src/views/Settings.tsx | 7 + server/internal/common.go | 5 + server/rest/container.go | 1 + server/rest/handlers.go | 33 +++- server/rest/service.go | 13 ++ 10 files changed, 238 insertions(+), 12 deletions(-) create mode 100644 frontend/src/components/CookiesTextField.tsx diff --git a/.dockerignore b/.dockerignore index acfeba9..7cb0604 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,15 +1,17 @@ -node_modules -downloads dist package-lock.json pnpm-lock.yaml .pnpm-debug.log -.parcel-cache -.git -src/server/core/*.exe -src/server/core/yt-dlp +node_modules .env *.mp4 *.ytdl +*.part *.db -build/ \ No newline at end of file +downloads +.DS_Store +build/ +yt-dlp-webui +session.dat +config.yml +cookies.txt diff --git a/.gitignore b/.gitignore index 775cbfb..7cb0604 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ build/ yt-dlp-webui session.dat config.yml +cookies.txt diff --git a/frontend/src/atoms/settings.ts b/frontend/src/atoms/settings.ts index dd73e44..88596b5 100644 --- a/frontend/src/atoms/settings.ts +++ b/frontend/src/atoms/settings.ts @@ -172,6 +172,15 @@ export const rpcHTTPEndpoint = selector({ } }) +export const cookiesState = atom({ + key: 'cookiesState', + default: localStorage.getItem('yt-dlp-cookies') ?? '', + effects: [ + ({ onSet }) => + onSet(c => localStorage.setItem('yt-dlp-cookies', c)) + ] +}) + export const themeSelector = selector({ key: 'themeSelector', get: ({ get }) => { diff --git a/frontend/src/components/CookiesTextField.tsx b/frontend/src/components/CookiesTextField.tsx new file mode 100644 index 0000000..1864a77 --- /dev/null +++ b/frontend/src/components/CookiesTextField.tsx @@ -0,0 +1,161 @@ +import { TextField } from '@mui/material' +import * as A from 'fp-ts/Array' +import * as E from 'fp-ts/Either' +import * as O from 'fp-ts/Option' +import { pipe } from 'fp-ts/lib/function' +import { useMemo } from 'react' +import { useRecoilState, useRecoilValue } from 'recoil' +import { Subject, debounceTime, distinctUntilChanged } from 'rxjs' +import { downloadTemplateState } from '../atoms/downloadTemplate' +import { cookiesState, serverURL } from '../atoms/settings' +import { useSubscription } from '../hooks/observable' +import { useToast } from '../hooks/toast' +import { ffetch } from '../lib/httpClient' + +const validateCookie = (cookie: string) => pipe( + cookie, + cookie => cookie.replace(/\s\s+/g, ' '), + cookie => cookie.replaceAll('\t', ' '), + cookie => cookie.split(' '), + E.of, + E.chain( + E.fromPredicate( + f => f.length === 7, + () => `missing parts` + ) + ), + E.chain( + E.fromPredicate( + f => f[0].length > 0, + () => 'missing domain' + ) + ), + E.chain( + E.fromPredicate( + f => f[1] === 'TRUE' || f[1] === 'FALSE', + () => `invalid include subdomains` + ) + ), + E.chain( + E.fromPredicate( + f => f[2].length > 0, + () => 'invalid path' + ) + ), + E.chain( + E.fromPredicate( + f => f[3] === 'TRUE' || f[3] === 'FALSE', + () => 'invalid secure flag' + ) + ), + E.chain( + E.fromPredicate( + f => isFinite(Number(f[4])), + () => 'invalid expiration' + ) + ), + E.chain( + E.fromPredicate( + f => f[5].length > 0, + () => 'invalid name' + ) + ), + E.chain( + E.fromPredicate( + f => f[6].length > 0, + () => 'invalid value' + ) + ), +) + +const CookiesTextField: React.FC = () => { + const serverAddr = useRecoilValue(serverURL) + const [customArgs, setCustomArgs] = useRecoilState(downloadTemplateState) + const [savedCookies, setSavedCookies] = useRecoilState(cookiesState) + + const { pushMessage } = useToast() + const flag = '--cookies=cookies.txt' + + const cookies$ = useMemo(() => new Subject(), []) + + const submitCookies = (cookies: string) => + ffetch(`${serverAddr}/api/v1/cookies`, { + method: 'POST', + body: JSON.stringify({ + cookies + }) + })() + + const validateNetscapeCookies = (cookies: string) => pipe( + cookies, + cookies => cookies.split('\n'), + cookies => cookies.filter(f => !f.startsWith('\n')), // empty lines + cookies => cookies.filter(f => !f.startsWith('# ')), // comments + cookies => cookies.filter(Boolean), // empty lines + A.map(validateCookie), + A.mapWithIndex((i, either) => pipe( + either, + E.matchW( + (l) => pushMessage(`Error in line ${i + 1}: ${l}`, 'warning'), + () => E.isRight(either) + ), + )), + A.filter(Boolean), + A.match( + () => false, + (c) => { + pushMessage(`Valid ${c.length} Netscape cookies`, 'info') + return true + } + ) + ) + + useSubscription( + cookies$.pipe( + debounceTime(650), + distinctUntilChanged() + ), + (cookies) => pipe( + cookies, + cookies => { + setSavedCookies(cookies) + return cookies + }, + validateNetscapeCookies, + O.fromPredicate(f => f === true), + O.match( + () => { + if (customArgs.includes(flag)) { + setCustomArgs(a => a.replace(flag, '')) + } + }, + async () => { + pipe( + await submitCookies(cookies), + E.match( + (l) => pushMessage(`${l}`, 'error'), + () => pushMessage(`Saved Netscape cookies`, 'success') + ) + ) + if (!customArgs.includes(flag)) { + setCustomArgs(a => `${a} ${flag}`) + } + } + ) + ) + ) + + return ( + cookies$.next(e.currentTarget.value)} + /> + ) +} + +export default CookiesTextField diff --git a/frontend/src/components/Downloads.tsx b/frontend/src/components/Downloads.tsx index 4915794..fe6f1aa 100644 --- a/frontend/src/components/Downloads.tsx +++ b/frontend/src/components/Downloads.tsx @@ -10,13 +10,13 @@ const Downloads: React.FC = () => { const listView = useRecoilValue(listViewState) const active = useRecoilValue(activeDownloadsState) - const [, setIsLoading] = useRecoilState(loadingAtom) + const [isLoading, setIsLoading] = useRecoilState(loadingAtom) useEffect(() => { if (active) { setIsLoading(false) } - }, [active?.length]) + }, [active?.length, isLoading]) if (listView) { return ( diff --git a/frontend/src/views/Settings.tsx b/frontend/src/views/Settings.tsx index ffe39b6..e21eaea 100644 --- a/frontend/src/views/Settings.tsx +++ b/frontend/src/views/Settings.tsx @@ -43,6 +43,7 @@ import { serverPortState, themeState } from '../atoms/settings' +import CookiesTextField from '../components/CookiesTextField' import { useToast } from '../hooks/toast' import { useI18n } from '../hooks/useI18n' import { useRPC } from '../hooks/useRPC' @@ -298,6 +299,12 @@ export default function Settings() { /> + + + Cookies + + +