Greasy Fork

AO3 关键词检测&折叠 | AO3 Keyword Detection & Collapse

检测关键词,折叠对应内容,并显示相应代替语句。v0.5.5支持指定区域:作品标题、所有标签、角色标签、关系标签、作品摘要、用户名(精准匹配、可设置白名单)。

目前为 2024-08-26 提交的版本。查看 最新版本

// ==UserScript==
// @name         AO3 关键词检测&折叠 | AO3 Keyword Detection & Collapse
// @author       ghostupload
// @namespace    urovom@游快贴贴
// @version      0.5.5
// @description  检测关键词,折叠对应内容,并显示相应代替语句。v0.5.5支持指定区域:作品标题、所有标签、角色标签、关系标签、作品摘要、用户名(精准匹配、可设置白名单)。
// @match        https://archiveofourown.org/*
// @icon         https://vi.ag925.top/download/ao3_content_filter_64x.ico
// @license      MIT
// @grant        none
// @AO3publish   https://archiveofourown.org/chapters/148721791
// ==/UserScript==
 
(function() {
    'use strict';
 
	// !!!请自定义修改此处折叠规则!!!
 
	// 自定义:检测位置、关键词、提示词、提示词颜色。
	// 检测位置参考示例,可选:作者、作品标题、所有标签、角色标签、关系标签、作品摘要。
 
	// ===============【规则】===============
 
    const filterRules = [
        { type: 'author', keywords: ['用户名A','用户名B'], replace: '已屏蔽用户', color: '#FF0000' },
        { type: 'title', keywords: ['甲乙','乙甲'], replace: '甲乙甲', color: '#AA3333' },
        { type: 'tag', keywords: ['Everyone loves','everyone loves','Everyone Loves'], replace: '万人迷tag!', color: '#AA00AA' },
        { type: 'characters', keywords: ['Original Character'], replace: '原创角色', color: '#3333AA' },
        { type: 'relationships', keywords: ['Original Female Character','Original Character','Original Male Character'], replace: '原创角色参与CP', color: '#AA3333' },
        { type: 'summary', keywords: ['甲乙','乙甲'], replace: '甲乙甲', color: '#AA3333' },
        { type: 'author', keywords: ['ghostupload'], replace: '一只野生的插件作者', color: '#238080' },
    ];
 
    // ==========================================
 
	// =============【用户名白名单】=============
	// 默认跳过检测
    const excludeAuthors = ['ghostupload', 'ghostuploader'];
    // ==========================================
 
    function filterContent() {
        const works = document.querySelectorAll('.blurb');
 
        works.forEach(work => {
			
			//检查用户名是否在白名单中
			const authorLink = work.querySelector('a[rel="author"]');
			const authorHref = authorLink ? authorLink.getAttribute('href') : '';
			if (excludeAuthors.some(author => authorHref.includes(`/${author}/`))) {
				return;
			}
			
            let replaceTexts = [];
            let found = new Set();
            const originalContent = {
                header: work.querySelector('.header.module')?.outerHTML || '',
                tags: work.querySelector('.tags.commas')?.outerHTML || '',
                summary: work.querySelector('.userstuff.summary')?.outerHTML || '',
                stats: work.querySelector('.stats')?.outerHTML || '',
            };
 
            // 读取语言信息
            const languageElement = work.querySelector('.stats .language + dd.language');
            const languageText = languageElement ? languageElement.innerText : '';
            const languageDisplay = createLanguageInfo('Language: ', languageText, '#AAAAAA');
 
        filterRules.forEach(rule => {
            if (found.has(rule.replace)) return;
 
            switch (rule.type) {
                case 'author': {
                    if (authorHref && rule.keywords.some(keyword => authorHref.includes(`/${keyword}/`))) {
                        replaceTexts.push(createReplaceText('Author: ', rule.replace, rule.color));
						found.add(rule.replace);
                    }
                    break;
                }
                case 'title': {
                    const titleText = work.querySelector('h4.heading a')?.innerText;
                    if (titleText && rule.keywords.some(keyword => titleText.includes(keyword))) {
                        replaceTexts.push(createReplaceText('Title: ', rule.replace, rule.color));
                        found.add(rule.replace);
                        return;
                    }
                    break;
                }
                case 'tag': {
                    const tags = work.querySelectorAll('.tags .tag');
                    for (const tag of tags) {
                        if (rule.keywords.some(keyword => tag.innerText.includes(keyword))) {
                            replaceTexts.push(createReplaceText('Tags: ', rule.replace, rule.color));
                            found.add(rule.replace);
                            return;
                        }
                    }
                    break;
                }
                case 'characters': {
                    const characterTags = work.querySelectorAll('.tags .characters .tag');
                    for (const tag of characterTags) {
                        if (rule.keywords.some(keyword => tag.innerText.includes(keyword))) {
                            replaceTexts.push(createReplaceText('Characters: ', rule.replace, rule.color));
                            found.add(rule.replace);
                            return;
                        }
                    }
                    break;
                }
                case 'relationships': {
                    const relationshipTags = work.querySelectorAll('.tags .relationships .tag');
                    for (const tag of relationshipTags) {
                        if (rule.keywords.some(keyword => tag.innerText.includes(keyword))) {
                            replaceTexts.push(createReplaceText('Relationships: ', rule.replace, rule.color));
                            found.add(rule.replace);
                            return;
                        }
                    }
                    break;
                }
                case 'summary': {
                    const summaryText = work.querySelector('.userstuff.summary')?.innerText;
                    if (summaryText && rule.keywords.some(keyword => summaryText.includes(keyword))) {
                        replaceTexts.push(createReplaceText('Summary: ', rule.replace, rule.color));
                        found.add(rule.replace);
                        return;
                    }
                    break;
                }
            }
        });
 
            if (replaceTexts.length > 0) {
                work.innerHTML = '';
                replaceTexts.forEach(text => {
                    work.appendChild(text);
                });
 
                // 添加语言信息
                if (languageText) {
                    work.appendChild(languageDisplay);
                }
 
                // 添加按钮
                const buttonContainer = document.createElement('div');
                buttonContainer.style.margin = '1em 0.5em 0.5em';
 
                const moreButton = document.createElement('span');
                moreButton.innerText = 'more';
                moreButton.style.color = '#CCCCCC';
                moreButton.style.fontWeight = 'bold';
                moreButton.style.cursor = 'pointer';
                moreButton.style.display = 'block';
                buttonContainer.appendChild(moreButton);
 
                const buttonGroup = document.createElement('div');
                buttonGroup.style.display = 'none';
                buttonGroup.style.marginTop = '0.5em';
 
                const buttons = [
                    { text: 'Header', content: originalContent.header },
                    { text: 'Tags', content: originalContent.tags },
                    { text: 'Summary', content: originalContent.summary },
                    { text: 'Stats', content: originalContent.stats },
                    { text: 'Show All', content: originalContent.header + originalContent.tags + originalContent.summary + originalContent.stats },
                ];
 
                buttons.forEach(buttonInfo => {
                    const button = document.createElement('button');
                    button.innerText = buttonInfo.text;
                    button.style.width = '5em';
                    button.style.margin = '0.2em';
                    button.style.border = '1px solid #808080';
                    button.style.padding = '2px';
                    button.style.cursor = 'pointer';
                    button.style.borderRadius = '0';
                    button.style.background = '#F6F6FF';
 
                    button.addEventListener('click', () => {
                        const existingContent = work.querySelector('.original-content');
 
                        if (existingContent && existingContent.innerHTML === buttonInfo.content) {
                            existingContent.remove();
                        } else {
                            if (existingContent) {
                                existingContent.remove();
                            }
                            if (buttonInfo.content) {
                                const contentDiv = document.createElement('div');
                                contentDiv.className = 'original-content';
                                contentDiv.innerHTML = buttonInfo.content;
                                work.appendChild(contentDiv);
                            }
                        }
                    });
 
                    buttonGroup.appendChild(button);
                });
 
                moreButton.addEventListener('click', () => {
                    if (buttonGroup.style.display === 'none') {
                        buttonGroup.style.display = 'block';
                    } else {
                        buttonGroup.style.display = 'none';
                        const existingContents = work.querySelectorAll('.original-content');
                        existingContents.forEach(content => content.remove());
                    }
                });
 
                buttonContainer.appendChild(buttonGroup);
                work.appendChild(buttonContainer);
            }
        });
    }
 
	// 生成提示句
    function createReplaceText(prefix, replace, color) {
        const p = document.createElement('p');
        p.style.margin = '1em 0 0.5em';
        p.style.fontWeight = 'bold';
 
        const prefixSpan = document.createElement('span');
        prefixSpan.style.color = color;
        prefixSpan.innerText = prefix;
 
        const replaceSpan = document.createElement('span');
        replaceSpan.style.color = color;
        replaceSpan.innerText = replace;
 
        p.appendChild(prefixSpan);
        p.appendChild(replaceSpan);
 
        return p;
    }
 
	// 显示语言
    function createLanguageInfo(prefix, content, color) {
        const p = document.createElement('p');
        p.style.margin = '1em 0 0.5em';
        p.style.fontWeight = 'bold';
        p.style.color = color;
        p.innerText = prefix + content;
        return p;
    }
 
    // 当页面加载时执行过滤函数
    window.addEventListener('load', filterContent);
})();