"use strict"; const TAB_CAPTURED = new Map(); const FULL_URL_BY_SAMPLE = new Map(); const HISTORY_KEY = "capturedS3History"; const MAX_HISTORY_ITEMS = 500; const SPLICE_DOMAIN_RE = /^https?:\/\/([a-z0-9-]+\.)*splice\.com(\/|$)/i; const AUDIO_SAMPLE_PATH_RE = /\/audio_samples\//i; const WAVEFORM_PATH_RE = /\.wv\.json$/i; const SCRAMBLED_PATH_RE = /-scrambled\//i; const HASH_RE = /^[a-f0-9]{64}$/i; const GRAPHQL_URL = "https://surfaces-graphql.splice.com/graphql"; function isDomainAllowed(pageUrl) { return typeof pageUrl === "string" && SPLICE_DOMAIN_RE.test(pageUrl); } function isS3Url(url) { if (typeof url !== "string") { return false; } try { const parsed = new URL(url); if (parsed.protocol !== "https:") { return false; } const host = parsed.hostname.toLowerCase(); const isAwsHost = host.endsWith(".amazonaws.com") || host.endsWith(".amazonaws.com.cn"); const isS3Host = host.includes(".s3.") || host.startsWith("s3."); return isAwsHost && isS3Host; } catch (error) { return false; } } function isAudioSampleUrl(url) { if (!isS3Url(url)) { return false; } try { const parsed = new URL(url); return AUDIO_SAMPLE_PATH_RE.test(parsed.pathname || ""); } catch (error) { return false; } } function isWaveformUrl(url) { try { const parsed = new URL(url); return WAVEFORM_PATH_RE.test(parsed.pathname || ""); } catch (error) { return false; } } function isPreviewUrl(url) { try { const parsed = new URL(url); return SCRAMBLED_PATH_RE.test(parsed.pathname || ""); } catch (error) { return false; } } function isFullUrl(url) { return isAudioSampleUrl(url) && !isWaveformUrl(url) && !isPreviewUrl(url); } function normalizeUrl(url) { try { return new URL(url).toString(); } catch (error) { return ""; } } function extractSampleIdFromUrl(url) { try { const parsed = new URL(url); const path = parsed.pathname || ""; const marker = "/audio_samples/"; const idx = path.indexOf(marker); if (idx < 0) { return ""; } const rest = path.slice(idx + marker.length); const parts = rest.split("/").filter(Boolean); const first = parts[0] || ""; const second = parts[1] || ""; const firstNormalized = first.replace(/-scrambled$/i, ""); if (HASH_RE.test(firstNormalized)) { return firstNormalized.toLowerCase(); } const secondStem = second.split(".")[0]; if (HASH_RE.test(secondStem)) { return secondStem.toLowerCase(); } if (HASH_RE.test(first)) { return first.toLowerCase(); } return ""; } catch (error) { return ""; } } function ensureTabState(tabId) { if (!TAB_CAPTURED.has(tabId)) { TAB_CAPTURED.set(tabId, { latestSampleId: "", entriesBySample: new Map() }); } return TAB_CAPTURED.get(tabId); } function tabIdFromSender(sender) { return sender && sender.tab && typeof sender.tab.id === "number" ? sender.tab.id : -1; } function maybeClearTabState(tabId) { if (typeof tabId === "number" && tabId >= 0) { TAB_CAPTURED.delete(tabId); } } function toHistoryRow(entry) { return { sampleId: entry.sampleId || "", previewUrl: entry.previewUrl || "", fullUrl: entry.fullUrl || "", url: entry.fullUrl || entry.previewUrl || "", source: entry.source || "", capturedAt: entry.capturedAt || Date.now() }; } function mergeEntry(tabState, inputEntry, source) { const sampleId = inputEntry.sampleId || extractSampleIdFromUrl(inputEntry.previewUrl || inputEntry.fullUrl || ""); if (!sampleId) { return null; } const existing = tabState.entriesBySample.get(sampleId) || { sampleId, previewUrl: "", fullUrl: "", source: source || "unknown", capturedAt: Date.now() }; const merged = { ...existing, sampleId, source: source || existing.source || "unknown", capturedAt: Date.now() }; if (inputEntry.previewUrl) { merged.previewUrl = inputEntry.previewUrl; } if (inputEntry.fullUrl) { merged.fullUrl = inputEntry.fullUrl; FULL_URL_BY_SAMPLE.set(sampleId, inputEntry.fullUrl); } else if (!merged.fullUrl && FULL_URL_BY_SAMPLE.has(sampleId)) { merged.fullUrl = FULL_URL_BY_SAMPLE.get(sampleId); } tabState.entriesBySample.set(sampleId, merged); tabState.latestSampleId = sampleId; return merged; } function upsertPreviewContext(tabId, context, source) { const tabState = ensureTabState(tabId); const previewUrl = normalizeUrl(context.previewUrl || ""); if (!previewUrl || !isAudioSampleUrl(previewUrl) || isWaveformUrl(previewUrl)) { return null; } const sampleId = (context.sampleId || extractSampleIdFromUrl(previewUrl)).toLowerCase(); if (!sampleId) { return null; } return mergeEntry( tabState, { sampleId, previewUrl, fullUrl: context.fullUrl ? normalizeUrl(context.fullUrl) : "" }, source || "graphql_preview" ); } function upsertUrl(tabId, url, source) { const normalized = normalizeUrl(url); if (!normalized || !isAudioSampleUrl(normalized) || isWaveformUrl(normalized)) { return null; } const sampleId = extractSampleIdFromUrl(normalized); if (!sampleId) { return null; } const entry = { sampleId, previewUrl: isPreviewUrl(normalized) ? normalized : "", fullUrl: isFullUrl(normalized) ? normalized : "" }; if (typeof tabId === "number" && tabId >= 0) { return mergeEntry(ensureTabState(tabId), entry, source); } if (entry.fullUrl) { FULL_URL_BY_SAMPLE.set(sampleId, entry.fullUrl); } return null; } async function appendHistoryRows(rows) { if (!rows.length) { return; } const data = await browser.storage.local.get(HISTORY_KEY); const current = Array.isArray(data[HISTORY_KEY]) ? data[HISTORY_KEY] : []; const combined = [...current]; const seen = new Set(current.map((item) => `${item.sampleId}|${item.url}`)); for (const row of rows) { const key = `${row.sampleId}|${row.url}`; if (!row.url || seen.has(key)) { continue; } seen.add(key); combined.push(row); } await browser.storage.local.set({ [HISTORY_KEY]: combined.slice(-MAX_HISTORY_ITEMS) }); } function decodePass(startIdx, arr, encodeBlk, endIdx) { let blockIdx = 0; for (let i = startIdx; i < endIdx; i += 1) { if (blockIdx > encodeBlk.length - 1) { blockIdx = 0; } arr[i] = arr[i] ^ encodeBlk.charCodeAt(blockIdx); blockIdx += 1; } return endIdx; } function decodeSpliceAudio(data) { const sizeData = Array.from(data.subarray(2, 10)); let size = 0; for (let i = sizeData.length - 1; i >= 0; i -= 1) { size = (256 * size) + sizeData[i]; } const encodingData = data.subarray(10, 28); const chunks = []; for (let i = 0; i < encodingData.length; i += 32768) { chunks.push(String.fromCharCode(...Array.from(encodingData.subarray(i, i + 32768)))); } const encodeBlk = chunks.join(""); const audioData = data.slice(28); const passIdx = decodePass(0, audioData, encodeBlk, size) + size; decodePass(passIdx, audioData, encodeBlk, passIdx + size); return audioData; } function deriveFilename(entry, mode) { const base = entry.sampleId || `splice-${Date.now()}`; if (mode === "full") { const full = entry.fullUrl || ""; if (full.endsWith(".wav") || full.includes(".wav?")) { return `${base}.wav`; } } return `${base}-decoded.mp3`; } async function downloadDirectUrl(url, entry) { const filename = deriveFilename(entry, "full"); await browser.downloads.download({ url, saveAs: true, filename, conflictAction: "uniquify" }); return { ok: true, mode: "full", filename }; } async function downloadDecodedPreview(previewUrl, entry) { const response = await fetch(previewUrl, { credentials: "include" }); if (!response.ok) { throw new Error(`Preview fetch failed (${response.status}).`); } const raw = new Uint8Array(await response.arrayBuffer()); const decoded = decodeSpliceAudio(raw); const blob = new Blob([decoded], { type: "audio/mpeg" }); const objectUrl = URL.createObjectURL(blob); const filename = deriveFilename(entry, "decoded"); try { await browser.downloads.download({ url: objectUrl, saveAs: true, filename, conflictAction: "uniquify" }); } finally { window.setTimeout(() => URL.revokeObjectURL(objectUrl), 30000); } return { ok: true, mode: "decoded", filename }; } function extractAudioUrlsFromObject(input, output) { if (!input) { return; } if (typeof input === "string") { const normalized = normalizeUrl(input); if (normalized && isFullUrl(normalized)) { output.add(normalized); } return; } if (Array.isArray(input)) { for (const item of input) { extractAudioUrlsFromObject(item, output); } return; } if (typeof input === "object") { for (const value of Object.values(input)) { extractAudioUrlsFromObject(value, output); } } } async function queryBackendForFullUrl(entry) { const sampleId = entry.sampleId || ""; if (!sampleId) { return ""; } if (FULL_URL_BY_SAMPLE.has(sampleId)) { return FULL_URL_BY_SAMPLE.get(sampleId); } const body = { operationName: "SampleLookupForDownload", query: "query SampleLookupForDownload($query: String!, $limit: Int = 5) {" + " assetsSearch(filter: {legacy: true, published: true, asset_type_slug: sample, query: $query}," + " pagination: {page: 1, limit: $limit}, sort: {sort: relevance, order: DESC}) {" + " items { ... on IAsset { uuid files { asset_file_type_slug url path } } } } }", variables: { query: sampleId, limit: 5 } }; try { const response = await fetch(GRAPHQL_URL, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }); if (!response.ok) { return ""; } const json = await response.json(); const urls = new Set(); extractAudioUrlsFromObject(json, urls); for (const url of urls) { const id = extractSampleIdFromUrl(url); if (id === sampleId && isFullUrl(url)) { FULL_URL_BY_SAMPLE.set(sampleId, url); return url; } } return ""; } catch (error) { return ""; } } async function resolveAndDownloadEntry(entryInput) { const entry = entryInput || {}; const sampleId = entry.sampleId || extractSampleIdFromUrl(entry.previewUrl || entry.fullUrl || ""); const normalizedEntry = { sampleId, previewUrl: normalizeUrl(entry.previewUrl || ""), fullUrl: normalizeUrl(entry.fullUrl || "") }; if (normalizedEntry.fullUrl && isFullUrl(normalizedEntry.fullUrl)) { return downloadDirectUrl(normalizedEntry.fullUrl, normalizedEntry); } const discoveredFull = await queryBackendForFullUrl(normalizedEntry); if (discoveredFull) { normalizedEntry.fullUrl = discoveredFull; return downloadDirectUrl(discoveredFull, normalizedEntry); } if (normalizedEntry.previewUrl) { return downloadDecodedPreview(normalizedEntry.previewUrl, normalizedEntry); } throw new Error("No preview or full URL available for this sample."); } function getLatestEntryForTab(tabId) { const state = TAB_CAPTURED.get(tabId); if (!state || !state.latestSampleId) { return null; } return state.entriesBySample.get(state.latestSampleId) || null; } function serializeEntry(entry) { return { sampleId: entry.sampleId || "", previewUrl: entry.previewUrl || "", fullUrl: entry.fullUrl || "", source: entry.source || "", capturedAt: entry.capturedAt || Date.now() }; } function serializeTabEntries(state) { return Array.from(state.entriesBySample.values()) .sort((a, b) => (b.capturedAt || 0) - (a.capturedAt || 0)) .map(serializeEntry); } browser.runtime.onMessage.addListener((message, sender) => { if (!message || typeof message.type !== "string") { return undefined; } if (message.type === "CAPTURE_PREVIEW_CONTEXT") { const tabId = tabIdFromSender(sender); if (tabId < 0) { return Promise.resolve({ ok: false, added: 0 }); } const senderUrl = sender && sender.tab ? sender.tab.url : ""; if (!isDomainAllowed(senderUrl)) { return Promise.resolve({ ok: false, added: 0 }); } const contexts = Array.isArray(message.contexts) ? message.contexts : []; const rows = []; for (const context of contexts) { const merged = upsertPreviewContext(tabId, context, "graphql_preview"); if (merged) { rows.push(toHistoryRow(merged)); } } return appendHistoryRows(rows).then(() => ({ ok: true, added: rows.length })); } if (message.type === "CAPTURE_URLS") { const tabId = tabIdFromSender(sender); const urls = Array.isArray(message.urls) ? message.urls : []; const rows = []; for (const raw of urls) { const merged = upsertUrl(tabId, raw, message.source || "network_preview"); if (merged) { rows.push(toHistoryRow(merged)); } } return appendHistoryRows(rows).then(() => ({ ok: true, added: rows.length })); } if (message.type === "GET_TAB_URLS") { const tabId = typeof message.tabId === "number" ? message.tabId : -1; const state = TAB_CAPTURED.get(tabId); if (!state) { return Promise.resolve({ ok: true, entries: [] }); } return Promise.resolve({ ok: true, entries: serializeTabEntries(state) }); } if (message.type === "GET_HISTORY") { return browser.storage.local.get(HISTORY_KEY).then((data) => { const rows = Array.isArray(data[HISTORY_KEY]) ? data[HISTORY_KEY] : []; rows.sort((a, b) => (b.capturedAt || 0) - (a.capturedAt || 0)); return { ok: true, history: rows }; }); } if (message.type === "DOWNLOAD_ENTRY") { return resolveAndDownloadEntry(message.entry) .then((result) => result) .catch((error) => ({ ok: false, error: error.message || "Download failed." })); } if (message.type === "DOWNLOAD_LATEST") { const tabId = tabIdFromSender(sender); const latest = getLatestEntryForTab(tabId); if (!latest) { return Promise.resolve({ ok: false, error: "No captured sample is available yet." }); } return resolveAndDownloadEntry(latest) .then((result) => result) .catch((error) => ({ ok: false, error: error.message || "Download failed." })); } if (message.type === "OPEN_HISTORY") { const historyUrl = browser.runtime.getURL("history.html"); return browser.tabs.create({ url: historyUrl }).then(() => ({ ok: true })); } return undefined; }); function handleWebRequestCapture(details) { const url = typeof details.url === "string" ? details.url : ""; if (!isAudioSampleUrl(url) || isWaveformUrl(url)) { return; } const tabId = typeof details.tabId === "number" ? details.tabId : -1; const merged = upsertUrl(tabId, url, "webrequest"); if (merged) { appendHistoryRows([toHistoryRow(merged)]).catch(() => { // Ignore history write failures. }); } } browser.webRequest.onBeforeRequest.addListener( handleWebRequestCapture, { urls: ["*://*.amazonaws.com/*", "*://*.amazonaws.com.cn/*"] } ); browser.tabs.onRemoved.addListener((tabId) => { maybeClearTabState(tabId); }); browser.webNavigation.onCommitted.addListener((details) => { if (details.frameId !== 0) { return; } maybeClearTabState(details.tabId); });