aboutsummaryrefslogtreecommitdiff
path: root/utils/api/tvdb/tvdb.go
diff options
context:
space:
mode:
Diffstat (limited to 'utils/api/tvdb/tvdb.go')
-rw-r--r--utils/api/tvdb/tvdb.go240
1 files changed, 110 insertions, 130 deletions
diff --git a/utils/api/tvdb/tvdb.go b/utils/api/tvdb/tvdb.go
index dd83643..5d6f78d 100644
--- a/utils/api/tvdb/tvdb.go
+++ b/utils/api/tvdb/tvdb.go
@@ -4,9 +4,9 @@ import (
"bytes"
"crypto/md5"
"encoding/json"
+ "errors"
"fmt"
"metachan/config"
- "metachan/database"
"metachan/entities"
"metachan/types"
"metachan/utils/logger"
@@ -14,206 +14,186 @@ import (
"time"
)
-var tvdbToken string
-var tvdbTokenExpiry time.Time
+const (
+ tvdbAPIBaseURL = "https://api4.thetvdb.com/v4"
+ tvdbLoginEndpoint = "/login"
+ tvdbImageBaseURL = "https://artworks.thetvdb.com"
+ timeout = 10 * time.Second
+ episodesTimeout = 15 * time.Second
+ tokenExpiry = 24 * time.Hour
+ contentType = "application/json"
+ acceptHeader = "application/json"
+ noDescription = "No description available"
+ recapType = "recap"
+)
-// authenticateTVDB authenticates with TVDB API and returns a token
-func authenticateTVDB() (string, error) {
- // Check if we have a valid token
- if tvdbToken != "" && time.Now().Before(tvdbTokenExpiry) {
- return tvdbToken, nil
+var (
+ clientInstance = &client{
+ httpClient: &http.Client{
+ Timeout: timeout,
+ },
}
+)
- if config.Config.TVDB.APIKey == "" {
- return "", fmt.Errorf("TVDB API key is not set")
+func authenticate() (string, error) {
+ if clientInstance.token != "" && time.Now().Before(clientInstance.tokenExpiry) {
+ return clientInstance.token, nil
}
- logger.Log("Authenticating with TVDB API", logger.LogOptions{
- Level: logger.Debug,
- Prefix: "TVDB",
- })
+ if config.API.TVDBKey == "" {
+ logger.Errorf("TVDB", "TVDB API key is not set")
+ return "", errors.New("TVDB API key is not set")
+ }
- client := &http.Client{Timeout: 10 * time.Second}
+ logger.Debugf("TVDB", "Authenticating with TVDB API")
- // Create request body with apikey
- authBody := map[string]string{"apikey": config.Config.TVDB.APIKey}
+ authBody := map[string]string{"apikey": config.API.TVDBKey}
jsonBody, err := json.Marshal(authBody)
if err != nil {
- return "", fmt.Errorf("failed to marshal auth body: %w", err)
+ logger.Errorf("TVDB", "Failed to marshal auth body: %v", err)
+ return "", errors.New("failed to marshal auth body")
}
- req, err := http.NewRequest("POST", "https://api4.thetvdb.com/v4/login", bytes.NewBuffer(jsonBody))
+ req, err := http.NewRequest("POST", tvdbAPIBaseURL+tvdbLoginEndpoint, bytes.NewBuffer(jsonBody))
if err != nil {
- return "", fmt.Errorf("failed to create auth request: %w", err)
+ logger.Errorf("TVDB", "Failed to create auth request: %v", err)
+ return "", errors.New("failed to create auth request")
}
- req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("Content-Type", contentType)
- resp, err := client.Do(req)
+ resp, err := clientInstance.httpClient.Do(req)
if err != nil {
- return "", fmt.Errorf("failed to authenticate: %w", err)
+ logger.Errorf("TVDB", "Failed to authenticate: %v", err)
+ return "", errors.New("failed to authenticate")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
- return "", fmt.Errorf("authentication failed with status: %d", resp.StatusCode)
+ logger.Errorf("TVDB", "Authentication failed with status: %d", resp.StatusCode)
+ return "", errors.New("authentication failed")
}
- var authResp TVDBAuthResponse
+ var authResp types.TVDBAuthResponse
if err := json.NewDecoder(resp.Body).Decode(&authResp); err != nil {
- return "", fmt.Errorf("failed to decode auth response: %w", err)
+ logger.Errorf("TVDB", "Failed to decode auth response: %v", err)
+ return "", errors.New("failed to decode auth response")
}
if authResp.Data.Token == "" {
- return "", fmt.Errorf("no token received from TVDB")
+ logger.Errorf("TVDB", "No token received from TVDB")
+ return "", errors.New("no token received from TVDB")
}
- // Store token and set expiry (TVDB tokens typically last 30 days, but we'll refresh after 24 hours to be safe)
- tvdbToken = authResp.Data.Token
- tvdbTokenExpiry = time.Now().Add(24 * time.Hour)
+ clientInstance.token = authResp.Data.Token
+ clientInstance.tokenExpiry = time.Now().Add(tokenExpiry)
- logger.Log("Successfully authenticated with TVDB", logger.LogOptions{
- Level: logger.Success,
- Prefix: "TVDB",
- })
+ logger.Successf("TVDB", "Successfully authenticated with TVDB")
- return tvdbToken, nil
+ return clientInstance.token, nil
}
-// GetSeriesEpisodes fetches all episodes for a TVDB series
-func GetSeriesEpisodes(tvdbID int) ([]TVDBEpisode, error) {
- token, err := authenticateTVDB()
+func GetSeriesEpisodes(tvdbID int) ([]types.TVDBEpisode, error) {
+ token, err := authenticate()
if err != nil {
- return nil, fmt.Errorf("failed to authenticate with TVDB: %w", err)
+ logger.Errorf("TVDB", "Failed to authenticate with TVDB for series %d: %v", tvdbID, err)
+ return nil, errors.New("failed to authenticate with TVDB")
}
- logger.Log(fmt.Sprintf("Fetching episodes for TVDB series %d", tvdbID), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "TVDB",
- })
+ logger.Debugf("TVDB", "Fetching episodes for TVDB series %d", tvdbID)
- client := &http.Client{Timeout: 15 * time.Second}
+ tempClient := &http.Client{Timeout: episodesTimeout}
- // TVDB v4 API endpoint for episodes
- url := fmt.Sprintf("https://api4.thetvdb.com/v4/series/%d/episodes/default", tvdbID)
+ url := fmt.Sprintf("%s/series/%d/episodes/default", tvdbAPIBaseURL, tvdbID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
- return nil, fmt.Errorf("failed to create request: %w", err)
+ logger.Errorf("TVDB", "Failed to create request for series %d: %v", tvdbID, err)
+ return nil, errors.New("failed to create request")
}
req.Header.Add("Authorization", "Bearer "+token)
- req.Header.Add("Accept", "application/json")
+ req.Header.Add("Accept", acceptHeader)
- resp, err := client.Do(req)
+ resp, err := tempClient.Do(req)
if err != nil {
- return nil, fmt.Errorf("failed to fetch episodes: %w", err)
+ logger.Errorf("TVDB", "Failed to fetch episodes for series %d: %v", tvdbID, err)
+ return nil, errors.New("failed to fetch episodes")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("failed to fetch episodes with status: %d", resp.StatusCode)
+ logger.Errorf("TVDB", "Failed to fetch episodes with status: %d", resp.StatusCode)
+ return nil, errors.New("failed to fetch episodes")
}
- var episodesResp TVDBEpisodesResponse
+ var episodesResp types.TVDBEpisodesResponse
if err := json.NewDecoder(resp.Body).Decode(&episodesResp); err != nil {
- return nil, fmt.Errorf("failed to decode episodes response: %w", err)
+ logger.Errorf("TVDB", "Failed to decode episodes response for series %d: %v", tvdbID, err)
+ return nil, errors.New("failed to decode episodes response")
}
- logger.Log(fmt.Sprintf("Successfully fetched %d episodes from TVDB for series %d", len(episodesResp.Data.Episodes), tvdbID), logger.LogOptions{
- Level: logger.Success,
- Prefix: "TVDB",
- })
+ logger.Successf("TVDB", "Successfully fetched %d episodes from TVDB for series %d", len(episodesResp.Data.Episodes), tvdbID)
return episodesResp.Data.Episodes, nil
}
-// ConvertTVDBEpisodesToAnimeEpisodes converts TVDB episodes to anime episode format
-func ConvertTVDBEpisodesToAnimeEpisodes(tvdbEpisodes []TVDBEpisode) []types.AnimeSingleEpisode {
- var animeEpisodes []types.AnimeSingleEpisode
+func EnrichEpisodesFromTVDB(anime *entities.Anime, tvdbEpisodes []types.TVDBEpisode) {
+ if anime == nil || len(anime.Episodes) == 0 {
+ return
+ }
- const tvdbImageBaseURL = "https://artworks.thetvdb.com"
+ malID := anime.MALID
- for _, ep := range tvdbEpisodes {
- // Generate episode ID from name
- titles := types.EpisodeTitles{
- English: ep.Name,
- Japanese: "",
- Romaji: "",
+ for i, ep := range tvdbEpisodes {
+ if i >= len(anime.Episodes) {
+ break
}
- thumbnailURL := ""
- if ep.Image != "" {
- thumbnailURL = ep.Image
- }
+ episode := &anime.Episodes[i]
- description := ep.Overview
- if description == "" {
- description = "No description available"
+ if episode.Title == nil {
+ episode.Title = &entities.Title{}
}
-
- isRecap := false
- if ep.FinaleType != nil && *ep.FinaleType == "recap" {
- isRecap = true
+ if ep.Name != "" {
+ episode.Title.English = ep.Name
}
- animeEpisodes = append(animeEpisodes, types.AnimeSingleEpisode{
- ID: generateEpisodeID(titles),
- Titles: titles,
- Description: description,
- Aired: ep.Aired,
- ThumbnailURL: thumbnailURL,
- Score: 0,
- Filler: false,
- Recap: isRecap,
- ForumURL: "",
- URL: "",
- })
- }
-
- return animeEpisodes
-}
+ if ep.Image != "" {
+ episode.ThumbnailURL = ep.Image
+ }
-// generateEpisodeID creates a unique episode ID from titles
-func generateEpisodeID(titles types.EpisodeTitles) string {
- var title string
- if titles.English != "" {
- title = titles.English
- } else if titles.Romaji != "" {
- title = titles.Romaji
- } else {
- title = titles.Japanese
- }
+ if ep.Overview != "" {
+ episode.Description = ep.Overview
+ } else {
+ episode.Description = noDescription
+ }
- // MD5 hash for ID generation to match Jikan episode IDs
- hash := md5.Sum([]byte(title))
- return fmt.Sprintf("%x", hash)
-}
+ if ep.Aired != "" {
+ episode.Aired = ep.Aired
+ }
-// FindSeasonMappings finds all anime mappings that belong to the same series based on TVDB ID
-func FindSeasonMappings(tvdbID int) ([]entities.AnimeMapping, error) {
- logger.Log(fmt.Sprintf("Finding season mappings for TVDB ID %d", tvdbID), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "TVDB",
- })
+ if ep.FinaleType != nil && *ep.FinaleType == recapType {
+ episode.Recap = true
+ }
- // Use our database function to find all mappings with the same TVDB ID
- mappings, err := database.GetAnimeMappingsByTVDBID(tvdbID)
- if err != nil {
- return nil, fmt.Errorf("failed to get season mappings: %w", err)
- }
+ episode.EpisodeNumber = ep.Number
+ episode.EpisodeLength = float64(ep.Runtime)
- if len(mappings) == 0 {
- logger.Log(fmt.Sprintf("No season mappings found for TVDB ID %d", tvdbID), logger.LogOptions{
- Level: logger.Debug,
- Prefix: "TVDB",
- })
- } else {
- logger.Log(fmt.Sprintf("Found %d season mappings for TVDB ID %d", len(mappings), tvdbID), logger.LogOptions{
- Level: logger.Info,
- Prefix: "TVDB",
- })
+ titleForID := ep.Name
+ if titleForID == "" && episode.Title != nil {
+ if episode.Title.English != "" {
+ titleForID = episode.Title.English
+ } else if episode.Title.Romaji != "" {
+ titleForID = episode.Title.Romaji
+ }
+ }
+ episode.EpisodeID = generateEpisodeID(malID, ep.Number, titleForID)
}
+}
- return mappings, nil
+func generateEpisodeID(malID int, episodeNumber int, title string) string {
+ uniqueString := fmt.Sprintf("%d-%d-%s", malID, episodeNumber, title)
+ hash := md5.Sum([]byte(uniqueString))
+ return fmt.Sprintf("%x", hash)
}