豆瓣电影网盘资源

在豆瓣电影页面显示网盘资源按钮,点击可搜索该影片的网盘资源。支持百度网盘、夸克网盘、迅雷网盘等多个网盘平台。

  1. // ==UserScript==
  2. // @name 豆瓣电影网盘资源
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.4
  5. // @description 在豆瓣电影页面显示网盘资源按钮,点击可搜索该影片的网盘资源。支持百度网盘、夸克网盘、迅雷网盘等多个网盘平台。
  6. // @author aipan.me
  7. // @match https://movie.douban.com/*
  8. // @grant GM_addStyle
  9. // @grant GM_xmlhttpRequest
  10. // @connect www.aipan.me
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. // 注入样式
  15. GM_addStyle(`
  16. .netdisk-btn {
  17. margin-left: 10px;
  18. padding: 0;
  19. border: 1px solid #6648ff;
  20. border-radius: 20px;
  21. color: white;
  22. cursor: pointer;
  23. width: 24px;
  24. height: 24px;
  25. display: inline-flex;
  26. align-items: center;
  27. justify-content: center;
  28. vertical-align: middle;
  29. }
  30.  
  31. .netdisk-btn:hover {
  32. background-color: #6648ff;
  33. }
  34.  
  35. .netdisk-btn img {
  36. width: 24px;
  37. height: 24px;
  38. display: block;
  39. }
  40.  
  41. .netdisk-modal {
  42. position: fixed;
  43. top: 50%;
  44. left: 50%;
  45. transform: translate(-50%, -50%);
  46. background: white;
  47. padding: 25px;
  48. border-radius: 4px;
  49. box-shadow: 0 2px 12px rgba(0,0,0,0.1);
  50. z-index: 10000;
  51. min-width: 400px;
  52. max-width: 90vw;
  53. max-height: 85vh;
  54. min-height: 300px;
  55. overflow-y: auto;
  56. font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  57. }
  58.  
  59. .netdisk-modal-header {
  60. display: flex;
  61. align-items: center;
  62. justify-content: space-between;
  63. margin-bottom: 20px;
  64. padding-bottom: 10px;
  65. border-bottom: 1px solid #eee;
  66. }
  67.  
  68. .netdisk-modal h3 {
  69. margin: 0;
  70. font-size: 16px;
  71. color: #494949;
  72. font-weight: normal;
  73. }
  74.  
  75. .netdisk-modal .close {
  76. padding: 0;
  77. background: none;
  78. border: none;
  79. color: #999;
  80. cursor: pointer;
  81. font-size: 20px;
  82. line-height: 1;
  83. transition: color 0.2s;
  84. }
  85.  
  86. .netdisk-modal .close:hover {
  87. color: #666;
  88. }
  89.  
  90. .content{
  91. width: 100%;
  92. }
  93.  
  94. .netdisk-links {
  95. margin: 0;
  96. padding: 0;
  97. list-style: none;
  98. }
  99.  
  100. .netdisk-links li {
  101. margin: 8px 0;
  102. padding: 12px;
  103. border: 1px solid #eee;
  104. border-radius: 3px;
  105. transition: background-color 0.2s;
  106. border-radius: 8px;
  107. }
  108.  
  109. .netdisk-links li:hover {
  110. background: #f9f9f9;
  111. }
  112.  
  113. .netdisk-links .link-name {
  114. font-size: 12px;
  115. color: #494949;
  116. }
  117.  
  118. .netdisk-links .service-link {
  119. display: flex;
  120. align-items: center;
  121. text-decoration: none;
  122. color: #3377aa;
  123. font-size: 13px;
  124. }
  125.  
  126. .netdisk-links .service-icon {
  127. width: 24px;
  128. height: 24px;
  129. border-radius: 2px;
  130. }
  131.  
  132. .netdisk-links .pwd-container {
  133. display: flex;
  134. align-items: center;
  135. gap: 8px;
  136. }
  137.  
  138. .netdisk-links .pwd {
  139. color: #666;
  140. font-size: 13px;
  141. background: #f5f5f5;
  142. padding: 3px 6px;
  143. border-radius: 2px;
  144. user-select: all;
  145. font-family: Menlo, Monaco, Consolas, monospace;
  146. }
  147.  
  148. .netdisk-links .copy-btn {
  149. padding: 3px 8px;
  150. font-size: 12px;
  151. color: #3377aa;
  152. background: none;
  153. border: 1px solid #3377aa;
  154. border-radius: 2px;
  155. cursor: pointer;
  156. transition: all 0.2s;
  157. }
  158.  
  159. .netdisk-links .copy-btn:hover {
  160. background: #3377aa;
  161. color: white;
  162. }
  163.  
  164. .netdisk-links .copy-btn.copied {
  165. background: #5c9a4f;
  166. border-color: #5c9a4f;
  167. color: white;
  168. }
  169.  
  170. .loading-container {
  171. display: flex;
  172. flex-direction: column;
  173. align-items: center;
  174. justify-content: center;
  175. padding: 30px 20px;
  176. min-height: 150px;
  177. width: 100%;
  178. }
  179.  
  180. .loading-text {
  181. color: #666;
  182. font-size: 13px;
  183. }
  184.  
  185. .loading-dots::after {
  186. content: '';
  187. animation: dots 1.4s steps(4, end) infinite;
  188. }
  189.  
  190. @keyframes dots {
  191. 0%, 20% { content: ''; }
  192. 40% { content: '.'; }
  193. 60% { content: '..'; }
  194. 80%, 100% { content: '...'; }
  195. }
  196.  
  197. .error {
  198. color: #ca4a4a;
  199. font-size: 13px;
  200. text-align: center;
  201. }
  202.  
  203. .close-error-btn {
  204. margin-top: 15px;
  205. padding: 6px 15px;
  206. border: 1px solid #ddd;
  207. border-radius: 3px;
  208. background: #f5f5f5;
  209. color: #666;
  210. cursor: pointer;
  211. font-size: 13px;
  212. transition: all 0.2s;
  213. }
  214.  
  215. .close-error-btn:hover {
  216. background: #eee;
  217. color: #333;
  218. }
  219.  
  220. .netdisk-links .link-info-container{
  221. display: flex;
  222. align-items: center;
  223. justify-content: center;
  224. gap: 15px;
  225. margin-top: 10px;
  226. }
  227.  
  228. .netdisk-links .link-info-container .link-info{
  229. display: flex;
  230. align-items: center;
  231. justify-content: center;
  232. gap: 15px;
  233. }
  234. `);
  235.  
  236. // 添加禁用滚动的函数
  237. function disableScroll() {
  238. document.body.style.overflow = 'hidden';
  239. document.body.style.paddingRight = '6px';
  240. }
  241.  
  242. // 添加恢复滚动的函数
  243. function enableScroll() {
  244. document.body.style.overflow = '';
  245. document.body.style.paddingRight = '';
  246. }
  247.  
  248. // API 请求函数
  249. async function searchNetDisk(movieName, onResultsUpdate) {
  250. const apiEndpoints = [
  251. "https://www.aipan.me/api/sources/aipan-search",
  252. "https://www.aipan.me/api/sources/x",
  253. "https://www.aipan.me/api/sources/xx",
  254. "https://www.aipan.me/api/sources/indexI",
  255. "https://www.aipan.me/api/sources/search-c",
  256. "https://www.aipan.me/api/sources/xxx",
  257. "https://www.aipan.me/api/sources/xxxx",
  258. ];
  259.  
  260. // 创建一个数组来存储所有的请求Promise
  261. const requests = apiEndpoints.map(endpoint => {
  262. return new Promise(async (resolve) => {
  263. try {
  264. const response = await new Promise((innerResolve, innerReject) => {
  265. GM_xmlhttpRequest({
  266. method: 'POST',
  267. url: endpoint,
  268. headers: {
  269. 'Content-Type': 'application/json'
  270. },
  271. data: JSON.stringify({ name: movieName }),
  272. onload: (response) => innerResolve(JSON.parse(response.responseText)),
  273. onerror: innerReject
  274. });
  275. });
  276. if (response.list && response.list.length > 0) {
  277. onResultsUpdate(response.list); // 每当有新结果就调用回调
  278. }
  279. } catch (error) {
  280. console.error(`API ${endpoint} 请求失败:`, error);
  281. }
  282. resolve(); // 无论成功失败都resolve,这样不会阻塞其他请求
  283. });
  284. });
  285. // 等待所有请求完成
  286. await Promise.all(requests);
  287. }
  288.  
  289. // 创建弹框函数
  290. function createModal(movieName) {
  291. const modal = document.createElement('div');
  292. modal.className = 'netdisk-modal';
  293. modal.innerHTML = `
  294. <div class="netdisk-modal-header">
  295. <h3>${movieName} - 网盘资源</h3>
  296. <button class="close">×</button>
  297. </div>
  298. <div class="content loading">
  299. <div class="loading-container">
  300. <div class="loading-text">
  301. 搜索资源中<span class="loading-dots"></span>
  302. </div>
  303. </div>
  304. </div>
  305. `;
  306. // 禁用背景滚动
  307. disableScroll();
  308. // 关闭按钮
  309. modal.querySelector('.close').addEventListener('click', () => {
  310. enableScroll();
  311. modal.remove();
  312. });
  313. // ESC 键关闭
  314. const escHandler = (e) => {
  315. if (e.key === 'Escape') {
  316. enableScroll();
  317. modal.remove();
  318. document.removeEventListener('keydown', escHandler);
  319. }
  320. };
  321. document.addEventListener('keydown', escHandler);
  322. // 点击外部关闭
  323. modal.addEventListener('click', (e) => {
  324. if (e.target === modal) {
  325. enableScroll();
  326. modal.remove();
  327. }
  328. });
  329. document.body.appendChild(modal);
  330. return modal;
  331. }
  332.  
  333. // 渲染结果函数
  334. function renderResults(modal, results) {
  335. const content = modal.querySelector('.content');
  336. // 如果是第一次渲染,清除loading状态
  337. if (content.classList.contains('loading')) {
  338. content.classList.remove('loading');
  339. content.innerHTML = ''; // 清空loading内容
  340. }
  341. const validResults = results.filter(result => result.links && result.links.length > 0);
  342. if (validResults.length === 0) {
  343. return; // 如果没有有效结果,直接返回,保持现有内容
  344. }
  345. const linksHtml = validResults.map(result => {
  346. const linksHtml = result.links.map(link => {
  347. // 根据链接判断网盘类型
  348. let service = link.service;
  349. try {
  350. if (link.link && typeof link.link === 'string') {
  351. const urlString = link.link.startsWith('http') ? link.link : `https://${link.link}`;
  352. const url = new URL(urlString);
  353. const hostname = url.hostname.toLowerCase();
  354. if (hostname.includes('baidu')) {
  355. service = 'BAIDU';
  356. } else if (hostname.includes('quark')) {
  357. service = 'QUARK';
  358. } else if (hostname.includes('xunlei')) {
  359. service = 'XUNLEI';
  360. } else if (hostname.includes('aliyun')) {
  361. service = 'ALIYUN';
  362. } else if (hostname.includes('uc')) {
  363. service = 'UC';
  364. } else if (hostname.includes('alipan')) {
  365. service = "ALIYUN";
  366. } else if (hostname.includes('cloud.189.cn')) {
  367. service = "189";
  368. }
  369. }
  370. } catch (error) {
  371. console.warn('Invalid URL:', link.link, error);
  372. }
  373.  
  374. // 网盘图标
  375. const iconUrl =
  376. {
  377. BAIDU:
  378. "https://nd-static.bdstatic.com/m-static/v20-main/favicon-main.ico",
  379. QUARK:
  380. "https://gw.alicdn.com/imgextra/i3/O1CN018r2tKf28YP7ev0fPF_!!6000000007944-2-tps-48-48.png",
  381. XUNLEI: "https://www.xunlei.com/favicon.ico",
  382. UC: "https://image.uc.cn/s/uae/g/61/uc-logo-v2.png",
  383. ALIYUN:
  384. "https://img.alicdn.com/imgextra/i2/O1CN01DOYcs71v3B6bOemVM_!!6000000006116-2-tps-512-512.png",
  385. 189: "https://cloud.189.cn/web/logo.ico",
  386. }[service] || "";
  387. // 修改提取码显示
  388. const pwdHtml = link.pwd ? `
  389. <div class="pwd-container">
  390. <span class="pwd">提取码:${link.pwd}</span>
  391. <button class="copy-btn" data-pwd="${link.pwd}">复制</button>
  392. </div>
  393. ` : '';
  394. return `
  395. <div class="link-info">
  396. <a href="${link.link}" target="_blank" class="service-link">
  397. <img src="${iconUrl}" class="service-icon" onerror="this.style.display='none'">
  398. </a>
  399. ${pwdHtml}
  400. </div>`;
  401. }).join('');
  402. return `<ul class="netdisk-links"><li><div class="link-name">${result.name}</div><div class="link-info-container">${linksHtml}</div></li></ul>`;
  403. }).join('');
  404. // 将新结果添加到现有内容中
  405. content.insertAdjacentHTML('beforeend', linksHtml);
  406. // 添加复制按钮功能
  407. content.querySelectorAll('.copy-btn').forEach(btn => {
  408. // 检查按钮是否已经添加了事件监听器
  409. if (!btn.hasAttribute('data-has-click-listener')) {
  410. btn.setAttribute('data-has-click-listener', 'true');
  411. btn.addEventListener('click', () => {
  412. const pwd = btn.dataset.pwd;
  413. navigator.clipboard.writeText(pwd).then(() => {
  414. const originalText = btn.textContent;
  415. btn.textContent = '已复制';
  416. btn.classList.add('copied');
  417. // 移除其他按钮的 copied 类
  418. content.querySelectorAll('.copy-btn').forEach(otherBtn => {
  419. if (otherBtn !== btn) {
  420. otherBtn.classList.remove('copied');
  421. otherBtn.textContent = '复制';
  422. }
  423. });
  424. setTimeout(() => {
  425. btn.textContent = originalText;
  426. btn.classList.remove('copied');
  427. }, 2000);
  428. });
  429. });
  430. }
  431. });
  432. }
  433.  
  434. // 错误处理函数
  435. function showError(modal, message) {
  436. const content = modal.querySelector('.content');
  437. content.classList.remove('loading');
  438. content.innerHTML = `
  439. <div class="loading-container">
  440. <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#ca4a4a" stroke-width="2">
  441. <circle cx="12" cy="12" r="10"/>
  442. <line x1="15" y1="9" x2="9" y2="15"/>
  443. <line x1="9" y1="9" x2="15" y2="15"/>
  444. </svg>
  445. <div class="error">${message}</div>
  446. </div>
  447. `;
  448. // 添加一个关闭按钮
  449. const closeBtn = document.createElement('button');
  450. closeBtn.className = 'close-error-btn';
  451. closeBtn.textContent = '关闭';
  452. closeBtn.onclick = () => {
  453. enableScroll();
  454. modal.remove();
  455. };
  456. content.querySelector('.loading-container').appendChild(closeBtn);
  457. }
  458.  
  459. // 添加网盘资源按钮函数
  460. function addNetDiskLinks() {
  461. console.log('开始查找电影标题');
  462. const movieSelectors = [
  463. '.screening-bd .title a',
  464. '.gaia-movie .item .title a',
  465. '.gaia-tv .item .title a',
  466. 'h1 span[property="v:itemreviewed"]',
  467. ];
  468. let movieElements = [];
  469. movieSelectors.forEach(selector => {
  470. const elements = document.querySelectorAll(selector);
  471. console.log(`使用选择器 ${selector} 找到 ${elements.length} 个元素`);
  472. movieElements = [...movieElements, ...elements];
  473. });
  474. console.log('总共找到电影元素:', movieElements.length);
  475. movieElements.forEach(titleElement => {
  476. const parentElement = titleElement.closest('h1') || titleElement.parentNode;
  477. if (parentElement.querySelector('.netdisk-btn')) {
  478. return;
  479. }
  480. const movieName = titleElement.textContent.trim();
  481. console.log('处理电影:', movieName);
  482. const resourceButton = document.createElement('button');
  483. resourceButton.className = 'netdisk-btn';
  484. // 创建图标
  485. const icon = document.createElement('img');
  486. icon.src = 'https://www.aipan.me/favicon.ico'; // 这里放你的图标base64
  487. icon.alt = '网盘资源';
  488. resourceButton.appendChild(icon);
  489. if (titleElement.matches('h1 span[property="v:itemreviewed"]')) {
  490. resourceButton.style.marginLeft = '10px';
  491. resourceButton.style.width = '24px';
  492. resourceButton.style.height = '24px';
  493. }
  494. resourceButton.addEventListener('click', async () => {
  495. const modal = createModal(movieName);
  496. try {
  497. // 修改为使用回调函数来更新结果
  498. let hasResults = false;
  499. await searchNetDisk(movieName, (newResults) => {
  500. hasResults = true;
  501. renderResults(modal, newResults);
  502. });
  503. // 如果所有API都完成了但没有任何结果,显示无结果消息
  504. if (!hasResults) {
  505. showError(modal, '暂无可用资源,请稍后再试');
  506. }
  507. } catch (error) {
  508. console.error('搜索失败:', error);
  509. showError(modal, '搜索失败,请稍后重试');
  510. }
  511. });
  512. parentElement.appendChild(resourceButton);
  513. });
  514. }
  515.  
  516. // 初始化脚本
  517. (function() {
  518. 'use strict';
  519. // 创建观察器
  520. const observer = new MutationObserver(() => {
  521. addNetDiskLinks();
  522. });
  523. // 初始化
  524. try {
  525. addNetDiskLinks();
  526. observer.observe(document.body, {
  527. childList: true,
  528. subtree: true
  529. });
  530. window.addEventListener('load', addNetDiskLinks);
  531. } catch (error) {
  532. console.error('插件运行出错:', error);
  533. }
  534. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址