Yiffyi/kaka

★ 2Forks 0GoGitHub ↗Compare

README

卡卡

深圳河套学院考勤提醒机器人。项目背景和需求见 CONTEXT.md。

当前已实现 Go 指令行框架、TOML 运行配置、两个考勤查询接口及 SQLite 查询快照。每次 CLI 查询使用调用方提供的 Cookie,按 --user 指定的键保存快照:门禁查询自动获取全部分页;月度查询保留系统逐周、逐日和汇总结果。成功后保存快照并输出 JSON,日志输出到 stderr。

企业微信已提供常驻单聊指令、后台通知和一次性收发测试,支持提交 Cookie、设置时间及手动查询。wecom serve 启动立即检查,之后默认在每小时 00、05、10…55 分统一逐用户查询和通知,不为用户创建轮询 goroutine;/notify on 开启。以 09:00 上班、18:00 下班为例,日内提醒为 09:00、12:00、18:00,月报为每月 1 日 09:00,进出门记录和 Cookie 失效在每轮检查。主动推送仍需通过 /notify test 完成真实平台验证。补打卡规则待验证,不自行计算缺勤或剩余补打卡次数。完整时间表及失败处理见 通知说明。

构建

另有独立的 Windows Cookie 登录助手,使用 WebView2 打开学院登录入口,登录后可复制 jeesite.session.id 或 /cookie 指令。它在 tools/cookie-login 下独立构建,不依赖下面的主程序和 CGO。

需要 go.mod 指定的 Go 工具链,以及 github.com/mattn/go-sqlite3 所需的 CGO 和 C 编译器。当前 go.mod 为 Go 1.27.1。

Windows PowerShell(使用已安装的 MSYS2 UCRT64 GCC,按本机安装位置调整):

$env:CGO_ENABLED = '1'
$env:CC = 'C:\msys64\ucrt64\bin\gcc.exe'
$env:PATH = 'C:\msys64\ucrt64\bin;' + $env:PATH
go build -trimpath -ldflags '-s -w' -o kaka.exe .

Linux(已安装 GCC):

CGO_ENABLED=1 go build -trimpath -ldflags '-s -w' -o kaka .

以上命令使用 -ldflags '-s -w' 去除符号表和 DWARF 调试信息,减小可执行文件体积;-trimpath 移除编译产物中的本地源码路径。主程序仍需启用 CGO 以支持 SQLite。需要使用调试器时,去掉 -ldflags '-s -w' 后重新构建。

配置

从项目根目录运行。默认读取 config.toml,也可通过 --config 指定路径。

初次配置时将 config.example.toml 复制为 config.toml,填写机器人配置。运行目录和文件位置由部署者自行选择。

base-url = "https://stu.slai.edu.cn"
http-proxy = ""
timeout = "20s"
database = "kaka.db"
log-level = "info"

配置文件不接收 Cookie 或用户账号映射;旧的 [users.*]、cookie、cookie-env 配置需删除,否则启动会报错。机器人用户通过 /cookie 提交凭据;CLI 的 query 和 cookie watch 使用 --cookie 或 KAKA_COOKIE,参数优先。Cookie 失效后需按登录指引获取新链接,或重新提交 Cookie。参数可能出现在终端历史和进程列表中,建议优先使用环境变量。

KAKA_BASE_URL、KAKA_HTTP_PROXY、KAKA_TIMEOUT、KAKA_DATABASE、KAKA_LOG_LEVEL 可覆盖对应配置项;--log-level 优先于环境变量。数据库文件的父目录需要已存在,所有相对路径均相对于当前工作目录。

校外访问时,在 TOML 顶层([wecom] 之前)设置 http-proxy = "http://代理地址:端口"。支持 HTTP/HTTPS 代理地址及 URL 中的用户名、密码;访问 HTTPS 考勤站点时使用标准 CONNECT 隧道,由代理连接校园站点,保持 TLS 证书校验。代理必须能够访问校园系统。留空时直连,不读取系统的 HTTP_PROXY / HTTPS_PROXY / NO_PROXY。

config.toml、默认数据库和构建产物已被 Git 忽略。Cookie 不写入查询快照或日志;数据库包含凭据,查询输出包含用户编号和门禁记录,应保存在自行选择的受保护位置。

查询

Windows(以下 Cookie 为占位符,运行前替换):

$env:KAKA_COOKIE = 'jeesite.session.id=实际值'
.\kaka.exe query swipes --user me --start 2026-09-07 --end 2026-09-07
.\kaka.exe query month --user me --month 2026-09
# 也可以通过参数提供(覆盖环境变量):
.\kaka.exe query month --user me --month 2026-09 --cookie 'jeesite.session.id=实际值'

Linux 使用 ./kaka 执行相同指令。也可在配置好 CGO 的环境中使用 go run . query ...。

  • swipes 的日期范围包含起止考勤日,实际窗口为起日 05:00 至末日次日 05:00(不含结束时刻)。保留所有地点和进出方向;count 为窗口内记录数。
  • month 使用接口的 startMonth 查询对应考勤月,保留上游返回的周范围,不按自然月切掉跨月日期。
  • 成功结果写入配置的 SQLite 数据库。相同用户、查询类型、查询范围的快照会被最新成功结果替换;查询失败保留原快照。
  • fetchedAt 是本次查询完成时间,不是上游数据同步时间。门禁记录的 createDate 也不能作为完整数据覆盖截止时间。
  • 输出中的 isMonthlyQualified、qualificationMessage 等是上游当前快照,当前月含未来日期,不能据此直接宣布最终月度缺勤。

每日有效时长统计

按北京时间 [当日05:00, 次日05:00) 划分考勤日,只累计成功的教学楼进出记录形成的完整区间。/swipes 与时长查询共用相同考勤日窗口,展示其中所有地点的记录。

CLI 查询示例(先设置 KAKA_COOKIE 或添加 --cookie):

.\kaka.exe query duration --user me --date 2026-09-07
.\kaka.exe query duration --user me --date 2026-09-07 --compare

--compare 同时读取该日所属考勤月的系统统计,输出系统秒数、本地减系统的差值,以及 match / mismatch / pending / missing 状态。成功查询写入 duration 快照,接口失败不保存不完整结果。已完成真实对比:2026-09-07 双方均为 38201 秒(10:36:41)。

切换机器人服务到新程序后,可使用 /duration、/duration yesterday 或 /duration 2026-09-07。默认日期是当前考勤日,因此凌晨 05:00 前仍属于前一天;该指令只计算本地时长,不额外请求月度对比。切换时先停止旧机器人服务,再运行:

.\kaka.exe --log-level debug wecom serve

未闭合区间不计入有效时长;当前考勤日仍未检测到出门时,会单独展示“如果仍在教学楼”的估算时长。保留未配对进门提示;不据此代替学院最终资格判断,也不处理请假、申诉、补打卡额度。模块接口和完整计算规则见 docs/attendance-statistics.md。

Cookie 过期时间监测

通过 --cookie 或 KAKA_COOKIE 提供待监测的 Cookie,复用 config.toml 中的考勤代理及请求超时。建议刚更新 Cookie 后启动:

.\kaka.exe cookie watch --interval 5m |
    Tee-Object -FilePath cookie-watch.jsonl

启动后立即检查,之后每次请求完成后等待指定间隔,默认 5 分钟。每次按当前考勤日窗口生成学院接口日期参数,只取第一页、最多一条;不查询全部分页、不写入 SQLite。标准输出为逐行 JSON,Tee-Object 同时显示并保存结果。再次运行会覆盖上面的输出文件,需要保留多次实验时使用不同文件名。

  • state: "valid":HTTP 200 且考勤 JSON 显示查询成功。
  • state: "expired":首次观察到 HTTP 302,记录结果后退出,退出码为 0,表示监测已完成。不会跟随重定向。
  • state: "unknown":网络故障、其他 HTTP 状态或响应异常;不推断 Cookie 过期,保持间隔继续检查。httpStatus 在收到 HTTP 响应时提供,网络失败时省略。

startedAt 是监测开始时间,requestStartedAt / checkedAt 是本次请求开始/完成时间,均保留时间本身的时区偏移;elapsed 是从监测开始到当前检查完成的时长,不是 Cookie 从签发起的总寿命。lastValidAt 保存最后一次成功请求的开始时间,作为保守的有效时间下界。正常单次失效的情况下,过期时刻落在最后一次 lastValidAt 与首次 302 的 checkedAt 之间;轮询间隔、请求耗时和中途故障都会影响精度。如果第一次就是 302,则没有 lastValidAt,无法据此推算此前有效多久。

程序固定使用启动时的 Cookie,不重新读取文件、不采用响应里的 Set-Cookie,不输出 Cookie 或考勤记录。请求本身仍可能刷新服务器端会话,因此测到的是持续访问情况下的有效期;它不能直接证明闲置过期时间。其他设备登录、退出登录或会话撤销也可能造成 302,程序记录该现象而不推断失效原因。

保持终端和电脑运行,Ctrl+C 可中止监测(退出码 1);中止不代表 Cookie 已过期。此指令由用户手动运行,开发测试只使用本地模拟服务。

登录与绑定 Cookie

推荐手机、电脑通用的登录链接方式:用户发送 /cookie,打开机器人回复的文档并按指引获取 https://stu.slai.edu.cn/sso/code?code=… 链接,直接粘贴到当前单聊。机器人自动提取 code,请求学院登录接口,确认 HTTP 302 且 Location 指向学院 /a 入口后,按 Set-Cookie 顺序处理覆盖与删除,获取最后生效的 jeesite.session.id,验证有效后绑定。无需先发送指令;已绑定用户也可直接发送新链接更新登录。失败时保留原有 Cookie,不记录 code、登录链接正文或 Cookie。

在 TOML 顶层配置文档地址(也可用 KAKA_LOGIN_GUIDE_URL):

login-guide-url = "https://doc.weixin.qq.com/smartpage/a1_AXUAEgbmAB0CNNl8GtTWXQDmm01zP?scode=APsA1Qf8AGULXp03q7AXUAEgbmAB0&p=1jigjo"

文档以可点击的 Markdown 链接发送。未配置时提示联系管理员补充。登录接口使用 base-url 和 http-proxy 配置;默认请求学院站点,只转发解析后的 code,不跟随重定向。实际登录及获取链接的步骤由用户按文档完成。

Windows 登录工具

用户在单聊发送 /cookie tool,机器人发送后台准备好的 Windows x64 登录助手。用户打开工具完成学院登录,点击“复制 /cookie 指令”,粘贴回当前单聊即可绑定。没有有效素材时会提示“当前无法上传文件,请联系管理员”,用户请求不会触发上传。

先在 Windows 上运行 tools/cookie-login/build.ps1 构建工具,将生成的 exe 放到部署目录,并在 TOML 顶层配置:

cookie-tool = "kaka-cookie-login.exe"

默认值如上;路径相对于服务工作目录,也可使用绝对路径或 KAKA_COOKIE_TOOL 环境变量。源码目录内运行时可指定 tools/cookie-login/dist/kaka-cookie-login.exe。主程序不嵌入或自动构建工具,Linux 部署也可发送预先构建的 Windows exe。只发送配置指定的文件,用户不能指定服务器路径。

后台启动立即检查,之后每小时检查一次:没有素材或上传已满两天时重新上传;素材三天过期。每次整体失败通知管理员,下一小时重试,连续失败最多 5 次(含首次);达到上限后停止上传,需要修复并重启服务。上传成功清零计数,刷新失败不替换仍有效的旧素材。素材和计数保存在当前进程内,重启后重新上传。

每批最多 10 个分片(单片至多 512 KiB),全部发送后等待 10 秒,按请求 ID 检查回执;只重发未确认分片,最多重发 5 次,仍缺回执则整体失败。明确拒绝或连接错误直接整体失败。普通文件上限 20 MiB。

管理员对话在 [wecom] 中指定:

admin-chat-id = "管理员的 userid 或群聊 chatid"
admin-chat-type = 1 # 1 为单聊,2 为群聊

支持 KAKA_WECOM_ADMIN_CHAT_ID、KAKA_WECOM_ADMIN_CHAT_TYPE。目标会话需先与机器人交互;标识来自企业微信回调。留空则仅记录错误日志,不推送。后台上传失败、用户文件发送失败会通知这里;管理员通知本身失败只记日志,避免递归。普通用户只收到文件不可用提示,不附上传错误详情。管理员配置不赋予额外指令权限。

企业微信指令服务

填写 config.toml 中的 [wecom] 凭据后,从项目根目录运行:

.\kaka.exe --log-level debug wecom serve
# 将日志保存到文件:
.\kaka.exe --log-level debug wecom serve 2> wecom-debug.jsonl

看到 WeCom subscribed; accepting single-chat instructions 后,在企业微信向机器人单聊发送 /help。常驻服务按用户顺序处理指令,不同用户独立处理;网络断开后按 1、2、4 秒递增重连,最长间隔 30 秒,凭据认证被拒或被其他实例替换时退出。Ctrl+C 停止服务。同一 BotID 只能保留一个长连接,不要同时运行 wecom test 和 wecom serve。

指令 作用
/help 显示指令示例
/status 显示最近登录检查、时间设置和通知状态
/chatid 查看自己的单聊会话 ID,可用于管理员对话配置
/cookie 发送手机、电脑通用的登录文档与指引
/cookie tool 发送 Windows Cookie 提取工具和使用说明
/cookie <完整Cookie值> 校验成功后替换自己的凭据
/set 查看时间与提醒日期
/set start 09:00、/set end 18:00 设置上班和计划离校时间(北京时间)
/set days mon,tue,wed,thu,fri、/set days daily 设置个人提醒日期,不代表学院应出勤日历
/notify [on|off|test] 查看或修改通知开关、测试主动推送
/swipes [today|yesterday|YYYY-MM-DD] 查询当前考勤日出入记录(05:00 起)
/duration [today|yesterday|YYYY-MM-DD] 计算教学楼有效时长,默认当前考勤日(05:00 起)
/attendance [current|last|YYYY-MM] 查询考勤月统计,默认本月
/unbind 删除考勤凭据和缓存、关闭通知,保留时间偏好

指令和固定关键词不区分大小写,Cookie 内部的大小写及空格保留。错误参数不会修改原有设置。当前只支持单聊文本,普通聊天只提示使用 /help,群聊不处理。“休息时间”已确认使用下班时间 end,无需 /set rest;当前不支持跨午夜作息。

机器人根据 BotID 与发送者 userid 自动隔离用户,Cookie 保存在配置指定的 SQLite 数据库中。CLI 的 --user 仅标识查询快照,不读取机器人凭据。首次启动通过 goose 自动升级表结构;数据库应存放于自行选择的受保护位置。详细说明见 docs/instructions.md。

企业微信收发测试

在 config.toml 中填写机器人长连接凭据:

[wecom]
bot-id = "你的 BotID"
secret = "你的长连接 Secret"
endpoint = "wss://openws.work.weixin.qq.com"
http-proxy = ""

也可通过 KAKA_WECOM_BOT_ID、KAKA_WECOM_SECRET 提供凭据;KAKA_WECOM_ENDPOINT、KAKA_WECOM_HTTP_PROXY 覆盖连接地址和代理。企业微信代理独立配置,留空直连,不继承考勤代理或系统代理。

.\kaka.exe wecom test --wait 2m

看到 WeCom subscribed 后,在企业微信中向机器人单聊发送 kaka ping。机器人回复“卡卡收发测试成功。”,收到平台成功回执后输出 JSON 并退出:

{"userId":"实际发送者标识","messageId":"实际消息标识","acknowledged":true}

可用 --from-user 限定发送者,--match 修改精确匹配文本,--reply 修改 Markdown 回复。群聊与非匹配消息不会触发回复。默认整个测试最多等待两分钟,连接或单次回执最多等待十秒;失败退出码为 1,成功为 0,Ctrl+C 可中止。

同一机器人只支持一个有效连接,测试连接会替换该机器人的旧连接。此指令不自动重连,不重发未确认的回复,不访问考勤接口或数据库。它验证接收消息及被动回复;主动推送请在 wecom serve 的单聊中发送 /notify test。协议依据、测试范围和联调状态见 docs/wecom-integration.md。

结构

main.go                   入口与退出信号
cmd/                      Cobra 指令、Viper TOML 配置、zerolog、依赖组装
attendance/               两个 HTTP 查询接口及响应类型
attendance/testdata/      脱敏响应样例
wecom/                    AI Bot WebSocket 收发测试与模拟服务测试
interaction/              单聊指令处理、用户设置与中文结果格式化
store/                    mattn/go-sqlite3 快照存储、sqlc 生成代码
store/migrations/         goose SQL 迁移(嵌入程序)
store/queries/            sqlc 查询 SQL
sqlc.json                 sqlc 开发工具配置
docs/integrations.md       接口约定、观察与联调状态
docs/wecom-integration.md  企业微信协议依据与收发测试说明
docs/instructions.md       用户指令与日志、持久化行为说明
docs/wecom/                用户保存的企业微信文档截取

SQL 与迁移

数据库驱动仍为 mattn/go-sqlite3。表结构由 store/migrations 中的 goose 迁移维护;Store.Open 使用实例化的 goose Provider,自动执行尚未应用的 Up 迁移,并在 goose_db_version 中记录版本。迁移嵌入二进制,运行时不依赖源码目录。

业务 SQL 放在 store/queries,sqlc 直接读取同一份 goose 迁移中的 Up 定义,生成 store/db.go、store/models.go 和 store/snapshots.sql.go。快照写入和读取均调用生成的方法,生成文件一并纳入版本管理,不手工修改。

修改查询或增加迁移后,从项目根目录运行:

go generate ./store

等价指令是 go tool sqlc generate -f sqlc.json,sqlc 的版本通过 go.mod 的 tool 指令固定。sqlc 官方支持 YAML/JSON 配置,因此开发工具使用 sqlc.json;应用配置保持 TOML。

后续表结构变化新增按序补零的迁移,例如 00002_add_....sql,分别写明 -- +goose Up 和 -- +goose Down,不要修改已经应用的迁移。打开数据库只自动升级,回退逻辑在临时数据库上测试,不在启动时自动执行 Down。

引入 goose 前的本地缓存没有迁移记录,本项目不提供旧数据库的自动接管或兼容逻辑。切换到新数据库路径并重新查询即可创建新缓存;旧文件可保留供核对。初始迁移使用 CREATE TABLE,已有同名表但无迁移记录时会报错,不会悄悄跳过。

验证

在上述 CGO 编译环境中运行:

go test ./...
go vet ./...
go test -race ./...

测试使用本地 HTTP/WebSocket 服务、代理和临时 SQLite,不访问学院站点或企业微信、不读取本地真实会话。覆盖请求参数与认证头、分页、错误与登录重定向、样例解析、多用户隔离、配置优先级、HTTP 代理及 HTTPS CONNECT、失败保留快照、重启后持久化、goose 升级/回退/重建和 sqlc 快照读写。企业微信测试覆盖认证、JSON 心跳、消息筛选、重复回调、回复及回执关联、拒绝与超时、取消、断线和连接替换,以及仅配置企业微信时的完整 CLI 收发。

真实接口联调状态和待确认字段见 docs/integrations.md。

Contributors

Yiffyi

Issues