Upload 4 files
Browse files- Dockerfile +63 -0
- login.js +161 -0
- package.json +14 -0
- server.js +116 -0
Dockerfile
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:20-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
wget \
|
| 7 |
+
gnupg \
|
| 8 |
+
ca-certificates \
|
| 9 |
+
libgconf-2-4 \
|
| 10 |
+
libxss1 \
|
| 11 |
+
libappindicator1 \
|
| 12 |
+
libasound2 \
|
| 13 |
+
libatk1.0-0 \
|
| 14 |
+
libc6 \
|
| 15 |
+
libcairo2 \
|
| 16 |
+
libcups2 \
|
| 17 |
+
libdbus-1-3 \
|
| 18 |
+
libexpat1 \
|
| 19 |
+
libfontconfig1 \
|
| 20 |
+
libgcc1 \
|
| 21 |
+
libgdk-pixbuf2.0-0 \
|
| 22 |
+
libglib2.0-0 \
|
| 23 |
+
libgtk-3-0 \
|
| 24 |
+
libnspr4 \
|
| 25 |
+
libpango-1.0-0 \
|
| 26 |
+
libpangocairo-1.0-0 \
|
| 27 |
+
libstdc++6 \
|
| 28 |
+
libx11-6 \
|
| 29 |
+
libx11-xcb1 \
|
| 30 |
+
libxcb1 \
|
| 31 |
+
libxcomposite1 \
|
| 32 |
+
libxcursor1 \
|
| 33 |
+
libxdamage1 \
|
| 34 |
+
libxext6 \
|
| 35 |
+
libxfixes3 \
|
| 36 |
+
libxi6 \
|
| 37 |
+
libxrandr2 \
|
| 38 |
+
libxrender1 \
|
| 39 |
+
libxss1 \
|
| 40 |
+
libxtst6 \
|
| 41 |
+
lsb-release \
|
| 42 |
+
xdg-utils \
|
| 43 |
+
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
|
| 44 |
+
&& 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 \
|
| 45 |
+
&& apt-get update \
|
| 46 |
+
&& apt-get install -y google-chrome-stable \
|
| 47 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 48 |
+
|
| 49 |
+
# 创建 logs 目录并更改其权限
|
| 50 |
+
RUN mkdir -p /app/logs && chown -R node:node /app
|
| 51 |
+
|
| 52 |
+
# 切换到非 root 用户
|
| 53 |
+
USER node
|
| 54 |
+
|
| 55 |
+
COPY --chown=node:node package*.json ./
|
| 56 |
+
|
| 57 |
+
RUN npm install
|
| 58 |
+
|
| 59 |
+
COPY --chown=node:node . .
|
| 60 |
+
|
| 61 |
+
EXPOSE 8080
|
| 62 |
+
|
| 63 |
+
CMD ["node", "server.js"]
|
login.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const puppeteer = require('puppeteer');
|
| 2 |
+
const axios = require('axios');
|
| 3 |
+
|
| 4 |
+
// Telegram 配置
|
| 5 |
+
let telegramConfig;
|
| 6 |
+
try {
|
| 7 |
+
telegramConfig = JSON.parse(process.env.TELEGRAM_JSON || '{}');
|
| 8 |
+
} catch (error) {
|
| 9 |
+
console.error('Error parsing TELEGRAM_JSON:', error);
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
function formatToISO(date) {
|
| 13 |
+
return date.toISOString().replace('T', ' ').replace('Z', '').replace(/\.\d{3}Z/, '');
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
async function delayTime(ms) {
|
| 17 |
+
return new Promise(resolve => setTimeout(resolve, ms));
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
async function sendTelegramMessage(message) {
|
| 21 |
+
if (!telegramConfig || !telegramConfig.cloudflareWorkerUrl || !telegramConfig.telegramBotToken || !telegramConfig.telegramBotUserId || !telegramConfig.customAuthKey) {
|
| 22 |
+
console.log('Telegram configuration not set or incomplete, skipping notification');
|
| 23 |
+
return;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
console.log('Attempting to send Telegram message...');
|
| 27 |
+
const url = `${telegramConfig.cloudflareWorkerUrl}/${telegramConfig.telegramBotToken}/sendMessage`;
|
| 28 |
+
try {
|
| 29 |
+
const response = await axios.post(url, {
|
| 30 |
+
chat_id: telegramConfig.telegramBotUserId,
|
| 31 |
+
text: message
|
| 32 |
+
}, {
|
| 33 |
+
headers: {
|
| 34 |
+
'X-Custom-Auth': telegramConfig.customAuthKey
|
| 35 |
+
}
|
| 36 |
+
});
|
| 37 |
+
console.log('Telegram notification sent successfully');
|
| 38 |
+
} catch (error) {
|
| 39 |
+
console.error('Error sending Telegram notification:', error.response ? error.response.data : error.message);
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
async function loginAccount(account, browser) {
|
| 44 |
+
const { username, password, panelnum, type } = account;
|
| 45 |
+
const page = await browser.newPage();
|
| 46 |
+
|
| 47 |
+
let url = type === 'ct8'
|
| 48 |
+
? 'https://panel.ct8.pl/login/?next=/'
|
| 49 |
+
: `https://panel${panelnum}.serv00.com/login/?next=/`;
|
| 50 |
+
|
| 51 |
+
try {
|
| 52 |
+
// 设置自定义 headers 来绕过 Cloudflare
|
| 53 |
+
await page.setExtraHTTPHeaders({
|
| 54 |
+
'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',
|
| 55 |
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
| 56 |
+
'Accept-Language': 'en-US,en;q=0.5',
|
| 57 |
+
'Accept-Encoding': 'gzip, deflate, br',
|
| 58 |
+
'Connection': 'keep-alive',
|
| 59 |
+
'Upgrade-Insecure-Requests': '1',
|
| 60 |
+
'Sec-Fetch-Dest': 'document',
|
| 61 |
+
'Sec-Fetch-Mode': 'navigate',
|
| 62 |
+
'Sec-Fetch-Site': 'none',
|
| 63 |
+
'Sec-Fetch-User': '?1',
|
| 64 |
+
'Cache-Control': 'max-age=0',
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
+
await page.goto(url, { waitUntil: 'networkidle0' });
|
| 68 |
+
|
| 69 |
+
const usernameInput = await page.$('#id_username');
|
| 70 |
+
if (usernameInput) {
|
| 71 |
+
await usernameInput.click({ clickCount: 3 });
|
| 72 |
+
await usernameInput.press('Backspace');
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
await page.type('#id_username', username);
|
| 76 |
+
await page.type('#id_password', password);
|
| 77 |
+
|
| 78 |
+
const loginButton = await page.$('#submit');
|
| 79 |
+
if (loginButton) {
|
| 80 |
+
await loginButton.click();
|
| 81 |
+
} else {
|
| 82 |
+
throw new Error('无法找到登录按钮');
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
await page.waitForNavigation({ timeout: 30000 });
|
| 86 |
+
|
| 87 |
+
const isLoggedIn = await page.evaluate(() => {
|
| 88 |
+
const logoutButton = document.querySelector('a[href="/logout/"]');
|
| 89 |
+
return logoutButton !== null;
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
const nowUtc = formatToISO(new Date());
|
| 93 |
+
const nowBeijing = formatToISO(new Date(new Date().getTime() + 8 * 60 * 60 * 1000));
|
| 94 |
+
|
| 95 |
+
if (isLoggedIn) {
|
| 96 |
+
const message = `账号 ${username} (${type}) 于北京时间 ${nowBeijing}(UTC时间 ${nowUtc})登录成功!`;
|
| 97 |
+
console.log(message);
|
| 98 |
+
await sendTelegramMessage(`${type}, [${nowBeijing.split(' ')[1].split('.')[0]}]\n${message}`);
|
| 99 |
+
return { success: true, message };
|
| 100 |
+
} else {
|
| 101 |
+
const message = `账号 ${username} (${type}) 登录失败,请检查账号和密码是否正确。`;
|
| 102 |
+
console.error(message);
|
| 103 |
+
await sendTelegramMessage(`${type}, [${nowBeijing.split(' ')[1].split('.')[0]}]\n${message}`);
|
| 104 |
+
return { success: false, message };
|
| 105 |
+
}
|
| 106 |
+
} catch (error) {
|
| 107 |
+
const message = `账号 ${username} (${type}) 登录时出现错误: ${error}`;
|
| 108 |
+
console.error(message);
|
| 109 |
+
await sendTelegramMessage(`${type}, [${formatToISO(new Date()).split(' ')[1].split('.')[0]}]\n${message}`);
|
| 110 |
+
return { success: false, message };
|
| 111 |
+
} finally {
|
| 112 |
+
await page.close();
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
(async () => {
|
| 117 |
+
const accountsJson = process.env.ACCOUNTS_JSON;
|
| 118 |
+
const accounts = JSON.parse(accountsJson);
|
| 119 |
+
|
| 120 |
+
const browser = await puppeteer.launch({
|
| 121 |
+
headless: true,
|
| 122 |
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
| 123 |
+
executablePath: '/usr/bin/google-chrome-stable'
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
const results = [];
|
| 127 |
+
for (const account of accounts) {
|
| 128 |
+
const result = await loginAccount(account, browser);
|
| 129 |
+
results.push({ ...account, ...result });
|
| 130 |
+
|
| 131 |
+
const delay = Math.floor(Math.random() * 8000) + 1000;
|
| 132 |
+
await delayTime(delay);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
await browser.close();
|
| 136 |
+
|
| 137 |
+
const successfulLogins = results.filter(r => r.success);
|
| 138 |
+
const failedLogins = results.filter(r => !r.success);
|
| 139 |
+
|
| 140 |
+
// 生成表格形式的输出
|
| 141 |
+
console.log('\n登录结果汇总:');
|
| 142 |
+
console.log('| 账号 | 类型 | 状态 | 消息 |');
|
| 143 |
+
console.log('|------|------|------|------|');
|
| 144 |
+
results.forEach(({ username, type, success, message }) => {
|
| 145 |
+
console.log(`| ${username} | ${type} | ${success ? '成功' : '失败'} | ${message} |`);
|
| 146 |
+
});
|
| 147 |
+
|
| 148 |
+
let summaryMessage = '\n登录结果统计:\n';
|
| 149 |
+
summaryMessage += `成功登录的账号:${successfulLogins.length}\n`;
|
| 150 |
+
summaryMessage += `登录失败的账号:${failedLogins.length}\n`;
|
| 151 |
+
|
| 152 |
+
if (failedLogins.length > 0) {
|
| 153 |
+
summaryMessage += '\n登录失败的账号列表:\n';
|
| 154 |
+
failedLogins.forEach(({ username, type }) => {
|
| 155 |
+
summaryMessage += `- ${username} (${type})\n`;
|
| 156 |
+
});
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
console.log(summaryMessage);
|
| 160 |
+
await sendTelegramMessage(`${accounts[0].type}, [${formatToISO(new Date()).split(' ')[1].split('.')[0]}]\n${summaryMessage}`);
|
| 161 |
+
})();
|
package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "account-keep-alive",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Auto-login account keep-alive script",
|
| 5 |
+
"main": "server.js",
|
| 6 |
+
"dependencies": {
|
| 7 |
+
"puppeteer": "^10.0.0",
|
| 8 |
+
"express": "^4.17.1",
|
| 9 |
+
"axios": "^0.21.1"
|
| 10 |
+
},
|
| 11 |
+
"scripts": {
|
| 12 |
+
"start": "node server.js"
|
| 13 |
+
}
|
| 14 |
+
}
|
server.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const { exec } = require('child_process');
|
| 3 |
+
const fs = require('fs');
|
| 4 |
+
const path = require('path');
|
| 5 |
+
const app = express();
|
| 6 |
+
const port = process.env.PORT || 8080;
|
| 7 |
+
|
| 8 |
+
// 使用 HOME 环境变量来创建日志目录
|
| 9 |
+
const logDir = path.join(process.env.HOME, 'logs');
|
| 10 |
+
if (!fs.existsSync(logDir)) {
|
| 11 |
+
fs.mkdirSync(logDir, { recursive: true });
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
let lastRunTime = null;
|
| 15 |
+
let nextRunTime = null;
|
| 16 |
+
let lastRunResults = null;
|
| 17 |
+
|
| 18 |
+
function log(message) {
|
| 19 |
+
const timestamp = new Date().toISOString();
|
| 20 |
+
const logMessage = `${timestamp}: ${message}\n`;
|
| 21 |
+
console.log(logMessage);
|
| 22 |
+
fs.appendFileSync(path.join(logDir, 'app.log'), logMessage);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
app.get('/', (req, res) => {
|
| 26 |
+
let tableHtml = '';
|
| 27 |
+
let summaryHtml = '';
|
| 28 |
+
|
| 29 |
+
if (lastRunResults) {
|
| 30 |
+
tableHtml = '<h2>上次登录结果:</h2>';
|
| 31 |
+
tableHtml += '<table border="1"><tr><th>账号</th><th>类型</th><th>状态</th><th>消息</th></tr>';
|
| 32 |
+
lastRunResults.forEach(result => {
|
| 33 |
+
tableHtml += `<tr><td>${result.username}</td><td>${result.type}</td><td>${result.success ? '成功' : '失败'}</td><td>${result.message}</td></tr>`;
|
| 34 |
+
});
|
| 35 |
+
tableHtml += '</table>';
|
| 36 |
+
|
| 37 |
+
const successfulLogins = lastRunResults.filter(r => r.success);
|
| 38 |
+
const failedLogins = lastRunResults.filter(r => !r.success);
|
| 39 |
+
|
| 40 |
+
summaryHtml = '<h2>登录结果统计:</h2>';
|
| 41 |
+
summaryHtml += `<p>成功登录的账号:${successfulLogins.length}</p>`;
|
| 42 |
+
summaryHtml += `<p>登录失败的账号:${failedLogins.length}</p>`;
|
| 43 |
+
|
| 44 |
+
if (failedLogins.length > 0) {
|
| 45 |
+
summaryHtml += '<h3>登录失败的账号列表:</h3><ul>';
|
| 46 |
+
failedLogins.forEach(({ username, type }) => {
|
| 47 |
+
summaryHtml += `<li>${username} (${type})</li>`;
|
| 48 |
+
});
|
| 49 |
+
summaryHtml += '</ul>';
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
res.send(`
|
| 54 |
+
<h1>登录脚本状态</h1>
|
| 55 |
+
<p>上次执行时间:${lastRunTime || '尚未执行'}</p>
|
| 56 |
+
<p>下次执行时间:${nextRunTime || '未设置'}</p>
|
| 57 |
+
<a href="/run">立即执行脚本</a>
|
| 58 |
+
<br><br>
|
| 59 |
+
<a href="/logs">查看日志</a>
|
| 60 |
+
${tableHtml}
|
| 61 |
+
${summaryHtml}
|
| 62 |
+
`);
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
app.get('/logs', (req, res) => {
|
| 66 |
+
const logPath = path.join(logDir, 'app.log');
|
| 67 |
+
fs.readFile(logPath, 'utf8', (err, data) => {
|
| 68 |
+
if (err) {
|
| 69 |
+
res.status(500).send('无法读取日志文件');
|
| 70 |
+
return;
|
| 71 |
+
}
|
| 72 |
+
res.send(`<pre>${data}</pre>`);
|
| 73 |
+
});
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
function runLoginScript() {
|
| 77 |
+
log('开始执行登录脚本');
|
| 78 |
+
exec('node login.js', (error, stdout, stderr) => {
|
| 79 |
+
if (error) {
|
| 80 |
+
log(`执行错误: ${error.message}`);
|
| 81 |
+
return;
|
| 82 |
+
}
|
| 83 |
+
if (stderr) {
|
| 84 |
+
log(`脚本错误输出: ${stderr}`);
|
| 85 |
+
return;
|
| 86 |
+
}
|
| 87 |
+
log(`脚本输出:\n${stdout}`);
|
| 88 |
+
|
| 89 |
+
// 解析输出以获取结果
|
| 90 |
+
const lines = stdout.split('\n');
|
| 91 |
+
const resultStartIndex = lines.findIndex(line => line.includes('| 账号 | 类型 | 状态 | 消息 |'));
|
| 92 |
+
if (resultStartIndex !== -1) {
|
| 93 |
+
lastRunResults = lines.slice(resultStartIndex + 2)
|
| 94 |
+
.filter(line => line.trim().startsWith('|'))
|
| 95 |
+
.map(line => {
|
| 96 |
+
const [, username, type, status, message] = line.split('|').map(item => item.trim());
|
| 97 |
+
return { username, type, success: status === '成功', message };
|
| 98 |
+
});
|
| 99 |
+
}
|
| 100 |
+
});
|
| 101 |
+
lastRunTime = new Date().toISOString();
|
| 102 |
+
nextRunTime = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
app.get('/run', (req, res) => {
|
| 106 |
+
runLoginScript();
|
| 107 |
+
res.send('脚本执行已启动,请稍后刷新页面查看结果。');
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
// 每7天自动运行一次脚本
|
| 111 |
+
setInterval(runLoginScript, 7 * 24 * 60 * 60 * 1000);
|
| 112 |
+
|
| 113 |
+
app.listen(port, () => {
|
| 114 |
+
log(`服务器运行在 http://localhost:${port}`);
|
| 115 |
+
runLoginScript(); // 启动时立即运行一次
|
| 116 |
+
});
|