Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

113 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

⚽ gptfoot (Telegram/Discord bot)

🌐 Overview

gptfoot is a bot for Telegram and Discord, meticulously designed to track match events for clubs across multiple leagues, all while minimizing message volume. It utilizes cutting-edge AI technology (OpenRouter API with MiniMax-M3, GPT-4o, Claude, Gemini and more) to provide live match commentary. The bot offers a broad range of features, from showcasing team lineups to reporting goals, red cards, and delivering comprehensive match analyses, all backed by advanced AI capabilities. It's a game-changer for those looking to stay updated on matches in a seamless and informative way.

πŸ›  Features

  • Detection of whether a team is playing a match today in different leagues
  • Real-time monitoring of match events using Telegram/Discord bots powered by AI
  • Integration with the api-football API for retrieving match data, predictions, and team statistics
  • AI-driven match analysis with contextual awareness:
    • History of last 5 matches (richer insights)
    • NEW v2.6.0 β€” Current-season statistics for the followed team in the league of the match (single API call per match, cached and reused for both pre-match and post-match LLM prompts)
  • NEW v2.6.0 β€” Compact season-stats summary appended after the post-match analysis (Markdown legacy compatible with Telegram & Discord)
  • NEW v2.6.0 β€” League type configurable via [LEAGUE_TYPES] (no more hard-coded league IDs in the source)
  • NEW v2.6.0 β€” config.ini.example template + .gitignore for safe public deployment
  • Can be used with a Telegram bot, a Discord bot, or both (configurable in config.ini)
  • Can be used with either the free or paid API from api-football (configurable in config.ini)
  • Professional logging system with automatic rotation (max 10MB, keeps 5 backups)
  • API cost tracking and reporting (transparent monitoring of AI API usage)
  • Match history storage (last 5 matches) for contextual AI analysis
  • Intelligent message splitting for long AI responses (respects platform limits)
  • API key validation at startup with helpful error messages
  • Retry mechanism with exponential backoff for improved reliability
  • Enhanced error handling for network issues and API failures
  • The frequency of messages is limited to ensure a pleasant experience for users
  • Works with the free version of api-football (up to 100 calls per day)
  • Support for dozens of languages
  • Configurable AI models (OpenRouter API)

πŸ†• What's New in v2.8.1

  • βœ… Fix β€” Missing venue no longer leaks None: when api-football returns a null stadium and/or city for a fixture (common on some leagues), the "match today" prompt used to literally send Stade : None, GenΓ¨ve to the LLM, which then wrote "stadium not provided". The location line is now built only from the fields that actually exist (venue + city, city only, venue only, or omitted), and the model is instructed never to say the venue is missing. No additional API call.
  • βœ… Fix β€” Graceful handling of empty lineups: on the free api-football tier, the startXI arrays are sometimes still empty at kickoff. Instead of dumping empty lineups into the prompt (which made the model ramble about "no lineup provided"), the bot now detects the absence of a starting XI and switches to a shorter pre-match preview (recent form + prediction only), without inventing players and without apologizing for the missing data. No additional API call, no extra LLM call.

πŸ†• What's New in v2.8.0

  • βœ… Fix β€” Infinite loop after penalty shootouts (free API): the shootout handler was waiting for the PEN status to change, but PEN is a final status (match finished after penalties) and never changes. On the free API tier this drained the daily quota and the end-of-match summary was never sent. The handler now monitors P (shootout in progress) and lets PEN be handled as a normal end of match.
  • βœ… Fix β€” Bot permanently dead after an API quota error: RateLimitExceededError escaping from the live-event loop used to kill the daily 09:00 check task silently β€” the script kept running but never checked matches again until a manual restart. The daily loop now catches all exceptions and resumes the next day. A related bug was also fixed: get_check_match_status was swallowing RateLimitExceededError in its generic except, preventing a clean stop of the match tracking.
  • βœ… Fix β€” Graceful shutdown: Ctrl+C (SIGINT), kill (SIGTERM) and SIGQUIT now log and stop the script cleanly (previously only SIGKILL could terminate it). SIGHUP is still ignored so the bot survives terminal closure under nohup.
  • βœ… Fix β€” Background tasks garbage collection: references to all asyncio background tasks are now kept (the event loop only holds weak references), tasks are named, and their unhandled exceptions are logged.
  • βœ… Fix β€” Lost Telegram messages on invalid Markdown: LLM-generated text with unbalanced Markdown made Telegram reject the message (it was only logged, never delivered). Messages are now retried in plain text, and Discord-style **bold** is converted to Telegram legacy *bold*.
  • βœ… Fix β€” Timezone-aware scheduling: all wait computations (daily 09:00 check, kickoff countdown, "is the match today?") now use the TIMEZONE from config.ini instead of the OS clock.
  • βœ… Robustness β€” Terminal match statuses: abandoned / cancelled / postponed / forfeited matches (ABD, CANC, PST, AWD, WO) now cleanly stop the live tracking loop, and suspended matches (SUSP) are handled like interruptions.
  • βœ… Robustness β€” AI failure fallbacks: when the OpenRouter API is definitively unavailable, users now receive a factual fallback (match announcement, "But de X !", raw match events) instead of an error string; failed analyses are no longer stored in the match history (they used to pollute the context of future prompts). Empty LLM responses (reasoning models exhausting max_tokens) are retried. Failed translations now fall back to the original message.
  • βœ… Robustness β€” Atomic JSON writes: telegram_chat_ids.json, discord_channels.json and match_analyses.json are written via a temp-file + atomic rename, so a crash mid-write can no longer corrupt them; corrupted files are also handled gracefully on read.
  • βœ… Robustness β€” Startup validation extended: LEAGUE_IDS and SEASON_ID are now validated at startup (previously an invalid SEASON_ID only crashed on match day). Oversized single lines in messages are now hard-split to respect Telegram/Discord limits.
  • βœ… Performance β€” Shared HTTP clients: one reusable aiohttp session (api-football) and one httpx client (OpenRouter) instead of a new session per request; both are closed cleanly on shutdown.
  • βœ… Dependencies updated: aiogram 3.30.0, aiohttp 3.14.3, discord.py 2.7.1, pytz 2026.2 (httpx unchanged). Removed the useless asyncio and configparser backports from requirements.txt. Note: aiogram 3.30 requires Python β‰₯ 3.10 (and < 3.15). Code compatibility with these versions has been verified.

πŸ†• What's New in v2.7.0

  • βœ… Migrated from Poe to OpenRouter: The bot now calls the OpenRouter API (https://openrouter.ai/api/v1/chat/completions) instead of Poe. The endpoint is OpenAI-compatible, so response parsing and cost tracking are unchanged. Required HTTP-Referer and X-Title headers are now sent. The config key is OPENROUTER_API_KEY (the old POE_API_KEY still works as a fallback for backward compatibility).
  • βœ… Default Model β€” MiniMax-M3: The example config ships with minimax/minimax-m3 as the default MAIN_MODEL and TRANSLATION_MODEL ($0.30 in / $1.20 out per 1M tokens). Any OpenRouter model slug works (openai/gpt-4o, anthropic/claude-3.5-sonnet, google/gemini-2.0-flash-001, etc.).

πŸ†• What's New in v2.6.1

  • βœ… Fix β€” End-of-season false positive: is_match_today() no longer broadcasts a misleading "API football is unavailable" message when the API returns 200 OK with an empty response array (typical case during the off-season, when no upcoming match is scheduled). The function now distinguishes a real API failure (5xx, timeout, network error) from a successful API call with no match planned, and only notifies users in the former case. During the off-season, the bot stays silent (logs only).

πŸ†• What's New in v2.6.0

  • βœ… Season Stats in AI Context: The current-season record of the followed team (in the league of the day's match) is now injected β€” in a compact one-liner β€” into both the pre-match (compomatch) and post-match (endmatch) LLM prompts. The LLM finally knows the team's W/D/L, goals for/against, clean sheets, and 5-match form within that specific competition.
  • βœ… Single API Call per Match for Season Stats: The /teams/statistics endpoint is called once before the kickoff and the response is cached in memory for the rest of the match (pre-match prompt + post-match prompt + post-match display block). Total cost: +1 api-football request per match.
  • βœ… Optimized Live Polling Budget: Live polling target lowered from 90 to 85 polls per match, freeing room for the new season-stats call while staying well under the 100 req/day free quota.
  • βœ… Visible Season Stats Block in Telegram & Discord: After the AI end-of-match analysis, a compact stats panel is appended to the message (record, goals, clean sheets, recent form with 🟒βšͺπŸ”΄ emojis). Fully compatible with Telegram (parse_mode=Markdown) and Discord.
  • βœ… Removed Hard-Coded League IDs: The previous if current_league_id == X / Y / Z chain in the live-polling loop has been replaced by a configurable [LEAGUE_TYPES] section in config.ini. Any league ID listed in LEAGUES_WITH_EXTRA_TIME is treated as potentially going to extra time (+30 min budget). Leagues not listed are treated as regular championships. The source code is now fully agnostic to country / league / team.
  • βœ… Public-Repo Hygiene: Added .gitignore (excludes config.ini, generated JSON, logs, virtualenv, OS files) and a config.ini.example template with neutral placeholders, so the repository can be published without leaking API keys or local data.
  • βœ… Default Model Updated: The example config previously shipped with Gemini-3-Flash as the default MAIN_MODEL and TRANSLATION_MODEL. As of v2.7.0 the default is the OpenRouter slug minimax/minimax-m3.

πŸ†• What's New in v2.5.0

  • βœ… Professional Logging: Rotating log files with automatic cleanup
  • βœ… Cost Tracking: Real-time monitoring of AI API costs with detailed summaries
  • βœ… Match History: Stores last 5 matches for contextual AI analysis
  • βœ… Smart Message Splitting: Automatically splits long messages for Telegram (4096 chars) and Discord (2000 chars)
  • βœ… API Validation: Validates all API keys at startup with clear error messages
  • βœ… Improved Reliability: Retry mechanism with exponential backoff for API calls
  • βœ… Better Error Handling: Enhanced error messages and graceful degradation
  • βœ… Poe API Support: Now supports Poe API with configurable models (Grok-4-Fast-Reasoning, etc.)
  • βœ… Enhanced AI Prompts: Richer context with match history for better analysis
  • βœ… Missed Penalty Detection: Smart detection that doesn't notify during penalty shootouts

πŸ›  Demo & press articles

🌟 Potential future updates

  • βœ… [DONE] Change of OpenAI's model to use GPT-4o
  • βœ… [DONE] Handle the cancellation of goals by VAR
  • βœ… [DONE] Professional logging system with rotation
  • βœ… [DONE] API cost tracking and monitoring
  • βœ… [DONE] Match history for contextual AI analysis
  • βœ… [DONE β€” v2.6.0] Inject current-season team statistics into pre-match and post-match AI prompts (per-league, one API call per match, cached)
  • βœ… [DONE β€” v2.6.0] Make the codebase fully agnostic (no hard-coded league IDs)
  • [Low priority] Allow API calls to retrieve the standings once per day, and on match days 1 hour after the match if API calls remain available. Enable users to check standings via Telegram and Discord commands (stored locally to avoid excessive API calls). Incorporate standings data in pre-match and post-match analysis for both free and paid API versions, referencing locally stored standings without making additional API calls. Consider whether to implement this for national championships only or for all competitions.
  • [Low priority] Make the bot more flexible by adding more events such as yellow cards and player changes, with the option of enabling or disabling events in config.ini. The idea behind the bot was to send only essential messages, so this is not a high priority.
  • [Low priority] Implement a better scoring management for penalty shootouts, as they are not handled the same way as regular goals
  • [Low priority] Improved handling of season ID retrieval (currently requires manual adjustment in config.ini at the beginning of each season)

βœ… Known Issues - Resolution Status

RESOLVED Issues βœ…

  • βœ… [SOLVED] A bug seems to occur when a new goal is scored but an old goal is updated (for example, in terms of time). The new goal is not sent, but the score is updated, which seems to prevent the sending of the new goal.

    • Resolution: Fixed with event_key_sub mechanism that prevents duplicate notifications even when API corrects goal timing
  • βœ… [SOLVED] When a penalty miss occurs, the following goals during a match are not sent under certain conditions

    • Resolution: Enhanced penalty miss detection with proper event tracking and continuation logic
  • βœ… [SOLVED] When a goal is disallowed under certain conditions, no alert is sent to indicate that the goal has been cancelled

    • Resolution: Improved VAR goal cancellation detection with proper score comparison and notification
  • βœ… [SOLVED] [Free API] A bug that seemed to prevent sending a goal if the goal was sent simultaneously with the first event (mainly free API)

    • Resolution: Fixed with improved is_first_event logic and score update mechanism
  • βœ… [SOLVED] [Free API] In very rare instances, if two goals are scored in quick succession and there's a delay in API score updates, the score might not be correctly updated until the next goal or the end of the match

    • Resolution: Enhanced score tracking with previous_score and current_score comparison, plus goal_events accumulation
  • βœ… [SOLVED β€” v2.5.1] Goal timing corrections from API were not reflected in sent messages, causing confusion about when goals were actually scored

    • Resolution: Implemented sent_events_details tracking system that detects timing changes and automatically sends correction notifications to users
  • βœ… [SOLVED β€” v2.5.1] Automatic correction messages now sent when timing changes are detected

  • βœ… [SOLVED β€” v2.6.0] League IDs were hard-coded in check_events(), making the bot non-portable across countries

    • Resolution: Removed all hard-coded IDs. Behavior driven by the optional [LEAGUE_TYPES] section in config.ini (LEAGUES_WITH_EXTRA_TIME).
  • βœ… [SOLVED β€” v2.6.0] AI pre-match and post-match analyses had no awareness of the team's current-season form within the specific competition being played

    • Resolution: One /teams/statistics call is made before kickoff and cached in memory, then reused in call_chatgpt_api_compomatch (start-of-match prompt) and call_chatgpt_api_endmatch (end-of-match prompt). Compact one-liner format keeps token usage minimal.
  • βœ… [SOLVED β€” v2.6.1] During the off-season, the bot was sending a daily false-positive message claiming "API football is unavailable" because is_match_today() treated a valid 200 OK response with an empty response array (no upcoming match) the same way as a real API failure

    • Resolution: Added an api_call_succeeded flag to differentiate "API OK but no fixture scheduled" (silent log only β€” expected during off-season) from "API actually unreachable" (still broadcast to users). No more daily spam between seasons.
  • βœ… [SOLVED β€” v2.8.0] [Free API] After a penalty shootout, the tracking loop never terminated (waiting for the final PEN status to change), draining the daily API quota and skipping the end-of-match summary

    • Resolution: The shootout handler now monitors P (shootout in progress); PEN is correctly treated as a final status by the end-of-match block.
  • βœ… [SOLVED β€” v2.8.0] A RateLimitExceededError raised during live tracking killed the daily check task silently β€” the bot never checked matches again until a manual restart

    • Resolution: The daily loop catches and logs all exceptions, then resumes the next day. The generic except in get_check_match_status no longer swallows the rate-limit exception.
  • βœ… [SOLVED β€” v2.8.0] The script could not be stopped with Ctrl+C or kill (only SIGKILL worked), and Telegram messages with invalid LLM-generated Markdown were silently lost

    • Resolution: Proper signal handlers (log + clean exit) and a plain-text retry fallback for Telegram.

Circumvented Issues πŸ•ΉοΈ

  • [πŸ•ΉοΈ CIRCUMVENTED β€” v2.5.1] [Free API] In very rare instances, a disallowed goal could go undetected if two scored goals were identified β€” including one that was later disallowed β€” within the same interval between two checks.

    • Status: Mitigated by a dual-layer safety net in the live-event loop: (1) explicit handling of Var events with "Goal Disallowed" detail (gptfoot.py:1359-1375), and (2) a backup score-decrease detector that fires send_goal_cancelled_message whenever current_score < previous_score (gptfoot.py:1461-1465). Residual probability < 1%, and even in that residual case the displayed score remains accurate.
  • [πŸ•ΉοΈ CIRCUMVENTED] [Free API] Some events occurring in the very last seconds of a period may not be detected

    • Status: Inherent limitation of the free api-football tier β€” the live-polling interval (~80-100 s) is constrained by the 100 req/day quota and cannot be shortened without exceeding it. The bot is calibrated to maximize coverage (85 polls/match target) while leaving margin for season-stats and lineup calls. Recommendation: subscribe to a paid api-football plan and set IS_PAID_API = true for full 15 s polling.
  • [πŸ•ΉοΈ CIRCUMVENTED] [Paid API] The script does not update the score with penalty shots as this is handled differently (AI end-of-match analysis partially incorrect on shootouts)

    • Status: By design β€” penalty shootout goals are tracked separately and included in the final match analysis
  • [πŸ•ΉοΈ CIRCUMVENTED] [Paid API] Goals invalidated during a penalty shootout session are not considered

    • Status: By design β€” only successful penalty shootout goals are reported to avoid confusion

πŸ“‹ Good to know

  • The bot cannot provide ongoing match information if launched on a server during the match; only upcoming matches are considered
  • Log files are automatically rotated when they reach 10 MB, keeping the last 5 backups
  • API costs are tracked and summarized at the end of each match (when enabled in config.ini)
  • Match analyses are stored locally in match_analyses.json for contextual AI insights
  • Season stats are NOT persisted on disk β€” they are fetched once per match and cached only in memory for the duration of that match
  • [Free API] Due to API call limitations, 5-minute breaks during extra time are considered as regular half-times, causing the script to pause for 13 minutes
  • [Free API] Due to API call limitations, during penalty shootout sessions, the script pauses for 20 minutes (good to know but penalty goals are managed differently than goals during a match)
  • [Free API] Live-polling interval is computed dynamically based on whether the league is listed in LEAGUES_WITH_EXTRA_TIME (longer total budget when extra time is possible). Target = 85 polls per match, leaving margin for is_match_today, lineups, and /teams/statistics calls under the 100 req/day quota.

πŸš€ Setup

Prerequisite: Python 3.10 to 3.14 (required by aiogram 3.30).

# 1. Clone
git clone https://github.com/Macmachi/gptfoot.git
cd gptfoot

# 2. Create your local config from the template
cp config.ini.example config.ini

# 3. Edit config.ini and fill in your API keys, TEAM_ID, LEAGUE_IDS, etc.

# 4. Install dependencies
pip install -r requirements.txt

# 5. Run
python gptfoot.py

⚠️ config.ini is intentionally listed in .gitignore so your secrets (API keys, tokens, team ID) never end up on GitHub. Always edit your local config.ini, never config.ini.example.

πŸ”§ Configuration

The bot is configured via config.ini (copy config.ini.example first). Sections:

[KEYS]

  • OPENROUTER_API_KEY β€” Your OpenRouter API key (https://openrouter.ai/keys)
  • TELEGRAM_BOT_TOKEN β€” Your Telegram bot token (from @BotFather)
  • DISCORD_BOT_TOKEN β€” Your Discord bot token
  • API_FOOTBALL_KEY β€” Your api-football.com API key
  • TEAM_ID β€” Numeric ID of the team to track (find it on the api-football dashboard)
  • TEAM_NAME β€” Display name of the team
  • LEAGUE_IDS β€” Comma-separated list of league IDs to monitor for this team
  • SEASON_ID β€” 4-digit season year (e.g. 2025 for the 2025-2026 season)

[OPTIONS]

  • USE_TELEGRAM β€” Enable/disable Telegram bot (true/false)
  • USE_DISCORD β€” Enable/disable Discord bot (true/false)
  • IS_PAID_API β€” Use paid api-football plan for more frequent updates (true/false)
  • ENABLE_COST_TRACKING β€” Track and log AI API costs (true/false)

[API_MODELS]

  • MAIN_MODEL β€” AI model for match analysis (OpenRouter slug, e.g. minimax/minimax-m3, openai/gpt-4o, anthropic/claude-3.5-sonnet, google/gemini-2.0-flash-001)
  • TRANSLATION_MODEL β€” AI model for translations (can be the same)

[API_PRICING]

  • INPUT_COST_PER_1M_TOKENS β€” Cost per 1M input tokens in USD (your model)
  • OUTPUT_COST_PER_1M_TOKENS β€” Cost per 1M output tokens in USD (your model)
  • CACHE_DISCOUNT_PERCENTAGE β€” Cache discount percentage if applicable

[SERVER]

  • TIMEZONE β€” Server timezone (e.g. Europe/Paris)

[LANGUAGES]

  • LANGUAGE β€” Output language in lowercase English (e.g. french, english, german). Anything other than french triggers automatic LLM translation.

[LEAGUE_TYPES] (optional, new in v2.6.0)

  • LEAGUES_WITH_EXTRA_TIME β€” Comma-separated league IDs that may go to extra time (cups, knockout phases, continental competitions). Leagues listed here use an extended live-polling budget (+30 min). Leave empty if all your monitored leagues are regular championships.

    Example for a setup that includes one knockout competition (cup or continental knockout) where extra time is possible β€” replace the ID with the actual league ID(s) you monitor:

    [LEAGUE_TYPES]
    LEAGUES_WITH_EXTRA_TIME = 1234
    

πŸ“Š Technical Improvements

Reliability

  • Retry mechanism with exponential backoff (max 3 attempts)
  • Improved error handling for network issues
  • Graceful degradation when AI API is unavailable
  • Timeout management (30 s for api-football, 60 s for AI API)

Performance

  • Optimized API call patterns (single /teams/statistics request per match, cached in memory)
  • Live polling targeted at 85 polls per match to keep margin under the 100 req/day free quota
  • Efficient event deduplication with dual-key system
  • Smart message batching for multiple goals

Maintainability

  • Refactored code with helper functions
  • Clear separation of concerns
  • Comprehensive logging for debugging
  • No hard-coded league IDs β€” everything driven by config.ini

User Experience

  • Intelligent message splitting (no truncation)
  • Contextual AI analysis with match history AND current-season stats per competition
  • Compact season-stats panel after each post-match analysis
  • Clear error messages
  • Cost transparency

πŸ“ Licence

Attribution-NonCommercial 4.0 International (https://creativecommons.org/licenses/by-nc/4.0/legalcode)

About

gptfoot is a Telegram and Discord bot that lets you follow matches of your favorite team across various leagues. Using OpenAI's GPT-4o, it provides live commentary and features like team lineups, goal alerts, red cards, and in-depth match analyses.

Resources

Stars

14 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages