PT 刷流 Vertex 刷流规则分享
MarkXiu
2026-09-03 22:03
1
Vertex 刷流规则脚本合集
本文整理自自己实际 Vertex 配置,纯粹是抛砖引玉,大佬轻喷。
这些脚本用于 qBittorrent 的自动删种、RSS 筛选和上传限速,分享出来供参考。不同 VPS 的 CPU、内存、磁盘容量、网络带宽、下载器版本和刷流目标不同,不建议直接照搬参数。
复制前,建议把自己的 VPS 配置和刷流目标交给 AI,让 AI 检查连接数、并发下载数、磁盘空间阈值、上传速度和删种条件。
重要资源建议设置为 keep 或 KEEP 分类,并先使用测试任务验证。
一、定时脚本
定时脚本按固定周期执行,适合做上传限速、标签处理等周期性任务。
黑盒-三倍限速(主要防止上传太猛, 下载完成已经超过3倍分享率,浪费流量)
- 执行周期:每分钟一次
功能:分享率达到 3 倍后把单种上传限速设为 1 MiB/s;低于 2.5 倍时恢复到 60 MiB/s;分类包含“发种”的任务跳过。
完整源码:
async () => {
/**
* Vertex 定时脚本:
* 对指定下载器里的种子按分享率自动切换单种上传限速。
*
* 规则:
* 1. 只检查配置中的下载器
* 2. 标签包含“发种”的种子跳过
* 3. 当 uploaded / size >= 3 时,设置单种上传限速为 1 MiB/s
* 4. 当 uploaded / size < 2.5 时,设置单种上传限速为 60 MiB/s
* 5. 只处理 size > 0、uploaded > 0、downloaded > 0 的种子
*/
const config = {
/**
* 达到这个分享率时,切到低速上传。
*/
enableLimitRatio: 3,
/**
* 低于这个分享率时,切到高速上传。
*/
disableLimitRatio: 2.5,
/**
* 低速限速,单位 Byte/s。
* 这里是 1 MiB/s。
*/
lowUploadLimit: 1 * 1024 * 1024,
/**
* 高速限速,单位 Byte/s。
* 这里是 60 MiB/s。
*/
highUploadLimit: 60 * 1024 * 1024,
/**
* 日志里展示用的文案。
*/
lowUploadLimitText: '1 MiB/s',
highUploadLimitText: '60 MiB/s',
/**
* 跳过带有这些标签的种子。
*/
skipTags: ['发种'],
/**
* 需要巡检的下载器列表。
*/
clients: [
{
name: 'hostdzire',
clientId:'YOUR_CLIENT_ID',
url: 'https://your-qbittorrent.example',
pass:'YOUR_PASSWORD'
},
{
name: 'takehost',
clientId:'YOUR_CLIENT_ID',
url: 'https://your-qbittorrent.example',
pass:'YOUR_PASSWORD'
}
]
};
const axios = require('axios');
logger.info('=========================================');
logger.info(`[分享率限速] 脚本启动,本次共检查 ${config.clients.length} 个下载器`);
for (const qb of config.clients) {
try {
const client = global.runningClient[qb.clientId];
if (!client || !client.maindata || !Array.isArray(client.maindata.torrents)) {
logger.info(`[${qb.name}] 缓存未就绪或下载器不可用,跳过`);
continue;
}
const torrents = client.maindata.torrents;
const toLowLimit = [];
const toHighLimit = [];
let skippedByTagCount = 0;
for (const t of torrents) {
const tags = String(t.tags || '');
const size = Number(t.size || 0);
const uploaded = Number(t.uploaded || 0);
const downloaded = Number(t.downloaded || 0);
const hash = String(t.hash || '').trim();
const shouldSkipByTag = config.skipTags.some(tag => tags.indexOf(tag) !== -1);
if (shouldSkipByTag) {
skippedByTagCount++;
continue;
}
if (!hash || size <= 0 || uploaded <= 0 || downloaded <= 0) {
continue;
}
const ratio = uploaded / size;
const currentLimit = Number(
t.upLimit !== undefined ? t.upLimit : (t.up_limit !== undefined ? t.up_limit : 0)
);
if (ratio >= config.enableLimitRatio && currentLimit !== config.lowUploadLimit) {
toLowLimit.push(t);
continue;
}
if (ratio < config.disableLimitRatio && currentLimit !== config.highUploadLimit) {
toHighLimit.push(t);
}
}
if (toLowLimit.length === 0 && toHighLimit.length === 0) {
logger.info(
`[${qb.name}] 巡检完成,无需处理;标签豁免数量: ${skippedByTagCount}`
);
continue;
}
logger.info(
`[${qb.name}] 准备处理:切低速 ${toLowLimit.length} 个,切高速 ${toHighLimit.length} 个;标签豁免数量: ${skippedByTagCount}`
);
const loginData = new URLSearchParams();
loginData.append('username', qb.user);
loginData.append('password', qb.pass);
const loginRes = await axios.post(
`${qb.url}/api/v2/auth/login`,
loginData.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
const cookie = loginRes.headers['set-cookie'];
if (!cookie) {
logger.info(`[${qb.name}] 登录失败,未获取到 Cookie`);
continue;
}
const requestHeaders = {
'Content-Type': 'application/x-www-form-urlencoded',
Cookie: Array.isArray(cookie) ? cookie.join('; ') : cookie
};
for (const t of toLowLimit) {
try {
const payload = new URLSearchParams();
payload.append('hashes', t.hash);
payload.append('limit', String(config.lowUploadLimit));
await axios.post(
`${qb.url}/api/v2/torrents/setUploadLimit`,
payload.toString(),
{ headers: requestHeaders }
);
const ratio = (Number(t.uploaded || 0) / Number(t.size || 1)).toFixed(2);
const uploadedGiB = (Number(t.uploaded || 0) / 1024 / 1024 / 1024).toFixed(2);
logger.info(
`[${qb.name}] 已切低速: ${t.name} | 比例: ${ratio}x | 已传: ${uploadedGiB} GiB | 限速: ${config.lowUploadLimitText}`
);
} catch (e) {
logger.info(`[${qb.name}] 切低速失败: ${t.name} | ${e.message}`);
}
}
for (const t of toHighLimit) {
try {
const payload = new URLSearchParams();
payload.append('hashes', t.hash);
payload.append('limit', String(config.highUploadLimit));
await axios.post(
`${qb.url}/api/v2/torrents/setUploadLimit`,
payload.toString(),
{ headers: requestHeaders }
);
const ratio = (Number(t.uploaded || 0) / Number(t.size || 1)).toFixed(2);
const uploadedGiB = (Number(t.uploaded || 0) / 1024 / 1024 / 1024).toFixed(2);
logger.info(
`[${qb.name}] 已切高速: ${t.name} | 比例: ${ratio}x | 已传: ${uploadedGiB} GiB | 限速: ${config.highUploadLimitText}`
);
} catch (e) {
logger.info(`[${qb.name}] 切高速失败: ${t.name} | ${e.message}`);
}
}
} catch (e) {
logger.info(`[${qb.name}] 节点执行异常: ${e.message}`);
}
}
};
二、删种规则
以下规则用于处理下载异常、低回报做种和磁盘空间压力。多条规则同时使用时,请在 Vertex 中确认优先级和执行顺序。
下载阶段考察分享率
- 类型:
javascript - 优先级:
90
完整源码:
(maindata, torrent) => {
/**
* Vertex 规则:
* 下载阶段按“分享率 = uploaded / downloaded”分段判断。
*
* 命中以下任一条件时返回 true:
* - 下载进度 >= 10%,且分享率 < 0.05
* - 下载进度 >= 30%,且分享率 < 0.09
* - 下载进度 >= 50%,且分享率 < 0.25
*/
const config = {
/**
* 只处理下载中的任务。
*/
targetStates: ['downloading', 'stalledDL'],
/**
* 永不处理的分类。
*/
keepCategories: ['keep'],
/**
* 新任务保护时间,单位:秒。
*
* 给任务一点时间建立连接和上传机会。
* 当前为 15 分钟。
*/
protectSeconds: 900,
/**
* 接近完成时不再处理。
*/
maxProgress: 0.95,
/**
* 分阶段分享率阈值。
*
* 分享率 = uploaded / downloaded
*/
ratioStages: [
{ minProgress: 0.10, maxRatio: 0.05 },
{ minProgress: 0.30, maxRatio: 0.09 },
{ minProgress: 0.50, maxRatio: 0.25 }
]
};
function isKeepCategory(t) {
const category = t.category || '';
return config.keepCategories.indexOf(category) !== -1;
}
function isTargetState(t) {
const state = t.state || '';
return config.targetStates.indexOf(state) !== -1;
}
if (isKeepCategory(torrent)) {
return false;
}
if (!isTargetState(torrent)) {
return false;
}
const now = Number(maindata?.now || Math.floor(Date.now() / 1000));
const addedTime = Number(torrent.addedTime || 0);
const progress = Number(torrent.progress || 0);
const downloaded = Number(torrent.downloaded || 0);
const uploaded = Number(torrent.uploaded || 0);
/**
* 新任务保护。
*/
if (addedTime > 0 && now - addedTime < config.protectSeconds) {
return false;
}
/**
* 非有效下载阶段直接跳过。
*/
if (progress <= 0 || progress >= config.maxProgress) {
return false;
}
/**
* 没有实际下载量时无法计算分享率。
*/
if (downloaded <= 0) {
return false;
}
const ratio = uploaded / downloaded;
for (const stage of config.ratioStages) {
if (progress >= stage.minProgress && ratio < stage.maxRatio) {
return true;
}
}
return false;
};
空间压力小于30GB
- 类型:
javascript - 优先级:
110
完整源码:
(maindata, torrent) => {
/**
* Vertex 删种规则:
* 空间不足时,删除“历史平均上传速度最低”的 1 个种子。
*
* 历史平均上传速度 = 已上传量 uploaded / 做种时长
*
* 只使用 Vertex 官方文档里明确存在的字段:
* - maindata.freeSpaceOnDisk
* - maindata.torrents
* - torrent.name
* - torrent.size
* - torrent.progress
* - torrent.uploaded
* - torrent.category
* - torrent.state
* - torrent.completedTime
*/
const config = {
/**
* 剩余空间低于这个值时,开始进入删种判断。
* 当前阈值:30 GiB。
*/
freeSpaceTrigger: 30 * 1024 ** 3,
/**
* 新完成种子的保护时间。
*
* 当前为 30 分钟。
* 刚完成的种子 uploaded 可能还没增长,
* 平均上传速度会天然很低,所以需要保护。
*/
protectSeconds: 0.5 * 3600,
/**
* 永不删除的分类。
*
* 你可以在 qBittorrent / Vertex 里给重要种子设置这些分类。
*/
keepCategories: ['keep', 'KEEP'],
/**
* 参与删种排序的状态。
*
* uploading = 正在做种并上传
* stalledUP = 做种但当前无上传
* queuedUP = 排队等待上传
* forcedUP = 强制做种
* pausedUP = 已完成但暂停
*
* checkingUP 没放进来,是为了避免校验中的种子被删。
*/
seedingStates: [
'uploading',
'stalledUP',
'queuedUP',
'forcedUP',
'pausedUP'
],
/**
* 最少候选数量。
*
* 候选数量小于这个值时不删。
* 这样可以避免只剩 1 个可删种子时还继续删。
*
* 候选种子少于 2 个时不删除。
*/
minCandidateCount: 2
};
/**
* 当前时间,单位:秒。
*
* Vertex 官方示例中使用 moment().unix()。
*/
const now = moment().unix();
/**
* 当前剩余空间,单位:Byte。
*/
const freeSpace = Number(maindata.freeSpaceOnDisk || 0);
/**
* 空间没有压力,直接不删。
*
* 所以这个规则不会无脑删种:
* 只有 freeSpace <= freeSpaceTrigger 时才会继续往下判断。
*/
if (freeSpace > config.freeSpaceTrigger) {
return false;
}
/**
* 判断是否为白名单分类。
*/
function isKeepCategory(t) {
const category = t.category || '';
return config.keepCategories.indexOf(category) !== -1;
}
/**
* 判断种子是否已完成。
*
* progress >= 1 表示 100% 完成。
*/
function isCompleted(t) {
return Number(t.progress || 0) >= 1;
}
/**
* 判断是否为做种相关状态。
*/
function isSeedingState(t) {
const state = t.state || '';
return config.seedingStates.indexOf(state) !== -1;
}
/**
* 计算完成后的做种时长,单位:秒。
*
* completedTime 是完成时间戳。
* 如果 completedTime <= 0,说明没有有效完成时间。
*/
function seedAgeSeconds(t) {
const completedTime = Number(t.completedTime || 0);
if (completedTime <= 0) {
return 0;
}
return Math.max(now - completedTime, 0);
}
/**
* 计算历史平均上传速度,单位:Byte/s。
*
* 公式:
* 历史平均上传速度 = uploaded / 做种时长
*
* uploaded 是总上传量,单位 Byte。
*/
function avgUploadSpeed(t) {
const age = seedAgeSeconds(t);
if (age <= 0) {
return 0;
}
return Number(t.uploaded || 0) / age;
}
/**
* 判断某个种子是否进入“候选池”。
*
* 只有满足以下条件才会参与排序:
* 1. 不是白名单分类
* 2. 已完成
* 3. 是做种相关状态
* 4. 完成时间超过保护期
*/
function isCandidate(t) {
if (isKeepCategory(t)) {
return false;
}
if (!isCompleted(t)) {
return false;
}
if (!isSeedingState(t)) {
return false;
}
if (seedAgeSeconds(t) < config.protectSeconds) {
return false;
}
return true;
}
/**
* 从全局种子列表中筛选候选种子。
*
* maindata.torrents 是 Vertex 文档中明确提供的种子列表。
*/
const candidates = (maindata.torrents || []).filter(isCandidate);
/**
* 候选数量太少,不删。
*/
if (candidates.length < config.minCandidateCount) {
return false;
}
/**
* 按历史平均上传速度从低到高排序。
*
* 平均速度越低,说明过去越低效。
*
* 如果平均速度相同:
* 优先删除 size 更大的种子,因为能释放更多空间。
*/
candidates.sort((a, b) => {
const avgA = avgUploadSpeed(a);
const avgB = avgUploadSpeed(b);
if (avgA !== avgB) {
return avgA - avgB;
}
return Number(b.size || 0) - Number(a.size || 0);
});
/**
* 候选池中历史平均上传速度最低的种子。
*/
const worst = candidates[0];
/**
* Vertex 会对每个 torrent 调用一次这个函数。
*
* 只有当前 torrent 正好是 worst 时,才返回 true 删除。
*
* 文档里没有 hash 字段,所以这里用:
* - name
* - size
* - completedTime
*
* 三个字段一起匹配,降低重名种子误删概率。
*/
return (
torrent.name === worst.name &&
Number(torrent.size || 0) === Number(worst.size || 0) &&
Number(torrent.completedTime || 0) === Number(worst.completedTime || 0)
);
};
黑盒-做种回报差-3倍跳车
- 类型:
javascript - 优先级:
40
完整源码:
(maindata, torrent) => {
const config = {
speedThreshold: 1 * 1024 * 1024, // 平均上传速度阈值:1 MiB/s
keepCategories: ['keep', 'KEEP'],
protectSeconds: 300, // 新完成种子保护 5 分钟 // 新完成种子保护 5 分钟
seedingStates: [
'uploading',
'stalledUP',
'queuedUP',
'forcedUP',
'pausedUP'
],
ratioDeleteThreshold: 3,
// 为空表示不过滤;填关键字可只看某个种子
debugNameKeyword: ''
};
const now = moment().unix();
const isKeep = (t) =>
config.keepCategories.includes(t.category || '');
const isCompleted = (t) =>
Number(t.progress || 0) >= 1;
const isSeeding = (t) =>
config.seedingStates.includes(t.state || '');
const age = (t) => {
const completedTime = Number(t.completedTime || 0);
return completedTime > 0 ? now - completedTime : 0;
};
const avgSpeed = (t) => {
const seedAge = age(t);
if (seedAge <= 0) return 0;
return Number(t.uploaded || 0) / seedAge;
};
const ratioField = (t) =>
Number(t.ratio || 0);
const uploadSizeRatio = (t) => {
const size = Number(t.size || 0);
const uploaded = Number(t.uploaded || 0);
if (size <= 0) return 0;
return uploaded / size;
};
const shouldDebug = (t) => {
if (!config.debugNameKeyword) return true;
return String(t.name || '').includes(config.debugNameKeyword);
};
const log = (t, stage, reason) => {
if (!shouldDebug(t)) return;
logger.info([
`[删种调试] ${stage}`,
`name=${t.name || '-'}`,
`state=${t.state || '-'}`,
`progress=${Number(t.progress || 0).toFixed(4)}`,
`ratioField=${ratioField(t).toFixed(4)}`,
`uploadSizeRatio=${uploadSizeRatio(t).toFixed(4)}`,
`uploadSpeed=${Number(t.uploadSpeed || 0)}`,
`age=${age(t)}`,
`avgSpeed=${avgSpeed(t).toFixed(2)}`,
`reason=${reason}`
].join(' | '));
};
if (isKeep(torrent)) {
return false;
}
if (!isCompleted(torrent)) {
return false;
}
if (!isSeeding(torrent)) {
return false;
}
if (ratioField(torrent) >= config.ratioDeleteThreshold) {
log(torrent, 'delete', 'ratio-field>=3');
return true;
}
if (age(torrent) < config.protectSeconds) {
log(torrent, 'skip', 'protect-seconds');
return false;
}
if (Number(torrent.uploadSpeed || 0) > config.speedThreshold) {
log(torrent, 'skip', 'active-uploading');
return false;
}
if (avgSpeed(torrent) < config.speedThreshold) {
log(torrent, 'delete', 'avg-speed-low');
return true;
}
log(torrent, 'skip', 'avg-speed-not-low');
return false;
};
长时间不发车删
- 类型:
normal - 优先级:
70 - 持续时间(秒):
1800 - 删除数量:
1
完整源码:
(maindata, torrent) => {
return false;
}
下载错误
- 类型:
normal - 优先级:
100 - 持续时间(秒):
300 - 仅删除种子记录:
False
完整源码:
(maindata, torrent) => {
return false;
}
慢车
- 类型:
javascript - 优先级:
90 - 持续时间(秒):
60
完整源码:
(maindata, torrent) => {
/**
* Vertex 规则:
* 在特定环境下,找出“下载中、上传偏低、且已开始稳定下载”的种子。
*
* 命中条件:
* 1. 不属于保留分类
* 2. 不在凌晨保护时段
* 3. 全局 leechingCount 大于 10
* 4. 当前种子 leecher 小于 100
* 5. 状态为 downloading
* 6. 上传速度 <= 550 KiB/s
* 7. 下载进度 >= 10%
* 8. 加种时间 >= 600 秒
*/
const config = {
/**
* 永不处理的分类。
*/
keepCategories: ['keep'],
/**
* 只处理下载中的种子。
*/
targetState: 'downloading',
/**
* 凌晨保护时段:
* 00:00 - 07:59 不处理。
*/
quietHoursStart: 0,
quietHoursEnd: 7,
/**
* 全局下载数量保护阈值。
*/
minLeechingCount: 10,
/**
* 热门种子保护阈值。
*/
maxLeecher: 100,
/**
* 上传速度上限。
*/
maxUploadSpeed: util.calSize(550, 'KiB'),
/**
* 最低下载进度。
*/
minProgress: 0.1,
/**
* 新加种子的最小存活时间,单位:秒。
*/
minAgeSeconds: 600
};
const currentHour = moment().hour();
const now = moment().unix();
const state = torrent.state || '';
const category = torrent.category || '';
const leecher = Number(torrent.leecher || 0);
const uploadSpeed = Number(torrent.uploadSpeed || 0);
const progress = Number(torrent.progress || 0);
const addedTime = Number(torrent.addedTime || 0);
const leechingCount = Number(maindata.leechingCount || 0);
/**
* 保留分类直接跳过。
*/
if (config.keepCategories.indexOf(category) !== -1) {
return false;
}
/**
* 热门种子不处理。
*/
if (leecher >= config.maxLeecher) {
return false;
}
/**
* 凌晨保护时段直接跳过。
*/
if (
currentHour >= config.quietHoursStart &&
currentHour <= config.quietHoursEnd
) {
return false;
}
/**
* 全局下载数太少时不处理。
*/
if (leechingCount <= config.minLeechingCount) {
return false;
}
/**
* 新加种子先保护一段时间。
*/
if (addedTime > 0 && now - addedTime < config.minAgeSeconds) {
return false;
}
/**
* 主规则:
* 下载中、上传偏低、且进度达到阈值时命中。
*/
return (
state === config.targetState &&
uploadSpeed <= config.maxUploadSpeed &&
progress >= config.minProgress
);
};
白盒-做种回报差
- 类型:
javascript - 优先级:
40
完整源码:
(maindata, torrent) => {
const config = {
speedThreshold: 1.5 * 1024 * 1024, // 1.5 MiB/s
keepCategories: ['keep', 'KEEP'],
protectSeconds: 900, // 新完成种子保护 15 分钟
// 做种超过 6 小时后进入强规则判断
maxSeedingSeconds: 6 * 3600,
seedingStates: [
'uploading',
'stalledUP',
'queuedUP',
'forcedUP',
'pausedUP'
]
};
const now = moment().unix();
const isKeep = (t) =>
config.keepCategories.includes(t.category || '');
const isCompleted = (t) =>
Number(t.progress || 0) >= 1;
const isSeeding = (t) =>
config.seedingStates.includes(t.state || '');
const age = (t) => {
const c = Number(t.completedTime || 0);
return c > 0 ? now - c : 0;
};
const avgSpeed = (t) => {
const a = age(t);
if (a <= 0) return 0;
return Number(t.uploaded || 0) / a;
};
const isActiveUploading = (t) =>
Number(t.uploadSpeed || 0) > 1 * 1024 * 1024;
const shouldDelete = (t) => {
// 1. 白名单永不删除
if (isKeep(t)) return false;
// 2. 未完成不删
if (!isCompleted(t)) return false;
// 3. 不是做种状态不处理
if (!isSeeding(t)) return false;
const tAge = age(t);
// 4. 新种保护
if (tAge < config.protectSeconds) return false;
// 5. 做种超过6小时:直接删除(强规则)
if (tAge >= config.maxSeedingSeconds) return true;
// 6. 实时高活跃上传保护
if (isActiveUploading(t)) return false;
// 7. 历史平均上传效率判断
return avgSpeed(t) < config.speedThreshold;
};
return shouldDelete(torrent);
};
黑种-3倍下载/上传
- 类型:
javascript - 优先级:
100 - 持续时间(秒):
60
完整源码:
(maindata, torrent) => {
/**
* Vertex 下载中删种规则:
* 1. 只处理下载中的任务
* 2. 新种保护期内不删,等待 Peer 连接稳定
* 3. 快完成时不删,避免收尾误删
* 4. 下载速度太低时不删
* 5. 上传速度已经不错时不删
* 6. 下载速度明显大于上传速度时删除
*/
const config = {
protectSeconds: 3 * 60, // 新任务保护 3 分钟
completeProtectProgress: 0.95,
minDownloadSpeed: 5 * 1024 ** 2, // 最低下载速度:5 MiB/s
minUploadSpeed: 2 * 1024 ** 2, // 上传保护阈值:2 MiB/s
badSpeedRatio: 3 // 下载速度必须超过上传速度 3 倍
};
const now = moment().unix();
const addedTime = Number(torrent.addedTime || 0);
const progress = Number(torrent.progress || 0);
const downSpeed = Number(torrent.downloadSpeed || 0);
const upSpeed = Number(torrent.uploadSpeed || 0);
if (torrent.state !== 'downloading') {
return false;
}
if (addedTime > 0 && now - addedTime < config.protectSeconds) {
return false;
}
if (progress > config.completeProtectProgress) {
return false;
}
if (downSpeed < config.minDownloadSpeed) {
return false;
}
if (upSpeed > config.minUploadSpeed) {
return false;
}
if ((upSpeed > 0 ? downSpeed / upSpeed : Infinity) > config.badSpeedRatio) {
return true;
}
return false;
};
空间压力小于10GB
- 类型:
javascript - 优先级:
110
完整源码:
(maindata, torrent) => {
/**
* Vertex 删种规则:
* 空间不足时,删除“历史平均上传速度最低”的 1 个种子。
*
* 历史平均上传速度 = 已上传量 uploaded / 做种时长
*
* 只使用 Vertex 官方文档里明确存在的字段:
* - maindata.freeSpaceOnDisk
* - maindata.torrents
* - torrent.name
* - torrent.size
* - torrent.progress
* - torrent.uploaded
* - torrent.category
* - torrent.state
* - torrent.completedTime
*/
const config = {
/**
* 剩余空间低于这个值时,开始进入删种判断。
* 当前阈值:10 GiB。
*/
freeSpaceTrigger: 10 * 1024 ** 3,
/**
* 新完成种子的保护时间。
*
* 当前为 30 分钟。
* 刚完成的种子 uploaded 可能还没增长,
* 平均上传速度会天然很低,所以需要保护。
*/
protectSeconds: 0.5 * 3600,
/**
* 永不删除的分类。
*
* 你可以在 qBittorrent / Vertex 里给重要种子设置这些分类。
*/
keepCategories: ['keep', 'KEEP'],
/**
* 参与删种排序的状态。
*
* uploading = 正在做种并上传
* stalledUP = 做种但当前无上传
* queuedUP = 排队等待上传
* forcedUP = 强制做种
* pausedUP = 已完成但暂停
*
* checkingUP 没放进来,是为了避免校验中的种子被删。
*/
seedingStates: [
'uploading',
'stalledUP',
'queuedUP',
'forcedUP',
'pausedUP'
],
/**
* 最少候选数量。
*
* 候选数量小于这个值时不删。
* 这样可以避免只剩 1 个可删种子时还继续删。
*
* 候选种子少于 2 个时不删除。
*/
minCandidateCount: 2
};
/**
* 当前时间,单位:秒。
*
* Vertex 官方示例中使用 moment().unix()。
*/
const now = moment().unix();
/**
* 当前剩余空间,单位:Byte。
*/
const freeSpace = Number(maindata.freeSpaceOnDisk || 0);
/**
* 空间没有压力,直接不删。
*
* 所以这个规则不会无脑删种:
* 只有 freeSpace <= freeSpaceTrigger 时才会继续往下判断。
*/
if (freeSpace > config.freeSpaceTrigger) {
return false;
}
/**
* 判断是否为白名单分类。
*/
function isKeepCategory(t) {
const category = t.category || '';
return config.keepCategories.indexOf(category) !== -1;
}
/**
* 判断种子是否已完成。
*
* progress >= 1 表示 100% 完成。
*/
function isCompleted(t) {
return Number(t.progress || 0) >= 1;
}
/**
* 判断是否为做种相关状态。
*/
function isSeedingState(t) {
const state = t.state || '';
return config.seedingStates.indexOf(state) !== -1;
}
/**
* 计算完成后的做种时长,单位:秒。
*
* completedTime 是完成时间戳。
* 如果 completedTime <= 0,说明没有有效完成时间。
*/
function seedAgeSeconds(t) {
const completedTime = Number(t.completedTime || 0);
if (completedTime <= 0) {
return 0;
}
return Math.max(now - completedTime, 0);
}
/**
* 计算历史平均上传速度,单位:Byte/s。
*
* 公式:
* 历史平均上传速度 = uploaded / 做种时长
*
* uploaded 是总上传量,单位 Byte。
*/
function avgUploadSpeed(t) {
const age = seedAgeSeconds(t);
if (age <= 0) {
return 0;
}
return Number(t.uploaded || 0) / age;
}
/**
* 判断某个种子是否进入“候选池”。
*
* 只有满足以下条件才会参与排序:
* 1. 不是白名单分类
* 2. 已完成
* 3. 是做种相关状态
* 4. 完成时间超过保护期
*/
function isCandidate(t) {
if (isKeepCategory(t)) {
return false;
}
if (!isCompleted(t)) {
return false;
}
if (!isSeedingState(t)) {
return false;
}
if (seedAgeSeconds(t) < config.protectSeconds) {
return false;
}
return true;
}
/**
* 从全局种子列表中筛选候选种子。
*
* maindata.torrents 是 Vertex 文档中明确提供的种子列表。
*/
const candidates = (maindata.torrents || []).filter(isCandidate);
/**
* 候选数量太少,不删。
*/
if (candidates.length < config.minCandidateCount) {
return false;
}
/**
* 按历史平均上传速度从低到高排序。
*
* 平均速度越低,说明过去越低效。
*
* 如果平均速度相同:
* 优先删除 size 更大的种子,因为能释放更多空间。
*/
candidates.sort((a, b) => {
const avgA = avgUploadSpeed(a);
const avgB = avgUploadSpeed(b);
if (avgA !== avgB) {
return avgA - avgB;
}
return Number(b.size || 0) - Number(a.size || 0);
});
/**
* 候选池中历史平均上传速度最低的种子。
*/
const worst = candidates[0];
/**
* Vertex 会对每个 torrent 调用一次这个函数。
*
* 只有当前 torrent 正好是 worst 时,才返回 true 删除。
*
* 文档里没有 hash 字段,所以这里用:
* - name
* - size
* - completedTime
*
* 三个字段一起匹配,降低重名种子误删概率。
*/
return (
torrent.name === worst.name &&
Number(torrent.size || 0) === Number(worst.size || 0) &&
Number(torrent.completedTime || 0) === Number(worst.completedTime || 0)
);
};
三、RSS 规则
RSS 规则负责控制自动加入下载器的任务,建议根据自己的磁盘容量和下载带宽调整限制。
rss-allow
- 类型:
javascript - 优先级:
12
完整源码:
(torrent) => {
const config = {
downloaderId: '1b55ff1e',
enableLogging: false,
// 单个种子体积范围
// 小于 800 MiB 的种子通常刷流效率偏低
// 大于 60 GiB 的种子不自动加入
minTorrentSize: 800 * 1024 * 1024, // 800 MiB
maxTorrentSize: 60 * 1024 * 1024 * 1024, // 60 GiB
// 当前下载器所有任务的体积上限
maxHoldingSize: 85 * 1024 * 1024 * 1024, // 85 GiB
// 未完成任务的总体积上限
maxIncompleteSize: 60 * 1024 * 1024 * 1024, // 60 GiB
// 下载任务上限
// 同时下载任务数量上限
maxDownloadingTaskCount: 5
};
const fs = require('fs');
const path = require('path');
function logMessage(message) {
if (!config.enableLogging) return;
const logFilePath = '/vertex/log22/log.txt';
const logDir = path.dirname(logFilePath);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
fs.appendFileSync(
logFilePath,
`[${new Date().toLocaleString('zh-CN', { hour12: false })}] ${message}\n`
);
}
// Vertex 运行时里,当前下载器对象从 global.runningClient 取
const client = global.runningClient[config.downloaderId];
if (!client || !client.maindata || !client.maindata.torrents) {
logMessage(`未找到下载器或 maindata 不可用: ${config.downloaderId}`);
return false;
}
const torrentSize = Number(torrent.size || 0);
// 1. 先控制单个种子的大小
if (torrentSize < config.minTorrentSize || torrentSize > config.maxTorrentSize) {
logMessage(`拒绝 ${torrent.name}:体积不在允许范围内`);
return false;
}
let holdingSize = 0;
let incompleteSize = 0;
let downloadingTaskCount = 0;
// 2. 统计当前下载器总体状态
for (const existingTorrent of client.maindata.torrents) {
const size = Number(existingTorrent.size || 0);
const progress = Number(existingTorrent.progress || 0);
const state = existingTorrent.state || '';
// 当前下载器总持有量
holdingSize += size;
// 未完成任务总量
if (progress < 1) {
incompleteSize += size;
}
// 下载任务数
if (
state === 'downloading' ||
state === 'stalledDL' ||
state === 'metaDL' ||
state === 'forcedDL'
) {
downloadingTaskCount += 1;
}
}
// 3. 控制总持有量
if ((holdingSize + torrentSize) > config.maxHoldingSize) {
logMessage(`拒绝 ${torrent.name}:总持有体积超限`);
return false;
}
// 4. 控制未完成任务总体积
if ((incompleteSize + torrentSize) > config.maxIncompleteSize) {
logMessage(`拒绝 ${torrent.name}:未完成任务总体积超限`);
return false;
}
// 5. 控制下载任务数量
if (downloadingTaskCount >= config.maxDownloadingTaskCount) {
logMessage(`拒绝 ${torrent.name}:下载任务数 ${downloadingTaskCount} >= ${config.maxDownloadingTaskCount}`);
return false;
}
// 全部通过,允许进入下载器
return true;
};
四、使用前检查与 AI 优化建议
- 先确认 Vertex 支持的字段、运行时和下载器接口。
- 将脚本中的下载器地址、用户名、密码、下载器 ID 和保存路径替换成自己的值。
- 把 VPS 的 CPU、内存、磁盘剩余空间、网络上行、下载器版本和刷流目标交给 AI 分析。
- 让 AI 重点检查连接数、并发下载数、磁盘写入压力、空间阈值和删种保护时间。
- 首次使用时先观察日志,不要一次启用所有自动删种规则。
- 给重要种子设置
keep或KEEP分类。 - 涉及自动删除数据的规则,启用前先备份规则和重要文件。
可以把下面这段话发给 AI:
这是我的 Vertex/qBittorrent 刷流规则。请根据我的 VPS 配置、CPU、内存、磁盘容量与剩余空间、上下行带宽、下载器版本、站点规则和刷流目标,检查连接数、并发数、上传限速、磁盘空间阈值、保护时间和自动删种条件。请指出可能误删的地方,并给出修改后的完整脚本。不要凭空增加依赖,也不要删除 keep/KEEP 白名单逻辑。
最新回复 (0)
没有回复
* 帖子来源NodeSeek
附近帖子
- ↑看到一个贴4级的号140都骗,等级真的可信吗?只是金额问题而已
- ↑claude又挂了
- ↑grok炸了?
- ↑芜湖,174天终于来到6级
- ↑求推荐个类似S3 Browser的管理工具
- 📍 PT 刷流 Vertex 刷流规则分享
- ↓grok崩了吗
- ↓捡垃圾搞得飞牛待机27-28w功耗合格吗
- ↓马上就用完了
- ↓新加坡有啥移动快乐🐔吗
- ↓不考虑游戏性 单论画面让你由衷佩服的游戏有哪些?