0.4.1.0.a8
- Converted all commands to slash-only commands
This commit is contained in:
parent
0349c36880
commit
77f92abe19
2
bot.py
2
bot.py
@ -9,7 +9,7 @@ from modules.common.boot_notice import post_boot_notice
|
|||||||
|
|
||||||
# Version consists of:
|
# Version consists of:
|
||||||
# Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update)
|
# Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update)
|
||||||
VERSION = "0.4.1.0.a7"
|
VERSION = "0.4.1.0.a8"
|
||||||
|
|
||||||
# ---------- Env loading ----------
|
# ---------- Env loading ----------
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
from discord import app_commands
|
||||||
from modules.common.settings import cfg # ENV-first config helper
|
from modules.common.settings import cfg # ENV-first config helper
|
||||||
from mod_perms import require_mod_ctx # <- use project mod perms
|
from mod_perms import require_mod_ctx, require_mod_interaction # <- use project mod perms
|
||||||
|
|
||||||
def now() -> float:
|
def now() -> float:
|
||||||
return time.time()
|
return time.time()
|
||||||
@ -225,10 +226,12 @@ class AutoVCCog(commands.Cog):
|
|||||||
|
|
||||||
# ------------- admin commands -------------
|
# ------------- admin commands -------------
|
||||||
|
|
||||||
@commands.hybrid_command(name="avc_status", description="Show Auto-VC status for this guild")
|
@app_commands.command(name="avc_status", description="Show Auto-VC status for this guild")
|
||||||
@commands.guild_only()
|
async def avc_status(self, interaction: discord.Interaction):
|
||||||
async def avc_status(self, ctx: commands.Context):
|
if not interaction.guild:
|
||||||
g = ctx.guild
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
|
||||||
|
g = interaction.guild
|
||||||
recs = sorted(self._vc_records(g.id), key=lambda r: r.get('created_ts', 0))
|
recs = sorted(self._vc_records(g.id), key=lambda r: r.get('created_ts', 0))
|
||||||
lines = [
|
lines = [
|
||||||
f"Trigger: <#{self.trigger_id or 0}> | Category: <#{self.category_id or 0}> | Prefix: `{self.prefix}` | Delay: {self.delay}s"
|
f"Trigger: <#{self.trigger_id or 0}> | Category: <#{self.category_id or 0}> | Prefix: `{self.prefix}` | Delay: {self.delay}s"
|
||||||
@ -244,29 +247,35 @@ class AutoVCCog(commands.Cog):
|
|||||||
t = self.empty_since.get(rec['channel_id'])
|
t = self.empty_since.get(rec['channel_id'])
|
||||||
tail = f" | idle {int(now()-t)}s" if t and (not ch or (ch and not ch.members)) else ""
|
tail = f" | idle {int(now()-t)}s" if t and (not ch or (ch and not ch.members)) else ""
|
||||||
lines.append(f"- #{idx}: {name} — {state}{tail}")
|
lines.append(f"- #{idx}: {name} — {state}{tail}")
|
||||||
|
|
||||||
msg = "Auto-VC status:\n" + "\n".join(lines) if lines else "No Auto-VC rooms tracked."
|
msg = "Auto-VC status:\n" + "\n".join(lines) if lines else "No Auto-VC rooms tracked."
|
||||||
await ctx.reply(msg)
|
await interaction.response.send_message(msg)
|
||||||
|
|
||||||
@commands.hybrid_command(name="avc_cleanup_now", description="[MOD] Run an immediate cleanup pass (delete idle rooms & renumber)")
|
@app_commands.command(name="avc_cleanup_now", description="[MOD] Run an immediate cleanup pass (delete idle rooms & renumber)")
|
||||||
@commands.guild_only()
|
async def avc_cleanup_now(self, interaction: discord.Interaction):
|
||||||
async def avc_cleanup_now(self, ctx: commands.Context):
|
if not interaction.guild:
|
||||||
if not await require_mod_ctx(ctx, "This command is restricted to moderators."):
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
if not await require_mod_interaction(interaction, "This command is restricted to moderators."):
|
||||||
return
|
return
|
||||||
# serialize vs sweeper and other admin ops
|
|
||||||
async with self._ops_lock:
|
|
||||||
await self._cleanup_pass(ctx.guild)
|
|
||||||
await self._log(ctx.guild, f"🧹 Cleanup pass invoked by {ctx.author.mention}")
|
|
||||||
await ctx.reply("Cleanup pass complete.", ephemeral=hasattr(ctx, "interaction") and ctx.interaction is not None)
|
|
||||||
|
|
||||||
@commands.hybrid_command(name="avc_renumber", description="[MOD] Force a renumber of tracked rooms")
|
await interaction.response.defer(ephemeral=True)
|
||||||
@commands.guild_only()
|
|
||||||
async def avc_renumber(self, ctx: commands.Context):
|
|
||||||
if not await require_mod_ctx(ctx, "This command is restricted to moderators."):
|
|
||||||
return
|
|
||||||
async with self._ops_lock:
|
async with self._ops_lock:
|
||||||
await self._renumber(ctx.guild)
|
await self._cleanup_pass(interaction.guild)
|
||||||
await self._log(ctx.guild, f"🔢 Renumber invoked by {ctx.author.mention}")
|
await self._log(interaction.guild, f"🧹 Cleanup pass invoked by {interaction.user.mention}")
|
||||||
await ctx.reply("Renumbered.", ephemeral=hasattr(ctx, "interaction") and ctx.interaction is not None)
|
await interaction.followup.send("Cleanup pass complete.", ephemeral=True)
|
||||||
|
|
||||||
|
@app_commands.command(name="avc_renumber", description="[MOD] Force a renumber of tracked rooms")
|
||||||
|
async def avc_renumber(self, interaction: discord.Interaction):
|
||||||
|
if not interaction.guild:
|
||||||
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
if not await require_mod_interaction(interaction, "This command is restricted to moderators."):
|
||||||
|
return
|
||||||
|
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
async with self._ops_lock:
|
||||||
|
await self._renumber(interaction.guild)
|
||||||
|
await self._log(interaction.guild, f"🔢 Renumber invoked by {interaction.user.mention}")
|
||||||
|
await interaction.followup.send("Renumbered.", ephemeral=True)
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(AutoVCCog(bot))
|
await bot.add_cog(AutoVCCog(bot))
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
from discord import app_commands
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from mod_perms import require_mod_ctx
|
from mod_perms import require_mod_ctx, require_mod_interaction
|
||||||
from modules.common.settings import cfg # ENV-first config helper
|
from modules.common.settings import cfg # ENV-first config helper
|
||||||
|
|
||||||
|
|
||||||
@ -196,30 +197,28 @@ class PirateCardsCog(commands.Cog):
|
|||||||
await self.refresh_card_for_account(guild, new_account)
|
await self.refresh_card_for_account(guild, new_account)
|
||||||
|
|
||||||
# -------- command (mod-gated via require_mod_ctx) --------
|
# -------- command (mod-gated via require_mod_ctx) --------
|
||||||
@commands.hybrid_command(name="pirate_cards_rebuild", description="[MOD] Rebuild pirate cards for all known pirates")
|
@app_commands.command(name="pirate_cards_rebuild", description="[MOD] Rebuild pirate cards for all known pirates")
|
||||||
async def pirate_cards_rebuild(self, ctx: commands.Context):
|
async def pirate_cards_rebuild(self, interaction: discord.Interaction):
|
||||||
if not await require_mod_ctx(ctx, "This command is restricted to moderators."):
|
if not interaction.guild:
|
||||||
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
if not await require_mod_interaction(interaction, "This command is restricted to moderators."):
|
||||||
return
|
return
|
||||||
if not ctx.guild:
|
|
||||||
return await ctx.reply("Use this in a server.", ephemeral=True)
|
|
||||||
|
|
||||||
async with self._lock_for(ctx.guild.id):
|
await interaction.response.defer(ephemeral=True)
|
||||||
guild = ctx.guild
|
guild = interaction.guild
|
||||||
ch = guild.get_channel(self.pirates_channel_id)
|
ch = guild.get_channel(self.pirates_channel_id)
|
||||||
if not ch:
|
if not ch:
|
||||||
return await ctx.reply("Configured pirates_list_channel_id not found.", ephemeral=True)
|
return await interaction.followup.send("Configured pirates_list_channel_id not found.", ephemeral=True)
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
for p in self.bot.data_manager.get('pirates'):
|
for p in self.bot.data_manager.get('pirates'):
|
||||||
try:
|
try:
|
||||||
await self.refresh_card_for_account(guild, p.get('account_name', ''))
|
await self.refresh_card_for_account(guild, p.get('account_name', ''))
|
||||||
count += 1
|
count += 1
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
is_slash = hasattr(ctx, "interaction") and ctx.interaction is not None
|
|
||||||
await ctx.reply(f"Rebuilt/updated {count} pirate cards.", ephemeral=is_slash)
|
|
||||||
|
|
||||||
|
await interaction.followup.send(f"Rebuilt/updated {count} pirate cards.", ephemeral=True)
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(PirateCardsCog(bot))
|
await bot.add_cog(PirateCardsCog(bot))
|
||||||
|
@ -518,18 +518,23 @@ class PirateReportCog(commands.Cog):
|
|||||||
return (matches[0], None)
|
return (matches[0], None)
|
||||||
|
|
||||||
# Remove pirate (mod-only)
|
# Remove pirate (mod-only)
|
||||||
@commands.hybrid_command(name='remove_pirate', description='[MOD] Remove an approved pirate entry')
|
@app_commands.command(name="remove_pirate", description="[MOD] Remove an approved pirate entry")
|
||||||
async def remove_pirate(self, ctx, account_name: str):
|
@app_commands.describe(account_name="Account name to remove")
|
||||||
if not await require_mod_ctx(ctx, "This command is restricted to moderators."):
|
async def remove_pirate(self, interaction: discord.Interaction, account_name: str):
|
||||||
|
if not interaction.guild:
|
||||||
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
if not await require_mod_interaction(interaction, "This command is restricted to moderators."):
|
||||||
return
|
return
|
||||||
|
|
||||||
acct_lower = account_name.strip().lower()
|
acct_lower = account_name.strip().lower()
|
||||||
dm = self.bot.data_manager
|
dm = self.bot.data_manager
|
||||||
if not any(p['account_name'].lower() == acct_lower for p in dm.get('pirates')):
|
if not any(p['account_name'].lower() == acct_lower for p in dm.get('pirates')):
|
||||||
return await ctx.reply("Pirate not found.")
|
return await interaction.response.send_message("Pirate not found.", ephemeral=True)
|
||||||
|
|
||||||
dm.remove('pirates', lambda p: p['account_name'].lower() == acct_lower)
|
dm.remove('pirates', lambda p: p['account_name'].lower() == acct_lower)
|
||||||
await self._modlog(ctx.guild, f"🗑️ Removed pirate {account_name} by {ctx.author.mention}")
|
await self._modlog(interaction.guild, f"🗑️ Removed pirate {account_name} by {interaction.user.mention}")
|
||||||
await self._refresh_pirates_list(ctx.guild)
|
await self._refresh_pirates_list(interaction.guild)
|
||||||
await ctx.reply("Removed.", ephemeral=hasattr(ctx, "interaction") and ctx.interaction is not None)
|
await interaction.response.send_message("Removed.", ephemeral=True)
|
||||||
|
|
||||||
# Modal launchers
|
# Modal launchers
|
||||||
@app_commands.command(name="report", description="Submit a pirate report for moderator review (opens a form)")
|
@app_commands.command(name="report", description="Submit a pirate report for moderator review (opens a form)")
|
||||||
@ -551,10 +556,15 @@ class PirateReportCog(commands.Cog):
|
|||||||
await interaction.response.send_modal(EncounterModal(self))
|
await interaction.response.send_modal(EncounterModal(self))
|
||||||
|
|
||||||
# ---- Migration: convert encounter identifiers to accounts (mod-only) ----
|
# ---- Migration: convert encounter identifiers to accounts (mod-only) ----
|
||||||
@commands.hybrid_command(name='encounters_migrate_ids', description='[MOD] Migrate encounter identifiers to account names')
|
@app_commands.command(name="encounters_migrate_ids", description="[MOD] Migrate encounter identifiers to account names")
|
||||||
async def encounters_migrate_ids(self, ctx: commands.Context):
|
async def encounters_migrate_ids(self, interaction: discord.Interaction):
|
||||||
if not await require_mod_ctx(ctx, "This command is restricted to moderators."):
|
if not interaction.guild:
|
||||||
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
if not await require_mod_interaction(interaction, "This command is restricted to moderators."):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
|
||||||
dm = self.bot.data_manager
|
dm = self.bot.data_manager
|
||||||
pirates = dm.get('pirates')
|
pirates = dm.get('pirates')
|
||||||
by_char = {}
|
by_char = {}
|
||||||
@ -562,10 +572,7 @@ class PirateReportCog(commands.Cog):
|
|||||||
by_char.setdefault(p['character_name'].lower(), []).append(p)
|
by_char.setdefault(p['character_name'].lower(), []).append(p)
|
||||||
by_acct = {p['account_name'].lower(): p for p in pirates}
|
by_acct = {p['account_name'].lower(): p for p in pirates}
|
||||||
|
|
||||||
changed = 0
|
changed = ambiguous = missing = already = 0
|
||||||
ambiguous = 0
|
|
||||||
missing = 0
|
|
||||||
already = 0
|
|
||||||
|
|
||||||
for e in dm.get('encounters'):
|
for e in dm.get('encounters'):
|
||||||
ident = e.get('identifier', '')
|
ident = e.get('identifier', '')
|
||||||
@ -583,26 +590,27 @@ class PirateReportCog(commands.Cog):
|
|||||||
ambiguous += 1
|
ambiguous += 1
|
||||||
continue
|
continue
|
||||||
acct = matches[0]['account_name']
|
acct = matches[0]['account_name']
|
||||||
# update this one entry atomically
|
|
||||||
def pred(x, ts=e['timestamp'], rid=e['reporter_id'], ident_old=ident):
|
def pred(x, ts=e['timestamp'], rid=e['reporter_id'], ident_old=ident):
|
||||||
return x.get('timestamp') == ts and x.get('reporter_id') == rid and x.get('identifier') == ident_old
|
return x.get('timestamp') == ts and x.get('reporter_id') == rid and x.get('identifier') == ident_old
|
||||||
def upd(x, acct_new=acct):
|
def upd(x, acct_new=acct):
|
||||||
x['identifier'] = acct_new
|
x['identifier'] = acct_new
|
||||||
return x
|
return x
|
||||||
|
|
||||||
ok = dm.update('encounters', pred, upd)
|
ok = dm.update('encounters', pred, upd)
|
||||||
if ok:
|
if ok:
|
||||||
changed += 1
|
changed += 1
|
||||||
|
|
||||||
await ctx.reply(
|
await interaction.followup.send(
|
||||||
f"Migration complete.\n"
|
"Migration complete.\n"
|
||||||
f"- Updated to accounts: **{changed}**\n"
|
f"- Updated to accounts: **{changed}**\n"
|
||||||
f"- Already accounts: **{already}**\n"
|
f"- Already accounts: **{already}**\n"
|
||||||
f"- Ambiguous character names (skipped): **{ambiguous}**\n"
|
f"- Ambiguous character names (skipped): **{ambiguous}**\n"
|
||||||
f"- Not found in pirates list (skipped): **{missing}**",
|
f"- Not found in pirates list (skipped): **{missing}**",
|
||||||
ephemeral=hasattr(ctx, "interaction") and ctx.interaction is not None
|
ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._refresh_pirates_list(ctx.guild)
|
await self._refresh_pirates_list(interaction.guild)
|
||||||
|
|
||||||
# Moderator reaction handling (atomic claim)
|
# Moderator reaction handling (atomic claim)
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from mod_perms import require_mod_ctx
|
from discord import app_commands
|
||||||
|
from mod_perms import require_mod_ctx, require_mod_interaction
|
||||||
from modules.common.settings import cfg as _cfg
|
from modules.common.settings import cfg as _cfg
|
||||||
|
|
||||||
|
|
||||||
@ -211,19 +212,17 @@ class PiratesListCog(commands.Cog):
|
|||||||
dm.remove("pirates_list_posts", lambda r, mid=m.id: r.get("message_id") == mid)
|
dm.remove("pirates_list_posts", lambda r, mid=m.id: r.get("message_id") == mid)
|
||||||
|
|
||||||
# Manual refresh command (hybrid: works as /pirates_list_refresh and !pirates_list_refresh)
|
# Manual refresh command (hybrid: works as /pirates_list_refresh and !pirates_list_refresh)
|
||||||
@commands.hybrid_command(name="pirates_list_refresh", description="[MOD] Rebuild the compact pirates list")
|
@app_commands.command(name="pirates_list_refresh", description="[MOD] Rebuild the compact pirates list")
|
||||||
@commands.cooldown(1, 10, commands.BucketType.guild) # tiny anti-spam
|
@app_commands.checks.cooldown(1, 10) # guild-scope anti-spam analogue
|
||||||
async def pirates_list_refresh(self, ctx: commands.Context):
|
async def pirates_list_refresh(self, interaction: discord.Interaction):
|
||||||
if not await require_mod_ctx(ctx, "This command is restricted to moderators."):
|
if not interaction.guild:
|
||||||
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
if not await require_mod_interaction(interaction, "This command is restricted to moderators."):
|
||||||
return
|
return
|
||||||
if not ctx.guild:
|
|
||||||
return await ctx.reply("Use this in a server.", ephemeral=True)
|
|
||||||
|
|
||||||
await self.refresh_list(ctx.guild)
|
|
||||||
|
|
||||||
is_slash = hasattr(ctx, "interaction") and ctx.interaction is not None
|
|
||||||
await ctx.reply("Pirates list refreshed.", ephemeral=is_slash)
|
|
||||||
|
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
await self.refresh_list(interaction.guild)
|
||||||
|
await interaction.followup.send("Pirates list refreshed.", ephemeral=True)
|
||||||
|
|
||||||
async def setup(bot: commands.Bot):
|
async def setup(bot: commands.Bot):
|
||||||
await bot.add_cog(PiratesListCog(bot))
|
await bot.add_cog(PiratesListCog(bot))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# modules/reaction_role/reaction_role.py
|
# modules/reaction_role/reaction_role.py
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
from discord import app_commands
|
||||||
from modules.common.emoji_accept import is_accept
|
from modules.common.emoji_accept import is_accept
|
||||||
from modules.common.settings import cfg # ENV-first helper
|
from modules.common.settings import cfg # ENV-first helper
|
||||||
|
|
||||||
@ -94,22 +95,22 @@ class ReactionRoleCog(commands.Cog):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# ---- commands (hybrid = prefix + slash) ----
|
# ---- commands (hybrid = prefix + slash) ----
|
||||||
@commands.hybrid_command(name='nick_same', description='Claim that your global display name matches your in-game name (triggers mod review)')
|
@app_commands.command(name="nick_same", description="Claim that your global display name matches your in-game name (triggers mod review)")
|
||||||
async def nick_same(self, ctx: commands.Context):
|
async def nick_same(self, interaction: discord.Interaction):
|
||||||
member = ctx.author if isinstance(ctx.author, discord.Member) else None
|
if not interaction.guild or not isinstance(interaction.user, discord.Member):
|
||||||
if not member or not ctx.guild:
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
return await ctx.reply("Use this in a server.", ephemeral=True)
|
|
||||||
|
member: discord.Member = interaction.user
|
||||||
|
|
||||||
# Atomic path handled inside NickNudge
|
|
||||||
nn = self.bot.get_cog('NickNudgeCog')
|
nn = self.bot.get_cog('NickNudgeCog')
|
||||||
if nn and hasattr(nn, 'ensure_pending_and_maybe_open'):
|
if nn and hasattr(nn, 'ensure_pending_and_maybe_open'):
|
||||||
try:
|
try:
|
||||||
await nn.ensure_pending_and_maybe_open(ctx.guild, member, source="nick_same")
|
await nn.ensure_pending_and_maybe_open(interaction.guild, member, source="nick_same")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await self.maybe_apply_full_access(member)
|
await self.maybe_apply_full_access(member)
|
||||||
await ctx.reply("Thanks — your nickname claim was sent for moderator review.", ephemeral=True)
|
await interaction.response.send_message("Thanks — your nickname claim was sent for moderator review.", ephemeral=True)
|
||||||
|
|
||||||
# ---- listeners ----
|
# ---- listeners ----
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
|
@ -6,22 +6,15 @@ import discord
|
|||||||
COUNTER_KEY_PREFIX = "cmd::"
|
COUNTER_KEY_PREFIX = "cmd::"
|
||||||
|
|
||||||
def _key_from_app(cmd: discord.app_commands.Command) -> str:
|
def _key_from_app(cmd: discord.app_commands.Command) -> str:
|
||||||
name = getattr(cmd, "qualified_name", None) or getattr(cmd, "name", "unknown")
|
return f"{COUNTER_KEY_PREFIX}{getattr(cmd, 'qualified_name', None) or getattr(cmd, 'name', 'unknown')}"
|
||||||
return f"{COUNTER_KEY_PREFIX}{name}"
|
|
||||||
|
|
||||||
def _key_from_ctx(ctx: commands.Context) -> str:
|
|
||||||
c = getattr(ctx, "command", None)
|
|
||||||
name = getattr(c, "qualified_name", None) or getattr(c, "name", "unknown")
|
|
||||||
return f"{COUNTER_KEY_PREFIX}{name}"
|
|
||||||
|
|
||||||
class UsageStatsCog(commands.Cog):
|
class UsageStatsCog(commands.Cog):
|
||||||
"""Count command runs once: app for slash, prefix for non-interaction Context."""
|
"""Slash-only metrics; count once per successful app command completion."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
print("[usage] UsageStatsCog init")
|
print("[usage] UsageStatsCog init (slash-only)")
|
||||||
|
|
||||||
# -------- slash / app-commands --------
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_app_command_completion(self, interaction: discord.Interaction, command: discord.app_commands.Command):
|
async def on_app_command_completion(self, interaction: discord.Interaction, command: discord.app_commands.Command):
|
||||||
dm = getattr(self.bot, "data_manager", None)
|
dm = getattr(self.bot, "data_manager", None)
|
||||||
@ -34,30 +27,10 @@ class UsageStatsCog(commands.Cog):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("[usage] app !! incr failed:", repr(e))
|
print("[usage] app !! incr failed:", repr(e))
|
||||||
|
|
||||||
# -------- prefix (and hybrid-as-prefix) --------
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_command_completion(self, ctx: commands.Context):
|
|
||||||
# If this Context came from a slash interaction (hybrid invoked via slash),
|
|
||||||
# DO NOT count here—the app listener already did.
|
|
||||||
if getattr(ctx, "interaction", None):
|
|
||||||
print("[usage] px ~~ skip: ctx.interaction is set (slash path already counted)")
|
|
||||||
return
|
|
||||||
|
|
||||||
dm = getattr(self.bot, "data_manager", None)
|
|
||||||
if not dm:
|
|
||||||
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] px !! incr failed:", repr(e))
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot):
|
async def setup(bot: commands.Bot):
|
||||||
# Avoid double registration if extensions are discovered/reloaded twice
|
|
||||||
if getattr(bot, "_usage_stats_loaded", False):
|
if getattr(bot, "_usage_stats_loaded", False):
|
||||||
print("[usage] UsageStatsCog already loaded; skipping duplicate add")
|
print("[usage] UsageStatsCog already loaded; skipping duplicate add")
|
||||||
return
|
return
|
||||||
await bot.add_cog(UsageStatsCog(bot))
|
await bot.add_cog(UsageStatsCog(bot))
|
||||||
bot._usage_stats_loaded = True
|
bot._usage_stats_loaded = True
|
||||||
print("[usage] UsageStatsCog loaded")
|
print("[usage] UsageStatsCog loaded (slash-only)")
|
||||||
|
@ -4,6 +4,7 @@ import time
|
|||||||
from typing import Optional, Set, Tuple
|
from typing import Optional, Set, Tuple
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
from discord import app_commands
|
||||||
from modules.common.emoji_accept import is_accept
|
from modules.common.emoji_accept import is_accept
|
||||||
from modules.common.settings import cfg # ENV-first helper
|
from modules.common.settings import cfg # ENV-first helper
|
||||||
|
|
||||||
@ -494,20 +495,18 @@ class UserCardsCog(commands.Cog):
|
|||||||
await asyncio.sleep(12 * 60 * 60) # twice a day
|
await asyncio.sleep(12 * 60 * 60) # twice a day
|
||||||
|
|
||||||
# ---------- mod command: rescan + live reconcile ----------
|
# ---------- mod command: rescan + live reconcile ----------
|
||||||
|
@app_commands.command(name="usercards_rescan", description="[MOD] Re-check all users and refresh cards")
|
||||||
|
@app_commands.default_permissions(manage_guild=True)
|
||||||
|
@app_commands.checks.has_permissions(manage_guild=True)
|
||||||
|
async def usercards_rescan(self, interaction: discord.Interaction):
|
||||||
|
if not interaction.guild:
|
||||||
|
return await interaction.response.send_message("Use this in a server.", ephemeral=True)
|
||||||
|
|
||||||
@commands.hybrid_command(
|
await interaction.response.defer(ephemeral=True)
|
||||||
name="usercards_rescan",
|
|
||||||
description="[MOD] Re-check all users and refresh cards"
|
|
||||||
)
|
|
||||||
@commands.has_permissions(manage_guild=True)
|
|
||||||
async def usercards_rescan(self, ctx: commands.Context):
|
|
||||||
g = ctx.guild
|
|
||||||
if not g:
|
|
||||||
return await ctx.reply("Use this in a server.", ephemeral=True)
|
|
||||||
|
|
||||||
|
g = interaction.guild
|
||||||
rchg, echg, nadd, nrem = await self._reconcile_agreements(g)
|
rchg, echg, nadd, nrem = await self._reconcile_agreements(g)
|
||||||
|
|
||||||
# Rebuild cards
|
|
||||||
updated = 0
|
updated = 0
|
||||||
for m in g.members:
|
for m in g.members:
|
||||||
if not m.bot:
|
if not m.bot:
|
||||||
@ -517,13 +516,12 @@ class UserCardsCog(commands.Cog):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await ctx.reply(
|
await interaction.followup.send(
|
||||||
f"Reconciled from messages. Changes — Rules: **{rchg}**, RoE: **{echg}**, "
|
f"Reconciled from messages. Changes — Rules: **{rchg}**, RoE: **{echg}**, "
|
||||||
f"Nickname (added): **{nadd}**, Nickname (removed): **{nrem}**. "
|
f"Nickname (added): **{nadd}**, Nickname (removed): **{nrem}**. "
|
||||||
f"Refreshed cards for **{updated}** members.",
|
f"Refreshed cards for **{updated}** members.",
|
||||||
ephemeral=True
|
ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(UserCardsCog(bot))
|
await bot.add_cog(UserCardsCog(bot))
|
||||||
|
Loading…
Reference in New Issue
Block a user