cbf_calendario 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +29 -4
- data/README.md +39 -393
- data/cbf_calendario.gemspec +4 -1
- data/lib/cbf_calendario/client.rb +31 -172
- data/lib/cbf_calendario/version.rb +1 -1
- data/lib/cbf_calendario.rb +3 -18
- metadata +31 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8df4a89b5bf5374d476138e47f0be488c3034dd67ad858fca6ac15ae842f7181
|
|
4
|
+
data.tar.gz: 1bbb1b57977d00b54ea99ce64a9abf0b6f45f665c20edcf342711ceb94b25a9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3827b32b1dafa53a360854fb7bbc7e486381fb0dd22e8c03cabb384df89998864374b1ce4f4422d25c6179e6f62fe5e3af80df2c41efb7f73b615c01ecca175
|
|
7
|
+
data.tar.gz: 7225d5da60496872dc09c83aa79b96d2583a80d7a396fc57ec30d4a96b99124a53f2015055e403e266566c451ae3e68f42cdde9afae391d4353fa3992197f861
|
data/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.1] - 2026-05-11
|
|
4
|
+
|
|
5
|
+
- Renomeação: `jogos_pendentes_no_dia` → `jogos_do_dia` em `CbfCalendario` e `CbfCalendario::Client` (sem alias)
|
|
6
|
+
|
|
7
|
+
## [0.4.0] - 2026-05-11
|
|
8
|
+
|
|
9
|
+
- `Client#jogos_pendentes_no_dia` / `CbfCalendario.jogos_pendentes_no_dia`: passa a listar **todos** os jogos do dia (não só os sem placar na API)
|
|
10
|
+
- Cada linha inclui `placar` (`"M x V"` quando mandante e visitante têm `gols` na API; caso contrário `nil`) além de `horario`
|
|
11
|
+
- README e gemspec atualizados para refletir o novo comportamento
|
|
12
|
+
|
|
13
|
+
## [0.3.3] - 2026-05-11
|
|
14
|
+
|
|
15
|
+
- Suíte de testes Minitest completa no padrão Rails (`test/`) cobrindo módulo principal, `Client`, `PartidaStats` e `Urls`
|
|
16
|
+
- `Rakefile` com task `rake test` para execução local da suíte
|
|
17
|
+
- CI no GitHub Actions (`.github/workflows/test.yml`) para rodar testes em push/PR nas versões Ruby 3.2, 3.3 e 3.4
|
|
18
|
+
- Dependências de desenvolvimento adicionadas: `rake` e `minitest (~> 5.22)`
|
|
19
|
+
|
|
20
|
+
## [0.3.2] - 2026-05-11
|
|
21
|
+
|
|
22
|
+
- Ajustes internos de robustez HTTP no cliente
|
|
23
|
+
- Suporte a redirecionamentos HTTP (ex.: 308) nas requisições internas do cliente
|
|
24
|
+
- README atualizado
|
|
25
|
+
|
|
26
|
+
## [0.3.1] - 2026-05-11
|
|
27
|
+
|
|
28
|
+
- `Client#jogos_pendentes_no_dia`: chave renomeada de `placar_ou_horario` para `horario`
|
|
29
|
+
- README atualizado para refletir o novo formato de retorno dos jogos pendentes
|
|
30
|
+
|
|
3
31
|
## [0.3.0] - 2026-05-11
|
|
4
32
|
|
|
5
|
-
-
|
|
6
|
-
- `Client#atleta_por_id` e atalho `CbfCalendario.atleta_por_id`
|
|
7
|
-
- `Client#clube_por_id` e atalho `CbfCalendario.clube_por_id`
|
|
8
|
-
- Novos erros de validação: `InvalidClubIdError` e `InvalidAthleteIdError`
|
|
33
|
+
- Melhorias incrementais no cliente HTTP e na documentação
|
|
9
34
|
- README simplificado e atualizado com as novas funções e exemplos de retorno
|
|
10
35
|
|
|
11
36
|
## [0.2.0] - 2026-05-10
|
data/README.md
CHANGED
|
@@ -4,11 +4,8 @@ Gem Ruby para consultar a API pública da CBF e retornar dados prontos para uso.
|
|
|
4
4
|
|
|
5
5
|
## O que a gem faz
|
|
6
6
|
|
|
7
|
-
- Lista jogos
|
|
7
|
+
- Lista todos os jogos do dia (calendário da API).
|
|
8
8
|
- Busca a partida completa por `id_jogo`.
|
|
9
|
-
- Busca atletas por `id_clube`.
|
|
10
|
-
- Busca atleta por `id_atleta`.
|
|
11
|
-
- Busca clube por `id_clube`.
|
|
12
9
|
- Gera estatísticas agregadas da súmula.
|
|
13
10
|
- Monta URLs públicas da CBF (partida e times).
|
|
14
11
|
- Retorna hashes Ruby para facilitar integração.
|
|
@@ -39,20 +36,32 @@ require 'cbf_calendario'
|
|
|
39
36
|
```ruby
|
|
40
37
|
require 'cbf_calendario'
|
|
41
38
|
|
|
42
|
-
jogos = CbfCalendario.
|
|
39
|
+
jogos = CbfCalendario.jogos_do_dia('10/05/2026')
|
|
43
40
|
partida = CbfCalendario.partida_completa('832031')
|
|
44
41
|
jogo = CbfCalendario.jogo_partida('832031')
|
|
45
|
-
atletas = CbfCalendario.atletas_do_clube('20001')
|
|
46
|
-
atleta = CbfCalendario.atleta_por_id('12345')
|
|
47
|
-
clube = CbfCalendario.clube_por_id('20001')
|
|
48
42
|
stats = CbfCalendario.estatisticas_agregadas(jogo)
|
|
49
43
|
```
|
|
50
44
|
|
|
45
|
+
## `partida_completa` e `jogo_partida`
|
|
46
|
+
|
|
47
|
+
Os dois consultam o mesmo endpoint da CBF: `GET /api/cbf/jogos/:id`. A diferença é **o que é devolvido**:
|
|
48
|
+
|
|
49
|
+
- **`partida_completa(id_jogo)`** — retorna o **payload inteiro** da API (hash com chaves string), em geral no formato `{ "jogo" => { ... } }`. Use quando precisar do JSON completo da resposta (incluindo o envelope com `"jogo"` e qualquer outra chave de topo que a API enviar).
|
|
50
|
+
|
|
51
|
+
- **`jogo_partida(id_jogo)`** — retorna **apenas** o hash interno `"jogo"` (mandante, visitante, `registros`, etc.). É o equivalente a `partida_completa(id_jogo)['jogo']`, útil quando você só trabalha com os dados da partida.
|
|
52
|
+
|
|
53
|
+
| Método | Retorno típico |
|
|
54
|
+
|--------|----------------|
|
|
55
|
+
| `partida_completa` | `{ "jogo" => { ... } }` |
|
|
56
|
+
| `jogo_partida` | `{ "id_jogo" => ..., "mandante" => {...}, ... }` |
|
|
57
|
+
|
|
58
|
+
Se você já chamou `partida = CbfCalendario.partida_completa(id)`, prefira `partida['jogo']` em vez de chamar `jogo_partida(id)` de novo na mesma rotina (evita uma segunda requisição HTTP idêntica).
|
|
59
|
+
|
|
51
60
|
## Funcionalidades principais
|
|
52
61
|
|
|
53
|
-
### 1) Jogos
|
|
62
|
+
### 1) Jogos do dia
|
|
54
63
|
|
|
55
|
-
Retorna
|
|
64
|
+
Retorna todas as partidas previstas ou já disputadas naquela data, conforme o calendário da CBF. Quando a API já trouxer placar, o campo `placar` vem preenchido (ex.: `"2 x 1"`); caso contrário, `placar` fica `nil` e `horario` reflete o horário previsto, se existir.
|
|
56
65
|
|
|
57
66
|
### 2) Partida completa por ID
|
|
58
67
|
|
|
@@ -71,29 +80,21 @@ Monta links da página da partida e das páginas de times.
|
|
|
71
80
|
### Módulo `CbfCalendario` (atalhos)
|
|
72
81
|
|
|
73
82
|
- `CbfCalendario.parse_data_br!(str)`
|
|
74
|
-
- `CbfCalendario.
|
|
83
|
+
- `CbfCalendario.jogos_do_dia(data, **opts)`
|
|
75
84
|
- `CbfCalendario.partida_completa(id_jogo, **opts)`
|
|
76
85
|
- `CbfCalendario.jogo_partida(id_jogo, **opts)`
|
|
77
|
-
- `CbfCalendario.atletas_do_clube(id_clube, **opts)`
|
|
78
|
-
- `CbfCalendario.atleta_por_id(id_atleta, **opts)`
|
|
79
|
-
- `CbfCalendario.clube_por_id(id_clube, **opts)`
|
|
80
86
|
- `CbfCalendario.estatisticas_agregadas(jogo)`
|
|
81
87
|
|
|
82
88
|
### Classe `CbfCalendario::Client`
|
|
83
89
|
|
|
84
90
|
- `CbfCalendario::Client.new(base_url: ..., read_timeout: ..., open_timeout: ...)`
|
|
85
|
-
- `client.
|
|
91
|
+
- `client.jogos_do_dia(data)`
|
|
86
92
|
- `client.calendario_json(data)`
|
|
87
93
|
- `client.partida_completa(id_jogo)`
|
|
88
94
|
- `client.jogo_partida(id_jogo)`
|
|
89
|
-
- `client.atletas_do_clube(id_clube)`
|
|
90
|
-
- `client.atleta_por_id(id_atleta)`
|
|
91
|
-
- `client.clube_por_id(id_clube)`
|
|
92
95
|
- `CbfCalendario::Client.parse_data_br!(str)`
|
|
93
96
|
- `CbfCalendario::Client.coerce_date!(data)`
|
|
94
97
|
- `CbfCalendario::Client.normalize_id_jogo!(id_jogo)`
|
|
95
|
-
- `CbfCalendario::Client.normalize_id_clube!(id_clube)`
|
|
96
|
-
- `CbfCalendario::Client.normalize_id_atleta!(id_atleta)`
|
|
97
98
|
|
|
98
99
|
### Módulo `CbfCalendario::PartidaStats`
|
|
99
100
|
|
|
@@ -120,7 +121,7 @@ Monta links da página da partida e das páginas de times.
|
|
|
120
121
|
# => #<Date: 2026-12-25 ...>
|
|
121
122
|
```
|
|
122
123
|
|
|
123
|
-
### `CbfCalendario.
|
|
124
|
+
### `CbfCalendario.jogos_do_dia('10/05/2026')`
|
|
124
125
|
|
|
125
126
|
```ruby
|
|
126
127
|
# => [
|
|
@@ -129,12 +130,26 @@ Monta links da página da partida e das páginas de times.
|
|
|
129
130
|
# serie: "Série A",
|
|
130
131
|
# mandante: "Flamengo",
|
|
131
132
|
# visitante: "Bahia",
|
|
132
|
-
#
|
|
133
|
+
# horario: "16:00",
|
|
134
|
+
# placar: nil,
|
|
133
135
|
# data: "10/05/2026",
|
|
134
136
|
# data_iso: "2026-05-10",
|
|
135
137
|
# local: "Maracanã",
|
|
136
138
|
# rodada: "6",
|
|
137
139
|
# id_jogo: "832031"
|
|
140
|
+
# },
|
|
141
|
+
# {
|
|
142
|
+
# campeonato: "Campeonato Brasileiro",
|
|
143
|
+
# serie: "Série A",
|
|
144
|
+
# mandante: "Palmeiras",
|
|
145
|
+
# visitante: "São Paulo",
|
|
146
|
+
# horario: "",
|
|
147
|
+
# placar: "1 x 0",
|
|
148
|
+
# data: "10/05/2026",
|
|
149
|
+
# data_iso: "2026-05-10",
|
|
150
|
+
# local: "Allianz Parque",
|
|
151
|
+
# rodada: "6",
|
|
152
|
+
# id_jogo: "832032"
|
|
138
153
|
# }
|
|
139
154
|
# ]
|
|
140
155
|
```
|
|
@@ -179,53 +194,6 @@ Monta links da página da partida e das páginas de times.
|
|
|
179
194
|
# }
|
|
180
195
|
```
|
|
181
196
|
|
|
182
|
-
### `CbfCalendario.atletas_do_clube('20001')`
|
|
183
|
-
|
|
184
|
-
```ruby
|
|
185
|
-
# => {
|
|
186
|
-
# clube_id: "20001",
|
|
187
|
-
# atletas: [
|
|
188
|
-
# {
|
|
189
|
-
# "id_atleta" => 12345,
|
|
190
|
-
# "nome_popular" => "Atleta Exemplo",
|
|
191
|
-
# "nome_completo" => "Atleta Exemplo da Silva",
|
|
192
|
-
# "posicao" => "MEI",
|
|
193
|
-
# ...
|
|
194
|
-
# }
|
|
195
|
-
# ]
|
|
196
|
-
# }
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### `CbfCalendario.atleta_por_id('12345')`
|
|
200
|
-
|
|
201
|
-
```ruby
|
|
202
|
-
# => {
|
|
203
|
-
# atleta_id: "12345",
|
|
204
|
-
# atleta: {
|
|
205
|
-
# "id_atleta" => 12345,
|
|
206
|
-
# "nome_popular" => "Atleta Exemplo",
|
|
207
|
-
# "nome_completo" => "Atleta Exemplo da Silva",
|
|
208
|
-
# "posicao" => "ATA",
|
|
209
|
-
# ...
|
|
210
|
-
# }
|
|
211
|
-
# }
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### `CbfCalendario.clube_por_id('20001')`
|
|
215
|
-
|
|
216
|
-
```ruby
|
|
217
|
-
# => {
|
|
218
|
-
# clube_id: "20001",
|
|
219
|
-
# clube: {
|
|
220
|
-
# "id_clube" => 20001,
|
|
221
|
-
# "nome" => "Time Exemplo",
|
|
222
|
-
# "sigla" => "TEX",
|
|
223
|
-
# "escudo" => "https://...",
|
|
224
|
-
# ...
|
|
225
|
-
# }
|
|
226
|
-
# }
|
|
227
|
-
```
|
|
228
|
-
|
|
229
197
|
### `CbfCalendario.estatisticas_agregadas(jogo)`
|
|
230
198
|
|
|
231
199
|
```ruby
|
|
@@ -256,8 +224,6 @@ Monta links da página da partida e das páginas de times.
|
|
|
256
224
|
|
|
257
225
|
- `CbfCalendario::InvalidDateError`: data inválida (formato esperado: `dd/mm/aaaa`).
|
|
258
226
|
- `CbfCalendario::InvalidGameIdError`: `id_jogo` inválido (somente dígitos).
|
|
259
|
-
- `CbfCalendario::InvalidClubIdError`: `id_clube` inválido (somente dígitos).
|
|
260
|
-
- `CbfCalendario::InvalidAthleteIdError`: `id_atleta` inválido (somente dígitos).
|
|
261
227
|
- `CbfCalendario::HttpError`: erro HTTP ou payload inválido da API.
|
|
262
228
|
- `CbfCalendario::Error`: classe base.
|
|
263
229
|
|
|
@@ -271,335 +237,15 @@ class CbfCalendarioSyncJob < ApplicationJob
|
|
|
271
237
|
|
|
272
238
|
def perform(data_iso: nil)
|
|
273
239
|
data = data_iso ? Date.iso8601(data_iso) : Date.current
|
|
274
|
-
CbfCalendario.
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Requisitos
|
|
280
|
-
|
|
281
|
-
- Ruby >= 3.0
|
|
282
|
-
|
|
283
|
-
## Licença
|
|
284
|
-
|
|
285
|
-
MIT. Veja `LICENSE.txt`.
|
|
286
|
-
# cbf_calendario
|
|
287
|
-
|
|
288
|
-
Cliente Ruby para a API pública da [CBF](https://www.cbf.com.br): **calendário** (jogos pendentes por dia), **partida** (`/api/cbf/jogos/:id`), **estatísticas derivadas** da súmula e **montagem de URLs** do site. Retornos em **hashes** Ruby para uso em **Rails** ou scripts.
|
|
289
|
-
|
|
290
|
-
## Instalação
|
|
291
|
-
|
|
292
|
-
```ruby
|
|
293
|
-
# Gemfile
|
|
294
|
-
gem 'cbf_calendario', '~> 0.2'
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
```bash
|
|
298
|
-
bundle install
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Em qualquer script Ruby (sem Bundler no load path):
|
|
302
|
-
|
|
303
|
-
```bash
|
|
304
|
-
gem install cbf_calendario
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
```ruby
|
|
308
|
-
require 'cbf_calendario'
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
## Uso direto (mesmo processo da requisição)
|
|
314
|
-
|
|
315
|
-
Ideal só para testes ou endpoints muito leves. Para produção, prefira **enfileirar um job** (seção seguinte).
|
|
316
|
-
|
|
317
|
-
```ruby
|
|
318
|
-
require 'cbf_calendario'
|
|
319
|
-
|
|
320
|
-
# Data como String dd/mm/aaaa, Date ou Time
|
|
321
|
-
jogos = CbfCalendario.jogos_pendentes_no_dia('10/05/2026')
|
|
322
|
-
# => [{ campeonato: "...", serie: "...", mandante: "...", ... }, ...]
|
|
323
|
-
|
|
324
|
-
jogos = CbfCalendario.jogos_pendentes_no_dia(Date.current)
|
|
325
|
-
|
|
326
|
-
# Cliente com opções (timeout HTTP, etc.)
|
|
327
|
-
client = CbfCalendario::Client.new(read_timeout: 45)
|
|
328
|
-
client.jogos_pendentes_no_dia('01/06/2026')
|
|
329
|
-
|
|
330
|
-
# Payload bruto da API (Hash) para o dia
|
|
331
|
-
client.calendario_json(Date.today)
|
|
332
|
-
|
|
333
|
-
# Parsing de data brasileira
|
|
334
|
-
CbfCalendario.parse_data_br!('25/12/2026') # => Date
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### Partida (resultado / súmula completa da API)
|
|
338
|
-
|
|
339
|
-
Mesmo endpoint usado em `show_game.rb`: **`GET /api/cbf/jogos/:id`**. O retorno é o **JSON completo** como `Hash` em Ruby (chaves **string**, igual ao JSON da CBF).
|
|
340
|
-
|
|
341
|
-
```ruby
|
|
342
|
-
# Payload inteiro: tipicamente { "jogo" => { ... registros, atletas, árbitros, ... } }
|
|
343
|
-
payload = CbfCalendario.partida_completa('832031')
|
|
344
|
-
|
|
345
|
-
jogo = payload['jogo']
|
|
346
|
-
puts jogo.dig('mandante', 'nome')
|
|
347
|
-
puts jogo.dig('mandante', 'gols')
|
|
348
|
-
puts jogo.dig('visitante', 'gols')
|
|
349
|
-
|
|
350
|
-
# Só o objeto jogo (atalho)
|
|
351
|
-
jogo = CbfCalendario.jogo_partida(832031)
|
|
352
|
-
|
|
353
|
-
# Estatísticas derivadas dos registros (gols por tipo, cartões, substituições…)
|
|
354
|
-
stats = CbfCalendario.estatisticas_agregadas(jogo)
|
|
355
|
-
# => { por_tipo_evento: {...}, gols_mandante_em_eventos: N, ... }
|
|
356
|
-
|
|
357
|
-
# URLs públicas (mandante/visitante/página da partida), mesma regra do show_game
|
|
358
|
-
cp = jogo['campeonato']
|
|
359
|
-
ano = (cp['ano'] || jogo['ano']).to_s
|
|
360
|
-
pagina = CbfCalendario::Urls.url_pagina_partida(jogo)
|
|
361
|
-
mandante_url = CbfCalendario::Urls.url_time(cp['nome'], cp['nome_categoria'], ano, jogo.dig('mandante', 'id'))
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Timeouts maiores (como no script original):
|
|
365
|
-
|
|
366
|
-
```ruby
|
|
367
|
-
CbfCalendario.partida_completa('832031', read_timeout: 45, open_timeout: 15)
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
Erros extras: `CbfCalendario::InvalidGameIdError` (ID inválido), `CbfCalendario::HttpError` (HTTP ou resposta sem `jogo`).
|
|
371
|
-
|
|
372
|
-
---
|
|
373
|
-
|
|
374
|
-
## Referência das funções públicas
|
|
375
|
-
|
|
376
|
-
### Módulo `CbfCalendario` (atalhos)
|
|
377
|
-
|
|
378
|
-
| Função | Descrição |
|
|
379
|
-
|--------|-----------|
|
|
380
|
-
| `parse_data_br!(str)` | Converte `"dd/mm/aaaa"` em `Date`. Lança `InvalidDateError`. |
|
|
381
|
-
| `jogos_pendentes_no_dia(data, **opts)` | Jogos do dia **ainda sem placar** na API. `data`: `String` BR, `Date` ou `Time`. Retorna `Array<Hash>` com **chaves símbolo**. `**opts` repassa para `Client.new`. |
|
|
382
|
-
| `partida_completa(id_jogo, **opts)` | `GET /api/cbf/jogos/:id` — payload JSON completo. Retorna `Hash` com **chaves string** (como o JSON). |
|
|
383
|
-
| `jogo_partida(id_jogo, **opts)` | Mesmo endpoint; retorna só `payload["jogo"]` (`Hash` ou levanta erro se ausente). |
|
|
384
|
-
| `estatisticas_agregadas(jogo)` | Agrega eventos da súmula (`registros`, substituições, etc.). Espera o **Hash `jogo`** vindo da API. Retorna `Hash` com **chaves símbolo**. |
|
|
385
|
-
|
|
386
|
-
### Classe `CbfCalendario::Client`
|
|
387
|
-
|
|
388
|
-
Construtor: `Client.new(base_url: "https://www.cbf.com.br", read_timeout: 30, open_timeout: 15)`.
|
|
389
|
-
|
|
390
|
-
| Método | Descrição |
|
|
391
|
-
|--------|-----------|
|
|
392
|
-
| `jogos_pendentes_no_dia(data)` | Igual ao atalho do módulo. |
|
|
393
|
-
| `calendario_json(data)` | Payload bruto do calendário: `GET /api/cbf/calendario/jogos/AAAA/MM/DD` (`Hash`, chaves string). |
|
|
394
|
-
| `partida_completa(id_jogo)` | Payload bruto da partida (`GET /api/cbf/jogos/:id`). |
|
|
395
|
-
| `jogo_partida(id_jogo)` | Somente o objeto `jogo`. |
|
|
396
|
-
| `Client.parse_data_br!(str)` | Igual `CbfCalendario.parse_data_br!`. |
|
|
397
|
-
| `Client.coerce_date!(data)` | Normaliza `Date` / `Time` / string `dd/mm/aaaa`. |
|
|
398
|
-
| `Client.normalize_id_jogo!(id)` | Valida ID numérico (`String` de dígitos) ou levanta `InvalidGameIdError`. |
|
|
399
|
-
|
|
400
|
-
### `CbfCalendario::PartidaStats`
|
|
401
|
-
|
|
402
|
-
| Método | Descrição |
|
|
403
|
-
|--------|-----------|
|
|
404
|
-
| `agregadas(jogo)` | Implementação central das agregações; mesmo resultado que `CbfCalendario.estatisticas_agregadas(jogo)`. |
|
|
405
|
-
|
|
406
|
-
Chaves típicas no **retorno** de `estatisticas_agregadas` / `PartidaStats.agregadas`:
|
|
407
|
-
|
|
408
|
-
| Chave (símbolo) | Conteúdo |
|
|
409
|
-
|-----------------|----------|
|
|
410
|
-
| `:por_tipo_evento` | Contagem por `tipo` em `registros` (ex.: `GOL`, `PENALIDADE`). |
|
|
411
|
-
| `:gols_por_classificacao_sumula` | Contagem por campo `resultado` nos eventos de gol. |
|
|
412
|
-
| `:gols_mandante_em_eventos` | Gols do mandante contados nos registros `GOL`. |
|
|
413
|
-
| `:gols_visitante_em_eventos` | Idem visitante. |
|
|
414
|
-
| `:cartoes_por_resultado` | Contagem por `resultado` em eventos `PENALIDADE`. |
|
|
415
|
-
| `:total_substituicoes_mandante` | Quantidade de substituições no mandante. |
|
|
416
|
-
| `:total_substituicoes_visitante` | Idem visitante. |
|
|
417
|
-
|
|
418
|
-
### `CbfCalendario::Urls` (links do site, sem HTTP)
|
|
419
|
-
|
|
420
|
-
Constante: `CbfCalendario::Urls::SITE_ROOT` (`"https://www.cbf.com.br"`).
|
|
421
|
-
|
|
422
|
-
| Método | Descrição |
|
|
423
|
-
|--------|-----------|
|
|
424
|
-
| `slug_segment(str)` | Slug à URL (remove acentos, minúsculas, hífens). |
|
|
425
|
-
| `segmento_campeonato(nome)` | Segmento do campeonato na URL (ex.: `campeonato-brasileiro`). |
|
|
426
|
-
| `segmento_categoria(nome_serie)` | Segmento da categoria/série (`serie-a`, `sub-20`, etc.). |
|
|
427
|
-
| `path_pagina_jogo(jogo)` | Path relativo da página da partida no site. |
|
|
428
|
-
| `url_pagina_partida(jogo, base: SITE_ROOT)` | URL absoluta da partida. |
|
|
429
|
-
| `url_time(campeonato_nome, categoria_nome, ano, clube_id, base: SITE_ROOT)` | URL da página do clube na temporada. |
|
|
430
|
-
|
|
431
|
-
O objeto `jogo`/`payload` usado em `Urls` deve ser o **Hash da API** (como em `jogo_partida`), com `campeonato`, `mandante`, `visitante`, `id_jogo`, etc.
|
|
432
|
-
|
|
433
|
-
---
|
|
434
|
-
|
|
435
|
-
## Rails: Solid Queue e jobs em background (recomendado)
|
|
436
|
-
|
|
437
|
-
A API da CBF é chamada via rede; **não bloqueie** requisições HTTP longas no processo web (ex.: Puma). Use **[Solid Queue](https://github.com/rails/solid_queue)** (Rails 8+):
|
|
438
|
-
|
|
439
|
-
1. Inclua `cbf_calendario` no `Gemfile`.
|
|
440
|
-
2. Configure o adapter **`solid_queue`** para o Active Job.
|
|
441
|
-
3. Implemente um **Active Job** que chama `CbfCalendario`.
|
|
442
|
-
4. Rode o worker **`bin/jobs`** (processo separado do servidor web).
|
|
443
|
-
|
|
444
|
-
### 1. Adapter Solid Queue
|
|
445
|
-
|
|
446
|
-
No `Gemfile`, garanta a gem (no Rails 8 costuma vir por padrão; confira o guia do seu app):
|
|
447
|
-
|
|
448
|
-
```ruby
|
|
449
|
-
gem 'cbf_calendario'
|
|
450
|
-
gem 'solid_queue'
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
`config/application.rb` (ou ambiente de produção):
|
|
454
|
-
|
|
455
|
-
```ruby
|
|
456
|
-
config.active_job.queue_adapter = :solid_queue
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
Em desenvolvimento e produção, mantenha `config.active_job.queue_adapter = :solid_queue` e rode `bin/jobs` quando precisar processar filas fora do ciclo de request.
|
|
460
|
-
|
|
461
|
-
### 2. Job com Active Job
|
|
462
|
-
|
|
463
|
-
`app/jobs/cbf_calendario_sync_job.rb`:
|
|
464
|
-
|
|
465
|
-
```ruby
|
|
466
|
-
# frozen_string_literal: true
|
|
467
|
-
|
|
468
|
-
class CbfCalendarioSyncJob < ApplicationJob
|
|
469
|
-
queue_as :default
|
|
470
|
-
|
|
471
|
-
# data_iso: "2026-05-10" ou nil para usar Date.current
|
|
472
|
-
def perform(data_iso: nil)
|
|
473
|
-
data =
|
|
474
|
-
if data_iso.present?
|
|
475
|
-
Date.iso8601(data_iso.to_s)
|
|
476
|
-
else
|
|
477
|
-
Date.current
|
|
478
|
-
end
|
|
479
|
-
|
|
480
|
-
jogos = CbfCalendario.jogos_pendentes_no_dia(data)
|
|
481
|
-
|
|
482
|
-
# Exemplo: persistir, notificar, cachear — adapte ao seu domínio
|
|
483
|
-
Rails.logger.info("[CBF] #{jogos.size} jogos pendentes em #{data}")
|
|
484
|
-
|
|
485
|
-
jogos.each do |jogo|
|
|
486
|
-
ExternalFixtureUpsertService.call(jogo) # seu serviço
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
jogos
|
|
490
|
-
rescue CbfCalendario::HttpError => e
|
|
491
|
-
Rails.logger.error("[CBF] HTTP: #{e.message}")
|
|
492
|
-
raise # deixa o Active Job aplicar retry conforme configuração
|
|
493
|
-
rescue CbfCalendario::InvalidDateError => e
|
|
494
|
-
Rails.logger.warn("[CBF] data inválida: #{e.message}")
|
|
495
|
-
raise
|
|
240
|
+
CbfCalendario.jogos_do_dia(data)
|
|
496
241
|
end
|
|
497
242
|
end
|
|
498
243
|
```
|
|
499
244
|
|
|
500
|
-
Enfileirar (controller, outro job, scheduler):
|
|
501
|
-
|
|
502
|
-
```ruby
|
|
503
|
-
# data específica
|
|
504
|
-
CbfCalendarioSyncJob.perform_later(data_iso: '2026-05-10')
|
|
505
|
-
|
|
506
|
-
# “hoje” no servidor
|
|
507
|
-
CbfCalendarioSyncJob.perform_later
|
|
508
|
-
|
|
509
|
-
# com delay
|
|
510
|
-
CbfCalendarioSyncJob.set(wait: 5.minutes).perform_later(data_iso: Date.tomorrow.iso8601)
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### 3. Processar a fila (`bin/jobs`)
|
|
514
|
-
|
|
515
|
-
Com Solid Queue, o worker padrão processa os jobs enfileirados:
|
|
516
|
-
|
|
517
|
-
```bash
|
|
518
|
-
bin/jobs
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
Rode em **processo separado** do `rails server` / Puma. Em produção, trate `bin/jobs` como serviço (systemd, container, Plataforma como serviço, etc.).
|
|
522
|
-
|
|
523
|
-
### 4. Agendamento periódico (Solid Queue)
|
|
524
|
-
|
|
525
|
-
Use o **recurring** do Solid Queue (ex.: `config/recurring.yml` no seu app) para enfileirar `CbfCalendarioSyncJob.perform_later` no horário desejado. A sintaxe e os arquivos exatos dependem da versão do Solid Queue — veja a [documentação do Solid Queue](https://github.com/rails/solid_queue) (seção *Recurring / cron*).
|
|
526
|
-
|
|
527
|
-
### 5. Serviço systemd (exemplo): `bin/jobs` no ar
|
|
528
|
-
|
|
529
|
-
Ajuste usuário, path e caminho do Ruby:
|
|
530
|
-
|
|
531
|
-
```ini
|
|
532
|
-
[Unit]
|
|
533
|
-
Description=Solid Queue — processador de jobs (Rails + cbf_calendario)
|
|
534
|
-
After=network.target
|
|
535
|
-
|
|
536
|
-
[Service]
|
|
537
|
-
Type=simple
|
|
538
|
-
User=deploy
|
|
539
|
-
WorkingDirectory=/var/www/seu_app/current
|
|
540
|
-
Environment=RAILS_ENV=production
|
|
541
|
-
ExecStart=/home/deploy/.rbenv/shims/bundle exec bin/jobs
|
|
542
|
-
Restart=always
|
|
543
|
-
RestartSec=10
|
|
544
|
-
|
|
545
|
-
[Install]
|
|
546
|
-
WantedBy=multi-user.target
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
```bash
|
|
550
|
-
sudo systemctl daemon-reload
|
|
551
|
-
sudo systemctl enable --now solid-queue-jobs.service
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
(O nome do arquivo `.service` pode ser o que preferir; o importante é executar `bundle exec bin/jobs`.)
|
|
555
|
-
|
|
556
|
-
---
|
|
557
|
-
|
|
558
|
-
## API dos hashes retornados (calendário / pendentes)
|
|
559
|
-
|
|
560
|
-
A tabela abaixo vale para **cada item** de `jogos_pendentes_no_dia` (chaves **símbolo**). O objeto **`jogo`** de `partida_completa` / `jogo_partida` segue o JSON da CBF (chaves **string**: `"mandante"`, `"registros"`, `"arbitros"`, etc.) — veja a referência completa acima.
|
|
561
|
-
|
|
562
|
-
| Chave | Significado |
|
|
563
|
-
|-------|-------------|
|
|
564
|
-
| `:campeonato` | Nome do campeonato |
|
|
565
|
-
| `:serie` | Série / divisão |
|
|
566
|
-
| `:mandante` | Time mandante |
|
|
567
|
-
| `:visitante` | Time visitante |
|
|
568
|
-
| `:placar_ou_horario` | Horário previsto ou placar, se houver |
|
|
569
|
-
| `:data` | Data como string na API |
|
|
570
|
-
| `:data_iso` | Data no formato `YYYY-MM-DD` |
|
|
571
|
-
| `:local` | Local do jogo |
|
|
572
|
-
| `:rodada` | Rodada |
|
|
573
|
-
| `:id_jogo` | Identificador do jogo na CBF |
|
|
574
|
-
|
|
575
|
-
### Erros
|
|
576
|
-
|
|
577
|
-
| Classe | Quando |
|
|
578
|
-
|--------|--------|
|
|
579
|
-
| `CbfCalendario::InvalidDateError` | Data em formato inválido |
|
|
580
|
-
| `CbfCalendario::InvalidGameIdError` | ID de jogo não numérico |
|
|
581
|
-
| `CbfCalendario::HttpError` | Falha HTTP ao chamar a API |
|
|
582
|
-
| `CbfCalendario::Error` | Classe base |
|
|
583
|
-
|
|
584
|
-
Em jobs, costuma-se **relançar** `HttpError` para retry exponencial; datas inválidas podem ser descartadas sem retry.
|
|
585
|
-
|
|
586
|
-
### Rails: responder JSON a partir dos hashes
|
|
587
|
-
|
|
588
|
-
```ruby
|
|
589
|
-
def index
|
|
590
|
-
jogos = CbfCalendario.jogos_pendentes_no_dia(params.require(:data))
|
|
591
|
-
render json: jogos.map(&:stringify_keys)
|
|
592
|
-
end
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
Para não bloquear o request em produção, prefira só enfileirar o job e devolver `202 Accepted`, ou ler resultado de cache/banco preenchido pelo job.
|
|
596
|
-
|
|
597
|
-
---
|
|
598
|
-
|
|
599
245
|
## Requisitos
|
|
600
246
|
|
|
601
247
|
- Ruby >= 3.0
|
|
602
248
|
|
|
603
249
|
## Licença
|
|
604
250
|
|
|
605
|
-
MIT
|
|
251
|
+
MIT. Veja `LICENSE.txt`.
|
data/cbf_calendario.gemspec
CHANGED
|
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ['Betbrothers']
|
|
9
9
|
spec.summary = 'Cliente Ruby para o calendário de jogos da CBF (hashes, uso em Rails)'
|
|
10
10
|
spec.description = <<~DESC
|
|
11
|
-
Consulta a API pública de calendário da CBF e devolve jogos
|
|
11
|
+
Consulta a API pública de calendário da CBF e devolve todos os jogos do dia
|
|
12
12
|
para uma data, como Array de hashes Ruby — adequado para uso em Ruby on Rails.
|
|
13
13
|
DESC
|
|
14
14
|
spec.license = 'MIT'
|
|
@@ -24,4 +24,7 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
%w[LICENSE.txt README.md CHANGELOG.md cbf_calendario.gemspec] + Dir['lib/**/*.rb']
|
|
25
25
|
end
|
|
26
26
|
spec.require_paths = ['lib']
|
|
27
|
+
|
|
28
|
+
spec.add_development_dependency 'minitest', '~> 5.22'
|
|
29
|
+
spec.add_development_dependency 'rake'
|
|
27
30
|
end
|
|
@@ -13,8 +13,6 @@ module CbfCalendario
|
|
|
13
13
|
class HttpError < Error; end
|
|
14
14
|
class InvalidDateError < Error; end
|
|
15
15
|
class InvalidGameIdError < Error; end
|
|
16
|
-
class InvalidClubIdError < Error; end
|
|
17
|
-
class InvalidAthleteIdError < Error; end
|
|
18
16
|
|
|
19
17
|
class Client
|
|
20
18
|
DEFAULT_BASE = 'https://www.cbf.com.br'
|
|
@@ -42,45 +40,6 @@ module CbfCalendario
|
|
|
42
40
|
partida_completa(id_jogo)['jogo']
|
|
43
41
|
end
|
|
44
42
|
|
|
45
|
-
# GET /api/cbf/atletas/:id_clube — retorna hash com dados dos atletas.
|
|
46
|
-
# Saída: { clube_id: "123", atletas: [ ... ] }
|
|
47
|
-
def atletas_do_clube(id_clube)
|
|
48
|
-
cid = Client.normalize_id_clube!(id_clube)
|
|
49
|
-
payload = get_json(api_path_atletas_clube(cid))
|
|
50
|
-
atletas =
|
|
51
|
-
if payload.is_a?(Array)
|
|
52
|
-
payload
|
|
53
|
-
elsif payload.is_a?(Hash) && payload['atletas'].is_a?(Array)
|
|
54
|
-
payload['atletas']
|
|
55
|
-
else
|
|
56
|
-
raise HttpError, 'Resposta sem lista de atletas'
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
{ clube_id: cid, atletas: atletas }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Busca dados completos de um clube por ID.
|
|
63
|
-
# Saída: { clube_id: "123", clube: { ... } }
|
|
64
|
-
def clube_por_id(id_clube)
|
|
65
|
-
cid = Client.normalize_id_clube!(id_clube)
|
|
66
|
-
payload = buscar_clube_payload(cid)
|
|
67
|
-
clube = extrair_clube_do_payload(payload, cid)
|
|
68
|
-
raise HttpError, 'Resposta sem dados do clube' unless clube.is_a?(Hash) && !clube.empty?
|
|
69
|
-
|
|
70
|
-
{ clube_id: cid, clube: clube }
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Busca dados completos de um atleta por ID.
|
|
74
|
-
# Saída: { atleta_id: "123", atleta: { ... } }
|
|
75
|
-
def atleta_por_id(id_atleta)
|
|
76
|
-
aid = Client.normalize_id_atleta!(id_atleta)
|
|
77
|
-
payload = buscar_atleta_payload(aid)
|
|
78
|
-
atleta = extrair_atleta_do_payload(payload, aid)
|
|
79
|
-
raise HttpError, 'Resposta sem dados do atleta' unless atleta.is_a?(Hash) && !atleta.empty?
|
|
80
|
-
|
|
81
|
-
{ atleta_id: aid, atleta: atleta }
|
|
82
|
-
end
|
|
83
|
-
|
|
84
43
|
def self.normalize_id_jogo!(id_jogo)
|
|
85
44
|
s = id_jogo.to_s.strip
|
|
86
45
|
raise InvalidGameIdError, 'id_jogo deve conter só dígitos (ex.: 832024)' unless s.match?(/\A\d+\z/)
|
|
@@ -88,28 +47,15 @@ module CbfCalendario
|
|
|
88
47
|
s
|
|
89
48
|
end
|
|
90
49
|
|
|
91
|
-
def self.normalize_id_clube!(id_clube)
|
|
92
|
-
s = id_clube.to_s.strip
|
|
93
|
-
raise InvalidClubIdError, 'id_clube deve conter só dígitos (ex.: 20001)' unless s.match?(/\A\d+\z/)
|
|
94
|
-
|
|
95
|
-
s
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def self.normalize_id_atleta!(id_atleta)
|
|
99
|
-
s = id_atleta.to_s.strip
|
|
100
|
-
raise InvalidAthleteIdError, 'id_atleta deve conter só dígitos (ex.: 12345)' unless s.match?(/\A\d+\z/)
|
|
101
|
-
|
|
102
|
-
s
|
|
103
|
-
end
|
|
104
|
-
|
|
105
50
|
# Aceita Date, Time ou String "dd/mm/aaaa".
|
|
106
|
-
# Retorna
|
|
107
|
-
#
|
|
51
|
+
# Retorna todos os jogos do dia no calendário (pendentes e já realizados).
|
|
52
|
+
# Array<Hash> com símbolos como chaves, ordenado e sem IDs duplicados.
|
|
53
|
+
# Cada hash: +campeonato+, +serie+, +mandante+, +visitante+, +horario+, +placar+,
|
|
108
54
|
# +data+, +data_iso+, +local+, +rodada+, +id_jogo+.
|
|
109
|
-
def
|
|
55
|
+
def jogos_do_dia(data)
|
|
110
56
|
date = Client.coerce_date!(data)
|
|
111
57
|
payload = get_json(api_path_calendario(date))
|
|
112
|
-
jogos =
|
|
58
|
+
jogos = extrair_jogos_do_dia(payload, date)
|
|
113
59
|
dedup_and_sort(jogos)
|
|
114
60
|
end
|
|
115
61
|
|
|
@@ -150,36 +96,17 @@ module CbfCalendario
|
|
|
150
96
|
"/api/cbf/jogos/#{id_jogo}"
|
|
151
97
|
end
|
|
152
98
|
|
|
153
|
-
def api_path_atletas_clube(id_clube)
|
|
154
|
-
"/api/cbf/atletas/#{id_clube}"
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def api_path_atleta(id_atleta)
|
|
158
|
-
"/api/cbf/atleta/#{id_atleta}"
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def api_path_atletas_detalhes(id_atleta)
|
|
162
|
-
"/api/cbf/atletas/detalhes/#{id_atleta}"
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def api_path_clube(id_clube)
|
|
166
|
-
"/api/cbf/clubes/#{id_clube}"
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def api_path_time(id_clube)
|
|
170
|
-
"/api/cbf/times/#{id_clube}"
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def api_path_clube_detalhes(id_clube)
|
|
174
|
-
"/api/cbf/clubes/detalhes/#{id_clube}"
|
|
175
|
-
end
|
|
176
|
-
|
|
177
99
|
def get_json(path)
|
|
178
100
|
uri = URI.join(base_url, path)
|
|
179
101
|
request_json(uri)
|
|
180
102
|
end
|
|
181
103
|
|
|
182
104
|
def request_json(uri)
|
|
105
|
+
res = perform_get(uri, accept: 'application/json')
|
|
106
|
+
JSON.parse(res.body)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def perform_get(uri, accept:, redirects_left: 5)
|
|
183
110
|
Net::HTTP.start(
|
|
184
111
|
uri.host,
|
|
185
112
|
uri.port,
|
|
@@ -190,13 +117,24 @@ module CbfCalendario
|
|
|
190
117
|
cert_store: ssl_cert_store
|
|
191
118
|
) do |http|
|
|
192
119
|
req = Net::HTTP::Get.new(uri)
|
|
193
|
-
req['Accept'] =
|
|
120
|
+
req['Accept'] = accept
|
|
194
121
|
res = http.request(req)
|
|
122
|
+
|
|
123
|
+
if res.is_a?(Net::HTTPRedirection)
|
|
124
|
+
raise HttpError, 'Muitas redireções na requisição HTTP' if redirects_left <= 0
|
|
125
|
+
|
|
126
|
+
location = res['location'].to_s
|
|
127
|
+
raise HttpError, 'Redirecionamento HTTP sem header Location' if location.empty?
|
|
128
|
+
|
|
129
|
+
next_uri = URI.join(uri.to_s, location)
|
|
130
|
+
return perform_get(next_uri, accept: accept, redirects_left: redirects_left - 1)
|
|
131
|
+
end
|
|
132
|
+
|
|
195
133
|
unless res.is_a?(Net::HTTPSuccess)
|
|
196
134
|
raise HttpError, "HTTP #{res.code}: #{res.message}"
|
|
197
135
|
end
|
|
198
136
|
|
|
199
|
-
|
|
137
|
+
res
|
|
200
138
|
end
|
|
201
139
|
end
|
|
202
140
|
|
|
@@ -210,22 +148,19 @@ module CbfCalendario
|
|
|
210
148
|
end
|
|
211
149
|
end
|
|
212
150
|
|
|
213
|
-
def
|
|
214
|
-
gm = jogo.dig('mandante', 'gols')
|
|
215
|
-
gv = jogo.dig('visitante', 'gols')
|
|
216
|
-
|
|
217
|
-
return "#{gm} x #{gv}" unless gm.nil? || gv.nil?
|
|
218
|
-
|
|
151
|
+
def horario_jogo(jogo)
|
|
219
152
|
jogo['hora'].to_s.strip
|
|
220
153
|
end
|
|
221
154
|
|
|
222
|
-
def
|
|
155
|
+
def placar_partida(jogo)
|
|
223
156
|
gm = jogo.dig('mandante', 'gols')
|
|
224
157
|
gv = jogo.dig('visitante', 'gols')
|
|
225
|
-
gm.nil? || gv.nil?
|
|
158
|
+
return nil if gm.nil? || gv.nil?
|
|
159
|
+
|
|
160
|
+
"#{gm} x #{gv}"
|
|
226
161
|
end
|
|
227
162
|
|
|
228
|
-
def
|
|
163
|
+
def extrair_jogos_do_dia(payload, data_calendario)
|
|
229
164
|
raiz = payload['jogos'] || {}
|
|
230
165
|
linhas = []
|
|
231
166
|
|
|
@@ -236,14 +171,13 @@ module CbfCalendario
|
|
|
236
171
|
next unless lista.is_a?(Array)
|
|
237
172
|
|
|
238
173
|
lista.each do |jogo|
|
|
239
|
-
next unless jogo_sem_placar?(jogo)
|
|
240
|
-
|
|
241
174
|
linhas << {
|
|
242
175
|
campeonato: campeonato.to_s.strip,
|
|
243
176
|
serie: serie.to_s.strip,
|
|
244
177
|
mandante: jogo.dig('mandante', 'nome').to_s.strip,
|
|
245
178
|
visitante: jogo.dig('visitante', 'nome').to_s.strip,
|
|
246
|
-
|
|
179
|
+
horario: horario_jogo(jogo),
|
|
180
|
+
placar: placar_partida(jogo),
|
|
247
181
|
data: jogo['data'].to_s.strip,
|
|
248
182
|
data_iso: data_calendario.strftime('%Y-%m-%d'),
|
|
249
183
|
local: jogo['local'].to_s.strip,
|
|
@@ -270,80 +204,5 @@ module CbfCalendario
|
|
|
270
204
|
out.sort_by { |j| [j[:campeonato], j[:serie], j[:rodada].to_i, j[:id_jogo]] }
|
|
271
205
|
end
|
|
272
206
|
|
|
273
|
-
def buscar_atleta_payload(id_atleta)
|
|
274
|
-
paths = [
|
|
275
|
-
api_path_atleta(id_atleta),
|
|
276
|
-
api_path_atletas_detalhes(id_atleta),
|
|
277
|
-
api_path_atletas_clube(id_atleta)
|
|
278
|
-
]
|
|
279
|
-
|
|
280
|
-
errors = []
|
|
281
|
-
paths.each do |path|
|
|
282
|
-
begin
|
|
283
|
-
return get_json(path)
|
|
284
|
-
rescue HttpError => e
|
|
285
|
-
errors << "#{path}: #{e.message}"
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
raise HttpError, "Não foi possível buscar o atleta #{id_atleta}. Tentativas: #{errors.join(' | ')}"
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
def extrair_atleta_do_payload(payload, id_atleta)
|
|
293
|
-
if payload.is_a?(Hash)
|
|
294
|
-
return payload if campo_id_atleta(payload) == id_atleta
|
|
295
|
-
return payload['atleta'] if payload['atleta'].is_a?(Hash)
|
|
296
|
-
return payload
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
return {} unless payload.is_a?(Array)
|
|
300
|
-
|
|
301
|
-
payload.find { |item| item.is_a?(Hash) && campo_id_atleta(item) == id_atleta } ||
|
|
302
|
-
payload.find { |item| item.is_a?(Hash) } || {}
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def campo_id_atleta(hash)
|
|
306
|
-
hash['id_atleta'].to_s.strip.empty? ? hash['id'].to_s.strip : hash['id_atleta'].to_s.strip
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
def buscar_clube_payload(id_clube)
|
|
310
|
-
paths = [
|
|
311
|
-
api_path_clube(id_clube),
|
|
312
|
-
api_path_time(id_clube),
|
|
313
|
-
api_path_clube_detalhes(id_clube)
|
|
314
|
-
]
|
|
315
|
-
|
|
316
|
-
errors = []
|
|
317
|
-
paths.each do |path|
|
|
318
|
-
begin
|
|
319
|
-
return get_json(path)
|
|
320
|
-
rescue HttpError => e
|
|
321
|
-
errors << "#{path}: #{e.message}"
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
raise HttpError, "Não foi possível buscar o clube #{id_clube}. Tentativas: #{errors.join(' | ')}"
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def extrair_clube_do_payload(payload, id_clube)
|
|
329
|
-
if payload.is_a?(Hash)
|
|
330
|
-
return payload if campo_id_clube(payload) == id_clube
|
|
331
|
-
return payload['clube'] if payload['clube'].is_a?(Hash)
|
|
332
|
-
return payload['time'] if payload['time'].is_a?(Hash)
|
|
333
|
-
return payload
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
return {} unless payload.is_a?(Array)
|
|
337
|
-
|
|
338
|
-
payload.find { |item| item.is_a?(Hash) && campo_id_clube(item) == id_clube } ||
|
|
339
|
-
payload.find { |item| item.is_a?(Hash) } || {}
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def campo_id_clube(hash)
|
|
343
|
-
return hash['id_clube'].to_s.strip unless hash['id_clube'].to_s.strip.empty?
|
|
344
|
-
return hash['id_time'].to_s.strip unless hash['id_time'].to_s.strip.empty?
|
|
345
|
-
|
|
346
|
-
hash['id'].to_s.strip
|
|
347
|
-
end
|
|
348
207
|
end
|
|
349
208
|
end
|
data/lib/cbf_calendario.rb
CHANGED
|
@@ -12,9 +12,9 @@ module CbfCalendario
|
|
|
12
12
|
Client.parse_data_br!(str)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# Atalho sem instanciar +Client+.
|
|
16
|
-
def
|
|
17
|
-
Client.new(**client_options).
|
|
15
|
+
# Atalho sem instanciar +Client+. Lista todos os jogos do dia (calendário completo).
|
|
16
|
+
def jogos_do_dia(data, **client_options)
|
|
17
|
+
Client.new(**client_options).jogos_do_dia(data)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# GET /api/cbf/jogos/:id — Hash completo da API (chaves string).
|
|
@@ -27,21 +27,6 @@ module CbfCalendario
|
|
|
27
27
|
Client.new(**client_options).jogo_partida(id_jogo)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# Hash com todos os atletas do clube.
|
|
31
|
-
def atletas_do_clube(id_clube, **client_options)
|
|
32
|
-
Client.new(**client_options).atletas_do_clube(id_clube)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Hash com os dados de um atleta específico.
|
|
36
|
-
def atleta_por_id(id_atleta, **client_options)
|
|
37
|
-
Client.new(**client_options).atleta_por_id(id_atleta)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Hash com os dados de um clube específico.
|
|
41
|
-
def clube_por_id(id_clube, **client_options)
|
|
42
|
-
Client.new(**client_options).clube_por_id(id_clube)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
30
|
# Estatísticas derivadas dos registros (+show_game.rb+).
|
|
46
31
|
def estatisticas_agregadas(jogo)
|
|
47
32
|
PartidaStats.agregadas(jogo)
|
metadata
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cbf_calendario
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Betbrothers
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.22'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.22'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
12
40
|
description: |
|
|
13
|
-
Consulta a API pública de calendário da CBF e devolve jogos
|
|
41
|
+
Consulta a API pública de calendário da CBF e devolve todos os jogos do dia
|
|
14
42
|
para uma data, como Array de hashes Ruby — adequado para uso em Ruby on Rails.
|
|
15
43
|
executables: []
|
|
16
44
|
extensions: []
|