mitekk/arbox-schedule

GitHub: mitekk/arbox-schedule

为 Arbox 健身房会员打造的自动化课程预订机器人,在注册开放时自动抢课、管理候补名单并发送邮件通知。

Stars: 0 | Forks: 0

# 📅 arbox-schedule **为 [Arbox](https://www.arboxapp.com/) 健身房打造的免手动操作课程预订工具** — _预订 · 候补 · 确认 · 通知_ [![License](https://img.shields.io/badge/License-none-lightgrey)](#license) ![Node](https://img.shields.io/badge/Node-22-7c3aed?logo=nodedotjs&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-5-3178c6?logo=typescript&logoColor=white) ![node-cron](https://img.shields.io/badge/node--cron-4-1a7f37?logo=clockify&logoColor=white) ![Resend](https://img.shields.io/badge/Resend-email-000000?logo=resend&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-ready-2496ed?logo=docker&logoColor=white) ![Vitest](https://img.shields.io/badge/Vitest-4-6e9f18?logo=vitest&logoColor=white) 一个小巧的 TypeScript 机器人,在注册开放的那一刻为您预订首选的 Arbox 课程;在课程满员时自动加入 候补名单,并在候补名额过期前自动确认——每一个步骤都会通过邮件 通知您。它作为一个长期运行的调度器运行,包含两个 cron 任务以及一个用于 一键取消预订的轻量级 HTTP 服务器。
- **探索** — [项目简介](#what-it-is) · [工作原理](#how-it-works) · [快速开始](#quick-start) · [架构](#architecture) · [技术栈](#tech-stack) · [配置](#configuration) · [测试](#testing) · [部署](#deployment) · [文档与决策](#docs--decisions) ## 项目简介 Arbox 会在每周五晚上开放下一周的预订。`arbox-schedule` 自动化了这场 抢课竞争,让您无需一直守在手机旁: - **周五 21:00(以色列时间)** — 预订任务会获取下周的课表,按系列 ID 顺序(优先级排序)查找您的首选 课程,并最多预订 **2 节课**。如果课程已经 满员,它会加入候补名单,并将记录持久化到本地状态中。 - **每 5 分钟** — 候补任务会轮询处于开放状态的候补记录。当 Arbox 释放名额(通过 设置 `availability_id`)时,机器人会立即确认预订并通过邮件通知您。过期 的记录将被清理。 - **每个步骤均发送邮件** 通过 [Resend](https://resend.com) — 每次预订运行后发送摘要,并在 候补名额被确认、丢失或过期时发送单独的提醒。 - **一键取消链接** — 预订邮件中包含一个带有 HMAC 签名的 `/cancel` URL,您可以直接在收件箱中 取消预订的课程。 ## 工作原理 ### 预订任务(周五) 针对下周(周日至周六),按您的系列 ID 优先顺序进行遍历——首先使用首选列表, 其次是备选列表——每周最多预订 **2 节课**。对于每个候选课程: | 课程状态 | 操作 | |---|---| | 已预订 / 在候补名单中 | 计入名额,并跳过 | | 有空位 (`free > 0`) | 立即预订 | | 课程已满 (`free == 0`) | 加入候补,并将记录写入 `state.json` | ### 候补确认 当已预订的用户取消时,Arbox 会通过在日程项中设置 `availability_id` 并发送通知,将排在候补名单第一位的人递补上去——随后用户有 **约 30 分钟** 的时间进行确认。 候补任务每 5 分钟轮询一次;当它检测到被追踪记录的 `availability_id` 不为空时,就会 使用该 ID 调用 `scheduleUser/insert` 进行确认。如果该 ID 已经过期,错误将被 记录下来,并在下一个周期重试该记录。 ### 取消链接与手动触发 调度器还运行一个轻量级的 HTTP 服务器(默认端口 `3000`),暴露以下接口: | 端点 | 用途 | |---|---| | `GET /cancel?token=…` | 通过预订邮件中嵌入的签名链接取消预订 (HMAC-SHA256, 有效期约 8 天) | | `POST /standby/run` | 按需触发候补任务 (`202 started`,或 `409 already-running`) | 只有在设置了 `CANCEL_SECRET` 和 `BASE_URL` 时才会生成取消链接。 ## 快速开始 需要 **Node 22+**(或 Docker)。安装、配置后,运行调度器: ``` npm install cp .env.example .env # then fill in the values below npm start # ts-node schedule/scheduler.ts — runs until killed ``` 启动时会打印已注册的任务和端点: ``` Booking job: every Friday at 21:00 Israel time (0 21 * * 5) Standby job: every 5 minutes (*/5 * * * *) Endpoints: POST /standby/run, GET /cancel?token=... HTTP server: listening on port 3000 ``` **获取您的 ID** — 如果您不知道您的 `BOX_ID`、`LOCATION_ID` 或 `MEMBERSHIP_ID`,只需设置您的 凭据并运行辅助工具: ``` npx ts-node scripts/discover-ids.ts # prints all three ``` **查找系列 ID** — 每节循环课程都属于一个稳定的系列(例如“HIIT,周日,08:10”)。 导出下周的课表以及每节课的 `series_fk`: ``` npx ts-node scripts/debug-schedule.ts ``` **立即触发预订任务**(对测试很有用): ``` npx ts-node scripts/trigger-booking.ts ``` ## 架构 单个长期运行的 Node 进程注册了两个 cron 任务和一个 HTTP 服务器。所有状态都保存在一个 JSON 文件中;唯一的出站依赖是 Arbox API 和 Resend。 ``` flowchart LR Cron["node-cron
Fri 21:00 · every 5 min"] HTTP["HTTP server :3000
/cancel · /standby/run"] Sched["scheduler.ts"] Arbox["Arbox API v2"] Resend["Resend (email)"] State[("state.json")] Cron --> Sched HTTP --> Sched Sched -- "book / standby / cancel" --> Arbox Sched -- "notify" --> Resend Sched -- "persist standby" --> State ``` ``` schedule/ scheduler.ts # entry point — registers both cron jobs + HTTP server booking.ts # Friday booking logic standby.ts # standby polling and auto-confirmation cancel.ts # HTTP server: signed /cancel links + /standby/run state.ts # JSON-based state persistence (standby list) config.ts # loads and validates env vars notify.ts # email notifications via Resend utils.ts # date helpers api/ client.ts # Arbox HTTP client (reverse-engineered v2 API) requests/ # auth · user · boxes · schedule calls types/ # response shapes scripts/ discover-ids.ts # one-off: prints BOX_ID, LOCATION_ID, MEMBERSHIP_ID trigger-booking.ts # manually trigger the booking job debug-schedule.ts # dump next-week schedule + test a booking attempt docs/ api.md # reverse-engineered Arbox API v2 reference ``` 状态会被写入工作目录下的 `state.json` 中(可通过 `STATE_FILE` 覆盖)。它会 追踪您正在候补的课程——这是唯一持久化的运行时产物。 ## 技术栈 | 层级 | 技术 | |---|---| | 运行时 | Node 22, TypeScript 5 (通过 `ts-node` 运行) | | 调度器 | [node-cron](https://www.npmjs.com/package/node-cron) (`Asia/Jerusalem` 时区) | | HTTP 服务器 | Node `http` (无框架) | | 邮件 | [Resend](https://resend.com) | | 配置 | `dotenv` | | 测试 | [Vitest](https://vitest.dev) | | 代码检查 / 格式化 | ESLint + Prettier (Husky pre-commit) | | 打包 | Docker (`node:22-alpine`) | ## 配置 将 `.env.example` 复制为 `.env` 并填入相应的值: | 变量 | 描述 | |---|---| | `ARBOX_EMAIL` | 您的 Arbox 登录邮箱 | | `ARBOX_PASSWORD` | 您的 Arbox 密码 | | `BOX_ID` | 您健身房 (box) 的数字 ID | | `LOCATION_ID` | 健身房位置的数字 ID (`locations_box` ID) | | `MEMBERSHIP_ID` | 您的有效会员记录 ID(预订时必填) | | `PRIMARY_SERIES_IDS` | 逗号分隔的系列 ID — 优先预订,按顺序 (例如 `76644881,76647404`) | | `SECONDARY_SERIES_IDS` | 逗号分隔的备选系列 ID — 在首选名额满员时使用 | | `RESEND_API_KEY` | 来自 [resend.com](https://resend.com) 的 API 密钥 | | `NOTIFICATION_EMAIL` | 接收预订通知的邮箱地址 | | `CANCEL_SECRET` | _(可选)_ 用于签名 `/cancel` 链接的 HMAC 密钥 | | `BASE_URL` | _(可选)_ 用于构建 `/cancel` 链接的公共基础 URL | | `PORT` | _(可选)_ HTTP 服务器端口 (默认为 `3000`) | | `STATE_FILE` | _(可选)_ 状态 JSON 文件的路径 (默认为 `./state.json`) | 在开发期间,Resend 会使用 `onboarding@resend.dev` 发送邮件,该地址只能向您的 Resend 验证邮箱发送邮件。一旦您拥有了验证过的发送域,请在 [`schedule/notify.ts`](schedule/notify.ts) 中更新 `from` 字段。 **邮件通知:** | 事件 | 主题 | |---|---| | 周五预订任务完成 | `Arbox booking — N of 2 lessons scheduled` | | 候补名额确认 | `✅ Standby confirmed` | | 候补位置丢失 | `❌ Standby slot lost` | | 候补记录过期(已超时) | `ℹ️ Standby expired` | ## 测试 ``` npm run typecheck # tsc --noEmit npm run lint # ESLint npm run format # Prettier (write) npm test # Vitest unit tests ``` 测试代码位于 [`tests/`](tests/) 中,涵盖了预订、候补、取消以及针对模拟 Arbox API 响应的 HTTP 服务器测试。 ## 部署 本仓库附带了一个 `Dockerfile`,可直接用于 [Coolify](https://coolify.io/) 或任何兼容 Docker 的主机: ``` docker build -t arbox-schedule . docker run -d \ --env-file .env \ -v $(pwd)/state.json:/app/state.json \ arbox-schedule ``` 容器启动时会运行 `ts-node schedule/scheduler.ts`。请在 `STATE_FILE` 路径(默认为 `/app/state.json`)挂载持久化存储卷,以便在重启后保留候补状态。在 Coolify 上:推送 仓库,让其自动检测 `Dockerfile`,设置环境变量,并挂载状态存储卷。 ## 文档与决策 - **Arbox API v2 参考:** [`docs/api.md`](docs/api.md) — 认证流程、请求/响应格式、错误 代码,以及一些不直观的行为(`has_spots` 与 `free` 的区别,候补确认流程)。 - **设计规范:** [`docs/superpowers/specs/2026-04-25-lesson-scheduler-design.md`](docs/superpowers/specs/2026-04-25-lesson-scheduler-design.md) - **实施计划:** [`docs/superpowers/plans/2026-04-25-lesson-scheduler.md`](docs/superpowers/plans/2026-04-25-lesson-scheduler.md) ## 许可证 本仓库目前没有许可证文件——默认保留所有权利。请添加一个 `LICENSE` 文件以明确复用条款。
标签:Docker, TypeScript, 安全插件, 安全防御评估, 定时任务, 自动化攻击, 自动化机器人, 自动预约, 请求拦截