@vixoniccom/footbal-score 1.4.6-dev.3 → 1.4.6-dev.4
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.
- package/CHANGELOG.md +2 -0
- package/configuration/dataGroup/dataInputs.ts +5 -37
- package/package.json +1 -1
- package/src/App.tsx +21 -153
- package/src/football-loader.ts +16 -0
- package/src/globals.d.ts +28 -0
- package/src/main.ts +34 -8
- package/src/parameters.d.ts +1 -1
- package/src/helpers/getFixture.ts +0 -48
- package/src/helpers/mockFixtures.ts +0 -74
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.4.6-dev.4](https://github.com/Vixonic/store-football-score/compare/v1.4.6-dev.2...v1.4.6-dev.4) (2026-06-22)
|
|
6
|
+
|
|
5
7
|
### [1.4.6-dev.3](https://github.com/Vixonic/store-football-score/compare/v1.4.6-dev.2...v1.4.6-dev.3) (2026-06-17)
|
|
6
8
|
|
|
7
9
|
### [1.4.6-dev.2](https://github.com/Vixonic/store-football-score/compare/v1.4.6-dev.1...v1.4.6-dev.2) (2026-06-17)
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import { Label, NumberInput, NumberRangeValue, SelectInput, Switch, TextFormat, TextInput } from '@vixoniccom/modules'
|
|
1
|
+
import { Label, NumberInput, NumberRangeValue, SelectInput, ServiceInput, Switch, TextFormat, TextInput } from '@vixoniccom/modules'
|
|
2
2
|
import { ShowValidations, timeSelect } from '../utils'
|
|
3
3
|
|
|
4
4
|
export const dataInputs = [
|
|
5
5
|
new Label({ label: 'Datos' }),
|
|
6
|
-
new
|
|
7
|
-
id: '
|
|
8
|
-
label: '
|
|
9
|
-
|
|
10
|
-
items: [
|
|
11
|
-
{ label: 'Mundial', value: '1' },
|
|
12
|
-
{ label: 'Copa América', value: '9' },
|
|
13
|
-
{ label: 'Euro Copa', value: '4' },
|
|
14
|
-
{ label: 'Copa Oro (Concacaf)', value: '22' },
|
|
15
|
-
{ label: 'Mundial de clubes', value: '15' },
|
|
16
|
-
{ label: 'Primera división Chile', value: '1220' },
|
|
17
|
-
{ label: 'Liga profesional Argentina', value: '128' },
|
|
18
|
-
],
|
|
19
|
-
defaultValue: '9',
|
|
6
|
+
new ServiceInput({
|
|
7
|
+
id: 'footballService',
|
|
8
|
+
label: 'Servicio de fútbol',
|
|
9
|
+
serviceType: 'FootballService' as any,
|
|
20
10
|
}),
|
|
21
11
|
new Switch({
|
|
22
12
|
id: 'matchesYesterday',
|
|
@@ -46,21 +36,6 @@ export const dataInputs = [
|
|
|
46
36
|
items: [...timeSelect],
|
|
47
37
|
defaultValue: '7pm',
|
|
48
38
|
}),
|
|
49
|
-
new SelectInput({
|
|
50
|
-
id: 'updateTime',
|
|
51
|
-
label: 'Actualización',
|
|
52
|
-
required: true,
|
|
53
|
-
items: [
|
|
54
|
-
{ label: '5 minutos', value: 300000 },
|
|
55
|
-
{ label: '10 minutos', value: 600000 },
|
|
56
|
-
{ label: '30 minutos', value: 1800000 },
|
|
57
|
-
{ label: '1 hora', value: 3600000 },
|
|
58
|
-
{ label: '6 horas', value: 21600000 },
|
|
59
|
-
{ label: '12 horas', value: 43200000 },
|
|
60
|
-
{ label: 'Diario', value: 86400000 },
|
|
61
|
-
],
|
|
62
|
-
defaultValue: 300000,
|
|
63
|
-
}),
|
|
64
39
|
new Switch({
|
|
65
40
|
id: 'titleEnabled',
|
|
66
41
|
label: 'Título App',
|
|
@@ -98,11 +73,4 @@ export const dataInputs = [
|
|
|
98
73
|
description: 'Duración por página (en segundos)',
|
|
99
74
|
range: new NumberRangeValue(1, 100),
|
|
100
75
|
}),
|
|
101
|
-
new Switch({
|
|
102
|
-
id: 'debugTimezone',
|
|
103
|
-
label: 'Debug Timezone',
|
|
104
|
-
displayOnTrue: 'Visible',
|
|
105
|
-
displayOnFalse: 'Oculto',
|
|
106
|
-
defaultValue: false,
|
|
107
|
-
}),
|
|
108
76
|
]
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -1,152 +1,39 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import moment from 'moment'
|
|
3
3
|
import { FontLoader, FormattedText, Table } from './components'
|
|
4
|
-
import { getFixture, resolvedTimezone } from './helpers/getFixture'
|
|
5
4
|
|
|
6
5
|
export type Props = {
|
|
7
|
-
|
|
6
|
+
appData?: VixonicData
|
|
8
7
|
start: boolean
|
|
8
|
+
fixtures: FootballFixture[]
|
|
9
|
+
serviceError?: string
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
|
|
12
|
+
export const App: React.FunctionComponent<Props> = ({ appData, start, fixtures }) => {
|
|
13
|
+
if (!appData || !start) return null
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const { parameters, downloadsPath } = appData
|
|
16
|
+
const { backgroundImage, padding, msj0 = '', matchesYesterday, matchesTomorrow } = parameters
|
|
15
17
|
|
|
16
|
-
const log = (msg: string, ...args: any[]) =>
|
|
17
|
-
console.log(`[⚽ football-score ${new Date().toLocaleTimeString()}]`, msg, ...args)
|
|
18
|
-
|
|
19
|
-
// Returns the timestamp (ms) when the next fetch should happen based on fixture states.
|
|
20
|
-
// Persisted in localStorage so re-mounts don't reset the schedule.
|
|
21
|
-
function computeNextFetchTime(fixtures: any[], pollIntervalMs: number): number {
|
|
22
|
-
const now = Date.now()
|
|
23
|
-
|
|
24
|
-
const liveMatches = fixtures.filter(f => LIVE_STATUSES.includes(f.fixture?.status?.short))
|
|
25
|
-
if (liveMatches.length > 0) {
|
|
26
|
-
const next = new Date(now + pollIntervalMs)
|
|
27
|
-
log(`🟢 ${liveMatches.length} partido(s) en vivo → próximo fetch en ${next.toLocaleTimeString()} (${pollIntervalMs / 1000}s)`)
|
|
28
|
-
return now + pollIntervalMs
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const nextMatchAt = fixtures
|
|
32
|
-
.filter(f => f.fixture?.status?.short === 'NS')
|
|
33
|
-
.map(f => new Date(f.fixture.date).getTime())
|
|
34
|
-
.filter(t => t > now)
|
|
35
|
-
.sort((a, b) => a - b)[0]
|
|
36
|
-
|
|
37
|
-
if (nextMatchAt !== undefined) {
|
|
38
|
-
const wakeUp = nextMatchAt - PRE_MATCH_MS
|
|
39
|
-
if (wakeUp <= now) {
|
|
40
|
-
const next = new Date(now + pollIntervalMs)
|
|
41
|
-
log(`🟡 Dentro de ventana pre-partido → próximo fetch en ${next.toLocaleTimeString()}`)
|
|
42
|
-
return now + pollIntervalMs
|
|
43
|
-
}
|
|
44
|
-
log(`⏰ Próximo partido: ${new Date(nextMatchAt).toLocaleTimeString()} → fetch a las ${new Date(wakeUp).toLocaleTimeString()} (10 min antes)`)
|
|
45
|
-
return wakeUp
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const tomorrow = new Date()
|
|
49
|
-
tomorrow.setDate(tomorrow.getDate() + 1)
|
|
50
|
-
tomorrow.setHours(0, 15, 0, 0)
|
|
51
|
-
log(`💤 Sin partidos pendientes → próximo check: ${tomorrow.toLocaleString()}`)
|
|
52
|
-
return tomorrow.getTime()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const App: React.FunctionComponent<Props> = ({ data, start }) => {
|
|
56
|
-
const [dataFixture, setDataFixture] = useState<any[]>([])
|
|
57
|
-
const [leagueState, setLeagueState] = useState<string>('')
|
|
58
|
-
const { parameters, downloadsPath } = data
|
|
59
|
-
const { backgroundImage, padding, msj0 = '', league = '9', debugTimezone = false } = parameters
|
|
60
18
|
const backgroundImageState = backgroundImage?.filename
|
|
61
19
|
? `url("${downloadsPath}/${backgroundImage.filename}")`
|
|
62
20
|
: ''
|
|
63
21
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const paramsRef = useRef(parameters)
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
leagueRef.current = league
|
|
70
|
-
paramsRef.current = parameters
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
const clearSchedule = () => {
|
|
74
|
-
if (timeoutRef.current !== null) {
|
|
75
|
-
clearTimeout(timeoutRef.current)
|
|
76
|
-
timeoutRef.current = null
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const fetchAndSchedule = async () => {
|
|
81
|
-
const { matchesYesterday, matchesTomorrow, updateTime: ut = 300000 } = paramsRef.current
|
|
82
|
-
const cached: any = await localforage.getItem('football-score')
|
|
83
|
-
|
|
84
|
-
log(`🔄 Fetching fixtures (liga ${leagueRef.current})...`)
|
|
85
|
-
let fixtures: any[]
|
|
86
|
-
try {
|
|
87
|
-
fixtures = await getFixture(leagueRef.current, matchesYesterday, matchesTomorrow)
|
|
88
|
-
log(`✅ ${fixtures.length} fixture(s) recibidos`)
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.error('[football-score] API error:', err)
|
|
91
|
-
fixtures = cached?.data ?? []
|
|
92
|
-
log(`❌ API falló → usando cache (${fixtures.length} fixtures)`)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const nextFetchTime = computeNextFetchTime(fixtures, ut)
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
await localforage.setItem('football-score', { date: Date.now(), data: fixtures, nextFetchTime })
|
|
99
|
-
} catch (err) {
|
|
100
|
-
console.error('[football-score] localStorage write error', err)
|
|
101
|
-
}
|
|
22
|
+
const today = moment().format('YYYY-MM-DD')
|
|
23
|
+
const yesterdayStr = moment().subtract(1, 'days').format('YYYY-MM-DD')
|
|
24
|
+
const tomorrowStr = moment().add(1, 'days').format('YYYY-MM-DD')
|
|
102
25
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const cached: any = await localforage.getItem('football-score')
|
|
111
|
-
const now = Date.now()
|
|
112
|
-
|
|
113
|
-
if (!cached?.data) {
|
|
114
|
-
log(`🆕 Sin cache → fetch inmediato`)
|
|
115
|
-
fetchAndSchedule()
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
setDataFixture(cached.data)
|
|
120
|
-
|
|
121
|
-
if (!cached.nextFetchTime || cached.nextFetchTime <= now) {
|
|
122
|
-
log(`🔁 Mount check: nextFetchTime ya pasó (${cached.nextFetchTime ? new Date(cached.nextFetchTime).toLocaleTimeString() : 'null'}) → fetch inmediato`)
|
|
123
|
-
fetchAndSchedule()
|
|
124
|
-
} else {
|
|
125
|
-
const mins = Math.round((cached.nextFetchTime - now) / 60000)
|
|
126
|
-
log(`💾 Mount check: usando cache → próximo fetch en ${new Date(cached.nextFetchTime).toLocaleTimeString()} (~${mins} min)`)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (!start) return
|
|
132
|
-
setLeagueState(league)
|
|
133
|
-
init()
|
|
134
|
-
return clearSchedule
|
|
135
|
-
}, [data])
|
|
136
|
-
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
if (leagueState === '' || leagueState === league) return
|
|
139
|
-
setLeagueState(league)
|
|
140
|
-
clearSchedule()
|
|
141
|
-
localforage.clear()
|
|
142
|
-
fetchAndSchedule()
|
|
143
|
-
}, [league])
|
|
144
|
-
|
|
145
|
-
const timezone = resolvedTimezone
|
|
26
|
+
const filteredFixtures = fixtures.filter(f => {
|
|
27
|
+
const fixtureDate = moment(f.fixture.date).format('YYYY-MM-DD')
|
|
28
|
+
if (fixtureDate === today) return true
|
|
29
|
+
if (fixtureDate === yesterdayStr && matchesYesterday) return true
|
|
30
|
+
if (fixtureDate === tomorrowStr && matchesTomorrow) return true
|
|
31
|
+
return false
|
|
32
|
+
})
|
|
146
33
|
|
|
147
34
|
return (
|
|
148
35
|
<>
|
|
149
|
-
{
|
|
36
|
+
{filteredFixtures.length > 0 ?
|
|
150
37
|
<div style={{
|
|
151
38
|
position: 'absolute',
|
|
152
39
|
top: 0,
|
|
@@ -163,7 +50,7 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
|
|
|
163
50
|
<FormattedText text={parameters.title0 || 'Partidos Mundial'} format={parameters.titleFormat} />
|
|
164
51
|
</div>
|
|
165
52
|
}
|
|
166
|
-
<Table dataFixture={
|
|
53
|
+
<Table dataFixture={filteredFixtures} parameters={parameters} />
|
|
167
54
|
</div> :
|
|
168
55
|
<div style={{
|
|
169
56
|
position: 'absolute',
|
|
@@ -183,25 +70,6 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
|
|
|
183
70
|
</div>
|
|
184
71
|
</div>
|
|
185
72
|
}
|
|
186
|
-
{debugTimezone && (
|
|
187
|
-
<div style={{
|
|
188
|
-
position: 'fixed',
|
|
189
|
-
bottom: 8,
|
|
190
|
-
right: 8,
|
|
191
|
-
background: 'rgba(0,0,0,0.75)',
|
|
192
|
-
color: '#00ff00',
|
|
193
|
-
fontSize: 10,
|
|
194
|
-
fontFamily: 'monospace',
|
|
195
|
-
padding: '4px 8px',
|
|
196
|
-
borderRadius: 4,
|
|
197
|
-
zIndex: 9999,
|
|
198
|
-
lineHeight: 1.4,
|
|
199
|
-
pointerEvents: 'none',
|
|
200
|
-
whiteSpace: 'nowrap',
|
|
201
|
-
}}>
|
|
202
|
-
TZ: {timezone}
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
205
73
|
</>
|
|
206
74
|
)
|
|
207
75
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class FootballLoader {
|
|
2
|
+
public onData?: (fixtures: FootballFixture[]) => void = undefined
|
|
3
|
+
public onError?: (error: string) => void = undefined
|
|
4
|
+
|
|
5
|
+
update(appData: VixonicData) {
|
|
6
|
+
const serviceId = appData.parameters.footballService?.id
|
|
7
|
+
if (!serviceId || !appData.services[serviceId]) return
|
|
8
|
+
|
|
9
|
+
const rawData = appData.services[serviceId].data as FootballServiceData | undefined
|
|
10
|
+
const fixtures = rawData?.fixtures ?? []
|
|
11
|
+
|
|
12
|
+
this.onData?.(fixtures)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
stop() {}
|
|
16
|
+
}
|
package/src/globals.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare interface FootballFixture {
|
|
2
|
+
fixture: {
|
|
3
|
+
id: number
|
|
4
|
+
date: string
|
|
5
|
+
status: { long: string; short: string; elapsed: number | null }
|
|
6
|
+
venue: { name: string; city: string }
|
|
7
|
+
}
|
|
8
|
+
league: {
|
|
9
|
+
id: number
|
|
10
|
+
name: string
|
|
11
|
+
country: string
|
|
12
|
+
logo: string
|
|
13
|
+
round: string
|
|
14
|
+
}
|
|
15
|
+
teams: {
|
|
16
|
+
home: { id: number; name: string; logo: string; winner: boolean | null }
|
|
17
|
+
away: { id: number; name: string; logo: string; winner: boolean | null }
|
|
18
|
+
}
|
|
19
|
+
goals: { home: number | null; away: number | null }
|
|
20
|
+
score: {
|
|
21
|
+
halftime: { home: number | null; away: number | null }
|
|
22
|
+
fulltime: { home: number | null; away: number | null }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare interface FootballServiceData {
|
|
27
|
+
fixtures: FootballFixture[]
|
|
28
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -1,27 +1,53 @@
|
|
|
1
|
-
import ReactDOM from 'react-dom'
|
|
2
1
|
import React from 'react'
|
|
3
|
-
import
|
|
4
|
-
|
|
2
|
+
import ReactDOM from 'react-dom'
|
|
3
|
+
import { App } from './App'
|
|
4
|
+
import { FootballLoader } from './football-loader'
|
|
5
5
|
const { ipcRenderer } = require('electron')
|
|
6
|
+
|
|
6
7
|
let start: boolean = false
|
|
8
|
+
let appData: VixonicData | undefined = undefined
|
|
9
|
+
let fixtures: FootballFixture[] = []
|
|
10
|
+
let serviceError: string | undefined = undefined
|
|
11
|
+
|
|
12
|
+
const footballLoader = new FootballLoader()
|
|
13
|
+
|
|
14
|
+
footballLoader.onData = (update) => {
|
|
15
|
+
fixtures = update
|
|
16
|
+
render()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
footballLoader.onError = (error) => {
|
|
20
|
+
serviceError = error
|
|
21
|
+
render()
|
|
22
|
+
}
|
|
7
23
|
|
|
8
24
|
ipcRenderer.on('preload', (_event: any, data: VixonicData) => {
|
|
9
|
-
|
|
25
|
+
appData = data
|
|
26
|
+
footballLoader.update(data)
|
|
27
|
+
render()
|
|
10
28
|
})
|
|
11
29
|
|
|
12
30
|
ipcRenderer.on('start', (_event: any, data: VixonicData) => {
|
|
31
|
+
appData = data
|
|
32
|
+
footballLoader.update(data)
|
|
13
33
|
start = true
|
|
14
|
-
render(
|
|
34
|
+
render()
|
|
15
35
|
})
|
|
16
36
|
|
|
17
37
|
ipcRenderer.on('update', (_event: any, data: VixonicData) => {
|
|
18
|
-
|
|
38
|
+
appData = data
|
|
39
|
+
footballLoader.update(data)
|
|
40
|
+
render()
|
|
19
41
|
})
|
|
20
42
|
|
|
21
43
|
ipcRenderer.on('finish', (_event: any) => {
|
|
44
|
+
footballLoader.stop()
|
|
22
45
|
console.log('finish')
|
|
23
46
|
})
|
|
24
47
|
|
|
25
|
-
function render(
|
|
26
|
-
ReactDOM.render(
|
|
48
|
+
function render() {
|
|
49
|
+
ReactDOM.render(
|
|
50
|
+
React.createElement(App, { start, appData, fixtures, serviceError }),
|
|
51
|
+
document.getElementById('root')
|
|
52
|
+
)
|
|
27
53
|
}
|
package/src/parameters.d.ts
CHANGED
|
@@ -5,5 +5,5 @@ declare type VixonicData = {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
declare type VixonicParameters = Partial<{
|
|
8
|
-
|
|
8
|
+
footballService: { id: string }, matchesYesterday: boolean, matchesTomorrow: boolean, timeStart: '12m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m' | '7m' | '8m' | '9m' | '10m' | '11m' | '12pm' | '1pm' | '2pm' | '3pm' | '4pm' | '5pm' | '6pm' | '7pm' | '8pm' | '9pm' | '10pm' | '11pm', timeEnd: '12m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m' | '7m' | '8m' | '9m' | '10m' | '11m' | '12pm' | '1pm' | '2pm' | '3pm' | '4pm' | '5pm' | '6pm' | '7pm' | '8pm' | '9pm' | '10pm' | '11pm', titleEnabled: boolean, title0: string, titleFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, msj0: string, formatMjs: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, alignVerticalMsj: 'flex-start' | 'center' | 'flex-end', itemsPerPage: number, durationPerPage: number, backgroundImage: {id?: string, filename?: string, extension?: string}, padding: string, columnGap: string, rowGap: string, timeFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthTime: string, statusFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthStatus: string, goalsFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthScore: string, descriptionEnabled: boolean, descriptionFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthDescription: string, imageSize: number, widthImage: string
|
|
9
9
|
}>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import moment from 'moment'
|
|
2
|
-
import axios from 'axios'
|
|
3
|
-
import { mockFixtures } from './mockFixtures'
|
|
4
|
-
|
|
5
|
-
if (!process.env.FOOTBALL_API_KEY || !process.env.FOOTBALL_API_HOST || !process.env.FOOTBALL_API_URL)
|
|
6
|
-
console.warn('Missing API credentials')
|
|
7
|
-
const FOOTBALL_API_KEY = process.env.FOOTBALL_API_KEY
|
|
8
|
-
const FOOTBALL_API_HOST = process.env.FOOTBALL_API_HOST
|
|
9
|
-
const FOOTBALL_API_URL = process.env.FOOTBALL_API_URL
|
|
10
|
-
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000
|
|
11
|
-
|
|
12
|
-
const USE_MOCK = false
|
|
13
|
-
|
|
14
|
-
export const resolvedTimezone = (() => {
|
|
15
|
-
try {
|
|
16
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
17
|
-
console.log(`[football-score] Timezone resolved: ${tz}`)
|
|
18
|
-
return tz
|
|
19
|
-
} catch (error) {
|
|
20
|
-
console.warn('[football-score] Failed to resolve timezone, defaulting to UTC', error)
|
|
21
|
-
return 'UTC'
|
|
22
|
-
}
|
|
23
|
-
})()
|
|
24
|
-
|
|
25
|
-
export const getFixture = async (league: string, yesterday?: boolean, tomorrow?: boolean) => {
|
|
26
|
-
if (USE_MOCK) return mockFixtures
|
|
27
|
-
|
|
28
|
-
const today = moment().format('YYYY-MM-DD')
|
|
29
|
-
const year = moment().format('YYYY')
|
|
30
|
-
const from = yesterday ? moment(Date.now() - MILLISECONDS_PER_DAY).format('YYYY-MM-DD') : today
|
|
31
|
-
const to = tomorrow ? moment(Date.now() + MILLISECONDS_PER_DAY).format('YYYY-MM-DD') : today
|
|
32
|
-
const timezone = resolvedTimezone
|
|
33
|
-
|
|
34
|
-
const options = {
|
|
35
|
-
method: 'GET',
|
|
36
|
-
url: FOOTBALL_API_URL,
|
|
37
|
-
params: { from, to, league, season: year, timezone },
|
|
38
|
-
headers: { 'X-RapidAPI-Key': FOOTBALL_API_KEY, 'X-RapidAPI-Host': FOOTBALL_API_HOST },
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const response = await axios.request(options)
|
|
43
|
-
return response.data.response
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error(error)
|
|
46
|
-
return []
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
export const mockFixtures = [
|
|
2
|
-
{
|
|
3
|
-
fixture: { id: 979139, timestamp: 1671375600, status: { short: 'PEN' } },
|
|
4
|
-
teams: {
|
|
5
|
-
home: { name: 'Argentina', logo: 'https://media.api-sports.io/football/teams/26.png' },
|
|
6
|
-
away: { name: 'France', logo: 'https://media.api-sports.io/football/teams/2.png' },
|
|
7
|
-
},
|
|
8
|
-
goals: { home: 3, away: 3 },
|
|
9
|
-
score: { penalty: { home: 4, away: 2 } },
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
fixture: { id: 855737, timestamp: 1669111200, status: { short: 'FT' } },
|
|
13
|
-
teams: {
|
|
14
|
-
home: { name: 'Argentina', logo: 'https://media.api-sports.io/football/teams/26.png' },
|
|
15
|
-
away: { name: 'Saudi Arabia', logo: 'https://media.api-sports.io/football/teams/23.png' },
|
|
16
|
-
},
|
|
17
|
-
goals: { home: 1, away: 2 },
|
|
18
|
-
score: { penalty: { home: null, away: null } },
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
fixture: { id: 871850, timestamp: 1669143600, status: { short: 'FT' } },
|
|
22
|
-
teams: {
|
|
23
|
-
home: { name: 'France', logo: 'https://media.api-sports.io/football/teams/2.png' },
|
|
24
|
-
away: { name: 'Australia', logo: 'https://media.api-sports.io/football/teams/20.png' },
|
|
25
|
-
},
|
|
26
|
-
goals: { home: 4, away: 1 },
|
|
27
|
-
score: { penalty: { home: null, away: null } },
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
fixture: { id: 855741, timestamp: 1669208400, status: { short: 'FT' } },
|
|
31
|
-
teams: {
|
|
32
|
-
home: { name: 'Germany', logo: 'https://media.api-sports.io/football/teams/25.png' },
|
|
33
|
-
away: { name: 'Japan', logo: 'https://media.api-sports.io/football/teams/12.png' },
|
|
34
|
-
},
|
|
35
|
-
goals: { home: 1, away: 2 },
|
|
36
|
-
score: { penalty: { home: null, away: null } },
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
fixture: { id: 871851, timestamp: 1669219200, status: { short: 'FT' } },
|
|
40
|
-
teams: {
|
|
41
|
-
home: { name: 'Spain', logo: 'https://media.api-sports.io/football/teams/9.png' },
|
|
42
|
-
away: { name: 'Costa Rica', logo: 'https://media.api-sports.io/football/teams/29.png' },
|
|
43
|
-
},
|
|
44
|
-
goals: { home: 7, away: 0 },
|
|
45
|
-
score: { penalty: { home: null, away: null } },
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
fixture: { id: 855746, timestamp: 1669316400, status: { short: 'FT' } },
|
|
49
|
-
teams: {
|
|
50
|
-
home: { name: 'Brazil', logo: 'https://media.api-sports.io/football/teams/6.png' },
|
|
51
|
-
away: { name: 'Serbia', logo: 'https://media.api-sports.io/football/teams/14.png' },
|
|
52
|
-
},
|
|
53
|
-
goals: { home: 2, away: 0 },
|
|
54
|
-
score: { penalty: { home: null, away: null } },
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
fixture: { id: 855735, timestamp: 1669035600, status: { short: 'FT' } },
|
|
58
|
-
teams: {
|
|
59
|
-
home: { name: 'England', logo: 'https://media.api-sports.io/football/teams/10.png' },
|
|
60
|
-
away: { name: 'Iran', logo: 'https://media.api-sports.io/football/teams/22.png' },
|
|
61
|
-
},
|
|
62
|
-
goals: { home: 6, away: 2 },
|
|
63
|
-
score: { penalty: { home: null, away: null } },
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
fixture: { id: 855745, timestamp: 1669305600, status: { short: 'FT' } },
|
|
67
|
-
teams: {
|
|
68
|
-
home: { name: 'Portugal', logo: 'https://media.api-sports.io/football/teams/27.png' },
|
|
69
|
-
away: { name: 'Ghana', logo: 'https://media.api-sports.io/football/teams/1504.png' },
|
|
70
|
-
},
|
|
71
|
-
goals: { home: 3, away: 2 },
|
|
72
|
-
score: { penalty: { home: null, away: null } },
|
|
73
|
-
},
|
|
74
|
-
]
|