paper-dynasty-discord/cogs/economy_new/scouting.py
Cal Corum d538c679c3 refactor: Consolidate scouting utilities, add test suite, use Discord timestamps
- Consolidate SCOUT_TOKENS_PER_DAY and get_scout_tokens_used() into
  helpers/scouting.py (was duplicated across 3 files)
- Add midnight_timestamp() utility to helpers/utils.py
- Remove _build_scouted_ids() wrapper, use self.claims directly
- Fix build_scout_embed return type annotation
- Use Discord <t:UNIX:R> relative timestamps for scout window countdown
- Add 66-test suite covering helpers, ScoutView, and cog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 03:04:53 +00:00

91 lines
2.9 KiB
Python

"""
Scouting Cog — Scout token management and expired opportunity cleanup.
"""
import datetime
import logging
import discord
from discord import app_commands
from discord.ext import commands, tasks
from api_calls import db_get
from helpers.scouting import SCOUT_TOKENS_PER_DAY, get_scout_tokens_used
from helpers.utils import int_timestamp
from helpers.discord_utils import get_team_embed
from helpers.main import get_team_by_owner
from helpers.constants import PD_SEASON, IMAGES
logger = logging.getLogger("discord_app")
class Scouting(commands.Cog):
"""Scout token tracking and expired opportunity cleanup."""
def __init__(self, bot):
self.bot = bot
self.cleanup_expired.start()
async def cog_unload(self):
self.cleanup_expired.cancel()
@app_commands.command(
name="scout-tokens",
description="Check how many scout tokens you have left today",
)
async def scout_tokens_command(self, interaction: discord.Interaction):
await interaction.response.defer(ephemeral=True)
team = await get_team_by_owner(interaction.user.id)
if not team:
await interaction.followup.send(
"You need a Paper Dynasty team first!",
ephemeral=True,
)
return
tokens_used = await get_scout_tokens_used(team["id"])
tokens_remaining = max(0, SCOUT_TOKENS_PER_DAY - tokens_used)
embed = get_team_embed(title="Scout Tokens", team=team)
embed.description = (
f"**{tokens_remaining}** of **{SCOUT_TOKENS_PER_DAY}** tokens remaining today.\n\n"
f"Tokens reset at midnight Central."
)
if tokens_remaining == 0:
embed.description += "\n\nYou've used all your tokens! Check back tomorrow."
await interaction.followup.send(embed=embed, ephemeral=True)
@tasks.loop(minutes=15)
async def cleanup_expired(self):
"""Log expired unclaimed scout opportunities.
This is a safety net — the ScoutView's on_timeout handles the UI side.
If the bot restarted mid-scout, those views are lost; this just logs it.
"""
try:
now = int_timestamp(datetime.datetime.now())
expired = await db_get(
"scout_opportunities",
params=[
("claimed", False),
("expired_before", now),
],
)
if expired and expired.get("count", 0) > 0:
logger.info(
f"Found {expired['count']} expired unclaimed scout opportunities"
)
except Exception as e:
logger.debug(f"Scout cleanup check failed (API may not be ready): {e}")
@cleanup_expired.before_loop
async def before_cleanup(self):
await self.bot.wait_until_ready()
async def setup(bot):
await bot.add_cog(Scouting(bot))