BanYouSb

通過網頁操作, 達成屏蔽與解除屏蔽使用者

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         BanYouSb
// @namespace    http://tampermonkey.net/
// @version      1.41
// @description  通過網頁操作, 達成屏蔽與解除屏蔽使用者
// @author       RogerYeah
// @match        *://jandan.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jandan.net
// @grant        none
// ==/UserScript==


(function () {
    // 檢查 localStorage 中是否存在 banCode 鍵值對 , 若不存在則新增一個空物件
    const BAN_CODE_KEY = 'banCode';

    function initializeBanCode() {
        try {
            const banCode = localStorage.getItem(BAN_CODE_KEY);
            if (!banCode) {
                localStorage.setItem(BAN_CODE_KEY, '{}');
            } else {
                console.log('Here is who you ban: ' + banCode);
            }
        } catch (error) {
            console.error('Error accessing localStorage:', error);
        }
    }

    initializeBanCode();

    // 將 localStorage 中的 banCode 鍵值對解析為 JSON 物件
    var banCode = JSON.parse(localStorage.getItem(BAN_CODE_KEY))

    // 獲取網頁中的所有評論列表
    var comment = document.getElementsByClassName("commentlist")
    var lis = comment[0].getElementsByTagName("li")

    // 獲取網頁中的所有 .text 元素
    var row = document.querySelectorAll('.text')

    // 定義屏蔽按鈕
    var voteElements = document.querySelectorAll(".jandan-vote")

    // 定義吐槽按鈕
    var tucao = document.querySelectorAll(".tucao-btn")


    //定義 unban 函式
    function unban(e) {
        // 獲取 li 元素
        var li = e.parentNode.parentNode.parentNode;

        // 獲取作者姓名
        var author = li.getElementsByClassName("author")[0].getElementsByTagName("strong")[0].textContent;

        // 確認是否解除屏蔽
        if (confirm("讓我看看 " + author + " 這傢夥有什麼長進")) {
            // 從 banCode 物件中刪除 author 對應的鍵值對
            delete banCode[author];

            // 將更新後的 banCode 物件存入 localStorage
            localStorage.setItem(BAN_CODE_KEY, JSON.stringify(banCode));

            // 重新載入頁面
            location.reload();
        }
    }

    // 定義 ban 函式
    function ban(e) {
        // 獲取 li 元素
        var li = e.parentNode.parentNode.parentNode;

        // 獲取作者姓名
        var author = li.getElementsByClassName("author")[0].children[0];

        // 獲取作者防偽碼
        var privCode = author.getAttribute('title').split('防伪码:').pop();

        // 確認是否屏蔽
        if (confirm("您確定要屏蔽 " + author.textContent + " 嗎?")) {
            // 將作者姓名和防偽碼新增至 banCode 物件中
            banCode[author.textContent] = privCode;

            // 將更新後的 banCode 物件存入 localStorage
            localStorage.setItem(BAN_CODE_KEY, JSON.stringify(banCode));

            // 重新載入頁面
            location.reload();
        }
    }

    // 定義 unBanUser 函式
    function unBanUser(e) {
        // 確認是否解除屏蔽
        if (confirm("讓我看看 " + e + " 這傢夥有什麼長進")) {
            // 從 banCode 物件中刪除 author 對應的鍵值對
            delete banCode[e];

            // 將更新後的 banCode 物件存入 localStorage
            localStorage.setItem(BAN_CODE_KEY, JSON.stringify(banCode));

            // 重新載入頁面
            location.reload();
        }
    }

    // 定義吐槽屏蔽函式
    function tucaoHandle(e) {
        const bannedUsers = JSON.parse(localStorage.getItem("banCode"));
        const bannedUsernames = Object.keys(bannedUsers);
        const isFBan = localStorage.getItem('FBan') === 'true';
        const tucaoList = e.querySelectorAll('.tucao-list .tucao-row');
        const tucaoHotList = e.querySelectorAll('.tucao-hot .tucao-row');
        let rowsToDelete = [];

        if (!bannedUsers) return; // 如果 banCode 為空,則直接返回

        bannedUsernames.forEach(bannedUser => {
            rowsToDelete = [];
            // 找到所有需要刪除的元素,並存儲到 rowsToDelete 陣列中
            tucaoList.forEach(row => {
                const tucaoAuthor = row.querySelector('.tucao-author');
                if (tucaoAuthor && tucaoAuthor.textContent.includes(bannedUser)) {
                    rowsToDelete.push(row.id);
                }
            });

            // 熱榜屏蔽
            tucaoHotList.forEach(row => {
                const tucaoAuthor = row.querySelector('.tucao-author');
                if (tucaoAuthor && tucaoAuthor.textContent.includes(bannedUser)) {
                    rowsToDelete.push(row.id);
                }
            });
        });

        if (isFBan) {
            rowsToDelete.forEach(row => {
                document.getElementById(row).remove()
            });
        } else {
            rowsToDelete.forEach(row => {
                var rowItem = document.getElementById(row);
                var contentBox = rowItem.querySelector(".tucao-content");
                var content = contentBox.textContent;

                document.getElementById(row).querySelector(".tucao-content").innerHTML = `
                    <del class="delete">
                        <span class="math-inline">已屏蔽</span>
                    </del>
                    <i class="peep" title="${content}">偷看一下(懸停)</i>
                `;
            });
        }
    }

    // 屏蔽防偽碼標記用戶
    for (let i = lis.length - 1; i >= 0; i--) {
        // 獲取作者姓名
        const author = lis[i].querySelector(".author strong");
        const authorName = author ? author.innerText : '';

        // 遍歷 banCode 物件中的所有鍵值對
        for (const [bannedAuthor, _] of Object.entries(banCode)) {
            // 若作者姓名與 banCode 物件中的鍵值對匹配 且 FBan Mode 為真
            if (authorName === bannedAuthor && localStorage.getItem('FBan') === 'true') {
                console.log(lis[i].remove());
            } else if(authorName === bannedAuthor) {
                // 獲取評論內容
                const contentBox = lis[i].querySelector(".text");
                const img = lis[i].querySelector("img");
                const content = contentBox.querySelector('p:not(.bad_content)').textContent.replace(/<br>/g, ' ');

                // 將評論內容替換為 "[已屏蔽]" 標記
                contentBox.innerHTML = `
                    <del class="delete">
                        <span class="math-inline">${authorName} - 已屏蔽</span>
                    </del>
                    <i class="peep" title="${content}">偷看一下(懸停)
                        ${img != null ? `<img style="opacity: 0;" src="${img.src}" alt="pic" />`: ''}
                    </i>
                `;

                break;
            }
        }
    }

    // 遍歷所有 .jandan-vote 元素
    for (var x = 0; x < voteElements.length; x++) {
        // 創建一個新的 a 元素
        var button = document.createElement("a");

        // 若評論內容包含 "[已屏蔽]" 標記
        if (row[x].innerHTML.includes('del')) {
            // 設定按鈕文字為 "[解除屏蔽]"
            button.textContent = "[解除屏蔽]";

            // 為按鈕添加點擊解除屏蔽函式
            button.addEventListener("click", function () {
                unban(this);
            });
        } else {
            // 設定按鈕文字為 "[屏蔽]"
            button.textContent = "[屏蔽]";

            // 為按鈕添加點擊屏蔽函式
            button.addEventListener("click", function () {
                ban(this);
            });
        }

        // 設定按鈕顏色為 "#c8c7cc"
        button.style.color = "#c8c7cc";

        // 將按鈕插入到 .jandan-vote 元素的開頭
        voteElements[x].prepend(button);
    }

    // 生成list按鈕Dom
    var counter = Object.keys(banCode).length
    var listDom = `
        <div class="banList">
            <div class="toggleArea">
                <div class="banModeSwitch" title="打開此開關完全屏蔽模式, 開啟後偷看功能關閉並完全屏蔽列表中的使用者(看不見任何與屏蔽使用者相關之項目)">
                    <div class="switch ${localStorage.getItem('FBan') === 'true' ? 'active' : ''}" id="switch"></div>
                    <span>FBan Mode</span>
                </div>
                屏蔽列表
                <div class="toggleList">
                    <span class="toggleList-show">+</span>
                    <span class="toggleList-hide" style="display: none;">-</span>
                </div>
            </div>
            <ul class="mainList" style="display: none;">
    `

    // 循環遍歷 banCode 對象中的已屏蔽用戶
    for (var li = 0; li < counter; li++) {
        listDom += `
                <li class="mainList-item">${Object.keys(banCode)[li]} <a class="unban" href="javascript: void();">x</a></li>
        `
    }

    listDom += `
            </ul>
            <form id="banForm">
                <input class="ban-input" type="text" placeholder="手動屏蔽(鍵入ID後, 猛擊Enter)" />
            </form>
        </div>
    `

    // 獲取 DOM 元素
    const wrapper = document.getElementById('wrapper');
    const listDomElement = document.createElement('div');
    listDomElement.innerHTML = listDom;
    wrapper.appendChild(listDomElement);

    // 添加點擊事件監聽器,用於處理刪除按鈕的點擊
    wrapper.addEventListener('click', (event) => {
        if (event.target.classList.contains('unban') && !event.target.classList.contains('clicked')) {
            const username = event.target.parentNode.textContent.trim().replace(' x', '');
            unBanUser(username);
            event.target.classList.add('clicked')
        }
        if (event.target.classList.contains('tucao-btn') && !event.target.classList.contains('clicked')) {
            var tucaoId = event.target.dataset.id;
            var tucaoBox = document.getElementById(`jandan-tucao-${tucaoId}`)
            var intervalBox = setInterval(() => {
                if (tucaoBox.innerText !== "数据加载中....biubiubiu....") {
                    tucaoHandle(tucaoBox);
                    clearInterval(intervalBox);
                    event.target.classList.add('clicked')
                }
            }, 100);
        }
    });

    // 獲取 DOM 元素
    const switcher = document.getElementById('switch');

    switcher.addEventListener('click', (event) => {
        if(localStorage.getItem('FBan') !== null) {
            const isFBan = localStorage.getItem('FBan') === 'true';
            localStorage.setItem('FBan', !isFBan);
            switcher.classList.toggle('active', !isFBan);
            location.reload();
        } else {
            if(confirm('打開此開關完全屏蔽模式, 開啟後偷看功能關閉並完全屏蔽列表中的使用者(看不見任何與被屏蔽使用者相關之項目)')) {
                const isFBan = localStorage.getItem('FBan') === 'true';
                localStorage.setItem('FBan', !isFBan);
                switcher.classList.toggle('active', !isFBan);
                location.reload();
            }
        }
    })

    // 獲取並緩存屏蔽用戶列表 DOM 元素
    const toggleList = document.querySelector('.toggleList');
    if(toggleList != null) {
        // 添加點擊事件監聽器,用於展開/收起屏蔽用戶列表
        toggleList.addEventListener('click', function() {
            const secondSpan = this.querySelector('span:nth-of-type(2)');
            const ul = document.querySelector('.mainList');

            secondSpan.style.display = secondSpan.style.display === 'none' ? 'block' : 'none';
            ul.style.display = ul.style.display === 'none' ? 'block' : 'none';
        });
    }

    // 手動屏蔽功能
    const myForm = document.getElementById('banForm');
    const myInput = document.querySelector('.ban-input');

    myForm.addEventListener('submit', function() {
        const inputValue = myInput.value;

        if (confirm("此屏蔽無法正確辨識身分, 換個暱稱就屏蔽不了了, 您確定要屏蔽 " + inputValue + " 嗎?")) {
            let banData = JSON.parse(localStorage.getItem("banCode")) || {}; // 初始化為空物件

            if (banData[inputValue] == undefined) { // 避免重複添加
                banData[inputValue] = null;
                localStorage.setItem("banCode", JSON.stringify(banData));
                setTimeout(() => {
                    location.reload();
                }, 100)
            } else {
                alert("該用戶已經被屏蔽");
            }
        }
    });

    setTimeout(() => {
        // 創建 style 元素
        var style = document.createElement('style');
         // 創建文本,包含 CSS 規則
        style.innerHTML = `
            .commentlist .row {
                overflow: visible;
            }

            .delete {
                display: inline-block;
                margin-bottom: 20px;
                margin-top: 7px;
                margin-right: 5px;
            }

            .tucao-content {
                .delete {
                    display: inline-block;
                    margin: 0;
                }
            }

            .peep {
                display: inline-block;
                position: relative;
                font-size: 10px;

                img {
                    position: absolute;
                    left: calc(100% + 10px);
                    opacity: 0;
                    max-width: 250px !important;
                    pointer-events: none;
                }

                &:hover img {
                    opacity: 1 !important;
                    transition: .5s ease;
                }
            }

            .banList {
                display: flex;
                flex-wrap: wrap;
                justify-content: flex-end;
                position: fixed;
                top: 84px;
                width: 1184px;
                pointer-events: none;
                width: calc((100vw - 984px - 60px) / 2);
                max-height: calc(100% - 300px);
                box-sizing: border-box;
                left: calc(100% - (100vw - 984px - 30px) / 2);
            }

            .toggleArea {
                padding: 5px;
                position: relative;
                text-align: center;
                width: 100%;
                border-radius: 5px;
                background: #bababa;
                color: #333;
                font-weight: bold;
                pointer-events: auto;

                .toggleList {
                    display: inline-block;
                    position: absolute;
                    width: 20px;
                    height: 20px;
                    right: 5px;

                    &-show,
                    &-hide {
                        position: relative;
                        display: inline-block;
                        width: 20px;
                        height: 20px;
                        background: #bababa;

                        &:hover {
                            color: #666;
                        }
                    }

                    &-hide {
                        position: absolute;
                        top: 0;
                    }
                }
            }

            .mainList {
                margin-bottom: 3px;
                position: relative;
                width: 90px;
                top: calc(100% + 10px);
                display: none;
                width: 100%;
                color: gray;
                overflow: auto;
                height: calc(100vh - 196px);

                &-item {
                    margin-bottom: 4px;
                    padding-bottom: 4px;
                    width: 100%;
                    border-bottom: 1px solid gray;
                    font-size: 12px;
                    white-space:nowrap;
                    text-overflow:ellipsis;
                    -o-text-overflow:ellipsis;
                    overflow: hidden;
                    max-width: 100%;
                    padding-right: 20px;
                    box-sizing: border-box;

                    .unban {
                        pointer-events: auto;
                        cursor: pointer;
                        position: absolute;
                        right: 0;
                    }
                }
            }

            #banForm {
                position: absolute;
                bottom: calc(100% + 5px);
                left: 0;
                width: 100%;
                pointer-events: auto;

                .ban-input {
                    position: relative;
                    width: 100%;
                    border-color: #d8d8d8;
                    border-width: 0px 00px 1px 0px;

                    &:focus {
                        outline: none;
                    }

                    &::placeholder {
                        color: #d8d8d8;
                    }
                }
            }

            .banModeSwitch {
                position: absolute;
                top: 5px;
                height: 20px;

                .switch {
                    position: relative;
                    width: 20px;
                    height: 10px;;
                    border-radius: 10px;
                    background: rgb(255 255 255 / 40%);

                    &:after {
                        content: '';
                        display: block;
                        position: absolute;
                        top: 2px;
                        left: 2px;
                        height: 6px;
                        width: 6px;
                        border-radius: 10px;
                        background: rgb(0 0 0 / 20%);
                    }

                    &.active {
                        background: rgb(187 255 204 / 40%);

                        &:after {
                            left: auto;
                            right: 2px;
                            background: rgb(22 165 0 / 40%);
                        }
                    }
                }

                span {
                    padding-top: 2px;
                    display: block;
                    color: #333 !important;
                    font-size: 7px;
                    font-weight: 100;
                }
            }

            #nav_top {
                z-index: 99999;
            }
        `;
        document.head.appendChild(style);
    }, 1);
})();