// ==UserScript==
// @name ACGSecrets Bangumi 分類抓取
// @namespace https://github.com/downwarjers/WebTweaks
// @version 2.3.2
// @description 針對 ACGSecrets.hk 網站,依據作品標籤(如「續作」、「新作」、「家長指引」)與名稱規則(正則表達式判斷季數、篇章),將新番列表自動分類為八大類。在頁面右下角提供「複製分類結果」與「下載 txt」按鈕。
// @author downwarjers
// @license MIT
// @match https://acgsecrets.hk/bangumi/*
// @grant GM_setClipboard
// @grant GM_download
// @downloadURL https://raw.githubusercontent.com/downwarjers/WebTweaks/main/UserScripts/acgsecrets-bangumi-copy/acgsecrets-bangumi-copy.user.js
// @updateURL https://raw.githubusercontent.com/downwarjers/WebTweaks/main/UserScripts/acgsecrets-bangumi-copy/acgsecrets-bangumi-copy.user.js
// ==/UserScript==
(function() {
'use strict';
// 支援半/全形數字:[0-90-9],以及中文數字、羅馬數字,新增中文大寫數字
const num = '[0-90-9一二三四五六七八九十壹貳參肆伍陸柒捌玖拾零佰仟萬ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]';
const numPlus = num + '+';
const fullNum = `[${num}]`;
// 內容續作作品模式:明確的數字或季數標示
const contentSequelPatterns = [
new RegExp(`第\\s*${numPlus}\\s*(季|部分|期|季度)`, 'i'),
new RegExp(`Season\\s*${numPlus}`, 'i'),
new RegExp(`S\\s*${numPlus}`, 'i'),
new RegExp(`\\s*${fullNum}(\\.|、)?${fullNum}\\s*$`, 'i'),
new RegExp(`\\s*${fullNum}$`, 'i'),
new RegExp(`\\s*${fullNum}\\s*(後篇|前篇|下篇|上篇)$`, 'i')
];
// 內容特別篇/續篇模式:包含「篇」、「幕」、「續篇」等關鍵字
const contentArcPatterns = [
new RegExp(`(續篇|篇|幕|章|外傳|劇場版|電影版|OVA|OAD|WEB|SP|特別篇)`, 'i'),
new RegExp(`[\\u4E00-\\u9FFF]+\\s*(篇|幕|章)`, 'i')
];
// 輔助函式:判斷名稱是否符合續作/特別篇模式
function isContentSequelOrArc(name) {
return contentSequelPatterns.some(p => p.test(name)) || contentArcPatterns.some(p => p.test(name));
}
function extractNames() {
const workNodes = document.querySelectorAll('div.clear-both.acgs-anime-block');
const continuingAndSequel = []; // 跨季繼續播放 且 符合續作判斷
const continuingAndNew = []; // 跨季繼續播放 且 為新作
const parentalAndSequel = []; // 家長指引 且 符合續作判斷
const parentalAndNew = []; // 家長指引 且 為新作
const taggedSequels = []; // 單純標籤續作 (未被上述組合捕獲)
const taggedNewSeries = []; // 單純標籤新作 (未被上述組合捕獲)
const contentBasedSequelsArcs = []; // 無任何標籤,但名稱符合續作/特別篇
const others = []; // 其他作品
workNodes.forEach(node => {
const nameEl = node.querySelector('div.entity_localized_name');
if (!nameEl) return;
const name = nameEl.textContent.trim();
if (!name) return;
let categorized = false;
// 檢查所有相關標籤和狀態
const mainTags = node.querySelectorAll('tags.main_tags');
let hasTaggedSequel = false;
let hasTaggedNewSeries = false;
mainTags.forEach(tag => {
if (tag.textContent.trim() === '續作') {
hasTaggedSequel = true;
}
if (tag.textContent.trim() === '新作' && tag.classList.contains('anime_new_series')) {
hasTaggedNewSeries = true;
}
});
// 檢查跨季繼續播放
const animeOnairDiv = node.querySelector('div.anime_onair');
const isContinuing = animeOnairDiv && animeOnairDiv.textContent.includes('跨季繼續播放:');
// 檢查家長指引
const animeTagDiv = node.querySelector('div.anime_tag');
let hasParentalGuidance = false;
if (animeTagDiv) {
const allTagsInAnimeTag = animeTagDiv.querySelectorAll('tags');
allTagsInAnimeTag.forEach(tag => {
if (tag.textContent.trim() === '家長指引') {
hasParentalGuidance = true;
}
});
}
// 判斷是否符合內容續作模式 (用於複合判斷)
const isNameSequelOrArc = isContentSequelOrArc(name);
// --- 按照新的優先級順序進行判斷 ---
// 1. 跨季繼續播放 且 符合續作判斷 (包含標籤續作 或 名稱續作)
if (isContinuing && (hasTaggedSequel || isNameSequelOrArc)) {
continuingAndSequel.push(name);
categorized = true;
} else if (isContinuing && hasTaggedNewSeries) { // 2. 跨季繼續播放 且 為新作
continuingAndNew.push(name);
categorized = true;
} else if (hasParentalGuidance && (hasTaggedSequel || isNameSequelOrArc)) { // 3. 家長指引 且 符合續作判斷
parentalAndSequel.push(name);
categorized = true;
} else if (hasParentalGuidance && hasTaggedNewSeries) { // 4. 家長指引 且 為新作
parentalAndNew.push(name);
categorized = true;
} else if (hasTaggedSequel) { // 5. 單純標籤續作 (未被複合條件捕獲)
taggedSequels.push(name);
categorized = true;
} else if (hasTaggedNewSeries) { // 6. 單純標籤新作 (未被複合條件捕獲)
taggedNewSeries.push(name);
categorized = true;
} else if (isNameSequelOrArc) { // 7. 無標籤但依名稱判斷為續作/特別篇
contentBasedSequelsArcs.push(name);
categorized = true;
}
if (categorized) return;
// 8. 其他作品 (所有上述條件都不符合)
others.push(name);
});
return {
continuingAndSequel,
continuingAndNew,
parentalAndSequel,
parentalAndNew,
taggedSequels,
taggedNewSeries,
contentBasedSequelsArcs,
others
};
}
function buildText({
continuingAndSequel,
continuingAndNew,
parentalAndSequel,
parentalAndNew,
taggedSequels,
taggedNewSeries,
contentBasedSequelsArcs,
others
}) {
const lines = [];
const addSection = (title, items) => {
if (items.length > 0) {
if (lines.length > 0) lines.push('');
lines.push(title);
lines.push(...items);
}
};
// 按照新的優先級和您的需求順序輸出
addSection('--- (1) 標籤續作 ---', taggedSequels);
addSection('--- (2) 標籤新作 ---', taggedNewSeries);
addSection('--- (3) 成人向 且 為續作 ---', parentalAndSequel);
addSection('--- (4) 成人向 且 為新作 ---', parentalAndNew);
addSection('--- (5) 跨季繼續播放 且 為續作 ---', continuingAndSequel);
addSection('--- (6) 跨季繼續播放 且 為新作 ---', continuingAndNew);
addSection('--- (7) 無標籤但依名稱判斷為續作/特別篇 ---', contentBasedSequelsArcs);
addSection('--- (8) 其他作品 ---', others);
if (lines.length === 0) {
lines.push('(沒有找到任何作品)');
}
return lines.join('\n');
}
function copyToClipboard(text) {
if (!text) {
alert('沒有內容可複製');
return;
}
try {
GM_setClipboard(text);
const workCount = text.split('\n').filter(line => !line.startsWith('---') && !line.startsWith('(') && line.trim() !== '').length;
alert(`已複製結果,共 ${workCount} 個作品名稱`);
} catch (e) {
console.log(e);
alert('複製失敗,請確認權限設定。');
}
}
function downloadAsTxt(text) {
if (!text) {
alert('沒有內容可下載');
return;
}
const blob = new Blob([text], { type: 'text/plain; charset=utf-8' });
const fn = `${location.pathname.split('/').pop()}_titles.txt`;
if (typeof GM_download === 'function') {
GM_download({ url: URL.createObjectURL(blob), name: fn, saveAs: true });
} else {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fn;
a.click();
}
}
// 核心修正:將資料抓取與文字產生邏輯整合進按鈕事件
function getCategorizedText() {
const data = extractNames();
return buildText(data);
}
function injectUI() {
const p = document.createElement('div');
const buttonStyle = `
position:fixed;
bottom:20px;
right:20px;
background:rgba(0,0,0,0.8);
color:#fff;
padding:10px;
border-radius:8px;
z-index:9999;
font-family:sans-serif;
`;
p.style.cssText=buttonStyle;
p.innerHTML = ``;
document.body.appendChild(p);
// 修正點:點擊時才執行抓取與產生文字,並傳入函式中
p.querySelector('#copyBtn').onclick = () => {
const text = getCategorizedText();
copyToClipboard(text);
};
p.querySelector('#downloadBtn').onclick = () => {
const text = getCategorizedText();
downloadAsTxt(text);
};
}
window.addEventListener('load', () => setTimeout(injectUI, 500));
})();