openliga 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1c536f8e8f075a90cdaebbc6c0a5b06de9b58dcc026b1a50240a709807ede9d9
4
+ data.tar.gz: e0acb89fa7e33e21ca65f1191d8fa1c567cfd05a3220c85d1be8363455c9ca44
5
+ SHA512:
6
+ metadata.gz: 33f66d0098831db3fbc807c7dbc165cc8052e2fbaa2c7795572740add7b012e95cca1347979a7f5f8769bcf24696df095a6a340419a48b93cc08ea636ec5460f
7
+ data.tar.gz: 39f97ffa909180c2bf93f4085ada7fc49a8f88e6da3ea967c8495ceabd1561e6fa847836bb043f90531e1f5b92be62a7aa03b995d30bb6fb6352fe9c6a567636
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+
2
+ ### 0.0.1 / 2026-08-28
3
+
4
+ * Everything is new. First release.
data/Manifest.txt ADDED
@@ -0,0 +1,18 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ bin/openliga
6
+ config/leagues_de.csv
7
+ config/leagues_more.csv
8
+ lib/openliga.rb
9
+ lib/openliga/convert-goals.rb
10
+ lib/openliga/convert-match.rb
11
+ lib/openliga/convert-score.rb
12
+ lib/openliga/convert.rb
13
+ lib/openliga/convert_helpers.rb
14
+ lib/openliga/download.rb
15
+ lib/openliga/leagues.rb
16
+ lib/openliga/models.rb
17
+ lib/openliga/pp_matches.rb
18
+ lib/openliga/tool.rb
data/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # openliga - openligadb.de api client/wrapper and football.txt format converter
2
+
3
+
4
+ * home :: [github.com/sportdb/sport.db](https://github.com/sportdb/sport.db)
5
+ * bugs :: [github.com/sportdb/sport.db/issues](https://github.com/sportdb/sport.db/issues)
6
+ * gem :: [rubygems.org/gems/openliga](https://rubygems.org/gems/openliga)
7
+ * rdoc :: [rubydoc.info/gems/openliga](http://rubydoc.info/gems/openliga)
8
+
9
+
10
+
11
+
12
+ ## Usage
13
+
14
+ ### OpenLigaDb (JSON) API Calls via Metal
15
+
16
+ Use the (to the metal) wrappers in `Òpenliga::Metal` to
17
+ make api json calls. Example:
18
+
19
+ ``` ruby
20
+ ### api call - /getavailableleagues
21
+ data = Openliga::Metal.leagues
22
+
23
+ ## api call - /getavailableteams/{code}/{year}
24
+ data = Openliga::Metal.teams( 'bl1', 2026 )
25
+
26
+ ## api call - /getmatchdata/{code}/{year}
27
+ data = Openliga::Metal.matches( 'bl1', 2026 )
28
+
29
+ ## api call - /getgoalgetters/{code}/{year}
30
+ data = Openliga::Metal.goalgetters( 'bl1', 2026 )
31
+ ```
32
+
33
+
34
+ Note - all json api calls get auto-magically cached locally
35
+ by default in `./cache`
36
+ e.g.
37
+
38
+ ```
39
+ └───api.openligadb.de/
40
+ │ getavailableleagues & getavailableleagues.meta.txt
41
+
42
+ ├───getavailableteams/
43
+ │ ├───bl1/
44
+ │ │ 2025 & 2025.meta.txt
45
+ │ │ 2026 & 2026.meta.txt
46
+ │ │
47
+ │ └───dfb/
48
+ │ 2025 & 2025.meta.txt
49
+ │ 2026 & 2026.meta.txt
50
+ └───getmatchdata/
51
+ ├───bl1/
52
+ │ 2025 & 2025.meta.txt
53
+ │ 2026 & 2026.meta.txt
54
+
55
+ └───dfb
56
+ 2025 & 2025.meta.txt
57
+ 2026 & 2026.meta.txt
58
+ ```
59
+
60
+ Use `Webcache.root = '<your path here>'` to change the web cache root directory.
61
+
62
+
63
+ To be continued...
64
+
65
+
66
+
67
+
68
+
69
+
70
+ ### About the Football (Match) Data
71
+
72
+ Conversion to Football.TXT Conversion Samples
73
+
74
+
75
+ openliga/2020-21_de.1.txt:
76
+
77
+ ```
78
+ ###
79
+ # converted from openligadb.de json to Football.TXT
80
+ # for source, see https://api.openligadb.de/getmatchdata/bl1/2020
81
+
82
+ = 1. Fußball-Bundesliga 2020/2021
83
+
84
+ ▪ 1. Spieltag
85
+ Fri Sep 18 2020
86
+ 20:30 FC Bayern München v FC Schalke 04 8-0 (3-0) @ Allianz Arena, München
87
+ (1-0 S. Gnabry 4'
88
+ 2-0 Goretzka 19'
89
+ 3-0 Robert Lewandowski 31' (p)
90
+ 4-0 S. Gnabry 47'
91
+ 5-0 S. Gnabry 59'
92
+ 6-0 T. Müller 69'
93
+ 7-0 Sanê 71'
94
+ 8-0 Musiala 81')
95
+ ...
96
+ ```
97
+ or
98
+
99
+ openliga/2025-26_de.cup.txt:
100
+
101
+ ```
102
+ ###
103
+ # converted from openligadb.de json to Football.TXT
104
+ # for source, see https://api.openligadb.de/getmatchdata/dfb/2025
105
+
106
+ = DFB Pokal 2025/2026
107
+
108
+ ▪ 1. Runde
109
+ Fri Aug 15 2025
110
+ 18:00 FC Gütersloh v 1. FC Union Berlin 0-5 (0-3)
111
+ 1. FC Saarbrücken v 1. FC Magdeburg 1-3 (0-2)
112
+ SG Sonnenhof Großaspach v Bayer 04 Leverkusen 0-4 (0-1)
113
+ 20:45 DSC Arminia Bielefeld v Werder Bremen 1-0 (0-0)
114
+ Sat Aug 16
115
+ 13:00 FK Pirmasens v Hamburger SV 1-2 (0-0)
116
+ BFC Dynamo v VfL Bochum 1-3 (0-0)
117
+ 15:30 SV Hemelingen v VfL Wolfsburg 0-9 (0-3)
118
+ FV Illertissen v 1. FC Nürnberg 3-3 aet (3-3, 2-0), 9-8 pen
119
+ Eintracht Norderstedt v FC St. Pauli 0-0 aet (0-0, 0-0), 2-3 pen
120
+ SV Sandhausen v RB Leipzig 2-4 (2-2)
121
+ Hansa Rostock v TSG Hoffenheim 0-4 (0-1)
122
+ Bahlinger SC v 1. FC Heidenheim 1846 0-5 (0-2)
123
+ 18:00 Sportfreunde Lotte v SC Freiburg 0-2 (0-1)
124
+ VfB Lübeck v SV Darmstadt 98 1-2 (0-1)
125
+ Energie Cottbus v Hannover 96 1-0 (1-0)
126
+
127
+
128
+ ...
129
+
130
+ ▪ Halbfinale
131
+ Wed Apr 22
132
+ 20:45 Bayer 04 Leverkusen v FC Bayern München 0-2 (0-1)
133
+ (0-1 H. Kane 22'
134
+ 0-2 Luis Díaz 90+4')
135
+ Thu Apr 23
136
+ 20:45 VfB Stuttgart v SC Freiburg 2-1 aet (1-1, 0-1)
137
+ (0-1 M. Eggestein 28'
138
+ 1-1 D. Undav 70'
139
+ 2-1 T. Tomas 119')
140
+
141
+ ▪ Endspiel
142
+ Sat May 23
143
+ 20:00 FC Bayern München v VfB Stuttgart 3-0 (0-0)
144
+ (1-0 H. Kane 55'
145
+ 2-0 H. Kane 80'
146
+ 3-0 H. Kane 90+2' (p))
147
+ ```
148
+
149
+
150
+
151
+ **What's missing (upstream)?**
152
+
153
+ the only match status is `finished: true|false`, that is,
154
+ there is no cancelled, annulled, awarded, abandonded, etc.
155
+
156
+
157
+ for goal minutes there is only an `is_overtime: true|false` flag for stoppage/injury/added time -
158
+ and while auto-calculation is possible if
159
+ recorded as `46+ => 45+1` or `95+ => 90+5` or such BUT
160
+ some entries are only records as `45+` or `90+`
161
+ resulting in `45+0` or `90+0`
162
+ and, thus, `45+` or `90+` at best.
163
+
164
+
165
+
166
+ for penalty shootouts there is a weirdo way to map to "plain" goals
167
+ starting with the after-extra time scor e.g.
168
+
169
+ ```
170
+ SG Sonnenhof Großaspach v DSC Arminia Bielefeld 2-2 aet (2-2, 1-2), 5-2 pen
171
+ (...
172
+ 3-2 Arbnor Nuraj (p)
173
+ 4-2 Luca Molinari (p)
174
+ 5-2 Franz Xaver Bleicher (p))
175
+ ```
176
+
177
+ and there's no way to record missed/saved penalties (??).
178
+
179
+
180
+
181
+
182
+
183
+ **What else?**
184
+
185
+ weirdo "legacy" match result (duplicate entries) with kind 'Unknown' e.g.
186
+
187
+ ```
188
+ !! warn - weirdo 'Unknown/Nachspielzeit' results found in
189
+ - name="DFB Pokal 2025/2026", season="2025", shortcut="dfb"
190
+
191
+ !! warn - weirdo 'Unknown/nach 90 Minuten' results found in
192
+ - name="DFB-Pokal 2023/2024", season="2023", shortcut="dfb"
193
+ - name="DFB-Pokal 2022/23", season="2022", shortcut="dfb2022"
194
+ - name="DFB-Pokal 2021/22", season="2021", shortcut="dfb2021"
195
+ - name="DFB-Pokal 2020/21", season="2020", shortcut="dfb2020"
196
+ ```
197
+
198
+
199
+ data errors:
200
+
201
+
202
+ ```
203
+ name="3. Fußball-Bundesliga 2022/2023", season="2022",
204
+ !! warn - skipping match with team missing:
205
+ {"matchID"=>67822,
206
+ "matchDateTime"=>"2023-09-09T15:30:00",
207
+ "timeZoneID"=>nil,
208
+ "leagueId"=>4570,
209
+ "leagueName"=>"3. Fußball-Bundesliga 2022/2023",
210
+ "leagueSeason"=>2022,
211
+ "leagueShortcut"=>"bl3",
212
+ "matchDateTimeUTC"=>"2023-09-09T13:30:00Z",
213
+ "group"=>{"groupName"=>"4. Spieltag", "groupOrderID"=>4, "groupID"=>40156},
214
+ "team1"=>{"teamId"=>0, "teamName"=>nil, "shortName"=>nil, "teamIconUrl"=>nil, "teamGroupName"=>nil},
215
+ "team2"=>{"teamId"=>0, "teamName"=>nil, "shortName"=>nil, "teamIconUrl"=>nil, "teamGroupName"=>nil},
216
+ "lastUpdateDateTime"=>"2026-05-23T14:41:39.19",
217
+ "matchIsFinished"=>false, ... }
218
+ ```
219
+
220
+
221
+
222
+ ## License
223
+
224
+ The `openliga` scripts are dedicated to the public domain.
225
+ Use as you please with no restrictions whatsoever.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'hoe'
2
+ ## require './lib/openliga/version.rb'
3
+
4
+
5
+ Hoe.spec 'openliga' do
6
+
7
+ self.version = '0.0.1'
8
+
9
+ self.summary = "openliga - openligadb.de api client/wrapper and football.txt format converter"
10
+ self.description = summary
11
+
12
+ self.urls = { home: 'https://github.com/sportdb/sport.db' }
13
+
14
+ self.author = 'Gerald Bauer'
15
+ self.email = 'gerald.bauer@gmail.com'
16
+
17
+ # switch extension to .markdown for gihub formatting
18
+ self.readme_file = 'README.md'
19
+ self.history_file = 'CHANGELOG.md'
20
+
21
+ self.extra_deps = [
22
+ ['cocos'],
23
+ ['season-formats'],
24
+ ['webget'],
25
+ ]
26
+
27
+ self.licenses = ['Public Domain']
28
+
29
+ self.spec_extras = {
30
+ required_ruby_version: '>= 3.1.0'
31
+ }
32
+
33
+ end
data/bin/openliga ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ## tip: to test run:
4
+ ## ruby -I ./lib bin/openliga
5
+
6
+
7
+ ##
8
+ ## note - always use latest (local) if present
9
+ $LOAD_PATH.unshift( '/sports/yorobot/sport.db.more/openliga/lib' )
10
+
11
+ require 'openliga'
12
+
13
+
14
+ Openliga::Tool.main( ARGV )
@@ -0,0 +1,65 @@
1
+ ####
2
+ # map known leagues with quality score 100
3
+
4
+ ## see
5
+ # - https://www.openligadb.de/Leagues?season=2026
6
+ # - https://www.openligadb.de/Leagues?season=2024
7
+ # - https://www.openligadb.de/Leagues?season=2023
8
+ # - https://www.openligadb.de/Leagues?season=2022
9
+ # - https://www.openligadb.de/Leagues?season=2021
10
+ # - https://www.openligadb.de/Leagues?season=2020
11
+
12
+ ##
13
+ ## add openliga league_id - why? why not?
14
+
15
+ ##
16
+ ## de.1 => bl1,
17
+ ## de.2 => bl2,
18
+ ## de.3 => bl3,
19
+ ## de.cup => dfb, dfb2022, dfb2021, dfb2020
20
+
21
+
22
+
23
+
24
+ code, season, league_name, league_season, league_shortcut
25
+
26
+ de.1, 2026/27, 1. Fußball-Bundesliga 2026/2027, 2026, bl1
27
+ de.2, 2026/27, 2. Fußball-Bundesliga 2026/2027, 2026, bl2
28
+ de.3, 2026/27, 3. Liga 2026/2027, 2026, bl3
29
+ de.cup, 2026/27, DFB Pokal 2026/2027, 2026, dfb
30
+
31
+
32
+ de.1, 2025/26, 1. Fußball-Bundesliga 2025/2026, 2025, bl1
33
+ de.2, 2025/26, 2. Fußball-Bundesliga 2025/2026, 2025, bl2
34
+ de.3, 2025/26, 3. Liga 2025/2026, 2025, bl3
35
+ de.cup, 2025/26, DFB Pokal 2025/2026, 2025, dfb
36
+
37
+
38
+ de.1, 2024/25, 1. Fußball-Bundesliga 2024/2025, 2024, bl1
39
+ de.2, 2024/25, 2. Fußball-Bundesliga 2024/2025, 2024, bl2
40
+ de.3, 2024/25, 3. Fußball-Bundesliga 2024/2025, 2024, bl3
41
+ de.cup, 2024/25, DFB-Pokal 2024/25, 2024, dfb
42
+
43
+
44
+ de.1, 2023/24, 1. Fußball-Bundesliga 2023/2024, 2023, bl1
45
+ de.2, 2023/24, 2. Fußball-Bundesliga 2023/2024, 2023, bl2
46
+ de.3, 2023/24, 3. Fußball-Bundesliga 2023/2024, 2023, bl3
47
+ de.cup, 2023/24, DFB-Pokal 2023/2024, 2023, dfb
48
+
49
+
50
+ de.1, 2022/23, 1. Fußball-Bundesliga 2022/2023, 2022, bl1
51
+ de.2, 2022/23, 2. Fußball-Bundesliga 2022/2023, 2022, bl2
52
+ de.3, 2022/23, 3. Fußball-Bundesliga 2022/2023, 2022, bl3
53
+ de.cup, 2022/23, DFB-Pokal 2022/23, 2022, dfb2022
54
+
55
+
56
+ de.1, 2021/22, 1. Fußball-Bundesliga 2021/2022, 2021, bl1
57
+ de.2, 2021/22, 2. Fußball-Bundesliga 2021/2022, 2021, bl2
58
+ de.3, 2021/22, 3. Fußball-Bundesliga 2021/2022, 2021, bl3
59
+ de.cup, 2021/22, DFB-Pokal 2021/22, 2021, dfb2021
60
+
61
+
62
+ de.1, 2020/21, 1. Fußball-Bundesliga 2020/2021, 2020, bl1
63
+ de.2, 2020/21, 2. Fußball-Bundesliga 2020/2021, 2020, bl2
64
+ de.3, 2020/21, 3. Fußball-Liga 2020/2021, 2020, bl3
65
+ de.cup, 2020/21, DFB-Pokal 2020/21, 2020, dfb2020
@@ -0,0 +1,43 @@
1
+
2
+
3
+ code, season, league_name, league_season, league_shortcut
4
+
5
+
6
+ ####
7
+ # clubs
8
+
9
+ ## at.1 => ÖBL,
10
+ ## # Ö-Bundesliga 2021 - ÖBL (2021)
11
+ ##
12
+ ## uefa.cl => uefacl22 (2022), uefacl (2021)
13
+ ## UEFA CL 2021/22, 2021, uefacl
14
+ # UEFA Europa League 2021, el21
15
+
16
+
17
+ uefa.cl, 2025/26, Champions League 2025/2026, 2025, ucl
18
+ uefa.cl, 2024/25, UEFA Champions League, 2024, ucl24
19
+ uefa.cl, 2023/24, UEFA Champions League 2023/2024, 2023, champion1
20
+ uefa.cl, 2022/23, UEFA Champions League 22/23, 2022, uefacl22
21
+
22
+
23
+ #####
24
+ # national teams
25
+
26
+ ## world => WM2022,
27
+ ##
28
+ ## euro => em, ## em (2024), em20 (2020)
29
+ ## southamerica => CA2024,
30
+ ##
31
+ ## Copa América 2024 CA2024
32
+ ## Copa América 2021 CA2021
33
+
34
+
35
+
36
+
37
+ world, 2026, WM 2026, 2026, wm26
38
+ world, 2022, WM-KATAR, 2022, WM2022
39
+
40
+ euro, 2024, UEFA EURO 2024, 2024, em
41
+ euro, 2021, UEFA EURO 2020, 2020, em20
42
+
43
+ copa, 2024, Copa América 2024, 2024, CA2024
@@ -0,0 +1,62 @@
1
+ module Openliga
2
+
3
+
4
+
5
+ def self.build_goals( h, team1:, team2: )
6
+
7
+ recs = []
8
+
9
+ h.each do |rec|
10
+
11
+ ## get team index (1|2) from team_id
12
+ team = if rec['scoringTeamId'].nil? then nil
13
+ elsif rec['scoringTeamId'] == team1 then 1
14
+ elsif rec['scoringTeamId'] == team2 then 2
15
+ else
16
+ raise ArgumentError,
17
+ "[goal] expected team ids #{[team1,team2].inspect}; got #{h.pretty_inspect}"
18
+ end
19
+
20
+
21
+ recs << Goal.new( team: team,
22
+ name: _clean_name( rec['goalGetterName']),
23
+ score: [rec['scoreTeam1'], rec['scoreTeam2']],
24
+ m: rec['matchMinute'],
25
+ stoppage: rec['isOvertime'],
26
+ pen: rec['isPenalty'],
27
+ og: rec['isOwnGoal'],
28
+ comment: _clean( rec[''] ) )
29
+ end
30
+
31
+ recs
32
+ end
33
+
34
+
35
+ end # module Openliga
36
+
37
+
38
+
39
+ __END__
40
+
41
+ "scoreTeam1"=>0,
42
+ "scoreTeam2"=>0,
43
+ "matchMinute"=>28,
44
+ "goalGetterID"=>19593,
45
+ "goalGetterName"=>"M. Svanberg",
46
+ "scoringTeamId"=>nil,
47
+ "isPenalty"=>false,
48
+ "isOwnGoal"=>false,
49
+ "isOvertime"=>false,
50
+
51
+
52
+ "goalID": 130535,
53
+ "scoreTeam1": 1,
54
+ "scoreTeam2": 0,
55
+ "matchMinute": 27,
56
+ "goalGetterID": 23135,
57
+ "goalGetterName": "M. Olise",
58
+ "scoringTeamId": 40,
59
+ "isPenalty": false,
60
+ "isOwnGoal": false,
61
+ "isOvertime": false,
62
+ "comment": null
@@ -0,0 +1,94 @@
1
+ module Openliga
2
+
3
+
4
+
5
+ def self.build_match( h )
6
+
7
+
8
+ score = if h['matchResults'].is_a?(Array)
9
+ if h['matchResults'].empty?
10
+ nil ## note - return nil on empty array e.g. []
11
+ else
12
+ build_score( h['matchResults'] )
13
+ end
14
+ else
15
+ raise ArgumentError, "matchResults array expected; got #{h['matchResults']}"
16
+ end
17
+
18
+
19
+ goals = if h['goals'].is_a?(Array)
20
+ if h['goals'].empty?
21
+ nil ## note - return nil on empty array e.g. []
22
+ else
23
+ build_goals( h['goals'], team1: h['team1']['teamId'],
24
+ team2: h['team2']['teamId'] )
25
+ end
26
+ else
27
+ raise ArgumentError, "goals array expected; got #{h['goals']}"
28
+ end
29
+
30
+
31
+ ##
32
+ ## note - ALWAYS parse datetime for now !!!
33
+ ## note ruby's time (always incl. local timezone!!)
34
+ ## thus split into date (class Date!!) and time(string)
35
+ ## e.g. # "2024-06-17T15:00:00",
36
+ datetime = Time.strptime( _clean(h['matchDateTime']), "%Y-%m-%dT%H:%M:%S")
37
+
38
+ date = Date.new( datetime.year, datetime.month, datetime.day )
39
+ time = "%02d:%02d" % [datetime.hour, datetime.min]
40
+
41
+
42
+ location = h['location'] || {}
43
+
44
+
45
+ Match.new(
46
+ team1: _clean(h['team1']['teamName']),
47
+ team2: _clean(h['team2']['teamName']),
48
+
49
+ finished: h['matchIsFinished'], ## assume bool
50
+ score: score,
51
+ goals: goals,
52
+
53
+ # local time
54
+ date: date,
55
+ time: time,
56
+ timezone: _clean(h['timeZoneID']), # e.g "W. Europe Standard Time" or often nil!!
57
+
58
+ ## group / round e.g. "1. Runde Gruppenphase"
59
+ round: _clean(h['group']['groupName']),
60
+
61
+ ground: _clean_geo(location['locationStadium']),
62
+ city: _clean_geo(location['locationCity']),
63
+
64
+ att: h['numberOfViewers'] ## attendance (as integer number)
65
+ )
66
+ end
67
+
68
+
69
+ end ## module Openliga
70
+
71
+
72
+
73
+ __END__
74
+
75
+
76
+ "location": {
77
+ "locationID": 34,
78
+ "locationCity": "München",
79
+ "locationStadium": "Allianz Arena"
80
+ },
81
+ "numberOfViewers": 61591
82
+ -or-
83
+ "location": {
84
+ "locationID": 184,
85
+ "locationCity": "Dortmund",
86
+ "locationStadium": "Signal-Iduna-Park"
87
+ },
88
+ "numberOfViewers": 300
89
+ -or-
90
+ "location": {
91
+ "locationID": 1091,
92
+ "locationCity": "",
93
+ "locationStadium": null
94
+ },