#!/usr/bin/env python3 """ Notification module - Sends messages to Discord and Telegram channels. Uses urllib (built-in) instead of requests to avoid SSL/URL parsing issues. """ import json import os import urllib.request import urllib.error import subprocess from typing import Optional, Dict, Any # Load credentials from environment or config file CONFIG_PATH = "/home/ubuntu/.openclaw/workspace/config/notifier.json" def load_config() -> dict: """Load notification configuration.""" if os.path.exists(CONFIG_PATH): with open(CONFIG_PATH) as f: return json.load(f) # Fallback to environment variables return { "discord": { "bot_token": os.getenv("DISCORD_BOT_TOKEN"), "webhook_url": os.getenv("DISCORD_WEBHOOK_URL") }, "telegram": { "bot_token": os.getenv("TELEGRAM_BOT_TOKEN") } } def http_post(url: str, headers: Dict[str, str], data: Dict[str, Any]) -> dict: """Make HTTP POST request using urllib.""" # Add User-Agent to avoid Cloudflare blocks headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' req = urllib.request.Request( url, data=json.dumps(data).encode('utf-8'), headers=headers, method='POST' ) with urllib.request.urlopen(req, timeout=10) as response: return json.loads(response.read().decode('utf-8')) def send_discord_message(channel_id: str, message: str, mention: Optional[str] = None): """Send message to Discord channel via bot API using curl (Cloudflare blocks urllib).""" config = load_config() token = config.get("discord", {}).get("bot_token") if not token: raise ValueError("Discord bot token not configured") url = f"https://discord.com/api/v10/channels/{channel_id}/messages" content = message if mention: content = f"@{mention} {message}" payload = { "content": content, "allowed_mentions": {"parse": []} # Don't parse mentions (use content only) } # Use curl to bypass Cloudflare blocks on urllib cmd = [ "curl", "-s", "-X", "POST", url, "-H", f"Authorization: Bot {token}", "-H", "Content-Type: application/json", "-d", json.dumps(payload) ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) if result.returncode != 0: raise RuntimeError(f"curl failed: {result.stderr}") return json.loads(result.stdout) def send_telegram_message(chat_id: str, message: str, mention: Optional[str] = None): """Send message to Telegram chat via bot API.""" config = load_config() token = config.get("telegram", {}).get("bot_token") if not token: raise ValueError("Telegram bot token not configured") url = f"https://api.telegram.org/bot{token}/sendMessage" text = message if mention: text = f"@{mention} {message}" payload = { "chat_id": chat_id, "text": text, "parse_mode": "Markdown" } headers = {"Content-Type": "application/json"} return http_post(url, headers, payload) def send_notification(channel: str, message: str, mention: Optional[str] = None): """ Send notification to specified channel. Channel format: - discord:123456789 - telegram:123456789 """ if channel.startswith("discord:"): channel_id = channel.replace("discord:", "") return send_discord_message(channel_id, message, mention) elif channel.startswith("telegram:"): chat_id = channel.replace("telegram:", "") return send_telegram_message(chat_id, message, mention) else: raise ValueError(f"Unknown channel format: {channel}") def send_resume_command(channel: str, message: str, mention: Optional[str] = None): """ Send a resume command that will trigger the agent to continue work. This is the same as send_notification but named clearly for intent. """ return send_notification(channel, message, mention) # CLI usage if __name__ == "__main__": import sys if len(sys.argv) < 3: print("Usage: notify.py [mention]") print("Example: notify.py discord:123456789 'Hello world' taro83") sys.exit(1) channel = sys.argv[1] message = sys.argv[2] mention = sys.argv[3] if len(sys.argv) > 3 else None result = send_notification(channel, message, mention) print(f"Sent: {result}")