Delete modules/pirate_cards/pirate_cards.py
This commit is contained in:
parent
256cb33f0c
commit
461679f8eb
@ -1,226 +0,0 @@
|
|||||||
# modules/pirate_cards/pirate_cards.py
|
|
||||||
import asyncio
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
from datetime import datetime
|
|
||||||
from mod_perms import require_mod_ctx # use your configured moderator roles
|
|
||||||
|
|
||||||
class PirateCardsCog(commands.Cog):
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
cfg = bot.config['DEFAULT']
|
|
||||||
self.pirates_channel_id = int(cfg['pirates_list_channel_id'])
|
|
||||||
self.modlog_channel_id = int(cfg.get('modlog_channel_id', '0')) if cfg.get('modlog_channel_id') else 0
|
|
||||||
|
|
||||||
# thresholds / samples (optional, with defaults)
|
|
||||||
try:
|
|
||||||
self.group_threshold = int(cfg.get('threat_group_threshold', '3'))
|
|
||||||
except Exception:
|
|
||||||
self.group_threshold = 3
|
|
||||||
try:
|
|
||||||
self.min_samples = int(cfg.get('threat_min_samples_for_stats', '3'))
|
|
||||||
except Exception:
|
|
||||||
self.min_samples = 3
|
|
||||||
|
|
||||||
# safe posting (don’t ping)
|
|
||||||
self._no_mentions = discord.AllowedMentions.none()
|
|
||||||
# serialize rebuilds per guild
|
|
||||||
self._locks: dict[int, asyncio.Lock] = {}
|
|
||||||
|
|
||||||
# -------- internals / helpers --------
|
|
||||||
def _lock_for(self, guild_id: int) -> asyncio.Lock:
|
|
||||||
self._locks.setdefault(guild_id, asyncio.Lock())
|
|
||||||
return self._locks[guild_id]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _esc(s: str) -> str:
|
|
||||||
"""Escape markdown & neutralize mentions for any user-sourced strings we show."""
|
|
||||||
safe = discord.utils.escape_markdown(str(s))
|
|
||||||
return safe.replace("@", "@\u200b")
|
|
||||||
|
|
||||||
def _color_for_threat(self, threat: int, enc_count: int) -> discord.Color:
|
|
||||||
"""
|
|
||||||
Gradient:
|
|
||||||
- If too few samples: dark gray (unknown)
|
|
||||||
- Else 0 -> green (0,255,0), 100 -> red (255,0,0)
|
|
||||||
"""
|
|
||||||
if enc_count < self.min_samples:
|
|
||||||
return discord.Color.dark_gray()
|
|
||||||
|
|
||||||
t = max(0, min(100, int(threat))) / 100.0
|
|
||||||
r = int(round(255 * t))
|
|
||||||
g = int(round(255 * (1.0 - t)))
|
|
||||||
return discord.Color.from_rgb(r, g, 0)
|
|
||||||
|
|
||||||
def _bucket(self, rate: float, samples: int) -> str:
|
|
||||||
if samples < self.min_samples:
|
|
||||||
return "unknown"
|
|
||||||
if rate <= 0:
|
|
||||||
return "never"
|
|
||||||
if rate <= 0.25:
|
|
||||||
return "rarely"
|
|
||||||
if rate <= 0.60:
|
|
||||||
return "sometimes"
|
|
||||||
if rate <= 0.85:
|
|
||||||
return "often"
|
|
||||||
return "always"
|
|
||||||
|
|
||||||
def _encounters_for(self, pirate: dict):
|
|
||||||
acct_l = str(pirate.get('account_name', '')).lower()
|
|
||||||
char_l = str(pirate.get('character_name', '')).lower()
|
|
||||||
out = []
|
|
||||||
for e in self.bot.data_manager.get('encounters'):
|
|
||||||
try:
|
|
||||||
ident = str(e.get('identifier', '')).lower()
|
|
||||||
if ident in (acct_l, char_l):
|
|
||||||
out.append(e)
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
return out
|
|
||||||
|
|
||||||
def _get_card_record(self, account_lower: str):
|
|
||||||
for r in self.bot.data_manager.get('pirate_cards'):
|
|
||||||
if r.get('account_lower') == account_lower:
|
|
||||||
return r
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def _modlog(self, guild: discord.Guild, content: str):
|
|
||||||
if not self.modlog_channel_id:
|
|
||||||
return
|
|
||||||
ch = guild.get_channel(self.modlog_channel_id)
|
|
||||||
if ch:
|
|
||||||
try:
|
|
||||||
await ch.send(content, allowed_mentions=self._no_mentions)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _build_embed(self, pirate: dict) -> discord.Embed:
|
|
||||||
encs = self._encounters_for(pirate)
|
|
||||||
total = len(encs)
|
|
||||||
# guard numeric fields
|
|
||||||
def _i(v, d=0):
|
|
||||||
try:
|
|
||||||
return int(v)
|
|
||||||
except Exception:
|
|
||||||
return d
|
|
||||||
|
|
||||||
group_rate = (sum(1 for e in encs if _i(e.get('group_size'), 0) >= self.group_threshold) / total) if total else 0.0
|
|
||||||
destroy_rate = (sum(1 for e in encs if bool(e.get('destruction'))) / total) if total else 0.0
|
|
||||||
|
|
||||||
# last encountered: date only (UTC)
|
|
||||||
last_date = "—"
|
|
||||||
if total:
|
|
||||||
try:
|
|
||||||
last_ts = max(int(float(e.get('timestamp', 0))) for e in encs)
|
|
||||||
last_date = datetime.utcfromtimestamp(last_ts).strftime('%Y-%m-%d')
|
|
||||||
except Exception:
|
|
||||||
last_date = "—"
|
|
||||||
|
|
||||||
groups_str = self._bucket(group_rate, total)
|
|
||||||
destr_str = self._bucket(destroy_rate, total)
|
|
||||||
|
|
||||||
# threat & color
|
|
||||||
threat = int(pirate.get('threat_level', 0))
|
|
||||||
color = self._color_for_threat(threat, total)
|
|
||||||
|
|
||||||
char = self._esc(pirate.get('character_name', 'Unknown'))
|
|
||||||
acct = self._esc(pirate.get('account_name', 'Unknown#00000'))
|
|
||||||
|
|
||||||
embed = discord.Embed(title=char, color=color)
|
|
||||||
embed.add_field(name="Account name", value=f"*{acct}*", inline=False)
|
|
||||||
embed.add_field(name="Threat", value=f"{threat}%", inline=True)
|
|
||||||
embed.add_field(name="In groups", value=groups_str, inline=True)
|
|
||||||
embed.add_field(name="Destructive", value=destr_str, inline=True)
|
|
||||||
embed.set_footer(text=f"Encounters: {total} | Last: {last_date}")
|
|
||||||
return embed
|
|
||||||
|
|
||||||
async def refresh_card_for_account(self, guild: discord.Guild, account_name: str):
|
|
||||||
"""Create or update a single pirate card by account name."""
|
|
||||||
acct_l = str(account_name or "").lower()
|
|
||||||
pirates = self.bot.data_manager.get('pirates')
|
|
||||||
p = next((x for x in pirates if str(x.get('account_name', '')).lower() == acct_l), None)
|
|
||||||
if not p:
|
|
||||||
return
|
|
||||||
|
|
||||||
channel = guild.get_channel(self.pirates_channel_id)
|
|
||||||
if not channel:
|
|
||||||
return
|
|
||||||
|
|
||||||
embed = await self._build_embed(p)
|
|
||||||
rec = self._get_card_record(acct_l)
|
|
||||||
|
|
||||||
if rec:
|
|
||||||
try:
|
|
||||||
msg = await channel.fetch_message(int(rec['message_id']))
|
|
||||||
await msg.edit(embed=embed)
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
# fall-through to recreate
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
msg = await channel.send(embed=embed, allowed_mentions=self._no_mentions)
|
|
||||||
self.bot.data_manager.add('pirate_cards', {
|
|
||||||
'account_lower': acct_l,
|
|
||||||
'message_id': msg.id
|
|
||||||
})
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def delete_card_for_account(self, guild: discord.Guild, account_name: str):
|
|
||||||
"""Delete a pirate card by account and remove its record."""
|
|
||||||
acct_l = str(account_name or "").lower()
|
|
||||||
channel = guild.get_channel(self.pirates_channel_id)
|
|
||||||
rec = self._get_card_record(acct_l)
|
|
||||||
|
|
||||||
if rec and channel:
|
|
||||||
try:
|
|
||||||
msg = await channel.fetch_message(int(rec['message_id']))
|
|
||||||
await msg.delete()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# ensure record is gone regardless
|
|
||||||
self.bot.data_manager.remove('pirate_cards', lambda r: r.get('account_lower') == acct_l)
|
|
||||||
|
|
||||||
async def update_on_edit(self, guild: discord.Guild, old_account: str, new_account: str):
|
|
||||||
"""When an account name changes, carry over the card mapping and refresh."""
|
|
||||||
old_l = str(old_account or "").lower()
|
|
||||||
new_l = str(new_account or "").lower()
|
|
||||||
rec = self._get_card_record(old_l)
|
|
||||||
if rec:
|
|
||||||
# replace mapping to new key
|
|
||||||
self.bot.data_manager.remove('pirate_cards', lambda r: r.get('account_lower') == old_l)
|
|
||||||
self.bot.data_manager.add('pirate_cards', {
|
|
||||||
'account_lower': new_l,
|
|
||||||
'message_id': rec['message_id']
|
|
||||||
})
|
|
||||||
await self.refresh_card_for_account(guild, new_account)
|
|
||||||
|
|
||||||
# -------- command (mod-gated via require_mod_ctx) --------
|
|
||||||
@commands.hybrid_command(name="pirate_cards_rebuild", description="Rebuild pirate cards for all known pirates")
|
|
||||||
async def pirate_cards_rebuild(self, ctx: commands.Context):
|
|
||||||
if not await require_mod_ctx(ctx, "This command is restricted to moderators."):
|
|
||||||
return
|
|
||||||
if not ctx.guild:
|
|
||||||
return await ctx.reply("Use this in a server.", ephemeral=True)
|
|
||||||
|
|
||||||
async with self._lock_for(ctx.guild.id):
|
|
||||||
guild = ctx.guild
|
|
||||||
ch = guild.get_channel(self.pirates_channel_id)
|
|
||||||
if not ch:
|
|
||||||
return await ctx.reply("Configured pirates_list_channel_id not found.", ephemeral=True)
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
for p in self.bot.data_manager.get('pirates'):
|
|
||||||
try:
|
|
||||||
await self.refresh_card_for_account(guild, p.get('account_name', ''))
|
|
||||||
count += 1
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
is_slash = hasattr(ctx, "interaction") and ctx.interaction is not None
|
|
||||||
await ctx.reply(f"Rebuilt/updated {count} pirate cards.", ephemeral=is_slash)
|
|
||||||
|
|
||||||
async def setup(bot):
|
|
||||||
await bot.add_cog(PirateCardsCog(bot))
|
|
Loading…
Reference in New Issue
Block a user