feat: implement MCP server and dashboard for football data platform

Add complete Nike football data platform with:
- FastMCP server exposing football data tools over HTTP
- RapidAPI client for free-api-live-football-data integration
- Bootstrap web dashboard with live match/standings views
- REST API endpoints for dashboard consumption
- Docker support with multi-stage build
- Comprehensive README with architecture docs
- Minimal .gitignore replacing verbose Python template
This commit is contained in:
2026-03-21 18:19:42 +00:00
parent b8689d530a
commit ee8436d5b8
81 changed files with 50251 additions and 176 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

185
.gitignore vendored
View File

@@ -1,176 +1,11 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/ venv/
ENV/ __pycache__/
env.bak/ *.pyc
venv.bak/ *.pyo
*.pyd
# Spyder project settings *.egg-info/
.spyderproject dist/
.spyproject build/
.eggs/
# Rope project settings *.log
.ropeproject .env
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc

198
README.md
View File

@@ -1,2 +1,198 @@
# nike # Nike — Football Data Platform
MCP server and web dashboard for football (soccer) data, with full MLS support.
Queries live data from [free-api-live-football-data](https://rapidapi.com/Creativesdev/api/free-api-live-football-data) (RapidAPI) and exposes it via MCP tools for conversational analysis and a Bootstrap status dashboard.
---
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ nike/server.py — single process on 0.0.0.0:{PORT} │
│ │
│ GET / → Bootstrap dashboard (dashboard.html)│
│ GET /api/* → Dashboard JSON API │
│ /mcp → FastMCP HTTP (streamable) │
└──────────┬──────────────────────────────────────────────┘
nike/rapidapi.py
(free-api-live-football-data client)
RapidAPI
(free-api-live-football-data.p.rapidapi.com)
```
### Module responsibilities
| Module | Role |
|--------|------|
| `nike/config.py` | Centralised settings from `.env` (API keys, constants) |
| `nike/rapidapi.py` | RapidAPI client with TTL cache (live data backend) |
| `nike/server.py` | FastAPI app: MCP tools, dashboard routes, mounts MCP ASGI |
| `nike/templates/dashboard.html` | Live status dashboard (Bootstrap 5, dark theme) |
### Legacy modules (preserved, not active)
| Module | Role |
|--------|------|
| `nike/api_football.py` | API-Football v3 client (original backend) |
| `nike/db.py` | PostgreSQL connection pool and query helpers |
| `nike/sync.py` | API → DB sync pipeline |
| `schema.sql` | Database DDL |
### MCP Tools
| Tool | Description |
|------|-------------|
| `search(query)` | Universal search across teams, players, leagues, matches |
| `live_scores()` | All currently live matches worldwide |
| `fixtures(league, date)` | Matches by league and/or date |
| `standings(league)` | Full league table |
| `team_info(team)` | Team profile + squad roster |
| `player_info(player)` | Player profile and details |
| `match_detail(event_id)` | Full match: score, stats, lineups, venue, referee |
| `head_to_head(event_id)` | H2H history for a matchup |
| `top_players(league, stat)` | Top scorers / assists / rated |
| `transfers(league_or_team, scope)` | Transfer activity |
| `news(scope, name)` | Trending, league, or team news |
---
## Setup
### Prerequisites
- Python >= 3.11
- A RapidAPI key for [free-api-live-football-data](https://rapidapi.com/Creativesdev/api/free-api-live-football-data)
### Install
```bash
cd ~/gitea/nike
python3 -m venv ~/env/nike
source ~/env/nike/bin/activate
pip install -e .
```
### Configure
Create (or edit) `.env` in the project root:
```env
RAPIDAPI_KEY=<your-rapidapi-key>
```
### Verify API connectivity
```bash
python scripts/test_rapidapi.py
```
This searches for MLS, fetches standings, finds Toronto FC, and pulls the squad roster.
---
## Running
### Development
```bash
python run.py
```
The server starts on `http://0.0.0.0:{PORT}`:
- **Dashboard** → `http://<host>:{PORT}/`
- **MCP endpoint** → `http://<host>:{PORT}/mcp`
### Production (systemd)
```bash
sudo cp nike.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now nike
```
---
## MCP Client Configuration
Nike exposes a Streamable HTTP MCP endpoint at `/mcp`. To connect an MCP client (e.g. Claude Desktop, Cline, or any MCP-compatible tool), add the following to your client's MCP server configuration:
```json
{
"mcpServers": {
"nike": {
"type": "streamable-http",
"url": "http://<host>:{PORT}/mcp"
}
}
}
```
Once connected, you can ask questions like:
- *"What matches are live right now?"*
- *"Show the MLS standings"*
- *"Who plays for Toronto FC?"*
- *"Who's the MLS top scorer?"*
- *"Any transfer news for Inter Miami?"*
- *"Tell me about Federico Bernardeschi"*
---
## Scripts
| Script | Purpose |
|--------|---------|
| `scripts/test_rapidapi.py` | Verify RapidAPI connectivity and fetch sample data |
| `scripts/test_db.py` | Verify PostgreSQL connectivity (legacy) |
| `scripts/test_api.py` | Verify API-Football connectivity (legacy) |
| `scripts/apply_schema.py` | Apply database schema (legacy) |
| `scripts/pull_tfc.py` | Full TFC data sync via API-Football (legacy) |
| `scripts/verify_db.py` | Print DB row counts (legacy) |
---
## Project Structure
```
nike/
├── .env # Secrets (not committed)
├── pyproject.toml # Package metadata & dependencies
├── run.py # Entrypoint: python run.py
├── schema.sql # Database DDL (legacy)
├── nike.service # systemd unit file
├── nike/
│ ├── __init__.py
│ ├── config.py # Settings from .env
│ ├── rapidapi.py # RapidAPI client (active backend)
│ ├── api_football.py # API-Football v3 client (legacy)
│ ├── db.py # DB pool + queries (legacy)
│ ├── sync.py # API → DB sync logic (legacy)
│ ├── server.py # FastAPI + MCP server
│ └── templates/
│ └── dashboard.html # Status dashboard
└── scripts/
├── test_rapidapi.py # RapidAPI smoke test
├── apply_schema.py # (legacy)
├── pull_tfc.py # (legacy)
├── test_api.py # (legacy)
├── test_db.py # (legacy)
└── verify_db.py # (legacy)
```
---
## API Quota
The free-api-live-football-data RapidAPI pricing:
| Plan | Price | Requests/Month |
|------|-------|----------------|
| Basic (Free) | $0 | 100 |
| Pro | $9.99/mo | 20,000 |
| Ultra | $19.99/mo | 200,000 |
| Mega | $49.99/mo | 500,000 |
Nike uses a 5-minute in-memory TTL cache to minimize API calls during conversations.

1
api.xml Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
{
"status": "success",
"response": {
"popular": [
{
"id": 47,
"name": "Premier League",
"localizedName": "Premier League",
"ccode": "ENG",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/47.png"
},
{
"id": 42,
"name": "Champions League",
"localizedName": "Champions League",
"ccode": "INT",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/42.png"
},
{
"id": 87,
"name": "LaLiga",
"localizedName": "LaLiga",
"ccode": "ESP",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/87.png"
},
{
"id": 77,
"name": "World Cup",
"localizedName": "FIFA World Cup",
"ccode": "INT",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/77.png"
},
{
"id": 54,
"name": "Bundesliga",
"localizedName": "Bundesliga",
"ccode": "GER",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/54.png"
},
{
"id": 73,
"name": "Europa League",
"localizedName": "Europa League",
"ccode": "INT",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/73.png"
},
{
"id": 53,
"name": "Ligue 1",
"localizedName": "Ligue 1",
"ccode": "FRA",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/53.png"
},
{
"id": 55,
"name": "Serie A",
"localizedName": "Serie A",
"ccode": "ITA",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/55.png"
},
{
"id": 138,
"name": "Copa del Rey",
"localizedName": "Copa del Rey",
"ccode": "ESP",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/138.png"
},
{
"id": 132,
"name": "FA Cup",
"localizedName": "FA Cup",
"ccode": "ENG",
"logo": "https://images.fotmob.com/image_resources/logo/leaguelogo/dark/132.png"
}
]
}
}

View File

@@ -0,0 +1,175 @@
{
"status": "success",
"response": {
"suggestions": [
{
"type": "league",
"id": "47",
"score": 301131,
"name": "Premier League",
"ccode": "ENG"
},
{
"type": "league",
"id": "519",
"score": 300091,
"name": "Premier League",
"ccode": "EGY"
},
{
"type": "league",
"id": "9066",
"score": 300073,
"name": "Premier League",
"ccode": "TAN"
},
{
"type": "league",
"id": "63",
"score": 300071,
"name": "Premier League",
"ccode": "RUS"
},
{
"type": "league",
"id": "10028",
"score": 300058,
"name": "Premier League Qualification",
"ccode": "TAN"
},
{
"type": "league",
"id": "441",
"score": 300054,
"name": "Premier League",
"ccode": "UKR"
},
{
"type": "league",
"id": "522",
"score": 300054,
"name": "Premier League",
"ccode": "GHA"
},
{
"type": "league",
"id": "9084",
"score": 300050,
"name": "Premier League 2",
"ccode": "ENG"
},
{
"type": "league",
"id": "10176",
"score": 300041,
"name": "Premier League 2 Div 2",
"ccode": "ENG"
},
{
"type": "league",
"id": "10068",
"score": 300036,
"name": "Premier League U18",
"ccode": "ENG"
},
{
"type": "league",
"id": "263",
"score": 300024,
"name": "Premier League",
"ccode": "BLR"
},
{
"type": "league",
"id": "225",
"score": 300023,
"name": "Premier League",
"ccode": "KAZ"
},
{
"type": "league",
"id": "461",
"score": 300021,
"name": "Premier League",
"ccode": "SIN"
},
{
"type": "league",
"id": "9986",
"score": 300021,
"name": "Premier League",
"ccode": "CAN"
},
{
"type": "league",
"id": "262",
"score": 300020,
"name": "Premier League",
"ccode": "AZE"
},
{
"type": "league",
"id": "10443",
"score": 300019,
"name": "Premier League",
"ccode": "BAN"
},
{
"type": "league",
"id": "9829",
"score": 300018,
"name": "Premier League Qualification",
"ccode": "UKR"
},
{
"type": "league",
"id": "9333",
"score": 300017,
"name": "Premier League Qualification",
"ccode": "RUS"
},
{
"type": "league",
"id": "118",
"score": 300014,
"name": "Premier League",
"ccode": "ARM"
},
{
"type": "league",
"id": "267",
"score": 300013,
"name": "Premier League",
"ccode": "BIH"
},
{
"type": "league",
"id": "10783",
"score": 300011,
"name": "Women's Premier League",
"ccode": "KSA"
},
{
"type": "league",
"id": "9255",
"score": 300010,
"name": "Premier League qualification",
"ccode": "BLR"
},
{
"type": "league",
"id": "250",
"score": 300008,
"name": "Premier League",
"ccode": "FRO"
},
{
"type": "league",
"id": "529",
"score": 300008,
"name": "Premier League",
"ccode": "KUW"
}
]
}
}

View File

@@ -0,0 +1,21 @@
{
"status": "success",
"response": {
"suggestions": [
{
"type": "league",
"id": "130",
"score": 300491,
"name": "MLS",
"ccode": "USA"
},
{
"type": "league",
"id": "10282",
"score": 300014,
"name": "MLS Next Pro",
"ccode": "USA"
}
]
}
}

View File

@@ -0,0 +1,79 @@
{
"status": "success",
"response": {
"suggestions": [
{
"type": "team",
"id": "9825",
"score": 300993,
"name": "Arsenal",
"leagueId": 47,
"leagueName": "Premier League"
},
{
"type": "team",
"id": "258657",
"score": 300043,
"name": "Arsenal (W)",
"leagueId": 9227,
"leagueName": "WSL"
},
{
"type": "team",
"id": "950214",
"score": 300010,
"name": "Arsenal U21",
"leagueId": 9084,
"leagueName": "Premier League 2"
},
{
"type": "team",
"id": "1113566",
"score": 300008,
"name": "Arsenal U18",
"leagueId": 10068,
"leagueName": "Premier League U18"
},
{
"type": "team",
"id": "10098",
"score": 300001,
"name": "Arsenal Sarandi",
"leagueId": 9213,
"leagueName": "Primera B Metropolitana"
},
{
"type": "team",
"id": "1677",
"score": 300001,
"name": "Arsenal Tula",
"leagueId": 338,
"leagueName": "First League"
},
{
"type": "team",
"id": "1142489",
"score": 300000,
"name": "Arsenal Dzerzhinsk",
"leagueId": 263,
"leagueName": "Premier League"
},
{
"type": "team",
"id": "324771",
"score": 300000,
"name": "FK Arsenal Tivat",
"leagueId": 232,
"leagueName": "1. CFL"
},
{
"type": "team",
"id": "553807",
"score": 300000,
"name": "Arsenal Tula II",
"leagueId": 9123,
"leagueName": "Second League Division B Group 3"
}
]
}
}

View File

@@ -0,0 +1,31 @@
{
"status": "success",
"response": {
"suggestions": [
{
"type": "team",
"id": "56453",
"score": 300012,
"name": "Toronto FC",
"leagueId": 130,
"leagueName": "Major League Soccer"
},
{
"type": "team",
"id": "1022954",
"score": 300001,
"name": "Inter Toronto FC",
"leagueId": 9986,
"leagueName": "Premier League"
},
{
"type": "team",
"id": "614319",
"score": 300000,
"name": "Toronto FC II",
"leagueId": 10282,
"leagueName": "MLS Next Pro"
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
{
"status": "success",
"response": {
"matches": []
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,3 @@
{
"message": "You have exceeded the MONTHLY quota for Requests on your current plan, BASIC. Upgrade your plan at https://rapidapi.com/Creativesdev/api/free-api-live-football-data"
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,827 @@
{
"status": "success",
"response": {
"list": {
"squad": [
{
"title": "coach",
"members": [
{
"id": 308837,
"height": null,
"age": 59,
"dateOfBirth": "1966-12-17",
"name": "Robin Fraser",
"ccode": "USA",
"cname": "USA",
"role": {
"key": "coach",
"fallback": "Coach"
},
"excludeFromRanking": true
}
]
},
{
"title": "keepers",
"members": [
{
"id": 1715087,
"name": "Adisa De Rosario",
"shirtNumber": null,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "keeper_long",
"fallback": "Keeper"
},
"positionId": 0,
"injured": true,
"injury": {
"id": "35",
"expectedReturn": "Early April 2026"
},
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "11",
"positionIdsDesc": "GK",
"height": 185,
"age": 21,
"dateOfBirth": "2004-10-27",
"transferValue": 100000
},
{
"id": 1338704,
"name": "Luka Gavran",
"shirtNumber": 1,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "keeper_long",
"fallback": "Keeper"
},
"positionId": 0,
"injury": null,
"rating": 4.87,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "11",
"positionIdsDesc": "GK",
"height": 198,
"age": 25,
"dateOfBirth": "2000-05-09",
"transferValue": 242993
},
{
"id": 342699,
"name": "William Yarbrough",
"shirtNumber": 23,
"ccode": "USA",
"cname": "USA",
"role": {
"key": "keeper_long",
"fallback": "Keeper"
},
"positionId": 0,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "11",
"positionIdsDesc": "GK",
"height": 187,
"age": 36,
"dateOfBirth": "1989-03-20",
"transferValue": 50000
}
]
},
{
"title": "defenders",
"members": [
{
"id": 574923,
"name": "Benjam\u00edn Kuscevic",
"shirtNumber": null,
"ccode": "CHI",
"cname": "Chile",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "34",
"positionIdsDesc": "CB",
"height": 186,
"age": 29,
"dateOfBirth": "1996-05-02",
"transferValue": 1483178
},
{
"id": 825691,
"name": "Henry Wingo",
"shirtNumber": 2,
"ccode": "USA",
"cname": "USA",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injured": true,
"injury": {
"id": "42",
"expectedReturn": "Early April 2026"
},
"rating": 6.14,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "71",
"positionIdsDesc": "RM",
"height": 183,
"age": 30,
"dateOfBirth": "1995-10-04",
"transferValue": 130182
},
{
"id": 1130753,
"name": "Zane Monlouis",
"shirtNumber": 12,
"ccode": "JAM",
"cname": "Jamaica",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": 6.44,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "34",
"positionIdsDesc": "CB",
"height": 185,
"age": 22,
"dateOfBirth": "2003-10-16",
"transferValue": 502064
},
{
"id": 1346549,
"name": "Nicksoen Gomis",
"shirtNumber": 15,
"ccode": "FRA",
"cname": "France",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injured": true,
"injury": {
"id": "73",
"expectedReturn": "Late March 2026"
},
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "36,38",
"positionIdsDesc": "CB,LB",
"height": 185,
"age": 23,
"dateOfBirth": "2002-03-15",
"transferValue": 238195
},
{
"id": 1106934,
"name": "Kobe Franklin",
"shirtNumber": 19,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": 5.44,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "32,38",
"positionIdsDesc": "RB,LB",
"height": 168,
"age": 22,
"dateOfBirth": "2003-05-10",
"transferValue": 240778
},
{
"id": 729506,
"name": "Richie Laryea",
"shirtNumber": 22,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": 6.19,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "38,32,66,34",
"positionIdsDesc": "LB,RB,CDM,CB",
"height": 175,
"age": 31,
"dateOfBirth": "1995-01-07",
"transferValue": 470837
},
{
"id": 431956,
"name": "Walker Zimmerman",
"shirtNumber": 25,
"ccode": "USA",
"cname": "USA",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": 5.51,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "34",
"positionIdsDesc": "CB",
"height": 191,
"age": 32,
"dateOfBirth": "1993-05-19",
"transferValue": 1163870
},
{
"id": 664764,
"name": "Raheem Edwards",
"shirtNumber": 44,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": 6.88,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 1,
"excludeFromRanking": false,
"positionIds": "38,68",
"positionIdsDesc": "LB,LWB",
"height": 173,
"age": 30,
"dateOfBirth": "1995-07-17",
"transferValue": 246577
},
{
"id": 1261310,
"name": "Kosi Thompson",
"shirtNumber": 47,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": 6.16,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "32,36,62",
"positionIdsDesc": "RB,CB,RWB",
"height": 178,
"age": 23,
"dateOfBirth": "2003-01-27",
"transferValue": 726678
},
{
"id": 1357562,
"name": "Adam Pearlman",
"shirtNumber": 51,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "32,72,33",
"positionIdsDesc": "RB,RM,CB",
"height": 183,
"age": 20,
"dateOfBirth": "2005-04-05",
"transferValue": 571247
},
{
"id": 1504606,
"name": "Lazar Stefanovic",
"shirtNumber": 76,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "37",
"positionIdsDesc": "CB",
"height": 187,
"age": 19,
"dateOfBirth": "2006-08-10",
"transferValue": 874785
},
{
"id": 1780464,
"name": "Stefan Kapor",
"shirtNumber": 98,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "defender_long",
"fallback": "Defender"
},
"positionId": 1,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": null,
"positionIdsDesc": null,
"height": null,
"age": 16,
"dateOfBirth": "2009-04-04",
"transferValue": null
}
]
},
{
"title": "midfielders",
"members": [
{
"id": 1187623,
"name": "Matheus Pereira",
"shirtNumber": 3,
"ccode": "BRA",
"cname": "Brazil",
"role": {
"key": "midfielder_long",
"fallback": "Midfielder"
},
"positionId": 2,
"injured": true,
"injury": {
"id": "47",
"expectedReturn": "Early April 2026"
},
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "78,107,59",
"positionIdsDesc": "LM,LW,LWB",
"height": 172,
"age": 25,
"dateOfBirth": "2000-12-21",
"transferValue": 1867261
},
{
"id": 1053698,
"name": "Jos\u00e9 Cifuentes",
"shirtNumber": 8,
"ccode": "ECU",
"cname": "Ecuador",
"role": {
"key": "midfielder_long",
"fallback": "Midfielder"
},
"positionId": 2,
"injury": null,
"rating": 6.83,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "64,76,86",
"positionIdsDesc": "CDM,CM,CAM",
"height": 178,
"age": 26,
"dateOfBirth": "1999-03-12",
"transferValue": 1895906
},
{
"id": 830601,
"name": "Djordje Mihailovic",
"shirtNumber": 10,
"ccode": "USA",
"cname": "USA",
"role": {
"key": "midfielder_long",
"fallback": "Midfielder"
},
"positionId": 2,
"injury": null,
"rating": 7.14,
"goals": 1,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "85,107,78,115",
"positionIdsDesc": "CAM,LW,LM,ST",
"height": 177,
"age": 27,
"dateOfBirth": "1998-11-10",
"transferValue": 6067551
},
{
"id": 1364471,
"name": "Alonso Coello",
"shirtNumber": 14,
"ccode": "ESP",
"cname": "Spain",
"role": {
"key": "midfielder_long",
"fallback": "Midfielder"
},
"positionId": 2,
"injury": null,
"rating": 6.23,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "73,66",
"positionIdsDesc": "CM,CDM",
"height": 185,
"age": 26,
"dateOfBirth": "1999-10-12",
"transferValue": 623924
},
{
"id": 432605,
"name": "Jonathan Osorio",
"shirtNumber": 21,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "midfielder_long",
"fallback": "Midfielder"
},
"positionId": 2,
"injury": null,
"rating": 7.07,
"goals": 0,
"penalties": 0,
"assists": 1,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "66,77,86,78",
"positionIdsDesc": "CDM,CM,CAM,LM",
"height": 175,
"age": 33,
"dateOfBirth": "1992-06-12",
"transferValue": 676957
},
{
"id": 1455365,
"name": "Markus Cimermancic",
"shirtNumber": 71,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "midfielder_long",
"fallback": "Midfielder"
},
"positionId": 2,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "76",
"positionIdsDesc": "CM",
"height": 175,
"age": 21,
"dateOfBirth": "2004-10-01",
"transferValue": 255919
},
{
"id": 1778338,
"name": "Malik Henry",
"shirtNumber": 78,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "midfielder_long",
"fallback": "Midfielder"
},
"positionId": 2,
"injury": null,
"rating": 6.44,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 1,
"excludeFromRanking": false,
"positionIds": "72,83",
"positionIdsDesc": "RM,RW",
"height": 163,
"age": 23,
"dateOfBirth": "2002-07-23",
"transferValue": 193874
}
]
},
{
"title": "attackers",
"members": [
{
"id": 1113737,
"name": "Theo Corbeanu",
"shirtNumber": 7,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "attacker_long",
"fallback": "Attacker"
},
"positionId": 3,
"injured": true,
"injury": {
"id": "14",
"expectedReturn": "Late April 2026"
},
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "87,83,104,72,84",
"positionIdsDesc": "LW,RW,ST,RM,CAM",
"height": 190,
"age": 23,
"dateOfBirth": "2002-05-17",
"transferValue": 1258500
},
{
"id": 848011,
"name": "Josh Sargent",
"shirtNumber": 9,
"ccode": "USA",
"cname": "USA",
"role": {
"key": "attacker_long",
"fallback": "Attacker"
},
"positionId": 3,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "115",
"positionIdsDesc": "ST",
"height": 185,
"age": 26,
"dateOfBirth": "2000-02-20",
"transferValue": 22347702
},
{
"id": 643482,
"name": "Derrick Etienne Jr.",
"shirtNumber": 11,
"ccode": "HAI",
"cname": "Haiti",
"role": {
"key": "attacker_long",
"fallback": "Attacker"
},
"positionId": 3,
"injury": null,
"rating": 6.71,
"goals": 1,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "115,87,79",
"positionIdsDesc": "ST,LW,LM",
"height": 178,
"age": 29,
"dateOfBirth": "1996-11-25",
"transferValue": 495791
},
{
"id": 1579304,
"name": "Emilio Aristiz\u00e1bal",
"shirtNumber": 17,
"ccode": "COL",
"cname": "Colombia",
"role": {
"key": "attacker_long",
"fallback": "Attacker"
},
"positionId": 3,
"injury": null,
"rating": 5.67,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "106",
"positionIdsDesc": "ST",
"height": 187,
"age": 20,
"dateOfBirth": "2005-08-05",
"transferValue": 1020000
},
{
"id": 655524,
"name": "D\u00e1niel Sall\u00f3i",
"shirtNumber": 20,
"ccode": "HUN",
"cname": "Hungary",
"role": {
"key": "attacker_long",
"fallback": "Attacker"
},
"positionId": 3,
"injury": null,
"rating": 6.1,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": false,
"positionIds": "107,78,83",
"positionIdsDesc": "LW,LM,RW",
"height": 185,
"age": 29,
"dateOfBirth": "1996-07-19",
"transferValue": 1598605
},
{
"id": 1338703,
"name": "Deandre Kerr",
"shirtNumber": 29,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "attacker_long",
"fallback": "Attacker"
},
"positionId": 3,
"injured": true,
"injury": {
"id": "87",
"expectedReturn": "Late March 2026"
},
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "115",
"positionIdsDesc": "ST",
"height": 180,
"age": 23,
"dateOfBirth": "2002-11-29",
"transferValue": 1095554
},
{
"id": 1276785,
"name": "Jules-Anthony Vilsaint",
"shirtNumber": 99,
"ccode": "CAN",
"cname": "Canada",
"role": {
"key": "attacker_long",
"fallback": "Attacker"
},
"positionId": 3,
"injury": null,
"rating": null,
"goals": 0,
"penalties": 0,
"assists": 0,
"rcards": 0,
"ycards": 0,
"excludeFromRanking": true,
"positionIds": "106",
"positionIdsDesc": "ST",
"height": 193,
"age": 23,
"dateOfBirth": "2003-01-06",
"transferValue": 286274
}
]
}
],
"isNationalTeam": false
}
}
}

View File

@@ -0,0 +1,4 @@
{
"status": "failed",
"message": "Request Failed Please try Again"
}

View File

@@ -0,0 +1,3 @@
{
"message": "You have exceeded the MONTHLY quota for Requests on your current plan, BASIC. Upgrade your plan at https://rapidapi.com/Creativesdev/api/free-api-live-football-data"
}

View File

@@ -0,0 +1,347 @@
{
"status": "success",
"response": {
"standing": [
{
"name": "Arsenal",
"shortName": "Arsenal",
"id": 9825,
"pageUrl": "/teams/9825/overview/arsenal",
"deduction": null,
"ongoing": null,
"played": 30,
"wins": 20,
"draws": 7,
"losses": 3,
"scoresStr": "59-22",
"goalConDiff": 37,
"pts": 67,
"idx": 1,
"qualColor": "#2AD572"
},
{
"name": "Manchester City",
"shortName": "Man City",
"id": 8456,
"pageUrl": "/teams/8456/overview/manchester-city",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 18,
"draws": 6,
"losses": 5,
"scoresStr": "59-27",
"goalConDiff": 32,
"pts": 60,
"idx": 2,
"qualColor": "#2AD572"
},
{
"name": "Manchester United",
"shortName": "Man United",
"id": 10260,
"pageUrl": "/teams/10260/overview/manchester-united",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 14,
"draws": 9,
"losses": 6,
"scoresStr": "51-40",
"goalConDiff": 11,
"pts": 51,
"idx": 3,
"qualColor": "#2AD572"
},
{
"name": "Aston Villa",
"shortName": "Aston Villa",
"id": 10252,
"pageUrl": "/teams/10252/overview/aston-villa",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 15,
"draws": 6,
"losses": 8,
"scoresStr": "39-34",
"goalConDiff": 5,
"pts": 51,
"idx": 4,
"qualColor": "#2AD572"
},
{
"name": "Chelsea",
"shortName": "Chelsea",
"id": 8455,
"pageUrl": "/teams/8455/overview/chelsea",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 13,
"draws": 9,
"losses": 7,
"scoresStr": "53-34",
"goalConDiff": 19,
"pts": 48,
"idx": 5,
"qualColor": "#0046A7"
},
{
"name": "Liverpool",
"shortName": "Liverpool",
"id": 8650,
"pageUrl": "/teams/8650/overview/liverpool",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 14,
"draws": 6,
"losses": 9,
"scoresStr": "48-39",
"goalConDiff": 9,
"pts": 48,
"idx": 6,
"qualColor": null
},
{
"name": "Brentford",
"shortName": "Brentford",
"id": 9937,
"pageUrl": "/teams/9937/overview/brentford",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 13,
"draws": 5,
"losses": 11,
"scoresStr": "44-40",
"goalConDiff": 4,
"pts": 44,
"idx": 7,
"qualColor": null
},
{
"name": "Everton",
"shortName": "Everton",
"id": 8668,
"pageUrl": "/teams/8668/overview/everton",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 12,
"draws": 7,
"losses": 10,
"scoresStr": "34-33",
"goalConDiff": 1,
"pts": 43,
"idx": 8,
"qualColor": null
},
{
"name": "AFC Bournemouth",
"shortName": "Bournemouth",
"id": 8678,
"pageUrl": "/teams/8678/overview/afc-bournemouth",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 9,
"draws": 13,
"losses": 7,
"scoresStr": "44-46",
"goalConDiff": -2,
"pts": 40,
"idx": 9,
"qualColor": null
},
{
"name": "Fulham",
"shortName": "Fulham",
"id": 9879,
"pageUrl": "/teams/9879/overview/fulham",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 12,
"draws": 4,
"losses": 13,
"scoresStr": "40-43",
"goalConDiff": -3,
"pts": 40,
"idx": 10,
"qualColor": null
},
{
"name": "Sunderland",
"shortName": "Sunderland",
"id": 8472,
"pageUrl": "/teams/8472/overview/sunderland",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 10,
"draws": 10,
"losses": 9,
"scoresStr": "30-34",
"goalConDiff": -4,
"pts": 40,
"idx": 11,
"qualColor": null
},
{
"name": "Newcastle United",
"shortName": "Newcastle",
"id": 10261,
"pageUrl": "/teams/10261/overview/newcastle-united",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 11,
"draws": 6,
"losses": 12,
"scoresStr": "42-43",
"goalConDiff": -1,
"pts": 39,
"idx": 12,
"qualColor": null
},
{
"name": "Crystal Palace",
"shortName": "Crystal Palace",
"id": 9826,
"pageUrl": "/teams/9826/overview/crystal-palace",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 10,
"draws": 8,
"losses": 11,
"scoresStr": "33-35",
"goalConDiff": -2,
"pts": 38,
"idx": 13,
"qualColor": null
},
{
"name": "Brighton & Hove Albion",
"shortName": "Brighton",
"id": 10204,
"pageUrl": "/teams/10204/overview/brighton-hove-albion",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 9,
"draws": 10,
"losses": 10,
"scoresStr": "38-36",
"goalConDiff": 2,
"pts": 37,
"idx": 14,
"qualColor": null
},
{
"name": "Leeds United",
"shortName": "Leeds",
"id": 8463,
"pageUrl": "/teams/8463/overview/leeds-united",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 7,
"draws": 10,
"losses": 12,
"scoresStr": "37-48",
"goalConDiff": -11,
"pts": 31,
"idx": 15,
"qualColor": null
},
{
"name": "Tottenham Hotspur",
"shortName": "Tottenham",
"id": 8586,
"pageUrl": "/teams/8586/overview/tottenham-hotspur",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 7,
"draws": 8,
"losses": 14,
"scoresStr": "39-46",
"goalConDiff": -7,
"pts": 29,
"idx": 16,
"qualColor": null
},
{
"name": "Nottingham Forest",
"shortName": "Nottm Forest",
"id": 10203,
"pageUrl": "/teams/10203/overview/nottingham-forest",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 7,
"draws": 7,
"losses": 15,
"scoresStr": "28-43",
"goalConDiff": -15,
"pts": 28,
"idx": 17,
"qualColor": null
},
{
"name": "West Ham United",
"shortName": "West Ham",
"id": 8654,
"pageUrl": "/teams/8654/overview/west-ham-united",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 7,
"draws": 7,
"losses": 15,
"scoresStr": "35-54",
"goalConDiff": -19,
"pts": 28,
"idx": 18,
"qualColor": "#FF4646"
},
{
"name": "Burnley",
"shortName": "Burnley",
"id": 8191,
"pageUrl": "/teams/8191/overview/burnley",
"deduction": null,
"ongoing": null,
"played": 29,
"wins": 4,
"draws": 7,
"losses": 18,
"scoresStr": "32-58",
"goalConDiff": -26,
"pts": 19,
"idx": 19,
"qualColor": "#FF4646"
},
{
"name": "Wolverhampton Wanderers",
"shortName": "Wolves",
"id": 8602,
"pageUrl": "/teams/8602/overview/wolverhampton-wanderers",
"deduction": null,
"ongoing": null,
"played": 30,
"wins": 3,
"draws": 7,
"losses": 20,
"scoresStr": "22-52",
"goalConDiff": -30,
"pts": 16,
"idx": 20,
"qualColor": "#FF4646"
}
]
}
}

View File

@@ -0,0 +1,67 @@
{
"status": "success",
"response": {
"players": [
{
"id": 737066,
"name": "Erling Haaland",
"teamId": 8456,
"teamName": "Manchester City",
"goals": 22,
"value": 22,
"stat": {
"name": "goals",
"value": 22,
"format": "number",
"fractions": 0
},
"teamColors": {
"darkMode": "#76b4e5",
"lightMode": "#69A8D8",
"fontDarkMode": "rgba(29, 29, 29, 1.0)",
"fontLightMode": "rgba(255, 255, 255, 1.0)"
}
},
{
"id": 1302005,
"name": "Igor Thiago",
"teamId": 9937,
"teamName": "Brentford",
"goals": 18,
"value": 18,
"stat": {
"name": "goals",
"value": 18,
"format": "number",
"fractions": 0
},
"teamColors": {
"darkMode": "#C00808",
"lightMode": "#C00808",
"fontDarkMode": "rgba(255, 255, 255, 1.0)",
"fontLightMode": "rgba(255, 255, 255, 1.0)"
}
},
{
"id": 933576,
"name": "Antoine Semenyo",
"teamId": 8456,
"teamName": "Manchester City",
"goals": 15,
"value": 15,
"stat": {
"name": "goals",
"value": 15,
"format": "number",
"fractions": 0
},
"teamColors": {
"darkMode": "#76b4e5",
"lightMode": "#69A8D8",
"fontDarkMode": "rgba(29, 29, 29, 1.0)",
"fontLightMode": "rgba(255, 255, 255, 1.0)"
}
}
]
}
}

View File

@@ -0,0 +1,62 @@
{
"status": "success",
"response": {
"news": [
{
"id": "ftbpro_01kk79jmekr0",
"imageUrl": "https://images2.minutemediacdn.com/image/upload/c_crop,w_1024,h_576,x_0,y_8/c_fill,w_912,h_516,f_auto,q_auto,g_auto/images/voltaxMediaLibrary/mmsport/si/01kk7af3bceybg72beqv.jpg",
"title": "Arsenal Need To Sell a Superstar: Four Players To Consider\u2014Ranked",
"gmtTime": "2026-03-08T23:00:00.000Z",
"sourceStr": "SI",
"sourceIconUrl": "https://images.fotmob.com/image_resources/news/si.png",
"page": {
"url": "/embed/news/01kk79jmekr0/arsenal-need-sell-superstar-four-players-considerranked"
}
},
{
"id": "ftbpro_01kk77nh7348",
"imageUrl": "https://images2.minutemediacdn.com/image/upload/c_crop,w_1919,h_1079,x_0,y_0/c_fill,w_912,h_516,f_auto,q_auto,g_auto/images/voltaxMediaLibrary/mmsport/si/01kk7b4hp7157m0sbm2f.jpg",
"title": "Transfer News, Rumors: Shock Zubimendi U-Turn Stings Man Utd; Real Madrid\u2019s Rodri Twist",
"gmtTime": "2026-03-09T00:05:00.000Z",
"sourceStr": "SI",
"sourceIconUrl": "https://images.fotmob.com/image_resources/news/si.png",
"page": {
"url": "/embed/news/01kk77nh7348/transfer-news-rumors-shock-zubimendi-u-turn-stings-man-utd-real-madrids-rodri-twist"
}
},
{
"id": "ftbpro_01khv73gpnbm",
"imageUrl": "https://images2.minutemediacdn.com/image/upload/c_crop,w_2141,h_1204,x_0,y_107/c_fill,w_912,h_516,f_auto,q_auto,g_auto/images/voltaxMediaLibrary/mmsport/si/01kj7qd6q1tmdh64dpn7.jpg",
"title": "Real Madrid\u2019s 10 Best Kits of All Time\u2014Ranked",
"gmtTime": "2026-03-08T22:00:00.000Z",
"sourceStr": "SI",
"sourceIconUrl": "https://images.fotmob.com/image_resources/news/si.png",
"page": {
"url": "/embed/news/01khv73gpnbm/real-madrids-10-best-kits-all-timeranked"
}
},
{
"id": "90hptfhwzvo61817ve6yo5cwr",
"imageUrl": "https://images.performgroup.com/di/library/omnisport/de/3d/pervis-estupinan_1vh3xc9744gyb15kz1mabotx3x.png?t=-741518868&w=520&h=300",
"title": "AC Milan 1-0 Inter: Estupinan strike settles Derby della Madonnina",
"gmtTime": "2026-03-08T22:55:22.000Z",
"sourceStr": "FotMob",
"sourceIconUrl": "https://images.fotmob.com/image_resources/news/fotmob.png",
"page": {
"url": "/news/90hptfhwzvo61817ve6yo5cwr-ac-milan-1-0-inter-estupinan-strike-settles-derby-della-madonnina"
}
},
{
"id": "ftbpro_01kk714gxhgj",
"imageUrl": "https://images2.minutemediacdn.com/image/upload/c_crop,w_1024,h_576,x_0,y_0/c_fill,w_912,h_516,f_auto,q_auto,g_auto/images/voltaxMediaLibrary/mmsport/si/01kk76ca6tg3gfyxsk6a.jpg",
"title": "'They Should Have Took Me'\u2014Tottenham Jibed By Record-Breaking English Manager",
"gmtTime": "2026-03-08T20:00:01.000Z",
"sourceStr": "SI",
"sourceIconUrl": "https://images.fotmob.com/image_resources/news/si.png",
"page": {
"url": "/embed/news/01kk714gxhgj/they-should-have-took-metottenham-jibed-record-breaking-english-manager"
}
}
]
}
}

View File

@@ -0,0 +1,70 @@
{
"teams": [
{
"idTeam": "133604",
"idESPN": "359",
"idAPIfootball": "42",
"intLoved": "9",
"strTeam": "Arsenal",
"strTeamAlternate": "Arsenal Football Club, AFC, Arsenal FC",
"strTeamShort": "ARS",
"intFormedYear": "1892",
"strSport": "Soccer",
"strLeague": "English Premier League",
"idLeague": "4328",
"strLeague2": "FA Cup",
"idLeague2": "4482",
"strLeague3": "EFL Cup",
"idLeague3": "4570",
"strLeague4": "UEFA Champions League",
"idLeague4": "4480",
"strLeague5": "Emirates Cup",
"idLeague5": "5648",
"strLeague6": "",
"idLeague6": null,
"strLeague7": "",
"idLeague7": null,
"strDivision": null,
"idVenue": "15528",
"strStadium": "Emirates Stadium",
"strKeywords": "Gunners, Gooners",
"strRSS": "",
"strLocation": "Holloway, London, England",
"intStadiumCapacity": "60338",
"strWebsite": "www.arsenal.com",
"strFacebook": "www.facebook.com/Arsenal",
"strTwitter": "twitter.com/arsenal",
"strInstagram": "instagram.com/arsenal",
"strDescriptionEN": "Arsenal Football Club is a professional football club based in Islington, London, England, that plays in the Premier League, the top flight of English football. The club has won 13 League titles, a record 13 FA Cups, 2 League Cups, 15 FA Community Shields, 1 League Centenary Trophy, 1 UEFA Cup Winners' Cup and 1 Inter-Cities Fairs Cup.\r\n\r\nArsenal was the first club from the South of England to join The Football League, in 1893, and they reached the First Division in 1904. Relegated only once, in 1913, they continue the longest streak in the top division, and have won the second-most top-flight matches in English football history. In the 1930s, Arsenal won five League Championships and two FA Cups, and another FA Cup and two Championships after the war. In 1970\u201371, they won their first League and FA Cup Double. Between 1989 and 2005, they won five League titles and five FA Cups, including two more Doubles. They completed the 20th century with the highest average league position.\r\n\r\nHerbert Chapman won Arsenal's first national trophies, but died prematurely. He helped introduce the WM formation, floodlights, and shirt numbers, and added the white sleeves and brighter red to the club's kit. Ars\u00e8ne Wenger was the longest-serving manager and won the most trophies. He won a record 7 FA Cups, and his title-winning team set an English record for the longest top-flight unbeaten league run at 49 games between 2003 and 2004, receiving the nickname The Invincibles.\r\n\r\nIn 1886, Woolwich munitions workers founded the club as Dial Square. In 1913, the club crossed the city to Arsenal Stadium in Highbury, becoming close neighbours of Tottenham Hotspur, and creating the North London derby. In 2006, they moved to the nearby Emirates Stadium. In terms of revenue, Arsenal is the ninth highest-earning football club in the world, earned \u20ac487.6m in 2016\u201317 season. Based on social media activity from 2014 to 2015, Arsenal's fanbase is the fifth largest in the world. In 2018, Forbes estimated the club was the third most valuable in England, with the club being worth $2.24 billion.",
"strDescriptionDE": "Der FC Arsenal (offiziell: Arsenal Football Club) \u2013 auch bekannt als (The) Arsenal, (The) Gunners (deutsche \u00dcbersetzung: \u201eSch\u00fctzen\u201c oder \u201eKanoniere\u201c) oder im deutschen Sprachraum auch Arsenal London genannt \u2013 ist ein 1886 gegr\u00fcndeter Fu\u00dfballverein aus dem Ortsteil Holloway des Nordlondoner Bezirks Islington. Mit 13 englischen Meisterschaften und elf FA-Pokalsiegen z\u00e4hlt der Klub zu den erfolgreichsten englischen Fu\u00dfballvereinen.Erst \u00fcber 40 Jahre nach der Gr\u00fcndung gewann Arsenal mit f\u00fcnf Ligatiteln und zwei FA Cups in den 1930er Jahren seine ersten bedeutenden Titel. Der n\u00e4chste Meilenstein war in der Saison 1970/71 der Gewinn des zweiten englischen \u201eDoubles\u201c im 20. Jahrhundert. In den vergangenen 20 Jahren etablierte sich Arsenal endg\u00fcltig als einer der erfolgreichsten englischen Fu\u00dfballvereine, und beim Gewinn zweier weiterer Doubles zu Beginn des 21. Jahrhunderts blieb die Mannschaft in der Ligasaison 2003/04 als zweite in der englischen Fu\u00dfballgeschichte ungeschlagen. Zunehmende europ\u00e4ische Ambitionen unterstrich der Verein in der Spielzeit 2005/06, als Arsenal als erster Londoner Verein das Finale der Champions League erreichte.",
"strDescriptionFR": "Arsenal Football Club est un club de football anglais fond\u00e9 le 1er d\u00e9cembre 1886 \u00e0 Londres. Son si\u00e8ge est situ\u00e9 dans le borough londonien d'Islington.\r\n\r\nArsenal participe au championnat d'Angleterre de football depuis 1919 dont il a remport\u00e9 treize \u00e9ditions, ce qui en fait le troisi\u00e8me club le plus titr\u00e9 d'Angleterre, ainsi que treize coupes d'Angleterre ce qui constitue un record (devant Manchester United, qui en a remport\u00e9 12). Sur le plan continental, les Gunners (en fran\u00e7ais : \u00ab les canonniers \u00bb) comptent \u00e0 leur palmar\u00e8s une Coupe d'Europe des vainqueurs de coupe obtenue en 1994. Deux fois finalistes de cette coupe des vainqueurs de coupe en 1980 et 1995, ils ont \u00e9galement atteint les finales de la coupe de l'UEFA en 2000, de la Ligue des champions en 2006 et de l'Europa League en 2019.\r\n\r\nR\u00e9sident d\u00e8s 1913 du stade de Highbury, situ\u00e9 dans un quartier du nord de Londres, le club s\u2019est install\u00e9 en 2006 dans une nouvelle enceinte de 60 355 places : l'Emirates Stadium, situ\u00e9 \u00e0 Ashburton Grove. Arsenal nourrit une certaine rivalit\u00e9 avec les nombreux autres clubs de la capitale, mais celle l'opposant \u00e0 Tottenham Hotspur, avec qui il dispute chaque ann\u00e9e le \u00ab North London derby \u00bb est particuli\u00e8rement ancienne et profonde.\r\n\r\nLe club est dirig\u00e9 par Chips Keswick (en) qui succ\u00e8de pour raisons de sant\u00e9 en juin 2013 \u00e0 Peter Hill-Wood, fils et petit-fils des anciens pr\u00e9sidents Denis et Samuel Hill-Wood. Il remplace son p\u00e8re \u00e0 la mort de ce dernier, en 1982, apr\u00e8s vingt ans de responsabilit\u00e9s. Son entra\u00eeneur depuis le 29 novembre 2019 est le Su\u00e9dois Fredrik Ljungberg, ancien joueur du club, apr\u00e8s le limogeage de l\u2019Espagnol Unai Emery. Cependant le 20 d\u00e9cembre 2019, l'Espagnol Mikel Arteta, lui aussi ancien joueur du club, est nomm\u00e9 entra\u00eeneur. Il \u00e9tait avant sa nomination l'assistant de Pep Guardiola \u00e0 Manchester City",
"strDescriptionCN": null,
"strDescriptionIT": "L'Arsenal Football Club, noto semplicemente come Arsenal, \u00e8 una societ\u00e0 calcistica inglese con sede a Londra, pi\u00f9 precisamente nel quartiere di Holloway, nel borgo di Islington.[3]\r\n\r\nFondato nel 1886, \u00e8 uno dei quattordici club che rappresentano la citt\u00e0 di Londra a livello professionistico,[4] nonch\u00e9 uno dei pi\u00f9 antichi del Paese. Milita nella massima serie del calcio inglese ininterrottamente dal 1919-1920, risultando quindi la squadra da pi\u00f9 tempo presente in First Division/Premier League. \u00c8 la prima squadra della capitale del Regno Unito per successi sportivi e, in ambito federale, la terza dopo Manchester United e Liverpool, essendosi aggiudicata nel corso della sua storia tredici campionati inglesi, dodici FA Cup (record di vittorie, condiviso con il Manchester United), due League Cup e quattordici Community Shield (una condivisa),[5] mentre in ambito internazionale ha conquistato una Coppa delle Coppe ed una Coppa delle Fiere. Inoltre \u00e8 una delle tredici squadre che hanno raggiunto le finali di tutte le tre principali competizioni gestite dalla UEFA: Champions League (2005-2006), Coppa UEFA (1999-2000) e Coppa delle Coppe (1979-1980, 1993-1994 e 1994-1995).[6]\r\n\r\nI colori sociali, rosso per la maglietta e bianco per i pantaloncini, hanno sub\u00ecto variazioni pi\u00f9 o meno evidenti nel corso degli anni. Anche la sede del club \u00e8 stata cambiata pi\u00f9 volte: inizialmente la squadra giocava a Woolwich, ma nel 1913 si spost\u00f2 all'Arsenal Stadium, nel quartiere di Highbury; dal 2006 disputa invece le sue partite casalinghe nel nuovo Emirates Stadium. Lo stemma \u00e8 stato modificato ripetutamente, ma al suo interno \u00e8 sempre comparso almeno un cannone. Proprio per questo motivo i giocatori ed i tifosi dell'Arsenal sono spesso soprannominati Gunners (in italiano \"cannonieri\").\r\n\r\nL'Arsenal conta su una schiera di tifosi molto nutrita, distribuita in ogni parte del mondo. Nel corso degli anni sono sorte profonde rivalit\u00e0 con i sostenitori di club concittadini, la pi\u00f9 sentita delle quali \u00e8 quella con i seguaci del Tottenham Hotspur, con i quali i Gunners giocano regolarmente il North London derby.[7] L'Arsenal \u00e8 anche uno dei club pi\u00f9 ricchi del mondo, con un patrimonio stimato di 1,3 miliardi di dollari, secondo la rivista Forbes nel 2014, facendone il quinto club pi\u00f9 ricco del pianeta e il secondo in Inghilterra.[8]",
"strDescriptionJP": "\u30a2\u30fc\u30bb\u30ca\u30eb\u30fb\u30d5\u30c3\u30c8\u30dc\u30fc\u30eb\u30fb\u30af\u30e9\u30d6\uff08Arsenal Football Club\u3001\u30a4\u30ae\u30ea\u30b9\u82f1\u8a9e\u767a\u97f3: [\u02c8\u0251\u02d0s\u0259nl \u02c8fut\u02ccb\u0254\u02d0l kl\u028cb]\uff09\u306f\u3001\u30a4\u30f3\u30b0\u30e9\u30f3\u30c9\u306e\u9996\u90fd\u30ed\u30f3\u30c9\u30f3\u5317\u90e8\u3092\u30db\u30fc\u30e0\u30bf\u30a6\u30f3\u3068\u3059\u308b\u3001\u30a4\u30f3\u30b0\u30e9\u30f3\u30c9\u30d7\u30ed\u30b5\u30c3\u30ab\u30fc\u30ea\u30fc\u30b0\uff08\u30d7\u30ec\u30df\u30a2\u30ea\u30fc\u30b0\uff09\u306b\u52a0\u76df\u3059\u308b\u30d7\u30ed\u30b5\u30c3\u30ab\u30fc\u30af\u30e9\u30d6\u3002\u30af\u30e9\u30d6\u30ab\u30e9\u30fc\u306f\u8d64\u3068\u767d\u3002\r\n\r\n\u73fe\u5728\u306e\u30db\u30fc\u30e0\u30b9\u30bf\u30b8\u30a2\u30e0\u306f\u30ed\u30f3\u30c9\u30f3\u306e\u30a8\u30df\u30ec\u30fc\u30c4\u30fb\u30b9\u30bf\u30b8\u30a2\u30e0\uff08\u53ce\u5bb9\u4eba\u657060,260\u4eba\uff09\u3002\u30d7\u30ec\u30df\u30a2\u30ea\u30fc\u30b0\u306b\u6240\u5c5e\u3057\u3001\u540c\u30ea\u30fc\u30b0\u306b\u304a\u3044\u30663\u56de\u306e\u512a\u52dd\u8a18\u9332\u3092\u6301\u3064\uff08\u30d5\u30c3\u30c8\u30dc\u30fc\u30eb\u30ea\u30fc\u30b0\u6642\u4ee3\u3092\u542b\u3081\u308b\u306813\u56de\uff09\u30022003-2004\u30b7\u30fc\u30ba\u30f3\u306b\u306f\u7121\u6557\u512a\u52dd\u3092\u9054\u6210\u3057\u305f\u30a4\u30f3\u30b0\u30e9\u30f3\u30c9\u5c48\u6307\u306e\u5f37\u8c6a\u3067\u3042\u308b\u30021886\u5e74\u306b\u8ecd\u9700\u5de5\u5834\u306e\u52b4\u50cd\u8005\u306e\u30af\u30e9\u30d6\u3068\u3057\u3066\u5275\u8a2d\u3055\u308c\u305f\u305f\u3081\u3001\u30c1\u30fc\u30e0\u306e\u30a8\u30f3\u30d6\u30ec\u30e0\u306f\u5927\u7832\u306e\u30de\u30fc\u30af\u3092\u6301\u3064\u3002\u300c\u30ac\u30ca\u30fc\u30ba\uff08Gunners\uff09\u300d\u306e\u611b\u79f0\u3067\u77e5\u3089\u308c[1]\u3001\u30b5\u30dd\u30fc\u30bf\u30fc\u306f\u300c\u30b0\u30fc\u30ca\u30fc\uff08Gooner\uff09\u300d\u3068\u547c\u3070\u308c\u308b\u3002\r\n\r\n\u30af\u30e9\u30d6\u306e\u30e2\u30c3\u30c8\u30fc\u306f\u300c\u52dd\u5229\u306f\u8abf\u548c\u306e\u4e2d\u304b\u3089\u751f\u307e\u308c\u308b\uff08\u30e9\u30c6\u30f3\u8a9e: Victoria Concordia Crescit\uff09\u300d\u30021949\u5e74\u304b\u3089\u4f7f\u7528\u3055\u308c\u305f\u30af\u30ec\u30b9\u30c8\u306b\u521d\u3081\u3066\u767b\u5834\u3059\u308b\u3002\u73fe\u884c\u306e\u30af\u30ec\u30b9\u30c8\u306f2002\u5e74\u304b\u3089\u4f7f\u7528\u3055\u308c\u3066\u304a\u308a\u3001\u5927\u7832\u306e\u4e0a\u306b\u30b5\u30f3\u30bb\u30ea\u30d5\u4f53\u3067\u30c1\u30fc\u30e0\u540d\u304c\u66f8\u304b\u308c\u3066\u3044\u308b\u3002",
"strDescriptionRU": "\u00ab\u0410\u0440\u0441\u0435\u043d\u0430\u0301\u043b\u00bb (\u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u043d\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u2014 \u0424\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0443\u0431 \u00ab\u0410\u0440\u0441\u0435\u043d\u0430\u043b\u00bb, \u0430\u043d\u0433\u043b. Arsenal Football Club, \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u043d\u043e\u0448\u0435\u043d\u0438\u0435: [\u02c8\u0251rs\u0259n\u0259l 'futb\u0254:l kl\u028cb]) \u2014 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 \u043f\u0440\u043e\u0444\u0435\u0441\u0441\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0443\u0431 \u0438\u0437 \u0421\u0435\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u041b\u043e\u043d\u0434\u043e\u043d\u0430, \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0439 \u0432 \u041f\u0440\u0435\u043c\u044c\u0435\u0440-\u043b\u0438\u0433\u0435. \u041e\u0441\u043d\u043e\u0432\u0430\u043d \u0432 \u043e\u043a\u0442\u044f\u0431\u0440\u0435 1886 \u0433\u043e\u0434\u0430. \u041a\u043b\u0443\u0431 13 \u0440\u0430\u0437 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u043e\u043c \u0410\u043d\u0433\u043b\u0438\u0438, 13 \u0440\u0430\u0437 \u0432\u044b\u0438\u0433\u0440\u044b\u0432\u0430\u043b \u041a\u0443\u0431\u043e\u043a \u0410\u043d\u0433\u043b\u0438\u0438. \u0414\u043e\u043c\u0430\u0448\u043d\u0438\u043c \u0441\u0442\u0430\u0434\u0438\u043e\u043d\u043e\u043c \u043a\u043b\u0443\u0431\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u042d\u043c\u0438\u0440\u0435\u0439\u0442\u0441, \u0432\u043c\u0435\u0449\u0430\u044e\u0449\u0438\u0439 60 704 \u0437\u0440\u0438\u0442\u0435\u043b\u0435\u0439[1].\r\n\r\n\u00ab\u0410\u0440\u0441\u0435\u043d\u0430\u043b\u00bb \u043f\u0440\u043e\u0432\u0451\u043b \u0431\u043e\u0301\u043b\u044c\u0448\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0441\u0432\u043e\u0435\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u0432 \u0432\u044b\u0441\u0448\u0435\u043c \u0434\u0438\u0432\u0438\u0437\u0438\u043e\u043d\u0435 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u0444\u0443\u0442\u0431\u043e\u043b\u0430, \u0432 1892 \u0433\u043e\u0434\u0443 \u0441\u0442\u0430\u043b \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u043a\u043b\u0443\u0431\u043e\u0432-\u043e\u0441\u043d\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0439 \u041f\u0440\u0435\u043c\u044c\u0435\u0440-\u043b\u0438\u0433\u0438[4]. \u0422\u0430\u043a\u0436\u0435 \u043a\u043b\u0443\u0431 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0435\u043a\u043e\u0440\u0434\u0441\u043c\u0435\u043d\u043e\u043c \u043f\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u0441\u0435\u0437\u043e\u043d\u043e\u0432 \u043f\u043e\u0434\u0440\u044f\u0434, \u043f\u0440\u043e\u0432\u0435\u0434\u0451\u043d\u043d\u044b\u0445 \u0432 \u0432\u044b\u0441\u0448\u0435\u043c \u0434\u0438\u0432\u0438\u0437\u0438\u043e\u043d\u0435 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u0430 \u0410\u043d\u0433\u043b\u0438\u0438, \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 1919 \u0433\u043e\u0434\u0430[5].\r\n\r\n\u041f\u043e \u0434\u0430\u043d\u043d\u044b\u043c Forbes \u043d\u0430 2018 \u0433\u043e\u0434, \u00ab\u0410\u0440\u0441\u0435\u043d\u0430\u043b\u00bb \u0437\u0430\u043d\u0438\u043c\u0430\u043b \u0448\u0435\u0441\u0442\u043e\u0435 \u043c\u0435\u0441\u0442\u043e \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0441\u0430\u043c\u044b\u0445 \u0434\u043e\u0440\u043e\u0433\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043a\u043b\u0443\u0431\u043e\u0432 \u043c\u0438\u0440\u0430 \u0438 \u043e\u0446\u0435\u043d\u0438\u0432\u0430\u043b\u0441\u044f \u0432 \u0441\u0443\u043c\u043c\u0443 2,238 \u043c\u043b\u0440\u0434. \u0434\u043e\u043b\u043b\u0430\u0440\u043e\u0432 \u0421\u0428\u0410[6]. \u041f\u043e \u0434\u0430\u043d\u043d\u044b\u043c \u0430\u0443\u0434\u0438\u0442\u043e\u0440\u0441\u043a\u043e\u0439 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 Deloitte, \u043f\u043e \u0438\u0442\u043e\u0433\u0430\u043c \u0441\u0435\u0437\u043e\u043d\u0430 2016/17 \u00ab\u0410\u0440\u0441\u0435\u043d\u0430\u043b\u00bb \u0437\u0430\u043d\u0438\u043c\u0430\u043b 6-\u044e \u0441\u0442\u0440\u043e\u0447\u043a\u0443 \u0432 \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0435 \u0441\u0430\u043c\u044b\u0445 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043a\u043b\u0443\u0431\u043e\u0432 \u0441 \u0434\u043e\u0445\u043e\u0434\u043e\u043c \u0432 \u0440\u0430\u0437\u043c\u0435\u0440\u0435 487,6 \u043c\u043b\u043d \u0435\u0432\u0440\u043e[7].",
"strDescriptionES": "El Arsenal Football Club (pronunciaci\u00f3n en ingl\u00e9s: /\u02c8\u0251\u02d0(\u0279)s\u0259n\u0259l \u02c8f\u028at\u02ccb\u0254l kl\u028cb/) es un club de f\u00fatbol profesional con sede en Holloway, Londres, Inglaterra, que juega en la m\u00e1xima categor\u00eda del f\u00fatbol de ese pa\u00eds, la Premier League. Uno de los m\u00e1s laureados del f\u00fatbol ingl\u00e9s, ha ganado 43 t\u00edtulos en su pa\u00eds, incluyendo 13 campeonatos de liga y un r\u00e9cord de 13 Copas de Inglaterra; tambi\u00e9n ha ganado dos t\u00edtulos internacionales: la Copa europea de Ferias en 1970 y la Recopa de Europa en 1994.\r\n\r\nFundado en 1886 en el sudeste de Londres, en 1893, se convirti\u00f3 en el primer club del sur de Inglaterra en unirse a la Football League. Gan\u00f3 sus primeros t\u00edtulos \u2014cinco ligas y dos FA Cups\u2014 en los a\u00f1os 1930. Luego de un per\u00edodo de escasez en los a\u00f1os posteriores a la Segunda Guerra Mundial, se convirti\u00f3 en el segundo club del siglo XX en ganar el doblete, obteniendo el Campeonato de Liga y la FA Cup de la temporada 1970-71. Los logros siguieron en los a\u00f1os 1990 y 2000. Durante esos a\u00f1os, Arsenal gan\u00f3 un doblete de copas nacionales, dos dobletes de Liga y Copa, y dos bicampeonatos de la Copa de Inglaterra. Finaliz\u00f3 una temporada de Liga invicto y se convirti\u00f3 en el primer club de Londres en llegar a la final de la Liga de Campeones de la UEFA.\r\n\r\nLos colores tradicionales del club, el blanco y el rojo, han evolucionado con el tiempo. Del mismo modo, fue cambiando la ubicaci\u00f3n del club; fundado en el distrito de Woolwich, en el sureste de Londres, en 1913 se mud\u00f3 al norte de Londres, m\u00e1s exactamente en el distrito de Highbury, donde fue establecido el Arsenal Stadium, que funcion\u00f3 hasta 2006, donde se hizo un traslado m\u00e1s corto, hacia los alrededores de Holloway, donde se levant\u00f3 el actual Emirates Stadium. La estad\u00eda hist\u00f3rica del Arsenal en el norte de Londres, ha creado con el tiempo una fuerte rivalidad con el Tottenham Hotspur, conocida como el Derbi del Norte de Londres.4\u200b\r\n\r\nArsenal posee una de las aficiones m\u00e1s numerosas del mundo.5\u200b6\u200b7\u200b Seg\u00fan la revista Forbes, el club fue el quinto club m\u00e1s valioso del mundo en 2014, con un valor de 1300 millones libras.8\u200b",
"strDescriptionPT": "Arsenal Football Club \u00e9 um clube de futebol, fundado e baseado em Londres, capital da Inglaterra.\r\n\r\nO clube disputa a Premier League e \u00e9 um dos mais bem sucedidos do futebol ingl\u00eas, tendo ganhado por 13 vezes o t\u00edtulo de campe\u00e3o do Campeonato Ingl\u00eas, sendo o terceiro em n\u00famero de conquistas, e por 13 vezes a Copa da Inglaterra (recorde), sendo tamb\u00e9m detentor do recorde de maior per\u00edodo de invencibilidade no Campeonato Ingl\u00eas e tamb\u00e9m o de ser o \u00fanico a ganhar a Premier League invicto. No plano internacional, conquistou a Recopa Europeia e a Ta\u00e7a das Cidades com Feiras, j\u00e1 tendo sido finalista da Liga dos Campe\u00f5es da UEFA, a principal competi\u00e7\u00e3o europeia de clubes.\r\n\r\nO Arsenal mudou de localiza\u00e7\u00e3o ao longo do tempo, tendo sido fundado em Woolwich, sudeste de Londres, em 1913 mudou-se para o norte de Londres, para o Arsenal Stadium, em Highbury. Em 2006 foi conclu\u00edda a constru\u00e7\u00e3o do Emirates Stadium, que est\u00e1 localizado nas proximidades de Holloway, o que foi prometido desde 2004, ap\u00f3s o clube fechar neg\u00f3cio com a Emirates Airlines, tendo capacidade para mais de 60.000 expectadores.[3]\r\n\r\nO Arsenal tem muitos torcedores por todo mundo, possuindo uma s\u00e9rie de rivalidades de longa data com outros clubes, sendo a mais not\u00e1vel delas contra os vizinhos do Tottenham, com quem disputa regularmente o North London Derby, bastante intensas tamb\u00e9m contra o Chelsea e o Manchester United.[4]\r\n\r\nSuas cores t\u00eam sido tradicionalmente vermelho e branco, mas seus uniformes evolu\u00edram ao longo da hist\u00f3ria. Um outro grande feito \u00e9 o de ser um dos 5 \u00fanicos clubes do mundo que j\u00e1 venceram a Sele\u00e7\u00e3o Brasileira.[5] J\u00e1 o Arsenal Ladies, \u00e9 um dos clubes mais bem sucedidos do futebol feminino na Inglaterra.",
"strDescriptionSE": null,
"strDescriptionNL": null,
"strDescriptionHU": null,
"strDescriptionNO": "Arsenal Football Club er en engelsk fotballklubb som spiller i Premier League. Klubben ble stiftet i 1886, og ble valgt inn i The Football League i 1893. \u00abThe Gunners\u00bb, som er kallenavnet til Arsenal, spiller hjemmekampene sine p\u00e5 Emirates Stadium i Holloway. Holloway er et sted i bydelen Islington som ligger i det nordlige London.",
"strDescriptionIL": null,
"strDescriptionPL": null,
"strColour1": "#EF0107",
"strColour2": "#fbffff",
"strColour3": "#013373",
"strGender": "Male",
"strCountry": "England",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/uyhbfe1612467038.png",
"strLogo": "https://r2.thesportsdb.com/images/media/team/logo/q2mxlz1512644512.png",
"strFanart1": "https://r2.thesportsdb.com/images/media/team/fanart/ouqjzl1769332470.jpg",
"strFanart2": "https://r2.thesportsdb.com/images/media/team/fanart/db23ct1769332477.jpg",
"strFanart3": "https://r2.thesportsdb.com/images/media/team/fanart/k3r9od1769332483.jpg",
"strFanart4": "https://r2.thesportsdb.com/images/media/team/fanart/2uvo8p1731313907.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/team/banner/24sngv1718273065.jpg",
"strEquipment": "https://r2.thesportsdb.com/images/media/team/equipment/fwav4f1752100996.png",
"strYoutube": "www.youtube.com/user/ArsenalTour",
"strLocked": "unlocked"
}
]
}

View File

@@ -0,0 +1,70 @@
{
"teams": [
{
"idTeam": "134148",
"idESPN": null,
"idAPIfootball": "1601",
"intLoved": "2",
"strTeam": "Toronto FC",
"strTeamAlternate": "",
"strTeamShort": "TOR",
"intFormedYear": "2006",
"strSport": "Soccer",
"strLeague": "American Major League Soccer",
"idLeague": "4346",
"strLeague2": "Leagues Cup",
"idLeague2": "5281",
"strLeague3": "",
"idLeague3": null,
"strLeague4": "",
"idLeague4": null,
"strLeague5": "",
"idLeague5": null,
"strLeague6": "",
"idLeague6": null,
"strLeague7": "",
"idLeague7": null,
"strDivision": null,
"idVenue": "16782",
"strStadium": "BMO Field",
"strKeywords": "",
"strRSS": "",
"strLocation": "Toronto, Ontario",
"intStadiumCapacity": "30991",
"strWebsite": "www.torontofc.ca",
"strFacebook": "www.facebook.com/torontofc",
"strTwitter": "twitter.com/torontofc",
"strInstagram": "www.instagram.com/torontofc",
"strDescriptionEN": "Toronto Football Club is a Canadian professional soccer club based in Toronto. The club competes in Major League Soccer (MLS) as a member of the Eastern Conference. The team plays its home matches at BMO Field, located at Exhibition Place on Toronto's shoreline west of Downtown Toronto. Toronto FC joined MLS in 2007 as an expansion team and was the first Canadian-based franchise in the league.\r\n\r\nThe first team is operated by Maple Leaf Sports & Entertainment, which also operates the MLS Next Pro affiliate team Toronto FC II and most other professional sports franchises in the city, like the Toronto Maple Leafs of the NHL, Toronto Raptors of the NBA and the Toronto Argonauts of the CFL.\r\n\r\nIn 2017, Toronto FC won the domestic treble with the MLS Cup, Supporters' Shield and Canadian Championship. They are seven-time winners of the Canadian Championship and were runners-up of the 2018 CONCACAF Champions League, as well as the MLS Cup in 2016 and 2019.\r\n\r\nAs of 2019, the club has an estimated value of US$395 million, making them the fifth most valuable club behind Atlanta United, LA Galaxy, Los Angeles FC, and Seattle Sounders, and have the highest player payroll in Major League Soccer.",
"strDescriptionDE": null,
"strDescriptionFR": null,
"strDescriptionCN": null,
"strDescriptionIT": "Il Toronto FC \u00e8 una societ\u00e0 calcistica canadese con sede nella citt\u00e0 di Toronto, fondata nel 2005. Milita nella Major League Soccer (MLS), il massimo campionato di calcio degli Stati Uniti d'America e del Canada.[1]\r\n\r\n\u00c8 stato il primo club non statunitense ad entrare a far parte della MLS e il primo ad aggiudicarsi la MLS.[2] \u00c8 stato inoltre il primo club nordamericano (e finora unico) a centrare il treble, ovvero la vittoria della coppa nazionale (2017), della stagione regolare (2017) e della MLS Cup (2017) in un'unica stagione. In ambito nazionale vanta la vittoria di 7 Canadian Championship, mentre in ambito internazionale ha raggiunto la finale della CONCACAF Champions League nel 2018.\r\n\r\nIl club \u00e8 una delle 5 squadre ad ad aver conquistato nella stessa stagione l'MLS Cup e il Supporters' Shield.\r\n\r\nLa squadra disputa le partite interne allo stadio BMO Field di Toronto.",
"strDescriptionJP": null,
"strDescriptionRU": null,
"strDescriptionES": null,
"strDescriptionPT": null,
"strDescriptionSE": null,
"strDescriptionNL": null,
"strDescriptionHU": null,
"strDescriptionNO": null,
"strDescriptionIL": null,
"strDescriptionPL": null,
"strColour1": "#B81137",
"strColour2": "#455560",
"strColour3": "",
"strGender": "Male",
"strCountry": "Canada",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strLogo": "https://r2.thesportsdb.com/images/media/team/logo/d1tbhj1547221736.png",
"strFanart1": "https://r2.thesportsdb.com/images/media/team/fanart/qxsttq1420795117.jpg",
"strFanart2": "https://r2.thesportsdb.com/images/media/team/fanart/ryfs3t1547729361.jpg",
"strFanart3": "https://r2.thesportsdb.com/images/media/team/fanart/vbstx91547729367.jpg",
"strFanart4": "https://r2.thesportsdb.com/images/media/team/fanart/tyw1av1547729372.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/team/banner/pssp4z1547805955.jpg",
"strEquipment": "https://www.thesportsdb.com/images/media/team/equipment/ly8y4n1766576019.png",
"strYoutube": "www.youtube.com/torontofc",
"strLocked": "unlocked"
}
]
}

View File

@@ -0,0 +1,3 @@
{
"Message": "No data found"
}

View File

@@ -0,0 +1,13 @@
{
"search": [
{
"idLeague": "5279",
"strLeague": "MLS Next Pro",
"strSport": "Soccer",
"strBadge": "https://r2.thesportsdb.com/images/media/league/badge/vokzs71650475719.png",
"strCountry": "United States",
"strCurrentSeason": "2026",
"strGender": "Male"
}
]
}

View File

@@ -0,0 +1,70 @@
{
"teams": [
{
"idTeam": "134148",
"idESPN": null,
"idAPIfootball": "1601",
"intLoved": "2",
"strTeam": "Toronto FC",
"strTeamAlternate": "",
"strTeamShort": "TOR",
"intFormedYear": "2006",
"strSport": "Soccer",
"strLeague": "American Major League Soccer",
"idLeague": "4346",
"strLeague2": "Leagues Cup",
"idLeague2": "5281",
"strLeague3": "",
"idLeague3": null,
"strLeague4": "",
"idLeague4": null,
"strLeague5": "",
"idLeague5": null,
"strLeague6": "",
"idLeague6": null,
"strLeague7": "",
"idLeague7": null,
"strDivision": null,
"idVenue": "16782",
"strStadium": "BMO Field",
"strKeywords": "",
"strRSS": "",
"strLocation": "Toronto, Ontario",
"intStadiumCapacity": "30991",
"strWebsite": "www.torontofc.ca",
"strFacebook": "www.facebook.com/torontofc",
"strTwitter": "twitter.com/torontofc",
"strInstagram": "www.instagram.com/torontofc",
"strDescriptionEN": "Toronto Football Club is a Canadian professional soccer club based in Toronto. The club competes in Major League Soccer (MLS) as a member of the Eastern Conference. The team plays its home matches at BMO Field, located at Exhibition Place on Toronto's shoreline west of Downtown Toronto. Toronto FC joined MLS in 2007 as an expansion team and was the first Canadian-based franchise in the league.\r\n\r\nThe first team is operated by Maple Leaf Sports & Entertainment, which also operates the MLS Next Pro affiliate team Toronto FC II and most other professional sports franchises in the city, like the Toronto Maple Leafs of the NHL, Toronto Raptors of the NBA and the Toronto Argonauts of the CFL.\r\n\r\nIn 2017, Toronto FC won the domestic treble with the MLS Cup, Supporters' Shield and Canadian Championship. They are seven-time winners of the Canadian Championship and were runners-up of the 2018 CONCACAF Champions League, as well as the MLS Cup in 2016 and 2019.\r\n\r\nAs of 2019, the club has an estimated value of US$395 million, making them the fifth most valuable club behind Atlanta United, LA Galaxy, Los Angeles FC, and Seattle Sounders, and have the highest player payroll in Major League Soccer.",
"strDescriptionDE": null,
"strDescriptionFR": null,
"strDescriptionCN": null,
"strDescriptionIT": "Il Toronto FC \u00e8 una societ\u00e0 calcistica canadese con sede nella citt\u00e0 di Toronto, fondata nel 2005. Milita nella Major League Soccer (MLS), il massimo campionato di calcio degli Stati Uniti d'America e del Canada.[1]\r\n\r\n\u00c8 stato il primo club non statunitense ad entrare a far parte della MLS e il primo ad aggiudicarsi la MLS.[2] \u00c8 stato inoltre il primo club nordamericano (e finora unico) a centrare il treble, ovvero la vittoria della coppa nazionale (2017), della stagione regolare (2017) e della MLS Cup (2017) in un'unica stagione. In ambito nazionale vanta la vittoria di 7 Canadian Championship, mentre in ambito internazionale ha raggiunto la finale della CONCACAF Champions League nel 2018.\r\n\r\nIl club \u00e8 una delle 5 squadre ad ad aver conquistato nella stessa stagione l'MLS Cup e il Supporters' Shield.\r\n\r\nLa squadra disputa le partite interne allo stadio BMO Field di Toronto.",
"strDescriptionJP": null,
"strDescriptionRU": null,
"strDescriptionES": null,
"strDescriptionPT": null,
"strDescriptionSE": null,
"strDescriptionNL": null,
"strDescriptionHU": null,
"strDescriptionNO": null,
"strDescriptionIL": null,
"strDescriptionPL": null,
"strColour1": "#B81137",
"strColour2": "#455560",
"strColour3": "",
"strGender": "Male",
"strCountry": "Canada",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strLogo": "https://r2.thesportsdb.com/images/media/team/logo/d1tbhj1547221736.png",
"strFanart1": "https://r2.thesportsdb.com/images/media/team/fanart/qxsttq1420795117.jpg",
"strFanart2": "https://r2.thesportsdb.com/images/media/team/fanart/ryfs3t1547729361.jpg",
"strFanart3": "https://r2.thesportsdb.com/images/media/team/fanart/vbstx91547729367.jpg",
"strFanart4": "https://r2.thesportsdb.com/images/media/team/fanart/tyw1av1547729372.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/team/banner/pssp4z1547805955.jpg",
"strEquipment": "https://www.thesportsdb.com/images/media/team/equipment/ly8y4n1766576019.png",
"strYoutube": "www.youtube.com/torontofc",
"strLocked": "unlocked"
}
]
}

View File

@@ -0,0 +1,484 @@
{
"events": [
{
"idEvent": "2406753",
"idAPIfootball": "1490151",
"strTimestamp": "2026-03-14T17:00:00",
"strEvent": "Toronto FC vs New York Red Bulls",
"strEventAlternate": "New York Red Bulls @ Toronto FC",
"strFilename": "American Major League Soccer 2026-03-14 Toronto FC vs New York Red Bulls",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "New York Red Bulls",
"intHomeScore": null,
"intRound": "4",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-03-14",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "134156",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/suytvy1473536462.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/4wgouh1706716030.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/l0z2931706714037.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/rt3hqv1706713405.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/6lpnqd1706718691.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406768",
"idAPIfootball": "1490165",
"strTimestamp": "2026-03-21T17:00:00",
"strEvent": "Toronto FC vs Columbus Crew",
"strEventAlternate": "Columbus Crew @ Toronto FC",
"strFilename": "American Major League Soccer 2026-03-21 Toronto FC vs Columbus Crew",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Columbus Crew",
"intHomeScore": null,
"intRound": "5",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-03-21",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "134152",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/dzs8cp1629059854.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/gdbc391706715993.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/pp3i0l1706714009.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/xab3h11706713371.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/y2lohb1706718675.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406782",
"idAPIfootball": "1490180",
"strTimestamp": "2026-04-04T17:00:00",
"strEvent": "Toronto FC vs Colorado Rapids",
"strEventAlternate": "Colorado Rapids @ Toronto FC",
"strFilename": "American Major League Soccer 2026-04-04 Toronto FC vs Colorado Rapids",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Colorado Rapids",
"intHomeScore": null,
"intRound": "6",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-04-04",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "134794",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/uppupv1473536412.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/tuqxzy1771753493.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/oqmxey1771754131.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/re0wo71771753307.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/b4c9171771753983.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406798",
"idAPIfootball": "1490196",
"strTimestamp": "2026-04-11T17:00:00",
"strEvent": "Toronto FC vs FC Cincinnati",
"strEventAlternate": "FC Cincinnati @ Toronto FC",
"strFilename": "American Major League Soccer 2026-04-11 Toronto FC vs FC Cincinnati",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "FC Cincinnati",
"intHomeScore": null,
"intRound": "7",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-04-11",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "136688",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vvhsqc1707631046.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/oag8hp1674489270.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/r6l2dl1706713803.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/p5mxz71706713223.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/3do5bk1706718587.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406813",
"idAPIfootball": "1490212",
"strTimestamp": "2026-04-18T17:00:00",
"strEvent": "Toronto FC vs Austin FC",
"strEventAlternate": "Austin FC @ Toronto FC",
"strFilename": "American Major League Soccer 2026-04-18 Toronto FC vs Austin FC",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Austin FC",
"intHomeScore": null,
"intRound": "8",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-04-18",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "140079",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/a3dlg61595434277.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/adf29m1771753504.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/ko7gbq1771754143.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/c7qigg1771753319.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/iitgo81771753993.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406829",
"idAPIfootball": "1490227",
"strTimestamp": "2026-04-22T23:30:00",
"strEvent": "Toronto FC vs Philadelphia Union",
"strEventAlternate": "Philadelphia Union @ Toronto FC",
"strFilename": "American Major League Soccer 2026-04-22 Toronto FC vs Philadelphia Union",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Philadelphia Union",
"intHomeScore": null,
"intRound": "9",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-04-22",
"dateEventLocal": null,
"strTime": "23:30:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "134142",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/gyznyo1602103682.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/qx2k6k1706715901.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/ail0fh1706713966.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/8sfg181706713309.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/z0ucvs1706718638.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406839",
"idAPIfootball": "1490237",
"strTimestamp": "2026-04-25T17:00:00",
"strEvent": "Toronto FC vs Atlanta United",
"strEventAlternate": "Atlanta United @ Toronto FC",
"strFilename": "American Major League Soccer 2026-04-25 Toronto FC vs Atlanta United",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Atlanta United",
"intHomeScore": null,
"intRound": "9",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-04-25",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "135851",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/ej091x1602103070.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/0d1mu81674489091.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/1i0wk21706713736.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/lh56x71706713125.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/349xhb1706718535.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406865",
"idAPIfootball": "1490252",
"strTimestamp": "2026-05-02T17:00:00",
"strEvent": "Toronto FC vs San Jose Earthquakes",
"strEventAlternate": "San Jose Earthquakes @ Toronto FC",
"strFilename": "American Major League Soccer 2026-05-02 Toronto FC vs San Jose Earthquakes",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "San Jose Earthquakes",
"intHomeScore": null,
"intRound": "10",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-05-02",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "134157",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/xyrqqt1420781048.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/epsk7c1659976798.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/3x100g1659980618.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/fqaaht1644524215.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/mu24rs1659979032.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406878",
"idAPIfootball": "1490267",
"strTimestamp": "2026-05-09T17:00:00",
"strEvent": "Toronto FC vs Inter Miami",
"strEventAlternate": "Inter Miami @ Toronto FC",
"strFilename": "American Major League Soccer 2026-05-09 Toronto FC vs Inter Miami",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Inter Miami",
"intHomeScore": null,
"intRound": "11",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-05-09",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "137699",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/m4it3e1602103647.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/yg9alq1706716044.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/2p6ac91706714071.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/jqdmuw1706713416.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/jaqgyc1706718698.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406900",
"idAPIfootball": "1490298",
"strTimestamp": "2026-05-16T23:30:00",
"strEvent": "Charlotte FC vs Toronto FC",
"strEventAlternate": "Toronto FC @ Charlotte FC",
"strFilename": "American Major League Soccer 2026-05-16 Charlotte FC vs Toronto FC",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": null,
"strHomeTeam": "Charlotte FC",
"strAwayTeam": "Toronto FC",
"intHomeScore": null,
"intRound": "12",
"intAwayScore": null,
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-05-16",
"dateEventLocal": null,
"strTime": "23:30:00",
"strTimeLocal": null,
"strGroup": null,
"idHomeTeam": "140078",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/b6p4uz1595434047.png",
"idAwayTeam": "134148",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"intScore": null,
"intScoreVotes": null,
"strResult": null,
"idVenue": "18776",
"strVenue": "Bank of America Stadium",
"strCountry": "United States",
"strCity": null,
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/dekc1y1674489274.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/21yoe01706713753.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/1n0kgw1706713151.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/q1vueq1706718550.jpg",
"strMap": null,
"strTweet1": null,
"strVideo": null,
"strStatus": "Not Started",
"strPostponed": "no",
"strLocked": "unlocked"
}
]
}

View File

@@ -0,0 +1,244 @@
{
"results": [
{
"idEvent": "2406751",
"idAPIfootball": "1490149",
"strTimestamp": "2026-03-08T23:00:00",
"strEvent": "FC Cincinnati vs Toronto FC",
"strEventAlternate": "Toronto FC @ FC Cincinnati",
"strFilename": "American Major League Soccer 2026-03-08 FC Cincinnati vs Toronto FC",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": "",
"strHomeTeam": "FC Cincinnati",
"strAwayTeam": "Toronto FC",
"intHomeScore": "0",
"intRound": "3",
"intAwayScore": "1",
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-03-08",
"dateEventLocal": "2026-03-08",
"strTime": "23:00:00",
"strTimeLocal": "18:00:00",
"strGroup": "",
"idHomeTeam": "136688",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vvhsqc1707631046.png",
"idAwayTeam": "134148",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"intScore": null,
"intScoreVotes": null,
"strResult": "",
"idVenue": "20820",
"strVenue": "TQL Stadium",
"strCountry": "United States",
"strCity": "",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/tnm6721674489170.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/frynnc1706713440.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/7xrdgm1706713089.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/zkk3rl1706718516.jpg",
"strMap": null,
"strTweet1": "",
"strVideo": "https://www.youtube.com/watch?v=LloAaRmqvag",
"strStatus": "Match Finished",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406731",
"idAPIfootball": "1490129",
"strTimestamp": "2026-03-01T02:30:00",
"strEvent": "Vancouver Whitecaps vs Toronto FC",
"strEventAlternate": "Toronto FC @ Vancouver Whitecaps",
"strFilename": "American Major League Soccer 2026-03-01 Vancouver Whitecaps vs Toronto FC",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": "",
"strHomeTeam": "Vancouver Whitecaps",
"strAwayTeam": "Toronto FC",
"intHomeScore": "3",
"intRound": "2",
"intAwayScore": "0",
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-03-01",
"dateEventLocal": "2026-02-28",
"strTime": "02:30:00",
"strTimeLocal": "18:30:00",
"strGroup": "",
"idHomeTeam": "134147",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/tpwxpy1473536521.png",
"idAwayTeam": "134148",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"intScore": null,
"intScoreVotes": null,
"strResult": "",
"idVenue": "25843",
"strVenue": "BC Place",
"strCountry": "Canada",
"strCity": "",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/oolq431659976740.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/ecyoz01706713746.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/7r0ggq1706713138.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/4lwklu1706718544.jpg",
"strMap": null,
"strTweet1": "",
"strVideo": "https://www.youtube.com/watch?v=RTawJpGyvPg",
"strStatus": "Match Finished",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2406713",
"idAPIfootball": "1490781",
"strTimestamp": "2026-02-22T01:30:00",
"strEvent": "FC Dallas vs Toronto FC",
"strEventAlternate": "Toronto FC @ FC Dallas",
"strFilename": "American Major League Soccer 2026-02-22 FC Dallas vs Toronto FC",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": "",
"strHomeTeam": "FC Dallas",
"strAwayTeam": "Toronto FC",
"intHomeScore": "3",
"intRound": "1",
"intAwayScore": "2",
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-02-22",
"dateEventLocal": "2026-02-21",
"strTime": "01:30:00",
"strTimeLocal": "19:30:00",
"strGroup": "",
"idHomeTeam": "134146",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vxy8xy1602103187.png",
"idAwayTeam": "134148",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"intScore": null,
"intScoreVotes": null,
"strResult": "",
"idVenue": "16781",
"strVenue": "Toyota Stadium Dallas",
"strCountry": "United States",
"strCity": "",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/9bjukc1659976671.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/fj461l1659980511.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/owuytt1644524082.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/1yvzoa1659978946.jpg",
"strMap": null,
"strTweet1": "",
"strVideo": "https://www.youtube.com/watch?v=Sf5YmqW5ONo",
"strStatus": "Match Finished",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2425216",
"idAPIfootball": "1514800",
"strTimestamp": "2026-02-14T09:45:00",
"strEvent": "Toronto FC vs Polissya Zhytomyr",
"strEventAlternate": "Polissya Zhytomyr @ Toronto FC",
"strFilename": "Club Friendlies 2026-02-14 Toronto FC vs Polissya Zhytomyr",
"strSport": "Soccer",
"idLeague": "4569",
"strLeague": "Club Friendlies",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/gb18781565430778.png",
"strSeason": "2026",
"strDescriptionEN": "",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Polissya Zhytomyr",
"intHomeScore": "2",
"intRound": "0",
"intAwayScore": "1",
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-02-14",
"dateEventLocal": "2026-02-14",
"strTime": "09:45:00",
"strTimeLocal": "04:45:00",
"strGroup": "",
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "140180",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/6r8o5l1725171180.png",
"intScore": null,
"intScoreVotes": null,
"strResult": "",
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": "",
"strPoster": "",
"strSquare": "",
"strFanart": null,
"strThumb": "",
"strBanner": "",
"strMap": null,
"strTweet1": "",
"strVideo": "",
"strStatus": "Match Finished",
"strPostponed": "no",
"strLocked": "unlocked"
},
{
"idEvent": "2425209",
"idAPIfootball": "1514792",
"strTimestamp": "2026-02-11T11:00:00",
"strEvent": "Toronto FC vs Fredrikstad",
"strEventAlternate": "Fredrikstad @ Toronto FC",
"strFilename": "Club Friendlies 2026-02-11 Toronto FC vs Fredrikstad",
"strSport": "Soccer",
"idLeague": "4569",
"strLeague": "Club Friendlies",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/gb18781565430778.png",
"strSeason": "2026",
"strDescriptionEN": "",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Fredrikstad",
"intHomeScore": "0",
"intRound": "0",
"intAwayScore": "1",
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-02-11",
"dateEventLocal": "2026-02-11",
"strTime": "11:00:00",
"strTimeLocal": "06:00:00",
"strGroup": "",
"idHomeTeam": "134148",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"idAwayTeam": "134749",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/9se6qv1690695269.png",
"intScore": null,
"intScoreVotes": null,
"strResult": "",
"idVenue": "16782",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strCity": "",
"strPoster": "",
"strSquare": "",
"strFanart": null,
"strThumb": "",
"strBanner": "",
"strMap": null,
"strTweet1": "",
"strVideo": "",
"strStatus": "Match Finished",
"strPostponed": "no",
"strLocked": "unlocked"
}
]
}

View File

@@ -0,0 +1,294 @@
{
"schedule": [
{
"idEvent": "2406753",
"strEvent": "Toronto FC vs New York Red Bulls",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "New York Red Bulls",
"idHomeTeam": "134148",
"idAwayTeam": "134156",
"intRound": "4",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-03-14T17:00:00",
"dateEvent": "2026-03-14",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/suytvy1473536462.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/rt3hqv1706713405.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/4wgouh1706716030.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-03-14 Toronto FC vs New York Red Bulls",
"strStatus": "Not Started"
},
{
"idEvent": "2406768",
"strEvent": "Toronto FC vs Columbus Crew",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Columbus Crew",
"idHomeTeam": "134148",
"idAwayTeam": "134152",
"intRound": "5",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-03-21T17:00:00",
"dateEvent": "2026-03-21",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/dzs8cp1629059854.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/xab3h11706713371.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/gdbc391706715993.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-03-21 Toronto FC vs Columbus Crew",
"strStatus": "Not Started"
},
{
"idEvent": "2406782",
"strEvent": "Toronto FC vs Colorado Rapids",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Colorado Rapids",
"idHomeTeam": "134148",
"idAwayTeam": "134794",
"intRound": "6",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-04-04T17:00:00",
"dateEvent": "2026-04-04",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/uppupv1473536412.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/re0wo71771753307.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/tuqxzy1771753493.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-04-04 Toronto FC vs Colorado Rapids",
"strStatus": "Not Started"
},
{
"idEvent": "2406798",
"strEvent": "Toronto FC vs FC Cincinnati",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "FC Cincinnati",
"idHomeTeam": "134148",
"idAwayTeam": "136688",
"intRound": "7",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-04-11T17:00:00",
"dateEvent": "2026-04-11",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vvhsqc1707631046.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/p5mxz71706713223.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/oag8hp1674489270.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-04-11 Toronto FC vs FC Cincinnati",
"strStatus": "Not Started"
},
{
"idEvent": "2406813",
"strEvent": "Toronto FC vs Austin FC",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Austin FC",
"idHomeTeam": "134148",
"idAwayTeam": "140079",
"intRound": "8",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-04-18T17:00:00",
"dateEvent": "2026-04-18",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/a3dlg61595434277.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/c7qigg1771753319.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/adf29m1771753504.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-04-18 Toronto FC vs Austin FC",
"strStatus": "Not Started"
},
{
"idEvent": "2406829",
"strEvent": "Toronto FC vs Philadelphia Union",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Philadelphia Union",
"idHomeTeam": "134148",
"idAwayTeam": "134142",
"intRound": "9",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-04-22T23:30:00",
"dateEvent": "2026-04-22",
"dateEventLocal": null,
"strTime": "23:30:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/gyznyo1602103682.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/8sfg181706713309.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/qx2k6k1706715901.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-04-22 Toronto FC vs Philadelphia Union",
"strStatus": "Not Started"
},
{
"idEvent": "2406839",
"strEvent": "Toronto FC vs Atlanta United",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Atlanta United",
"idHomeTeam": "134148",
"idAwayTeam": "135851",
"intRound": "9",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-04-25T17:00:00",
"dateEvent": "2026-04-25",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/ej091x1602103070.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/lh56x71706713125.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/0d1mu81674489091.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-04-25 Toronto FC vs Atlanta United",
"strStatus": "Not Started"
},
{
"idEvent": "2406865",
"strEvent": "Toronto FC vs San Jose Earthquakes",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "San Jose Earthquakes",
"idHomeTeam": "134148",
"idAwayTeam": "134157",
"intRound": "10",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-05-02T17:00:00",
"dateEvent": "2026-05-02",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/xyrqqt1420781048.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/fqaaht1644524215.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/epsk7c1659976798.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-05-02 Toronto FC vs San Jose Earthquakes",
"strStatus": "Not Started"
},
{
"idEvent": "2406878",
"strEvent": "Toronto FC vs Inter Miami",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Inter Miami",
"idHomeTeam": "134148",
"idAwayTeam": "137699",
"intRound": "11",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-05-09T17:00:00",
"dateEvent": "2026-05-09",
"dateEventLocal": null,
"strTime": "17:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/m4it3e1602103647.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/jqdmuw1706713416.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/yg9alq1706716044.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-05-09 Toronto FC vs Inter Miami",
"strStatus": "Not Started"
},
{
"idEvent": "2406900",
"strEvent": "Charlotte FC vs Toronto FC",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Charlotte FC",
"strAwayTeam": "Toronto FC",
"idHomeTeam": "140078",
"idAwayTeam": "134148",
"intRound": "12",
"intHomeScore": null,
"intAwayScore": null,
"strTimestamp": "2026-05-16T23:30:00",
"dateEvent": "2026-05-16",
"dateEventLocal": null,
"strTime": "23:30:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/b6p4uz1595434047.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strVenue": "Bank of America Stadium",
"strCountry": "United States",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/1n0kgw1706713151.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/dekc1y1674489274.jpg",
"strVideo": null,
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-05-16 Charlotte FC vs Toronto FC",
"strStatus": "Not Started"
}
]
}

View File

@@ -0,0 +1,294 @@
{
"schedule": [
{
"idEvent": "2406751",
"strEvent": "FC Cincinnati vs Toronto FC",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "FC Cincinnati",
"strAwayTeam": "Toronto FC",
"idHomeTeam": "136688",
"idAwayTeam": "134148",
"intRound": "3",
"intHomeScore": "0",
"intAwayScore": "1",
"strTimestamp": "2026-03-08T23:00:00",
"dateEvent": "2026-03-08",
"dateEventLocal": "2026-03-08",
"strTime": "23:00:00",
"strTimeLocal": "18:00:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vvhsqc1707631046.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strVenue": "TQL Stadium",
"strCountry": "United States",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/7xrdgm1706713089.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/tnm6721674489170.jpg",
"strVideo": "https://www.youtube.com/watch?v=LloAaRmqvag",
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-03-08 FC Cincinnati vs Toronto FC",
"strStatus": "Match Finished"
},
{
"idEvent": "2406731",
"strEvent": "Vancouver Whitecaps vs Toronto FC",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Vancouver Whitecaps",
"strAwayTeam": "Toronto FC",
"idHomeTeam": "134147",
"idAwayTeam": "134148",
"intRound": "2",
"intHomeScore": "3",
"intAwayScore": "0",
"strTimestamp": "2026-03-01T02:30:00",
"dateEvent": "2026-03-01",
"dateEventLocal": "2026-02-28",
"strTime": "02:30:00",
"strTimeLocal": "18:30:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/tpwxpy1473536521.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strVenue": "BC Place",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/7r0ggq1706713138.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/oolq431659976740.jpg",
"strVideo": "https://www.youtube.com/watch?v=RTawJpGyvPg",
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-03-01 Vancouver Whitecaps vs Toronto FC",
"strStatus": "Match Finished"
},
{
"idEvent": "2406713",
"strEvent": "FC Dallas vs Toronto FC",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "FC Dallas",
"strAwayTeam": "Toronto FC",
"idHomeTeam": "134146",
"idAwayTeam": "134148",
"intRound": "1",
"intHomeScore": "3",
"intAwayScore": "2",
"strTimestamp": "2026-02-22T01:30:00",
"dateEvent": "2026-02-22",
"dateEventLocal": "2026-02-21",
"strTime": "01:30:00",
"strTimeLocal": "19:30:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vxy8xy1602103187.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strVenue": "Toyota Stadium Dallas",
"strCountry": "United States",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/owuytt1644524082.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/9bjukc1659976671.jpg",
"strVideo": "https://www.youtube.com/watch?v=Sf5YmqW5ONo",
"strPostponed": "no",
"strFilename": "American Major League Soccer 2026-02-22 FC Dallas vs Toronto FC",
"strStatus": "Match Finished"
},
{
"idEvent": "2425216",
"strEvent": "Toronto FC vs Polissya Zhytomyr",
"idLeague": "4569",
"strLeague": "Club Friendlies",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Polissya Zhytomyr",
"idHomeTeam": "134148",
"idAwayTeam": "140180",
"intRound": "0",
"intHomeScore": "2",
"intAwayScore": "1",
"strTimestamp": "2026-02-14T09:45:00",
"dateEvent": "2026-02-14",
"dateEventLocal": "2026-02-14",
"strTime": "09:45:00",
"strTimeLocal": "04:45:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/6r8o5l1725171180.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "",
"strPoster": "",
"strVideo": "",
"strPostponed": "no",
"strFilename": "Club Friendlies 2026-02-14 Toronto FC vs Polissya Zhytomyr",
"strStatus": "Match Finished"
},
{
"idEvent": "2425209",
"strEvent": "Toronto FC vs Fredrikstad",
"idLeague": "4569",
"strLeague": "Club Friendlies",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Fredrikstad",
"idHomeTeam": "134148",
"idAwayTeam": "134749",
"intRound": "0",
"intHomeScore": "0",
"intAwayScore": "1",
"strTimestamp": "2026-02-11T11:00:00",
"dateEvent": "2026-02-11",
"dateEventLocal": "2026-02-11",
"strTime": "11:00:00",
"strTimeLocal": "06:00:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/9se6qv1690695269.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "",
"strPoster": "",
"strVideo": "",
"strPostponed": "no",
"strFilename": "Club Friendlies 2026-02-11 Toronto FC vs Fredrikstad",
"strStatus": "Match Finished"
},
{
"idEvent": "2425206",
"strEvent": "Toronto FC vs AIK",
"idLeague": "4569",
"strLeague": "Club Friendlies",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "AIK",
"idHomeTeam": "134148",
"idAwayTeam": "134011",
"intRound": "0",
"intHomeScore": "2",
"intAwayScore": "2",
"strTimestamp": "2026-02-08T15:00:00",
"dateEvent": "2026-02-08",
"dateEventLocal": "2026-02-07",
"strTime": "15:00:00",
"strTimeLocal": "09:00:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rwsrxq1420769503.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "",
"strPoster": "",
"strVideo": "",
"strPostponed": "no",
"strFilename": "Club Friendlies 2026-02-08 Toronto FC vs AIK",
"strStatus": "Match Finished"
},
{
"idEvent": "2425183",
"strEvent": "Toronto FC vs Jeonbuk Hyundai Motors",
"idLeague": "4569",
"strLeague": "Club Friendlies",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Jeonbuk Hyundai Motors",
"idHomeTeam": "134148",
"idAwayTeam": "138111",
"intRound": "0",
"intHomeScore": "2",
"intAwayScore": "0",
"strTimestamp": "2026-01-31T13:00:00",
"dateEvent": "2026-01-31",
"dateEventLocal": "2026-01-31",
"strTime": "13:00:00",
"strTimeLocal": "08:00:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/8jif3b1747853225.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "",
"strPoster": "",
"strVideo": "",
"strPostponed": "no",
"strFilename": "Club Friendlies 2026-01-31 Toronto FC vs Jeonbuk Hyundai Motors",
"strStatus": "Match Finished"
},
{
"idEvent": "2425155",
"strEvent": "Toronto FC vs Incheon United",
"idLeague": "4569",
"strLeague": "Club Friendlies",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Incheon United",
"idHomeTeam": "134148",
"idAwayTeam": "138110",
"intRound": "0",
"intHomeScore": "4",
"intAwayScore": "1",
"strTimestamp": "2026-01-28T15:00:00",
"dateEvent": "2026-01-28",
"dateEventLocal": null,
"strTime": "15:00:00",
"strTimeLocal": null,
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/2no9nq1579473100.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "",
"strPoster": "",
"strVideo": null,
"strPostponed": "no",
"strFilename": "Club Friendlies 2026-01-28 Toronto FC vs Incheon United",
"strStatus": "Match Finished"
},
{
"idEvent": "2192510",
"strEvent": "Toronto FC vs Orlando City",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Toronto FC",
"strAwayTeam": "Orlando City",
"idHomeTeam": "134148",
"idAwayTeam": "135292",
"intRound": "51",
"intHomeScore": "4",
"intAwayScore": "2",
"strTimestamp": "2025-10-18T22:00:00",
"dateEvent": "2025-10-18",
"dateEventLocal": "2025-10-18",
"strTime": "22:00:00",
"strTimeLocal": "18:00:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/qyppxw1423832326.png",
"strVenue": "BMO Field",
"strCountry": "Canada",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/vruhs31736768055.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/7zrec61674489293.jpg",
"strVideo": "https://www.youtube.com/watch?v=n9HxS3fULdk",
"strPostponed": "no",
"strFilename": "American Major League Soccer 2025-10-18 Toronto FC vs Orlando City",
"strStatus": "Match Finished"
},
{
"idEvent": "2192264",
"strEvent": "Los Angeles FC vs Toronto FC",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strSport": "Soccer",
"strHomeTeam": "Los Angeles FC",
"strAwayTeam": "Toronto FC",
"idHomeTeam": "136050",
"idAwayTeam": "134148",
"intRound": "26",
"intHomeScore": "2",
"intAwayScore": "0",
"strTimestamp": "2025-10-09T02:30:00",
"dateEvent": "2025-10-09",
"dateEventLocal": "2025-10-08",
"strTime": "02:30:00",
"strTimeLocal": "19:30:00",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/7nbj2a1602103638.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"strVenue": "BMO Stadium",
"strCountry": "United States",
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/8tksek1736767451.jpg",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/8ynzkg1736773640.jpg",
"strVideo": "https://www.youtube.com/watch?v=qXM4fqLLJ_I",
"strPostponed": "no",
"strFilename": "American Major League Soccer 2025-10-09 Los Angeles FC vs Toronto FC",
"strStatus": "Match Finished"
}
]
}

View File

@@ -0,0 +1,52 @@
{
"events": [
{
"idEvent": "2406751",
"idAPIfootball": "1490149",
"strTimestamp": "2026-03-08T23:00:00",
"strEvent": "FC Cincinnati vs Toronto FC",
"strEventAlternate": "Toronto FC @ FC Cincinnati",
"strFilename": "American Major League Soccer 2026-03-08 FC Cincinnati vs Toronto FC",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": "",
"strHomeTeam": "FC Cincinnati",
"strAwayTeam": "Toronto FC",
"intHomeScore": "0",
"intRound": "3",
"intAwayScore": "1",
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-03-08",
"dateEventLocal": "2026-03-08",
"strTime": "23:00:00",
"strTimeLocal": "18:00:00",
"strGroup": "",
"idHomeTeam": "136688",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vvhsqc1707631046.png",
"idAwayTeam": "134148",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"intScore": null,
"intScoreVotes": null,
"strResult": "",
"idVenue": "20820",
"strVenue": "TQL Stadium",
"strCountry": "United States",
"strCity": "",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/tnm6721674489170.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/frynnc1706713440.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/7xrdgm1706713089.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/zkk3rl1706718516.jpg",
"strMap": null,
"strTweet1": "",
"strVideo": "https://www.youtube.com/watch?v=LloAaRmqvag",
"strStatus": "Match Finished",
"strPostponed": "no",
"strLocked": "unlocked"
}
]
}

View File

@@ -0,0 +1,52 @@
{
"lookup": [
{
"idEvent": "2406751",
"idAPIfootball": "1490149",
"strTimestamp": "2026-03-08T23:00:00",
"strEvent": "FC Cincinnati vs Toronto FC",
"strEventAlternate": "Toronto FC @ FC Cincinnati",
"strFilename": "American Major League Soccer 2026-03-08 FC Cincinnati vs Toronto FC",
"strSport": "Soccer",
"idLeague": "4346",
"strLeague": "American Major League Soccer",
"strLeagueBadge": "https://r2.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png",
"strSeason": "2026",
"strDescriptionEN": "",
"strHomeTeam": "FC Cincinnati",
"strAwayTeam": "Toronto FC",
"intHomeScore": "0",
"intRound": "3",
"intAwayScore": "1",
"intSpectators": null,
"strOfficial": "",
"dateEvent": "2026-03-08",
"dateEventLocal": "2026-03-08",
"strTime": "23:00:00",
"strTimeLocal": "18:00:00",
"strGroup": "",
"idHomeTeam": "136688",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vvhsqc1707631046.png",
"idAwayTeam": "134148",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rsxyrr1473536512.png",
"intScore": null,
"intScoreVotes": null,
"strResult": "",
"idVenue": "20820",
"strVenue": "TQL Stadium",
"strCountry": "United States",
"strCity": "",
"strPoster": "https://r2.thesportsdb.com/images/media/event/poster/tnm6721674489170.jpg",
"strSquare": "https://r2.thesportsdb.com/images/media/event/square/frynnc1706713440.jpg",
"strFanart": null,
"strThumb": "https://r2.thesportsdb.com/images/media/event/thumb/7xrdgm1706713089.jpg",
"strBanner": "https://r2.thesportsdb.com/images/media/event/banner/zkk3rl1706718516.jpg",
"strMap": null,
"strTweet1": "",
"strVideo": "https://www.youtube.com/watch?v=LloAaRmqvag",
"strStatus": "Match Finished",
"strPostponed": "no",
"strLocked": "unlocked"
}
]
}

View File

@@ -0,0 +1,3 @@
{
"Message": "No data found"
}

View File

@@ -0,0 +1,3 @@
{
"Message": "No data found"
}

View File

@@ -0,0 +1,3 @@
{
"Message": "No data found"
}

View File

@@ -0,0 +1,19 @@
{
"player": [
{
"idPlayer": "34148472",
"idTeam": "134781",
"strPlayer": "Federico Bernardeschi",
"strTeam": "Bologna",
"strSport": "Soccer",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/ptaxx21515958866.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/fwmcl01758896784.png",
"strNationality": "Italy",
"dateBorn": "1994-02-16",
"strStatus": "Active",
"strGender": "Male",
"strPosition": "Winger",
"relevance": "28.932828903198242"
}
]
}

View File

@@ -0,0 +1,75 @@
{
"lookup": [
{
"idPlayer": "34146036",
"idTeam": "134148",
"idTeam2": "0",
"idTeamNational": null,
"idAPIfootball": "18915",
"idPlayerManager": null,
"idWikidata": "Q6396794",
"idTransferMkt": "111114",
"idESPN": "140531",
"strNationality": "Ireland",
"strPlayer": "Kevin Long",
"strPlayerAlternate": "",
"strTeam": "Toronto FC",
"strTeam2": "",
"strSport": "Soccer",
"intSoccerXMLTeamID": "28",
"dateBorn": "1990-08-18",
"dateDied": null,
"strNumber": "5",
"dateSigned": "2010-07-01",
"strSigning": "",
"strWage": "",
"strOutfitter": "",
"strKit": "",
"strAgent": "",
"strBirthLocation": "Cork, Ireland",
"strDeathLocation": null,
"strEthnicity": "",
"strStatus": "Active",
"strDescriptionEN": "Kevin Finbarr Long (born 18 August 1990) is an Irish professional footballer who plays for Major League Soccer club Toronto FC and the Republic of Ireland national team.\r\nHe plays mainly as a centre back but can also fill in at full back.\r\n\r\nLong came through the Cork City youth structure, before signing on professional terms in January 2008. He captained Cork City Youths successful FAI Youth Cup and Munster Cup teams in 2009. Shortly after, the centre half made his senior City debut away to St Patricks Athletic coming on as a second half substitute. Long then made his first senior start on 19 July 2009 in a home friendly against Ipswich Town, partnering Dan Murray in defence.\r\n\r\nIt was reported that Long travelled over to Preston North End for a trial, but whilst there, there was interest from League 1 sides Leeds United and Charlton Athletic and Premier League side Burnley.\r\n\r\nHe scored his first goal for Burnley in a 4-3 FA Cup defeat at Southampton on 4 January 2014. On 1 January 2015, Long made his Premier League debut with Burnley in a 3\u20133 draw away to Newcastle United. He replaced the injured Jason Shackell in the 17th minute, but 20 minutes later was replaced by Steven Reid after suffering an injury of his own, becoming the third Burnley player to be taken off injured in the match at St James' Park.\r\n\r\nIn November 2015, after regaining full fitness following his cruciate knee injury, he joined League One side Barnsley on a one-month loan deal. He scored a late winner against Oldham Athletic on his debut to help his side to a 2-1 win. However in his second game he was sent off against Peterborough United.",
"strDescriptionDE": null,
"strDescriptionFR": null,
"strDescriptionCN": null,
"strDescriptionIT": null,
"strDescriptionJP": null,
"strDescriptionRU": null,
"strDescriptionES": null,
"strDescriptionPT": null,
"strDescriptionSE": null,
"strDescriptionNL": null,
"strDescriptionHU": null,
"strDescriptionNO": null,
"strDescriptionIL": null,
"strDescriptionPL": null,
"strGender": "Male",
"strSide": "",
"strPosition": "Centre-Back",
"strCollege": null,
"strFacebook": "t.co/fen2q0xYtf",
"strWebsite": "",
"strTwitter": "twitter.com/kevinlong28",
"strInstagram": "www.instagram.com/kevinlong28",
"strYoutube": "",
"strHeight": "188 cm",
"strWeight": "183 lbs",
"intLoved": "0",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/tq6f5h1557314699.jpg",
"strPoster": null,
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/qi34vk1766575443.png",
"strCartoon": null,
"strRender": "https://r2.thesportsdb.com/images/media/player/render/3idgla1575974022.png",
"strBanner": null,
"strFanart1": null,
"strFanart2": null,
"strFanart3": null,
"strFanart4": null,
"strCreativeCommons": "No",
"strLocked": "unlocked",
"strLastName": "Long"
}
]
}

View File

@@ -0,0 +1,235 @@
{
"list": [
{
"idPlayer": "34146036",
"strPlayer": "Kevin Long",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/tq6f5h1557314699.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/qi34vk1766575443.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/3idgla1575974022.png",
"dateBorn": "1990-08-18",
"strPosition": "Centre-Back"
},
{
"idPlayer": "34164645",
"strPlayer": "Djordje Mihailovi\u0107",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/iexxfp1662720581.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/kvtl3r1766575412.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/o48nh11620763928.png",
"dateBorn": "1998-11-10",
"strPosition": "Attacking Midfield"
},
{
"idPlayer": "34165110",
"strPlayer": "Walker Zimmerman",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/ql13io1662731506.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/190oag1766741902.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/6oe28j1622016455.png",
"dateBorn": "1993-05-19",
"strPosition": "Centre-Back"
},
{
"idPlayer": "34165262",
"strPlayer": "Derrick Etienne",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/yv4k1k1548617647.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/9kwou81766575620.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/81yr7p1608042138.png",
"dateBorn": "1996-11-25",
"strPosition": "Left Wing"
},
{
"idPlayer": "34173658",
"strPlayer": "Sigurd Rosted",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/4eexde1610625953.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/22mdli1766575883.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/e1si7s1682678318.png",
"dateBorn": "1994-07-22",
"strPosition": "Defender"
},
{
"idPlayer": "34176006",
"strPlayer": "Henry Wingo",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/w5u8is1610712583.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/83q9ln1766575157.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/hgv2lj1652986766.png",
"dateBorn": "1995-10-04",
"strPosition": "Right-Back"
},
{
"idPlayer": "34180209",
"strPlayer": "Richie Laryea",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/owdl2g1621328018.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/milfrs1766575517.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/hxtaz01621328022.png",
"dateBorn": "1995-01-07",
"strPosition": "Midfielder"
},
{
"idPlayer": "34180872",
"strPlayer": "Jos\u00e9 Cifuentes",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/2fqfxp1623150919.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/2eyl0h1766575746.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/gdgxvl1623150892.png",
"dateBorn": "1999-03-12",
"strPosition": "Central Midfield"
},
{
"idPlayer": "34180980",
"strPlayer": "Jonathan Osorio",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/exyn7k1621866558.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/j30x9f1766575844.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/o57ift1621866654.png",
"dateBorn": "1992-06-12",
"strPosition": "Midfielder"
},
{
"idPlayer": "34195803",
"strPlayer": "Robin Fraser",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/2b2a0u1641470637.jpg",
"strCutout": null,
"strRender": null,
"dateBorn": "1966-12-17",
"strPosition": "Manager"
},
{
"idPlayer": "34197810",
"strPlayer": "Deandre Kerr",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/6294rp1652706567.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/udw9ui1766575474.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/7h068n1652706582.png",
"dateBorn": "2002-11-29",
"strPosition": "Forward"
},
{
"idPlayer": "34199281",
"strPlayer": "Kosi Thompson",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/s9z1mq1654852335.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/o4e1kx1766575863.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/5mzpxf1654852394.png",
"dateBorn": "2003-01-27",
"strPosition": "Midfielder"
},
{
"idPlayer": "34200759",
"strPlayer": "Theo Corbeanu",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/y0mbrj1696779390.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/grbcb41766575664.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/95xhoq1742905064.png",
"dateBorn": "2002-05-17",
"strPosition": "Right Winger"
},
{
"idPlayer": "34217581",
"strPlayer": "Alonso Coello",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/gw9gw21682679020.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/tkn51q1766575773.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/35xp641682679116.png",
"dateBorn": "1999-10-12",
"strPosition": "Midfielder"
},
{
"idPlayer": "34217935",
"strPlayer": "Kobe Franklin",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/s7e3n91683191394.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/uku81g1766575824.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/xsupuc1683191398.png",
"dateBorn": "2003-05-10",
"strPosition": "Defender"
},
{
"idPlayer": "34220731",
"strPlayer": "Jules-Anthony Vilsaint",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/4xflwp1695202996.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/dsxu6f1766575229.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/f2j8su1695203031.png",
"dateBorn": "2003-01-06",
"strPosition": "Forward"
},
{
"idPlayer": "34221018",
"strPlayer": "Luka Gavran",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/8h84ds1695640668.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/a3ghm91766575645.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/xx795j1695640681.png",
"dateBorn": "2000-05-09",
"strPosition": "Goalkeeper"
},
{
"idPlayer": "34229521",
"strPlayer": "Nickseon Gomis",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/bhf5vs1709718526.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/7c4l8h1766575544.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/f5jvw71709718592.png",
"dateBorn": "2002-03-15",
"strPosition": "Defender"
},
{
"idPlayer": "34334976",
"strPlayer": "Zane Monlouis",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/8fy1wf1742908301.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/lcbhkz1766575296.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/878kzg1742908697.png",
"dateBorn": "2003-10-16",
"strPosition": "Defender"
},
{
"idPlayer": "34334977",
"strPlayer": "Markus Cimermancic",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": "https://r2.thesportsdb.com/images/media/player/thumb/y4196f1742908482.jpg",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/eynx1q1766575721.png",
"strRender": "https://r2.thesportsdb.com/images/media/player/render/tkfqvs1742908599.png",
"dateBorn": "2004-10-01",
"strPosition": "Central Midfield"
},
{
"idPlayer": "34347948",
"strPlayer": "Malik Henry",
"idTeam": "134148",
"strTeam": "Toronto FC",
"strThumb": null,
"strCutout": null,
"strRender": null,
"dateBorn": "2002-07-23",
"strPosition": "Central Midfield"
}
]
}

View File

@@ -0,0 +1,424 @@
{
"table": [
{
"idStanding": "8825962",
"intRank": "1",
"idTeam": "133604",
"strTeam": "Arsenal",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/uyhbfe1612467038.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WWWDD",
"strDescription": "Promotion - Champions League (League phase)",
"intPlayed": "30",
"intWin": "20",
"intLoss": "3",
"intDraw": "7",
"intGoalsFor": "59",
"intGoalsAgainst": "22",
"intGoalDifference": "37",
"intPoints": "67",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825963",
"intRank": "2",
"idTeam": "133613",
"strTeam": "Manchester City",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/vwpvry1467462651.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "DWWWW",
"strDescription": "Promotion - Champions League (League phase)",
"intPlayed": "29",
"intWin": "18",
"intLoss": "5",
"intDraw": "6",
"intGoalsFor": "59",
"intGoalsAgainst": "27",
"intGoalDifference": "32",
"intPoints": "60",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825964",
"intRank": "3",
"idTeam": "133612",
"strTeam": "Manchester United",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/xzqdr11517660252.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LWWDW",
"strDescription": "Promotion - Champions League (League phase)",
"intPlayed": "29",
"intWin": "14",
"intLoss": "6",
"intDraw": "9",
"intGoalsFor": "51",
"intGoalsAgainst": "40",
"intGoalDifference": "11",
"intPoints": "51",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825965",
"intRank": "4",
"idTeam": "133601",
"strTeam": "Aston Villa",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/jykrpv1717309891.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LLDWD",
"strDescription": "Promotion - Champions League (League phase)",
"intPlayed": "29",
"intWin": "15",
"intLoss": "8",
"intDraw": "6",
"intGoalsFor": "39",
"intGoalsAgainst": "34",
"intGoalDifference": "5",
"intPoints": "51",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825966",
"intRank": "5",
"idTeam": "133610",
"strTeam": "Chelsea",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/yvwvtu1448813215.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WLDDW",
"strDescription": "Promotion - Europa League (League phase)",
"intPlayed": "29",
"intWin": "13",
"intLoss": "7",
"intDraw": "9",
"intGoalsFor": "53",
"intGoalsAgainst": "34",
"intGoalDifference": "19",
"intPoints": "48",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825967",
"intRank": "6",
"idTeam": "133602",
"strTeam": "Liverpool",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/kfaher1737969724.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LWWWL",
"strDescription": "",
"intPlayed": "29",
"intWin": "14",
"intLoss": "9",
"intDraw": "6",
"intGoalsFor": "48",
"intGoalsAgainst": "39",
"intGoalDifference": "9",
"intPoints": "48",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825968",
"intRank": "7",
"idTeam": "134355",
"strTeam": "Brentford",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/grv1aw1546453779.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "DWLDW",
"strDescription": "",
"intPlayed": "29",
"intWin": "13",
"intLoss": "11",
"intDraw": "5",
"intGoalsFor": "44",
"intGoalsAgainst": "40",
"intGoalDifference": "4",
"intPoints": "44",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825969",
"intRank": "8",
"idTeam": "133615",
"strTeam": "Everton",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/eqayrf1523184794.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WWLLW",
"strDescription": "",
"intPlayed": "29",
"intWin": "12",
"intLoss": "10",
"intDraw": "7",
"intGoalsFor": "34",
"intGoalsAgainst": "33",
"intGoalDifference": "1",
"intPoints": "43",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825970",
"intRank": "9",
"idTeam": "134301",
"strTeam": "Bournemouth",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/y08nak1534071116.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "DDDWD",
"strDescription": "",
"intPlayed": "29",
"intWin": "9",
"intLoss": "7",
"intDraw": "13",
"intGoalsFor": "44",
"intGoalsAgainst": "46",
"intGoalDifference": "-2",
"intPoints": "40",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825971",
"intRank": "10",
"idTeam": "133600",
"strTeam": "Fulham",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/xwwvyt1448811086.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LWWLL",
"strDescription": "",
"intPlayed": "29",
"intWin": "12",
"intLoss": "13",
"intDraw": "4",
"intGoalsFor": "40",
"intGoalsAgainst": "43",
"intGoalDifference": "-3",
"intPoints": "40",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825972",
"intRank": "11",
"idTeam": "133603",
"strTeam": "Sunderland",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/tprtus1448813498.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WDLLL",
"strDescription": "",
"intPlayed": "29",
"intWin": "10",
"intLoss": "9",
"intDraw": "10",
"intGoalsFor": "30",
"intGoalsAgainst": "34",
"intGoalDifference": "-4",
"intPoints": "40",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825973",
"intRank": "12",
"idTeam": "134777",
"strTeam": "Newcastle United",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/lhwuiz1621593302.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WLLWL",
"strDescription": "",
"intPlayed": "29",
"intWin": "11",
"intLoss": "12",
"intDraw": "6",
"intGoalsFor": "42",
"intGoalsAgainst": "43",
"intGoalDifference": "-1",
"intPoints": "39",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825974",
"intRank": "13",
"idTeam": "133632",
"strTeam": "Crystal Palace",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/ia6i3m1656014992.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WLWLW",
"strDescription": "",
"intPlayed": "29",
"intWin": "10",
"intLoss": "11",
"intDraw": "8",
"intGoalsFor": "33",
"intGoalsAgainst": "35",
"intGoalDifference": "-2",
"intPoints": "38",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825975",
"intRank": "14",
"idTeam": "133619",
"strTeam": "Brighton and Hove Albion",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/ywypts1448810904.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LWWLL",
"strDescription": "",
"intPlayed": "29",
"intWin": "9",
"intLoss": "10",
"intDraw": "10",
"intGoalsFor": "38",
"intGoalsAgainst": "36",
"intGoalDifference": "2",
"intPoints": "37",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825976",
"intRank": "15",
"idTeam": "133635",
"strTeam": "Leeds United",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/jcgrml1756649030.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LLDDW",
"strDescription": "",
"intPlayed": "29",
"intWin": "7",
"intLoss": "12",
"intDraw": "10",
"intGoalsFor": "37",
"intGoalsAgainst": "48",
"intGoalDifference": "-11",
"intPoints": "31",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825977",
"intRank": "16",
"idTeam": "133616",
"strTeam": "Tottenham Hotspur",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/dfyfhl1604094109.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LLLLL",
"strDescription": "",
"intPlayed": "29",
"intWin": "7",
"intLoss": "14",
"intDraw": "8",
"intGoalsFor": "39",
"intGoalsAgainst": "46",
"intGoalDifference": "-7",
"intPoints": "29",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825978",
"intRank": "17",
"idTeam": "133720",
"strTeam": "Nottingham Forest",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/bk4qjs1546440351.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "DLLDL",
"strDescription": "",
"intPlayed": "29",
"intWin": "7",
"intLoss": "15",
"intDraw": "7",
"intGoalsFor": "28",
"intGoalsAgainst": "43",
"intGoalDifference": "-15",
"intPoints": "28",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825979",
"intRank": "18",
"idTeam": "133636",
"strTeam": "West Ham United",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/yutyxs1467459956.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WLDDW",
"strDescription": "Relegation - Championship",
"intPlayed": "29",
"intWin": "7",
"intLoss": "15",
"intDraw": "7",
"intGoalsFor": "35",
"intGoalsAgainst": "54",
"intGoalDifference": "-19",
"intPoints": "28",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825980",
"intRank": "19",
"idTeam": "133623",
"strTeam": "Burnley",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/ql7nl31686893820.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "LLDWL",
"strDescription": "Relegation - Championship",
"intPlayed": "29",
"intWin": "4",
"intLoss": "18",
"intDraw": "7",
"intGoalsFor": "32",
"intGoalsAgainst": "58",
"intGoalDifference": "-26",
"intPoints": "19",
"dateUpdated": "2026-03-10 07:00:37"
},
{
"idStanding": "8825981",
"intRank": "20",
"idTeam": "133599",
"strTeam": "Wolverhampton Wanderers",
"strBadge": "https://r2.thesportsdb.com/images/media/team/badge/u9qr031621593327.png/tiny",
"idLeague": "4328",
"strLeague": "English Premier League",
"strSeason": "2025-2026",
"strForm": "WWLDD",
"strDescription": "Relegation - Championship",
"intPlayed": "30",
"intWin": "3",
"intLoss": "20",
"intDraw": "7",
"intGoalsFor": "22",
"intGoalsAgainst": "52",
"intGoalDifference": "-30",
"intPoints": "16",
"dateUpdated": "2026-03-10 07:00:37"
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
{
"events": null
}

View File

@@ -0,0 +1,290 @@
{
"livescore": [
{
"idLiveScore": "29571878",
"idEvent": "2438107",
"strSport": "Soccer",
"idLeague": "4432",
"strLeague": "Uruguayan Primera Division",
"idHomeTeam": "136051",
"idAwayTeam": "136052",
"strHomeTeam": "Boston River",
"strAwayTeam": "Liverpool Montevideo",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/8zq9n91736218442.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/0xat6f1678717839.png",
"intHomeScore": "0",
"intAwayScore": "1",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "00:30",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 02:22:23"
},
{
"idLiveScore": "29573091",
"idEvent": "2404558",
"strSport": "Soccer",
"idLeague": "5075",
"strLeague": "Jamaican Premier League",
"idHomeTeam": "139116",
"idAwayTeam": "144757",
"strHomeTeam": "Waterhouse",
"strAwayTeam": "Montego Bay United",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/a3feu21582145219.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/xmp5y51726276300.png",
"intHomeScore": "1",
"intAwayScore": "0",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "00:30",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 02:31:23"
},
{
"idLiveScore": "29573226",
"idEvent": "2429251",
"strSport": "Soccer",
"idLeague": "4686",
"strLeague": "Ecuadorian Serie A",
"idHomeTeam": "138231",
"idAwayTeam": "140792",
"strHomeTeam": "Orense",
"strAwayTeam": "Manta",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/0pishb1734023740.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/mwnqej1614891525.png",
"intHomeScore": "1",
"intAwayScore": "1",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "00:30",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 02:32:23"
},
{
"idLiveScore": "29577082",
"idEvent": "2390578",
"strSport": "Soccer",
"idLeague": "4815",
"strLeague": "Costa-Rica Liga FPD",
"idHomeTeam": "140122",
"idAwayTeam": "139707",
"strHomeTeam": "Sporting San Jose",
"strAwayTeam": "P\u00e9rez Zeled\u00f3n",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/7wzxlo1606770023.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/rbsepr1589122379.png",
"intHomeScore": "3",
"intAwayScore": "2",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "01:00",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 03:01:24"
},
{
"idLiveScore": "29580120",
"idEvent": "2421648",
"strSport": "Soccer",
"idLeague": "4819",
"strLeague": "Panama Liga Panamena de Futbol",
"idHomeTeam": "139738",
"idAwayTeam": "145086",
"strHomeTeam": "Deportivo \u00c1rabe Unido",
"strAwayTeam": "Veraguas United",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/lkhkf11589297941.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/zwx9x51649354013.png",
"intHomeScore": "2",
"intAwayScore": "3",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "01:30",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 03:24:23"
},
{
"idLiveScore": "29580513",
"idEvent": "2414954",
"strSport": "Soccer",
"idLeague": "4497",
"strLeague": "Colombia Categor\u00eda Primera A",
"idHomeTeam": "137611",
"idAwayTeam": "137604",
"strHomeTeam": "Deportivo Pasto",
"strAwayTeam": "Am\u00e9rica de Cali",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/zlmyh31576159327.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/lwxcn71576156105.png",
"intHomeScore": "2",
"intAwayScore": "0",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "01:30",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 03:27:23"
},
{
"idLiveScore": "29580904",
"idEvent": "2422431",
"strSport": "Soccer",
"idLeague": "4688",
"strLeague": "Peruvian Primera Division",
"idHomeTeam": "138311",
"idAwayTeam": "138323",
"strHomeTeam": "Alianza Lima",
"strAwayTeam": "Melgar",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/y5wov41580040189.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/0puqm41765861529.png",
"intHomeScore": "3",
"intAwayScore": "1",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "01:30",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 03:30:23"
},
{
"idLiveScore": "29584000",
"idEvent": "2390575",
"strSport": "Soccer",
"idLeague": "4815",
"strLeague": "Costa-Rica Liga FPD",
"idHomeTeam": "147407",
"idAwayTeam": "139706",
"strHomeTeam": "Municipal Liberia",
"strAwayTeam": "Guadalupe",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/l26ju61687165399.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/vkt01e1606771982.png",
"intHomeScore": "1",
"intAwayScore": "0",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "02:00",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 03:54:23"
},
{
"idLiveScore": "29637910",
"idEvent": "2427095",
"strSport": "Soccer",
"idLeague": "5179",
"strLeague": "Asian Cup Women",
"idHomeTeam": "136814",
"idAwayTeam": "144951",
"strHomeTeam": "Japan Women",
"strAwayTeam": "Vietnam Women",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/jlrg6s1760929323.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/81kjun1642445873.png",
"intHomeScore": "4",
"intAwayScore": "0",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "09:00",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 10:52:23"
},
{
"idLiveScore": "29638966",
"idEvent": "2409781",
"strSport": "Soccer",
"idLeague": "5630",
"strLeague": "Myanmar National League",
"idHomeTeam": "151094",
"idAwayTeam": "139512",
"strHomeTeam": "Yadanarbon",
"strAwayTeam": "Yangon United",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/00bqi61739766200.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/4acusz1739764445.png",
"intHomeScore": "2",
"intAwayScore": "3",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "09:00",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 11:00:24"
},
{
"idLiveScore": "29638967",
"idEvent": "2427096",
"strSport": "Soccer",
"idLeague": "5179",
"strLeague": "Asian Cup Women",
"idHomeTeam": "144946",
"idAwayTeam": "144948",
"strHomeTeam": "India Women",
"strAwayTeam": "Chinese Taipei Women",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/outmnn1760929034.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/zhisgs1760929236.png",
"intHomeScore": "1",
"intAwayScore": "3",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "FT",
"strProgress": "90",
"strEventTime": "09:00",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 11:00:24"
},
{
"idLiveScore": "29645207",
"idEvent": "2285438",
"strSport": "Soccer",
"idLeague": "4676",
"strLeague": "Turkish 1 Lig",
"idHomeTeam": "138969",
"idAwayTeam": "138982",
"strHomeTeam": "Vanspor FK",
"strAwayTeam": "Adana Demirspor",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/imwcrs1679510336.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/hewt221626264325.png",
"intHomeScore": "1",
"intAwayScore": "0",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "2H",
"strProgress": "59",
"strEventTime": "10:30",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 11:48:23"
},
{
"idLiveScore": "29645208",
"idEvent": "2435699",
"strSport": "Soccer",
"idLeague": "4719",
"strLeague": "AFC Champions League Elite",
"idHomeTeam": "139889",
"idAwayTeam": "138108",
"strHomeTeam": "Machida Zelvia",
"strAwayTeam": "Gangwon FC",
"strHomeTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/99zl6k1590070905.png",
"strAwayTeamBadge": "https://r2.thesportsdb.com/images/media/team/badge/c4igx71579729617.png",
"intHomeScore": "1",
"intAwayScore": "0",
"intEventScore": null,
"intEventScoreTotal": null,
"strStatus": "2H",
"strProgress": "89",
"strEventTime": "10:00",
"dateEvent": "2026-03-10",
"updated": "2026-03-10 11:48:23"
}
]
}

View File

@@ -0,0 +1,403 @@
{
"lookup": [
{
"idLineup": "864558",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Defender",
"strPositionShort": "D",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "23",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/yv1fuq1766222423.png",
"idPlayer": "34145366",
"strPlayer": "Adedeji Oshilaja",
"idTeam": "134381",
"strTeam": "Mansfield",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864564",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Midfielder",
"strPositionShort": "M",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "3",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/wigvjk1766222238.png",
"idPlayer": "34148796",
"strPlayer": "Stephen McLaughlin",
"idTeam": "134381",
"strTeam": "Mansfield",
"strCountry": "Ireland",
"strSeason": "2025-2026"
},
{
"idLineup": "864573",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Left Wing",
"strPositionShort": "M",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "19",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/07fzpg1769331977.png",
"idPlayer": "34153358",
"strPlayer": "Leandro Trossard",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Belgium",
"strSeason": "2025-2026"
},
{
"idLineup": "864562",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Midfielder",
"strPositionShort": "M",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "25",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/ethhbe1766223156.png",
"idPlayer": "34159025",
"strPlayer": "Louis Reed",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864567",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Goalkeeper",
"strPositionShort": "G",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "13",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/mad9wz1768229982.png",
"idPlayer": "34160650",
"strPlayer": "Kepa Arrizabalaga",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Spain",
"strSeason": "2025-2026"
},
{
"idLineup": "864576",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Centre-Forward",
"strPositionShort": "F",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "9",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/rwsqi51769330458.png",
"idPlayer": "34160962",
"strPlayer": "Gabriel Jesus",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Brazil",
"strSeason": "2025-2026"
},
{
"idLineup": "864560",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Centre-Forward",
"strPositionShort": "M",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "7",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/04lwea1766222596.png",
"idPlayer": "34161076",
"strPlayer": "Lucas Akins",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "Grenada",
"strSeason": "2025-2026"
},
{
"idLineup": "864566",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Centre-Forward",
"strPositionShort": "F",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "29",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/2qalar1766222815.png",
"idPlayer": "34161977",
"strPlayer": "Tyler Roberts",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "Wales",
"strSeason": "2025-2026"
},
{
"idLineup": "864557",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Defender",
"strPositionShort": "D",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "2",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/u6shyg1766222184.png",
"idPlayer": "34161986",
"strPlayer": "Kyle Knoyle",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864556",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Goalkeeper",
"strPositionShort": "G",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "1",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/s39b3t1766222085.png",
"idPlayer": "34162006",
"strPlayer": "Liam Roberts",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864565",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Forward",
"strPositionShort": "F",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "18",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/rgjbgn1766222737.png",
"idPlayer": "34163692",
"strPlayer": "Rhys Oates",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864570",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Defensive Midfield",
"strPositionShort": "M",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "16",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/ia9o8w1769331494.png",
"idPlayer": "34164499",
"strPlayer": "Christian N\u00f8rgaard",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Denmark",
"strSeason": "2025-2026"
},
{
"idLineup": "864575",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Centre-Forward",
"strPositionShort": "F",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "29",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/hem4r91694204364.png",
"idPlayer": "34169226",
"strPlayer": "Kai Havertz",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Germany",
"strSeason": "2025-2026"
},
{
"idLineup": "864574",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Left Wing",
"strPositionShort": "M",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "11",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/pnwfls1769331152.png",
"idPlayer": "34169883",
"strPlayer": "Gabriel Martinelli",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Brazil",
"strSeason": "2025-2026"
},
{
"idLineup": "864571",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Right Winger",
"strPositionShort": "M",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "20",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/0x05ou1769330933.png",
"idPlayer": "34170520",
"strPlayer": "Noni Madueke",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864569",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Centre-Back",
"strPositionShort": "D",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "33",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/e6icgz1769330125.png",
"idPlayer": "34175834",
"strPlayer": "Riccardo Calafiori",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Italy",
"strSeason": "2025-2026"
},
{
"idLineup": "864561",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Midfielder",
"strPositionShort": "M",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "13",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/f6h02p1759864169.png",
"idPlayer": "34177947",
"strPlayer": "Jonathan Russell",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864568",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Centre-Back",
"strPositionShort": "D",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "3",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/mzik4g1769331540.png",
"idPlayer": "34194118",
"strPlayer": "Cristhian Mosquera",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "Spain",
"strSeason": "2025-2026"
},
{
"idLineup": "864563",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Defensive Midfield",
"strPositionShort": "M",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "40",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/f7bh071766405710.png",
"idPlayer": "34219277",
"strPlayer": "George Abbott",
"idTeam": "134382",
"strTeam": "Wycombe Wanderers",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864559",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Defender",
"strPositionShort": "D",
"strFormation": null,
"strHome": "Yes",
"strSubstitute": "No",
"intSquadNumber": "20",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/m84oa11766222366.png",
"idPlayer": "34222234",
"strPlayer": "Frazer Blake-Tracy",
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strCountry": "England",
"strSeason": "2025-2026"
},
{
"idLineup": "864572",
"idEvent": "2434724",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"strPosition": "Midfielder",
"strPositionShort": "M",
"strFormation": null,
"strHome": "No",
"strSubstitute": "No",
"intSquadNumber": "56",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/jzgrge1766827300.png",
"idPlayer": "34432407",
"strPlayer": "Max Dowman",
"idTeam": "133604",
"strTeam": "Arsenal",
"strCountry": "England",
"strSeason": "2025-2026"
}
]
}

View File

@@ -0,0 +1,166 @@
{
"lookup": [
{
"idStatistic": "526548",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Shots on Goal",
"intHome": "5",
"intAway": "8"
},
{
"idStatistic": "526549",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Shots off Goal",
"intHome": "9",
"intAway": "6"
},
{
"idStatistic": "526550",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Total Shots",
"intHome": "18",
"intAway": "19"
},
{
"idStatistic": "526551",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Blocked Shots",
"intHome": "4",
"intAway": "5"
},
{
"idStatistic": "526552",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Shots insidebox",
"intHome": "11",
"intAway": "17"
},
{
"idStatistic": "526553",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Shots outsidebox",
"intHome": "7",
"intAway": "2"
},
{
"idStatistic": "526554",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Fouls",
"intHome": "16",
"intAway": "6"
},
{
"idStatistic": "526555",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Corner Kicks",
"intHome": "4",
"intAway": "8"
},
{
"idStatistic": "526556",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Offsides",
"intHome": "1",
"intAway": "1"
},
{
"idStatistic": "526557",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Ball Possession",
"intHome": "33",
"intAway": "67"
},
{
"idStatistic": "526558",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Yellow Cards",
"intHome": "4",
"intAway": "2"
},
{
"idStatistic": "526559",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Red Cards",
"intHome": "0",
"intAway": "0"
},
{
"idStatistic": "526560",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Goalkeeper Saves",
"intHome": "5",
"intAway": "4"
},
{
"idStatistic": "526561",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Total passes",
"intHome": "260",
"intAway": "544"
},
{
"idStatistic": "526562",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Passes accurate",
"intHome": "191",
"intAway": "459"
},
{
"idStatistic": "526563",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "Passes %",
"intHome": "73",
"intAway": "84"
},
{
"idStatistic": "526564",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "expected_goals",
"intHome": "0",
"intAway": "0"
},
{
"idStatistic": "526565",
"idEvent": "2434724",
"idApiFootball": "1523412",
"strEvent": "Mansfield Town vs Arsenal",
"strStat": "goals_prevented",
"intHome": "0",
"intAway": "0"
}
]
}

View File

@@ -0,0 +1,382 @@
{
"lookup": [
{
"idTimeline": "1628218",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 1",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34153358",
"strPlayer": "Leandro Trossard",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/07fzpg1769331977.png",
"idAssist": "34182526",
"strAssist": "Piero Hincapi\u00e9",
"intTime": "38",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628219",
"idEvent": "2434724",
"strTimeline": "Goal",
"strTimelineDetail": "Normal Goal",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34170520",
"strPlayer": "Noni Madueke",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/0x05ou1769330933.png",
"idAssist": "34169883",
"strAssist": "Gabriel Martinelli",
"intTime": "41",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628220",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 1",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34161977",
"strPlayer": "Tyler Roberts",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/2qalar1766222815.png",
"idAssist": "34217628",
"strAssist": "Will Evans",
"intTime": "46",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628221",
"idEvent": "2434724",
"strTimeline": "Goal",
"strTimelineDetail": "Normal Goal",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34217628",
"strPlayer": "Will Evans",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/cf9nlh1766222631.png",
"idAssist": "0",
"strAssist": "",
"intTime": "50",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628222",
"idEvent": "2434724",
"strTimeline": "Card",
"strTimelineDetail": "Yellow Card",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34159025",
"strPlayer": "Louis Reed",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/ethhbe1766223156.png",
"idAssist": "0",
"strAssist": "",
"intTime": "60",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "Persistent fouling",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628223",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 2",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34219277",
"strPlayer": "George Abbott",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/f7bh071766405710.png",
"idAssist": "34217722",
"strAssist": "Aaron Lewis",
"intTime": "62",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628224",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 3",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34169226",
"strPlayer": "Kai Havertz",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/hem4r91694204364.png",
"idAssist": "34172278",
"strAssist": "Eberechi Eze",
"intTime": "62",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628225",
"idEvent": "2434724",
"strTimeline": "Goal",
"strTimelineDetail": "Normal Goal",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34172278",
"strPlayer": "Eberechi Eze",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/jcabmd1769330240.png",
"idAssist": "34164499",
"strAssist": "Christian N\u00f8rgaard",
"intTime": "66",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628226",
"idEvent": "2434724",
"strTimeline": "Card",
"strTimelineDetail": "Yellow Card",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34175834",
"strPlayer": "Riccardo Calafiori",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/e6icgz1769330125.png",
"idAssist": "0",
"strAssist": "",
"intTime": "70",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628227",
"idEvent": "2434724",
"strTimeline": "Card",
"strTimelineDetail": "Yellow Card",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34172278",
"strPlayer": "Eberechi Eze",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/jcabmd1769330240.png",
"idAssist": "0",
"strAssist": "",
"intTime": "74",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "Argument",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628228",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 4",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34175834",
"strPlayer": "Riccardo Calafiori",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/e6icgz1769330125.png",
"idAssist": "34252280",
"strAssist": "Jaden Dixon",
"intTime": "76",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628229",
"idEvent": "2434724",
"strTimeline": "Card",
"strTimelineDetail": "Yellow Card",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34161076",
"strPlayer": "Lucas Akins",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/04lwea1766222596.png",
"idAssist": "0",
"strAssist": "",
"intTime": "77",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "Foul",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628230",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 5",
"strHome": "No",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34432407",
"strPlayer": "Max Dowman",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/jzgrge1766827300.png",
"idAssist": "34169884",
"strAssist": "Bukayo Saka",
"intTime": "77",
"strPeriod": null,
"idTeam": "133604",
"strTeam": "Arsenal",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628231",
"idEvent": "2434724",
"strTimeline": "Card",
"strTimelineDetail": "Yellow Card",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34148796",
"strPlayer": "Stephen McLaughlin",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/wigvjk1766222238.png",
"idAssist": "0",
"strAssist": "",
"intTime": "80",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "Simulation",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628232",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 3",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34163692",
"strPlayer": "Rhys Oates",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/rgjbgn1766222737.png",
"idAssist": "34158800",
"strAssist": "Victor Adeboyejo",
"intTime": "81",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628233",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 4",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34148796",
"strPlayer": "Stephen McLaughlin",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/wigvjk1766222238.png",
"idAssist": "34301205",
"strAssist": "Oliver Irow",
"intTime": "81",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628234",
"idEvent": "2434724",
"strTimeline": "subst",
"strTimelineDetail": "Substitution 5",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34161986",
"strPlayer": "Kyle Knoyle",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/u6shyg1766222184.png",
"idAssist": "34147620",
"strAssist": "Elliott Hewitt",
"intTime": "87",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "NULL",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
},
{
"idTimeline": "1628235",
"idEvent": "2434724",
"strTimeline": "Card",
"strTimelineDetail": "Yellow Card",
"strHome": "Yes",
"strEvent": "Mansfield Town vs Arsenal",
"idAPIfootball": "1523412",
"idPlayer": "34217722",
"strPlayer": "Aaron Lewis",
"strCutout": "https://r2.thesportsdb.com/images/media/player/cutout/wwn1la1766222448.png",
"idAssist": "0",
"strAssist": "",
"intTime": "90",
"strPeriod": null,
"idTeam": "134381",
"strTeam": "Mansfield Town",
"strComment": "Foul",
"dateEvent": "2026-03-07",
"strSeason": "2025-2026"
}
]
}

View File

@@ -0,0 +1,260 @@
# Free API Live Football Data — API Reference
**Provider:** Sby Smart API (Creativesdev) on RapidAPI
**Base URL:** `https://free-api-live-football-data.p.rapidapi.com`
## Authentication
All requests require two headers:
```
x-rapidapi-host: free-api-live-football-data.p.rapidapi.com
x-rapidapi-key: YOUR_API_KEY
```
## Pricing
| Plan | Price | Requests/Month |
|------|-------|----------------|
| Basic (Free) | $0 | 100 |
| Pro | $9.99/mo | 20,000 |
| Ultra | $19.99/mo | 200,000 |
| Mega | $49.99/mo | 500,000 |
All plans include the same features/endpoints — only volume differs.
---
## Key IDs
- **leagueid** — League identifier (e.g., `42` for Premier League, `47` for another league). Use search or league list to find MLS ID.
- **teamid** — Team identifier (e.g., `8650`). Use team search or team list to find.
- **playerid** — Player identifier (e.g., `671529`). Use player search or squad list to find.
- **eventid** — Match/event identifier (e.g., `4621624`). Use fixtures or livescores to find.
---
## Endpoints
### Popular Leagues
```
GET /football-popular-leagues
```
### Countries
```
GET /football-get-all-countries
```
### Seasons
```
GET /football-league-all-seasons
```
---
### Livescores
```
GET /football-current-live
```
Returns all currently live matches worldwide with scores, status, and timing info.
---
### Fixtures
```
GET /football-get-matches-by-date?date={YYYYMMDD}
GET /football-get-matches-by-date-and-league?date={YYYYMMDD}
GET /football-get-all-matches-by-league?leagueid={leagueid}
```
---
### Leagues
```
GET /football-get-all-leagues
GET /football-get-all-leagues-with-countries
GET /football-get-league-detail?leagueid={leagueid}
GET /football-get-league-logo?leagueid={leagueid}
```
---
### Teams
```
GET /football-get-list-all-team?leagueid={leagueid}
GET /football-get-list-home-team?leagueid={leagueid}
GET /football-get-list-away-team?leagueid={leagueid}
GET /football-league-team?teamid={teamid}
GET /football-team-logo?teamid={teamid}
```
---
### Players / Athletes / Squad
```
GET /football-get-list-player?teamid={teamid}
GET /football-get-player-detail?playerid={playerid}
GET /football-get-player-logo?playerid={playerid}
```
---
### Events / Matches
```
GET /football-get-match-detail?eventid={eventid}
GET /football-get-match-score?eventid={eventid}
GET /football-get-match-status?eventid={eventid}
GET /football-get-match-highlights?eventid={eventid}
GET /football-get-match-location?eventid={eventid}
GET /football-get-match-all-stats?eventid={eventid}
GET /football-get-match-firstHalf-stats?eventid={eventid}
GET /football-get-match-secondhalf-stats?eventid={eventid}
GET /football-get-match-referee?eventid={eventid}
```
---
### Odds
```
GET /football-event-odds?eventid={eventid}&countrycode={CC}
GET /football-get-match-oddspoll?eventid={eventid}
GET /football-get-match-odds-voteresult?eventid={eventid}
```
---
### Statistics
```
GET /football-get-match-event-all-stats?eventid={eventid}
GET /football-get-match-event-firstHalf-stats?eventid={eventid}
GET /football-get-match-event-secondhalf-stats?eventid={eventid}
```
---
### Lineups
```
GET /football-get-hometeam-lineup?eventid={eventid}
GET /football-get-awayteam-lineup?eventid={eventid}
```
---
### Head to Head
```
GET /football-get-head-to-head?eventid={eventid}
```
---
### Standings
```
GET /football-get-standing-all?leagueid={leagueid}
GET /football-get-standing-home?leagueid={leagueid}
GET /football-get-standing-away?leagueid={leagueid}
```
---
### Rounds
```
GET /football-get-all-rounds?leagueid={leagueid}
GET /football-get-rounds-detail?roundid={roundid}
GET /football-get-rounds-players?leagueid={leagueid}
```
---
### Trophies
```
GET /football-get-trophies-all-seasons?leagueid={leagueid}
GET /football-get-trophies-detail?leagueid={leagueid}&season={season}
```
Season format example: `2023/2024` (URL-encoded as `2023%2F2024`)
---
### Top Players
```
GET /football-get-top-players-by-assists?leagueid={leagueid}
GET /football-get-top-players-by-goals?leagueid={leagueid}
GET /football-get-top-players-by-rating?leagueid={leagueid}
```
---
### Transfers
```
GET /football-get-all-transfers?page={page}
GET /football-get-top-transfers?page={page}
GET /football-get-market-value-transfers?page={page}
GET /football-get-league-transfers?leagueid={leagueid}
GET /football-get-team-contract-extension-transfers?teamid={teamid}
GET /football-get-team-players-in-transfers?teamid={teamid}
GET /football-get-team-players-out-transfers?teamid={teamid}
```
---
### News
```
GET /football-get-trendingnews
GET /football-get-league-news?leagueid={leagueid}&page={page}
GET /football-get-team-news?teamid={teamid}&page={page}
```
---
### Search
```
GET /football-all-search?search={query}
GET /football-teams-search?search={query}
GET /football-players-search?search={query}
GET /football-leagues-search?search={query}
GET /football-matches-search?search={query}
```
---
## Typical Workflow for MLS
1. **Find MLS league ID:** `GET /football-leagues-search?search=mls`
2. **Get Toronto FC team ID:** `GET /football-teams-search?search=toronto`
3. **Get upcoming fixtures:** `GET /football-get-all-matches-by-league?leagueid={MLS_ID}`
4. **Get live scores:** `GET /football-current-live` (filter for MLS matches)
5. **Get match details:** `GET /football-get-match-detail?eventid={eventid}`
6. **Get lineups:** `GET /football-get-hometeam-lineup?eventid={eventid}`
7. **Get match stats:** `GET /football-get-match-all-stats?eventid={eventid}`
8. **Get standings:** `GET /football-get-standing-all?leagueid={MLS_ID}`
---
## Notes
- Data appears to be sourced from FotMob based on field naming conventions and ID patterns.
- No external documentation exists — this reference was compiled from the RapidAPI playground.
- Date format for fixtures: `YYYYMMDD` (e.g., `20241107`)
- The "Statistics" category endpoints overlap with the match stats endpoints under "Events/Matches" — they may return the same or differently structured data. Test both.

View File

@@ -0,0 +1,385 @@
# TheSportsDB API V1 — Validated Reference
**Validated on:** 2026-03-09 using free test key `3`
**Base URL:** `https://www.thesportsdb.com/api/v1/json/{key}`
**Auth:** Key embedded in URL path (free test key: `3`, premium: same Patreon key as V2)
**Source samples:** `docs/api_samples/sportsdb/`
> **This document covers ONLY what was tested and confirmed with real API calls.**
> V2 endpoints are NOT covered — they require a premium key and have not been tested.
---
## Free Key Limitations
The free key (`3`) returns **limited results** (typically 5 rows for standings, returns wrong/random events for some lookups). The response **structure and field names** are valid regardless — only row count and relevance are affected.
---
## Validated IDs
| Entity | TheSportsDB ID | Notes |
|--------|----------------|-------|
| MLS | `4346` | Listed as "American Major League Soccer" |
| English Premier League | `4328` | "English Premier League" |
| Toronto FC | `134148` | Short: `TOR`, league: 4346 |
| Arsenal | `133604` | Short: `ARS`, league: 4328 |
| Federico Bernardeschi | `34148472` | Position: Winger |
---
## Endpoints
### 1. Search Teams
```
GET /searchteams.php?t={team_name}
```
**Tested with:** `Toronto FC`, `Arsenal`
**Response wrapper:** `{"teams": [...]}`
**Returns:** Array of matching team objects (1 exact match each)
#### Validated fields (all values are strings or null):
| Field | Example (TFC) | Example (Arsenal) | Notes |
|-------|---------------|-------------------|-------|
| `idTeam` | `"134148"` | `"133604"` | String, not int |
| `idESPN` | `null` | `"359"` | Nullable |
| `idAPIfootball` | `"1601"` | `"42"` | Cross-ref to API-Football |
| `intLoved` | `"2"` | `"9"` | String of int |
| `strTeam` | `"Toronto FC"` | `"Arsenal"` | Full name |
| `strTeamAlternate` | `""` | `"Arsenal Football Club, AFC, Arsenal FC"` | Comma-separated aliases |
| `strTeamShort` | `"TOR"` | `"ARS"` | 3-letter code |
| `intFormedYear` | `"2006"` | `"1892"` | String of int |
| `strSport` | `"Soccer"` | `"Soccer"` | |
| `strLeague` | `"American Major League Soccer"` | `"English Premier League"` | Primary league |
| `idLeague` | `"4346"` | `"4328"` | String of int |
| `strLeague2`..`strLeague7` | `"Leagues Cup"` | `"FA Cup"`, `"EFL Cup"`, etc. | Additional leagues/cups |
| `idLeague2`..`idLeague7` | `"5281"` | `"4482"`, `"4570"`, etc. | IDs for above |
| `strDivision` | `null` | `null` | |
| `idVenue` | `"16782"` | `"15528"` | String of int |
| `strStadium` | `"BMO Field"` | `"Emirates Stadium"` | |
| `strKeywords` | `""` | `"Gunners, Gooners"` | Nicknames |
| `strRSS` | `""` | `""` | |
| `strLocation` | `"Toronto, Ontario"` | `"Holloway, London, England"` | |
| `intStadiumCapacity` | `"30991"` | `"60338"` | String of int |
| `strWebsite` | `"www.torontofc.ca"` | `"www.arsenal.com"` | No protocol |
| `strFacebook` | `"www.facebook.com/torontofc"` | `"www.facebook.com/Arsenal"` | |
| `strTwitter` | `"twitter.com/torontofc"` | `"twitter.com/arsenal"` | |
| `strInstagram` | `"www.instagram.com/torontofc"` | `"instagram.com/arsenal"` | |
| `strDescriptionEN` | Full paragraph | Full paragraph | English bio, `\r\n` separated |
| `strDescriptionDE`..`strDescriptionPL` | `null` or text | `null` or text | 14 language variants |
| `strColour1` | `"#B81137"` | `"#EF0107"` | Hex color |
| `strColour2` | `"#455560"` | `"#023474"` | |
| `strColour3` | `""` | `"#9C824A"` | May be empty |
| `strGender` | `"Male"` | `"Male"` | |
| `strCountry` | `"Canada"` | `"England"` | |
| `strBadge` | URL | URL | PNG badge |
| `strLogo` | URL | URL | PNG logo |
| `strFanart1`..`strFanart4` | URL or null | URL or null | Fan art images |
| `strBanner` | URL | URL | Banner image |
| `strEquipment` | URL | URL | Kit/equipment image |
| `strYoutube` | `"www.youtube.com/torontofc"` | `"www.youtube.com/ArsenalTour"` | |
| `strLocked` | `"unlocked"` | `"unlocked"` | |
**Key observations:**
- ALL numeric values are returned as **strings** (e.g., `"134148"` not `134148`)
- Team can belong to up to 7 leagues/cups
- `idAPIfootball` provides cross-reference to API-Football IDs
---
### 2. Lookup Team by ID
```
GET /lookupteam.php?id={idTeam}
```
**Tested with:** `134148` (Toronto FC) — but free key returned Arsenal instead (known free-key bug)
**Response wrapper:** `{"teams": [...]}`
**Fields:** Identical to search response above.
**⚠ Free key pitfall:** Lookup by ID returned the wrong team (Arsenal instead of TFC). This is a free-key limitation, not a field-structure issue. The response shape is the same as search.
---
### 3. Previous Team Events (Last Results)
```
GET /eventslast.php?id={idTeam}
```
**Tested with:** `134148` (Toronto FC)
**Response wrapper:** `{"results": [...]}` ← Note: key is `results`, NOT `events`
**Returns:** Last 5 results for the team (free key returned 1)
#### Validated event fields:
| Field | Example | Notes |
|-------|---------|-------|
| `idEvent` | `"2425216"` | String |
| `idAPIfootball` | `"1514800"` | Cross-ref, nullable |
| `strEvent` | `"Toronto FC vs Polissya Zhytomyr"` | Match title |
| `strEventAlternate` | `"Polissya Zhytomyr @ Toronto FC"` | Away @ Home format |
| `strFilename` | `"Club Friendlies 2026-02-14 Toronto FC vs Polissya Zhytomyr"` | League + date + teams |
| `strSport` | `"Soccer"` | |
| `idLeague` | `"4569"` | String — league of this match |
| `strLeague` | `"Club Friendlies"` | |
| `strLeagueBadge` | URL | League badge |
| `strSeason` | `"2026"` | or `"2025-2026"` for Euro leagues |
| `strDescriptionEN` | `""` | Match description (usually empty) |
| `strHomeTeam` | `"Toronto FC"` | |
| `strAwayTeam` | `"Polissya Zhytomyr"` | |
| `intHomeScore` | `"2"` | String of int, null if not played |
| `intAwayScore` | `"1"` | String of int, null if not played |
| `intRound` | `"0"` | String, `"0"` for friendlies |
| `intSpectators` | `null` | Nullable |
| `strOfficial` | `""` | Referee, often empty |
| `strTimestamp` | `"2026-02-14T09:45:00"` | ISO-ish, no timezone |
| `dateEvent` | `"2026-02-14"` | YYYY-MM-DD |
| `dateEventLocal` | `"2026-02-14"` | Local date |
| `strTime` | `"09:45:00"` | UTC time |
| `strTimeLocal` | `"04:45:00"` | Local time |
| `strGroup` | `""` | Group stage name, nullable |
| `idHomeTeam` | `"134148"` | String |
| `strHomeTeamBadge` | URL | Team badge |
| `idAwayTeam` | `"140180"` | String |
| `strAwayTeamBadge` | URL | Team badge |
| `intScore` | `null` | Aggregate/extra score? Always null in sample |
| `intScoreVotes` | `null` | Always null |
| `strResult` | `""` | Text result summary, empty |
| `idVenue` | `"16782"` | String |
| `strVenue` | `"BMO Field"` | |
| `strCountry` | `"Canada"` | |
| `strCity` | `""` | Often empty |
| `strPoster` | `""` | Image URLs, often empty |
| `strSquare` | `""` | |
| `strFanart` | `null` | |
| `strThumb` | `""` | |
| `strBanner` | `""` | |
| `strMap` | `null` | |
| `strTweet1` | `""` | |
| `strVideo` | `""` | Highlight video URL |
| `strStatus` | `"Match Finished"` | See status values below |
| `strPostponed` | `"no"` | `"no"` or `"yes"` |
| `strLocked` | `"unlocked"` | |
**Known `strStatus` values:** `"Match Finished"`, `"Not Started"`, `null` (old events)
---
### 4. Next Team Events (Upcoming)
```
GET /eventsnext.php?id={idTeam}
```
**Tested with:** `134148` (Toronto FC)
**Response wrapper:** `{"events": [...]}` ← Note: key is `events` (different from `eventslast`)
**Returns:** Next 515 upcoming events
**Fields:** Identical to the event structure in section 3 above, except:
- `intHomeScore` / `intAwayScore` = `null` (not yet played)
- `strStatus` = `"Not Started"`
- `dateEventLocal` / `strTimeLocal` may be `null`
- `strThumb` may have a URL (match preview image)
**⚠ Free key pitfall:** Free key returned Bolton Wanderers fixtures instead of Toronto FC fixtures. The event field structure is still valid.
---
### 5. Event Lookup by ID
```
GET /lookupevent.php?id={idEvent}
```
**Tested with:** `2425216`
**Response wrapper:** `{"events": [...]}`
**Returns:** Single event in array
**Fields:** Same event structure as section 3. All fields present.
**⚠ Free key pitfall:** Free key returned a completely different event (Liverpool vs Swansea 2014 instead of TFC vs Polissya 2026). The field structure is still valid — the values are just wrong.
---
### 6. Search Players
```
GET /searchplayers.php?p={player_name}
```
**Tested with:** `Bernardeschi`
**Response wrapper:** `{"player": [...]}` ← Note: key is `player`, singular
**Returns:** Array of matching players
#### Validated fields:
| Field | Example | Notes |
|-------|---------|-------|
| `idPlayer` | `"34148472"` | String |
| `idTeam` | `"134781"` | String |
| `strPlayer` | `"Federico Bernardeschi"` | Full name |
| `strTeam` | `"Bologna"` | Current team (may not match Toronto FC if transferred) |
| `strSport` | `"Soccer"` | |
| `strThumb` | URL | Player thumbnail |
| `strCutout` | URL | Player cutout image |
| `strNationality` | `"Italy"` | |
| `dateBorn` | `"1994-02-16"` | YYYY-MM-DD |
| `strStatus` | `"Active"` | `"Active"` or `"Retired"` |
| `strGender` | `"Male"` | |
| `strPosition` | `"Winger"` | |
| `relevance` | `"28.93..."` | Float as string, search relevance score |
**Note:** V1 player search returns a **minimal** field set (13 fields). V2 `lookup/player/{id}` is expected to return full bio, height, weight, description, etc. — **not yet tested**.
---
### 7. Standings / League Table
```
GET /lookuptable.php?l={idLeague}&s={season}
```
**Tested with:** `l=4328&s=2025-2026` (EPL), `l=4346&s=2026` (MLS)
**Response wrapper:** `{"table": [...]}`
**Returns:** Standings rows (free key: 5 rows; premium: full table)
#### Validated fields:
| Field | Example (EPL) | Example (MLS) | Notes |
|-------|---------------|---------------|-------|
| `idStanding` | `"8793837"` | `"8790287"` | String |
| `intRank` | `"1"` | `"1"` | String of int |
| `idTeam` | `"133604"` | `"150261"` | String |
| `strTeam` | `"Arsenal"` | `"San Diego FC"` | |
| `strBadge` | URL `/tiny` | URL `/tiny` | Tiny badge variant |
| `idLeague` | `"4328"` | `"4346"` | String |
| `strLeague` | `"English Premier League"` | `"American Major League Soccer"` | |
| `strSeason` | `"2025-2026"` | `"2026"` | Euro vs US format |
| `strForm` | `"WWWDD"` | `"WWW"` | Recent results W/D/L |
| `strDescription` | `"Promotion - Champions League..."` | `"Promotion - MLS (Play Offs...)"` | Qualification zone |
| `intPlayed` | `"30"` | `"3"` | String of int |
| `intWin` | `"20"` | `"3"` | |
| `intLoss` | `"3"` | `"0"` | |
| `intDraw` | `"7"` | `"0"` | |
| `intGoalsFor` | `"59"` | `"8"` | |
| `intGoalsAgainst` | `"22"` | `"0"` | |
| `intGoalDifference` | `"37"` | `"8"` | |
| `intPoints` | `"67"` | `"9"` | |
| `dateUpdated` | `"2026-03-09 06:01:21"` | `"2026-03-09 06:00:50"` | Last update timestamp |
**Season format:**
- European leagues: `"2025-2026"`
- MLS / calendar-year leagues: `"2026"`
**MLS standings note:** MLS has two conferences (East & West). The API returns **both mixed together** — ranks restart at `1` for each conference. There is no explicit conference field. You must use the rank patterns or `strDescription` to infer conference grouping.
---
### 8. Events by Date
```
GET /eventsday.php?d={YYYY-MM-DD}&s=Soccer # all soccer events
GET /eventsday.php?d={YYYY-MM-DD}&l={idLeague} # filtered by league
```
**Tested with:** `d=2026-03-09&s=Soccer`, `d=2026-03-09&l=4346`
**Response wrapper:** `{"events": [...]}` or `{"events": null}`
**Fields:** Same event structure as section 3.
**Results:**
- Global soccer on 2026-03-09: Returned **1 event** (Melbourne Victory vs Western Sydney, from 2014) — free key returns stale/random data
- MLS on 2026-03-09: Returned `{"events": null}` — no matches or free key limitation
**⚠ Free key:** Events-by-date is unreliable on free key. The field structure matches section 3 but the actual events returned are wrong/old. Premium key should return correct current-day events.
---
## Response Patterns Summary
| Endpoint | Wrapper Key | Null when empty |
|----------|-------------|-----------------|
| `searchteams.php` | `teams` | Unknown (always had results) |
| `lookupteam.php` | `teams` | Unknown |
| `eventslast.php` | `results` | Unknown |
| `eventsnext.php` | `events` | Unknown |
| `lookupevent.php` | `events` | Unknown |
| `searchplayers.php` | `player` | Unknown |
| `lookuptable.php` | `table` | Unknown |
| `eventsday.php` | `events` | **Yes** — returns `{"events": null}` |
---
## Data Type Warning
**All numeric fields are returned as strings.** Every `id*`, `int*`, and numeric value must be cast to `int` before use:
```python
team_id = int(team["idTeam"]) # "134148" → 134148
capacity = int(team["intStadiumCapacity"]) # "30991" → 30991
```
---
## V1 Endpoints NOT Tested
These V1 endpoints exist in the API but were not called during discovery:
| Endpoint | Purpose |
|----------|---------|
| `searchevents.php` | Search events by name |
| `eventsnextleague.php` | Next events for a league |
| `eventspastleague.php` | Previous events for a league |
| `eventsseason.php` | All events in a season |
| `lookup_all_players.php` | Full squad (V1 version) |
| `lookupplayer.php` | Detailed player profile |
| `all_sports.php` | All sports (used only for health check) |
| `all_leagues.php` | All leagues |
| `search_all_leagues.php` | Search leagues |
---
## V2 Endpoints — NOT TESTED (Premium Key Required)
These are the V2 endpoints built into `nike/sportsdb.py` but **entirely untested**. All field names in the code are guessed from TheSportsDB OpenAPI docs and have NOT been validated against real responses.
| Endpoint | Guessed wrapper key | What we don't know |
|----------|--------------------|--------------------|
| `GET /search/league/{name}` | `search` | Actual field names |
| `GET /search/team/{name}` | `search` | May differ from V1 |
| `GET /search/player/{name}` | `search` | May differ from V1 |
| `GET /lookup/event/{id}` | `lookup` or `events` | May have extra fields vs V1 |
| `GET /lookup/event_stats/{id}` | `lookup` | Field names like `strStat`, `intHome`, `intAway` are **guesses** |
| `GET /lookup/event_timeline/{id}` | `lookup` | Field names like `intTime`, `strTimeline`, `strPlayer` are **guesses** |
| `GET /lookup/event_lineup/{id}` | `lookup` | Fields like `intSquadNumber`, `strSubstitute`, `strHome` are **guesses** |
| `GET /lookup/player/{id}` | `lookup` or `players` | May have `strHeight`, `strWeight`, `strDescriptionEN` |
| `GET /lookup/player_contracts/{id}` | Unknown | Unknown |
| `GET /lookup/player_honours/{id}` | Unknown | Unknown |
| `GET /lookup/event_tv/{id}` | Unknown | Unknown |
| `GET /lookup/venue/{id}` | Unknown | Unknown |
| `GET /list/teams/{leagueId}` | `list` | Unknown |
| `GET /list/seasons/{leagueId}` | `list` | Unknown |
| `GET /list/players/{teamId}` | `list` | Field names for squad members unknown |
| `GET /schedule/next/league/{id}` | Unknown | Unknown |
| `GET /schedule/previous/league/{id}` | Unknown | Unknown |
| `GET /schedule/next/team/{id}` | Unknown | Unknown |
| `GET /schedule/previous/team/{id}` | Unknown | May match V1 `eventsnext` or differ |
| `GET /schedule/full/team/{id}` | Unknown | Unknown |
| `GET /schedule/league/{id}/{season}` | Unknown | Unknown |
| `GET /livescore/soccer` | `livescore` | All field names **guessed** |
---
## Next Steps (After Getting Premium Key)
1. Set `SPORTSDB_KEY=your_key` in `.env`
2. Run `python scripts/discover_sportsdb.py` — it auto-detects premium and tests all V2 endpoints
3. Review saved JSON files in `docs/api_samples/sportsdb/`
4. Update this document with V2 validated fields
5. Fix `sportsdb.py` client if any URL paths or wrapper keys are wrong
6. Design `schema.sql` from real field names (current schema is drafted from guesses)
7. Fix `server.py` tool formatters to use actual field names

View File

@@ -0,0 +1,308 @@
# TheSportsDB API V2 — Validated Field Reference
> **All fields below are validated against real API responses** from discovery
> runs with a premium key ($9/mo Patreon). Last validated: 2026-03-10.
## Authentication
- **V2**: `X-API-KEY` header
- **V1**: Key in URL path: `/api/v1/json/{key}/...`
- Same premium key works for both
## V2 Base URL
```
https://www.thesportsdb.com/api/v2/json
```
---
## Endpoints & Response Schemas
### Search Leagues — `/search/league/{name}`
Wrapper key: `search`
| Field | Type | Example |
|-------|------|---------|
| `idLeague` | string | `"5279"` |
| `strLeague` | string | `"MLS Next Pro"` |
| `strSport` | string | `"Soccer"` |
| `strBadge` | string/null | URL |
| `strCountry` | string | `"United States"` |
| `strCurrentSeason` | string | `"2026"` |
| `strGender` | string | `"Male"` |
> ⚠️ V2 league search is unreliable — "MLS" returns "MLS Next Pro" (ID 5279),
> not "American Major League Soccer" (ID 4346). Use V1 team search + `idLeague`
> field as the authoritative source for league IDs.
---
### Schedule (Next/Previous) — `/schedule/{next|previous}/team/{teamId}`
Wrapper key: `schedule`
| Field | Type | Example |
|-------|------|---------|
| `idEvent` | string | `"2406751"` |
| `strEvent` | string | `"FC Cincinnati vs Toronto FC"` |
| `idLeague` | string | `"4346"` |
| `strLeague` | string | `"American Major League Soccer"` |
| `strSport` | string | `"Soccer"` |
| `strHomeTeam` | string | `"FC Cincinnati"` |
| `strAwayTeam` | string | `"Toronto FC"` |
| `idHomeTeam` | string | `"136688"` |
| `idAwayTeam` | string | `"134148"` |
| `intRound` | string | `"3"` |
| `intHomeScore` | string/null | `"0"` |
| `intAwayScore` | string/null | `"1"` |
| `strTimestamp` | string | `"2026-03-08T23:00:00"` |
| `dateEvent` | string | `"2026-03-08"` |
| `dateEventLocal` | string | `"2026-03-08"` |
| `strTime` | string | `"23:00:00"` |
| `strTimeLocal` | string | `"18:00:00"` |
| `strHomeTeamBadge` | string | URL |
| `strAwayTeamBadge` | string | URL |
| `strVenue` | string | `"TQL Stadium"` |
| `strCountry` | string | `"United States"` |
| `strThumb` | string/null | URL |
| `strPoster` | string/null | URL |
| `strVideo` | string/null | YouTube URL |
| `strPostponed` | string | `"no"` |
| `strFilename` | string | descriptive filename |
| `strStatus` | string | `"Match Finished"` / `"Not Started"` |
---
### Event Lookup — `/lookup/event/{eventId}`
Wrapper key: `lookup`
All schedule fields plus:
| Field | Type | Example |
|-------|------|---------|
| `idAPIfootball` | string | `"1490149"` |
| `strEventAlternate` | string | `"Toronto FC @ FC Cincinnati"` |
| `strFilename` | string | descriptive |
| `strLeagueBadge` | string | URL |
| `strSeason` | string | `"2026"` |
| `strDescriptionEN` | string | `""` |
| `intSpectators` | null/string | `null` |
| `strOfficial` | string | referee name |
| `strGroup` | string | `""` |
| `intScore` | null | |
| `intScoreVotes` | null | |
| `strResult` | string | `""` |
| `idVenue` | string | `"20820"` |
| `strCity` | string | `""` |
| `strSquare` | string/null | URL |
| `strFanart` | string/null | URL |
| `strBanner` | string/null | URL |
| `strMap` | string/null | |
| `strTweet1` | string | `""` |
| `strLocked` | string | `"unlocked"` |
---
### Event Stats — `/lookup/event_stats/{eventId}`
Wrapper key: `lookup`
| Field | Type | Example |
|-------|------|---------|
| `idStatistic` | string | `"526548"` |
| `idEvent` | string | `"2434724"` |
| `idApiFootball` | string | `"1523412"` |
| `strEvent` | string | `"Mansfield Town vs Arsenal"` |
| `strStat` | string | `"Shots on Goal"` |
| `intHome` | string | `"5"` |
| `intAway` | string | `"8"` |
Known `strStat` values: `Shots on Goal`, `Shots off Goal`, `Total Shots`,
`Blocked Shots`, `Shots insidebox`, `Shots outsidebox`, `Fouls`,
`Corner Kicks`, `Offsides`, `Ball Possession`, `Yellow Cards`, `Red Cards`,
`Goalkeeper Saves`, `Total passes`, `Passes accurate`, `Passes %`,
`expected_goals`, `goals_prevented`.
> ⚠️ Not all matches have stats. MLS and smaller leagues may return
> `{"Message": "No data found"}`.
---
### Event Timeline — `/lookup/event_timeline/{eventId}`
Wrapper key: `lookup`
| Field | Type | Example |
|-------|------|---------|
| `idTimeline` | string | `"1628218"` |
| `idEvent` | string | `"2434724"` |
| `strTimeline` | string | `"subst"` / `"Goal"` / `"Yellow Card"` |
| `strTimelineDetail` | string | `"Substitution 1"` / `"Normal Goal"` |
| `strHome` | string | `"Yes"` / `"No"` |
| `strEvent` | string | `"Mansfield Town vs Arsenal"` |
| `idAPIfootball` | string | `"1523412"` |
| `idPlayer` | string | `"34153358"` |
| `strPlayer` | string | `"Leandro Trossard"` |
| `strCutout` | string/null | URL |
| `idAssist` | string/null | `"34182526"` |
| `strAssist` | string/null | `"Piero Hincapié"` |
| `intTime` | string | `"38"` |
| `strPeriod` | string/null | |
| `idTeam` | string | `"133604"` |
| `strTeam` | string | `"Arsenal"` |
| `strComment` | string | `"NULL"` (literal string, not null) |
| `dateEvent` | string | `"2026-03-07"` |
| `strSeason` | string | `"2025-2026"` |
> ⚠️ `strComment` can be the literal string `"NULL"` — treat it as null.
---
### Event Lineup — `/lookup/event_lineup/{eventId}`
Wrapper key: `lookup`
| Field | Type | Example |
|-------|------|---------|
| `idLineup` | string | `"864558"` |
| `idEvent` | string | `"2434724"` |
| `strEvent` | string | `"Mansfield Town vs Arsenal"` |
| `idAPIfootball` | string | `"1523412"` |
| `strPosition` | string | `"Defender"` |
| `strPositionShort` | string | `"D"` / `"G"` / `"M"` / `"F"` |
| `strFormation` | string/null | |
| `strHome` | string | `"Yes"` / `"No"` |
| `strSubstitute` | string | `"Yes"` / `"No"` |
| `intSquadNumber` | string | `"23"` |
| `strCutout` | string/null | URL |
| `idPlayer` | string | `"34145366"` |
| `strPlayer` | string | `"Adedeji Oshilaja"` |
| `idTeam` | string | `"134381"` |
| `strTeam` | string | `"Mansfield"` |
| `strCountry` | string | `"England"` |
| `strSeason` | string | `"2025-2026"` |
---
### Squad (Roster) — `/list/players/{teamId}`
Wrapper key: `list`
| Field | Type | Example |
|-------|------|---------|
| `idPlayer` | string | `"34146036"` |
| `strPlayer` | string | `"Kevin Long"` |
| `idTeam` | string | `"134148"` |
| `strTeam` | string | `"Toronto FC"` |
| `strThumb` | string/null | URL |
| `strCutout` | string/null | URL |
| `strRender` | string/null | URL |
| `dateBorn` | string | `"1990-08-18"` |
| `strPosition` | string | `"Centre-Back"` |
> ⚠️ Squad list does NOT include `strNumber`, `strNationality`, `strHeight`,
> `strWeight`. Use `/lookup/player/{id}` for the full profile.
---
### Player Detail — `/lookup/player/{playerId}`
Wrapper key: `lookup`
| Field | Type | Example |
|-------|------|---------|
| `idPlayer` | string | `"34146036"` |
| `idTeam` | string | `"134148"` |
| `idTeam2` | string | `"0"` |
| `idTeamNational` | string/null | |
| `idAPIfootball` | string | `"18915"` |
| `idPlayerManager` | string/null | |
| `idWikidata` | string | `"Q6396794"` |
| `idTransferMkt` | string | `"111114"` |
| `idESPN` | string | `"140531"` |
| `strNationality` | string | `"Ireland"` |
| `strPlayer` | string | `"Kevin Long"` |
| `strPlayerAlternate` | string | `""` |
| `strTeam` | string | `"Toronto FC"` |
| `strTeam2` | string | `""` |
| `strSport` | string | `"Soccer"` |
| `dateBorn` | string | `"1990-08-18"` |
| `dateDied` | null/string | |
| `strNumber` | string | `"5"` |
| `dateSigned` | string | `"2010-07-01"` |
| `strSigning` | string | |
| `strWage` | string | |
| `strBirthLocation` | string | `"Cork, Ireland"` |
| `strStatus` | string | `"Active"` |
| `strDescriptionEN` | string | biography |
| `strGender` | string | `"Male"` |
| `strSide` | string | `""` |
| `strPosition` | string | `"Centre-Back"` |
| `strHeight` | string | `"188 cm"` |
| `strWeight` | string | `"183 lbs"` |
| `intLoved` | string | `"0"` |
| `strThumb` | string/null | URL |
| `strCutout` | string/null | URL |
| `strRender` | string/null | URL |
| `strBanner` | string/null | URL |
| `strLastName` | string | `"Long"` |
---
### Livescores — `/livescore/soccer`
Wrapper key: `livescore`
| Field | Type | Example |
|-------|------|---------|
| `idLiveScore` | string | `"29571878"` |
| `idEvent` | string | `"2438107"` |
| `strSport` | string | `"Soccer"` |
| `idLeague` | string | `"4432"` |
| `strLeague` | string | `"Uruguayan Primera Division"` |
| `idHomeTeam` | string | `"136051"` |
| `idAwayTeam` | string | `"136052"` |
| `strHomeTeam` | string | `"Boston River"` |
| `strAwayTeam` | string | `"Liverpool Montevideo"` |
| `strHomeTeamBadge` | string | URL |
| `strAwayTeamBadge` | string | URL |
| `intHomeScore` | string | `"0"` |
| `intAwayScore` | string | `"1"` |
| `intEventScore` | null | |
| `intEventScoreTotal` | null | |
| `strStatus` | string | `"FT"` / `"1H"` / `"2H"` / `"HT"` |
| `strProgress` | string | `"90"` |
| `strEventTime` | string | `"00:30"` |
| `dateEvent` | string | `"2026-03-10"` |
| `updated` | string | `"2026-03-10 02:22:23"` |
---
## V1 Endpoints (Supplementary)
See [sportsdb_api_v1_reference.md](sportsdb_api_v1_reference.md) for full V1
validated fields. Key V1-only endpoints:
| Endpoint | Wrapper Key | Purpose |
|----------|-------------|---------|
| `/lookuptable.php?l=&s=` | `table` | League standings |
| `/eventsday.php?d=&s=` | `events` | Events by date |
| `/eventslast.php?id=` | `results` | Team's last 5 results |
| `/eventsnext.php?id=` | `events` | Team's next 5 fixtures |
| `/searchteams.php?t=` | `teams` | Team search |
| `/searchplayers.php?p=` | `player` | Player search |
---
## Important Notes
1. **All numeric values are strings** — always cast with `int()` or handle `None`
2. **All IDs are strings** — e.g. `"134148"` not `134148`
3. **`strComment` can be literal `"NULL"`** — not JSON null
4. **`strHome`/`strSubstitute` are `"Yes"`/`"No"`** — not booleans
5. **Wrapper keys differ by endpoint**`search`, `lookup`, `list`, `schedule`, `livescore`, `table`
6. **Not all matches have stats/lineup/timeline** — smaller leagues return `{"Message": "No data found"}`
7. **League search is unreliable on V2** — use V1 team search to discover league IDs
## Verified IDs
| Entity | Name | TheSportsDB ID |
|--------|------|----------------|
| League | American Major League Soccer | 4346 |
| League | English Premier League | 4328 |
| Team | Toronto FC | 134148 |
| Team | Arsenal | 133604 |

17
nike.service Normal file
View File

@@ -0,0 +1,17 @@
[Unit]
Description=Nike Football Data Platform (MCP + Dashboard)
After=network.target
[Service]
Type=simple
User=robert
WorkingDirectory=/home/robert/gitea/nike
ExecStart=/home/robert/gitea/nike/venv/bin/python run.py
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nike
[Install]
WantedBy=multi-user.target

BIN
nike/.DS_Store vendored Normal file

Binary file not shown.

1
nike/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Nike football data platform."""

199
nike/api_football.py Normal file
View File

@@ -0,0 +1,199 @@
"""
API-Football v3 client (api-sports.io).
Docs: https://www.api-football.com/documentation-v3
Base: https://v3.football.api-sports.io
Auth: x-apisports-key header
All v3 responses follow: {"response": [...], "results": N, "paging": {...}}
"""
import time
import requests
from nike import config
_HEADERS = {
"x-apisports-key": config.API_FOOTBALL_KEY,
}
_last_remaining: int | None = None
# ── Internal HTTP helper ──────────────────────────────────
def _get(endpoint: str, params: dict | None = None, timeout: int = 15) -> dict:
"""GET {BASE}/{endpoint}?{params}. Raises on non-2xx."""
global _last_remaining
url = f"{config.API_FOOTBALL_BASE}/{endpoint}"
resp = requests.get(url, headers=_HEADERS, params=params, timeout=timeout)
resp.raise_for_status()
_last_remaining = resp.headers.get("x-ratelimit-requests-remaining")
data = resp.json()
# v3 reports errors in the body, not via HTTP status
errors = data.get("errors")
if errors and isinstance(errors, dict) and errors:
raise RuntimeError(f"API-Football error: {errors}")
return data
def _response(data: dict) -> list:
"""Extract the 'response' list from a v3 envelope."""
r = data.get("response", [])
return r if isinstance(r, list) else []
# ── Connectivity ──────────────────────────────────────────
def check_connection() -> dict:
"""Quick connectivity + quota probe via /status."""
try:
t0 = time.time()
data = _get("status", timeout=8)
latency_ms = round((time.time() - t0) * 1000, 1)
acct = data.get("response", {}).get("requests", {})
remaining = acct.get("limit_day", 100) - acct.get("current", 0)
return {
"connected": True,
"latency_ms": latency_ms,
"quota_remaining": remaining,
"quota_limit": acct.get("limit_day", 100),
}
except Exception as e:
return {"connected": False, "latency_ms": None,
"quota_remaining": None, "quota_limit": 100, "error": str(e)}
# ── Teams ─────────────────────────────────────────────────
def search_teams(name: str) -> list[dict]:
"""
GET /teams?search=<name>
Response items: {"team": {...}, "venue": {...}}
"""
data = _get("teams", {"search": name})
return _response(data)
def get_teams_by_league(league_id: int, season: int = 2025) -> list[dict]:
"""
GET /teams?league=<id>&season=<year>
"""
data = _get("teams", {"league": league_id, "season": season})
return _response(data)
# ── Squad / Players ───────────────────────────────────────
def get_squad(team_id: int) -> list[dict]:
"""
GET /players/squads?team=<id>
Response: [{"team": {...}, "players": [{id, name, number, position, photo}, ...]}]
Returns flat list of player dicts.
"""
data = _get("players/squads", {"team": team_id})
rows = _response(data)
if not rows:
return []
# First item contains the squad
return rows[0].get("players", [])
def get_player_detail(player_id: int, season: int = 2025) -> dict:
"""
GET /players?id=<id>&season=<year>
"""
data = _get("players", {"id": player_id, "season": season})
rows = _response(data)
return rows[0] if rows else {}
# ── Fixtures ──────────────────────────────────────────────
def get_fixtures(team_id: int, season: int, league_id: int) -> list[dict]:
"""
GET /fixtures?team=<id>&season=<year>&league=<id>
Response items:
{"fixture": {...}, "league": {...}, "teams": {...}, "goals": {...}, ...}
"""
data = _get("fixtures", {
"team": team_id,
"season": season,
"league": league_id,
})
return _response(data)
# ── Standings ─────────────────────────────────────────────
def get_standings(league_id: int, season: int) -> list[dict]:
"""
GET /standings?league=<id>&season=<year>
Response: [{"league": {"standings": [[{...}, ...]]}}]
Returns flat list of standing rows.
"""
data = _get("standings", {"league": league_id, "season": season})
rows = _response(data)
if not rows:
return []
# Nested: response[0].league.standings is a list-of-lists (conference groups)
league_data = rows[0].get("league", {})
standings = league_data.get("standings", [])
# Flatten groups
flat: list[dict] = []
for group in standings:
if isinstance(group, list):
flat.extend(group)
elif isinstance(group, dict):
flat.append(group)
return flat
# ── Player season stats ───────────────────────────────────
def get_player_stats(player_id: int, season: int, league_id: int) -> list[dict]:
"""
GET /players?id=<id>&season=<year>&league=<id>
"""
data = _get("players", {
"id": player_id,
"season": season,
"league": league_id,
})
return _response(data)
# ── Live fixtures ─────────────────────────────────────────
def get_live_fixtures(team_id: int) -> list[dict]:
"""
GET /fixtures?live=all&team=<id>
"""
try:
data = _get("fixtures", {"live": "all", "team": team_id}, timeout=10)
return _response(data)
except Exception:
return []
# ── Match score ───────────────────────────────────────────
def get_match_score(fixture_id: int) -> dict:
"""
GET /fixtures?id=<id>
"""
data = _get("fixtures", {"id": fixture_id})
rows = _response(data)
return rows[0] if rows else {}
# ── Utility ───────────────────────────────────────────────
def last_quota_remaining() -> int | None:
"""Last known remaining daily quota from response headers."""
try:
return int(_last_remaining) if _last_remaining is not None else None
except (ValueError, TypeError):
return None

38
nike/config.py Normal file
View File

@@ -0,0 +1,38 @@
"""Centralised configuration loaded from .env."""
import os
from pathlib import Path
from dotenv import load_dotenv
# Always load from project root regardless of working directory
_ENV_PATH = Path(__file__).resolve().parent.parent / '.env'
load_dotenv(_ENV_PATH)
# ── Database ──────────────────────────────────────────────
DB_HOST = os.getenv('DB_HOST', 'portia.incus')
DB_PORT = int(os.getenv('DB_PORT', 5432))
DB_USER = os.getenv('DB_USER', 'nike')
DB_PASSWORD = os.getenv('DB_PASSWORD', '')
DB_NAME = os.getenv('DB_NAME', 'nike')
# ── TheSportsDB ───────────────────────────────────────────
SPORTSDB_KEY = os.getenv('SPORTSDB_KEY', '3') # '3' = free test key
SPORTSDB_V2 = "https://www.thesportsdb.com/api/v2/json"
SPORTSDB_V1 = f"https://www.thesportsdb.com/api/v1/json/{SPORTSDB_KEY}"
# ── Followed teams (team_name:league_name, ...) ──────────
_raw_teams = os.getenv('NIKE_TEAMS', 'Toronto FC:MLS, Arsenal:Premier League')
FOLLOWED_TEAMS = [
tuple(pair.strip().split(':', 1))
for pair in _raw_teams.split(',')
if ':' in pair
]
# ── Legacy API keys (preserved, not active) ───────────────
RAPIDAPI_KEY = os.getenv('RAPIDAPI_KEY', '')
API_FOOTBALL_KEY = os.getenv('API_FOOTBALL_KEY', '')
# ── Server ────────────────────────────────────────────────
SERVER_HOST = os.getenv('NIKE_HOST', '0.0.0.0')
SERVER_PORT = int(os.getenv('NIKE_PORT', 8000))
SERVER_NAME = "Nike — Football Data Platform"

686
nike/db.py Normal file
View File

@@ -0,0 +1,686 @@
"""
Database connection pool and cache helpers for the TheSportsDB cache layer.
Pool lifecycle (create_pool/close_pool/get_conn) is unchanged.
Query helpers are rewritten for the new schema (schema.sql).
Caching strategy:
- cache_team/cache_league/cache_event/cache_players: upsert into DB
- query_* helpers: read from cache
- cache_meta: TTL-aware freshness checks for volatile data
"""
import json
import time
from contextlib import contextmanager
from datetime import datetime, timezone
from typing import Any
import psycopg2
import psycopg2.pool
import psycopg2.extras
from nike import config
_pool: psycopg2.pool.ThreadedConnectionPool | None = None
# ══════════════════════════════════════════════════════════
# Pool lifecycle
# ══════════════════════════════════════════════════════════
def create_pool(minconn: int = 2, maxconn: int = 10) -> None:
"""Initialise the global connection pool."""
global _pool
_pool = psycopg2.pool.ThreadedConnectionPool(
minconn, maxconn,
host=config.DB_HOST,
port=config.DB_PORT,
user=config.DB_USER,
password=config.DB_PASSWORD,
dbname=config.DB_NAME,
connect_timeout=5,
)
def close_pool() -> None:
"""Close all connections in the pool."""
global _pool
if _pool:
_pool.closeall()
_pool = None
@contextmanager
def get_conn():
"""Context-manager that checks out / returns a pooled connection."""
if _pool is None:
raise RuntimeError("DB pool not initialised — call create_pool() first")
conn = _pool.getconn()
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
_pool.putconn(conn)
# ══════════════════════════════════════════════════════════
# Health & diagnostics
# ══════════════════════════════════════════════════════════
def check_connection() -> dict:
"""Return dict with connected bool, latency_ms, and pg_version."""
try:
t0 = time.time()
conn = psycopg2.connect(
host=config.DB_HOST, port=config.DB_PORT,
user=config.DB_USER, password=config.DB_PASSWORD,
dbname=config.DB_NAME, connect_timeout=5,
)
latency_ms = round((time.time() - t0) * 1000, 1)
cur = conn.cursor()
cur.execute("SELECT version();")
version = cur.fetchone()[0].split(',')[0]
cur.close()
conn.close()
return {"connected": True, "latency_ms": latency_ms, "version": version}
except Exception as e:
return {"connected": False, "latency_ms": None, "version": None, "error": str(e)}
def get_table_counts() -> dict[str, int]:
"""Return row counts for all cache tables."""
tables = [
'leagues', 'teams', 'players', 'events',
'event_stats', 'event_lineup', 'event_timeline', 'cache_meta',
]
counts = {}
try:
with get_conn() as conn:
cur = conn.cursor()
for t in tables:
try:
cur.execute(f"SELECT COUNT(*) FROM {t}")
counts[t] = cur.fetchone()[0]
except psycopg2.errors.UndefinedTable:
conn.rollback()
counts[t] = -1
cur.close()
except Exception:
counts = {t: -1 for t in tables}
return counts
def get_last_cache_time() -> str | None:
"""Return the most recent updated_at across cached tables."""
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
SELECT MAX(latest) FROM (
SELECT MAX(updated_at) AS latest FROM teams
UNION ALL
SELECT MAX(updated_at) FROM events
UNION ALL
SELECT MAX(updated_at) FROM players
) sub
""")
row = cur.fetchone()
cur.close()
return row[0].isoformat() if row and row[0] else None
except Exception:
return None
# ══════════════════════════════════════════════════════════
# Row helpers
# ══════════════════════════════════════════════════════════
def _rows_as_dicts(cur) -> list[dict]:
cols = [d[0] for d in cur.description]
return [dict(zip(cols, row)) for row in cur.fetchall()]
def _safe_int(val) -> int | None:
"""Convert string/int to int, return None on failure."""
if val is None:
return None
try:
return int(val)
except (ValueError, TypeError):
return None
# ══════════════════════════════════════════════════════════
# Cache meta (TTL-aware freshness for volatile data)
# ══════════════════════════════════════════════════════════
def is_cache_fresh(cache_key: str) -> bool:
"""Check if a cache entry exists and hasn't expired."""
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
SELECT fetched_at, ttl_seconds FROM cache_meta
WHERE cache_key = %s
""", (cache_key,))
row = cur.fetchone()
cur.close()
if not row:
return False
fetched_at, ttl = row
if fetched_at.tzinfo is None:
fetched_at = fetched_at.replace(tzinfo=timezone.utc)
age_s = (datetime.now(timezone.utc) - fetched_at).total_seconds()
return age_s < ttl
except Exception:
return False
def get_cached_json(cache_key: str) -> Any | None:
"""Return cached JSON data if fresh, else None."""
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
SELECT fetched_at, ttl_seconds, data_json FROM cache_meta
WHERE cache_key = %s
""", (cache_key,))
row = cur.fetchone()
cur.close()
if not row:
return None
fetched_at, ttl, data = row
if fetched_at.tzinfo is None:
fetched_at = fetched_at.replace(tzinfo=timezone.utc)
age_s = (datetime.now(timezone.utc) - fetched_at).total_seconds()
if age_s >= ttl:
return None
return data
except Exception:
return None
def set_cache_meta(cache_key: str, ttl_seconds: int = 3600,
data: Any = None) -> None:
"""Insert or update a cache freshness marker."""
try:
with get_conn() as conn:
cur = conn.cursor()
data_json = json.dumps(data, default=str) if data is not None else None
cur.execute("""
INSERT INTO cache_meta (cache_key, fetched_at, ttl_seconds, data_json)
VALUES (%s, NOW(), %s, %s)
ON CONFLICT (cache_key) DO UPDATE
SET fetched_at = NOW(), ttl_seconds = %s, data_json = %s
""", (cache_key, ttl_seconds, data_json, ttl_seconds, data_json))
cur.close()
except Exception:
pass
def invalidate_cache(pattern: str = "%") -> int:
"""Delete cache_meta entries matching a LIKE pattern. Returns count."""
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("DELETE FROM cache_meta WHERE cache_key LIKE %s", (pattern,))
count = cur.rowcount
cur.close()
return count
except Exception:
return 0
# ══════════════════════════════════════════════════════════
# Cache writers (upsert from API responses)
# ══════════════════════════════════════════════════════════
def cache_league(league: dict) -> None:
"""Upsert a league from TheSportsDB search/lookup response."""
lid = _safe_int(league.get("idLeague"))
if not lid:
return
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
INSERT INTO leagues (id, name, sport, country, badge_url,
banner_url, description)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
sport = EXCLUDED.sport,
country = EXCLUDED.country,
badge_url = EXCLUDED.badge_url,
banner_url = EXCLUDED.banner_url,
description = EXCLUDED.description,
updated_at = NOW()
""", (
lid,
league.get("strLeague"),
league.get("strSport", "Soccer"),
league.get("strCountry"),
league.get("strBadge"),
league.get("strBanner"),
league.get("strDescriptionEN"),
))
cur.close()
except Exception:
pass
def cache_team(team: dict) -> None:
"""Upsert a team from TheSportsDB search/lookup response."""
tid = _safe_int(team.get("idTeam"))
if not tid:
return
lid = _safe_int(team.get("idLeague"))
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
INSERT INTO teams (id, name, short_name, alternate_name, league_id,
league_name, formed_year, sport, country, stadium,
stadium_capacity, location, venue_id, badge_url, logo_url,
banner_url, equipment_url, colour1, colour2, colour3,
website, description)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
short_name = EXCLUDED.short_name,
alternate_name = EXCLUDED.alternate_name,
league_id = EXCLUDED.league_id,
league_name = EXCLUDED.league_name,
formed_year = EXCLUDED.formed_year,
country = EXCLUDED.country,
stadium = EXCLUDED.stadium,
stadium_capacity = EXCLUDED.stadium_capacity,
location = EXCLUDED.location,
venue_id = EXCLUDED.venue_id,
badge_url = EXCLUDED.badge_url,
logo_url = EXCLUDED.logo_url,
banner_url = EXCLUDED.banner_url,
equipment_url = EXCLUDED.equipment_url,
colour1 = EXCLUDED.colour1,
colour2 = EXCLUDED.colour2,
colour3 = EXCLUDED.colour3,
website = EXCLUDED.website,
description = EXCLUDED.description,
updated_at = NOW()
""", (
tid,
team.get("strTeam"),
team.get("strTeamShort"),
team.get("strTeamAlternate"),
lid,
team.get("strLeague"),
_safe_int(team.get("intFormedYear")),
team.get("strSport", "Soccer"),
team.get("strCountry"),
team.get("strStadium"),
_safe_int(team.get("intStadiumCapacity")),
team.get("strLocation"),
_safe_int(team.get("idVenue")),
team.get("strBadge"),
team.get("strLogo"),
team.get("strBanner"),
team.get("strEquipment"),
team.get("strColour1"),
team.get("strColour2"),
team.get("strColour3"),
team.get("strWebsite"),
team.get("strDescriptionEN"),
))
cur.close()
except Exception:
pass
def cache_player(player: dict) -> None:
"""Upsert a player from TheSportsDB list/search/lookup response."""
pid = _safe_int(player.get("idPlayer"))
if not pid:
return
tid = _safe_int(player.get("idTeam"))
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
INSERT INTO players (id, team_id, team_name, name, nationality,
date_of_birth, position, squad_number, height, weight,
gender, status, thumb_url, cutout_url, description)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (id) DO UPDATE SET
team_id = EXCLUDED.team_id,
team_name = EXCLUDED.team_name,
name = EXCLUDED.name,
nationality = EXCLUDED.nationality,
date_of_birth = EXCLUDED.date_of_birth,
position = EXCLUDED.position,
squad_number = EXCLUDED.squad_number,
height = EXCLUDED.height,
weight = EXCLUDED.weight,
gender = EXCLUDED.gender,
status = EXCLUDED.status,
thumb_url = EXCLUDED.thumb_url,
cutout_url = EXCLUDED.cutout_url,
description = EXCLUDED.description,
updated_at = NOW()
""", (
pid,
tid,
player.get("strTeam"),
player.get("strPlayer"),
player.get("strNationality"),
player.get("dateBorn"),
player.get("strPosition"),
player.get("strNumber"),
player.get("strHeight"),
player.get("strWeight"),
player.get("strGender"),
player.get("strStatus"),
player.get("strThumb"),
player.get("strCutout"),
player.get("strDescriptionEN"),
))
cur.close()
except Exception:
pass
def cache_event(event: dict) -> None:
"""Upsert a match/event from TheSportsDB response."""
eid = _safe_int(event.get("idEvent"))
if not eid:
return
lid = _safe_int(event.get("idLeague"))
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
INSERT INTO events (id, name, league_id, league_name, season,
round, home_team_id, away_team_id, home_team, away_team,
home_score, away_score, event_date, event_time,
event_timestamp, venue, venue_id, city, country,
referee, spectators, status, postponed,
poster_url, thumb_url, video_url)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,
%s::time,%s::timestamptz,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
league_id = EXCLUDED.league_id,
league_name = EXCLUDED.league_name,
season = EXCLUDED.season,
round = EXCLUDED.round,
home_team_id = EXCLUDED.home_team_id,
away_team_id = EXCLUDED.away_team_id,
home_team = EXCLUDED.home_team,
away_team = EXCLUDED.away_team,
home_score = EXCLUDED.home_score,
away_score = EXCLUDED.away_score,
event_date = EXCLUDED.event_date,
event_time = EXCLUDED.event_time,
event_timestamp = EXCLUDED.event_timestamp,
venue = EXCLUDED.venue,
venue_id = EXCLUDED.venue_id,
city = EXCLUDED.city,
country = EXCLUDED.country,
referee = EXCLUDED.referee,
spectators = EXCLUDED.spectators,
status = EXCLUDED.status,
postponed = EXCLUDED.postponed,
poster_url = EXCLUDED.poster_url,
thumb_url = EXCLUDED.thumb_url,
video_url = EXCLUDED.video_url,
updated_at = NOW()
""", (
eid,
event.get("strEvent"),
lid,
event.get("strLeague"),
event.get("strSeason"),
str(event.get("intRound", "")) or None,
_safe_int(event.get("idHomeTeam")),
_safe_int(event.get("idAwayTeam")),
event.get("strHomeTeam"),
event.get("strAwayTeam"),
_safe_int(event.get("intHomeScore")),
_safe_int(event.get("intAwayScore")),
event.get("dateEvent"),
event.get("strTime"),
event.get("strTimestamp"),
event.get("strVenue"),
_safe_int(event.get("idVenue")),
event.get("strCity"),
event.get("strCountry"),
event.get("strOfficial"),
_safe_int(event.get("intSpectators")),
event.get("strStatus"),
event.get("strPostponed", "no"),
event.get("strPoster"),
event.get("strThumb"),
event.get("strVideo"),
))
cur.close()
except Exception:
pass
def cache_event_stats(event_id: int, stats: list[dict]) -> None:
"""Upsert event statistics from V2 lookup_event_stats response."""
if not stats:
return
try:
with get_conn() as conn:
cur = conn.cursor()
for s in stats:
sid = _safe_int(s.get("idStatistic"))
if not sid:
continue
cur.execute("""
INSERT INTO event_stats (id, event_id, id_api_football,
event_name, stat_name, home_value, away_value)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (id) DO UPDATE SET
home_value = EXCLUDED.home_value,
away_value = EXCLUDED.away_value
""", (
sid, event_id, s.get("idApiFootball"),
s.get("strEvent"), s.get("strStat"),
s.get("intHome"), s.get("intAway"),
))
cur.close()
except Exception:
pass
def cache_event_timeline(event_id: int, timeline: list[dict]) -> None:
"""Upsert event timeline from V2 lookup_event_timeline response."""
if not timeline:
return
try:
with get_conn() as conn:
cur = conn.cursor()
for t in timeline:
tid = _safe_int(t.get("idTimeline"))
if not tid:
continue
cur.execute("""
INSERT INTO event_timeline (id, event_id, id_api_football,
event_name, event_type, event_detail, is_home, minute,
period, player_id, player_name, cutout_url,
assist_id, assist_name, team_id, team_name, comment,
event_date, season)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (id) DO UPDATE SET
event_type = EXCLUDED.event_type,
event_detail = EXCLUDED.event_detail,
minute = EXCLUDED.minute,
player_name = EXCLUDED.player_name,
assist_name = EXCLUDED.assist_name,
comment = EXCLUDED.comment
""", (
tid, event_id, t.get("idAPIfootball"),
t.get("strEvent"), t.get("strTimeline"),
t.get("strTimelineDetail"), t.get("strHome"),
_safe_int(t.get("intTime")), t.get("strPeriod"),
_safe_int(t.get("idPlayer")), t.get("strPlayer"),
t.get("strCutout"), _safe_int(t.get("idAssist")),
t.get("strAssist"), _safe_int(t.get("idTeam")),
t.get("strTeam"),
t.get("strComment") if t.get("strComment") != "NULL" else None,
t.get("dateEvent"), t.get("strSeason"),
))
cur.close()
except Exception:
pass
def cache_event_lineup(event_id: int, lineup: list[dict]) -> None:
"""Upsert event lineup from V2 lookup_event_lineup response."""
if not lineup:
return
try:
with get_conn() as conn:
cur = conn.cursor()
for p in lineup:
lid = _safe_int(p.get("idLineup"))
if not lid:
continue
cur.execute("""
INSERT INTO event_lineup (id, event_id, id_api_football,
event_name, player_id, player_name, team_id, team_name,
is_home, position, position_short, formation,
squad_number, is_substitute, cutout_url, country, season)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (id) DO UPDATE SET
position = EXCLUDED.position,
squad_number = EXCLUDED.squad_number,
is_substitute = EXCLUDED.is_substitute
""", (
lid, event_id, p.get("idAPIfootball"),
p.get("strEvent"), _safe_int(p.get("idPlayer")),
p.get("strPlayer"), _safe_int(p.get("idTeam")),
p.get("strTeam"), p.get("strHome"),
p.get("strPosition"), p.get("strPositionShort"),
p.get("strFormation"),
_safe_int(p.get("intSquadNumber")),
p.get("strSubstitute", "No"),
p.get("strCutout"), p.get("strCountry"),
p.get("strSeason"),
))
cur.close()
except Exception:
pass
# ══════════════════════════════════════════════════════════
# Cache readers
# ══════════════════════════════════════════════════════════
def query_team(team_name: str) -> dict | None:
"""Lookup a cached team by name (ILIKE)."""
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
SELECT id, name, short_name, league_name, stadium,
stadium_capacity, location, country, badge_url,
colour1, colour2, description
FROM teams
WHERE name ILIKE %s OR short_name ILIKE %s
OR alternate_name ILIKE %s
LIMIT 1
""", (f'%{team_name}%', f'%{team_name}%', f'%{team_name}%'))
rows = _rows_as_dicts(cur)
cur.close()
return rows[0] if rows else None
except Exception:
return None
def query_roster(team_id: int) -> list[dict]:
"""Return cached players for a team."""
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
SELECT id, name, position, squad_number, nationality,
date_of_birth, height, weight, status
FROM players
WHERE team_id = %s
ORDER BY position, squad_number NULLS LAST
""", (team_id,))
rows = _rows_as_dicts(cur)
cur.close()
return rows
except Exception:
return []
def query_player_by_id(player_id: int) -> dict | None:
"""Return a cached player by TheSportsDB idPlayer."""
if not player_id:
return None
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
SELECT id, name, position, squad_number, nationality,
date_of_birth, height, weight, status, team_name
FROM players
WHERE id = %s
""", (player_id,))
rows = _rows_as_dicts(cur)
cur.close()
return rows[0] if rows else None
except Exception:
return None
def query_events_for_team(team_id: int, status: str = 'all') -> list[dict]:
"""Return cached events for a team."""
status_filter = ""
if status == 'finished':
status_filter = "AND status = 'Match Finished'"
elif status == 'upcoming':
status_filter = "AND status = 'Not Started'"
elif status == 'today':
status_filter = "AND event_date = CURRENT_DATE"
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute(f"""
SELECT id, name, league_name, season, round,
home_team, away_team, home_score, away_score,
event_date, event_time, venue, referee,
spectators, status
FROM events
WHERE (home_team_id = %s OR away_team_id = %s)
{status_filter}
ORDER BY event_date DESC, event_time DESC
""", (team_id, team_id))
rows = _rows_as_dicts(cur)
cur.close()
return rows
except Exception:
return []
def query_event(event_id: int) -> dict | None:
"""Return a single cached event."""
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
SELECT * FROM events WHERE id = %s
""", (event_id,))
rows = _rows_as_dicts(cur)
cur.close()
return rows[0] if rows else None
except Exception:
return None

356
nike/rapidapi.py Normal file
View File

@@ -0,0 +1,356 @@
"""
RapidAPI client for free-api-live-football-data.
Provider : Sby Smart API (Creativesdev) on RapidAPI
Base URL : https://free-api-live-football-data.p.rapidapi.com
Auth : x-rapidapi-key + x-rapidapi-host headers
All functions return parsed Python dicts/lists (or raise on error).
A lightweight TTL cache prevents duplicate API calls within a conversation.
"""
from __future__ import annotations
import time
from typing import Any
import requests
from nike import config
# ── HTTP plumbing ────────────────────────────────────────
_HEADERS = {
"x-rapidapi-key": config.RAPIDAPI_KEY,
"x-rapidapi-host": config.RAPIDAPI_HOST,
}
def _get(path: str, params: dict | None = None, timeout: int = 15) -> Any:
"""GET {BASE}{path}?{params} with TTL cache. Returns parsed JSON body."""
url = f"{config.RAPIDAPI_BASE}{path}"
cache_key = f"{path}|{sorted(params.items()) if params else ''}"
# Check cache
hit = _CACHE.get(cache_key)
if hit:
ts, data = hit
if time.time() - ts < _CACHE_TTL:
return data
resp = requests.get(url, headers=_HEADERS, params=params, timeout=timeout)
resp.raise_for_status()
data = resp.json()
# Cache the response
_CACHE[cache_key] = (time.time(), data)
return data
# ── TTL cache ────────────────────────────────────────────
_CACHE: dict[str, tuple[float, Any]] = {}
_CACHE_TTL = 300 # 5 minutes
def clear_cache() -> None:
"""Flush the in-memory response cache."""
_CACHE.clear()
# ── Connectivity check ──────────────────────────────────
def check_connection() -> dict:
"""Quick probe — hit a lightweight endpoint and measure latency."""
try:
t0 = time.time()
_get("/football-popular-leagues", timeout=8)
latency_ms = round((time.time() - t0) * 1000, 1)
return {"connected": True, "latency_ms": latency_ms, "backend": "RapidAPI"}
except Exception as e:
return {"connected": False, "latency_ms": None, "backend": "RapidAPI",
"error": str(e)}
# ── Search ───────────────────────────────────────────────
def search_all(query: str) -> Any:
"""Universal search across teams, players, leagues, matches."""
return _get("/football-all-search", {"search": query})
def search_teams(query: str) -> Any:
"""Search for teams by name."""
return _get("/football-teams-search", {"search": query})
def search_players(query: str) -> Any:
"""Search for players by name."""
return _get("/football-players-search", {"search": query})
def search_leagues(query: str) -> Any:
"""Search for leagues by name."""
return _get("/football-leagues-search", {"search": query})
def search_matches(query: str) -> Any:
"""Search for matches by team/event name."""
return _get("/football-matches-search", {"search": query})
# ── Leagues & countries ──────────────────────────────────
def get_popular_leagues() -> Any:
"""List popular leagues."""
return _get("/football-popular-leagues")
def get_all_leagues() -> Any:
"""List all available leagues."""
return _get("/football-get-all-leagues")
def get_league_detail(league_id: int) -> Any:
"""Get detailed info for a specific league."""
return _get("/football-get-league-detail", {"leagueid": league_id})
def get_all_countries() -> Any:
"""List all countries with leagues."""
return _get("/football-get-all-countries")
# ── Live scores ──────────────────────────────────────────
def get_live_matches() -> Any:
"""All currently live matches worldwide."""
return _get("/football-current-live")
# ── Fixtures ─────────────────────────────────────────────
def get_matches_by_date(date: str) -> Any:
"""
Matches on a given date.
date format: YYYYMMDD (e.g. '20260308')
"""
return _get("/football-get-matches-by-date", {"date": date})
def get_matches_by_date_and_league(date: str, league_id: int) -> Any:
"""Matches on a given date filtered by league."""
return _get("/football-get-matches-by-date-and-league",
{"date": date, "leagueid": league_id})
def get_league_matches(league_id: int) -> Any:
"""All matches for a league (current season)."""
return _get("/football-get-all-matches-by-league", {"leagueid": league_id})
# ── Teams ────────────────────────────────────────────────
def get_league_teams(league_id: int) -> Any:
"""All teams in a league."""
return _get("/football-get-list-all-team", {"leagueid": league_id})
def get_team_detail(team_id: int) -> Any:
"""Detailed info for a specific team."""
return _get("/football-league-team", {"teamid": team_id})
# ── Players / Squad ──────────────────────────────────────
def get_squad(team_id: int) -> Any:
"""Full squad roster for a team."""
return _get("/football-get-list-player", {"teamid": team_id})
def get_player_detail(player_id: int) -> Any:
"""Detailed player profile."""
return _get("/football-get-player-detail", {"playerid": player_id})
# ── Match detail & stats ────────────────────────────────
def get_match_detail(event_id: int) -> Any:
"""Full match overview (teams, score, status, timing)."""
return _get("/football-get-match-detail", {"eventid": event_id})
def get_match_score(event_id: int) -> Any:
"""Current/final score for a match."""
return _get("/football-get-match-score", {"eventid": event_id})
def get_match_status(event_id: int) -> Any:
"""Match status (scheduled, live, finished, etc.)."""
return _get("/football-get-match-status", {"eventid": event_id})
def get_match_stats(event_id: int) -> Any:
"""Full-match statistics (possession, shots, passes, etc.)."""
return _get("/football-get-match-all-stats", {"eventid": event_id})
def get_match_first_half_stats(event_id: int) -> Any:
"""First-half statistics."""
return _get("/football-get-match-firstHalf-stats", {"eventid": event_id})
def get_match_second_half_stats(event_id: int) -> Any:
"""Second-half statistics."""
return _get("/football-get-match-secondhalf-stats", {"eventid": event_id})
def get_match_highlights(event_id: int) -> Any:
"""Match highlights / key events."""
return _get("/football-get-match-highlights", {"eventid": event_id})
def get_match_location(event_id: int) -> Any:
"""Match venue info."""
return _get("/football-get-match-location", {"eventid": event_id})
def get_match_referee(event_id: int) -> Any:
"""Match referee info."""
return _get("/football-get-match-referee", {"eventid": event_id})
# ── Lineups ──────────────────────────────────────────────
def get_home_lineup(event_id: int) -> Any:
"""Home team lineup and formation."""
return _get("/football-get-hometeam-lineup", {"eventid": event_id})
def get_away_lineup(event_id: int) -> Any:
"""Away team lineup and formation."""
return _get("/football-get-awayteam-lineup", {"eventid": event_id})
def get_lineups(event_id: int) -> dict:
"""Both lineups combined into a single dict."""
return {
"home": get_home_lineup(event_id),
"away": get_away_lineup(event_id),
}
# ── Head to head ─────────────────────────────────────────
def get_head_to_head(event_id: int) -> Any:
"""Head-to-head history for the teams in a match."""
return _get("/football-get-head-to-head", {"eventid": event_id})
# ── Standings ────────────────────────────────────────────
def get_standings(league_id: int) -> Any:
"""Full league table (overall)."""
return _get("/football-get-standing-all", {"leagueid": league_id})
def get_home_standings(league_id: int) -> Any:
"""Home-only standings."""
return _get("/football-get-standing-home", {"leagueid": league_id})
def get_away_standings(league_id: int) -> Any:
"""Away-only standings."""
return _get("/football-get-standing-away", {"leagueid": league_id})
# ── Top players ──────────────────────────────────────────
def get_top_scorers(league_id: int) -> Any:
"""Top goal scorers in the league."""
return _get("/football-get-top-players-by-goals", {"leagueid": league_id})
def get_top_assists(league_id: int) -> Any:
"""Top assist providers in the league."""
return _get("/football-get-top-players-by-assists", {"leagueid": league_id})
def get_top_rated(league_id: int) -> Any:
"""Highest-rated players in the league."""
return _get("/football-get-top-players-by-rating", {"leagueid": league_id})
# ── Transfers ────────────────────────────────────────────
def get_league_transfers(league_id: int) -> Any:
"""Transfer activity for a league."""
return _get("/football-get-league-transfers", {"leagueid": league_id})
def get_team_transfers_in(team_id: int) -> Any:
"""Players signed by a team."""
return _get("/football-get-team-players-in-transfers", {"teamid": team_id})
def get_team_transfers_out(team_id: int) -> Any:
"""Players who left a team."""
return _get("/football-get-team-players-out-transfers", {"teamid": team_id})
def get_all_transfers(page: int = 1) -> Any:
"""All recent transfers (paginated)."""
return _get("/football-get-all-transfers", {"page": page})
def get_top_transfers(page: int = 1) -> Any:
"""Top transfers by value (paginated)."""
return _get("/football-get-top-transfers", {"page": page})
# ── News ─────────────────────────────────────────────────
def get_trending_news() -> Any:
"""Trending football news worldwide."""
return _get("/football-get-trendingnews")
def get_league_news(league_id: int, page: int = 1) -> Any:
"""News for a specific league."""
return _get("/football-get-league-news", {"leagueid": league_id, "page": page})
def get_team_news(team_id: int, page: int = 1) -> Any:
"""News for a specific team."""
return _get("/football-get-team-news", {"teamid": team_id, "page": page})
# ── Rounds ───────────────────────────────────────────────
def get_all_rounds(league_id: int) -> Any:
"""All rounds/matchdays for a league."""
return _get("/football-get-all-rounds", {"leagueid": league_id})
def get_round_detail(round_id: int) -> Any:
"""Detail for a specific round."""
return _get("/football-get-rounds-detail", {"roundid": round_id})
# ── Trophies ─────────────────────────────────────────────
def get_trophies_all_seasons(league_id: int) -> Any:
"""Trophy/title winners across all seasons."""
return _get("/football-get-trophies-all-seasons", {"leagueid": league_id})
def get_trophies_detail(league_id: int, season: str) -> Any:
"""Trophy detail for a specific season (e.g. '2023/2024')."""
return _get("/football-get-trophies-detail",
{"leagueid": league_id, "season": season})
# ── Odds ─────────────────────────────────────────────────
def get_event_odds(event_id: int, country_code: str = "US") -> Any:
"""Betting odds for a match."""
return _get("/football-event-odds",
{"eventid": event_id, "countrycode": country_code})

790
nike/server.py Normal file
View File

@@ -0,0 +1,790 @@
"""
Nike MCP Server + Bootstrap Dashboard
======================================
Single process on 0.0.0.0:8000
/ → Bootstrap status dashboard
/api/* → Dashboard JSON API
/mcp → FastMCP HTTP endpoint (streamable-HTTP)
Data flow:
MCP tool → sportsdb.py (live API) → format for LLM
↕ (cache permanent data)
db.py (PostgreSQL)
"""
from __future__ import annotations
import time
from collections import deque
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from fastmcp import FastMCP
from mcp.types import ToolAnnotations
import nike.db as db
import nike.sportsdb as api
from nike import config
# ── Request log ──────────────────────────────────────────
_REQUEST_LOG: deque[dict] = deque(maxlen=500)
_SERVER_START = datetime.now(timezone.utc)
def _log(tool: str, args: dict, duration_ms: float, caller: str = "unknown") -> None:
_REQUEST_LOG.appendleft({
"tool": tool,
"args": args,
"duration_ms": round(duration_ms, 1),
"caller": caller,
"timestamp": datetime.now(timezone.utc).isoformat(),
})
# ── Helpers ──────────────────────────────────────────────
def _resolve_team(name: str) -> dict | None:
"""
Search for a team by name, cache it, return the first match.
Returns the raw TheSportsDB team dict or None.
"""
# Check DB cache first
cached = db.query_team(name)
if cached:
return cached
# Hit the API
result = api.v1_search_teams(name)
teams = result.get("teams") if result else None
if not teams:
return None
team = teams[0]
db.cache_team(team)
# Also cache the league if present
if team.get("idLeague"):
db.cache_league({
"idLeague": team["idLeague"],
"strLeague": team.get("strLeague"),
"strSport": team.get("strSport", "Soccer"),
"strCountry": team.get("strCountry"),
})
return team
def _normalize_team_name(name: str) -> str:
"""Map common abbreviations to full names."""
aliases = {
"tfc": "Toronto FC",
"toronto": "Toronto FC",
}
return aliases.get(name.strip().lower(), name)
def _format_score(home_score, away_score) -> str:
"""Format a score pair, handling None."""
if home_score is not None and away_score is not None:
return f"{home_score}-{away_score}"
return "vs"
# ── FastMCP instance ──────────────────────────────────────
mcp = FastMCP(
name=config.SERVER_NAME,
instructions=(
"You have access to Nike, a football (soccer) data platform "
"powered by TheSportsDB. It covers major leagues worldwide "
"including MLS and the Premier League.\n\n"
"Available tools:\n"
" • get_roster(team_name) — current squad grouped by position\n"
" • get_player_info(player_name) — profile: position, nationality, DOB\n"
" • get_fixtures(team_name, status) — recent results + upcoming matches\n"
" • get_standings(league, season) — full league table with points, GD, form\n"
" • get_match_result(team_name, date) — specific match score & details\n"
" • get_match_detail(event_id) — deep stats, lineup, timeline for a match\n"
" • get_livescores() — current live soccer scores\n"
" • get_team_info(team_name) — team profile with stadium, colors, description\n\n"
"Interpret 'TFC' as 'Toronto FC'. "
f"Followed teams: {', '.join(t[0] for t in config.FOLLOWED_TEAMS)}."
),
)
# ══════════════════════════════════════════════════════════
# MCP Tools
# ══════════════════════════════════════════════════════════
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_team_info(team_name: str = "Toronto FC") -> str:
"""
Get team profile: stadium, capacity, location, founded year, colors.
Use team_name like 'Toronto FC', 'Arsenal', 'TFC'.
"""
t0 = time.time()
team_name = _normalize_team_name(team_name)
team = _resolve_team(team_name)
duration = (time.time() - t0) * 1000
_log("get_team_info", {"team_name": team_name}, duration)
if not team:
return f"Team '{team_name}' not found."
# Handle both raw API dicts and cached DB dicts
name = team.get("strTeam") or team.get("name", "")
lines = [
f"=== {name} ===",
f"League : {team.get('strLeague') or team.get('league_name', 'N/A')}",
f"Country : {team.get('strCountry') or team.get('country', 'N/A')}",
f"Stadium : {team.get('strStadium') or team.get('stadium', 'N/A')}",
f"Capacity : {team.get('intStadiumCapacity') or team.get('stadium_capacity', 'N/A')}",
f"Location : {team.get('strLocation') or team.get('location', 'N/A')}",
f"Founded : {team.get('intFormedYear') or team.get('formed_year', 'N/A')}",
]
desc = team.get("strDescriptionEN") or team.get("description")
if desc:
# First paragraph only
first_para = desc.split('\r\n')[0].split('\n')[0]
if len(first_para) > 300:
first_para = first_para[:297] + "..."
lines.append(f"\n{first_para}")
return "\n".join(lines)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_roster(team_name: str = "Toronto FC") -> str:
"""
Get the current squad for a team, grouped by position.
Requires a premium TheSportsDB key for V2 squad data.
Falls back to cached roster if available.
"""
t0 = time.time()
team_name = _normalize_team_name(team_name)
# Resolve team to get ID
team = _resolve_team(team_name)
if not team:
duration = (time.time() - t0) * 1000
_log("get_roster", {"team_name": team_name}, duration)
return f"Team '{team_name}' not found."
team_id = int(team.get("idTeam") or team.get("id"))
display_name = team.get("strTeam") or team.get("name", team_name)
# Try V2 squad list (premium)
# Note: V2 /list/players only returns idPlayer, strPlayer, idTeam,
# strTeam, strThumb, strCutout, strRender, dateBorn, strPosition.
# It does NOT include strNumber or strNationality — those come from
# the full player detail (lookup_player), which we skip to avoid N+1.
players = []
is_premium = config.SPORTSDB_KEY not in ('3', '')
if is_premium:
try:
result = api.list_players(team_id)
raw_players = result.get("list") or result.get("player") or []
for p in raw_players:
db.cache_player(p)
# Check DB for enriched data (number, nationality)
cached = db.query_player_by_id(int(p.get("idPlayer", 0)))
players.append({
"name": p.get("strPlayer"),
"position": p.get("strPosition", "Unknown"),
"number": (cached or {}).get("squad_number") or "-",
"nationality": (cached or {}).get("nationality") or "",
"dob": p.get("dateBorn", ""),
})
except Exception:
pass
# Fallback: cached roster from DB
if not players:
cached = db.query_roster(team_id)
for p in cached:
players.append({
"name": p["name"],
"position": p.get("position", "Unknown"),
"number": p.get("squad_number", "-"),
"nationality": p.get("nationality", ""),
})
duration = (time.time() - t0) * 1000
_log("get_roster", {"team_name": team_name}, duration)
if not players:
return (f"No squad data for '{display_name}'. "
"A premium TheSportsDB key is needed for squad lists.")
# Group by position
by_pos: dict[str, list] = {}
for p in players:
pos = p.get("position") or "Unknown"
by_pos.setdefault(pos, []).append(p)
lines = [f"=== {display_name} — Squad ==="]
order = ['Goalkeeper', 'Defender', 'Midfielder', 'Attacker', 'Coach']
for pos in order + [k for k in by_pos if k not in order]:
if pos not in by_pos:
continue
lines.append(f"\n{pos.upper()}S:")
for p in by_pos[pos]:
num = str(p.get('number') or '-').rjust(2)
lines.append(f" #{num} {p['name']} ({p.get('nationality', '')})")
lines.append(f"\nTotal: {len(players)} players")
return "\n".join(lines)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_player_info(player_name: str) -> str:
"""
Get profile info for a player: position, nationality, DOB, team, status.
"""
t0 = time.time()
# V1 search
result = api.v1_search_players(player_name)
players = result.get("player") if result else None
duration = (time.time() - t0) * 1000
_log("get_player_info", {"player_name": player_name}, duration)
if not players:
return f"Player '{player_name}' not found."
p = players[0]
db.cache_player(p)
lines = [
f"=== {p.get('strPlayer', player_name)} ===",
f"Team : {p.get('strTeam', 'N/A')}",
f"Position : {p.get('strPosition', 'N/A')}",
f"Nationality: {p.get('strNationality', 'N/A')}",
f"DOB : {p.get('dateBorn', 'N/A')}",
f"Status : {p.get('strStatus', 'N/A')}",
f"Gender : {p.get('strGender', 'N/A')}",
]
# If premium, get full detail
pid = p.get("idPlayer")
if pid and config.SPORTSDB_KEY not in ('3', ''):
try:
detail = api.lookup_player(int(pid))
pdata = (detail.get("lookup") or detail.get("players") or [None])[0]
if pdata:
if pdata.get("strHeight"):
lines.append(f"Height : {pdata['strHeight']}")
if pdata.get("strWeight"):
lines.append(f"Weight : {pdata['strWeight']}")
if pdata.get("strNumber"):
lines.append(f"Number : {pdata['strNumber']}")
desc = pdata.get("strDescriptionEN")
if desc:
first_para = desc.split('\r\n')[0].split('\n')[0]
if len(first_para) > 300:
first_para = first_para[:297] + "..."
lines.append(f"\n{first_para}")
except Exception:
pass
return "\n".join(lines)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_fixtures(team_name: str = "Toronto FC",
status: str = "all") -> str:
"""
Get recent results and upcoming matches for a team.
status: 'all' (default), 'upcoming', 'past'.
"""
t0 = time.time()
team_name = _normalize_team_name(team_name)
team = _resolve_team(team_name)
if not team:
duration = (time.time() - t0) * 1000
_log("get_fixtures", {"team_name": team_name, "status": status}, duration)
return f"Team '{team_name}' not found."
team_id = int(team.get("idTeam") or team.get("id"))
display_name = team.get("strTeam") or team.get("name", team_name)
events = []
# Previous results
if status in ('all', 'past'):
try:
prev = api.v1_previous_team(team_id)
for e in (prev.get("results") or []):
db.cache_event(e)
events.append(e)
except Exception:
pass
# Upcoming fixtures
if status in ('all', 'upcoming'):
try:
nxt = api.v1_next_team(team_id)
for e in (nxt.get("events") or []):
events.append(e)
except Exception:
pass
duration = (time.time() - t0) * 1000
_log("get_fixtures", {"team_name": team_name, "status": status}, duration)
if not events:
return f"No fixtures found for '{display_name}'."
lines = [f"=== {display_name} — Fixtures ({status}) ==="]
for e in events:
date = e.get("dateEvent", "?")
home = e.get("strHomeTeam", "?")
away = e.get("strAwayTeam", "?")
score = _format_score(e.get("intHomeScore"), e.get("intAwayScore"))
st = e.get("strStatus") or ""
league = e.get("strLeague", "")
lines.append(f" {date} {home} {score} {away} ({st}) [{league}]")
return "\n".join(lines)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_standings(league: str = "American Major League Soccer",
season: str = "2026") -> str:
"""
Get the league table / standings.
league: 'American Major League Soccer', 'English Premier League', etc.
season: '2025-2026' for Euro leagues, '2026' for MLS.
"""
t0 = time.time()
# Map common names to TheSportsDB league IDs
league_ids = {
"mls": 4346,
"major league soccer": 4346,
"american major league soccer": 4346,
"epl": 4328,
"premier league": 4328,
"english premier league": 4328,
}
league_id = league_ids.get(league.strip().lower())
# If not in map, try searching
if not league_id:
try:
team_search = api.v1_search_teams(league)
teams = team_search.get("teams") or []
if teams:
league_id = int(teams[0].get("idLeague", 0))
except Exception:
pass
if not league_id:
duration = (time.time() - t0) * 1000
_log("get_standings", {"league": league, "season": season}, duration)
return f"League '{league}' not found. Try the full name like 'English Premier League'."
# Always fetch live from API (volatile data)
try:
result = api.v1_standings(league_id, season)
except Exception as ex:
duration = (time.time() - t0) * 1000
_log("get_standings", {"league": league, "season": season}, duration)
return f"Error fetching standings: {ex}"
table = result.get("table") or []
duration = (time.time() - t0) * 1000
_log("get_standings", {"league": league, "season": season}, duration)
if not table:
return f"No standings for '{league}' season {season}."
league_name = table[0].get("strLeague", league)
lines = [
f"=== {league_name} {season} Standings ===",
f"{'Rk':>2} {'Team':<28} {'Pts':>4} {'P':>3} {'W':>3} {'D':>3} "
f"{'L':>3} {'GF':>4} {'GA':>4} {'GD':>4} Form",
]
for r in table:
lines.append(
f"{r.get('intRank','?'):>2} {r.get('strTeam','?'):<28} "
f"{r.get('intPoints','?'):>4} "
f"{r.get('intPlayed','?'):>3} {r.get('intWin','?'):>3} "
f"{r.get('intDraw','?'):>3} {r.get('intLoss','?'):>3} "
f"{r.get('intGoalsFor','?'):>4} {r.get('intGoalsAgainst','?'):>4} "
f"{r.get('intGoalDifference','?'):>4} "
f"{r.get('strForm', '')}"
)
return "\n".join(lines)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_match_result(team_name: str, match_date: str) -> str:
"""
Get match results for a team on a specific date.
match_date format: YYYY-MM-DD.
Example: get_match_result('Toronto FC', '2026-03-09')
"""
t0 = time.time()
team_name = _normalize_team_name(team_name)
# Resolve team for league info
team = _resolve_team(team_name)
league_id = None
if team:
league_id = int(team.get("idLeague") or team.get("league_id") or 0) or None
# Try events-by-date filtered by league
events = []
try:
if league_id:
result = api.v1_events_by_date_league(match_date, league_id)
else:
result = api.v1_events_by_date(match_date)
for e in (result.get("events") or []):
# Filter for matching team
home = (e.get("strHomeTeam") or "").lower()
away = (e.get("strAwayTeam") or "").lower()
search = team_name.lower()
if search in home or search in away:
db.cache_event(e)
events.append(e)
except Exception:
pass
# Also check previous results (in case events-by-date misses it)
if not events and team:
team_id = int(team.get("idTeam") or team.get("id"))
try:
prev = api.v1_previous_team(team_id)
for e in (prev.get("results") or []):
if e.get("dateEvent") == match_date:
db.cache_event(e)
events.append(e)
except Exception:
pass
duration = (time.time() - t0) * 1000
_log("get_match_result", {"team_name": team_name, "match_date": match_date}, duration)
if not events:
return f"No match found for '{team_name}' on {match_date}."
lines = []
for e in events:
score = _format_score(e.get("intHomeScore"), e.get("intAwayScore"))
lines += [
f"=== {e.get('strEvent', 'Match')} ===",
f"{e.get('strHomeTeam')} {score} {e.get('strAwayTeam')}",
f"Date : {e.get('dateEvent')} {e.get('strTime', '')}",
f"League : {e.get('strLeague', 'N/A')} (Round {e.get('intRound', '?')})",
f"Venue : {e.get('strVenue', 'N/A')}",
f"Referee: {e.get('strOfficial') or 'N/A'}",
f"Status : {e.get('strStatus', 'N/A')}",
]
if e.get("intSpectators"):
lines.append(f"Attend.: {e['intSpectators']}")
lines.append("")
return "\n".join(lines)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_match_detail(event_id: int) -> str:
"""
Get deep match detail: stats, lineup, and timeline for a specific match.
Requires a premium TheSportsDB key.
Get the event_id from get_fixtures or get_match_result first.
"""
t0 = time.time()
if config.SPORTSDB_KEY in ('3', ''):
duration = (time.time() - t0) * 1000
_log("get_match_detail", {"event_id": event_id}, duration)
return "Match detail requires a premium TheSportsDB key."
lines = [f"=== Match Detail (Event {event_id}) ==="]
# V2 event lookup
try:
ev = api.lookup_event(event_id)
events = ev.get("lookup") or ev.get("events") or []
if events:
e = events[0]
# Cache the event itself (parent row for sub-tables)
db.cache_event(e)
score = _format_score(e.get("intHomeScore"), e.get("intAwayScore"))
lines += [
f"{e.get('strHomeTeam')} {score} {e.get('strAwayTeam')}",
f"Date : {e.get('dateEvent')} {e.get('strTime', '')}",
f"League : {e.get('strLeague', 'N/A')}",
f"Venue : {e.get('strVenue', 'N/A')}",
f"Status : {e.get('strStatus', 'N/A')}",
]
except Exception as ex:
lines.append(f"Event lookup failed: {ex}")
# Stats
try:
stats_resp = api.lookup_event_stats(event_id)
stats = stats_resp.get("lookup") or []
if stats:
db.cache_event_stats(event_id, stats)
lines.append("\nSTATISTICS:")
for s in stats:
lines.append(
f" {s.get('strStat', '?'):<25} "
f"{s.get('intHome', '?'):>6} - {s.get('intAway', '?')}"
)
except Exception:
pass
# Timeline
try:
tl_resp = api.lookup_event_timeline(event_id)
timeline = tl_resp.get("lookup") or []
if timeline:
db.cache_event_timeline(event_id, timeline)
lines.append("\nTIMELINE:")
for t in timeline:
minute = t.get("intTime", "?")
event_type = t.get("strTimeline", "?")
detail = t.get("strTimelineDetail", "")
player = t.get("strPlayer", "")
team = t.get("strTeam", "")
assist = t.get("strAssist", "")
line = f"{minute}' {event_type}: {player} ({team})"
if detail:
line += f"{detail}"
if assist:
line += f" [assist: {assist}]"
lines.append(f" {line}")
except Exception:
pass
# Lineup
try:
lu_resp = api.lookup_event_lineup(event_id)
lineup = lu_resp.get("lookup") or []
if lineup:
db.cache_event_lineup(event_id, lineup)
# strHome is "Yes"/"No", strSubstitute is "Yes"/"No"
home_xi = [p for p in lineup if p.get("strSubstitute") != "Yes"
and p.get("strHome") == "Yes"]
away_xi = [p for p in lineup if p.get("strSubstitute") != "Yes"
and p.get("strHome") != "Yes"]
subs = [p for p in lineup if p.get("strSubstitute") == "Yes"]
if home_xi:
lines.append(f"\nHOME XI:")
for p in home_xi:
num = str(p.get('intSquadNumber') or '').rjust(2)
pos = p.get('strPositionShort') or p.get('strPosition', '?')
lines.append(f" #{num} {p.get('strPlayer', '?')} ({pos})")
if away_xi:
lines.append(f"\nAWAY XI:")
for p in away_xi:
num = str(p.get('intSquadNumber') or '').rjust(2)
pos = p.get('strPositionShort') or p.get('strPosition', '?')
lines.append(f" #{num} {p.get('strPlayer', '?')} ({pos})")
if subs:
lines.append(f"\nSUBSTITUTES ({len(subs)}):")
for p in subs[:10]:
num = str(p.get('intSquadNumber') or '').rjust(2)
pos = p.get('strPositionShort') or p.get('strPosition', '?')
lines.append(f" #{num} {p.get('strPlayer', '?')} ({pos})")
except Exception:
pass
duration = (time.time() - t0) * 1000
_log("get_match_detail", {"event_id": event_id}, duration)
return "\n".join(lines)
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_livescores() -> str:
"""
Get current live soccer scores worldwide.
Requires a premium TheSportsDB key.
"""
t0 = time.time()
if config.SPORTSDB_KEY in ('3', ''):
duration = (time.time() - t0) * 1000
_log("get_livescores", {}, duration)
return "Live scores require a premium TheSportsDB key."
try:
result = api.livescores_soccer()
except Exception as ex:
duration = (time.time() - t0) * 1000
_log("get_livescores", {}, duration)
return f"Error fetching live scores: {ex}"
games = result.get("livescore") or []
duration = (time.time() - t0) * 1000
_log("get_livescores", {}, duration)
if not games:
return "No live soccer matches at the moment."
# Group by league
by_league: dict[str, list] = {}
for g in games:
league = g.get("strLeague", "Unknown")
by_league.setdefault(league, []).append(g)
lines = ["=== Live Soccer Scores ==="]
for league, matches in by_league.items():
lines.append(f"\n{league}:")
for g in matches:
score = _format_score(g.get("intHomeScore"), g.get("intAwayScore"))
progress = g.get("strProgress", "")
status = g.get("strStatus", "")
time_str = f"{progress}'" if progress else status
lines.append(
f" {g.get('strHomeTeam', '?')} {score} "
f"{g.get('strAwayTeam', '?')} ({time_str})"
)
lines.append(f"\n{len(games)} matches live")
return "\n".join(lines)
# ── MCP Prompt ────────────────────────────────────────────
@mcp.prompt()
def football_analyst() -> str:
"""Prime the assistant with football analyst context."""
teams_str = ", ".join(t[0] for t in config.FOLLOWED_TEAMS)
return (
"You are a football data analyst with access to the Nike platform. "
"Nike provides live football (soccer) data from TheSportsDB covering "
"major leagues worldwide, including MLS and the Premier League.\n\n"
f"Followed teams: {teams_str}\n\n"
"Tools available:\n"
" • get_team_info(team_name) — team profile with stadium, colors\n"
" • get_roster(team_name) — squad grouped by position\n"
" • get_player_info(player_name) — player profile\n"
" • get_fixtures(team_name, status) — recent results + upcoming\n"
" • get_standings(league, season) — league table\n"
" • get_match_result(team_name, date) — specific match result\n"
" • get_match_detail(event_id) — deep stats, lineup, timeline\n"
" • get_livescores() — live scores worldwide\n\n"
"Interpret fuzzy references: 'TFC''Toronto FC'. "
"For match detail (stats/lineup/timeline), first use get_fixtures "
"or get_match_result to find the event_id, then call get_match_detail."
)
# ── Build MCP ASGI app (needed before lifespan) ──────────
_mcp_app = mcp.http_app(path="/")
# ── FastAPI dashboard app ─────────────────────────────────
_TEMPLATES = Jinja2Templates(
directory=str(Path(__file__).parent / "templates")
)
@asynccontextmanager
async def _lifespan(app: FastAPI):
"""Start DB pool + MCP session manager on startup, close on shutdown."""
db.create_pool(minconn=2, maxconn=10)
async with _mcp_app.router.lifespan_context(app):
yield
db.close_pool()
dashboard = FastAPI(title="Nike Dashboard", lifespan=_lifespan)
# ── Dashboard routes ──────────────────────────────────────
@dashboard.get("/", response_class=HTMLResponse)
async def index(request: Request):
return _TEMPLATES.TemplateResponse("dashboard.html", {"request": request})
@dashboard.get("/api/status")
async def api_status():
db_status = db.check_connection()
api_conn = api.check_connection()
counts = db.get_table_counts()
last_cache = db.get_last_cache_time()
uptime_s = int((datetime.now(timezone.utc) - _SERVER_START).total_seconds())
uptime_str = f"{uptime_s // 3600}h {(uptime_s % 3600) // 60}m {uptime_s % 60}s"
is_premium = config.SPORTSDB_KEY not in ('3', '')
tools = [
{"name": "get_team_info", "description": "Team profile", "readonly": True},
{"name": "get_roster", "description": "Squad by position", "readonly": True},
{"name": "get_player_info", "description": "Player profile", "readonly": True},
{"name": "get_fixtures", "description": "Results & upcoming", "readonly": True},
{"name": "get_standings", "description": "League table", "readonly": True},
{"name": "get_match_result", "description": "Match on a date", "readonly": True},
{"name": "get_match_detail", "description": "Stats/lineup/timeline", "readonly": True,
"premium": True},
{"name": "get_livescores", "description": "Live scores", "readonly": True,
"premium": True},
]
return JSONResponse({
"database": db_status,
"api": api_conn,
"mcp": {
"running": True,
"transport": "HTTP (Streamable)",
"endpoint": f"http://{config.SERVER_HOST}:{config.SERVER_PORT}/mcp",
"port": config.SERVER_PORT,
"uptime": uptime_str,
"tool_count": len(tools),
"premium": is_premium,
},
"data": {
"table_counts": counts,
"last_cache": last_cache,
"followed": [{"team": t[0], "league": t[1]}
for t in config.FOLLOWED_TEAMS],
},
"tools": tools,
})
@dashboard.get("/api/logs")
async def api_logs(limit: int = 50):
return JSONResponse({"logs": list(_REQUEST_LOG)[:limit]})
@dashboard.post("/api/cache/invalidate")
async def api_cache_invalidate():
"""Clear all cache_meta entries and the sportsdb in-memory cache."""
count = db.invalidate_cache("%")
api.clear_cache()
return JSONResponse({
"ok": True,
"cache_meta_cleared": count,
"memory_cache_cleared": True,
})
# ── Mount MCP onto dashboard ──────────────────────────────
dashboard.mount("/mcp", _mcp_app)
# ── Entry point ───────────────────────────────────────────
def main():
uvicorn.run(
"nike.server:dashboard",
host=config.SERVER_HOST,
port=config.SERVER_PORT,
reload=False,
log_level="info",
ws="wsproto",
)
if __name__ == "__main__":
main()

311
nike/sportsdb.py Normal file
View File

@@ -0,0 +1,311 @@
"""
TheSportsDB API client — V2 (primary) + V1 (standings, H2H, events-by-date).
V2: https://www.thesportsdb.com/api/v2/json (X-API-KEY header)
V1: https://www.thesportsdb.com/api/v1/json/{key} (key in URL path)
Both use the same premium key ($9/mo Patreon).
Free test key for V1 is '3' (limited results, no V2 access).
All functions return parsed Python dicts/lists (or raise on error).
A lightweight TTL cache prevents duplicate API calls within a conversation.
"""
from __future__ import annotations
import time
from typing import Any
import requests
from nike import config
# ── HTTP plumbing ────────────────────────────────────────
_V2_HEADERS = {
"X-API-KEY": config.SPORTSDB_KEY,
}
# TTL cache
_CACHE: dict[str, tuple[float, Any]] = {}
_CACHE_TTL = 300 # 5 minutes
def clear_cache() -> None:
"""Flush the in-memory response cache."""
_CACHE.clear()
def _get_v2(path: str, timeout: int = 15) -> Any:
"""GET V2 endpoint. Path should start with /."""
url = f"{config.SPORTSDB_V2}{path}"
cache_key = f"v2|{path}"
hit = _CACHE.get(cache_key)
if hit:
ts, data = hit
if time.time() - ts < _CACHE_TTL:
return data
resp = requests.get(url, headers=_V2_HEADERS, timeout=timeout)
resp.raise_for_status()
data = resp.json()
_CACHE[cache_key] = (time.time(), data)
return data
def _get_v1(path: str, params: dict | None = None, timeout: int = 15) -> Any:
"""GET V1 endpoint. Path should start with /."""
url = f"{config.SPORTSDB_V1}{path}"
cache_key = f"v1|{path}|{sorted(params.items()) if params else ''}"
hit = _CACHE.get(cache_key)
if hit:
ts, data = hit
if time.time() - ts < _CACHE_TTL:
return data
resp = requests.get(url, params=params, timeout=timeout)
resp.raise_for_status()
data = resp.json()
_CACHE[cache_key] = (time.time(), data)
return data
# ── Connectivity check ──────────────────────────────────
def check_connection() -> dict:
"""Quick probe — hit a lightweight V1 endpoint."""
try:
t0 = time.time()
_get_v1("/searchteams.php", {"t": "Arsenal"}, timeout=8)
latency_ms = round((time.time() - t0) * 1000, 1)
return {"connected": True, "latency_ms": latency_ms, "backend": "TheSportsDB"}
except Exception as e:
return {"connected": False, "latency_ms": None, "backend": "TheSportsDB",
"error": str(e)}
# ══════════════════════════════════════════════════════════
# V2 ENDPOINTS (premium key required)
# ══════════════════════════════════════════════════════════
# ── Search ───────────────────────────────────────────────
def search_leagues(name: str) -> Any:
"""Search leagues by name. Returns {'search': [...]}."""
return _get_v2(f"/search/league/{name}")
def search_teams(name: str) -> Any:
"""Search teams by name. Returns {'search': [...]}."""
return _get_v2(f"/search/team/{name}")
def search_players(name: str) -> Any:
"""Search players by name. Returns {'search': [...]}."""
return _get_v2(f"/search/player/{name}")
def search_events(name: str) -> Any:
"""Search events/matches by name. Returns {'search': [...]}."""
return _get_v2(f"/search/event/{name}")
# ── Lookup ───────────────────────────────────────────────
def lookup_league(league_id: int) -> Any:
"""Full league details."""
return _get_v2(f"/lookup/league/{league_id}")
def lookup_team(team_id: int) -> Any:
"""Full team details (description, stadium, social, colours)."""
return _get_v2(f"/lookup/team/{team_id}")
def lookup_player(player_id: int) -> Any:
"""Full player bio, stats, images."""
return _get_v2(f"/lookup/player/{player_id}")
def lookup_player_contracts(player_id: int) -> Any:
"""Player career history (teams, years, wages)."""
return _get_v2(f"/lookup/player_contracts/{player_id}")
def lookup_player_honours(player_id: int) -> Any:
"""Trophies / honours won."""
return _get_v2(f"/lookup/player_honours/{player_id}")
def lookup_event(event_id: int) -> Any:
"""Match details: teams, score, venue, referee, status."""
return _get_v2(f"/lookup/event/{event_id}")
def lookup_event_lineup(event_id: int) -> Any:
"""Both teams' lineups: positions, squad numbers, sub status."""
return _get_v2(f"/lookup/event_lineup/{event_id}")
def lookup_event_stats(event_id: int) -> Any:
"""Full match statistics: shots, possession, passes, cards, xG."""
return _get_v2(f"/lookup/event_stats/{event_id}")
def lookup_event_timeline(event_id: int) -> Any:
"""Match events timeline: goals, subs, cards with minute + player."""
return _get_v2(f"/lookup/event_timeline/{event_id}")
def lookup_event_tv(event_id: int) -> Any:
"""TV broadcast details for a match."""
return _get_v2(f"/lookup/event_tv/{event_id}")
def lookup_venue(venue_id: int) -> Any:
"""Venue details: capacity, location, images."""
return _get_v2(f"/lookup/venue/{venue_id}")
# ── List ─────────────────────────────────────────────────
def list_teams(league_id: int) -> Any:
"""All teams in a league."""
return _get_v2(f"/list/teams/{league_id}")
def list_seasons(league_id: int) -> Any:
"""All seasons for a league."""
return _get_v2(f"/list/seasons/{league_id}")
def list_players(team_id: int) -> Any:
"""All players on a team (squad roster)."""
return _get_v2(f"/list/players/{team_id}")
# ── Schedule ─────────────────────────────────────────────
def schedule_next_league(league_id: int) -> Any:
"""Next 10 events in a league."""
return _get_v2(f"/schedule/next/league/{league_id}")
def schedule_previous_league(league_id: int) -> Any:
"""Previous 10 events in a league."""
return _get_v2(f"/schedule/previous/league/{league_id}")
def schedule_next_team(team_id: int) -> Any:
"""Next 10 events for a team."""
return _get_v2(f"/schedule/next/team/{team_id}")
def schedule_previous_team(team_id: int) -> Any:
"""Previous 10 events for a team."""
return _get_v2(f"/schedule/previous/team/{team_id}")
def schedule_full_team(team_id: int) -> Any:
"""Full season schedule for a team."""
return _get_v2(f"/schedule/full/team/{team_id}")
def schedule_league_season(league_id: int, season: str) -> Any:
"""Full season schedule for a league. Season format: '2025-2026' or '2026'."""
return _get_v2(f"/schedule/league/{league_id}/{season}")
# ── Livescores ───────────────────────────────────────────
def livescores_soccer() -> Any:
"""All live soccer scores (2-min delay on premium)."""
return _get_v2("/livescore/soccer")
def livescores_league(league_id: int) -> Any:
"""Live scores for a specific league."""
return _get_v2(f"/livescore/{league_id}")
# ── All (reference data) ────────────────────────────────
def all_countries() -> Any:
"""All supported countries."""
return _get_v2("/all/countries")
def all_sports() -> Any:
"""All supported sports."""
return _get_v2("/all/sports")
def all_leagues() -> Any:
"""All supported leagues."""
return _get_v2("/all/leagues")
# ══════════════════════════════════════════════════════════
# V1 ENDPOINTS (fills V2 gaps: standings, H2H, events-by-date)
# ══════════════════════════════════════════════════════════
def v1_standings(league_id: int, season: str) -> Any:
"""
League table / standings.
Season format: '2025-2026' or '2026'.
Returns {'table': [...]}.
"""
return _get_v1("/lookuptable.php", {"l": league_id, "s": season})
def v1_events_by_date(date: str, sport: str = "Soccer") -> Any:
"""
All events on a date.
Date format: YYYY-MM-DD.
Returns {'events': [...]}.
"""
return _get_v1("/eventsday.php", {"d": date, "s": sport})
def v1_events_by_date_league(date: str, league_id: int) -> Any:
"""
Events on a date filtered by league.
Date format: YYYY-MM-DD.
"""
return _get_v1("/eventsday.php", {"d": date, "l": league_id})
def v1_event_results(event_id: int) -> Any:
"""Lookup a single event by ID."""
return _get_v1("/lookupevent.php", {"id": event_id})
def v1_search_teams(name: str) -> Any:
"""Search teams by name (V1). Returns {'teams': [...]}."""
return _get_v1("/searchteams.php", {"t": name})
def v1_search_players(name: str) -> Any:
"""Search players by name (V1). Returns {'player': [...]}."""
return _get_v1("/searchplayers.php", {"p": name})
def v1_lookup_team(team_id: int) -> Any:
"""Team detail by ID (V1). Returns {'teams': [...]}."""
return _get_v1("/lookupteam.php", {"id": team_id})
def v1_next_team(team_id: int) -> Any:
"""Next 5 events for a team (V1). Returns {'events': [...]}."""
return _get_v1("/eventsnext.php", {"id": team_id})
def v1_previous_team(team_id: int) -> Any:
"""Last 5 results for a team (V1). Returns {'results': [...]}."""
return _get_v1("/eventslast.php", {"id": team_id})
def v1_season_events(league_id: int, season: str) -> Any:
"""All events in a league season (V1). Returns {'events': [...]}."""
return _get_v1("/eventsseason.php", {"id": league_id, "s": season})

250
nike/sync.py Normal file
View File

@@ -0,0 +1,250 @@
"""
Data synchronisation logic.
Reusable by both scripts/ and the MCP sync_data tool.
"""
from __future__ import annotations
from nike import api_football, config
from nike.db import get_conn
def _upsert_league(cur, league_id: int, season: int) -> int:
cur.execute("""
INSERT INTO leagues (api_football_id, name, country, current_season, is_followed)
VALUES (%s, 'Major League Soccer', 'USA', %s, TRUE)
ON CONFLICT (api_football_id) DO UPDATE SET
current_season = EXCLUDED.current_season,
updated_at = NOW()
RETURNING id
""", (league_id, season))
return cur.fetchone()[0]
def _upsert_team(cur, t: dict, v: dict | None, *, followed: bool = False) -> int:
cur.execute("""
INSERT INTO teams (api_football_id, name, country, logo_url,
venue_name, venue_city, venue_capacity, is_followed)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (api_football_id) DO UPDATE SET
name = EXCLUDED.name,
logo_url = EXCLUDED.logo_url,
venue_name = EXCLUDED.venue_name,
is_followed = CASE WHEN EXCLUDED.is_followed THEN TRUE
ELSE teams.is_followed END,
updated_at = NOW()
RETURNING id
""", (
t['id'], t['name'], t.get('country'), t.get('logo'),
v.get('name') if v else None,
v.get('city') if v else None,
v.get('capacity') if v else None,
followed,
))
return cur.fetchone()[0]
def sync_team_data(team_search: str = config.TFC_SEARCH,
seasons: list[int] = config.SEASONS) -> dict:
"""
Find a team by name, then upsert league / team / squad / fixtures.
Returns a result dict with counts of records stored.
"""
results: dict = {
"team": None, "seasons": {}, "players": 0,
"squad": [], "all_fixtures": [],
"errors": [], "api_calls": 0,
}
# 1. Find team
teams = api_football.search_teams(team_search)
results["api_calls"] += 1
tfc = None
for item in teams:
t = item['team']
if 'Toronto' in t['name']:
tfc = item
break
if not tfc:
# fallback: fetch all teams in MLS and scan for Toronto
teams2 = api_football.get_teams_by_league(config.MLS_LEAGUE_ID)
results["api_calls"] += 1
for item in teams2:
if 'Toronto' in item['team']['name']:
tfc = item
break
if not tfc:
results["errors"].append("Toronto FC not found in API")
return results
tfc_team = tfc['team']
tfc_venue = tfc.get('venue', {})
results["team"] = tfc_team['name']
with get_conn() as conn:
cur = conn.cursor()
league_db_id = _upsert_league(cur, config.MLS_LEAGUE_ID, max(seasons))
tfc_db_id = _upsert_team(cur, tfc_team, tfc_venue, followed=True)
# 2. Squad
squad = api_football.get_squad(tfc_team['id'])
results["api_calls"] += 1
for p in squad:
cur.execute("""
INSERT INTO players (api_football_id, name, position,
shirt_number, current_team_id, photo_url)
VALUES (%s, %s, %s, %s, %s, %s)
ON CONFLICT (api_football_id) DO UPDATE SET
name = EXCLUDED.name,
position = EXCLUDED.position,
shirt_number = EXCLUDED.shirt_number,
current_team_id = EXCLUDED.current_team_id,
photo_url = EXCLUDED.photo_url,
updated_at = NOW()
""", (p['id'], p['name'], p.get('position'),
p.get('number'), tfc_db_id, p.get('photo')))
results["players"] = len(squad)
results["squad"] = squad
# 3. Fixtures per season
for season in seasons:
fixtures = []
try:
fixtures = api_football.get_fixtures(
tfc_team['id'], season, config.MLS_LEAGUE_ID)
results["api_calls"] += 1
except Exception as e:
results["errors"].append(f"Fixtures {season}: {e}")
results["seasons"][season] = 0
continue
count = 0
for f in fixtures:
fix = f['fixture']
teams = f['teams']
goals = f['goals']
lg = f['league']
home, away = teams['home'], teams['away']
for team_data in [home, away]:
cur.execute("""
INSERT INTO teams (api_football_id, name, country, logo_url)
VALUES (%s, %s, 'USA', %s)
ON CONFLICT (api_football_id) DO NOTHING
""", (team_data['id'], team_data['name'], team_data.get('logo')))
cur.execute("SELECT id FROM teams WHERE api_football_id = %s", (home['id'],))
home_db = cur.fetchone()[0]
cur.execute("SELECT id FROM teams WHERE api_football_id = %s", (away['id'],))
away_db = cur.fetchone()[0]
cur.execute("""
INSERT INTO fixtures
(api_football_id, league_id, season, round, match_date,
venue_name, venue_city, status, elapsed_minutes,
home_team_id, away_team_id, home_goals, away_goals)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (api_football_id) DO UPDATE SET
status = EXCLUDED.status,
home_goals = EXCLUDED.home_goals,
away_goals = EXCLUDED.away_goals,
elapsed_minutes = EXCLUDED.elapsed_minutes,
updated_at = NOW()
""", (
fix['id'], league_db_id, season, lg.get('round'), fix['date'],
fix['venue'].get('name') if fix.get('venue') else None,
fix['venue'].get('city') if fix.get('venue') else None,
fix['status']['short'], fix['status'].get('elapsed'),
home_db, away_db, goals.get('home'), goals.get('away'),
))
count += 1
hg = goals.get('home')
ag = goals.get('away')
score = f"{hg}-{ag}" if hg is not None and ag is not None else ""
results["all_fixtures"].append({
"date": fix["date"],
"home": home["name"],
"away": away["name"],
"status": fix["status"]["short"],
"score": score,
"venue": (fix.get("venue") or {}).get("name") or "TBD",
"round": lg.get("round", ""),
})
results["seasons"][season] = count
# 4. Followed entity
cur.execute("""
INSERT INTO followed_entities
(entity_type, entity_id, api_football_id, priority)
VALUES ('team', %s, %s, 'high')
ON CONFLICT DO NOTHING
""", (tfc_db_id, tfc_team['id']))
cur.close()
return results
def sync_standings(league_id: int, season: int) -> dict:
"""Sync current MLS standings into the DB."""
rows = api_football.get_standings(league_id, season)
inserted = 0
with get_conn() as conn:
cur = conn.cursor()
for entry in rows:
# Field names vary by API — try common variants
team_data = (entry.get('team') or
{'id': entry.get('teamid') or entry.get('team_id'),
'name': entry.get('teamname') or entry.get('name', ''),
'logo': entry.get('logo')})
# Nested stats may live under 'all', 'overall', or at top level
stats = entry.get('all') or entry.get('overall') or entry
team_api_id = team_data.get('id') or team_data.get('teamid')
if not team_api_id:
continue
# Ensure team exists
cur.execute("""
INSERT INTO teams (api_football_id, name, country, logo_url)
VALUES (%s, %s, 'USA', %s)
ON CONFLICT (api_football_id) DO NOTHING
""", (team_api_id, team_data.get('name', ''), team_data.get('logo')))
cur.execute("SELECT id FROM leagues WHERE api_football_id = %s", (league_id,))
league_row = cur.fetchone()
if not league_row:
continue
league_db_id = league_row[0]
cur.execute("SELECT id FROM teams WHERE api_football_id = %s", (team_api_id,))
team_row = cur.fetchone()
if not team_row:
continue
team_db_id = team_row[0]
played = (stats.get('played') or stats.get('gp') or
stats.get('games_played') or 0)
wins = (stats.get('win') or stats.get('wins') or stats.get('w') or 0)
draws = (stats.get('draw') or stats.get('draws') or stats.get('d') or 0)
losses = (stats.get('lose') or stats.get('losses') or stats.get('l') or 0)
goals = stats.get('goals') or {}
gf = goals.get('for') or stats.get('goals_for') or 0
ga = goals.get('against') or stats.get('goals_against') or 0
cur.execute("""
INSERT INTO standings
(league_id, season, team_id, rank, points, played,
wins, draws, losses, goals_for, goals_against,
goal_difference, form)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT DO NOTHING
""", (
league_db_id, season, team_db_id,
entry.get('rank') or entry.get('position'),
entry.get('points') or entry.get('pts'),
played, wins, draws, losses, gf, ga,
entry.get('goalsDiff') or entry.get('goal_difference') or (gf - ga),
entry.get('form', ''),
))
inserted += 1
cur.close()
return {"standings_inserted": inserted}

View File

@@ -0,0 +1,472 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nike — Football Data Platform</title>
<!-- Bootstrap 5 -->
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
crossorigin="anonymous" />
<!-- Bootstrap Icons -->
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
crossorigin="anonymous" />
<style>
body { background-color: #0d111a; }
/* Top nav */
.navbar-brand .badge { font-size: .6rem; vertical-align: middle; }
/* Status cards */
.status-card { border-left: 4px solid transparent; transition: border-color .3s; }
.status-card.ok { border-left-color: #198754; }
.status-card.fail { border-left-color: #dc3545; }
.status-card.unknown { border-left-color: #6c757d; }
.dot {
display:inline-block; width:10px; height:10px;
border-radius:50%; margin-right: 6px;
}
.dot.green { background:#198754; box-shadow: 0 0 6px #198754; animation: pulse 2s infinite; }
.dot.red { background:#dc3545; }
.dot.grey { background:#6c757d; }
@keyframes pulse {
0%,100% { opacity:1; }
50% { opacity:.4; }
}
/* Uptime badge */
.uptime { font-size:.75rem; color:#adb5bd; }
/* Table tweaks */
.table-sm td, .table-sm th { font-size: .83rem; }
.log-table { max-height: 340px; overflow-y: auto; }
/* Quota bar */
.quota-label { font-size: .75rem; color: #adb5bd; }
/* Tool badge */
.tool-readonly { background: #0d6efd22; color: #6ea8fe; }
.tool-write { background: #dc354522; color: #ea868f; }
/* Refreshing spinner */
#refreshSpinner { display: none; }
/* Toast */
#syncToast { min-width: 300px; }
</style>
</head>
<body>
<!-- ── Navbar ─────────────────────────────────────────── -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark border-bottom border-secondary">
<div class="container-fluid">
<a class="navbar-brand fw-bold" href="/">
<i class="bi bi-trophy-fill text-warning me-2"></i>Nike
<span class="badge bg-secondary ms-2">Football Data Platform</span>
</a>
<div class="ms-auto d-flex align-items-center gap-3">
<span class="uptime" id="uptimeDisplay"></span>
<span id="refreshSpinner" class="spinner-border spinner-border-sm text-secondary"></span>
<button class="btn btn-sm btn-outline-secondary" onclick="refreshStatus()">
<i class="bi bi-arrow-clockwise"></i> Refresh
</button>
</div>
</div>
</nav>
<!-- ── Toast ─────────────────────────────────────────── -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index:11">
<div id="syncToast" class="toast align-items-center" role="alert">
<div class="d-flex">
<div class="toast-body" id="syncToastBody">Sync started…</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
</div>
<div class="container-fluid py-4 px-4">
<!-- ── Row 1: Status Cards ─────────────────────────── -->
<div class="row g-3 mb-4">
<!-- Database -->
<div class="col-md-4">
<div class="card status-card unknown h-100" id="cardDb">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="card-subtitle text-muted mb-1 text-uppercase" style="font-size:.7rem;letter-spacing:.08em">
Database
</h6>
<h5 class="card-title mb-0">
<span class="dot grey" id="dotDb"></span>
<span id="dbStatus">Checking…</span>
</h5>
</div>
<i class="bi bi-database-fill fs-2 text-secondary opacity-25"></i>
</div>
<hr class="my-2" />
<div class="row row-cols-2 g-0 small text-muted">
<div>Host</div> <div id="dbHost" class="text-end text-light"></div>
<div>Latency</div> <div id="dbLatency" class="text-end text-light"></div>
<div>Version</div> <div id="dbVersion" class="text-end text-light" style="font-size:.72rem"></div>
<div>Status</div> <div id="dbMsg" class="text-end text-light"></div>
</div>
</div>
</div>
</div>
<!-- API-Football -->
<div class="col-md-4">
<div class="card status-card unknown h-100" id="cardApi">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="card-subtitle text-muted mb-1 text-uppercase" style="font-size:.7rem;letter-spacing:.08em">
API-Football
</h6>
<h5 class="card-title mb-0">
<span class="dot grey" id="dotApi"></span>
<span id="apiStatus">Checking…</span>
</h5>
</div>
<i class="bi bi-globe2 fs-2 text-secondary opacity-25"></i>
</div>
<hr class="my-2" />
<div class="row row-cols-2 g-0 small text-muted mb-2">
<div>Latency</div> <div id="apiLatency" class="text-end text-light"></div>
<div>Quota used</div> <div id="apiQuota" class="text-end text-light"></div>
</div>
<div class="quota-label mb-1">Daily quota</div>
<div class="progress" style="height:6px;" title="Remaining API calls">
<div id="quotaBar" class="progress-bar bg-success" style="width:100%"></div>
</div>
</div>
</div>
</div>
<!-- MCP Server -->
<div class="col-md-4">
<div class="card status-card ok h-100" id="cardMcp">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="card-subtitle text-muted mb-1 text-uppercase" style="font-size:.7rem;letter-spacing:.08em">
MCP Server
</h6>
<h5 class="card-title mb-0">
<span class="dot green"></span>Running
</h5>
</div>
<i class="bi bi-cpu-fill fs-2 text-secondary opacity-25"></i>
</div>
<hr class="my-2" />
<div class="row row-cols-2 g-0 small text-muted">
<div>Transport</div> <div id="mcpTransport" class="text-end text-light"></div>
<div>Endpoint</div> <div id="mcpEndpoint" class="text-end text-light" style="font-size:.7rem;word-break:break-all"></div>
<div>Uptime</div> <div id="mcpUptime" class="text-end text-light"></div>
<div>Tools</div> <div id="mcpTools" class="text-end text-light"></div>
</div>
</div>
</div>
</div>
</div><!-- /Row 1 -->
<!-- ── Row 2: Data Summary + Sync ─────────────────── -->
<div class="row g-3 mb-4">
<!-- Data summary -->
<div class="col-md-8">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-table me-2 text-info"></i>Database Contents</span>
<span class="badge bg-secondary" id="lastSyncBadge">Last sync: —</span>
</div>
<div class="card-body p-0">
<table class="table table-sm table-hover table-striped mb-0 text-nowrap">
<thead class="table-dark">
<tr>
<th class="ps-3">Table</th>
<th class="text-end pe-3">Rows</th>
<th class="text-end pe-3">Status</th>
</tr>
</thead>
<tbody id="tableCountsBody">
<tr><td colspan="3" class="text-center text-muted py-3">Loading…</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Sync control -->
<div class="col-md-4">
<div class="card h-100">
<div class="card-header">
<i class="bi bi-arrow-repeat me-2 text-warning"></i>Data Sync
</div>
<div class="card-body d-flex flex-column justify-content-between">
<div>
<p class="text-muted small mb-3">
Triggers a fresh pull from <strong>API-Football</strong> for Toronto FC —
both 2025 and 2026 seasons. Uses approx. 4 of your 100 daily quota calls.
</p>
<ul class="small text-muted ps-3 mb-3">
<li>Squad (players/squads)</li>
<li>Fixtures 2025 (fixtures)</li>
<li>Fixtures 2026 (fixtures)</li>
<li>Team &amp; league upsert</li>
</ul>
</div>
<div>
<button id="syncBtn" class="btn btn-warning w-100 fw-bold" onclick="triggerSync()">
<i class="bi bi-cloud-download-fill me-2"></i>Sync TFC Data
</button>
<div id="syncResult" class="mt-2 small"></div>
</div>
</div>
</div>
</div>
</div><!-- /Row 2 -->
<!-- ── Row 3: Tools + Request Log ──────────────────── -->
<div class="row g-3">
<!-- MCP Tools -->
<div class="col-md-5">
<div class="card h-100">
<div class="card-header">
<i class="bi bi-tools me-2 text-primary"></i>MCP Tools
</div>
<div class="card-body p-0">
<table class="table table-sm table-hover mb-0">
<thead class="table-dark">
<tr>
<th class="ps-3">Tool</th>
<th>Description</th>
<th class="text-center">Type</th>
</tr>
</thead>
<tbody id="toolsBody">
<tr><td colspan="3" class="text-center text-muted py-3">Loading…</td></tr>
</tbody>
</table>
</div>
<div class="card-footer text-muted small">
MCP endpoint: <code id="mcpEndpointFooter"></code>
</div>
</div>
</div>
<!-- Request Log -->
<div class="col-md-7">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-journal-text me-2 text-success"></i>Request Log</span>
<span class="badge bg-dark border border-secondary" id="logCount">0 entries</span>
</div>
<div class="card-body p-0 log-table">
<table class="table table-sm table-hover mb-0 text-nowrap">
<thead class="table-dark sticky-top">
<tr>
<th class="ps-3" style="width:130px">Time</th>
<th>Tool</th>
<th>Args</th>
<th class="text-end pe-3">ms</th>
</tr>
</thead>
<tbody id="logBody">
<tr><td colspan="4" class="text-center text-muted py-3">No requests yet</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div><!-- /Row 3 -->
</div><!-- /container -->
<!-- ── Scripts ────────────────────────────────────────── -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous"></script>
<script>
const toast = new bootstrap.Toast(document.getElementById('syncToast'));
function setDot(id, state) {
const d = document.getElementById(id);
d.className = `dot ${state}`;
}
function setCard(id, state) {
const c = document.getElementById(id);
c.className = `card status-card h-100 ${state}`;
}
function setText(id, val) {
const el = document.getElementById(id);
if (el) el.textContent = val ?? '—';
}
async function refreshStatus() {
document.getElementById('refreshSpinner').style.display = 'inline-block';
try {
const [status, logs] = await Promise.all([
fetch('/api/status').then(r => r.json()),
fetch('/api/logs?limit=50').then(r => r.json()),
]);
renderStatus(status);
renderLogs(logs.logs || []);
} catch (e) {
console.error('Status fetch failed:', e);
} finally {
document.getElementById('refreshSpinner').style.display = 'none';
}
}
function renderStatus(s) {
// ── Database ──
const db = s.database;
if (db.connected) {
setDot('dotDb', 'green');
setCard('cardDb', 'ok');
setText('dbStatus', 'Connected');
setText('dbHost', db.host || 'portia.incus');
setText('dbLatency', db.latency_ms != null ? db.latency_ms + ' ms' : '—');
setText('dbVersion', db.version || '—');
setText('dbMsg', 'OK');
} else {
setDot('dotDb', 'red');
setCard('cardDb', 'fail');
setText('dbStatus', 'Offline');
setText('dbMsg', db.error || 'Connection failed');
setText('dbHost', '—');
setText('dbLatency', '—');
setText('dbVersion', '—');
}
// ── API ──
const apiv = s.api;
if (apiv.connected) {
setDot('dotApi', 'green');
setCard('cardApi', 'ok');
setText('apiStatus', 'Reachable');
setText('apiLatency', apiv.latency_ms != null ? apiv.latency_ms + ' ms' : '—');
const used = (apiv.quota_limit || 100) - (apiv.quota_remaining || 0);
const pct = Math.round((apiv.quota_remaining || 0) / (apiv.quota_limit || 100) * 100);
setText('apiQuota', `${used} / ${apiv.quota_limit || 100}`);
const bar = document.getElementById('quotaBar');
bar.style.width = pct + '%';
bar.className = `progress-bar ${pct > 40 ? 'bg-success' : pct > 15 ? 'bg-warning' : 'bg-danger'}`;
} else {
setDot('dotApi', 'red');
setCard('cardApi', 'fail');
setText('apiStatus', 'Unreachable');
setText('apiLatency', '—');
setText('apiQuota', '—');
}
// ── MCP ──
const mcp = s.mcp;
setText('mcpTransport', mcp.transport);
setText('mcpEndpoint', mcp.endpoint);
setText('mcpUptime', mcp.uptime);
setText('mcpTools', mcp.tool_count + ' tools');
setText('uptimeDisplay', 'Uptime: ' + mcp.uptime);
document.getElementById('mcpEndpointFooter').textContent = mcp.endpoint;
// ── Data counts ──
const counts = s.data.table_counts;
const lastSync = s.data.last_sync;
setText('lastSyncBadge', lastSync ? 'Last sync: ' + lastSync.slice(0, 19).replace('T', ' ') : 'Not synced');
const rows = Object.entries(counts).map(([tbl, cnt]) => {
const ok = cnt > 0;
return `<tr>
<td class="ps-3">${tbl}</td>
<td class="text-end pe-3 ${ok ? 'text-light' : 'text-muted'}">${cnt < 0 ? 'error' : cnt.toLocaleString()}</td>
<td class="text-end pe-3">${cnt > 0 ? '<i class="bi bi-check-circle-fill text-success"></i>' : '<span class="text-muted">—</span>'}</td>
</tr>`;
});
document.getElementById('tableCountsBody').innerHTML = rows.join('') || '<tr><td colspan="3" class="text-center text-muted">No data</td></tr>';
// ── Tools ──
const tools = s.tools || [];
const toolRows = tools.map(t => `
<tr>
<td class="ps-3"><code>${t.name}</code></td>
<td class="text-muted small">${t.description}</td>
<td class="text-center">
${t.readonly
? '<span class="badge tool-readonly">read-only</span>'
: '<span class="badge tool-write">write</span>'}
</td>
</tr>
`);
document.getElementById('toolsBody').innerHTML = toolRows.join('') || '<tr><td colspan="3" class="text-center text-muted">No tools</td></tr>';
}
function renderLogs(logs) {
document.getElementById('logCount').textContent = logs.length + ' entries';
if (!logs.length) {
document.getElementById('logBody').innerHTML =
'<tr><td colspan="4" class="text-center text-muted py-3">No requests yet</td></tr>';
return;
}
const rows = logs.map(l => {
const args = Object.entries(l.args || {})
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
.join(', ');
const ts = l.timestamp.slice(11, 19);
return `<tr>
<td class="ps-3 text-muted">${ts}</td>
<td><code>${l.tool}</code></td>
<td class="text-muted small">${args || '—'}</td>
<td class="text-end pe-3 ${l.duration_ms > 500 ? 'text-warning' : 'text-muted'}">${l.duration_ms}</td>
</tr>`;
});
document.getElementById('logBody').innerHTML = rows.join('');
}
async function triggerSync() {
const btn = document.getElementById('syncBtn');
const result = document.getElementById('syncResult');
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Syncing…';
result.textContent = '';
document.getElementById('syncToastBody').textContent = 'Sync started — this takes ~10 seconds…';
toast.show();
try {
const resp = await fetch('/api/sync', { method: 'POST' });
const data = await resp.json();
if (data.ok) {
const r = data.result;
result.innerHTML = `<span class="text-success">✅ Synced ${r.players} players, ${Object.values(r.seasons || {}).reduce((a,b)=>a+b,0)} fixtures in ${data.duration_s}s</span>`;
document.getElementById('syncToastBody').textContent = `Sync complete in ${data.duration_s}s`;
await refreshStatus();
} else {
result.innerHTML = `<span class="text-danger">❌ ${data.error}</span>`;
document.getElementById('syncToastBody').textContent = 'Sync failed: ' + data.error;
}
} catch (e) {
result.innerHTML = `<span class="text-danger">❌ Network error</span>`;
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-cloud-download-fill me-2"></i>Sync TFC Data';
}
}
// Initial load + auto-refresh every 30s
refreshStatus();
setInterval(refreshStatus, 30_000);
setInterval(() => fetch('/api/logs?limit=50').then(r=>r.json()).then(d=>renderLogs(d.logs||[])), 5_000);
</script>
</body>
</html>

30
pyproject.toml Normal file
View File

@@ -0,0 +1,30 @@
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "nike"
version = "0.1.0"
description = "Football data platform with MCP server"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.115",
"uvicorn[standard]>=0.30",
"fastmcp>=2.0",
"psycopg2-binary>=2.9",
"python-dotenv>=1.0",
"requests>=2.32",
"jinja2>=3.1",
]
[project.optional-dependencies]
dev = [
"httpx>=0.27", # for FastAPI test client
"pytest>=8",
"pytest-asyncio>=0.24",
]
[tool.setuptools.packages.find]
where = ["."]
include = ["nike*"]

15
run.py Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python3
"""
Nike — Football Data Platform
Launch with: python run.py
"""
import sys
import os
# Ensure the project root is on sys.path
sys.path.insert(0, os.path.dirname(__file__))
from nike.server import main
if __name__ == "__main__":
main()

194
schema.sql Normal file
View File

@@ -0,0 +1,194 @@
-- Nike Football Database Schema — TheSportsDB cache layer
--
-- Caching strategy:
-- PERMANENT: leagues, teams, players (change rarely, cache aggressively)
-- SEMI-PERMANENT: finished match results, lineups, stats, timeline
-- VOLATILE (NOT cached): standings, live scores, upcoming fixtures
--
-- All IDs come from TheSportsDB (idLeague, idTeam, idEvent, idPlayer).
-- No surrogate keys — TheSportsDB IDs are the primary keys.
-- ══════════════════════════════════════════════════════════
-- Core reference tables (permanent cache)
-- ══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS leagues (
id INTEGER PRIMARY KEY, -- TheSportsDB idLeague
name VARCHAR(255) NOT NULL, -- strLeague
sport VARCHAR(50) DEFAULT 'Soccer', -- strSport
country VARCHAR(100), -- strCountry
badge_url VARCHAR(500), -- strBadge
banner_url VARCHAR(500), -- strBanner
description TEXT, -- strDescriptionEN
is_followed BOOLEAN DEFAULT FALSE,
cached_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS teams (
id INTEGER PRIMARY KEY, -- TheSportsDB idTeam
name VARCHAR(255) NOT NULL, -- strTeam
short_name VARCHAR(10), -- strTeamShort
alternate_name VARCHAR(255), -- strTeamAlternate
league_id INTEGER, -- idLeague (primary), no FK — cached opportunistically
league_name VARCHAR(255), -- strLeague
formed_year INTEGER, -- intFormedYear
sport VARCHAR(50) DEFAULT 'Soccer',
country VARCHAR(100), -- strCountry
stadium VARCHAR(255), -- strStadium
stadium_capacity INTEGER, -- intStadiumCapacity
location VARCHAR(255), -- strLocation
venue_id INTEGER, -- idVenue
badge_url VARCHAR(500), -- strBadge
logo_url VARCHAR(500), -- strLogo
banner_url VARCHAR(500), -- strBanner
equipment_url VARCHAR(500), -- strEquipment
colour1 VARCHAR(20), -- strColour1
colour2 VARCHAR(20), -- strColour2
colour3 VARCHAR(20), -- strColour3
website VARCHAR(255), -- strWebsite
description TEXT, -- strDescriptionEN
is_followed BOOLEAN DEFAULT FALSE,
cached_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS players (
id INTEGER PRIMARY KEY, -- TheSportsDB idPlayer
team_id INTEGER, -- idTeam, no FK — cached opportunistically
team_name VARCHAR(255), -- strTeam
name VARCHAR(255) NOT NULL, -- strPlayer
nationality VARCHAR(100), -- strNationality
date_of_birth DATE, -- dateBorn
position VARCHAR(50), -- strPosition
squad_number VARCHAR(10), -- strNumber
height VARCHAR(20), -- strHeight
weight VARCHAR(20), -- strWeight
gender VARCHAR(10), -- strGender
status VARCHAR(30), -- strStatus (Active/Retired)
thumb_url VARCHAR(500), -- strThumb
cutout_url VARCHAR(500), -- strCutout
description TEXT, -- strDescriptionEN
cached_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ══════════════════════════════════════════════════════════
-- Match results (cached when strStatus = 'Match Finished')
-- ══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY, -- TheSportsDB idEvent
name VARCHAR(255), -- strEvent
league_id INTEGER, -- idLeague, no FK — cached opportunistically
league_name VARCHAR(255), -- strLeague
season VARCHAR(20), -- strSeason
round VARCHAR(20), -- intRound
home_team_id INTEGER, -- idHomeTeam, no FK — cached opportunistically
away_team_id INTEGER, -- idAwayTeam, no FK — cached opportunistically
home_team VARCHAR(255), -- strHomeTeam
away_team VARCHAR(255), -- strAwayTeam
home_score INTEGER, -- intHomeScore
away_score INTEGER, -- intAwayScore
event_date DATE, -- dateEvent
event_time TIME, -- strTime
event_timestamp TIMESTAMPTZ, -- strTimestamp
venue VARCHAR(255), -- strVenue
venue_id INTEGER, -- idVenue
city VARCHAR(100), -- strCity
country VARCHAR(100), -- strCountry
referee VARCHAR(255), -- strOfficial
spectators INTEGER, -- intSpectators
status VARCHAR(50), -- strStatus
postponed VARCHAR(5) DEFAULT 'no', -- strPostponed
poster_url VARCHAR(500), -- strPoster
thumb_url VARCHAR(500), -- strThumb
video_url VARCHAR(500), -- strVideo
cached_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ── Match detail sub-tables (V2, cached for finished matches) ──
CREATE TABLE IF NOT EXISTS event_stats (
id INTEGER PRIMARY KEY, -- TheSportsDB idStatistic
event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE,
id_api_football VARCHAR(20), -- idApiFootball
event_name VARCHAR(255), -- strEvent
stat_name VARCHAR(100) NOT NULL, -- strStat (e.g. "Shots on Goal")
home_value VARCHAR(50), -- intHome
away_value VARCHAR(50), -- intAway
UNIQUE(event_id, stat_name)
);
CREATE TABLE IF NOT EXISTS event_lineup (
id INTEGER PRIMARY KEY, -- TheSportsDB idLineup
event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE,
id_api_football VARCHAR(20), -- idAPIfootball
event_name VARCHAR(255), -- strEvent
player_id INTEGER, -- idPlayer
player_name VARCHAR(255), -- strPlayer
team_id INTEGER, -- idTeam
team_name VARCHAR(255), -- strTeam
is_home VARCHAR(5), -- strHome ("Yes"/"No")
position VARCHAR(50), -- strPosition
position_short VARCHAR(5), -- strPositionShort (G/D/M/F)
formation VARCHAR(20), -- strFormation
squad_number INTEGER, -- intSquadNumber
is_substitute VARCHAR(5) DEFAULT 'No', -- strSubstitute ("Yes"/"No")
cutout_url VARCHAR(500), -- strCutout
country VARCHAR(100), -- strCountry
season VARCHAR(20), -- strSeason
UNIQUE(event_id, player_id)
);
CREATE TABLE IF NOT EXISTS event_timeline (
id INTEGER PRIMARY KEY, -- TheSportsDB idTimeline
event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE,
id_api_football VARCHAR(20), -- idAPIfootball
event_name VARCHAR(255), -- strEvent
event_type VARCHAR(100), -- strTimeline ("Goal", "subst", "Yellow Card")
event_detail VARCHAR(255), -- strTimelineDetail ("Substitution 1")
is_home VARCHAR(5), -- strHome ("Yes"/"No")
minute INTEGER, -- intTime
period VARCHAR(20), -- strPeriod
player_id INTEGER, -- idPlayer
player_name VARCHAR(255), -- strPlayer
cutout_url VARCHAR(500), -- strCutout
assist_id INTEGER, -- idAssist
assist_name VARCHAR(255), -- strAssist
team_id INTEGER, -- idTeam
team_name VARCHAR(255), -- strTeam
comment TEXT, -- strComment
event_date DATE, -- dateEvent
season VARCHAR(20), -- strSeason
UNIQUE(event_id, minute, player_id, event_type)
);
-- ══════════════════════════════════════════════════════════
-- Cache management
-- ══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS cache_meta (
cache_key VARCHAR(255) PRIMARY KEY, -- e.g. "standings:4346:2026"
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ttl_seconds INTEGER NOT NULL DEFAULT 3600,
data_json JSONB -- optional: store raw response
);
-- ══════════════════════════════════════════════════════════
-- Indexes
-- ══════════════════════════════════════════════════════════
CREATE INDEX IF NOT EXISTS idx_teams_league ON teams(league_id);
CREATE INDEX IF NOT EXISTS idx_teams_followed ON teams(is_followed) WHERE is_followed;
CREATE INDEX IF NOT EXISTS idx_players_team ON players(team_id);
CREATE INDEX IF NOT EXISTS idx_events_date ON events(event_date);
CREATE INDEX IF NOT EXISTS idx_events_home_team ON events(home_team_id);
CREATE INDEX IF NOT EXISTS idx_events_away_team ON events(away_team_id);
CREATE INDEX IF NOT EXISTS idx_events_league ON events(league_id);
CREATE INDEX IF NOT EXISTS idx_events_status ON events(status);
CREATE INDEX IF NOT EXISTS idx_event_stats_event ON event_stats(event_id);
CREATE INDEX IF NOT EXISTS idx_event_lineup_event ON event_lineup(event_id);
CREATE INDEX IF NOT EXISTS idx_event_tl_event ON event_timeline(event_id);
CREATE INDEX IF NOT EXISTS idx_cache_meta_fetched ON cache_meta(fetched_at);

49
scripts/apply_schema.py Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Apply Nike schema to Portia PostgreSQL."""
import os
import sys
from dotenv import load_dotenv
import psycopg2
load_dotenv('/home/robert/gitea/nike/.env')
try:
conn = psycopg2.connect(
host=os.getenv('DB_HOST'),
port=int(os.getenv('DB_PORT', 5432)),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD'),
dbname='nike',
)
except Exception as e:
print(f"❌ Cannot connect to DB: {e}")
sys.exit(1)
conn.autocommit = True
cur = conn.cursor()
schema_path = os.path.join(os.path.dirname(__file__), '..', 'schema.sql')
with open(schema_path, 'r') as f:
sql = f.read()
try:
cur.execute(sql)
print("✅ Schema applied successfully.")
except Exception as e:
print(f"❌ Schema error: {e}")
cur.close()
conn.close()
sys.exit(1)
cur.execute("""
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' ORDER BY table_name;
""")
tables = cur.fetchall()
print(f" {len(tables)} tables in public schema:")
for t in tables:
print(f"{t[0]}")
cur.close()
conn.close()

327
scripts/discover_api.py Normal file
View File

@@ -0,0 +1,327 @@
#!/usr/bin/env python3
"""
API Discovery Script — walk the RapidAPI top-down, save raw responses.
Workflow:
1. Search for leagues (MLS, Premier League) → get league IDs
2. Search for teams (Toronto FC, Arsenal) → get team IDs
3. Get league matches → find recent past + upcoming event IDs
4. Get match detail, score, stats, location for a finished match
5. Get match highlights (goals, cards, events)
6. Get lineups for a finished match
7. Get squad roster + sample player detail
8. Get standings
Saves every response to docs/api_samples/{step}_{endpoint}.json
Cost: ~18 API calls
"""
import sys
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from nike import rapidapi as rapi
SAMPLES_DIR = Path(__file__).resolve().parent.parent / "docs" / "api_samples"
SAMPLES_DIR.mkdir(parents=True, exist_ok=True)
def save(name: str, data) -> None:
"""Save raw API response to a JSON file."""
path = SAMPLES_DIR / f"{name}.json"
with open(path, "w") as f:
json.dump(data, f, indent=2, default=str)
size_kb = path.stat().st_size / 1024
print(f" ✓ Saved {path.name} ({size_kb:.1f} KB)")
def extract_id(data, label="item") -> int | None:
"""Try to pull an ID from the first search result."""
resp = data.get("response") if isinstance(data, dict) else None
# Handle nested response structures
items = []
if isinstance(resp, dict):
for key in ("suggestions", "teams", "leagues", "players", "matches"):
if key in resp and isinstance(resp[key], list) and resp[key]:
items = resp[key]
break
if not items:
items = [resp]
elif isinstance(resp, list):
items = resp
else:
# Try alternate envelope keys
for key in ("data", "result"):
val = data.get(key)
if isinstance(val, list) and val:
items = val
break
for item in items:
if isinstance(item, dict):
eid = item.get("id") or item.get("primaryId")
if eid:
name = item.get("name") or item.get("title") or "?"
print(f"{label}: {name} (ID: {eid})")
return int(eid)
print(f" ⚠ Could not extract ID from {label} response")
# Dump top-level keys for debugging
if isinstance(data, dict):
print(f" Top-level keys: {list(data.keys())}")
if isinstance(resp, dict):
print(f" response keys: {list(resp.keys())}")
return None
def find_event_ids(data, limit=2):
"""Extract event IDs from a matches/fixtures response."""
resp = data.get("response") if isinstance(data, dict) else None
events = {"past": [], "upcoming": []}
items = []
if isinstance(resp, dict):
for key in ("matches", "events", "fixtures", "allMatches"):
if key in resp and isinstance(resp[key], list):
items = resp[key]
break
if not items:
# Flatten if resp itself contains sub-lists
for key, val in resp.items():
if isinstance(val, list) and val and isinstance(val[0], dict):
items = val
break
elif isinstance(resp, list):
items = resp
else:
for key in ("data", "result"):
val = data.get(key)
if isinstance(val, list):
items = val
break
for item in items:
if not isinstance(item, dict):
continue
eid = item.get("id") or item.get("eventId") or item.get("primaryId")
if not eid:
continue
# Detect finished vs upcoming
status = item.get("status", {})
if isinstance(status, dict):
finished = (status.get("finished", False) or
status.get("short") == "FT" or
status.get("type") == "finished")
elif isinstance(status, str):
finished = status.lower() in ("ft", "aet", "pen", "finished")
else:
finished = False
bucket = "past" if finished else "upcoming"
events[bucket].append(int(eid))
return {k: v[:limit] for k, v in events.items()}
def main():
print("=" * 60)
print(" Nike API Discovery — Mapping Response Structures")
print("=" * 60)
# ══════════════════════════════════════════════════════
# STEP 1: League discovery
# ══════════════════════════════════════════════════════
print("\n[1/8] Searching for leagues...")
mls = rapi.search_leagues("MLS")
save("01_search_leagues_mls", mls)
mls_id = extract_id(mls, "MLS")
epl = rapi.search_leagues("Premier League")
save("01_search_leagues_epl", epl)
epl_id = extract_id(epl, "Premier League")
popular = rapi.get_popular_leagues()
save("01_popular_leagues", popular)
# ══════════════════════════════════════════════════════
# STEP 2: Team discovery
# ══════════════════════════════════════════════════════
print("\n[2/8] Searching for teams...")
tfc = rapi.search_teams("Toronto FC")
save("02_search_teams_tfc", tfc)
tfc_id = extract_id(tfc, "Toronto FC")
ars = rapi.search_teams("Arsenal")
save("02_search_teams_arsenal", ars)
ars_id = extract_id(ars, "Arsenal")
# ══════════════════════════════════════════════════════
# STEP 3: League matches (fixtures)
# ══════════════════════════════════════════════════════
print("\n[3/8] Fetching league matches...")
mls_events = {"past": [], "upcoming": []}
if mls_id:
mls_matches = rapi.get_league_matches(mls_id)
save("03_league_matches_mls", mls_matches)
mls_events = find_event_ids(mls_matches)
print(f" → Events found: {len(mls_events['past'])} past, "
f"{len(mls_events['upcoming'])} upcoming")
if not mls_events["past"] and not mls_events["upcoming"]:
# Dump structure hints to help debug
resp = mls_matches.get("response")
if isinstance(resp, dict):
print(f" response keys: {list(resp.keys())}")
for k, v in resp.items():
if isinstance(v, list) and v:
print(f" response.{k}[0] keys: "
f"{list(v[0].keys()) if isinstance(v[0], dict) else type(v[0])}")
elif isinstance(resp, list) and resp:
print(f" response[0] keys: "
f"{list(resp[0].keys()) if isinstance(resp[0], dict) else type(resp[0])}")
else:
print(" ⚠ No MLS ID, skipping")
# ══════════════════════════════════════════════════════
# STEP 4: Match detail (finished match)
# ══════════════════════════════════════════════════════
print("\n[4/8] Fetching match detail...")
event_id = None
if mls_events["past"]:
event_id = mls_events["past"][0]
elif mls_events["upcoming"]:
event_id = mls_events["upcoming"][0]
if event_id:
print(f" Using event ID: {event_id}")
detail = rapi.get_match_detail(event_id)
save("04_match_detail", detail)
score = rapi.get_match_score(event_id)
save("04_match_score", score)
status = rapi.get_match_status(event_id)
save("04_match_status", status)
location = rapi.get_match_location(event_id)
save("04_match_location", location)
else:
print(" ⚠ No event ID found — skipping match detail")
# ══════════════════════════════════════════════════════
# STEP 5: Match stats + highlights (goals, cards, events)
# ══════════════════════════════════════════════════════
print("\n[5/8] Fetching match stats & highlights...")
if event_id:
stats = rapi.get_match_stats(event_id)
save("05_match_stats", stats)
highlights = rapi.get_match_highlights(event_id)
save("05_match_highlights", highlights)
referee = rapi.get_match_referee(event_id)
save("05_match_referee", referee)
else:
print(" ⚠ Skipping (no event ID)")
# ══════════════════════════════════════════════════════
# STEP 6: Lineups
# ══════════════════════════════════════════════════════
print("\n[6/8] Fetching lineups...")
if event_id:
home_lineup = rapi.get_home_lineup(event_id)
save("06_lineup_home", home_lineup)
away_lineup = rapi.get_away_lineup(event_id)
save("06_lineup_away", away_lineup)
else:
print(" ⚠ Skipping (no event ID)")
# ══════════════════════════════════════════════════════
# STEP 7: Squad & player detail
# ══════════════════════════════════════════════════════
print("\n[7/8] Fetching squad & player detail...")
player_id = None
if tfc_id:
squad = rapi.get_squad(tfc_id)
save("07_squad_tfc", squad)
# Extract a player ID from the squad response
resp = squad.get("response") if isinstance(squad, dict) else None
if isinstance(resp, list) and resp:
for p in resp:
if isinstance(p, dict):
pid = p.get("id") or p.get("primaryId")
if pid:
player_id = int(pid)
print(f" → Sample player: {p.get('name', '?')} (ID: {player_id})")
break
elif isinstance(resp, dict):
# May be nested under squad/players/roster
for key in ("squad", "players", "roster", "members"):
if key in resp and isinstance(resp[key], list):
for p in resp[key]:
if isinstance(p, dict):
pid = p.get("id") or p.get("primaryId")
if pid:
player_id = int(pid)
print(f" → Sample player: {p.get('name', '?')} "
f"(ID: {player_id})")
break
break
if not player_id:
print(f" response keys: {list(resp.keys())}")
if player_id:
player = rapi.get_player_detail(player_id)
save("07_player_detail", player)
else:
print(" ⚠ Could not find a player ID in squad response")
else:
print(" ⚠ No TFC ID, skipping")
# ══════════════════════════════════════════════════════
# STEP 8: Standings
# ══════════════════════════════════════════════════════
print("\n[8/8] Fetching standings...")
if mls_id:
standings = rapi.get_standings(mls_id)
save("08_standings_mls", standings)
# ══════════════════════════════════════════════════════
# Summary
# ══════════════════════════════════════════════════════
print("\n" + "=" * 60)
files = sorted(SAMPLES_DIR.glob("*.json"))
print(f" Saved {len(files)} response samples to docs/api_samples/")
for f in files:
size_kb = f.stat().st_size / 1024
print(f" {f.name:.<50} {size_kb:.1f} KB")
print("=" * 60)
print("\n Discovered IDs:")
print(f" MLS league ID : {mls_id}")
print(f" EPL league ID : {epl_id}")
print(f" Toronto FC team ID : {tfc_id}")
print(f" Arsenal team ID : {ars_id}")
if event_id:
print(f" Sample event ID : {event_id}")
if player_id:
print(f" Sample player ID : {player_id}")
print()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,383 @@
#!/usr/bin/env python3
"""
TheSportsDB API Discovery — walk the API top-down, save raw responses.
Uses V1 free key ('3') for basic structure validation.
With a premium key, also tests V2 endpoints.
Workflow:
1. Search for leagues (MLS, Premier League) → IDs
2. Search for teams (Toronto FC, Arsenal) → IDs
3. Get team schedules (next + previous matches) → event IDs
4. Get match detail, stats, lineup, timeline for a finished match
5. Get squad roster + sample player detail
6. Get standings (V1)
7. Get events by date (V1)
8. Get livescores
Saves every response to docs/api_samples/sportsdb/{step}_{endpoint}.json
"""
import sys
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from nike import config
from nike import sportsdb as api
SAMPLES_DIR = Path(__file__).resolve().parent.parent / "docs" / "api_samples" / "sportsdb"
SAMPLES_DIR.mkdir(parents=True, exist_ok=True)
# Track API calls
_call_count = 0
def save(name: str, data) -> None:
"""Save raw API response to a JSON file."""
global _call_count
_call_count += 1
path = SAMPLES_DIR / f"{name}.json"
with open(path, "w") as f:
json.dump(data, f, indent=2, default=str)
size_kb = path.stat().st_size / 1024
print(f" ✓ Saved {path.name} ({size_kb:.1f} KB)")
def show_keys(data, label="response"):
"""Print top-level structure of a response."""
if isinstance(data, dict):
for k, v in data.items():
if isinstance(v, list):
print(f" {label}.{k}: list ({len(v)} items)")
if v and isinstance(v[0], dict):
print(f" [0] keys: {list(v[0].keys())}")
elif isinstance(v, dict):
print(f" {label}.{k}: dict ({len(v)} keys)")
elif v is None:
print(f" {label}.{k}: null")
else:
print(f" {label}.{k}: {str(v)[:80]}")
elif isinstance(data, list):
print(f" {label}: list ({len(data)} items)")
if data and isinstance(data[0], dict):
print(f" [0] keys: {list(data[0].keys())}")
elif data is None:
print(f" {label}: null")
def extract_items(data: dict, key: str) -> list:
"""Safely extract a list from a response dict."""
items = data.get(key)
return items if isinstance(items, list) else []
def main():
is_premium = config.SPORTSDB_KEY not in ('3', '')
print("=" * 60)
print(" TheSportsDB API Discovery")
print(f" Key: {'Premium' if is_premium else 'Free (V1 only)'}")
print("=" * 60)
# ══════════════════════════════════════════════════════
# STEP 1: Search for leagues (V1 free works for search)
# ══════════════════════════════════════════════════════
print("\n[1/8] Searching for leagues...")
mls_id = None
epl_id = None
if is_premium:
# V2 search
mls = api.search_leagues("MLS")
save("01_v2_search_leagues_mls", mls)
show_keys(mls)
items = extract_items(mls, "search")
if items:
mls_id = items[0].get("idLeague")
print(f" → MLS: {items[0].get('strLeague')} (ID: {mls_id})")
epl = api.search_leagues("Premier League")
save("01_v2_search_leagues_epl", epl)
items = extract_items(epl, "search")
if items:
epl_id = items[0].get("idLeague")
print(f" → EPL: {items[0].get('strLeague')} (ID: {epl_id})")
else:
print(" (V2 search requires premium — using V1 search)")
# Also try V1 search (works on free key)
mls_v1 = api.v1_search_teams("Toronto FC")
save("01_v1_search_teams_tfc", mls_v1)
show_keys(mls_v1)
teams = extract_items(mls_v1, "teams")
tfc_id = None
if teams:
tfc_id = teams[0].get("idTeam")
mls_id = mls_id or teams[0].get("idLeague")
print(f" → Toronto FC: ID={tfc_id}, league={teams[0].get('strLeague')} "
f"(leagueID={teams[0].get('idLeague')})")
ars_v1 = api.v1_search_teams("Arsenal")
save("01_v1_search_teams_arsenal", ars_v1)
teams = extract_items(ars_v1, "teams")
ars_id = None
if teams:
ars_id = teams[0].get("idTeam")
epl_id = epl_id or teams[0].get("idLeague")
print(f" → Arsenal: ID={ars_id}, league={teams[0].get('strLeague')} "
f"(leagueID={teams[0].get('idLeague')})")
# ══════════════════════════════════════════════════════
# STEP 2: Team detail
# ══════════════════════════════════════════════════════
print("\n[2/8] Fetching team details...")
if tfc_id:
tfc_detail = api.v1_lookup_team(int(tfc_id))
save("02_v1_team_detail_tfc", tfc_detail)
teams = extract_items(tfc_detail, "teams")
if teams:
t = teams[0]
print(f"{t.get('strTeam')}: {t.get('strStadium')}, "
f"{t.get('strStadiumLocation')}")
print(f" Keys: {list(t.keys())[:15]}...")
# ══════════════════════════════════════════════════════
# STEP 3: Schedule — next + previous matches
# ══════════════════════════════════════════════════════
print("\n[3/8] Fetching team schedules...")
event_id = None
if tfc_id:
# V1 previous (last 5)
prev = api.v1_previous_team(int(tfc_id))
save("03_v1_previous_tfc", prev)
results = extract_items(prev, "results")
print(f" Previous matches: {len(results)}")
for m in results[:3]:
eid = m.get("idEvent")
home = m.get("strHomeTeam", "?")
away = m.get("strAwayTeam", "?")
hscore = m.get("intHomeScore", "?")
ascore = m.get("intAwayScore", "?")
date = m.get("dateEvent", "?")
print(f" {date} {home} {hscore}-{ascore} {away} (eventID: {eid})")
if not event_id and hscore is not None:
event_id = int(eid)
# V1 next (next 5)
nxt = api.v1_next_team(int(tfc_id))
save("03_v1_next_tfc", nxt)
events = extract_items(nxt, "events")
print(f" Upcoming matches: {len(events)}")
for m in events[:3]:
home = m.get("strHomeTeam", "?")
away = m.get("strAwayTeam", "?")
date = m.get("dateEvent", "?")
print(f" {date} {home} vs {away}")
if is_premium and tfc_id:
# V2 schedule endpoints
prev_v2 = api.schedule_previous_team(int(tfc_id))
save("03_v2_previous_tfc", prev_v2)
show_keys(prev_v2)
next_v2 = api.schedule_next_team(int(tfc_id))
save("03_v2_next_tfc", next_v2)
show_keys(next_v2)
# ══════════════════════════════════════════════════════
# STEP 4: Match detail (V2 lookup for a finished match)
# ══════════════════════════════════════════════════════
print("\n[4/8] Fetching match detail...")
if event_id:
print(f" Using event ID: {event_id}")
# V1 event lookup (works on free)
ev1 = api.v1_event_results(event_id)
save("04_v1_event_detail", ev1)
events = extract_items(ev1, "events")
if events:
e = events[0]
print(f" V1 event keys: {list(e.keys())[:15]}...")
print(f"{e.get('strHomeTeam')} {e.get('intHomeScore')}-"
f"{e.get('intAwayScore')} {e.get('strAwayTeam')}")
print(f" Venue: {e.get('strVenue')}, Referee: {e.get('strOfficial')}")
if is_premium:
# V2 event detail
ev2 = api.lookup_event(event_id)
save("04_v2_event_detail", ev2)
show_keys(ev2)
# V2 stats
stats = api.lookup_event_stats(event_id)
save("04_v2_event_stats", stats)
show_keys(stats)
stat_items = extract_items(stats, "lookup")
if stat_items:
print(f" Stats sample:")
for s in stat_items[:5]:
print(f" {s.get('strStat')}: "
f"Home={s.get('intHome')} Away={s.get('intAway')}")
# V2 timeline
timeline = api.lookup_event_timeline(event_id)
save("04_v2_event_timeline", timeline)
show_keys(timeline)
tl_items = extract_items(timeline, "lookup")
if tl_items:
print(f" Timeline events: {len(tl_items)}")
for t in tl_items[:5]:
print(f" {t.get('intTime')}' {t.get('strTimeline')}: "
f"{t.get('strPlayer')} ({t.get('strTeam')})")
# V2 lineup
lineup = api.lookup_event_lineup(event_id)
save("04_v2_event_lineup", lineup)
show_keys(lineup)
lineup_items = extract_items(lineup, "lookup")
if lineup_items:
print(f" Lineup entries: {len(lineup_items)}")
for p in lineup_items[:3]:
print(f" #{p.get('intSquadNumber')} {p.get('strPlayer')} "
f"({p.get('strPosition')}) "
f"home={p.get('strHome')} sub={p.get('strSubstitute')}")
else:
print(" ⚠ No event ID found — skipping")
# ══════════════════════════════════════════════════════
# STEP 5: Squad roster + player detail
# ══════════════════════════════════════════════════════
print("\n[5/8] Fetching squad & player detail...")
player_id = None
if is_premium and tfc_id:
squad = api.list_players(int(tfc_id))
save("05_v2_squad_tfc", squad)
show_keys(squad)
players = extract_items(squad, "list")
if players:
p = players[0]
player_id = p.get("idPlayer")
print(f" Squad size: {len(players)}")
print(f" Sample player: {p.get('strPlayer')} "
f"(#{p.get('strNumber')}, {p.get('strPosition')})")
print(f" Player keys: {list(p.keys())[:15]}...")
if player_id:
pdetail = api.lookup_player(int(player_id))
save("05_v2_player_detail", pdetail)
show_keys(pdetail)
else:
# V1 player search
players_v1 = api.v1_search_players("Bernardeschi")
save("05_v1_search_player", players_v1)
show_keys(players_v1)
items = extract_items(players_v1, "player")
if items:
player_id = items[0].get("idPlayer")
print(f"{items[0].get('strPlayer')} (ID: {player_id})")
print(f" Keys: {list(items[0].keys())[:15]}...")
# ══════════════════════════════════════════════════════
# STEP 6: Standings (V1)
# ══════════════════════════════════════════════════════
print("\n[6/8] Fetching standings...")
if epl_id:
# EPL should have current season data
standings = api.v1_standings(int(epl_id), "2025-2026")
save("06_v1_standings_epl", standings)
table = extract_items(standings, "table")
print(f" EPL standings: {len(table)} teams")
if table:
print(f" [0] keys: {list(table[0].keys())}")
for row in table[:3]:
print(f" #{row.get('intRank')} {row.get('strTeam')} "
f"P:{row.get('intPlayed')} W:{row.get('intWin')} "
f"D:{row.get('intDraw')} L:{row.get('intLoss')} "
f"Pts:{row.get('intPoints')}")
if mls_id:
standings_mls = api.v1_standings(int(mls_id), "2026")
save("06_v1_standings_mls", standings_mls)
table = extract_items(standings_mls, "table")
print(f" MLS standings: {len(table)} teams")
if table:
for row in table[:3]:
print(f" #{row.get('intRank')} {row.get('strTeam')} "
f"Pts:{row.get('intPoints')}")
# ══════════════════════════════════════════════════════
# STEP 7: Events by date (V1)
# ══════════════════════════════════════════════════════
print("\n[7/8] Fetching events by date...")
events_today = api.v1_events_by_date("2026-03-09")
save("07_v1_events_today", events_today)
ev_list = extract_items(events_today, "events")
print(f" Soccer events today: {len(ev_list)}")
if ev_list:
print(f" [0] keys: {list(ev_list[0].keys())[:15]}...")
for e in ev_list[:3]:
print(f" {e.get('strLeague')}: {e.get('strHomeTeam')} vs "
f"{e.get('strAwayTeam')} ({e.get('strStatus')})")
if mls_id:
mls_events = api.v1_events_by_date_league("2026-03-09", int(mls_id))
save("07_v1_events_today_mls", mls_events)
ev_list = extract_items(mls_events, "events")
print(f" MLS events today: {len(ev_list)}")
for e in ev_list[:5]:
print(f" {e.get('strHomeTeam')} vs {e.get('strAwayTeam')} "
f"({e.get('strStatus')})")
# ══════════════════════════════════════════════════════
# STEP 8: Livescores (V2 premium only)
# ══════════════════════════════════════════════════════
print("\n[8/8] Fetching livescores...")
if is_premium:
live = api.livescores_soccer()
save("08_v2_livescores", live)
show_keys(live)
games = extract_items(live, "livescore")
print(f" Live soccer matches: {len(games)}")
for g in games[:3]:
print(f" {g.get('strHomeTeam')} {g.get('intHomeScore')}-"
f"{g.get('intAwayScore')} {g.get('strAwayTeam')} "
f"({g.get('strStatus')} {g.get('strProgress')}')")
else:
print(" (Livescores require premium key)")
# ══════════════════════════════════════════════════════
# Summary
# ══════════════════════════════════════════════════════
print("\n" + "=" * 60)
files = sorted(SAMPLES_DIR.glob("*.json"))
print(f" Saved {len(files)} response samples to docs/api_samples/sportsdb/")
for f in files:
size_kb = f.stat().st_size / 1024
print(f" {f.name:.<55} {size_kb:.1f} KB")
print("=" * 60)
print(f"\n API calls made: {_call_count}")
print(f" Key type: {'Premium (V1+V2)' if is_premium else 'Free (V1 only)'}")
print(f"\n Discovered IDs:")
print(f" MLS league ID : {mls_id}")
print(f" EPL league ID : {epl_id}")
print(f" Toronto FC team ID : {tfc_id}")
print(f" Arsenal team ID : {ars_id}")
if event_id:
print(f" Sample event ID : {event_id}")
if player_id:
print(f" Sample player ID : {player_id}")
print()
if __name__ == "__main__":
main()

103
scripts/pull_tfc.py Normal file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
Pull Toronto FC squad and fixtures and store in Portia DB.
Delegates all API calls and DB writes to nike.sync.sync_team_data()
so this script stays thin and the real logic lives in one place.
"""
import sys
from pathlib import Path
# Make sure the project root is on the path when run directly
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from datetime import date
from nike import config, db
from nike.sync import sync_team_data
def main() -> None:
if not config.API_FOOTBALL_KEY:
print("❌ API_FOOTBALL_KEY not set in .env")
sys.exit(1)
print("=" * 60)
print(" NIKE — Toronto FC Data Pull")
print(f" API: {config.API_FOOTBALL_BASE}")
print("=" * 60)
db.create_pool()
try:
results = sync_team_data(
team_search=config.TFC_SEARCH,
seasons=config.SEASONS,
)
finally:
db.close_pool()
# ── Summary ──────────────────────────────────────────
if results["errors"]:
print("\n⚠️ Errors encountered:")
for e in results["errors"]:
print(f"{e}")
print(f"\n✅ Team: {results.get('team', 'unknown')}")
print(f" Players: {results['players']}")
print(f" API calls: {results['api_calls']}")
for season, count in results.get("seasons", {}).items():
print(f" Fixtures {season}: {count}")
if results.get("squad"):
_print_squad(results["squad"])
if results.get("all_fixtures"):
_print_fixtures(results["all_fixtures"])
def _print_squad(squad: list[dict]) -> None:
print("\n" + "=" * 60)
print(" SQUAD")
print("=" * 60)
pos_order = ["Goalkeeper", "Defender", "Midfielder", "Attacker"]
by_pos: dict = {}
for p in squad:
by_pos.setdefault(p.get("position") or "Unknown", []).append(p)
for pos in pos_order + [k for k in by_pos if k not in pos_order]:
if pos not in by_pos:
continue
print(f"\n {pos.upper()}S:")
for p in sorted(by_pos[pos],
key=lambda x: x["number"] if isinstance(x.get("number"), int) else 99):
print(f" #{str(p.get('number', '-')).rjust(2)} {p['name']}")
def _print_fixtures(fixtures: list[dict]) -> None:
print("\n" + "=" * 60)
print(" FIXTURES")
print("=" * 60)
today = date.today().isoformat()
today_f = [f for f in fixtures if f["date"][:10] == today]
completed = [f for f in fixtures if f["status"] in ("FT", "AET", "PEN")]
upcoming = [f for f in fixtures if f["status"] in ("NS", "TBD", "PST")]
if today_f:
print("\n 🔴 TODAY:")
for f in today_f:
score = f" {f['score']}" if f.get("score") else ""
print(f" {f['home']}{score} {f['away']} @ {f['venue']} [{f['status']}]")
if completed:
print("\n RECENT RESULTS (last 5):")
for f in completed[-5:]:
print(f" {f['date'][:10]} {f['home']} {f['score']} {f['away']}")
if upcoming:
print("\n UPCOMING (next 5):")
for f in sorted(upcoming, key=lambda x: x["date"])[:5]:
print(f" {f['date'][:10]} {f['home']} vs {f['away']} [{f.get('round', '')}]")
if __name__ == "__main__":
main()

75
scripts/test_api.py Normal file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
Test API connectivity, find Toronto FC, and list popular leagues
so we can confirm the correct MLS league ID for this API.
Uses 2 API quota calls.
"""
import json
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from nike import config, api_football
def main() -> None:
if not config.API_FOOTBALL_KEY:
print("❌ API_FOOTBALL_KEY not set in .env")
sys.exit(1)
print(f"API: {config.API_FOOTBALL_BASE}\n")
# ── 1. Popular leagues (connectivity probe + league ID discovery) ──
print("── Popular Leagues ──────────────────────────")
try:
t0 = time.time()
data = api_football._get("football-popular-leagues", timeout=8)
latency_ms = round((time.time() - t0) * 1000, 1)
except Exception as e:
print(f"❌ Not connected: {e}")
sys.exit(1)
print(f"✅ Connected latency={latency_ms}ms "
f"quota_remaining={api_football.last_quota_remaining()}")
leagues = (data.get('response', {}).get('popular') or
data.get('response', {}).get('leagues') or [])
if not isinstance(leagues, list):
print(f" Raw response structure:\n{json.dumps(data, indent=2)[:1000]}")
else:
print(f" {len(leagues)} league(s) returned:")
for lg in leagues:
lg_id = lg.get('id', '?')
lg_name = lg.get('name', str(lg))
ccode = lg.get('ccode', '')
print(f" [{str(lg_id):>5}] {lg_name} ({ccode})")
print(f"\n ↳ Current config.MLS_LEAGUE_ID = {config.MLS_LEAGUE_ID}")
print(" Update nike/config.py if the ID above doesn't match MLS.\n")
# ── 2. Search for Toronto FC ───────────────────────────────────
print("── Search: Toronto ──────────────────────────")
raw_search = api_football._get("football-teams-search", {"search": "Toronto"})
print(f" quota_remaining={api_football.last_quota_remaining()}")
# Try all common envelope keys; fall back to full raw dump
raw_list = (raw_search.get('response', {}).get('suggestions') or
raw_search.get('response', {}).get('teams') or
raw_search.get('data') or raw_search.get('result'))
if not isinstance(raw_list, list) or not raw_list:
print(f" 0 results — raw response:\n{json.dumps(raw_search, indent=2)[:1500]}")
else:
teams = [api_football._normalise_team_item(t)
for t in raw_list if t.get('type', 'team') == 'team']
print(f" {len(teams)} team result(s):")
for item in teams:
t = item['team']
v = item.get('venue') or {}
print(f" [{str(t.get('id')):>6}] {t.get('name')} ({t.get('country')})")
if v.get('name'):
print(f" Venue: {v.get('name')}, {v.get('city')} cap={v.get('capacity')}")
if __name__ == "__main__":
main()

36
scripts/test_db.py Normal file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""Test connection to Portia PostgreSQL."""
import os
import sys
import time
from dotenv import load_dotenv
import psycopg2
load_dotenv('/home/robert/gitea/nike/.env')
try:
t0 = time.time()
conn = psycopg2.connect(
host=os.getenv('DB_HOST'),
port=int(os.getenv('DB_PORT', 5432)),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD'),
dbname='nike',
connect_timeout=5,
)
latency_ms = round((time.time() - t0) * 1000, 1)
cur = conn.cursor()
cur.execute('SELECT version();')
version = cur.fetchone()[0]
cur.execute("SELECT current_database(), current_user;")
db, user = cur.fetchone()
cur.close()
conn.close()
print(f"✅ Connected in {latency_ms}ms")
print(f" Database : {db}")
print(f" User : {user}")
print(f" Version : {version.split(',')[0]}")
except Exception as e:
print(f"❌ Connection failed: {e}")
sys.exit(1)

107
scripts/test_rapidapi.py Normal file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Smoke test for the RapidAPI (free-api-live-football-data) backend.
Verifies connectivity, finds MLS, fetches standings and TFC squad.
Run: python scripts/test_rapidapi.py
"""
import sys
import json
from pathlib import Path
# Ensure project root is on the path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from nike import config
from nike import rapidapi as rapi
def _pp(label: str, data) -> None:
"""Pretty-print a section."""
print(f"\n{'='*60}")
print(f" {label}")
print(f"{'='*60}")
print(json.dumps(data, indent=2, default=str)[:3000]) # trim for readability
def main():
if not config.RAPIDAPI_KEY:
print("ERROR: RAPIDAPI_KEY is not set in .env")
sys.exit(1)
print(f"RapidAPI key: {config.RAPIDAPI_KEY[:8]}...{config.RAPIDAPI_KEY[-4:]}")
print(f"Base URL: {config.RAPIDAPI_BASE}")
# 1. Connectivity
print("\n1) Checking connectivity...")
status = rapi.check_connection()
print(f" Connected: {status['connected']}")
if not status["connected"]:
print(f" Error: {status.get('error')}")
sys.exit(1)
print(f" Latency: {status['latency_ms']} ms")
# 2. Search for MLS
print("\n2) Searching for 'MLS'...")
mls_data = rapi.search_leagues("MLS")
_pp("MLS search results", mls_data)
# Try to extract league ID
mls_id = None
resp = mls_data.get("response") if isinstance(mls_data, dict) else None
if isinstance(resp, list):
for item in resp:
if isinstance(item, dict):
mls_id = item.get("id") or item.get("primaryId")
if mls_id:
mls_id = int(mls_id)
break
print(f"\n MLS League ID: {mls_id}")
# 3. Standings
if mls_id:
print("\n3) Fetching MLS standings...")
standings = rapi.get_standings(mls_id)
_pp("MLS Standings", standings)
# 4. Search for Toronto FC
print("\n4) Searching for 'Toronto FC'...")
tfc_data = rapi.search_teams("Toronto FC")
_pp("TFC search results", tfc_data)
tfc_id = None
resp = tfc_data.get("response") if isinstance(tfc_data, dict) else None
if isinstance(resp, list):
for item in resp:
if isinstance(item, dict):
tfc_id = item.get("id") or item.get("primaryId")
if tfc_id:
tfc_id = int(tfc_id)
break
print(f"\n TFC Team ID: {tfc_id}")
# 5. Squad
if tfc_id:
print("\n5) Fetching TFC squad...")
squad = rapi.get_squad(tfc_id)
_pp("TFC Squad", squad)
# 6. Live matches (just check it works)
print("\n6) Checking live matches endpoint...")
live = rapi.get_live_matches()
resp = live.get("response") if isinstance(live, dict) else None
count = len(resp) if isinstance(resp, list) else 0
print(f" Live matches right now: {count}")
# 7. Trending news
print("\n7) Fetching trending news...")
news_data = rapi.get_trending_news()
_pp("Trending News", news_data)
print("\n" + "="*60)
print(" ALL CHECKS PASSED")
print("="*60)
if __name__ == "__main__":
main()

71
scripts/verify_db.py Normal file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Quick verification of Nike DB contents."""
import os
from dotenv import load_dotenv
import psycopg2
load_dotenv('/home/robert/gitea/nike/.env')
conn = psycopg2.connect(
host=os.getenv('DB_HOST'),
port=int(os.getenv('DB_PORT', 5432)),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD'),
dbname='nike',
)
cur = conn.cursor()
tables = [
'leagues', 'teams', 'players', 'fixtures', 'standings',
'match_stats', 'match_events', 'player_season_stats',
'player_match_stats', 'followed_entities',
]
print("Nike DB Contents")
print("-" * 40)
for table in tables:
cur.execute(f"SELECT COUNT(*) FROM {table}")
count = cur.fetchone()[0]
status = "" if count > 0 else ""
print(f" {status} {table:<28} {count:>6} rows")
# TFC summary
print()
cur.execute("""
SELECT t.name, COUNT(p.id) AS players
FROM teams t
LEFT JOIN players p ON p.current_team_id = t.id
WHERE t.is_followed = TRUE
GROUP BY t.name
""")
for row in cur.fetchall():
print(f"{row[0]}: {row[1]} players in roster")
cur.execute("""
SELECT COUNT(*) FROM fixtures f
JOIN teams t ON (t.id = f.home_team_id OR t.id = f.away_team_id)
WHERE t.is_followed = TRUE
""")
fix_count = cur.fetchone()[0]
print(f" 📅 Followed team fixtures: {fix_count}")
cur.execute("""
SELECT match_date::date, home.name, away.name, home_goals, away_goals, status
FROM fixtures f
JOIN teams home ON home.id = f.home_team_id
JOIN teams away ON away.id = f.away_team_id
JOIN teams ft ON (ft.id = f.home_team_id OR ft.id = f.away_team_id)
WHERE ft.is_followed = TRUE AND f.match_date >= NOW()
ORDER BY f.match_date ASC
LIMIT 3
""")
upcoming = cur.fetchall()
if upcoming:
print()
print(" Next fixtures:")
for row in upcoming:
print(f" {row[0]} {row[1]} vs {row[2]} ({row[5]})")
cur.close()
conn.close()