Skip to content

Instantly share code, notes, and snippets.

@viartemev
Last active March 21, 2022 09:51
Show Gist options
  • Save viartemev/5c90e410a6589a2169f30176965c2663 to your computer and use it in GitHub Desktop.
Save viartemev/5c90e410a6589a2169f30176965c2663 to your computer and use it in GitHub Desktop.
NHL recap downloader
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
"sync"
"time"
)
type GameInfo struct {
Title string
Video string
}
func main() {
start := time.Now()
var wg sync.WaitGroup
gamesVideos := make(map[int]*GameInfo)
upcomingGames := fetchUpcomingGames()
finishedGames := filterFinishedGames(upcomingGames)
for _, game := range finishedGames {
title := fmt.Sprintf("%v vs %v", game.Teams.Home.Team.Name, game.Teams.Away.Team.Name)
gamesVideos[game.GamePk] = &GameInfo{title, "video link"}
}
for _, date := range upcomingGames.Dates {
for _, game := range date.Games {
wg.Add(1)
game := game
go func() {
gameInfo := fetchGameInfo(game.GamePk)
video := extractGameVideo(gameInfo)
gamesVideos[game.GamePk] = &GameInfo{Title: gamesVideos[game.GamePk].Title, Video: video}
defer wg.Done()
}()
}
}
wg.Wait()
for _, info := range gamesVideos {
fmt.Printf("Game %v %v\n", info.Title, info.Video)
}
log.Printf("Operations took %s", time.Since(start))
}
func filterFinishedGames(schedule Schedule) []Games {
var finishedGames []Games
for _, date := range schedule.Dates {
for _, game := range date.Games {
if game.Status.AbstractGameState == "Final" {
finishedGames = append(finishedGames, game)
}
}
}
return finishedGames
}
func extractGameVideo(game Game) string {
var video string
for _, media := range game.Media.Epg {
if media.Title == "Recap" {
for _, item := range media.Items {
for _, playback := range item.Playbacks {
if strings.Contains(playback.Name, "FLASH_1800K") {
video = playback.Url
}
}
}
}
}
return video
}
func fetchGameInfo(gamePk int) Game {
client := http.Client{
Timeout: time.Second * 2,
}
url := "https://statsapi.web.nhl.com/api/v1/game/" + fmt.Sprintf("%v", gamePk) + "/content"
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
res, getErr := client.Do(req)
if getErr != nil {
log.Fatal(getErr)
}
if res.Body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
log.Fatal(err)
}
}(res.Body)
}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
log.Fatal(readErr)
}
game := &Game{}
jsonErr := json.Unmarshal(body, &game)
if jsonErr != nil {
log.Fatal(jsonErr)
}
return *game
}
type Game struct {
Link string `json:"link"`
Media struct {
Epg []struct {
Title string `json:"title"`
Items []struct {
Playbacks []struct {
Name string `json:"name"`
Url string `json:"url"`
}
}
}
}
}
func fetchUpcomingGames() Schedule {
client := http.Client{
Timeout: time.Second * 2,
}
url := "https://statsapi.web.nhl.com/api/v1/schedule"
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
res, getErr := client.Do(req)
if getErr != nil {
log.Fatal(getErr)
}
if res.Body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
log.Fatal(err)
}
}(res.Body)
}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
log.Fatal(readErr)
}
schedule := &Schedule{}
jsonErr := json.Unmarshal(body, &schedule)
if jsonErr != nil {
log.Fatal(jsonErr)
}
return *schedule
}
type Schedule struct {
Dates []struct {
Date string `json:"date"`
Games []Games
}
}
type Games struct {
GamePk int `json:"gamePk"`
Teams Teams
Status struct {
AbstractGameState string `json:"abstractGameState"`
}
}
type Teams struct {
Away struct {
Team Team
} `json:"away"`
Home struct {
Team Team
} `json:"home"`
}
type Team struct {
Name string `json:"name"`
}
package com.viartemev
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.jackson.responseObject
import com.github.kittinunf.result.Result
import com.github.kittinunf.result.flatMap
import com.github.kittinunf.result.map
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
fun formatMessage(game: Game): String {
return "*${game.gameInfo.title}*\n🥅🏒 *${game.gameInfo.score}*\n[Recap](${game.video})"
}
fun main(args: Array<String>): Unit = runBlocking {
Fuel.get("https://statsapi.web.nhl.com/api/v1/schedule")
.responseObject<Schedule>()
.third
.fold({ schedule ->
fetchGames(schedule.extractGameInfo())
.map { result ->
println("Result: $result")
result.map(::formatMessage) }
.map { url ->
println("Here: $url")
url.map {
val response = Fuel.get(
"https://api.telegram.org/bot742436334:AAFstZitbGd5llQIGJL6-FBKFldg61Nf7_Y/sendMessage",
listOf("chat_id" to "-1001256296754", "parse_mode" to "markdown", "text" to it)
)
.response()
println("Code ${response.second.statusCode}")
}
}
}, errorHandler)
}
val errorHandler: (FuelError) -> Unit = { println("Error during schedule extraction: $it") }
suspend fun fetchGames(gamesInfo: List<GameInfo>): List<Result<Game, Exception>> {
return coroutineScope {
gamesInfo
.map { async { it.extractGames() } }
.awaitAll()
}
}
fun GameInfo.extractGames() =
Fuel.get("https://statsapi.web.nhl.com/api/v1/game/${this.gamePk}/content")
.responseObject<GameContent>()
.third
.flatMap { gameContent -> Result.of(Game(this, gameContent.extractVideo())) }
fun GameContent.extractVideo() = this
.media
.epg
.filter { epg -> epg.title == "Recap" }
.flatMap { epg -> epg.items.flatMap { item -> item.playbacks ?: emptyList() } }
.find { playback -> playback?.name == "FLASH_1800K_960X540" }
?.url
fun Schedule.extractGameInfo() = this
.dates
.flatMap { date ->
date.games.map { game ->
GameInfo(
date.date,
"${game.teams.away.team.name} - ${game.teams.home.team.name}",
"${game.teams.away.score} - ${game.teams.home.score}",
game.gamePk
)
}
}
data class GameInfo(
val date: String,
val title: String,
val score: String,
val gamePk: Long
)
data class Game(
val gameInfo: GameInfo,
val video: String?
) {
override fun toString(): String {
return """
${gameInfo.date} \n ${gameInfo.title} \n ${gameInfo.score} \n $video
""".trimIndent()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment