贴吧助手

能按照用户和关键词屏蔽不想看的贴,屏蔽低于指定等级用户,也可以给用户打标签帮助自己记忆此人的特点,屏蔽置顶直播

// ==UserScript==
// @name         贴吧助手
// @namespace    蒋晓楠
// @version      20240307
// @description  能按照用户和关键词屏蔽不想看的贴,屏蔽低于指定等级用户,也可以给用户打标签帮助自己记忆此人的特点,屏蔽置顶直播
// @author       蒋晓楠
// @license MIT
// @match        https://tieba.baidu.com/p/*
// @match        https://tieba.baidu.com/f?*kw=*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_notification
// @grant GM_addStyle
// @grant GM_addElement
// @grant GM_registerMenuCommand
// ==/UserScript==
//执行间隔(秒)
let Interval = 1;
//过期数据清理延时(秒)
let ClearExpireDataDelay = 30;
//过期数据清理几率百分比,范围0-100
let ClearExpireDataPercent = 2;
//以下不懂的不要修改
//页面类型
const PAGE_TYPE_LIST = 0;//列表
const PAGE_TYPE_POST = 1;//帖子
let PageType = location.pathname === "/f" ? PAGE_TYPE_LIST : PAGE_TYPE_POST;

//获取数据
function GetData() {
    return GM_getValue("List", { User: [], UserTime: {}, Keyword: { Title: {}, Content: {} }, UserTag: {} });
}

//保存数据
function SaveData(Data) {
    GM_setValue("List", Data);
}

//修改配置
function SetConfig(Key, Value) {
    GM_setValue("Config:" + Key, Value);
}

//获取配置
function GetConfig(Key, Default) {
    return GM_getValue("Config:" + Key, Default);
}

//删除配置
function RemoveConfig(Key) {
    GM_deleteValue("Config:" + Key);
}

//提醒
function Tips(Message) {
    GM_notification({ title: "贴吧助手", text: Message, timeout: 3000 });
}

//初始化样式
function InitCSS() {
    GM_addStyle(`.JXNButton{border:1px solid;margin-right:10px}.BlockUser{width:90%}.JXNInput{width:50px;}.JXNTag{border:1px solid black;margin:5px 1px;display:inline-block;min-width:50px;}.JXNTagBox{width:90px;}.JXNHolder{width:250px;position:absolute;left:10px;z-index:1;top:135px}.PostListBlockUser{position:absolute;right:0;margin-right:0;z-index:1}`);
}

//获取有效天数
function GetExpireDay() {
    return GetConfig("ExpireDay", 180);
}

//设置有效天数
function SetExpireDay(Value) {
    SetConfig("ExpireDay", parseInt(Value));
}

//获取当前时间
function GetNowTime() {
    let Now = new Date();
    return parseInt(Now.getFullYear().toString() + (Now.getMonth() + 1).toString().padStart(2, "0") + Now.getDate().toString().padStart(2, "0"));
}

//获取新的过期时间
function GetNewExpiredDay() {
    let Now = new Date();
    Now.setDate(Now.getDate() + GetExpireDay());
    return parseInt(Now.getFullYear().toString() + (Now.getMonth() + 1).toString().padStart(2, "0") + Now.getDate().toString().padStart(2, "0"));
}

//屏蔽用户存在
function BlockUserIsExist(Id) {
    return GetData().User.indexOf(Id) > -1;
}

//添加屏蔽用户
function AddBlockUser(Id) {
    let Data = GetData();
    Data.User.push(Id);
    Data.UserTime[Id] = GetNewExpiredDay();
    SaveData(Data);
}

//移除屏蔽用户
function RemoveBlockUser(Id) {
    let Data = GetData();
    let Index = Data.User.indexOf(Id);
    if (Index > -1) {
        Data.User.splice(Index, 1);
        delete Data.UserTime[Id];
        SaveData(Data);
    }
}

//获取当前关键词类型
function GetNowKeyWordType() {
    return parseInt(document.querySelector(".JXNMatchType").value);
}

//获取当前关键词模式
function GetNowKeyWordMode() {
    return parseInt(document.querySelector(".JXNMatchMode").value);
}

//屏蔽关键词存在
function BlockKeywordIsExist(Type, Value) {
    return GetData().Keyword[Type === 0 ? "Title" : "Content"][Value] !== undefined;
}

//添加屏蔽关键词
function AddBlockKeyword(Type, Mode, Value) {
    let Data = GetData();
    Data.Keyword[Type === 0 ? "Title" : "Content"][Value] = { Mode: Mode, Time: GetNewExpiredDay() };
    SaveData(Data);
}

//移除屏蔽关键词
function RemoveBlockKeyword(Type, Value) {
    let Data = GetData();
    let KeyWord = Data.Keyword;
    let Key = Type === 0 ? "Title" : "Content";
    if (KeyWord[Key][Value] !== undefined) {
        delete Data.Keyword[Key][Value];
        SaveData(Data);
    }
}

//获取用户标签
function GetUserTags(UserId) {
    let Data = GetData();
    if (Data.UserTag[UserId] === undefined) {
        return {};
    } else {
        return Data.UserTag[UserId];
    }
}

//用户添加标签
function UserAddTag(UserId, Tag) {
    let Data = GetData();
    //初始化该用户的标签
    if (Data.UserTag[UserId] === undefined) {
        Data.UserTag[UserId] = {};
    }
    Data.UserTag[UserId][Tag] = GetNewExpiredDay();
    SaveData(Data);
}

//用户移除标签
function UserRemoveTag(UserId, Tag) {
    let Data = GetData();
    let UserTags = Data.UserTag[UserId];
    if (UserTags !== undefined && UserTags[Tag] !== undefined) {
        Object.getOwnPropertyNames(UserTags).length === 1 ? delete Data.UserTag[UserId] : delete UserTags[Tag];
        SaveData(Data);
    }
}

//帖子列表的额外屏蔽
function PostListExtraBlock() {
    //置顶直播
    if (GetConfig("BlockTopLive") === true && document.querySelector("[id^=pagelet_live]") !== null) {
        document.querySelector("[id^=pagelet_live]").remove();
    }
    //关闭会员红贴
    if (GetConfig("BanMemberPost", true) && document.querySelector(".member_thread_title_frs") !== null) {
        document.querySelectorAll(".member_thread_title_frs").forEach((TitleHolder) => {
            TitleHolder.classList.remove("member_thread_title_frs");
        });
    }
}

//内容过滤
function ContentFilter(Content) {
    Content = Content.trim();
    if (Content !== "") {
        //过滤非中文/英文/数字
        Content = Content.replace(/[^\da-zA-Z\u4e00-\u9fa5]/g, "");
    }
    return Content;
}

//检测标题关键词
function CheckKeywordTitle(Title) {
    let TitleKeyWords = GetData().Keyword.Title;
    for (const Keyword in TitleKeyWords) {
        if (TitleKeyWords[Keyword].Mode === 0) {
            if (Title.indexOf(Keyword) > -1) {
                return Keyword;
            }
        } else if (Keyword === Title) {
            return Keyword;
        }
    }
    return false;
}

//检测内容关键词
function CheckKeywordContent(Content) {
    let ContentWords = GetData().Keyword.Content;
    for (const Keyword in ContentWords) {
        if (ContentWords[Keyword].Mode === 0) {
            if (Content.indexOf(Keyword) > -1) {
                return Keyword;
            }
        } else if (Keyword === Content) {
            return Keyword;
        }
    }
    return false;
}

//帖子列表初始化
function InitPostList() {
    let StartTime = (new Date()).getTime();
    let Posts = document.querySelectorAll(".j_thread_list:not(.JXNProcessed)");
    let Number = Posts.length;
    if (Number > 0) {
        let BlockAlphaNumberTitle = GetConfig("BlockAlphaNumberTitle", false),
            EnableContentFilter = GetConfig("ContentFilter", false);
        for (let i = 0; i < Posts.length; i++) {
            let Post = Posts[i];
            let User = Post.querySelector(".tb_icon_author");
            if (User === null) {
                //添加已处理标记
                Post.classList.add("JXNProcessed");
            } else {
                let DisplayName = Post.querySelector(".frs-author-name").textContent;
                let UserId = JSON.parse(Post.querySelector(".tb_icon_author").getAttribute("data-field")).user_id;
                let Title = Post.querySelector(".threadlist_title>a").getAttribute("title");
                //屏蔽纯字母数字标题
                if (BlockAlphaNumberTitle && /^[0-9a-zA-Z]*$/.test(Title)) {
                    console.log(`${DisplayName}(${UserId})的帖子[${Title}]被屏蔽因为标题为纯字母数字`);
                    Post.remove();
                }
                //按用户ID屏蔽
                else if (BlockUserIsExist(UserId)) {
                    console.log(`${DisplayName}(${UserId})的帖子[${Title}]被屏蔽因为在用户屏蔽列表内`);
                    Post.remove();
                } else {
                    //按关键词屏蔽
                    let Result = CheckKeywordTitle(Title);
                    if (Result === false) {
                        let Content = Post.querySelector(".threadlist_abs_onlyline");
                        if (Content !== null) {
                            Content = Content.textContent;
                            if (EnableContentFilter)
                                Content = ContentFilter(Content);
                            if (Content !== "") {
                                Result = CheckKeywordContent(Content);
                                if (Result !== false) {
                                    console.log(`${DisplayName}(${UserId})的帖子[${Title}]被屏蔽因为内容触发关键词[${Result}]`);
                                    Post.remove();
                                    continue;
                                }
                            }
                        }
                        //添加屏蔽按钮
                        let BlockButton = document.createElement("button");
                        BlockButton.classList.add("JXNButton");
                        BlockButton.classList.add("PostListBlockUser");
                        BlockButton.textContent = "屏蔽用户";
                        BlockButton.onclick = () => {
                            if (BlockUserIsExist(UserId)) {
                                Tips("此用户已存在");
                            } else {
                                AddBlockUser(UserId)
                                //添加取消屏蔽按钮
                                let CancelButton = document.createElement("button");
                                CancelButton.classList.add("JXNButton");
                                CancelButton.classList.add("PostListBlockUser");
                                CancelButton.textContent = "取消屏蔽";
                                CancelButton.onclick = () => {
                                    if (BlockUserIsExist(UserId)) {
                                        RemoveBlockUser(UserId);
                                        CancelButton.remove();
                                        Tips("已取消对此用户的屏蔽");
                                    } else {
                                        Tips("此用户不存在");
                                    }
                                }
                                Post.prepend(CancelButton);
                                Tips("添加成功,刷新后将自动屏蔽此用户的发帖与回复");
                                BlockButton.remove();
                            }
                        }
                        Post.prepend(BlockButton);
                        Post.classList.add("JXNProcessed");
                    } else {
                        console.log(`${DisplayName}(${UserId})的帖子[${Title}]被屏蔽因为标题触发关键词[${Result}]`);
                        Post.remove();
                    }
                }
            }
        }
        //额外屏蔽
        PostListExtraBlock();
        console.log("屏蔽用时:" + ((new Date()).getTime() - StartTime) + "毫秒");
    }
}

//初始化用户面板
function InitUserPanel(UserId, UserBlock) {
    setTimeout(() => {
        let BlockButton = GM_addElement(UserBlock, "button", {
            class: "JXNButton BlockUser",
            textContent: "屏蔽"
        });
        BlockButton.onclick = () => {
            if (BlockUserIsExist(UserId)) {
                Tips("此用户已存在");
            } else {
                AddBlockUser(UserId)
                //添加取消屏蔽按钮
                let CancelButton = GM_addElement(UserBlock, "button", {
                    textContent: "取消屏蔽", class: "JXNButton BlockUser"
                });
                CancelButton.onclick = () => {
                    if (BlockUserIsExist(UserId)) {
                        RemoveBlockUser(UserId);
                        Tips("已取消对此用户的屏蔽");
                    } else {
                        Tips("此用户不存在");
                    }
                }
                Tips("添加成功,刷新后将自动屏蔽此用户的发帖与回复");
                BlockButton.remove();
            }
        }
        let Tags = GetUserTags(UserId);
        for (const Tag in Tags) {
            let NowTag = GM_addElement(UserBlock, "button", {
                textContent: Tag,
                class: "JXNTag",
                title: "点击删除此标签"
            });
            NowTag.onclick = () => {
                UserRemoveTag(UserId, Tag);
                Tips("删除完成");
                NowTag.remove();
            }
        }
        //创建添加标签框
        let TagBox = GM_addElement(UserBlock, "input", {
            type: "text",
            class: "JXNInput JXNTagBox"
        });
        let Button = GM_addElement(UserBlock, "button", {
            textContent: "添加",
            class: "JXNButton JXNTagRemove",
            title: "添加标签"
        });
        Button.onclick = () => {
            let Tag = TagBox.value;
            if (Tag !== "") {
                UserAddTag(UserId, Tag);
                Tips("添加完成,刷新后会正确显示");
            }
        }
    }, Interval);
}

//帖子内初始化
function InitPosts() {
    let StartTime = (new Date()).getTime();
    let Posts = document.querySelectorAll(".l_post:not([data-index]):not(.JXNProcessed)");
    let Number = Posts.length;
    if (Number > 0) {
        let BlockLevelUserValue = GetConfig("BlockLevelUser", 2),
            EnableContentFilter = GetConfig("ContentFilter", false);
        let ContentResult;
        for (let i = 0; i < Number; i++) {
            let Post = Posts[i];
            //按等级屏蔽用户
            if (BlockLevelUserValue > 18) {
                Post.remove();
                continue;
            }
            let UserBlock = Post.querySelector(".p_author");
            let DisplayName = UserBlock.querySelector(".p_author_name").textContent;
            let UserId = JSON.parse(Post.querySelector(".d_name").getAttribute("data-field")).user_id;
            if (BlockLevelUserValue > 1 && parseInt(UserBlock.querySelector(".d_badge_lv").textContent) < BlockLevelUserValue) {
                console.log(`${DisplayName}(${UserId})的楼层被屏蔽因为等级低于${BlockLevelUserValue}`);
                Post.remove();
                continue;
            }
            if (BlockUserIsExist(UserId)) {
                console.log(`${DisplayName}(${UserId})的楼层被屏蔽因为在用户屏蔽列表内`);
                Post.remove();
            } else {
                let Content = Post.querySelector(".d_post_content").textContent;
                if (EnableContentFilter)
                    Content = ContentFilter(Content);
                //检测内容关键词
                if (Content.length > 0 && (ContentResult = CheckKeywordContent(Content)) !== false) {
                    console.log(`${DisplayName}(${UserId})的楼层被屏蔽因为内容触发关键词[${ContentResult}]`);
                    Post.remove();
                    continue;
                }
                //初始化用户面板
                InitUserPanel(UserId, UserBlock);
                //添加已处理标记
                Post.classList.add("JXNProcessed");
            }
        }
        console.log("屏蔽用时:" + ((new Date()).getTime() - StartTime) + "毫秒");
    }
}

//通过地址初始化相应功能
function InitFunctionByURL() {
    let Function = PageType === PAGE_TYPE_LIST ? InitPostList : InitPosts;
    //初始执行
    Function();
    //定期执行
    setInterval(() => {
        Function();
    }, Interval * 1000);
}

//初始化操作界面
function InitUI() {
    //脚本菜单
    //屏蔽置顶直播
    GM_registerMenuCommand((GetConfig("BlockTopLive", false) === true ? "✅" : "❎") + "屏蔽置顶直播", () => {
        SetConfig("BlockTopLive", GetConfig("BlockTopLive", false) !== true);
    });
    //屏蔽纯字母数字标题
    GM_registerMenuCommand((GetConfig("BlockAlphaNumberTitle", false) === true ? "✅" : "❎") + "屏蔽纯字母数字标题", () => {
        SetConfig("BlockAlphaNumberTitle", GetConfig("BlockAlphaNumberTitle", false) !== true);
    });
    //关闭会员红贴
    GM_registerMenuCommand((GetConfig("BanMemberPost", true) === true ? "✅" : "❎") + "关闭会员红贴", () => {
        SetConfig("BanMemberPost", GetConfig("BanMemberPost", false) !== true);
    });
    //内容预过滤
    GM_registerMenuCommand((GetConfig("ContentFilter", false) === true ? "✅" : "❎") + "内容预过滤", () => {
        SetConfig("ContentFilter", GetConfig("ContentFilter", false) !== true);
    });
    //导出数据
    GM_registerMenuCommand("导出数据", () => {
        let ExportJson = document.createElement("a");
        ExportJson.download = "贴吧助手.json";
        ExportJson.href = URL.createObjectURL(new Blob([JSON.stringify(GetData())]));
        ExportJson.click();
    });
    //页面菜单
    //油猴脚本不提供在指定元素后面创建的api
    let Holder = document.createElement("div");
    Holder.classList.add("JXNHolder");
    let PositionElement = document.getElementById("head");
    PositionElement.after(Holder);
    //有效天数
    GM_addElement(Holder, "span", { textContent: "有效天数" });
    let Expire = GM_addElement(Holder, "input", {
        type: "number",
        min: 1,
        step: 1,
        value: GetExpireDay(), class: "JXNInput"
    });
    GM_addElement(Holder, "br", {});
    if (PageType === PAGE_TYPE_POST) {
        //按等级屏蔽用户
        GM_addElement(Holder, "span", { textContent: "屏蔽低于此等级用户" });
        let BlockLevelUser = GM_addElement(Holder, "input", {
            type: "number", value: GetConfig("BlockLevelUser", 2), min: 1, max: 19, step: 1
        });
        BlockLevelUser.onchange = () => {
            SetConfig("BlockLevelUser", this.value);
        }
        GM_addElement(Holder, "br", {});
    }
    //关键词
    GM_addElement(Holder, "span", { textContent: "关键词" });
    let Keyword = GM_addElement(Holder, "input", {
        type: "text",
    });
    GM_addElement(Holder, "br", {});
    //匹配类型
    GM_addElement(Holder, "span", { textContent: "匹配类型" });
    let MatchType = GM_addElement(Holder, "select", { class: "JXNMatchType" });
    MatchType.add(new Option("标题", 0));
    MatchType.add(new Option("内容", 1));
    MatchType.value = GetConfig("MatchType", 0);
    //匹配模式
    GM_addElement(Holder, "span", { textContent: "匹配模式" });
    let MatchMode = GM_addElement(Holder, "select", { class: "JXNMatchMode" });
    MatchMode.add(new Option("部分", 0));
    MatchMode.add(new Option("全部", 1));
    MatchMode.value = GetConfig("MatchMode", 0);
    GM_addElement(Holder, "br", {});
    let AddKeyword = GM_addElement(Holder, "button", {
        textContent: "添加", class: "JXNButton"
    });
    let RemoveKeyword = GM_addElement(Holder, "button", {
        textContent: "删除", class: "JXNButton"
    });
    //导入选择块
    let ImportFile = GM_addElement(Holder, "input", {
        type: "file",
        hidden: true,
        accept: "application/json",
        value: false
    });
    //导入数据
    let ImportList = GM_addElement(Holder, "button", {
        textContent: "导入数据", class: "JXNButton"
    });
    //绑定事件
    //有效天数
    Expire.onchange = () => {
        SetExpireDay(Expire.value);
    }
    //匹配类型
    MatchType.onchange = () => {
        SetConfig("MatchType", MatchType.value);
    }
    //匹配模式
    MatchMode.onchange = () => {
        SetConfig("MatchMode", MatchMode.value);
    }
    //添加删除关键词
    AddKeyword.onclick = () => {
        let Value = Keyword.value;
        if (Value !== "") {
            if (BlockKeywordIsExist(GetNowKeyWordType(), Value)) {
                Tips("此关键词已存在");
            } else {
                AddBlockKeyword(GetNowKeyWordType(), GetNowKeyWordMode(), Value);
                Tips("添加成功");
            }
        }
    };
    RemoveKeyword.onclick = () => {
        let Value = Keyword.value;
        if (Value !== "") {
            if (BlockKeywordIsExist(GetNowKeyWordType(), Value)) {
                RemoveBlockKeyword(GetNowKeyWordType(), Value);
                Tips("删除成功");
            } else {
                Tips("此关键词不存在");
            }
        }
    }
    //导入数据
    ImportFile.onchange = () => {
        if (ImportFile.files.length > 0) {
            let JsonList = ImportFile.files[0];
            let Reader = new FileReader();
            Reader.onload = (Result) => {
                try {
                    SaveData(JSON.parse(Result.target.result));
                    Tips("导入完成");
                } catch (e) {
                    alert("读取的文件格式不正确");
                }
            };
            Reader.readAsText(JsonList);
        }
    }
    ImportList.onclick = () => {
        ImportFile.click();
    }
}

//清理过期数据
function ClearExpireData() {
    //浏览帖子达到一定时间才去执行清理
    setTimeout(() => {
        if (1 + Math.round(Math.random() * 99) <= ClearExpireDataPercent) {
            let NowDate = GetNowTime();
            let Data = GetData();
            //清理用户
            for (let i = 0; i < Data.User.length; i++) {
                let Id = Data.User[i];
                if (Data.UserTime[Id] < NowDate) {
                    console.log(`删除过期用户[${Id}]因为它的时间${Data.UserTime[Id]}]小于当前时间`);
                    RemoveBlockUser(Id);
                }
            }
            //清理标签
            let UserTag = Data.UserTag;
            for (const UserId in UserTag) {
                let Tags = UserTag[UserId];
                for (const Tag in Tags) {
                    if (Tags[Tag] < NowDate) {
                        console.log(`删除过期用户[${UserId}]的标签[${Tag}]因为它的时间${Tags[Tag]}小于当前时间`);
                        UserRemoveTag(UserId, Tag);
                    }
                }
            }
            //清理关键词
            let KeyWord = Data.Keyword;
            for (const BaseKey in KeyWord) {
                let Value = KeyWord[BaseKey];
                for (const Key in Value) {
                    let TheKeyWord = Value[Key];
                    if (TheKeyWord.Time < NowDate) {
                        console.log(`删除过期${BaseKey === "Title" ? "标题" : "内容"}关键词[${Key}]因为它的时间[${TheKeyWord.Time}]小于当前时间`);
                        RemoveBlockKeyword(BaseKey === "Title" ? 0 : 1, Key);
                    }
                }
            }
        }
    }, ClearExpireDataDelay * 1000);
}

//显示信息
function DisplayInfo() {
    setTimeout(() => {
        let Data = GetData();
        console.log("完整数据", Data);
        console.log("屏蔽数量:关键词-标题(" + Object.keys(Data.Keyword.Title).length + "),关键词-内容(" + Object.keys(Data.Keyword.Content).length + "),用户(" + Data.User.length + ")");
        console.log("当前时间:" + GetNowTime());
    }, 1000);
}

//运行
function Run() {
    InitCSS();
    InitUI();
    ClearExpireData();
    InitFunctionByURL();
    DisplayInfo();
}

Run();