diff --git a/bot.py b/bot.py index 15b0540..795efdc 100644 --- a/bot.py +++ b/bot.py @@ -14,6 +14,8 @@ from pathlib import Path from dotenv import load_dotenv import logging from urllib.parse import urlparse +import subprocess +import sys # Load .env file load_dotenv() @@ -157,54 +159,83 @@ def fetch_url_content(url): logger.error(f" āŒ Error: {e}") return {"title": "Fetch failed", "status": "error", "error": str(e), "content": ""} -# Analyze with OpenClaw gateway (Haiku) -def analyze_with_gateway(url, title, content): - """Send to OpenClaw gateway for AI analysis""" - logger.debug(f" šŸ¤– Analyzing with gateway: {url}") +# Analyze content with AI (Haiku via gateway) +def analyze_content(url, title, content, link_type): + """Analyze content and create intelligent summary""" + logger.debug(f" šŸ¤– Analyzing content: {url}") # Build analysis prompt - prompt = f"""Analyze this webpage briefly (2-3 sentences max): + analysis_prompt = f"""Analyze this link and create a brief summary useful for Laurent. -URL: {url} -Title: {title} +**Link**: {link_type} - {title} +**URL**: {url} -Content excerpt: -{content[:1000]} +**Content (first 1500 chars)**: +{content[:1500]} -Provide: -1. What is this about? (1 sentence) -2. Who should read this? (optional) -3. Suggested Tududi tag (e.g., "to-read", "learning", "inspiration", "tool") +--- -Keep it concise!""" +Respond in JSON format ONLY (no markdown, no explanation): +{{ + "summary": "1-2 sentences max: What is it? Why would Laurent find it useful?", + "tag": "one of: to-read, tool, inspiration, learning, reference, interesting, project, tutorial, article, code, security", + "relevance": "very-relevant OR relevant OR nice-to-have" +}} + +Be concise and practical.""" try: - response = requests.post( - f"{GATEWAY_URL}/v1/messages", - headers={"Authorization": f"Bearer {GATEWAY_TOKEN}"} if GATEWAY_TOKEN else {}, - json={ - "messages": [{"role": "user", "content": prompt}], - "model": "openrouter/anthropic/claude-haiku-4.5", - "max_tokens": 150 - }, - timeout=10 + # Use OpenClaw CLI to invoke sessions_spawn + # This spawns a sub-agent that analyzes the content + result = subprocess.run( + [ + sys.executable, "-m", "openclaw", + "sessions", "spawn", + "--task", analysis_prompt, + "--model", "openrouter/anthropic/claude-haiku-4.5", + "--thinking", "off", + "--timeout", "15" + ], + capture_output=True, + text=True, + timeout=20 ) - if response.status_code == 200: - result = response.json() - analysis = result.get("content", [{}])[0].get("text", "") - logger.debug(f" āœ“ Analysis complete") - return analysis + if result.returncode == 0: + output = result.stdout + logger.debug(f" Sub-agent response: {output[:200]}") + + # Try to parse JSON + try: + json_match = re.search(r'\{[^{}]*"summary"[^{}]*\}', output, re.DOTALL) + if json_match: + analysis_data = json.loads(json_match.group()) + logger.debug(f" āœ“ Analysis parsed successfully") + return analysis_data + except json.JSONDecodeError: + pass + + # Fallback: extract summary from text + summary_line = output.split('\n')[0][:200] + return { + "summary": summary_line, + "tag": "interesting", + "relevance": "relevant" + } else: - logger.warning(f" Gateway error: {response.status_code}") + logger.warning(f" Sub-agent error: {result.stderr[:200]}") return None + + except subprocess.TimeoutExpired: + logger.warning(f" Analysis timeout") + return None except Exception as e: - logger.warning(f" Gateway timeout/error: {e}") + logger.warning(f" Analysis error: {e}") return None # Send to Tududi inbox -def add_to_tududi(title, url, link_type, analysis=""): - """Add to Tududi inbox with summary""" +def add_to_tududi(title, url, link_type, summary="", tag=""): + """Add to Tududi inbox with intelligent summary""" logger.debug(f" šŸ“Œ Adding to Tududi: {title}") try: @@ -212,9 +243,14 @@ def add_to_tududi(title, url, link_type, analysis=""): logger.warning(" TUDUDI_API_KEY not set") return False + # Format the inbox content content = f"šŸ“Œ **{link_type}**: {title}\nšŸ”— {url}" - if analysis: - content += f"\n\nšŸ’” Summary:\n{analysis}" + + if summary: + content += f"\n\nšŸ’” **Summary**:\n{summary}" + + if tag: + content += f"\n\nšŸ·ļø **Tag**: {tag}" response = requests.post( f"{TUDUDI_API_URL}/inbox", @@ -226,8 +262,8 @@ def add_to_tududi(title, url, link_type, analysis=""): timeout=5 ) - if response.status_code == 200: - logger.info(f" āœ“ Added to Tududi inbox") + if response.status_code in [200, 201]: # 200 or 201 are both OK + logger.info(f" āœ“ Added to Tududi inbox with tag: {tag}") return True else: logger.warning(f" Tududi error: {response.status_code}") @@ -278,24 +314,27 @@ class LinkAnalyzerBot(discord.Client): fetch_result = fetch_url_content(url) title = fetch_result["title"] - # Analyze with gateway (disabled for now - no valid endpoint) - analysis = None - # if fetch_result["status"] == "ok": - # analysis = analyze_with_gateway(url, title, fetch_result.get("content", "")) + # Analyze content if fetch was successful + analysis_data = None + if fetch_result["status"] == "ok": + analysis_data = analyze_content(url, title, fetch_result.get("content", ""), link_type) - # Add to Tududi - tududi_ok = add_to_tududi(title, url, link_type, analysis or "") + # Prepare summary for Tududi + summary_text = "" + tag = "interesting" + if analysis_data: + summary_text = analysis_data.get("summary", "") + tag = analysis_data.get("tag", "interesting") + + # Add to Tududi with summary + tududi_ok = add_to_tududi(title, url, link_type, summary_text, tag) # Format response for Discord response_text = f"šŸ“Œ **{link_type}**: {title}" - if fetch_result.get("description"): - # Add description if available - desc = fetch_result["description"][:150] - response_text += f"\nšŸ“ {desc}" - if analysis: - # Truncate to 200 chars for Discord - summary = analysis[:200].split('\n')[0] - response_text += f"\nšŸ’” {summary}" + if summary_text: + response_text += f"\n\nšŸ’” {summary_text}" + if tag: + response_text += f"\n\nšŸ·ļø Tag: `{tag}`" logger.debug(f"Posting response: {response_text}")