// Background script for the extension browser.storage.local.get("playlists").then(function (data) { if (!data.playlists) { console.log("pre-populating playlists"); browser.storage.local.set({ playlists: { "listening-1": [], "listening-2": [], "listening-3": [], "watching-1": [], "watching-2": [], }, }); } else { console.log("no need to pre-populate playlists"); } }); browser.storage.local.get("history").then(function (data) { if (!data.history) { console.log("pre-populating history"); browser.storage.local.set({ history: {}, }); } else { console.log("no need to pre-populate history"); } }); // Create context menu items when the extension is installed browser.runtime.onInstalled.addListener(() => { // Parent menu item browser.contextMenus.create({ id: "my-playlist-menu", title: "Add to Playlist", contexts: ["link"], targetUrlPatterns: ["https://*.youtube.com/watch*"], }); // Sub-menu items browser.contextMenus.create({ id: "listening-1", parentId: "my-playlist-menu", title: "Listening - 1", contexts: ["link"], }); browser.contextMenus.create({ id: "listening-2", parentId: "my-playlist-menu", title: "Listening - 2", contexts: ["link"], }); browser.contextMenus.create({ id: "listening-3", parentId: "my-playlist-menu", title: "Listening - 3", contexts: ["link"], }); // Separator browser.contextMenus.create({ id: "separator-1", parentId: "my-playlist-menu", type: "separator", contexts: ["link"], }); browser.contextMenus.create({ id: "watching-1", parentId: "my-playlist-menu", title: "Watching - 1", contexts: ["link"], }); browser.contextMenus.create({ id: "watching-2", parentId: "my-playlist-menu", title: "Watching - 2", contexts: ["link"], }); // test message //browser.tabs.sendMessage(tab.id, payload); }); function findMatchingUrlKey(current, inputUrl) { // Extract the "v" query parameter from input URL let inputVParam; try { const urlObj = new URL(inputUrl); inputVParam = urlObj.searchParams.get("v"); } catch (e) { return false; } // If no "v" parameter found, return false if (!inputVParam) { return false; } // Check each key in the current object for (const key in current) { const items = current[key]; // Check each item in the array for (const item of items) { try { const itemUrl = new URL(item.url); const itemVParam = itemUrl.searchParams.get("v"); // If the "v" parameters match, return this key if (itemVParam === inputVParam) { return key; } } catch (e) { // Skip malformed URLs continue; } } } // No match found return false; } async function addLinkToPlaylist(plName, item) { const { playlists: currentPlaylists } = await browser.storage.local.get("playlists"); const alreadyHave = findMatchingUrlKey(currentPlaylists, item.linkUrl); if (alreadyHave) { console.log("already have that link in", alreadyHave); } else { const { [plName]: playlist, ...others } = currentPlaylists; await browser.storage.local.set({ playlists: { [plName]: [...playlist, { url: item.linkUrl, title: item.linkText }], ...others, }, }); } } // Context menu click handler browser.contextMenus.onClicked.addListener(async (item, _tab) => { addLinkToPlaylist(item.menuItemId, item); }); // Function to navigate a tab to a new URL function navigateTab(tabId, url) { return browser.tabs.update(tabId, { url: url }); } async function updateHistory(message) { const { history: currentHistory } = await browser.storage.local.get("history"); console.log("currentHistory", currentHistory); const q = new URL(message.url); const v = q.searchParams.get("v"); console.log("v??", message.url, q, q.searchParams.get("v")); console.log("history?", currentHistory[v]); if (currentHistory[v]) { const { [v]: existing, ...rest } = currentHistory; await browser.storage.local.set({ history: { [v]: { ...existing, duration: !isNaN(existing.duration) && isFinite(existing.duration) ? Math.max(message.duration, existing.duration) : message.duration, history: [ ...existing.history, { action: message.type, position: message.timestamp, timestamp: Date.now(), }, ], }, ...rest, }, }); } else { await browser.storage.local.set({ history: { [v]: { url: message.url, title: message.title, duration: message.duration, history: [ { action: message.type, position: message.timestamp, timestamp: Date.now(), }, ], }, ...currentHistory, }, }); } console.log("proposed:", { url: message.url, title: message.title, duration: message.duration, history: [ { action: message.type, position: message.timestamp, timestamp: Date.now(), }, ], }); } // Listen for messages from popup or content scripts browser.runtime.onMessage.addListener((message, sender, sendResponse) => { console.log("MESSAGE", message); switch (message.type) { case "ended": console.log("ENDED"); case "play": case "playing": case "pause": updateHistory(message); break; } if (message.command === "navigate") { if (message.tabId && message.url) { navigateTab(message.tabId, message.url) .then(() => sendResponse({ status: "success" })) .catch((error) => sendResponse({ status: "error", message: error.message }), ); return true; // Required for async sendResponse } } });