// ==UserScript==
// @name 豆瓣电影网盘资源
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @description 在豆瓣电影页面显示网盘资源按钮,点击可搜索该影片的网盘资源。支持百度网盘、夸克网盘、迅雷网盘等多个网盘平台。
// @author 你的名字
// @match https://movie.douban.com/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect www.aipan.me
// @license MIT
// ==/UserScript==
// 注入样式
GM_addStyle(`
.netdisk-btn {
margin-left: 10px;
padding: 0;
border: 1px solid #6648ff;
border-radius: 20px;
color: white;
cursor: pointer;
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
}
.netdisk-btn:hover {
background-color: #6648ff;
}
.netdisk-btn img {
width: 24px;
height: 24px;
display: block;
}
.netdisk-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 25px;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
z-index: 10000;
min-width: 400px;
max-width: 90vw;
max-height: 85vh;
min-height: 300px;
overflow-y: auto;
font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
.netdisk-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.netdisk-modal h3 {
margin: 0;
font-size: 16px;
color: #494949;
font-weight: normal;
}
.netdisk-modal .close {
padding: 0;
background: none;
border: none;
color: #999;
cursor: pointer;
font-size: 20px;
line-height: 1;
transition: color 0.2s;
}
.netdisk-modal .close:hover {
color: #666;
}
.content{
width: 100%;
}
.netdisk-links {
margin: 0;
padding: 0;
list-style: none;
}
.netdisk-links li {
margin: 8px 0;
padding: 12px;
border: 1px solid #eee;
border-radius: 3px;
transition: background-color 0.2s;
border-radius: 8px;
}
.netdisk-links li:hover {
background: #f9f9f9;
}
.netdisk-links .link-name {
font-size: 12px;
color: #494949;
}
.netdisk-links .service-link {
display: flex;
align-items: center;
text-decoration: none;
color: #3377aa;
font-size: 13px;
}
.netdisk-links .service-icon {
width: 24px;
height: 24px;
border-radius: 2px;
}
.netdisk-links .pwd-container {
display: flex;
align-items: center;
gap: 8px;
}
.netdisk-links .pwd {
color: #666;
font-size: 13px;
background: #f5f5f5;
padding: 3px 6px;
border-radius: 2px;
user-select: all;
font-family: Menlo, Monaco, Consolas, monospace;
}
.netdisk-links .copy-btn {
padding: 3px 8px;
font-size: 12px;
color: #3377aa;
background: none;
border: 1px solid #3377aa;
border-radius: 2px;
cursor: pointer;
transition: all 0.2s;
}
.netdisk-links .copy-btn:hover {
background: #3377aa;
color: white;
}
.netdisk-links .copy-btn.copied {
background: #5c9a4f;
border-color: #5c9a4f;
color: white;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 30px 20px;
min-height: 150px;
width: 100%;
}
.loading-text {
color: #666;
font-size: 13px;
}
.loading-dots::after {
content: '';
animation: dots 1.4s steps(4, end) infinite;
}
@keyframes dots {
0%, 20% { content: ''; }
40% { content: '.'; }
60% { content: '..'; }
80%, 100% { content: '...'; }
}
.error {
color: #ca4a4a;
font-size: 13px;
text-align: center;
}
.close-error-btn {
margin-top: 15px;
padding: 6px 15px;
border: 1px solid #ddd;
border-radius: 3px;
background: #f5f5f5;
color: #666;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.close-error-btn:hover {
background: #eee;
color: #333;
}
.netdisk-links .link-info-container{
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin-top: 10px;
}
.netdisk-links .link-info-container .link-info{
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
}
`);
// 添加禁用滚动的函数
function disableScroll() {
document.body.style.overflow = 'hidden';
document.body.style.paddingRight = '6px';
}
// 添加恢复滚动的函数
function enableScroll() {
document.body.style.overflow = '';
document.body.style.paddingRight = '';
}
// API 请求函数
async function searchNetDisk(movieName) {
const apiEndpoints = [
"https://www.aipan.me/api/sources/aipan-search",
"https://www.aipan.me/api/sources/x",
"https://www.aipan.me/api/sources/xx",
"https://www.aipan.me/api/sources/indexI",
"https://www.aipan.me/api/sources/search-c",
"https://www.aipan.me/api/sources/xxx",
"https://www.aipan.me/api/sources/xxxx",
];
const results = [];
for (const endpoint of apiEndpoints) {
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: endpoint,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({ name: movieName }),
onload: (response) => resolve(JSON.parse(response.responseText)),
onerror: reject
});
});
if (response.code === 200 && response.list) {
results.push(...response.list);
}
} catch (error) {
console.error(`API ${endpoint} 请求失败:`, error);
}
}
return results;
}
// 创建弹框函数
function createModal(movieName) {
const modal = document.createElement('div');
modal.className = 'netdisk-modal';
modal.innerHTML = `
<div class="netdisk-modal-header">
<h3>${movieName} - 网盘资源</h3>
<button class="close">×</button>
</div>
<div class="content loading">
<div class="loading-container">
<div class="loading-text">
搜索资源中<span class="loading-dots"></span>
</div>
</div>
</div>
`;
// 禁用背景滚动
disableScroll();
// 关闭按钮
modal.querySelector('.close').addEventListener('click', () => {
enableScroll();
modal.remove();
});
// ESC 键关闭
const escHandler = (e) => {
if (e.key === 'Escape') {
enableScroll();
modal.remove();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
// 点击外部关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
enableScroll();
modal.remove();
}
});
document.body.appendChild(modal);
return modal;
}
// 渲染结果函数
function renderResults(modal, results) {
const content = modal.querySelector('.content');
content.classList.remove('loading');
const validResults = results.filter(result => result.links && result.links.length > 0);
if (validResults.length === 0) {
content.innerHTML = `
<div class="loading-container">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#ca4a4a" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>
<div class="error">暂无可用资源,请稍后再试</div>
</div>
`;
return;
}
const linksHtml = validResults.map(result => {
const linksHtml = result.links.map(link => {
// 根据链接判断网盘类型
let service = link.service;
try {
if (link.link && typeof link.link === 'string') {
const urlString = link.link.startsWith('http') ? link.link : `https://${link.link}`;
const url = new URL(urlString);
const hostname = url.hostname.toLowerCase();
if (hostname.includes('baidu')) {
service = 'BAIDU';
} else if (hostname.includes('quark')) {
service = 'QUARK';
} else if (hostname.includes('xunlei')) {
service = 'XUNLEI';
} else if (hostname.includes('aliyun')) {
service = 'ALIYUN';
} else if (hostname.includes('uc')) {
service = 'UC';
} else if (hostname.includes('alipan')) {
service = "ALIYUN";
} else if (hostname.includes('cloud.189.cn')) {
service = "189";
}
}
} catch (error) {
console.warn('Invalid URL:', link.link, error);
}
// 网盘图标
const iconUrl = {
'BAIDU': 'https://nd-static.bdstatic.com/m-static/v20-main/favicon-main.ico',
'QUARK': 'https://pc-index.quark.cn/favicon.ico',
'XUNLEI': 'https://www.xunlei.com/favicon.ico',
'UC': 'https://www.uc.cn/favicon.ico',
'ALIYUN': 'https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico',
'189': 'https://cloud.189.cn/favicon.ico'
}[service] || '';
// 修改提取码显示
const pwdHtml = link.pwd ? `
<div class="pwd-container">
<span class="pwd">提取码:${link.pwd}</span>
<button class="copy-btn" data-pwd="${link.pwd}">复制</button>
</div>
` : '';
return `
<div class="link-info">
<a href="${link.link}" target="_blank" class="service-link">
<img src="${iconUrl}" class="service-icon" onerror="this.style.display='none'">
</a>
${pwdHtml}
</div>`;
}).join('');
return `<ul class="netdisk-links"><li><div class="link-name">${result.name}</div><div class="link-info-container">${linksHtml}</div></li></ul>`;
}).join('');
content.innerHTML = linksHtml;
// 添加复制按钮功能
content.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => {
const pwd = btn.dataset.pwd;
navigator.clipboard.writeText(pwd).then(() => {
const originalText = btn.textContent;
btn.textContent = '已复制';
btn.classList.add('copied');
// 移除其他按钮的 copied 类
content.querySelectorAll('.copy-btn').forEach(otherBtn => {
if (otherBtn !== btn) {
otherBtn.classList.remove('copied');
otherBtn.textContent = '复制';
}
});
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('copied');
}, 2000);
});
});
});
}
// 错误处理函数
function showError(modal, message) {
const content = modal.querySelector('.content');
content.classList.remove('loading');
content.innerHTML = `
<div class="loading-container">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#ca4a4a" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>
<div class="error">${message}</div>
</div>
`;
// 添加一个关闭按钮
const closeBtn = document.createElement('button');
closeBtn.className = 'close-error-btn';
closeBtn.textContent = '关闭';
closeBtn.onclick = () => {
enableScroll();
modal.remove();
};
content.querySelector('.loading-container').appendChild(closeBtn);
}
// 添加网盘资源按钮函数
function addNetDiskLinks() {
console.log('开始查找电影标题');
const movieSelectors = [
'.screening-bd .title a',
'.gaia-movie .item .title a',
'.gaia-tv .item .title a',
'h1 span[property="v:itemreviewed"]',
];
let movieElements = [];
movieSelectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
console.log(`使用选择器 ${selector} 找到 ${elements.length} 个元素`);
movieElements = [...movieElements, ...elements];
});
console.log('总共找到电影元素:', movieElements.length);
movieElements.forEach(titleElement => {
const parentElement = titleElement.closest('h1') || titleElement.parentNode;
if (parentElement.querySelector('.netdisk-btn')) {
return;
}
const movieName = titleElement.textContent.trim();
console.log('处理电影:', movieName);
const resourceButton = document.createElement('button');
resourceButton.className = 'netdisk-btn';
// 创建图标
const icon = document.createElement('img');
icon.src = 'data:image/png;base64,...'; // 这里放你的图标base64
icon.alt = '网盘资源';
resourceButton.appendChild(icon);
if (titleElement.matches('h1 span[property="v:itemreviewed"]')) {
resourceButton.style.marginLeft = '10px';
resourceButton.style.width = '24px';
resourceButton.style.height = '24px';
}
resourceButton.addEventListener('click', async () => {
const modal = createModal(movieName);
try {
const results = await searchNetDisk(movieName);
renderResults(modal, results);
} catch (error) {
console.error('搜索失败:', error);
showError(modal, '搜索失败,请稍后重试');
}
});
parentElement.appendChild(resourceButton);
});
}
// 初始化脚本
(function() {
'use strict';
// 创建观察器
const observer = new MutationObserver(() => {
addNetDiskLinks();
});
// 初始化
try {
addNetDiskLinks();
observer.observe(document.body, {
childList: true,
subtree: true
});
window.addEventListener('load', addNetDiskLinks);
} catch (error) {
console.error('插件运行出错:', error);
}
})();