您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
增加一键转存按钮,调用115web接口转存,可自定义Cookie和目标文件夹ID,并保存设置,右下角可修改设置,可用ui来查看文件夹id并保存设置,支持并适配移动端与pc端
// ==UserScript== // @name 115分享页一键转存按钮 // @version 0.6 // @description 增加一键转存按钮,调用115web接口转存,可自定义Cookie和目标文件夹ID,并保存设置,右下角可修改设置,可用ui来查看文件夹id并保存设置,支持并适配移动端与pc端 // @author 楠 // @match *://115cdn.com/s/* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @license MIT // @icon https://115.com/favicon.ico // @namespace https://greasyfork.org/users/1514724 // ==/UserScript== (function() { 'use strict'; function showToast(message, duration = 2500) { const toast = document.createElement('div'); const isError = message.includes('❌') || message.includes('⚠️') || message.includes('请先设置') || message.includes('不能为空') || message.includes('已经转存过') || message.includes('失败') || message.includes('无法解析'); const bgGradient = isError ? 'linear-gradient(135deg, #ff5252, #b71c1c)' : 'linear-gradient(135deg, #4CAF50, #2E7D32)'; Object.assign(toast.style, { position: 'fixed', top: '110px', right: '20px', padding: '16px 24px', background: bgGradient, color: '#fff', borderRadius: '12px', boxShadow: isError ? '0 6px 20px rgba(0,0,0,0.2), 0 0 20px rgba(255,82,82,0.3)' : '0 6px 20px rgba(0,0,0,0.2), 0 0 20px rgba(76,175,80,0.3)', fontSize: '14px', fontWeight: '500', opacity: '0', transform: 'translateX(100%) scale(0.9)', transition: 'all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55)', zIndex: 10000, maxWidth: '300px', backdropFilter: 'blur(10px)', border: '1px solid rgba(255,255,255,0.1)', overflow: 'hidden' }); const progressBar = document.createElement('div'); Object.assign(progressBar.style, { position: 'absolute', bottom: '0', left: '0', height: '3px', background: 'linear-gradient(90deg, rgba(255,255,255,0.5), rgba(255,255,255,0.8))', width: '100%', transform: 'scaleX(1)', transformOrigin: 'left center', transition: 'transform linear', borderRadius: '0 0 12px 12px' }); toast.appendChild(progressBar); const icon = document.createElement('span'); icon.innerHTML = isError ? '⚠️' : '✓'; Object.assign(icon.style, { display: 'inline-block', marginRight: '10px', fontSize: '16px', fontWeight: 'bold', verticalAlign: 'middle' }); const textSpan = document.createElement('span'); textSpan.textContent = message; textSpan.style.verticalAlign = 'middle'; toast.appendChild(icon); toast.appendChild(textSpan); document.body.appendChild(toast); requestAnimationFrame(() => { toast.style.opacity = '1'; toast.style.transform = 'translateX(0) scale(1)'; progressBar.style.transition = `transform ${duration}ms linear`; progressBar.style.transform = 'scaleX(0)'; }); toast.addEventListener('mouseenter', () => { toast.style.transform = 'translateX(0) scale(1.05)'; toast.style.boxShadow = isError ? '0 8px 25px rgba(0,0,0,0.25), 0 0 30px rgba(255,82,82,0.4)' : '0 8px 25px rgba(0,0,0,0.25), 0 0 30px rgba(76,175,80,0.4)'; }); toast.addEventListener('mouseleave', () => { toast.style.transform = 'translateX(0) scale(1)'; toast.style.boxShadow = isError ? '0 6px 20px rgba(0,0,0,0.2), 0 0 20px rgba(255,82,82,0.3)' : '0 6px 20px rgba(0,0,0,0.2), 0 0 20px rgba(76,175,80,0.3)'; }); setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(100%) scale(0.9)'; setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 500); }, duration); } async function getFolders(cid = 0) { const cookie = GM_getValue('cookie'); if (!cookie) { showToast('请先设置Cookie'); return []; } try { const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://webapi.115.com/files?aid=1&cid=${cid}&show_dir=1&nsprefix=1`, headers: { "Cookie": cookie, "User-Agent": "Mozilla/5.0" }, onload: resolve, onerror: reject }); }); const data = JSON.parse(response.responseText); if (data.state && data.data) { return data.data .filter(item => item.fl && item.fl.length === 0) .map(item => ({ name: item.n, cid: item.cid })); } return []; } catch (error) { console.error('获取文件夹列表失败:', error); showToast('获取文件夹列表失败'); return []; } } function showSettingsModal() { if (document.querySelector('#tm-settings-modal')) return; const cookie = GM_getValue('cookie') || ''; const cid = GM_getValue('target_cid') || ''; const copyLinkEnabled = GM_getValue('copy_link_enabled', false); const overlay = document.createElement('div'); overlay.id = 'tm-settings-modal'; Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', background: 'rgba(0,0,0,0.5)', zIndex: 10001, display: 'flex', justifyContent: 'center', alignItems: 'center' }); const modal = document.createElement('div'); Object.assign(modal.style, { background: '#fff', padding: '20px 25px', borderRadius: '10px', width: '420px', boxShadow: '0 6px 20px rgba(0,0,0,0.3)', fontFamily: 'Arial, sans-serif', maxHeight: '80vh', overflowY: 'auto' }); modal.innerHTML = ` <h3 style="margin-top:0;margin-bottom:15px;color:#333">115 设置</h3> <div style="margin-bottom:10px;"> <label style="display:block;margin-bottom:5px;color:#555;">Cookie:</label> <div style="display:flex;align-items:center;gap:8px;"> <input id="tm-cookie-input" type="password" value="${cookie}" style="flex:1;padding:6px;border:1px solid #ccc;border-radius:4px;"> <button id="tm-toggle-cookie" style="padding:6px 10px;border:none;border-radius:4px;background:#666;color:#fff;cursor:pointer;white-space:nowrap;">显示</button> </div> </div> <div style="margin-bottom:15px;"> <label style="display:block;margin-bottom:5px;color:#555;">目标文件夹 CID:</label> <div style="display:flex;gap:10px;"> <input id="tm-cid-input" type="text" value="${cid}" style="flex:1;padding:6px;border:1px solid #ccc;border-radius:4px;"> <button id="tm-browse-folders" style="padding:6px 12px;border:none;border-radius:4px;background:#2196F3;color:#fff;cursor:pointer;">浏览文件夹</button> </div> </div> <div style="margin-bottom:15px;padding:10px;background:#f9f9f9;border-radius:4px;"> <label style="display:flex;align-items:center;gap:8px;cursor:pointer;"> <input id="tm-copy-link-toggle" type="checkbox" ${copyLinkEnabled ? 'checked' : ''}> <span style="color:#555;">启用复制本页链接功能</span> </label> </div> <div style="text-align:right;"> <button id="tm-settings-cancel" style="margin-right:10px;padding:6px 12px;border:none;border-radius:4px;background:#ccc;color:#fff;cursor:pointer;">取消</button> <button id="tm-settings-save" style="padding:6px 12px;border:none;border-radius:4px;background:#4CAF50;color:#fff;cursor:pointer;">保存</button> </div> `; overlay.appendChild(modal); document.body.appendChild(overlay); const cookieInput = overlay.querySelector('#tm-cookie-input'); const toggleCookieBtn = overlay.querySelector('#tm-toggle-cookie'); toggleCookieBtn.addEventListener('click', function() { if (cookieInput.type === 'password') { cookieInput.type = 'text'; toggleCookieBtn.textContent = '隐藏'; } else { cookieInput.type = 'password'; toggleCookieBtn.textContent = '显示'; } }); overlay.querySelector('#tm-browse-folders').onclick = () => { const cookieValue = document.querySelector('#tm-cookie-input').value.trim(); GM_setValue('cookie', cookieValue); showFolderBrowser(); }; overlay.querySelector('#tm-settings-cancel').onclick = () => overlay.remove(); overlay.querySelector('#tm-settings-save').onclick = () => { const newCookie = document.querySelector('#tm-cookie-input').value.trim(); const newCid = document.querySelector('#tm-cid-input').value.trim(); const copyLinkEnabled = document.querySelector('#tm-copy-link-toggle').checked; GM_setValue('cookie', newCookie); GM_setValue('target_cid', newCid); GM_setValue('copy_link_enabled', copyLinkEnabled); showToast('✅ 设置已保存'); overlay.remove(); if (copyLinkEnabled) { addCopyLinkButton(); } else { removeCopyLinkButton(); } }; } async function showFolderBrowser() { if (document.querySelector('#tm-folder-browser')) return; const overlay = document.createElement('div'); overlay.id = 'tm-folder-browser'; Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', background: 'rgba(0,0,0,0.5)', zIndex: 10002, display: 'flex', justifyContent: 'center', alignItems: 'center' }); const modal = document.createElement('div'); Object.assign(modal.style, { background: '#fff', padding: '20px', borderRadius: '10px', width: '500px', maxHeight: '80vh', boxShadow: '0 6px 20px rgba(0,0,0,0.3)', fontFamily: 'Arial, sans-serif', display: 'flex', flexDirection: 'column' }); modal.innerHTML = ` <h3 style="margin-top:0;margin-bottom:15px;color:#333">浏览文件夹</h3> <div id="tm-current-path" style="margin-bottom:10px;padding:8px;background:#f5f5f5;border-radius:4px;">根目录</div> <div id="tm-folders-list" style="flex:1;overflow-y:auto;margin-bottom:15px;min-height:200px;"> <div style="text-align:center;padding:40px 0;">加载中...</div> </div> <div style="display:flex;justify-content:space-between;"> <button id="tm-folder-back" style="padding:6px 12px;border:none;border-radius:4px;background:#ccc;color:#fff;cursor:pointer;">返回上级</button> <button id="tm-folder-cancel" style="padding:6px 12px;border:none;border-radius:4px;background:#ccc;color:#fff;cursor:pointer;">取消</button> <button id="tm-folder-select" style="padding:6px 12px;border:none;border-radius:4px;background:#4CAF50;color:#fff;cursor:pointer;">选择当前文件夹</button> </div> `; overlay.appendChild(modal); document.body.appendChild(overlay); let currentCid = 0; let currentPath = ["根目录"]; let cidStack = []; let pathStack = []; async function loadFolders(cid = 0) { const foldersList = document.getElementById('tm-folders-list'); foldersList.innerHTML = '<div style="text-align:center;padding:40px 0;">加载中...</div>'; const folders = await getFolders(cid); if (folders.length === 0) { foldersList.innerHTML = '<div style="text-align:center;padding:40px 0;color:#999;">该目录下没有文件夹</div>'; return; } foldersList.innerHTML = ''; folders.forEach(folder => { const folderItem = document.createElement('div'); folderItem.className = 'tm-folder-item'; folderItem.style.padding = '10px'; folderItem.style.borderBottom = '1px solid #eee'; folderItem.style.cursor = 'pointer'; folderItem.style.display = 'flex'; folderItem.style.justifyContent = 'space-between'; folderItem.innerHTML = ` <span>${folder.name}</span> <span style="color:#999;">CID: ${folder.cid}</span> `; folderItem.onclick = () => { cidStack.push(currentCid); pathStack.push([...currentPath]); currentCid = folder.cid; currentPath.push(folder.name); updatePathDisplay(); loadFolders(currentCid); }; foldersList.appendChild(folderItem); }); } function updatePathDisplay() { const pathElement = document.getElementById('tm-current-path'); pathElement.textContent = currentPath.join(' / '); } document.getElementById('tm-folder-back').onclick = () => { if (cidStack.length > 0) { currentCid = cidStack.pop(); currentPath = pathStack.pop(); updatePathDisplay(); loadFolders(currentCid); } }; document.getElementById('tm-folder-cancel').onclick = () => { overlay.remove(); }; document.getElementById('tm-folder-select').onclick = () => { if (currentCid !== 0) { const cidInput = document.querySelector('#tm-cid-input'); if (cidInput) { cidInput.value = currentCid; } showToast(`已选择: ${currentPath.join(' / ')}`); } overlay.remove(); }; loadFolders(currentCid); updatePathDisplay(); } function addCopyLinkButton() { if (document.querySelector('#tm-copy-link-btn')) return; const btn = document.createElement('div'); btn.id = 'tm-copy-link-btn'; btn.textContent = '📋 复制链接'; Object.assign(btn.style, { position: 'fixed', bottom: '205px', right: '25px', backgroundColor: '#2196F3', color: '#fff', padding: '8px 12px', borderRadius: '8px', cursor: 'pointer', zIndex: 10000, fontWeight: 'bold', boxShadow: '0 4px 12px rgba(0,0,0,0.2)', textAlign: 'center', display: 'inline-block' }); btn.onclick = function() { const currentUrl = window.location.href; navigator.clipboard.writeText(currentUrl).then(() => { showToast('✅ 链接已复制到剪贴板'); }).catch(err => { showToast('❌ 复制失败: ' + err); }); }; document.body.appendChild(btn); } function removeCopyLinkButton() { const btn = document.querySelector('#tm-copy-link-btn'); if (btn && btn.parentNode) { btn.parentNode.removeChild(btn); } } function addSettingsButton() { if (document.querySelector('#tm-settings-btn')) return; const btn = document.createElement('div'); btn.id = 'tm-settings-btn'; btn.textContent = '⚙️ 115设置'; Object.assign(btn.style, { position: 'fixed', bottom: '160px', right: '26px', backgroundColor: '#2196F3', color: '#fff', padding: '8px 12px', borderRadius: '8px', cursor: 'pointer', zIndex: 10000, fontWeight: 'bold', boxShadow: '0 4px 12px rgba(0,0,0,0.2)' }); btn.onclick = showSettingsModal; document.body.appendChild(btn); } function copyTo115() { const cookie = GM_getValue('cookie'); const target_cid = GM_getValue('target_cid'); if (!cookie) { showToast('⚠️ 请先设置Cookie', 3000); showSettingsModal(); return; } if (!target_cid) { showToast('⚠️ 请先设置目标文件夹CID', 3000); showSettingsModal(); return; } const share_link = location.href; const share_code_match = share_link.match(/\/s\/([^?]+)/); const password_match = share_link.match(/password=([^&]{4})/); if (!share_code_match || !password_match) { showToast('❌ 无法解析分享链接或密码', 3000); return; } const share_code = share_code_match[1]; const receive_code = password_match[1]; GM_xmlhttpRequest({ method: "POST", url: "https://proapi.115.com/android/2.0/share/receive", headers: { "Cookie": cookie, "Content-Type": "application/x-www-form-urlencoded" }, data: `share_code=${encodeURIComponent(share_code)}&receive_code=${encodeURIComponent(receive_code)}&cid=${encodeURIComponent(target_cid)}&is_check=0`, onload: function(response) { try { const responseData = JSON.parse(response.responseText); if (responseData.errno === 4100024) { showToast('⚠️ 你已经转存过该文件'); } else if (responseData.state === true) { showToast('✅ 转存成功!'); } else { showToast('❌ 转存失败: ' + (responseData.error || response.responseText)); } } catch (e) { showToast('❌ 响应解析失败: ' + response.responseText); console.error('Response parse error:', e, response.responseText); } }, onerror: function(error) { showToast('❌ 转存接口调用失败'); console.error(error); } }); } function addCustomButton() { const original1 = document.querySelector('#js-share_save3'); if (original1 && !document.querySelector('#tm-copy-save-btn1')) { const button = original1.cloneNode(true); button.id = 'tm-copy-save-btn1'; button.removeAttribute('href'); button.removeAttribute('onclick'); button.textContent = '一键转存'; button.style.backgroundColor = '#4CAF50'; button.style.color = '#fff'; button.style.borderColor = '#4CAF50'; button.onclick = copyTo115; original1.parentNode.insertBefore(button, original1.nextSibling); } const original2 = document.querySelector('a[btn="save"]'); if (original2 && !document.querySelector('#tm-copy-save-btn2')) { const button = document.createElement('a'); button.id = 'tm-copy-save-btn2'; button.className = original2.className; button.innerHTML = `<i class="icon-operate ifo-saveto"></i><span>一键转存</span>`; button.style.backgroundColor = '#4CAF50'; button.style.color = '#fff'; button.style.borderColor = '#4CAF50'; button.style.cursor = 'pointer'; button.onclick = copyTo115; original2.parentNode.insertBefore(button, original2.nextSibling); } const original3 = document.querySelector('a[btn="confirm"].button.btn-large'); if (original3 && !document.querySelector('#tm-copy-save-btn3')) { const button = document.createElement('a'); button.id = 'tm-copy-save-btn3'; button.className = 'button btn-large'; button.innerHTML = '<span>一键转存</span>'; button.style.backgroundColor = '#4CAF50'; button.style.color = '#fff'; button.style.borderColor = '#4CAF50'; button.style.marginTop = '-15px'; button.style.display = 'block'; button.style.cursor = 'pointer'; button.onclick = copyTo115; original3.parentNode.appendChild(document.createElement('br')); original3.parentNode.appendChild(button); } } const observer = new MutationObserver(addCustomButton); observer.observe(document.body, {childList: true, subtree: true}); if (GM_getValue('copy_link_enabled', false)) { addCopyLinkButton(); } addCustomButton(); addSettingsButton(); })();