diff --git a/.github/workflows/glados.yml b/.github/workflows/glados.yml index ae5252d..ae2d431 100644 --- a/.github/workflows/glados.yml +++ b/.github/workflows/glados.yml @@ -1,13 +1,22 @@ name: GLaDOS Auto Checkin on: - workflow_dispatch: + workflow_dispatch: # 手动触发 schedule: - - cron: '0 4 * * *' + - cron: '0 4 * * *' # 每日 UTC 4 点签到(北京时间 12 点) + - cron: '0 0 1 * *' # 每月 1 号保活空提交 jobs: checkin: + name: GLaDOS 签到 runs-on: ubuntu-latest + timeout-minutes: 15 + concurrency: + group: glados-checkin + cancel-in-progress: false + + permissions: + contents: read steps: - uses: actions/checkout@v4 @@ -15,16 +24,46 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.11' - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install requests + run: pip install requests - name: Run checkin run: python checkin.py env: + COOKIES: ${{ secrets.COOKIES }} + SENDKEY: ${{ secrets.SENDKEY }} + SERVERCHAN_KEY: ${{ secrets.SERVERCHAN_KEY }} TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }} TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }} - COOKIES: ${{ secrets.COOKIES }} \ No newline at end of file + PUSHPLUS_TOKEN: ${{ secrets.PUSHPLUS_TOKEN }} + DINGTALK_WEBHOOK: ${{ secrets.DINGTALK_WEBHOOK }} + DINGTALK_SECRET: ${{ secrets.DINGTALK_SECRET }} + FEISHU_WEBHOOK: ${{ secrets.FEISHU_WEBHOOK }} + FEISHU_SECRET: ${{ secrets.FEISHU_SECRET }} + WECOM_BOT_WEBHOOK: ${{ secrets.WECOM_BOT_WEBHOOK }} + YUNHU_TOKEN: ${{ secrets.YUNHU_TOKEN }} + YUNHU_RECV_ID: ${{ secrets.YUNHU_RECV_ID }} + YUNHU_RECV_TYPE: ${{ secrets.YUNHU_RECV_TYPE }} + + keepalive: + name: 每月空提交保活 + if: github.event_name == 'schedule' && github.event.schedule == '0 0 1 * *' + runs-on: ubuntu-latest + timeout-minutes: 5 + + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: 创建空提交并推送 + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git commit --allow-empty -m "chore: keepalive to prevent workflow suspension" + git push diff --git a/README.md b/README.md index a14d4ae..7d7dfd2 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,167 @@ -# 📌 GLaDOS 自动签到 +# 📌 GLaDOS 自动签到 一个基于 **GitHub Actions** 的 **GLaDOS 自动签到脚本**。 - **无需服务器、无需编程基础**,每天自动帮你签到。 ------- +**无需服务器、无需编程基础**,每天自动帮你签到。 -## ✨ 功能说明 +--- -- ✅ **每天自动签到** -- 🔁 **已签到自动识别,不会报错** -- 👥 **支持多个账号** -- 📬 **可选签到结果推送(Telegram Bot)** -- 🆓 **完全免费,使用 GitHub Actions** +## ✨ 功能特性 ------- +- ✅ 每天自动签到,已签到自动识别 +- 👥 支持多账号(用 `&` 连接) +- 📊 查询总积分和剩余天数 +- 📬 8 种推送渠道:PushDeer / Server酱 / Telegram / PushPlus / 钉钉 / 飞书 / 企业微信 / 云湖 +- 🔄 网络请求自动重试(指数退避) +- 🔒 日志脱敏(邮箱/Cookie 自动隐藏) +- ✅ Cookie 格式预验证 +- 🔧 每月自动空提交保活 +- 🆓 完全免费 + +--- ## 📂 项目结构 ``` . -├── checkin.py # 签到脚本(不用动) +├── checkin.py # 签到脚本 └── .github/workflows/ - └── glados.yml # GitHub Actions 配置(不用动) + └── glados.yml # GitHub Actions 配置 ``` ------- +--- ## 🚀 使用教程 ### 第一步:Fork 本项目 -1. 点击右上角 **Fork** -2. Fork 到你自己的 GitHub 账号下 - -👉 后续所有操作都在你 **自己的仓库** 中完成 +点击右上角 **Fork**,Fork 到你自己的 GitHub 账号下。 ------- +--- ### 第二步:获取 GLaDOS Cookie -1. 打开浏览器,登录:https://glados.cloud +1. 打开浏览器,登录 https://glados.cloud 2. 按 **F12** 打开开发者工具 -3. 找到: - - Chrome:`Application` → `Cookies` - - Firefox:`存储` → `Cookies` -4. 选择 `glados.cloud` -5. **复制完整 Cookie 内容** - -示例(示意): +3. 找到 `Application` → `Cookies` → `glados.cloud` +4. 复制完整 Cookie 内容 +示例: ``` koa:sess=xxxxxx; koa:sess.sig=yyyyyy ``` -⚠️ **必须是完整的一整段,不要只复制一半** +⚠️ **必须是完整的一整段** ------- +--- ### 第三步:添加 GitHub Secrets 进入你 Fork 后的仓库: -1. 点击 **Settings** -2. 左侧选择 **Secrets and variables → Actions** -3. 点击 **New repository secret** +1. **Settings** → **Secrets and variables** → **Actions** +2. 点击 **New repository secret** +3. 添加: + - **Name**:`COOKIES` + - **Value**:粘贴刚才复制的 Cookie +4. 点击 **Save** -#### 添加第一个 Secret(必填) +--- -- **Name**:`COOKIES` -- **Value**:粘贴刚才复制的 Cookie +### 第四步:(可选)配置推送 -点击 **Save** +在 GitHub Secrets 中添加对应的环境变量: ------- +| 渠道 | 必填环境变量 | 可选 | +|------|-------------|------| +| PushDeer | `SENDKEY` | - | +| Server酱 | `SERVERCHAN_KEY` | - | +| Telegram | `TG_BOT_TOKEN` + `TG_CHAT_ID` | - | +| PushPlus | `PUSHPLUS_TOKEN` | - | +| 钉钉机器人 | `DINGTALK_WEBHOOK` | `DINGTALK_SECRET` | +| 飞书机器人 | `FEISHU_WEBHOOK` | `FEISHU_SECRET` | +| 企业微信机器人 | `WECOM_BOT_WEBHOOK` | - | +| 云湖机器人 | `YUNHU_TOKEN` + `YUNHU_RECV_ID` | `YUNHU_RECV_TYPE` | -### (可选)第四步:开启签到结果推送 +--- -如果你想每天收到签到通知(可选): +## 👥 多账号配置 -1. 在 Telegram 搜索 `@BotFather` -2. 发送 `/newbot` 创建机器人,拿到 `TG_BOT_TOKEN` -3. 打开你新建的机器人,先发一条任意消息 -4. 访问:`https://api.telegram.org/bot<你的TG_BOT_TOKEN>/getUpdates` -5. 在返回内容中找到你的 `chat.id`(这就是 `TG_CHAT_ID`) -6. 在 GitHub Secrets 中添加下面两个: +多个账号的 Cookie 用 `&` 连接: -- **Name**:`TG_BOT_TOKEN` -- **Value**:你的 Telegram Bot Token +``` +cookie_账号1 & cookie_账号2 & cookie_账号3 +``` -- **Name**:`TG_CHAT_ID` -- **Value**:你的聊天 ID(数字,可能是负数) +⚠️ 不要换行,不要用逗号 -不填也没关系,只是不会推送。 +--- ------- +## ⏰ 签到时间 -## 👥 多账号如何添加? +每天 **UTC 04:00**(北京时间 **中午 12 点**)自动运行。 -### 规则很简单: +--- -> **多个账号的 Cookie,用 `&` 连接** +## 📋 签到结果 -示例: +| 状态 | 说明 | +|------|------| +| ✅ 成功 | 签到成功,显示获得积分 | +| 🔄 已签到 | 今日已签到过 | +| ❌ 失败 | 签到失败,显示原因 | -``` -cookie_账号1 & cookie_账号2 & cookie_账号3 -``` +--- -⚠️ 注意事项: +## ❓ 常见问题 -- 不要换行 -- 不要用逗号 -- 每个 cookie 都是完整的一段 +**Q: 签到提示 Cookie 失效?** ------- +A: Cookie 有有效期,请重新登录获取最新 Cookie 并更新 Secrets。 -## ⏰ 自动签到时间说明 +**Q: Actions 被暂停了?** -项目默认设置为: +A: 项目内置每月空提交保活。如仍被暂停,手动触发一次 `workflow_dispatch`。 -``` -每天 UTC 04:00 自动运行 -``` +**Q: 日志中的邮箱为什么显示不完整?** + +A: 出于隐私保护,邮箱会自动脱敏(如 `te***t@example.com`)。 + +**Q: 可以同时配置多个推送渠道吗?** + +A: 可以,配置多个 Secrets 即可同时推送。 + +--- + +## 🔄 更新日志 + +### v2.0.0 + +**功能新增** +- 新增 Telegram Bot 推送 +- 新增 PushPlus(推送加)推送 +- 新增钉钉机器人推送(支持加签验证) +- 新增飞书机器人推送(支持加签验证) +- 新增企业微信机器人推送 +- 新增云湖机器人推送 +- 新增总积分查询功能 +- 新增网络请求自动重试机制(指数退避) +- 新增 Cookie 格式预验证 +- 新增日志脱敏处理(邮箱/Cookie 自动隐藏) +- 新增每月自动空提交保活机制 + +**问题修复** +- 修复 PushPlus 推送域名问题 +- 修复飞书机器人加签算法 + +**优化改进** +- Python 版本升级至 3.11 +- 多账号间请求增加随机延迟 +- GitHub Actions 添加超时和并发控制 +- 代码整合为单文件,简化部署 -换算成北京时间: +--- -> 🕛 **每天中午 12 点自动签到** +## 📄 许可证 -你无需做任何操作,它会每天自动运行。 \ No newline at end of file +MIT License diff --git a/checkin.py b/checkin.py index 30fe095..c4bc9b7 100644 --- a/checkin.py +++ b/checkin.py @@ -1,13 +1,23 @@ +""" +GLaDOS 自动签到脚本 +支持多账号、多种推送渠道、重试机制、日志脱敏 +""" import os import json import time import random +import hashlib +import hmac +import base64 +import urllib.parse +from typing import List, Dict, Any, Tuple +from functools import wraps import requests - +# ==================== 配置 ==================== CHECKIN_URL = "https://glados.cloud/api/user/checkin" STATUS_URL = "https://glados.cloud/api/user/status" - +POINTS_URL = "https://glados.cloud/api/user/points" HEADERS_BASE = { "origin": "https://glados.cloud", "referer": "https://glados.cloud/console/checkin", @@ -18,15 +28,55 @@ ), "content-type": "application/json;charset=UTF-8", } - PAYLOAD = {"token": "glados.cloud"} -TIMEOUT = 10 +TIMEOUT = 12 +MAX_RETRY = 3 +RETRY_MIN_WAIT = 2.0 +RETRY_MAX_WAIT = 10.0 +MIN_DELAY = 1.0 +MAX_DELAY = 2.0 +TELEGRAM_MAX_LENGTH = 4000 +TELEGRAM_TRUNCATE_LENGTH = 3990 +COOKIE_MASK_LENGTH = 10 +COOKIE_MIN_LENGTH = 20 -def push_telegram(bot_token: str, chat_id: str, title: str, content: str): - """推送消息到 Telegram Bot""" - if not bot_token or not chat_id: - return +# ==================== 工具函数 ==================== +def safe_json(resp: requests.Response) -> Dict[str, Any]: + """安全解析 JSON 响应""" + try: + return resp.json() + except Exception: + return {} + + +def mask_email(email: str) -> str: + """ + 邮箱脱敏:保留前两个字符和最后一个字符,中间用 *** 替代 + Examples: + mask_email("test@example.com") -> "te***t@example.com" + mask_email("ab@example.com") -> "ab***b@example.com" + mask_email("a@example.com") -> "a***a@example.com" + mask_email("unknown") -> "unknown" + """ + if not email or email == "unknown" or "@" not in email: + return email + try: + name, domain = email.rsplit("@", 1) + if len(name) <= 2: + masked_name = f"{name}***{name[-1]}" + else: + masked_name = f"{name[:2]}***{name[-1]}" + return f"{masked_name}@{domain}" + except Exception: + return email + + +def mask_cookie(cookie: str) -> str: + """Cookie 脱敏(只显示前后各10个字符)""" + if not cookie or len(cookie) <= COOKIE_MIN_LENGTH: + return "***" + return f"{cookie[:COOKIE_MASK_LENGTH]}...{cookie[-COOKIE_MASK_LENGTH:]}" url = f"https://api.telegram.org/bot{bot_token}/sendMessage" text = f"{title}\n\n{content}" @@ -35,102 +85,448 @@ def push_telegram(bot_token: str, chat_id: str, title: str, content: str): if len(text) > 4000: text = text[:3990] + "..." - data = { - "chat_id": chat_id, - "text": text, - } +def validate_cookie(cookie: str) -> Tuple[bool, str]: + """验证 Cookie 是否包含必要字段""" + if not cookie or not cookie.strip(): + return False, "Cookie 为空" + cookie = cookie.strip() + if "koa:sess" not in cookie: + return False, "Cookie 缺少必要字段: koa:sess" + if "koa:sess.sig" not in cookie: + return False, "Cookie 缺少必要字段: koa:sess.sig" + return True, "" + + +def retry_on_failure(max_retries: int = MAX_RETRY, min_wait: float = RETRY_MIN_WAIT, + max_wait: float = RETRY_MAX_WAIT): + """重试装饰器(指数退避)""" + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + last_exception = None + for attempt in range(max_retries + 1): + try: + return func(*args, **kwargs) + except Exception as e: + last_exception = e + if attempt < max_retries: + wait_time = min(min_wait * (2 ** attempt), max_wait) + print(f"⚠️ 第 {attempt + 1} 次尝试失败: {e},{wait_time:.1f}秒后重试...") + time.sleep(wait_time) + raise last_exception # type: ignore + return wrapper + return decorator + + +# ==================== 推送函数 ==================== +def push_deer(key: str, title: str, content: str) -> None: + """PushDeer 推送""" + if not key: + return + try: + r = requests.post( + "https://api2.pushdeer.com/message/push", + json={"pushkey": key, "text": f"{title}\n\n{content}", "type": "text"}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("code") == 0: + print("✅ PushDeer 推送成功") + else: + print(f"⚠️ PushDeer 推送失败: {resp.get('message', r.text)}") + except Exception as e: + print(f"⚠️ PushDeer 推送异常: {e}") + +def push_serverchan(key: str, title: str, content: str) -> None: + """Server酱推送""" + if not key: + return try: - resp = requests.post(url, json=data, timeout=TIMEOUT) - if resp.status_code == 200 and safe_json(resp).get("ok"): + r = requests.post( + f"https://sctapi.ftqq.com/{key}.send", + data={"title": title, "desp": content}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("code") == 0: + print("✅ Server酱推送成功") + else: + print(f"⚠️ Server酱推送失败: {resp.get('message', r.text)}") + except Exception as e: + print(f"⚠️ Server酱推送异常: {e}") + + +def push_telegram(bot_token: str, chat_id: str, title: str, content: str) -> None: + """Telegram Bot 推送""" + if not bot_token or not chat_id: + return + text = f"{title}\n\n{content}" + if len(text) > TELEGRAM_MAX_LENGTH: + text = text[:TELEGRAM_TRUNCATE_LENGTH] + "\n..." + try: + r = requests.post( + f"https://api.telegram.org/bot{bot_token}/sendMessage", + json={"chat_id": chat_id, "text": text}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("ok"): print("✅ Telegram 推送成功") else: - print(f"⚠️ Telegram 推送失败: HTTP {resp.status_code} | {resp.text}") + print(f"⚠️ Telegram 推送失败: HTTP {r.status_code} | {resp.get('description', r.text)}") except Exception as e: print(f"⚠️ Telegram 推送异常: {e}") -def push_all(bot_token: str, chat_id: str, title: str, content: str): - """推送到 Telegram(如果已配置)""" +def push_pushplus(token: str, title: str, content: str) -> None: + """PushPlus 推送""" + if not token: + return + try: + r = requests.post( + "https://www.pushplus.plus/send", + json={"token": token, "title": title, "content": content, "template": "html"}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("code") == 200: + print("✅ PushPlus 推送成功") + else: + print(f"⚠️ PushPlus 推送失败: {resp.get('msg', r.text)}") + except Exception as e: + print(f"⚠️ PushPlus 推送异常: {e}") + + +def push_dingtalk(webhook_url: str, title: str, content: str) -> None: + """钉钉机器人推送(支持加签)""" + if not webhook_url: + return + try: + secret = os.getenv("DINGTALK_SECRET", "") + if secret: + timestamp = str(round(time.time() * 1000)) + string_to_sign = f"{timestamp}\n{secret}" + hmac_code = hmac.new( + secret.encode("utf-8"), + string_to_sign.encode("utf-8"), + digestmod=hashlib.sha256, + ).digest() + sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) + separator = "&" if "?" in webhook_url else "?" + webhook_url = f"{webhook_url}{separator}timestamp={timestamp}&sign={sign}" + + r = requests.post( + webhook_url, + json={ + "msgtype": "markdown", + "markdown": {"title": title, "text": f"### {title}\n\n{content}"}, + }, + headers={"Content-Type": "application/json"}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("errcode") == 0: + print("✅ 钉钉机器人推送成功") + else: + print(f"⚠️ 钉钉机器人推送失败: {resp.get('errmsg', r.text)}") + except Exception as e: + print(f"⚠️ 钉钉机器人推送异常: {e}") + + +def push_feishu(webhook_url: str, title: str, content: str) -> None: + """飞书机器人推送(支持加签)""" + if not webhook_url: + return + try: + data: Dict[str, Any] = { + "msg_type": "interactive", + "card": { + "header": { + "title": {"tag": "plain_text", "content": title}, + "template": "blue", + }, + "elements": [{"tag": "markdown", "content": content}], + }, + } + + secret = os.getenv("FEISHU_SECRET", "") + if secret: + timestamp = str(round(time.time())) + string_to_sign = f"{timestamp}\n{secret}" + # 飞书签名:以 string_to_sign 为 key,空字符串为 message + hmac_code = hmac.new( + string_to_sign.encode("utf-8"), + b"", + digestmod=hashlib.sha256, + ).digest() + sign = base64.b64encode(hmac_code).decode("utf-8") + data["timestamp"] = timestamp + data["sign"] = sign + + r = requests.post( + webhook_url, + json=data, + headers={"Content-Type": "application/json"}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("code") == 0: + print("✅ 飞书机器人推送成功") + else: + print(f"⚠️ 飞书机器人推送失败: {resp.get('msg', r.text)}") + except Exception as e: + print(f"⚠️ 飞书机器人推送异常: {e}") + + +def push_wecom_bot(webhook_url: str, title: str, content: str) -> None: + """企业微信机器人推送""" + if not webhook_url: + return + try: + r = requests.post( + webhook_url, + json={ + "msgtype": "markdown", + "markdown": {"content": f"### {title}\n\n{content}"}, + }, + headers={"Content-Type": "application/json"}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("errcode") == 0: + print("✅ 企业微信机器人推送成功") + else: + print(f"⚠️ 企业微信机器人推送失败: {resp.get('errmsg', r.text)}") + except Exception as e: + print(f"⚠️ 企业微信机器人推送异常: {e}") + + +def push_yunhu(token: str, recv_id: str, title: str, content: str) -> None: + """云湖机器人推送""" + if not token or not recv_id: + return + try: + recv_type = os.getenv("YUNHU_RECV_TYPE", "group") + r = requests.post( + "https://chat-go.jwzhd.com/open-apis/v1/bot/send-message", + json={ + "token": token, + "recvId": recv_id, + "recvType": recv_type, + "contentType": 1, + "content": f"**{title}**\n\n{content}", + }, + headers={"Content-Type": "application/json"}, + timeout=TIMEOUT, + ) + resp = safe_json(r) + if r.ok and resp.get("code") == 1: + print("✅ 云湖机器人推送成功") + else: + print(f"⚠️ 云湖机器人推送失败: {resp.get('msg', resp.get('message', r.text))}") + except Exception as e: + print(f"⚠️ 云湖机器人推送异常: {e}") + + +def push_all(title: str, content: str) -> None: + """推送到所有已配置的通知渠道""" + configured = [] + + # PushDeer + key = os.getenv("SENDKEY", "") + if key: + push_deer(key, title, content) + configured.append("PushDeer") + + # Server酱 + key = os.getenv("SERVERCHAN_KEY", "") + if key: + push_serverchan(key, title, content) + configured.append("Server酱") + + # Telegram + bot_token = os.getenv("TG_BOT_TOKEN", "") + chat_id = os.getenv("TG_CHAT_ID", "") if bot_token and chat_id: push_telegram(bot_token, chat_id, title, content) + configured.append("Telegram") + + # PushPlus + token = os.getenv("PUSHPLUS_TOKEN", "") + if token: + push_pushplus(token, title, content) + configured.append("PushPlus") + + # 钉钉机器人 + webhook = os.getenv("DINGTALK_WEBHOOK", "") + if webhook: + push_dingtalk(webhook, title, content) + configured.append("钉钉机器人") + + # 飞书机器人 + webhook = os.getenv("FEISHU_WEBHOOK", "") + if webhook: + push_feishu(webhook, title, content) + configured.append("飞书机器人") + + # 企业微信机器人 + webhook = os.getenv("WECOM_BOT_WEBHOOK", "") + if webhook: + push_wecom_bot(webhook, title, content) + configured.append("企业微信机器人") + + # 云湖机器人 + token = os.getenv("YUNHU_TOKEN", "") + recv_id = os.getenv("YUNHU_RECV_ID", "") + if token and recv_id: + push_yunhu(token, recv_id, title, content) + configured.append("云湖机器人") + + if not configured: + print("⚠️ 未配置任何推送服务,请在 Secrets 中设置至少一种推送渠道") else: - print("⚠️ 未配置 Telegram 推送,请在 Secrets 中配置 TG_BOT_TOKEN 和 TG_CHAT_ID") + print(f"📬 已推送至: {', '.join(configured)}") + + +# ==================== 签到逻辑 ==================== +def classify_checkin(code: int, message: str) -> str: + """ + 判断签到结果: ok / repeat / fail + GLaDOS API: code=0 成功, code=1 已签到, 其他失败 + """ + if code == 0: + return "ok" + if code == 1: + return "repeat" + msg = message.lower() + if "got" in msg: + return "ok" + if any(kw in msg for kw in ("repeat", "already", "重复", "已签到", "签到过", "请勿")): + return "repeat" + return "fail" + + +@retry_on_failure() +def checkin_request(session: requests.Session, headers: Dict[str, str]) -> Dict[str, Any]: + """执行签到请求(带重试)""" + r = session.post(CHECKIN_URL, headers=headers, data=json.dumps(PAYLOAD), timeout=TIMEOUT) + return safe_json(r) + + +@retry_on_failure() +def get_status(session: requests.Session, url: str, headers: Dict[str, str]) -> Dict[str, Any]: + """查询账号状态/积分(带重试)""" + r = session.get(url, headers=headers, timeout=TIMEOUT) + return safe_json(r) + +def checkin_account(session: requests.Session, cookie: str, index: int) -> Dict[str, str]: + """执行单个账号的签到,返回账号信息字典""" + headers = dict(HEADERS_BASE) + headers["cookie"] = cookie + + email = "unknown" + days = "-" + total_points = "-" + earned = 0 + status = "" -def safe_json(resp): try: - return resp.json() - except Exception: - return {} + # 1. 签到 + j = checkin_request(session, headers) + code = j.get("code", -2) + message = j.get("message", "") + earned = j.get("points", 0) or 0 + result = classify_checkin(code, message) + if result == "ok": + status = f"✅ 成功 (+{earned}积分)" + elif result == "repeat": + status = "🔄 已签到" + else: + status = f"❌ 失败({message})" -def main(): - bot_token = os.getenv("TG_BOT_TOKEN", "") - chat_id = os.getenv("TG_CHAT_ID", "") - cookies_env = os.getenv("COOKIES", "") - cookies = [c.strip() for c in cookies_env.split("&") if c.strip()] + # 2. 查询账号状态(剩余天数、邮箱) + try: + s = get_status(session, STATUS_URL, headers) + data = s.get("data") or {} + email = data.get("email", email) + if data.get("leftDays") is not None: + days = f"{int(float(data['leftDays']))} 天" + except Exception: + pass + + # 3. 查询总积分 + try: + p = get_status(session, POINTS_URL, headers) + if p.get("points") is not None: + total_points = f"{int(float(p['points']))} 积分" + except Exception: + pass + + except Exception as e: + status = f"❌ 异常({e})" + + return { + "index": index, + "email": mask_email(email), + "status": status, + "total_points": total_points, + "remaining_days": days, + } + + +# ==================== 主流程 ==================== +def main() -> None: + cookies = [c.strip() for c in os.getenv("COOKIES", "").split("&") if c.strip()] if not cookies: - push_all(bot_token, chat_id, "GLaDOS 签到", "❌ 未检测到 COOKIES") + push_all("GLaDOS 签到", "❌ 未检测到 COOKIES,请配置 GitHub Secrets") return + print(f"📋 检测到 {len(cookies)} 个账号") + + # 验证 Cookie 格式 + for idx, cookie in enumerate(cookies, 1): + is_valid, error_msg = validate_cookie(cookie) + if not is_valid: + print(f"⚠️ 账号 {idx} Cookie 格式异常: {error_msg}") + print(f" Cookie 片段: {mask_cookie(cookie)}") + session = requests.Session() ok = fail = repeat = 0 lines = [] for idx, cookie in enumerate(cookies, 1): - headers = dict(HEADERS_BASE) - headers["cookie"] = cookie + print(f"\n🔄 正在处理账号 {idx}/{len(cookies)}...") + acc = checkin_account(session, cookie, idx) - email = "unknown" - points = "-" - days = "-" - - try: - r = session.post( - CHECKIN_URL, - headers=headers, - data=json.dumps(PAYLOAD), - timeout=TIMEOUT, - ) - - j = safe_json(r) - msg = j.get("message", "") - msg_lower = msg.lower() - - if "got" in msg_lower: - ok += 1 - points = j.get("points", "-") - status = "✅ 成功" - elif "repeat" in msg_lower or "already" in msg_lower: - repeat += 1 - status = "🔁 已签到" - else: - fail += 1 - status = "❌ 失败" - - # 状态接口(允许失败) - s = session.get(STATUS_URL, headers=headers, timeout=TIMEOUT) - sj = safe_json(s).get("data") or {} - email = sj.get("email", email) - if sj.get("leftDays") is not None: - days = f"{int(float(sj['leftDays']))} 天" - - except Exception: + if "✅" in acc["status"]: + ok += 1 + elif "🔄" in acc["status"]: + repeat += 1 + else: fail += 1 - status = "❌ 异常" - lines.append(f"{idx}. {email} | {status} | P:{points} | 剩余:{days}") - time.sleep(random.uniform(1, 2)) + lines.append( + f"{acc['index']}. {acc['email']} | {acc['status']} | " + f"总积分:{acc['total_points']} | 剩余:{acc['remaining_days']}" + ) + + # 非最后一个账号时随机延迟 + if idx < len(cookies): + time.sleep(random.uniform(MIN_DELAY, MAX_DELAY)) - title = f"GLaDOS 签到完成 ✅{ok} ❌{fail} 🔁{repeat}" + title = f"GLaDOS 签到完成 ✅{ok} ❌{fail} 🔄{repeat}" content = "\n".join(lines) + print(f"\n{'='*50}") print(content) - - push_all(bot_token, chat_id, title, content) + print(f"{'='*50}") + + push_all(title, content) if __name__ == "__main__": - main() \ No newline at end of file + main()