步骤示例:在 115 重命名弹窗里输入番号 WAAA-580 → 点“加载” → 脚本会自动从抓信息并翻译标题 → 自动填好文件名 → 点击“确定”完成。
主要功能(简单版):自动抓取番号/演员/标题。自动把标题翻译并拼成格式化文件名(默认格式为:番号+演员+翻译结果)适配旧版本和新版本115 页面如果有自己的命名规则可以丢给 AI 修改,非常方便

注意:需要自己自行替换腾讯翻译的 SECRET_ID/SECRET_KEY// ==UserScript==// [url=home.php?mod=space&uid=264574]@name[/url] 115网盘重命名辅助
// @namespace http://tampermonkey.net/
// [url=home.php?mod=space&uid=361048]@Version[/url] 4.4
// @description 兼容旧版 .file-rename 与新版 data-dialog="true" 弹窗;优化新版 modal 注入位置与布局,避免遮挡标题/输入区。保留 Javbus 爬取与腾讯云翻译功能。
// @author You
// @match https://*.115.com/*
// @grant GM_xmlhttpRequest
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @connect tmt.tencentcloudapi.com
// @connect javbus.com
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
try {
console.info('[gm-115-rename] start v4.4');
/********** 配置区:请注意不要在公共场合泄露 SECRET_ID/SECRET_KEY **********/
const TENCENT_TRANSLATE_API = "https://tmt.tencentcloudapi.com";
const SECRET_ID = "AK****t3E";
const SECRET_KEY = "4****z";
/*****************************************************************************/
const INJECT_ATTR = 'data-gm-115-rename-helper';
/***** 强健写入函数(支持受控 React/Vue 输入) *****/
const setValueAndNotify = (el, value) => {
if (!el) return;
try {
const proto = Object.getPrototypeOf(el);
const desc = Object.getOwnPropertyDescriptor(proto, 'value')
|| Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')
|| Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value');
if (desc && desc.set) {
desc.set.call(el, value);
} else {
el.value = value;
}
try { el.setAttribute('value', value); } catch (e) { /* ignore */ }
const ie = new InputEvent('input', { bubbles: true, cancelable: true, composed: true, data: value });
el.dispatchEvent(ie);
el.dispatchEvent(new Event('change', { bubbles: true }));
try { el.blur && el.blur(); } catch (e) { /* ignore */ }
console.debug('[gm-115-rename] wrote value to', el, value);
} catch (e) {
console.warn('[gm-115-rename] setValueAndNotify error', e);
}
};
/***** 腾讯云翻译(TC3 HMAC-SHA256 签名) *****/
const translateText = (text, source = "auto", target = "zh") => {
const now = new Date();
const date = now.toISOString().slice(0, 10);
const timestamp = Math.floor(now.getTime() / 1000);
const requestPayload = { SourceText: text, Source: source, Target: target, ProjectId: 0 };
const httpRequestMethod = "POST";
const canonicalHeaders = `content-type:application/json\nhost:tmt.tencentcloudapi.com\n`;
const signedHeaders = "content-type;host";
const hashedRequestPayload = CryptoJS.SHA256(JSON.stringify(requestPayload)).toString();
const canonicalRequest = `${httpRequestMethod}\n/\n\n${canonicalHeaders}\n${signedHeaders}\n${hashedRequestPayload}`;
const hashedCanonicalRequest = CryptoJS.SHA256(canonicalRequest).toString();
const stringToSign = `TC3-HMAC-SHA256\n${timestamp}\n${date}/tmt/tc3_request\n${hashedCanonicalRequest}`;
const kDate = CryptoJS.HmacSHA256(date, `TC3${SECRET_KEY}`);
const kService = CryptoJS.HmacSHA256("tmt", kDate);
const kSigning = CryptoJS.HmacSHA256("tc3_request", kService);
const signature = CryptoJS.HmacSHA256(stringToSign, kSigning).toString();
const authorization = `TC3-HMAC-SHA256 Credential=${SECRET_ID}/${date}/tmt/tc3_request, SignedHeaders=${signedHeaders}, Signature=${signature}`;
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: TENCENT_TRANSLATE_API,
headers: {
"Content-Type": "application/json",
"Authorization": authorization,
"X-TC-Action": "TextTranslate",
"X-TC-Timestamp": timestamp.toString(),
"X-TC-Version": "2018-03-21",
"X-TC-Region": "ap-beijing",
},
data: JSON.stringify(requestPayload),
onload: (response) => {
try {
const result = JSON.parse(response.responseText || '{}');
if (result.Response && result.Response.TargetText != null) {
resolve(result.Response.TargetText);
} else {
reject('翻译返回格式异常: ' + (response.responseText || 'empty'));
}
} catch (e) {
reject('翻译解析失败: ' + e);
}
},
onerror: () => reject('翻译请求失败(网络)'),
});
});
};
/***** 爬取 Javbus 页面(容错多种 DOM 结构) *****/
const fetchJavbusContent = (fileCode) => {
const url = `https://www.javbus.com/${encodeURIComponent(fileCode)}`;
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
headers: { "User-Agent": "Mozilla/5.0" },
onload: (response) => {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, "text/html");
const codeEl = doc.querySelector("div.row.movie p:first-of-type span:nth-of-type(2)")
|| doc.querySelector(".header .identifier")
|| doc.querySelector("span#aka")
|| null;
const titleEl = doc.querySelector("body > div.container > h3")
|| doc.querySelector("h3.movie-title")
|| doc.querySelector('meta[property="og:title"]')
|| null;
const actorEl = doc.querySelector("div.row.movie p:last-of-type")
|| doc.querySelector(".star-name")
|| null;
const code = (codeEl && (codeEl.textContent || codeEl.getAttribute && codeEl.getAttribute('content')) && (codeEl.textContent || codeEl.getAttribute('content')).trim()) || fileCode;
const title = (titleEl && (titleEl.textContent || titleEl.getAttribute && titleEl.getAttribute('content')) && (titleEl.textContent || titleEl.getAttribute('content')).trim()) || "标题未找到";
const actorName = (actorEl && actorEl.textContent && actorEl.textContent.trim()) || "演员未找到";
resolve({ code, title, actorName });
} catch (e) {
reject('页面解析失败: ' + e);
}
},
onerror: () => reject('爬取失败(网络)'),
});
});
};
/***** 旧版注入(.file-rename) *****/
const addCustomElementsOld = (targetElement) => {
if (!targetElement) return;
if (targetElement.getAttribute && targetElement.getAttribute(INJECT_ATTR)) {
console.debug('[gm-115-rename] old already injected');
return;
}
try { targetElement.setAttribute(INJECT_ATTR, '1'); } catch (e) {}
console.debug('[gm-115-rename] injecting into old target', targetElement);
const container = document.createElement('div');
container.style.cssText = 'display:flex;align-items:center;margin-top:10px;gap:8px;';
const inputBox = document.createElement('input');
inputBox.type = 'text';
inputBox.placeholder = '输入番号(例如:MIDV-654)';
inputBox.style.cssText = 'flex:1;height:36px;padding:0 10px;border:1px solid #dcdcdc;border-radius:4px;font-size:14px;color:#333;';
const loadButton = document.createElement('button');
loadButton.textContent = '加载';
loadButton.style.cssText = 'height:36px;padding:0 14px;background:#108ee9;color:#fff;border:none;border-radius:4px;cursor:pointer;';
loadButton.addEventListener('click', async () => {
const fileCode = inputBox.value.trim();
if (!fileCode) return alert('请输入有效的番号!');
loadButton.disabled = true;
const original = loadButton.textContent;
loadButton.textContent = '加载中...';
try {
const { code, title, actorName } = await fetchJavbusContent(fileCode);
const titleForTranslation = title.replace(new RegExp(code, 'gi'), '').trim();
const translatedTitle = await translateText(titleForTranslation);
const formattedName = `${code} ${actorName} ${translatedTitle}`;
const nameCover = targetElement.querySelector && targetElement.querySelector("p[rel='name_cover']");
if (nameCover) {
nameCover.textContent = formattedName;
const textArea = targetElement.querySelector && targetElement.querySelector("textarea[rel='txt']");
if (textArea) setValueAndNotify(textArea, formattedName);
} else {
try { navigator.clipboard && navigator.clipboard.writeText(formattedName); } catch (e) {}
alert('未找到旧版 name_cover,已复制到剪贴板:\n' + formattedName);
}
} catch (err) {
alert(err);
console.warn('[gm-115-rename] old load error', err);
} finally {
loadButton.textContent = original;
loadButton.disabled = false;
}
});
container.appendChild(inputBox);
container.appendChild(loadButton);
try {
targetElement.appendChild(container);
} catch (e) {
try { targetElement.insertAdjacentElement('afterend', container); } catch (e2) { console.error(e2); }
}
};
/***** 新版 modal 注入(插入到 modal body,避免 header 遮挡) *****/
const addCustomElementsForModal = (modalRoot) => {
if (!modalRoot) return;
if (modalRoot.getAttribute && modalRoot.getAttribute(INJECT_ATTR)) {
console.debug('[gm-115-rename] modal already injected');
return;
}
try { modalRoot.setAttribute(INJECT_ATTR, '1'); } catch (e) {}
console.debug('[gm-115-rename] injecting into modal (body insertion)');
// 优先定位 modal body(避免 header)
const bodySelectors = [
'.px-6.py-6', // exact body in your example
'div.flex-1.overflow-y-auto', // common body container
'div[class*="overflow-y-auto"]', // fallback
'.modal-body', // generic
];
let contentBlock = null;
for (const sel of bodySelectors) {
try {
const found = modalRoot.querySelector(sel);
if (found) { contentBlock = found; break; }
} catch (e) { /* ignore invalid selectors */ }
}
// 最后回退,但尽量避免 header:选取 .px-6.py-4(header)只在无其它选择时使用
if (!contentBlock) {
const px6 = modalRoot.querySelector('.px-6');
const headerCheck = px6 && px6.classList && px6.classList.contains('py-4'); // likely header
if (px6 && !headerCheck) contentBlock = px6;
else contentBlock = modalRoot; // 最后回退到 modalRoot
}
// 找到真实用于填写文件名的输入(优先匹配你提到的 class)
const filenameInput = modalRoot.querySelector('input.flex-1.outline-none.text-sm')
|| modalRoot.querySelector('input[class*="flex-1"][class*="outline-none"][class*="text-sm"]')
|| modalRoot.querySelector('input[placeholder*="请输入文件名"]')
|| modalRoot.querySelector('input[type="text"]');
// 构造控件
const outer = document.createElement('div');
outer.className = 'gm-rename-modal-row';
outer.style.cssText = [
'display:flex',
'gap:12px',
'align-items:center',
'margin:12px 0',
'padding:10px',
'border-radius:8px',
'background:linear-gradient(180deg, rgba(255,255,255,0.9), rgba(250,250,250,0.95))',
'border:1px solid rgba(0,0,0,0.06)',
'box-shadow:0 2px 8px rgba(15,23,42,0.04)',
'width:100%',
'box-sizing:border-box'
].join(';');
const left = document.createElement('div');
left.style.cssText = 'flex:1; display:flex; gap:8px; align-items:center;';
const label = document.createElement('div');
label.textContent = '番号:';
label.style.cssText = 'min-width:56px; color:#111827; font-size:14px; font-weight:500;';
const numberInput = document.createElement('input');
numberInput.type = 'text';
numberInput.placeholder = '例如:MIDV-654';
numberInput.style.cssText = [
'flex:1',
'height:44px',
'padding:0 12px',
'border-radius:6px',
'border:1px solid #e6eef8',
'box-sizing:border-box',
'font-size:14px',
'outline:none',
'background:#fff'
].join(';');
const right = document.createElement('div');
right.style.cssText = 'display:flex; gap:8px; align-items:center;';
const helper = document.createElement('button');
helper.type = 'button';
helper.title = '清除';
helper.innerHTML = '✕';
helper.style.cssText = [
'height:38px',
'width:38px',
'display:inline-flex',
'align-items:center',
'justify-content:center',
'border-radius:6px',
'border:1px solid #e6eef8',
'background:#fff',
'cursor:pointer',
'color:#6b7280'
].join(';');
const loadButton = document.createElement('button');
loadButton.type = 'button';
loadButton.textContent = '加载';
loadButton.style.cssText = [
'height:44px',
'min-width:90px',
'padding:0 16px',
'border-radius:6px',
'border:none',
'cursor:pointer',
'background:#0b7ef0',
'color:#fff',
'font-weight:600'
].join(';');
// spinner
const spinnerSVG = '<svg width="16" height="16" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><circle cx="25" cy="25" r="20" stroke="#fff" stroke-width="5" fill="none" stroke-linecap="round" stroke-dasharray="31.4 31.4" transform="rotate(-90 25 25)"><animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.9s" repeatCount="indefinite"/></circle></svg>';
left.appendChild(label);
left.appendChild(numberInput);
right.appendChild(helper);
right.appendChild(loadButton);
outer.appendChild(left);
outer.appendChild(right);
// 插入:优先放在 body 的开头(afterbegin),避免插到 header 内
try {
if (contentBlock && contentBlock.insertAdjacentElement) {
contentBlock.insertAdjacentElement('afterbegin', outer);
} else {
modalRoot.appendChild(outer);
}
} catch (e) {
try { modalRoot.appendChild(outer); } catch (e2) { console.error(e2); }
}
helper.addEventListener('click', () => { numberInput.value = ''; numberInput.focus(); });
loadButton.addEventListener('click', async () => {
const fileCode = numberInput.value.trim();
if (!fileCode) return alert('请输入番号(例如:MIDV-654)');
const orig = loadButton.innerHTML;
loadButton.innerHTML = spinnerSVG;
loadButton.disabled = true;
helper.disabled = true;
try {
const { code, title, actorName } = await fetchJavbusContent(fileCode);
const titleForTranslation = title.replace(new RegExp(code, 'gi'), '').trim();
const translatedTitle = await translateText(titleForTranslation);
const formattedName = `${code} ${actorName} ${translatedTitle}`;
if (filenameInput) {
setValueAndNotify(filenameInput, formattedName);
} else {
const oldTarget = document.querySelector('.file-rename');
if (oldTarget) {
const nameCover = oldTarget.querySelector && oldTarget.querySelector("p[rel='name_cover']");
if (nameCover) {
nameCover.textContent = formattedName;
const ta = oldTarget.querySelector && oldTarget.querySelector("textarea[rel='txt']");
if (ta) setValueAndNotify(ta, formattedName);
}
} else {
try { navigator.clipboard && navigator.clipboard.writeText(formattedName); } catch (e) {}
alert('生成的重命名已复制到剪贴板:\n' + formattedName);
}
}
} catch (err) {
alert(err);
console.warn('[gm-115-rename] modal load error', err);
} finally {
loadButton.innerHTML = orig;
loadButton.disabled = false;
helper.disabled = false;
}
});
numberInput.addEventListener('keydown', (ev) => {
if (ev.key === 'Enter') {
ev.preventDefault();
loadButton.click();
}
});
// 如果 modal 被移除,断开任何观察器(小优化)
const mo = new MutationObserver(() => {
if (!document.contains(modalRoot)) {
try { mo.disconnect(); } catch (e) {}
}
});
mo.observe(document.body, { childList: true, subtree: true });
};
/***** 扫描已有目标并注入 *****/
const scanExisting = () => {
try {
document.querySelectorAll && document.querySelectorAll('.file-rename').forEach(el => {
try { addCustomElementsOld(el); } catch (e) { console.warn(e); }
});
document.querySelectorAll && document.querySelectorAll('[data-dialog="true"]').forEach(el => {
try { addCustomElementsForModal(el); } catch (e) { console.warn(e); }
});
Array.from(document.querySelectorAll('div[role="dialog"], div[class*="fixed"]'))
.filter(el => el.querySelector && el.querySelector('input[placeholder*="请输入文件名"], input[type="text"]'))
.forEach(el => {
try { addCustomElementsForModal(el); } catch (e) { console.warn(e); }
});
} catch (e) {
console.warn('[gm-115-rename] scanExisting error', e);
}
};
/***** MutationObserver 监听动态插入 *****/
const observeDynamicElements = () => {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes && mutation.addedNodes.length) {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
const el = node;
if (el.classList && el.classList.contains('file-rename')) {
try { addCustomElementsOld(el); } catch (e) { console.warn(e); }
}
if (el.getAttribute && el.getAttribute('data-dialog') === 'true') {
try { addCustomElementsForModal(el); } catch (e) { console.warn(e); }
}
try {
el.querySelectorAll && el.querySelectorAll('.file-rename').forEach(sub => addCustomElementsOld(sub));
el.querySelectorAll && el.querySelectorAll('[data-dialog="true"]').forEach(sub => addCustomElementsForModal(sub));
} catch (e) { /* ignore */ }
});
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
console.debug('[gm-115-rename] MutationObserver started');
};
/***** 启动:先扫描再监听;前 10s 内短轮询提高命中率 *****/
const initialize = () => {
scanExisting();
observeDynamicElements();
let tries = 0;
const maxTries = 20;
const intervalId = setInterval(() => {
tries++;
scanExisting();
if (tries >= maxTries) {
clearInterval(intervalId);
console.debug('[gm-115-rename] initial scanning finished');
}
}, 500);
};
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
console.info('[gm-115-rename] initialized v4.4');
} catch (e) {
console.error('[gm-115-rename] fatal error', e);
}
})();複製代碼
还没有评论,来抢沙发吧。