278 lines
7.5 KiB
Go
278 lines
7.5 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"list_app/models"
|
|
)
|
|
|
|
// PostersDir is where dynamically fetched poster images are cached on disk.
|
|
// Unlike the static frontend assets, these are downloaded at runtime and
|
|
// can't be embedded into the binary, so they live in a real directory next
|
|
// to it, served separately (see routes.New).
|
|
const PostersDir = "posters"
|
|
|
|
func PosterDiskPath(webImg string) string {
|
|
rel := strings.TrimPrefix(webImg, "/poster/")
|
|
return filepath.Join(PostersDir, rel)
|
|
}
|
|
|
|
type omdbRes struct {
|
|
Title string `json:"Title"`
|
|
Released string `json:"Released"`
|
|
Response string `json:"Response"`
|
|
Poster string `json:"Poster"`
|
|
Type string `json:"Type"`
|
|
ImdbID string `json:"imdbID"`
|
|
Year string `json:"Year"`
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, body any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(body)
|
|
}
|
|
|
|
func fromStringToTable(input string) (models.Table, bool) {
|
|
switch input {
|
|
case "games":
|
|
return models.Games, true
|
|
case "movies":
|
|
return models.Movies, true
|
|
case "series":
|
|
return models.Series, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func downloadImage(media models.Media, table models.Table) {
|
|
outputPath := "/poster/" + string(table) + "/" + media.Code + ".jpg"
|
|
|
|
response, err := http.Get(media.Poster)
|
|
if err != nil || !(response.StatusCode >= 200 && response.StatusCode < 300) {
|
|
log.Println("fetch image error")
|
|
log.Println(media.Title)
|
|
return
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
imageBytes, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
log.Println("fetch image error")
|
|
log.Println(media.Title)
|
|
return
|
|
}
|
|
|
|
destPath := PosterDiskPath(outputPath)
|
|
if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
if err := os.WriteFile(destPath, imageBytes, 0644); err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
|
|
models.UpdateWebImg(table, media.Code, outputPath)
|
|
}
|
|
|
|
func createMed(mediaCode string) (models.Media, error) {
|
|
omdbKey, ok := models.GetValue(models.OmdbKey)
|
|
if !ok || omdbKey == "" {
|
|
return models.Media{}, fmt.Errorf("error when creating media")
|
|
}
|
|
|
|
uri := "http://www.omdbapi.com/?i=" + url.QueryEscape(mediaCode) + "&apikey=" + url.QueryEscape(omdbKey)
|
|
resp, err := http.Get(uri)
|
|
if err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating media")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var mData omdbRes
|
|
if err := json.NewDecoder(resp.Body).Decode(&mData); err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating media")
|
|
}
|
|
|
|
if mData.Response == "False" {
|
|
return models.Media{}, fmt.Errorf("wrong code")
|
|
}
|
|
|
|
media := models.Media{
|
|
ID: 0,
|
|
Code: mData.ImdbID,
|
|
Title: mData.Title,
|
|
Released: mData.Released,
|
|
WebImg: "",
|
|
Poster: mData.Poster,
|
|
Year: mData.Year,
|
|
}
|
|
|
|
tableType := models.Series
|
|
if strings.EqualFold(mData.Type, "movie") {
|
|
tableType = models.Movies
|
|
}
|
|
|
|
found := models.FindOne(tableType, mediaCode)
|
|
if len(found) != 0 {
|
|
downloadImage(media, tableType)
|
|
return models.Media{}, fmt.Errorf("media already exists")
|
|
}
|
|
|
|
models.Save(tableType, mData.ImdbID, mData.Title, mData.Released, "", mData.Poster, mData.Year)
|
|
downloadImage(media, tableType)
|
|
|
|
return media, nil
|
|
}
|
|
|
|
func createGame(gameCodeStr string) (models.Media, error) {
|
|
twitchClientID, ok1 := models.GetValue(models.TwitchClientID)
|
|
twitchClientSecret, ok2 := models.GetValue(models.TwitchClientSecret)
|
|
if !ok1 || !ok2 || twitchClientID == "" || twitchClientSecret == "" {
|
|
return models.Media{}, fmt.Errorf("error when creating game")
|
|
}
|
|
|
|
gameFound := models.FindOne(models.Games, gameCodeStr)
|
|
if len(gameFound) != 0 {
|
|
return models.Media{}, fmt.Errorf("game already exists")
|
|
}
|
|
|
|
tokenURI := "https://id.twitch.tv/oauth2/token?client_id=" + url.QueryEscape(twitchClientID) +
|
|
"&client_secret=" + url.QueryEscape(twitchClientSecret) + "&grant_type=client_credentials"
|
|
tokenResp, err := http.Post(tokenURI, "application/x-www-form-urlencoded", nil)
|
|
if err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating game: %w", err)
|
|
}
|
|
defer tokenResp.Body.Close()
|
|
|
|
var tokenData struct {
|
|
AccessToken string `json:"access_token"`
|
|
}
|
|
if err := json.NewDecoder(tokenResp.Body).Decode(&tokenData); err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating game: %w", err)
|
|
}
|
|
|
|
gameCode, err := strconv.Atoi(gameCodeStr)
|
|
if err != nil {
|
|
return models.Media{}, fmt.Errorf("wrong code")
|
|
}
|
|
|
|
doIgdbRequest := func(path, body string) ([]byte, error) {
|
|
req, err := http.NewRequest(http.MethodPost, "https://api.igdb.com/v4/"+path, strings.NewReader(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
req.Header.Set("Client-ID", twitchClientID)
|
|
req.Header.Set("Authorization", "Bearer "+tokenData.AccessToken)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
return io.ReadAll(resp.Body)
|
|
}
|
|
|
|
gamesBody, err := doIgdbRequest("games", fmt.Sprintf("fields name, first_release_date; where id = %d;", gameCode))
|
|
if err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating game: %w", err)
|
|
}
|
|
|
|
var gameData []struct {
|
|
Name string `json:"name"`
|
|
FirstReleaseDate int64 `json:"first_release_date"`
|
|
}
|
|
if err := json.Unmarshal(gamesBody, &gameData); err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating game: %w", err)
|
|
}
|
|
if len(gameData) == 0 {
|
|
return models.Media{}, fmt.Errorf("wrong code")
|
|
}
|
|
|
|
releaseDate := time.Unix(gameData[0].FirstReleaseDate, 0).UTC()
|
|
dateStr := releaseDate.Format("2 Jan 2006")
|
|
|
|
coversBody, err := doIgdbRequest("covers", fmt.Sprintf("fields image_id; where game = %d;", gameCode))
|
|
if err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating game: %w", err)
|
|
}
|
|
|
|
var coverData []struct {
|
|
ImageID string `json:"image_id"`
|
|
}
|
|
if err := json.Unmarshal(coversBody, &coverData); err != nil {
|
|
return models.Media{}, fmt.Errorf("error when creating game: %w", err)
|
|
}
|
|
|
|
imageID := ""
|
|
if len(coverData) > 0 {
|
|
imageID = coverData[0].ImageID
|
|
}
|
|
|
|
game := models.Media{
|
|
ID: 0,
|
|
Code: gameCodeStr,
|
|
Title: gameData[0].Name,
|
|
Released: dateStr,
|
|
WebImg: "",
|
|
Poster: fmt.Sprintf("https://images.igdb.com/igdb/image/upload/t_cover_big/%s.jpg", imageID),
|
|
Year: strconv.Itoa(releaseDate.Year()),
|
|
}
|
|
|
|
models.Save(models.Games, game.Code, game.Title, game.Released, game.WebImg, game.Poster, game.Year)
|
|
downloadImage(game, models.Games)
|
|
|
|
return game, nil
|
|
}
|
|
|
|
// Create adds a movie, series, or game by its OMDB (imdb "tt...") or IGDB
|
|
// (numeric) code, dispatching to the right lookup based on the code's shape.
|
|
func Create(code string) (models.Media, error) {
|
|
if strings.HasPrefix(code, "tt") {
|
|
return createMed(code)
|
|
}
|
|
return createGame(code)
|
|
}
|
|
|
|
func List(w http.ResponseWriter, r *http.Request) {
|
|
mediaTable, ok := fromStringToTable(r.PathValue("mediaType"))
|
|
if !ok {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"message": "Error when getting media."})
|
|
return
|
|
}
|
|
|
|
media := models.Find(mediaTable)
|
|
writeJSON(w, http.StatusOK, media)
|
|
}
|
|
|
|
func CheckImages() {
|
|
checkTableImages(models.Games)
|
|
checkTableImages(models.Movies)
|
|
checkTableImages(models.Series)
|
|
}
|
|
|
|
func checkTableImages(table models.Table) {
|
|
list := models.Find(table)
|
|
|
|
for _, element := range list {
|
|
path := PosterDiskPath(element.WebImg)
|
|
if _, err := os.Stat(path); err == nil {
|
|
continue
|
|
}
|
|
log.Println(element.Title)
|
|
downloadImage(element, table)
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
}
|