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>
105 lines
3.2 KiB
Python
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))
|