- 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>
91 lines
2.9 KiB
Python
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))
|