paper-dynasty-discord/cogs/economy_new/scouting.py
Cal Corum 2d5bd86d52 feat: Add Scouting feature (Wonder Pick-style social pack opening)
When a player opens a pack, a scout opportunity is posted to #pack-openings
with face-down card buttons. Other players can blind-pick one card using
daily scout tokens (2/day), receiving a copy. The opener keeps all cards.

New files:
- discord_ui/scout_view.py: ScoutView with dynamic buttons and claim logic
- helpers/scouting.py: create_scout_opportunity() and embed builder
- cogs/economy_new/scouting.py: /scout-tokens command and cleanup task

Modified:
- helpers/main.py: Hook into open_st_pr_packs() after display_cards()
- paperdynasty.py: Register scouting cog

Requires new API endpoints in paper-dynasty-database (scout_opportunities).
Tracks #44.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:22:58 +00:00

105 lines
3.2 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.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")
SCOUT_TOKENS_PER_DAY = 2
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
now = datetime.datetime.now()
midnight = int_timestamp(
datetime.datetime(now.year, now.month, now.day, 0, 0, 0)
)
used_today = await db_get(
"rewards",
params=[
("name", "Scout Token"),
("team_id", team["id"]),
("created_after", midnight),
],
)
tokens_used = used_today["count"] if used_today else 0
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))