Greasy Fork

Greasy Fork is available in English.

Hostloc根据关键字和用户名屏蔽帖子

根据关键字和用户名屏蔽帖子,根据用户名屏蔽签名

当前为 2024-07-18 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

    // ==UserScript==
    // @name         Hostloc根据关键字和用户名屏蔽帖子
    // @namespace    https://hostloc.com/
    // @version      0.2.6
    // @description  根据关键字和用户名屏蔽帖子,根据用户名屏蔽签名
    // @author       kiwi
    // @homepage     https://github.com/FlyxFly/hostloc-block-post-and-signature
    // @match        https://hostloc.com/forum-*
    // @match        https://hostloc.com/thread-*
    // @match        https://hostloc.com/forum.php?mod=viewthread&tid=*
    // @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
    // @require      https://unpkg.com/sweetalert/dist/sweetalert.min.js
    // ==/UserScript==

    (function() {
        'use strict';

        const now = function(){
            return new Date().getTime();
        }


        // 移除数组中的空元素
        if(!Array.prototype.trim){
            Array.prototype.trim = function removeEmptyElements () {
                return this.filter((x)=>{
                    return x;
                })
            }
        }


        // 查询数组是否存在某个值,忽略大小写
        if(!Array.prototype.contains){
            Array.prototype.contains = function checkIfInArray (target) {
                for(let i=0;i<this.length;i++){
                    if(this[i].toUpperCase().includes(target.toUpperCase())){
                        return true;
                    }
                }
                return false
            }
        }


        // 将html字符串转换为dom元素
        const htmlToElement = function (html) {
            const template = document.createElement('template');
            html = html.trim(); // Never return a text node of whitespace as the result
            template.innerHTML = html;
            return template.content.firstChild;
        }



        class HostLocBlocker{

            constructor(){
                this.config={
                    blockedUser:[],
                    blockedKeyword:[],
                    blockedSignatureUser:[],
                    pantry:{
                        APIKey:null,
                        basket:null
                    }
                }
                this.dataKeys = ['blockedUser','blockedKeyword','blockedSignatureUser'];
                this.contentStorage=[];
                this.localStorageKey = 'hostlocBlockPlugin';
            }

            saveToLocal(){
                const toBeStored = {
                    timestamp: now(),
                    data:this.config
                }
                
                localStorage.setItem(this.localStorageKey,JSON.stringify(toBeStored));
            }

            // 从localstorage读取数据并加载到this.config
            restoreFromLocal(){
                const data=localStorage.getItem(this.localStorageKey);
                // 如果localStorage中没有数据,则初始化数据
                if(!data){
                    this.saveToLocal();
                    return;
                }

                const jsonData=JSON.parse(data);
                // 如果有指定键值不存在,则初始化数据
                const keys = ['blockedUser', 'blockedKeyword', 'blockedSignatureUser'];
                if(!keys.every(key => jsonData.data.hasOwnProperty(key))) {
                    this.saveToLocal();
                    return;
                }
                // 如果有 pantry 数据,则检查 pantry 数据是否过期,如果过期则从云端获取数据
                if(jsonData.data.pantry.APIKey && jsonData.data.pantry.basket){
                    this.config.pantry = jsonData.data.pantry;
                    if((now() - jsonData.timestamp) > 12*3600*1000){
                        return this.modifyCloudData('get');
                    } 
                }
                for (let x in this.dataKeys) {
                    const key = this.dataKeys[x];
                    this.config[key]=jsonData.data[key];
                }
                

            }

            async modifyCloudData(action){

                // 如果云端数据没有配置APIKey和basket,则保存到本地并提示用户
                if(!this.config.pantry.APIKey || !this.config.pantry.basket){
                    this.saveToLocal();
                    swal({
                        text:'已保存到本地浏览器,清理浏览器缓存可能会导致数据丢失',
                        icon:'info',
                        button: '我知道了'
                    });
                    return false;
                }

                // 操作云端数据
                const url = `https://getpantry.cloud/apiv1/pantry/${this.config.pantry.APIKey}/basket/${this.config.pantry.basket}`;
                switch(action){
                    case 'get':
                        swal({
                            title:'正在获取云端数据',
                            text:'每天第一次打开需要同步数据,请稍后...',
                            icon:'info',
                            button:false
                        });
                        fetch(url,{
                            method:'GET',
                            headers: {
                                'Content-Type': 'application/json'
                            },
                        })
                        .then((response)=>{
                            return response.text()
                        })
                        .then((text)=>{
                            if(text.includes('pantry with id')){
                                throw new Error('Pantry Key 不存在,请检查');
                            }

                            if(text.includes('does not exist')){
                                throw new Error('Baskey 不存在,请检查');
                            }

                            const data = JSON.parse(text);
                            // 检查云端数据是否有对应键值,如果有就加载到本对象中
                            if(data.pantry && data.blockedKeyword && data.blockedSignatureUser && data.blockedUser){
                                this.config = data;
                                swal({
                                    text:'云端数据加载成功',
                                    icon:'success',
                                    button:'好的'
                                });
                            }else{
                                swal({
                                    text:'云端数据格式不正确,初始化数据',
                                    icon:'info',
                                    button: '我知道了'
                                })
                            }

                            // 如果云端数据结构不正确,则直接保存本对象自带的初始化数据
                            this.saveToLocal();
                            this.restoreFromLocal();
                            this.startBlockProcess();
                            
                            console.log('Got data from cloud.',data);
                        }).catch((e)=>{
                            swal({
                                title:'云端数据加载失败',
                                text:e.message,
                                icon:'error',
                                button:'好的'
                            })
                        })
                        
                        break;

                    case 'save':
                        swal({
                            text:'正在保存数据到云端,请稍后',
                            icon:'info',
                            button: false
                        });
                        fetch(url, {
                            method:'POST',
                            headers: {
                                'Content-Type': 'application/json'
                            },
                            body:JSON.stringify(this.config)
                        }).then((res)=>{
                            swal({
                                text:'数据保存到云端成功,可以跳转到其他页面了',
                                icon:'success',
                                button:'好的'
                            });
                            console.log('Save data to cloud',res);
                        }).catch((e)=>{
                            swal({
                                title:'保存失败',
                                text:e.message,
                                icon:'error',
                                button:'好的'
                            });
                        })
                        break;

                    default:
                        return false;
                }
            }


            hideFromList(){
                const blockedKeyword = this.config.blockedKeyword;
                const blockedUser = this.config.blockedUser;
                document.querySelectorAll('#threadlisttableid tbody').forEach((item,index)=>{
                    if(item.id.includes('normalthread')){
                        const title=item.querySelector('a.s.xst').innerText;
                        for (let i = blockedKeyword.length - 1; i >= 0; i--) {
                            if(title.toUpperCase().includes(blockedKeyword[i].toUpperCase())){
                                // item.querySelector('a.s.xst').innerText='已屏蔽';
                                item.style.display='none';
                                break;
                            }
                        }


                        const nameA=item.querySelectorAll('td.by')[0].querySelector('a');
                        if(nameA){
                            const userName=nameA.innerText.trim().toUpperCase();
                            if(blockedUser.contains(userName)){
                                // item.querySelector('a.s.xst').innerText='已屏蔽';
                                item.style.display='none';
                            }
                        }
                    }
                })

            }


            hideReplyAndSignature(){
                const blockedSignatureUser = this.config.blockedSignatureUser;
                const blockedKeyword = this.config.blockedKeyword;
                const contentStorage = this.contentStorage;
                const blockedUser = this.config.blockedUser;
                document.querySelectorAll('#postlist>div').forEach((post)=>{
                    if(!post.id.includes('post_')){
                        return false;
                    }
                    const userLink=post.querySelector('a.xw1');
                    if(userLink){
                        const userName=userLink.innerText.trim();
                        // 根据用户名屏蔽发帖
                        if(userName && blockedUser.includes(userName)){
                            post.style.display='none';
                            return false;
                        }

                        // 根据用户名屏蔽签名
                        if(blockedSignatureUser.includes(userName) && post.querySelector('div.sign')){
                            const signature=post.querySelector('div.sign');
                            const contentText=signature.innerText;
                            const contentHTML=signature.innerHTML;
                            const storageKey=post.id+'signature';
                            contentStorage[storageKey]=contentHTML;
                            signature.innerHTML=`<span style="font-style:italic;font-size:10px;color:gray" class="hidden-by-script" data-restore-key="${storageKey}" title="${contentText}">已屏蔽,鼠标移到此处查看内容,点击还原内容</span>`;
                        }
                    }

                    const tds=post.querySelectorAll('td');
                    tds.forEach((td)=>{
                        // 查找帖子内容容器: td.postmessage_{thread_id}
                        if(td.id.includes('postmessage_')){
                            const content=td.innerText;
                            for (let i = blockedKeyword.length - 1; i >= 0; i--) {
                                // 根据关键字屏蔽发帖内容
                                if(content.toUpperCase().includes(blockedKeyword[i].toUpperCase())){
                                    const contentHTML=td.innerHTML;
                                    const contentText=td.innerText;
                                    contentStorage[post.id]=contentHTML;
                                    td.innerHTML=`<span style="font-style:italic;font-size:10px;color:gray" class="hidden-by-script" data-restore-key="${post.id}" title="${content}">已屏蔽,鼠标移到此处查看内容,点击还原内容</span>`;
                                    break;
                                }

                            }
                        }
                    })
                })
            }

            addSettingButton(){
    
                const p =  document.querySelectorAll('#um p')[1];
                p.appendChild(htmlToElement(`<span class="pipe">|</span>`));
                p.appendChild(htmlToElement(`<a class="showmenu" id="show-block-panel">屏蔽名单设置</a>`));
            
            }


            addSettingPanel(){
                const div = document.createElement('div');
                div.id='hostloc-blocker-panel-wrapper';
                div.innerHTML = `<!DOCTYPE html>
                <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <meta http-equiv="X-UA-Compatible" content="IE=edge">
                    <meta name="viewport" content="width=device-width, initial-scale=1.0">
                    <title>Document</title>
                </head>
                <body>
                    <div id="hostloc-blocker-panel" class="modal">
                        <div class="modal-title">
                            <p>Hostloc 屏蔽插件设置面板</p>
                        </div>
                        <div class="modal-content">
                            <div class="column api-setting">
                                <div class="field">
                                    <label for="">Pantry API Key</label>
                                    <input type="text" placeholder="没有可不填" name="pantry-api-key">
                                </div>
                
                                <div class="field">
                                    <label for="">Pantry Basket</label>
                                    <input type="text" placeholder="没有可不填" name="pantry-basket-name">
                                </div>
                                <p class="help">如填写Pantry API参数,可同时保存到云端。不填则仅保存到本地。<a href="https://getpantry.cloud/" target="_blank">点此注册</a></p>
                                <p class="help">如只填Pantry API参数,则从云端获取配置。</p>
                            </div>
                            <div class="columns">
                                <div class="column">
                                    <label>屏蔽发帖</label>
                                    <p class="help">每行一个<strong>区分大小写</strong></p>
                                    <textarea name="blocked-user" id="blocked-user" cols="15" rows="13"></textarea>
                                </div>
                                <div class="column">
                                    <label>屏蔽签名</label>
                                    <p class="help">每行一个,<strong>区分大小写</strong></p>
                                    <textarea name="blocked-signature-user" id="blocked-signature-user" cols="15" rows="13"></textarea>
                                </div>
                                <div class="column">
                                    <label>屏蔽关键字</label>
                                    <p class="help">每行一个,<strong>不分大小写</strong></p>
                                    <textarea name="blocked-keyword" id="blocked-keyword" cols="15" rows="13"></textarea>
                                </div>
                            </div>
                        </div>
                    
                        <div class="modal-footer">
                        <button class="save">保存并关闭</button>
                        <p class="help">保存后刷新页面生效</p>
                        </div>
                    </div>
                
                    <style>
            
                        #hostloc-blocker-panel{
                            display: none;
                            background-color: white;
                            position: fixed;
                            left: 50%;
                            top: 100px;
                            transform: translateX(-50%);
                            padding: 20px;
                            width: 500px;
                            border-radius: 10px;
                            font-family:"Microsoft Yahei",Georgia, 'Times New Roman', Times, serif;
                            box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
                        }

                        #hostloc-blocker-panel.is-active{
                            display: block
                        }
                
                        #hostloc-blocker-panel .modal-content p{
                            margin-bottom: 0;
                        }
                
                        #hostloc-blocker-panel ::-webkit-scrollbar {
                            width: 10px;
                        }
                
                        #hostloc-blocker-panel .columns{
                            margin-top: 10px;
                            display: flex;
                            justify-content: space-evenly;
                        }
                
                        #hostloc-blocker-panel label{
                            font-weight: 700;
                            font-size: 15px;
                        }
                
                        #hostloc-blocker-panel .modal-title{
                            text-align: center;
                            border-bottom: 1px solid rgb(238, 238, 238);
                        }
                
                        #hostloc-blocker-panel .modal-content {
                            padding-bottom: 20px;
                            border-bottom: 1px solid rgb(238, 238, 238);
                        }
                        #hostloc-blocker-panel .modal-content .help{
                            color: rgb(43, 43, 43);
                            font-size: 11px;
                            margin-top: 0;
                        }
                        #hostloc-blocker-panel .modal-content .api-setting{
                            margin-top: 20px;
                        }
                        #hostloc-blocker-panel .modal-content .api-setting .field{
                            display: flex;
                            width: 100%;
                            position: relative;
                            height: 40px;
                            align-items: center;
                        }
                
                        #hostloc-blocker-panel .modal-content .api-setting label{
                            line-height: 40px;
                            display: block;
                            width: 30%;
                            height: 100%;
                        }
                
                        #hostloc-blocker-panel .modal-content .api-setting input{
                            height: 25px;
                            display: block;
                            width: 70%;
                
                        }
                
                        #hostloc-blocker-panel .modal-content .api-setting::after{
                            clear: both;
                        }
                
                        #hostloc-blocker-panel .modal-content textarea, 
                        #hostloc-blocker-panel .modal-content input{
                            border:1px solid #ddd;
                            border-radius: 5px;
                            color: #363636;
                            
                            border-color: 1px solid #b5b5b5;
                            font-size: 15px;
                        
                        }
                        #hostloc-blocker-panel textarea:hover, 
                        #hostloc-blocker-panel input:hover{
                            border-color: #b5b5b5;
                        }
                        #hostloc-blocker-panel textarea:focus, 
                        #hostloc-blocker-panel input:focus{
                            outline:none
                        }
                
                        #hostloc-blocker-panel .modal-footer{
                            padding:30px 30px 0;
                            text-align: right;
                        }
                
                        #hostloc-blocker-panel .modal-footer button{
                            padding: 5px 15px;
                            border-color: #235994;
                            background-color: #06C;
                            background-position: 0 -48px;
                            color: #FFF;
                
                        }
                    </style>
                </body>
                </html>`;
            document.body.appendChild(div);

            }

            addPanelEvents(){
                const panel = document.querySelector('#hostloc-blocker-panel');
                const saveButton = panel.querySelector('.save');
                const inputPantryAPIKey = panel.querySelector('[name="pantry-api-key"]');
                const inputBasketName = panel.querySelector('[name="pantry-basket-name"]');
                const textareaBlockedUser = panel.querySelector('textarea[name="blocked-user"]');
                const textareaBlockedSignatureUser = panel.querySelector('textarea[name="blocked-signature-user"]');
                const textareaBlockedKeyword = panel.querySelector('textarea[name="blocked-keyword"]');
                const openPanelLink = document.querySelector('#show-block-panel');

                panel.addEventListener('click',(event)=>{
                    event.stopPropagation();
                })

                document.body.addEventListener('click',()=>{
                    panel.classList.remove('is-active');
                })

                openPanelLink.addEventListener('click',(event)=>{
                    event.stopPropagation();
                    textareaBlockedKeyword.value = this.config.blockedKeyword.join('\n');
                    textareaBlockedSignatureUser.value = this.config.blockedSignatureUser.join('\n');
                    textareaBlockedUser.value = this.config.blockedUser.join('\n');
                    inputPantryAPIKey.value = this.config.pantry.APIKey;
                    inputBasketName.value = this.config.pantry.basket;
                    panel.classList.add('is-active');

                });

                saveButton.addEventListener('click',()=>{
                    this.config.pantry.APIKey = inputPantryAPIKey.value;
                    this.config.pantry.basket = inputBasketName.value;
                    
                    // 如果仅输入了apikey 则表示从云端拉取数据,将覆盖本地数据
                    if(!textareaBlockedKeyword.value && !textareaBlockedSignatureUser.value && !textareaBlockedUser.value){
                        this.modifyCloudData('get');
                        return;
                    }else{
                        // 将api key 和数据保存到本地
                        this.config.blockedKeyword = textareaBlockedKeyword.value.split('\n').trim();
                        this.config.blockedSignatureUser = textareaBlockedSignatureUser.value.split('\n').trim();
                        this.config.blockedUser = textareaBlockedUser.value.split('\n').trim();

                        
                        // 保存到本地
                        this.saveToLocal();
                        // 将数据保存到云端
                        this.modifyCloudData('save');

                    }
                    panel.classList.remove('is-active');
                })
            }




            startBlockProcess(){
                if(location.href.includes('forum')){
                    this.hideFromList();
                }

                if(location.href.includes('thread')){
                    this.hideReplyAndSignature();
                }

                //监听点击事件,恢复被屏蔽的签名和帖子
                if(location.href.includes('thread')){
                    document.querySelector('#postlist').addEventListener('click',(e)=>{
                        const item=e.target;
                        if(item.className.includes('hidden-by-script')){
                            item.innerHTML=this.contentStorage[item.dataset.restoreKey];
                            item.title='';
                            item.style='';
                        }
                        
                    })
                }
            }

            init(){
                this.addSettingPanel();
                this.addSettingButton();
                this.addPanelEvents();
                this.restoreFromLocal();
            
                this.startBlockProcess();
                
            }


        }

        const app=new HostLocBlocker();
        app.init();
    })();