# Badge Fetching Design (TODO-065) ## Purpose Fetch, cache, and display TeamSpeak client badges. Badges are visual indicators (icons) shown next to client names in the UI. ## Protocol Details **Source:** `badges-content.teamspeak.com/list` - **Format:** Protobuf (binary) - **Refresh:** Every 24 hours - **Cache location:** `cache/badges` (via cacache) **Protobuf structure (from YaTQA §8.9):** 1. `BigNum`: revision number 2. `BigNum`: Unix timestamp 3. Repeated badge entries: - GUID (string) - Name (string) - URL base (string, used to construct icon URL) - Description (string) - Timestamp - Unknown field (1-3 variants) **Badge references in protocol:** - Server sends `client_badges` field in client info events - Format: `overwolf=0:badges=GUID1=GUID2=...` - Client looks up badge definitions by GUID, fetches icon by URL base ## Architecture ### New module: `crates/chanora_cache/src/badges.rs` ``` badges.rs ├── BadgeMeta { guid, name, url_base, description } ├── BadgeCache │ ├── fetch_badge_list() -> Vec │ ├── get_icon(guid) -> Option │ ├── refresh_if_stale() │ └── metadata: RwLock> ``` ### Extend `chanora_cache/src/lib.rs` - Add `PREFIX_BADGE: &str = "bg_"` for badge icon blobs - Update `validate_key()` to accept badge prefix (GUID format: 8-4-4-4-12 hex) ## Data Flow ``` 1. On connect → server sends client_badges (GUIDs) 2. BadgeCache.refresh_if_stale(): - Check last_refresh timestamp - If >24h: fetch badges-content.teamspeak.com/list - Parse Protobuf → Vec - Update metadata map 3. For each GUID in client_badges: - Lookup BadgeMeta by GUID - Fetch icon from url_base (HTTP GET) - Cache icon blob in BlobCache (PREFIX_BADGE + guid) - Return icon bytes to UI 4. Flutter UI displays badge icons in: - Client info panel (profile) - Chat message sender badges ``` ## Dependencies | Crate | Status | Purpose | |-------|--------|---------| | `reqwest` | Already in `chanora_protocol` | HTTP fetch | | `prost` | **Add** | Protobuf parsing | | `cacache` | Already in `chanora_cache` | Blob storage | | `tokio` | Already | Async runtime | **Cargo.toml additions for `chanora_cache`:** ```toml reqwest = { version = "0.13", default-features = false, features = ["rustls-tls"] } prost = "0.13" ``` ## Integration Points | Component | Integration | |-----------|-------------| | `chanora_protocol/src/adapter.rs` | Parse `client_badges` from `notifyclientupdated` events | | `chanora_bridge` | Expose `get_badge_icon(guid)` and `get_client_badges(client_id)` to Flutter | | Flutter UI | Display badge icons in `ClientInfoPanel` and `ChatMessage` widgets | ## Files to Create/Modify - **Create:** `crates/chanora_cache/src/badges.rs` - **Modify:** `crates/chanora_cache/src/lib.rs` (add `PREFIX_BADGE`, export module) - **Modify:** `crates/chanora_cache/Cargo.toml` (add reqwest, prost) - **Modify:** `crates/chanora_protocol/src/adapter.rs` (parse client_badges) - **Modify:** `crates/chanora_bridge/src/` (expose badge API to Flutter) ## Error Handling - **Network failure:** Log warning, serve stale cache if available, retry on next refresh - **Protobuf parse error:** Log error, keep previous badge data, alert user "badges unavailable" - **Icon fetch failure:** Cache miss, show placeholder or skip badge display ## Effort Estimate **L (3-5 days)** - Day 1: Protobuf schema research, add prost dependency, parse badge list - Day 2: BadgeCache implementation, icon fetch and caching - Day 3: Protocol adapter integration (parse client_badges) - Day 4: Flutter bridge API, UI display - Day 5: Testing, error handling, edge cases ## Open Questions 1. **Protobuf schema:** Need to define `.proto` file or use dynamic parsing. Prost requires compile-time schema — may need to reverse-engineer from reference or use `protobuf` crate for runtime parsing. 2. **Icon URL construction:** Exact URL pattern for badge icons needs verification (likely `https://badges-content.teamspeak.com/{url_base}.png`). 3. **Offline mode:** Should we ship a static badge list as fallback? (YAGNI for now)