-
Notifications
You must be signed in to change notification settings - Fork 0
/
init.lua
298 lines (267 loc) · 11 KB
/
init.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
local obj = {}
obj.__index = obj
-- Metadata
obj.name = "FotMobSchedule"
obj.version = "2.11"
obj.author = "James Turnbull <[email protected]>"
obj.homepage = "https://github.com/jamtur01/FotMobSchedule.spoon"
obj.license = "MIT - https://opensource.org/licenses/MIT"
-- Constants
local DEFAULT_INTERVAL = 3600
local BASE_URL = "https://www.fotmob.com"
local API_BASE_URL = "https://www.fotmob.com/api/"
local DEFAULT_TEAM = { name = "Arsenal Women", id = 258657 }
local DEFAULT_SHOW_NEXT_GAMES = 5
local DEFAULT_SHOW_LAST_GAMES = 5
local X_FM_REQ_HEADER_VALUE = "eyJib2R5Ijp7InVybCI6Ii9hcGkvbWF0Y2hlcz9kYXRlPTIwMjQxMTA2JnRpbWV6b25lPUF1c3RyYWxpYSUyRk1lbGJvdXJuZSZjY29kZTM9QVVTIiwiY29kZSI6MTczMDgyNDU4NDUwMywiZm9vIjoiNThlOWE0MDg1In0sInNpZ25hdHVyZSI6IkRBOTk0NTJBQ0RDNTlFOTRGQzFBQjE4NDdBNTU3RUY1In0="
-- Configuration
obj.logger = hs.logger.new('FotMobSchedule', 'info')
obj.interval = DEFAULT_INTERVAL
obj.menuBar = nil
obj.lastSchedule = nil
obj.teams = hs.settings.get("FotMobSchedule_teams") or { DEFAULT_TEAM }
obj.showNextGames = hs.settings.get("FotMobSchedule_showNextGames") or DEFAULT_SHOW_NEXT_GAMES
obj.showLastGames = hs.settings.get("FotMobSchedule_showLastGames") or DEFAULT_SHOW_LAST_GAMES
-- Helper functions
local function fetchData(url, callback)
obj.logger.d("Fetching data from URL: " .. url)
local headers = {
["x-fm-req"] = X_FM_REQ_HEADER_VALUE
}
hs.http.asyncGet(url, headers, function(status, body, responseHeaders)
if status ~= 200 then
obj.logger.ef("Failed to fetch data from %s. Status: %d", url, status)
callback(nil)
else
callback(hs.json.decode(body))
end
end)
end
local function getTimestampFromUTCDateTable(dateTable)
local timestampUTC = os.time(dateTable)
local timeDiff = os.difftime(os.time(), os.time(os.date("!*t")))
return timestampUTC + timeDiff
end
local function formatDate(utcTime)
local timestamp = nil
if type(utcTime) == "string" then
local pattern = "(%d+)-(%d+)-(%d+)T(%d+):(%d+)"
local year, month, day, hour, min = utcTime:match(pattern)
if year then
local dateTable = {year=year, month=month, day=day, hour=hour, min=min, sec=0}
timestamp = getTimestampFromUTCDateTable(dateTable)
end
elseif type(utcTime) == "number" then
timestamp = utcTime / 1000
end
if timestamp then
return os.date("%a %b %d, %I:%M %p", timestamp), timestamp
else
return "Date unknown", nil
end
end
local function getMatchStatus(fixture)
local status = fixture.status
if status.cancelled then
return "cancelled"
elseif status.finished then
return "finished"
elseif status.started and status.ongoing then
return "in-progress"
elseif not status.started and not status.cancelled and not status.finished then
local matchDate, _ = formatDate(status.utcTime)
local today = os.date("%x", os.time())
if matchDate and matchDate:find(today) then
return "today"
else
return "upcoming"
end
else
return "unknown"
end
end
-- Main functions
local function processSchedule(teamData)
obj.logger.d("Processing schedule for team: " .. teamData.details.name)
local schedule = {}
local fixtures = teamData.fixtures and teamData.fixtures.allFixtures and teamData.fixtures.allFixtures.fixtures
if fixtures then
for _, fixture in ipairs(fixtures) do
local matchDateStr, timestamp = formatDate(fixture.status.utcTime)
local status = getMatchStatus(fixture)
local matchTitle = string.format("%s vs %s", fixture.home.name, fixture.away.name)
local tournamentName = fixture.tournament and fixture.tournament.name or "Unknown Tournament"
local matchUrl = BASE_URL .. fixture.pageUrl
local result = fixture.status.scoreStr or "Upcoming"
local homeScore = fixture.home.score
local awayScore = fixture.away.score
obj.logger.d("Fixture status: " .. hs.inspect(fixture.status))
obj.logger.d(string.format("Home Score: %s, Away Score: %s", tostring(homeScore), tostring(awayScore)))
local winner = nil
if status == "finished" then
obj.logger.d(string.format("Match finished between %s and %s", fixture.home.name, fixture.away.name))
if homeScore ~= nil and awayScore ~= nil then
if homeScore > awayScore then
winner = fixture.home.id
elseif awayScore > homeScore then
winner = fixture.away.id
else
winner = "draw"
end
else
obj.logger.w("Scores are nil; cannot determine winner")
end
else
obj.logger.d("Match status is not 'finished'; skipping winner determination")
end
local matchData = {
date = matchDateStr,
timestamp = timestamp,
match = matchTitle,
home = fixture.home,
away = fixture.away,
status = status,
url = matchUrl,
result = result,
tournament = tournamentName,
winner = winner,
}
table.insert(schedule, matchData)
end
else
obj.logger.w("No fixtures found or unexpected data structure")
end
obj.logger.d("Processed " .. #schedule .. " fixtures")
return schedule
end
function obj:fetchSchedule(callback)
obj.logger.d("Starting to fetch schedules")
local allSchedules = {}
local remainingTeams = #self.teams
for _, team in ipairs(self.teams) do
obj.logger.d("Fetching schedule for team: " .. team.name)
fetchData(API_BASE_URL .. "teams?id=" .. team.id, function(data)
if data then
allSchedules[team.name] = processSchedule(data)
else
obj.logger.e("Failed to fetch data for team: " .. team.name)
end
remainingTeams = remainingTeams - 1
if remainingTeams == 0 then
obj.logger.d("Finished fetching schedules")
self.lastSchedule = allSchedules
callback(allSchedules)
end
end)
end
end
function obj:updateMenu()
self:fetchSchedule(function(schedules)
if not schedules then
hs.notify.new({title="FotMob Schedule Error", informativeText="Failed to fetch FotMob schedule"}):send()
return
end
local menuItems = {}
local matches = {}
for team, schedule in pairs(schedules) do
for _, match in ipairs(schedule) do
table.insert(matches, match)
end
end
local matchesByTournament = {}
for _, match in ipairs(matches) do
local tournament = match.tournament
if not matchesByTournament[tournament] then
matchesByTournament[tournament] = {}
end
table.insert(matchesByTournament[tournament], match)
end
local sortedTournaments = {}
for tournament in pairs(matchesByTournament) do
table.insert(sortedTournaments, tournament)
end
table.sort(sortedTournaments)
for _, tournament in ipairs(sortedTournaments) do
local tournamentMatches = matchesByTournament[tournament]
table.sort(tournamentMatches, function(a, b)
return a.timestamp < b.timestamp
end)
table.insert(menuItems, { title = tournament, disabled = true })
for _, match in ipairs(tournamentMatches) do
local icon = nil
if match.status == "finished" then
icon = hs.image.imageFromName("NSStatusAvailable")
elseif match.status == "in-progress" then
icon = hs.image.imageFromName("NSStatusPartiallyAvailable")
elseif match.status == "cancelled" then
icon = hs.image.imageFromName("NSStatusUnavailable")
elseif match.status == "today" then
icon = hs.image.imageFromName("NSTouchBarComposeTemplate")
else
icon = hs.image.imageFromName("NSStatusNone")
end
local title = match.match
if match.status == "finished" then
local trophy = "🏆 "
local drawEmoji = "🤝 "
local matchTitleWithEmoji = title
if match.winner then
if match.winner == "draw" then
matchTitleWithEmoji = string.format("%s%s vs %s", drawEmoji, match.home.name, match.away.name)
elseif match.winner == match.home.id then
matchTitleWithEmoji = string.format("%s%s vs %s", trophy, match.home.name, match.away.name)
elseif match.winner == match.away.id then
matchTitleWithEmoji = string.format("%s vs %s%s", match.home.name, match.away.name, trophy)
end
end
title = string.format("%s (%s)", matchTitleWithEmoji, match.result)
elseif match.status == "in-progress" then
title = string.format("%s (Live)", title)
else
title = string.format("%s - %s", title, match.date)
end
local styledTitle = hs.styledtext.new(title, {
font = { name = "Helvetica", size = 14 },
paragraphStyle = { alignment = "left" }
})
table.insert(menuItems, {
title = styledTitle,
image = icon,
fn = function() hs.urlevent.openURL(match.url) end,
tooltip = match.date
})
end
table.insert(menuItems, { title = "-" })
end
if #menuItems == 0 then
table.insert(menuItems, { title = "No matches found" })
end
self.menuBar:setMenu(menuItems)
end)
end
function obj:start()
obj.logger.d("Starting FotMobSchedule")
if not self.menuBar then
self.menuBar = hs.menubar.new()
local iconPath = hs.spoons.resourcePath("fotmob-icon.png")
local iconImage = hs.image.imageFromPath(iconPath)
if not self.menuBar:setIcon(iconImage) then
self.menuBar:setTitle("F")
end
self.menuBar:setTooltip("FotMob Schedule")
end
self:updateMenu()
self.timer = hs.timer.new(self.interval, function() self:updateMenu() end)
self.timer:start()
return self
end
function obj:stop()
obj.logger.i("Stopping FotMobSchedule")
if self.timer then self.timer:stop() end
if self.menuBar then
self.menuBar:delete()
self.menuBar = nil
end
return self
end
return obj