auto-save 2026-05-09 17:52 (~3)

This commit is contained in:
2026-05-09 17:52:25 +08:00
parent 4dc1b245b7
commit d0b83a4969
3 changed files with 24 additions and 20 deletions

View File

@@ -1,19 +1,5 @@
{ {
"entries": [ "entries": [
{
"files_changed": 1,
"hash": "d8b5e3a",
"message": "auto-save 2026-05-07 15:26 (~1)",
"ts": "2026-05-07T15:26:53+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "a6a644e",
"message": "auto-save 2026-05-07 15:32 (~1)",
"ts": "2026-05-07T15:34:35+08:00",
"type": "commit"
},
{ {
"files_changed": 1, "files_changed": 1,
"hash": "d8dcef7", "hash": "d8dcef7",
@@ -3474,6 +3460,19 @@
"type": "session-heartbeat", "type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 master · 1 项未提交变更 · 最近提交auto-save 2026-05-09 17:41 (~1)", "message": "Codex 会话活跃 · 最近命令codex · 分支 master · 1 项未提交变更 · 最近提交auto-save 2026-05-09 17:41 (~1)",
"files_changed": 1 "files_changed": 1
},
{
"ts": "2026-05-09T17:46:54+08:00",
"type": "commit",
"message": "auto-save 2026-05-09 17:46 (~1)",
"hash": "4dc1b24",
"files_changed": 1
},
{
"ts": "2026-05-09T09:48:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 master · 1 项未提交变更 · 最近提交auto-save 2026-05-09 17:46 (~1)",
"files_changed": 1
} }
] ]
} }

View File

@@ -18,6 +18,7 @@
2. 事件订阅里添加请求地址:默认应用用 `https://hermes.kang-kang.com/feishu/events`;其它应用用 `https://hermes.kang-kang.com/feishu/events/{app_id}` 2. 事件订阅里添加请求地址:默认应用用 `https://hermes.kang-kang.com/feishu/events`;其它应用用 `https://hermes.kang-kang.com/feishu/events/{app_id}`
3. 如果启用事件加密,需要先给本服务补充解密支持;当前版本按明文事件回调处理。 3. 如果启用事件加密,需要先给本服务补充解密支持;当前版本按明文事件回调处理。
4. 建议配置 `FEISHU_VERIFICATION_TOKEN`,并保持和飞书后台一致。 4. 建议配置 `FEISHU_VERIFICATION_TOKEN`,并保持和飞书后台一致。
5. 如果飞书后台刚切换过 token`FEISHU_VERIFICATION_TOKEN` 可临时用英文逗号配置多个值,兼容旧事件投递和新 URL 校验。
## 主动通知示例 ## 主动通知示例

View File

@@ -37,6 +37,10 @@ def _csv_set(value: str) -> set[str]:
return {item.strip() for item in value.split(",") if item.strip()} return {item.strip() for item in value.split(",") if item.strip()}
def _csv_list(value: str) -> list[str]:
return [item.strip() for item in value.split(",") if item.strip()]
def _load_feishu_apps() -> dict[str, dict[str, Any]]: def _load_feishu_apps() -> dict[str, dict[str, Any]]:
apps: dict[str, dict[str, Any]] = {} apps: dict[str, dict[str, Any]] = {}
for suffix in ["", *[f"_{idx}" for idx in range(2, 10)]]: for suffix in ["", *[f"_{idx}" for idx in range(2, 10)]]:
@@ -47,7 +51,7 @@ def _load_feishu_apps() -> dict[str, dict[str, Any]]:
apps[app_id] = { apps[app_id] = {
"app_id": app_id, "app_id": app_id,
"app_secret": app_secret, "app_secret": app_secret,
"verification_token": _env(f"FEISHU_VERIFICATION_TOKEN{suffix}"), "verification_tokens": _csv_list(_env(f"FEISHU_VERIFICATION_TOKEN{suffix}")),
"default_receive_id": _env(f"FEISHU_DEFAULT_RECEIVE_ID{suffix}"), "default_receive_id": _env(f"FEISHU_DEFAULT_RECEIVE_ID{suffix}"),
"default_receive_id_type": _env( "default_receive_id_type": _env(
f"FEISHU_DEFAULT_RECEIVE_ID_TYPE{suffix}", f"FEISHU_DEFAULT_RECEIVE_ID_TYPE{suffix}",
@@ -188,17 +192,17 @@ def token_digest(value: str) -> str:
def verify_callback_token(body: dict[str, Any], app_id: str) -> bool: def verify_callback_token(body: dict[str, Any], app_id: str) -> bool:
app = Config.feishu_apps.get(app_id, {}) app = Config.feishu_apps.get(app_id, {})
expected = app.get("verification_token", "") expected_tokens = app.get("verification_tokens", [])
if not expected: if not expected_tokens:
return True return True
token = callback_token(body) token = callback_token(body)
ok = token == expected ok = token in expected_tokens
if not ok: if not ok:
logging.warning( logging.warning(
"invalid Feishu verification token app_id=%s got=%s expected=%s body_keys=%s header_keys=%s event_keys=%s", "invalid Feishu verification token app_id=%s got=%s expected_any=%s body_keys=%s header_keys=%s event_keys=%s",
app_id, app_id,
token_digest(token), token_digest(token),
token_digest(expected), [token_digest(item) for item in expected_tokens],
sorted(body.keys()), sorted(body.keys()),
sorted(body.get("header", {}).keys()) if isinstance(body.get("header"), dict) else [], sorted(body.get("header", {}).keys()) if isinstance(body.get("header"), dict) else [],
sorted(body.get("event", {}).keys()) if isinstance(body.get("event"), dict) else [], sorted(body.get("event", {}).keys()) if isinstance(body.get("event"), dict) else [],