Greasy Fork

Greasy Fork is available in English.

qB-WebUI 标记tracker异常

在 qBittorrent WebUI 中添加按钮,用于标记tracker状态出错的种子

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            qB-WebUI 标记tracker异常
// @name:en         qB-WebUI tag trackerERR
// @namespace       localhost
// @version         0.2.2
// @author          ColderCoder, avatasia, Schalkiii, flashlab
// @description     在 qBittorrent WebUI 中添加按钮,用于标记tracker状态出错的种子
// @description:en  add a button in qBittorrent WebUI to tag torrents with tracker error
// @license         MIT
// @run-at          document-end
// @match           http://192.168.10.72:9091
// @match           http://127.0.0.1:9091
// ==/UserScript==
/* globals torrentsTable */
//require qB API v2.3.0 +

const extraStatus = [1]; // 需要额外标记的种子状态,包括:未联系(1)/更新中(3)/未工作(4)
const keywords = ['registered', 'deleted', 'exist', 'banned', 'err', '删']; // 关键词用于匹配未注册种子的服务器消息
const tagNames = ['trackerErr', 'notContact', 'unregister', 'updating', 'notWork']; //待写入的标签名
const host = window.location.href;
const baseURL = host + 'api/v2/torrents/';

function getList(scope) {
    if (scope == "all") {
        return getFetch('info')
    } else if (scope == "list") {
        return torrentsTable.getFilteredAndSortedRows().map(item => item.full_data);
    } else {
        return null
    }
}

async function getFetch(route) {
    try {
        const response = await fetch(baseURL + route);
        if (!response.ok) {
            throw new Error('Error fetching info!');
        }
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Error: ', route, error);
        return null;
    }
}

async function processTorrents(scope = 'all') {
    const tagList = new Map();
    let count = 0;
    let torrentCounts = 0;
    let ignoreDiscontact = false; //累计超过10次网络请求失败是否终止操作
    const torrentList = await getList(scope);
    if (!torrentList || torrentList.length == 0) return;
    for (const torrent of torrentList) {
        const trackers = await getFetch(`trackers?hash=${torrent.hash}`);
        if (!trackers) {
            count++
            if (!ignoreDiscontact && count > 10 && window.confirm("网络请求失败过多,是否终止?")) {
                break;
            } else {
                ignoreDiscontact = true;
                continue
            }
        }
        let newtag = null;
        let allTrackersNotWorking = true;
        for (const tracker of trackers) {
            if (tracker.status === 4) { //tracker is not working
                for (const msg of keywords) {
                    if (tracker.msg.includes(msg)) {
                        console.log(`Unregistered: ${torrent.name}: ${tracker.msg}`);
                        newtag = tagNames[2];
                    }
                }
            } else if (tracker.status !== 4){
                // 如果有任何一个 tracker 不是状态 4,则该种子不符合所有 tracker 都不工作的条件
                allTrackersNotWorking = false;
            } else if (extraStatus.includes(tracker.status)) {
                newtag = tagNames[tracker.status]
            }
        }
        // 如果所有 tracker 都不工作并且没有其他标记,则打上 tagName[0]
        if (allTrackersNotWorking && !newtag) newtag = tagNames[0];
        if (newtag && !torrent.tags.includes(newtag)) {
            tagList.set(newtag, tagList.get(newtag) ? `${tagList.get(newtag)}|${torrent.hash}` : torrent.hash)
            torrentCounts++
        }
    }
    if (!window.confirm(`共获取 ${torrentList.length} 项(失败 ${count}项),共 ${torrentCounts} 项需要标记,是否写入标签?`)) return;
    if (torrentCounts == 0) return;
    const url = `${baseURL}addTags`;
    for (const [tags, hashes] of tagList) {
        let data = new URLSearchParams();
        data.append('hashes', hashes);
        data.append('tags', tags);
        try {
            fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: data
            })
                .then(response => {
                console.log(response);
            })
                .catch(error => {
                console.error('Error:', tags, hashes, error);
            });
        } catch (error) {
            console.error('Error:', error.message);
        }

    }
    alert("完成!")
}

const newBtn = document.createElement("li");
newBtn.innerHTML = "<a class='js-modal'><b> 标记tracker异常 </b></a>";
document.querySelector("#desktopNavbar > ul").append(newBtn);

newBtn.addEventListener("click", async function() {
    const scope = window.prompt("!!!请先手动删除[tagNames]中包含的标签!!!\n检查全部种子请输入 [all]\n仅检查当前表格中显示的种子请输入 [list]", "all")
    if (!scope) return;
    await processTorrents(scope);
});