diff options
| author | Fuwn <[email protected]> | 2025-09-20 22:10:38 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-09-20 22:10:38 -0700 |
| commit | e7ae17f31cc05279fa300c340587fc807af7b872 (patch) | |
| tree | 4975ccaa7999870a750d141d26195b63ba1b1626 /src | |
| parent | feat(irm): Update post removal message subject (diff) | |
| download | umabot-e7ae17f31cc05279fa300c340587fc807af7b872.tar.xz umabot-e7ae17f31cc05279fa300c340587fc807af7b872.zip | |
feat(irm): Discord logging
Diffstat (limited to 'src')
| -rw-r--r-- | src/umabot/config.py | 6 | ||||
| -rw-r--r-- | src/umabot/discord_client.py | 165 | ||||
| -rw-r--r-- | src/umabot/rules/intelligent_roleplay_moderator.py | 39 |
3 files changed, 210 insertions, 0 deletions
diff --git a/src/umabot/config.py b/src/umabot/config.py index 6d8ebdc..2d87ee1 100644 --- a/src/umabot/config.py +++ b/src/umabot/config.py @@ -39,6 +39,10 @@ class Config: # Mod mail reasoning level (0=none, 1=brief, 2=full) reasoning_level: int = 2 + # Discord webhook configuration + discord_webhook_url: Optional[str] = None + discord_log_channel_id: Optional[str] = None + @classmethod def from_env(cls) -> "Config": """Create configuration from environment variables.""" @@ -61,6 +65,8 @@ class Config: roleplay_limit_window_hours=int(os.getenv("ROLEPLAY_LIMIT_WINDOW_HOURS", "24")), dry_run=os.getenv("DRY_RUN", "false").lower() == "true", reasoning_level=int(os.getenv("REASONING_LEVEL", "2")), + discord_webhook_url=os.getenv("DISCORD_WEBHOOK_URL"), + discord_log_channel_id=os.getenv("DISCORD_LOG_CHANNEL_ID"), ) def validate(self) -> None: diff --git a/src/umabot/discord_client.py b/src/umabot/discord_client.py new file mode 100644 index 0000000..e700936 --- /dev/null +++ b/src/umabot/discord_client.py @@ -0,0 +1,165 @@ +"""Discord webhook client for logging moderation actions.""" + +import requests +import json +from typing import Optional +from loguru import logger + + +class DiscordWebhookClient: + """Client for sending messages to Discord via webhooks.""" + + def __init__(self, webhook_url: str, channel_id: Optional[str] = None): + """Initialize the Discord webhook client. + + Args: + webhook_url: The Discord webhook URL + channel_id: Optional channel ID to override the webhook's default channel + """ + self.webhook_url = webhook_url + self.channel_id = channel_id + self.logger = logger.bind(component="DiscordWebhookClient") + + def send_message(self, content: str, username: str = "UmaBot", avatar_url: Optional[str] = None) -> bool: + """Send a message to Discord. + + Args: + content: The message content + username: The username to display (defaults to UmaBot) + avatar_url: Optional avatar URL + + Returns: + bool: True if successful, False otherwise + """ + try: + payload = { + "content": content, + "username": username + } + + if avatar_url: + payload["avatar_url"] = avatar_url + + if self.channel_id: + payload["channel_id"] = self.channel_id + + response = requests.post( + self.webhook_url, + data=json.dumps(payload), + headers={"Content-Type": "application/json"}, + timeout=10 + ) + + if response.status_code == 204: + self.logger.debug("Discord message sent successfully") + return True + else: + self.logger.error(f"Failed to send Discord message: {response.status_code} - {response.text}") + return False + + except Exception as e: + self.logger.error(f"Error sending Discord message: {e}") + return False + + def send_embed(self, embed: dict, username: str = "UmaBot", avatar_url: Optional[str] = None) -> bool: + """Send an embed message to Discord. + + Args: + embed: The embed object + username: The username to display (defaults to UmaBot) + avatar_url: Optional avatar URL + + Returns: + bool: True if successful, False otherwise + """ + try: + payload = { + "embeds": [embed], + "username": username + } + + if avatar_url: + payload["avatar_url"] = avatar_url + + if self.channel_id: + payload["channel_id"] = self.channel_id + + response = requests.post( + self.webhook_url, + data=json.dumps(payload), + headers={"Content-Type": "application/json"}, + timeout=10 + ) + + if response.status_code == 204: + self.logger.debug("Discord embed sent successfully") + return True + else: + self.logger.error(f"Failed to send Discord embed: {response.status_code} - {response.text}") + return False + + except Exception as e: + self.logger.error(f"Error sending Discord embed: {e}") + return False + + def log_moderation_action(self, action: str, submission_id: str, author: str, + title: str, reason: Optional[str] = None, + post_url: Optional[str] = None) -> bool: + """Log a moderation action to Discord using an embed. + + Args: + action: The action taken (e.g., "Removed", "Reflair to Art") + submission_id: The Reddit submission ID + author: The Reddit username + title: The post title + reason: Optional reason for the action + post_url: Optional URL to the post + + Returns: + bool: True if successful, False otherwise + """ + # Determine color based on action + color = 0x00ff00 # Green for reflair + if "Removed" in action: + color = 0xff0000 # Red for removal + + # Create embed + embed = { + "title": f"🔨 {action}", + "color": color, + "fields": [ + { + "name": "📝 Post", + "value": title[:1024] if title else "No title", # Discord field limit + "inline": False + }, + { + "name": "👤 Author", + "value": f"u/{author}", + "inline": True + }, + { + "name": "🆔 Post ID", + "value": submission_id, + "inline": True + } + ], + "timestamp": None, # Will be set to current time by Discord + "footer": { + "text": "UmaBot" + } + } + + # Add reason field if provided + if reason: + embed["fields"].append({ + "name": "📋 Reason", + "value": reason[:1024], # Discord field limit + "inline": False + }) + + # Add post URL if provided + if post_url: + embed["url"] = post_url + + return self.send_embed(embed) diff --git a/src/umabot/rules/intelligent_roleplay_moderator.py b/src/umabot/rules/intelligent_roleplay_moderator.py index 865b4a4..50aa7d6 100644 --- a/src/umabot/rules/intelligent_roleplay_moderator.py +++ b/src/umabot/rules/intelligent_roleplay_moderator.py @@ -3,6 +3,7 @@ import praw.models from .base import Rule from .intelligent_moderator_base import IntelligentModeratorBase +from ..discord_client import DiscordWebhookClient class IntelligentRoleplayModerator(Rule, IntelligentModeratorBase): @@ -13,6 +14,14 @@ class IntelligentRoleplayModerator(Rule, IntelligentModeratorBase): Rule.__init__(self, config) IntelligentModeratorBase.__init__(self, config.openai_api_key) self.subreddit = subreddit + + # Initialize Discord webhook client if configured + self.discord_client = None + if config.discord_webhook_url: + self.discord_client = DiscordWebhookClient( + webhook_url=config.discord_webhook_url, + channel_id=config.discord_log_channel_id + ) def should_remove(self, submission: praw.models.Submission) -> bool: """Evaluate a roleplay post and take appropriate action.""" @@ -130,6 +139,20 @@ class IntelligentRoleplayModerator(Rule, IntelligentModeratorBase): submission.author.message(subject, message) self.logger.info(f"Sent art flair change notification to {username}") + # Log to Discord if configured + if self.discord_client: + # Get AI reasoning for Discord log + ai_reason = evaluation.get('reasoning', 'Post appears to be primarily showcasing artwork') + + self.discord_client.log_moderation_action( + action="Reflair to Art", + submission_id=submission.id, + author=username, + title=submission.title or "No title", + reason=ai_reason, + post_url=f"https://reddit.com{submission.permalink}" + ) + except Exception as e: self.logger.error(f"Error sending art flair notification for {submission.id}: {e}") @@ -153,6 +176,22 @@ class IntelligentRoleplayModerator(Rule, IntelligentModeratorBase): submission.author.message(subject, message) self.logger.info(f"Sent low-effort removal notification to {username}") + # Log to Discord if configured + if self.discord_client: + # Get brief reason for Discord log + brief_reason = "Low-effort content" + if formatted_reasoning: + brief_reason = formatted_reasoning[:100] + "..." if len(formatted_reasoning) > 100 else formatted_reasoning + + self.discord_client.log_moderation_action( + action="Removed", + submission_id=submission.id, + author=username, + title=submission.title or "No title", + reason=brief_reason, + post_url=f"https://reddit.com{submission.permalink}" + ) + except Exception as e: self.logger.error(f"Error sending low-effort notification for {submission.id}: {e}") |