// ==UserScript==
// @name 115分享页一键转存按钮
// @version 0.2
// @description 原按钮旁增加绿色一键转存按钮,调用115web接口转存,可自定义Cookie和目标文件夹ID,并保存设置,右下角可修改设置
// @author GPT
// @match *://115cdn.com/s/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @author 楠
// @license MIT
// @namespace https://greasyfork.org/users/1514724
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
function showToast(message, duration = 2500) {
const toast = document.createElement('div');
Object.assign(toast.style, {
position: 'fixed',
top: '110px',
right: '20px',
padding: '16px 24px',
background: 'linear-gradient(135deg, #4CAF50, #2E7D32)',
color: '#fff',
borderRadius: '12px',
boxShadow: '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 = '✓';
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 = '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 = '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);
}
function showSettingsModal() {
if (document.querySelector('#tm-settings-modal')) return;
const cookie = GM_getValue('cookie') || '';
const cid = GM_getValue('target_cid') || '';
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: '360px',
boxShadow: '0 6px 20px rgba(0,0,0,0.3)',
fontFamily: 'Arial, sans-serif'
});
modal.innerHTML = `
115 设置
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
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();
if (!newCookie || !newCid) {
showToast('⚠️ Cookie和CID不能为空');
return;
}
GM_setValue('cookie', newCookie);
GM_setValue('target_cid', newCid);
showToast('✅ 设置已保存');
overlay.remove();
};
}
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: '20px',
right: '20px',
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 || !target_cid) {
showToast('请先设置 Cookie 和目标文件夹CID', 3000);
showSettingsModal();
return;
}
const share_link = location.href;
const share_code_match = share_link.match(/\/s\/([^?]+)/);
const receive_code_match = share_link.match(/password=([^&]+)/);
if(!share_code_match || !receive_code_match) {
showToast('无法解析分享链接或密码', 3000);
return;
}
const share_code = share_code_match[1];
const receive_code = receive_code_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 = `一键转存`;
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 = '一键转存';
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});
addCustomButton();
addSettingsButton();
})();