Cmpedu Resource Downloader

机械工业出版社教育服务网资源下载,无需登录(不可用),无需教师权限,油猴脚本。

  1. // ==UserScript==
  2. // @name Cmpedu Resource Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0
  5. // @description 机械工业出版社教育服务网资源下载,无需登录(不可用),无需教师权限,油猴脚本。
  6. // @author yanyaoli
  7. // @match *://*.cmpedu.com/ziyuans/ziyuan/*
  8. // @match *://*.cmpedu.com/books/book/*
  9. // @connect *.cmpedu.com
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // 样式注入
  18. GM_addStyle(`
  19. .cmp-panel { position: fixed; top: 20px; right: 20px; width: 300px; max-height: 70vh; background: #ced6e0; border-radius: 12px; box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15); z-index: 99999; font-family: 'Segoe UI', system-ui, sans-serif; overflow: hidden; transition: transform 0.2s ease; }
  20. .panel-header { padding: 16px; background: #a4b0be; border-bottom: 1px solid #747d8c; display: flex; justify-content: space-between; align-items: center; }
  21. .panel-title { margin: 0; font-size: 16px; color: #1a1a1a; font-weight: 600; }
  22. .close-btn { background: none; border: none; cursor: pointer; color: #6b7280; font-size: 24px; line-height: 1; padding: 4px; transition: color 0.2s; }
  23. .close-btn:hover { color: #1a1a1a; }
  24. .panel-content { padding: 16px; max-height: calc(70vh - 73px); overflow-y: auto; }
  25. .resource-item { padding: 12px 0; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
  26. .resource-item:hover { color: #1e90ff; }
  27. .resource-item:last-child { border-bottom: none; }
  28. .skeleton {
  29. background: #f2f2f2;
  30. border-radius: 4px;
  31. height: 20px;
  32. width: 100%;
  33. margin-bottom: 12px;
  34. animation: pulse 1.5s infinite;
  35. }
  36. .skeleton:last-child { margin-bottom: 0; }
  37. .error-message { color: #dc3545; display: flex; align-items: center; gap: 8px; padding: 12px; background: #fff5f5; border-radius: 8px; margin: 8px 0; }
  38. @keyframes skeleton-loading {
  39. 0% { background-position: 200% 0; }
  40. 100% { background-position: -200% 0; }
  41. }
  42. @media (max-width: 480px) { .cmp-panel { width: 90%; right: 5%; left: auto; top: 10px; } }
  43. `);
  44.  
  45. // 基础配置
  46. const isMobile = window.location.host.startsWith('m.');
  47. const baseUrl = isMobile ? 'http://m.cmpedu.com' : 'http://www.cmpedu.com';
  48. const panelId = "downloadPanel";
  49.  
  50. function extractBookId() {
  51. if (window.location.href.includes('books/book')) {
  52. return window.location.pathname.split("/").pop().split(".")[0];
  53. }
  54. if (window.location.href.includes('ziyuans/ziyuan')) {
  55. const el = document.getElementById('BOOK_ID');
  56. return el ? el.value : null;
  57. }
  58. return null;
  59. }
  60.  
  61. function createPanel() {
  62. const panel = document.createElement('div');
  63. panel.id = panelId;
  64. panel.className = 'cmp-panel';
  65. panel.innerHTML = `
  66. <div class="panel-header">
  67. <h3 class="panel-title">资源下载</h3>
  68. <button class="close-btn" aria-label="关闭">×</button>
  69. </div>
  70. <div class="panel-content">
  71. <div class="skeleton"></div>
  72. <div class="skeleton"></div>
  73. <div class="skeleton"></div>
  74. </div>
  75. `;
  76. document.body.appendChild(panel);
  77. panel.querySelector('.close-btn').addEventListener('click', () => panel.remove());
  78. return panel;
  79. }
  80.  
  81. function updatePanelContent(panel, content) {
  82. const panelContent = panel.querySelector('.panel-content');
  83. panelContent.innerHTML = content;
  84. }
  85.  
  86. function createResourceItem(title) {
  87. return `
  88. <div class="resource-item">
  89. <strong style="flex: 1;">${title}</strong>
  90. </div>
  91. `;
  92. }
  93.  
  94. function updateResourceItem(panel, index, content, downloadLink) {
  95. const items = panel.querySelectorAll('.resource-item');
  96. if(items[index]) {
  97. const item = items[index];
  98. item.innerHTML = `<strong style="flex: 1;">${content}</strong>`;
  99. item.style.cursor = 'pointer';
  100. item.setAttribute('data-download-link', downloadLink);
  101. item.onclick = () => window.open(downloadLink, '_blank');
  102. } else {
  103. // If we don't have an existing item, append a new one
  104. const panelContent = panel.querySelector('.panel-content');
  105. const newItem = document.createElement('div');
  106. newItem.className = 'resource-item';
  107. newItem.innerHTML = `<strong style="flex: 1;">${content}</strong>`;
  108. newItem.style.cursor = 'pointer';
  109. newItem.setAttribute('data-download-link', downloadLink);
  110. newItem.onclick = () => window.open(downloadLink, '_blank');
  111. panelContent.appendChild(newItem);
  112. }
  113. }
  114.  
  115. function processResourceResponse(response, title) {
  116. const downloadLinks = response.responseText.match(/window\.location\.href=\'(https?:\/\/[^\'"]+)\'/);
  117. if (downloadLinks) {
  118. return [title, downloadLinks[1]];
  119. }
  120. return [null, null];
  121. }
  122.  
  123. // 主逻辑
  124. const bookId = extractBookId();
  125. if (!bookId) {
  126. console.error("无法提取 BOOK_ID");
  127. return;
  128. }
  129. const resourceUrl = `${baseUrl}/ziyuans/index.htm?BOOK_ID=${bookId}`;
  130. const panel = createPanel();
  131.  
  132. GM_xmlhttpRequest({
  133. method: "GET",
  134. url: resourceUrl,
  135. onload: function(response) {
  136. const parser = new DOMParser();
  137. const doc = parser.parseFromString(response.responseText, "text/html");
  138. const resourceDivs = doc.querySelectorAll("div.row.gjzy_list");
  139. const resources = Array.from(resourceDivs).map(div => {
  140. return {
  141. title: div.querySelector("div.gjzy_listRTit")?.textContent.trim() || "未知资源",
  142. resourceId: div.querySelector("a")?.href.split("/").pop().split(".")[0]
  143. };
  144. });
  145.  
  146. if (resources.length === 0) {
  147. updatePanelContent(panel, "<strong>未找到资源。</strong>");
  148. return;
  149. }
  150.  
  151. // Clear skeleton placeholders before adding real content
  152. updatePanelContent(panel, '');
  153.  
  154. resources.forEach(({ title, resourceId }, index) => {
  155. const downloadUrl = `${baseUrl}/ziyuans/d_ziyuan.df?id=${resourceId}`;
  156. GM_xmlhttpRequest({
  157. method: "GET",
  158. url: downloadUrl,
  159. headers: {
  160. "Accept-Encoding": "gzip, deflate",
  161. "Connection": "keep-alive",
  162. "Accept": "text/html, */*; q=0.01",
  163. "User-Agent": "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
  164. "Accept-Language": "en-US,en;q=0.9",
  165. "X-Requested-With": "XMLHttpRequest"
  166. },
  167. onload: function(response) {
  168. const [resourceTitle, downloadLink] = processResourceResponse(response, title);
  169. if (resourceTitle) {
  170. updateResourceItem(panel, index, resourceTitle, downloadLink);
  171. } else {
  172. updateResourceItem(panel, index, `${title} - 链接解析失败`, null);
  173. }
  174. },
  175. onerror: function() {
  176. updateResourceItem(panel, index, `${title} - 请求失败`, null);
  177. }
  178. });
  179. });
  180. },
  181. onerror: function() {
  182. updatePanelContent(panel, "<strong>获取资源页面失败。</strong>");
  183. }
  184. });
  185. })();

QingJ © 2025

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