您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
右侧显示多平台搜索 + 烂番茄+IMDb评分(从豆瓣提取IMDb ID精准抓取),支持自主添加平台
// ==UserScript== // @name 豆瓣电影多平台一键直达 + 烂番茄+IMDb评分 // @description 右侧显示多平台搜索 + 烂番茄+IMDb评分(从豆瓣提取IMDb ID精准抓取),支持自主添加平台 // @author bai // @version 2.6 // @icon https://movie.douban.com/favicon.ico // @grant GM_addStyle // @grant GM_xmlhttpRequest // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js // @include https://movie.douban.com/subject/* // @run-at document-end // @license Apache-2.0 // @namespace https://greasyfork.org/users/967749 // ==/UserScript== $(function () { try { // 1. 注入页面样式 GM_addStyle(` #douban_movie_multi_platform { margin: 10px 0; padding: 12px; background: #f8f9fa; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); border: 1px solid #eee; } #douban_movie_multi_platform h3 { font-size: 16px; color: #333; border-bottom: 1px solid #e9ecef; padding-bottom: 6px; font-weight: bold; margin: 0 0 8px; } .movie_platforms { display: flex; flex-direction: column; gap: 8px; } .movie_platform { display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: 4px; background: white; transition: background 0.2s; border: 1px solid #f0f0f0; } .movie_platform:hover { background: #f1f8fe; } .platform_icon { font-size: 18px; min-width: 22px; text-align: center; } .platform_name { font-size: 14px; color: #555; flex-shrink: 0; width: 60px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; } .platform_link { font-size: 14px; padding: 4px 8px; border: 1px solid #00a1d6; background: #fff; color: #00a1d6; border-radius: 4px; text-decoration: none; transition: all 0.2s; } .platform_link:hover { background: #00a1d6; color: white; } /* 烂番茄评分样式 */ .rotten_rating { margin-left: auto; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: bold; cursor: pointer; } .rotten_fresh { background-color: #4CAF50; color: white; } .rotten_rotten { background-color: #F44336; color: white; } /* IMDb评分样式 */ .imdb_rating { margin-left: auto; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: bold; background-color: #F5C518; color: #000; cursor: pointer; display: flex; align-items: center; gap: 4px; } .imdb_rating .rating_count { font-size: 10px; color: #333; font-weight: normal; } /* 通用加载和错误样式 */ .rating_loading { color: #666; font-size: 12px; display: flex; align-items: center; gap: 4px; } .rating_loading::before { content: ""; width: 8px; height: 8px; border: 2px solid #ccc; border-top-color: #666; border-radius: 50%; animation: spin 1s linear infinite; } .rating_error { color: #e53935; font-size: 12px; } @keyframes spin { to { transform: rotate(360deg); } } `); // 2. 提取电影核心信息 function getMovieInfo() { // 提取标题和年份 let title = $('h1').first().text().replace(/\s*\(\d{4}\)\s*$/, '').trim(); const yearMatch = $('h1').first().text().match(/\((\d{4})\)/); const year = yearMatch? yearMatch[1] : ''; if (!title) title = $('#wrapper > h1 > span').text().replace(/\s*\(\d{4}\)\s*$/, '').trim(); // 提取导演/主演(用于搜索) let director = $('#info span:contains("导演")').nextAll('a').first().text().trim() || ''; let actor = $('#info span:contains("主演")').nextAll('a').first().text().trim() || ''; const keyword = [title, year, director || actor].filter(Boolean).join(' '); // 从豆瓣页面提取IMDb ID const imdbId = getImdbIdFromDouban(); console.log('[电影信息] 提取结果:', { title, year, keyword, imdbId }); return { title, year, keyword, imdbId }; } // 3. 从豆瓣页面提取IMDb ID function getImdbIdFromDouban() { const imdbSpan = Array.from(document.querySelectorAll('#info span.pl')) .find(span => span.textContent.includes('IMDb:')); return imdbSpan? imdbSpan.nextSibling?.textContent?.trim() : null; } const movieInfo = getMovieInfo(); if (!movieInfo.title) { console.error('无法提取电影标题,脚本终止'); return; } // 4. 平台配置区 const platforms = [ { name: '烂番茄', icon: '🍅', url: `https://www.rottentomatoes.com/search?search=${encodeURIComponent(`${movieInfo.title} ${movieInfo.year}`)}`, hasRating: true, ratingType: 'rotten' }, { name: 'IMDb', icon: '⭐', url: movieInfo.imdbId? `https://www.imdb.com/title/${movieInfo.imdbId}/` : `https://www.imdb.com/find?q=${encodeURIComponent(`${movieInfo.title} ${movieInfo.year}`)}`, hasRating: true, ratingType: 'imdb' }, { name: 'B站', icon: '📺', url: `https://search.bilibili.com/all?keyword=${encodeURIComponent(movieInfo.keyword)}`, hasRating: false }, { name: '抖音', icon: '♪', url: `https://www.douyin.com/search/${encodeURIComponent(movieInfo.keyword)}`, hasRating: false }, { name: 'GAZE', icon: '📽️', url: `https://gaze.run/filter?search=${encodeURIComponent(movieInfo.keyword)}`, hasRating: false }, { name: '努努', icon: '🎥', url: `https://nnyy.in/so?q=${encodeURIComponent(movieInfo.keyword)}`, hasRating: false }, { name: 'Youtube', icon: '▶', url: `https://www.youtube.com/results?search_query=${encodeURIComponent(movieInfo.keyword)}`, hasRating: false } ]; // 5. 生成模块HTML function createPlatformModule() { let html = ` <div id="douban_movie_multi_platform"> <h3>电影资源直达</h3> <div class="movie_platforms"> `; platforms.forEach((plat, index) => { const ratingPlaceholder = plat.hasRating ? `<span class="rating_loading">获取${plat.name}评分中...</span>` : ''; html += ` <div class="movie_platform" id="platform_${index}"> <div class="platform_icon">${plat.icon}</div> <div class="platform_name">${plat.name}</div> <a href="${plat.url}" target="_blank" class="platform_link">去搜索</a> ${ratingPlaceholder} </div> `; }); html += `</div></div>`; return html; } // 6. 插入右侧侧边栏(增加容错处理) const moduleHtml = createPlatformModule(); const aside = $('.aside'); if (aside.length) { const watchArea = aside.find('h2:contains("在哪儿看这部电影")').closest('div'); if (watchArea.length) { watchArea.before(moduleHtml); } else { aside.prepend(moduleHtml); } } else if ($('#content').length) { $('#content').prepend(moduleHtml); } else { // 终极容错:直接添加到body $('body').append(`<div style="position:fixed;top:20px;right:20px;z-index:9999;width:300px;">${moduleHtml}</div>`); } // 7. 字符串相似度算法 function stringSimilarity(str1, str2) { const len1 = str1.length, len2 = str2.length; const matrix = Array.from({ length: len1 + 1 }, () => Array(len2 + 1).fill(0)); for (let i = 0; i <= len1; i++) matrix[i][0] = i; for (let j = 0; j <= len2; j++) matrix[0][j] = j; for (let i = 1; i <= len1; i++) { for (let j = 1; j <= len2; j++) { const cost = str1[i-1].toLowerCase() === str2[j-1].toLowerCase()? 0 : 1; matrix[i][j] = Math.min( matrix[i-1][j] + 1, matrix[i][j-1] + 1, matrix[i-1][j-1] + cost ); } } return 1 - matrix[len1][len2] / Math.max(len1, len2); } // 8. 抓取烂番茄评分 function getRottenTomatoesRating(title, year) { const searchTerm = encodeURIComponent(`${title} ${year}`); const rtSearchUrl = `https://www.rottentomatoes.com/search?search=${searchTerm}`; return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: rtSearchUrl, timeout: 15000, onload: (response) => { if (response.status!== 200) { resolve({ rating: null, error: '请求失败', url: null }); return; } try { const tempDiv = document.createElement('div'); tempDiv.innerHTML = response.responseText; const movieItems = tempDiv.querySelectorAll('search-page-media-row[data-qa="data-row"]'); let bestMatch = { rating: null, score: 0, url: null }; movieItems.forEach(item => { const itemTitle = item.querySelector('h2')?.textContent || ''; const itemYear = item.getAttribute('releaseyear') || ''; const tomatometerScore = item.getAttribute('tomatometerscore') || ''; const itemUrl = item.querySelector('a[data-qa="info-name"]')?.getAttribute('href') || ''; let score = stringSimilarity(title, itemTitle); if (year && itemYear === year) score += 0.3; if (tomatometerScore && score > bestMatch.score) { bestMatch = { rating: `${tomatometerScore}%`, score: score, url: itemUrl? (itemUrl.startsWith('http')? itemUrl : `https://www.rottentomatoes.com${itemUrl}`) : null }; } }); resolve(bestMatch.rating ? { rating: bestMatch.rating, error: null, url: bestMatch.url } : { rating: null, error: '未找到匹配电影', url: null }); } catch (e) { console.error('烂番茄解析错误:', e); resolve({ rating: null, error: '解析失败', url: null }); } }, onerror: (error) => { resolve({ rating: null, error: `请求错误: ${error.message}`, url: null }); }, ontimeout: () => { resolve({ rating: null, error: '请求超时', url: null }); } }); }); } // 9. 抓取IMDb评分 function getImdbRating() { if (movieInfo.imdbId) { const imdbUrl = `https://www.imdb.com/title/${movieInfo.imdbId}/`; return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: imdbUrl, timeout: 15000, onload: (response) => { if (response.status!== 200) { resolve({ rating: null, count: null, error: '请求失败', url: imdbUrl }); return; } try { const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); const ratingContainer = doc.querySelector('[data-testid="hero-rating-bar__aggregate-rating__score"]'); if (!ratingContainer) { resolve({ rating: null, count: null, error: '未找到评分', url: imdbUrl }); return; } const score = ratingContainer.querySelector('span:first-child')?.textContent?.trim() || null; const count = ratingContainer.parentElement?.querySelector('div[class*="dwhNqC"]')?.textContent?.trim() || null; resolve({ rating: score? `${score}/10` : null, count: count || null, error: null, url: imdbUrl }); } catch (e) { console.error('IMDb解析错误:', e); resolve({ rating: null, count: null, error: '解析失败', url: imdbUrl }); } }, onerror: (error) => { resolve({ rating: null, count: null, error: `请求错误: ${error.message}`, url: imdbUrl }); }, ontimeout: () => { resolve({ rating: null, count: null, error: '请求超时', url: imdbUrl }); } }); }); } // 降级逻辑 const searchTerm = encodeURIComponent(`${movieInfo.title} ${movieInfo.year}`); const imdbSearchUrl = `https://www.imdb.com/find?q=${searchTerm}`; return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: imdbSearchUrl, timeout: 15000, onload: (response) => { if (response.status!== 200) { resolve({ rating: null, count: null, error: '请求失败', url: null }); return; } try { const tempDiv = document.createElement('div'); tempDiv.innerHTML = response.responseText; const movieItems = tempDiv.querySelectorAll('li.ipc-metadata-list-summary-item'); let bestMatch = { rating: null, count: null, score: 0, url: null }; movieItems.forEach(item => { const itemTitle = item.querySelector('.ipc-metadata-list-summary-item__t')?.textContent || ''; const itemYear = item.querySelector('.ipc-metadata-list-summary-item__li')?.textContent?.match(/\d{4}/)?.[0] || ''; const ratingElem = item.querySelector('.ipc-rating-star--base')?.textContent || null; const itemUrl = item.querySelector('a.ipc-metadata-list-summary-item__t')?.getAttribute('href') || ''; let score = stringSimilarity(movieInfo.title, itemTitle); if (movieInfo.year && itemYear === movieInfo.year) score += 0.3; if (ratingElem && score > bestMatch.score) { bestMatch = { rating: `${ratingElem.trim()}/10`, count: null, score: score, url: itemUrl? (itemUrl.startsWith('http')? itemUrl : `https://www.imdb.com${itemUrl}`) : null }; } }); resolve(bestMatch.rating ? { rating: bestMatch.rating, count: bestMatch.count, error: null, url: bestMatch.url } : { rating: null, count: null, error: '未找到匹配电影', url: null }); } catch (e) { console.error('IMDb搜索解析错误:', e); resolve({ rating: null, count: null, error: '解析失败', url: null }); } }, onerror: (error) => { resolve({ rating: null, count: null, error: `请求错误: ${error.message}`, url: null }); }, ontimeout: () => { resolve({ rating: null, count: null, error: '请求超时', url: null }); } }); }); } // 10. 处理评分显示(修复链接问题) function updateRatingDisplay(platformIndex, ratingInfo, ratingType) { const $platform = $(`#platform_${platformIndex}`); const $loading = $platform.find('.rating_loading'); if (!$loading.length) return; // 确保URL格式正确 let targetUrl = ratingInfo.url || '#'; if (targetUrl &&!targetUrl.startsWith('http')) { targetUrl = 'https://www.' + targetUrl.replace(/^\/+/, ''); } if (ratingInfo.rating && targetUrl) { if (ratingType === 'rotten') { const isFresh = parseInt(ratingInfo.rating) >= 70; $loading.replaceWith(` <a href="${targetUrl}" target="_blank" class="rotten_rating ${isFresh? 'rotten_fresh' : 'rotten_rotten'}"> 新鲜度 ${ratingInfo.rating} </a> `); } else if (ratingType === 'imdb') { const countHtml = ratingInfo.count? `<span class="rating_count">(${ratingInfo.count})</span>` : ''; $loading.replaceWith(` <a href="${targetUrl}" target="_blank" class="imdb_rating"> ${ratingInfo.rating} ${countHtml} </a> `); } } else { $loading.replaceWith(` <span class="rating_error">${ratingInfo.error || '未找到评分'}</span> `); } } // 11. 获取并显示评分 const ratingPlatforms = platforms.filter(p => p.hasRating); ratingPlatforms.forEach(plat => { const index = platforms.findIndex(p => p.name === plat.name); if (index === -1) return; if (plat.ratingType === 'rotten') { getRottenTomatoesRating(movieInfo.title, movieInfo.year) .then(ratingInfo => updateRatingDisplay(index, ratingInfo, 'rotten')) .catch(err => console.error('烂番茄评分获取失败:', err)); } else if (plat.ratingType === 'imdb') { getImdbRating() .then(ratingInfo => updateRatingDisplay(index, ratingInfo, 'imdb')) .catch(err => console.error('IMDb评分获取失败:', err)); } }); } catch (e) { console.error('脚本执行错误:', e); // 显示错误提示 $('body').append(` <div style="position:fixed;top:20px;left:20px;z-index:9999;background:red;color:white;padding:10px;"> 电影多平台脚本出错: ${e.message} </div> `); } });