您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds "Open in Stremio" buttons to Google search results and knowledge panels for IMDb titles.
// ==UserScript== // @name Google Search: Stremio Links // @namespace https://github.com/sinazadeh/userscripts // @version 2.0.0 // @description Adds "Open in Stremio" buttons to Google search results and knowledge panels for IMDb titles. // @author TheSina // @match *://www.google.*/* // @exclude *://*.google.*/recaptcha/* // @grant GM_addStyle // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; const STREMIO_ICON_URL = 'https://www.stremio.com/website/stremio-logo-small.png'; const addStremioButtonToGoogle = () => { let seriesOptions = document.querySelector( "div[data-attrid='kc:/tv/tv_program:media_actions_wholepage']", ); let movieOptions = document.querySelector( "div[data-attrid='kc:/film/film:media_actions_wholepage']", ); let filmReviewContainer = document.querySelector( "div[data-attrid='kc:/film/film:reviews']", ); let seriesReviewContainer = document.querySelector( "div[data-attrid='kc:/tv/tv_program:reviews']", ); let watchOption = null; let reviewContainer = null; let contentType = 'movie'; let imdbCode = null; if (seriesOptions) { watchOption = seriesOptions; reviewContainer = seriesReviewContainer; contentType = 'series'; } else if (movieOptions) { watchOption = movieOptions; reviewContainer = filmReviewContainer; } if (watchOption === null) { return; } if (reviewContainer != null) { let imdbEle = reviewContainer.querySelector( "a[href*='https://www.imdb.com/']", ); if (imdbEle) { let imdbParts = imdbEle.href.split('/'); imdbCode = imdbParts.pop() || imdbParts.pop(); } } if (imdbCode === null) { let imdbLink = document.querySelector( "a[href*='https://www.imdb.com/']", )?.href; imdbCode = imdbLink?.match(/title\/(tt\d+)/)?.[1]; } if (imdbCode === null) { return; } let childCount = watchOption.firstElementChild.firstElementChild.childElementCount; let watchNowEle = watchOption.firstElementChild.firstElementChild.firstElementChild; if (childCount === 2) { let divEle = document.createElement('div'); watchNowEle = watchOption.firstElementChild.firstElementChild.insertBefore( divEle, watchNowEle, ); } // Remove previous button if exists let prev = watchNowEle.querySelector('.stremio-cta__href'); if (prev) prev.remove(); // Inject custom CSS for styling (no black background) and diamond icon if (!document.getElementById('stremio-cta-style')) { const style = document.createElement('style'); style.id = 'stremio-cta-style'; style.textContent = ` .stremio-cta__href { display: flex; align-items: center; gap: 14px; border-radius: 8px; padding: 6px 0; margin: 8px 0; text-decoration: none !important; } .stremio-cta__icon-wrap { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; background: none; border-radius: 6px; box-shadow: none; } .stremio-cta__icon { /* keep inner content upright */ transform: none; display:flex; align-items:center; justify-content:center; } .stremio-png-icon { width: 36px; height: 36px; object-fit: contain; transform: none; display: block; } .stremio-play { width: 22px; height: 22px; clip-path: polygon(10% 0%, 100% 50%, 10% 100%); background: white; opacity: 0.95; } .stremio-cta__texts { display: flex; flex-direction: column; align-items: flex-start; } .stremio-cta__title { font-family: 'Segoe UI', 'Arial', sans-serif; font-size: 16px; color: #ffffff; font-weight: 600; line-height: 1.1; margin:0; } .stremio-cta__subtitle { font-family: 'Segoe UI', 'Arial', sans-serif; font-size: 12px; color: rgba(255,255,255,0.75); margin-top:4px; } /* When page uses light background, slightly adapt colors */ .stremio-cta__href.light .stremio-cta__icon-wrap { background: linear-gradient(135deg,#6f4df0 0%,#3aa1ff 100%); } .stremio-cta__href.light .stremio-cta__title { color: #181818; } .stremio-cta__href.light .stremio-cta__subtitle { color: #666; } `; document.head.appendChild(style); } // Determine if surrounding area is dark to flip text color const isAreaDark = (() => { try { const bg = window.getComputedStyle(watchOption).backgroundColor || ''; if (!bg) return true; // default to dark for Google knowledge panels // crude check for rgb darkness const m = bg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); if (!m) return true; const r = Number(m[1]), g = Number(m[2]), b = Number(m[3]); const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b; return lum < 128; } catch (e) { return true; } })(); const lightClass = isAreaDark ? '' : 'light'; // Try to use the repo PNG; fall back to CSS-drawn icon if it fails to load watchNowEle.innerHTML = ` <a class="stremio-cta__href ${lightClass}" href='stremio:///detail/${contentType}/${imdbCode}'> <div class="stremio-cta__icon-wrap"> <img class="stremio-png-icon" src="${STREMIO_ICON_URL}" alt="Stremio icon" /> <div class="stremio-cta__icon css-fallback"><div class="stremio-play"></div></div> </div> <div class="stremio-cta__texts"> <div class="stremio-cta__title">Stremio</div> <div class="stremio-cta__subtitle">Freedom to stream</div> </div> </a> `; // If PNG loads, hide the CSS fallback. If it errors, keep fallback visible. const img = watchNowEle.querySelector('.stremio-png-icon'); const fallback = watchNowEle.querySelector('.css-fallback'); if (img && fallback) { img.addEventListener('load', () => { img.style.display = 'block'; fallback.style.display = 'none'; }); img.addEventListener('error', () => { img.style.display = 'none'; fallback.style.display = 'flex'; }); // initial style img.style.display = 'none'; fallback.style.display = 'flex'; } }; // Run on page load and after navigation (for Google SPA) const runScript = () => { addStremioButtonToGoogle(); }; window.addEventListener('load', runScript); // For Google SPA navigation let lastUrl = location.href; setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; runScript(); } }, 1000); })();