// ==UserScript==
// @name PikPak 番号重命名助手
// @name:en PikPak JAV Renamer Assistant
// @name:ja PikPak JAV リネームアシスタント
// @name:zh-CN PikPak 番号重命名助手
// @namespace https://github.com/CheerChen
// @version 0.8.0
// @description Adds a rename button next to input field in PikPak rename dialog. Click the button to fetch information from AV-wiki and auto-rename files. Supports file extension preservation and optimized number format matching.
// @description:en Adds a rename button next to input field in PikPak rename dialog. Click the button to fetch information from AV-wiki and auto-rename files. Supports file extension preservation and optimized number format matching.
// @description:ja PikPakのリネームダイアログの入力フィールドの横にリネームボタンを追加します。ボタンをクリックしてAV-wikiから情報を取得し、ファイルを自動リネームします。ファイル拡張子の保護と最適化された番号形式マッチングをサポート。
// @description:zh-CN 在PikPak重命名对话框的输入框旁边添加重命名按钮。点击按钮从AV-wiki获取信息并自动重命名文件。支持文件扩展名保护和优化的番号格式匹配。
// @author cheerchen37
// @match *://*mypikpak.com/*
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @connect av-wiki.net
// @connect api-drive.mypikpak.com
// @icon https://www.google.com/s2/favicons?domain=mypikpak.com
// @license MIT
// @homepage https://github.com/CheerChen/userscripts
// @supportURL https://github.com/CheerChen/userscripts/issues
// ==/UserScript==
(function() {
'use strict';
console.log("脚本已加载");
// 添加样式
const style = document.createElement('style');
style.textContent = `
.pikpak-rename-btn {
margin-left: 8px;
background: transparent;
border: none;
color: #8c8c8c;
width: 20px;
height: 20px;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
transition: all 0.2s ease;
opacity: 0.7;
flex-shrink: 0;
}
.pikpak-rename-btn:hover {
background: rgba(0, 0, 0, 0.08);
opacity: 1;
color: #606266;
}
.pikpak-rename-btn:active {
background: rgba(0, 0, 0, 0.12);
transform: scale(0.95);
}
.pikpak-rename-btn:disabled {
background: transparent;
color: #c0c4cc;
cursor: not-allowed;
opacity: 0.5;
}
.pikpak-input-wrapper {
display: flex;
align-items: center;
}
`;
document.head.appendChild(style);
// 1. 定位到元素的部分
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(node => {
if (node.className === "el-dialog") {
handleDialog(node);
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
function handleDialog(node) {
const input = node.querySelector('input.el-input__inner[type="text"]');
if (!input) return;
// 检查是否已经添加过按钮
if (input.parentElement.parentElement.querySelector('.pikpak-rename-btn')) return;
// 找到 el-input__wrapper 容器
const inputWrapper = input.closest('.el-input__wrapper');
if (!inputWrapper) return;
// 添加包装器类名以便定位
inputWrapper.parentElement.classList.add('pikpak-input-wrapper');
// 创建重命名按钮
const renameBtn = document.createElement('button');
renameBtn.className = 'pikpak-rename-btn';
renameBtn.innerHTML = '✨';
renameBtn.title = '智能重命名';
// 添加点击事件
renameBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const originalValue = input.value;
const keyword = extractKeyword(originalValue);
if (!keyword) {
console.log("未能提取有效关键字");
alert("未能从文件名中提取有效的番号格式");
return;
}
// 禁用按钮并显示加载状态
renameBtn.disabled = true;
renameBtn.innerHTML = '⋯';
renameBtn.style.opacity = '0.5';
queryAVwiki(keyword, input, originalValue, renameBtn);
});
// 将按钮添加到输入框容器的同级位置(右侧)
inputWrapper.parentElement.appendChild(renameBtn);
}
// 2. 提取查询元素的部分
function extractKeyword(text) {
// 优化番号格式匹配,处理 3dsvr-0960 -> 3dsvr-960 的情况
let match = text.match(/([a-zA-Z]+)-0*(\d+)/);
if (match) {
// 移除数字部分前面的0,但保留至少一位数字
const cleanedNumber = match[2].replace(/^0+/, '') || '0';
return `${match[1]}-${cleanedNumber}`;
}
// 尝试匹配包含字母和数字的模式,忽略后面的字符
match = text.match(/([a-zA-Z]+)-(\d+)/);
if (match) {
return match[0];
}
// 处理没有连字符的情况,如 ABC0123 -> ABC-123
match = text.match(/([a-zA-Z]+)0*(\d+)/);
if (match) {
const cleanedNumber = match[2].replace(/^0+/, '') || '0';
return `${match[1]}-${cleanedNumber}`;
}
// 通用匹配
match = text.match(/([a-zA-Z]{3,})(\d+)/);
if (match) {
return `${match[1]}-${match[2]}`;
}
return null;
}
// 3. 使用关键字查询AV-wiki的部分
function queryAVwiki(keyword, input, originalValue, renameBtn) {
console.log("keyword " + keyword);
const encodedKeyword = encodeURIComponent(keyword);
const url = `https://av-wiki.net/?s=${encodedKeyword}&post_type=product`;
console.log("url " + url);
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, "text/html");
const listItems = doc.querySelectorAll('.post .archive-list .read-more a');
const keywordRegex = new RegExp(keyword.match(/[a-zA-Z]+/)[0], 'i');
// 查找第一个有效链接
for (let item of listItems) {
if (item.href) {
if (!keywordRegex.test(item.href)){
continue;
}
const detailUrl = item.href;
console.log("detailUrl "+ detailUrl)
GM_xmlhttpRequest({
method: "GET",
url: detailUrl,
onload: function(response) {
parseResponseWiki(response.responseText, input, originalValue, renameBtn);
},
onerror: function(error) {
console.log("详情页请求出错: ", error);
resetButton(renameBtn, "请求详情页错误");
}
});
return; // 找到有效链接后结束循环
}
}
// 如果没有找到有效链接,恢复按钮状态
resetButton(renameBtn, "未找到匹配的番号");
},
onerror: function(error) {
console.log("请求出错: ", error);
resetButton(renameBtn, "网络请求错误");
}
});
}
// 4. 解析响应并更新输入框
function parseResponseWiki(html, input, originalValue, renameBtn) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
// 尝试从meta标签中提取备选信息
const ogTitle = doc.querySelector('.blockquote-like p');
let name = ogTitle ? ogTitle.textContent : '未找到名称';
// 清理名称中的特殊字符
name = name.replace(/[\/:*?"<>|\x00-\x1F]/g, '_');
// 检查原始值是否包含文件扩展名
const extensionMatch = originalValue.match(/(\.[^.]+)$/);
const extension = extensionMatch ? extensionMatch[1] : '';
// 如果有扩展名,保留它;否则直接使用新名称
input.value = extension ? `${name}${extension}` : `${name}`;
triggerInputChange(input);
// 恢复按钮状态
resetButton(renameBtn, "重命名成功", true);
}
// 5. 重置按钮状态
function resetButton(renameBtn, message, isSuccess = false) {
renameBtn.disabled = false;
renameBtn.innerHTML = '✨';
renameBtn.style.opacity = '';
if (message) {
// 显示消息提示
if (isSuccess) {
console.log("重命名成功");
// 成功时短暂显示对勾
renameBtn.innerHTML = '✓';
renameBtn.style.color = '#67c23a';
setTimeout(() => {
renameBtn.innerHTML = '✨';
renameBtn.style.color = '';
}, 1500);
} else {
console.log(message);
alert(message);
}
}
}
// 6. 触发输入变化事件
function triggerInputChange(element) {
// 创建一个新的键盘事件
var event = new Event('input', {
bubbles: true,
cancelable: true,
});
element.value = element.value.trim(); // 移除空格
element.dispatchEvent(event); // 再次触发input事件
}
})();