aboutsummaryrefslogtreecommitdiff
path: root/utils/api
diff options
context:
space:
mode:
authorBobby <[email protected]>2025-05-09 02:25:54 +0530
committerBobby <[email protected]>2025-05-09 02:25:54 +0530
commitcb41c834529f696c2f83adbb41e6b592c89495f3 (patch)
tree5180a0c055c2b2f4b55a3374c83405d20e858d78 /utils/api
parent9ebde192fbde6d6d6be2d7d86485eca9a0c5e026 (diff)
downloadmetachan-cb41c834529f696c2f83adbb41e6b592c89495f3.tar.xz
metachan-cb41c834529f696c2f83adbb41e6b592c89495f3.zip
refactored types
Diffstat (limited to 'utils/api')
-rw-r--r--utils/api/anilist/anilist.go (renamed from utils/api/anilist.go)23
-rw-r--r--utils/api/anilist/types.go207
-rw-r--r--utils/api/aniskip/aniskip.go (renamed from utils/api/aniskip.go)57
-rw-r--r--utils/api/aniskip/types.go15
-rw-r--r--utils/api/jikan/jikan.go (renamed from utils/api/jikan.go)23
-rw-r--r--utils/api/jikan/types.go177
-rw-r--r--utils/api/malsync/malsync.go (renamed from utils/api/malsync.go)7
-rw-r--r--utils/api/malsync/types.go31
-rw-r--r--utils/api/streaming/streaming.go (renamed from utils/api/streaming.go)38
-rw-r--r--utils/api/streaming/types.go38
-rw-r--r--utils/api/tmdb/tmdb.go (renamed from utils/api/tmdb.go)82
-rw-r--r--utils/api/tmdb/types.go54
-rw-r--r--utils/api/tvdb/tvdb.go (renamed from utils/api/tvdb.go)13
13 files changed, 633 insertions, 132 deletions
diff --git a/utils/api/anilist.go b/utils/api/anilist/anilist.go
index cda860a..afb88e4 100644
--- a/utils/api/anilist.go
+++ b/utils/api/anilist/anilist.go
@@ -1,10 +1,9 @@
-package api
+package anilist
import (
"bytes"
"encoding/json"
"fmt"
- "metachan/types"
"metachan/utils/logger"
"net/http"
)
@@ -24,7 +23,7 @@ func NewAniListClient() *AniListClient {
}
// GetAnime fetches anime details from AniList by ID using a simpler approach
-func (c *AniListClient) GetAnime(anilistID int) (*types.AnilistAnimeResponse, error) {
+func (c *AniListClient) GetAnime(anilistID int) (*AnilistAnimeResponse, error) {
// Create a much simpler request with minimal formatting that might trigger Cloudflare
query := `
query ($id: Int) {
@@ -92,8 +91,8 @@ func (c *AniListClient) GetAnime(anilistID int) (*types.AnilistAnimeResponse, er
}
// Log the request for debugging
- logger.Log(fmt.Sprintf("Sending request to AniList for ID %d", anilistID), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Sending request to AniList for ID %d", anilistID), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AniList",
})
@@ -116,8 +115,8 @@ func (c *AniListClient) GetAnime(anilistID int) (*types.AnilistAnimeResponse, er
resp, err = c.client.Do(req)
if err != nil {
lastErr = err
- logger.Log(fmt.Sprintf("AniList request attempt %d failed: %v", i+1, err), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("AniList request attempt %d failed: %v", i+1, err), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AniList",
})
continue
@@ -129,14 +128,14 @@ func (c *AniListClient) GetAnime(anilistID int) (*types.AnilistAnimeResponse, er
body := make([]byte, 1024)
n, _ := resp.Body.Read(body)
lastErr = fmt.Errorf("server returned %d: %s", resp.StatusCode, string(body[:n]))
- logger.Log(fmt.Sprintf("AniList returned non-200 status on attempt %d: %v", i+1, lastErr), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("AniList returned non-200 status on attempt %d: %v", i+1, lastErr), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AniList",
})
continue
}
- var anilistResponse types.AnilistAnimeResponse
+ var anilistResponse AnilistAnimeResponse
if err := json.NewDecoder(resp.Body).Decode(&anilistResponse); err != nil {
lastErr = fmt.Errorf("failed to decode response: %w", err)
continue
@@ -151,8 +150,8 @@ func (c *AniListClient) GetAnime(anilistID int) (*types.AnilistAnimeResponse, er
if anilistResponse.Data.Media.CoverImage.ExtraLarge != "" {
logger.Log(fmt.Sprintf("Found cover data - Color: %s, Image: %s",
anilistResponse.Data.Media.CoverImage.Color,
- anilistResponse.Data.Media.CoverImage.ExtraLarge), types.LogOptions{
- Level: types.Debug,
+ anilistResponse.Data.Media.CoverImage.ExtraLarge), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AniList",
})
}
diff --git a/utils/api/anilist/types.go b/utils/api/anilist/types.go
new file mode 100644
index 0000000..367719b
--- /dev/null
+++ b/utils/api/anilist/types.go
@@ -0,0 +1,207 @@
+package anilist
+
+// AnilistAnimeResponse represents the response from AniList API
+type AnilistAnimeResponse struct {
+ Data struct {
+ Media struct {
+ ID int `json:"id"`
+ MALID int `json:"idMal"`
+ Title struct {
+ Romaji string `json:"romaji"`
+ English string `json:"english"`
+ Native string `json:"native"`
+ UserPreferred string `json:"userPreferred"`
+ } `json:"title"`
+ Type string `json:"type"`
+ Format string `json:"format"`
+ Status string `json:"status"`
+ Description string `json:"description"`
+ StartDate struct {
+ Year int `json:"year"`
+ Month int `json:"month"`
+ Day int `json:"day"`
+ } `json:"startDate"`
+ EndDate struct {
+ Year int `json:"year"`
+ Month int `json:"month"`
+ Day int `json:"day"`
+ } `json:"endDate"`
+ Season string `json:"season"`
+ SeasonYear int `json:"seasonYear"`
+ Episodes int `json:"episodes"`
+ Duration int `json:"duration"`
+ Chapters int `json:"chapters"`
+ Volumes int `json:"volumes"`
+ CountryOfOrigin string `json:"countryOfOrigin"`
+ IsLicensed bool `json:"isLicensed"`
+ Source string `json:"source"`
+ Hashtag string `json:"hashtag"`
+ Trailer struct {
+ ID string `json:"id"`
+ Site string `json:"site"`
+ Thumbnail string `json:"thumbnail"`
+ } `json:"trailer"`
+ CoverImage struct {
+ ExtraLarge string `json:"extraLarge"`
+ Large string `json:"large"`
+ Medium string `json:"medium"`
+ Color string `json:"color"`
+ } `json:"coverImage"`
+ BannerImage string `json:"bannerImage"`
+ Genres []string `json:"genres"`
+ Synonyms []string `json:"synonyms"`
+ AverageScore int `json:"averageScore"`
+ MeanScore int `json:"meanScore"`
+ Popularity int `json:"popularity"`
+ IsLocked bool `json:"isLocked"`
+ Trending int `json:"trending"`
+ Favorites int `json:"favorites"`
+ Tags []struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Category string `json:"category"`
+ Rank int `json:"rank"`
+ IsGeneralSpoiler bool `json:"isGeneralSpoiler"`
+ IsMediaSpoiler bool `json:"isMediaSpoiler"`
+ IsAdult bool `json:"isAdult"`
+ } `json:"tags"`
+ Relations struct {
+ Edges []struct {
+ ID int `json:"id"`
+ RelationType string `json:"relationType"`
+ Node struct {
+ ID int `json:"id"`
+ Title struct {
+ Romaji string `json:"romaji"`
+ English string `json:"english"`
+ Native string `json:"native"`
+ UserPreferred string `json:"userPreferred"`
+ } `json:"title"`
+ Format string `json:"format"`
+ Type string `json:"type"`
+ Status string `json:"status"`
+ CoverImage struct {
+ ExtraLarge string `json:"extraLarge"`
+ Large string `json:"large"`
+ Medium string `json:"medium"`
+ Color string `json:"color"`
+ } `json:"coverImage"`
+ BannerImage string `json:"bannerImage"`
+ } `json:"node"`
+ } `json:"edges"`
+ } `json:"relations"`
+ Characters struct {
+ Edges []struct {
+ Role string `json:"role"`
+ Node struct {
+ ID int `json:"id"`
+ Name struct {
+ First string `json:"first"`
+ Last string `json:"last"`
+ Middle string `json:"middle"`
+ Full string `json:"full"`
+ Native string `json:"native"`
+ UserPreferred string `json:"userPreferred"`
+ } `json:"name"`
+ Image struct {
+ Large string `json:"large"`
+ Medium string `json:"medium"`
+ } `json:"image"`
+ Description string `json:"description"`
+ Age string `json:"age"`
+ } `json:"node"`
+ } `json:"edges"`
+ } `json:"characters"`
+ Staff struct {
+ Edges []struct {
+ Role string `json:"role"`
+ Node struct {
+ ID int `json:"id"`
+ Name struct {
+ First string `json:"first"`
+ Last string `json:"last"`
+ Middle string `json:"middle"`
+ Full string `json:"full"`
+ Native string `json:"native"`
+ UserPreferred string `json:"userPreferred"`
+ } `json:"name"`
+ Image struct {
+ Large string `json:"large"`
+ Medium string `json:"medium"`
+ } `json:"image"`
+ Description string `json:"description"`
+ PrimaryOccupations []string `json:"primaryOccupations"`
+ Gender string `json:"gender"`
+ Age int `json:"age"`
+ LanguageV2 string `json:"languageV2"`
+ } `json:"node"`
+ } `json:"edges"`
+ } `json:"staff"`
+ Studios struct {
+ Edges []struct {
+ IsMain bool `json:"isMain"`
+ Node struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ } `json:"node"`
+ } `json:"edges"`
+ } `json:"studios"`
+ IsAdult bool `json:"isAdult"`
+ NextAiringEpisode struct {
+ ID int `json:"id"`
+ AiringAt int `json:"airingAt"`
+ TimeUntilAiring int `json:"timeUntilAiring"`
+ Episode int `json:"episode"`
+ } `json:"nextAiringEpisode"`
+ AiringSchedule struct {
+ Nodes []struct {
+ ID int `json:"id"`
+ Episode int `json:"episode"`
+ AiringAt int `json:"airingAt"`
+ TimeUntilAiring int `json:"timeUntilAiring"`
+ } `json:"nodes"`
+ } `json:"airingSchedule"`
+ Trends struct {
+ Nodes []struct {
+ Date int `json:"date"`
+ Trending int `json:"trending"`
+ Popularity int `json:"popularity"`
+ InProgress int `json:"inProgress"`
+ } `json:"nodes"`
+ } `json:"trends"`
+ ExternalLinks []struct {
+ ID int `json:"id"`
+ URL string `json:"url"`
+ Site string `json:"site"`
+ } `json:"externalLinks"`
+ StreamingEpisodes []struct {
+ Title string `json:"title"`
+ Thumbnail string `json:"thumbnail"`
+ URL string `json:"url"`
+ Site string `json:"site"`
+ } `json:"streamingEpisodes"`
+ Rankings []struct {
+ ID int `json:"id"`
+ Rank int `json:"rank"`
+ Type string `json:"type"`
+ Format string `json:"format"`
+ Year int `json:"year"`
+ Season string `json:"season"`
+ AllTime bool `json:"allTime"`
+ Context string `json:"context"`
+ } `json:"rankings"`
+ Stats struct {
+ ScoreDistribution []struct {
+ Score int `json:"score"`
+ Amount int `json:"amount"`
+ } `json:"scoreDistribution"`
+ StatusDistribution []struct {
+ Status string `json:"status"`
+ Amount int `json:"amount"`
+ } `json:"statusDistribution"`
+ } `json:"stats"`
+ SiteURL string `json:"siteUrl"`
+ } `json:"media"`
+ } `json:"data"`
+}
diff --git a/utils/api/aniskip.go b/utils/api/aniskip/aniskip.go
index c5aa462..fb95f47 100644
--- a/utils/api/aniskip.go
+++ b/utils/api/aniskip/aniskip.go
@@ -1,11 +1,10 @@
-package api
+package aniskip
import (
"context"
"encoding/json"
"fmt"
"io"
- "metachan/types"
"metachan/utils/logger"
"metachan/utils/ratelimit"
"net/http"
@@ -22,7 +21,7 @@ type AniSkipClient struct {
client *http.Client
rateLimiter *ratelimit.RateLimiter
maxRetries int
- cache map[string][]types.AnimeSkipTimes
+ cache map[string][]AnimeSkipTimes
cacheMutex sync.RWMutex
cacheTTL time.Duration
cacheTime map[string]time.Time
@@ -31,7 +30,7 @@ type AniSkipClient struct {
// EpisodeSkipTimesResult contains skip times for a specific episode
type EpisodeSkipTimesResult struct {
EpisodeNumber int
- SkipTimes []types.AnimeSkipTimes
+ SkipTimes []AnimeSkipTimes
}
// NewAniSkipClient creates a new client for the AniSkip API
@@ -42,7 +41,7 @@ func NewAniSkipClient() *AniSkipClient {
},
rateLimiter: ratelimit.NewRateLimiter(10, 10*time.Second), // Conservative rate limit
maxRetries: 2,
- cache: make(map[string][]types.AnimeSkipTimes),
+ cache: make(map[string][]AnimeSkipTimes),
cacheTime: make(map[string]time.Time),
cacheTTL: 24 * time.Hour, // Cache skip times for 24 hours
}
@@ -54,7 +53,7 @@ func (c *AniSkipClient) getCacheKey(malID, episode int) string {
}
// getFromCache tries to get skip times from cache
-func (c *AniSkipClient) getFromCache(malID, episode int) ([]types.AnimeSkipTimes, bool) {
+func (c *AniSkipClient) getFromCache(malID, episode int) ([]AnimeSkipTimes, bool) {
key := c.getCacheKey(malID, episode)
c.cacheMutex.RLock()
@@ -72,7 +71,7 @@ func (c *AniSkipClient) getFromCache(malID, episode int) ([]types.AnimeSkipTimes
}
// saveToCache saves skip times to cache
-func (c *AniSkipClient) saveToCache(malID, episode int, skipTimes []types.AnimeSkipTimes) {
+func (c *AniSkipClient) saveToCache(malID, episode int, skipTimes []AnimeSkipTimes) {
key := c.getCacheKey(malID, episode)
c.cacheMutex.Lock()
@@ -83,7 +82,7 @@ func (c *AniSkipClient) saveToCache(malID, episode int, skipTimes []types.AnimeS
}
// GetSkipTimesForEpisode fetches skip times for a specific anime episode
-func (c *AniSkipClient) GetSkipTimesForEpisode(malID, episodeNumber int) ([]types.AnimeSkipTimes, error) {
+func (c *AniSkipClient) GetSkipTimesForEpisode(malID, episodeNumber int) ([]AnimeSkipTimes, error) {
// Check cache first
if skipTimes, found := c.getFromCache(malID, episodeNumber); found {
return skipTimes, nil
@@ -123,8 +122,8 @@ func (c *AniSkipClient) GetSkipTimesForEpisode(malID, episodeNumber int) ([]type
if resp.StatusCode == http.StatusNotFound {
// No skip times found, not an error
- c.saveToCache(malID, episodeNumber, []types.AnimeSkipTimes{})
- return []types.AnimeSkipTimes{}, nil
+ c.saveToCache(malID, episodeNumber, []AnimeSkipTimes{})
+ return []AnimeSkipTimes{}, nil
}
if resp.StatusCode != http.StatusOK {
@@ -174,14 +173,14 @@ func (c *AniSkipClient) GetSkipTimesForEpisode(malID, episodeNumber int) ([]type
// If no results found
if !skipResp.Found || len(skipResp.Results) == 0 {
- c.saveToCache(malID, episodeNumber, []types.AnimeSkipTimes{})
- return []types.AnimeSkipTimes{}, nil
+ c.saveToCache(malID, episodeNumber, []AnimeSkipTimes{})
+ return []AnimeSkipTimes{}, nil
}
// Convert to our skip times format
- var skipTimes []types.AnimeSkipTimes
+ var skipTimes []AnimeSkipTimes
for _, result := range skipResp.Results {
- skipTime := types.AnimeSkipTimes{
+ skipTime := AnimeSkipTimes{
SkipType: result.SkipType,
StartTime: result.Interval.StartTime,
EndTime: result.Interval.EndTime,
@@ -197,10 +196,10 @@ func (c *AniSkipClient) GetSkipTimesForEpisode(malID, episodeNumber int) ([]type
}
// GetSkipTimesForEpisodesBatch fetches skip times for episodes in batches
-func (c *AniSkipClient) GetSkipTimesForEpisodesBatch(malID int, episodes []int) (map[int][]types.AnimeSkipTimes, error) {
+func (c *AniSkipClient) GetSkipTimesForEpisodesBatch(malID int, episodes []int) (map[int][]AnimeSkipTimes, error) {
// If we have fewer than 3 episodes, use individual requests instead
if len(episodes) < 3 {
- results := make(map[int][]types.AnimeSkipTimes)
+ results := make(map[int][]AnimeSkipTimes)
for _, ep := range episodes {
skipTimes, err := c.GetSkipTimesForEpisode(malID, ep)
if err != nil {
@@ -213,7 +212,7 @@ func (c *AniSkipClient) GetSkipTimesForEpisodesBatch(malID int, episodes []int)
// Check if all episodes are cached and return them
allCached := true
- cachedResults := make(map[int][]types.AnimeSkipTimes)
+ cachedResults := make(map[int][]AnimeSkipTimes)
for _, ep := range episodes {
if skipTimes, found := c.getFromCache(malID, ep); found {
@@ -318,7 +317,7 @@ func (c *AniSkipClient) GetSkipTimesForEpisodesBatch(malID int, episodes []int)
return nil, fmt.Errorf("failed to decode batch response: %w", err)
}
- results := make(map[int][]types.AnimeSkipTimes)
+ results := make(map[int][]AnimeSkipTimes)
// Process results
for epStr, epData := range skipResp {
@@ -327,11 +326,11 @@ func (c *AniSkipClient) GetSkipTimesForEpisodesBatch(malID int, episodes []int)
continue // Skip if we can't parse the episode number
}
- var skipTimes []types.AnimeSkipTimes
+ var skipTimes []AnimeSkipTimes
if epData.Found {
for _, result := range epData.Results {
- skipTimes = append(skipTimes, types.AnimeSkipTimes{
+ skipTimes = append(skipTimes, AnimeSkipTimes{
SkipType: result.SkipType,
StartTime: result.Interval.StartTime,
EndTime: result.Interval.EndTime,
@@ -349,16 +348,16 @@ func (c *AniSkipClient) GetSkipTimesForEpisodesBatch(malID int, episodes []int)
}
// GetSkipTimesForEpisodes fetches skip times for multiple episodes efficiently
-func (c *AniSkipClient) GetSkipTimesForEpisodes(malID int, episodeCount int, maxConcurrent int) []types.EpisodeSkipResult {
+func (c *AniSkipClient) GetSkipTimesForEpisodes(malID int, episodeCount int, maxConcurrent int) []EpisodeSkipResult {
startTime := time.Now()
// If episode count is small, just use single endpoint
if episodeCount <= 5 {
- results := []types.EpisodeSkipResult{}
+ results := []EpisodeSkipResult{}
for i := 1; i <= episodeCount; i++ {
skipTimes, err := c.GetSkipTimesForEpisode(malID, i)
if err == nil && len(skipTimes) > 0 {
- results = append(results, types.EpisodeSkipResult{
+ results = append(results, EpisodeSkipResult{
EpisodeNumber: i,
SkipTimes: skipTimes,
})
@@ -375,7 +374,7 @@ func (c *AniSkipClient) GetSkipTimesForEpisodes(malID int, episodeCount int, max
// Batch size - we'll process episodes in batches
const batchSize = 25
- var results []types.EpisodeSkipResult
+ var results []EpisodeSkipResult
// Process in batches
for i := 0; i < episodeCount; i += batchSize {
@@ -387,8 +386,8 @@ func (c *AniSkipClient) GetSkipTimesForEpisodes(malID int, episodeCount int, max
batchEpisodes := allEpisodes[i:end]
batchResults, err := c.GetSkipTimesForEpisodesBatch(malID, batchEpisodes)
if err != nil {
- logger.Log(fmt.Sprintf("Error fetching skip times batch %d-%d: %v", i+1, end, err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Error fetching skip times batch %d-%d: %v", i+1, end, err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "AniSkip",
})
continue
@@ -397,7 +396,7 @@ func (c *AniSkipClient) GetSkipTimesForEpisodes(malID int, episodeCount int, max
// Add results to the final list
for epNum, skipTimes := range batchResults {
if len(skipTimes) > 0 {
- results = append(results, types.EpisodeSkipResult{
+ results = append(results, EpisodeSkipResult{
EpisodeNumber: epNum,
SkipTimes: skipTimes,
})
@@ -406,8 +405,8 @@ func (c *AniSkipClient) GetSkipTimesForEpisodes(malID int, episodeCount int, max
}
logger.Log(fmt.Sprintf("AniSkip: Fetched skip times for %d episodes of %d in %s",
- len(results), episodeCount, time.Since(startTime)), types.LogOptions{
- Level: types.Debug,
+ len(results), episodeCount, time.Since(startTime)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AniSkip",
})
diff --git a/utils/api/aniskip/types.go b/utils/api/aniskip/types.go
new file mode 100644
index 0000000..24c9618
--- /dev/null
+++ b/utils/api/aniskip/types.go
@@ -0,0 +1,15 @@
+package aniskip
+
+// AnimeSkipTimes represents skip time intervals for anime episodes
+type AnimeSkipTimes struct {
+ SkipType string `json:"skip_type"`
+ StartTime float64 `json:"start_time"`
+ EndTime float64 `json:"end_time"`
+ EpisodeLength float64 `json:"episode_length"`
+}
+
+// EpisodeSkipResult contains skip times for a specific episode
+type EpisodeSkipResult struct {
+ EpisodeNumber int
+ SkipTimes []AnimeSkipTimes
+}
diff --git a/utils/api/jikan.go b/utils/api/jikan/jikan.go
index 0f9ff83..9dcf00a 100644
--- a/utils/api/jikan.go
+++ b/utils/api/jikan/jikan.go
@@ -1,4 +1,4 @@
-package api
+package jikan
import (
"context"
@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"math"
- "metachan/types"
"metachan/utils/ratelimit"
"net/http"
"strconv"
@@ -121,7 +120,7 @@ func (c *JikanClient) makeRequest(ctx context.Context, url string) ([]byte, erro
}
// GetAnime fetches basic anime information by MAL ID
-func (c *JikanClient) GetAnime(malID int) (*types.JikanAnimeResponse, error) {
+func (c *JikanClient) GetAnime(malID int) (*JikanAnimeResponse, error) {
apiURL := fmt.Sprintf("https://api.jikan.moe/v4/anime/%d", malID)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
@@ -132,7 +131,7 @@ func (c *JikanClient) GetAnime(malID int) (*types.JikanAnimeResponse, error) {
return nil, fmt.Errorf("failed to get anime data: %w", err)
}
- var animeResponse types.JikanAnimeResponse
+ var animeResponse JikanAnimeResponse
if err := json.Unmarshal(bodyBytes, &animeResponse); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
@@ -145,7 +144,7 @@ func (c *JikanClient) GetAnime(malID int) (*types.JikanAnimeResponse, error) {
}
// GetFullAnime fetches detailed anime information by MAL ID
-func (c *JikanClient) GetFullAnime(malID int) (*types.JikanAnimeResponse, error) {
+func (c *JikanClient) GetFullAnime(malID int) (*JikanAnimeResponse, error) {
apiURL := fmt.Sprintf("https://api.jikan.moe/v4/anime/%d/full", malID)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
@@ -156,7 +155,7 @@ func (c *JikanClient) GetFullAnime(malID int) (*types.JikanAnimeResponse, error)
return nil, fmt.Errorf("failed to get anime full data: %w", err)
}
- var animeResponse types.JikanAnimeResponse
+ var animeResponse JikanAnimeResponse
if err := json.Unmarshal(bodyBytes, &animeResponse); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
@@ -169,9 +168,9 @@ func (c *JikanClient) GetFullAnime(malID int) (*types.JikanAnimeResponse, error)
}
// GetAnimeEpisodes fetches all episodes for an anime by MAL ID
-func (c *JikanClient) GetAnimeEpisodes(malID int) (*types.JikanAnimeEpisodeResponse, error) {
- result := types.JikanAnimeEpisodeResponse{
- Data: []types.JikanAnimeEpisode{},
+func (c *JikanClient) GetAnimeEpisodes(malID int) (*JikanAnimeEpisodeResponse, error) {
+ result := JikanAnimeEpisodeResponse{
+ Data: []JikanAnimeEpisode{},
}
maxPages := 25 // Safety limit to avoid excessive requests
@@ -198,7 +197,7 @@ func (c *JikanClient) GetAnimeEpisodes(malID int) (*types.JikanAnimeEpisodeRespo
return nil, fmt.Errorf("failed to get anime episodes page %d: %w", page, err)
}
- var pageResponse types.JikanAnimeEpisodeResponse
+ var pageResponse JikanAnimeEpisodeResponse
if err := json.Unmarshal(bodyBytes, &pageResponse); err != nil {
// Return what we have if we got some pages successfully
if len(result.Data) > 0 {
@@ -224,7 +223,7 @@ func (c *JikanClient) GetAnimeEpisodes(malID int) (*types.JikanAnimeEpisodeRespo
}
// GetAnimeCharacters fetches all characters for an anime by MAL ID
-func (c *JikanClient) GetAnimeCharacters(malID int) (*types.JikanAnimeCharacterResponse, error) {
+func (c *JikanClient) GetAnimeCharacters(malID int) (*JikanAnimeCharacterResponse, error) {
apiURL := fmt.Sprintf("https://api.jikan.moe/v4/anime/%d/characters", malID)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
@@ -235,7 +234,7 @@ func (c *JikanClient) GetAnimeCharacters(malID int) (*types.JikanAnimeCharacterR
return nil, fmt.Errorf("failed to get anime characters: %w", err)
}
- var characterResponse types.JikanAnimeCharacterResponse
+ var characterResponse JikanAnimeCharacterResponse
if err := json.Unmarshal(bodyBytes, &characterResponse); err != nil {
return nil, fmt.Errorf("failed to decode characters response: %w", err)
}
diff --git a/utils/api/jikan/types.go b/utils/api/jikan/types.go
new file mode 100644
index 0000000..cbcb543
--- /dev/null
+++ b/utils/api/jikan/types.go
@@ -0,0 +1,177 @@
+package jikan
+
+// JikanPagination represents the pagination data in Jikan API responses
+type JikanPagination struct {
+ LastVisiblePage int `json:"last_visible_page"`
+ HasNextPage bool `json:"has_next_page"`
+}
+
+// JikanGenericMALStructure represents a common structure for various MAL entities
+type JikanGenericMALStructure struct {
+ MALID int `json:"mal_id"`
+ Type string `json:"type"`
+ URL string `json:"url"`
+ Name string `json:"name"`
+}
+
+// JikanAnimeResponse represents the main anime response from Jikan API
+type JikanAnimeResponse struct {
+ Data struct {
+ MALID int `json:"mal_id"`
+ URL string `json:"url"`
+ Images struct {
+ JPG struct {
+ ImageURL string `json:"image_url"`
+ SmallImageURL string `json:"small_image_url"`
+ LargeImageURL string `json:"large_image_url"`
+ } `json:"jpg"`
+ WebP struct {
+ ImageURL string `json:"image_url"`
+ SmallImageURL string `json:"small_image_url"`
+ LargeImageURL string `json:"large_image_url"`
+ } `json:"webp"`
+ } `json:"images"`
+ Trailer struct {
+ YoutubeID string `json:"youtube_id"`
+ URL string `json:"url"`
+ EmbedURL string `json:"embed_url"`
+ Images struct {
+ ImageURL string `json:"image_url"`
+ SmallImageURL string `json:"small_image_url"`
+ MediumImageURL string `json:"medium_image_url"`
+ LargeImageURL string `json:"large_image_url"`
+ MaximumImageURL string `json:"maximum_image_url"`
+ } `json:"images"`
+ } `json:"trailer"`
+ Approved bool `json:"approved"`
+ Titles []struct {
+ Type string `json:"type"`
+ Title string `json:"title"`
+ } `json:"titles"`
+ Title string `json:"title"`
+ TitleEnglish string `json:"title_english"`
+ TitleJapanese string `json:"title_japanese"`
+ TitleSynonyms []string `json:"title_synonyms"`
+ Type string `json:"type"`
+ Source string `json:"source"`
+ Episodes int `json:"episodes"`
+ Status string `json:"status"`
+ Airing bool `json:"airing"`
+ Aired struct {
+ From string `json:"from"`
+ To string `json:"to"`
+ Prop struct {
+ From struct {
+ Day int `json:"day"`
+ Month int `json:"month"`
+ Year int `json:"year"`
+ } `json:"from"`
+ To struct {
+ Day int `json:"day"`
+ Month int `json:"month"`
+ Year int `json:"year"`
+ } `json:"to"`
+ } `json:"prop"`
+ String string `json:"string"`
+ } `json:"aired"`
+ Duration string `json:"duration"`
+ Rating string `json:"rating"`
+ Score float64 `json:"score"`
+ ScoredBy int `json:"scored_by"`
+ Rank int `json:"rank"`
+ Popularity int `json:"popularity"`
+ Members int `json:"members"`
+ Favorites int `json:"favorites"`
+ Synopsis string `json:"synopsis"`
+ Background string `json:"background"`
+ Season string `json:"season"`
+ Year int `json:"year"`
+ Broadcast struct {
+ Day string `json:"day"`
+ Time string `json:"time"`
+ Timezone string `json:"timezone"`
+ String string `json:"string"`
+ } `json:"broadcast"`
+ Producers []JikanGenericMALStructure `json:"producers"`
+ Licensors []JikanGenericMALStructure `json:"licensors"`
+ Studios []JikanGenericMALStructure `json:"studios"`
+ Genres []JikanGenericMALStructure `json:"genres"`
+ ExplicitGenres []JikanGenericMALStructure `json:"explicit_genres"`
+ Themes []JikanGenericMALStructure `json:"themes"`
+ Demographics []JikanGenericMALStructure `json:"demographics"`
+ Relations []struct {
+ Relation string `json:"relation"`
+ Entry []JikanGenericMALStructure `json:"entry"`
+ } `json:"relations"`
+ Theme struct {
+ Openings []string `json:"openings"`
+ Endings []string `json:"endings"`
+ } `json:"theme"`
+ External []struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"external"`
+ Streaming []struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"streaming"`
+ } `json:"data"`
+}
+
+// JikanAnimeEpisode represents an episode from Jikan API
+type JikanAnimeEpisode struct {
+ MALID int `json:"mal_id"`
+ URL string `json:"url"`
+ Title string `json:"title"`
+ TitleJapanese string `json:"title_japanese"`
+ TitleRomaji string `json:"title_romaji"`
+ Aired string `json:"aired"`
+ Score float64 `json:"score"`
+ Filler bool `json:"filler"`
+ Recap bool `json:"recap"`
+ ForumURL string `json:"forum_url"`
+}
+
+// JikanAnimeEpisodeResponse represents the episodes response from Jikan API
+type JikanAnimeEpisodeResponse struct {
+ Pagination JikanPagination `json:"pagination"`
+ Data []JikanAnimeEpisode `json:"data"`
+}
+
+// JikanAnimeCharacterResponse represents the characters response from Jikan API
+type JikanAnimeCharacterResponse struct {
+ Data []struct {
+ Character struct {
+ MALID int `json:"mal_id"`
+ URL string `json:"url"`
+ Images struct {
+ JPG struct {
+ ImageURL string `json:"image_url"`
+ SmallImageURL string `json:"small_image_url"`
+ } `json:"jpg"`
+ WebP struct {
+ ImageURL string `json:"image_url"`
+ SmallImageURL string `json:"small_image_url"`
+ } `json:"webp"`
+ } `json:"images"`
+ Name string `json:"name"`
+ } `json:"character"`
+ Role string `json:"role"`
+ VoiceActors []struct {
+ Person struct {
+ MALID int `json:"mal_id"`
+ URL string `json:"url"`
+ Images struct {
+ JPG struct {
+ ImageURL string `json:"image_url"`
+ } `json:"jpg"`
+ WebP struct {
+ ImageURL string `json:"image_url"`
+ } `json:"webp"`
+ } `json:"images"`
+ Name string `json:"name"`
+ } `json:"person"`
+ Language string `json:"language"`
+ } `json:"voice_actors"`
+ } `json:"data"`
+}
diff --git a/utils/api/malsync.go b/utils/api/malsync/malsync.go
index d323841..379c6ad 100644
--- a/utils/api/malsync.go
+++ b/utils/api/malsync/malsync.go
@@ -1,11 +1,10 @@
-package api
+package malsync
import (
"context"
"encoding/json"
"fmt"
"io"
- "metachan/types"
"net/http"
"time"
)
@@ -31,7 +30,7 @@ func NewMALSyncClient() *MALSyncClient {
}
// GetAnimeByMALID fetches anime metadata from MALSync by MAL ID
-func (c *MALSyncClient) GetAnimeByMALID(malID int) (*types.MALSyncAnimeResponse, error) {
+func (c *MALSyncClient) GetAnimeByMALID(malID int) (*MALSyncAnimeResponse, error) {
apiURL := fmt.Sprintf("%s/anime/%d", malsyncAPIBaseURL, malID)
// Create context with timeout
@@ -85,7 +84,7 @@ func (c *MALSyncClient) GetAnimeByMALID(malID int) (*types.MALSyncAnimeResponse,
return nil, fmt.Errorf("failed to read response: %w", err)
}
- var malSyncResponse types.MALSyncAnimeResponse
+ var malSyncResponse MALSyncAnimeResponse
if err := json.Unmarshal(bodyBytes, &malSyncResponse); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
diff --git a/utils/api/malsync/types.go b/utils/api/malsync/types.go
new file mode 100644
index 0000000..9944166
--- /dev/null
+++ b/utils/api/malsync/types.go
@@ -0,0 +1,31 @@
+package malsync
+
+// MALSyncStreamingSite represents a single streaming site entry in the MALSync API
+type MALSyncStreamingSite struct {
+ ID int `json:"id,omitempty"`
+ Identifier any `json:"identifier"`
+ Image string `json:"image,omitempty"`
+ MalID int `json:"malId,omitempty"`
+ AniID int `json:"aniId,omitempty"`
+ Page string `json:"page"`
+ Title string `json:"title"`
+ Type string `json:"type"`
+ URL string `json:"url"`
+ External bool `json:"external,omitempty"`
+}
+
+// MALSyncSitesCollection represents the nested structure of streaming sites
+// Format: map[platformName]map[identifier]siteObject
+type MALSyncSitesCollection map[string]map[string]MALSyncStreamingSite
+
+// MALSyncAnimeResponse is the top-level response from the MALSync API
+type MALSyncAnimeResponse struct {
+ ID int `json:"id"`
+ Type string `json:"type"`
+ Title string `json:"title"`
+ URL string `json:"url"`
+ Total int `json:"total"`
+ Image string `json:"image"`
+ AnidbID int `json:"anidbId,omitempty"`
+ Sites MALSyncSitesCollection `json:"Sites"`
+}
diff --git a/utils/api/streaming.go b/utils/api/streaming/streaming.go
index 067704e..4d0f625 100644
--- a/utils/api/streaming.go
+++ b/utils/api/streaming/streaming.go
@@ -1,10 +1,9 @@
-package api
+package streaming
import (
"encoding/json"
"fmt"
"maps"
- "metachan/types"
"metachan/utils/mappers"
"net/http"
"net/url"
@@ -18,21 +17,6 @@ const (
allanimeBaseURL = "https://api.allanime.day/api"
)
-// AllAnimeClient provides methods for interacting with the AllAnime API
-type AllAnimeClient struct {
- client *http.Client
- headers http.Header
-}
-
-// StreamingSearchResult represents a search result from AllAnime
-type StreamingSearchResult struct {
- ID string `json:"_id"`
- Name string `json:"name"`
- SubEpisodes int `json:"sub_episodes"`
- DubEpisodes int `json:"dub_episodes"`
- Similarity float64 `json:"similarity"`
-}
-
// NewAllAnimeClient creates a new AllAnime client
func NewAllAnimeClient() *AllAnimeClient {
headers := http.Header{
@@ -163,7 +147,7 @@ func (c *AllAnimeClient) getClockLink(urlStr string) (string, error) {
}
// processSourceURL processes a streaming source URL from AllAnime
-func (c *AllAnimeClient) processSourceURL(sourceURL, sourceType string) *types.AnimeStreamingSource {
+func (c *AllAnimeClient) processSourceURL(sourceURL, sourceType string) *AnimeStreamingSource {
var decodedURL string
if strings.HasPrefix(sourceURL, "--") {
decodedURL = c.decodeURL(sourceURL)
@@ -176,7 +160,7 @@ func (c *AllAnimeClient) processSourceURL(sourceURL, sourceType string) *types.A
// Check if it's a clock link
if strings.Contains(processedURL, "/apivtwo/clock") {
if directURL, err := c.getClockLink(processedURL); err == nil {
- return &types.AnimeStreamingSource{
+ return &AnimeStreamingSource{
URL: directURL,
Server: getServerName(sourceType),
Type: "direct",
@@ -188,7 +172,7 @@ func (c *AllAnimeClient) processSourceURL(sourceURL, sourceType string) *types.A
directPatterns := []string{"fast4speed.rsvp", "sharepoint.com", ".m3u8", ".mp4"}
for _, pattern := range directPatterns {
if strings.Contains(processedURL, pattern) {
- return &types.AnimeStreamingSource{
+ return &AnimeStreamingSource{
URL: processedURL,
Server: getServerName(sourceType),
Type: "direct",
@@ -197,7 +181,7 @@ func (c *AllAnimeClient) processSourceURL(sourceURL, sourceType string) *types.A
}
// Return as regular source if not direct
- return &types.AnimeStreamingSource{
+ return &AnimeStreamingSource{
URL: processedURL,
Server: getServerName(sourceType),
Type: "embed",
@@ -378,7 +362,7 @@ func (c *AllAnimeClient) GetEpisodesList(showID string, mode string) ([]string,
}
// GetEpisodeLinks gets streaming links for a specific episode
-func (c *AllAnimeClient) GetEpisodeLinks(showID, episode, mode string) ([]types.AnimeStreamingSource, error) {
+func (c *AllAnimeClient) GetEpisodeLinks(showID, episode, mode string) ([]AnimeStreamingSource, error) {
episodeQuery := `
query ($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) {
episode(
@@ -424,7 +408,7 @@ func (c *AllAnimeClient) GetEpisodeLinks(showID, episode, mode string) ([]types.
episodeData := data["data"].(map[string]any)["episode"].(map[string]any)
sourceUrls := episodeData["sourceUrls"].([]any)
- var links []types.AnimeStreamingSource
+ var links []AnimeStreamingSource
for _, source := range sourceUrls {
sourceMap := source.(map[string]any)
if sourceURL, ok := sourceMap["sourceUrl"].(string); ok {
@@ -442,7 +426,7 @@ func (c *AllAnimeClient) GetEpisodeLinks(showID, episode, mode string) ([]types.
}
// GetStreamingSources fetches both sub and dub streaming sources for an anime episode
-func (c *AllAnimeClient) GetStreamingSources(title string, episodeNumber int) (*types.AnimeStreaming, error) {
+func (c *AllAnimeClient) GetStreamingSources(title string, episodeNumber int) (*AnimeStreaming, error) {
// Search for the anime
searchResults, err := c.SearchAnime(title)
if err != nil {
@@ -456,9 +440,9 @@ func (c *AllAnimeClient) GetStreamingSources(title string, episodeNumber int) (*
// Use the best match (first result)
bestMatch := searchResults[0]
- streaming := &types.AnimeStreaming{
- Sub: []types.AnimeStreamingSource{},
- Dub: []types.AnimeStreamingSource{},
+ streaming := &AnimeStreaming{
+ Sub: []AnimeStreamingSource{},
+ Dub: []AnimeStreamingSource{},
}
// Get sub episodes if available
diff --git a/utils/api/streaming/types.go b/utils/api/streaming/types.go
new file mode 100644
index 0000000..1b12da0
--- /dev/null
+++ b/utils/api/streaming/types.go
@@ -0,0 +1,38 @@
+package streaming
+
+import "net/http"
+
+// AllAnimeClient provides methods for interacting with the AllAnime API
+type AllAnimeClient struct {
+ client *http.Client
+ headers http.Header
+}
+
+// AnimeStreamingSource represents a single streaming source for an episode
+type AnimeStreamingSource struct {
+ URL string `json:"url"`
+ Server string `json:"server"`
+ Type string `json:"type"` // direct or embed
+}
+
+// AnimeStreaming represents all available streaming sources for an episode
+type AnimeStreaming struct {
+ Sub []AnimeStreamingSource `json:"sub"`
+ Dub []AnimeStreamingSource `json:"dub"`
+}
+
+// StreamingSearchResult represents a search result from streaming providers
+type StreamingSearchResult struct {
+ ID string `json:"_id"`
+ Name string `json:"name"`
+ SubEpisodes int `json:"sub_episodes"`
+ DubEpisodes int `json:"dub_episodes"`
+ Similarity float64 `json:"similarity"`
+}
+
+// EpisodeStreamingResult contains streaming sources for a specific episode
+// Used for parallel streaming source fetching
+type EpisodeStreamingResult struct {
+ EpisodeNumber int
+ Streaming *AnimeStreaming
+}
diff --git a/utils/api/tmdb.go b/utils/api/tmdb/tmdb.go
index 1344080..1c5a87b 100644
--- a/utils/api/tmdb.go
+++ b/utils/api/tmdb/tmdb.go
@@ -1,4 +1,4 @@
-package api
+package tmdb
import (
"encoding/json"
@@ -31,8 +31,8 @@ func makeRequestWithRetries(req *http.Request, maxRetries int) (*http.Response,
sleepTime := backoffTime + jitter
logger.Log(fmt.Sprintf("TMDB request retry %d/%d after %v due to: %v",
- attempt, maxRetries, sleepTime, lastErr), types.LogOptions{
- Level: types.Debug,
+ attempt, maxRetries, sleepTime, lastErr), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "TMDB",
})
@@ -120,7 +120,7 @@ func normalizeTitle(title string) string {
}
// searchTVShowsByTitle searches for TV shows on TMDB by title
-func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, countryPriority string) ([]types.TMDBShowResult, error) {
+func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, countryPriority string) ([]TMDBShowResult, error) {
if config.Config.TMDB.ReadAccessToken == "" {
return nil, fmt.Errorf("TMDB is not initialized")
}
@@ -131,8 +131,8 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c
query = normalizeTitle(alternativeTitle)
}
- logger.Log(fmt.Sprintf("Searching TMDB for TV show: %s", query), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Searching TMDB for TV show: %s", query), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "TMDB",
})
@@ -163,7 +163,7 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c
}
// Parse response
- var searchResponse types.TMDBSearchResponse
+ var searchResponse TMDBSearchResponse
if err := json.NewDecoder(resp.Body).Decode(&searchResponse); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
@@ -171,7 +171,7 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c
results := searchResponse.Results
// Filter results if needed
- var filteredResults []types.TMDBShowResult
+ var filteredResults []TMDBShowResult
for _, show := range results {
if (isAdult && show.Adult) || (!isAdult && !show.Adult) {
filteredResults = append(filteredResults, show)
@@ -180,8 +180,8 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c
// Sort by country priority if specified
if countryPriority != "" && len(filteredResults) > 0 {
- var prioritizedResults []types.TMDBShowResult
- var otherResults []types.TMDBShowResult
+ var prioritizedResults []TMDBShowResult
+ var otherResults []TMDBShowResult
for _, show := range filteredResults {
hasPriority := false
@@ -204,13 +204,13 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c
}
if len(filteredResults) == 0 {
- logger.Log(fmt.Sprintf("No TMDB shows found for: %s", query), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("No TMDB shows found for: %s", query), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
} else {
- logger.Log(fmt.Sprintf("Found %d TMDB shows for: %s", len(filteredResults), query), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Found %d TMDB shows for: %s", len(filteredResults), query), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "TMDB",
})
}
@@ -219,7 +219,7 @@ func searchTVShowsByTitle(title string, alternativeTitle string, isAdult bool, c
}
// getTVShowDetails gets details for a TV show from TMDB
-func getTVShowDetails(showID int) (*types.TMDBShowDetails, error) {
+func getTVShowDetails(showID int) (*TMDBShowDetails, error) {
if config.Config.TMDB.ReadAccessToken == "" {
return nil, fmt.Errorf("TMDB is not initialized")
}
@@ -246,7 +246,7 @@ func getTVShowDetails(showID int) (*types.TMDBShowDetails, error) {
}
// Parse response
- var showDetails types.TMDBShowDetails
+ var showDetails TMDBShowDetails
if err := json.NewDecoder(resp.Body).Decode(&showDetails); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
@@ -255,7 +255,7 @@ func getTVShowDetails(showID int) (*types.TMDBShowDetails, error) {
}
// getSeasonDetails gets details for a TV season from TMDB
-func getSeasonDetails(showID, seasonNumber int) (*types.TMDBSeasonDetails, error) {
+func getSeasonDetails(showID, seasonNumber int) (*TMDBSeasonDetails, error) {
if config.Config.TMDB.ReadAccessToken == "" {
return nil, fmt.Errorf("TMDB is not initialized")
}
@@ -282,7 +282,7 @@ func getSeasonDetails(showID, seasonNumber int) (*types.TMDBSeasonDetails, error
}
// Parse response
- var seasonDetails types.TMDBSeasonDetails
+ var seasonDetails TMDBSeasonDetails
if err := json.NewDecoder(resp.Body).Decode(&seasonDetails); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
@@ -291,12 +291,12 @@ func getSeasonDetails(showID, seasonNumber int) (*types.TMDBSeasonDetails, error
}
// findBestSeason finds the best matching season for an anime
-func findBestSeason(shows []types.TMDBShowResult, title string, episodeCount int, airDate string) (int, int, error) {
+func findBestSeason(shows []TMDBShowResult, title string, episodeCount int, airDate string) (int, int, error) {
for _, show := range shows {
showDetails, err := getTVShowDetails(show.ID)
if err != nil {
- logger.Log(fmt.Sprintf("Failed to get details for show %d: %v", show.ID, err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to get details for show %d: %v", show.ID, err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
continue
@@ -325,8 +325,8 @@ func findBestSeason(shows []types.TMDBShowResult, title string, episodeCount int
// If either count or air date matches, consider it a potential match
if episodeCountMatches || airDateMatches {
logger.Log(fmt.Sprintf("Found matching season for \"%s\": Show ID %d, Season %d",
- title, show.ID, season.SeasonNumber), types.LogOptions{
- Level: types.Info,
+ title, show.ID, season.SeasonNumber), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TMDB",
})
return show.ID, season.SeasonNumber, nil
@@ -340,8 +340,8 @@ func findBestSeason(shows []types.TMDBShowResult, title string, episodeCount int
// AttachEpisodeDescriptions enriches anime episodes with descriptions and thumbnails from TMDB
func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode, alternativeTitle string, tmdbID int) []types.AnimeSingleEpisode {
if config.Config.TMDB.ReadAccessToken == "" {
- logger.Log("TMDB is not configured, skipping episode description enrichment", types.LogOptions{
- Level: types.Warn,
+ logger.Log("TMDB is not configured, skipping episode description enrichment", logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
return episodes
@@ -351,8 +351,8 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode
return episodes
}
- logger.Log(fmt.Sprintf("Enriching episodes for: %s", title), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Enriching episodes for: %s", title), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TMDB",
})
@@ -367,8 +367,8 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode
// Try to get show details and find the best season
showDetails, err := getTVShowDetails(showID)
if err != nil {
- logger.Log(fmt.Sprintf("Failed to get TMDB show details for ID %d: %v", tmdbID, err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to get TMDB show details for ID %d: %v", tmdbID, err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
return episodes
@@ -405,24 +405,24 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode
}
}
- logger.Log(fmt.Sprintf("Using TMDB ID %d with season %d", showID, seasonNumber), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Using TMDB ID %d with season %d", showID, seasonNumber), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TMDB",
})
} else {
// Search for the TV show on TMDB if we don't have a direct ID
shows, err := searchTVShowsByTitle(title, alternativeTitle, false, "JP")
if err != nil {
- logger.Log(fmt.Sprintf("Failed to search TV shows: %v", err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to search TV shows: %v", err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
return episodes
}
if len(shows) == 0 {
- logger.Log(fmt.Sprintf("No TV shows found for: %s", title), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("No TV shows found for: %s", title), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
return episodes
@@ -436,8 +436,8 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode
showID, seasonNumber, err = findBestSeason(shows, title, len(episodes), airDate)
if err != nil {
- logger.Log(fmt.Sprintf("Failed to find best season: %v", err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to find best season: %v", err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
return episodes
@@ -447,8 +447,8 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode
// Get season details with episode information
seasonDetails, err := getSeasonDetails(showID, seasonNumber)
if err != nil {
- logger.Log(fmt.Sprintf("Failed to get season details: %v", err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to get season details: %v", err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TMDB",
})
return episodes
@@ -489,8 +489,8 @@ func AttachEpisodeDescriptions(title string, episodes []types.AnimeSingleEpisode
}
logger.Log(fmt.Sprintf("Successfully enriched %d episodes with descriptions and %d with thumbnails for: %s",
- len(enrichedEpisodes), thumbnailCount, title), types.LogOptions{
- Level: types.Success,
+ len(enrichedEpisodes), thumbnailCount, title), logger.LogOptions{
+ Level: logger.Success,
Prefix: "TMDB",
})
diff --git a/utils/api/tmdb/types.go b/utils/api/tmdb/types.go
new file mode 100644
index 0000000..8743573
--- /dev/null
+++ b/utils/api/tmdb/types.go
@@ -0,0 +1,54 @@
+package tmdb
+
+// TMDBShowResult represents a TV show result from TMDB search
+type TMDBShowResult struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ FirstAirDate string `json:"first_air_date"`
+ OriginCountry []string `json:"origin_country"`
+ Adult bool `json:"adult"`
+}
+
+// TMDBSearchResponse represents the response from TMDB search API
+type TMDBSearchResponse struct {
+ Page int `json:"page"`
+ Results []TMDBShowResult `json:"results"`
+ TotalPages int `json:"total_pages"`
+ TotalResults int `json:"total_results"`
+}
+
+// TMDBEpisode represents a TV episode from TMDB
+type TMDBEpisode struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Overview string `json:"overview"`
+ StillPath string `json:"still_path"`
+ AirDate string `json:"air_date"`
+ EpisodeNumber int `json:"episode_number"`
+ SeasonNumber int `json:"season_number"`
+}
+
+// TMDBSeasonDetails represents a TV season from TMDB
+type TMDBSeasonDetails struct {
+ ID int `json:"id"`
+ AirDate string `json:"air_date"`
+ EpisodeCount int `json:"episode_count"`
+ Name string `json:"name"`
+ Overview string `json:"overview"`
+ SeasonNumber int `json:"season_number"`
+ Episodes []TMDBEpisode `json:"episodes"`
+}
+
+// TMDBShowDetails represents a TV show from TMDB
+type TMDBShowDetails struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Overview string `json:"overview"`
+ Seasons []struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ SeasonNumber int `json:"season_number"`
+ EpisodeCount int `json:"episode_count"`
+ AirDate string `json:"air_date"`
+ } `json:"seasons"`
+}
diff --git a/utils/api/tvdb.go b/utils/api/tvdb/tvdb.go
index cee3de2..230be54 100644
--- a/utils/api/tvdb.go
+++ b/utils/api/tvdb/tvdb.go
@@ -4,14 +4,13 @@ import (
"fmt"
"metachan/database"
"metachan/entities"
- "metachan/types"
"metachan/utils/logger"
)
// 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), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Finding season mappings for TVDB ID %d", tvdbID), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "TVDB",
})
@@ -22,13 +21,13 @@ func FindSeasonMappings(tvdbID int) ([]entities.AnimeMapping, error) {
}
if len(mappings) == 0 {
- logger.Log(fmt.Sprintf("No season mappings found for TVDB ID %d", tvdbID), types.LogOptions{
- Level: types.Debug,
+ 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), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Found %d season mappings for TVDB ID %d", len(mappings), tvdbID), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TVDB",
})
}