// ==UserScript==
// @name 115Rename2025
// @namespace http://tampermonkey.net/
// @version 1.4.1
// @description 115网盘视频整理工具:1.根据番号查询并重命名文件 2.支持javbus/avmoo查询 3.演员归档自动分类 4.可设置归档根目录 5.支持中文字幕和无码标记 6.支持文件夹归档 7.增强用户体验的通知提示 8.性能优化 9.评分同步 10.支持批量处理
// @author db117 wusuowei111 Chunluren
// @include https://115.com/*
// @icon https://115.com/favicon.ico
// @domain javbus.com
// @domain avmoo.host
// @domain avsox.host
// @domain javdb.com
// @domain fc2ppvdb.com
// @connect javdb.com
// @connect fc2ppvdb.com
// @grant GM_notification
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
/*
* 更新日志:
* v1.4.1 (2024-05-xx):
* 1. 实现多网站轮询查询功能,优先javbus,失败后查询javdb (取代原有4个改名按钮)
* 2. 新增FC2-PPV格式支持,针对FC2视频自动到fc2ppvdb.com查询
* 3. 改进番号提取逻辑,支持更多格式:
* - FC2-PPV、PPV-xxxxx等FC2系列格式
* - 带空格的番号格式,如"SONE- 101- UC" → "SONE-101"
* - 日期格式番号,如"041117_510"等
* 4. 自动过滤文件名中的常见后缀(UC、C、U、字幕等)
* 5. 针对不同网站查询失败情况提供更友好的错误提示
* 6. 增强域名前缀清理,支持hhd800.com@等格式
*/
(function () {
// 添加一个独特的标识符,确保元素唯一
const rootInfoId = 'archive-root-info-' + Date.now();
// 在执行任何操作前,先清除可能存在的所有归档根目录信息元素
function cleanupExistingRootInfo() {
try {
// 清除主文档中的元素
const mainDocElements = document.querySelectorAll('[id^="archive-root-info"]');
if (mainDocElements.length > 0) {
console.log(`清理主文档中发现的${mainDocElements.length}个归档根目录信息元素`);
mainDocElements.forEach(element => element.remove());
}
// 尝试清除所有iframe中的元素
const iframes = document.querySelectorAll('iframe');
iframes.forEach(iframe => {
try {
if (iframe.contentDocument) {
const iframeElements = iframe.contentDocument.querySelectorAll('[id^="archive-root-info"]');
if (iframeElements.length > 0) {
console.log(`清理iframe中发现的${iframeElements.length}个归档根目录信息元素`);
iframeElements.forEach(element => element.remove());
}
}
} catch (e) {
// 跨域iframe可能会抛出异常,忽略
console.log("无法访问iframe内容,可能是跨域限制");
}
});
} catch (e) {
console.error("清理归档根目录信息元素时出错:", e);
}
}
// 立即执行清理
cleanupExistingRootInfo();
// 添加样式,使用更明确的选择器
const notificationStyle = `
<style>
/* 归档根目录信息样式 */
[id^="archive-root-info"] {
position: fixed;
top: 20px;
right: 20px;
max-width: 300px;
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 20px;
border-radius: 4px;
z-index: 9998;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border-left: 4px solid #1890ff;
}
/* 临时通知样式 */
.custom-notification {
position: fixed;
top: 80px; /* 位于根目录通知下方 */
right: 20px;
max-width: 300px;
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 20px;
border-radius: 4px;
z-index: 9999;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
opacity: 0;
transform: translateY(-10px);
}
.custom-notification.success {
border-left: 4px solid #52c41a;
}
.custom-notification.error {
border-left: 4px solid #f5222d;
}
.custom-notification.info {
border-left: 4px solid #1890ff;
}
.custom-notification.show {
opacity: 1;
transform: translateY(0);
}
</style>`;
// 添加样式到页面
$('head').append(notificationStyle);
// 默认使用根目录ID
const ROOT_DIR_CID = "0"; // 115网盘根目录的ID为"0"
// 尝试从存储中获取根目录ID,如果不存在则为null(表示未设置)
let archiveRootCid = GM_getValue("archiveRootCid", null);
let archiveRootName = GM_getValue("archiveRootName", null);
// 简单的页面内通知函数
window.showPageNotification = function(message, type = 'info', duration = 3000) {
// 为不同类型通知设置不同的默认持续时间
if (duration === 3000) {
if (type === 'success') duration = 3000; // 成功通知更短
else if (type === 'error') duration = 5000; // 错误通知更长
else if (type === 'info') duration = 3000; // 信息通知标准
}
const notificationId = 'custom-notification-' + Date.now();
const notificationHtml = `<div id="${notificationId}" class="custom-notification ${type}">${message}</div>`;
// 添加通知到页面
$('body').append(notificationHtml);
// 显示通知
setTimeout(() => {
$(`#${notificationId}`).addClass('show');
}, 10);
// 自动关闭
setTimeout(() => {
$(`#${notificationId}`).removeClass('show');
setTimeout(() => {
$(`#${notificationId}`).remove();
}, 300);
}, duration);
};
// 简单函数:显示归档根目录信息
function showArchiveRootInfo() {
// 确保清理可能存在的其他实例
cleanupExistingRootInfo();
// 根据是否设置了根目录,显示不同的信息
let rootDirMessage;
if (archiveRootCid && archiveRootName) {
rootDirMessage = `当前归档根目录: "${archiveRootName}"`;
} else {
rootDirMessage = "当前无归档根目录,将使用115网盘根目录";
}
// 创建信息元素,使用带时间戳的ID确保唯一性
const infoElement = $(`<div id="${rootInfoId}" class="archive-root-info">${rootDirMessage}</div>`);
// 确保只添加到主文档,不添加到iframe
if (window.self === window.top) {
$('body').append(infoElement);
console.log("在主文档中显示归档根目录信息: " + rootDirMessage);
}
}
// 设置延迟计时器
let rootInfoTimer = null;
// 只在主窗口(非iframe)中显示一次
function initializeRootInfo() {
// 只在主窗口中初始化,避免在iframe中创建
if (window.self !== window.top) {
console.log("处于iframe中,跳过显示归档根目录信息");
return;
}
// 清除可能存在的计时器
if (rootInfoTimer) {
clearTimeout(rootInfoTimer);
}
// 设置新的计时器
rootInfoTimer = setTimeout(function() {
showArchiveRootInfo();
rootInfoTimer = null; // 清除引用
}, 2000);
}
// 在页面完全加载后显示
$(window).on('load', function() {
initializeRootInfo();
});
// 如果window.load事件已触发,直接初始化
if (document.readyState === 'complete') {
initializeRootInfo();
}
// 不再在document.ready时创建,避免重复
// 按钮
let rename_list = `
<li id="rename_list">
<a id="rename_all_multi" class="mark" href="javascript:;">改名(多网站轮询)</a>
<a id="rename_all_multi_date" class="mark" href="javascript:;">改名(多网站轮询)_时间</a>
<a id="archive_to_folder" class="mark" href="javascript:;">归档至文件夹</a>
<a id="set_archive_root" class="mark" href="javascript:;">设置为归档根目录</a>
<a id="get_javdb_rating" class="mark" href="javascript:;">获取javdb评分</a>
</li>
`;
/**
* 添加按钮的定时任务
*/
let interval = setInterval(buttonInterval, 1000);
// javbus
let javbusBase = "https://www.javbus.com/";
// 直接访问番号页面 (使用番号直接访问)
let javbusDirectAccess = javbusBase;
// 无码页面基础URL
let javbusUncensoredBase = javbusBase + "uncensored/";
// javdb
let javdbBase = "https://javdb.com/";
let javdbSearchBase = "https://javdb.com/search?q=";
let javdbDirectAccess = "https://javdb.com/";
// fc2ppvdb
let fc2ppvdbBase = "https://fc2ppvdb.com/articles/";
// avmoo
// 有码
let avmooSearch = "https://avmoo.host/cn/search/";
// 无码
let avmooUncensoredSearch = "https://avsox.host/cn/search/";
'use strict';
/**
* 添加按钮定时任务(检测到可以添加时添加按钮)
*/
function buttonInterval() {
let open_dir = $("div#js_float_content li[val='open_dir']");
if (open_dir.length !== 0 && $("li#rename_list").length === 0) {
open_dir.before(rename_list);
$("a#rename_all_multi").click(
function () {
rename(rename_multi, false);
});
$("a#rename_all_multi_date").click(
function () {
rename(rename_multi, true);
});
$("a#archive_to_folder").click(
function () {
archiveToActorFolder();
});
$("a#set_archive_root").click(
function () {
setArchiveRoot();
});
$("a#get_javdb_rating").click(
function () {
getJavdbRating();
});
// 根据是否设置了根目录,显示不同的日志
if (archiveRootCid) {
console.log("添加按钮,当前归档根目录: " + archiveRootName + " (CID: " + archiveRootCid + ")");
} else {
console.log("添加按钮,未设置归档根目录,将使用115网盘根目录");
}
// 按钮添加时不再创建根目录信息,依赖页面加载时的创建
console.log("按钮已添加,根目录信息状态: " + (window.rootInfoDisplayed ? "已显示" : "未显示"));
// 结束定时任务
clearInterval(interval);
}
}
/**
* 设置归档根目录
* 获取选中的文件夹信息并保存为归档根目录
*/
function setArchiveRoot() {
// 获取选中的文件夹
let selectedFolder = $("iframe[rel='wangpan']")
.contents()
.find("li.selected");
// 检查是否只选择了一个文件夹
if (selectedFolder.length !== 1) {
GM_notification(getDetails("请只选择一个文件夹", "设置失败"));
showPageNotification("请只选择一个文件夹", 'error', 3000);
console.log("设置归档根目录失败:选择了 " + selectedFolder.length + " 个项目,请只选择一个文件夹");
return;
}
let $item = $(selectedFolder[0]);
// 检查是否是文件夹
let file_type = $item.attr("file_type");
if (file_type !== "0") {
GM_notification(getDetails("请选择文件夹类型", "设置失败"));
showPageNotification("请选择文件夹类型", 'error', 3000);
console.log("设置归档根目录失败:选中的不是文件夹");
return;
}
// 获取文件夹ID和名称
let cid = $item.attr("cate_id");
let name = $item.attr("title");
if (cid) {
// 保存到GM存储中
GM_setValue("archiveRootCid", cid);
GM_setValue("archiveRootName", name);
// 更新当前变量
archiveRootCid = cid;
archiveRootName = name;
// 更新归档根目录显示
cleanupExistingRootInfo();
showArchiveRootInfo();
GM_notification(getDetails(name, "归档根目录设置成功"));
showPageNotification(`归档根目录设置成功: "${name}"`, 'success', 5000);
console.log("归档根目录设置成功: " + name + " (CID: " + cid + ")");
} else {
GM_notification(getDetails("无法获取文件夹ID", "设置失败"));
showPageNotification("无法获取文件夹ID", 'error', 3000);
console.log("设置归档根目录失败:无法获取文件夹ID");
}
}
/**
* 执行改名方法
* @param call 回调函数
* @param addDate 是否添加时间
*/
function rename(call, addDate) {
// 获取选中的文件数量
let selectedCount = $("iframe[rel='wangpan']").contents().find("li.selected").length;
showPageNotification(`开始处理 ${selectedCount} 个文件...`, 'info', 3000);
// 记录成功处理的数量
let successCount = 0;
// 获取所有已选择的文件
let list = $("iframe[rel='wangpan']")
.contents()
.find("li.selected")
.each(function (index, v) {
let $item = $(v);
// 原文件名称
let file_name = $item.attr("title");
// 文件类型
let file_type = $item.attr("file_type");
console.log("处理文件: " + file_name);
// 文件id
let fid;
// 后缀名
let suffix;
if (file_type === "0") {
// 文件夹
fid = $item.attr("cate_id");
} else {
// 文件
fid = $item.attr("file_id");
// 处理后缀
let lastIndexOf = file_name.lastIndexOf('.');
if (lastIndexOf !== -1) {
suffix = file_name.substr(lastIndexOf, file_name.length);
}
}
if (fid && file_name) {
// 先检查是否是FC2或PPV格式的文件
let isFC2 = /FC2/i.test(file_name) || /PPV[-_\s]?\d{5,7}/i.test(file_name);
if (isFC2) {
console.log("检测到可能的FC2格式文件: " + file_name);
// 尝试直接从文件名提取FC2编号
let fc2Number = extractFC2Number(file_name);
if (fc2Number) {
console.log("直接从文件名提取FC2编号: " + fc2Number);
let fc2Code = "FC2-PPV-" + fc2Number;
call(fid, fc2Code, suffix, false, false, addDate, function() {
successCount++;
if (successCount === selectedCount) {
showPageNotification(`所有 ${successCount} 个文件处理完成`, 'success', 5000);
}
});
return;
} else if (/PPV[-_\s]?(\d{5,7})/i.test(file_name)) {
// 特殊处理PPV-XXXXX格式
let ppvMatch = file_name.match(/PPV[-_\s]?(\d{5,7})/i);
if (ppvMatch && ppvMatch[1]) {
console.log("从PPV格式提取FC2编号: " + ppvMatch[1]);
let fc2Code = "FC2-PPV-" + ppvMatch[1];
call(fid, fc2Code, suffix, false, false, addDate, function() {
successCount++;
if (successCount === selectedCount) {
showPageNotification(`所有 ${successCount} 个文件处理完成`, 'success', 5000);
}
});
return;
}
}
}
// 不是FC2格式或者提取FC2编号失败,尝试常规番号提取
let fh = getVideoCode(file_name);
if (fh) {
// 校验是否是中文字幕
let chineseCaptions = checkChineseCaptions(fh, file_name);
let Uncensored = checkUncensored(fh, file_name);
// 执行查询
call(fid, fh, suffix, chineseCaptions, Uncensored, addDate, function() {
// 成功回调
successCount++;
// 如果所有文件都处理完毕,显示总结通知
if (successCount === selectedCount) {
showPageNotification(`所有 ${successCount} 个文件处理完成`, 'success', 5000);
}
});
} else {
console.log("无法从文件名中提取到番号: " + file_name);
}
}
});
}
/**
* 提取FC2编号
* @param title 文件名或番号
* @returns {string|null} FC2编号,如果不是FC2格式则返回null
*/
function extractFC2Number(title) {
// 格式1: FC2-PPV-1234567
let match = title.match(/FC2[-\s]?PPV[-\s]?(\d{5,7})/i);
if (match && match[1]) {
return match[1];
}
// 格式2: FC2PPV_1234567 或 FC2PPV-1234567
match = title.match(/FC2PPV[_-]?(\d{5,7})/i);
if (match && match[1]) {
return match[1];
}
// 格式3: 单纯的FC2 1234567
match = title.match(/FC2\s+(\d{5,7})/i);
if (match && match[1]) {
return match[1];
}
// 格式4: 单纯的PPV-1234567
match = title.match(/PPV[-_\s]?(\d{5,7})/i);
if (match && match[1]) {
return match[1];
}
return null;
}
/**
* 多网站轮询查询重命名
* 先尝试javbus,然后javdb,对于FC2-PPV格式尝试fc2ppvdb
*/
function rename_multi(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback) {
console.log("开始多网站轮询查询: " + fh);
// 检查番号是否为FC2格式
let isFC2 = /FC2/i.test(fh) || /^PPV-\d{5,7}$/i.test(fh);
if (isFC2) {
// 提取FC2编号
let fc2Number;
if (/FC2/i.test(fh)) {
fc2Number = extractFC2Number(fh);
} else if (/^PPV-(\d{5,7})$/i.test(fh)) {
// 处理PPV-XXXXX格式
let ppvMatch = fh.match(/^PPV-(\d{5,7})$/i);
fc2Number = ppvMatch ? ppvMatch[1] : null;
}
if (fc2Number) {
console.log("检测到FC2格式,提取编号: " + fc2Number);
requestFC2(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback);
return;
}
}
// 首先尝试javbus
requestMultiSource(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback, "javbus");
}
/**
* 多来源查询处理
* @param fid 文件ID
* @param fh 番号
* @param suffix 后缀
* @param chineseCaptions 是否有中文字幕
* @param Uncensored 是否无码
* @param addDate 是否添加日期
* @param callback 回调函数
* @param source 当前尝试的来源
*/
function requestMultiSource(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback, source) {
if (source === "javbus") {
console.log("尝试使用javbus查询: " + fh);
// 使用javbus查询,如果失败则尝试javdb
requestJavbus(fid, fh, suffix, chineseCaptions, Uncensored, addDate, javbusDirectAccess, function() {
// 成功回调
if (typeof callback === 'function') {
callback();
}
}, 0, function() {
// 失败回调,尝试javdb
console.log("javbus查询失败,尝试javdb: " + fh);
requestMultiSource(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback, "javdb");
});
} else if (source === "javdb") {
console.log("尝试使用javdb查询: " + fh);
requestJavdb(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback);
} else {
// 所有来源都失败
console.log("所有来源查询失败: " + fh);
GM_notification(getDetails(fh, "所有来源查询失败"));
showPageNotification(`无法在所有网站找到"${fh}"的信息`, 'error', 3000);
if (typeof callback === 'function') {
callback(); // 确保回调函数被执行
}
}
}
/**
* 请求javbus,并请求115进行改名
* @param fid 文件id
* @param fh 番号
* @param suffix 后缀
* @param chineseCaptions 是否有中文字幕
* @param Uncensored 是否无码
* @param url 请求地址
* @param addDate 是否添加时间
* @param callback 成功回调
* @param uncensoredAttempt 是否已尝试过无码查询
* @param failCallback 失败回调
*/
function requestJavbus(fid, fh, suffix, chineseCaptions, Uncensored, addDate, url, callback, uncensoredAttempt = 0, failCallback) {
GM_xmlhttpRequest({
method: "GET",
url: url + fh,
onload: xhr => {
// 匹配标题
let response = $(xhr.responseText);
// 尝试不同方式获取标题
let title = null;
// 方法1: 从详情页面的标题获取
let h3Title = response.find("h3");
if (h3Title.length > 0) {
title = h3Title.text().trim();
// 删除番号部分(如果有),保留后面的标题
if (title.toUpperCase().indexOf(fh.toUpperCase()) === 0) {
title = title.substring(fh.length).trim();
}
console.log("从h3获取标题: " + title);
}
// 方法2: 如果方法1没有获取到标题,尝试从img标签获取
if (!title || title.length === 0) {
title = response.find("div.photo-frame img").attr("title");
console.log("从img标签title属性获取标题: " + title);
}
// 方法3: 如果前两种方法都失败,尝试获取页面标题
if (!title || title.length === 0) {
title = response.find("title").text().trim();
// 清理标题中可能的额外信息
if (title.indexOf(" - JavBus") > 0) {
title = title.substring(0, title.indexOf(" - JavBus")).trim();
}
if (title.toUpperCase().indexOf(fh.toUpperCase()) === 0) {
title = title.substring(fh.length).trim();
}
console.log("从页面title获取标题: " + title);
}
// 时间
let date = response.find("div.photo-info date:last").html();
// 如果无法通过 date:last 获取日期,尝试其他方法
if (!date) {
console.log("未找到日期,尝试其他方法获取");
// 方法1: 查找带有日期标识的元素
let dateLabel = response.find("p.header:contains('発売日:')").next("p");
if (dateLabel.length > 0) {
date = dateLabel.text().trim();
console.log("从日期标签获取到日期: " + date);
}
// 方法2: 查找所有日期格式的文本
if (!date) {
let allText = response.text();
let dateMatch = allText.match(/\d{4}-\d{2}-\d{2}/);
if (dateMatch) {
date = dateMatch[0];
console.log("从页面文本匹配到日期: " + date);
}
}
} else {
console.log("找到日期: " + date);
}
if (title && title.length > 0) {
console.log("最终使用标题: " + title);
// 构建新名称
let newName = buildNewName(fh, suffix, chineseCaptions, Uncensored, title);
// 添加时间
if (addDate && date) {
// 标准化日期格式为YYYY-MM-DD
if (date.match(/\d{4}-\d{2}-\d{2}/)) {
// 已经是标准格式
newName = date + "_" + newName;
} else if (date.match(/\d{4}\/\d{2}\/\d{2}/)) {
// 转换斜杠格式为连字符格式
newName = date.replace(/\//g, "-") + "_" + newName;
} else {
// 其他格式,尝试提取数字
let numbers = date.match(/\d+/g);
if (numbers && numbers.length >= 3) {
let year = numbers[0].length === 4 ? numbers[0] : "20" + numbers[0];
let month = numbers[1].padStart(2, "0");
let day = numbers[2].padStart(2, "0");
let formattedDate = `${year}-${month}-${day}`;
console.log("格式化日期: " + date + " -> " + formattedDate);
newName = formattedDate + "_" + newName;
} else {
// 无法格式化,使用原始日期
newName = date + "_" + newName;
}
}
console.log("添加日期后的文件名: " + newName);
}
if (newName) {
// 修改名称
send_115(fid, newName, fh, callback);
}
} else if (url !== javbusUncensoredBase && uncensoredAttempt === 0) {
// 如果当前不是无码站点且未尝试过无码查询,则尝试一次无码查询
console.log("未找到标题,尝试一次无码页面查询: " + fh);
requestJavbus(fid, fh, suffix, chineseCaptions, Uncensored, addDate, javbusUncensoredBase, callback, 1, failCallback);
} else {
// 已经尝试过无码查询或当前就是无码查询,尝试下一个来源
console.log("javbus查询尝试完毕,无法获取标题: " + fh);
if (typeof failCallback === 'function') {
failCallback(); // 执行失败回调,尝试下一个来源
} else if (typeof callback === 'function') {
callback(); // 确保回调函数被执行
}
}
},
onerror: xhr => {
console.log("javbus请求失败: ", xhr);
if (typeof failCallback === 'function') {
failCallback(); // 执行失败回调,尝试下一个来源
} else if (typeof callback === 'function') {
callback(); // 确保回调函数被执行
}
}
})
}
/**
* 请求FC2PPV DB获取标题信息
*/
function requestFC2(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback) {
console.log("请求FC2PPV DB: " + fc2Number);
// 构建查询URL
const fc2Url = fc2ppvdbBase + fc2Number;
console.log("FC2查询URL: " + fc2Url);
// 显示查询进度通知
showPageNotification(`正在从FC2PPVDB查询: ${fc2Number}`, 'info', 2000);
GM_xmlhttpRequest({
method: "GET",
url: fc2Url,
timeout: 10000, // 10秒超时
onload: xhr => {
try {
if (xhr.status !== 200) {
console.log(`FC2PPV DB请求返回非200状态码: ${xhr.status}`);
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, "查询返回非200状态码");
return;
}
let response = $(xhr.responseText);
// 查找标题元素
let title = null;
let articleLink = response.find('a[href*="adult.contents.fc2.com"]');
if (articleLink.length > 0) {
title = articleLink.text().trim();
console.log("从FC2PPV DB获取到标题: " + title);
}
// 如果没有找到标题,尝试其他方法
if (!title || title.length === 0) {
// 尝试找到页面标题
title = response.find("title").text().trim();
if (title.includes(" - FC2PPVDB")) {
title = title.replace(" - FC2PPVDB", "").trim();
}
console.log("从页面标题获取标题: " + title);
}
// 如果仍然没有找到标题,尝试找到任何链接或标题类元素
if (!title || title.length === 0) {
let possibleElements = response.find("h1, h2, h3, a.title");
if (possibleElements.length > 0) {
title = possibleElements.first().text().trim();
console.log("从其他元素获取到标题: " + title);
}
}
// 仔细检查页面内容是否包含没有找到项目的提示
let pageText = response.text();
if (pageText.includes("No articles found") || pageText.includes("没有找到相关项目") || pageText.includes("Not Found")) {
console.log("FC2PPV DB页面显示未找到内容");
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, "未找到该FC2编号的内容");
return;
}
// 查找日期信息
let date = null;
let dateElement = response.find("time");
if (dateElement.length > 0) {
date = dateElement.attr("datetime") || dateElement.text().trim();
console.log("找到日期信息: " + date);
// 尝试格式化日期
if (date) {
let dateMatch = date.match(/(\d{4})[/-](\d{1,2})[/-](\d{1,2})/);
if (dateMatch) {
date = `${dateMatch[1]}-${dateMatch[2].padStart(2, '0')}-${dateMatch[3].padStart(2, '0')}`;
console.log("格式化日期: " + date);
}
}
}
if (title && title.length > 0) {
// 构建标准FC2番号格式
let standardFC2 = "FC2-PPV-" + fc2Number;
console.log("标准化FC2番号: " + standardFC2);
// 构建新名称
let newName = buildNewName(standardFC2, suffix, chineseCaptions, Uncensored, title);
// 添加时间
if (addDate && date) {
newName = date + "_" + newName;
console.log("添加日期后的文件名: " + newName);
}
if (newName) {
// 修改名称
send_115(fid, newName, standardFC2, callback);
return;
} else {
// 找到标题但构建名称失败
console.log("构建名称失败");
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, "构建文件名失败");
}
} else {
// 没有找到标题
console.log("FC2PPV DB未找到标题");
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, "未找到标题信息");
}
} catch (e) {
console.log("处理FC2PPV DB响应出错: ", e);
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, "处理响应出错: " + e.message);
}
},
onerror: xhr => {
console.log("FC2PPV DB请求失败: ", xhr);
// 检查是否是因为@connect权限问题
let errorMsg = xhr.error || xhr.toString();
if (errorMsg.includes("@connect") || errorMsg.includes("Refused to connect")) {
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback,
"fc2ppvdb.com不在@connect列表中,请更新脚本或在Tampermonkey设置中添加该域名", true);
} else {
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, "请求失败");
}
},
ontimeout: () => {
console.log("FC2PPV DB请求超时");
handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, "请求超时");
}
});
}
/**
* 处理FC2查询错误
* 对于FC2文件,如果查询失败,不再尝试其他网站
*/
function handleFC2Error(fid, fh, suffix, chineseCaptions, Uncensored, addDate, fc2Number, callback, errorMsg, isConnectError = false) {
let standardFC2 = "FC2-PPV-" + fc2Number;
// 如果是@connect错误,给出特殊提示
if (isConnectError) {
let notificationMsg = `${standardFC2}: ${errorMsg}`;
console.log(notificationMsg);
GM_notification(getDetails(notificationMsg, "FC2查询失败-权限问题"));
showPageNotification(notificationMsg, 'error', 8000);
// 显示详细说明
let connectHelp = "请在Tampermonkey脚本设置中添加@connect和@domain: fc2ppvdb.com,或更新脚本版本。";
console.log(connectHelp);
showPageNotification(connectHelp, 'info', 10000);
if (typeof callback === 'function') {
callback(); // 确保回调函数被执行
}
return;
}
// 普通错误处理
let notificationMsg = `${standardFC2}: ${errorMsg}`;
console.log(notificationMsg);
GM_notification(getDetails(standardFC2, "FC2查询失败"));
showPageNotification(notificationMsg, 'error', 5000);
// 对于FC2文件,查询失败就不再尝试其他网站,直接返回
if (typeof callback === 'function') {
callback(); // 确保回调函数被执行
}
}
/**
* 请求javdb获取标题信息
*/
function requestJavdb(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback) {
console.log("请求JavDB: " + fh);
// 构建搜索URL
const searchUrl = javdbSearchBase + fh;
GM_xmlhttpRequest({
method: "GET",
url: searchUrl,
onload: xhr => {
try {
// 解析搜索结果页面
let response = $(xhr.responseText);
// 查找第一个搜索结果
let firstResult = response.find('.movie-list .item').first();
if (firstResult.length > 0) {
// 获取详情页链接
let detailLink = firstResult.find('a.box').attr('href');
if (detailLink) {
// 确保链接是完整的URL
if (detailLink.startsWith('/')) {
detailLink = javdbDirectAccess + detailLink.substring(1);
}
console.log("获取到详情页链接: " + detailLink);
// 请求详情页
GM_xmlhttpRequest({
method: "GET",
url: detailLink,
onload: detailXhr => {
try {
let detailResponse = $(detailXhr.responseText);
// 获取标题
let title = null;
// 方法1: 从h2标签获取
let h2Title = detailResponse.find('h2.title');
if (h2Title.length > 0) {
title = h2Title.text().trim();
console.log("从h2获取标题: " + title);
}
// 方法2: 从页面标题获取
if (!title || title.length === 0) {
title = detailResponse.find('title').text().trim();
if (title.includes(" - JavDB")) {
title = title.replace(" - JavDB", "").trim();
}
console.log("从页面标题获取标题: " + title);
}
// 获取日期
let date = null;
// 方法1: 查找含有发行日期的元素
let dateElements = detailResponse.find('.panel-block .value');
dateElements.each(function() {
let text = $(this).text().trim();
let dateMatch = text.match(/(\d{4}-\d{2}-\d{2})/);
if (dateMatch) {
date = dateMatch[0];
console.log("找到日期信息: " + date);
return false; // 找到日期后停止循环
}
});
// 方法2: 在整个页面文本中查找日期格式
if (!date) {
let pageText = detailResponse.text();
let dateMatch = pageText.match(/(\d{4}-\d{2}-\d{2})/);
if (dateMatch) {
date = dateMatch[0];
console.log("从页面文本中匹配到日期: " + date);
}
}
if (title && title.length > 0) {
// 处理标题中可能存在的番号前缀
if (title.toUpperCase().indexOf(fh.toUpperCase()) === 0) {
title = title.substring(fh.length).trim();
console.log("移除番号前缀后的标题: " + title);
}
// 构建新名称
let newName = buildNewName(fh, suffix, chineseCaptions, Uncensored, title);
// 添加时间
if (addDate && date) {
newName = date + "_" + newName;
console.log("添加日期后的文件名: " + newName);
}
if (newName) {
// 修改名称
send_115(fid, newName, fh, callback);
return;
}
}
// 如果找不到有效信息,通知失败
console.log("JavDB详情页没有找到有效信息");
GM_notification(getDetails(fh, "JavDB未找到有效信息"));
if (typeof callback === 'function') {
callback();
}
} catch (e) {
console.log("处理JavDB详情页响应出错: ", e);
GM_notification(getDetails(fh, "处理JavDB详情页出错"));
if (typeof callback === 'function') {
callback();
}
}
},
onerror: err => {
console.log("JavDB详情页请求失败: ", err);
GM_notification(getDetails(fh, "JavDB详情页请求失败"));
if (typeof callback === 'function') {
callback();
}
}
});
} else {
console.log("未找到详情页链接");
GM_notification(getDetails(fh, "JavDB未找到详情页链接"));
if (typeof callback === 'function') {
callback();
}
}
} else {
console.log("JavDB搜索结果为空");
GM_notification(getDetails(fh, "JavDB搜索结果为空"));
if (typeof callback === 'function') {
callback();
}
}
} catch (e) {
console.log("处理JavDB搜索响应出错: ", e);
GM_notification(getDetails(fh, "处理JavDB搜索结果出错"));
if (typeof callback === 'function') {
callback();
}
}
},
onerror: xhr => {
console.log("JavDB搜索请求失败: ", xhr);
GM_notification(getDetails(fh, "JavDB搜索请求失败"));
if (typeof callback === 'function') {
callback();
}
}
});
}
/**
* 构建新名称
* @param fh 番号
* @param suffix 后缀
* @param chineseCaptions 是否有中文字幕
* @param title 番号标题
* @returns {string} 新名称
*/
function buildNewName(fh, suffix, chineseCaptions, Uncensored, title) {
if (title) {
let newName = String(fh);
// 有中文字幕
if (chineseCaptions) {
newName = newName + "【中文字幕】";
}
// 有无码
if (Uncensored) {
newName = newName + "【无码】";
}
// 拼接标题
newName = newName + " " + title;
if (suffix) {
// 文件保存后缀名
newName = newName + suffix;
}
return newName;
}
}
/**
* 请求115接口
* @param id 文件id
* @param name 要修改的名称
* @param fh 番号
* @param callback 成功回调
*/
function send_115(id, name, fh, callback) {
let file_name = stringStandard(name);
$.post("https://webapi.115.com/files/edit", {
fid: id,
file_name: file_name
},
function (data, status) {
let result = JSON.parse(data);
if (!result.state) {
GM_notification(getDetails(fh, "修改失败"));
showPageNotification(`${fh} 修改失败: ${result.error}`, 'error', 3000);
console.log("请求115接口异常: " + unescape(result.error
.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1')));
} else {
GM_notification(getDetails(fh, "修改成功"));
showPageNotification(`${fh} 修改成功`, 'success', 2000);
console.log("修改文件名称,fh:" + fh, "name:" + file_name);
if (typeof callback === 'function') {
callback();
}
}
}
);
}
/**
* 通知参数
* @param text 内容
* @param title 标题
* @returns {{text: *, title: *, timeout: number}}
*/
function getDetails(text, title) {
return {
text: text,
title: title,
timeout: 1000
};
}
/**
* 115名称不接受(\/:*?\"<>|)
* @param name
*/
function stringStandard(name) {
return name.replace(/\\/g, "")
.replace(/\//g, " ")
.replace(/:/g, " ")
.replace(/\?/g, " ")
.replace(/"/g, " ")
.replace(/</g, " ")
.replace(/>/g, " ")
.replace(/\|/g, "")
.replace(/\*/g, " ");
}
/**
* 校验是否为中文字幕
* @param fh 番号
* @param title 标题
*/
function checkChineseCaptions(fh, title) {
if (title.indexOf("中文") !== -1) {
return true;
}
let regExp = new RegExp(fh + "[_-](UC|C)");
let match = title.toUpperCase().match(regExp);
if (match) {
return true;
}
}
/**
* 校验是否为无码
* @param fh 番号
* @param title 标题
*/
function checkUncensored(fh, title) {
if (title.indexOf("无码") !== -1) {
return true;
}
let regExp = new RegExp(fh + "[_-](UC|U)");
let match = title.toUpperCase().match(regExp);
if (match) {
return true; // 如果标题中包含 "-U" 或 "_U",则返回 true,表示为无码
}
return false; // 默认情况下,返回 false,表示不是无码
}
/**
* 获取番号
* @param title 源标题
* @returns {string} 提取的番号
*/
function getVideoCode(title) {
// 排除明显的非番号格式,如单纯的文件格式或编码格式
const nonAvCodes = ["MP4", "MKV", "AVI", "RMVB", "WMV", "MOV", "FLV",
"X264", "X265", "HEVC", "H264", "H265", "AAC", "MP3"];
// 常见的后缀,需要在提取番号时移除
const commonSuffixes = ["UC", "C", "U", "字幕"];
// 处理域名前缀
title = title.replace(/^[a-zA-Z0-9-]+\.(com|net|org|info|xyz|cc|me|tv|io)@/i, "");
title = title.toUpperCase().replace("SIS001", "")
.replace("1080P", "")
.replace("720P", "");
console.log("处理文件名: " + title);
// 预处理:移除末尾的常见后缀
let cleanTitle = title;
for (let suffix of commonSuffixes) {
let suffixPattern = new RegExp(`[-\\s_]*${suffix}\\s*$`, 'i');
cleanTitle = cleanTitle.replace(suffixPattern, '');
}
// 移除多余的空格,简化后续处理
cleanTitle = cleanTitle.replace(/\s+/g, ' ').trim();
if (cleanTitle !== title) {
console.log("预处理后的文件名: " + cleanTitle);
}
// 优先检查FC2格式
// FC2-PPV-1234567 或其他FC2变种格式
let fc2Match = cleanTitle.match(/FC2[-\s]?PPV[-\s]?(\d{5,7})/i) ||
cleanTitle.match(/FC2PPV[_-]?(\d{5,7})/i) ||
cleanTitle.match(/FC2\s+(\d{5,7})/i);
if (fc2Match && fc2Match[1]) {
let fc2Code = "FC2-PPV-" + fc2Match[1];
console.log("找到FC2番号: " + fc2Code);
return fc2Code;
}
// 改进常规AV番号格式匹配,允许模式中有空格
// SONE- 101 -> SONE-101
let t = cleanTitle.match(/([A-Z]{2,6})[-\s_]*(\d{2,5})/);
if (t && t[1] && t[2]) {
let code = t[1] + "-" + t[2];
console.log("从带空格格式中提取番号: " + code);
return code;
}
// 尝试传统匹配方式
t = cleanTitle.match(/[A-Z]{2,6}[-_]?\d{2,5}/);
if (!t) {
// 日期+编号格式,如041117_510
t = cleanTitle.match(/\d{6}_\d{3}/);
if (t) {
let code = t.toString();
// 验证日期格式是否合理
let month = parseInt(code.substring(0, 2));
let day = parseInt(code.substring(2, 4));
let year = parseInt(code.substring(4, 6));
// 检查日期是否在合理范围内
if (month >= 1 && month <= 12 && day >= 1 && day <= 31 && year >= 0 && year <= 99) {
console.log("找到日期格式番号: " + code);
return code;
}
}
}
if (!t) {
// 一本道格式
t = cleanTitle.match(/1PONDO[-_]\d{6}[-_]\d{2,4}/);
if (t) {
t = t.toString().replace("1PONDO_", "")
.replace("1PONDO-", "");
}
}
if (!t) {
// HEYZO格式
t = cleanTitle.match(/HEYZO[-_]?\d{4}/);
}
if (!t) {
// 加勒比格式
t = cleanTitle.match(/CARIB[-_]\d{6}[-_]\d{3}/);
if (t) {
t = t.toString().replace("CARIB-", "")
.replace("CARIB_", "");
}
}
if (!t) {
// 东京热格式
t = cleanTitle.match(/N[-_]\d{4}/);
}
if (!t) {
// FC2格式 (补充检查,以防前面的优先检查没有匹配到)
t = cleanTitle.match(/FC2[-_]?(PPV)?[-_]?\d{6,7}/i);
if (t) {
// 提取FC2编号
let fc2NumMatch = t.toString().match(/\d{6,7}/);
if (fc2NumMatch) {
let fc2Code = "FC2-PPV-" + fc2NumMatch[0];
console.log("第二次检查找到FC2番号: " + fc2Code);
return fc2Code;
}
}
}
if (!t) {
// T28系列
t = cleanTitle.match(/T28[-_]\d{3,4}/);
}
if (!t) {
// 通用格式:2-5个字母后跟3-5个数字
t = cleanTitle.match(/[A-Z]{2,5}[-_]?\d{3,5}/);
}
if (!t) {
// 日期类型番号,如210622-001
t = cleanTitle.match(/\d{6}[-_]\d{2,4}/);
}
// 非常宽松的匹配逻辑,只在上面都匹配不到时使用
if (!t) {
// 任意字母+数字组合
t = cleanTitle.match(/[A-Z]+\d{3,5}/);
}
// 处理提取到的番号
if (t) {
let code = t.toString();
// 检查是否为已知的非番号字符串
if (nonAvCodes.includes(code)) {
console.log("跳过文件格式或编码格式: " + code);
return null;
}
// 检查番号长度是否合理(4-15字符之间)
if (code.length < 4) {
console.log("提取的番号过短 (" + code.length + " 字符),不是有效番号: " + code);
return null;
}
if (code.length > 15) {
console.log("提取的番号过长 (" + code.length + " 字符),不是有效番号: " + code);
return null;
}
// 额外检查:番号必须同时包含字母和数字(日期格式除外)
if (!/\d{6}_\d{3}/.test(code) && (!/[A-Z]/i.test(code) || !/\d/.test(code))) {
console.log("提取的番号格式不正确 (缺少字母或数字): " + code);
return null;
}
// 格式化番号,确保查询成功率
// 1. 移除下划线,替换为连字符
code = code.replace(/_/g, "-");
// 2. 如果番号中没有连字符但同时包含字母和数字,在字母和数字之间添加连字符
// 例如: STARS265 -> STARS-265
if (!code.includes("-")) {
let letterPartMatch = code.match(/^([A-Z]+)/);
let numberPartMatch = code.match(/(\d+)$/);
if (letterPartMatch && numberPartMatch) {
let letterPart = letterPartMatch[1];
let numberPart = numberPartMatch[1];
// 重新构建番号,确保字母和数字之间有连字符
if (code === letterPart + numberPart) {
let newCode = letterPart + "-" + numberPart;
console.log("格式化番号: " + code + " -> " + newCode);
code = newCode;
}
}
}
console.log("找到番号: " + code + " (用于查询的URL: " + javbusBase + code + ")");
return code;
}
// 没有匹配到任何番号格式
console.log("未找到任何番号格式: " + title);
return null;
}
/**
* 执行归档到演员文件夹的功能
*/
function archiveToActorFolder() {
// 获取选中的文件数量
let selectedCount = $("iframe[rel='wangpan']").contents().find("li.selected").length;
let processedCount = 0;
let successCount = 0;
// 不再显示额外的归档根目录提示
showPageNotification(`开始处理 ${selectedCount} 个项目...`, 'info', 3000);
// 获取所有已选择的文件
$("iframe[rel='wangpan']")
.contents()
.find("li.selected")
.each(function (index, v) {
let $item = $(v);
// 原文件名称
let file_name = $item.attr("title");
// 文件类型
let file_type = $item.attr("file_type");
// 根据类型获取正确的ID
let fid;
if (file_type === "0") {
// 文件夹
fid = $item.attr("cate_id");
console.log("处理文件夹: " + file_name + ", ID: " + fid);
// 从文件夹名称中提取番号
let fh = getVideoCode(file_name);
if (fh) {
console.log("从文件夹名称中提取到番号: " + fh);
// 使用与文件相同的逻辑处理文件夹
requestJavbusForActor(fid, fh, function() {
processedCount++;
successCount++;
checkAllCompleted();
}, function() {
processedCount++;
checkAllCompleted();
});
} else {
console.log("无法从文件夹名称中提取番号: " + file_name);
showPageNotification(`无法从"${file_name}"提取番号`, 'error', 3000);
processedCount++;
checkAllCompleted();
}
} else {
// 文件
fid = $item.attr("file_id");
if (fid && file_name) {
let fh = getVideoCode(file_name);
if (fh) {
// 执行查询演员并归档
requestJavbusForActor(fid, fh, function() {
processedCount++;
successCount++;
checkAllCompleted();
}, function() {
processedCount++;
checkAllCompleted();
});
} else {
console.log("无法从文件名称中提取番号: " + file_name);
showPageNotification(`无法从"${file_name}"提取番号`, 'error', 3000);
processedCount++;
checkAllCompleted();
}
} else {
processedCount++;
checkAllCompleted();
}
}
});
// 检查是否所有文件都处理完毕
function checkAllCompleted() {
if (processedCount === selectedCount) {
if (successCount > 0) {
showPageNotification(`处理完成: ${successCount}/${selectedCount} 个项目成功处理`, 'success', 5000);
} else {
showPageNotification(`处理完成: 没有成功处理的项目`, 'info', 5000);
}
}
}
}
/**
* 请求javbus获取演员信息并归档
* @param fid 文件id
* @param fh 番号
* @param successCallback 成功回调
* @param failCallback 失败回调
*/
function requestJavbusForActor(fid, fh, successCallback, failCallback) {
console.log("开始查询番号演员信息: " + fh);
GM_xmlhttpRequest({
method: "GET",
url: javbusDirectAccess + fh,
onload: xhr => {
// 分析返回的HTML内容
let response = $(xhr.responseText);
console.log("获取到javbus页面内容,开始查找演员信息");
// 打印页面中的演员相关HTML,用于调试
let actorElements = response.find("span.genre");
console.log("找到潜在演员元素数量: " + actorElements.length);
// 查找所有演员元素
let actresses = [];
actorElements.each(function() {
let anchor = $(this).find("a[href*='/star/']");
if (anchor.length > 0) {
let actorName = anchor.text().trim();
let actorLink = anchor.attr("href");
console.log("找到演员: " + actorName + ", 链接: " + actorLink);
actresses.push({ name: actorName, link: actorLink });
}
});
if (actresses.length > 0) {
// 取第一个演员
let firstActress = actresses[0];
console.log("选择首位演员: " + firstActress.name + " 进行归档");
// 查找或创建文件夹,然后移动文件
findOrCreateFolderAndMove(fid, firstActress.name, successCallback, failCallback);
} else {
console.log("在有码页面未找到演员信息,尝试查询无码页面");
if (javbusDirectAccess !== javbusUncensoredBase) {
// 尝试查询无码
GM_xmlhttpRequest({
method: "GET",
url: javbusUncensoredBase + fh,
onload: xhrUnc => {
let responseUnc = $(xhrUnc.responseText);
console.log("获取到javbus无码页面内容,开始查找演员信息");
// 打印页面中的演员相关HTML,用于调试
let actorElementsUnc = responseUnc.find("span.genre");
console.log("找到潜在演员元素数量(无码): " + actorElementsUnc.length);
// 查找所有演员元素
let actressesUnc = [];
actorElementsUnc.each(function() {
let anchor = $(this).find("a[href*='/star/']");
if (anchor.length > 0) {
let actorName = anchor.text().trim();
let actorLink = anchor.attr("href");
console.log("找到演员(无码): " + actorName + ", 链接: " + actorLink);
actressesUnc.push({ name: actorName, link: actorLink });
}
});
if (actressesUnc.length > 0) {
// 取第一个演员
let firstActressUnc = actressesUnc[0];
console.log("选择首位演员(无码): " + firstActressUnc.name + " 进行归档");
// 查找或创建文件夹,然后移动文件
findOrCreateFolderAndMove(fid, firstActressUnc.name, successCallback, failCallback);
} else {
console.log("HTML内容: " + xhrUnc.responseText.substring(0, 500) + "...");
GM_notification(getDetails(fh, "未找到演员信息"));
console.log("在有码和无码页面均未找到演员信息: " + fh);
}
}
});
} else {
console.log("HTML内容: " + xhr.responseText.substring(0, 500) + "...");
GM_notification(getDetails(fh, "未找到演员信息"));
console.log("在有码页面未找到演员信息: " + fh);
}
}
},
onerror: err => {
console.log("请求javbus页面失败: ", err);
GM_notification(getDetails(fh, "请求javbus页面失败"));
showPageNotification(`请求javbus页面失败: ${fh}`, 'error', 3000);
if (typeof failCallback === 'function') {
failCallback();
}
}
});
}
/**
* 查找或创建文件夹,然后移动文件
* @param fid 文件id
* @param actorName 演员名称
* @param successCallback 成功回调
* @param failCallback 失败回调
*/
function findOrCreateFolderAndMove(fid, actorName, successCallback, failCallback) {
console.log("开始查找或创建文件夹: " + actorName);
// 使用保存的归档根目录ID,如果未设置则使用根目录
let cid = archiveRootCid || ROOT_DIR_CID;
let dirName = archiveRootName || "根目录";
console.log("使用归档目录: " + dirName + " (CID: " + cid + ")");
// 清理演员名称,确保不会因为特殊字符导致查询问题
actorName = stringStandard(actorName);
console.log("处理后的演员名称: " + actorName);
// 首先检查目标文件夹下是否直接存在同名文件夹(列出所有子文件夹)
$.get("https://webapi.115.com/files", {
aid: 1,
cid: cid,
limit: 1000, // 获取足够多的文件/文件夹
offset: 0,
show_dir: 1, // 只显示文件夹
format: "json"
}, function(listData) {
let listResult = typeof listData === 'string' ? JSON.parse(listData) : listData;
console.log("直接获取目录下的所有文件夹,开始查找匹配项");
let folderFound = false;
let targetCid = null;
// 检查是否有匹配的文件夹
if (listResult.state && listResult.data && listResult.data.length > 0) {
console.log("发现 " + listResult.data.length + " 个文件/文件夹");
// 遍历所有项目,查找匹配的文件夹 (使用some而不是forEach,可以在找到匹配项后立即中断)
listResult.data.some(function(item) {
// 仅检查文件夹类型 - 115API中文件夹信息在'n'字段,不是'name'
// 文件夹类型可能通过'is_dir'或'm'为0或者存在'cid'而没有'fid'来判断
let isFolder = item.is_dir || (item.m === 0 && item.cid && !item.fid);
let folderName = item.n || item.name;
if (isFolder && folderName === actorName) {
folderFound = true;
targetCid = item.cid;
console.log("找到完全匹配的文件夹(直接列表): " + folderName + ", ID: " + item.cid);
return true; // 找到匹配项,中断循环
}
return false; // 继续循环
});
}
if (folderFound && targetCid) {
// 文件夹存在,直接移动文件
console.log("使用现有文件夹: " + actorName + ", ID: " + targetCid);
moveFileToFolder(fid, targetCid, actorName, successCallback, failCallback);
} else {
// 如果直接列表没找到,再尝试搜索
console.log("直接列表未找到,尝试使用搜索API查找: " + actorName);
$.get("https://webapi.115.com/files/search", {
search_value: actorName,
format: "json",
aid: "1", // 搜索范围为115网盘
cid: cid, // 使用固定目录
file_type: "0", // 只搜索文件夹
limit: 1000
}, function(data) {
let result = typeof data === 'string' ? JSON.parse(data) : data;
console.log("搜索文件夹结果,开始查找匹配项");
let searchFolderFound = false;
let searchTargetCid = null;
// 检查搜索结果
if (result.state && result.data && result.data.count > 0) {
console.log("找到 " + result.data.count + " 个可能的文件夹");
// 遍历搜索结果,查找完全匹配的文件夹 (使用some而不是forEach)
result.data.list.some(function(item) {
if (item.name === actorName && item.file_type === "0") {
searchFolderFound = true;
searchTargetCid = item.cid;
console.log("找到完全匹配的文件夹(搜索): " + item.name + ", ID: " + item.cid);
return true; // 找到匹配项,中断循环
}
return false; // 继续循环
});
}
if (searchFolderFound && searchTargetCid) {
// 搜索找到了文件夹,使用它
console.log("搜索发现现有文件夹: " + actorName + ", ID: " + searchTargetCid);
moveFileToFolder(fid, searchTargetCid, actorName, successCallback, failCallback);
} else {
// 最后确认,再次检查目标文件夹是否存在
console.log("最终确认,检查文件夹是否存在: " + actorName);
$.get("https://webapi.115.com/files", {
aid: 1,
cid: cid,
limit: 1000,
offset: 0,
show_dir: 1,
format: "json"
}, function(finalCheckData) {
let finalCheck = typeof finalCheckData === 'string' ? JSON.parse(finalCheckData) : finalCheckData;
let finalFolderFound = false;
let finalTargetCid = null;
// 最后一次检查文件夹是否存在
if (finalCheck.state && finalCheck.data && finalCheck.data.length > 0) {
// 使用some代替forEach
finalCheck.data.some(function(item) {
// 同样更新此处的文件夹检测逻辑
let isFolder = item.is_dir || (item.m === 0 && item.cid && !item.fid);
let folderName = item.n || item.name;
if (isFolder && folderName === actorName) {
finalFolderFound = true;
finalTargetCid = item.cid;
console.log("最终确认找到文件夹: " + folderName + ", ID: " + item.cid);
return true; // 找到匹配项,中断循环
}
return false; // 继续循环
});
}
if (finalFolderFound && finalTargetCid) {
console.log("最终确认发现文件夹,使用它: " + actorName);
moveFileToFolder(fid, finalTargetCid, actorName, successCallback, failCallback);
} else {
// 文件夹确实不存在,创建新文件夹
console.log("多次确认后,确定需要创建新文件夹: " + actorName);
$.post("https://webapi.115.com/files/add", {
pid: cid,
cname: actorName
}, function(createData) {
let createResult = typeof createData === 'string' ? JSON.parse(createData) : createData;
console.log("创建文件夹结果: ", createResult);
if (createResult.state) {
// 获取新创建的文件夹cid
let newFolderCid = createResult.cid;
console.log("新文件夹创建成功,ID: " + newFolderCid);
moveFileToFolder(fid, newFolderCid, actorName, successCallback, failCallback);
} else {
console.log("创建文件夹失败,响应码: " + createResult.errno + ", 错误: " + createResult.error);
if (createResult.errno === 20004) {
// 如果是"文件夹已存在"错误,尝试再次获取文件夹列表
console.log("文件夹已存在错误,尝试最后一次查找");
// 短暂延迟后再次尝试
setTimeout(function() {
$.get("https://webapi.115.com/files", {
aid: 1,
cid: cid,
limit: 1000,
offset: 0,
show_dir: 1,
format: "json"
}, function(lastData) {
let lastCheck = typeof lastData === 'string' ? JSON.parse(lastData) : lastData;
let foundFolder = null;
if (lastCheck.state && lastCheck.data && lastCheck.data.length > 0) {
// 使用some代替forEach
lastCheck.data.some(function(item) {
// 同样更新此处的文件夹检测逻辑
let isFolder = item.is_dir || (item.m === 0 && item.cid && !item.fid);
let folderName = item.n || item.name;
if (isFolder && folderName === actorName) {
foundFolder = item;
return true; // 找到匹配项,中断循环
}
return false; // 继续循环
});
}
if (foundFolder) {
console.log("最后尝试成功找到文件夹: " + foundFolder.name + ", ID: " + foundFolder.cid);
moveFileToFolder(fid, foundFolder.cid, actorName, successCallback, failCallback);
} else {
GM_notification(getDetails(actorName, "无法找到或创建文件夹"));
console.log("所有尝试均失败,无法找到或创建文件夹: " + actorName);
}
});
}, 1000);
} else {
GM_notification(getDetails(actorName, "创建文件夹失败"));
console.log("创建文件夹失败:", createResult);
}
}
}).fail(function(xhr) {
console.log("创建文件夹请求失败: ", xhr.status, xhr.statusText);
GM_notification(getDetails(actorName, "创建文件夹请求失败"));
});
}
}).fail(function(err) {
console.log("最终确认请求失败: ", err);
GM_notification(getDetails(actorName, "最终确认请求失败"));
});
}
}).fail(function(err) {
console.log("搜索文件夹请求失败: ", err);
GM_notification(getDetails(actorName, "搜索文件夹失败"));
});
}
}).fail(function(err) {
console.log("列出文件夹请求失败: ", err);
GM_notification(getDetails(actorName, "列出文件夹失败"));
});
}
/**
* 移动文件到指定文件夹
* @param fid 文件id
* @param targetCid 目标文件夹id
* @param actorName 演员名称(用于通知)
* @param successCallback 成功回调
* @param failCallback 失败回调
*/
function moveFileToFolder(fid, targetCid, actorName, successCallback, failCallback) {
console.log("开始移动文件: " + fid + " 到文件夹: " + actorName + " (" + targetCid + ")");
$.post("https://webapi.115.com/files/move", {
pid: targetCid,
fid: fid
}, function(data) {
let result = typeof data === 'string' ? JSON.parse(data) : data;
console.log("移动文件结果: ", result);
if (result.state) {
GM_notification(getDetails(actorName, "归档成功"));
showPageNotification(`文件成功归档到 ${actorName}`, 'success', 2000);
console.log("文件已成功移动到: " + actorName);
if (typeof successCallback === 'function') {
successCallback();
}
} else {
// 检查是否是临时错误(如"尚未完成,请稍后再试")
let errorMsg = result.error || '未知错误';
let isTemporaryError = errorMsg.includes('尚未完成') ||
errorMsg.includes('请稍后再试') ||
errorMsg.includes('队列已满');
if (isTemporaryError) {
// 对于临时错误,只记录日志,不显示任何通知
console.log("移动文件处理中(临时状态):", errorMsg);
// 移除临时错误的通知
// showPageNotification(`归档到 ${actorName} 处理中: ${errorMsg}`, 'info', 3000);
// 仍然调用成功回调,因为这类错误通常会在后台继续处理最终成功
if (typeof successCallback === 'function') {
successCallback();
}
} else {
// 真正的错误才显示失败通知
GM_notification(getDetails(actorName, "归档失败"));
showPageNotification(`归档到 ${actorName} 失败: ${errorMsg}`, 'error', 3000);
console.log("移动文件失败:", result);
if (typeof failCallback === 'function') {
failCallback();
}
}
}
}).fail(function(err) {
console.log("移动文件请求失败: ", err);
GM_notification(getDetails(actorName, "移动文件失败"));
showPageNotification(`移动文件请求失败: ${err.statusText || '网络错误'}`, 'error', 3000);
if (typeof failCallback === 'function') {
failCallback();
}
});
}
/**
* 获取javdb评分并更新115文件评分
*/
function getJavdbRating() {
// 获取选中的文件数量
let selectedCount = $("iframe[rel='wangpan']").contents().find("li.selected").length;
let processedCount = 0;
let successCount = 0;
showPageNotification(`开始处理 ${selectedCount} 个项目的评分...`, 'info', 3000);
// 获取所有已选择的文件/文件夹
$("iframe[rel='wangpan']")
.contents()
.find("li.selected")
.each(function (index, v) {
let $item = $(v);
// 原文件名称
let file_name = $item.attr("title");
// 文件类型
let file_type = $item.attr("file_type");
// 根据类型获取正确的ID
let fid;
if (file_type === "0") {
// 文件夹
fid = $item.attr("cate_id");
console.log("处理文件夹评分: " + file_name + ", ID: " + fid);
} else {
// 文件
fid = $item.attr("file_id");
console.log("处理文件评分: " + file_name + ", ID: " + fid);
}
if (fid && file_name) {
let fh = getVideoCode(file_name);
if (fh) {
// 执行查询评分
requestJavdbRating(fid, fh, file_name, function(success) {
processedCount++;
if (success) successCount++;
checkAllCompleted();
});
} else {
console.log("无法从名称中提取番号: " + file_name);
showPageNotification(`无法从"${file_name}"提取番号`, 'error', 3000);
processedCount++;
checkAllCompleted();
}
} else {
processedCount++;
checkAllCompleted();
}
});
// 检查是否所有文件都处理完毕
function checkAllCompleted() {
if (processedCount === selectedCount) {
if (successCount > 0) {
showPageNotification(`评分处理完成: ${successCount}/${selectedCount} 个项目成功更新`, 'success', 5000);
} else {
showPageNotification(`评分处理完成: 没有成功处理的项目`, 'info', 5000);
}
}
}
}
/**
* 请求javdb获取评分并更新115评分
* @param fid 文件id
* @param fh 番号
* @param file_name 文件名
* @param callback 回调函数,参数为是否成功
*/
function requestJavdbRating(fid, fh, file_name, callback) {
console.log("开始查询番号评分信息: " + fh);
// 使用符合JavDB格式的搜索URL
const searchUrl = javdbSearchBase + fh + "&f=all";
console.log("使用搜索URL: " + searchUrl);
GM_xmlhttpRequest({
method: "GET",
url: searchUrl,
timeout: 10000, // 设置10秒超时
onload: xhr => {
let status = xhr.status;
let responseText = xhr.responseText;
if (status === 200) {
console.log("成功获取搜索页面,开始解析结果");
try {
// 解析页面
const parser = new DOMParser();
const doc = parser.parseFromString(responseText, "text/html");
// 获取电影列表中的第一个项目
const firstItem = doc.querySelector('.movie-list .item');
if (firstItem) {
console.log("找到搜索结果的第一项");
// 直接从item元素的score属性获取评分
const scoreAttr = firstItem.getAttribute('score');
if (scoreAttr) {
const rating = parseFloat(scoreAttr);
const userNum = firstItem.getAttribute('usernum') || '未知';
if (!isNaN(rating)) {
console.log(`获取到"${fh}"的评分: ${rating}分 (${userNum}人评价)`);
// JavDB实际是5分满分制,115也是5分满分制,无需除以2转换
let star115 = Math.round(rating);
console.log(`转换评分: ${rating},四舍五入为 ${star115}星`);
update115Rating(fid, star115, fh, file_name, callback);
return;
} else {
console.log("评分格式不正确: " + scoreAttr);
}
} else {
console.log("未找到评分属性");
// 尝试从评分文本中获取
const ratingElement = firstItem.querySelector('.score .value');
if (ratingElement) {
const ratingText = ratingElement.textContent.trim();
console.log("找到评分元素文本: " + ratingText);
// 提取数字部分
let ratingMatch = ratingText.match(/(\d+\.\d+)分/);
if (ratingMatch && ratingMatch[1]) {
const rating = parseFloat(ratingMatch[1]);
console.log(`从评分文本提取到评分: ${rating}`);
// JavDB实际是5分满分制,115也是5分满分制,无需除以2转换
let star115 = Math.round(rating);
console.log(`转换评分: ${rating},四舍五入为 ${star115}星`);
update115Rating(fid, star115, fh, file_name, callback);
return;
}
}
}
// 如果没有在搜索结果中找到评分,获取详情页
const detailLink = firstItem.querySelector('a.box');
if (detailLink) {
const href = detailLink.getAttribute('href');
if (href) {
const detailUrl = javdbBase + href.substring(1); // 去掉开头的斜杠
console.log("获取到详情页链接: " + detailUrl);
// 访问详情页获取评分
GM_xmlhttpRequest({
method: "GET",
url: detailUrl,
timeout: 10000,
onload: detailXhr => {
if (detailXhr.status === 200) {
processJavdbRating(fid, fh, detailXhr.responseText, file_name, callback);
} else {
console.log("无法获取详情页: " + fh);
showPageNotification(`无法获取"${fh}"的详情页`, 'error', 3000);
callback(false);
}
},
onerror: err => {
console.log("请求详情页失败: ", err);
let errorMsg = "未知错误";
if (err.error) {
errorMsg = err.error.substring(0, 100);
}
showPageNotification(`请求"${fh}"详情页失败: ${errorMsg}`, 'error', 3000);
callback(false);
},
ontimeout: () => {
console.log("请求详情页超时");
showPageNotification(`请求"${fh}"详情页超时`, 'error', 3000);
callback(false);
}
});
return;
}
}
} else {
// 尝试找到任何电影列表项目
const anyMovieItem = doc.querySelector('.movie-list .item, div[class*="movie-list"] .item, div[id="waterfall"] .item');
if (anyMovieItem) {
console.log("找到替代的搜索结果项");
// 直接从item元素的score属性获取评分
const scoreAttr = anyMovieItem.getAttribute('score');
if (scoreAttr) {
const rating = parseFloat(scoreAttr);
const userNum = anyMovieItem.getAttribute('usernum') || '未知';
if (!isNaN(rating)) {
console.log(`获取到"${fh}"的评分: ${rating}分 (${userNum}人评价)`);
// JavDB实际是5分满分制,115也是5分满分制,无需除以2转换
let star115 = Math.round(rating);
console.log(`转换评分: ${rating},四舍五入为 ${star115}星`);
update115Rating(fid, star115, fh, file_name, callback);
return;
}
}
}
console.log("未找到搜索结果");
showPageNotification(`"${fh}"在JavDB中未找到搜索结果`, 'error', 3000);
callback(false);
}
} catch (e) {
console.log("解析搜索页面出错: ", e);
showPageNotification(`解析"${fh}"的搜索页面出错`, 'error', 3000);
callback(false);
}
} else {
console.log("搜索请求失败: " + status);
showPageNotification(`搜索"${fh}"失败,状态码: ${status}`, 'error', 3000);
callback(false);
}
},
onerror: err => {
console.log("请求javdb页面失败: ", err);
let errorMsg = "未知错误";
if (err.error) {
// 如果错误中包含域名不在@connect列表,给出明确提示
if (err.error.indexOf("This domain is not a part of the @connect list") !== -1) {
errorMsg = "JavDB域名未在@connect列表中,请更新脚本或添加域名到@connect";
showPageNotification("需要在脚本设置中允许访问JavDB,请检查@connect设置", 'error', 5000);
} else {
errorMsg = err.error.substring(0, 100);
}
}
console.log("请求错误详情: ", errorMsg);
showPageNotification(`请求"${fh}"的JavDB页面失败: ${errorMsg}`, 'error', 3000);
callback(false);
},
ontimeout: () => {
console.log("请求javdb页面超时");
showPageNotification(`请求"${fh}"的JavDB页面超时`, 'error', 3000);
callback(false);
}
});
}
/**
* 处理JavDB页面内容,提取评分并更新115
* @param fid 文件id
* @param fh 番号
* @param responseText 页面内容
* @param file_name 文件名
* @param callback 回调函数
*/
function processJavdbRating(fid, fh, responseText, file_name, callback) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(responseText, "text/html");
// 尝试多种选择器来定位评分元素
let ratingText = null;
let ratingElement = null;
// 方法1: 查找评分区域
ratingElement = doc.querySelector('.panel-block .value');
if (ratingElement) {
ratingText = ratingElement.textContent.trim();
console.log("方法1找到评分: " + ratingText);
}
// 方法2: 查找带有"分"字的元素
if (!ratingText) {
const allElements = doc.querySelectorAll('*');
for (const element of allElements) {
const text = element.textContent.trim();
if (text.match(/\d+\.\d+分/) && text.indexOf('評價') > 0) {
ratingText = text;
console.log("方法2找到评分: " + ratingText);
break;
}
}
}
// 方法3: 直接在HTML中查找评分模式
if (!ratingText) {
const ratingMatch = responseText.match(/(\d+\.\d+)分, 由(\d+)人評價/);
if (ratingMatch && ratingMatch[1]) {
ratingText = ratingMatch[0];
console.log("方法3找到评分: " + ratingText);
}
}
if (ratingText) {
// 从评分文本中提取数字
const ratingMatch = ratingText.match(/(\d+\.\d+)分/);
if (ratingMatch && ratingMatch[1]) {
const rating = parseFloat(ratingMatch[1]);
if (!isNaN(rating)) {
console.log(`获取到"${fh}"的评分: ${rating}`);
// JavDB实际是5分满分制,115也是5分满分制,无需除以2转换
let star115 = Math.round(rating);
console.log(`转换评分: ${rating},四舍五入为 ${star115}星`);
update115Rating(fid, star115, fh, file_name, callback);
return;
}
}
}
// 如果所有方法都失败
console.log("页面内容中未找到有效评分信息");
showPageNotification(`未能解析"${fh}"的评分信息`, 'error', 3000);
callback(false);
} catch (e) {
console.log("处理评分页面出错: ", e);
showPageNotification(`处理"${fh}"的评分页面出错`, 'error', 3000);
callback(false);
}
}
/**
* 更新115文件评分
* @param fid 文件id
* @param star 评分(1-5)
* @param fh 番号(用于通知)
* @param file_name 文件名(用于日志)
* @param callback 回调函数
*/
function update115Rating(fid, star, fh, file_name, callback) {
console.log(`开始更新评分: 文件ID=${fid}, 番号=${fh}, 评分=${star}星`);
// 确保星级在1-5之间
star = Math.max(1, Math.min(5, star));
// 使用有效的评分API (备用API 1)
$.ajax({
url: "https://webapi.115.com/files/score",
type: "POST",
data: {
file_id: fid,
score: star
},
dataType: "json",
success: function(result) {
console.log("评分API响应:", JSON.stringify(result));
if (result && result.state) {
console.log(`成功更新评分: ${fh}, 文件: ${file_name}, 评分: ${star}星`);
showPageNotification(`"${fh}"评分更新为${star}星`, 'success', 2000);
callback(true);
} else {
// 尝试备用API
console.log(`第一个API评分失败: ${fh}, 错误信息:`, result);
console.log(`尝试备用API...`);
// 尝试备用API (备用API 2)
$.ajax({
url: "https://webapi.115.com/files/edit_property",
type: "POST",
data: {
file_id: fid,
property: "score",
value: star
},
dataType: "json",
success: function(backupResult) {
console.log("备用API响应:", JSON.stringify(backupResult));
if (backupResult && backupResult.state) {
console.log(`备用API成功更新评分: ${fh}`);
showPageNotification(`"${fh}"评分更新为${star}星`, 'success', 2000);
callback(true);
} else {
console.log("所有评分API尝试都失败");
showPageNotification(`"${fh}"评分更新失败,请尝试手动评分`, 'error', 3000);
callback(false);
}
},
error: function() {
console.log("备用API请求失败");
showPageNotification(`"${fh}"评分更新失败,请尝试手动评分`, 'error', 3000);
callback(false);
}
});
}
},
error: function(xhr) {
console.log(`评分API请求失败: ${xhr.status}, ${xhr.statusText}`);
// 尝试备用API
$.ajax({
url: "https://webapi.115.com/files/edit_property",
type: "POST",
data: {
file_id: fid,
property: "score",
value: star
},
dataType: "json",
success: function(backupResult) {
console.log("备用API响应:", JSON.stringify(backupResult));
if (backupResult && backupResult.state) {
console.log(`备用API成功更新评分: ${fh}`);
showPageNotification(`"${fh}"评分更新为${star}星`, 'success', 2000);
callback(true);
} else {
console.log("所有评分API尝试都失败");
showPageNotification(`"${fh}"评分更新失败,请尝试手动评分`, 'error', 3000);
callback(false);
}
},
error: function() {
console.log("备用API请求失败");
showPageNotification(`"${fh}"评分更新失败,请尝试手动评分`, 'error', 3000);
callback(false);
}
});
}
});
}
})();