mirror of
https://github.com/HChaZZY/NodeSeek-Signin.git
synced 2025-12-06 11:33:49 +08:00
Compare commits
4 Commits
e5edbf2813
...
dac0b5ee66
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dac0b5ee66 | ||
|
|
8ff20b8f85 | ||
|
|
166337713d | ||
|
|
9df3e7c8ec |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.git
|
||||
.github
|
||||
.venv
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
README.md
|
||||
35
.env.example
Normal file
35
.env.example
Normal file
@@ -0,0 +1,35 @@
|
||||
# 账户配置
|
||||
USER1=your_username1
|
||||
PASS1=your_password1
|
||||
# USER2=your_username2
|
||||
# PASS2=your_password2
|
||||
|
||||
# --- 验证码配置 (二选一) ---
|
||||
|
||||
# 方案A: 自建 CloudFreed 服务
|
||||
SOLVER_TYPE=turnstile
|
||||
API_BASE_URL=http://your_cloudflare_service_ip:3000
|
||||
CLIENTT_KEY=your_cloudfreed_client_key
|
||||
|
||||
# 方案B: YesCaptcha 服务
|
||||
# SOLVER_TYPE=yescaptcha
|
||||
# # YesCaptcha 国内节点: https://cn.yescaptcha.com, 国际节点: https://api.yescaptcha.com
|
||||
# API_BASE_URL=https://api.yescaptcha.com
|
||||
# CLIENTT_KEY=your_yescaptcha_client_key
|
||||
|
||||
# 功能配置
|
||||
# NS_RANDOM=true
|
||||
|
||||
# 每日运行时间配置
|
||||
# 支持固定时间 (例如 '09:00') 或随机时间范围 (例如 '08:00-10:59')
|
||||
# 如果不设置,默认为 '08:00-10:59'
|
||||
# RUN_AT=09:00
|
||||
RUN_AT=08:00-10:59
|
||||
|
||||
# 通知配置
|
||||
# TG_BOT_TOKEN=
|
||||
# TG_USER_ID=
|
||||
# TG_THREAD_ID=
|
||||
|
||||
# 模拟Docker环境,用于本地测试
|
||||
# IN_DOCKER=true
|
||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Python virtual environment
|
||||
.venv/
|
||||
|
||||
# Python cache
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Sensitive configuration
|
||||
.env
|
||||
|
||||
# Cookie data files
|
||||
cookie/*
|
||||
!cookie/.gitkeep
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE/Editor configuration
|
||||
.vscode/
|
||||
.idea/
|
||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# 使用轻量级的 Python 基础镜像
|
||||
FROM python:3.9-alpine
|
||||
|
||||
# 设置时区为 GMT+8
|
||||
RUN apk add --no-cache tzdata ca-certificates
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制 requirements.txt 并安装依赖
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制所有 .py 文件到工作目录
|
||||
COPY *.py ./
|
||||
|
||||
# 设置默认启动命令
|
||||
CMD ["python", "scheduler.py"]
|
||||
104
README.md
104
README.md
@@ -9,15 +9,19 @@
|
||||
|
||||
</div>
|
||||
|
||||
[Deepflood论坛签到](https://github.com/yowiv/deepflood-Signin)
|
||||
|
||||
|
||||
## 📝 项目介绍
|
||||
|
||||
这是一个用于 NodeSeek 论坛自动签到的工具,支持通过 GitHub Actions 或青龙面板进行定时自动签到操作。签到模式默认为随机签到,帮助用户轻松获取论坛每日"鸡腿"奖励。
|
||||
这是一个用于 NodeSeek 论坛自动签到的工具,支持通过 GitHub Actions、青龙面板或 Docker Compose 进行定时自动签到操作。签到模式默认为随机签到,帮助用户轻松获取论坛每日"鸡腿"奖励。
|
||||
|
||||
|
||||
## ✨ 功能特点
|
||||
|
||||
- 📅 支持 GitHub Actions 自动运行
|
||||
- 🦉 支持青龙面板定时任务
|
||||
- 🐳 支持 Docker Compose 一键部署
|
||||
- 🍪 支持 Cookie 或账号密码登录方式
|
||||
- 👥 支持多账号批量签到
|
||||
- 🔐 支持多种验证码解决方案
|
||||
@@ -55,7 +59,60 @@ ql repo https://github.com/yowiv/NodeSeek-Signin.git
|
||||
|
||||
然后在环境变量中添加所需配置。
|
||||
|
||||
### 方式三:账号密码登录(自动获取新Cookie)
|
||||
### 方式三:Docker Compose
|
||||
|
||||
这种部署方式可以实现本地自动化定时签到,并支持自动处理验证码。
|
||||
|
||||
**第一步:克隆项目**
|
||||
|
||||
首先,将整个项目克隆到你的服务器上:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/yowiv/NodeSeek-Signin.git
|
||||
cd NodeSeek-Signin
|
||||
```
|
||||
|
||||
**第二步:配置环境变量**
|
||||
|
||||
将 `.env.example` 文件复制或重命名为 `.env`,然后编辑 `.env` 文件,填入你的配置信息。
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
你需要根据注释提示,填写以下关键信息:
|
||||
- **账户信息**: `USER1`, `PASS1`, `USER2`, `PASS2` 等。
|
||||
- **验证码服务**: 如果使用账号密码登录,必须配置验证码服务。推荐使用 `yescaptcha`,并填入 `CLIENTT_KEY`。
|
||||
- **定时任务**: `RUN_AT` 变量用于设置签到任务的执行时间。
|
||||
- **固定时间**: 如 `10:30`,表示每天上午10点30分执行。
|
||||
- **时间范围**: 如 `10:00-18:00`,表示在每天上午10点到下午6点之间随机选择一个时间点执行。
|
||||
- **默认值**: 如果不设置,默认为 `09:00-21:00`。
|
||||
|
||||
**第三步:启动服务**
|
||||
|
||||
在存放 `docker-compose.yml` 和 `.env` 文件的目录下,执行以下命令在后台构建并启动服务:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
**第四步:查看日志**
|
||||
|
||||
你可以使用以下命令实时查看容器的日志,以确认服务是否正常运行和签到是否成功:
|
||||
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
**第五步:停止服务**
|
||||
|
||||
如果需要停止并移除服务容器,可以执行以下命令:
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### 方式四:账号密码登录(自动获取新Cookie)
|
||||
|
||||
当 Cookie 失效时,系统会尝试使用账号密码方式登录并获取新的 Cookie。登录需要通过验证码验证,支持以下两种验证码解决方案:
|
||||
|
||||
@@ -171,6 +228,7 @@ PASS3=密码3
|
||||
| `USER1`、`USER2`... | 可选 | - | NodeSeek 论坛用户名,当 Cookie 失效时使用 |
|
||||
| `PASS1`、`PASS2`... | 可选 | - | NodeSeek 论坛密码 |
|
||||
| `NS_RANDOM` | 可选 | true | 是否随机签到(true/false) |
|
||||
| `RUN_AT` | 可选 | `09:00-21:00` | **仅Docker Compose可用**。设置定时任务执行时间,支持固定时间 `10:30` 或时间范围 `10:00-18:00` |
|
||||
| `SOLVER_TYPE` | 可选 | turnstile | 验证码解决方案(turnstile/yescaptcha) |
|
||||
| `API_BASE_URL` | 条件必需 | - | CloudFreed 服务地址,当 SOLVER_TYPE=turnstile 时必填 |
|
||||
| `CLIENTT_KEY` | 必需 | - | 验证码服务客户端密钥 |
|
||||
@@ -187,3 +245,45 @@ PASS3=密码3
|
||||
## ⚠️ 免责声明
|
||||
|
||||
本项目仅供学习交流使用,请遵守 NodeSeek 论坛的相关规定和条款。
|
||||
|
||||
## 👨💻 本地开发与测试
|
||||
|
||||
为了方便在本地进行开发和调试,项目提供了一套零侵入的测试方案。你可以按照以下步骤在本地环境中运行签到脚本。
|
||||
|
||||
### 第一步:创建虚拟环境
|
||||
|
||||
首先,建议创建一个独立的 Python 虚拟环境,以避免依赖冲突。
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
# Windows
|
||||
.venv\Scripts\activate
|
||||
# macOS / Linux
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
### 第二步:安装依赖
|
||||
|
||||
项目包含两份依赖文件:`requirements.txt` 用于生产环境,`requirements-dev.txt` 包含本地测试所需的额外库。
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
### 第三步:配置环境变量
|
||||
|
||||
将环境变量示例文件 `.env.example` 复制一份并重命名为 `.env`,然后根据你的需求填入测试配置,例如账号密码和验证码服务密钥。
|
||||
|
||||
```bash
|
||||
cp .env.example .env```
|
||||
|
||||
### 第四步:运行测试
|
||||
|
||||
配置完成后,执行以下命令即可运行一次性的签到测试。
|
||||
|
||||
```bash
|
||||
python test_run.py
|
||||
```
|
||||
|
||||
该脚本会自动加载 `.env` 文件中的环境变量,并执行主签到程序,让你可以在本地快速验证签到逻辑是否正常工作。
|
||||
|
||||
0
cookie/.gitkeep
Normal file
0
cookie/.gitkeep
Normal file
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
nodeseek-signin:
|
||||
build: .
|
||||
image: nodeseek-signin:latest
|
||||
container_name: nodeseek-signin
|
||||
command: ["python", "scheduler.py"]
|
||||
environment:
|
||||
- IN_DOCKER=true
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./cookie:/app/cookie
|
||||
restart: always
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
from curl_cffi import requests
|
||||
@@ -20,6 +19,10 @@ except ImportError:
|
||||
# ---------------- 环境检测函数 ----------------
|
||||
def detect_environment():
|
||||
"""检测当前运行环境"""
|
||||
# 优先检测是否在 Docker 环境中
|
||||
if os.environ.get("IN_DOCKER") == "true":
|
||||
return "docker"
|
||||
|
||||
# 检测是否在青龙环境中
|
||||
ql_path_markers = ['/ql/data/', '/ql/config/', '/ql/', '/.ql/']
|
||||
in_ql_env = False
|
||||
@@ -138,12 +141,31 @@ def save_cookie_to_ql(var_name: str, cookie: str):
|
||||
print(f"青龙面板环境变量操作异常: {str(e)}")
|
||||
return False
|
||||
|
||||
# ---------------- Docker Cookie 文件保存 ----------------
|
||||
COOKIE_FILE_PATH = "./cookie/NS_COOKIE.txt"
|
||||
|
||||
def save_cookie_to_file(cookie_str: str):
|
||||
"""将Cookie保存到文件"""
|
||||
try:
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(COOKIE_FILE_PATH), exist_ok=True)
|
||||
with open(COOKIE_FILE_PATH, "w") as f:
|
||||
f.write(cookie_str)
|
||||
print(f"Cookie 已成功保存到文件: {COOKIE_FILE_PATH}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存Cookie到文件失败: {e}")
|
||||
return False
|
||||
|
||||
# ---------------- 统一变量保存函数 ----------------
|
||||
def save_cookie(var_name: str, cookie: str):
|
||||
"""根据当前环境保存Cookie到相应位置"""
|
||||
env_type = detect_environment()
|
||||
|
||||
if env_type == "qinglong":
|
||||
if env_type == "docker":
|
||||
print("检测到Docker环境,保存Cookie到文件...")
|
||||
return save_cookie_to_file(cookie)
|
||||
elif env_type == "qinglong":
|
||||
print("检测到青龙环境,保存变量到青龙面板...")
|
||||
return save_cookie_to_ql(var_name, cookie)
|
||||
elif env_type == "github":
|
||||
@@ -393,7 +415,21 @@ if __name__ == "__main__":
|
||||
break
|
||||
|
||||
# 读取现有Cookie
|
||||
all_cookies = os.getenv("NS_COOKIE", "")
|
||||
all_cookies = ""
|
||||
if detect_environment() == "docker":
|
||||
print(f"Docker环境,尝试从 {COOKIE_FILE_PATH} 读取Cookie...")
|
||||
if os.path.exists(COOKIE_FILE_PATH):
|
||||
try:
|
||||
with open(COOKIE_FILE_PATH, "r") as f:
|
||||
all_cookies = f.read().strip()
|
||||
print("成功从文件加载Cookie。")
|
||||
except Exception as e:
|
||||
print(f"从文件读取Cookie失败: {e}")
|
||||
else:
|
||||
print("Cookie文件不存在,将使用空Cookie。")
|
||||
else:
|
||||
all_cookies = os.getenv("NS_COOKIE", "")
|
||||
|
||||
cookie_list = all_cookies.split("&")
|
||||
cookie_list = [c.strip() for c in cookie_list if c.strip()]
|
||||
|
||||
|
||||
1
requirements-dev.txt
Normal file
1
requirements-dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
python-dotenv
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
curl_cffi
|
||||
requests
|
||||
tzdata
|
||||
125
scheduler.py
Normal file
125
scheduler.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import random
|
||||
import subprocess
|
||||
import re
|
||||
from datetime import timezone, timedelta
|
||||
|
||||
# 测试程序时使用
|
||||
# from dotenv import load_dotenv
|
||||
# load_dotenv()
|
||||
|
||||
GMT8 = timezone(timedelta(hours=8))
|
||||
|
||||
def get_run_config():
|
||||
"""
|
||||
从环境变量 RUN_AT 读取并解析运行时间配置。
|
||||
- 'HH:MM': 固定时间
|
||||
- 'HH:MM-HH:MM': 随机时间范围
|
||||
- 未设置或格式错误: 默认为 '08:00-10:59'
|
||||
返回一个元组 (mode, value)
|
||||
"""
|
||||
run_at_env = os.environ.get('RUN_AT', '08:00-10:59')
|
||||
|
||||
if re.fullmatch(r'\d{2}:\d{2}', run_at_env):
|
||||
print(f"检测到固定时间模式: {run_at_env}", flush=True)
|
||||
return 'fixed', run_at_env
|
||||
|
||||
if re.fullmatch(r'\d{2}:\d{2}-\d{2}:\d{2}', run_at_env):
|
||||
print(f"检测到随机时间范围模式: {run_at_env}", flush=True)
|
||||
return 'range', run_at_env
|
||||
|
||||
if os.environ.get('RUN_AT'):
|
||||
print(f"警告: 环境变量 RUN_AT 的格式 '{run_at_env}' 无效。", flush=True)
|
||||
|
||||
print("将使用默认随机时间范围 '08:00-10:59'。", flush=True)
|
||||
return 'range', '08:00-10:59'
|
||||
|
||||
def calculate_next_run_time(mode, value):
|
||||
"""
|
||||
根据当前时间和配置计算下一次运行的 datetime 对象。
|
||||
智能寻找下一个可用的运行时间点(今天或明天),并使用 GMT+8 时区。
|
||||
"""
|
||||
now = datetime.datetime.now(GMT8)
|
||||
|
||||
if mode == 'fixed':
|
||||
h, m = map(int, value.split(':'))
|
||||
next_run_attempt = now.replace(hour=h, minute=m, second=0, microsecond=0)
|
||||
if next_run_attempt > now:
|
||||
return next_run_attempt
|
||||
else:
|
||||
return next_run_attempt + datetime.timedelta(days=1)
|
||||
|
||||
elif mode == 'range':
|
||||
start_str, end_str = value.split('-')
|
||||
start_h, start_m = map(int, start_str.split(':'))
|
||||
end_h, end_m = map(int, end_str.split(':'))
|
||||
|
||||
start_time = datetime.time(start_h, start_m)
|
||||
end_time = datetime.time(end_h, end_m)
|
||||
|
||||
start_today = now.replace(hour=start_h, minute=start_m, second=0, microsecond=0)
|
||||
|
||||
if now < start_today:
|
||||
target_date = now.date()
|
||||
else:
|
||||
target_date = now.date() + datetime.timedelta(days=1)
|
||||
|
||||
start_target = datetime.datetime.combine(target_date, start_time, tzinfo=GMT8)
|
||||
end_target = datetime.datetime.combine(target_date, end_time, tzinfo=GMT8)
|
||||
|
||||
if start_target > end_target:
|
||||
end_target += datetime.timedelta(days=1)
|
||||
|
||||
start_timestamp = int(start_target.timestamp())
|
||||
end_timestamp = int(end_target.timestamp())
|
||||
|
||||
random_timestamp = random.randint(start_timestamp, end_timestamp)
|
||||
return datetime.datetime.fromtimestamp(random_timestamp, tz=GMT8)
|
||||
|
||||
def run_checkin_task():
|
||||
"""
|
||||
执行 nodeseek_sign.py 脚本。
|
||||
"""
|
||||
print(f"[{datetime.datetime.now(GMT8).strftime('%Y-%m-%d %H:%M:%S')}] 开始执行签到任务...", flush=True)
|
||||
try:
|
||||
subprocess.run([sys.executable, "nodeseek_sign.py"], check=True)
|
||||
print(f"[{datetime.datetime.now(GMT8).strftime('%Y-%m-%d %H:%M:%S')}] 签到任务执行完毕。", flush=True)
|
||||
except FileNotFoundError:
|
||||
print("错误: 'nodeseek_sign.py' 未找到。请确保它与 scheduler.py 位于同一目录。", flush=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"签到任务执行失败,返回码: {e.returncode}", flush=True)
|
||||
except Exception as e:
|
||||
print(f"执行签到任务时发生未知错误: {e}", flush=True)
|
||||
|
||||
def main():
|
||||
"""
|
||||
主调度循环。
|
||||
"""
|
||||
print("调度器启动...", flush=True)
|
||||
mode, value = get_run_config()
|
||||
print(f"调度模式: '{mode}', 配置值: '{value}'", flush=True)
|
||||
|
||||
# run_checkin_task() # 启动时执行,用于测试程序
|
||||
|
||||
while True:
|
||||
next_run_time = calculate_next_run_time(mode, value)
|
||||
now = datetime.datetime.now(GMT8)
|
||||
sleep_duration = (next_run_time - now).total_seconds()
|
||||
|
||||
if sleep_duration > 0:
|
||||
print(f"下一次签到任务计划在: {next_run_time.strftime('%Y-%m-%d %H:%M:%S')}", flush=True)
|
||||
hours, remainder = divmod(sleep_duration, 3600)
|
||||
minutes, _ = divmod(remainder, 60)
|
||||
print(f"程序将休眠 {int(hours)} 小时 {int(minutes)} 分钟。", flush=True)
|
||||
time.sleep(sleep_duration)
|
||||
else:
|
||||
print("计算出的下一个运行时间已过,等待 60 秒后重试...", flush=True)
|
||||
time.sleep(60)
|
||||
|
||||
run_checkin_task()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
14
test_run.py
Normal file
14
test_run.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
import subprocess
|
||||
from dotenv import load_dotenv
|
||||
|
||||
def main():
|
||||
"""
|
||||
加载 .env 文件并执行签到脚本
|
||||
"""
|
||||
load_dotenv()
|
||||
print(".env 文件已加载,正在准备执行签到脚本...")
|
||||
subprocess.run([sys.executable, "nodeseek_sign.py"])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user