### Install Playwright Dependencies and Run Tests Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/README.md Steps to set up and run Playwright site integration tests. This includes installing Node.js dependencies, installing Grunt globally if needed, running the Playwright Grunt task, navigating to the test directory, installing Playwright dependencies, and installing the Chromium browser. ```bash npm i # npm i grunt -g # if grunt isn't already installed uncomment grant playwright cd tests/playwright npm i npx playwright install --with-deps chromium npx playwright test ``` -------------------------------- ### Run Jest Unit Tests Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/REVIEWER_GUIDE.md This snippet shows how to install dependencies and run Jest unit tests for the browser extension. Unit tests are currently minimal and serve as a development safeguard. ```bash npm ci npm test ``` -------------------------------- ### TMDb Dynamic Routing Example (TV vs Movie) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/wiki/Site-integrations This example demonstrates dynamic routing for TMDb. It differentiates between TV shows and movies based on the URL, routing to Sonarr or Radarr. The `getSearch` function extracts the TMDb ID for movies from the canonical URL or uses the trimmed title text for TV shows. ```javascript resolveSiteType: function (document, url) { if (/themoviedb\.org\/tv\//i.test(url)) return 'sonarr'; if (/themoviedb\.org\/movie\//i.test(url)) return 'radarr'; return null; }, getSearch: function (_el, document) { const href = document.querySelector('link[rel="canonical"]')?.href || ''; const isMovie = /themoviedb\.org\/movie\//i.test(href); if (isMovie) { const m = href.match(/\/(\d{2,10})-/); return m ? 'tmdb:' + m[1] : ''; } return (document.querySelector('.header .title h2 a')?.textContent || '').trim(); } ``` -------------------------------- ### IMDb Dynamic Routing Example (TV vs Movie) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/wiki/Site-integrations This example shows how to implement dynamic routing for IMDb. It uses `resolveSiteType` to determine if the content is a TV show or a movie based on the `og:type` meta tag, directing it to Sonarr or Radarr respectively. The `getSearch` function extracts the IMDb ID from the canonical URL. ```javascript resolveSiteType: function (document) { return pick(document, 'meta[property="og:type"]', 'content', [ { siteId: 'sonarr', pattern: /(tv_show|other)/i }, { siteId: 'radarr', pattern: /(movie|other)/i } ]); }, getSearch: function (_el, document) { const href = document.querySelector('link[rel="canonical"]')?.href || ''; const m = href.match(/(?tt\d{5,10})/i); return m ? 'imdb:' + m.groups.tt : ''; } ``` -------------------------------- ### Build Extension using Grunt Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/README.md Instructions to build the browser extension using Grunt. This involves installing dependencies and running the release task. The release task utilizes a Powershell script for web-ext, which can be configured to use bash. ```powershell npm i grant release ``` -------------------------------- ### Run Playwright Integration Tests Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/REVIEWER_GUIDE.md This snippet outlines the steps to install dependencies and run Playwright integration tests for the browser extension. These tests verify icon injection and URL logic on target pages and may be flaky due to external site dependencies or layout changes. ```bash npm ci grunt release cd tests/playwright npm i npx playwright install --with-deps chromium npx playwright test ``` -------------------------------- ### Run Unit Tests with Jest Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/README.md Instructions to run unit tests using Jest. This involves installing Node.js dependencies and executing the test script. Jest is used for asserting code changes during development. ```bash npm i npm run test ``` -------------------------------- ### Build and Development Commands (Bash) Source: https://context7.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/llms.txt This section outlines the bash commands required for building and developing the browser extension. It covers installing dependencies, building release packages for different browsers (Chromium MV3, Firefox MV2), running in development mode with watch, executing integration tests with Playwright, running unit tests, and loading the extension for local testing. ```bash # Install dependencies npm install # Build release packages for all browsers npm run release # Development mode with watch npm run debug # Run Playwright integration tests npm run playwright cd tests/playplaywright npm install npx playwright install --with-deps chromium npx playwright test # Run unit tests npm run test # Load extension for local testing # Chromium: web-ext run -s dist/chromium -t chromium npm run chromium ``` -------------------------------- ### Implement Replace Function in getSearch Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/wiki/Site-integrations This example shows how to perform a string replacement operation within the `getSearch` function. It utilizes the `replace` method with a regular expression to find and substitute specific text. This is helpful for normalizing or transforming search terms before they are used. ```javascript text = text.replace(/from/i, 'to') ``` -------------------------------- ### Get Extension Settings with Migration in JavaScript Source: https://context7.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/llms.txt Asynchronously retrieves the extension's settings from browser sync storage. It automatically handles data migration and populates any missing properties using the default settings structure. This function ensures that the returned settings object is always complete and up-to-date. ```javascript /** * Retrieves settings from local storage with automatic migration * @returns {Promise} The complete settings object */ async function getSettings() { let data = await browser.storage.sync.get({ 'sonarrRadarrLidarrAutosearchSettings': defaultSettings }); // Check and add missing integrations for (let i = 0; i < defaultSettings.integrations.length; i++) { if (!data.sonarrRadarrLidarrAutosearchSettings.integrations .some(integration => integration.id === defaultSettings.integrations[i].id)) { data.sonarrRadarrLidarrAutosearchSettings.integrations.push( defaultSettings.integrations[i] ); } } return data.sonarrRadarrLidarrAutosearchSettings; } // Usage example const settings = await getSettings(); console.log('Enabled sites:', settings.sites.filter(s => s.enabled).map(s => s.name)); // Output: Enabled sites: ['Sonarr', 'Radarr', 'Lidarr'] ``` -------------------------------- ### Build and Run Extension (Firefox - web-ext) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/REVIEWER_GUIDE.md Commands to build the extension and run it in Firefox using the 'web-ext' tool for development and testing. This automates the process of running the extension in a Firefox instance. ```bash npm ci grunt release npm run firefox ``` -------------------------------- ### Build and Load Extension (Firefox - Temporary) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/REVIEWER_GUIDE.md Steps to build the extension and temporarily load it in Firefox for testing. This method uses Firefox's 'Load Temporary Add-on' feature. ```bash npm ci && grunt release Visit about:debugging#/runtime/this-firefox. “Load Temporary Add-on” → pick any file inside dist/firefox (e.g., manifest.json). ``` -------------------------------- ### Build and Load Extension (Chromium) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/REVIEWER_GUIDE.md Instructions to build the extension using npm and grunt, then load it as an unpacked extension in Chrome for testing. This process involves running build commands and navigating the Chrome extensions page. ```bash npm ci && grunt release Open chrome://extensions → Enable Developer Mode. Click “Load unpacked” → select dist/chromium. ``` -------------------------------- ### Create Site-Specific Engines with Declarative Configuration (JavaScript) Source: https://context7.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/llms.txt Builds site-specific engines using a declarative configuration pattern. It handles URL matching, DOM selection, and icon injection for integrating with various media sites. ```javascript /** * Build an engine from declarative configuration * @param {DefaultEngineConfig} cfg - Engine configuration * @returns {EngineInstance} Executable engine instance */ window.__servarrEngines.helpers.DefaultEngine = function DefaultEngine(cfg) { return { id: cfg.id, deferMs: cfg.deferMs || 0, match: function(document, url) { return cfg.urlIncludes.some(s => url.indexOf(s) >= 0); }, candidates: function(ctx) { const elements = Array.from( ctx.document.querySelectorAll(cfg.containerSelector) ); const siteType = cfg.resolveSiteType ? cfg.resolveSiteType(ctx.document, ctx.url, ctx.settings) : cfg.siteType; return { siteType: siteType, elements: elements, getSearch: (el) => cfg.getSearch(el, ctx.document), insert: (args) => { // Create and inject icon link const a = document.createElement('a'); a.href = args.link; a.target = '_blank'; a.innerHTML = `...`; args.el.prepend(a); } }; } }; }; ``` -------------------------------- ### Configure Servarr Version-Specific Settings Source: https://context7.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/llms.txt The `versionConfig` array and `getVersionConfig` function provide a system for mapping specific Servarr versions to their corresponding UI elements, such as search paths and input selectors. This ensures that the extension can correctly interact with different versions of Sonarr and Radarr, maintaining compatibility and functionality. ```javascript // Version-specific configuration mapping const versionConfig = [ { id: 'sonarr', configs: [ { versionMatch: /^2/, searchPath: '/addseries/', searchInputSelector: '.add-series-search .x-series-search' }, { versionMatch: /^[3|4|5|6]/, searchPath: '/add/new/', searchInputSelector: 'input[name="seriesLookup"]' } ] }, { id: 'radarr', configs: [ { versionMatch: /^[0|2]/, searchPath: '/addmovies/', searchInputSelector: '.add-movies-search .x-movies-search' }, { versionMatch: /^[3|4|5|6]/, searchPath: '/add/new/', searchInputSelector: 'input[name="movieLookup"]' } ] } ]; // Get config for a specific version let getVersionConfig = (siteType, version) => versionConfig .find(v => v.id === siteType) .configs .find(c => c.versionMatch.test(version)); // Usage example const config = getVersionConfig('sonarr', '4.0.5'); console.log(config); // Output: { versionMatch: /^[3|4|5|6]/, searchPath: '/add/new/', // searchInputSelector: 'input[name="seriesLookup"]' } ``` -------------------------------- ### Create a New Site Integration Engine (JavaScript) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/wiki/Site-integrations This JavaScript code defines a new site integration engine for the Servarr browser extension. It uses helper functions to configure the engine's behavior, including where to run, where to inject icons, how to determine the target Servarr app, and how to extract search terms. The engine is then registered in `window.__servarrEngines.list`. ```javascript // src/content/engines/integrations/yoursite.js (function () { if (!window.__servarrEngines) window.__servarrEngines = { list: [], helpers: {} }; const Def = window.__servarrEngines.helpers.DefaultEngine; const pick = window.__servarrEngines.helpers.pickSiteIdFromDocument; // optional helper // Build an engine using the DefaultEngine config const YourSiteEngine = Def({ id: 'yoursite', // Where to run (simple substring checks against window.location.href) // Not required if using a custom `match` function below. urlIncludes: ['yoursite.example.com/path'], // When to run (more complex logic, e.g., regex + DOM gates) // Not required if using `urlIncludes` above. match: function (document, url) { // Example: simple regex match on the URL urlMatches = /.+letterboxd\.com\/film\/.+/i.test(url); if (!urlMatches) return false; // Example: gate on DOM content return !(document.querySelector('a[href*="themoviedb.org/movie/"]')); }, // Where to place the icon (container to inject into) containerSelector: 'h1.title', // Prepend/append/before/after within the container insertWhere: 'prepend', // 'prepend' | 'append' | 'before' | 'after' // Optional wrapper around the (for tricky layouts) // wrapLinkWithContainer: '
', // Optional: wait for SPA content to render (ms) // deferMs: 1000, // Decide which Servarr app to target: // 1) Fixed (use this) // siteType: 'sonarr', // 'sonarr' | 'radarr' | 'lidarr' | 'readarr_ebook' | 'readarr_audiobook' // 2) OR dynamic (use this): pick based on DOM/content resolveSiteType: function (document, url, settings) { // Example of rule-based routing (like the old `rules`): // Read a value from the page (e.g., og:type), then match patterns. // Return 'sonarr' or 'radarr' (etc.), or null to skip. return pick(document, 'meta[property="og:type"]', 'content', [ { siteId: 'sonarr', pattern: /video.tv_show/i }, { siteId: 'radarr', pattern: /video.movie/i }, ]); }, // How to extract the search term for the link getSearch: function (el, document) { // Example 1: plain text from an element // return (document.querySelector('h1.title')?.textContent || '').trim(); // Example 2: pull an ID from a URL & prefix it // const href = document.querySelector('link[rel="canonical"]')?.href || ''; // const m = href.match(/\/(?\d{2,10})-/); // return m ? 'tmdb:' + m.groups.id : ''; return ''; }, // Icon styles (you can stick to the defaults or tweak per site) iconStyle: 'width: 28px; margin: -4px 10px 0 0;' }); window.__servarrEngines.list.push(YourSiteEngine); })(); ``` -------------------------------- ### Layout Tip: Wrap Link with Container Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/wiki/Site-integrations When a container cannot directly hold anchors or requires a stable insertion point, the `wrapLinkWithContainer` option can be used. This allows you to define a specific HTML element to wrap around the injected link, providing a predictable layout. ```javascript wrapLinkWithContainer: '
' ``` -------------------------------- ### Inspect Extension Storage (JavaScript) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/REVIEWER_GUIDE.md JavaScript code snippet to inspect the extension's synchronized storage using the `browser.storage.sync.get()` API. This is useful for debugging and verifying stored configuration data. ```javascript await browser.storage.sync.get(); ``` -------------------------------- ### Create a New Site Integration Engine (JavaScript) Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/blob/master/wiki/SITE_INTEGRATIONS.md This JavaScript code defines a new site integration engine for the Servarr browser extension. It specifies the conditions for running, the location for the icon, and how to determine the target Servarr app and extract search terms. It utilizes helper functions for configuration and DOM manipulation. ```javascript // src/content/engines/integrations/yoursite.js (function () { if (!window.__servarrEngines) window.__servarrEngines = { list: [], helpers: {} }; const Def = window.__servarrEngines.helpers.DefaultEngine; const pick = window.__servarrEngines.helpers.pickSiteIdFromDocument; // optional helper // Build an engine using the DefaultEngine config const YourSiteEngine = Def({ id: 'yoursite', // Where to run (simple substring checks against window.location.href) // Not required if using a custom `match` function below. urlIncludes: ['yoursite.example.com/path'], // When to run (more complex logic, e.g., regex + DOM gates) // Not required if using `urlIncludes` above. match: function (document, url) { // Example: simple regex match on the URL urlMatches = /.+letterboxd\.com\/film\/.+/i.test(url); if (!urlMatches) return false; // Example: gate on DOM content return !(document.querySelector('a[href*="themoviedb.org/movie/"]')); }, // Where to place the icon (container to inject into) containerSelector: 'h1.title', // Prepend/append/before/after within the container insertWhere: 'prepend', // 'prepend' | 'append' | 'before' | 'after' // Optional wrapper around the
(for tricky layouts) // wrapLinkWithContainer: '
', // Optional: wait for SPA content to render (ms) // deferMs: 1000, // Decide which Servarr app to target: // 1) Fixed (use this) // siteType: 'sonarr', // 'sonarr' | 'radarr' | 'lidarr' | 'readarr_ebook' | 'readarr_audiobook' // 2) OR dynamic (use this): pick based on DOM/content resolveSiteType: function (document, url, settings) { // Example of rule-based routing (like the old `rules`): // Read a value from the page (e.g., og:type), then match patterns. // Return 'sonarr' or 'radarr' (etc.), or null to skip. return pick(document, 'meta[property="og:type"]', 'content', [ { siteId: 'sonarr', pattern: /video\.tv_show/i }, { siteId: 'radarr', pattern: /video\.movie/i }, ]); }, // How to extract the search term for the link getSearch: function (el, document) { // Example 1: plain text from an element // return (document.querySelector('h1.title')?.textContent || '').trim(); // Example 2: pull an ID from a URL & prefix it // const href = document.querySelector('link[rel="canonical"]')?.href || ''; // const m = href.match(/\/(?\d{2,10})-/); // return m ? 'tmdb:' + m.groups.id : ''; return ''; }, // Icon styles (you can stick to the defaults or tweak per site) iconStyle: 'width: 28px; margin: -4px 10px 0 0;' }); window.__servarrEngines.list.push(YourSiteEngine); })(); ``` -------------------------------- ### Servarr Context Menu Handler (JavaScript) Source: https://context7.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/llms.txt Manages browser context menus for initiating searches across enabled Servarr instances (Sonarr, Radarr, Lidarr) based on selected text. It dynamically builds menu items and handles click events to open search URLs in new tabs. Requires `browser.contextMenus` API, `getSettings`, and `browser.tabs.create`. ```javascript /** * Build context menu tree from settings * @param {Setting} settings - Current extension settings */ async function buildMenus(settings) { await browser.contextMenus?.removeAll(); if (!settings?.config?.enabled || !settings?.config?.contextMenu) return; const enabledSites = settings.sites.filter(site => site.enabled); if (enabledSites.length === 0) return; // Create parent menu browser.contextMenus.create({ title: 'Search Servarr', id: 'sonarrRadarrLidarr', contexts: ['selection'] }); // Create child menu items per enabled site for (const site of enabledSites) { browser.contextMenus.create({ title: `Search ${site.name}`, parentId: 'sonarrRadarrLidarr', id: `${site.id}Menu`, contexts: ['selection'] }); } } /** * Handle context menu click events */ async function onClickHandler(info, tab) { const settings = await getSettings(); for (const site of settings.sites) { if (info.menuItemId === `${site.id}Menu`) { const searchUrl = site.domain.replace(/\/$/, '') + site.searchPath + encodeURIComponent(info.selectionText); await browser.tabs.create({ url: searchUrl }); } } } browser.contextMenus?.onClicked.addListener(onClickHandler); ``` -------------------------------- ### Add New Site Integration to Settings UI Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/wiki/Site-integrations This code snippet demonstrates how to add a new site integration to the extension's settings UI. It requires an `id` that matches the engine's ID, a user-friendly `name`, an `image` path for the logo, and an initial `enabled` state. ```javascript // Somewhere in defaultSettings.integrations (or the relevant registry) { id: 'yoursite', name: 'Your Site', image: 'yoursite.png', enabled: true } ``` -------------------------------- ### SPA/Slow DOM Page Handling Source: https://github.com/trossr32/sonarr-radarr-lidarr-autosearch-browser-extension/wiki/Site-integrations For Single Page Applications (SPAs) or pages where the target DOM elements load slowly, you can specify a `deferMs` property. This tells the extension to wait a specified number of milliseconds before attempting to inject elements, allowing the DOM to fully render. ```javascript deferMs: 1000 // or 2000/3000 based on observation ```