// ==UserScript== // @name 豆b番——豆瓣电影显示IMDb和烂番茄评分 // @namespace https://github.com/fisheepx // @version 1.0.0 // @description 在豆瓣电影页面显示IMDb和烂番茄评分 // @author fish // @license MIT // @match https://movie.douban.com/subject/* // @grant GM_xmlhttpRequest // @homepage https://github.com/fisheepx/douban-imdb-rt // @supportURL https://github.com/fisheepx/douban-imdb-rt/issues // @icon https://raw.githubusercontent.com/fisheepx/douban-imdb-rt/main/assets/icon/icon.png // @compatible edge 最新版 运行正常 // @compatible chrome 最新版 运行正常 // @compatible firefox 最新版 运行正常 // @compatible safari 最新版 运行正常 // @downloadURL https://update.greasyfork.icu/scripts/527986/%E8%B1%86b%E7%95%AA%E2%80%94%E2%80%94%E8%B1%86%E7%93%A3%E7%94%B5%E5%BD%B1%E6%98%BE%E7%A4%BAIMDb%E5%92%8C%E7%83%82%E7%95%AA%E8%8C%84%E8%AF%84%E5%88%86.user.js // @updateURL https://update.greasyfork.icu/scripts/527986/%E8%B1%86b%E7%95%AA%E2%80%94%E2%80%94%E8%B1%86%E7%93%A3%E7%94%B5%E5%BD%B1%E6%98%BE%E7%A4%BAIMDb%E5%92%8C%E7%83%82%E7%95%AA%E8%8C%84%E8%AF%84%E5%88%86.meta.js // ==/UserScript== (function() { 'use strict'; // SVG图标定义 const TOMATO_ICONS = { fresh: ` `, rotten: ` `, empty: ` ` }; const POPCORN_ICONS = { full: ` `, tipped: ` `, empty: ` ` }; // 创建评分区域的HTML模板 const imdbSectionHTML = `
加载中... 加载中...
`; const rtSectionHTML = `
加载中... 影评人 • 加载中...
加载中... 观众 • 加载中...
`; // 主要功能类 class MovieRatings { constructor() { this.initialize(); } initialize() { const imdbId = this.getImdbId(); if (!imdbId) return; this.createRatingsSections(); this.fetchImdbRatings(imdbId); } getImdbId() { const imdbSpan = Array.from(document.querySelectorAll('#info span.pl')) .find(span => span.textContent.includes('IMDb:')); return imdbSpan ? imdbSpan.nextSibling.textContent.trim() : null; } createRatingsSections() { const ratingsOnWeight = document.querySelector('.ratings-on-weight'); if (!ratingsOnWeight?.parentNode) return; // 创建IMDb评分区域 this.imdbSection = this.createSection('imdb', imdbSectionHTML); // 创建烂番茄评分区域 this.rtSection = this.createSection('rt', rtSectionHTML); // 插入到页面 ratingsOnWeight.parentNode.insertBefore(this.imdbSection, ratingsOnWeight.nextSibling); ratingsOnWeight.parentNode.insertBefore(this.rtSection, this.imdbSection.nextSibling); } createSection(type, template) { const section = document.createElement('div'); section.className = 'rating_wrap clearbox'; section.style.cssText = 'margin-top: 12px; padding-top: 12px;'; section.innerHTML = template; return section; } async fetchImdbRatings(imdbId) { try { const response = await this.makeRequest(`https://www.imdb.com/title/${imdbId}/`); const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); const ratingData = this.extractImdbRating(doc); this.updateImdbUI(ratingData, imdbId); const englishTitle = this.getEnglishTitle(doc); if (englishTitle) { await this.fetchRottenTomatoesRatings(englishTitle); } } catch (error) { this.handleError('imdb'); } } extractImdbRating(doc) { const ratingContainer = doc.querySelector('[data-testid="hero-rating-bar__aggregate-rating__score"]'); if (!ratingContainer) return { score: 'N/A', count: '0' }; const score = ratingContainer.querySelector('span:first-child')?.textContent.trim(); const count = ratingContainer.parentElement?.querySelector('div[class*="dwhNqC"]')?.textContent.trim(); return { score, count }; } getEnglishTitle(doc) { return doc.querySelector('h1[data-testid="hero__pageTitle"]')?.textContent.trim(); } async fetchRottenTomatoesRatings(title) { try { const searchUrl = `https://www.rottentomatoes.com/search?search=${encodeURIComponent(title)}`; const searchResponse = await this.makeRequest(searchUrl); const searchDoc = new DOMParser().parseFromString(searchResponse.responseText, 'text/html'); const movieLink = searchDoc.querySelector('search-page-media-row a[data-qa="info-name"]'); if (!movieLink) throw new Error('未找到匹配的烂番茄页面'); const movieUrl = movieLink.getAttribute('href'); const movieResponse = await this.makeRequest(movieUrl); this.processRottenTomatoesPage(movieResponse, movieUrl); } catch (error) { this.handleError('rt'); } } processRottenTomatoesPage(response, movieUrl) { const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); const scoreData = this.extractRottenTomatoesScores(doc); this.updateRottenTomatoesUI(scoreData, movieUrl); } extractRottenTomatoesScores(doc) { const scripts = doc.querySelectorAll('script[type="application/json"]'); for (const script of scripts) { if (script.textContent.includes('"audienceScore"') && script.textContent.includes('"criticsScore"')) { try { const data = JSON.parse(script.textContent); if (data.audienceScore && data.criticsScore) { return data; } } catch (e) {} } } return null; } updateImdbUI(ratingData, imdbId) { const ratingNum = this.imdbSection.querySelector('.rating_num'); const ratingSum = this.imdbSection.querySelector('span'); ratingNum.textContent = `${ratingData.score}/10`; ratingSum.textContent = `${ratingData.count}人评价`; ratingNum.onclick = () => window.open(`https://www.imdb.com/title/${imdbId}/`, '_blank'); } updateRottenTomatoesUI(scoreData, movieUrl) { if (!scoreData) { this.handleError('rt'); return; } const [criticsRating, audienceRating] = this.rtSection.querySelectorAll('.rating_num'); const [criticsCount, audienceCount] = this.rtSection.querySelectorAll('span'); // 更新影评人评分 this.updateCriticsScore(scoreData.criticsScore, criticsRating, criticsCount); // 更新观众评分 this.updateAudienceScore(scoreData.audienceScore, audienceRating, audienceCount); // 添加点击事件 [criticsRating, audienceRating].forEach(rating => { rating.style.cursor = 'pointer'; rating.onclick = () => window.open(movieUrl, '_blank'); }); } updateCriticsScore(criticsScore, ratingElement, countElement) { const tomatoContainer = this.rtSection.querySelector('.tomato-container'); if (!criticsScore?.score) { ratingElement.textContent = 'N/A'; countElement.textContent = '影评人 • 暂无数据'; tomatoContainer.innerHTML = TOMATO_ICONS.empty; } else { ratingElement.textContent = criticsScore.score + '%'; countElement.textContent = '影评人'; tomatoContainer.innerHTML = criticsScore.sentiment === 'POSITIVE' ? TOMATO_ICONS.fresh : TOMATO_ICONS.rotten; } this.setIconStyle(tomatoContainer); } updateAudienceScore(audienceScore, ratingElement, countElement) { const popcornContainer = this.rtSection.querySelector('.popcorn-container'); if (!audienceScore?.score) { ratingElement.textContent = 'N/A'; countElement.textContent = '观众 • 暂无数据'; popcornContainer.innerHTML = POPCORN_ICONS.empty; } else { const score = parseInt(audienceScore.score); ratingElement.textContent = score + '%'; countElement.textContent = '观众'; popcornContainer.innerHTML = score >= 60 ? POPCORN_ICONS.full : POPCORN_ICONS.tipped; } this.setIconStyle(popcornContainer); } setIconStyle(container) { const svg = container.querySelector('svg'); if (svg) { svg.style.width = '24px'; svg.style.height = '24px'; } } handleError(type) { if (type === 'imdb') { const ratingNum = this.imdbSection.querySelector('.rating_num'); const ratingSum = this.imdbSection.querySelector('span'); ratingNum.textContent = '获取失败'; ratingSum.textContent = '获取失败'; ratingNum.style.color = '#999'; } else if (type === 'rt') { const [criticsRating, audienceRating] = this.rtSection.querySelectorAll('.rating_num'); const [criticsCount, audienceCount] = this.rtSection.querySelectorAll('span'); criticsRating.textContent = 'N/A'; audienceRating.textContent = 'N/A'; criticsCount.textContent = '影评人 • 暂无数据'; audienceCount.textContent = '观众 • 暂无数据'; } } makeRequest(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, onload: resolve, onerror: reject }); }); } } // 初始化 new MovieRatings(); })();