// ==UserScript==
// @name 【云】XMXMKB自动收藏功能
// @namespace http://tampermonkey.net/
// @version 1.7
// @description 基于导入的收藏数据自动收藏匹配的列表项,支持JSON文件导入和自动收藏功能
// @author You
// @match https://www.xmxmkb.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 添加样式
GM_addStyle(`
.auto-favorite-btn {
position: fixed;
top: 20px;
right: 520px;
background: #28a745;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
z-index: 9999;
font-size: 13px;
transition: all 0.3s;
}
.auto-favorite-btn:hover {
background: #218838;
transform: scale(1.05);
}
.auto-favorite-btn.loading {
background: #ffc107;
pointer-events: none;
}
.auto-favorite-btn.loading::after {
content: '';
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid transparent;
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 5px;
}
.auto-favorite-panel {
position: fixed;
top: 20px;
right: 20px;
width: 400px;
max-height: 600px;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
font-family: Arial, sans-serif;
overflow: hidden;
display: none;
}
.auto-favorite-header {
background: #28a745;
color: white;
padding: 15px 20px;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.auto-favorite-title {
font-weight: bold;
font-size: 16px;
}
.auto-favorite-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: white;
}
.auto-favorite-close:hover {
color: #ccc;
}
.auto-favorite-content {
padding: 20px;
max-height: 400px;
overflow-y: auto;
}
.import-section {
margin-bottom: 20px;
}
.import-label {
display: block;
margin-bottom: 10px;
font-weight: bold;
color: #333;
}
.import-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
margin-bottom: 10px;
}
.import-input:focus {
outline: none;
border-color: #28a745;
}
.import-btn {
background: #28a745;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.import-btn:hover {
background: #218838;
}
.import-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.import-info {
margin-top: 8px;
padding: 8px 12px;
background: #e8f5e8;
border-radius: 4px;
border: 1px solid #c3e6c3;
font-size: 12px;
}
.ignore-section {
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
border: 1px solid #e9ecef;
}
.ignore-info {
margin-top: 10px;
padding: 8px 12px;
background: white;
border-radius: 4px;
border: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.contain-section {
margin-bottom: 20px;
padding: 15px;
background: #f0f8ff;
border-radius: 4px;
border: 1px solid #b3d9ff;
}
.test-section {
margin-bottom: 20px;
padding: 15px;
background: #fff3cd;
border-radius: 4px;
border: 1px solid #ffeaa7;
}
.status-section {
margin-bottom: 20px;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.status-label {
font-weight: bold;
color: #333;
}
.status-value {
color: #666;
}
.auto-favorite-actions {
padding: 15px 20px;
border-top: 1px solid #ddd;
display: flex;
gap: 10px;
}
.auto-favorite-action-btn {
background: #28a745;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
flex: 1;
transition: all 0.3s;
}
.auto-favorite-action-btn:hover {
background: #218838;
}
.auto-favorite-action-btn.danger {
background: #dc3545;
}
.auto-favorite-action-btn.danger:hover {
background: #c82333;
}
.auto-favorite-message {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 15px 20px;
border-radius: 8px;
color: white;
font-size: 14px;
font-weight: bold;
z-index: 10001;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
animation: messageSlideIn 0.3s ease-out;
}
.auto-favorite-message-success {
background: #28a745;
}
.auto-favorite-message-error {
background: #dc3545;
}
.auto-favorite-message-info {
background: #17a2b8;
}
@keyframes messageSlideIn {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.8);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`);
// 自动收藏管理器
class AutoFavoriteManager {
constructor() {
this.importedFavorites = [];
this.autoFavoriteEnabled = false;
this.processedItems = new Set();
this.ignoreChars = [];
this.minMatchLength = 10;
this.init();
}
init() {
console.log('🚀 自动收藏管理器初始化开始...');
this.loadImportedFavorites();
this.loadIgnoreChars();
this.loadMinMatchLength();
this.createUI();
// 如果有导入的收藏数据,自动启用收藏功能
if (this.importedFavorites.length > 0) {
this.autoFavoriteEnabled = true;
console.log('🎯 检测到已导入收藏数据,自动启用收藏功能');
this.updateStatus();
}
this.startAutoProcess();
this.testNormalizeFunction();
console.log('✅ 自动收藏管理器初始化完成');
}
// 测试标准化函数
testNormalizeFunction() {
console.log('🧪 测试字符串标准化功能...');
const testCases = [
'Hello World!',
'Test, Title.',
'【测试】标题',
'Title (2023)',
'Test-Title_123',
' Multiple Spaces ',
'Mixed.Case,Title!'
];
testCases.forEach(testCase => {
const normalized = this.normalizeString(testCase);
console.log(` - "${testCase}" -> "${normalized}"`);
});
console.log('✅ 字符串标准化功能测试完成');
}
// 加载已导入的收藏数据
loadImportedFavorites() {
try {
const data = localStorage.getItem('fav-list');
if (data) {
const parsedData = JSON.parse(data);
// 检查数据格式,支持新的对象格式和旧的数组格式
if (parsedData.favorites && Array.isArray(parsedData.favorites)) {
this.importedFavorites = parsedData.favorites;
} else if (Array.isArray(parsedData)) {
this.importedFavorites = parsedData;
} else {
this.importedFavorites = [];
}
} else {
this.importedFavorites = [];
}
console.log(`📂 已从localStorage加载导入的收藏数据: ${this.importedFavorites.length} 项`);
if (this.importedFavorites.length > 0) {
console.log('📋 导入的收藏标题列表:');
this.importedFavorites.forEach((fav, index) => {
console.log(` ${index + 1}. "${fav.title}" (ID: ${fav.id})`);
});
} else {
console.log('📭 暂无导入的收藏数据');
}
} catch (e) {
console.error('❌ 加载导入数据失败:', e);
this.importedFavorites = [];
}
}
// 保存导入的收藏数据
saveImportedFavorites() {
try {
const dataToSave = {
favorites: this.importedFavorites,
lastUpdated: Date.now(),
version: '1.7'
};
localStorage.setItem('fav-list', JSON.stringify(dataToSave));
console.log(`💾 已保存导入的收藏数据到localStorage: ${this.importedFavorites.length} 项`);
} catch (e) {
console.error('❌ 保存导入数据失败:', e);
}
}
// 加载忽略字符设置
loadIgnoreChars() {
try {
const data = localStorage.getItem('fav-list');
if (data) {
const parsedData = JSON.parse(data);
this.ignoreChars = parsedData.ignoreChars || [];
} else {
this.ignoreChars = [];
}
console.log(`📂 已从localStorage加载忽略字符/字符串设置: ${this.ignoreChars.length} 个`);
if (this.ignoreChars.length > 0) {
console.log('🚫 忽略字符/字符串列表:');
this.ignoreChars.forEach((chars, index) => {
console.log(` ${index + 1}. "${chars}" (长度: ${chars.length})`);
});
} else {
console.log('📭 暂无忽略字符/字符串设置');
}
} catch (e) {
console.error('❌ 加载忽略字符设置失败:', e);
this.ignoreChars = [];
}
}
// 保存忽略字符设置
saveIgnoreChars() {
try {
const existingData = localStorage.getItem('fav-list');
let dataToSave = existingData ? JSON.parse(existingData) : {};
dataToSave.ignoreChars = this.ignoreChars;
dataToSave.lastUpdated = Date.now();
dataToSave.version = '1.7';
localStorage.setItem('fav-list', JSON.stringify(dataToSave));
console.log(`💾 已保存忽略字符/字符串设置到localStorage: ${this.ignoreChars.length} 个`);
} catch (e) {
console.error('❌ 保存忽略字符设置失败:', e);
}
}
// 加载最小匹配长度设置
loadMinMatchLength() {
try {
const data = localStorage.getItem('fav-list');
if (data) {
const parsedData = JSON.parse(data);
this.minMatchLength = parsedData.minMatchLength || 10;
} else {
this.minMatchLength = 10;
}
console.log(`📂 已从localStorage加载最小匹配长度设置: ${this.minMatchLength}`);
} catch (e) {
console.error('❌ 加载最小匹配长度设置失败:', e);
this.minMatchLength = 10;
}
}
// 保存最小匹配长度设置
saveMinMatchLength() {
try {
const existingData = localStorage.getItem('fav-list');
let dataToSave = existingData ? JSON.parse(existingData) : {};
dataToSave.minMatchLength = this.minMatchLength;
dataToSave.lastUpdated = Date.now();
dataToSave.version = '1.7';
localStorage.setItem('fav-list', JSON.stringify(dataToSave));
console.log(`💾 已保存最小匹配长度设置到localStorage: ${this.minMatchLength}`);
} catch (e) {
console.error('❌ 保存最小匹配长度设置失败:', e);
}
}
// 创建用户界面
createUI() {
console.log('🎨 开始创建用户界面...');
// 创建主按钮
const mainBtn = document.createElement('button');
mainBtn.className = 'auto-favorite-btn';
mainBtn.textContent = '自动收藏';
mainBtn.onclick = () => this.togglePanel();
document.body.appendChild(mainBtn);
console.log('✅ 主按钮已创建');
// 创建面板
const panel = document.createElement('div');
panel.className = 'auto-favorite-panel';
panel.innerHTML = `
<div class="auto-favorite-header">
<div class="auto-favorite-title">自动收藏管理</div>
<button class="auto-favorite-close">×</button>
</div>
<div class="auto-favorite-content">
<div class="import-section">
<label class="import-label">导入收藏数据 (JSON格式):</label>
<input type="file" class="import-input" accept=".json" id="importFile">
<button class="import-btn" id="importBtn">导入数据</button>
<div class="import-info">
<span class="status-label">支持格式:</span>
<span class="status-value">标准收藏格式、黑名单格式、自动去重追加</span>
</div>
</div>
<div class="status-section">
<div class="status-item">
<span class="status-label">已导入收藏:</span>
<span class="status-value" id="importedCount">${this.importedFavorites.length}</span>
</div>
<div class="status-item">
<span class="status-label">自动收藏状态:</span>
<span class="status-value" id="autoStatus">${this.autoFavoriteEnabled ? '已启用' : '已禁用'}</span>
</div>
<div class="status-item">
<span class="status-label">已处理项目:</span>
<span class="status-value" id="processedCount">${this.processedItems.size}</span>
</div>
</div>
<div class="ignore-section">
<label class="import-label">忽略字符/字符串设置:</label>
<input type="text" class="import-input" id="ignoreChars" placeholder="输入要忽略的字符或字符串,用逗号分隔,如: 百度网盘,...,【】,(),[],{}">
<button class="import-btn" id="saveIgnoreBtn">保存设置</button>
<div class="ignore-info">
<span class="status-label">当前忽略字符/字符串:</span>
<span class="status-value" id="currentIgnoreChars">${this.ignoreChars.join(', ') || '无'}</span>
</div>
</div>
<div class="contain-section">
<label class="import-label">包含匹配设置:</label>
<input type="number" class="import-input" id="minLength" placeholder="最小匹配长度" min="1" max="100" value="${this.minMatchLength}">
<button class="import-btn" id="saveContainBtn">保存设置</button>
<div class="ignore-info">
<span class="status-label">当前最小长度:</span>
<span class="status-value" id="currentMinLength">${this.minMatchLength}</span>
</div>
</div>
<div class="test-section">
<label class="import-label">自定义测试:</label>
<input type="text" class="import-input" id="testString" placeholder="输入要测试的标题字符串">
<button class="import-btn" id="testStringBtn">测试匹配</button>
</div>
</div>
<div class="auto-favorite-actions">
<button class="auto-favorite-action-btn" id="toggleAutoBtn">${this.autoFavoriteEnabled ? '禁用自动收藏' : '启用自动收藏'}</button>
<button class="auto-favorite-action-btn" id="testMatchBtn">测试匹配</button>
<button class="auto-favorite-action-btn danger" id="clearDataBtn">清空数据</button>
</div>
`;
document.body.appendChild(panel);
// 绑定事件
this.bindEvents(panel);
console.log('✅ 用户界面创建完成');
}
// 绑定事件
bindEvents(panel) {
// 关闭按钮
panel.querySelector('.auto-favorite-close').onclick = () => this.hidePanel();
// 文件导入
const importFile = panel.querySelector('#importFile');
const importBtn = panel.querySelector('#importBtn');
importBtn.onclick = () => this.importData(importFile);
// 保存忽略字符设置
const saveIgnoreBtn = panel.querySelector('#saveIgnoreBtn');
saveIgnoreBtn.onclick = () => this.saveIgnoreCharsSetting();
// 保存包含匹配设置
const saveContainBtn = panel.querySelector('#saveContainBtn');
saveContainBtn.onclick = () => this.saveContainSetting();
// 自定义字符串测试
const testStringBtn = panel.querySelector('#testStringBtn');
testStringBtn.onclick = () => this.testCustomString();
// 切换自动收藏
panel.querySelector('#toggleAutoBtn').onclick = () => this.toggleAutoFavorite();
// 测试匹配
panel.querySelector('#testMatchBtn').onclick = () => this.testMatching();
// 清空数据
panel.querySelector('#clearDataBtn').onclick = () => this.clearData();
}
// 显示/隐藏面板
togglePanel() {
const panel = document.querySelector('.auto-favorite-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
hidePanel() {
const panel = document.querySelector('.auto-favorite-panel');
panel.style.display = 'none';
}
// 导入数据
importData(fileInput) {
const file = fileInput.files[0];
if (!file) {
this.showMessage('请选择要导入的文件', 'error');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const jsonData = JSON.parse(e.target.result);
// 检测导入格式类型
const importType = this.detectImportFormat(jsonData);
console.log(`📋 检测到导入格式: ${importType}`);
let newFavorites = [];
let importMessage = '';
if (importType === 'favorites') {
// 标准收藏格式
if (!jsonData.favorites || !Array.isArray(jsonData.favorites)) {
this.showMessage('文件格式不正确,请确保包含favorites数组', 'error');
return;
}
newFavorites = jsonData.favorites.map(fav => ({
id: fav.id,
title: fav.title,
url: fav.url,
images: fav.images || [],
timestamp: fav.timestamp || Date.now(),
superLiked: fav.superLiked || false,
hasImages: fav.hasImages || (fav.images && fav.images.length > 0)
}));
importMessage = `成功导入 ${newFavorites.length} 个收藏项`;
} else if (importType === 'blacklist') {
// 黑名单格式
if (!jsonData.blacklist || !Array.isArray(jsonData.blacklist)) {
this.showMessage('黑名单格式不正确,请确保包含blacklist数组', 'error');
return;
}
newFavorites = jsonData.blacklist.map((title, index) => ({
id: `blacklist_${Date.now()}_${index}`,
title: title,
url: '',
images: [],
timestamp: Date.now(),
superLiked: false,
hasImages: false,
source: 'blacklist' // 标记来源
}));
importMessage = `成功从黑名单导入 ${newFavorites.length} 个收藏项`;
} else {
this.showMessage('不支持的文件格式,请使用标准收藏格式或黑名单格式', 'error');
return;
}
// 追加去重处理
const result = this.appendAndDeduplicate(newFavorites);
this.saveImportedFavorites();
this.updateStatus();
this.showMessage(`${importMessage},新增 ${result.added} 个,跳过重复 ${result.duplicates} 个`, 'success');
// 清空文件输入
fileInput.value = '';
} catch (e) {
console.error('导入失败:', e);
this.showMessage('文件解析失败,请检查JSON格式', 'error');
}
};
reader.readAsText(file);
}
// 检测导入格式类型
detectImportFormat(jsonData) {
console.log('🔍 开始检测导入格式...');
console.log('📋 JSON数据结构:', Object.keys(jsonData));
// 检查是否包含favorites数组
if (jsonData.favorites && Array.isArray(jsonData.favorites)) {
console.log('✅ 检测到标准收藏格式 (favorites)');
return 'favorites';
}
// 检查是否包含blacklist数组
if (jsonData.blacklist && Array.isArray(jsonData.blacklist)) {
console.log('✅ 检测到黑名单格式 (blacklist)');
return 'blacklist';
}
// 检查是否是直接的数组格式(兼容旧版本)
if (Array.isArray(jsonData)) {
console.log('✅ 检测到直接数组格式 (legacy)');
return 'favorites';
}
console.log('❌ 未识别的格式');
return 'unknown';
}
// 追加去重处理
appendAndDeduplicate(newFavorites) {
console.log('🔄 开始追加去重处理...');
console.log(`📊 当前收藏数量: ${this.importedFavorites.length}`);
console.log(`📊 新导入数量: ${newFavorites.length}`);
let added = 0;
let duplicates = 0;
newFavorites.forEach(newFav => {
// 检查是否已存在(基于标题进行去重)
const exists = this.importedFavorites.some(existingFav => {
const normalizedExisting = this.normalizeString(existingFav.title);
const normalizedNew = this.normalizeString(newFav.title);
return normalizedExisting === normalizedNew;
});
if (!exists) {
this.importedFavorites.push(newFav);
added++;
console.log(`✅ 新增收藏: "${newFav.title}"`);
} else {
duplicates++;
console.log(`⏭️ 跳过重复: "${newFav.title}"`);
}
});
console.log(`📊 去重结果: 新增 ${added} 个,跳过重复 ${duplicates} 个`);
console.log(`📊 最终收藏数量: ${this.importedFavorites.length}`);
return { added, duplicates };
}
// 保存忽略字符设置
saveIgnoreCharsSetting() {
const input = document.querySelector('#ignoreChars');
const inputValue = input.value.trim();
if (!inputValue) {
this.showMessage('请输入要忽略的字符或字符串', 'error');
return;
}
try {
// 解析输入的字符/字符串,支持逗号分隔
const chars = inputValue.split(',').map(char => char.trim()).filter(char => char.length > 0);
if (chars.length === 0) {
this.showMessage('请输入有效的字符或字符串', 'error');
return;
}
this.ignoreChars = chars;
this.saveIgnoreChars();
this.updateIgnoreCharsDisplay();
console.log(`✅ 已保存忽略字符/字符串设置: ${chars.length} 个`);
chars.forEach((char, index) => {
console.log(` ${index + 1}. "${char}" (长度: ${char.length})`);
});
this.showMessage(`已保存 ${chars.length} 个忽略字符/字符串设置`, 'success');
// 清空输入框
input.value = '';
} catch (e) {
console.error('❌ 保存忽略字符设置失败:', e);
this.showMessage('保存设置失败', 'error');
}
}
// 更新忽略字符显示
updateIgnoreCharsDisplay() {
const display = document.querySelector('#currentIgnoreChars');
if (display) {
display.textContent = this.ignoreChars.join(', ') || '无';
}
}
// 保存包含匹配设置
saveContainSetting() {
const input = document.querySelector('#minLength');
const inputValue = input.value.trim();
if (!inputValue) {
this.showMessage('请输入最小匹配长度', 'error');
return;
}
const length = parseInt(inputValue);
if (isNaN(length) || length < 1 || length > 100) {
this.showMessage('请输入1-100之间的有效数字', 'error');
return;
}
this.minMatchLength = length;
this.saveMinMatchLength();
this.updateMinLengthDisplay();
console.log(`✅ 已保存最小匹配长度设置: ${length}`);
this.showMessage(`已保存最小匹配长度: ${length}`, 'success');
}
// 更新最小长度显示
updateMinLengthDisplay() {
const display = document.querySelector('#currentMinLength');
if (display) {
display.textContent = this.minMatchLength;
}
}
// 自定义字符串测试
testCustomString() {
const input = document.querySelector('#testString');
const testString = input.value.trim();
if (!testString) {
this.showMessage('请输入要测试的字符串', 'error');
return;
}
console.log('🧪 开始自定义字符串测试...');
console.log(`📝 测试字符串: "${testString}"`);
console.log(`📊 当前设置:`);
console.log(` - 导入收藏数量: ${this.importedFavorites.length}`);
console.log(` - 忽略字符/字符串: ${this.ignoreChars.length} 个`);
console.log(` - 最小匹配长度: ${this.minMatchLength}`);
if (this.ignoreChars.length > 0) {
console.log(` - 忽略列表: ${this.ignoreChars.join(', ')}`);
}
if (this.importedFavorites.length === 0) {
this.showMessage('请先导入收藏数据', 'error');
console.log('❌ 测试失败:未导入收藏数据');
return;
}
console.log(`\n🔍 开始详细匹配测试...`);
// 测试各种匹配逻辑
const matchResult = this.testSingleMatchDetailed(testString);
if (matchResult) {
console.log(`\n✅ 匹配成功!`);
console.log(` - 匹配类型: ${matchResult.type}`);
console.log(` - 匹配详情: ${matchResult.details}`);
console.log(` - 匹配的收藏ID: ${matchResult.matched.id}`);
console.log(` - 匹配的收藏标题: "${matchResult.matched.title}"`);
this.showMessage(`匹配成功: ${matchResult.type}`, 'success');
} else {
console.log(`\n❌ 未找到匹配的收藏`);
console.log(`\n📋 导入的收藏标题列表:`);
this.importedFavorites.forEach((fav, index) => {
console.log(` ${index + 1}. "${fav.title}" (ID: ${fav.id})`);
});
this.showMessage('未找到匹配的收藏', 'error');
}
// 清空输入框
input.value = '';
}
// 切换自动收藏状态
toggleAutoFavorite() {
this.autoFavoriteEnabled = !this.autoFavoriteEnabled;
this.updateStatus();
const btn = document.querySelector('#toggleAutoBtn');
btn.textContent = this.autoFavoriteEnabled ? '禁用自动收藏' : '启用自动收藏';
console.log(`🔄 自动收藏状态切换: ${this.autoFavoriteEnabled ? '已启用' : '已禁用'}`);
console.log(` - 导入收藏数量: ${this.importedFavorites.length}`);
console.log(` - 已处理项目: ${this.processedItems.size}`);
this.showMessage(
this.autoFavoriteEnabled ? '自动收藏已启用' : '自动收藏已禁用',
'info'
);
}
// 测试匹配功能
testMatching() {
console.log('🧪 开始测试匹配功能...');
if (this.importedFavorites.length === 0) {
this.showMessage('请先导入收藏数据', 'error');
console.log('❌ 测试失败:未导入收藏数据');
return;
}
const items = document.querySelectorAll('.pbw');
if (items.length === 0) {
this.showMessage('当前页面没有找到列表项', 'error');
console.log('❌ 测试失败:当前页面没有找到列表项');
return;
}
console.log(`📋 开始测试 ${items.length} 个列表项的匹配情况...`);
let testResults = {
total: 0,
matched: 0,
matchTypes: {
containMatch: 0
}
};
items.forEach((item, index) => {
const link = item.querySelector('a');
if (!link) return;
const title = link.textContent.trim();
const id = item.id;
if (!id) return;
testResults.total++;
console.log(`\n📝 测试第${index + 1}项: "${title}" (ID: ${id})`);
// 测试各种匹配逻辑
const matchResult = this.testSingleMatch(title);
if (matchResult) {
testResults.matched++;
testResults.matchTypes[matchResult.type]++;
console.log(`✅ 匹配成功 (${matchResult.type}): ${matchResult.details}`);
} else {
console.log(`❌ 未匹配`);
}
});
// 输出测试结果统计
console.log(`\n📊 测试结果统计:`);
console.log(` - 总测试项: ${testResults.total}`);
console.log(` - 匹配成功: ${testResults.matched}`);
console.log(` - 匹配率: ${((testResults.matched / testResults.total) * 100).toFixed(1)}%`);
console.log(` - 包含匹配: ${testResults.matchTypes.containMatch}`);
this.showMessage(`测试完成: ${testResults.matched}/${testResults.total} 项匹配成功`, 'info');
}
// 测试单个标题的匹配情况
testSingleMatch(title) {
// 标准化当前标题
const normalizedCurrentTitle = this.normalizeString(title);
// 包含匹配逻辑:导入的收藏名称包含列表页标题
const matched = this.importedFavorites.find(fav => {
const normalizedFavTitle = this.normalizeString(fav.title);
return normalizedFavTitle.includes(normalizedCurrentTitle);
});
if (matched) {
return {
type: 'containMatch',
details: `包含匹配: "${title}" -> "${matched.title}" (标准化: "${this.normalizeString(matched.title)}" 包含 "${normalizedCurrentTitle}")`
};
}
return null;
}
// 详细的单个匹配测试
testSingleMatchDetailed(title) {
console.log(`\n🔍 测试标题: "${title}"`);
console.log(`📏 标题长度: ${title.length}`);
// 标准化当前标题
const normalizedCurrentTitle = this.normalizeString(title);
console.log(`📝 当前标题标准化: "${normalizedCurrentTitle}"`);
// 包含匹配逻辑:导入的收藏名称包含列表页标题
console.log(`\n1️⃣ 测试包含匹配...`);
const matched = this.importedFavorites.find(fav => {
const normalizedFavTitle = this.normalizeString(fav.title);
const isMatch = normalizedFavTitle.includes(normalizedCurrentTitle);
console.log(` - 导入标题: "${fav.title}"`);
console.log(` - 导入标题标准化: "${normalizedFavTitle}"`);
console.log(` - 包含检查: "${normalizedFavTitle}" 包含 "${normalizedCurrentTitle}" -> ${isMatch}`);
return isMatch;
});
if (matched) {
return {
type: 'containMatch',
details: `包含匹配: "${title}" -> "${matched.title}" (标准化包含匹配)`,
matched: matched
};
}
console.log(`\n❌ 包含匹配未成功`);
return null;
}
// 清空数据
clearData() {
if (confirm('确定要清空所有导入的收藏数据吗?此操作不可恢复。')) {
this.importedFavorites = [];
this.processedItems.clear();
this.saveImportedFavorites();
this.updateStatus();
this.showMessage('数据已从localStorage清空', 'info');
}
}
// 更新状态显示
updateStatus() {
const importedCount = document.querySelector('#importedCount');
const autoStatus = document.querySelector('#autoStatus');
const processedCount = document.querySelector('#processedCount');
if (importedCount) importedCount.textContent = this.importedFavorites.length;
if (autoStatus) autoStatus.textContent = this.autoFavoriteEnabled ? '已启用' : '已禁用';
if (processedCount) processedCount.textContent = this.processedItems.size;
this.updateIgnoreCharsDisplay();
this.updateMinLengthDisplay();
}
// 开始自动处理
startAutoProcess() {
console.log('🚀 自动收藏功能已启动,开始监控页面变化...');
// 每3秒检查一次页面变化
setInterval(() => {
if (this.autoFavoriteEnabled && this.importedFavorites.length > 0) {
this.processListPage();
} else if (this.autoFavoriteEnabled && this.importedFavorites.length === 0) {
console.log('⚠️ 自动收藏已启用但未导入收藏数据');
}
}, 3000);
// 初始检查
setTimeout(() => {
if (this.autoFavoriteEnabled && this.importedFavorites.length > 0) {
console.log('📋 执行初始页面扫描...');
this.processListPage();
}
}, 1000);
}
// 处理列表页
processListPage() {
const items = document.querySelectorAll('.pbw');
let processedCount = 0;
let matchedCount = 0;
let alreadyFavoritedCount = 0;
let newItemsCount = 0;
console.log(`🔍 开始扫描页面,发现 ${items.length} 个列表项`);
items.forEach((item, index) => {
const link = item.querySelector('a');
if (!link) {
console.log(`❌ 第${index + 1}项:未找到链接元素`);
return;
}
const title = link.textContent.trim();
const url = link.href;
const id = item.id;
if (!id) {
console.log(`❌ 第${index + 1}项:未找到ID,标题: "${title}"`);
return;
}
if (this.processedItems.has(id)) {
console.log(`⏭️ 第${index + 1}项:已处理过,跳过 - "${title}"`);
return;
}
newItemsCount++;
console.log(`📝 第${index + 1}项:检查项目 - "${title}" (ID: ${id})`);
// 检查是否匹配导入的收藏
const matchedFavorite = this.findMatchingFavorite(title);
if (matchedFavorite) {
matchedCount++;
console.log(`✅ 第${index + 1}项:找到匹配的收藏 - "${title}"`);
// 检查是否已经收藏 - 查找 list-item-buttons 容器下的第一个按钮
const buttonContainer = item.querySelector('.list-item-buttons');
if (buttonContainer) {
const favoriteBtn = buttonContainer.querySelector('button:first-child');
if (favoriteBtn) {
if (favoriteBtn.classList.contains('favorited')) {
alreadyFavoritedCount++;
console.log(`💚 第${index + 1}项:已经收藏,跳过 - "${title}"`);
} else {
console.log(`🎯 第${index + 1}项:准备自动收藏 - "${title}"`);
// 自动点击收藏按钮
this.autoClickFavorite(favoriteBtn, id, title, url);
processedCount++;
}
} else {
console.log(`❌ 第${index + 1}项:未找到收藏按钮 - "${title}"`);
}
} else {
console.log(`❌ 第${index + 1}项:未找到按钮容器 - "${title}"`);
}
} else {
console.log(`❌ 第${index + 1}项:未匹配到导入的收藏 - "${title}"`);
}
// 标记为已处理
this.processedItems.add(id);
});
// 输出扫描结果统计
console.log(`📊 扫描完成统计:`);
console.log(` - 总列表项: ${items.length}`);
console.log(` - 新项目: ${newItemsCount}`);
console.log(` - 匹配收藏: ${matchedCount}`);
console.log(` - 已收藏: ${alreadyFavoritedCount}`);
console.log(` - 新收藏: ${processedCount}`);
console.log(` - 已处理总数: ${this.processedItems.size}`);
if (processedCount > 0) {
console.log(`🎉 本次自动收藏了 ${processedCount} 个项目`);
this.updateStatus();
} else if (matchedCount > 0) {
console.log(`ℹ️ 找到 ${matchedCount} 个匹配项,但都已收藏或无法操作`);
} else {
console.log(`ℹ️ 本次扫描未找到可自动收藏的项目`);
}
}
// 查找匹配的收藏
findMatchingFavorite(title) {
console.log(`🔍 开始匹配标题: "${title}"`);
// 标准化当前标题
const normalizedCurrentTitle = this.normalizeString(title);
// 包含匹配逻辑:导入的收藏名称包含列表页标题
const matched = this.importedFavorites.find(fav => {
const normalizedFavTitle = this.normalizeString(fav.title);
// 检查导入的收藏标题是否包含当前标题
return normalizedFavTitle.includes(normalizedCurrentTitle);
});
if (matched) {
console.log(`✅ 匹配成功 (包含匹配): "${title}" -> 收藏ID: ${matched.id}`);
console.log(` - 当前标题标准化: "${normalizedCurrentTitle}"`);
console.log(` - 导入标题标准化: "${this.normalizeString(matched.title)}"`);
console.log(` - 包含关系: "${this.normalizeString(matched.title)}" 包含 "${normalizedCurrentTitle}"`);
return matched;
}
console.log(`❌ 未找到匹配的收藏: "${title}"`);
return null;
}
// 标准化字符串,用于匹配对比
normalizeString(str) {
if (!str || typeof str !== 'string') {
return str;
}
let normalized = str;
// 1. 去掉第一个空格前面的内容
const spaceIndex = normalized.indexOf(' ');
if (spaceIndex !== -1) {
normalized = normalized.substring(spaceIndex + 1).trim();
}
// 2. 移除所有空格、制表符、换行符等空白字符
normalized = normalized.replace(/\s+/g, '');
// 3. 移除所有标点符号和特殊字符
normalized = normalized.replace(/[.,;:!?()[\]{}"'`~@#$%^&*+=|\\/<>]/g, '');
// 4. 转换为大写
normalized = normalized.toUpperCase();
// 5. 去掉"百度网盘"和"..."
normalized = normalized.replace(/百度网盘/g, '');
normalized = normalized.replace(/\.\.\./g, '');
console.log(`🔄 字符串标准化: "${str}" -> "${normalized}"`);
return normalized;
}
// 处理标题文本,应用忽略字符和标准化处理(保留原函数以兼容)
processTitle(title) {
return this.normalizeString(title);
}
// 自动点击收藏按钮
autoClickFavorite(btn, id, title, url) {
try {
console.log(`🎯 开始自动收藏操作: "${title}" (ID: ${id})`);
console.log(` - 按钮状态: ${btn.className}`);
console.log(` - 按钮文本: ${btn.textContent}`);
console.log(` - 链接地址: ${url}`);
// 检查是否存在修眉收藏功能脚本的页面处理器
if (window.pageHandler && window.pageHandler.toggleFavorite) {
console.log(`🔄 使用修眉收藏功能脚本的收藏方法`);
// 使用修眉收藏功能脚本的收藏方法
window.pageHandler.toggleFavorite(id, title, url, btn);
console.log(`✅ 通过修眉收藏功能脚本收藏成功: "${title}" (ID: ${id})`);
} else {
console.log(`🔄 使用原生点击事件`);
// 模拟点击事件 - 修复view属性问题,使用更兼容的方式
try {
// 方法1:尝试使用document.defaultView
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: document.defaultView
});
btn.dispatchEvent(event);
console.log(`✅ 通过原生点击事件收藏成功: "${title}" (ID: ${id})`);
} catch (e) {
console.log(`⚠️ 方法1失败,尝试方法2: ${e.message}`);
try {
// 方法2:不指定view属性
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true
});
btn.dispatchEvent(event);
console.log(`✅ 通过原生点击事件收藏成功: "${title}" (ID: ${id})`);
} catch (e2) {
console.log(`⚠️ 方法2失败,尝试方法3: ${e2.message}`);
// 方法3:直接调用click方法
btn.click();
console.log(`✅ 通过直接click方法收藏成功: "${title}" (ID: ${id})`);
}
}
}
// 显示成功消息
this.showMessage(`自动收藏: ${title}`, 'success');
// 延迟检查按钮状态变化
setTimeout(() => {
const newBtnState = btn.className;
const newBtnText = btn.textContent;
console.log(`🔄 收藏后按钮状态检查:`);
console.log(` - 新按钮状态: ${newBtnState}`);
console.log(` - 新按钮文本: ${newBtnText}`);
if (newBtnState.includes('favorited') || newBtnText.includes('已收藏')) {
console.log(`✅ 确认收藏状态已更新: "${title}"`);
} else {
console.log(`⚠️ 收藏状态可能未正确更新: "${title}"`);
}
}, 1000);
} catch (e) {
console.error(`❌ 自动收藏失败: "${title}"`, e);
this.showMessage(`自动收藏失败: ${title}`, 'error');
}
}
// 显示消息
showMessage(message, type = 'info') {
// 移除现有消息
const existingMessage = document.querySelector('.auto-favorite-message');
if (existingMessage) {
existingMessage.remove();
}
// 创建新消息
const messageEl = document.createElement('div');
messageEl.className = `auto-favorite-message auto-favorite-message-${type}`;
messageEl.textContent = message;
document.body.appendChild(messageEl);
// 3秒后自动移除
setTimeout(() => {
if (messageEl.parentNode) {
messageEl.remove();
}
}, 3000);
}
}
// 等待页面加载完成后初始化
console.log('📄 页面加载状态:', document.readyState);
console.log('🌐 当前页面URL:', window.location.href);
// 检查localStorage中是否有收藏数据
const hasFavData = localStorage.getItem('fav-list');
if (hasFavData) {
console.log('📂 检测到localStorage中有收藏数据,准备自动运行');
} else {
console.log('📭 localStorage中暂无收藏数据');
}
function initializeAutoFavorite() {
console.log('✅ 开始初始化自动收藏管理器');
new AutoFavoriteManager();
}
if (document.readyState === 'loading') {
console.log('⏳ 等待页面加载完成...');
document.addEventListener('DOMContentLoaded', initializeAutoFavorite);
} else {
console.log('✅ 页面已加载完成,立即初始化自动收藏管理器');
initializeAutoFavorite();
}
// 监听页面变化,确保在单页应用中也能正常工作
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
console.log('🔄 检测到页面URL变化,重新初始化自动收藏管理器');
setTimeout(initializeAutoFavorite, 1000); // 延迟1秒确保页面完全加载
}
}).observe(document, { subtree: true, childList: true });
})();
Wrap
Beautify