您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
CSDN 全能助手 - 支持VIP文章/文库解锁、资源下载直链获取
// ==UserScript== // @name CSDN Helper // @namespace https://github.com/minglu6/unlock-vip // @version 1.0.1 // @description CSDN 全能助手 - 支持VIP文章/文库解锁、资源下载直链获取 // @author minglu6 // @match https://blog.csdn.net/*/article/details/* // @match https://*.blog.csdn.net/article/details/* // @match https://wenku.csdn.net/answer/* // @match https://download.csdn.net/download/*/* // @grant GM_xmlhttpRequest // @icon https://g.csdnimg.cn/static/logo/favicon32.ico // @connect 175.24.164.85 // @license MIT // @run-at document-end // ==/UserScript== (function () { 'use strict'; const CONFIG = { apiBaseUrl: 'http://175.24.164.85/api', requestTimeout: 60000, enableLog: false, preferPreview: true, }; class APIClient { constructor(baseUrl) { this.baseUrl = baseUrl; } async request(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const headers = { 'Content-Type': 'application/json', ...options.headers }; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url: url, headers: headers, data: options.body ? JSON.stringify(options.body) : undefined, timeout: options.timeout || CONFIG.requestTimeout, onload: (response) => { try { if (response.status >= 200 && response.status < 300) { const data = JSON.parse(response.responseText); resolve(data); } else { const error = JSON.parse(response.responseText || '{}'); reject(new Error(error.detail || `请求失败 (${response.status})`)); } } catch (e) { reject(new Error(`解析响应失败: ${e.message}`)); } }, onerror: () => reject(new Error('网络请求失败')), ontimeout: () => reject(new Error('请求超时')) }); }); } async downloadArticle(url) { return await this.request('/article/download', { method: 'POST', body: { url }, timeout: CONFIG.requestTimeout }); } async getDownloadLink(url) { return await this.request('/file/get-download-link', { method: 'POST', body: { url } }); } } class LogPanel { constructor() { this.panel = null; this.logList = null; if (CONFIG.enableLog) { this.init(); } } init() { this.panel = document.createElement('div'); this.panel.id = 'csdn-unlock-log-panel'; this.panel.style.cssText = ` position: fixed !important; bottom: 20px !important; right: 20px !important; width: 400px !important; max-height: 500px !important; background: rgba(0, 0, 0, 0.92) !important; color: #fff !important; font-size: 13px !important; border-radius: 10px !important; box-shadow: 0 4px 20px rgba(0,0,0,0.3) !important; z-index: 2147483647 !important; overflow: hidden !important; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; `; const header = document.createElement('div'); header.style.cssText = ` display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 12px 16px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border-bottom: 1px solid rgba(255,255,255,0.1) !important; `; const title = document.createElement('span'); title.textContent = '🔓 CSDN 解锁日志'; title.style.fontWeight = 'bold'; const clearBtn = document.createElement('button'); clearBtn.textContent = '清空'; clearBtn.style.cssText = ` background: rgba(255,255,255,0.2) !important; border: none !important; color: #fff !important; padding: 4px 12px !important; border-radius: 5px !important; cursor: pointer !important; font-size: 12px !important; `; clearBtn.onmouseover = () => clearBtn.style.background = 'rgba(255,255,255,0.3)'; clearBtn.onmouseout = () => clearBtn.style.background = 'rgba(255,255,255,0.2)'; clearBtn.onclick = () => this.clear(); header.appendChild(title); header.appendChild(clearBtn); this.logList = document.createElement('div'); this.logList.style.cssText = ` padding: 12px !important; overflow-y: auto !important; max-height: 400px !important; `; this.panel.appendChild(header); this.panel.appendChild(this.logList); document.documentElement.appendChild(this.panel); } log(message, type = 'info') { if (!CONFIG.enableLog || !this.logList) return; const line = document.createElement('div'); line.style.cssText = ` padding: 6px 8px !important; margin-bottom: 4px !important; border-radius: 5px !important; font-size: 12px !important; line-height: 1.5 !important; `; const timestamp = new Date().toLocaleTimeString('zh-CN'); const icon = type === 'error' ? '❌' : type === 'success' ? '✅' : type === 'warning' ? '⚠️' : 'ℹ️'; const color = type === 'error' ? 'rgba(239, 68, 68, 0.2)' : type === 'success' ? 'rgba(34, 197, 94, 0.2)' : type === 'warning' ? 'rgba(234, 179, 8, 0.2)' : 'rgba(59, 130, 246, 0.2)'; line.style.background = color; line.innerHTML = `<span style="opacity: 0.7;">${timestamp}</span> ${icon} ${message}`; this.logList.appendChild(line); while (this.logList.childNodes.length > 100) { this.logList.removeChild(this.logList.firstChild); } this.logList.scrollTop = this.logList.scrollHeight; } clear() { if (this.logList) { this.logList.innerHTML = ''; } } } class ResultPanel { constructor() { this.overlay = null; this.iframe = null; this.init(); } init() { this.overlay = document.createElement('div'); this.overlay.style.cssText = ` position: fixed !important; inset: 0 !important; background: rgba(0, 0, 0, 0.85) !important; z-index: 2147483646 !important; display: none !important; align-items: center !important; justify-content: center !important; padding: 40px !important; `; const container = document.createElement('div'); container.style.cssText = ` width: min(1200px, 95vw) !important; height: min(90vh, 1200px) !important; background: #0f0f0f !important; border-radius: 12px !important; overflow: hidden !important; display: flex !important; flex-direction: column !important; box-shadow: 0 8px 40px rgba(0,0,0,0.5) !important; `; const header = document.createElement('div'); header.style.cssText = ` display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 14px 20px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: #fff !important; `; const title = document.createElement('span'); title.textContent = '🎉 解锁成功'; title.style.cssText = 'font-weight: bold; font-size: 16px;'; const actions = document.createElement('div'); actions.style.cssText = 'display: flex; gap: 10px;'; const openNewTab = document.createElement('a'); openNewTab.textContent = '新标签打开'; openNewTab.target = '_blank'; openNewTab.style.cssText = ` padding: 6px 14px !important; background: rgba(255,255,255,0.2) !important; color: #fff !important; text-decoration: none !important; border-radius: 6px !important; font-size: 13px !important; `; const closeBtn = document.createElement('button'); closeBtn.textContent = '关闭'; closeBtn.style.cssText = ` padding: 6px 14px !important; background: rgba(255,255,255,0.2) !important; color: #fff !important; border: none !important; border-radius: 6px !important; cursor: pointer !important; font-size: 13px !important; `; closeBtn.onclick = () => this.hide(); actions.appendChild(openNewTab); actions.appendChild(closeBtn); header.appendChild(title); header.appendChild(actions); this.iframe = document.createElement('iframe'); this.iframe.style.cssText = ` flex: 1 !important; border: none !important; background: #fff !important; `; this.iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-modals'); container.appendChild(header); container.appendChild(this.iframe); this.overlay.appendChild(container); document.documentElement.appendChild(this.overlay); this.openNewTabLink = openNewTab; } show(content, title = '解锁成功') { if (CONFIG.preferPreview) { this.iframe.srcdoc = content; this.overlay.style.display = 'flex'; const blob = new Blob([content], { type: 'text/html' }); const blobUrl = URL.createObjectURL(blob); this.openNewTabLink.href = blobUrl; } else { const blob = new Blob([content], { type: 'text/html' }); const blobUrl = URL.createObjectURL(blob); window.open(blobUrl, '_blank'); } } hide() { this.overlay.style.display = 'none'; this.iframe.srcdoc = ''; } } class UnlockController { constructor() { this.apiClient = new APIClient(CONFIG.apiBaseUrl); this.logger = new LogPanel(); this.resultPanel = new ResultPanel(); } async unlockArticle(url) { try { this.logger.log(`开始解锁: ${url}`, 'info'); this.logger.log('正在下载文章...', 'info'); const result = await this.apiClient.downloadArticle(url); if (result.success && result.content) { this.logger.log(`解锁成功: ${result.title || '未知标题'}`, 'success'); this.logger.log(`文件大小: ${(result.file_size / 1024).toFixed(2)} KB`, 'info'); this.resultPanel.show(result.content, result.title); return true; } else { throw new Error(result.error || '下载失败'); } } catch (error) { this.logger.log(`解锁失败: ${error.message}`, 'error'); throw error; } } async getDownloadLink(url) { try { this.logger.log(`获取下载链接: ${url}`, 'info'); const result = await this.apiClient.getDownloadLink(url); if (result.success && result.download_url) { this.logger.log('获取下载链接成功', 'success'); return result.download_url; } else { throw new Error(result.error || '获取下载链接失败'); } } catch (error) { this.logger.log(`获取下载链接失败: ${error.message}`, 'error'); throw error; } } } class UIInjector { constructor(controller) { this.controller = controller; } injectArticleButton() { const url = window.location.href; const vipSelectors = [ 'a.article-vip-box[href="https://mall.csdn.net/vip"]', '#vip-info-wrap.vip-info-wrap', '.info-header-text' ]; let vipElement = null; for (const selector of vipSelectors) { vipElement = document.querySelector(selector); if (vipElement) break; } if (!vipElement) { console.log('[CSDN Unlock] 未检测到VIP内容'); return; } const button = document.createElement('button'); button.textContent = '🔓 一键解锁'; button.style.cssText = ` padding: 8px 20px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: #fff !important; border: none !important; border-radius: 6px !important; cursor: pointer !important; font-size: 14px !important; font-weight: 500 !important; margin-left: 12px !important; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4) !important; transition: all 0.3s ease !important; `; button.onmouseover = () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.6)'; }; button.onmouseout = () => { button.style.transform = 'translateY(0)'; button.style.boxShadow = '0 2px 8px rgba(102, 126, 234, 0.4)'; }; let isProcessing = false; button.onclick = async () => { if (isProcessing) return; isProcessing = true; const originalText = button.textContent; button.textContent = '⏳ 解锁中...'; button.disabled = true; button.style.opacity = '0.7'; try { await this.controller.unlockArticle(url); button.textContent = '✅ 解锁成功'; } catch (error) { alert(`解锁失败:${error.message}`); button.textContent = originalText; } finally { setTimeout(() => { button.textContent = originalText; button.disabled = false; button.style.opacity = '1'; isProcessing = false; }, 2000); } }; this.insertButton(button, vipElement); } insertButton(button, vipElement) { const barContent = document.querySelector('.article-bar-top .bar-content'); if (barContent) { barContent.appendChild(button); return; } const dataDiv = document.querySelector('.data'); if (dataDiv) { dataDiv.appendChild(button); return; } if (vipElement.parentElement) { vipElement.parentElement.insertBefore(button, vipElement.nextSibling); } else { document.body.appendChild(button); } } injectDownloadButton() { if (document.getElementById('csdn-unlock-download-btn')) return; const downloadBtnContainer = document.querySelector('#downloadBtn'); if (!downloadBtnContainer) { console.log('[CSDN Helper] 未找到 #downloadBtn,尝试其他选择器...'); const selectors = [ '.download-btn', '.dl_download_box', '#download', '.resource_download', '.dl_download_link', 'main', 'body' ]; let targetElement = null; for (const selector of selectors) { targetElement = document.querySelector(selector); if (targetElement) break; } if (!targetElement) { console.log('[CSDN Helper] 未找到合适的插入位置,将创建固定按钮'); this.createFixedDownloadButton(); return; } this.createStandaloneButton(targetElement); return; } const button = document.createElement('button'); button.id = 'csdn-unlock-download-btn'; button.type = 'button'; button.className = 'el-button relative el-button--success el-button--medium'; button.style.cssText = ` margin-left: 12px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border-color: #667eea !important; transition: all 0.3s ease !important; `; const span = document.createElement('span'); span.textContent = '🔗 获取直链'; button.appendChild(span); button.onmouseover = () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.6)'; }; button.onmouseout = () => { button.style.transform = 'translateY(0)'; button.style.boxShadow = '0 2px 8px rgba(102, 126, 234, 0.4)'; }; button.onclick = async (e) => { e.preventDefault(); e.stopPropagation(); const url = window.location.href; const originalText = span.textContent; span.textContent = '⏳ 获取中...'; button.disabled = true; try { const downloadUrl = await this.controller.getDownloadLink(url); window.open(downloadUrl, '_blank'); span.textContent = '✅ 已打开'; } catch (error) { alert(`获取失败:${error.message}`); span.textContent = originalText; } finally { setTimeout(() => { span.textContent = originalText; button.disabled = false; }, 2000); } }; downloadBtnContainer.appendChild(button); console.log('[CSDN Helper] 下载按钮已注入到 #downloadBtn'); } createStandaloneButton(targetElement) { const button = document.createElement('button'); button.id = 'csdn-unlock-download-btn'; button.textContent = '🔗 获取直链'; button.style.cssText = ` padding: 10px 24px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: #fff !important; border: none !important; border-radius: 6px !important; cursor: pointer !important; font-size: 14px !important; font-weight: 500 !important; margin: 10px !important; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4) !important; transition: all 0.3s ease !important; `; button.onmouseover = () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.6)'; }; button.onmouseout = () => { button.style.transform = 'translateY(0)'; button.style.boxShadow = '0 2px 8px rgba(102, 126, 234, 0.4)'; }; button.onclick = async () => { const url = window.location.href; const originalText = button.textContent; button.textContent = '⏳ 获取中...'; button.disabled = true; try { const downloadUrl = await this.controller.getDownloadLink(url); window.open(downloadUrl, '_blank'); button.textContent = '✅ 已打开'; } catch (error) { alert(`获取失败:${error.message}`); button.textContent = originalText; } finally { setTimeout(() => { button.textContent = originalText; button.disabled = false; }, 2000); } }; if (targetElement.tagName === 'BODY' || targetElement.tagName === 'MAIN') { targetElement.insertBefore(button, targetElement.firstChild); } else { targetElement.parentElement.insertBefore(button, targetElement.nextSibling); } console.log('[CSDN Helper] 独立下载按钮已注入'); } createFixedDownloadButton() { const button = document.createElement('button'); button.id = 'csdn-unlock-download-btn'; button.textContent = '🔗 获取直链'; button.style.cssText = ` position: fixed !important; top: 100px !important; right: 20px !important; padding: 12px 24px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: #fff !important; border: none !important; border-radius: 8px !important; cursor: pointer !important; font-size: 14px !important; font-weight: 600 !important; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5) !important; z-index: 2147483646 !important; transition: all 0.3s ease !important; `; button.onmouseover = () => { button.style.transform = 'translateY(-2px) scale(1.05)'; button.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.7)'; }; button.onmouseout = () => { button.style.transform = 'translateY(0) scale(1)'; button.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.5)'; }; button.onclick = async () => { const url = window.location.href; const originalText = button.textContent; button.textContent = '⏳ 获取中...'; button.disabled = true; try { const downloadUrl = await this.controller.getDownloadLink(url); window.open(downloadUrl, '_blank'); button.textContent = '✅ 已打开'; } catch (error) { alert(`获取失败:${error.message}`); button.textContent = originalText; } finally { setTimeout(() => { button.textContent = originalText; button.disabled = false; }, 2000); } }; document.body.appendChild(button); console.log('[CSDN Helper] 固定下载按钮已创建'); } } function init() { console.log('[CSDN Helper] 初始化中...'); const controller = new UnlockController(); const injector = new UIInjector(controller); const hostname = window.location.hostname; function tryInject(retryCount = 0) { if (hostname.includes('blog.csdn.net') || hostname.includes('wenku.csdn.net')) { injector.injectArticleButton(); } else if (hostname.includes('download.csdn.net')) { injector.injectDownloadButton(); if (!document.getElementById('csdn-unlock-download-btn') && retryCount < 5) { console.log(`[CSDN Helper] 按钮注入失败,${500}ms 后重试 (${retryCount + 1}/5)`); setTimeout(() => tryInject(retryCount + 1), 500); } } } tryInject(); console.log('[CSDN Helper] 初始化完成'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();