// ==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 += `\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 += `\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 += `\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(`
📝 简介样式
选择生成简介的显示格式
🖼️ 海报尺寸
影响两个按钮的图片输出尺寸
📁 云盘搜索 (${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(`
云盘搜索· · · · · ·
${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();
}
}
});
})();