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:
185
.gitignore
vendored
185
.gitignore
vendored
@@ -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/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# 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
|
||||
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
*.log
|
||||
.env
|
||||
|
||||
198
README.md
198
README.md
@@ -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.
|
||||
|
||||
77
docs/api_samples/01_popular_leagues.json
Normal file
77
docs/api_samples/01_popular_leagues.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
175
docs/api_samples/01_search_leagues_epl.json
Normal file
175
docs/api_samples/01_search_leagues_epl.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
21
docs/api_samples/01_search_leagues_mls.json
Normal file
21
docs/api_samples/01_search_leagues_mls.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
79
docs/api_samples/02_search_teams_arsenal.json
Normal file
79
docs/api_samples/02_search_teams_arsenal.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
31
docs/api_samples/02_search_teams_tfc.json
Normal file
31
docs/api_samples/02_search_teams_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
13735
docs/api_samples/03_league_matches_epl.json
Normal file
13735
docs/api_samples/03_league_matches_epl.json
Normal file
File diff suppressed because it is too large
Load Diff
6
docs/api_samples/03_league_matches_mls.json
Normal file
6
docs/api_samples/03_league_matches_mls.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"status": "success",
|
||||
"response": {
|
||||
"matches": []
|
||||
}
|
||||
}
|
||||
18703
docs/api_samples/03_matches_by_date_20260308.json
Normal file
18703
docs/api_samples/03_matches_by_date_20260308.json
Normal file
File diff suppressed because it is too large
Load Diff
4
docs/api_samples/04_match_detail.json
Normal file
4
docs/api_samples/04_match_detail.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/04_match_detail_tfc.json
Normal file
4
docs/api_samples/04_match_detail_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/04_match_location.json
Normal file
4
docs/api_samples/04_match_location.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/04_match_location_tfc.json
Normal file
4
docs/api_samples/04_match_location_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/04_match_score.json
Normal file
4
docs/api_samples/04_match_score.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/04_match_score_tfc.json
Normal file
4
docs/api_samples/04_match_score_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/04_match_status.json
Normal file
4
docs/api_samples/04_match_status.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/04_match_status_tfc.json
Normal file
4
docs/api_samples/04_match_status_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/05_match_highlights_tfc.json
Normal file
4
docs/api_samples/05_match_highlights_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/05_match_referee_tfc.json
Normal file
4
docs/api_samples/05_match_referee_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
4
docs/api_samples/05_match_stats_tfc.json
Normal file
4
docs/api_samples/05_match_stats_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
3
docs/api_samples/06_lineup_away_tfc.json
Normal file
3
docs/api_samples/06_lineup_away_tfc.json
Normal 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"
|
||||
}
|
||||
4
docs/api_samples/06_lineup_home_tfc.json
Normal file
4
docs/api_samples/06_lineup_home_tfc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
827
docs/api_samples/07_squad_tfc.json
Normal file
827
docs/api_samples/07_squad_tfc.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
4
docs/api_samples/08_standings_mls.json
Normal file
4
docs/api_samples/08_standings_mls.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"message": "Request Failed Please try Again"
|
||||
}
|
||||
3
docs/api_samples/09_head_to_head_tfc.json
Normal file
3
docs/api_samples/09_head_to_head_tfc.json
Normal 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"
|
||||
}
|
||||
347
docs/api_samples/extra_get_standing_all_47.json
Normal file
347
docs/api_samples/extra_get_standing_all_47.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
67
docs/api_samples/extra_get_top_players_by_goals_47.json
Normal file
67
docs/api_samples/extra_get_top_players_by_goals_47.json
Normal 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)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
docs/api_samples/extra_get_trendingnews_.json
Normal file
62
docs/api_samples/extra_get_trendingnews_.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
70
docs/api_samples/sportsdb/01_v1_search_teams_arsenal.json
Normal file
70
docs/api_samples/sportsdb/01_v1_search_teams_arsenal.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
70
docs/api_samples/sportsdb/01_v1_search_teams_tfc.json
Normal file
70
docs/api_samples/sportsdb/01_v1_search_teams_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
docs/api_samples/sportsdb/01_v2_search_leagues_epl.json
Normal file
3
docs/api_samples/sportsdb/01_v2_search_leagues_epl.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Message": "No data found"
|
||||
}
|
||||
13
docs/api_samples/sportsdb/01_v2_search_leagues_mls.json
Normal file
13
docs/api_samples/sportsdb/01_v2_search_leagues_mls.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
70
docs/api_samples/sportsdb/02_v1_team_detail_tfc.json
Normal file
70
docs/api_samples/sportsdb/02_v1_team_detail_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
484
docs/api_samples/sportsdb/03_v1_next_tfc.json
Normal file
484
docs/api_samples/sportsdb/03_v1_next_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
244
docs/api_samples/sportsdb/03_v1_previous_tfc.json
Normal file
244
docs/api_samples/sportsdb/03_v1_previous_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
294
docs/api_samples/sportsdb/03_v2_next_tfc.json
Normal file
294
docs/api_samples/sportsdb/03_v2_next_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
294
docs/api_samples/sportsdb/03_v2_previous_tfc.json
Normal file
294
docs/api_samples/sportsdb/03_v2_previous_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
52
docs/api_samples/sportsdb/04_v1_event_detail.json
Normal file
52
docs/api_samples/sportsdb/04_v1_event_detail.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
52
docs/api_samples/sportsdb/04_v2_event_detail.json
Normal file
52
docs/api_samples/sportsdb/04_v2_event_detail.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
docs/api_samples/sportsdb/04_v2_event_lineup.json
Normal file
3
docs/api_samples/sportsdb/04_v2_event_lineup.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Message": "No data found"
|
||||
}
|
||||
3
docs/api_samples/sportsdb/04_v2_event_stats.json
Normal file
3
docs/api_samples/sportsdb/04_v2_event_stats.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Message": "No data found"
|
||||
}
|
||||
3
docs/api_samples/sportsdb/04_v2_event_timeline.json
Normal file
3
docs/api_samples/sportsdb/04_v2_event_timeline.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Message": "No data found"
|
||||
}
|
||||
19
docs/api_samples/sportsdb/05_v1_search_player.json
Normal file
19
docs/api_samples/sportsdb/05_v1_search_player.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
75
docs/api_samples/sportsdb/05_v2_player_detail.json
Normal file
75
docs/api_samples/sportsdb/05_v2_player_detail.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
235
docs/api_samples/sportsdb/05_v2_squad_tfc.json
Normal file
235
docs/api_samples/sportsdb/05_v2_squad_tfc.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
424
docs/api_samples/sportsdb/06_v1_standings_epl.json
Normal file
424
docs/api_samples/sportsdb/06_v1_standings_epl.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
1264
docs/api_samples/sportsdb/06_v1_standings_mls.json
Normal file
1264
docs/api_samples/sportsdb/06_v1_standings_mls.json
Normal file
File diff suppressed because it is too large
Load Diff
5476
docs/api_samples/sportsdb/07_v1_events_today.json
Normal file
5476
docs/api_samples/sportsdb/07_v1_events_today.json
Normal file
File diff suppressed because it is too large
Load Diff
3
docs/api_samples/sportsdb/07_v1_events_today_mls.json
Normal file
3
docs/api_samples/sportsdb/07_v1_events_today_mls.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"events": null
|
||||
}
|
||||
290
docs/api_samples/sportsdb/08_v2_livescores.json
Normal file
290
docs/api_samples/sportsdb/08_v2_livescores.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
403
docs/api_samples/sportsdb/09_v2_event_lineup_real.json
Normal file
403
docs/api_samples/sportsdb/09_v2_event_lineup_real.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
166
docs/api_samples/sportsdb/09_v2_event_stats_real.json
Normal file
166
docs/api_samples/sportsdb/09_v2_event_stats_real.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
382
docs/api_samples/sportsdb/09_v2_event_timeline_real.json
Normal file
382
docs/api_samples/sportsdb/09_v2_event_timeline_real.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
260
docs/rapidapi-football-api-reference.md
Normal file
260
docs/rapidapi-football-api-reference.md
Normal 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.
|
||||
385
docs/sportsdb_api_v1_reference.md
Normal file
385
docs/sportsdb_api_v1_reference.md
Normal 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 5–15 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
|
||||
308
docs/sportsdb_api_v2_reference.md
Normal file
308
docs/sportsdb_api_v2_reference.md
Normal 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
17
nike.service
Normal 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
BIN
nike/.DS_Store
vendored
Normal file
Binary file not shown.
1
nike/__init__.py
Normal file
1
nike/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Nike football data platform."""
|
||||
199
nike/api_football.py
Normal file
199
nike/api_football.py
Normal 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
38
nike/config.py
Normal 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
686
nike/db.py
Normal 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
356
nike/rapidapi.py
Normal 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
790
nike/server.py
Normal 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
311
nike/sportsdb.py
Normal 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
250
nike/sync.py
Normal 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}
|
||||
472
nike/templates/dashboard.html
Normal file
472
nike/templates/dashboard.html
Normal 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 & 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
30
pyproject.toml
Normal 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
15
run.py
Normal 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
194
schema.sql
Normal 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
49
scripts/apply_schema.py
Normal 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
327
scripts/discover_api.py
Normal 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()
|
||||
383
scripts/discover_sportsdb.py
Normal file
383
scripts/discover_sportsdb.py
Normal 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
103
scripts/pull_tfc.py
Normal 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
75
scripts/test_api.py
Normal 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
36
scripts/test_db.py
Normal 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
107
scripts/test_rapidapi.py
Normal 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
71
scripts/verify_db.py
Normal 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()
|
||||
Reference in New Issue
Block a user