aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-09-20 22:10:38 -0700
committerFuwn <[email protected]>2025-09-20 22:10:38 -0700
commite7ae17f31cc05279fa300c340587fc807af7b872 (patch)
tree4975ccaa7999870a750d141d26195b63ba1b1626 /src
parentfeat(irm): Update post removal message subject (diff)
downloadumabot-e7ae17f31cc05279fa300c340587fc807af7b872.tar.xz
umabot-e7ae17f31cc05279fa300c340587fc807af7b872.zip
feat(irm): Discord logging
Diffstat (limited to 'src')
-rw-r--r--src/umabot/config.py6
-rw-r--r--src/umabot/discord_client.py165
-rw-r--r--src/umabot/rules/intelligent_roleplay_moderator.py39
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}")