0.4.1.0.a3
- Slight bugfix to docsite execution counter due to global command tree silently resulting in duplicate triggers
This commit is contained in:
parent
4e86eb43fc
commit
730d479e2d
2
bot.py
2
bot.py
@ -9,7 +9,7 @@ from modules.common.boot_notice import post_boot_notice
|
||||
|
||||
# Version consists of:
|
||||
# Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update)
|
||||
VERSION = "0.4.1.0.a2"
|
||||
VERSION = "0.4.1.0.a3"
|
||||
|
||||
# ---------- Env loading ----------
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
from discord.ext import commands
|
||||
import discord
|
||||
from collections import OrderedDict
|
||||
|
||||
COUNTER_KEY_PREFIX = "cmd::"
|
||||
|
||||
@ -17,66 +18,96 @@ def _key_from_ctx(ctx: commands.Context) -> str:
|
||||
return f"{COUNTER_KEY_PREFIX}{name}"
|
||||
|
||||
class UsageStatsCog(commands.Cog):
|
||||
"""Lightweight command run/attempt counters persisted in DataManager."""
|
||||
"""Lightweight command run counters with de-dup + hybrid guards."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
# Simple LRU caches for seen events (avoid double/quad increments)
|
||||
self._seen_interactions: OrderedDict[int, int] = OrderedDict() # slash
|
||||
self._seen_messages: OrderedDict[int, int] = OrderedDict() # prefix/hybrid
|
||||
print("[usage] UsageStatsCog init")
|
||||
|
||||
# ---- successful completions ----
|
||||
# ----- small LRU helpers -----
|
||||
@staticmethod
|
||||
def _lru_mark_seen(lru: OrderedDict, key, cap: int) -> bool:
|
||||
"""Return True if already seen; otherwise record and trim to cap."""
|
||||
if key is None:
|
||||
return False
|
||||
if key in lru:
|
||||
# refresh order
|
||||
lru.move_to_end(key, last=True)
|
||||
return True
|
||||
lru[key] = 1
|
||||
# trim if needed
|
||||
while len(lru) > cap:
|
||||
lru.popitem(last=False)
|
||||
return False
|
||||
|
||||
# ----- successful completions -----
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_app_command_completion(self, interaction: discord.Interaction, command: discord.app_commands.Command):
|
||||
# De-dup by interaction id
|
||||
iid = getattr(interaction, "id", None)
|
||||
if self._lru_mark_seen(self._seen_interactions, iid, cap=2048):
|
||||
print(f"[usage] app ~~ dup ignored (iid={iid})")
|
||||
return
|
||||
|
||||
dm = getattr(self.bot, "data_manager", None)
|
||||
if not dm:
|
||||
print("[usage] skip app (no data_manager)")
|
||||
print("[usage] app ~~ skip (no data_manager)")
|
||||
return
|
||||
try:
|
||||
key = _key_from_app(command)
|
||||
newv = dm.incr_counter(key, 1)
|
||||
print(f"[usage] app ++ {key} -> {newv}")
|
||||
except Exception as e:
|
||||
print("[usage] incr app failed:", repr(e))
|
||||
print("[usage] app !! incr failed:", repr(e))
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_completion(self, ctx: commands.Context):
|
||||
# If a HybridCommand was invoked via slash, some setups can still fire this.
|
||||
# Guard: only count here when NOT an interaction-backed invoke.
|
||||
if isinstance(getattr(ctx, "command", None), commands.HybridCommand) and getattr(ctx, "interaction", None):
|
||||
print("[usage] px ~~ hybrid-as-slash; ignore here (app listener handled it)")
|
||||
return
|
||||
|
||||
# De-dup by message id
|
||||
msg = getattr(ctx, "message", None)
|
||||
mid = getattr(msg, "id", None)
|
||||
if self._lru_mark_seen(self._seen_messages, mid, cap=4096):
|
||||
print(f"[usage] px ~~ dup ignored (mid={mid})")
|
||||
return
|
||||
|
||||
dm = getattr(self.bot, "data_manager", None)
|
||||
if not dm:
|
||||
print("[usage] skip prefix (no data_manager)")
|
||||
print("[usage] px ~~ skip (no data_manager)")
|
||||
return
|
||||
try:
|
||||
key = _key_from_ctx(ctx)
|
||||
newv = dm.incr_counter(key, 1)
|
||||
print(f"[usage] px ++ {key} -> {newv}")
|
||||
except Exception as e:
|
||||
print("[usage] incr prefix failed:", repr(e))
|
||||
print("[usage] px !! incr failed:", repr(e))
|
||||
|
||||
# ---- attempts that error (optional but useful while testing) ----
|
||||
# ----- attempts that error (optional, keep while validating) -----
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_app_command_error(self, interaction: discord.Interaction, error: Exception):
|
||||
# Count attempts (separate key) so you can confirm the listener fires at all.
|
||||
dm = getattr(self.bot, "data_manager", None)
|
||||
cmd = getattr(interaction, "command", None)
|
||||
if dm and isinstance(cmd, discord.app_commands.Command):
|
||||
try:
|
||||
key = _key_from_app(cmd).replace("cmd::", "cmd_attempt::")
|
||||
newv = dm.incr_counter(key, 1)
|
||||
print(f"[usage] app !! {key} -> {newv} ({type(error).__name__})")
|
||||
except Exception as e:
|
||||
print("[usage] app err incr failed:", repr(e))
|
||||
# We won't increment exec counters on error; this is only for debug visibility.
|
||||
iid = getattr(interaction, "id", None)
|
||||
print(f"[usage] app ** error ({type(error).__name__}) iid={iid}")
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx: commands.Context, error: Exception):
|
||||
dm = getattr(self.bot, "data_manager", None)
|
||||
if dm and getattr(ctx, "command", None):
|
||||
try:
|
||||
key = _key_from_ctx(ctx).replace("cmd::", "cmd_attempt::")
|
||||
newv = dm.incr_counter(key, 1)
|
||||
print(f"[usage] px !! {key} -> {newv} ({type(error).__name__})")
|
||||
except Exception as e:
|
||||
print("[usage] px err incr failed:", repr(e))
|
||||
mid = getattr(getattr(ctx, "message", None), "id", None)
|
||||
print(f"[usage] px ** error ({type(error).__name__}) mid={mid}")
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
# Prevent duplicate registration if extensions are reloaded / auto-discovered twice
|
||||
if getattr(bot, "_usage_stats_loaded", False):
|
||||
print("[usage] UsageStatsCog already loaded; skipping duplicate add")
|
||||
return
|
||||
await bot.add_cog(UsageStatsCog(bot))
|
||||
bot._usage_stats_loaded = True
|
||||
print("[usage] UsageStatsCog loaded")
|
||||
|
Loading…
Reference in New Issue
Block a user