Wrap the wallet deduction in try/except so a failed db_patch immediately
stops the view and shows an error, instead of leaving it open for 30s.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
message.edit(view=self) re-registers the view in discord.py's ViewStore,
resetting the 30-minute timeout timer. Scouted packs never showed
"Scout Window Closed" because each scout pushed the timeout further out.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a user exceeds their 2/day scout token limit, they are now offered
a button to purchase an extra token for 200₼ instead of being blocked.
Updates /scout-tokens message to mention the purchase option.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spread scout buttons across multiple rows (5 per row) instead of
all on row 0. Cap at 25 buttons (Discord max) using the last 25 cards.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The rewards API requires a week field. The scout claim callback was
posting without it, causing a 422 validation error when a user
selected a card from a scout opportunity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add SCOUTABLE_PACK_TYPES env var (default: Standard,Premium) to control
which pack types offer scout opportunities
- Unify embed construction into build_scout_embed() — removes 3 near-duplicate
embed builders across scout_view.py and scouting.py
- Replace manual total_scouts counter with derived property from claims dict
- Remove redundant db_get("current") API call per scout click — use PD_SEASON
- Remove duplicate expiry computation in create_scout_opportunity
- Move send_to_channel to top-level import, remove redundant local import
- Update tests to match simplified code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Include pack_id in db_post("cards") payload (API requires it)
- Player names now link to card image URLs in scout embed
- Display format: "🟡 All-Star — [2023 Mike Trout](card_image_url)"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix int_timestamp() no-arg path returning seconds instead of
milliseconds, which would silently break the daily scout token cap
against the real API
- Acknowledge double-click interactions with ephemeral message instead
of silently returning (Discord requires all interactions to be acked)
- Reorder scout flow: create card copy before consuming token so a
failure doesn't cost the player a token for nothing
- Move build_scouted_card_list import to top of scout_view.py
- Remove unused asyncio import from helpers/scouting.py
- Fix footer text inconsistency ("One scout per player" everywhere)
- Update tests for new operation order and double-click behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
- Add FRANCHISE_NORMALIZE dict and helper to constants.py
- Update economy.py to normalize team_choice and use sname
- Update helpers/main.py franchise queries to use sname
- Update selectors.py to normalize franchise on player updates
Part of cross-era player matching fix for AI rosters
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add 'Athletics' alias to ALL_MLB_TEAMS, IMAGES['mvp'], and AL_TEAM_IDS
to support both old franchise name ("Oakland Athletics") and new mlbclub
name ("Athletics") after the team's relocation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Users were receiving "This interaction failed" when trying to open Team
Choice packs that had neither pack_team nor pack_cardset assigned in the
database.
The previous fix only handled packs WITH cardset but WITHOUT team. This
adds handling for completely unconfigured Team Choice packs.
Now shows a helpful error message: "This Team Choice pack needs to be
assigned a team and cardset. Please contact an admin to configure this pack."
This prevents the KeyError exception that was being thrown at
helpers.py:1692 when open_choice_pack() received a pack with pack_team=None.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When opening a Team Choice pack without a pre-assigned team, the bot would:
1. Edit the interaction to remove the pack selection view
2. Send team selection (AL/NL) via interaction.channel.send()
3. Return without sending a followup to the interaction
4. Discord left waiting for a response that never came
Changed interaction.channel.send() to interaction.followup.send() with
a helpful message explaining what's happening. This properly responds to
the interaction and allows the team selection dropdown to function.
The issue occurred at discord_ui/selectors.py:188 in SelectOpenPack callback.
Fixes: /open-packs command hangs when selecting 'Team Choice' pack type
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>