Threads 關注者管理工具

獲取所有關注者並批量移除追蹤

// ==UserScript==
// @name         Threads 關注者管理工具
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  獲取所有關注者並批量移除追蹤
// @author       You
// @match        https://www.threads.net/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // 存儲所有關注者
    let allFollowers = [];
    // 標記腳本是否已經初始化
    let isInitialized = false;

    // 創建用戶界面
    function createUI() {
        // 確保不會重複創建UI
        if (isInitialized || document.getElementById('threads-unfollower')) {
            return;
        }

        isInitialized = true;

        // 創建主容器
        const container = document.createElement('div');
        container.id = 'threads-unfollower';
        container.style.position = 'fixed';
        container.style.top = '20px';
        container.style.right = '20px';
        container.style.zIndex = '9999';
        container.style.backgroundColor = '#222';
        container.style.border = '1px solid #444';
        container.style.padding = '15px';
        container.style.borderRadius = '5px';
        container.style.boxShadow = '0 2px 10px rgba(0,0,0,0.5)';
        container.style.maxHeight = '80vh';
        container.style.overflow = 'auto';
        container.style.width = '320px';
        container.style.fontFamily = 'Arial, sans-serif';

        // 標題
        const header = document.createElement('h3');
        header.textContent = 'Threads 關注者管理';
        header.style.margin = '0 0 15px 0';
        header.style.color = '#e0e0e0';
        header.style.fontSize = '16px';
        header.style.textAlign = 'center';

        // 按鈕容器
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'space-between';
        buttonContainer.style.marginBottom = '15px';

        // 獲取關注者按鈕
        const fetchButton = document.createElement('button');
        fetchButton.textContent = '獲取所有關注者';
        fetchButton.style.padding = '8px 12px';
        fetchButton.style.backgroundColor = '#4b4b4b';
        fetchButton.style.color = '#e0e0e0';
        fetchButton.style.border = '1px solid #666';
        fetchButton.style.borderRadius = '4px';
        fetchButton.style.cursor = 'pointer';
        fetchButton.style.flex = '1';
        fetchButton.style.marginRight = '10px';
        fetchButton.style.fontSize = '14px';
        fetchButton.addEventListener('click', fetchAllFollowers);

        // 移除所有追蹤按鈕
        const removeAllButton = document.createElement('button');
        removeAllButton.textContent = '全部移除追蹤';
        removeAllButton.style.padding = '8px 12px';
        removeAllButton.style.backgroundColor = '#8B0000';
        removeAllButton.style.color = 'white';
        removeAllButton.style.border = 'none';
        removeAllButton.style.borderRadius = '4px';
        removeAllButton.style.cursor = 'pointer';
        removeAllButton.style.flex = '1';
        removeAllButton.style.fontSize = '14px';
        removeAllButton.addEventListener('click', removeAllFollowers);

        buttonContainer.appendChild(fetchButton);
        buttonContainer.appendChild(removeAllButton);

        // 狀態顯示區域
        const status = document.createElement('div');
        status.id = 'follower-status';
        status.textContent = '就緒';
        status.style.margin = '10px 0';
        status.style.color = '#e0e0e0';
        status.style.padding = '5px';
        status.style.backgroundColor = '#333';
        status.style.borderRadius = '3px';
        status.style.fontSize = '14px';

        // 計數器
        const counter = document.createElement('div');
        counter.id = 'follower-counter';
        counter.textContent = '關注者: 0';
        counter.style.margin = '10px 0';
        counter.style.color = '#e0e0e0';
        counter.style.fontSize = '14px';

        // 進度條
        const progressBar = document.createElement('div');
        progressBar.id = 'progress-bar-container';
        progressBar.style.width = '100%';
        progressBar.style.backgroundColor = '#444';
        progressBar.style.borderRadius = '4px';
        progressBar.style.marginTop = '10px';
        progressBar.style.display = 'none';
        progressBar.style.overflow = 'hidden';
        progressBar.style.height = '15px';

        const progressFill = document.createElement('div');
        progressFill.id = 'progress-bar-fill';
        progressFill.style.height = '100%';
        progressFill.style.width = '0%';
        progressFill.style.backgroundColor = '#4CAF50';
        progressFill.style.borderRadius = '4px';
        progressFill.style.transition = 'width 0.3s ease';
        progressBar.appendChild(progressFill);

        // 關注者列表
        const followerList = document.createElement('div');
        followerList.id = 'follower-list';
        followerList.style.marginTop = '15px';
        followerList.style.borderTop = '1px solid #444';
        followerList.style.paddingTop = '10px';

        // 添加元素到容器
        container.appendChild(header);
        container.appendChild(buttonContainer);
        container.appendChild(status);
        container.appendChild(counter);
        container.appendChild(progressBar);
        container.appendChild(followerList);

        // 添加到頁面
        document.body.appendChild(container);
    }

    // 更新狀態消息
    function updateStatus(message) {
        const status = document.getElementById('follower-status');
        if (status) {
            status.textContent = message;
        }
        console.log('[Threads 關注者管理]:', message);
    }

    // 更新關注者計數
    function updateCounter() {
        const counter = document.getElementById('follower-counter');
        if (counter) {
            counter.textContent = `關注者: ${allFollowers.length}`;
        }
    }

    // 設置進度條的進度
    function updateProgressBar(percent, show = true) {
        const progressBar = document.getElementById('progress-bar-container');
        const progressFill = document.getElementById('progress-bar-fill');

        if (!progressBar || !progressFill) return;

        progressBar.style.display = show ? 'block' : 'none';
        progressFill.style.width = `${percent}%`;
    }

    // 安全地設置元素內容
    function safelySetInnerHTML(element, html) {
        try {
            element.innerHTML = html;
        } catch (error) {
            console.error('無法設置 innerHTML:', error);
            // 使用 textContent 作為備用
            element.textContent = '內容載入失敗,請檢查控制台。';
        }
    }

    // 在用戶界面中顯示關注者
    function displayFollowers() {
        const followerList = document.getElementById('follower-list');
        if (!followerList) return;

        try {
            // 清空列表
            while (followerList.firstChild) {
                followerList.removeChild(followerList.firstChild);
            }

            if (allFollowers.length === 0) {
                const noFollowers = document.createElement('div');
                noFollowers.textContent = '沒有找到關注者。';
                noFollowers.style.color = '#e0e0e0';
                noFollowers.style.padding = '10px 0';
                noFollowers.style.textAlign = 'center';
                followerList.appendChild(noFollowers);
                return;
            }

            // 創建關注者列表
            allFollowers.forEach((follower, index) => {
                const item = document.createElement('div');
                item.style.display = 'flex';
                item.style.alignItems = 'center';
                item.style.padding = '10px 0';
                item.style.borderBottom = index < allFollowers.length - 1 ? '1px solid #444' : 'none';
                item.dataset.userId = follower.pk;

                // 頭像
                const img = document.createElement('img');
                img.src = follower.profilePicUrl;
                img.alt = follower.username;
                img.style.width = '40px';
                img.style.height = '40px';
                img.style.borderRadius = '50%';
                img.style.marginRight = '15px';
                img.style.objectFit = 'cover';

                // 用戶名和全名
                const userInfo = document.createElement('div');
                userInfo.style.flex = '1';

                const username = document.createElement('div');
                username.textContent = follower.username;
                username.style.fontWeight = 'bold';
                username.style.color = '#e0e0e0';
                username.style.fontSize = '14px';

                const fullName = document.createElement('div');
                fullName.textContent = follower.fullName || '';
                fullName.style.fontSize = '12px';
                fullName.style.color = '#aaa';
                fullName.style.marginTop = '3px';

                userInfo.appendChild(username);
                userInfo.appendChild(fullName);

                // 移除追蹤按鈕
                const removeBtn = document.createElement('button');
                removeBtn.textContent = '移除追蹤';
                removeBtn.style.padding = '5px 10px';
                removeBtn.style.backgroundColor = '#3a3a3a';
                removeBtn.style.color = '#e0e0e0';
                removeBtn.style.border = '1px solid #555';
                removeBtn.style.borderRadius = '4px';
                removeBtn.style.cursor = 'pointer';
                removeBtn.style.fontSize = '12px';
                removeBtn.style.transition = 'all 0.2s ease';

                removeBtn.addEventListener('click', async () => {
                    removeBtn.disabled = true;
                    removeBtn.textContent = '處理中...';
                    removeBtn.style.opacity = '0.7';

                    try {
                        await removeFollower(follower.pk);
                        item.style.opacity = '0.5';
                        removeBtn.textContent = '已移除';
                        removeBtn.style.backgroundColor = '#555';
                    } catch (error) {
                        removeBtn.textContent = '失敗';
                        removeBtn.style.backgroundColor = '#8B0000';
                        console.error('移除追蹤失敗:', error);
                        setTimeout(() => {
                            removeBtn.disabled = false;
                            removeBtn.textContent = '重試';
                            removeBtn.style.backgroundColor = '#3a3a3a';
                            removeBtn.style.opacity = '1';
                        }, 2000);
                    }
                });

                item.appendChild(img);
                item.appendChild(userInfo);
                item.appendChild(removeBtn);

                followerList.appendChild(item);
            });
        } catch (error) {
            console.error('顯示關注者列表時出錯:', error);
            if (error.message.includes('TrustedHTML')) {
                updateStatus(`已成功獲取所有 ${allFollowers.length} 位關注者,但顯示存在安全限制。`);

                // 創建一個簡單的信息提示
                const infoMessage = document.createElement('div');
                infoMessage.textContent = `因瀏覽器安全限制,無法顯示完整列表。已獲取 ${allFollowers.length} 位關注者。請查看控制台獲取完整數據。`;
                infoMessage.style.color = '#e0e0e0';
                infoMessage.style.padding = '10px';
                infoMessage.style.backgroundColor = '#444';
                infoMessage.style.borderRadius = '4px';
                infoMessage.style.margin = '10px 0';

                // 清空列表並添加信息
                while (followerList.firstChild) {
                    followerList.removeChild(followerList.firstChild);
                }
                followerList.appendChild(infoMessage);
            } else {
                throw error;
            }
        }
    }

    // 獲取所有關注者的主函數
    async function fetchAllFollowers() {
        allFollowers = [];
        updateStatus('正在獲取關注者...');
        updateProgressBar(0, true);

        // 從 cookies 中獲取 CSRF 令牌和用戶 ID
        const csrftoken = getCookie('csrftoken');
        const userId = getCookie('ds_user_id');

        if (!csrftoken || !userId) {
            updateStatus('錯誤:缺少所需的 cookies。請確保已登錄 Threads。');
            updateProgressBar(0, false);
            return;
        }

        try {
            // 開始獲取關注者,無游標(第一頁)
            await fetchFollowersPage(null, csrftoken, userId);

            // 用結果更新 UI
            updateProgressBar(100);
            updateStatus(`完成!找到 ${allFollowers.length} 位關注者。`);
            displayFollowers();

            // 記錄數據以進行調試
            console.log('所有關注者:', allFollowers);
        } catch (error) {
            console.error('獲取關注者時出錯:', error);
            updateStatus(`錯誤: ${error.message}`);
            updateProgressBar(0, false);
        }
    }

    // 獲取一頁關注者的函數
    async function fetchFollowersPage(cursor, csrftoken, userId) {
        // 根據是否有游標確定要使用的查詢
        const friendlyName = cursor ?
            "BarcelonaFriendshipsFollowersTabRefetchableQuery" :
            "BarcelonaFriendshipsFollowersTabQuery";

        // 根據查詢確定要使用的 doc_id
        const docId = cursor ? "9226067564176291" : "9523819394337000";

        // 準備請求的變量
        const variables = cursor ?
            {
                "after": cursor,
                "first": 20,
                "id": userId,
                "__relay_internal__pv__BarcelonaIsLoggedInrelayprovider": true,
                "__relay_internal__pv__BarcelonaIsCrawlerrelayprovider": false,
                "__relay_internal__pv__BarcelonaHasDisplayNamesrelayprovider": false
            } :
            {
                "first": 20,
                "userID": userId,
                "__relay_internal__pv__BarcelonaIsLoggedInrelayprovider": true,
                "__relay_internal__pv__BarcelonaIsCrawlerrelayprovider": false,
                "__relay_internal__pv__BarcelonaHasDisplayNamesrelayprovider": false,
                "__relay_internal__pv__BarcelonaShouldShowFediverseListsrelayprovider": true
            };

        // 創建表單數據
        const formData = new URLSearchParams();
        formData.append('av', '17841467954992287');
        formData.append('__user', userId);
        formData.append('__a', '1');
        formData.append('__req', cursor ? 'r' : 'o');
        formData.append('dpr', '1');
        formData.append('fb_api_caller_class', 'RelayModern');
        formData.append('fb_api_req_friendly_name', friendlyName);
        formData.append('variables', JSON.stringify(variables));
        formData.append('server_timestamps', 'true');
        formData.append('doc_id', docId);

        try {
            // 發送 API 請求
            const response = await fetch('https://www.threads.net/graphql/query', {
                method: 'POST',
                headers: {
                    'content-type': 'application/x-www-form-urlencoded',
                    'x-csrftoken': csrftoken,
                    'x-fb-friendly-name': friendlyName,
                    'x-ig-app-id': '238260118697367'
                },
                body: formData,
                credentials: 'include'
            });

            const data = await response.json();

            // 處理API回應結構差異,第一次請求使用user,後續請求使用fetch__XDTUserDict
            let followersData;
            let pageInfo;

            if (data.data.user && data.data.user.followers) {
                // 第一次請求的結構
                followersData = data.data.user.followers.edges;
                pageInfo = data.data.user.followers.page_info;
            } else if (data.data.fetch__XDTUserDict && data.data.fetch__XDTUserDict.followers) {
                // 後續請求的結構
                followersData = data.data.fetch__XDTUserDict.followers.edges;
                pageInfo = data.data.fetch__XDTUserDict.followers.page_info;
            } else {
                console.error('未知的API回應結構:', data);
                throw new Error('無效的響應數據');
            }

            // 處理每個關注者
            followersData.forEach(edge => {
                if (edge.node) {
                    allFollowers.push({
                        id: edge.node.id,
                        pk: edge.node.pk,
                        username: edge.node.username,
                        fullName: edge.node.full_name || '',
                        profilePicUrl: edge.node.profile_pic_url,
                        followedBy: edge.node.friendship_status.followed_by,
                        following: edge.node.friendship_status.following
                    });
                }
            });

            // 更新 UI
            updateCounter();
            updateStatus(`目前已獲取 ${allFollowers.length} 位關注者...`);

            // 檢查是否有更多頁面要獲取
            if (pageInfo.has_next_page && pageInfo.end_cursor) {
                // 獲取下一頁
                await fetchFollowersPage(pageInfo.end_cursor, csrftoken, userId);
            }
        } catch (error) {
            console.error('獲取關注者頁面時出錯:', error);
            throw error;
        }
    }

    // 移除追蹤單個用戶
    async function removeFollower(userId) {
        const csrftoken = getCookie('csrftoken');
        const myUserId = getCookie('ds_user_id');

        if (!csrftoken || !myUserId) {
            throw new Error('缺少 CSRF 令牌或用戶ID');
        }

        const formData = new URLSearchParams();
        formData.append('av', '17841467954992287');
        formData.append('__user', myUserId);
        formData.append('__a', '1');
        formData.append('__req', '1i');
        formData.append('dpr', '1');
        formData.append('fb_api_caller_class', 'RelayModern');
        formData.append('fb_api_req_friendly_name', 'useBarcelonaRemoveFollowerMutation');
        formData.append('variables', JSON.stringify({ "userID": userId }));
        formData.append('server_timestamps', 'true');
        formData.append('doc_id', '9347461858632460');

        const response = await fetch('https://www.threads.net/graphql/query', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'X-CSRFToken': csrftoken,
                'X-IG-App-ID': '238260118697367',
                'X-FB-Friendly-Name': 'useBarcelonaRemoveFollowerMutation'
            },
            body: formData,
            credentials: 'include'
        });

        const data = await response.json();

        if (!data || !data.data || !data.status || data.status !== 'ok') {
            console.error('移除追蹤響應異常:', data);
            throw new Error('移除追蹤請求失敗');
        }

        return data;
    }

    // 移除所有追蹤
    async function removeAllFollowers() {
        if (allFollowers.length === 0) {
            updateStatus('沒有關注者可以移除追蹤。請先獲取關注者列表。');
            return;
        }

        const confirmRemove = confirm(`確定要移除追蹤所有 ${allFollowers.length} 位用戶嗎?\n此操作無法撤銷。`);
        if (!confirmRemove) return;

        updateProgressBar(0, true);
        updateStatus(`開始批量移除追蹤,共 ${allFollowers.length} 位用戶...`);

        const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
        let successCount = 0;
        let failCount = 0;
        let continuousFailCount = 0;

        for (let i = 0; i < allFollowers.length; i++) {
            const follower = allFollowers[i];
            const progress = Math.round(((i + 1) / allFollowers.length) * 100);

            updateProgressBar(progress);
            updateStatus(`正在處理 ${i+1}/${allFollowers.length}:${follower.username}`);

            try {
                await removeFollower(follower.pk);
                successCount++;
                continuousFailCount = 0;

                // 更新UI
                const followerItem = document.querySelector(`[data-user-id="${follower.pk}"]`);
                if (followerItem) {
                    followerItem.style.opacity = '0.5';
                    const removeBtn = followerItem.querySelector('button');
                    if (removeBtn) {
                        removeBtn.textContent = '已移除';
                        removeBtn.style.backgroundColor = '#555';
                        removeBtn.disabled = true;
                    }
                }

                // 避免請求過於頻繁,添加隨機延遲
                await delay(Math.random() * 1000 + 1500);
            } catch (error) {
                console.error(`移除 ${follower.username} 的追蹤失敗:`, error);
                failCount++;
                continuousFailCount++;

                // 如果連續失敗,暫停一段時間
                if (continuousFailCount > 3) {
                    updateStatus(`連續失敗 ${continuousFailCount} 次,暫停中...`);
                    await delay(5000); // 暫停5秒
                }

                // 錯誤太多,詢問是否繼續
                if (continuousFailCount > 10) {
                    const continueProcess = confirm(`已發生多次連續錯誤 (${continuousFailCount}次)。\n是否繼續進行?`);
                    if (!continueProcess) {
                        updateStatus(`操作已暫停。成功: ${successCount}, 失敗: ${failCount}`);
                        return;
                    }
                    continuousFailCount = 0; // 重置連續失敗計數
                }
            }
        }

        updateProgressBar(100);
        updateStatus(`完成! 成功移除追蹤 ${successCount} 位用戶,失敗 ${failCount} 位。`);
    }

    // 獲取 cookie 值的輔助函數
    function getCookie(name) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
        return null;
    }

    // 初始化腳本
    function init() {
        // 僅在 Threads 網站上運行
        if (window.location.hostname === 'www.threads.net') {
            // 確保頁面完全加載後再創建UI
            if (document.readyState === 'complete' || document.readyState === 'interactive') {
                setTimeout(createUI, 1000);
            } else {
                window.addEventListener('DOMContentLoaded', () => {
                    setTimeout(createUI, 1000);
                });
            }
        }
    }

    // 啟動腳本
    init();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址