Skip to content

feat(chat): hydrate fromUser/toUser via users/show?userIds (notedeck#460)#8

Merged
hitalin merged 1 commit into
mainfrom
feat/chat-hydrate-users-460
May 9, 2026
Merged

feat(chat): hydrate fromUser/toUser via users/show?userIds (notedeck#460)#8
hitalin merged 1 commit into
mainfrom
feat/chat-hydrate-users-460

Conversation

@hitalin
Copy link
Copy Markdown
Owner

@hitalin hitalin commented May 9, 2026

Summary

Misskey 本家の chat 系 REST/WS は Lite packer 固定で `fromUser` / `toUser` を含まない (リクエストパラメータでも切替不可)。`chat/history` だけが Detailed packer で両方を含む。

本 PR は notecli の API client が `users/show?userIds=[...]` で透過的に hydrate するようにする。

Why

notedeck #460 のチャット履歴キャッシュで「ユーザー名/アイコンが表示できない user」が出ていた。実測で chat_messages_cache 4453 行中 2909 行 (65%) が `fromUser` null。timeline 経由で大量に prefetch された結果、Misskey の Lite packer 仕様 (本家) によって user 情報が抜け落ちている。

経路 packer `fromUser` `toUser`
`chat/history` (REST) Detailed
`chat/messages/user-timeline` (REST) Lite ❌ null ❌ null
`chat/messages/room-timeline` (REST) Lite (room なので無し)
`chat:message` (WS, 1on1) Lite ❌ null ❌ null
`chat:message` (WS, room) Lite (room なので無し)

Changes

`src/api.rs`

  • 新規 public: `MisskeyClient::get_users_bulk(host, token, &[String]) -> Vec`
    • `users/show?userIds=[...]` で複数 user を 1 リクエストにまとめて取得
    • 空配列入力なら API を叩かず即 `Ok(vec![])`
  • 新規 (pub crate): `MisskeyClient::hydrate_chat_message_users(host, token, &mut [ChatMessage])`
    • messages から null になっている fromUser/toUser の userId を `HashSet` で集約
    • bulk fetch して in-place 埋める
    • fetch 失敗は `tracing::warn` で済ませて null のまま継続 (best-effort)
  • 改修: `get_chat_user_messages` / `get_chat_room_messages` の deserialize 直後に hydrate 呼び出し
  • `get_chat_history` は変更なし — Detailed packer で既に user が入ってるので重複 fetch 回避

`src/streaming.rs`

  • `connection_task` / `run_ws_session` / `ws_loop` / `handle_ws_message` のシグネチャに `api_client` / `account_host` / `account_token` を伝搬
  • subscription info の `host` (String) と function arg の `host` (&str) が同 scope で shadow されるのを避けるため、function arg を `account_host` / `account_token` に命名
  • `chat:message` ブランチで deserialize 直後に `api_client.hydrate_chat_message_users(...)` を呼ぶ。`std::slice::from_mut(&mut msg)` で 1 件 hydrate。WS は通常 1 件単位なので、bulk と言えど id 1〜2 個程度

Test plan

新規ユニットテスト (wiremock):

  • `get_users_bulk_returns_users` — 2 user を返すモックで bulk 取得が成功
  • `get_users_bulk_empty_input_returns_empty_without_request` — 空配列で API を叩かない
  • `get_chat_user_messages_hydrates_null_users` — timeline が null user → hydrate 後に from/toUser が埋まる
  • `hydrate_keeps_existing_user_and_skips_show` — 既存 non-null user → users/show が呼ばれない (`expect(0)`)
  • `hydrate_swallows_users_show_error` — users/show が 500 → null のまま panic せず messages 返る

```
cargo test --lib # 136 passed (5 新規)
cargo fmt --check
cargo clippy --no-default-features -- -D warnings # 私の変更範囲は warning ゼロ
```

Note

main の CI は本 PR 以前から既存の lint 違反 (`db.rs:1089` ほか) で fail 状態。
本 PR の変更範囲では fmt / clippy / test 全パスしている。既存 lint 違反は別 PR で扱う。

Related

🤖 Generated with Claude Code

…460)

Misskey 本家の `chat/messages/{user|room}-timeline` REST と `chat:message` WS
は Lite packer 固定で `fromUser` / `toUser` を含まない (Lite packer の仕様で
リクエストパラメータでも切替不可)。`chat/history` だけが Detailed packer で
両方を含む。これにより notedeck では timeline 経由で取得した chat messages
の name/avatar が表示できない不具合があった (実測 65% の cache 行が null)。

本 commit で notecli の API client が透過的に hydrate するように修正:

- `MisskeyClient::get_users_bulk`: `users/show?userIds=[...]` で複数 user を
  1 リクエストで bulk 取得 (新規 public API)
- `MisskeyClient::hydrate_chat_message_users`: messages から null になっている
  fromUser/toUser を集めて bulk fetch → in-place 埋める private helper。
  fetch 失敗は warn ログで済ませて null のまま継続 (best-effort)
- `get_chat_user_messages` / `get_chat_room_messages` の deserialize 直後に
  hydrate 呼び出しを追加。`get_chat_history` は Detailed packer なので不要
- WS streaming (`handle_ws_message` の chat:message 分岐) でも hydrate。
  `connection_task` / `run_ws_session` / `ws_loop` / `handle_ws_message` の
  シグネチャに `api_client` / `account_host` / `account_token` を伝搬。
  shadowed `host` 変数 (subscription info の host) との衝突を回避する命名

これで notedeck 側は Cargo.toml の rev を更新するだけで、既存 cache の null
行も次回 prefetch / loadOlder / WS event の UPSERT で自動上書きされて治る。

テスト: api.rs に wiremock ベースで 5 件追加 (bulk fetch / hydrate /
existing user skip / 500 error swallowing / timeline + hydrate 統合)。
@hitalin hitalin self-assigned this May 9, 2026
@hitalin hitalin merged commit 091b5e7 into main May 9, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant