sticker_cache.py
1 """ 2 Sticker description cache for Telegram. 3 4 When users send stickers, we describe them via the vision tool and cache 5 the descriptions keyed by file_unique_id so we don't re-analyze the same 6 sticker image on every send. Descriptions are concise (1-2 sentences). 7 8 Cache location: ~/.hermes/sticker_cache.json 9 """ 10 11 import json 12 import time 13 from typing import Optional 14 15 from hermes_cli.config import get_hermes_home 16 17 18 CACHE_PATH = get_hermes_home() / "sticker_cache.json" 19 20 # Vision prompt for describing stickers -- kept concise to save tokens 21 STICKER_VISION_PROMPT = ( 22 "Describe this sticker in 1-2 sentences. Focus on what it depicts -- " 23 "character, action, emotion. Be concise and objective." 24 ) 25 26 27 def _load_cache() -> dict: 28 """Load the sticker cache from disk.""" 29 if CACHE_PATH.exists(): 30 try: 31 return json.loads(CACHE_PATH.read_text(encoding="utf-8")) 32 except (json.JSONDecodeError, OSError): 33 return {} 34 return {} 35 36 37 def _save_cache(cache: dict) -> None: 38 """Save the sticker cache to disk.""" 39 CACHE_PATH.parent.mkdir(parents=True, exist_ok=True) 40 CACHE_PATH.write_text( 41 json.dumps(cache, indent=2, ensure_ascii=False), 42 encoding="utf-8", 43 ) 44 45 46 def get_cached_description(file_unique_id: str) -> Optional[dict]: 47 """ 48 Look up a cached sticker description. 49 50 Returns: 51 dict with keys {description, emoji, set_name, cached_at} or None. 52 """ 53 cache = _load_cache() 54 return cache.get(file_unique_id) 55 56 57 def cache_sticker_description( 58 file_unique_id: str, 59 description: str, 60 emoji: str = "", 61 set_name: str = "", 62 ) -> None: 63 """ 64 Store a sticker description in the cache. 65 66 Args: 67 file_unique_id: Telegram's stable sticker identifier. 68 description: Vision-generated description text. 69 emoji: Associated emoji (e.g. "😀"). 70 set_name: Sticker set name if available. 71 """ 72 cache = _load_cache() 73 cache[file_unique_id] = { 74 "description": description, 75 "emoji": emoji, 76 "set_name": set_name, 77 "cached_at": time.time(), 78 } 79 _save_cache(cache) 80 81 82 def build_sticker_injection( 83 description: str, 84 emoji: str = "", 85 set_name: str = "", 86 ) -> str: 87 """ 88 Build the warm-style injection text for a sticker description. 89 90 Returns a string like: 91 [The user sent a sticker 😀 from "MyPack"~ It shows: "A cat waving" (=^.w.^=)] 92 """ 93 context = "" 94 if set_name and emoji: 95 context = f" {emoji} from \"{set_name}\"" 96 elif emoji: 97 context = f" {emoji}" 98 99 return f"[The user sent a sticker{context}~ It shows: \"{description}\" (=^.w.^=)]" 100 101 102 def build_animated_sticker_injection(emoji: str = "") -> str: 103 """ 104 Build injection text for animated/video stickers we can't analyze. 105 """ 106 if emoji: 107 return ( 108 f"[The user sent an animated sticker {emoji}~ " 109 f"I can't see animated ones yet, but the emoji suggests: {emoji}]" 110 ) 111 return "[The user sent an animated sticker~ I can't see animated ones yet]"