| import { SlashCommandBuilder } from 'discord.js'; |
| import { |
| BOOK_CHOICES, |
| DATE_WINDOW_CHOICES, |
| RESOLUTION_CHOICES, |
| SPORT_CHOICES, |
| STATUS_FILTER_CHOICES, |
| } from './constants.js'; |
|
|
| function addAnalyticsFilters(command, options = {}) { |
| command.addStringOption((option) => |
| option |
| .setName('date_window') |
| .setDescription('Filter analytics by time window.') |
| .setRequired(false) |
| .addChoices(...DATE_WINDOW_CHOICES) |
| ); |
|
|
| if (options.includeSport !== false) { |
| command.addStringOption((option) => |
| option |
| .setName('sport') |
| .setDescription('Filter by sport/league.') |
| .setRequired(false) |
| .addChoices(...SPORT_CHOICES) |
| ); |
| } |
|
|
| if (options.includeBook) { |
| command.addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Filter by sportsbook.') |
| .setRequired(false) |
| .addChoices(...BOOK_CHOICES) |
| ); |
| } |
|
|
| if (options.includeStatus) { |
| command.addStringOption((option) => |
| option |
| .setName('status') |
| .setDescription('Filter by bet status.') |
| .setRequired(false) |
| .addChoices(...STATUS_FILTER_CHOICES) |
| ); |
| } |
|
|
| return command; |
| } |
|
|
| const SPORTSBOOK_BOOK_CHOICES = [ |
| { name: 'Circa', value: 'Circa' }, |
| { name: 'FanDuel', value: 'FanDuel' }, |
| { name: 'DraftKings', value: 'DraftKings' }, |
| { name: 'BetMGM', value: 'BetMGM' }, |
| { name: 'Caesars', value: 'Caesars' }, |
| { name: 'Fanatics', value: 'Fanatics' }, |
| { name: 'Bally Bet', value: 'Bally Bet' }, |
| { name: 'Hard Rock Bet', value: 'Hard Rock Bet' }, |
| ]; |
|
|
| const DFS_BOOK_CHOICES = [ |
| { name: 'Betr Picks', value: 'Betr Picks' }, |
| { name: 'DraftKings Pick6', value: 'DraftKings Pick6' }, |
| { name: 'PrizePicks', value: 'PrizePicks' }, |
| { name: 'Underdog Fantasy', value: 'Underdog Fantasy' }, |
| ]; |
|
|
| const EXCHANGE_BOOK_CHOICES = [ |
| { name: 'Kalshi', value: 'Kalshi' }, |
| { name: 'Novig', value: 'Novig' }, |
| { name: 'Polymarket', value: 'Polymarket' }, |
| { name: 'ProphetX', value: 'ProphetX' }, |
| ]; |
|
|
| const MARKET_INTELLIGENCE_MARKET_CHOICES = [ |
| { name: 'Home Runs', value: 'home_runs' }, |
| { name: 'Hits', value: 'hits' }, |
| { name: 'Total Bases', value: 'total_bases' }, |
| { name: 'RBIs', value: 'rbis' }, |
| { name: 'Runs', value: 'runs' }, |
| { name: 'Steals', value: 'steals' }, |
| { name: 'Hits + Runs + RBIs', value: 'hits_runs_rbis' }, |
| { name: 'Pitcher Strikeouts', value: 'pitcher_strikeouts_generic' }, |
| ]; |
|
|
| function addMarketIntelligenceFilters(command, options = {}) { |
| const bookChoices = options.bookChoices ?? SPORTSBOOK_BOOK_CHOICES; |
| if (options.includeBook && options.bookRequired === true) { |
| command.addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Optional book filter.') |
| .setRequired(true) |
| .addChoices(...bookChoices) |
| ); |
| } |
|
|
| if (options.includePlayer && options.playerRequired === true) { |
| command.addStringOption((option) => |
| option |
| .setName('player') |
| .setDescription('Optional player filter.') |
| .setRequired(true) |
| ); |
| } |
|
|
| if (options.includeMarket !== false) { |
| command.addStringOption((option) => |
| option |
| .setName('market') |
| .setDescription('Optional market filter.') |
| .setRequired(options.marketRequired === true) |
| .addChoices(...MARKET_INTELLIGENCE_MARKET_CHOICES) |
| ); |
| } |
|
|
| if (options.includeBook && options.bookRequired !== true) { |
| command.addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Optional book filter.') |
| .setRequired(false) |
| .addChoices(...bookChoices) |
| ); |
| } |
|
|
| if (options.includeTeam) { |
| command.addStringOption((option) => |
| option |
| .setName('team') |
| .setDescription('Optional team code filter, for example KCR or CHC.') |
| .setRequired(false) |
| ); |
| } |
|
|
| if (options.includePlayer && options.playerRequired !== true) { |
| command.addStringOption((option) => |
| option |
| .setName('player') |
| .setDescription('Optional player filter.') |
| .setRequired(false) |
| ); |
| } |
|
|
| if (options.includeMinEdge) { |
| command.addNumberOption((option) => |
| option |
| .setName('min_edge') |
| .setDescription('Optional minimum sharp edge threshold as a decimal, for example 0.04 for 4%.') |
| .setRequired(false) |
| ); |
| } |
|
|
| if (options.includeMinWidth) { |
| command.addNumberOption((option) => |
| option |
| .setName('min_width') |
| .setDescription('Optional minimum market width threshold as a decimal, for example 0.05 for 5%.') |
| .setRequired(false) |
| ); |
| } |
|
|
| if (options.includeLimit !== false) { |
| command.addIntegerOption((option) => |
| option |
| .setName('limit') |
| .setDescription('Optional result limit.') |
| .setRequired(false) |
| .setMinValue(1) |
| .setMaxValue(25) |
| ); |
| } |
|
|
| return command; |
| } |
|
|
| function addMatchupOptions(command, options = {}) { |
| if (options.includePlayer) { |
| command.addStringOption((option) => |
| option |
| .setName('player') |
| .setDescription('Player name to look up.') |
| .setRequired(true) |
| ); |
| } |
|
|
| if (options.includePlayerType) { |
| command.addStringOption((option) => |
| option |
| .setName('player_type') |
| .setDescription('Optionally force hitter or pitcher context.') |
| .setRequired(false) |
| .addChoices( |
| { name: 'Auto', value: 'auto' }, |
| { name: 'Hitter', value: 'hitter' }, |
| { name: 'Pitcher', value: 'pitcher' } |
| ) |
| ); |
| } |
|
|
| if (options.includeTeam) { |
| command.addStringOption((option) => |
| option |
| .setName('team') |
| .setDescription('Optional team filter, for example CHC or KCR.') |
| .setRequired(false) |
| ); |
| } |
|
|
| if (options.includeDate !== false) { |
| command.addStringOption((option) => |
| option |
| .setName('date') |
| .setDescription('Optional slate date in YYYY-MM-DD format. Defaults to the latest available slate.') |
| .setRequired(false) |
| ); |
| } |
|
|
| if (options.includeLimit !== false) { |
| command.addIntegerOption((option) => |
| option |
| .setName('limit') |
| .setDescription('Optional result limit.') |
| .setRequired(false) |
| .setMinValue(1) |
| .setMaxValue(15) |
| ); |
| } |
|
|
| return command; |
| } |
|
|
| const BASEBALL_CHART_BOOK_CHOICES = SPORTSBOOK_BOOK_CHOICES; |
| const PITCHER_SUITE_WINDOW_CHOICES = [ |
| { name: 'Last 5', value: 'last_5' }, |
| { name: 'Last 10', value: 'last_10' }, |
| { name: 'Season 2026', value: 'season_2026' }, |
| { name: 'Career', value: 'career' }, |
| ]; |
| const PITCHER_SUITE_SPLIT_CHOICES = [ |
| { name: 'Overall', value: 'overall' }, |
| { name: 'Vs LHB', value: 'vs_lhb' }, |
| { name: 'Vs RHB', value: 'vs_rhb' }, |
| ]; |
| const PITCHER_TREND_VIEW_CHOICES = [ |
| { name: 'Velocity', value: 'velo' }, |
| { name: 'Spin', value: 'spin' }, |
| { name: 'Release', value: 'release' }, |
| { name: 'Form', value: 'form' }, |
| { name: 'Results', value: 'results' }, |
| { name: 'Baseline', value: 'baseline' }, |
| ]; |
| const PITCHER_ARSENAL_VIEW_CHOICES = [ |
| { name: 'Shape', value: 'shape' }, |
| { name: 'Movement', value: 'movement' }, |
| { name: 'Usage', value: 'usage' }, |
| { name: 'Evolution', value: 'evolution' }, |
| { name: 'Outcomes', value: 'outcomes' }, |
| { name: 'Platoon', value: 'platoon' }, |
| ]; |
| const PITCHER_LOCATION_VIEW_CHOICES = [ |
| { name: 'Heatmap', value: 'heatmap' }, |
| { name: 'By Pitch', value: 'bypitch' }, |
| { name: 'Two Strike', value: 'twostrike' }, |
| { name: 'Chase', value: 'chase' }, |
| { name: 'Damage', value: 'damage' }, |
| { name: 'Miss', value: 'miss' }, |
| ]; |
| const PITCHER_APPROACH_VIEW_CHOICES = [ |
| { name: 'Count Usage', value: 'count_usage' }, |
| { name: 'Count Whiff', value: 'count_whiff' }, |
| { name: 'Ahead / Behind', value: 'ahead_behind' }, |
| { name: 'First Pitch', value: 'first_pitch' }, |
| { name: 'Putaway', value: 'putaway' }, |
| ]; |
| const PITCHER_COMPARE_VIEW_CHOICES = [ |
| { name: 'Current vs Career', value: 'current_vs_career' }, |
| { name: 'Recent vs Baseline', value: 'recent_vs_baseline' }, |
| { name: 'Year Over Year', value: 'year_over_year' }, |
| { name: 'Risk Reward', value: 'risk_reward' }, |
| ]; |
| const PITCHER_COMPARE_TO_CHOICES = [ |
| { name: 'Season 2026', value: 'season_2026' }, |
| { name: 'Career', value: 'career' }, |
| { name: 'Prior 5', value: 'prior_5' }, |
| { name: 'Prior 10', value: 'prior_10' }, |
| ]; |
| const PITCHER_COUNT_BUCKET_CHOICES = [ |
| { name: 'All', value: 'all' }, |
| { name: 'First Pitch', value: 'first_pitch' }, |
| { name: 'Ahead', value: 'ahead' }, |
| { name: 'Behind', value: 'behind' }, |
| { name: 'Putaway', value: 'putaway' }, |
| { name: 'Two Strike', value: 'two_strike' }, |
| ]; |
|
|
| const DIRECT_ODDS_MARKET_CHOICES = [ |
| { name: 'Home Runs', value: 'batter_home_runs' }, |
| { name: 'Hits', value: 'batter_hits' }, |
| { name: 'Total Bases', value: 'batter_total_bases' }, |
| { name: 'RBIs', value: 'batter_rbis' }, |
| { name: 'Runs', value: 'batter_runs_scored' }, |
| { name: 'Steals', value: 'batter_stolen_bases' }, |
| { name: 'Hits + Runs + RBIs', value: 'batter_hits_runs_rbis' }, |
| { name: 'Pitcher Strikeouts', value: 'pitcher_strikeouts' }, |
| ]; |
|
|
| function addBaseballChartDateOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('date') |
| .setDescription('Optional slate date in YYYY-MM-DD format. Defaults to the latest available slate.') |
| .setRequired(false) |
| ); |
| } |
|
|
| function addBaseballChartWindowOption(command) { |
| return command.addIntegerOption((option) => |
| option |
| .setName('window') |
| .setDescription('Optional number of recent slate points to chart.') |
| .setRequired(false) |
| .setMinValue(3) |
| .setMaxValue(14) |
| ); |
| } |
|
|
| function addBaseballChartLimitOption(command) { |
| return command.addIntegerOption((option) => |
| option |
| .setName('limit') |
| .setDescription('Optional result limit.') |
| .setRequired(false) |
| .setMinValue(1) |
| .setMaxValue(15) |
| ); |
| } |
|
|
| function addBaseballChartBookOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Optional sportsbook filter.') |
| .setRequired(false) |
| .addChoices(...BASEBALL_CHART_BOOK_CHOICES) |
| ); |
| } |
|
|
| function addPitcherSuitePitcherOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('pitcher') |
| .setDescription('Pitcher name to chart.') |
| .setRequired(true) |
| ); |
| } |
|
|
| function addPitcherSuiteViewOption(command, choices) { |
| return command.addStringOption((option) => |
| option |
| .setName('view') |
| .setDescription('Chart view to render.') |
| .setRequired(true) |
| .addChoices(...choices) |
| ); |
| } |
|
|
| function addPitcherSuitePitchTypeOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('pitch_type') |
| .setDescription('Optional pitch type filter, for example FF, SL, or CH.') |
| .setRequired(false) |
| ); |
| } |
|
|
| function addPitcherSuiteWindowOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('window') |
| .setDescription('Optional time window.') |
| .setRequired(false) |
| .addChoices(...PITCHER_SUITE_WINDOW_CHOICES) |
| ); |
| } |
|
|
| function addPitcherSuiteSplitOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('split') |
| .setDescription('Optional batter-side split.') |
| .setRequired(false) |
| .addChoices(...PITCHER_SUITE_SPLIT_CHOICES) |
| ); |
| } |
|
|
| function addPitcherSuiteCompareToOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('compare_to') |
| .setDescription('Optional comparison baseline.') |
| .setRequired(false) |
| .addChoices(...PITCHER_COMPARE_TO_CHOICES) |
| ); |
| } |
|
|
| function addPitcherSuiteCountBucketOption(command) { |
| return command.addStringOption((option) => |
| option |
| .setName('count_bucket') |
| .setDescription('Optional count bucket filter.') |
| .setRequired(false) |
| .addChoices(...PITCHER_COUNT_BUCKET_CHOICES) |
| ); |
| } |
|
|
| export const commands = [ |
| new SlashCommandBuilder() |
| .setName('bet') |
| .setDescription('Log a new bet using a private modal.') |
| .addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Choose the sportsbook for this bet.') |
| .setRequired(true) |
| .addChoices(...BOOK_CHOICES) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('sport') |
| .setDescription('Choose the sport/league for this bet.') |
| .setRequired(true) |
| .addChoices(...SPORT_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('bulkadd') |
| .setDescription('Log multiple bets privately for a single sportsbook and sport.') |
| .addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Choose the sportsbook for this batch.') |
| .setRequired(true) |
| .addChoices(...BOOK_CHOICES) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('sport') |
| .setDescription('Choose the sport/league for this batch.') |
| .setRequired(true) |
| .addChoices(...SPORT_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('editbet') |
| .setDescription('Edit one of your open bets.') |
| .addIntegerOption((option) => |
| option.setName('bet_id').setDescription('Your bet number to edit.').setRequired(true) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Update sportsbook.') |
| .setRequired(false) |
| .addChoices(...BOOK_CHOICES) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('sport') |
| .setDescription('Update sport/league.') |
| .setRequired(false) |
| .addChoices(...SPORT_CHOICES) |
| ) |
| .addStringOption((option) => |
| option.setName('prop').setDescription('Update prop/bet text.').setRequired(false) |
| ) |
| .addStringOption((option) => |
| option.setName('odds').setDescription('Update odds.').setRequired(false) |
| ) |
| .addNumberOption((option) => |
| option.setName('stake').setDescription('Update stake amount.').setRequired(false) |
| ), |
| new SlashCommandBuilder() |
| .setName('deletebet') |
| .setDescription('Soft delete one of your bets so it no longer counts in stats.') |
| .addIntegerOption((option) => |
| option.setName('bet_id').setDescription('Your bet number to delete.').setRequired(true) |
| ) |
| .addStringOption((option) => |
| option.setName('reason').setDescription('Optional reason for deletion.').setRequired(false) |
| ), |
| new SlashCommandBuilder() |
| .setName('resolve') |
| .setDescription('Resolve one of your tracked bets.') |
| .addIntegerOption((option) => |
| option.setName('bet_id').setDescription('Your bet number to resolve.').setRequired(true) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('result') |
| .setDescription('The final result for this bet.') |
| .setRequired(true) |
| .addChoices(...RESOLUTION_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('resolveall') |
| .setDescription('Resolve multiple of your tracked bets with the same result.') |
| .addStringOption((option) => |
| option |
| .setName('bet_ids') |
| .setDescription('Comma-separated bet IDs, for example: 12, 13, 14') |
| .setRequired(true) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('result') |
| .setDescription('The final result for all listed bets.') |
| .setRequired(true) |
| .addChoices(...RESOLUTION_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('bankroll') |
| .setDescription('View or update your bankroll and unit settings.') |
| .addNumberOption((option) => |
| option.setName('starting_bankroll').setDescription('Set your starting bankroll.').setRequired(false) |
| ) |
| .addNumberOption((option) => |
| option.setName('unit_size').setDescription('Set your unit size in dollars.').setRequired(false) |
| ), |
| addAnalyticsFilters( |
| new SlashCommandBuilder() |
| .setName('roi') |
| .setDescription('Show your ROI, net profit, and cumulative profit chart.'), |
| { includeBook: true } |
| ), |
| addAnalyticsFilters( |
| new SlashCommandBuilder() |
| .setName('bets') |
| .setDescription('Show your total bets and browse your logged bets.'), |
| { includeBook: true, includeStatus: true } |
| ), |
| addAnalyticsFilters( |
| new SlashCommandBuilder() |
| .setName('summary') |
| .setDescription('Show your full betting summary and chart.'), |
| { includeBook: true } |
| ), |
| addAnalyticsFilters( |
| new SlashCommandBuilder() |
| .setName('books') |
| .setDescription('Show your ROI and win rate broken down by sportsbook.'), |
| { includeSport: true } |
| ), |
| addAnalyticsFilters( |
| new SlashCommandBuilder() |
| .setName('sports') |
| .setDescription('Show your ROI and win rate broken down by sport.'), |
| { includeBook: true, includeSport: false } |
| ), |
| addAnalyticsFilters( |
| new SlashCommandBuilder() |
| .setName('export') |
| .setDescription('Export your bets to CSV.'), |
| { includeBook: true, includeStatus: true } |
| ), |
| new SlashCommandBuilder() |
| .setName('commands') |
| .setDescription('Post a public command reference embed.'), |
| new SlashCommandBuilder() |
| .setName('scanstatus') |
| .setDescription('Show the market scanner status and latest run info.'), |
| new SlashCommandBuilder() |
| .setName('oddsquota') |
| .setDescription('Show the latest Odds API quota usage headers.'), |
| new SlashCommandBuilder() |
| .setName('scanrun') |
| .setDescription('Run the Circa market scanner manually.'), |
| new SlashCommandBuilder() |
| .setName('scanreport') |
| .setDescription('Post the daily scan reports immediately.'), |
| new SlashCommandBuilder() |
| .setName('circatest') |
| .setDescription('Run a Circa OCR diagnostic and preview parsed entries.'), |
| new SlashCommandBuilder() |
| .setName('circamarket') |
| .setDescription('Show the latest Circa market in the current channel.') |
| .addStringOption((option) => |
| option |
| .setName('market') |
| .setDescription('Choose which Circa market to show.') |
| .setRequired(true) |
| .addChoices( |
| { name: 'Home Runs', value: 'home_runs' }, |
| { name: 'Hits', value: 'hits' }, |
| { name: 'Total Bases', value: 'total_bases' }, |
| { name: 'RBIs', value: 'rbis' }, |
| { name: 'Runs', value: 'runs' }, |
| { name: 'Steals', value: 'steals' }, |
| { name: 'Hits + Runs + RBIs', value: 'hits_runs_rbis' }, |
| { name: 'Pitcher Strikeouts', value: 'pitcher_strikeouts_generic' } |
| ) |
| ), |
| new SlashCommandBuilder() |
| .setName('circahr') |
| .setDescription('Show the latest Circa Home Runs market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('circahits') |
| .setDescription('Show the latest Circa Hits market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('circatb') |
| .setDescription('Show the latest Circa Total Bases market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('circarbis') |
| .setDescription('Show the latest Circa RBIs market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('circaruns') |
| .setDescription('Show the latest Circa Runs market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('circasb') |
| .setDescription('Show the latest Circa Steals market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('circahrri') |
| .setDescription('Show the latest Circa Hits + Runs + RBIs market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('circak') |
| .setDescription('Show the latest Circa Pitcher Strikeouts market in this channel.'), |
| new SlashCommandBuilder() |
| .setName('hrodds') |
| .setDescription('Show current home run odds for one player across books.') |
| .addStringOption((option) => |
| option |
| .setName('player') |
| .setDescription('Player name to look up, for example Aaron Judge.') |
| .setRequired(true) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('book') |
| .setDescription('Optionally limit the result to one book. Defaults to all books including Circa.') |
| .setRequired(false) |
| .addChoices(...SPORTSBOOK_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('hitodds') |
| .setDescription('Show current hits odds for one player across books.') |
| .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('tbodds') |
| .setDescription('Show current total bases odds for one player across books.') |
| .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('rbiodds') |
| .setDescription('Show current RBI odds for one player across books.') |
| .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('runodds') |
| .setDescription('Show current run-scored odds for one player across books.') |
| .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('sbodds') |
| .setDescription('Show current stolen base odds for one player across books.') |
| .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('kodds') |
| .setDescription('Show current pitcher strikeout odds for one player across books.') |
| .addStringOption((option) => option.setName('player').setDescription('Pitcher name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one book.').setRequired(false).addChoices(...SPORTSBOOK_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('dfsodds') |
| .setDescription('Show current DFS or pick\'em prices for one player within the DFS lane.') |
| .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('market').setDescription('Choose which DFS market to query.').setRequired(true).addChoices(...DIRECT_ODDS_MARKET_CHOICES) |
| ) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one DFS source.').setRequired(false).addChoices(...DFS_BOOK_CHOICES) |
| ), |
| new SlashCommandBuilder() |
| .setName('exchangeodds') |
| .setDescription('Show current exchange prices for one player within the exchange lane.') |
| .addStringOption((option) => option.setName('player').setDescription('Player name to look up.').setRequired(true)) |
| .addStringOption((option) => |
| option.setName('market').setDescription('Choose which exchange market to query.').setRequired(true).addChoices(...DIRECT_ODDS_MARKET_CHOICES) |
| ) |
| .addStringOption((option) => |
| option.setName('book').setDescription('Optionally limit the result to one exchange source.').setRequired(false).addChoices(...EXCHANGE_BOOK_CHOICES) |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('edgeboard') |
| .setDescription('Show the best current edges versus the sharp book.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('playeredge') |
| .setDescription('Show sharp comparisons across all supported markets for one player.'), |
| { includePlayer: true, playerRequired: true, includeMarket: true, includeBook: true, includeMinEdge: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('marketedge') |
| .setDescription('Show the best current edges for one market.'), |
| { includeMarket: true, marketRequired: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('widthboard') |
| .setDescription('Show the widest current market splits across books.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinWidth: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('consensusvs') |
| .setDescription('Compare one book to the current sharp market.'), |
| { includeMarket: true, includeBook: true, bookRequired: true, includeTeam: true, includeMinEdge: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('dfsedgeboard') |
| .setDescription('Show the best current DFS or pick\'em edges within the DFS lane.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('dfsplayeredge') |
| .setDescription('Show DFS lane comparisons across all supported markets for one player.'), |
| { includePlayer: true, playerRequired: true, includeMarket: true, includeBook: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('dfsmarketedge') |
| .setDescription('Show the best current DFS lane edges for one market.'), |
| { includeMarket: true, marketRequired: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('dfswidthboard') |
| .setDescription('Show the widest current DFS lane market splits.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinWidth: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('dfsconsensusvs') |
| .setDescription('Compare one DFS source to the current DFS lane consensus.'), |
| { includeMarket: true, includeBook: true, bookRequired: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: DFS_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('exchangeedgeboard') |
| .setDescription('Show the best current exchange-lane edges.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('exchangeplayeredge') |
| .setDescription('Show exchange-lane comparisons across all supported markets for one player.'), |
| { includePlayer: true, playerRequired: true, includeMarket: true, includeBook: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('exchangemarketedge') |
| .setDescription('Show the best current exchange-lane edges for one market.'), |
| { includeMarket: true, marketRequired: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('exchangewidthboard') |
| .setDescription('Show the widest current exchange-lane market splits.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinWidth: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('exchangeconsensusvs') |
| .setDescription('Compare one exchange source to the current exchange lane consensus.'), |
| { includeMarket: true, includeBook: true, bookRequired: true, includeTeam: true, includeMinEdge: true, includeLimit: true, bookChoices: EXCHANGE_BOOK_CHOICES } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('steam') |
| .setDescription('Show recent price movement anchored to the current sharp market.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includePlayer: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('sharpboard') |
| .setDescription('Show the best current sharp-vs-soft values.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('bookscoreboard') |
| .setDescription('Rank books by sharp mispricing count and size.'), |
| { includeMarket: true, includeBook: true, includeTeam: true, includeMinEdge: true, includeLimit: true } |
| ), |
| addMarketIntelligenceFilters( |
| new SlashCommandBuilder() |
| .setName('markethealth') |
| .setDescription('Show coverage and market quality by supported market.'), |
| { includeMarket: true, includeLimit: true } |
| ), |
| addMatchupOptions( |
| new SlashCommandBuilder() |
| .setName('matchuphitters') |
| .setDescription('Show the best hitter matchups from the hosted MLB artifact slate.'), |
| { includeTeam: true, includeDate: true, includeLimit: true } |
| ), |
| addMatchupOptions( |
| new SlashCommandBuilder() |
| .setName('matchuppitchers') |
| .setDescription('Show the best pitcher matchups from the hosted MLB artifact slate.'), |
| { includeTeam: true, includeDate: true, includeLimit: true } |
| ), |
| addMatchupOptions( |
| new SlashCommandBuilder() |
| .setName('playercontext') |
| .setDescription('Show matchup context for one hitter or pitcher.'), |
| { includePlayer: true, includePlayerType: true, includeDate: true, includeLimit: false } |
| ), |
| addMatchupOptions( |
| new SlashCommandBuilder() |
| .setName('bestmatchups') |
| .setDescription('Show a compact board of the best hitter matchups for the active slate.'), |
| { includeTeam: true, includeDate: true, includeLimit: true } |
| ), |
| new SlashCommandBuilder() |
| .setName('teambestmatchups') |
| .setDescription('Show the best 3 hitter matchups for one team from the active slate.') |
| .addStringOption((option) => |
| option |
| .setName('team') |
| .setDescription('Team filter, for example CHC or KCR.') |
| .setRequired(true) |
| ) |
| .addStringOption((option) => |
| option |
| .setName('date') |
| .setDescription('Optional slate date in YYYY-MM-DD format. Defaults to the latest available slate.') |
| .setRequired(false) |
| ) |
| .addIntegerOption((option) => |
| option |
| .setName('limit') |
| .setDescription('Optional result limit.') |
| .setRequired(false) |
| .setMinValue(1) |
| .setMaxValue(15) |
| ), |
| new SlashCommandBuilder() |
| .setName('matchuphealth') |
| .setDescription('Show hosted artifact freshness and Cockroach fallback status for matchup data.'), |
| addBaseballChartDateOption( |
| addBaseballChartLimitOption( |
| new SlashCommandBuilder() |
| .setName('hrboardchart') |
| .setDescription('Render a branded chart of the top home run targets on the active slate.') |
| .addStringOption((option) => |
| option |
| .setName('team') |
| .setDescription('Optional team filter, for example CHC or KCR.') |
| .setRequired(false) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addBaseballChartWindowOption( |
| new SlashCommandBuilder() |
| .setName('hrtrend') |
| .setDescription('Render a rolling home run trend chart for one hitter.') |
| .addStringOption((option) => |
| option |
| .setName('player') |
| .setDescription('Hitter name to chart.') |
| .setRequired(true) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| new SlashCommandBuilder() |
| .setName('hrprofile') |
| .setDescription('Render a home run profile radar for one hitter.') |
| .addStringOption((option) => |
| option |
| .setName('player') |
| .setDescription('Hitter name to chart.') |
| .setRequired(true) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addBaseballChartBookOption( |
| addBaseballChartLimitOption( |
| new SlashCommandBuilder() |
| .setName('hrvaluechart') |
| .setDescription('Render a price-versus-model chart for current home run targets.') |
| .addStringOption((option) => |
| option |
| .setName('team') |
| .setDescription('Optional team filter, for example CHC or KCR.') |
| .setRequired(false) |
| ) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| new SlashCommandBuilder() |
| .setName('hrzone') |
| .setDescription('Render a zone overlay card for one hitter against the probable pitcher.') |
| .addStringOption((option) => |
| option |
| .setName('player') |
| .setDescription('Hitter name to chart.') |
| .setRequired(true) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addBaseballChartWindowOption( |
| new SlashCommandBuilder() |
| .setName('ktrend') |
| .setDescription('Render a rolling strikeout trend chart for one pitcher.') |
| .addStringOption((option) => |
| option |
| .setName('pitcher') |
| .setDescription('Pitcher name to chart.') |
| .setRequired(true) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addBaseballChartBookOption( |
| new SlashCommandBuilder() |
| .setName('kladder') |
| .setDescription('Render a strikeout ladder chart for one pitcher.') |
| .addStringOption((option) => |
| option |
| .setName('pitcher') |
| .setDescription('Pitcher name to chart.') |
| .setRequired(true) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| new SlashCommandBuilder() |
| .setName('kprofile') |
| .setDescription('Render a pitcher strikeout profile radar.') |
| .addStringOption((option) => |
| option |
| .setName('pitcher') |
| .setDescription('Pitcher name to chart.') |
| .setRequired(true) |
| ) |
| ), |
| addBaseballChartDateOption( |
| new SlashCommandBuilder() |
| .setName('kmatchup') |
| .setDescription('Render a pitcher strikeout matchup card against the opposing lineup.') |
| .addStringOption((option) => |
| option |
| .setName('pitcher') |
| .setDescription('Pitcher name to chart.') |
| .setRequired(true) |
| ) |
| ), |
| addBaseballChartDateOption( |
| new SlashCommandBuilder() |
| .setName('kcount') |
| .setDescription('Render a count leverage chart for one pitcher.') |
| .addStringOption((option) => |
| option |
| .setName('pitcher') |
| .setDescription('Pitcher name to chart.') |
| .setRequired(true) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addPitcherSuiteSplitOption( |
| addPitcherSuiteCompareToOption( |
| addPitcherSuiteWindowOption( |
| addPitcherSuitePitchTypeOption( |
| addPitcherSuiteViewOption( |
| addPitcherSuitePitcherOption( |
| new SlashCommandBuilder() |
| .setName('pitchertrend') |
| .setDescription('Render time-based pitcher analysis charts beyond the daily slate.') |
| ), |
| PITCHER_TREND_VIEW_CHOICES |
| ) |
| ) |
| ) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addPitcherSuiteSplitOption( |
| addPitcherSuiteWindowOption( |
| addPitcherSuitePitchTypeOption( |
| addPitcherSuiteViewOption( |
| addPitcherSuitePitcherOption( |
| new SlashCommandBuilder() |
| .setName('pitcherarsenal') |
| .setDescription('Render arsenal, movement, usage, and pitch outcome views for one pitcher.') |
| ), |
| PITCHER_ARSENAL_VIEW_CHOICES |
| ) |
| ) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addPitcherSuiteCountBucketOption( |
| addPitcherSuiteSplitOption( |
| addPitcherSuitePitchTypeOption( |
| addPitcherSuiteViewOption( |
| addPitcherSuitePitcherOption( |
| new SlashCommandBuilder() |
| .setName('pitcherlocation') |
| .setDescription('Render location, zone, and attack-pattern charts for one pitcher.') |
| ), |
| PITCHER_LOCATION_VIEW_CHOICES |
| ) |
| ) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addPitcherSuiteSplitOption( |
| addPitcherSuiteWindowOption( |
| addPitcherSuitePitchTypeOption( |
| addPitcherSuiteViewOption( |
| addPitcherSuitePitcherOption( |
| new SlashCommandBuilder() |
| .setName('pitcherapproach') |
| .setDescription('Render count-state and game-plan charts for one pitcher.') |
| ), |
| PITCHER_APPROACH_VIEW_CHOICES |
| ) |
| ) |
| ) |
| ) |
| ), |
| addBaseballChartDateOption( |
| addPitcherSuiteWindowOption( |
| addPitcherSuiteViewOption( |
| addPitcherSuitePitcherOption( |
| new SlashCommandBuilder() |
| .setName('pitchercompare') |
| .setDescription('Render baseline, year-over-year, and risk/reward comparison charts for one pitcher.') |
| ), |
| PITCHER_COMPARE_VIEW_CHOICES |
| ) |
| ) |
| ), |
| new SlashCommandBuilder() |
| .setName('alerts') |
| .setDescription('Post the analyst alert-role embed to the welcome channel.'), |
| new SlashCommandBuilder() |
| .setName('welcome') |
| .setDescription('Post the welcome embed and tag everyone.'), |
| new SlashCommandBuilder() |
| .setName('bulkdeletemessages') |
| .setDescription('Bulk delete recent messages from the current channel.') |
| .addIntegerOption((option) => |
| option |
| .setName('count') |
| .setDescription('How many of the most recent messages to inspect and bulk delete.') |
| .setRequired(true) |
| .setMinValue(1) |
| .setMaxValue(1000) |
| ), |
| ].map((command) => command.toJSON()); |
|
|