@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 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 SelectInput({
7
- id: 'league',
8
- label: 'Competencia',
9
- required: true,
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vixoniccom/footbal-score",
3
3
  "alias": "Resultados Deportivos",
4
- "version": "1.4.6-dev.3",
4
+ "version": "1.4.6-dev.4",
5
5
  "description": "Muestra resultados en vivo de fútbol.",
6
6
  "main": "main.js",
7
7
  "author": "",
package/src/App.tsx CHANGED
@@ -1,152 +1,39 @@
1
- import React, { useEffect, useRef, useState } from 'react'
2
- import localforage from 'localforage'
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
- data: VixonicData
6
+ appData?: VixonicData
8
7
  start: boolean
8
+ fixtures: FootballFixture[]
9
+ serviceError?: string
9
10
  }
10
11
 
11
- const LIVE_STATUSES = ['1H', 'HT', '2H', 'P', 'ET', 'BT', 'LIVE', 'INT']
12
- const PRE_MATCH_MS = 5 * 60 * 1000
12
+ export const App: React.FunctionComponent<Props> = ({ appData, start, fixtures }) => {
13
+ if (!appData || !start) return null
13
14
 
14
- localforage.config({ name: 'request_api_football' })
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 timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
65
- // Refs keep the timeout callback from closing over stale props
66
- const leagueRef = useRef(league)
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
- setDataFixture(fixtures)
104
-
105
- clearSchedule()
106
- timeoutRef.current = setTimeout(fetchAndSchedule, Math.max(0, nextFetchTime - Date.now()))
107
- }
108
-
109
- const init = async () => {
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
- {dataFixture.length > 0 ?
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={dataFixture} parameters={parameters} />
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
+ }
@@ -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 { App, Props } from './App'
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
- render(data)
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(data)
34
+ render()
15
35
  })
16
36
 
17
37
  ipcRenderer.on('update', (_event: any, data: VixonicData) => {
18
- render(data)
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(data: VixonicData) {
26
- ReactDOM.render(React.createElement<Props>(App, {data, start}), document.getElementById('root'))
48
+ function render() {
49
+ ReactDOM.render(
50
+ React.createElement(App, { start, appData, fixtures, serviceError }),
51
+ document.getElementById('root')
52
+ )
27
53
  }
@@ -5,5 +5,5 @@ declare type VixonicData = {
5
5
  }
6
6
 
7
7
  declare type VixonicParameters = Partial<{
8
- league: '1' | '9' | '4' | '22' | '15' | '1220' | '128' | '265', 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', updateTime: 300000 | 600000 | 1800000 | 3600000 | 21600000 | 43200000 | 86400000, 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, debugTimezone: boolean
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
- ]