From 769fc87f0a473a3db2caf2c2f25bc09993738178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=81scar=20M=2E=20Lage?= Date: Thu, 10 Oct 2024 20:03:49 +0200 Subject: [PATCH] Added games --- env.sample | 6 +- internal/games/api_igdb.go | 235 +++++++++++++++++++++++++++++++++++ internal/games/controller.go | 102 +++++++++++++++ internal/games/model.go | 5 +- main.go | 23 +++- obsidian/games.yml.md | 5 + templates/game.md.tpl | 4 +- 7 files changed, 368 insertions(+), 12 deletions(-) create mode 100644 internal/games/api_igdb.go diff --git a/env.sample b/env.sample index 5d133f4..80478b7 100644 --- a/env.sample +++ b/env.sample @@ -2,12 +2,12 @@ TRAKT_CLIENT_ID=your_trakt_client_id TRAKT_CLIENT_SECRET=your_trakt_client_secret TRAKT_REDIRECT_URI=your_trakt_redirect_uri -IMDB_API_KEY=your_imdb_api_key -IGDB_API_KEY=your_igdb_api_key +FANART_API_KEY=your_fanart_api_key GOOGLE_BOOKS_API_KEY=your_gbooks_api_key GOOGLE_CUSTOM_SEARCH_ENGINE_API_KEY=your_cse_api_key GOOGLE_CX=your_google_cx -FANART_API_KEY=your_fanart_api_key +TWITCH_CLIENT_ID=your_twitch_client_id +TWITCH_SECRET_ID=your_twitch_secret_id SPOTIFY_API_KEY=your_spotify_api_key # PATH to Obisdian files where the information is diff --git a/internal/games/api_igdb.go b/internal/games/api_igdb.go new file mode 100644 index 0000000..b73d970 --- /dev/null +++ b/internal/games/api_igdb.go @@ -0,0 +1,235 @@ +package games + +import ( + "bytes" + "encoding/json" + "fmt" + "hugo-medialog/utils" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "time" +) + +type TokenResponse struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + TokenType string `json:"token_type"` +} + +func getAccessToken() (string, error) { + clientID := os.Getenv("TWITCH_CLIENT_ID") + clientSecret := os.Getenv("TWITCH_SECRET_ID") + + authURL := "https://id.twitch.tv/oauth2/token" + + data := url.Values{} + data.Set("client_id", clientID) + data.Set("client_secret", clientSecret) + data.Set("grant_type", "client_credentials") + + req, err := http.NewRequest("POST", authURL, strings.NewReader(data.Encode())) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("error: non-200 response code: %d", resp.StatusCode) + } + + var tokenResp TokenResponse + if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { + return "", err + } + + return tokenResp.AccessToken, nil +} + +func SearchGameByTitle(title string, game *Game) error { + + clientID := os.Getenv("TWITCH_CLIENT_ID") + accessToken, err := getAccessToken() + if err != nil { + fmt.Printf("Error getting access token: %v\n", err) + return err + } + + url := "https://api.igdb.com/v4/games" + + query := fmt.Sprintf("search \"%s\";\nfields id,name,slug,cover,artworks,first_release_date,genres,platforms,screenshots,slug,url;\n", title) + req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(query))) + if err != nil { + return err + } + + req.Header.Set("Client-ID", clientID) + req.Header.Set("Authorization", "Bearer "+accessToken) + req.Header.Set("Content-Type", "text/plain") + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + var games []map[string]interface{} + if err := json.Unmarshal(body, &games); err != nil { + return err + } + + if len(games) == 0 { + return fmt.Errorf("No games found") + } + + first_game := games[0] + + // Timestamp + if timestamp, ok := first_game["first_release_date"].(float64); ok { + game.Year = time.Unix(int64(timestamp), 0).Format("2006") + game.Subtitle = game.Year + } + // Cover + coverID := first_game["cover"].(float64) + coverURL, err := makeRequest("covers", coverID) + if err != nil { + fmt.Printf("Err: %s", err) + } + game.Image = coverURL + + // Artwork + if artworks, ok := first_game["artworks"].([]interface{}); ok && len(artworks) > 0 { + artworkURL, err := makeRequest("artworks", artworks[0].(float64)) + if err != nil { + fmt.Printf("Err: %s", err) + } + game.Poster = artworkURL + } + + // Screenshots + if screenshots, ok := first_game["screenshots"].([]interface{}); ok && len(screenshots) > 0 { + artworkURL, err := makeRequest("screenshots", screenshots[0].(float64)) + if err != nil { + fmt.Printf("Err: %s", err) + } + game.Background = artworkURL + } + + game.ID = int(first_game["id"].(float64)) + game.Link = first_game["url"].(string) + + return nil +} + +func getImageURL(imageID int, size string) string { + return fmt.Sprintf("https://images.igdb.com/igdb/image/upload/t_%s/%d.jpg", size, imageID) +} + +// Function to make requests to IGDB +func makeRequest(endpoint string, id float64) (string, error) { + + clientID := os.Getenv("TWITCH_CLIENT_ID") + accessToken, err := getAccessToken() + if err != nil { + fmt.Printf("Error getting access token: %v\n", err) + return "", err + } + + // Create the request body + idStr := fmt.Sprint(id) + body := fmt.Sprintf("fields id, image_id; where id = (%s);", idStr) + + req, err := http.NewRequest("POST", "https://api.igdb.com/v4/"+endpoint, strings.NewReader(body)) + if err != nil { + return "", err + } + + // Add headers + req.Header.Add("Client-ID", clientID) + req.Header.Add("Authorization", "Bearer "+accessToken) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + // Send the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Read the response + responseData, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + // Parse the response + var coverResponse []map[string]interface{} + err = json.Unmarshal(responseData, &coverResponse) + if err != nil { + return "", err + } + + // Get the image_id and construct the URL + if len(coverResponse) > 0 { + imageID := coverResponse[0]["image_id"].(string) + coverURL := fmt.Sprintf("https://images.igdb.com/igdb/image/upload/t_original/%s.jpg", imageID) + return coverURL, nil + } + + return "", fmt.Errorf("no image found for ID %d", id) +} + +func DownloadImage(url, slug, suffix string) error { + imageDir := filepath.Join(os.Getenv("MARKDOWN_OUTPUT_GAMES_DIR"), os.Getenv("IMAGES_OUTPUT_DIR")) + if err := utils.CreateDirIfNotExists(imageDir); err != nil { + return err + } + filename := fmt.Sprintf("%s%s.jpg", slug, suffix) + filePath := filepath.Join(imageDir, filename) + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("error downloading image: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("non-200 response while downloading image: %d", resp.StatusCode) + } + + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("error creating image file: %w", err) + } + defer file.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("error reading image data: %w", err) + } + + _, err = file.Write(body) + if err != nil { + return fmt.Errorf("error writing image data: %w", err) + } + + fmt.Printf(" - Image saved successfully at: %s\n", filePath) + return nil +} diff --git a/internal/games/controller.go b/internal/games/controller.go index 8b13789..c387aac 100644 --- a/internal/games/controller.go +++ b/internal/games/controller.go @@ -1 +1,103 @@ +package games +import ( + "fmt" + "hugo-medialog/utils" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +func LoadGames() ([]Game, error) { + gamesFile := os.Getenv("OBSIDIAN_GAMES_FILE") + fileData, err := os.ReadFile(gamesFile) + if err != nil { + return nil, err + } + + var games []Game + err = yaml.Unmarshal(fileData, &games) + if err != nil { + return nil, err + } + + return games, nil +} + +func ProcessGames(games []Game) error { + utils.Sep() + for _, game := range games { + fmt.Printf("Title: %s\n", game.Title) + + // If we dont have ID, search movie by Title and get the ID + if game.ID == 0 { + err := SearchGameByTitle(game.Title, &game) + if err != nil { + fmt.Printf("Error searching game by title %s: %s\n", game.Title, err) + continue + } + } + game.Slug = utils.Sluggify(game.Title) + + // Download Images + if game.Image != "" { + suffix := "" + err := DownloadImage(game.Image, game.Slug, suffix) + if err != nil { + fmt.Printf("Error downloading %s: %s\n", game.Image, err) + } + game.Image = fmt.Sprintf("%s.jpg", game.Slug) + } + if game.Poster != "" { + suffix := "-poster" + err := DownloadImage(game.Poster, game.Slug, suffix) + if err != nil { + fmt.Printf("Error downloading %s: %s\n", game.Image, err) + } + game.Poster = fmt.Sprintf("%s%s.jpg", game.Slug, suffix) + } + if game.Background != "" { + suffix := "-background" + err := DownloadImage(game.Background, game.Slug, suffix) + if err != nil { + fmt.Printf("Error downloading %s: %s\n", game.Background, err) + } + game.Background = fmt.Sprintf("%s%s.jpg", game.Slug, suffix) + } + + err := generateGameMarkdown(game) + if err != nil { + return err + } + + utils.Sep() + } + return nil +} + +func generateGameMarkdown(game Game) error { + templatePath := filepath.Join(os.Getenv("TEMPLATES_DIR"), "game.md.tpl") + outputDir := os.Getenv("MARKDOWN_OUTPUT_GAMES_DIR") + if err := utils.CreateDirIfNotExists(outputDir); err != nil { + return err + } + outputPath := filepath.Join(outputDir, fmt.Sprintf("%s.md", game.Slug)) + + data := map[string]interface{}{ + "Title": game.Title, + "Platform": game.Platform, + "Link": game.Link, + "Subtitle": game.Year, + "Year": game.Year, + "Rate": game.Rate, + "Progress": game.Progress, + "Image": game.Image, + "Poster": game.Poster, + "Background": game.Background, + "Date": game.Date, + "Tags": "playing", + } + + return utils.GenerateMarkdown(templatePath, outputPath, data) +} diff --git a/internal/games/model.go b/internal/games/model.go index 5e784d4..bf06f45 100644 --- a/internal/games/model.go +++ b/internal/games/model.go @@ -2,9 +2,12 @@ package games type Game struct { Title string `yaml:"title"` + Platform string `yaml:"platform"` + ID int `yaml:"igdb_id"` Link string `yaml:"link"` + Slug string `yaml:"slug"` Subtitle string `yaml:"subtitle"` - Year int `yaml:"year"` + Year string `yaml:"year"` Rate float64 `yaml:"rate"` Progress string `yaml:"progress"` Image string `yaml:"image"` diff --git a/main.go b/main.go index 1efccc5..38195ab 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "hugo-medialog/internal/books" + "hugo-medialog/internal/games" "hugo-medialog/internal/movies" "hugo-medialog/internal/series" "hugo-medialog/utils" @@ -13,7 +14,7 @@ func main() { // Load .env file utils.LoadConfig() - // Parameter + // --media parameter media := flag.String("media", "", "Specify the media type to process: movies, series, books, games, or music") flag.Parse() @@ -27,8 +28,8 @@ func main() { processSeries() case "books": processBooks() - // case "games": - // processGames() + case "games": + processGames() // case "music": // processMusic() default: @@ -41,10 +42,10 @@ func processAll() { processMovies() processSeries() processBooks() + processGames() } func processMovies() { - // Movies moviesList, err := movies.LoadMovies() if err != nil { fmt.Printf("Error reading movies file: %v\n", err) @@ -57,7 +58,6 @@ func processMovies() { } func processSeries() { - // Series seriesList, err := series.LoadSeries() if err != nil { fmt.Printf("Error reading series file: %v\n", err) @@ -70,7 +70,6 @@ func processSeries() { } func processBooks() { - // Books booksList, err := books.LoadBooks() if err != nil { fmt.Printf("Error reading books file: %v\n", err) @@ -81,3 +80,15 @@ func processBooks() { fmt.Printf("Error processing books: %v\n", err) } } + +func processGames() { + gamesList, err := games.LoadGames() + if err != nil { + fmt.Printf("Error reading games file: %v\n", err) + return + } + err = games.ProcessGames(gamesList) + if err != nil { + fmt.Printf("Error processing games: %v\n", err) + } +} diff --git a/obsidian/games.yml.md b/obsidian/games.yml.md index 99ba718..46b4f3e 100644 --- a/obsidian/games.yml.md +++ b/obsidian/games.yml.md @@ -2,3 +2,8 @@ progress: "40%" rate: 9.8 date: 2024-10-08 + +- title: "Diablo 4" + progress: "100%" + rate: 9.8 + date: 2024-10-08 diff --git a/templates/game.md.tpl b/templates/game.md.tpl index 18514cd..5ba5d31 100644 --- a/templates/game.md.tpl +++ b/templates/game.md.tpl @@ -6,8 +6,8 @@ year: {{ .Year }} rate: {{ .Rate }} progress: {{ .Progress }} image: {{ .Image }} -poster-image: {{ .PosterImage }} -background-image: {{ .BackgroundImage }} +poster-image: {{ .Poster }} +background-image: {{ .Background }} date: {{ .Date }} draft: false tags: {{ .Tags }}