From 8771d932d23fcc6aa36c92d1f7759f965d64a796 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Wed, 3 Sep 2025 21:16:37 -0700 Subject: feat(roleplay_limiter): Add static limiter module back --- README.md | 16 ++++++-- src/umabot/bot.py | 4 +- src/umabot/rules/__init__.py | 4 +- src/umabot/rules/roleplay_limiter.py | 80 ++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 90621ac..12f8639 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,26 @@ A modular Reddit bot for automated post moderation built with Python and PRAW. - **Dry Run Mode**: Test the bot without actually removing posts - **Comprehensive Logging**: Detailed logs for monitoring and debugging -### Surge-Based Roleplay Limiting +### Roleplay Limiting Options -The bot uses intelligent surge detection to manage roleplay posts: +The bot supports two roleplay limiting modes: + +#### Surge-Based Limiting (Default) +Intelligent surge detection that adjusts limits based on subreddit activity: - **Normal Activity** (< 20 roleplay posts): Users can post up to 5 roleplay posts per time window - **Moderate Surge** (20+ roleplay posts): Users limited to 3 roleplay posts per time window - **High Surge** (40+ roleplay posts): Users limited to 1 roleplay post per time window - **Extreme Surge** (60+ roleplay posts): All roleplay posts temporarily blocked -The system automatically adjusts limits based on recent roleplay activity and provides dynamic removal messages explaining the current restrictions. +#### Static Limiting (Optional) +Fixed limit that doesn't change based on activity: + +- **Fixed Limit**: Users can post a configurable number of roleplay posts per time window (default: 1) +- **Simple**: No surge detection, just enforces the same limit for all users +- **Predictable**: Consistent behavior regardless of subreddit activity + +To switch to static limiting, uncomment the `StaticRoleplayLimiter` line in `src/umabot/bot.py` and comment out the `RoleplayLimiter` line. ## Quick Start diff --git a/src/umabot/bot.py b/src/umabot/bot.py index 6c9797c..cb17b9b 100644 --- a/src/umabot/bot.py +++ b/src/umabot/bot.py @@ -10,6 +10,7 @@ from loguru import logger from .config import Config from .rules import SpamDetector, RoleplayLimiter +# from .rules import StaticRoleplayLimiter # Disabled by default - uncomment to use static limiting class HealthCheckHandler(BaseHTTPRequestHandler): @@ -75,7 +76,8 @@ class UmaBot: # Initialize rules self.rules = [ SpamDetector(config), - RoleplayLimiter(config, self.subreddit) + RoleplayLimiter(config, self.subreddit) # Surge-based roleplay limiter (default) + # StaticRoleplayLimiter(config) # Uncomment to use static roleplay limiting instead ] # Track processed submissions to avoid processing old posts diff --git a/src/umabot/rules/__init__.py b/src/umabot/rules/__init__.py index e912e70..7573843 100644 --- a/src/umabot/rules/__init__.py +++ b/src/umabot/rules/__init__.py @@ -2,6 +2,6 @@ from .base import Rule from .spam_detector import SpamDetector -from .roleplay_limiter import RoleplayLimiter +from .roleplay_limiter import RoleplayLimiter, StaticRoleplayLimiter -__all__ = ["Rule", "SpamDetector", "RoleplayLimiter"] +__all__ = ["Rule", "SpamDetector", "RoleplayLimiter", "StaticRoleplayLimiter"] diff --git a/src/umabot/rules/roleplay_limiter.py b/src/umabot/rules/roleplay_limiter.py index 60490a9..5aea520 100644 --- a/src/umabot/rules/roleplay_limiter.py +++ b/src/umabot/rules/roleplay_limiter.py @@ -138,3 +138,83 @@ class RoleplayLimiter(Rule): except Exception as e: self.logger.error(f"Error checking flair for submission {submission.id}: {e}") return False + + +class StaticRoleplayLimiter(Rule): + """Static roleplay limiter that limits users to a fixed number of roleplay posts per time window.""" + + def __init__(self, config): + """Initialize the static roleplay limiter.""" + super().__init__(config) + self.user_roleplay_posts: Dict[str, List[float]] = {} + self.max_roleplay_posts = config.max_roleplay_posts_per_day + self.time_window = config.roleplay_limit_window_hours * 60 * 60 # Convert hours to seconds + self.roleplay_flair = "Roleplay" + + def should_remove(self, submission: praw.models.Submission) -> bool: + """Check if a user has posted too many roleplay posts.""" + if not submission.author: + return False + + # Check if this is a roleplay post + if not self._is_roleplay_post(submission): + return False + + username = submission.author.name + current_time = time.time() + + # Clean old posts from tracking + self._clean_old_posts(username, current_time) + + # Count current roleplay posts in the time window + if username not in self.user_roleplay_posts: + self.user_roleplay_posts[username] = [] + + post_count = len(self.user_roleplay_posts[username]) + + # Add current post to tracking + self.user_roleplay_posts[username].append(current_time) + + # Check if this post exceeds the limit + if post_count >= self.max_roleplay_posts: + self.logger.info( + f"User {username} has posted {post_count + 1} roleplay posts in {self.config.roleplay_limit_window_hours} hours " + f"(limit: {self.max_roleplay_posts})" + ) + return True + + return False + + def get_removal_message(self, submission: praw.models.Submission) -> str: + """Get the static roleplay removal message.""" + return self.config.roleplay_message + + def _clean_old_posts(self, username: str, current_time: float) -> None: + """Remove roleplay posts older than the time window from tracking.""" + if username not in self.user_roleplay_posts: + return + + cutoff_time = current_time - self.time_window + self.user_roleplay_posts[username] = [ + post_time for post_time in self.user_roleplay_posts[username] + if post_time > cutoff_time + ] + + def _is_roleplay_post(self, submission: praw.models.Submission) -> bool: + """Check if a submission has the roleplay flair.""" + try: + # Check link flair text + if hasattr(submission, 'link_flair_text') and submission.link_flair_text: + return submission.link_flair_text.lower() == self.roleplay_flair.lower() + + # Check flair template ID (if using new flair system) + if hasattr(submission, 'link_flair_template_id') and submission.link_flair_template_id: + # You might need to map flair template IDs to names + # For now, we'll just check the text + pass + + return False + + except Exception as e: + self.logger.error(f"Error checking flair for submission {submission.id}: {e}") + return False -- cgit v1.2.3