Skill: wjsoj/pku-portal (version 1.1.0)

--- name: portal description: "北京大学校内信息门户 (portal.pku.edu.cn / its.pku.edu.cn) CLI 工具。当用户提及 portal、校内信息门户、空闲教室、空教室、教室查询、一教/二教/三教/四教/理教空闲、校历、学年校历、上课时间、网费、网费余额、网费充值、上网账号、查余额、微信充值网费、支付宝充值网费 时使用此 skill。也支持 `portal netfee watch --threshold` 做低余额监测（低于阈值退出码 2，适合 cron 报警）。Also use when dealing with `/publicQuery/classroomQuery/retrClassRoomFree.do`、simso 校历 Vue bundle、its.pku.edu.cn `/cas/webLogin` / `/cas/ITSweb` / `/myConn.jsp` / `/netportal/itsUtil?operation=info`、或 `/paybank/user.PayBankOrderPKU` → cwsf.pku.edu.cn [REDACTED_SECRET] 3 步充值流程。" version: 1.1.0 --- # portal - 北大校内信息门户 CLI A CLI client for PKU's campus info portal — 空闲教室、校历、网费 in one command. ## Architecture - **Crate location**: `crates/portal/` - **Auth flow**: - 空闲教室 / 校历：**无需登录** - 网费：its.pku.edu.cn 自己的上网账号密码（**不是** IAAA SSO），复用 `pkuinfo_common::credential`（多数学生上网密码与 IAAA 相同） - **API**: - 空闲教室：`GET /publicQuery/classroomQuery/retrClassRoomFree.do?buildingName=<中文>&time=<中文>`，返回 `{success, rows:[{room, cap, c1..c12}]}` - 校历：simso.pku.edu.cn 是 Vue SPA，数据硬编码在 `js/ccSchoolCalendar.<hash>.js` bundle 里。**真正的 `_v("...")` 文本节点都在 webpack render 函数里，不在 `{name:"Calendar…"` 元数据块里**。simso 开发者经常原地复用老 .vue 文件改内容、不更新内部 `name:`，所以按内部命名匹配会拿错学年。正确做法：tab pane label → Home `Calendar<tag>:<var>` → `<var>=<mod>.exports` → `<mod>=Object(o["a"])(<data>,<render>,…)` → 在 `<render>` 函数体内按 `"第一学期"===t.xq?[` / `"第二学期"===t.xq?[` 切分上/下学期 - 网费状态：`POST /cas/webLogin` → `/netportal/itsUtil?operation=info`（HTML 表含余额）→ `POST /cas/ITSweb cmd=select` → `GET /myConn.jsp`（在线 IP 会话） - 网费充值：3 步 `pkuConfirm` → `pkuSendOrder` → cwsf.pku.edu.cn 收银台 AJAX [REDACTED_SECRET] 返回 `{data:{urlCode}}` - **验证码**：复用 `pkuinfo_common::captcha`（manual / utool 免费 / ttshitu / yunma） ## Key Source Files - `src/main.rs` — tokio::main 调用 `pku_portal::run()` - `src/lib.rs` — Clap CLI + dispatch - `src/client.rs` — reqwest client (simple 和 with cookies 两档) - `src/freeclassroom.rs` — 空闲教室 API + 渲染 - `src/calendar.rs` — simso Vue bundle 解析 - `src/netfee.rs` — ITS 登录 / 余额 / 会话 / 3 步充值 / 付款码 QR 渲染 ## CLI Commands | Command | 用途 | |---------|-----| | `free-classroom <building> [-d today\|tomorrow\|day-after]` / `fc` | 查询空闲教室。building: 一教/二教/三教/四教/理教/文史/哲学/地学/国关/政管 | | `calendar [-y 2025-2026] [-s first\|second\|all]` / `cal` | 显示校历。`--semester` 可选 first/second/all（也接受 1/2/上/下） | | `netfee status` | 余额 + 在线会话 | | `netfee recharge <amount> [-m wechat\|alipay] [--captcha utool]` | 充值，终端打印微信/支付宝付款二维码 | | `netfee watch --threshold 10` | 低余额监测，余额 < 阈值返回退出码 2 | ## Auto-Login for AI Agents ```bash info-auth check portal free-classroom 一教 # 无需登录 portal calendar --year 2025-2026 --semester first # 无需登录 portal netfee status # 需要 keyring 凭据 portal netfee recharge 10 -m wechat # 微信扫码付款，二维码直接打终端 portal netfee watch -t 5 || notify "PKU netfee low" ``` ## Development Notes - 所有文案中文；错误 `anyhow::Result` + `.context("...")` - its 的 `/paybank/user.PayBankOrderPKU` 响应页**内嵌的** `alert("验证码不能为空！")` 是 JS 函数体里的 literal，不是真正的错误。只匹配独立 `<script>alert("...");history.back();</script>` 才是真错误 - step1 `paytype=""` 即可；真正的 `payType=02`（微信）/`01`（支付宝）是 step3 收银台 AJAX 的字段 - step2 响应会被重定向到 `cwsf.pku.edu.cn` 域，step3 要用那个域的 origin 而不是 its.pku.edu.cn - 微信付款二维码用 `qrcode` crate 的 `Dense1x2` 在终端渲染；兜底打印原始 `urlCode` 字符串 - Session 持久化 `~/.config/info/portal/` - 校历是 best-effort：simso 改版 JS 编译方式就会解析失败，错误信息里会提示用户


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)