// ==UserScript== // @name 自动登录CSDN // @namespace https://csdn-download.local // @version 1.5.0 // @description CSDN下载页辅助:移除登录弹窗/遮罩、提取真实文件地址;未登录时后台API静默登录(API变动自动发现新地址),失败再跳登录页自动填账密 // @author 自写脚本 // @match *://download.csdn.net/* // @match *://blog.csdn.net/*/article/details/* // @match *://passport.csdn.net/* // @run-at document-start // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @connect passport.csdn.net // @connect csdnimg.cn // @license MIT // @updateURL https://raw.githubusercontent.com/690743321/my-userscripts/main/csdn-autologin_latest.user.js // @downloadURL https://raw.githubusercontent.com/690743321/my-userscripts/main/csdn-autologin_latest.user.js // ==/UserScript== (function () { 'use strict'; var VERSION = '1.5.0'; /* ============================================================ * 账号配置:不内置账密,需通过脚本菜单「设置账号密码」自行填写 * ============================================================ */ function getAccount() { var u = '', p = ''; try { if (typeof GM_getValue === 'function') { u = GM_getValue('csdn_user', ''); p = GM_getValue('csdn_pwd', ''); } } catch (e) {} return { user: u, pwd: p }; } function hasAccount() { var acc = getAccount(); return !!(acc.user && acc.pwd); } var HOST = location.hostname; /* ============================================================ * 通用工具 * ============================================================ */ // 原生赋值(兼容 React/Vue 受控输入框) function setNativeValue(el, value) { try { var proto = el.__proto__; var desc = Object.getOwnPropertyDescriptor(proto, 'value'); if (desc && desc.set) { desc.set.call(el, value); } else { el.value = value; } el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) { el.value = value; } } function isLoggedIn() { // CSDN 登录后的特征 Cookie var c = document.cookie || ''; return c.indexOf('UserToken=') >= 0 || c.indexOf('UserName=') >= 0; } function showTip(text, color) { var old = document.getElementById('csdn-dl-tip'); if (old) old.parentNode.removeChild(old); var tip = document.createElement('div'); tip.id = 'csdn-dl-tip'; tip.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:9999999;' + 'background:' + (color || 'rgba(20,20,30,0.95)') + ';color:#fff;padding:12px 22px;border-radius:8px;' + 'font:14px/1.5 sans-serif;box-shadow:0 4px 16px rgba(0,0,0,0.4);'; tip.textContent = text; (document.body || document.documentElement).appendChild(tip); return tip; } /* ============================================================ * A. 登录页逻辑:自动填账密 + 自动点登录 * ============================================================ */ function runLoginPage() { console.log('[CSDN-DL] 登录页,准备自动登录'); if (!hasAccount()) { setTimeout(showAccountTip, 500); return; } var filled = false; var clicked = false; function tryFillAndLogin() { // 找账号框:可见的非密码文本框 var userInput = null; var inputs = document.querySelectorAll('input'); for (var i = 0; i < inputs.length; i++) { var inp = inputs[i]; var t = (inp.type || '').toLowerCase(); if (t === 'password') continue; if (t !== 'text' && t !== 'tel' && t !== 'email' && t !== '') continue; var ph = (inp.placeholder || '') + (inp.name || '') + (inp.id || ''); if (/账号|手机号|邮箱|用户名|user|account|phone|email|login/i.test(ph) || !userInput) { userInput = inp; if (/账号|手机号|邮箱|用户名|user|account/i.test(ph)) break; } } // 找密码框 var pwdInput = document.querySelector('input[type="password"]'); if (userInput && pwdInput && !filled) { filled = true; var acc = getAccount(); setNativeValue(userInput, acc.user); setNativeValue(pwdInput, acc.pwd); console.log('[CSDN-DL] 已填写账密'); showTip('已自动填写账密,正在登录…'); setTimeout(clickLogin, 600); } } function clickLogin() { if (clicked) return; // 找登录按钮:button 或含"登录"文字的可点元素 var candidates = document.querySelectorAll('button, [class*="log"], [class*="btn"], a'); for (var i = 0; i < candidates.length; i++) { var el = candidates[i]; var txt = (el.textContent || '').trim(); if (txt === '登录' || txt === '登 录' || /^登\s*录$/.test(txt)) { // 排除"登录/注册"导航链接(通常在登录页顶部) if (el.closest && el.closest('.toolbar, nav, header')) continue; clicked = true; el.click(); console.log('[CSDN-DL] 已点击登录按钮'); watchLoginResult(); return; } } } function watchLoginResult() { var n = 0; var timer = setInterval(function () { n++; // 离开登录页 = 登录成功 if (location.hostname !== 'passport.csdn.net') { clearInterval(timer); return; } // 检测验证码/滑块出现 var captcha = document.querySelector('[class*="captcha"], [class*="verify"], [class*="slide"], [id*="captcha"], [class*="geetest"], [class*="nc_"]'); if (captcha) { clearInterval(timer); showTip('出现验证码/滑块,请手动完成验证', 'rgba(200,80,20,0.95)'); return; } if (n > 10) { // 10 秒后仍在登录页 clearInterval(timer); showTip('自动登录未完成,请检查是否需要验证码或手动登录', 'rgba(200,80,20,0.95)'); } }, 1000); } // 等 DOM 出现输入框 var mo = new MutationObserver(function () { tryFillAndLogin(); }); function startObs() { if (document.documentElement) { mo.observe(document.documentElement, { childList: true, subtree: true }); } tryFillAndLogin(); setTimeout(tryFillAndLogin, 1000); setTimeout(tryFillAndLogin, 2500); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startObs); } else { startObs(); } } /* ============================================================ * B. 内容页逻辑:登录态检测 + 去弹窗 + 提取地址 * ============================================================ */ var LOGIN_SELECTORS = [ '.passport-login-container', '.passport-login-pop-container', '[class*="login-modal"]', '[class*="login-dialog"]', '[class*="login-mask"]', '[id*="passport-login"]' ]; function injectCSS() { var css = LOGIN_SELECTORS.join(',') + '{display:none!important;visibility:hidden!important;}'; css += 'body{overflow:auto!important;}'; try { if (typeof GM_addStyle === 'function') { GM_addStyle(css); return; } } catch (e) {} function doInject() { var parent = document.head || document.documentElement; if (!parent) { setTimeout(doInject, 10); return; } var style = document.createElement('style'); style.textContent = css; style.setAttribute('data-script', 'csdn-dl'); parent.appendChild(style); } doInject(); } function removeLoginPopups() { for (var i = 0; i < LOGIN_SELECTORS.length; i++) { var els = document.querySelectorAll(LOGIN_SELECTORS[i]); for (var j = 0; j < els.length; j++) { try { els[j].parentNode && els[j].parentNode.removeChild(els[j]); } catch (e) {} } } try { document.documentElement.style.setProperty('overflow', 'auto', 'important'); if (document.body) document.body.style.setProperty('overflow', 'auto', 'important'); } catch (e) {} } function installRedirectBlocker() { var _open = window.open; window.open = function (url) { if (url && String(url).indexOf('passport.csdn.net') >= 0) { console.log('[CSDN-DL] 拦截登录页弹窗:', url); return null; } return _open.apply(this, arguments); }; } function extractDownloadUrl() { try { var state = window.__INITIAL_STATE__ || window.__NUXT__ || {}; var found = []; JSON.stringify(state, function (k, v) { if (typeof v === 'string' && (v.indexOf('.zip') >= 0 || v.indexOf('.rar') >= 0 || v.indexOf('.7z') >= 0 || v.indexOf('.pdf') >= 0 || v.indexOf('.tar') >= 0 || v.indexOf('.gz') >= 0) && (v.indexOf('http') === 0)) { found.push(v); } return v; }); var scripts = document.querySelectorAll('script'); for (var i = 0; i < scripts.length; i++) { var text = scripts[i].textContent || ''; var m = text.match(/"((?:https?:)?\/\/[^"]*?\.(?:zip|rar|7z|pdf|tar|gz)[^"]*)"/i); if (m && m[1]) found.push(m[1]); } return found.length ? found[0] : null; } catch (e) { return null; } } // 设置面板(通过脚本猫菜单"设置账号密码"打开) function showSettingsPanel() { var old = document.getElementById('csdn-dl-settings'); if (old) old.parentNode.removeChild(old); var acc = getAccount(); var mask = document.createElement('div'); mask.id = 'csdn-dl-settings'; mask.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;z-index:99999999;' + 'background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;'; mask.innerHTML = '