package series 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 serie details by Trakt ID func GetSerieByID(traktID int) (*Serie, error) { token, err := GetTraktToken() if err != nil { return nil, err } client := traktOAuthConfig.Client(context.Background(), token) // Call the Trakt API to get serie details by ID apiURL := fmt.Sprintf("%s/shows/%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 serie: %v", resp.Status) } body, _ := ioutil.ReadAll(resp.Body) var traktSerie Serie if err := json.Unmarshal(body, &traktSerie); err != nil { return nil, err } return &traktSerie, nil } // Function to search for a serie by title func SearchSerieByTitle(title string, serie *Serie) 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/show?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 serie: %v", resp.Status) } body, _ := ioutil.ReadAll(resp.Body) // utils.DebugBody(body) // Parse trakt.tv response to a struct var traktResponse []struct { Serie 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"` TVDB int `json:"tvdb"` } `json:"ids"` } `json:"show"` } if err := json.Unmarshal(body, &traktResponse); err != nil { return err } if len(traktResponse) == 0 { return fmt.Errorf("no serie found with title %s", title) } // Using 1st result to fill our Serie structure foundSerie := traktResponse[0].Serie serie.Title = foundSerie.Title serie.IDs.Slug = foundSerie.IDs.Slug serie.IDs.IMDB = foundSerie.IDs.IMDB serie.IDs.TMDB = foundSerie.IDs.TMDB serie.IDs.Trakt = foundSerie.IDs.Trakt serie.IDs.TVDB = foundSerie.IDs.TVDB serie.Subtitle = fmt.Sprintf("%d", foundSerie.Year) serie.Year = foundSerie.Year serie.Link = fmt.Sprintf("https://trakt.tv/shows/%d", foundSerie.IDs.Trakt) return nil }