Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from bundle
Telegram MTProto MCP server with userbot watcher, chat/DM parser and context builders
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
context_builder_llm.py
1"""2context_builder_llm.py — LLM-саммари чатов и лично известных людей.34Логика:5- Чаты: саммари чата + краткое досье на ВСЕХ участников сразу (один блок)6- Люди: подробный профиль только если есть личка (chat_type=private)7"""89import json10import subprocess11import time12from pathlib import Path13from collections import defaultdict14from datetime import datetime, timezone1516CONTEXT_DIR = Path(__file__).parent / "context"17MESSAGES_FILE = CONTEXT_DIR / "messages.jsonl"18CHATS_DIR = CONTEXT_DIR / "chats"19PEOPLE_DIR = CONTEXT_DIR / "people"2021CHATS_DIR.mkdir(parents=True, exist_ok=True)22PEOPLE_DIR.mkdir(parents=True, exist_ok=True)2324OWNER_ID = int(os.getenv("TELEGRAM_OWNER_ID", "0"))25MAX_MSGS = 8026PAUSE = 8272829def ask_claude(prompt: str) -> str:30time.sleep(PAUSE)31result = subprocess.run(32["openclaw", "agent", "--agent", "main", "--message", prompt, "--json"],33capture_output=True, text=True, timeout=30034)35try:36data = json.loads(result.stdout)37return data["result"]["payloads"][0]["text"]38except Exception:39return result.stdout.strip() or "ошибка"404142def fmt_msgs(msgs: list) -> str:43lines = []44for m in msgs[-MAX_MSGS:]:45d = "→" if m.get("direction") == "out" else "←"46name = m.get("sender_name", "?")47text = (m.get("text") or "").strip()48if not text:49continue50ts = m.get("ts", "")[:10]51reply = f" [↩ {m['reply_to_text'][:40]}]" if m.get("reply_to_text") else ""52lines.append(f"{d} {name} [{ts}]: {text}{reply}")53return "\n".join(lines)545556def load_messages():57if not MESSAGES_FILE.exists():58return []59msgs = []60with open(MESSAGES_FILE, encoding="utf-8") as f:61for line in f:62try:63msgs.append(json.loads(line))64except Exception:65pass66return msgs676869def main():70print("[llm_builder] Загружаю...")71msgs = load_messages()72if not msgs:73print("[llm_builder] Нет сообщений.")74return75print(f"[llm_builder] {len(msgs)} записей")7677# Группируем по чатам78chats = defaultdict(lambda: {"title": "", "type": "", "msgs": []})79for m in msgs:80cid = m.get("chat_id")81chats[cid]["title"] = m.get("chat_title", str(cid))82chats[cid]["type"] = m.get("chat_type", "")83chats[cid]["msgs"].append(m)8485# Находим личные чаты (не с собой)86private_chat_ids = set()87for cid, info in chats.items():88if info["type"] == "private" and cid != OWNER_ID:89private_chat_ids.add(cid)9091updated = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")9293# Саммари групповых чатов94for cid, info in chats.items():95if info["type"] != "group":96continue97if len(info["msgs"]) < 5:98continue99100print(f"[llm_builder] Чат: {info['title']}")101text = fmt_msgs(info["msgs"])102103# Собираем участников (не owner)104participants = defaultdict(list)105for m in info["msgs"]:106sid = m.get("sender_id")107if sid and sid != OWNER_ID:108name = m.get("sender_name", str(sid))109t = (m.get("text") or "").strip()110if t:111participants[name].append(t[:100])112113# Краткие характеристики участников114people_summary = ""115if participants:116people_lines = []117for name, texts in participants.items():118sample = " / ".join(texts[:3])119people_lines.append(f"- {name}: «{sample}»")120people_summary = "\n".join(people_lines)121122prompt = f"""Чат "{info['title']}" (тип: {info['type']}). Последние сообщения (→ outgoing (owner), ← incoming):123124{text}125126Участники и примеры их сообщений:127{people_summary}128129Дай структурированное резюме:1301. О чём этот чат и какова его атмосфера (2-3 предложения)1312. Краткое досье на каждого участника (1 строка: имя — кто такой, роль в чате)1323. Актуальные темы на сейчас133134Максимум 200 слов, без лишней воды."""135136summary = ask_claude(prompt)137138chat_file = CHATS_DIR / f"{cid}.md"139existing = chat_file.read_text(encoding="utf-8") if chat_file.exists() else ""140if "## LLM Анализ" in existing:141existing = existing[:existing.index("## LLM Анализ")]142chat_file.write_text(143existing.rstrip() + f"\n\n## LLM Анализ\n*{updated}*\n\n{summary}\n",144encoding="utf-8"145)146147# Профили людей только из личек148for cid, info in chats.items():149if cid not in private_chat_ids:150continue151if len(info["msgs"]) < 3:152continue153154# Находим собеседника155other_name = ""156other_id = cid # для личек chat_id = user_id собеседника157for m in info["msgs"]:158sid = m.get("sender_id")159if sid and sid != OWNER_ID:160other_name = m.get("sender_name", str(cid))161other_id = sid162break163164if not other_name:165continue166167print(f"[llm_builder] Личка: {other_name}")168text = fmt_msgs(info["msgs"])169170prompt = f"""Личная переписка between owner (→) и {other_name} (←):171172{text}173174Составь подробный профиль {other_name}:1751. Кто этот человек — возраст, характер, роль in owner's life1762. Как они общаются, какие темы1773. Что сейчас происходит у этого человека1784. Relationship with owner — близость, история, эмоциональный тон179180Максимум 150 слов."""181182summary = ask_claude(prompt)183184person_file = PEOPLE_DIR / f"{other_id}.md"185existing = person_file.read_text(encoding="utf-8") if person_file.exists() else ""186if "## LLM Анализ" in existing:187existing = existing[:existing.index("## LLM Анализ")]188person_file.write_text(189existing.rstrip() + f"\n\n## LLM Анализ (личка)\n*{updated}*\n\n{summary}\n",190encoding="utf-8"191)192193print("[llm_builder] Готово!")194195196if __name__ == "__main__":197main()198