hugo-medialog/internal/movies/api_trakt.go

199 lines
5.0 KiB
Go

// internal/movies/api_trakt.go
package movies
import (
"context"
"encoding/json"
"fmt"
"hugo-medialog/utils"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strconv"
"golang.org/x/oauth2"
)
var (
traktAPIBaseURL = "https://api.trakt.tv"
traktClientID string
traktClientSecret string
traktRedirectURI string
)
var traktOAuthConfig = &oauth2.Config{
ClientID: os.Getenv("TRAKT_CLIENT_ID"),
ClientSecret: os.Getenv("TRAKT_CLIENT_SECRET"),
Endpoint: oauth2.Endpoint{
AuthURL: "https://api.trakt.tv/oauth/authorize",
TokenURL: "https://api.trakt.tv/oauth/token",
},
RedirectURL: os.Getenv("TRAKT_REDIRECT_URI"),
}
func init() {
// Load runtime variables
utils.LoadConfig()
traktClientID = os.Getenv("TRAKT_CLIENT_ID")
traktClientSecret = os.Getenv("TRAKT_CLIENT_SECRET")
traktRedirectURI = os.Getenv("TRAKT_REDIRECT_URI")
if traktClientID == "" || traktClientSecret == "" || traktRedirectURI == "" {
log.Fatal("Missing Trakt API credentials in environment variables")
}
}
// Function to fetch a token and save it for reuse
func GetTraktToken() (*oauth2.Token, error) {
// Check if the token already exists (from a previous session)
tokenFile := "trakt_token.json"
if _, err := os.Stat(tokenFile); err == nil {
// If token file exists, read and return it
file, err := os.Open(tokenFile)
if err != nil {
return nil, err
}
defer file.Close()
token := &oauth2.Token{}
if err = json.NewDecoder(file).Decode(token); err != nil {
return nil, err
}
return token, nil
}
// If no token exists, we need to go through the OAuth flow
authURL := traktOAuthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Visit the URL to authorize: %v\n", authURL)
// After user authorization, you will receive an authorization code.
// Once you have the code, get a token by calling the token endpoint.
var authCode string
fmt.Print("Enter authorization code: ")
fmt.Scan(&authCode)
token, err := traktOAuthConfig.Exchange(context.Background(), authCode)
if err != nil {
return nil, err
}
// Save the token to file for future use
file, err := os.Create(tokenFile)
if err != nil {
return nil, err
}
defer file.Close()
if err := json.NewEncoder(file).Encode(token); err != nil {
return nil, err
}
return token, nil
}
// Function to get movie details by Trakt ID
func GetMovieByID(traktID int) (*Movie, error) {
token, err := GetTraktToken()
if err != nil {
return nil, err
}
client := traktOAuthConfig.Client(context.Background(), token)
// Call the Trakt API to get movie details by ID
apiURL := fmt.Sprintf("%s/movies/%s", traktAPIBaseURL, strconv.Itoa(traktID))
req, _ := http.NewRequest("GET", apiURL, nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
req.Header.Set("trakt-api-version", "2")
req.Header.Set("trakt-api-key", traktClientID)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch movie: %v", resp.Status)
}
body, _ := ioutil.ReadAll(resp.Body)
var traktMovie Movie
if err := json.Unmarshal(body, &traktMovie); err != nil {
return nil, err
}
return &traktMovie, nil
}
// Function to search for a movie by title
func SearchMovieByTitle(title string, movie *Movie) error {
token, err := GetTraktToken()
if err != nil {
return err
}
client := traktOAuthConfig.Client(context.Background(), token)
// URL encode the title
query := url.QueryEscape(title)
apiURL := fmt.Sprintf("%s/search/movie?query=%s", traktAPIBaseURL, query)
req, _ := http.NewRequest("GET", apiURL, nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
req.Header.Set("trakt-api-version", "2")
req.Header.Set("trakt-api-key", traktClientID)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to search movie: %v", resp.Status)
}
body, _ := ioutil.ReadAll(resp.Body)
// utils.DebugBody(body)
// Parse trakt.tv response to a struct
var traktResponse []struct {
Movie struct {
Title string `json:"title"`
Year int `json:"year"`
IDs struct {
Slug string `json:"slug"`
IMDB string `json:"imdb"`
TMDB int `json:"tmdb"`
Trakt int `json:"trakt"`
} `json:"ids"`
} `json:"movie"`
}
if err := json.Unmarshal(body, &traktResponse); err != nil {
return err
}
if len(traktResponse) == 0 {
return fmt.Errorf("no movie found with title %s", title)
}
// Using 1st result to fill our Movie structure
foundMovie := traktResponse[0].Movie
movie.Title = foundMovie.Title
movie.IDs.Slug = foundMovie.IDs.Slug
movie.IDs.IMDB = foundMovie.IDs.IMDB
movie.IDs.TMDB = foundMovie.IDs.TMDB
movie.IDs.Trakt = foundMovie.IDs.Trakt
movie.Subtitle = fmt.Sprintf("%d", foundMovie.Year)
movie.Year = foundMovie.Year
movie.Link = fmt.Sprintf("https://trakt.tv/movies/%d", foundMovie.IDs.Trakt)
return nil
}