115Rename2025

115网盘视频整理工具:1.根据番号查询并重命名文件 2.支持javbus/avmoo查询 3.演员归档自动分类 4.可设置归档根目录 5.支持中文字幕和无码标记 6.支持文件夹归档 7.增强用户体验的通知提示 8.性能优化

Versión del día 09/04/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name            115Rename2025
// @namespace           http://tampermonkey.net/
// @version             1.3
// @description         115网盘视频整理工具:1.根据番号查询并重命名文件 2.支持javbus/avmoo查询 3.演员归档自动分类 4.可设置归档根目录 5.支持中文字幕和无码标记 6.支持文件夹归档 7.增强用户体验的通知提示 8.性能优化
// @author              db117 wusuowei111 Chunluren 
// @include             https://115.com/*
// @icon      	 https://115.com/favicon.ico
// @domain              javbus.com
// @domain              avmoo.host
// @domain              avsox.host
// @grant               GM_notification
// @grant               GM_xmlhttpRequest
// @grant               GM_setValue
// @grant               GM_getValue
// @license             MIT
// ==/UserScript==

(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_javbus" class="mark" href="javascript:;">改名javbus</a>
                <a id="rename_all_javbus_date" class="mark" href="javascript:;">改名javbus_时间</a>
                <a id="rename_all_avmoo" class="mark" href="javascript:;">改名avmoo</a>
                <a id="rename_all_avmoo_date" class="mark" href="javascript:;">改名avmoo_时间</a>
                <a id="archive_to_folder" class="mark" href="javascript:;">归档至文件夹</a>
                <a id="set_archive_root" class="mark" href="javascript:;">设置为归档根目录</a>
            </li>
        `;
    /**
     * 添加按钮的定时任务
     */
    let interval = setInterval(buttonInterval, 1000);

    // javbus
    let javbusBase = "https://www.javbus.com/";
    // 直接访问番号页面 (使用番号直接访问)
    let javbusDirectAccess = javbusBase;
    // 无码页面基础URL
    let javbusUncensoredBase = javbusBase + "uncensored/";

    // 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_javbus").click(
                function () {
                    rename(rename_javbus, false);
                });
            $("a#rename_all_javbus_date").click(
                function () {
                    rename(rename_javbus, true);
                });
            $("a#rename_all_avmoo").click(
                function () {
                    rename(rename_avmoo, false);
                });
            $("a#rename_all_avmoo_date").click(
                function () {
                    rename(rename_avmoo, true);
                });
            $("a#archive_to_folder").click(
                function () {
                    archiveToActorFolder();
                });
            $("a#set_archive_root").click(
                function () {
                    setArchiveRoot();
                });
            
            // 根据是否设置了根目录,显示不同的日志
            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");

                // 文件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) {
                    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);
                            }
                        });
                    }
                }
            });
    }

    /**
     * 通过javbus进行查询
     */
    function rename_javbus(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback) {
        requestJavbus(fid, fh, suffix, chineseCaptions, Uncensored, addDate, javbusDirectAccess, callback, 0);
    }

    /**
     * 请求javbus,并请求115进行改名
     * @param fid               文件id
     * @param fh                番号
     * @param suffix            后缀
     * @param chineseCaptions   是否有中文字幕
     * @param url               请求地址
     * @param addDate           是否添加时间
     * @param callback          成功回调
     */
    function requestJavbus(fid, fh, suffix, chineseCaptions, Uncensored, addDate, url, callback, uncensoredAttempt = 0) {
        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();

                if (title && title.length > 0) {
                    console.log("最终使用标题: " + title);
                    // 构建新名称
                    let newName = buildNewName(fh, suffix, chineseCaptions, Uncensored, title);

                    // 添加时间
                    if (addDate && date) {
                        newName = date + "_" + 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);
                } else {
                    // 已经尝试过无码查询或当前就是无码查询,直接结束
                    console.log("所有查询尝试完毕,无法获取标题: " + fh);
                    GM_notification(getDetails(fh, "无法获取标题"));
                    if (typeof callback === 'function') {
                        callback(); // 确保回调函数被执行
                    }
                }
            }
        })
    }

    /**
     * 通过avmoo进行查询
     */
    function rename_avmoo(fid, fh, suffix, chineseCaptions, Uncensored, addDate, callback) {
        requestAvmoo(fid, fh, suffix, chineseCaptions, Uncensored, addDate, avmooSearch, callback, 0);
    }

    /**
     * 请求avmoo,并请求115进行改名
     */
    function requestAvmoo(fid, fh, suffix, chineseCaptions, Uncensored, addDate, url, callback, uncensoredAttempt = 0) {
        GM_xmlhttpRequest({
            method: "GET",
            url: url + fh,
            onload: xhr => {
                // 匹配标题
                let response = $(xhr.responseText);
                if (!(response.find("div.alert").length)) {
                    // 尝试不同方式获取标题
                    let title = null;
                    
                    // 首先尝试从图片标题获取
                    title = response.find("div.photo-frame img").attr("title");
                    console.log("AVMOO从img标签title属性获取标题: " + title);
                    
                    // 如果没有获取到,尝试从标题获取
                    if (!title || title.length === 0) {
                        title = response.find("h3").text().trim();
                        // 清理标题
                        if (title.toUpperCase().indexOf(fh.toUpperCase()) === 0) {
                            title = title.substring(fh.length).trim();
                        }
                        console.log("AVMOO从h3获取标题: " + title);
                    }

                    // 时间
                    let date = response.find("div.photo-info date:last").html();

                    if (title && title.length > 0) {
                        console.log("AVMOO最终使用标题: " + title);
                        // 构建新名称
                        let newName = buildNewName(fh, suffix, chineseCaptions, Uncensored, title);

                        // 添加时间
                        if (addDate && date) {
                            newName = date + "_" + newName;
                        }

                        if (newName) {
                            // 修改名称
                            send_115(fid, newName, fh, callback);
                        }
                    } else if (url !== avmooUncensoredSearch && uncensoredAttempt === 0) {
                        // 如果当前不是无码站点且未尝试过无码查询,则尝试一次无码查询
                        console.log("AVMOO未找到标题,尝试一次无码页面查询: " + fh);
                        requestAvmoo(fid, fh, suffix, chineseCaptions, Uncensored, addDate, avmooUncensoredSearch, callback, 1);
                    } else {
                        // 已经尝试过无码查询或当前就是无码查询,直接结束
                        console.log("AVMOO所有查询尝试完毕,无法获取标题: " + fh);
                        GM_notification(getDetails(fh, "无法获取标题"));
                        if (typeof callback === 'function') {
                            callback(); // 确保回调函数被执行
                        }
                    }
                } else if (url !== avmooUncensoredSearch && uncensoredAttempt === 0) {
                    // 如果当前不是无码站点且未尝试过无码查询,则尝试一次无码查询
                    console.log("AVMOO未找到内容,尝试一次无码页面查询: " + fh);
                    requestAvmoo(fid, fh, suffix, chineseCaptions, Uncensored, addDate, avmooUncensoredSearch, callback, 1);
                } else {
                    // 已经尝试过无码查询或当前就是无码查询,直接结束
                    console.log("AVMOO所有查询尝试完毕,无法获取标题: " + fh);
                    GM_notification(getDetails(fh, "无法获取标题"));
                    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"];
        
        title = title.toUpperCase().replace("SIS001", "")
            .replace("1080P", "")
            .replace("720P", "");

        // 优先匹配明确的厂商番号格式
        // 常见的AV番号格式,如STARS-123, ABP-123, IPX-123等
        let t = title.match(/[A-Z]{2,6}[-_]?\d{3,5}/);
        
        if (!t) {
            // 一本道格式
            t = title.match(/1PONDO[-_]\d{6}[-_]\d{2,4}/);
            if (t) {
                t = t.toString().replace("1PONDO_", "")
                    .replace("1PONDO-", "");
            }
        }
        
        if (!t) {
            // HEYZO格式
            t = title.match(/HEYZO[-_]?\d{4}/);
        }
        
        if (!t) {
            // 加勒比格式
            t = title.match(/CARIB[-_]\d{6}[-_]\d{3}/);
            if (t) {
                t = t.toString().replace("CARIB-", "")
                    .replace("CARIB_", "");
            }
        }
        
        if (!t) {
            // 东京热格式
            t = title.match(/N[-_]\d{4}/);
        }
        
        if (!t) {
            // FC2格式
            t = title.match(/FC2[-_]?(PPV)?[-_]?\d{6,7}/i);
        }
        
        if (!t) {
            // T28系列
            t = title.match(/T28[-_]\d{3,4}/);
        }
        
        if (!t) {
            // 通用格式:2-5个字母后跟3-5个数字
            t = title.match(/[A-Z]{2,5}[-_]?\d{3,5}/);
        }
        
        if (!t) {
            // 日期类型番号,如210622-001
            t = title.match(/\d{6}[-_]\d{2,4}/);
        }
        
        // 非常宽松的匹配逻辑,只在上面都匹配不到时使用
        if (!t) {
            // 任意字母+数字组合
            t = title.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 (!/[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;
        }
        
        // 没有匹配到任何番号格式
        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();
            }
        });
    }
})();