Latest fixes.

This commit is contained in:
2026-05-21 14:07:11 -05:00
parent 15cf9e72bb
commit 0765aaa9f7
8 changed files with 2195 additions and 60 deletions
+57 -12
View File
@@ -9,6 +9,7 @@ Features:
* View karma leaderboards (top/bottom)
* Rate limiting to prevent spam
* Room-specific karma tracking
* Pertarget throttle (max votes per target per minute)
Commands:
!karma - Show this help
@@ -43,9 +44,13 @@ import time
# Configuration
# ---------------------------------------------------------------------------
# Global cooldown: one karma point per hour per voter
# Pertarget cooldown: one karma point per hour per user
COOLDOWN_SECONDS = 3600
# Pertarget throttle: max votes a target can receive per minute
PER_TARGET_THROTTLE_COUNT = 5
PER_TARGET_THROTTLE_SECONDS = 3600
# Database file
DB_FILE = "karma.db"
@@ -56,6 +61,9 @@ display_name_cache = {}
# Last time we refreshed the cache (per room)
cache_timestamp = {}
# Pertarget throttle tracker: (room_id, user_id) -> list of monotonic timestamps
_target_vote_times: dict[tuple[str, str], list[float]] = {}
# ---------------------------------------------------------------------------
# Helper: pluralize "point" vs "points"
# ---------------------------------------------------------------------------
@@ -132,29 +140,25 @@ async def refresh_display_name_cache(bot, room_id):
try:
if hasattr(bot, 'async_client') and bot.async_client:
resp = await bot.async_client.joined_members(room_id)
if resp.members is not None:
if resp.members:
name_map = {}
for member in resp.members:
display_name = (member.display_name or "").strip()
if display_name:
name_map[display_name.lower()] = member.user_id
# Also store without emojis
# Also store without emojis for easier matching
clean_name = re.sub(r'[^\w\s]', '', display_name).strip().lower()
if clean_name and clean_name != display_name.lower():
name_map[clean_name] = member.user_id
display_name_cache[room_id] = name_map
cache_timestamp[room_id] = now
logging.info(f"Cached {len(name_map)} display names for room {room_id}")
# DEBUG: show first 5 names
sample = list(name_map.items())[:5]
logging.debug(f"Sample display names: {sample}")
return
else:
logging.warning(f"joined_members returned None members for room {room_id}")
except Exception as e:
logging.warning(f"Could not refresh display name cache: {e}")
# init empty cache on failure
# If we couldn't get members, initialize empty cache
display_name_cache[room_id] = {}
cache_timestamp[room_id] = now
@@ -173,7 +177,7 @@ def resolve_display_name(room_id, display_name, bot=None):
# Strip HTML tags (Matrix mention pills)
clean = re.sub(r'<[^>]+>', '', display_name).strip()
# Reject Matrix IDs outright (only if the raw input is an ID, not the cleaned one)
# Reject Matrix IDs outright
if is_matrix_id(display_name):
return None
@@ -181,7 +185,7 @@ def resolve_display_name(room_id, display_name, bot=None):
if room_id in display_name_cache:
name_map = display_name_cache[room_id]
# Try exact match (caseinsensitive)
# Try exact match (case-insensitive)
key = clean.lower()
if key in name_map:
return name_map[key]
@@ -451,6 +455,28 @@ def format_karma_display(display_name, points):
return f"⚖️ **{display_name}** has neutral karma (0)"
# ---------------------------------------------------------------------------
# Pertarget throttle helpers
# ---------------------------------------------------------------------------
def _is_target_throttled(room_id: str, user_id: str) -> bool:
"""Return True if the target user has received too many votes recently."""
key = (room_id, user_id)
now = time.monotonic()
times = _target_vote_times.get(key, [])
# Remove old entries
times = [t for t in times if now - t < PER_TARGET_THROTTLE_SECONDS]
_target_vote_times[key] = times
return len(times) >= PER_TARGET_THROTTLE_COUNT
def _record_target_vote(room_id: str, user_id: str):
"""Record that a vote was just cast for the target user."""
key = (room_id, user_id)
times = _target_vote_times.get(key, [])
times.append(time.monotonic())
_target_vote_times[key] = times
# ---------------------------------------------------------------------------
# Command Handlers
# ---------------------------------------------------------------------------
@@ -556,6 +582,14 @@ async def process_karma_vote(room, display_name, action, voter, bot):
await bot.api.send_markdown_message(room.room_id, "❌ You cannot modify your own karma!")
return
# Pertarget throttle: limit how many votes a target can receive per minute
if _is_target_throttled(room_id, user_id):
await bot.api.send_markdown_message(
room.room_id,
f"{display_name} is receiving too many votes right now. Please try again later."
)
return
# Check cooldown
if is_on_cooldown(room_id, user_id, voter_str):
remaining = get_cooldown_remaining(room_id, user_id, voter_str)
@@ -573,6 +607,9 @@ async def process_karma_vote(room, display_name, action, voter, bot):
new_points = update_karma(room_id, user_id, change, voter_str)
update_cooldown(room_id, user_id, voter_str)
# Record target vote for throttle
_record_target_vote(room_id, user_id)
# Get display name for response
display_name_resolved = get_display_name_from_user_id(bot, room_id, user_id)
response = format_karma_display(display_name_resolved, new_points)
@@ -807,6 +844,11 @@ async def handle_inline_karma(room, message, bot):
logging.debug(f"Skipping self-modification: {sender} -> {display_name}")
continue
# Pertarget throttle for inline votes
if _is_target_throttled(room_id, user_id):
logging.debug(f"Inline target throttle active for {user_id}")
continue
# Check cooldown
if is_on_cooldown(room_id, user_id, sender):
logging.debug(f"Cooldown active for {sender} -> {user_id}")
@@ -817,6 +859,9 @@ async def handle_inline_karma(room, message, bot):
new_points = update_karma(room_id, user_id, change, sender)
update_cooldown(room_id, user_id, sender)
# Record target vote for throttle
_record_target_vote(room_id, user_id)
# Format response
display_name_resolved = get_display_name_from_user_id(bot, room_id, user_id)
arrow = "⬆️" if change > 0 else "⬇️"
@@ -855,7 +900,7 @@ def setup(bot):
# Plugin Metadata
# ---------------------------------------------------------------------------
__version__ = "1.0.1"
__version__ = "1.0.2"
__author__ = "Funguy Bot"
__description__ = "Room karma tracking system (display names only, no Matrix IDs)"
__help__ = """