Latest fixes.
This commit is contained in:
+57
-12
@@ -9,6 +9,7 @@ Features:
|
||||
* View karma leaderboards (top/bottom)
|
||||
* Rate limiting to prevent spam
|
||||
* Room-specific karma tracking
|
||||
* Per‑target 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
|
||||
# Per‑target cooldown: one karma point per hour per user
|
||||
COOLDOWN_SECONDS = 3600
|
||||
|
||||
# Per‑target 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 = {}
|
||||
|
||||
# Per‑target 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 (case‑insensitive)
|
||||
# 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)"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per‑target 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
|
||||
|
||||
# Per‑target 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
|
||||
|
||||
# Per‑target 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__ = """
|
||||
|
||||
Reference in New Issue
Block a user