Cleanup code and prepare for AI usage
This commit is contained in:
Vendored
+1
-9
@@ -2,15 +2,7 @@
|
|||||||
"java.compile.nullAnalysis.mode": "automatic",
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
"java.configuration.updateBuildConfiguration": "interactive",
|
"java.configuration.updateBuildConfiguration": "interactive",
|
||||||
"java.test.config": {
|
"java.test.config": {
|
||||||
"envFile": "${workspaceFolder}/.env",
|
"envFile": "${workspaceFolder}/.env"
|
||||||
"env": {
|
|
||||||
"AWS_ACCESS_KEY_ID": "AKIA4G4WGB26FLCFCU4M",
|
|
||||||
"AWS_SECRET_ACCESS_KEY": "MZBrGLSXRRVR6Yza6n4NrZCoCkgibON3kpYCvNPH",
|
|
||||||
"FORUM_PASSWORD": "original",
|
|
||||||
"FORUM_USERNAME": "Julian",
|
|
||||||
"TLW_DATABASE_PASSWORD": "TippligaWuerzb_1",
|
|
||||||
"TLW_DATABASE_USERNAME": "d0144ddb"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"java.debug.settings.onBuildFailureProceed": true,
|
"java.debug.settings.onBuildFailureProceed": true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<!-- headroom:rtk-instructions -->
|
||||||
|
# RTK (Rust Token Killer) - Token-Optimized Commands
|
||||||
|
|
||||||
|
When running shell commands, **always prefix with `rtk`**. This reduces context
|
||||||
|
usage by 60-90% with zero behavior change. If rtk has no filter for a command,
|
||||||
|
it passes through unchanged — so it is always safe to use.
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
```bash
|
||||||
|
# Git (59-80% savings)
|
||||||
|
rtk git status rtk git diff rtk git log
|
||||||
|
|
||||||
|
# Files & Search (60-75% savings)
|
||||||
|
rtk ls <path> rtk read <file> rtk grep <pattern>
|
||||||
|
rtk find <pattern> rtk diff <file>
|
||||||
|
|
||||||
|
# Build (80-90% savings) — shows errors only
|
||||||
|
rtk ./gradlew build rtk ./gradlew compileJava
|
||||||
|
|
||||||
|
# Test (90-99% savings) — shows failures only
|
||||||
|
rtk ./gradlew test
|
||||||
|
|
||||||
|
# Analysis (70-90% savings)
|
||||||
|
rtk err <cmd> rtk log <file> rtk json <file>
|
||||||
|
rtk summary <cmd> rtk deps rtk env
|
||||||
|
|
||||||
|
# GitHub (26-87% savings)
|
||||||
|
rtk gh pr view <n> rtk gh run list rtk gh issue list
|
||||||
|
|
||||||
|
# Infrastructure (85% savings)
|
||||||
|
rtk docker ps rtk kubectl get rtk docker logs <c>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- In command chains, prefix each segment: `rtk git add . && rtk git commit -m "msg"`
|
||||||
|
- For debugging, use raw command without rtk prefix
|
||||||
|
- `rtk proxy <cmd>` runs command without filtering but tracks usage
|
||||||
|
<!-- /headroom:rtk-instructions -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TLW Database Tool — Agent Guide
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
Java CLI tool managing the Tippliga Würzburg football prediction league. Syncs match data from API-Football, manages MySQL database (phpBB forum), pushes Google Calendar events, sends WhatsApp reminders via OpenAI-generated messages, and uploads fixtures to S3.
|
||||||
|
|
||||||
|
## Build & Test
|
||||||
|
```bash
|
||||||
|
./gradlew build # compile + test + jar
|
||||||
|
./gradlew test # run all tests
|
||||||
|
./gradlew clean test # clean build + test
|
||||||
|
./gradlew jar # build fat JAR (all deps bundled)
|
||||||
|
./gradlew dependencies # list dependency tree
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run Modes (CLI)
|
||||||
|
```
|
||||||
|
-m MatchdaysUpdater - Update delivery dates from match times
|
||||||
|
-m MatchesCreatorFootball - Create match schedule from API-Football
|
||||||
|
-m MatchesUpdaterFootball - Update match team/datetime from API-Football
|
||||||
|
-m MatchesResultsUpdater - Push match results to website
|
||||||
|
-m TeamsUpdater - Add missing teams to DB
|
||||||
|
-m APIFootballUpdater - Fetch fixtures/rounds from API-Football → S3
|
||||||
|
-m MatchesListGistUpdater - Update forum posts with fixture lists
|
||||||
|
-m PostChecksum - Print checksum of a config post
|
||||||
|
-m WhatsAppNotifier - Send WhatsApp reminders for missing bets
|
||||||
|
```
|
||||||
|
Required: `-s <season> -l <league> -c <configFile>`
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
```
|
||||||
|
App.java (CLI entry)
|
||||||
|
├── apifootball/ — API-Football HTTP client, match model, S3 caching
|
||||||
|
├── googlecalendar/ — Google Calendar API CRUD for matchday events
|
||||||
|
├── teamidmatcher/ — BiMap: Tippliga team ID ↔ API-Football team ID
|
||||||
|
├── tippliga/ — Domain models & operations (match, matchday, team, league)
|
||||||
|
├── tippligaforum/ — MySQL DB connector, phpBB forum post management
|
||||||
|
└── whatsapp/ — OpenAI-generated reminders, WhatsApp API sender
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Conventions
|
||||||
|
- Package: `de.jeyp91.*`
|
||||||
|
- SQL columns referenced by ordinal position (constants like `final int SEASON = 1`)
|
||||||
|
- Logger via SLF4J: `LoggerFactory.getLogger(Class.class)`
|
||||||
|
- Config stored as JSON in phpBB forum posts (not local files)
|
||||||
|
- API keys / secrets in environment variables
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- JUnit 4 + TestNG
|
||||||
|
- Tests in `src/test/java/de/jeyp91/`
|
||||||
|
- Run single test: `./gradlew test --tests "de.jeyp91.apifootball.APIFootballMatchTest"`
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# TLW Database Tool
|
||||||
|
|
||||||
|
CLI tool for **Tippliga Würzburg** football prediction league administration. Syncs match data from [API-Football](https://www.api-football.com/), manages a MySQL database backing a phpBB forum, pushes Google Calendar events, sends WhatsApp reminders with AI-generated messages via OpenAI, and uploads fixture data to S3.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
App.java (CLI entry with argparse4j)
|
||||||
|
├── apifootball/ — API-Football HTTP client, match model, S3 caching
|
||||||
|
│ ├── APIFootballUpdater Fetch raw fixtures/rounds → S3
|
||||||
|
│ ├── APIFootballConnector Singleton, reads cached S3 data, resolves matchdays
|
||||||
|
│ ├── APIFootballMatch API-Football match model (extends BaseMatch)
|
||||||
|
│ └── APIFootballMatchesProvider Resolves config entries to match lists
|
||||||
|
├── googlecalendar/ — Google Calendar API CRUD
|
||||||
|
│ ├── GoogleCalendarConnector OAuth2 + service setup
|
||||||
|
│ ├── CalendarConfigProvider Reads calendar ID/URL from JSON resource
|
||||||
|
│ └── TippligaGoogleEventManager Create/update/delete matchday events
|
||||||
|
├── teamidmatcher/ — Bi-directional ID mapping
|
||||||
|
│ ├── TeamIDMatcher HashBiMap: Tippliga team ID ↔ API-Football ID
|
||||||
|
│ └── TeamIDMatcherTemplateCreator Generates config template from API teams
|
||||||
|
├── tippliga/ — Domain models & operations
|
||||||
|
│ ├── TLWMatch / TLWMatchday / TLWTeam / TLWLeague (DB-mapped models)
|
||||||
|
│ ├── TLWMatchesCreatorFootball Build match schedule from API data
|
||||||
|
│ ├── TLWMatchesCreatorTipperLeague Build tipper-vs-tipper league matches
|
||||||
|
│ ├── TLWMatchesCreatorTipperPokal Build knockout tournament matches
|
||||||
|
│ ├── TLWMatchesUpdaterFootball Update teams/datetimes from API
|
||||||
|
│ ├── TLWMatchesResultsUpdater Push finished match results to website
|
||||||
|
│ ├── TLWMatchdaysCreator Compute delivery dates from match times
|
||||||
|
│ ├── TLWMatchdaysUpdater Write delivery date updates to DB + Calendar
|
||||||
|
│ ├── TLWTeamsCreator / TLWTeamsUpdater Team management
|
||||||
|
│ └── TLWMatchesManagerBase Shared helpers (date math, match matching)
|
||||||
|
├── tippligaforum/ — MySQL + phpBB integration
|
||||||
|
│ ├── TippligaSQLConnector Singleton JDBC connector (all queries)
|
||||||
|
│ ├── TippligaConfigProvider Reads JSON config from forum posts
|
||||||
|
│ ├── TippligaWebsiteConnector HTTP client for admin result updates
|
||||||
|
│ └── MatchesListCreator / MatchesListForumUpdater Fixture list posts
|
||||||
|
└── whatsapp/ — WhatsApp reminder system
|
||||||
|
├── WhatsAppNotifier Sends messages via WhatsApp API
|
||||||
|
├── OpenAIConnector Generates reminder text via GPT-3.5
|
||||||
|
└── WhatsAppReminder Record: user, matchday, missing bets
|
||||||
|
|
||||||
|
BaseMatch — Abstract match with status/comparison/shared fields
|
||||||
|
S3Provider — AWS S3 read/write for cached API-Football JSON
|
||||||
|
ResourceProvider — Reads JSON config files from classpath resources
|
||||||
|
StatusHolder — Global error flag
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Java 17+
|
||||||
|
- MySQL database (phpBB with Tippliga extension)
|
||||||
|
- API-Football API key (free tier)
|
||||||
|
- Google Calendar API service account + credentials file
|
||||||
|
- AWS S3 bucket for fixture caching
|
||||||
|
- WhatsApp API endpoint
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
| Variable | Description |
|
||||||
|
|---|---|
|
||||||
|
| `TLW_DATABASE_USERNAME` | MySQL username |
|
||||||
|
| `TLW_DATABASE_PASSWORD` | MySQL password |
|
||||||
|
| `FORUM_USERNAME` | phpBB admin username (for website connector) |
|
||||||
|
| `FORUM_PASSWORD` | phpBB admin password |
|
||||||
|
| `OPENAI_TOKEN` | OpenAI API token (GPT reminders) |
|
||||||
|
| `TLW_WHATSAPP_HOST` | WhatsApp API host |
|
||||||
|
| `TLW_WHATSAPP_PORT` | WhatsApp API port |
|
||||||
|
| `TLW_WHATSAPP_API_KEY` | WhatsApp API key |
|
||||||
|
| `AWS_ACCESS_KEY_ID` | AWS credentials (S3) |
|
||||||
|
| `AWS_SECRET_ACCESS_KEY` | AWS credentials (S3) |
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
Place these in `src/main/resources/`:
|
||||||
|
- `Tippliga/Team_ID_Matcher_Config.json` — team ID mapping
|
||||||
|
- `Tippliga/Ligen_v3.json` — league definitions
|
||||||
|
- `Google_Calendar_Config.json` — calendar IDs
|
||||||
|
- `Tippliga_Configs/<config>.json` — per-season match configs
|
||||||
|
|
||||||
|
Configs are stored as phpBB forum posts (JSON wrapped in `[code]` BBCode).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build fat JAR
|
||||||
|
./gradlew jar
|
||||||
|
|
||||||
|
# Run modes (all require -s <season> -l <league> -c <configFile>)
|
||||||
|
java -jar build/libs/tlw-database-tool-1.0.jar -m MatchdaysUpdater -s 2025 -l 1 -c tl_2025
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI Modes
|
||||||
|
|
||||||
|
| Mode | Function |
|
||||||
|
|---|---|
|
||||||
|
| `MatchdaysUpdater` | Update delivery dates from match times, sync Google Calendar |
|
||||||
|
| `MatchesCreatorFootball` | Create match schedule from API-Football |
|
||||||
|
| `MatchesUpdaterFootball` | Update match teams/datetimes from API-Football |
|
||||||
|
| `MatchesResultsUpdater` | Push finished results to website |
|
||||||
|
| `TeamsUpdater` | Add missing teams to DB |
|
||||||
|
| `APIFootballUpdater` | Fetch raw fixtures/rounds → S3 |
|
||||||
|
| `MatchesListGistUpdater` | Update forum posts with fixture lists |
|
||||||
|
| `PostChecksum` | Print MD5 checksum of a config post |
|
||||||
|
| `WhatsAppNotifier` | Send WhatsApp reminders for missing bets |
|
||||||
|
|
||||||
|
### Build Commands
|
||||||
|
```bash
|
||||||
|
./gradlew build # compile + test + jar
|
||||||
|
./gradlew test # run all tests
|
||||||
|
./gradlew clean test # clean build + test
|
||||||
|
./gradlew jar # build fat JAR
|
||||||
|
./gradlew dependencies # list dependency tree
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Conventions
|
||||||
|
- Package: `de.jeyp91.*`
|
||||||
|
- SQL columns referenced by ordinal position in ResultSet (constants like `final int SEASON = 1`)
|
||||||
|
- Logger via SLF4J: `LoggerFactory.getLogger(Class.class)`
|
||||||
|
- Match status constants inherited from `BaseMatch`
|
||||||
|
- Config JSON stored in phpBB forum posts, loaded via `TippligaConfigProvider`
|
||||||
|
- Secrets in environment variables only
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- JUnit 4 + TestNG
|
||||||
|
- Tests under `src/test/java/de/jeyp91/`
|
||||||
|
- Run single test: `./gradlew test --tests "de.jeyp91.apifootball.APIFootballMatchTest"`
|
||||||
|
|
||||||
|
## How It Works (Data Flow)
|
||||||
|
|
||||||
|
1. **APIFootballUpdater** fetches fixtures/rounds from API-Football → caches in S3
|
||||||
|
2. **APIFootballConnector** reads cached S3 data, resolves API matchdays to Tippliga matchdays
|
||||||
|
3. **TLWMatchesCreatorFootball** config on which API matches map to which Tippliga matchdays → SQL INSERT
|
||||||
|
4. **TLWMatchdaysCreator** computes delivery deadlines from match times
|
||||||
|
5. **TLWMatchdaysUpdater** writes deadlines to DB + creates/updates Google Calendar events
|
||||||
|
6. **TLWMatchesUpdaterFootball** updates team IDs, datetimes from latest API data
|
||||||
|
7. **TLWMatchesResultsUpdater** pushes finished match results to the website via HTTP
|
||||||
|
8. **WhatsAppNotifier** queries DB for users with missing bets, generates GPT reminders, sends via WhatsApp API
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# apifootball — API-Football Integration
|
||||||
|
|
||||||
|
Fetches, caches, and serves match data from [API-Football](https://www.api-football.com/). Raw JSON is cached in S3 to avoid repeated API calls.
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
### APIFootballUpdater
|
||||||
|
HTTP client that fetches raw JSON from API-Football and writes to S3.
|
||||||
|
|
||||||
|
- `updateAllFixtures(season)` — fetches all leagues' fixtures
|
||||||
|
- `updateAllRounds(season)` — fetches all leagues' round metadata
|
||||||
|
- `getRawData(requestPath, season, league)` — calls API-Football with rate-limit retry
|
||||||
|
- `getLeagues()` — reads league IDs from `Ligen_v3.json` resource
|
||||||
|
|
||||||
|
Handles the Tippliga↔API-Football season offset: for most leagues, Tippliga season N corresponds to API-Football season N-1 (except World Cup `league=1` and Euro `league=4`).
|
||||||
|
|
||||||
|
### APIFootballConnector
|
||||||
|
Singleton that reads cached S3 JSON and resolves API data into `APIFootballMatch` objects.
|
||||||
|
|
||||||
|
- `getMatchDataByLeagueAndMatchday(league, matchday)` — all matches for a matchday
|
||||||
|
- `getMatchDataByLeagueAndMatchID(league, id)` — single match by API fixture ID
|
||||||
|
- `getMatchdays(leagueId)` — round metadata from S3
|
||||||
|
- `getTeamsForLeague(season, league)` — live team list from API
|
||||||
|
|
||||||
|
### APIFootballMatch
|
||||||
|
Extends `BaseMatch`. Parses a raw API-Football JSON fixture object.
|
||||||
|
|
||||||
|
Parses: fixture ID, league ID, team IDs + names, round string, datetime, status, fulltime/extratime/penalty scores.
|
||||||
|
|
||||||
|
Key method: `getMatchdayFromRoundString(season, round, leagueId)` — converts API round names (e.g. `"Regular Season - 1"`) to Tippliga matchday numbers. Falls back to the rounds JSON for non-regular-season formats.
|
||||||
|
|
||||||
|
### APIFootballMatchesProvider
|
||||||
|
High-level resolver that reads matchday config (from the JSON config file) and returns flat `APIFootballMatch` lists.
|
||||||
|
|
||||||
|
Supports two config entry types:
|
||||||
|
- `AllMatchesOfMatchday` — fetch all matches for a given API league+matchday
|
||||||
|
- `SingleMatch` — fetch a specific match by API league+fixture ID
|
||||||
|
|
||||||
|
Honors `showTable` and `ko` flags from config.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# googlecalendar — Google Calendar Integration
|
||||||
|
|
||||||
|
Manages matchday deadline events in Google Calendar. Each matchday can have up to 3 delivery deadlines (1st, 2nd, 3rd chance).
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
### GoogleCalendarConnector
|
||||||
|
Sets up the Google Calendar API service using a service account.
|
||||||
|
|
||||||
|
- Reads service account credentials from `tippliga-4e0def9ef447.json` (classpath resource)
|
||||||
|
- Uses `CalendarScopes.CALENDAR` scope
|
||||||
|
- Returns authenticated `com.google.api.services.calendar.Calendar` service
|
||||||
|
|
||||||
|
### CalendarConfigProvider
|
||||||
|
Reads calendar ID and URL from `Google_Calendar_Config.json` resource. Currently hardcoded to league 1.
|
||||||
|
|
||||||
|
### TippligaGoogleEventManager
|
||||||
|
CRUD operations for matchday deadline events.
|
||||||
|
|
||||||
|
- `createOrUpdateEventsForMatchday(matchday)` — creates/updates up to 3 events per matchday
|
||||||
|
- `createNewEvent()` / `updateEvent()` / `deleteEvent()` — individual event operations
|
||||||
|
- `deleteAllEvents(season)` — wipe all events for a season
|
||||||
|
- `updateAllMatchdays(matchdays)` — batch update; automatically deletes events for removed delivery dates
|
||||||
|
|
||||||
|
Event details:
|
||||||
|
- **Summary**: `"Tippliga Spieltag N tippen!"` (or custom matchday name)
|
||||||
|
- **Description**: season, league, matchday, delivery deadline number
|
||||||
|
- **Location**: `https://tippliga-wuerzburg.de/app.php/football/bet`
|
||||||
|
- **Time**: delivery deadline datetime, Europe/Berlin timezone
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# teamidmatcher — Team ID Mapping
|
||||||
|
|
||||||
|
Maintains a bi-directional mapping between Tippliga (phpBB) team IDs and API-Football team IDs. Uses a Guava `HashBiMap` loaded from `Team_ID_Matcher_Config.json`.
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
### TeamIDMatcher
|
||||||
|
Singleton `HashBiMap<Integer, Integer>` mapping Tippliga IDs ↔ API-Football IDs.
|
||||||
|
|
||||||
|
- `getApiFootballIdFromTippligaId(id)` — Tippliga → API-Football
|
||||||
|
- `getTippligaIdFromApiFootballId(match, homeguest)` — API-Football → Tippliga, for a given match and team side
|
||||||
|
- `getTeamNameFromTippligaId(id)` — human-readable team name
|
||||||
|
|
||||||
|
Sets `StatusHolder.setError()` if a lookup fails. Used by `TLWMatch`, `TLWMatchesUpdaterFootball`, `TLWMatchesResultsUpdater`.
|
||||||
|
|
||||||
|
Config format (`Team_ID_Matcher_Config.json`):
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{ "tippligaID": 1, "teamname": "Team Name", "apiFootballID": 12345 },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### TeamIDMatcherTemplateCreator
|
||||||
|
Utility that fetches teams from API-Football for a given league and generates a JSON config template. Useful when setting up new seasons — outputs skeleton entries with `apiFootballID` pre-filled, `tippligaID` left blank.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# tippliga — Domain Models & Operations
|
||||||
|
|
||||||
|
Core domain package with match, matchday, team, and league models plus operations for creating, updating, and managing the prediction league schedule. All extend `TLWMatchesManagerBase` or `TLWMatchesCreatorBase`.
|
||||||
|
|
||||||
|
## Base Classes
|
||||||
|
|
||||||
|
### BaseMatch (`de.jeyp91.BaseMatch`)
|
||||||
|
Abstract class with shared match state: team IDs, goals (home/guest/overtime/penalty), datetime, status, KO flag, show-table flag, and a `COMPARISON` enum (`IDENTICAL`, `DIFFERENT`, `DIFFERENT_DATETIME`, `DIFFERENT_RESULT`).
|
||||||
|
|
||||||
|
Status constants:
|
||||||
|
- `STATUS_NOTSTARTED = 0`
|
||||||
|
- `STATUS_STARTED = 1`
|
||||||
|
- `STATUS_PROVISIONAL_RESULT_AVAILABLE = 2`
|
||||||
|
- `STATUS_FINISHED = 3`
|
||||||
|
- `STATUS_NOT_EVALUATED = 4`
|
||||||
|
|
||||||
|
### TLWMatchesCreatorBase
|
||||||
|
Abstract creator with `TLWMatches` list, `getSQLInsertString()` generating `REPLACE INTO` queries, `getMatchesForMatchday()`, and `getNumberOfMatchdays()`.
|
||||||
|
|
||||||
|
### TLWMatchesManagerBase
|
||||||
|
Shared helpers used by updaters:
|
||||||
|
- Date parsing from TLW (`yyyy-MM-dd HH:mm:ss`) and API-Football (`yyyy-MM-dd'T'HH:mm:ss`) formats
|
||||||
|
- `getFirstMatchtimeTLW()` / `getFirstMatchtimeAPIFootball()` — earliest match in a list
|
||||||
|
- `getDaysDifference()` — calendar day diff
|
||||||
|
- `getMatchesStartingFromSecondDay()` / `getMatchesStartingFromSecondWeek()` — filter helpers for delivery date computation
|
||||||
|
- `getMatchingMatch()` — matches an API match to a TLW match by home/guest team IDs
|
||||||
|
|
||||||
|
## Domain Models
|
||||||
|
|
||||||
|
### TLWMatch (extends BaseMatch)
|
||||||
|
Full match model mapped from `phpbb_footb_matches` table. Multiple constructors:
|
||||||
|
- From `ResultSet` (DB query)
|
||||||
|
- From `APIFootballMatch` + `TeamIDMatcher` (API sync)
|
||||||
|
- Manual constructors for creators
|
||||||
|
- Copy constructor
|
||||||
|
|
||||||
|
Key methods:
|
||||||
|
- `getSQLQueryInsert()` / `getSQLQueryReplace()` — generates SQL values
|
||||||
|
- `updateMatch(APIFootballMatch)` — updates teams, goals, datetime from API data
|
||||||
|
- `isSameMatchPlaceholder()` — matches placeholder config entries to existing matches
|
||||||
|
|
||||||
|
### TLWMatchday
|
||||||
|
Mapped from `phpbb_footb_matchdays`. Fields: season, league, matchday, status, 3 delivery dates, matchday name, match count.
|
||||||
|
|
||||||
|
### TLWTeam
|
||||||
|
Mapped from `phpbb_footb_teams`. Fields: season, league, team ID, name, short name, symbol, group ID, matchday.
|
||||||
|
|
||||||
|
### TLWLeague
|
||||||
|
Mapped from `phpbb_footb_leagues`. Fields: season, league ID, league name, short name.
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
|
||||||
|
### TLWMatchesCreatorFootball
|
||||||
|
Builds match schedule from API-Football data and JSON config. For each matchday config entry:
|
||||||
|
1. Resolves API matches via `APIFootballMatchesProvider`
|
||||||
|
2. Creates `TLWMatch` objects with team IDs matched via `TeamIDMatcher`
|
||||||
|
3. Fills remaining slots with placeholder matches (empty teams)
|
||||||
|
|
||||||
|
Handles `deliveryDateMode`: in `"single"` mode, matches beyond the first day get `status = -1` to prevent early betting on later matches.
|
||||||
|
|
||||||
|
### TLWMatchesCreatorTipperLeague
|
||||||
|
Creates matches for tipper-vs-tipper leagues (where tippers are "teams"). Reads pairing config from `Tipper_Match_Pair_Config.json` and tipper-team mapping from `Tipper_Team_Config.json`.
|
||||||
|
|
||||||
|
### TLWMatchesCreatorTipperPokal
|
||||||
|
Creates single-matchday knockout tournament matches from a tipper list config. Each pair of tippers is one match with `koMatch = 1` and `showTable = false`.
|
||||||
|
|
||||||
|
### TLWMatchesUpdaterFootball
|
||||||
|
Compares current DB matches with latest API data and generates SQL UPDATE statements. Only operates on matchdays where the first match is still in the future. Updates:
|
||||||
|
- Team IDs (via `TeamIDMatcher`)
|
||||||
|
- Match datetimes
|
||||||
|
- Status flags (0 = current matchday, -1 = next-day match, -2 = later)
|
||||||
|
- `show_table` / `ko_match` flags
|
||||||
|
|
||||||
|
Also handles:
|
||||||
|
- `addMissingMatches()` — inserts API matches not yet in DB (finds placeholder slots by datetime/formula)
|
||||||
|
- `addPlaceholderMatches()` — adds config-defined placeholders for TBD matchups
|
||||||
|
|
||||||
|
### TLWMatchesResultsUpdater
|
||||||
|
For finished matches, compares DB goals with API goals and pushes updates to the website via `TippligaWebsiteConnector`. Detects changes to:
|
||||||
|
- Full-time goals
|
||||||
|
- Overtime goals (for KO matches with `betKOType = 2`)
|
||||||
|
- Penalty → overtime mapping (for `betKOType = 1` or `3`)
|
||||||
|
- Status transitions from provisional to confirmed
|
||||||
|
|
||||||
|
### TLWMatchdaysCreator
|
||||||
|
Computes delivery dates (betting deadlines) from match times. Supports two modes:
|
||||||
|
- **"single"**: one deadline = first match time
|
||||||
|
- **"multiple"** (default): 3 deadlines — first match, first match of second day, first match of second week
|
||||||
|
|
||||||
|
Also handles `additionalDeliveryDate` config flags for special matchday events.
|
||||||
|
|
||||||
|
### TLWMatchdaysUpdater
|
||||||
|
Compares original DB matchdays with recomputed deadlines and generates SQL UPDATEs and Google Calendar syncs. Only updates future matchdays (deadline in the past is skipped).
|
||||||
|
|
||||||
|
### TLWTeamsCreator
|
||||||
|
Extracts unique team IDs from a list of matches and generates `REPLACE INTO phpbb_footb_teams` SQL, preserving group and matchday info from match data.
|
||||||
|
|
||||||
|
### TLWTeamsUpdater
|
||||||
|
Detects teams referenced in matches but not yet present in the league's team table. Generates INSERT SQL to add them (copies team metadata from existing entries in other seasons/leagues).
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# tippligaforum — MySQL & phpBB Integration
|
||||||
|
|
||||||
|
Database connector, config loader, website HTTP client, and forum post management.
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
### TippligaSQLConnector
|
||||||
|
Singleton JDBC connector to the phpBB MySQL database (`d0144ddb`). Connects to production via credentials from environment variables, with a fallback to localhost.
|
||||||
|
|
||||||
|
All major queries in one class:
|
||||||
|
- `getTeams()`, `getMatches()`, `getMatchdays()`, `getLeague()` — read domain objects
|
||||||
|
- `getUpdatedMatchdaysBasedOnMatches()` — complex SQL that computes delivery deadlines from match times (uses `DATEDIFF` to split deadlines across days/weeks)
|
||||||
|
- `getNextWhatsAppReminders(hours)` — multi-table query finding users with missing bets on upcoming matchdays, joins with reminder tracking to avoid duplicates
|
||||||
|
- `updatePost()` / `getPost()` / `getChecksumOfPost()` — phpBB post CRUD
|
||||||
|
- `executeQuery()` / `executeUpdate()` — raw SQL access
|
||||||
|
- `markWhatsAppReminderAsSent()` — logs sent reminders
|
||||||
|
|
||||||
|
Post updates use the phpBB checksum mechanism: `post_checksum` = MD5 of XML-escaped post text.
|
||||||
|
|
||||||
|
### TippligaConfigProvider
|
||||||
|
Reads JSON configuration from phpBB forum posts. Configs are stored as topics under the `Admin → Tippliga-Config → <season>` forum hierarchy.
|
||||||
|
|
||||||
|
- `getTippligaConfig(config)` — fetches post, strips `[code]` BBCode wrapper, parses JSON
|
||||||
|
- `getChecksumOfConfigPost(configFile)` — reads post checksum for integrity verification
|
||||||
|
|
||||||
|
### TippligaWebsiteConnector
|
||||||
|
HTTP client that logs into the phpBB frontend + admin panel and submits match results. Used by `TLWMatchesResultsUpdater`.
|
||||||
|
|
||||||
|
Login flow:
|
||||||
|
1. GET login page → extract `creation_time`, `form_token`, `sid` from hidden fields
|
||||||
|
2. POST credentials → capture session cookies
|
||||||
|
3. GET admin panel → extract admin credential token
|
||||||
|
4. POST admin login with `password_<credential>` parameter
|
||||||
|
|
||||||
|
Result submission:
|
||||||
|
1. GET results management page → extract CSRF tokens
|
||||||
|
2. POST form with season, league, matchday, match scores (home, guest, overtime)
|
||||||
|
|
||||||
|
### MatchesListCreator
|
||||||
|
Generates a JSON match list config from raw API-Football fixture data (read from S3). Creates `SingleMatch` config entries with matchday, matchtime, team names, and fixture IDs. Used by `MatchesListForumUpdater` to create fixture list forum posts.
|
||||||
|
|
||||||
|
### MatchesListForumUpdater
|
||||||
|
For each league:
|
||||||
|
1. Creates `MatchesListCreator` from S3 fixture data
|
||||||
|
2. Formats as pretty-printed JSON
|
||||||
|
3. Wraps in `[code]` BBCode
|
||||||
|
4. Finds the correct forum post (under `Admin → Tippliga-Config → <season> → Ligen → <country> <league>`)
|
||||||
|
5. Updates the post text via `TippligaSQLConnector`
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# whatsapp — WhatsApp Reminder System
|
||||||
|
|
||||||
|
Sends personalized WhatsApp reminders to users who haven't submitted their bets before the deadline. Uses OpenAI GPT-3.5 to generate natural-language reminder messages.
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
### WhatsAppNotifier
|
||||||
|
Orchestrates the reminder workflow:
|
||||||
|
|
||||||
|
1. Queries DB for upcoming reminders at 2 checkpoints: **24 hours** and **2 hours** before deadline
|
||||||
|
2. For each reminder, generates a message via `OpenAIConnector`
|
||||||
|
3. Sends via WhatsApp API (HTTP POST to configurable host:port)
|
||||||
|
4. Marks sent reminders in DB to avoid duplicates
|
||||||
|
|
||||||
|
Requires env vars: `TLW_WHATSAPP_HOST`, `TLW_WHATSAPP_PORT`, `TLW_WHATSAPP_API_KEY`.
|
||||||
|
|
||||||
|
### OpenAIConnector
|
||||||
|
Caches and generates reminder text via OpenAI Chat Completions API (model `gpt-3.5-turbo-0125`).
|
||||||
|
|
||||||
|
Prompt structure:
|
||||||
|
- **System prompt** (German): "Generate a message reminding someone to submit missing tips. Must sound like a friend. Use correct singular/plural."
|
||||||
|
- **User input**: JSON with league name, matchday name, delivery day, time
|
||||||
|
- **Output template** has 2 variants — 24h deadline uses a friendly intro, 2h deadline emphasizes urgency
|
||||||
|
|
||||||
|
Message format:
|
||||||
|
```
|
||||||
|
Hey <username>,
|
||||||
|
<AI-generated intro>
|
||||||
|
Bitte gib die fehlenden Tipps bis <today/morgen> um <HH:MM> ab.
|
||||||
|
Du kannst deine Tipps wie immer unter https://tippliga-wuerzburg.de/app.php/football/bet abgeben.
|
||||||
|
Viele Grüße
|
||||||
|
Die Tippliga Admins
|
||||||
|
```
|
||||||
|
|
||||||
|
Messages are cached per (league + matchday + delivery) key to avoid repeated API calls.
|
||||||
|
|
||||||
|
### WhatsAppReminder
|
||||||
|
Java `record` with fields: season, league, leagueName, matchday, matchdayName, deliveryDate, todayTomorrow, time, remainingHours, userId, username, phoneNumber, missingBets.
|
||||||
|
|
||||||
|
Maps directly to the SQL query result from `TippligaSQLConnector.getNextWhatsAppReminders()`.
|
||||||
+3
-1
@@ -14,7 +14,9 @@ pipeline {
|
|||||||
withCredentials([
|
withCredentials([
|
||||||
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
||||||
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
||||||
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD')
|
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key_id', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY_ID'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY')
|
||||||
]) {
|
]) {
|
||||||
withEnv([
|
withEnv([
|
||||||
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
||||||
|
|||||||
+3
-1
@@ -14,7 +14,9 @@ pipeline {
|
|||||||
withCredentials([
|
withCredentials([
|
||||||
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
||||||
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
||||||
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD')
|
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key_id', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY_ID'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY')
|
||||||
]) {
|
]) {
|
||||||
withEnv([
|
withEnv([
|
||||||
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
||||||
|
|||||||
+3
-1
@@ -14,7 +14,9 @@ pipeline {
|
|||||||
withCredentials([
|
withCredentials([
|
||||||
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
||||||
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
||||||
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD')
|
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key_id', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY_ID'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY')
|
||||||
]) {
|
]) {
|
||||||
withEnv([
|
withEnv([
|
||||||
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
||||||
|
|||||||
+3
-1
@@ -14,7 +14,9 @@ pipeline {
|
|||||||
withCredentials([
|
withCredentials([
|
||||||
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
||||||
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
||||||
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD')
|
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key_id', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY_ID'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY')
|
||||||
]) {
|
]) {
|
||||||
withEnv([
|
withEnv([
|
||||||
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
||||||
|
|||||||
+3
-1
@@ -14,7 +14,9 @@ pipeline {
|
|||||||
withCredentials([
|
withCredentials([
|
||||||
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
|
||||||
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
usernamePassword(credentialsId: 'forum_database', usernameVariable: 'TLW_DATABASE_USERNAME', passwordVariable: 'TLW_DATABASE_PASSWORD'),
|
||||||
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD')
|
usernamePassword(credentialsId: 'forum_user', usernameVariable: 'FORUM_USERNAME', passwordVariable: 'FORUM_PASSWORD'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key_id', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY_ID'),
|
||||||
|
string(credentialsId: 'google_calendar_private_key', variable: 'GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY')
|
||||||
]) {
|
]) {
|
||||||
withEnv([
|
withEnv([
|
||||||
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
"JAVA_HOME=${jdkPath}/jdk-22.0.2"
|
||||||
|
|||||||
@@ -1,45 +1,14 @@
|
|||||||
package de.jeyp91.googlecalendar;
|
package de.jeyp91.googlecalendar;
|
||||||
|
|
||||||
import com.google.common.io.Resources;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class CalendarConfigProvider {
|
public class CalendarConfigProvider {
|
||||||
|
|
||||||
private static JSONObject googleCalendarConfig = null;
|
private static String GOOGLE_CALENDAR_ID = "825f79shtm9n3uknj99iuu2qho";
|
||||||
|
|
||||||
private static JSONObject getGoogleCalendarConfig() {
|
|
||||||
if(googleCalendarConfig == null) {
|
|
||||||
//JSON parser object to parse read file
|
|
||||||
JSONParser jsonParser = new JSONParser();
|
|
||||||
URL url = Resources.getResource("Google_Calendar_Config.json");
|
|
||||||
String jsonConfig = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
jsonConfig = Resources.toString(url, StandardCharsets.UTF_8);
|
|
||||||
//Read JSON file
|
|
||||||
googleCalendarConfig = (JSONObject) jsonParser.parse(jsonConfig);
|
|
||||||
|
|
||||||
} catch (IOException | ParseException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return googleCalendarConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getCalendarUrl() {
|
public static String getCalendarUrl() {
|
||||||
JSONObject leagueConfig = (JSONObject) getGoogleCalendarConfig().get(String.valueOf(1));
|
return GOOGLE_CALENDAR_ID + "@group.calendar.google.com";
|
||||||
return (String) leagueConfig.get("url");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getCalendarId() {
|
public static String getCalendarId() {
|
||||||
JSONObject leagueConfig = (JSONObject) getGoogleCalendarConfig().get(String.valueOf(1));
|
return GOOGLE_CALENDAR_ID;
|
||||||
return (String) leagueConfig.get("id");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,33 +8,69 @@ import com.google.api.services.calendar.Calendar;
|
|||||||
import com.google.api.services.calendar.CalendarScopes;
|
import com.google.api.services.calendar.CalendarScopes;
|
||||||
import com.google.auth.http.HttpCredentialsAdapter;
|
import com.google.auth.http.HttpCredentialsAdapter;
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
|
import com.google.auth.oauth2.ServiceAccountCredentials;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
public class GoogleCalendarConnector {
|
public class GoogleCalendarConnector {
|
||||||
private static final String APPLICATION_NAME = "Google Calendar API Java Quickstart";
|
private static final String APPLICATION_NAME = "Google Calendar API Java Quickstart";
|
||||||
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
|
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
|
||||||
|
|
||||||
/**
|
private static final String PROJECT_ID = "tippliga";
|
||||||
* Global instance of the scopes required by this quickstart.
|
private static final String CLIENT_MAIL = "tlw-707@tippliga.iam.gserviceaccount.com";
|
||||||
* If modifying these scopes, delete your previously saved tokens/ folder.
|
private static final String CLIENT_ID = "101590566618120833710";
|
||||||
*/
|
private static final String TOKEN_URI = "https://oauth2.googleapis.com/token";
|
||||||
private static final String SERVICE_CREDENTIALS_FILE_PATH = "/tippliga-4e0def9ef447.json";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an authorized Credential object.
|
|
||||||
* @return An authorized Credential object.
|
|
||||||
* @throws IOException If the Google_Credentials.json file cannot be found.
|
|
||||||
*/
|
|
||||||
private static GoogleCredentials getGoogleCredentials() throws IOException {
|
private static GoogleCredentials getGoogleCredentials() throws IOException {
|
||||||
InputStream in = GoogleCalendarConnector.class.getResourceAsStream(SERVICE_CREDENTIALS_FILE_PATH);
|
String privateKeyId = getEnv("GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY_ID");
|
||||||
GoogleCredentials credentials = GoogleCredentials.fromStream(in)
|
String privateKeyPem = getEnv("GOOGLE_CALENDAR_TIPPLIGA_SERVICE_ACCOUNT_PRIVATE_KEY");
|
||||||
|
|
||||||
|
GoogleCredentials credentials = ServiceAccountCredentials.newBuilder()
|
||||||
|
.setClientEmail(CLIENT_MAIL)
|
||||||
|
.setClientId(CLIENT_ID)
|
||||||
|
.setPrivateKey(parsePrivateKey(privateKeyPem))
|
||||||
|
.setPrivateKeyId(privateKeyId)
|
||||||
|
.setProjectId(PROJECT_ID)
|
||||||
|
.setTokenServerUri(URI.create(TOKEN_URI))
|
||||||
|
.build()
|
||||||
.createScoped(Collections.singleton(CalendarScopes.CALENDAR));
|
.createScoped(Collections.singleton(CalendarScopes.CALENDAR));
|
||||||
|
|
||||||
credentials.refreshIfExpired();
|
credentials.refreshIfExpired();
|
||||||
return credentials;
|
return credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PrivateKey parsePrivateKey(String pemData) throws IOException {
|
||||||
|
String normalized = pemData.replace("\\n", "\n");
|
||||||
|
String stripped = normalized
|
||||||
|
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||||
|
.replace("-----END PRIVATE KEY-----", "")
|
||||||
|
.replaceAll("\\s", "");
|
||||||
|
byte[] encoded = Base64.getDecoder().decode(stripped);
|
||||||
|
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
|
||||||
|
try {
|
||||||
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||||
|
return kf.generatePrivate(keySpec);
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
|
throw new IOException("Failed to parse private key", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getEnv(String name) throws IOException {
|
||||||
|
String value = System.getenv(name);
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
throw new IOException("Environment variable " + name + " is not set");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public static com.google.api.services.calendar.Calendar getSerivce() {
|
public static com.google.api.services.calendar.Calendar getSerivce() {
|
||||||
Calendar service = null;
|
Calendar service = null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
package de.jeyp91.openligadb;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.client.ClientProtocolException;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.json.simple.JSONArray;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class OpenLigaDBConnector {
|
|
||||||
|
|
||||||
private final String OPENLIGADB_API_URL = "http://www.openligadb.de/api/";
|
|
||||||
|
|
||||||
public OpenLigaDBConnector() {}
|
|
||||||
|
|
||||||
public OpenLigaDBMatch getMatchDataOfSingleMatch(int id) {
|
|
||||||
String url = OPENLIGADB_API_URL + "getmatchdata/" + id;
|
|
||||||
JSONObject matchAsJson = getDataAsJSONObject(url);
|
|
||||||
return new OpenLigaDBMatch(matchAsJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<OpenLigaDBMatch> getMatchDataOfMatchday(int season, String league, int matchday) {
|
|
||||||
|
|
||||||
String url = OPENLIGADB_API_URL + "getmatchdata/" + league + "/" + (season - 1) + "/" + matchday;
|
|
||||||
|
|
||||||
JSONArray matches = getDataAsJSONArray(url);
|
|
||||||
ArrayList<OpenLigaDBMatch> matchesList = new ArrayList<>();
|
|
||||||
|
|
||||||
for(Object match: matches) {
|
|
||||||
matchesList.add(new OpenLigaDBMatch((JSONObject) match));
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchesList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<OpenLigaDBMatch> getMatchDataOfCurrentMatchday(String league) {
|
|
||||||
|
|
||||||
String url = OPENLIGADB_API_URL + "getmatchdata/" + league;
|
|
||||||
|
|
||||||
JSONArray matches = getDataAsJSONArray(url);
|
|
||||||
ArrayList<OpenLigaDBMatch> matchesList = new ArrayList<>();
|
|
||||||
|
|
||||||
for(Object match: matches) {
|
|
||||||
matchesList.add(new OpenLigaDBMatch((JSONObject) match));
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchesList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenLigaDBMatch getMatchDataByMatchId(int id) {
|
|
||||||
String url = OPENLIGADB_API_URL + "getmatchdata/" + id;
|
|
||||||
JSONObject match = getDataAsJSONObject(url);
|
|
||||||
return new OpenLigaDBMatch(match);
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONArray getDataAsJSONArray(String requestUrl) {
|
|
||||||
|
|
||||||
String rawData = getRawData(requestUrl);
|
|
||||||
JSONParser parser = new JSONParser();
|
|
||||||
JSONArray data = null;
|
|
||||||
try {
|
|
||||||
data = (JSONArray) parser.parse(rawData);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
/* TODO */
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONObject getDataAsJSONObject(String requestUrl) {
|
|
||||||
|
|
||||||
String rawData = getRawData(requestUrl);
|
|
||||||
JSONParser parser = new JSONParser();
|
|
||||||
JSONObject data = null;
|
|
||||||
try {
|
|
||||||
data = (JSONObject) parser.parse(rawData);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
/* TODO */
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getRawData(String requestUrl) {
|
|
||||||
|
|
||||||
HttpClient client = HttpClientBuilder.create().build();
|
|
||||||
HttpGet request = new HttpGet(requestUrl);
|
|
||||||
|
|
||||||
// add request header
|
|
||||||
request.addHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
HttpResponse response = null;
|
|
||||||
try {
|
|
||||||
response = client.execute(request);
|
|
||||||
} catch (ClientProtocolException e) {
|
|
||||||
/* TODO */
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
/* TODO */
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
BufferedReader rd = null;
|
|
||||||
try {
|
|
||||||
rd = new BufferedReader(
|
|
||||||
new InputStreamReader(response.getEntity().getContent())
|
|
||||||
);
|
|
||||||
} catch (IOException e) {
|
|
||||||
/* TODO */
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
String line = "";
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
line = rd.readLine();
|
|
||||||
} catch (IOException e) {
|
|
||||||
/* TODO */
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop reading if last line was found.
|
|
||||||
if (line == null) break;
|
|
||||||
|
|
||||||
result.append(line);
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package de.jeyp91.openligadb;
|
|
||||||
|
|
||||||
import de.jeyp91.BaseMatch;
|
|
||||||
import de.jeyp91.tippliga.TLWMatch;
|
|
||||||
import org.json.simple.JSONArray;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import java.util.regex.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class OpenLigaDBMatch extends BaseMatch {
|
|
||||||
|
|
||||||
private final int RESULT_TYPE_ENDRESULT = 2;
|
|
||||||
|
|
||||||
private Integer season = null;
|
|
||||||
private int matchId;
|
|
||||||
private int leagueId;
|
|
||||||
private boolean matchIsFinished;
|
|
||||||
|
|
||||||
public OpenLigaDBMatch(JSONObject json) {
|
|
||||||
this.matchId = Integer.parseInt(json.get("MatchID").toString());
|
|
||||||
this.season = getSeasonFromLeageName(json.get("LeagueName").toString());
|
|
||||||
this.leagueId = Integer.parseInt(json.get("LeagueId").toString());
|
|
||||||
this.teamIdHome = Integer.parseInt(((JSONObject) json.get("Team1")).get("TeamId").toString());
|
|
||||||
this.teamIdGuest = Integer.parseInt(((JSONObject) json.get("Team2")).get("TeamId").toString());
|
|
||||||
for(int i = 0; i < ((JSONArray) json.get("MatchResults")).size(); i++) {
|
|
||||||
if(Integer.parseInt((((JSONObject) ((JSONArray) json.get("MatchResults")).get(i)).get("ResultTypeID")).toString()) == RESULT_TYPE_ENDRESULT) {
|
|
||||||
this.goalsHome = Integer.parseInt(((JSONObject) ((JSONArray) json.get("MatchResults")).get(i)).get("PointsTeam1").toString());
|
|
||||||
this.goalsGuest = Integer.parseInt(((JSONObject) ((JSONArray) json.get("MatchResults")).get(i)).get("PointsTeam2").toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.matchday = Integer.parseInt(((JSONObject) json.get("Group")).get("GroupOrderID").toString());
|
|
||||||
this.matchIsFinished = (Boolean) json.get("MatchIsFinished");
|
|
||||||
this.matchDatetime = (String) json.get("MatchDateTime");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSeasonFromLeageName(String leagueName) {
|
|
||||||
Pattern p = Pattern.compile("\\d*/\\d*");
|
|
||||||
Matcher m = p.matcher(leagueName);
|
|
||||||
m.find();
|
|
||||||
String seasonString = m.group();
|
|
||||||
p = Pattern.compile("\\d+");
|
|
||||||
m = p.matcher(seasonString);
|
|
||||||
Integer season = null;
|
|
||||||
while(m.find()) {
|
|
||||||
season = Integer.parseInt(m.group());
|
|
||||||
}
|
|
||||||
return season < 100 ? season + 2000 : season;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSameMatch(TLWMatch compareMatch) {
|
|
||||||
if(this.getSeason()!= compareMatch.getSeason()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(this.getMatchday() != compareMatch.getMatchday()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(this.getTeamIdHome() != compareMatch.getTeamIdHome()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(this.getTeamIdGuest() != compareMatch.getTeamIdGuest()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String thisDateTime = this.getMatchDateTime().replace("T", " ");
|
|
||||||
String tempDateTime = compareMatch.getMatchDateTime().replace("T", " ");
|
|
||||||
if(!tempDateTime.equals(thisDateTime)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(this.goalsHome != compareMatch.getGoalsHome() ||
|
|
||||||
this.goalsGuest != compareMatch.getGoalsGuest()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSeason() {
|
|
||||||
return this.season;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getMatchId() {
|
|
||||||
return this.matchId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getMatchIsFinished() {
|
|
||||||
return this.matchIsFinished;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"1": {
|
|
||||||
"id": "825f79shtm9n3uknj99iuu2qho",
|
|
||||||
"url": "825f79shtm9n3uknj99iuu2qho@group.calendar.google.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
{
|
|
||||||
"numberOfMatchdays": 39,
|
|
||||||
"matchesPerMatchday": 12,
|
|
||||||
"pointMode": 4,
|
|
||||||
"pointsTendency": 1,
|
|
||||||
"pointsDifference": 2,
|
|
||||||
"pointsDirectHit": 3,
|
|
||||||
"ko": 0,
|
|
||||||
"matchdayConfig": [
|
|
||||||
{
|
|
||||||
"TLWMatchday": 1,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 2,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 3,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 4,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 5,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 6,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 7,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 8,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 9,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 10,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 11,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 12,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 13,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 14,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 15,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 16,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 17,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 18,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 19,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 20,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 21,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 22,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 23,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 24,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 25,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 26,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 27,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 28,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 29,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 30,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 31,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 32,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 33,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 34,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 35,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 36,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 37,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 38,
|
|
||||||
"matchesConfig": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TLWMatchday": 39,
|
|
||||||
"matchesConfig": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ import de.jeyp91.apifootball.APIFootballMatch;
|
|||||||
import de.jeyp91.teamidmatcher.TeamIDMatcher;
|
import de.jeyp91.teamidmatcher.TeamIDMatcher;
|
||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
import org.json.simple.parser.JSONParser;
|
import org.json.simple.parser.JSONParser;
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -11,7 +10,7 @@ import static org.junit.Assert.assertEquals;
|
|||||||
public class TeamIDMatcherTest {
|
public class TeamIDMatcherTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getOpenLigaDbIdFromTippligaIdTest() {
|
public void getAPIFootballIdFromTippligaIdTest() {
|
||||||
int apiFootballId;
|
int apiFootballId;
|
||||||
|
|
||||||
apiFootballId = TeamIDMatcher.getApiFootballIdFromTippligaId(505);
|
apiFootballId = TeamIDMatcher.getApiFootballIdFromTippligaId(505);
|
||||||
@@ -23,7 +22,7 @@ public class TeamIDMatcherTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getTippligaIdFromOpenLigaDbIdTest() throws Exception {
|
public void getTippligaIdFromAPIFootballIdTest() throws Exception {
|
||||||
int tlwId;
|
int tlwId;
|
||||||
String jsonMatch = "{" +
|
String jsonMatch = "{" +
|
||||||
"\"fixture_id\":1," +
|
"\"fixture_id\":1," +
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.jeyp91.apifootball;
|
package de.jeyp91.apifootball;
|
||||||
|
|
||||||
import de.jeyp91.openligadb.OpenLigaDBConnector;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|||||||
Reference in New Issue
Block a user