您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
115离线下载功能,重构为统一的悬浮窗交互,并支持手动配置UserID和Cookie,增强稳定性。新增手动输入离线链接功能。
// ==UserScript== // @name 发送到115 (悬浮窗版 - 手动配置) // @author zxf10608 (重构 by AI Engineer) // @version 8.1.4 // @icon https://115.com/favicon.ico // @namespace https://greasyfork.org/zh-CN/scripts/408466 // @description 115离线下载功能,重构为统一的悬浮窗交互,并支持手动配置UserID和Cookie,增强稳定性。新增手动输入离线链接功能。 // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @require https://greasyfork.org/scripts/398240-gm-config-zh-cn/code/GM_config_zh-CN.js // @require https://greasyfork.org/scripts/412267-base64-v1-0/code/base64_v10.js // @require https://cdn.jsdelivr.net/npm/[email protected]/toastr.min.js // @resource toastrCss https://cdn.jsdelivr.net/npm/[email protected]/build/toastr.min.css // @match http*://*/* // @match http*://*.115.com/* // @exclude http*://*.115.com/bridge* // @exclude http*://*.115.com/*/static* // @exclude http*://*.baidu.com/* // @exclude http*://*.iqiyi.com/* // @exclude http*://*.qq.com/* // @exclude http*://*.youku.com/* // @exclude http*://*.bilibili.com/ // @exclude http*://*.pptv.com/* // @exclude http*://*.fun.tv/* // @exclude http*://*.sohu.com/* // @exclude http*://*.le.com/* // @exclude http*://*.tudou.com/* // @exclude http*://*.bilibili.com/* // @exclude http*://music.163.com/* // @exclude http*://github.com/* // @exclude http*://gitee.com/* // @exclude http*://btcache.me/* // @exclude http*://*.jd.com/* // @exclude http*://*.taobao.com/* // @exclude http*://*.tmall.com/* // @exclude http*://*.vip.com/* // @exclude http*://*.pinduoduo.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_download // @grant GM_openInTab // @grant GM_setClipboard // @grant GM_getResourceText // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @connect 115.com // @connect * // @grant unsafeWindow // @grant window.open // @grant window.close // @run-at document-start // @compatible chrome // @license GPL License // ==/UserScript== (function() { 'use strict'; var newVersion = 'v8.1.4'; // 版本号更新 if (typeof GM_config == 'undefined') { alert('115优化大师:\n网络异常,相关库文件加载失败,脚本无法使用,请刷新网页重新加载!'); return; } // 配置界面 function config() { var windowCss = ''; // All styles are now in addNewUIStyles GM_registerMenuCommand('设置', () => GM_config.open()); // 添加菜单命令:手动输入链接离线 GM_registerMenuCommand('手动输入链接离线', showManualInputModal); // [v8.1.3] 恢复完整的配置项 GM_config.init({ id: 'Cfg', title: `115优化大师 ${newVersion}`, isTabs: true, skin: 'tab', css: windowCss, frameStyle: { height: '550px', width: '445px', zIndex: '2147483648' }, fields: { offline_Down: { section: ['离线升级', ''], label: '启用悬浮窗一键离线', type: 'checkbox', default: true, }, offline_result: { label: '任务添加后显示离线结果', type: 'checkbox', default: true }, open_List: { label: '离线后自动打开任务列表', type: 'checkbox', default: false }, open_search: { label: '离线成功后开启视频搜索', type: 'checkbox', default: true, line: 'start' }, search_result: { label: '显示视频搜索结果', type: 'checkbox', default: true }, open_Popup: { label: '搜到视频自动播放', type: 'checkbox', default: false, line: 'end' }, fuzzy_find: { label: '启用下载地址模糊匹配', type: 'checkbox', default: false }, folder_config: { section: ['自定义文件夹', '可配置多个离线下载目标文件夹'], label: '文件夹配置 (格式: 别名=CID)', type: 'textarea', default: '默认=0', title: '每行一条记录,格式为"别名=CID值"。\n例如:\n电影=1234567890123456789\n剧集=9876543210987654321', css: 'width: 95%; height: 120px;' }, manual_credential: { section: ['手动身份凭证', '如果自动获取凭证失效,请在此手动配置'], label: '启用手动配置凭证', labelPos: 'right', type: 'checkbox', default: false, title: '勾选后,脚本将使用下方填写的UserID和Cookie,推荐使用此方式。' }, manualUserID: { label: '115 UserID', type: 'text', default: '', title: '请填写您的115数字ID' }, manualCookie: { label: '115 Cookie', type: 'textarea', default: '', title: '请填入完整的115登录Cookie' }, credential_help: { label: '如何获取凭证?', type: 'button', click: function() { alert('1. 登录115网盘。\n2. 按F12打开开发者工具,切换到【网络(Network)】标签。\n3. 刷新页面或随便点击一个文件夹。\n4. 在网络请求列表中,找到任意一个发往 115.com 的请求,点击它。\n5. 在右侧出现的【标头(Headers)】面板中,向下滚动到【请求标头(Request Headers)】区域。\n6. UserID通常可以在Cookie值中找到(如`USER_ID=...;`),或者在页面源代码中搜索`USER_ID`。\n7. 完整的Cookie值,请直接复制`Cookie:`后面的所有文本。'); } }, }, events: { save: function() { GM_config.close(); location.reload(); } }, }); } config(); // ================================================================ // 全局变量 // ================================================================ var G = GM_config; var localHref = window.location.href, show_result = G.get('offline_result'), down_reg = /^(magnet|thunder|ftp|ed2k):/i; var UA = navigator.userAgent, sign_url = 'http://115.com/?ct=offline&ac=space', add_urls = 'http://115.com/web/lixian/?ct=lixian&ac=add_task_urls'; var lists_url = 'http://115.com/web/lixian/?ct=lixian&ac=task_lists', a_list = `<br><a target="_blank" class="openList" href="javascript:void(0);" style="color:blue;" title="点击打开离线链接任务列表">打开任务列表</a>`; var detectedLinks = [], $fab, $modal; // ================================================================ // 所有函数定义 (确保在使用前定义) // ================================================================ function addNewUIStyles() { GM_addStyle(` /* Modern Theme */ :root { --primary-color: #2c6fbb; --primary-color-light: #f0f6ff; --background-color: #ffffff; --text-color: #333; --secondary-text-color: #555; --border-color: #e0e0e0; --shadow-color: rgba(44, 111, 187, 0.2); --success-color: #4caf50; --error-color: #f44336; } /* Floating Action Button */ .s115-fab { position: fixed; bottom: 40px; right: 40px; width: 56px; height: 56px; background: linear-gradient(145deg, #3a8dff, #2c6fbb); border-radius: 50%; display: none; justify-content: center; align-items: center; cursor: pointer; box-shadow: 0 8px 25px var(--shadow-color); z-index: 2147483640; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); color: white; font-size: 22px; font-weight: bold; border: none; } .s115-fab:hover { transform: scale(1.1) rotate(15deg); box-shadow: 0 12px 30px var(--shadow-color); } /* Modal Styles */ .s115-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 2147483641; display: none; justify-content: center; align-items: center; backdrop-filter: blur(5px); animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .s115-modal-window { background-color: var(--background-color); width: 90%; max-width: 700px; max-height: 90vh; border-radius: 16px; box-shadow: 0 15px 40px rgba(0,0,0,0.15); display: flex; flex-direction: column; overflow: hidden; transform: scale(0.95); animation: zoomIn 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) forwards; } @keyframes zoomIn { from { transform: scale(0.95); opacity: 0; } to { transform: scale(1); opacity: 1; } } .s115-modal-header { padding: 20px 30px; font-size: 22px; font-weight: 700; color: var(--primary-color); border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; background-color: var(--primary-color-light); } #s115-close-modal { background: none; border: none; font-size: 30px; color: #aaa; cursor: pointer; padding: 0; line-height: 1; transition: all 0.2s; } #s115-close-modal:hover { color: var(--text-color); transform: rotate(90deg); } .s115-modal-content { padding: 10px 30px; overflow-y: auto; flex-grow: 1; } .s115-modal-content ul { list-style: none; padding: 0; margin: 0; } .s115-modal-content li { display: flex; align-items: center; padding: 15px 5px; border-bottom: 1px solid #f0f0f0; transition: background-color 0.2s; } .s115-modal-content li:last-child { border-bottom: none; } .s115-modal-content li:hover { background-color: var(--primary-color-light); border-radius: 8px; } .s115-modal-content input[type="checkbox"] { margin-right: 20px; min-width: 20px; height: 20px; accent-color: var(--primary-color); cursor: pointer; } .s115-modal-content span { word-break: break-all; color: var(--text-color); font-size: 15px; } .s115-modal-footer { padding: 20px 30px; border-top: 1px solid var(--border-color); background-color: #f9f9f9; display: flex; justify-content: space-between; align-items: center; gap: 16px; } /* General Button & Select Styles */ .s115-modal-footer button, #Cfg .config_var button, #s115-folder-select { padding: 0 20px; height: 44px; border: 1px solid var(--border-color); border-radius: 10px; cursor: pointer; font-size: 15px; font-weight: 600; transition: all 0.2s ease; } #s115-folder-select { background-color: white; } #s115-folder-select:focus, #Cfg .config_var input[type="text"]:focus, #Cfg .config_var textarea:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 4px var(--shadow-color); } .s115-btn-primary, #Cfg #Cfg_saveBtn { background: linear-gradient(145deg, #3a8dff, #2c6fbb); color: white; border: none; box-shadow: 0 4px 10px var(--shadow-color); } .s115-btn-primary:hover, #Cfg #Cfg_saveBtn:hover { opacity: 0.9; box-shadow: 0 6px 15px var(--shadow-color); transform: translateY(-2px); } .s115-modal-footer button:not(.s115-btn-primary) { background-color: #f0f0f0; color: var(--text-color); } .s115-modal-footer button:not(.s115-btn-primary):hover { background-color: #e0e0e0; } /* GM_Config Styles Refactor */ #Cfg { border-radius: 16px !important; overflow: hidden !important; border: none !important; box-shadow: 0 15px 40px rgba(0,0,0,0.15) !important; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } #Cfg .config_header { background: linear-gradient(to right, #3a8dff, #2c6fbb) !important; color: white !important; padding: 20px !important; font-size: 24px !important; text-align: center; } #Cfg .tab-container { background-color: var(--primary-color-light) !important; padding: 10px 20px 0 20px !important; border-bottom: 1px solid var(--border-color); } #Cfg .tab { border: none !important; background-color: transparent !important; padding: 12px 18px !important; font-size: 15px !important; color: var(--secondary-text-color) !important; border-radius: 10px 10px 0 0 !important; transition: all 0.3s; position: relative; bottom: -1px; } #Cfg .tab[selected='true'] { background-color: var(--background-color) !important; color: var(--primary-color) !important; font-weight: 700 !important; border-top: 1px solid var(--border-color) !important; border-left: 1px solid var(--border-color) !important; border-right: 1px solid var(--border-color) !important; } #Cfg .config_var { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; align-items: center; padding: 15px 20px; border-bottom: 1px solid #f0f0f0; } #Cfg .config_var:last-of-type { border-bottom: none; } #Cfg .section_header_div { grid-column: 1 / -1; border-bottom: none; padding: 20px 20px 5px 20px; } #Cfg .section_header { font-size: 18px; font-weight: 700; color: var(--primary-color); margin: 0; padding-bottom: 10px; border-bottom: 3px solid var(--primary-color-light); } #Cfg .config_var .field_label { font-weight: 600; font-size: 15px; } #Cfg .config_var input[type="text"], #Cfg .config_var textarea, #Cfg .config_var button { width: 100%; box-sizing: border-box; } #Cfg .config_var input[type="text"], #Cfg .config_var textarea { padding: 12px; border: 1px solid var(--border-color); border-radius: 10px; font-size: 15px; transition: all 0.2s ease; } #Cfg .config_var textarea { height: 140px; resize: vertical; } #Cfg .config_var input[type="checkbox"] { height: 0; width: 0; visibility: hidden; } #Cfg .config_var .field_label[for*="checkbox"] { position: relative; cursor: pointer; padding-left: 55px; line-height: 24px; } #Cfg .config_var .field_label[for*="checkbox"]::before { content: ''; position: absolute; left: 0; top: 0; width: 44px; height: 24px; border-radius: 12px; background: #ccc; transition: background-color 0.3s; } #Cfg .config_var .field_label[for*="checkbox"]::after { content: ''; position: absolute; left: 2px; top: 2px; width: 20px; height: 20px; border-radius: 50%; background: white; transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.2); } #Cfg .config_var input[type="checkbox"]:checked + label::before { background-color: var(--success-color); } #Cfg .config_var input[type="checkbox"]:checked + label::after { transform: translateX(20px); } #Cfg #Cfg_buttons_holder { padding: 20px; border-top: 1px solid var(--border-color); background-color: #f9f9f9; text-align: right; } /* 手动输入链接离线模态框样式 */ .s115-manual-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 2147483643; display: none; justify-content: center; align-items: center; backdrop-filter: blur(5px); animation: fadeIn 0.3s ease; } .s115-manual-modal-window { background-color: var(--background-color); width: 90%; max-width: 500px; max-height: 80vh; border-radius: 16px; box-shadow: 0 15px 40px rgba(0,0,0,0.15); display: flex; flex-direction: column; overflow: hidden; transform: scale(0.95); animation: zoomIn 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) forwards; } .s115-manual-modal-header { padding: 20px 30px; font-size: 22px; font-weight: 700; color: white; border-bottom: 1px solid rgba(255,255,255,0.1); display: flex; justify-content: space-between; align-items: center; background: linear-gradient(145deg, #37c976, #229455); } #s115-manual-close-modal { background: none; border: none; font-size: 30px; color: rgba(255,255,255,0.8); cursor: pointer; padding: 0; line-height: 1; transition: all 0.2s; } #s115-manual-close-modal:hover { color: white; transform: rotate(90deg); } .s115-manual-modal-content { padding: 20px; display: flex; flex-direction: column; gap: 15px; } #s115-manual-input { width: 100%; height: 200px; padding: 15px; border: 1px solid var(--border-color); border-radius: 10px; font-size: 15px; resize: vertical; transition: border 0.3s; } #s115-manual-input:focus { outline: none; border-color: #37c976; box-shadow: 0 0 0 4px rgba(55, 201, 118, 0.15); } .s115-manual-select-group { display: flex; align-items: center; gap: 10px; } #s115-manual-folder-select { flex: 1; height: 46px; padding: 10px 15px; border: 1px solid var(--border-color); border-radius: 8px; font-size: 15px; background-color: white; } .s115-manual-modal-footer { padding: 20px 30px; border-top: 1px solid var(--border-color); background-color: #f9f9f9; display: flex; justify-content: space-between; } .s115-btn-secondary { padding: 0 20px; height: 44px; background-color: #f0f0f0; color: var(--text-color); border: none; border-radius: 10px; cursor: pointer; font-size: 15px; font-weight: 600; transition: all 0.2s ease; } .s115-btn-secondary:hover { background-color: #e0e0e0; } `); } function notice() { GM_addStyle(GM_getResourceText('toastrCss')); GM_addStyle('.toast{font-size:15px!important;width:360px!important;} .toast-title{font-size:16px!important;text-align:center}'); toastr.options = { "closeButton": true, "debug": false, "progressBar": true, "timeOut": 8000, "extendedTimeOut": 8000, "positionClass": 'toast-top-right', "allowHtml": true, "newestOnTop": false, }; } function getRightUrl(url) { var n = url.trim(); if (/^thunder/i.test(n)) { n = decodeURIComponent(decode64(n.replace(/thunder:\/\//i, '')).slice(2, -2)); } // After thunder decoding, it could be an ed2k or magnet link if (/^ed2k:\/\//i.test(n)) { const parts = n.split('|'); if (parts.length >= 5 && parts[1] === 'file') { // Reconstruct to ensure a clean, standard link n = `ed2k://|file|${parts[2]}|${parts[3]}|${parts[4]}|/`; } } if (/^magnet/i.test(n)) { var h = n.split('&')[0].substring(20) || n.substring(20); if (h.length == 32) h = base32To16(h); n = 'magnet:?xt=urn:btih:' + h; } else if (/^\/\//.test(n)) { n = location.protocol + n; } else if (/^\/(?!\/)/.test(n)) { n = location.protocol + '//' + location.host + url; } return n; } function base32To16(str) { if (str.length % 8 !== 0 || /[0189]/.test(str)) return str; str = str.toUpperCase(); var bin = "", newStr = "", i; for (i = 0; i < str.length; i++) { var c = str.charCodeAt(i); c = '0000' + (c < 65 ? c - 24 : c - 65).toString(2); bin += c.substr(c.length - 5); } for (i = 0; i < bin.length; i += 4) { newStr += parseInt(bin.substring(i, i + 4), 2).toString(16); } return newStr; } function verify() { if (confirm('立即打开验证账号弹窗?')) { try { if (window.open('https://captchaapi.115.com/?ac=security_code&type=web&cb=Close' + Date.now(), '请验证账号', `height=500,width=335,top=${(window.screen.availHeight - 500) / 2},left=${(window.screen.availWidth - 335) / 2},toolbar=no,menubar=no`) === null) alert('验证弹窗已被拦截!'); } catch (e) { alert('验证弹窗已被拦截!'); } } } function get115Headers() { const h = { "User-Agent": UA, Origin: "https://115.com" }; if (G.get('manual_credential') && G.get('manualCookie').trim() !== '') { h['Cookie'] = G.get('manualCookie'); } return h; } function getAttribute(e) { var d = []; $.each(e.attributes, (i, attr) => { if (attr.specified && attr.value.length > 30) d.push(attr.value); }); if ($(e).text().length > 25) d.push($(e).text()); return d; } var offline = (() => ({ getSign: (key, cid) => new Promise((resolve, reject) => { if (/^\w+=/.test(key)) { resolve(key); return; } const UserID = G.get('manual_credential') ? G.get('manualUserID') : GM_getValue('115ID') || ''; if (!UserID) { toastr.error('请先登录115或在设置中手动填写UserID!', '认证失败'); return reject('No UserID'); } GM_xmlhttpRequest({ method: 'GET', url: sign_url, responseType: 'json', headers: get115Headers(), onload: (res) => { if (res.responseText.includes('<html')) { toastr.error('请先登录115网盘或检查Cookie是否正确!', '离线任务添加失败'); setTimeout(() => { if (confirm('立即打开115登录页面?')) GM_openInTab('https://115.com/?mode=login', false); }, 3000); return reject('Not logged in or invalid cookie'); } const data = { uid: UserID, sign: res.response.sign, time: res.response.time, wp_path_id: cid || '0', savepath: '' }; const value = $.isPlainObject(key) ? $.param($.extend(data, key)) : $.param(data) + `&url=${encodeURIComponent(key)}`; resolve(value); }, onerror: reject, }); }), getData: (herf, key, cid) => offline.getSign(key, cid).then(value => new Promise((resolve, reject) => { const headers = { ...get115Headers(), "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Accept": "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest" }; GM_xmlhttpRequest({ method: 'POST', data: value, url: herf, responseType: 'json', headers, onload: (res) => resolve(res.response), onerror: reject }); })), addButton: () => { $('[href]').not('[Searched]').each(function() { const url = $(this).attr('href'); if (!/^(magnet|thunder|ftp|ed2k):/i.test(url) && !/\.(torrent|rar|zip|7z|mp4|rmvb|mkv|avi)$/i.test(url)) return; $(this).attr('Searched', 'true'); const link = getRightUrl(url); if (!detectedLinks.some(item => item.url === link)) { detectedLinks.push({ url: link, text: $(this).text() }); } }); updateFab(); }, addLink: () => { $('a,button,span,li').not('[Searched],[href*="google"]').each(function() { if ($(this).find('img').length > 0) return; for (let attr of getAttribute(this)) { if (/(^|\/|&|-|\.|\?|=|:|#|_|@)([a-f0-9]{40}|[a-z2-7]{32})(?!\w)/i.test(attr)) { const link = getRightUrl('magnet:?xt=urn:btih:' + attr.match(/(?:[a-f0-9]{40}|[a-z2-7]{32})/i)[0]); if (!detectedLinks.some(item => item.url === link)) { detectedLinks.push({ url: link, text: $(this).text() }); $(this).attr('Searched', 'true'); updateFab(); } return; } } }); }, addEd2kLinksFromText: () => { const bodyText = document.body.innerText; const ed2kRegex = /(ed2k:\/\/(?:\|file\|[^|]+\|\d+\|[a-fA-F0-9]{32}\|\/))/gi; let match; while ((match = ed2kRegex.exec(bodyText)) !== null) { const link = getRightUrl(match[0]); if (!detectedLinks.some(item => item.url === link)) { const parts = link.split('|'); const text = parts.length > 2 ? parts[2] : link; // Use filename as text detectedLinks.push({ url: link, text: text }); } } }, }))(); function executeBatchDownload(links, folderCid) { if (links.length === 0) { toastr.warning('没有发现任何可离线的链接'); return; } const btn = $('#s115-batch-download'); if(btn.attr('disabled')) return; btn.attr('disabled', true).css('opacity', '0.5'); setTimeout(() => btn.attr('disabled', false).css('opacity', '1'), links.length > 10 ? 10000 : 5000); var linksParams = {}; links.forEach((link, i) => linksParams[`url[${i}]`] = link.url); offline.getData(add_urls, linksParams, folderCid).then(json => { if (json.state) { let s = 0, e = 0, f = 0; json.result.forEach(r => { if (r.state) s++; else if (r.errcode == 10008) e++; else f++; }); toastr.success(`成功 ${s}, 存在 ${e}, 失败 ${f}` + a_list, `离线任务结果`, { timeOut: 10000 }); if (G.get('open_List') && s > 0) setTimeout(() => GM_openInTab('https://115.com/?tab=offline&mode=wangpan', false), 2000); } else if (json.errcode == 911) { toastr.warning('账号异常,请验证。', '离线任务失败'); setTimeout(verify, 1000); } else { toastr.error((json.error_msg || '未知错误') + a_list, '离线任务失败'); } }).catch(err => { toastr.error('服务器繁忙或网络错误', '离线任务异常'); console.error(err); }); } function handleBatchDownload() { var $checkedItems = $('.s115-link-checkbox:checked'), l = $checkedItems.length; if (l === 0) { toastr.warning('请至少选择一个链接。'); return; } const links = $checkedItems.map((i, el) => { return { url: $(el).val(), text: $(el).siblings('span').text() }; }).get(); const selectedCid = $('#s115-folder-select').val(); hideModal(); executeBatchDownload(links, selectedCid); } function showModal() { let linkHtml = ''; detectedLinks.forEach(link => { const text = $('<div>').text(link.text || link.url).html(); linkHtml += `<li><input type="checkbox" class="s115-link-checkbox" value="${link.url}" checked><span title="${link.url}">${text}</span></li>`; }); $('#s115-link-list').html(linkHtml); let folderHtml = ''; const folderConfig = G.get('folder_config').trim().split('\n'); folderConfig.forEach(line => { const parts = line.split('='); if (parts.length === 2) { const alias = parts[0].trim(); const cid = parts[1].trim(); if (alias && /^\d+$/.test(cid)) { folderHtml += `<option value="${cid}">${alias}</option>`; } } }); $('#s115-folder-select').html(folderHtml); $modal.css('display', 'flex'); } function hideModal() { $modal.hide(); } function updateFab() { var len = detectedLinks.length; if (len > 0) { $fab.css('display','flex').text(len); } else { $fab.hide(); } } function createUI() { $('body').append(` <div class="s115-fab">0</div> <div class="s115-modal-overlay"><div class="s115-modal-window"> <div class="s115-modal-header"><span>115离线下载任务列表</span><button id="s115-close-modal" title="关闭">×</button></div> <div class="s115-modal-content"><ul id="s115-link-list"></ul></div> <div class="s115-modal-footer"> <div><button id="s115-select-all">全选</button><button id="s115-deselect-all" style="margin-left: 10px;">取消</button></div> <div> <select id="s115-folder-select" style="padding: 8px; border-radius: 5px; border: 1px solid #ccc; margin-right: 10px;"></select> <button class="s115-btn-primary" id="s115-batch-download">离线下载选中项</button> </div> </div></div></div> <!-- 手动输入链接离线模态框 --> <div class="s115-manual-modal-overlay"> <div class="s115-manual-modal-window"> <div class="s115-manual-modal-header"> <span>手动输入链接离线下载</span> <button id="s115-manual-close-modal" title="关闭">×</button> </div> <div class="s115-manual-modal-content"> <textarea id="s115-manual-input" placeholder="请输入磁力链接、ed2k链接、thunder链接或普通下载链接,多个链接请分行输入..." ></textarea> <div class="s115-manual-select-group"> <label>保存到文件夹:</label> <select id="s115-manual-folder-select"></select> </div> </div> <div class="s115-manual-modal-footer"> <button class="s115-btn-secondary" id="s115-manual-clear">清空</button> <button class="s115-btn-primary" id="s115-manual-submit">开始离线下载</button> </div> </div> </div> `); $fab = $('.s115-fab'); $modal = $('.s115-modal-overlay'); // 绑定主浮动窗口事件 $fab.on('click', showModal); $modal.on('click', function(e) { if ($(e.target).is('#s115-close-modal') || $(e.target).is('.s115-modal-overlay')) hideModal(); }); $('#s115-select-all').on('click', () => $('#s115-link-list :checkbox').prop('checked', true)); $('#s115-deselect-all').on('click', () => $('#s115-link-list :checkbox').prop('checked', false)); $('#s115-batch-download').on('click', handleBatchDownload); // 绑定手动输入窗口事件 $('#s115-manual-close-modal').on('click', () => $('.s115-manual-modal-overlay').hide()); $('#s115-manual-clear').on('click', () => $('#s115-manual-input').val('')); $('#s115-manual-submit').on('click', handleManualDownload); $('.s115-manual-modal-overlay').on('click', function(e) { if ($(e.target).is('.s115-manual-modal-overlay')) { $('.s115-manual-modal-overlay').hide(); } }); } // ================================================================ // 手动输入链接离线功能 // ================================================================ function showManualInputModal() { populateFolderDropdown('#s115-manual-folder-select'); $('.s115-manual-modal-overlay').css('display', 'flex'); $('#s115-manual-input').focus(); } function handleManualDownload() { const inputText = $('#s115-manual-input').val().trim(); if (!inputText) { toastr.warning('请输入至少一个链接'); return; } // 分割输入的链接 const links = inputText.split('\n') .map(link => link.trim()) .filter(link => link.length > 0) .map(link => ({ url: getRightUrl(link), text: link.substring(0, 80) + (link.length > 80 ? '...' : '') })); if (links.length === 0) { toastr.warning('没有找到有效链接'); return; } const selectedCid = $('#s115-manual-folder-select').val(); $('.s115-manual-modal-overlay').hide(); executeBatchDownload(links, selectedCid); } function populateFolderDropdown(selector) { let folderHtml = '<option value="0">默认文件夹</option>'; const folderConfig = G.get('folder_config').trim().split('\n'); folderConfig.forEach(line => { const parts = line.split('='); if (parts.length === 2) { const alias = parts[0].trim(); const cid = parts[1].trim(); if (alias && /^\d+$/.test(cid)) { folderHtml += `<option value="${cid}">${alias}</option>`; } } }); $(selector).html(folderHtml); } // ================================================================ // 主执行逻辑 // ================================================================ $(document).ready(function() { notice(); if (localHref.includes('captchaapi.115.com')) { $('body').on('click', '.vcode-hint', () => setTimeout(() => window.close(), 200)); return; } if (!G.get('manual_credential') && localHref.includes('115.com/')) { try { if (unsafeWindow.USER_ID) { GM_setValue('115ID', unsafeWindow.USER_ID); console.log('115账号ID自动获取成功!'); } } catch (e) { console.log('115账号未登录或页面结构变化,无法自动获取UserID。'); } } if (G.get('offline_Down') && !localHref.includes('115.com/')) { addNewUIStyles(); createUI(); // Use MutationObserver for more efficient link detection const scanForLinks = () => { offline.addButton(); offline.addEd2kLinksFromText(); if (G.get('fuzzy_find')) offline.addLink(); updateFab(); // Consolidate FAB update }; // Debounce function to avoid excessive scanning on busy pages let debounceTimer; const debouncedScan = () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(scanForLinks, 300); }; // Initial scan on page load debouncedScan(); // Set up an observer for dynamically added content const observer = new MutationObserver(debouncedScan); observer.observe(document.body, { childList: true, subtree: true }); } }); })();