前在github上部署的定时脚本建议撤掉,有封号风险。
在佬友 @Coker 的提点下,在huggingface成功加入telegram通知账号登录状态
废话不多说,先上效果图
hugging的logs:
访问huggingface网页:
telegram中:
开始前准备:
必须
1.huggingface账号
2.serv00账号
https://www.serv00.com
非必须
这三个是推送登录的消息所必需的,没有也能用脚本
1.cloudflare账号
2.绑定在cloudflare的域名
3.telegram账号
huggingface脚本
创建空间时,名字建议取怪一点。
Dockerfile文件
FROM node:20-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
wget \
gnupg \
ca-certificates \
libgconf-2-4 \
libxss1 \
libappindicator1 \
libasound2 \
libatk1.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgcc1 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
lsb-release \
xdg-utils \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update \
&& apt-get install -y google-chrome-stable \
&& rm -rf /var/lib/apt/lists/*
# 创建 logs 目录并更改其权限
RUN mkdir -p /app/logs && chown -R node:node /app
# 切换到非 root 用户
USER node
COPY --chown=node:node package*.json ./
RUN npm install
COPY --chown=node:node . .
EXPOSE 8080
CMD ["node", "server.js"]
login.js文件
const puppeteer = require('puppeteer');
const axios = require('axios');
// Telegram 配置
let telegramConfig;
try {
telegramConfig = JSON.parse(process.env.TELEGRAM_JSON || '{}');
} catch (error) {
console.error('Error parsing TELEGRAM_JSON:', error);
}
function formatToISO(date) {
return date.toISOString().replace('T', ' ').replace('Z', '').replace(/\.\d{3}Z/, '');
}
async function delayTime(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sendTelegramMessage(message) {
if (!telegramConfig || !telegramConfig.cloudflareWorkerUrl || !telegramConfig.telegramBotToken || !telegramConfig.telegramBotUserId || !telegramConfig.customAuthKey) {
console.log('Telegram configuration not set or incomplete, skipping notification');
return;
}
console.log('Attempting to send Telegram message...');
const url = `${telegramConfig.cloudflareWorkerUrl}/${telegramConfig.telegramBotToken}/sendMessage`;
try {
const response = await axios.post(url, {
chat_id: telegramConfig.telegramBotUserId,
text: message
}, {
headers: {
'X-Custom-Auth': telegramConfig.customAuthKey
}
});
console.log('Telegram notification sent successfully');
} catch (error) {
console.error('Error sending Telegram notification:', error.response ? error.response.data : error.message);
}
}
async function loginAccount(account, browser) {
const { username, password, panelnum, type } = account;
const page = await browser.newPage();
let url = type === 'ct8'
? 'https://panel.ct8.pl/login/?next=/'
: `https://panel${panelnum}.serv00.com/login/?next=/`;
try {
// 设置自定义 headers 来绕过 Cloudflare
await page.setExtraHTTPHeaders({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Cache-Control': 'max-age=0',
});
await page.goto(url, { waitUntil: 'networkidle0' });
const usernameInput = await page.$('#id_username');
if (usernameInput) {
await usernameInput.click({ clickCount: 3 });
await usernameInput.press('Backspace');
}
await page.type('#id_username', username);
await page.type('#id_password', password);
const loginButton = await page.$('#submit');
if (loginButton) {
await loginButton.click();
} else {
throw new Error('无法找到登录按钮');
}
await page.waitForNavigation({ timeout: 30000 });
const isLoggedIn = await page.evaluate(() => {
const logoutButton = document.querySelector('a[href="/logout/"]');
return logoutButton !== null;
});
const nowUtc = formatToISO(new Date());
const nowBeijing = formatToISO(new Date(new Date().getTime() + 8 * 60 * 60 * 1000));
if (isLoggedIn) {
const message = `账号 ${username} (${type}) 于北京时间 ${nowBeijing}(UTC时间 ${nowUtc})登录成功!`;
console.log(message);
await sendTelegramMessage(`${type}, [${nowBeijing.split(' ')[1].split('.')[0]}]\n${message}`);
return { success: true, message };
} else {
const message = `账号 ${username} (${type}) 登录失败,请检查账号和密码是否正确。`;
console.error(message);
await sendTelegramMessage(`${type}, [${nowBeijing.split(' ')[1].split('.')[0]}]\n${message}`);
return { success: false, message };
}
} catch (error) {
const message = `账号 ${username} (${type}) 登录时出现错误: ${error}`;
console.error(message);
await sendTelegramMessage(`${type}, [${formatToISO(new Date()).split(' ')[1].split('.')[0]}]\n${message}`);
return { success: false, message };
} finally {
await page.close();
}
}
(async () => {
const accountsJson = process.env.ACCOUNTS_JSON;
const accounts = JSON.parse(accountsJson);
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
executablePath: '/usr/bin/google-chrome-stable'
});
const results = [];
for (const account of accounts) {
const result = await loginAccount(account, browser);
results.push({ ...account, ...result });
const delay = Math.floor(Math.random() * 8000) + 1000;
await delayTime(delay);
}
await browser.close();
const successfulLogins = results.filter(r => r.success);
const failedLogins = results.filter(r => !r.success);
// 生成表格形式的输出
console.log('\n登录结果汇总:');
console.log('| 账号 | 类型 | 状态 | 消息 |');
console.log('|------|------|------|------|');
results.forEach(({ username, type, success, message }) => {
console.log(`| ${username} | ${type} | ${success ? '成功' : '失败'} | ${message} |`);
});
let summaryMessage = '\n登录结果统计:\n';
summaryMessage += `成功登录的账号:${successfulLogins.length}\n`;
summaryMessage += `登录失败的账号:${failedLogins.length}\n`;
if (failedLogins.length > 0) {
summaryMessage += '\n登录失败的账号列表:\n';
failedLogins.forEach(({ username, type }) => {
summaryMessage += `- ${username} (${type})\n`;
});
}
console.log(summaryMessage);
await sendTelegramMessage(`${accounts[0].type}, [${formatToISO(new Date()).split(' ')[1].split('.')[0]}]\n${summaryMessage}`);
})();
package.json文件
{
"name": "account-keep-alive",
"version": "1.0.0",
"description": "Auto-login account keep-alive script",
"main": "server.js",
"dependencies": {
"puppeteer": "^10.0.0",
"express": "^4.17.1",
"axios": "^0.21.1"
},
"scripts": {
"start": "node server.js"
}
}
server.js文件
给出两个版本,第一个版本访问huggingface网页不会显示你的账号登录信息,适用于不想别人知道serv00/ct8账号名的佬友,第二种适用于想通过访问huggingface网站就能直接获取账号登录信息的佬友
无网页账号信息:
const express = require('express');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const app = express();
const port = process.env.PORT || 8080;
// 使用 HOME 环境变量来创建日志目录
const logDir = path.join(process.env.HOME, 'logs');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
function log(message) {
const timestamp = new Date().toISOString();
const logMessage = `${timestamp}: ${message}\n`;
console.log(logMessage);
fs.appendFileSync(path.join(logDir, 'app.log'), logMessage);
}
app.get('/', (req, res) => {
res.send(`
<h1>登录脚本状态</h1>
<p>上次执行时间:${lastRunTime || '尚未执行'}</p>
<p>下次执行时间:${nextRunTime || '未设置'}</p>
<a href="/run">立即执行脚本</a>
<br><br>
<a href="/logs">查看日志</a>
`);
});
app.get('/logs', (req, res) => {
const logPath = path.join(logDir, 'app.log');
fs.readFile(logPath, 'utf8', (err, data) => {
if (err) {
res.status(500).send('无法读取日志文件');
return;
}
res.send(`<pre>${data}</pre>`);
});
});
let lastRunTime = null;
let nextRunTime = null;
function runLoginScript() {
log('开始执行登录脚本');
exec('node login.js', (error, stdout, stderr) => {
if (error) {
log(`执行错误: ${error.message}`);
return;
}
if (stderr) {
log(`脚本错误输出: ${stderr}`);
return;
}
log(`脚本输出:\n${stdout}`);
});
lastRunTime = new Date().toISOString();
nextRunTime = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
}
app.get('/run', (req, res) => {
runLoginScript();
res.send('脚本执行已启动,请查看日志获取详细信息。');
});
// 每7天自动运行一次脚本
setInterval(runLoginScript, 7 * 24 * 60 * 60 * 1000);
app.listen(port, () => {
log(`服务器运行在 http://localhost:${port}`);
runLoginScript(); // 启动时立即运行一次
});
有网页账号信息
const express = require('express');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const app = express();
const port = process.env.PORT || 8080;
// 使用 HOME 环境变量来创建日志目录
const logDir = path.join(process.env.HOME, 'logs');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
let lastRunTime = null;
let nextRunTime = null;
let lastRunResults = null;
function log(message) {
const timestamp = new Date().toISOString();
const logMessage = `${timestamp}: ${message}\n`;
console.log(logMessage);
fs.appendFileSync(path.join(logDir, 'app.log'), logMessage);
}
app.get('/', (req, res) => {
let tableHtml = '';
let summaryHtml = '';
if (lastRunResults) {
tableHtml = '<h2>上次登录结果:</h2>';
tableHtml += '<table border="1"><tr><th>账号</th><th>类型</th><th>状态</th><th>消息</th></tr>';
lastRunResults.forEach(result => {
tableHtml += `<tr><td>${result.username}</td><td>${result.type}</td><td>${result.success ? '成功' : '失败'}</td><td>${result.message}</td></tr>`;
});
tableHtml += '</table>';
const successfulLogins = lastRunResults.filter(r => r.success);
const failedLogins = lastRunResults.filter(r => !r.success);
summaryHtml = '<h2>登录结果统计:</h2>';
summaryHtml += `<p>成功登录的账号:${successfulLogins.length}</p>`;
summaryHtml += `<p>登录失败的账号:${failedLogins.length}</p>`;
if (failedLogins.length > 0) {
summaryHtml += '<h3>登录失败的账号列表:</h3><ul>';
failedLogins.forEach(({ username, type }) => {
summaryHtml += `<li>${username} (${type})</li>`;
});
summaryHtml += '</ul>';
}
}
res.send(`
<h1>登录脚本状态</h1>
<p>上次执行时间:${lastRunTime || '尚未执行'}</p>
<p>下次执行时间:${nextRunTime || '未设置'}</p>
<a href="/run">立即执行脚本</a>
<br><br>
<a href="/logs">查看日志</a>
${tableHtml}
${summaryHtml}
`);
});
app.get('/logs', (req, res) => {
const logPath = path.join(logDir, 'app.log');
fs.readFile(logPath, 'utf8', (err, data) => {
if (err) {
res.status(500).send('无法读取日志文件');
return;
}
res.send(`<pre>${data}</pre>`);
});
});
function runLoginScript() {
log('开始执行登录脚本');
exec('node login.js', (error, stdout, stderr) => {
if (error) {
log(`执行错误: ${error.message}`);
return;
}
if (stderr) {
log(`脚本错误输出: ${stderr}`);
return;
}
log(`脚本输出:\n${stdout}`);
// 解析输出以获取结果
const lines = stdout.split('\n');
const resultStartIndex = lines.findIndex(line => line.includes('| 账号 | 类型 | 状态 | 消息 |'));
if (resultStartIndex !== -1) {
lastRunResults = lines.slice(resultStartIndex + 2)
.filter(line => line.trim().startsWith('|'))
.map(line => {
const [, username, type, status, message] = line.split('|').map(item => item.trim());
return { username, type, success: status === '成功', message };
});
}
});
lastRunTime = new Date().toISOString();
nextRunTime = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
}
app.get('/run', (req, res) => {
runLoginScript();
res.send('脚本执行已启动,请稍后刷新页面查看结果。');
});
// 每7天自动运行一次脚本
setInterval(runLoginScript, 7 * 24 * 60 * 60 * 1000);
app.listen(port, () => {
log(`服务器运行在 http://localhost:${port}`);
runLoginScript(); // 启动时立即运行一次
});
在README.md
文件末尾---
前加入app_port: 8080
cloudflare的worker脚本
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
// 确保请求方法是 POST
if (request.method !== 'POST') {
return new Response('Only POST requests are allowed', { status: 405 })
}
// 从请求 URL 中提取 bot token 和方法
const url = new URL(request.url)
const pathParts = url.pathname.split('/')
const botToken = pathParts[1]
const method = pathParts[2]
// 验证自定义认证头
const authHeader = request.headers.get('X-Custom-Auth')
if (authHeader !== 'your-secret-key') {
return new Response('Unauthorized', { status: 401 })
}
// 构建 Telegram API URL
const telegramUrl = `https://api.telegram.org/bot${botToken}/${method}`
// 转发请求到 Telegram API
const telegramResponse = await fetch(telegramUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: request.body
})
// 返回 Telegram 的响应
return new Response(telegramResponse.body, {
status: telegramResponse.status,
headers: telegramResponse.headers
})
}
将your-secret-key
头部改为你自己想设置的,避免别人滥用你的反代
worker需要使用自定义域名,只有这样huggingface才能请求
huggingface后台变量:
ACCOUNTS_JSON
[
{ "username": "serv00user1", "password": "serv00password1", "panelnum": "0", "type": "serv00" },
{ "username": "serv00user2", "password": "serv00password2", "panelnum": "4", "type": "serv00" },
{ "username": "serv00user3", "password": "serv00password3", "panelnum": "7", "type": "serv00" },
{ "username": "ct8user1", "password": "ct8password1", "type": "ct8" },
{ "username": "ct8user2", "password": "ct8password2", "type": "ct8" }
]
TELEGRAM_JSON
{
"cloudflareWorkerUrl": "https://你CF worker域名",
"telegramBotToken": "YOUR_BOT_TOKEN",
"telegramBotUserId": "YOUR_USER_ID",
"customAuthKey": "你设置的头部"
}
变量部分不太懂的参考这里:
脚本每七天自动登录一次
最后,由于huggingface不定期访问会休眠,所以需要加个定时任务,下面的随便选一个进行保活即可。