├── README.md ├── workers ├── wetest-vip.js ├── sspanel.js ├── dockerhub.js └── glados.js ├── sspanel.md └── glados.md /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 CF worker 工具集合 2 | 3 | ## 📋 功能模块 4 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 19 | 23 | 24 | 25 | 30 | 35 | 36 |
8 |

🛡️ GLaDOS 签到

9 |
11 |

🌐 SSPanel 通用签到

12 |
16 |

自动完成 GLaDOS 的每日签到任务

17 |

💡 多账户 | 🔔 tg通知 | 📊 签到统计

18 |
20 |

自动完成 SSPanel 面板每日签到任务

21 |

💡 多账户 | 🔔 tg通知

22 |
26 | 27 | GLaDOS详情 28 | 29 | 31 | 32 | SSPanel详情 33 | 34 |
37 | 38 | --- 39 | 40 | ## 📄 许可证 41 | 42 | 本项目基于 [MIT License](LICENSE) 开源协议 43 | 44 |
45 | Built with ❤️ by Axin 46 |
47 | -------------------------------------------------------------------------------- /workers/wetest-vip.js: -------------------------------------------------------------------------------- 1 | addEventListener('fetch', event => { 2 | event.respondWith(handleRequest(event.request)) 3 | }) 4 | 5 | async function handleRequest(request) { 6 | const apiUrl = 'https://www.wetest.vip/api/cf2dns/get_cloudflare_ip?key=o1zrmHAF&type=v6' 7 | 8 | try { 9 | const response = await fetch(apiUrl) 10 | if (!response.ok) { 11 | throw new Error(`HTTP error! status: ${response.status}`) 12 | } 13 | 14 | const data = await response.json() 15 | 16 | if (!data.status || !data.info) { 17 | throw new Error('Invalid data format') 18 | } 19 | 20 | let result = [] 21 | 22 | // 处理所有移动IP 23 | if (data.info.CM) { 24 | result.push(...data.info.CM.map(ip => `[${ip.address}]#移动-IPV6`)) 25 | } 26 | 27 | // 处理所有联通IP 28 | if (data.info.CU) { 29 | result.push(...data.info.CU.map(ip => `[${ip.address}]#联通-IPV6`)) 30 | } 31 | 32 | // 处理所有电信IP 33 | if (data.info.CT) { 34 | result.push(...data.info.CT.map(ip => `[${ip.address}]#电信-IPV6`)) 35 | } 36 | 37 | // 将数组转换为换行分隔的字符串 38 | const formattedResult = result.join('\n') 39 | 40 | return new Response(formattedResult, { 41 | headers: { 42 | 'content-type': 'text/plain;charset=UTF-8', 43 | 'Access-Control-Allow-Origin': '*' 44 | } 45 | }) 46 | 47 | } catch (error) { 48 | return new Response(`Error: ${error.message}`, { 49 | status: 500, 50 | headers: { 51 | 'content-type': 'text/plain;charset=UTF-8', 52 | 'Access-Control-Allow-Origin': '*' 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sspanel.md: -------------------------------------------------------------------------------- 1 | # SSPanel 机场自动签到脚本 2 | 3 | 基于 Cloudflare Workers 的 SSPanel 机场多账号自动签到工具,支持 Telegram 通知。 4 | 5 | ## 📝 签到结果示例 6 | image 7 | 8 | ## 功能特性 9 | 10 | - 支持多个机场账号批量签到 11 | - 自动处理登录和 CSRF Token 12 | - 支持 Telegram Bot 消息通知 13 | - **定时任务自动签到**(也可手动触发) 14 | 15 | ## 部署 16 | 17 | 1. 在 [Cloudflare Workers](https://workers.cloudflare.com/) 创建新的 Worker 18 | 2. 复制[sspanel.js](https://raw.githubusercontent.com/axinhouzilaoyue/cloudflare/refs/heads/main/workers/sspanel.js)代码到编辑器: 19 | 3. 修改 `AIRPORTS` 配置 20 | 4. 设置环境变量(可选) 21 | 5. 部署并测试 22 | 23 | ## 配置 24 | 25 | ### 1. 机场账号配置 26 | 27 | 修改代码中的 `AIRPORTS` 数组: 28 | 29 | ```javascript 30 | const AIRPORTS = [ 31 | { 32 | name: "机场名称", 33 | url: "https://example.com", 34 | email: "your-email@example.com", 35 | password: "your-password" 36 | } 37 | ]; 38 | ``` 39 | 40 | ### 2. Telegram 通知配置(可选) 41 | 42 | 在 Cloudflare Workers 中设置环境变量: 43 | 44 | | 变量名 | 说明 | 获取方式 | 45 | |--------|------|----------| 46 | | `TGTOKEN` | Telegram Bot Token | 联系 @BotFather 创建 Bot | 47 | | `TGID` | Telegram Chat ID | 联系 @userinfobot 获取 | 48 | 49 | ## API 端点 50 | 51 | | 端点 | 方法 | 功能 | 52 | |------|------|------| 53 | | `/` | GET | 显示帮助信息 | 54 | | `/checkin` | POST | 执行签到(无通知) | 55 | | `/tg` | POST | 执行签到并发送 Telegram 通知 | 56 | | `/status` | GET | 查看配置状态 | 57 | 58 | ## ❤️自动签到-定时任务 59 | 60 | 在 Workers 控制台的"触发器"页面添加 Cron 触发器: 61 | 62 | ``` 63 | 0 9 * * * # 每天上午 9 点执行 64 | ``` 65 | 66 | ## 手动使用示例 67 | 68 | ### 带通知签到 69 | ```bash 70 | 网页访问 https://your-worker.workers.dev/tg 71 | ``` 72 | 73 | ### 查看状态 74 | ```bash 75 | 网页访问 https://your-worker.workers.dev/status 76 | ``` 77 | 78 | ## 响应格式 79 | 80 | ### 成功响应 81 | ```json 82 | { 83 | "success": true, 84 | "timestamp": "2024-06-10T01:00:00.000Z", 85 | "telegram_sent": true, 86 | "message": "签到完成,已发送 Telegram 通知", 87 | "results": [ 88 | { 89 | "airport": "示例机场", 90 | "email": "user@example.com", 91 | "success": true, 92 | "message": "签到成功", 93 | "time": "2024-06-10 09:00:00" 94 | } 95 | ] 96 | } 97 | ``` 98 | 99 | ### 配置错误响应 100 | ```json 101 | { 102 | "success": false, 103 | "error": "Telegram 配置未完成", 104 | "help": { 105 | "TGTOKEN": "从 @BotFather 获取的 Bot Token", 106 | "TGID": "你的 Telegram Chat ID" 107 | } 108 | } 109 | ``` 110 | 111 | ## 注意事项 112 | 113 | - 定时任务会自动发送 Telegram 通知(如已配置) 114 | - 单个机场签到失败不影响其他机场 115 | - 支持多种 SSPanel 版本的响应格式 116 | - 建议使用环境变量存储敏感信息 117 | -------------------------------------------------------------------------------- /glados.md: -------------------------------------------------------------------------------- 1 | # GLaDOS 签到 Cloudflare Worker 2 | 3 | 一个基于 Cloudflare Worker 的 GLaDOS 自动签到工具,支持多账号管理和 Telegram 通知功能。 4 | 5 | ## 📝 签到结果示例 6 | image 7 | 8 | ## 📊 签到历史统计 9 | image 10 | 11 | ## ✨ 功能特点 12 | - 🔄 自动执行 GLaDOS 每日签到 13 | - 👥 支持多账号管理 14 | - 📊 查询账号剩余天数 15 | - 📱 Telegram 消息通知 16 | - ⏱️ 支持定时任务自动执行 17 | - 🔒 安全存储账号信息 18 | 19 | ## 🚀 部署指南 20 | 21 | ### 1. 创建 Cloudflare Worker 22 | 23 | 1. 登录 [Cloudflare Dashboard](https://dash.cloudflare.com/) 24 | 2. 进入 `Workers & Pages` 页面 25 | 3. 点击 `Create application` 26 | 4. 选择 `Create Worker` 27 | 5. 将 [glados.js](https://raw.githubusercontent.com/axinhouzilaoyue/cloudflare/refs/heads/main/workers/glados.js) 的代码复制到编辑器中 28 | 7. 点击 `Save and Deploy` 29 | 30 | ### 2. 配置账号信息 31 | 32 | 有两种方式配置账号信息: 33 | 34 | #### 方式一:使用环境变量(推荐) 35 | 36 | 1. 在 Worker 详情页面,点击 `Settings` > `Variables` 37 | 2. 添加以下环境变量: 38 | - `GLADOS_ACCOUNTS`: 包含账号信息的 JSON 数组,格式如下(**变量类型要选文本**): 39 | ``` 40 | [ 41 | {"email": "your_email_1@example.com", "cookie": "your_cookie_1"}, 42 | {"email": "your_email_2@example.com", "cookie": "your_cookie_2"} 43 | ] 44 | ``` 45 | 46 | - `TGTOKEN`: Telegram Bot Token(可选) 47 | - `TGID`: Telegram Chat ID(可选) 48 | 49 | #### 方式二:直接修改代码 50 | 51 | 在代码中直接修改 `accounts` 数组: 52 | 53 | ``` 54 | let accounts = [ 55 | {email: "your_email@example.com", cookie: "your_cookie_here"} 56 | ]; 57 | ``` 58 | 59 | ### 3. 设置定时任务 60 | 61 | 1. 在 Worker 详情页面,点击 `Triggers` 62 | 2. 添加 Cron 触发器,例如 `0 0 * * *`(每天 UTC 时间 0:00 执行) 63 | 64 | ## 🔍 获取 Cookie 65 | 66 | 1. 登录 [GLaDOS 官网](https://glados.rocks/) 67 | 2. 打开浏览器开发者工具(F12) 68 | 3. 切换到 `Network` 标签页 69 | 4. 刷新页面,找到任意请求 70 | 5. 在请求头中找到 `Cookie` 字段并复制完整内容 71 | 72 | ## 📡 API 端点 73 | 74 | Worker 提供以下 API 端点: 75 | 76 | - `/checkin` - 执行签到并返回账号状态 77 | - `/status` - 仅查询账号状态 78 | - `/tg` - 执行签到并发送结果到 Telegram 79 | - `/checkinChart` - 查看积分历史图表 80 | 81 | ## 📱 Telegram 通知设置 82 | 83 | 1. 创建 Telegram Bot,获取 Bot Token 84 | - 与 [@BotFather](https://t.me/BotFather) 对话创建 85 | 2. 获取 Chat ID 86 | - 可使用 [@userinfobot](https://t.me/userinfobot) 获取 87 | 3. 将 Bot Token 和 Chat ID 添加到环境变量 88 | 89 | ## ⚠️ 注意事项 90 | 91 | - Cookie 包含敏感信息,请妥善保管 92 | - 建议使用环境变量存储账号信息,而非硬编码在代码中 93 | - 如遇签到失败,请检查 Cookie 是否过期 94 | 95 | ## 📄 许可证 96 | 97 | MIT License 98 | 99 | --- 100 | 101 | 希望这个工具能帮助您自动完成 GLaDOS 的签到任务!如有问题或建议,欢迎提交 Issue。 102 | -------------------------------------------------------------------------------- /workers/sspanel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 精简版 SSPanel 机场自动签到 (支持 Telegram 通知) 3 | */ 4 | 5 | // 配置机场信息 6 | const AIRPORTS = [ 7 | { 8 | name: "示例机场1", 9 | url: "https://example1.com", 10 | email: "your-email@example.com", 11 | password: "your-password" 12 | } 13 | ]; 14 | 15 | // Telegram 配置变量 16 | let botToken = ''; // 从环境变量 TGTOKEN 获取 17 | let chatId = ''; // 从环境变量 TGID 获取 18 | 19 | class AirportCheckin { 20 | constructor(config) { 21 | this.config = config; 22 | this.cookies = new Map(); 23 | } 24 | 25 | async request(url, options = {}) { 26 | const headers = { 27 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 28 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 29 | 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 30 | ...options.headers 31 | }; 32 | 33 | if (this.cookies.size > 0) { 34 | headers['Cookie'] = Array.from(this.cookies.entries()) 35 | .map(([key, value]) => `${key}=${value}`) 36 | .join('; '); 37 | } 38 | 39 | const response = await fetch(url, { ...options, headers }); 40 | 41 | // 保存Cookie 42 | const setCookieHeaders = response.headers.getSetCookie ? response.headers.getSetCookie() : 43 | response.headers.get('Set-Cookie') ? [response.headers.get('Set-Cookie')] : []; 44 | 45 | setCookieHeaders.forEach(cookie => { 46 | const parts = cookie.split(';')[0].split('='); 47 | if (parts.length >= 2) { 48 | this.cookies.set(parts[0].trim(), parts.slice(1).join('=').trim()); 49 | } 50 | }); 51 | 52 | return response; 53 | } 54 | 55 | async login() { 56 | try { 57 | const loginUrl = `${this.config.url}/auth/login`; 58 | 59 | // 获取登录页面 60 | const loginPageResponse = await this.request(loginUrl); 61 | const loginPageText = await loginPageResponse.text(); 62 | 63 | // 提取CSRF token 64 | let csrfToken = ''; 65 | const tokenMatch = loginPageText.match(/name=["\']_token["\']\s+value=["\']([^"\']+)["\']/) || 66 | loginPageText.match(/csrf["\']?\s*:\s*["\']([^"\']+)["\']/) || 67 | loginPageText.match(/token["\']?\s*:\s*["\']([^"\']+)["\']/); 68 | if (tokenMatch) { 69 | csrfToken = tokenMatch[1]; 70 | } 71 | 72 | // 构建登录数据 73 | const loginData = new URLSearchParams({ 74 | email: this.config.email, 75 | passwd: this.config.password, 76 | remember_me: 'on' 77 | }); 78 | if (csrfToken) loginData.append('_token', csrfToken); 79 | 80 | // 提交登录 81 | const response = await this.request(loginUrl, { 82 | method: 'POST', 83 | headers: { 84 | 'Content-Type': 'application/x-www-form-urlencoded', 85 | 'Referer': loginUrl, 86 | 'Origin': this.config.url 87 | }, 88 | body: loginData.toString() 89 | }); 90 | 91 | const text = await response.text(); 92 | 93 | // 检查JSON响应 94 | try { 95 | const json = JSON.parse(text); 96 | if (json.ret === 1 || json.code === 0 || json.success === true) { 97 | return { success: true, message: '登录成功' }; 98 | } else { 99 | return { success: false, message: json.msg || json.message || '登录失败' }; 100 | } 101 | } catch { 102 | // 检查HTML响应 103 | if (text.includes('用户中心') || text.includes('dashboard') || response.status >= 300) { 104 | return { success: true, message: '登录成功' }; 105 | } else if (text.includes('密码错误') || text.includes('登录失败')) { 106 | return { success: false, message: '账号密码错误' }; 107 | } else { 108 | return { success: text.length > 100, message: text.length > 100 ? '登录成功' : '登录失败' }; 109 | } 110 | } 111 | } catch (error) { 112 | return { success: false, message: `登录异常: ${error.message}` }; 113 | } 114 | } 115 | 116 | async checkin() { 117 | try { 118 | const response = await this.request(`${this.config.url}/user/checkin`, { 119 | method: 'POST', 120 | headers: { 121 | 'Content-Type': 'application/x-www-form-urlencoded', 122 | 'X-Requested-With': 'XMLHttpRequest' 123 | } 124 | }); 125 | 126 | const text = await response.text(); 127 | 128 | // 检查JSON响应 129 | try { 130 | const result = JSON.parse(text); 131 | return { 132 | success: result.ret === 1 || result.ret === 0 || result.code === 0, 133 | message: result.msg || result.message || '签到完成' 134 | }; 135 | } catch { 136 | // 检查HTML响应 137 | if (text.includes('签到成功') || text.includes('获得了')) { 138 | return { success: true, message: '签到成功' }; 139 | } else if (text.includes('已签到') || text.includes('已经签到')) { 140 | return { success: true, message: '今日已签到' }; 141 | } else { 142 | return { success: false, message: '签到失败' }; 143 | } 144 | } 145 | } catch (error) { 146 | return { success: false, message: `签到异常: ${error.message}` }; 147 | } 148 | } 149 | 150 | async run() { 151 | const result = { 152 | airport: this.config.name, 153 | email: this.config.email, 154 | success: false, 155 | message: '', 156 | time: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) 157 | }; 158 | 159 | const loginResult = await this.login(); 160 | if (!loginResult.success) { 161 | result.message = loginResult.message; 162 | return result; 163 | } 164 | 165 | const checkinResult = await this.checkin(); 166 | result.success = checkinResult.success; 167 | result.message = checkinResult.message; 168 | 169 | return result; 170 | } 171 | } 172 | 173 | // 初始化环境变量 174 | function initializeVariables(env) { 175 | botToken = env.TGTOKEN || ''; 176 | chatId = env.TGID || ''; 177 | } 178 | 179 | // 发送消息到 Telegram 180 | async function sendTelegramMessage(results) { 181 | if (!botToken || !chatId) { 182 | console.log('Telegram 配置未设置,跳过通知'); 183 | return { sent: false, error: 'Telegram 配置未设置' }; 184 | } 185 | 186 | try { 187 | const now = new Date(); 188 | const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); 189 | const formattedTime = beijingTime.toISOString().slice(0, 19).replace('T', ' '); 190 | 191 | let message = `✈️ SSPanel 机场签到报告\n`; 192 | message += `${formattedTime}\n\n`; 193 | 194 | // 添加签到结果 195 | let successCount = 0; 196 | let failureCount = 0; 197 | 198 | results.forEach(result => { 199 | const statusIcon = result.success ? '✅' : '❌'; 200 | const statusText = result.success ? '成功' : '失败'; 201 | 202 | message += `${result.airport}\n`; 203 | message += `${statusIcon} ${result.email}\n`; 204 | message += `📝 ${result.message}\n`; 205 | message += `⏰ ${result.time}\n\n`; 206 | 207 | if (result.success) { 208 | successCount++; 209 | } else { 210 | failureCount++; 211 | } 212 | }); 213 | 214 | // 添加统计信息 215 | message += `📊 签到统计\n`; 216 | message += `✅ 成功: ${successCount} 个账号\n`; 217 | message += `❌ 失败: ${failureCount} 个账号\n`; 218 | message += `🔢 总计: ${results.length} 个账号`; 219 | 220 | const url = `https://api.telegram.org/bot${botToken}/sendMessage`; 221 | const payload = { 222 | chat_id: chatId, 223 | text: message, 224 | parse_mode: 'HTML' 225 | }; 226 | 227 | const response = await fetch(url, { 228 | method: 'POST', 229 | headers: { 230 | 'Content-Type': 'application/json', 231 | 'User-Agent': 'Mozilla/5.0 (compatible; SSPanel-Checkin/1.0)' 232 | }, 233 | body: JSON.stringify(payload) 234 | }); 235 | 236 | if (response.ok) { 237 | console.log('Telegram 消息发送成功'); 238 | return { sent: true }; 239 | } else { 240 | const errorData = await response.text(); 241 | console.error('Telegram 消息发送失败:', errorData); 242 | return { sent: false, error: `发送失败: ${response.status}` }; 243 | } 244 | } catch (error) { 245 | console.error('Telegram 通知异常:', error.message); 246 | return { sent: false, error: error.message }; 247 | } 248 | } 249 | 250 | // 执行所有机场签到 251 | async function handleRequest() { 252 | const results = []; 253 | for (const config of AIRPORTS) { 254 | try { 255 | const checkin = new AirportCheckin(config); 256 | const result = await checkin.run(); 257 | results.push(result); 258 | } catch (error) { 259 | // 如果某个机场签到出错,记录错误但继续处理其他机场 260 | results.push({ 261 | airport: config.name, 262 | email: config.email, 263 | success: false, 264 | message: `处理异常: ${error.message}`, 265 | time: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) 266 | }); 267 | } 268 | } 269 | return { success: true, timestamp: new Date().toISOString(), results }; 270 | } 271 | 272 | export default { 273 | async fetch(request, env) { 274 | try { 275 | // 初始化环境变量 276 | initializeVariables(env); 277 | 278 | const url = new URL(request.url); 279 | 280 | // 路由处理 281 | switch (url.pathname) { 282 | case "/tg": 283 | // 检查 Telegram 配置 284 | if (!botToken || !chatId) { 285 | return new Response(JSON.stringify({ 286 | success: false, 287 | error: "Telegram 配置未完成", 288 | message: "请在 Cloudflare Workers 环境变量中设置 TGTOKEN 和 TGID", 289 | help: { 290 | "TGTOKEN": "从 @BotFather 获取的 Bot Token", 291 | "TGID": "你的 Telegram Chat ID (可从 @userinfobot 获取)" 292 | } 293 | }, null, 2), { 294 | status: 400, 295 | headers: { 'Content-Type': 'application/json' } 296 | }); 297 | } 298 | 299 | // 执行签到并发送 Telegram 通知 300 | const tgResult = await handleRequest(); 301 | const telegramResult = await sendTelegramMessage(tgResult.results); 302 | 303 | return new Response(JSON.stringify({ 304 | ...tgResult, 305 | telegram_sent: telegramResult.sent, 306 | telegram_error: telegramResult.error || null, 307 | message: telegramResult.sent 308 | ? "签到完成,已发送 Telegram 通知" 309 | : `签到完成,但 Telegram 通知发送失败: ${telegramResult.error}` 310 | }, null, 2), { 311 | status: telegramResult.sent ? 200 : 206, // 206 表示部分成功 312 | headers: { 'Content-Type': 'application/json' } 313 | }); 314 | 315 | case "/status": 316 | // 仅显示配置状态 317 | return new Response(JSON.stringify({ 318 | airports_configured: AIRPORTS.length, 319 | telegram_configured: !!(botToken && chatId), 320 | endpoints: [ 321 | "/ - 显示帮助信息", 322 | "/checkin - 执行签到(不发送通知)", 323 | "/tg - 执行签到并发送 Telegram 通知", 324 | "/status - 显示配置状态" 325 | ] 326 | }, null, 2), { 327 | headers: { 'Content-Type': 'application/json' } 328 | }); 329 | 330 | case "/checkin": 331 | // 仅执行签到,不发送通知 332 | const checkinResult = await handleRequest(); 333 | return new Response(JSON.stringify(checkinResult, null, 2), { 334 | headers: { 'Content-Type': 'application/json' } 335 | }); 336 | 337 | default: 338 | // 默认帮助页面 339 | const helpMessage = ` 340 | SSPanel 机场自动签到服务 341 | 342 | 🔧 环境变量配置: 343 | - TGTOKEN: Telegram Bot Token (可选) 344 | - TGID: Telegram Chat ID (可选) 345 | 346 | 📡 可用端点: 347 | - GET / - 显示此帮助信息 348 | - POST /checkin - 执行机场签到 349 | - POST /tg - 执行签到并发送 Telegram 通知 350 | - GET /status - 查看配置状态 351 | 352 | 📊 当前状态: 353 | - 已配置机场: ${AIRPORTS.length} 个 354 | - Telegram 通知: ${botToken && chatId ? '✅ 已配置' : '❌ 未配置'} 355 | 356 | ⏰ 支持 Cloudflare Workers 定时任务 357 | `; 358 | 359 | return new Response(helpMessage, { 360 | headers: { 'Content-Type': 'text/plain; charset=utf-8' } 361 | }); 362 | } 363 | } catch (error) { 364 | return new Response(JSON.stringify({ 365 | success: false, 366 | error: error.message, 367 | timestamp: new Date().toISOString() 368 | }), { 369 | status: 500, 370 | headers: { 'Content-Type': 'application/json' } 371 | }); 372 | } 373 | }, 374 | 375 | // 定时任务处理函数 376 | async scheduled(event, env, ctx) { 377 | console.log('SSPanel 机场签到定时任务开始'); 378 | try { 379 | // 初始化环境变量 380 | initializeVariables(env); 381 | 382 | // 执行签到 383 | const result = await handleRequest(); 384 | 385 | // 尝试发送 Telegram 通知 386 | const telegramResult = await sendTelegramMessage(result.results); 387 | 388 | console.log('SSPanel 机场签到定时任务完成'); 389 | return new Response(JSON.stringify({ 390 | ...result, 391 | telegram_sent: telegramResult.sent, 392 | telegram_error: telegramResult.error || null, 393 | scheduled: true, 394 | message: telegramResult.sent 395 | ? "定时任务完成,已发送 Telegram 通知" 396 | : `定时任务完成,Telegram 通知: ${telegramResult.error || '未配置'}` 397 | })); 398 | } catch (error) { 399 | console.error('定时任务失败:', error); 400 | 401 | // 即使出错也尝试发送错误通知(如果配置了 Telegram) 402 | if (botToken && chatId) { 403 | await sendTelegramMessage([{ 404 | airport: "系统", 405 | email: "定时任务", 406 | success: false, 407 | message: `定时任务执行失败: ${error.message}`, 408 | time: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) 409 | }]); 410 | } 411 | 412 | return new Response(JSON.stringify({ 413 | success: false, 414 | error: error.message, 415 | timestamp: new Date().toISOString() 416 | }), { status: 500 }); 417 | } 418 | } 419 | }; 420 | -------------------------------------------------------------------------------- /workers/dockerhub.js: -------------------------------------------------------------------------------- 1 | // _worker.js 2 | 3 | // Docker镜像仓库主机地址 4 | let hub_host = 'registry-1.docker.io'; 5 | // Docker认证服务器地址 6 | const auth_url = 'https://auth.docker.io'; 7 | // 自定义的工作服务器地址 8 | let workers_url = ''; 9 | 10 | let 屏蔽爬虫UA = ['netcraft']; 11 | 12 | // 根据主机名选择对应的上游地址 13 | function routeByHosts(host) { 14 | // 定义路由表 15 | const routes = { 16 | // 生产环境 17 | "quay": "quay.io", 18 | "gcr": "gcr.io", 19 | "k8s-gcr": "k8s.gcr.io", 20 | "k8s": "registry.k8s.io", 21 | "ghcr": "ghcr.io", 22 | "cloudsmith": "docker.cloudsmith.io", 23 | "nvcr": "nvcr.io", 24 | 25 | // 测试环境 26 | "test": "registry-1.docker.io", 27 | }; 28 | 29 | if (host in routes) return [ routes[host], false ]; 30 | else return [ hub_host, true ]; 31 | } 32 | 33 | /** @type {RequestInit} */ 34 | const PREFLIGHT_INIT = { 35 | // 预检请求配置 36 | headers: new Headers({ 37 | 'access-control-allow-origin': '*', // 允许所有来源 38 | 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法 39 | 'access-control-max-age': '1728000', // 预检请求的缓存时间 40 | }), 41 | } 42 | 43 | /** 44 | * 构造响应 45 | * @param {any} body 响应体 46 | * @param {number} status 响应状态码 47 | * @param {Object} headers 响应头 48 | */ 49 | function makeRes(body, status = 200, headers = {}) { 50 | headers['access-control-allow-origin'] = '*' // 允许所有来源 51 | return new Response(body, { status, headers }) // 返回新构造的响应 52 | } 53 | 54 | /** 55 | * 构造新的URL对象 56 | * @param {string} urlStr URL字符串 57 | */ 58 | function newUrl(urlStr) { 59 | try { 60 | return new URL(urlStr) // 尝试构造新的URL对象 61 | } catch (err) { 62 | return null // 构造失败返回null 63 | } 64 | } 65 | 66 | function isUUID(uuid) { 67 | // 定义一个正则表达式来匹配 UUID 格式 68 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 69 | 70 | // 使用正则表达式测试 UUID 字符串 71 | return uuidRegex.test(uuid); 72 | } 73 | 74 | async function nginx() { 75 | const text = ` 76 | 77 | 78 | 79 | Welcome to nginx! 80 | 87 | 88 | 89 |

Welcome to nginx!

90 |

If you see this page, the nginx web server is successfully installed and 91 | working. Further configuration is required.

92 | 93 |

For online documentation and support please refer to 94 | nginx.org.
95 | Commercial support is available at 96 | nginx.com.

97 | 98 |

Thank you for using nginx.

99 | 100 | 101 | ` 102 | return text; 103 | } 104 | 105 | async function searchInterface() { 106 | const text = ` 107 | 108 | 109 | 110 | Docker Hub Search 111 | 154 | 155 | 156 | 162 |
163 | 164 | 169 |
170 | 185 | 186 | 187 | `; 188 | return text; 189 | } 190 | 191 | export default { 192 | async fetch(request, env, ctx) { 193 | const getReqHeader = (key) => request.headers.get(key); // 获取请求头 194 | 195 | let url = new URL(request.url); // 解析请求URL 196 | const userAgentHeader = request.headers.get('User-Agent'); 197 | const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null"; 198 | if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA)); 199 | workers_url = `https://${url.hostname}`; 200 | const pathname = url.pathname; 201 | 202 | // 获取请求参数中的 ns 203 | const ns = url.searchParams.get('ns'); 204 | const hostname = url.searchParams.get('hubhost') || url.hostname; 205 | const hostTop = hostname.split('.')[0]; // 获取主机名的第一部分 206 | 207 | let checkHost; // 在这里定义 checkHost 变量 208 | // 如果存在 ns 参数,优先使用它来确定 hub_host 209 | if (ns) { 210 | if (ns === 'docker.io') { 211 | hub_host = 'registry-1.docker.io'; // 设置上游地址为 registry-1.docker.io 212 | } else { 213 | hub_host = ns; // 直接使用 ns 作为 hub_host 214 | } 215 | } else { 216 | checkHost = routeByHosts(hostTop); 217 | hub_host = checkHost[0]; // 获取上游地址 218 | } 219 | 220 | const fakePage = checkHost ? checkHost[1] : false; // 确保 fakePage 不为 undefined 221 | console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n伪装首页: ${fakePage}`); 222 | const isUuid = isUUID(pathname.split('/')[1].split('/')[0]); 223 | 224 | if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0) { 225 | // 首页改成一个nginx伪装页 226 | return new Response(await nginx(), { 227 | headers: { 228 | 'Content-Type': 'text/html; charset=UTF-8', 229 | }, 230 | }); 231 | } 232 | 233 | const conditions = [ 234 | isUuid, 235 | pathname.includes('/_'), 236 | pathname.includes('/r/'), 237 | pathname.includes('/v2/repositories'), 238 | pathname.includes('/v2/user'), 239 | pathname.includes('/v2/orgs'), 240 | pathname.includes('/v2/_catalog'), 241 | pathname.includes('/v2/categories'), 242 | pathname.includes('/v2/feature-flags'), 243 | pathname.includes('search'), 244 | pathname.includes('source'), 245 | pathname == '/', 246 | pathname == '/favicon.ico', 247 | pathname == '/auth/profile', 248 | ]; 249 | 250 | if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) { 251 | if (env.URL302) { 252 | return Response.redirect(env.URL302, 302); 253 | } else if (env.URL) { 254 | if (env.URL.toLowerCase() == 'nginx') { 255 | //首页改成一个nginx伪装页 256 | return new Response(await nginx(), { 257 | headers: { 258 | 'Content-Type': 'text/html; charset=UTF-8', 259 | }, 260 | }); 261 | } else return fetch(new Request(env.URL, request)); 262 | } else if (url.pathname == '/'){ 263 | return new Response(await searchInterface(), { 264 | headers: { 265 | 'Content-Type': 'text/html; charset=UTF-8', 266 | }, 267 | }); 268 | } 269 | 270 | const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search); 271 | 272 | // 复制原始请求的标头 273 | const headers = new Headers(request.headers); 274 | 275 | // 确保 Host 头部被替换为 hub.docker.com 276 | headers.set('Host', 'registry.hub.docker.com'); 277 | 278 | const newRequest = new Request(newUrl, { 279 | method: request.method, 280 | headers: headers, 281 | body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null, 282 | redirect: 'follow' 283 | }); 284 | 285 | return fetch(newRequest); 286 | } 287 | 288 | // 修改包含 %2F 和 %3A 的请求 289 | if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) { 290 | let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F'); 291 | url = new URL(modifiedUrl); 292 | console.log(`handle_url: ${url}`); 293 | } 294 | 295 | // 处理token请求 296 | if (url.pathname.includes('/token')) { 297 | let token_parameter = { 298 | headers: { 299 | 'Host': 'auth.docker.io', 300 | 'User-Agent': getReqHeader("User-Agent"), 301 | 'Accept': getReqHeader("Accept"), 302 | 'Accept-Language': getReqHeader("Accept-Language"), 303 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 304 | 'Connection': 'keep-alive', 305 | 'Cache-Control': 'max-age=0' 306 | } 307 | }; 308 | let token_url = auth_url + url.pathname + url.search; 309 | return fetch(new Request(token_url, request), token_parameter); 310 | } 311 | 312 | // 修改 /v2/ 请求路径 313 | if ( hub_host == 'registry-1.docker.io' && /^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) { 314 | //url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/'); 315 | url.pathname = '/v2/library/' + url.pathname.split('/v2/')[1]; 316 | console.log(`modified_url: ${url.pathname}`); 317 | } 318 | 319 | // 更改请求的主机名 320 | url.hostname = hub_host; 321 | 322 | // 构造请求参数 323 | let parameter = { 324 | headers: { 325 | 'Host': hub_host, 326 | 'User-Agent': getReqHeader("User-Agent"), 327 | 'Accept': getReqHeader("Accept"), 328 | 'Accept-Language': getReqHeader("Accept-Language"), 329 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 330 | 'Connection': 'keep-alive', 331 | 'Cache-Control': 'max-age=0' 332 | }, 333 | cacheTtl: 3600 // 缓存时间 334 | }; 335 | 336 | // 添加Authorization头 337 | if (request.headers.has("Authorization")) { 338 | parameter.headers.Authorization = getReqHeader("Authorization"); 339 | } 340 | 341 | // 发起请求并处理响应 342 | let original_response = await fetch(new Request(url, request), parameter); 343 | let original_response_clone = original_response.clone(); 344 | let original_text = original_response_clone.body; 345 | let response_headers = original_response.headers; 346 | let new_response_headers = new Headers(response_headers); 347 | let status = original_response.status; 348 | 349 | // 修改 Www-Authenticate 头 350 | if (new_response_headers.get("Www-Authenticate")) { 351 | let auth = new_response_headers.get("Www-Authenticate"); 352 | let re = new RegExp(auth_url, 'g'); 353 | new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url)); 354 | } 355 | 356 | // 处理重定向 357 | if (new_response_headers.get("Location")) { 358 | return httpHandler(request, new_response_headers.get("Location")); 359 | } 360 | 361 | // 返回修改后的响应 362 | let response = new Response(original_text, { 363 | status, 364 | headers: new_response_headers 365 | }); 366 | return response; 367 | } 368 | }; 369 | 370 | /** 371 | * 处理HTTP请求 372 | * @param {Request} req 请求对象 373 | * @param {string} pathname 请求路径 374 | */ 375 | function httpHandler(req, pathname) { 376 | const reqHdrRaw = req.headers; 377 | 378 | // 处理预检请求 379 | if (req.method === 'OPTIONS' && 380 | reqHdrRaw.has('access-control-request-headers') 381 | ) { 382 | return new Response(null, PREFLIGHT_INIT); 383 | } 384 | 385 | let rawLen = ''; 386 | 387 | const reqHdrNew = new Headers(reqHdrRaw); 388 | 389 | const refer = reqHdrNew.get('referer'); 390 | 391 | let urlStr = pathname; 392 | 393 | const urlObj = newUrl(urlStr); 394 | 395 | /** @type {RequestInit} */ 396 | const reqInit = { 397 | method: req.method, 398 | headers: reqHdrNew, 399 | redirect: 'follow', 400 | body: req.body 401 | }; 402 | return proxy(urlObj, reqInit, rawLen); 403 | } 404 | 405 | /** 406 | * 代理请求 407 | * @param {URL} urlObj URL对象 408 | * @param {RequestInit} reqInit 请求初始化对象 409 | * @param {string} rawLen 原始长度 410 | */ 411 | async function proxy(urlObj, reqInit, rawLen) { 412 | const res = await fetch(urlObj.href, reqInit); 413 | const resHdrOld = res.headers; 414 | const resHdrNew = new Headers(resHdrOld); 415 | 416 | // 验证长度 417 | if (rawLen) { 418 | const newLen = resHdrOld.get('content-length') || ''; 419 | const badLen = (rawLen !== newLen); 420 | 421 | if (badLen) { 422 | return makeRes(res.body, 400, { 423 | '--error': `bad len: ${newLen}, except: ${rawLen}`, 424 | 'access-control-expose-headers': '--error', 425 | }); 426 | } 427 | } 428 | const status = res.status; 429 | resHdrNew.set('access-control-expose-headers', '*'); 430 | resHdrNew.set('access-control-allow-origin', '*'); 431 | resHdrNew.set('Cache-Control', 'max-age=1500'); 432 | 433 | // 删除不必要的头 434 | resHdrNew.delete('content-security-policy'); 435 | resHdrNew.delete('content-security-policy-report-only'); 436 | resHdrNew.delete('clear-site-data'); 437 | 438 | return new Response(res.body, { 439 | status, 440 | headers: resHdrNew 441 | }); 442 | } 443 | 444 | async function ADD(envadd) { 445 | var addtext = envadd.replace(/[ |"'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 446 | if (addtext.charAt(0) == ',') addtext = addtext.slice(1); 447 | if (addtext.charAt(addtext.length - 1) == ',') addtext = addtext.slice(0, addtext.length - 1); 448 | const add = addtext.split(','); 449 | return add; 450 | } 451 | -------------------------------------------------------------------------------- /workers/glados.js: -------------------------------------------------------------------------------- 1 | // GLaDOS 签到 Cloudflare Worker 2 | // 支持多账号签到,使用 JSON 格式的环境变量,支持 Telegram 通知 3 | 4 | // 常量定义 5 | const API_BASE_URL = "https://glados.rocks/api"; 6 | const API_ENDPOINTS = { 7 | CHECKIN: "/user/checkin", 8 | STATUS: "/user/status" 9 | }; 10 | const CONTENT_TYPE_TEXT = { 'Content-Type': 'text/plain;charset=UTF-8' }; 11 | const CONTENT_TYPE_HTML = { 'Content-Type': 'text/html;charset=UTF-8' }; 12 | 13 | // 全局变量 14 | let accounts = [ 15 | // 示例:直接在代码中配置账号 16 | // {email: 'example@gmail.com', cookie: 'your_cookie_here'} 17 | ]; 18 | let botToken = ''; // Telegram Bot Token 19 | let chatId = ''; // Telegram Chat ID 20 | let checkinResults = []; 21 | let accountStatus = []; 22 | let pointsHistory = []; 23 | let workerUrl = ''; 24 | 25 | export default { 26 | // HTTP 请求处理函数 27 | async fetch(request, env, ctx) { 28 | await initializeVariables(env); 29 | const url = new URL(request.url); 30 | 31 | // 路由处理 32 | switch (url.pathname) { 33 | case "/tg": 34 | await performAllCheckins(); 35 | await checkAllAccountStatus(); 36 | await sendTelegramMessage(request); 37 | return createResponse("已执行签到并发送结果到 Telegram"); 38 | 39 | case "/checkin": 40 | await performAllCheckins(); 41 | await checkAllAccountStatus(); 42 | return createResponse(checkinResults.join("\n") + "\n\n" + accountStatus.join("\n")); 43 | 44 | case "/status": 45 | await checkAllAccountStatus(); 46 | return createResponse(accountStatus.join("\n")); 47 | 48 | case "/checkinChart": 49 | await fetchPointsHistory(); 50 | return generateChartResponse(); 51 | 52 | default: 53 | return createResponse( 54 | "GLaDOS 多账号签到服务正在运行\n\n可用端点:\n" + 55 | "/checkin - 执行签到并查询状态\n" + 56 | "/status - 仅查询账号状态\n" + 57 | "/tg - 执行签到并发送结果到 Telegram\n" + 58 | "/checkinChart - 查看积分历史图表" 59 | ); 60 | } 61 | }, 62 | 63 | // 定时任务处理函数 64 | async scheduled(controller, env, ctx) { 65 | console.log('GLaDOS 多账号签到定时任务开始'); 66 | try { 67 | await initializeVariables(env); 68 | await performAllCheckins(); 69 | await checkAllAccountStatus(); 70 | await sendTelegramMessage(); 71 | console.log('GLaDOS 多账号签到定时任务完成'); 72 | } catch (error) { 73 | console.error('定时任务失败:', error); 74 | checkinResults.push(`定时任务执行失败: ${error.message}`); 75 | await sendTelegramMessage(); 76 | } 77 | }, 78 | }; 79 | 80 | // 创建统一的响应对象 81 | function createResponse(content, headers = CONTENT_TYPE_TEXT, status = 200) { 82 | return new Response(content, { status, headers }); 83 | } 84 | 85 | // 初始化变量 86 | async function initializeVariables(env) { 87 | // 保存代码中已配置的账号 88 | const hardcodedAccounts = [...accounts]; 89 | 90 | // 重置列表 91 | accounts = []; 92 | checkinResults = []; 93 | accountStatus = []; 94 | pointsHistory = []; 95 | 96 | // 设置 Telegram 信息 97 | botToken = env.TGTOKEN || botToken; 98 | chatId = env.TGID || chatId; 99 | workerUrl = env.WORKER_URL || workerUrl; 100 | 101 | // 尝试从环境变量加载账号 102 | try { 103 | const accountsJson = env.GLADOS_ACCOUNTS || '[]'; 104 | const parsedAccounts = JSON.parse(accountsJson); 105 | 106 | if (Array.isArray(parsedAccounts) && parsedAccounts.length > 0) { 107 | accounts = parsedAccounts.filter(acc => acc.email && acc.cookie); 108 | console.log(`从环境变量加载了 ${accounts.length} 个账号`); 109 | } 110 | } catch (error) { 111 | console.error('解析环境变量账号信息失败:', error); 112 | } 113 | 114 | // 如果环境变量中没有配置任何账号,使用代码中硬编码的账号 115 | if (accounts.length === 0 && hardcodedAccounts.length > 0) { 116 | accounts = hardcodedAccounts.filter(acc => acc.email && acc.cookie); 117 | console.log(`使用代码中配置的 ${accounts.length} 个账号`); 118 | } 119 | 120 | if (accounts.length === 0) { 121 | console.warn('未配置任何账号信息,请在代码中或环境变量中设置账号'); 122 | } 123 | } 124 | 125 | // 生成随机 User-Agent 126 | function generateUserAgent() { 127 | const userAgents = [ 128 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", 129 | "Mozilla/5.0 (Linux; Android 10; zh-CN; SM-G9750) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36", 130 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15", 131 | ]; 132 | return userAgents[Math.floor(Math.random() * userAgents.length)]; 133 | } 134 | 135 | // 生成请求头 136 | function generateHeaders(cookie) { 137 | return { 138 | "Accept": "application/json, text/plain, */*", 139 | "Accept-Encoding": "gzip, deflate, br", 140 | "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", 141 | "Authorization": "9876543210987654321098765432109-1234-567", 142 | "Content-Type": "application/json;charset=UTF-8", 143 | "Cookie": cookie, 144 | "Origin": "https://glados.rocks", 145 | "Sec-Ch-Ua": '"Not-A.Brand";v="99", "Chromium";v="124"', 146 | "Sec-Ch-Ua-Mobile": "?0", 147 | "Sec-Ch-Ua-Platform": '"Windows"', 148 | "Sec-Fetch-Dest": "empty", 149 | "Sec-Fetch-Mode": "cors", 150 | "Sec-Fetch-Site": "same-origin", 151 | "User-Agent": generateUserAgent() 152 | }; 153 | } 154 | 155 | // 执行API请求 156 | async function makeApiRequest(endpoint, method, cookie, data = null) { 157 | if (!cookie) { 158 | throw new Error('Cookie 未设置'); 159 | } 160 | 161 | const url = API_BASE_URL + endpoint; 162 | const headers = generateHeaders(cookie); 163 | const options = { 164 | method, 165 | headers 166 | }; 167 | 168 | if (data) { 169 | options.body = JSON.stringify(data); 170 | } 171 | 172 | const response = await fetch(url, options); 173 | 174 | if (!response.ok) { 175 | throw new Error(`请求失败: ${response.status} ${response.statusText}`); 176 | } 177 | 178 | return response.json(); 179 | } 180 | 181 | // 转换签到消息 182 | function translateMessage(responseData) { 183 | if (!responseData || typeof responseData !== 'object') { 184 | return "无效的签到数据 ⚠️"; 185 | } 186 | 187 | const rawMessage = responseData.message; 188 | const currentBalance = responseData.list && responseData.list[0] ? 189 | Math.floor(parseFloat(responseData.list[0].balance)) : '未知'; 190 | 191 | if (rawMessage === "Please Try Tomorrow") { 192 | return `签到失败,请明天再试 🤖\n当前余额:${currentBalance}积分`; 193 | } else if (rawMessage && rawMessage.includes("Checkin! Got")) { 194 | const match = rawMessage.match(/Got (\d+) Points?/); 195 | const points = match ? match[1] : '未知'; 196 | return `签到成功,获得${points}积分 🎉\n当前余额:${currentBalance}积分`; 197 | } else if (rawMessage === "Checkin Repeats! Please Try Tomorrow") { 198 | return `重复签到,请明天再试 🔁\n当前余额:${currentBalance}积分`; 199 | } else { 200 | return `未知的签到结果: ${rawMessage} ❓\n当前余额:${currentBalance}积分`; 201 | } 202 | } 203 | 204 | // 格式化天数 205 | function formatDays(daysStr) { 206 | const days = parseFloat(daysStr); 207 | if (Number.isInteger(days)) { 208 | return days.toString(); 209 | } 210 | return days.toFixed(8).replace(/\.?0+$/, ''); 211 | } 212 | 213 | // 获取Worker的URL 214 | function getWorkerUrl(request) { 215 | if (!request) return ''; 216 | try { 217 | const url = new URL(request.url); 218 | return `${url.protocol}//${url.host}`; 219 | } catch (error) { 220 | console.error('获取Worker URL失败:', error); 221 | return ''; 222 | } 223 | } 224 | 225 | // 发送消息到 Telegram 226 | async function sendTelegramMessage(request) { 227 | if (botToken === '' || chatId === '') { 228 | return; 229 | } 230 | 231 | const now = new Date(); 232 | const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); 233 | const formattedTime = beijingTime.toISOString().slice(0, 19).replace('T', ' '); 234 | 235 | let message = `📊 GLaDOS 签到报告\n`; 236 | message += `${formattedTime}\n\n`; 237 | 238 | if (checkinResults.length > 0) { 239 | message += `📝 签到结果\n${checkinResults.join("\n")}\n\n`; 240 | } 241 | 242 | if (accountStatus.length > 0) { 243 | message += `📈 账号状态\n${accountStatus.join("\n")}\n\n`; 244 | } 245 | 246 | message += `✅ 共完成 ${accounts.length} 个账号的签到任务`; 247 | 248 | // 添加图表链接,仅当request参数存在时 249 | // 获取 Worker URL 250 | const baseUrl = request ? getWorkerUrl(request) : workerUrl; 251 | const chartUrl = baseUrl + "/checkinChart"; 252 | message += `\n\n📊 点击查看积分历史图表`; 253 | 254 | const url = `https://api.telegram.org/bot${botToken}/sendMessage?chat_id=${chatId}&parse_mode=HTML&text=${encodeURIComponent(message)}`; 255 | return fetch(url, { 256 | method: 'get', 257 | headers: { 258 | 'Accept': 'text/html,application/xhtml+xml,application/xml;', 259 | 'Accept-Encoding': 'gzip, deflate, br', 260 | 'User-Agent': 'Mozilla/5.0 Chrome/90.0.4430.72' 261 | } 262 | }); 263 | } 264 | 265 | // 执行所有账号的签到 266 | async function performAllCheckins() { 267 | checkinResults = []; 268 | 269 | if (accounts.length === 0) { 270 | checkinResults.push("⚠️ 未配置任何账号信息"); 271 | return; 272 | } 273 | 274 | for (const account of accounts) { 275 | try { 276 | const result = await performCheckin(account.email, account.cookie); 277 | checkinResults.push(result); 278 | } catch (error) { 279 | console.error(`账号 ${account.email} 签到错误:`, error); 280 | checkinResults.push(`${account.email}: 签到过程发生错误: ${error.message} ❌`); 281 | } 282 | } 283 | 284 | return checkinResults; 285 | } 286 | 287 | // 检查所有账号的状态 288 | async function checkAllAccountStatus() { 289 | accountStatus = []; 290 | 291 | if (accounts.length === 0) { 292 | accountStatus.push("⚠️ 未配置任何账号信息"); 293 | return; 294 | } 295 | 296 | for (const account of accounts) { 297 | try { 298 | const result = await checkAccountStatus(account.email, account.cookie); 299 | accountStatus.push(result); 300 | } catch (error) { 301 | console.error(`账号 ${account.email} 状态查询错误:`, error); 302 | accountStatus.push(`${account.email}: 获取状态失败 - ${error.message} ❌`); 303 | } 304 | } 305 | 306 | return accountStatus; 307 | } 308 | 309 | // 执行单个账号签到 310 | async function performCheckin(email, cookie) { 311 | try { 312 | const data = { token: "glados.one" }; 313 | const responseData = await makeApiRequest(API_ENDPOINTS.CHECKIN, 'POST', cookie, data); 314 | const translatedMessage = translateMessage(responseData); 315 | 316 | const result = `${email}: ${translatedMessage}`; 317 | return result; 318 | } catch (error) { 319 | throw new Error(`签到失败: ${error.message}`); 320 | } 321 | } 322 | 323 | // 检查单个账号状态 324 | async function checkAccountStatus(email, cookie) { 325 | try { 326 | const data = await makeApiRequest(API_ENDPOINTS.STATUS, 'GET', cookie); 327 | 328 | if (!data.data || !data.data.leftDays) { 329 | throw new Error('响应数据格式不正确'); 330 | } 331 | 332 | const leftDays = formatDays(data.data.leftDays); 333 | return `${email}: 剩余 ${leftDays} 天`; 334 | } catch (error) { 335 | throw new Error(`获取状态失败: ${error.message}`); 336 | } 337 | } 338 | 339 | // 获取积分历史数据 340 | async function fetchPointsHistory() { 341 | pointsHistory = []; 342 | 343 | if (accounts.length === 0) { 344 | return; 345 | } 346 | 347 | for (const account of accounts) { 348 | try { 349 | // 获取积分历史 350 | const data = { token: "glados.one" }; 351 | const responseData = await makeApiRequest(API_ENDPOINTS.CHECKIN, 'POST', account.cookie, data); 352 | 353 | // 获取账号状态(剩余天数) 354 | const statusData = await makeApiRequest(API_ENDPOINTS.STATUS, 'GET', account.cookie); 355 | const leftDays = statusData.data && statusData.data.leftDays ? formatDays(statusData.data.leftDays) : "未知"; 356 | 357 | if (responseData.code === 1 && Array.isArray(responseData.list)) { 358 | // 处理积分历史数据 359 | const accountData = { 360 | email: account.email, 361 | leftDays: leftDays, // 添加剩余天数信息 362 | history: responseData.list.map(item => ({ 363 | time: new Date(parseInt(item.time)), 364 | balance: parseFloat(item.balance), 365 | change: parseFloat(item.change), 366 | business: item.business 367 | })).sort((a, b) => a.time - b.time) // 按时间排序 368 | }; 369 | pointsHistory.push(accountData); 370 | } 371 | } catch (error) { 372 | console.error(`获取账号 ${account.email} 积分历史错误:`, error); 373 | } 374 | } 375 | } 376 | 377 | // 生成图表响应 378 | function generateChartResponse() { 379 | // 如果没有数据,返回提示信息 380 | if (pointsHistory.length === 0) { 381 | return createResponse("未获取到任何积分历史数据,请确保账号配置正确。"); 382 | } 383 | 384 | // 生成HTML页面,包含Chart.js图表 385 | const html = generateChartHtml(); 386 | return new Response(html, { 387 | status: 200, 388 | headers: CONTENT_TYPE_HTML 389 | }); 390 | } 391 | 392 | // 生成图表HTML 393 | function generateChartHtml() { 394 | return ` 395 | 396 | 397 | 398 | 399 | 400 | GLaDOS 积分历史图表 401 | 402 | 403 | 690 | 691 | 692 |
693 |

GLaDOS 积分历史

694 | 695 |
696 | 702 | 708 |
709 | ${pointsHistory.map((accountData, index) => { 710 | // 提取数据用于图表 711 | const dates = accountData.history.map(item => item.time.toLocaleDateString()); 712 | const balances = accountData.history.map(item => item.balance); 713 | // 计算统计信息 714 | const currentBalance = balances.length > 0 ? balances[balances.length - 1] : 0; 715 | const changes = accountData.history.map(item => item.change); 716 | const totalEarned = changes.filter(change => change > 0).reduce((sum, change) => sum + change, 0); 717 | const totalSpent = Math.abs(changes.filter(change => change < 0).reduce((sum, change) => sum + change, 0)); 718 | 719 | // 计算签到次数 720 | const checkinCount = accountData.history.filter(item => 721 | item.business && item.business.includes('checkin') 722 | ).length; 723 | 724 | // 计算兑换次数 725 | const collectCount = accountData.history.filter(item => 726 | item.business && item.business.includes('collect') 727 | ).length; 728 | 729 | return ` 730 | 774 | `; 775 | }).join('')} 776 |
777 | 778 | 984 | 985 | 986 | `; 987 | } 988 | --------------------------------------------------------------------------------