// ==UserScript== // @name 豆瓣侠 // @version 1.0.5 // @description 云盘搜索 + 电影/音乐/图书简介生成 + 查看原图 + 1080p图床上传 + 微软翻译 + 评论补全 + 设置面板 + 样式切换 + 图片尺寸可选 // @author 全网搜:wpys.cc // @run-at document-end // @icon https://img3.doubanio.com/favicon.ico // @match https://movie.douban.com/subject/* // @match https://book.douban.com/subject/* // @match https://music.douban.com/subject/* // @grant GM_setClipboard // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect api.wmdb.tv // @connect api.themoviedb.org // @connect image.tmdb.org // @connect img.wpzy.cc // @connect img1.doubanio.com // @connect img2.doubanio.com // @connect img3.doubanio.com // @connect img9.doubanio.com // @connect images.weserv.nl // @connect edge.microsoft.com // @connect api-edge.cognitive.microsofttranslator.com // @require https://code.jquery.com/jquery-3.6.0.min.js // ==/UserScript== (function() { 'use strict'; // ==================== TMDb 配置 ==================== const TMDB_CONFIG = { apiKey: GM_getValue('tmdb-api-key', ''), baseUrl: 'https://api.themoviedb.org/3', imageBaseUrl: 'https://image.tmdb.org/t/p/', language: 'zh-CN', enabled: GM_getValue('tmdb-enabled', true) }; // ==================== 图片尺寸配置 ==================== const IMAGE_SIZE_CONFIG = { small: { name: '小图', width: 360, uploadQuality: 0.7 }, medium: { name: '中图', width: 480, uploadQuality: 0.7 }, large: { name: '大图(默认)', width: 1080, uploadQuality: 0.7 } }; function getCurrentImageSizeConfig() { const sizeMode = GM_getValue('image-size', 'large'); return IMAGE_SIZE_CONFIG[sizeMode] || IMAGE_SIZE_CONFIG.large; } // ==================== 简介样式配置 ==================== const MD_STYLE_CONFIG = { classic: { name: '经典样式(默认)', template: generateClassicMarkdown }, modern: { name: '现代简洁风', template: generateModernMarkdown }, card: { name: '信息卡片风', template: generateCardMarkdown } }; function getCurrentStyle() { const styleMode = GM_getValue('md-style', 'classic'); return MD_STYLE_CONFIG[styleMode] || MD_STYLE_CONFIG.classic; } // ==================== 样式生成函数 ==================== function generateClassicMarkdown(data) { let md = ''; if (data.posterUrl) md += `![${data.imageType || '海报'}](${data.posterUrl})\n\n`; if (data.type === 'movie') { if (data.title) md += `◎片  名 ${data.title}\n`; if (data.year) md += `◎年  代 ${data.year}\n`; if (data.country) md += `◎产  地 ${data.country}\n`; if (data.genre) md += `◎类  别 ${data.genre}\n`; if (data.language) md += `◎语  言 ${data.language}\n`; if (data.releaseDate) md += `◎上映日期 ${data.releaseDate}\n`; if (data.imdbLink) md += `◎IMDb链接 <${data.imdbLink}>\n`; if (data.rating) md += `◎豆瓣评分 ${data.rating}/10 from ${data.votes} users\n`; if (data.doubanLink) md += `◎豆瓣链接 ${data.doubanLink}\n`; if (data.duration) md += `◎片  长 ${data.duration}\n`; if (data.director) md += `◎导  演 ${data.director}\n`; if (data.writer) md += `◎编  剧 ${data.writer}\n`; if (data.actorsFormatted) md += `◎主  演 ${data.actorsFormatted}`; } else if (data.type === 'book') { if (data.title) md += `◎书  名 ${data.title}\n`; if (data.author) md += `◎作  者 ${data.author}\n`; if (data.publisher) md += `◎出版社  ${data.publisher}\n`; if (data.series) md += `◎丛  书 ${data.series}\n`; if (data.isbn) md += `◎ISBN   ${data.isbn}\n`; if (data.rating) md += `◎豆瓣评分 ${data.rating}/10 from ${data.votes} users\n`; if (data.doubanLink) md += `◎豆瓣链接 ${data.doubanLink}\n\n`; } else if (data.type === 'music') { if (data.title) md += `◎专辑名称 ${data.title}\n`; if (data.artist) md += `◎表演者  ${data.artist}\n`; if (data.typeName) md += `◎专辑类型 ${data.typeName}\n`; if (data.media) md += `◎介质   ${data.media}\n`; if (data.date) md += `◎发行时间 ${data.date}\n`; if (data.publisher) md += `◎出版者  ${data.publisher}\n`; if (data.rating) md += `◎豆瓣评分 ${data.rating}/10 from ${data.votes} users\n`; if (data.doubanLink) md += `◎豆瓣链接 ${data.doubanLink}\n\n`; if (data.tracks && data.tracks.length) { md += `◎曲  目\n\n`; data.tracks.forEach((track, i) => { md += `  ${i+1}. ${track}\n`; }); md += `\n`; } } if (data.description) { md += `\n◎简  介\n\n  ${data.description.replace(/\n/g, '\n  ')}\n`; } return md; } function generateModernMarkdown(data) { let md = ''; if (data.posterUrl) md += `![${data.imageType || '海报'}](${data.posterUrl})\n\n`; let titleLine = `**${data.title}**`; if (data.year) titleLine += ` (${data.year})`; if (data.country) titleLine += ` · ${data.country}`; if (data.genre) titleLine += ` · ${data.genre}`; md += `${titleLine}\n\n`; if (data.director) md += `🎬 **导演**:${data.director}\n`; if (data.writer) md += `✍️ **编剧**:${data.writer}\n`; if (data.artist) md += `🎤 **表演者**:${data.artist}\n`; if (data.author) md += `📖 **作者**:${data.author}\n`; if (data.publisher) md += `🏢 **出版社**:${data.publisher}\n`; if (data.actors && data.actors.length) md += `👥 **主演**:${data.actors.slice(0, 8).join(' / ')}\n`; if (data.rating) md += `⭐ **豆瓣**:${data.rating}/10 (${data.votes}人评)\n`; md += `🔗 **链接**:[豆瓣](${data.doubanLink})`; if (data.imdbLink) md += ` · [IMDb](${data.imdbLink})`; md += `\n`; const extraInfo = []; if (data.language) extraInfo.push(data.language); if (data.releaseDate) extraInfo.push(data.releaseDate); if (data.duration) extraInfo.push(data.duration); if (data.typeName) extraInfo.push(data.typeName); if (data.media) extraInfo.push(data.media); if (data.date) extraInfo.push(data.date); if (data.series) extraInfo.push(`丛书:${data.series}`); if (data.isbn) extraInfo.push(`ISBN:${data.isbn}`); if (extraInfo.length > 0) md += `📅 **其他**:${extraInfo.join(' · ')}\n`; md += `\n---\n\n`; if (data.description) md += `**剧情简介**\n\n${data.description}\n`; if (data.tracks && data.tracks.length) { md += `\n### 曲目\n\n`; data.tracks.slice(0, 12).forEach((track, i) => { md += `${i+1}. ${track}\n`; }); if (data.tracks.length > 12) md += `... 共${data.tracks.length}首\n`; } return md; } function generateCardMarkdown(data) { let md = ''; if (data.posterUrl) md += `![${data.imageType || '海报'}](${data.posterUrl})\n\n`; md += `# ${data.title}\n\n`; md += `| 项目 | 内容 |\n`; md += `|------|------|\n`; if (data.year) md += `| 年份 | ${data.year} |\n`; if (data.country) md += `| 地区 | ${data.country} |\n`; if (data.genre) md += `| 类型 | ${data.genre} |\n`; if (data.language) md += `| 语言 | ${data.language} |\n`; if (data.releaseDate) md += `| 上映 | ${data.releaseDate} |\n`; if (data.duration) md += `| 片长 | ${data.duration} |\n`; if (data.director) md += `| 导演 | ${data.director} |\n`; if (data.writer) md += `| 编剧 | ${data.writer} |\n`; if (data.artist) md += `| 表演者 | ${data.artist} |\n`; if (data.author) md += `| 作者 | ${data.author} |\n`; if (data.publisher) md += `| 出版社 | ${data.publisher} |\n`; if (data.series) md += `| 丛书 | ${data.series} |\n`; if (data.isbn) md += `| ISBN | ${data.isbn} |\n`; if (data.typeName) md += `| 专辑类型 | ${data.typeName} |\n`; if (data.media) md += `| 介质 | ${data.media} |\n`; if (data.date) md += `| 发行时间 | ${data.date} |\n`; if (data.actors && data.actors.length) md += `| 主演 | ${data.actors.slice(0, 8).join(' / ')} |\n`; if (data.rating) md += `| 豆瓣 | ${data.rating}/10 (${data.votes}人评) |\n`; md += `| 链接 | [豆瓣](${data.doubanLink})`; if (data.imdbLink) md += ` · [IMDb](${data.imdbLink})`; md += ` |\n`; md += `\n## 简介\n\n`; if (data.description) md += `${data.description}\n`; if (data.tracks && data.tracks.length) { md += `\n## 曲目\n\n`; data.tracks.slice(0, 12).forEach((track, i) => { md += `${i+1}. ${track}\n`; }); if (data.tracks.length > 12) md += `\n*共${data.tracks.length}首*\n`; } return md; } // ==================== 样式 ==================== GM_addStyle(` .c-aside { margin-bottom: 30px; } .c-aside h2 { font-size: 14px; color: #333; margin-bottom: 10px; } .c-aside-body a { border-radius: 6px; color: #006400; display: inline-block; margin: 0 8px 8px 0; padding: 0 8px; text-align: center; width: 65px; background-color: #f5f5f5; text-decoration: none; font-size: 12px; line-height: 24px; font-weight: bold; } .c-aside-body a:hover { background-color: #e8e8e8; } #md-generator { margin: 20px 0; padding: 15px; background: #f9f9f9; border-radius: 6px; border-left: 4px solid #00a65a; } .button-container { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%; } .gen-btn { background: #00a65a; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; text-align: center; width: auto; min-width: 200px; max-width: 100%; margin: 0 auto; transition: all 0.3s; } .gen-btn:hover:not(:disabled) { background: #008d4c; transform: translateY(-1px); } .gen-btn:disabled { background: #ccc; cursor: wait; } #loading-status, #tg-status { margin-left: 0; font-size: 12px; color: #666; } .success { color: #5cb85c; } .error { color: #d9534f; } .warning { color: #f0ad4e; } #md-preview { margin-top: 15px; padding: 15px; background: #f5f5f5; border-radius: 4px; border: 1px solid #ddd; display: none; } #md-preview pre { margin: 0; white-space: pre-wrap; font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.6; background: #f5f5f5; padding: 10px; } .preview-header { display: flex; justify-content: flex-start; align-items: center; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px dashed #ccc; } #md-copy-btn { background: #337ab7; color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; } #md-copy-btn:hover { background: #286090; } #md-copy-btn.copied { background: #5cb85c; } .view-original { display: inline-block; margin-left: 10px; font-size: 12px; color: #00a65a; text-decoration: none; } .view-original:hover { text-decoration: underline; } #doudanxia-settings { position: fixed; top: 80px; right: 20px; width: 420px; background: white; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 9999; padding: 20px; font-size: 13px; max-height: 80vh; overflow-y: auto; } .settings-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #00a65a; } .settings-header h3 { margin: 0; color: #00a65a; } .settings-section { margin-bottom: 20px; padding: 12px; background: #f9f9f9; border-radius: 6px; } .settings-section h4 { margin: 0 0 10px 0; font-size: 14px; color: #333; } .site-tag { display: inline-block; background: #e8e8e8; padding: 4px 12px; border-radius: 20px; font-size: 12px; margin: 0 5px 5px 0; } .close-btn { background: #f0f0f0; border: none; padding: 5px 20px; border-radius: 20px; cursor: pointer; font-size: 12px; } .close-btn:hover { background: #e0e0e0; } .settings-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; margin-top: 5px; box-sizing: border-box; } .settings-checkbox { margin-right: 8px; vertical-align: middle; } .settings-label { cursor: pointer; vertical-align: middle; } .tmdb-status { font-size: 11px; margin-top: 5px; } .tmdb-status.enabled { color: #5cb85c; } .tmdb-status.disabled { color: #d9534f; } .tmdb-status.warning { color: #f0ad4e; } `); // ==================== 云盘搜索站点 ==================== const PAN_SITES = [ { name: '网盘论坛', url: 'https://wpzy.cc/?q=' }, { name: '网盘影视', url: 'https://wpzy.me/?q=' }, { name: '网盘资源社', url: 'https://www.wpzy.cc/?q=' }, { name: '全网搜索', url: 'https://wpys.cc/s/' }, { name: '博哥影视', url: 'https://blog.wpys.cc/search/' }, { name: 'TG 频道', url: 'https://tg.wpzy.cc/?q=' } ]; // ==================== 设置变量 ==================== const enableGenerator = GM_getValue('enable-generator', true); // ==================== 设置面板函数 ==================== function showSettingsPanel() { if ($('#doudanxia-settings').length) { $('#doudanxia-settings').remove(); return; } const sitesHtml = PAN_SITES.map(site => `${site.name}` ).join(''); const currentEnable = GM_getValue('enable-generator', true); const tmdbEnabled = GM_getValue('tmdb-enabled', true); const tmdbApiKey = GM_getValue('tmdb-api-key', ''); const currentStyle = GM_getValue('md-style', 'classic'); const currentSize = GM_getValue('image-size', 'large'); let tmdbStatusClass = 'disabled'; let tmdbStatusText = '⚠️ 未启用 TMDb 接口'; if (tmdbEnabled) { if (tmdbApiKey) { tmdbStatusClass = 'enabled'; tmdbStatusText = '✅ TMDb 已启用,API Key 已配置'; } else { tmdbStatusClass = 'warning'; tmdbStatusText = '⚠️ TMDb 已启用,但未配置 API Key,请填写有效的 API Key'; } } $('body').append(`

🎬 豆瓣侠设置

v1.0.5
关闭后只保留云盘搜索

📝 简介样式

选择生成简介的显示格式

🖼️ 海报尺寸

影响两个按钮的图片输出尺寸

🎬 TMDb 备用接口

${tmdbStatusText}
🔗 注册 TMDb获取 API Key
💡 TMDb 仅用于按钮1的电影海报获取

📁 云盘搜索 (${PAN_SITES.length}个站点)

${sitesHtml}
点击链接自动搜索当前资源
`); $('#close-settings').click(() => $('#doudanxia-settings').remove()); $('#enable-generator').change(function() { GM_setValue('enable-generator', $(this).is(':checked')); if (confirm('设置已保存,是否刷新页面?')) location.reload(); }); $('#md-style-select').change(function() { const newStyle = $(this).val(); GM_setValue('md-style', newStyle); if (confirm('样式已更改,是否刷新页面?')) location.reload(); }); $('#image-size-select').change(function() { const newSize = $(this).val(); GM_setValue('image-size', newSize); if (confirm('图片尺寸已更改,是否刷新页面?')) location.reload(); }); $('#tmdb-enabled').change(function() { GM_setValue('tmdb-enabled', $(this).is(':checked')); const newEnabled = $(this).is(':checked'); const apiKey = $('#tmdb-api-key').val().trim(); if (newEnabled && !apiKey) { alert('TMDb 已启用,但未填写 API Key。请先获取并填写 API Key。'); } }); $('#tmdb-api-key').change(function() { const newKey = $(this).val().trim(); GM_setValue('tmdb-api-key', newKey); const enabled = $('#tmdb-enabled').is(':checked'); const statusDiv = $(this).siblings('.tmdb-status'); if (enabled && newKey) { statusDiv.removeClass('disabled warning').addClass('enabled').text('✅ TMDb 已启用,API Key 已配置'); } else if (enabled && !newKey) { statusDiv.removeClass('enabled disabled').addClass('warning').text('⚠️ TMDb 已启用,但未配置 API Key,请填写有效的 API Key'); } else { statusDiv.removeClass('enabled warning').addClass('disabled').text('⚠️ 未启用 TMDb 接口'); } }); $(document).mouseup(function(e) { const container = $('#doudanxia-settings'); if (!container.is(e.target) && container.has(e.target).length === 0) { container.remove(); } }); } // ==================== 图床配置 ==================== const IMAGE_HOST = { baseURL: 'https://img.wpzy.cc', uploadApi: '/upload' }; // ==================== 图床上传器 ==================== class ImageUploader { constructor() { this.baseURL = IMAGE_HOST.baseURL; this.uploadApi = IMAGE_HOST.uploadApi; } async downloadImage(imageUrl) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: imageUrl, headers: { 'Referer': 'https://movie.douban.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, responseType: 'blob', onload: (res) => { if (res.status === 200) { resolve(res.response); } else { reject(new Error(`下载失败: ${res.status}`)); } }, onerror: reject }); }); } async loadImage(blob) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = URL.createObjectURL(blob); }); } async processImage(imageBlob, targetWidth) { const img = await this.loadImage(imageBlob); const originalWidth = img.width; const originalHeight = img.height; let finalBlob = imageBlob; if (originalWidth > targetWidth) { const ratio = targetWidth / originalWidth; const newHeight = Math.round(originalHeight * ratio); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = targetWidth; canvas.height = newHeight; ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(img, 0, 0, targetWidth, newHeight); finalBlob = await new Promise((resolve) => { canvas.toBlob(resolve, 'image/jpeg', 0.7); }); console.log(`调整完成: ${canvas.width}x${canvas.height}, 质量 70%`); } else { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = originalWidth; canvas.height = originalHeight; ctx.drawImage(img, 0, 0); finalBlob = await new Promise((resolve) => { canvas.toBlob(resolve, 'image/jpeg', 0.7); }); console.log(`原图压缩: ${originalWidth}x${originalHeight}, 质量 70%`); } URL.revokeObjectURL(img.src); return finalBlob; } async uploadToHost(blob) { const fileName = 'movie_poster.jpg'; const formData = new FormData(); formData.append('file', blob, fileName); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: `${this.baseURL}${this.uploadApi}`, data: formData, responseType: 'json', timeout: 60000, onload: resolve, onerror: reject }); }); } async upload(imageUrl, targetWidth) { try { console.log(`图床上传模式 - 目标宽度: ${targetWidth}px, 质量: 70%`); const imageBlob = await this.downloadImage(imageUrl); const processedBlob = await this.processImage(imageBlob, targetWidth); const response = await this.uploadToHost(processedBlob); console.log('上传结果:', response); if (response.status === 200) { const data = response.response; if (data && data.success && data.data && data.data.link) { const imgurUrl = data.data.link; const proxyUrl = imgurUrl.replace('https://i.imgur.com', 'https://img.wpzy.cc/v2'); return { success: true, url: proxyUrl }; } } return { success: false, error: `HTTP ${response.status}: ${JSON.stringify(response.response)}` }; } catch (error) { console.error('上传异常:', error); return { success: false, error: error.message }; } } } // ==================== 微软翻译 ==================== async function translateToChinese(text) { if (!text || /[\u4e00-\u9fa5]/.test(text)) return text; console.log('原文长度:', text.length); console.log('原文前100字:', text.substring(0, 100)); if (text.length > 5000) { console.log('原文超过5000,截断'); text = text.substring(0, 5000); } try { const token = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://edge.microsoft.com/translate/auth', onload: (res) => resolve(res.responseText), onerror: reject }); }); const result = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://api-edge.cognitive.microsofttranslator.com/translate?to=zh-Hans&api-version=3.0', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, data: JSON.stringify([{ "Text": text }]), responseType: 'json', onload: (res) => { console.log('翻译API返回状态:', res.status); resolve(res.response); }, onerror: reject }); }); const translated = result?.[0]?.translations?.[0]?.text || text; console.log('译文长度:', translated.length); console.log('译文前100字:', translated.substring(0, 100)); return translated; } catch (e) { console.error('微软翻译失败:', e); return text; } } // ==================== 从当前页面获取点赞最高评论 ==================== function getTopRatedComment() { let topComment = null; let maxVotes = 0; $('.comment-item').each(function() { const $comment = $(this); const votesText = $comment.find('.votes').text().trim(); const votes = parseInt(votesText) || 0; const content = $comment.find('.short').text().trim(); if (content.length > 30 && votes > maxVotes) { maxVotes = votes; topComment = content; } }); return topComment; } // ==================== 从豆瓣页面获取简介 ==================== function getDescriptionFromPage() { let description = ''; if ($('#link-report > span.all.hidden').length) { description = $('#link-report > span.all.hidden').text(); console.log('从 span.all.hidden 获取简介'); } else if ($('[property="v:summary"]').length) { description = $('[property="v:summary"]').text(); console.log('从 v:summary 获取简介'); } if (description) { description = description.replace(/\s+/g, ' ').trim(); console.log('简介长度:', description.length); } return description; } // ==================== 从豆瓣页面提取中文标题 ==================== function getChineseTitle() { const titleElement = $('#content h1 span').first(); let rawTitle = titleElement.text().trim(); if (!rawTitle) { rawTitle = $('h1 span[property="v:itemreviewed"]').text().trim(); } let title = rawTitle.replace(/[\((][^\))]*[\))]/g, '').trim(); const parts = title.split(' '); if (parts.length > 1 && !/[\u4e00-\u9fa5]/.test(parts[1])) { title = parts[0]; } return title; } // ==================== TMDb 接口函数 ==================== async function searchMovieOnTMDB(title, year) { if (!TMDB_CONFIG.enabled || !TMDB_CONFIG.apiKey) return null; return new Promise((resolve) => { const cleanTitle = title.replace(/[((][^)]*[))]/g, '').trim(); const params = new URLSearchParams({ api_key: TMDB_CONFIG.apiKey, query: cleanTitle, language: TMDB_CONFIG.language, year: year || '' }); GM_xmlhttpRequest({ method: 'GET', url: `https://api.themoviedb.org/3/search/movie?${params}`, timeout: 8000, onload: function(res) { if (res.status === 200) { try { const data = JSON.parse(res.responseText); if (data.results && data.results.length > 0) { resolve(data.results[0]); } else { resolve(null); } } catch(e) { resolve(null); } } else { resolve(null); } }, onerror: function() { resolve(null); } }); }); } async function getMovieDetailsFromTMDB(tmdbId) { if (!TMDB_CONFIG.enabled || !TMDB_CONFIG.apiKey) return null; return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: `https://api.themoviedb.org/3/movie/${tmdbId}?api_key=${TMDB_CONFIG.apiKey}&language=${TMDB_CONFIG.language}&append_to_response=credits`, timeout: 8000, onload: function(res) { if (res.status === 200) { try { resolve(JSON.parse(res.responseText)); } catch(e) { resolve(null); } } else { resolve(null); } }, onerror: function() { resolve(null); } }); }); } // ==================== WMDB 接口函数 ==================== async function getMoviePosterFromWMDB(doubanId) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: `https://api.wmdb.tv/movie/api?id=${doubanId}`, timeout: 8000, onload: function(res) { if (res.status === 200) { try { const data = JSON.parse(res.responseText); if (data?.data) { const cnData = data.data.find(d => d.lang === 'Cn') || data.data[0]; if (cnData?.poster) { resolve(cnData.poster); } else { resolve(null); } } else { resolve(null); } } catch (e) { resolve(null); } } else { resolve(null); } }, onerror: function() { resolve(null); } }); }); } // ==================== 复制到剪贴板 ==================== function copyToClipboard() { const text = $('#md-content').text(); if (!text) return; GM_setClipboard(text); const btn = $('#md-copy-btn'); btn.addClass('copied').text('✅ 复制成功'); setTimeout(() => { btn.removeClass('copied').text('📋 一键复制'); }, 2000); } // ==================== 初始化云盘搜索 ==================== function initPanSearch(title) { const searchKeyword = encodeURIComponent(title.split(' ')[0]); $('#content div.aside').prepend(`
🎬 豆瓣侠 · 资源搜索 v1.0.5

云盘搜索· · · · · ·

${PAN_SITES.map(site => `${site.name}` ).join('')}
`); } // ==================== 查看原图功能 ==================== function initMovieViewOriginal() { setTimeout(function() { if ($('#mainpic').length) { let posterImg = document.querySelector('#mainpic > a > img'); if (posterImg) { let postersUrl = posterImg.getAttribute('src'); let rawUrl = postersUrl.replace(/photo\/[sl](_ratio_poster|pic)\/public\/(p\d+).+$/, "photo/l/public/$2.jpg"); if (rawUrl === postersUrl) { rawUrl = postersUrl.split('?')[0]; } if ($('#mainpic p.gact').length === 0) { $("#mainpic").append("

"); } $('#mainpic p.gact').after(`查看原图`); } } }, 500); } function initBookViewOriginal() { setTimeout(function() { if ($('#mainpic').length) { let posterAnchor = document.querySelector('#mainpic > a.nbg'); if (posterAnchor) { let postersUrl = posterAnchor.getAttribute('href'); if ($('#mainpic p.gact').length === 0) { $("#mainpic").append("

"); } $('#mainpic p.gact').after(`查看原图`); } } }, 500); } function initMusicViewOriginal() { setTimeout(function() { if ($('#mainpic').length) { let posterAnchor = document.querySelector('#mainpic > a.nbg'); if (posterAnchor) { let postersUrl = posterAnchor.getAttribute('href'); let rawUrl = postersUrl; if (postersUrl.includes('/m/')) { rawUrl = postersUrl.replace('/m/', '/l/'); } else if (postersUrl.includes('/small/')) { rawUrl = postersUrl.replace('/small/', '/large/'); } else if (postersUrl.includes('/icon/')) { rawUrl = postersUrl.replace('/icon/', '/large/'); } if ($('#mainpic p.gact').length === 0) { $("#mainpic").append("

"); } $('#mainpic p.gact').after(`查看原图`); } } }, 500); setTimeout(function() { if ($('#mainpic').length && $('#mainpic p.gact').length && !$('#mainpic .view-original').length) { let posterAnchor = document.querySelector('#mainpic > a.nbg') || document.querySelector('#mainpic a'); if (posterAnchor) { let postersUrl = posterAnchor.getAttribute('href'); let rawUrl = postersUrl.replace('/m/', '/l/'); $('#mainpic p.gact').after(`查看原图`); } } }, 1500); } // ==================== 初始化电影页面按钮 ==================== function initMovieButtons() { const subjectId = window.location.href.match(/\/(\d{7,8})\//)?.[1]; if (!subjectId) return; const uploader = new ImageUploader(); let activeButton = null; $('#info').after(`
`); $('#md-copy-btn').click(copyToClipboard); // ==================== 按钮1:接口海报模式 ==================== $('#md-generator-btn').click(async function() { const $btn = $(this); const $status = $('#loading-status'); const $preview = $('#md-preview'); const $content = $('#md-content'); if ($preview.is(':visible') && activeButton === 'btn1') { $preview.slideUp(300); $btn.text('📋 生成简介 · 接口海报'); activeButton = null; return; } $btn.prop('disabled', true).text('⏳ 获取数据...'); $status.removeClass('success error').text('正在获取数据...'); try { const sizeConfig = getCurrentImageSizeConfig(); const currentStyle = getCurrentStyle(); const infoText = $('#info').text(); const chineseTitle = getChineseTitle(); let year = ''; const yearElement = $('#content .year').text().trim(); if (yearElement) { year = yearElement.replace(/[()]/g, ''); } let genre = ''; const genreElements = $('#info span[property="v:genre"]'); if (genreElements.length) { genre = genreElements.map(function() { return $(this).text(); }).get().join(' / '); } let country = ''; const countryMatch = infoText.match(/制片国家\/地区:\s*([^\n]+)/); if (countryMatch) { country = countryMatch[1].trim(); } let language = ''; const languageMatch = infoText.match(/语言:\s*([^\n]+)/); if (languageMatch) { language = languageMatch[1].trim(); } let releaseDate = ''; const dateElement = $('#info span[property="v:initialReleaseDate"]'); if (dateElement.length) { releaseDate = dateElement.text().trim(); } let duration = ''; const durationMatch = infoText.match(/片长:\s*([^\n]+)/); if (durationMatch) { duration = durationMatch[1].trim(); } let rating = ''; let votes = ''; const ratingElement = $('strong.ll.rating_num'); if (ratingElement.length) { rating = ratingElement.text().trim(); } const votesElement = $('a.rating_people span'); if (votesElement.length) { votes = votesElement.text().trim(); } let director = ''; const directorElements = $('#info a[rel="v:directedBy"]'); if (directorElements.length) { director = directorElements.map(function() { return $(this).text(); }).get().join(' / '); } let writer = ''; const writerMatch = infoText.match(/编剧:\s*([^\n]+)/); if (writerMatch) { writer = writerMatch[1].trim(); } let actors = []; $('#info .actor a, #info a[rel="v:starring"]').each(function() { const actorName = $(this).text().trim(); if (actorName && !actors.includes(actorName)) { actors.push(actorName); } }); let actorsFormatted = ''; if (actors.length > 0) { const maxActors = Math.min(actors.length, 8); for (let i = 0; i < maxActors; i++) { actorsFormatted += actors[i]; if (i < maxActors - 1) { actorsFormatted += ' '; } if ((i + 1) % 2 === 0 && i < maxActors - 1) { actorsFormatted += '\n    '; } } actorsFormatted += '\n'; if (actors.length > 8) { actorsFormatted += `    等 ${actors.length} 位主演\n`; } } let imdbLink = ''; const imdbMatch = infoText.match(/IMDb:\s*([^\n]+)/); if (imdbMatch && imdbMatch[1]) { const imdbId = imdbMatch[1].trim(); if (imdbId.match(/tt\d+/)) { imdbLink = `https://www.imdb.com/title/${imdbId}/`; } } $status.text('正在获取海报...'); let posterUrl = ''; try { const wmdbPoster = await getMoviePosterFromWMDB(subjectId); if (wmdbPoster) { posterUrl = `https://images.weserv.nl/?url=${encodeURIComponent(wmdbPoster)}&w=${sizeConfig.width}&fit=inside&output=webp`; } } catch(e) { console.log('WMDB 海报获取失败:', e); } if (!posterUrl && TMDB_CONFIG.enabled && TMDB_CONFIG.apiKey) { try { const searchResult = await searchMovieOnTMDB(chineseTitle, year); if (searchResult) { const tmdbDetail = await getMovieDetailsFromTMDB(searchResult.id); if (tmdbDetail?.poster_path) { posterUrl = `https://images.weserv.nl/?url=${encodeURIComponent('https://image.tmdb.org/t/p/w500' + tmdbDetail.poster_path)}&w=${sizeConfig.width}&fit=inside&output=webp`; } } } catch(e) { console.log('TMDb 海报获取失败:', e); } } let description = getDescriptionFromPage(); if (!description || description.trim() === '') { $status.text('正在获取热门评论...'); const topComment = getTopRatedComment(); if (topComment) { description = topComment; } } if (description) { $status.text('正在翻译简介...'); description = await translateToChinese(description); } const data = { type: 'movie', imageType: '海报', posterUrl: posterUrl, title: chineseTitle, year: year, country: country, genre: genre, language: language, releaseDate: releaseDate, duration: duration, rating: rating, votes: votes, director: director, writer: writer, actors: actors, actorsFormatted: actorsFormatted, imdbLink: imdbLink, doubanLink: `https://movie.douban.com/subject/${subjectId}/`, description: description }; const md = currentStyle.template(data); $content.text(md); $preview.slideDown(300); $btn.text('📋 收缩预览'); $status.addClass('success').text(`✅ 生成成功 (${sizeConfig.name} · ${currentStyle.name})`); activeButton = 'btn1'; } catch(e) { $status.addClass('error').text('❌ 生成失败: ' + e.message); $btn.text('📋 重试'); } finally { $btn.prop('disabled', false); } }); // ==================== 按钮2:图床上传模式 ==================== $('#md-generator-btn2').click(async function() { const $btn = $(this); const $status = $('#tg-status'); const $preview = $('#md-preview'); const $content = $('#md-content'); if ($preview.is(':visible') && activeButton === 'btn2') { $preview.slideUp(300); $btn.text('📋 生成简介 · 图床上传'); activeButton = null; return; } $btn.prop('disabled', true).text('⏳ 上传中...'); $status.text('正在下载并处理图片...'); try { const sizeConfig = getCurrentImageSizeConfig(); const currentStyle = getCurrentStyle(); let doubanUrl = null; const img = document.querySelector('#mainpic > a > img'); if (img) { let url = img.getAttribute('src'); doubanUrl = url.replace(/photo\/[sl](_ratio_poster|pic)\/public\/(p\d+).+$/, "photo/l/public/$2.jpg"); if (doubanUrl === url) { doubanUrl = url.split('?')[0]; } } if (!doubanUrl) throw new Error('无法获取豆瓣原图'); const uploadResult = await uploader.upload(doubanUrl, sizeConfig.width); if (!uploadResult.success) throw new Error(uploadResult.error); $status.text('正在从豆瓣页面获取数据...'); const infoText = $('#info').text(); const chineseTitle = getChineseTitle(); let year = ''; const yearElement = $('#content .year').text().trim(); if (yearElement) { year = yearElement.replace(/[()]/g, ''); } let genre = ''; const genreElements = $('#info span[property="v:genre"]'); if (genreElements.length) { genre = genreElements.map(function() { return $(this).text(); }).get().join(' / '); } let country = ''; const countryMatch = infoText.match(/制片国家\/地区:\s*([^\n]+)/); if (countryMatch) { country = countryMatch[1].trim(); } let language = ''; const languageMatch = infoText.match(/语言:\s*([^\n]+)/); if (languageMatch) { language = languageMatch[1].trim(); } let releaseDate = ''; const dateElement = $('#info span[property="v:initialReleaseDate"]'); if (dateElement.length) { releaseDate = dateElement.text().trim(); } let duration = ''; const durationMatch = infoText.match(/片长:\s*([^\n]+)/); if (durationMatch) { duration = durationMatch[1].trim(); } let rating = ''; let votes = ''; const ratingElement = $('strong.ll.rating_num'); if (ratingElement.length) { rating = ratingElement.text().trim(); } const votesElement = $('a.rating_people span'); if (votesElement.length) { votes = votesElement.text().trim(); } let director = ''; const directorElements = $('#info a[rel="v:directedBy"]'); if (directorElements.length) { director = directorElements.map(function() { return $(this).text(); }).get().join(' / '); } let writer = ''; const writerMatch = infoText.match(/编剧:\s*([^\n]+)/); if (writerMatch) { writer = writerMatch[1].trim(); } let actors = []; $('#info .actor a, #info a[rel="v:starring"]').each(function() { const actorName = $(this).text().trim(); if (actorName && !actors.includes(actorName)) { actors.push(actorName); } }); let actorsFormatted = ''; if (actors.length > 0) { const maxActors = Math.min(actors.length, 8); for (let i = 0; i < maxActors; i++) { actorsFormatted += actors[i]; if (i < maxActors - 1) { actorsFormatted += ' '; } if ((i + 1) % 2 === 0 && i < maxActors - 1) { actorsFormatted += '\n    '; } } actorsFormatted += '\n'; if (actors.length > 8) { actorsFormatted += `    等 ${actors.length} 位主演\n`; } } let imdbLink = ''; const imdbMatch = infoText.match(/IMDb:\s*([^\n]+)/); if (imdbMatch && imdbMatch[1]) { const imdbId = imdbMatch[1].trim(); if (imdbId.match(/tt\d+/)) { imdbLink = `https://www.imdb.com/title/${imdbId}/`; } } let description = getDescriptionFromPage(); if (!description || description.trim() === '') { $status.text('正在获取热门评论...'); const topComment = getTopRatedComment(); if (topComment) { description = topComment; } } if (description) { $status.text('正在翻译简介...'); description = await translateToChinese(description); } const data = { type: 'movie', imageType: '海报', posterUrl: uploadResult.url, title: chineseTitle, year: year, country: country, genre: genre, language: language, releaseDate: releaseDate, duration: duration, rating: rating, votes: votes, director: director, writer: writer, actors: actors, actorsFormatted: actorsFormatted, imdbLink: imdbLink, doubanLink: `https://movie.douban.com/subject/${subjectId}/`, description: description }; const md = currentStyle.template(data); $content.text(md); $preview.slideDown(300); $btn.text('📋 收缩预览 2'); $status.text(`✅ 生成成功 (${sizeConfig.name} · ${currentStyle.name})`).addClass('success'); activeButton = 'btn2'; } catch(e) { $status.text(`❌ 失败: ${e.message}`).addClass('error'); $btn.text('📋 重试 2'); } finally { $btn.prop('disabled', false); } }); } // ==================== 图书生成器 ==================== function initBookGenerator() { const uploader = new ImageUploader(); $('#info').after(`
`); $('#md-copy-btn').click(copyToClipboard); $('#md-generator-btn').click(async function() { const $btn = $(this); const $status = $('#loading-status'); const $preview = $('#md-preview'); const $content = $('#md-content'); if ($preview.is(':visible')) { $preview.slideUp(300); $btn.text('📋 生成图书简介 · 图床上传'); return; } $btn.prop('disabled', true).text('⏳ 上传中...'); $status.text('正在获取图片...'); try { const sizeConfig = getCurrentImageSizeConfig(); const currentStyle = getCurrentStyle(); let doubanUrl = null; const anchor = document.querySelector('#mainpic > a.nbg'); if (anchor) { doubanUrl = anchor.getAttribute('href'); } if (!doubanUrl) throw new Error('无法获取豆瓣原图'); const uploadResult = await uploader.upload(doubanUrl, sizeConfig.width); if (!uploadResult.success) throw new Error(uploadResult.error); const infoText = $('#info').text(); const doubanId = window.location.href.match(/\d{7,8}/)?.[0] || ''; const data = { type: 'book', imageType: '封面', posterUrl: uploadResult.url, title: $('#wrapper > h1 > span').first().text().trim(), author: $('#info a[href*="search?cat=1003"]').map(function() { return $(this).text().trim(); }).get().join(' / '), publisher: (infoText.match(/出版社:\s*([^\n]+)/) || [])[1]?.trim() || '', series: (infoText.match(/丛书:\s*([^\n]+)/) || [])[1]?.trim() || '', isbn: (infoText.match(/ISBN:\s*([0-9X-]+)/) || [])[1]?.trim() || '', rating: $('strong.ll.rating_num').text().trim(), votes: $('a.rating_people span').text().trim(), doubanLink: `https://book.douban.com/subject/${doubanId}/`, description: '' }; let description = getDescriptionFromPage(); if (description) { $status.text('正在翻译简介...'); data.description = await translateToChinese(description); } const md = currentStyle.template(data); $content.text(md); $preview.slideDown(300); $btn.text('📋 收缩预览'); $status.text(`✅ 生成成功 (${sizeConfig.name} · ${currentStyle.name})`).addClass('success'); } catch(e) { $status.text(`❌ 失败: ${e.message}`).addClass('error'); $btn.text('📋 重试'); } finally { $btn.prop('disabled', false); } }); } // ==================== 音乐生成器 ==================== function initMusicGenerator() { const uploader = new ImageUploader(); $('#info').after(`
`); $('#md-copy-btn').click(copyToClipboard); $('#md-generator-btn').click(async function() { const $btn = $(this); const $status = $('#loading-status'); const $preview = $('#md-preview'); const $content = $('#md-content'); if ($preview.is(':visible')) { $preview.slideUp(300); $btn.text('📋 生成音乐简介 · 图床上传'); return; } $btn.prop('disabled', true).text('⏳ 上传中...'); $status.text('正在获取图片...'); try { const sizeConfig = getCurrentImageSizeConfig(); const currentStyle = getCurrentStyle(); const doubanId = window.location.href.match(/\d{7,8}/)?.[0] || ''; let doubanUrl = null; const anchor = document.querySelector('#mainpic > a.nbg'); if (anchor && anchor.href) { let url = anchor.href; if (url.includes('/m/')) { doubanUrl = url.replace('/m/', '/l/'); } else if (url.includes('/small/')) { doubanUrl = url.replace('/small/', '/large/'); } else { doubanUrl = url; } } if (!doubanUrl) { const img = document.querySelector('#mainpic > a > img') || document.querySelector('#mainpic img'); if (img && img.src) { let url = img.src.split('?')[0]; if (url.includes('/m/')) { doubanUrl = url.replace('/m/', '/l/'); } else if (url.includes('/small/')) { doubanUrl = url.replace('/small/', '/large/'); } else { doubanUrl = url; } } } if (!doubanUrl) { const viewLink = document.querySelector('#mainpic .view-original'); if (viewLink) { doubanUrl = viewLink.getAttribute('href'); } } if (!doubanUrl) throw new Error('无法获取豆瓣原图'); const uploadResult = await uploader.upload(doubanUrl, sizeConfig.width); if (!uploadResult.success) throw new Error(uploadResult.error); const infoText = $('#info').text(); let titleElement = $('#wrapper > h1 > span'); const data = { type: 'music', imageType: '封面', posterUrl: uploadResult.url, title: titleElement.length ? titleElement.first().text().trim() : $('#content h1').first().text().trim(), artist: $('#info a[href*="search?cat=1003"]').first().text().trim(), typeName: (infoText.match(/专辑类型:\s*([^\n]+)/) || [])[1]?.trim() || '', media: (infoText.match(/介质:\s*([^\n]+)/) || [])[1]?.trim() || '', date: (infoText.match(/发行时间:\s*([^\n]+)/) || [])[1]?.trim() || '', publisher: (infoText.match(/出版者:\s*([^\n]+)/) || [])[1]?.trim() || '', rating: $('strong.ll.rating_num').text().trim(), votes: $('a.rating_people span').text().trim(), doubanLink: `https://music.douban.com/subject/${doubanId}/`, tracks: [], description: '' }; $('.track-list li, #content .song-item').each(function() { const track = $(this).text().trim(); if (track) data.tracks.push(track); }); let description = getDescriptionFromPage(); if (description) { $status.text('正在翻译简介...'); data.description = await translateToChinese(description); } const md = currentStyle.template(data); $content.text(md); $preview.slideDown(300); $btn.text('📋 收缩预览'); $status.text(`✅ 生成成功 (${sizeConfig.name} · ${currentStyle.name})`).addClass('success'); } catch(e) { $status.text(`❌ 失败: ${e.message}`).addClass('error'); $btn.text('📋 重试'); } finally { $btn.prop('disabled', false); } }); } // ==================== 主入口 ==================== $(document).ready(function() { const pageType = location.host.split('.')[0]; const subjectId = location.href.match(/(\d{7,8})/); if (!subjectId) return; $("#db-global-nav > div > div.top-nav-info").append(`⚙️ 豆瓣侠设置`); $("#doudanxia-setting-btn").click(showSettingsPanel); let title = ''; if (pageType === 'music') { title = $('#wrapper > h1 > span').first().text().trim() || $('#content h1').first().text().trim(); } else if (pageType === 'book') { title = $('#wrapper > h1 > span').first().text().trim(); } else { title = $('#content h1 span').first().text().trim(); } initPanSearch(title); if (pageType === 'movie') { initMovieViewOriginal(); } else if (pageType === 'book') { initBookViewOriginal(); } else if (pageType === 'music') { initMusicViewOriginal(); } if (pageType === 'movie') { let imdb_anchor = $('#info span.pl:contains("IMDb")'); if (imdb_anchor.length > 0) { let imdb_text = imdb_anchor[0].nextSibling?.nodeValue; if (imdb_text) { let imdb_id = imdb_text.trim(); let imdb_link = `https://www.imdb.com/title/${imdb_id}/`; $(imdb_anchor[0].nextSibling).replaceWith(` ${imdb_id}`); } } } if (enableGenerator) { if (pageType === 'movie') { initMovieButtons(); } else if (pageType === 'book') { initBookGenerator(); } else if (pageType === 'music') { initMusicGenerator(); } } }); })();