From f711c6b7a40dc62a292512a5e04dad800bc21b41 Mon Sep 17 00:00:00 2001 From: NimBold Date: Tue, 7 Jul 2026 12:44:19 +0330 Subject: [PATCH] feat(media): support explicit fetch media intent --- Extensions/Firefox | 2 +- src-tauri/src/extension_server.rs | 8 +- src-tauri/src/lib.rs | 81 ++++++++++++++++--- src-tauri/src/parity.rs | 5 ++ src/bindings/ExtensionDownload.ts | 2 +- src/components/AddDownloadsModal.tsx | 14 +++- src/ipc.ts | 2 +- src/store/useDownloadStore.test.ts | 108 ++++++++++++++++++++------ src/store/useDownloadStore.ts | 23 ++++-- src/utils/addDownloadMetadata.test.ts | 44 +++++++++++ src/utils/addDownloadMetadata.ts | 17 +++- src/utils/downloads.ts | 7 +- src/utils/mediaMetadata.ts | 2 + 13 files changed, 268 insertions(+), 47 deletions(-) diff --git a/Extensions/Firefox b/Extensions/Firefox index 19e4b1b..e2b2ec7 160000 --- a/Extensions/Firefox +++ b/Extensions/Firefox @@ -1 +1 @@ -Subproject commit 19e4b1b6d1a8a67b547bbcfe8edd0e7ab9329392 +Subproject commit e2b2ec7b4e0b2dbcee788f55f14bc8212629bc6c diff --git a/src-tauri/src/extension_server.rs b/src-tauri/src/extension_server.rs index 2c2885f..2a15124 100644 --- a/src-tauri/src/extension_server.rs +++ b/src-tauri/src/extension_server.rs @@ -31,7 +31,7 @@ const CLIENT_NONCE_HEADER: &str = "x-firelink-client-nonce"; const SERVER_PROOF_HEADER: &str = "x-firelink-server-proof"; const SERVER_PORT_HEADER: &str = "x-firelink-server-port"; const SERVER_PROOF_PREFIX: &[u8] = b"firelink-server-proof\n"; -const PROTOCOL_VERSION: &str = "3"; +const PROTOCOL_VERSION: &str = "4"; type HmacSha256 = Hmac; pub type SharedExtensionToken = Arc>; @@ -61,6 +61,8 @@ struct ExtensionRequest { headers: Option, #[serde(default)] cookies: Option, + #[serde(default)] + media: bool, } #[derive(Clone, Serialize, TS)] @@ -72,6 +74,7 @@ pub struct ExtensionDownload { filename: Option, headers: Option, cookies: Option, + media: bool, } pub async fn start_server( @@ -318,6 +321,7 @@ fn normalize_download(payload: ExtensionRequest) -> Option { filename, headers: payload.headers.filter(|value| !value.trim().is_empty()), cookies: payload.cookies.filter(|value| !value.trim().is_empty()), + media: payload.media, }) } @@ -478,7 +482,7 @@ mod tests { assert_eq!(response.headers().get(SERVER_HEADER).unwrap(), "1"); assert_eq!( response.headers().get(PROTOCOL_VERSION_HEADER).unwrap(), - "3" + "4" ); server.abort(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a61af29..8390ea0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -20,16 +20,22 @@ fn get_metadata_cache() -> &'static std::sync::Mutex> { fn metadata_info_cache_key( url: &str, cookie_source: Option<&str>, + username: Option<&str>, + password: Option<&str>, headers: Option<&str>, cookies: Option<&str>, proxy: Option<&str>, + user_agent: Option<&str>, ) -> String { serde_json::json!([ url, cookie_source.unwrap_or(""), + username.unwrap_or(""), + password.unwrap_or(""), headers.unwrap_or(""), cookies.unwrap_or(""), - proxy.unwrap_or("") + proxy.unwrap_or(""), + user_agent.unwrap_or("") ]) .to_string() } @@ -1320,6 +1326,7 @@ async fn fetch_media_metadata( app_handle: tauri::AppHandle, url: String, cookie_browser: Option, + user_agent: Option, username: Option, password: Option, headers: Option, @@ -1370,6 +1377,7 @@ async fn fetch_media_metadata( app_handle.clone(), url.clone(), cookie_browser.clone(), + user_agent.clone(), username.clone(), password.clone(), headers.clone(), @@ -1387,7 +1395,7 @@ async fn fetch_media_metadata( browser ); fetch_media_metadata_uncached( - app_handle, url, None, username, password, headers, cookies, proxy, + app_handle, url, None, user_agent, username, password, headers, cookies, proxy, ) .await } @@ -1427,6 +1435,7 @@ async fn fetch_media_metadata_uncached( app_handle: tauri::AppHandle, url: String, cookie_browser: Option, + user_agent: Option, username: Option, password: Option, headers: Option, @@ -1436,9 +1445,12 @@ async fn fetch_media_metadata_uncached( let info_cache_key = metadata_info_cache_key( &url, cookie_browser.as_deref(), + username.as_deref(), + password.as_deref(), headers.as_deref(), cookies.as_deref(), proxy.as_deref(), + user_agent.as_deref(), ); // Pass bundled tools by absolute path so extraction never depends on @@ -1489,20 +1501,24 @@ async fn fetch_media_metadata_uncached( } } + if let Some(ua) = user_agent.as_deref().map(str::trim).filter(|s| !s.is_empty()) { + cmd = cmd.arg("--user-agent").arg(ua); + } + let mut config_file = tempfile::Builder::new() .prefix("ytdlp-") .suffix(".conf") .tempfile() .map_err(|e| e.to_string())?; let mut config_content = String::new(); - if let Some(user) = username { + if let Some(user) = username.as_deref() { if !user.is_empty() { - append_ytdlp_config_option(&mut config_content, "--username", &user); + append_ytdlp_config_option(&mut config_content, "--username", user); } } - if let Some(pass) = password { + if let Some(pass) = password.as_deref() { if !pass.is_empty() { - append_ytdlp_config_option(&mut config_content, "--password", &pass); + append_ytdlp_config_option(&mut config_content, "--password", pass); } } append_ytdlp_http_headers(&mut config_content, headers.as_deref(), cookies.as_deref())?; @@ -2678,14 +2694,14 @@ pub(crate) async fn start_media_download_internal( .tempfile() .map_err(|e| e.to_string())?; let mut config_content = String::new(); - if let Some(user) = username { + if let Some(user) = username.as_deref() { if !user.is_empty() { - append_ytdlp_config_option(&mut config_content, "--username", &user); + append_ytdlp_config_option(&mut config_content, "--username", user); } } - if let Some(pass) = password { + if let Some(pass) = password.as_deref() { if !pass.is_empty() { - append_ytdlp_config_option(&mut config_content, "--password", &pass); + append_ytdlp_config_option(&mut config_content, "--password", pass); } } append_ytdlp_http_headers(&mut config_content, headers.as_deref(), cookies.as_deref())?; @@ -2820,9 +2836,12 @@ pub(crate) async fn start_media_download_internal( let info_cache_key = metadata_info_cache_key( &url, effective_cookie_source.as_deref(), + username.as_deref(), + password.as_deref(), headers.as_deref(), cookies.as_deref(), proxy.as_deref(), + user_agent.as_deref(), ); if let Some(json_str) = cache.remove(&info_cache_key) { let temp_dir = std::env::temp_dir(); @@ -4604,27 +4623,69 @@ mod tests { let base = metadata_info_cache_key( "https://example.com/watch?v=1", Some("firefox"), + Some("user-one"), + Some("pass-one"), Some("User-Agent: Browser A"), Some("session=one"), None, + Some("Custom UA A"), ); let changed_headers = metadata_info_cache_key( "https://example.com/watch?v=1", Some("firefox"), + Some("user-one"), + Some("pass-one"), Some("User-Agent: Browser B"), Some("session=one"), None, + Some("Custom UA A"), ); let changed_cookies = metadata_info_cache_key( "https://example.com/watch?v=1", Some("firefox"), + Some("user-one"), + Some("pass-one"), Some("User-Agent: Browser A"), Some("session=two"), None, + Some("Custom UA A"), + ); + let changed_username = metadata_info_cache_key( + "https://example.com/watch?v=1", + Some("firefox"), + Some("user-two"), + Some("pass-one"), + Some("User-Agent: Browser A"), + Some("session=one"), + None, + Some("Custom UA A"), + ); + let changed_password = metadata_info_cache_key( + "https://example.com/watch?v=1", + Some("firefox"), + Some("user-one"), + Some("pass-two"), + Some("User-Agent: Browser A"), + Some("session=one"), + None, + Some("Custom UA A"), + ); + let changed_user_agent = metadata_info_cache_key( + "https://example.com/watch?v=1", + Some("firefox"), + Some("user-one"), + Some("pass-one"), + Some("User-Agent: Browser A"), + Some("session=one"), + None, + Some("Custom UA B"), ); assert_ne!(base, changed_headers); assert_ne!(base, changed_cookies); + assert_ne!(base, changed_username); + assert_ne!(base, changed_password); + assert_ne!(base, changed_user_agent); } #[test] diff --git a/src-tauri/src/parity.rs b/src-tauri/src/parity.rs index 436da69..3445310 100644 --- a/src-tauri/src/parity.rs +++ b/src-tauri/src/parity.rs @@ -478,6 +478,11 @@ pub static SUPPORTED_DOMAINS: &[&str] = &[ "reddit.com", "v.redd.it", "soundcloud.com", + "pornhub.com", + "redtube.com", + "xhamster.com", + "xnxx.com", + "xvideos.com", ]; #[tauri::command] diff --git a/src/bindings/ExtensionDownload.ts b/src/bindings/ExtensionDownload.ts index fdd659b..c2af9a2 100644 --- a/src/bindings/ExtensionDownload.ts +++ b/src/bindings/ExtensionDownload.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ExtensionDownload = { urls: Array, referer: string | null, silent: boolean, filename: string | null, headers: string | null, cookies: string | null, }; +export type ExtensionDownload = { urls: Array, referer: string | null, silent: boolean, filename: string | null, headers: string | null, cookies: string | null, media: boolean, }; diff --git a/src/components/AddDownloadsModal.tsx b/src/components/AddDownloadsModal.tsx index 00eb84c..3778e3c 100644 --- a/src/components/AddDownloadsModal.tsx +++ b/src/components/AddDownloadsModal.tsx @@ -57,6 +57,7 @@ export const AddDownloadsModal = () => { pendingAddFilename, pendingAddHeaders, pendingAddCookies, + pendingAddMediaUrls, toggleAddModal, addDownload, queues @@ -136,6 +137,7 @@ export const AddDownloadsModal = () => { pendingAddReferer, pendingAddHeaders, pendingAddCookies, + pendingAddMediaUrls, baseDownloadFolder, perServerConnections ]); @@ -173,10 +175,17 @@ export const AddDownloadsModal = () => { }, [saveLocation, isAddModalOpen]); useEffect(() => { + const forcedMediaUrls = new Set(pendingAddMediaUrls.map(url => { + try { + return new URL(url).href; + } catch { + return url; + } + })); setParsedItems(current => - reconcileDownloadRows(urls, current, pendingAddFilename || undefined) + reconcileDownloadRows(urls, current, pendingAddFilename || undefined, forcedMediaUrls) ); - }, [urls, pendingAddFilename]); + }, [urls, pendingAddFilename, pendingAddMediaUrls]); useEffect(() => { for (const row of parsedItems) { @@ -205,6 +214,7 @@ export const AddDownloadsModal = () => { const mediaData = await fetchMediaMetadataDeduped({ url: row.sourceUrl, cookieBrowser: browserArg, + userAgent: settingsStore.customUserAgent.trim() || null, username: useAuth ? username.trim() || null : login?.username || null, password: useAuth ? password || null : keychainPassword, headers: headers?.trim() || null, diff --git a/src/ipc.ts b/src/ipc.ts index 76947da..0b70b69 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -21,7 +21,7 @@ type CommandMap = { result: MetadataResponse; }; fetch_media_metadata: { - args: { url: string; cookieBrowser: string | null; username: string | null; password: string | null; headers: string | null; cookies: string | null; proxy: string | null }; + args: { url: string; cookieBrowser: string | null; userAgent: string | null; username: string | null; password: string | null; headers: string | null; cookies: string | null; proxy: string | null }; result: MediaMetadata; }; get_aria2_engine_status: { args: undefined; result: EngineStatusItem }; diff --git a/src/store/useDownloadStore.test.ts b/src/store/useDownloadStore.test.ts index 71edb46..a668a5a 100644 --- a/src/store/useDownloadStore.test.ts +++ b/src/store/useDownloadStore.test.ts @@ -64,6 +64,7 @@ describe('useDownloadStore', () => { pendingAddFilename: '', pendingAddHeaders: '', pendingAddCookies: '', + pendingAddMediaUrls: [], }); }); @@ -487,7 +488,8 @@ describe('useDownloadStore', () => { silent: false, filename: 'file.bin', headers: 'X-Test: value', - cookies: 'session=secret' + cookies: 'session=secret', + media: false }); const state = useDownloadStore.getState(); @@ -495,53 +497,115 @@ describe('useDownloadStore', () => { expect(state.pendingAddUrls).toBe('https://example.com/file.bin'); expect(state.pendingAddReferer).toBe('https://example.com/page'); expect(state.pendingAddFilename).toBe('file.bin'); - expect(state.pendingAddHeaders).toBe('X-Test: value'); - expect(state.pendingAddCookies).toBe('session=secret'); - }); + expect(state.pendingAddHeaders).toBe('X-Test: value'); + expect(state.pendingAddCookies).toBe('session=secret'); + expect(state.pendingAddMediaUrls).toEqual([]); + }); it('does not reuse stale extension metadata for a later single-link handoff', async () => { useDownloadStore.setState({ isAddModalOpen: true, pendingAddUrls: '', pendingAddReferer: 'https://old.example/page', - pendingAddFilename: '7aae36e6-00ec-4e7d-8dec-f14ace170bdb', - pendingAddHeaders: 'X-Old: value', - pendingAddCookies: 'old=session' - }); + pendingAddFilename: '7aae36e6-00ec-4e7d-8dec-f14ace170bdb', + pendingAddHeaders: 'X-Old: value', + pendingAddCookies: 'old=session', + pendingAddMediaUrls: [] + }); await useDownloadStore.getState().handleExtensionDownload({ urls: ['https://github.com/center2055/OnionHop/releases/download/v3.5/OnionHop-3.5-macOS-arm64.dmg'], referer: 'https://github.com/center2055/OnionHop/releases/tag/v3.5', silent: false, - filename: null, - headers: 'User-Agent: Firefox Test', - cookies: null - }); + filename: null, + headers: 'User-Agent: Firefox Test', + cookies: null, + media: false + }); const state = useDownloadStore.getState(); expect(state.pendingAddUrls).toBe('https://github.com/center2055/OnionHop/releases/download/v3.5/OnionHop-3.5-macOS-arm64.dmg'); expect(state.pendingAddReferer).toBe('https://github.com/center2055/OnionHop/releases/tag/v3.5'); expect(state.pendingAddFilename).toBe(''); - expect(state.pendingAddHeaders).toBe('User-Agent: Firefox Test'); - expect(state.pendingAddCookies).toBe(''); - }); + expect(state.pendingAddHeaders).toBe('User-Agent: Firefox Test'); + expect(state.pendingAddCookies).toBe(''); + expect(state.pendingAddMediaUrls).toEqual([]); + }); it('routes silent extension captures to the Add Modal instead of queuing immediately', async () => { await useDownloadStore.getState().handleExtensionDownload({ urls: ['https://example.com/downloads/report.pdf'], referer: 'https://example.com/page', silent: true, - filename: 'report.pdf', - headers: 'User-Agent: Test', - cookies: 'session=secret' - }); + filename: 'report.pdf', + headers: 'User-Agent: Test', + cookies: 'session=secret', + media: false + }); const state = useDownloadStore.getState(); expect(state.isAddModalOpen).toBe(true); expect(state.pendingAddUrls).toBe('https://example.com/downloads/report.pdf'); expect(state.pendingAddReferer).toBe('https://example.com/page'); expect(state.pendingAddFilename).toBe('report.pdf'); - expect(state.pendingAddHeaders).toBe('User-Agent: Test'); - expect(state.pendingAddCookies).toBe('session=secret'); - }); + expect(state.pendingAddHeaders).toBe('User-Agent: Test'); + expect(state.pendingAddCookies).toBe('session=secret'); + expect(state.pendingAddMediaUrls).toEqual([]); + }); + + it('preserves explicit extension media intent for non-allow-listed pages', async () => { + await useDownloadStore.getState().handleExtensionDownload({ + urls: ['https://adult.example/watch/123'], + referer: 'https://adult.example/watch/123', + silent: false, + filename: null, + headers: 'User-Agent: Firefox Test', + cookies: 'session=secret', + media: true + }); + + const state = useDownloadStore.getState(); + expect(state.isAddModalOpen).toBe(true); + expect(state.pendingAddUrls).toBe('https://adult.example/watch/123'); + expect(state.pendingAddMediaUrls).toEqual(['https://adult.example/watch/123']); + }); + + it('deduplicates forced media URLs and drops stale media intent when opening fresh', async () => { + useDownloadStore.setState({ + isAddModalOpen: true, + pendingAddUrls: 'https://adult.example/watch/123', + pendingAddMediaUrls: ['https://adult.example/watch/123'] + }); + + await useDownloadStore.getState().handleExtensionDownload({ + urls: ['https://adult.example/watch/123'], + referer: 'https://adult.example/watch/123', + silent: false, + filename: null, + headers: 'User-Agent: Firefox Test', + cookies: 'session=secret', + media: true + }); + + expect(useDownloadStore.getState().pendingAddMediaUrls).toEqual([ + 'https://adult.example/watch/123' + ]); + + useDownloadStore.setState({ + isAddModalOpen: false, + pendingAddMediaUrls: ['https://stale.example/watch'] + }); + + await useDownloadStore.getState().handleExtensionDownload({ + urls: ['https://example.com/file.bin'], + referer: 'https://example.com/page', + silent: false, + filename: 'file.bin', + headers: 'User-Agent: Firefox Test', + cookies: null, + media: false + }); + + expect(useDownloadStore.getState().pendingAddMediaUrls).toEqual([]); + }); }); diff --git a/src/store/useDownloadStore.ts b/src/store/useDownloadStore.ts index 48e2245..b273d85 100644 --- a/src/store/useDownloadStore.ts +++ b/src/store/useDownloadStore.ts @@ -252,6 +252,7 @@ interface DownloadState { pendingAddFilename: string; pendingAddHeaders: string; pendingAddCookies: string; + pendingAddMediaUrls: string[]; selectedPropertiesDownloadId: string | null; toggleAddModal: (isOpen: boolean) => void; openAddModalWithUrls: ( @@ -259,7 +260,8 @@ interface DownloadState { referer?: string | null, filename?: string | null, headers?: string | null, - cookies?: string | null + cookies?: string | null, + media?: boolean ) => void; handleExtensionDownload: (request: ExtensionDownloadRequest) => Promise; deleteModalState: DeleteModalState; @@ -383,6 +385,7 @@ export const useDownloadStore = create((set, get) => ({ pendingAddFilename: '', pendingAddHeaders: '', pendingAddCookies: '', + pendingAddMediaUrls: [], selectedPropertiesDownloadId: null, deleteModalState: { isOpen: false }, openDeleteModal: (downloadIds) => set({ @@ -398,18 +401,27 @@ export const useDownloadStore = create((set, get) => ({ pendingAddReferer: '', pendingAddFilename: '', pendingAddHeaders: '', - pendingAddCookies: '' + pendingAddCookies: '', + pendingAddMediaUrls: [] }), - openAddModalWithUrls: (urls, referer, filename, headers, cookies) => set((state) => { + openAddModalWithUrls: (urls, referer, filename, headers, cookies, media = false) => set((state) => { const existingUrls = state.isAddModalOpen && state.pendingAddUrls ? state.pendingAddUrls : ''; const mergedUrls = existingUrls ? `${existingUrls}\n${urls}` : urls; + const existingMediaUrls = state.isAddModalOpen ? state.pendingAddMediaUrls : []; + const pendingAddMediaUrls = media + ? [...new Set([ + ...existingMediaUrls, + ...urls.split('\n').map(url => url.trim()).filter(Boolean) + ])] + : existingMediaUrls; return { isAddModalOpen: true, pendingAddUrls: mergedUrls, pendingAddReferer: referer?.trim() || '', pendingAddFilename: filename?.trim() || '', pendingAddHeaders: headers?.trim() || '', - pendingAddCookies: cookies?.trim() || '' + pendingAddCookies: cookies?.trim() || '', + pendingAddMediaUrls }; }), handleExtensionDownload: async (request) => { @@ -421,7 +433,8 @@ export const useDownloadStore = create((set, get) => ({ request.referer, urls.length === 1 ? request.filename : null, request.headers, - request.cookies + request.cookies, + request.media === true ); }, setSelectedPropertiesDownloadId: (id) => set({ selectedPropertiesDownloadId: id }), diff --git a/src/utils/addDownloadMetadata.test.ts b/src/utils/addDownloadMetadata.test.ts index ddee68f..7d40ad7 100644 --- a/src/utils/addDownloadMetadata.test.ts +++ b/src/utils/addDownloadMetadata.test.ts @@ -30,6 +30,7 @@ describe('add download metadata workflow', () => { 'https://example.com/file.zip\nhttps://example.com/new.zip', [existing], undefined, + new Set(), () => `new-${nextId++}` ); @@ -47,12 +48,55 @@ describe('add download metadata workflow', () => { 'https://example.com/a\nhttps://example.com/a\nfile:///tmp/private\nnot-a-url', [], undefined, + new Set(), () => `row-${nextId++}` ); expect(rows.map(item => item.status)).toEqual(['loading', 'invalid', 'invalid']); }); + it('forces explicit extension media fetches through media metadata for any http page', () => { + const rows = reconcileDownloadRows( + 'https://adult.example/watch/123', + [], + undefined, + new Set(['https://adult.example/watch/123']) + ); + + expect(rows[0]).toMatchObject({ + sourceUrl: 'https://adult.example/watch/123', + isMedia: true, + status: 'loading' + }); + }); + + it('upgrades an existing normal row when the user explicitly fetches it as media', () => { + const existing = row({ + sourceUrl: 'https://adult.example/watch/123', + downloadUrl: 'https://adult.example/watch/123', + file: '123', + status: 'ready', + generation: 2, + isMedia: false + }); + + const rows = reconcileDownloadRows( + 'https://adult.example/watch/123', + [existing], + undefined, + new Set(['https://adult.example/watch/123']) + ); + + expect(rows[0]).toMatchObject({ + sourceUrl: 'https://adult.example/watch/123', + isMedia: true, + status: 'loading', + generation: 3, + formats: undefined, + selectedFormat: undefined + }); + }); + it('refreshes only failed metadata and preserves successful format selection', () => { const ready = row({ id: 'ready', diff --git a/src/utils/addDownloadMetadata.ts b/src/utils/addDownloadMetadata.ts index eb63fc4..e4ab1ab 100644 --- a/src/utils/addDownloadMetadata.ts +++ b/src/utils/addDownloadMetadata.ts @@ -71,6 +71,7 @@ export const reconcileDownloadRows = ( rawText: string, currentRows: AddDownloadDraftRow[], pendingFilename?: string, + forceMediaUrls: ReadonlySet = new Set(), createId: () => string = () => crypto.randomUUID() ): AddDownloadDraftRow[] => { const inputs = parseInputLines(rawText); @@ -78,7 +79,19 @@ export const reconcileDownloadRows = ( return inputs.map(input => { const preserved = existing.get(input.sourceUrl); - if (preserved) return preserved; + if (preserved) { + if (input.valid && forceMediaUrls.has(input.sourceUrl) && !preserved.isMedia) { + return { + ...preserved, + status: 'loading', + generation: preserved.generation + 1, + isMedia: true, + formats: undefined, + selectedFormat: undefined + }; + } + return preserved; + } const fallback = canonicalizeDownloadFileName( inputs.length === 1 && pendingFilename @@ -93,7 +106,7 @@ export const reconcileDownloadRows = ( file: fallback, status: input.valid ? 'loading' : 'invalid', generation: input.valid ? 1 : 0, - isMedia: input.valid && isMediaUrl(input.sourceUrl) + isMedia: input.valid && (forceMediaUrls.has(input.sourceUrl) || isMediaUrl(input.sourceUrl)) }; }); }; diff --git a/src/utils/downloads.ts b/src/utils/downloads.ts index db37512..a76300e 100644 --- a/src/utils/downloads.ts +++ b/src/utils/downloads.ts @@ -18,7 +18,12 @@ let MEDIA_DOMAINS = [ 'v.redd.it', 'soundcloud.com', 'facebook.com', - 'fb.watch' + 'fb.watch', + 'pornhub.com', + 'redtube.com', + 'xhamster.com', + 'xnxx.com', + 'xvideos.com' ]; const ACTIVE_DOWNLOAD_STATUSES: ReadonlySet = new Set([ diff --git a/src/utils/mediaMetadata.ts b/src/utils/mediaMetadata.ts index a7a5326..50985f2 100644 --- a/src/utils/mediaMetadata.ts +++ b/src/utils/mediaMetadata.ts @@ -4,6 +4,7 @@ import type { MediaMetadata } from '../bindings/MediaMetadata'; type FetchMediaMetadataArgs = { url: string; cookieBrowser: string | null; + userAgent: string | null; username: string | null; password: string | null; headers: string | null; @@ -17,6 +18,7 @@ const metadataKey = (args: FetchMediaMetadataArgs) => JSON.stringify([ args.url, args.cookieBrowser, + args.userAgent, args.username, args.password, args.headers,