既然都砸锅了,举报的也都举报了。那也不藏着掖着了
食用方法:
复制至vertex定时脚本模块
执行周期:0 5 * * * 每天凌晨5点执行
配置SCP账户信息和NC小🐔在Vertex的QB下载器ID(支持多🐔)
如果多个账号,那就复制一份再跑一个定时
且用且珍惜吧
另外有人有不要的vps1000翻倍甩我一只,我还没玩够!
async () => {
// 配置区域⬇⬇⬇
const CONFIG = {
scp: {
username: "your_scp_username", // SCP登录用户名
password: "your_scp_password", // SCP登录密码
rebootType: "POWERCYCLE",
},
clients: [
"13310b9b", // NC在Vertex的QB下载器ID
"13310b99",
],
delays: {
qbitPause: 30, // 暂停种子等待重启时间(秒)
powerOn: 300, // 开机等待恢复种子时间(秒)
serverReboot: 3, // 服务器间重启间隔(秒)
}
};
// 配置区域⬆⬆⬆
let QB_CLIENTS = null;
let SERVERS_TO_REBOOT = [];
let NEED_RESUME = false;
const util = require('../libs/util');
class ScpClient {
constructor(config) {
this.config = config;
this.headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
};
this.site_key = null;
this.serverMap = new Map();
this.BASE_URL = "https://www.servercontrolpanel.de";
}
static async create(config) {
const client = new ScpClient(config);
if (await client.login()) {
if (!await client._fetchServers()) {
logger.sc("登录成功但获取服务器信息失败");
}
return client;
}
throw new Error("SCP登录失败");
}
async _findSiteKey(response) {
if (typeof response !== 'string') throw new Error("响应不是字符串,无法查找 site_key");
const matches = response.match(/site_key\s*=\s*"([^"]+)"/g);
if (!matches?.length) throw new Error("未找到 site_key");
this.site_key = matches[matches.length - 1].match(/"([^"]+)"/)[1];
return this.site_key;
}
async _refreshSession() {
const response = await util.requestPromise({
method: 'GET',
url: `${this.BASE_URL}/SCP/Home?site_key=${this.site_key}`,
headers: this.headers
});
if (response.body.includes('SCP | Login')) {
logger.sc('会话已过期');
return false;
}
logger.sc('更新会话,site_key:', this.site_key);
return await this._findSiteKey(response.body);
}
async _fetchServers() {
try {
if (!await this._refreshSession()) {
logger.sc('获取服务器列表时会话已过期');
return false;
}
const response = await util.requestPromise({
method: 'GET',
url: `${this.BASE_URL}/SCP/Home?site_key=${this.site_key}`,
headers: this.headers
});
await this._findSiteKey(response.body);
if (!response.body || response.body.includes('SCP | Login')) {
logger.sc("获取服务器列表失败:未获取到有效响应或需要重新登录");
return false;
}
const linksMatch = response.body.match(/links\['[^']+'\]\s*=\s*"VServersKVM\?selectedVServerId=\d+"/g);
if (!linksMatch) {
logger.sc("页面中未找到服务器信息");
return false;
}
this.serverMap.clear();
let foundServers = false;
for (const link of linksMatch) {
const [, name] = link.match(/links\['([^']+)'\]/);
const [, serverId] = link.match(/selectedVServerId=(\d+)/);
if (serverId) {
foundServers = true;
logger.sc(`SCP找到服务器: ${name} (ID: ${serverId})`);
await util.sleep(1000);
const serverInfo = await this._getServerInfo(serverId);
if (serverInfo) {
this.serverMap.set(name, {
...serverInfo,
name
});
}
}
}
if (!foundServers) {
logger.sc("未找到任何服务器信息");
return false;
}
return this.serverMap.size > 0;
} catch (error) {
logger.sc("获取服务器列表时出错:", error.message);
return false;
}
}
async _getServerInfo(serverId) {
try {
const response = await util.requestPromise({
method: 'GET',
url: `${this.BASE_URL}/SCP/VServersKVM`,
qs: {
selectedVServerId: serverId,
page: "vServerKVMGeneral",
site_key: this.site_key
},
headers: this.headers
});
if (!response.body) {
logger.sc(`服务器 ${serverId} 响应为空`);
return null;
}
await this._findSiteKey(response.body).catch(e =>
logger.sc(`更新 site_key 失败: ${e.message}`));
const info = {
id: serverId,
ip: response.body.match(/<th>IPv4<\/th>[\s\S]*?<td>\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*<br\/>/)?.[1]?.trim(),
hostname: response.body.match(/id="hostname"[^>]*value="([^"]+)"/)?.[1]?.trim(),
status: response.body.match(/>state<\/td>\s*<td>([^<]+)</)?.[1]?.trim(),
uptime: response.body.match(/>Uptime<\/td>\s*<td>([^<]+)</)?.[1]?.trim(),
traffic: response.body.match(/>Traffic[^>]*>[^>]*>([^<]+)</)?.[1]?.trim(),
cpu: response.body.match(/>CPU<\/td>\s*<td>(\d+)</)?.[1]?.trim(),
ram: response.body.match(/>RAM<\/td>\s*<td>([^<]+)</)?.[1]?.trim(),
disk: response.body.match(/>Disk 1<\/td>\s*<td>([^<]+)</)?.[1]?.trim()
};
if (!info.ip) {
logger.sc(`服务器 ${serverId} 缺少关键信息:`, info);
logger.sc(`HTML内容:`, response.body);
return null;
}
logger.sc(`服务器 ${serverId} IP地址: ${info.ip}`);
return info;
} catch (error) {
logger.sc(`获取服务器 ${serverId} 信息失败:`, error.message);
return null;
}
}
async login() {
try {
const loginPage = await util.requestPromise({
method: 'GET',
url: `${this.BASE_URL}/SCP/Login`,
headers: this.headers
});
if (!loginPage.body || !loginPage.headers['set-cookie']) {
logger.sc("登录页面获取失败");
return false;
}
const cookies = loginPage.headers['set-cookie']
.map(cookie => cookie.split(';')[0])
.filter(cookie => cookie.includes('JSESSIONID=') || cookie.includes('cookiesession1='))
.join('; ');
if (!cookies) {
logger.sc("缺少必要cookie");
return false;
}
this.headers.Cookie = cookies;
await this._findSiteKey(loginPage.body);
logger.sc('初始site_key:', this.site_key);
const loginResponse = await util.requestPromise({
method: 'POST',
url: `${this.BASE_URL}/SCP/Login`,
headers: this.headers,
form: {
site_key: this.site_key,
username: this.config.username,
password: this.config.password
}
});
if (loginResponse.statusCode === 302) {
await this._refreshSession();
logger.sc('SCP登录成功');
return true;
}
logger.sc(`登录失败,状态码: ${loginResponse.statusCode}`);
logger.sc("登录响应内容:", loginResponse.body);
return false;
} catch (error) {
logger.sc("登录过程中出错:", error.message);
if (error.response) {
logger.sc("服务器响应:", {
statusCode: error.response.statusCode,
headers: error.response.headers,
body: error.response.body
});
}
return false;
}
}
async rebootServer(serverId) {
try {
const siteKeyValid = await this._refreshSession() || await this.login();
if (!siteKeyValid) return false;
let success = await this._executeReboot(serverId);
if (!success) {
await this._refreshSession();
success = await this._executeReboot(serverId);
}
return success;
} catch (error) {
logger.sc(`重启服务器 ${serverId} 时出错:`, error);
return false;
}
}
async _executeReboot(server_id) {
const response = await util.requestPromise({
url: `${this.BASE_URL}/SCP/VServersKVM`,
method: 'POST',
headers: this.headers,
form: {
selectedVServerId: server_id,
page: "vServerKVMControl",
action: this.config.rebootType,
site_key: this.site_key
}
});
await this._findSiteKey(response.body);
const successText = this.config.rebootType === "RESET"
? 'Changes successfully executed'
: 'vServerJob_KVMControl.poweroff_text';
return response.body.includes(successText);
}
findServerByIp(ip) {
const [name, info] = [...this.serverMap].find(([, info]) => info.ip === ip) || [];
return info ? { name, ...info } : null;
}
}
async function getServersToReboot(scpClient) {
if (!QB_CLIENTS) {
logger.sc("下载器列表未初始化");
return false;
}
SERVERS_TO_REBOOT = await Promise.all(CONFIG.clients
.map(async clientId => {
const client = QB_CLIENTS.find(c => c.id === clientId);
if (!client?.clientUrl) {
logger.sc(`下载器 ${clientId} 无效或缺少URL信息`);
return null;
}
const ip = client.clientUrl.match(/http:\/\/([^:]+):/)?.[1];
if (!ip) {
logger.sc(`下载器 ${clientId} 无法解析IP地址: ${client.clientUrl}`);
return null;
}
logger.sc(`下载器 ${clientId}(${client.alias}) URL: ${client.clientUrl}`);
const serverInfo = scpClient.findServerByIp(ip);
if (serverInfo) {
logger.sc(`下载器 ${clientId}(${client.alias}) 找到对应服务器: ${serverInfo.name}(${serverInfo.id})`);
return { clientId, ...serverInfo };
} else {
logger.sc(`下载器 ${clientId}(${client.alias}) 未找到对应服务器`);
return null;
}
}))
.then(results => results.filter(Boolean));
return SERVERS_TO_REBOOT.length > 0;
}
async function controlAllTorrents(action) {
const method = action === 'pause' ? 'pauseTorrent' : 'resumeTorrent';
const actionText = action === 'pause' ? '暂停' : '启动';
await Promise.all(CONFIG.clients.map(async (clientId) => {
const client = global.runningClient[clientId];
if (!client?.status) {
logger.sc(`${clientId}无法连接,请检查下载器clientId是否正确,或者下载器太快跑飞了`);
return;
}
await client[method]("all");
logger.sc(`${clientId}已${actionText}所有种子`);
}));
}
async function main() {
let scpClient;
try {
QB_CLIENTS = util.listClient();
logger.sc(`已获取 ${QB_CLIENTS.length} 个下载器信息`);
scpClient = await ScpClient.create(CONFIG.scp);
if (!await getServersToReboot(scpClient)) {
logger.sc("没有找到需要重启的服务器");
return;
}
try {
await controlAllTorrents('pause');
NEED_RESUME = true;
logger.sc(`等待 ${CONFIG.delays.qbitPause} 秒后重启服务器...`);
await util.sleep(CONFIG.delays.qbitPause * 1000);
for (const server of SERVERS_TO_REBOOT) {
const success = await scpClient.rebootServer(server.id);
logger.sc(`${server.name}(${server.ip}) 重启${success ? '成功' : '失败'}`);
await util.sleep(CONFIG.delays.serverReboot * 1000);
}
logger.sc(`等待 ${CONFIG.delays.powerOn} 秒后重新启动下载器任务...`);
await util.sleep(CONFIG.delays.powerOn * 1000);
await controlAllTorrents('resume');
NEED_RESUME = false;
} catch (error) {
if (NEED_RESUME) {
logger.sc("执行出错,尝试恢复下载器任务");
await controlAllTorrents('resume');
}
throw error;
}
} catch (error) {
logger.sc("执行过程中出错:", error);
}
}
await main();
};