footballdata-api 0.2.0
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 +7 -0
- data/CHANGELOG.md +6 -0
- data/Manifest.txt +15 -0
- data/README.md +165 -0
- data/Rakefile +33 -0
- data/bin/fbdat +200 -0
- data/lib/footballdata/convert.rb +332 -0
- data/lib/footballdata/download.rb +131 -0
- data/lib/footballdata/generator.rb +33 -0
- data/lib/footballdata/leagues.rb +59 -0
- data/lib/footballdata/mods.rb +22 -0
- data/lib/footballdata/prettyprint.rb +189 -0
- data/lib/footballdata/stat.rb +59 -0
- data/lib/footballdata/teams.rb +90 -0
- data/lib/footballdata/version.rb +20 -0
- data/lib/footballdata.rb +56 -0
- metadata +155 -0
@@ -0,0 +1,332 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Footballdata
|
5
|
+
|
6
|
+
|
7
|
+
TIMEZONES = {
|
8
|
+
'eng.1' => 'Europe/London',
|
9
|
+
'eng.2' => 'Europe/London',
|
10
|
+
|
11
|
+
'es.1' => 'Europe/Madrid',
|
12
|
+
|
13
|
+
'de.1' => 'Europe/Berlin',
|
14
|
+
'fr.1' => 'Europe/Paris',
|
15
|
+
'it.1' => 'Europe/Rome',
|
16
|
+
'nl.1' => 'Europe/Amsterdam',
|
17
|
+
|
18
|
+
'pt.1' => 'Europe/Lisbon',
|
19
|
+
|
20
|
+
## todo/fix - pt.1
|
21
|
+
## one team in madeira!!! check for different timezone??
|
22
|
+
## CD Nacional da Madeira
|
23
|
+
|
24
|
+
'br.1' => 'America/Sao_Paulo',
|
25
|
+
## todo/fix - brazil has 4 timezones
|
26
|
+
## really only two in use for clubs
|
27
|
+
## west and east (amazonas et al)
|
28
|
+
## for now use west for all - why? why not?
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
def self.convert( league:, season: )
|
33
|
+
|
34
|
+
### note/fix: cl (champions league for now is a "special" case)
|
35
|
+
# if league.downcase == 'cl'
|
36
|
+
# convert_cl( league: league,
|
37
|
+
# season: season )
|
38
|
+
# return
|
39
|
+
# end
|
40
|
+
|
41
|
+
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
42
|
+
|
43
|
+
league_code = LEAGUES[league.downcase]
|
44
|
+
|
45
|
+
matches_url = Metal.competition_matches_url( league_code, season.start_year )
|
46
|
+
teams_url = Metal.competition_teams_url( league_code, season.start_year )
|
47
|
+
|
48
|
+
data = Webcache.read_json( matches_url )
|
49
|
+
data_teams = Webcache.read_json( teams_url )
|
50
|
+
|
51
|
+
|
52
|
+
## check for time zone
|
53
|
+
tz_name = TIMEZONES[ league.downcase ]
|
54
|
+
if tz_name.nil?
|
55
|
+
puts "!! ERROR - sorry no timezone configured for league #{league}"
|
56
|
+
exit 1
|
57
|
+
end
|
58
|
+
|
59
|
+
tz = TZInfo::Timezone.get( tz_name )
|
60
|
+
pp tz
|
61
|
+
|
62
|
+
## build a (reverse) team lookup by name
|
63
|
+
puts "#{data_teams['teams'].size} teams"
|
64
|
+
|
65
|
+
teams_by_name = data_teams['teams'].reduce( {} ) do |h,rec|
|
66
|
+
h[ rec['name'] ] = rec
|
67
|
+
h
|
68
|
+
end
|
69
|
+
|
70
|
+
pp teams_by_name.keys
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
mods = MODS[ league.downcase ] || {}
|
75
|
+
|
76
|
+
|
77
|
+
recs = []
|
78
|
+
|
79
|
+
teams = Hash.new( 0 )
|
80
|
+
|
81
|
+
|
82
|
+
# stat = Stat.new
|
83
|
+
|
84
|
+
# track stati counts
|
85
|
+
stati = Hash.new(0)
|
86
|
+
|
87
|
+
|
88
|
+
matches = data[ 'matches']
|
89
|
+
matches.each do |m|
|
90
|
+
# stat.update( m )
|
91
|
+
|
92
|
+
team1 = m['homeTeam']['name']
|
93
|
+
team2 = m['awayTeam']['name']
|
94
|
+
|
95
|
+
score = m['score']
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
if m['stage'] == 'REGULAR_SEASON'
|
100
|
+
teams[ team1 ] += 1
|
101
|
+
teams[ team2 ] += 1
|
102
|
+
|
103
|
+
### mods - rename club names
|
104
|
+
unless mods.nil? || mods.empty?
|
105
|
+
team1 = mods[ team1 ] if mods[ team1 ]
|
106
|
+
team2 = mods[ team2 ] if mods[ team2 ]
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
comments = ''
|
112
|
+
ft = ''
|
113
|
+
ht = ''
|
114
|
+
|
115
|
+
stati[m['status']] += 1 ## track stati counts for logs
|
116
|
+
|
117
|
+
case m['status']
|
118
|
+
when 'SCHEDULED', 'TIMED' ## , 'IN_PLAY'
|
119
|
+
ft = ''
|
120
|
+
ht = ''
|
121
|
+
when 'FINISHED'
|
122
|
+
## todo/fix: assert duration == "REGULAR"
|
123
|
+
assert( score['duration'] == 'REGULAR', 'score.duration REGULAR expected' )
|
124
|
+
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
125
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
126
|
+
when 'AWARDED'
|
127
|
+
## todo/fix: assert duration == "REGULAR"
|
128
|
+
assert( score['duration'] == 'REGULAR', 'score.duration REGULAR expected' )
|
129
|
+
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
130
|
+
ft << ' (*)'
|
131
|
+
ht = ''
|
132
|
+
comments = 'awarded'
|
133
|
+
when 'CANCELLED'
|
134
|
+
## note cancelled might have scores!!
|
135
|
+
## ht only or ft+ht!!! (see fr 2021/22)
|
136
|
+
ft = '(*)'
|
137
|
+
ht = ''
|
138
|
+
comments = 'canceled' ## us eng ? -> canceled, british eng. cancelled ?
|
139
|
+
when 'POSTPONED'
|
140
|
+
ft = '(*)'
|
141
|
+
ht = ''
|
142
|
+
comments = 'postponed'
|
143
|
+
else
|
144
|
+
puts "!! ERROR: unsupported match status >#{m['status']}< - sorry:"
|
145
|
+
pp m
|
146
|
+
exit 1
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
##
|
151
|
+
## add time, timezone(tz)
|
152
|
+
## 2023-08-18T18:30:00Z
|
153
|
+
## e.g. "utcDate": "2020-05-09T00:00:00Z",
|
154
|
+
## "utcDate": "2023-08-18T18:30:00Z",
|
155
|
+
|
156
|
+
## -- todo - make sure / assert it's always utc - how???
|
157
|
+
## utc = ## tz_utc.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' )
|
158
|
+
## note: DateTime.strptime is supposed to be unaware of timezones!!!
|
159
|
+
## use to parse utc
|
160
|
+
utc = DateTime.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' ).to_time.utc
|
161
|
+
assert( utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) == m['utcDate'], 'utc time mismatch' )
|
162
|
+
|
163
|
+
local = tz.to_local( utc )
|
164
|
+
|
165
|
+
|
166
|
+
## do NOT add time if status is SCHEDULED
|
167
|
+
## or POSTPONED for now
|
168
|
+
## otherwise assume time always present - why? why not?
|
169
|
+
|
170
|
+
|
171
|
+
## todo/fix: assert matchday is a number e.g. 1,2,3, etc.!!!
|
172
|
+
recs << [m['matchday'].to_s, ## note: convert integer to string!!!
|
173
|
+
local.strftime( '%Y-%m-%d' ),
|
174
|
+
['SCHEDULED','POSTPONED'].include?( m['status'] ) ? '' : local.strftime( '%H:%M' ),
|
175
|
+
local.strftime( '%Z / UTC%z' ),
|
176
|
+
team1,
|
177
|
+
ft,
|
178
|
+
ht,
|
179
|
+
team2,
|
180
|
+
comments,
|
181
|
+
## add more columns e.g. utc date, status
|
182
|
+
m['status'], # e.g. FINISHED, TIMED, etc.
|
183
|
+
m['utcDate'],
|
184
|
+
]
|
185
|
+
|
186
|
+
|
187
|
+
print '%2s' % m['matchday']
|
188
|
+
print ' - '
|
189
|
+
print '%-26s' % team1
|
190
|
+
print ' '
|
191
|
+
print ft
|
192
|
+
print ' '
|
193
|
+
print "(#{ht})" unless ht.empty?
|
194
|
+
print ' '
|
195
|
+
print '%-26s' % team2
|
196
|
+
print ' '
|
197
|
+
print comments
|
198
|
+
print ' | '
|
199
|
+
## print date.to_date ## strip time
|
200
|
+
print utc.strftime( '%a %b %-d %Y' )
|
201
|
+
print ' -- '
|
202
|
+
print utc
|
203
|
+
print "\n"
|
204
|
+
else
|
205
|
+
puts "!!! unexpected stage:"
|
206
|
+
puts "-- skipping #{m['stage']}"
|
207
|
+
# exit 1
|
208
|
+
end
|
209
|
+
end # each match
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
## note: get season from first match
|
214
|
+
## assert - all other matches include the same season
|
215
|
+
## e.g.
|
216
|
+
# "season": {
|
217
|
+
# "id": 154,
|
218
|
+
# "startDate": "2018-08-03",
|
219
|
+
# "endDate": "2019-05-05",
|
220
|
+
# "currentMatchday": 46
|
221
|
+
# }
|
222
|
+
|
223
|
+
start_date = Date.strptime( matches[0]['season']['startDate'], '%Y-%m-%d' )
|
224
|
+
end_date = Date.strptime( matches[0]['season']['endDate'], '%Y-%m-%d' )
|
225
|
+
|
226
|
+
dates = "#{start_date.strftime('%b %-d')} - #{end_date.strftime('%b %-d')}"
|
227
|
+
|
228
|
+
buf = ''
|
229
|
+
buf << "#{season.key} (#{dates}) - "
|
230
|
+
buf << "#{teams.keys.size} clubs, "
|
231
|
+
# buf << "#{stat[:regular_season][:matches]} matches, "
|
232
|
+
# buf << "#{stat[:regular_season][:goals]} goals"
|
233
|
+
buf << "\n"
|
234
|
+
|
235
|
+
puts buf
|
236
|
+
|
237
|
+
|
238
|
+
=begin
|
239
|
+
## note: warn if stage is greater one and not regular season!!
|
240
|
+
File.open( './errors.txt' , 'a:utf-8' ) do |f|
|
241
|
+
if stat[:all][:stage].keys != ['REGULAR_SEASON']
|
242
|
+
f.write "!! WARN - league: #{league}, season: #{season.key} includes non-regular stage(s):\n"
|
243
|
+
f.write " #{stat[:all][:stage].keys.inspect}\n"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
=end
|
247
|
+
|
248
|
+
|
249
|
+
File.open( './logs.txt', 'a:utf-8' ) do |f|
|
250
|
+
f.write "==== #{league} #{season.key} =============\n"
|
251
|
+
f.write " match stati: #{stati.inspect}\n"
|
252
|
+
end
|
253
|
+
|
254
|
+
=begin
|
255
|
+
f.write "\n================================\n"
|
256
|
+
f.write "==== #{league} =============\n"
|
257
|
+
f.write buf
|
258
|
+
f.write " match status: #{stat[:regular_season][:status].inspect}\n"
|
259
|
+
f.write " match duration: #{stat[:regular_season][:duration].inspect}\n"
|
260
|
+
|
261
|
+
f.write "#{teams.keys.size} teams:\n"
|
262
|
+
teams.each do |name, count|
|
263
|
+
rec = teams_by_name[ name ]
|
264
|
+
f.write " #{count}x #{name}"
|
265
|
+
if rec
|
266
|
+
f.write " | #{rec['shortName']} " if name != rec['shortName']
|
267
|
+
f.write " › #{rec['area']['name']}"
|
268
|
+
f.write " - #{rec['address']}"
|
269
|
+
else
|
270
|
+
puts "!! ERROR - no team record found in teams.json for >#{name}<"
|
271
|
+
exit 1
|
272
|
+
end
|
273
|
+
f.write "\n"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
=end
|
277
|
+
|
278
|
+
|
279
|
+
##
|
280
|
+
## sort buy utc date ??? - why? why not?
|
281
|
+
|
282
|
+
# recs = recs.sort { |l,r| l[1] <=> r[1] }
|
283
|
+
|
284
|
+
|
285
|
+
## reformat date / beautify e.g. Sat Aug 7 1993
|
286
|
+
recs = recs.map do |rec|
|
287
|
+
rec[1] = Date.strptime( rec[1], '%Y-%m-%d' ).strftime( '%a %b %-d %Y' )
|
288
|
+
rec
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
headers = [
|
293
|
+
'Matchday',
|
294
|
+
'Date',
|
295
|
+
'Time',
|
296
|
+
'Timezone', ## move back column - why? why not?
|
297
|
+
'Team 1',
|
298
|
+
'FT',
|
299
|
+
'HT',
|
300
|
+
'Team 2',
|
301
|
+
'Comments',
|
302
|
+
##
|
303
|
+
'Status', # e.g.
|
304
|
+
'UTC', # date utc
|
305
|
+
]
|
306
|
+
|
307
|
+
## note: change season_key from 2019/20 to 2019-20 (for path/directory!!!!)
|
308
|
+
write_csv( "#{config.convert.out_dir}/#{season.to_path}/#{league.downcase}.csv",
|
309
|
+
recs,
|
310
|
+
headers: headers )
|
311
|
+
|
312
|
+
|
313
|
+
teams.each do |name, count|
|
314
|
+
rec = teams_by_name[ name ]
|
315
|
+
print " #{count}x "
|
316
|
+
print name
|
317
|
+
if rec
|
318
|
+
print " | #{rec['shortName']} " if name != rec['shortName']
|
319
|
+
print " › #{rec['area']['name']}"
|
320
|
+
print " - #{rec['address']}"
|
321
|
+
else
|
322
|
+
puts "!! ERROR - no team record found in teams.json for #{name}"
|
323
|
+
exit 1
|
324
|
+
end
|
325
|
+
print "\n"
|
326
|
+
end
|
327
|
+
|
328
|
+
## pp stat
|
329
|
+
end # method convert
|
330
|
+
end # module Footballdata
|
331
|
+
|
332
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Footballdata
|
2
|
+
|
3
|
+
|
4
|
+
#################
|
5
|
+
## porcelain "api"
|
6
|
+
def self.schedule( league:, season: )
|
7
|
+
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
8
|
+
|
9
|
+
league_code = LEAGUES[ league.downcase ]
|
10
|
+
puts " mapping league >#{league}< to >#{league_code}<"
|
11
|
+
|
12
|
+
Metal.teams( league_code, season.start_year )
|
13
|
+
Metal.matches( league_code, season.start_year )
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def self.matches( league:, season: )
|
18
|
+
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
19
|
+
|
20
|
+
league_code = LEAGUES[ league.downcase ]
|
21
|
+
puts " mapping league >#{league}< to >#{league_code}<"
|
22
|
+
Metal.matches( league_code, season.start_year )
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def self.teams( league:, season: )
|
27
|
+
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
28
|
+
|
29
|
+
league_code = LEAGUES[ league.downcase ]
|
30
|
+
puts " mapping league >#{league}< to >#{league_code}<"
|
31
|
+
Metal.teams( league_code, season.start_year )
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
##################
|
37
|
+
## plumbing metal "helpers"
|
38
|
+
|
39
|
+
class Metal
|
40
|
+
|
41
|
+
def self.get( url,
|
42
|
+
auth: true,
|
43
|
+
headers: {} )
|
44
|
+
|
45
|
+
token = ENV['FOOTBALLDATA']
|
46
|
+
## note: because of public workflow log - do NOT output token
|
47
|
+
## puts token
|
48
|
+
|
49
|
+
request_headers = {}
|
50
|
+
request_headers['X-Auth-Token'] = token if auth && token
|
51
|
+
request_headers['User-Agent'] = 'ruby'
|
52
|
+
request_headers['Accept'] = '*/*'
|
53
|
+
|
54
|
+
request_headers = request_headers.merge( headers ) unless headers.empty?
|
55
|
+
|
56
|
+
|
57
|
+
## note: add format: 'json' for pretty printing json (before) save in cache
|
58
|
+
response = Webget.call( url, headers: request_headers )
|
59
|
+
|
60
|
+
## for debugging print pretty printed json first 400 chars
|
61
|
+
puts response.json.pretty_inspect[0..400]
|
62
|
+
|
63
|
+
exit 1 if response.status.nok? # e.g. HTTP status code != 200
|
64
|
+
|
65
|
+
response.json
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
BASE_URL = 'http://api.football-data.org/v4'
|
71
|
+
|
72
|
+
|
73
|
+
def self.competitions_url
|
74
|
+
"#{BASE_URL}/competitions"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.competitions( auth: false )
|
78
|
+
get( competitions_url, auth: auth )
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
## just use matches_url - why? why not?
|
83
|
+
def self.competition_matches_url( code, year ) "#{BASE_URL}/competitions/#{code}/matches?season=#{year}"; end
|
84
|
+
def self.competition_teams_url( code, year ) "#{BASE_URL}/competitions/#{code}/teams?season=#{year}"; end
|
85
|
+
def self.competition_standings_url( code, year ) "#{BASE_URL}/competitions/#{code}/standings?season=#{year}"; end
|
86
|
+
def self.competition_scorers_url( code, year ) "#{BASE_URL}/competitions/#{code}/scorers?season=#{year}"; end
|
87
|
+
|
88
|
+
def self.matches( code, year,
|
89
|
+
headers: {} )
|
90
|
+
get( competition_matches_url( code, year ),
|
91
|
+
headers: headers )
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.todays_matches_url( date=Date.today )
|
95
|
+
"#{BASE_URL}/matches?"+
|
96
|
+
"dateFrom=#{date.strftime('%Y-%m-%d')}&"+
|
97
|
+
"dateTo=#{(date+1).strftime('%Y-%m-%d')}"
|
98
|
+
end
|
99
|
+
def self.todays_matches( date=Date.today ) ## use/rename to matches_today or such - why? why not?
|
100
|
+
get( todays_matches_url( date ) )
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def self.teams( code, year ) get( competition_teams_url( code, year )); end
|
105
|
+
def self.standings( code, year ) get( competition_standings_url( code, year )); end
|
106
|
+
def self.scorers( code, year ) get( competition_scorers_url( code, year )); end
|
107
|
+
|
108
|
+
################
|
109
|
+
## more
|
110
|
+
def self.competition( code )
|
111
|
+
get( "#{BASE_URL}/competitions/#{code}" )
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.team( id )
|
115
|
+
get( "#{BASE_URL}/teams/#{id}" )
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.match( id )
|
119
|
+
get( "#{BASE_URL}/matches/#{id}" )
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.person( id )
|
123
|
+
get( "#{BASE_URL}/persons/#{id}" )
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.areas
|
127
|
+
get( "#{BASE_URL}/areas" )
|
128
|
+
end
|
129
|
+
end # class Metal
|
130
|
+
end # module Footballdata
|
131
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
##
|
3
|
+
### check - change Generator to Writer
|
4
|
+
## and write( league:, season: ) - why? why not?
|
5
|
+
|
6
|
+
module Footballdata
|
7
|
+
class Generator
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
###########
|
12
|
+
## always download for now - why? why not?
|
13
|
+
## support cache - why? why not?
|
14
|
+
def generate( league:, season: )
|
15
|
+
|
16
|
+
## for testing use cached version always - why? why not?
|
17
|
+
## step 1 - download
|
18
|
+
Footballdata.schedule( league: league, season: season )
|
19
|
+
|
20
|
+
## step 2 - convert (to .csv)
|
21
|
+
|
22
|
+
## todo/fix - convert in-memory and return matches
|
23
|
+
Footballdata.convert( league: league, season: season )
|
24
|
+
|
25
|
+
source_dir = Footballdata.config.convert.out_dir
|
26
|
+
|
27
|
+
Writer.write( league: league,
|
28
|
+
season: season,
|
29
|
+
source: source_dir )
|
30
|
+
end # def generate
|
31
|
+
|
32
|
+
end # class Generator
|
33
|
+
end # module Footballdata
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Footballdata
|
2
|
+
|
3
|
+
LEAGUES = {
|
4
|
+
'eng.1' => 'PL', # incl. team(s) from wales
|
5
|
+
'eng.2' => 'ELC',
|
6
|
+
# PL - Premier League , England 27 seasons | 2019-08-09 - 2020-07-25 / matchday 31
|
7
|
+
# ELC - Championship , England 3 seasons | 2019-08-02 - 2020-07-22 / matchday 38
|
8
|
+
#
|
9
|
+
# 2019 => 2019/20
|
10
|
+
# 2018 => 2018/19
|
11
|
+
# 2017 => xxx 2017-18 - requires subscription !!!
|
12
|
+
|
13
|
+
'es.1' => 'PD',
|
14
|
+
# PD - Primera Division , Spain 27 seasons | 2019-08-16 - 2020-07-19 / matchday 31
|
15
|
+
|
16
|
+
'pt.1' => 'PPL',
|
17
|
+
# PPL - Primeira Liga , Portugal 9 seasons | 2019-08-10 - 2020-07-26 / matchday 28
|
18
|
+
|
19
|
+
'de.1' => 'BL1',
|
20
|
+
# BL1 - Bundesliga , Germany 24 seasons | 2019-08-16 - 2020-06-27 / matchday 34
|
21
|
+
|
22
|
+
'nl.1' => 'DED',
|
23
|
+
# DED - Eredivisie , Netherlands 10 seasons | 2019-08-09 - 2020-03-08 / matchday 34
|
24
|
+
|
25
|
+
'fr.1' => 'FL1', # incl. team(s) monaco
|
26
|
+
# FL1 - Ligue 1, France
|
27
|
+
# 9 seasons | 2019-08-09 - 2020-05-31 / matchday 38
|
28
|
+
#
|
29
|
+
# 2019 => 2019/20
|
30
|
+
# 2018 => 2018/19
|
31
|
+
# 2017 => xxx 2017-18 - requires subscription !!!
|
32
|
+
|
33
|
+
'it.1' => 'SA',
|
34
|
+
# SA - Serie A , Italy 15 seasons | 2019-08-24 - 2020-08-02 / matchday 27
|
35
|
+
|
36
|
+
'br.1' => 'BSA',
|
37
|
+
# BSA - Série A, Brazil
|
38
|
+
# 4 seasons | 2020-05-03 - 2020-12-06 / matchday 10
|
39
|
+
#
|
40
|
+
# 2020 => 2020
|
41
|
+
# 2019 => 2019
|
42
|
+
# 2018 => 2018
|
43
|
+
# 2017 => xxx 2017 - requires subscription !!!
|
44
|
+
|
45
|
+
## todo/check: use champs and NOT cl - why? why not?
|
46
|
+
'uefa.cl' => 'CL', ## note: cl is country code for chile!! - use champs - why? why not?
|
47
|
+
## was europe.cl / cl
|
48
|
+
|
49
|
+
## Copa Libertadores
|
50
|
+
'copa.l' => 'CLI',
|
51
|
+
|
52
|
+
############
|
53
|
+
## national teams
|
54
|
+
'euro' => 'EC',
|
55
|
+
'world' => 'WC',
|
56
|
+
|
57
|
+
}
|
58
|
+
end # module Footballdata
|
59
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Footballdata
|
2
|
+
|
3
|
+
#########
|
4
|
+
## Mods
|
5
|
+
# e.g.
|
6
|
+
# Cardiff City FC | Cardiff › Wales - Cardiff City Stadium, Leckwith Road Cardiff CF11 8AZ
|
7
|
+
# AS Monaco FC | Monaco › Monaco - Avenue des Castellans Monaco 98000
|
8
|
+
|
9
|
+
MODS = {
|
10
|
+
'br.1' => {
|
11
|
+
'América FC' => 'América Mineiro', # in year 2018 ??
|
12
|
+
},
|
13
|
+
'pt.1' => {
|
14
|
+
'Vitória SC' => 'Vitória Guimarães', ## avoid easy confusion with Vitória SC <=> Vitória FC
|
15
|
+
'Vitória FC' => 'Vitória Setúbal',
|
16
|
+
},
|
17
|
+
}
|
18
|
+
|
19
|
+
end # module Footballdata
|
20
|
+
|
21
|
+
|
22
|
+
|