aboutsummaryrefslogtreecommitdiff
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
parent9ebde192fbde6d6d6be2d7d86485eca9a0c5e026 (diff)
downloadmetachan-cb41c834529f696c2f83adbb41e6b592c89495f3.tar.xz
metachan-cb41c834529f696c2f83adbb41e6b592c89495f3.zip
refactored types
-rw-r--r--config/config.go6
-rw-r--r--controllers/anime.go5
-rw-r--r--database/database.go12
-rw-r--r--database/migrate.go9
-rw-r--r--database/status.go9
-rw-r--r--metachan/main.go9
-rw-r--r--middleware/logger.go41
-rw-r--r--services/anime/helpers.go132
-rw-r--r--services/anime/service.go119
-rw-r--r--tasks/anisync.task.go36
-rw-r--r--tasks/manager.go60
-rw-r--r--tasks/tasks.go4
-rw-r--r--types/anime.go541
-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.go (renamed from types/tmdb.go)2
-rw-r--r--utils/api/tvdb/tvdb.go (renamed from utils/api/tvdb.go)13
-rw-r--r--utils/logger/logger.go64
-rw-r--r--utils/logger/types.go (renamed from types/logger.go)2
28 files changed, 912 insertions, 850 deletions
diff --git a/config/config.go b/config/config.go
index 63b1e39..242c39b 100644
--- a/config/config.go
+++ b/config/config.go
@@ -13,9 +13,9 @@ import (
var Config *types.ServerConfig
func init() {
- logOptions := types.LogOptions{
+ logOptions := logger.LogOptions{
Prefix: "Config",
- Level: types.Error,
+ Level: logger.Error,
Fatal: true,
}
@@ -55,7 +55,7 @@ func init() {
logger.Log("Invalid TMDB read access token or TMDB read access token not set", logOptions)
}
- logOptions.Level = types.Success
+ logOptions.Level = logger.Success
logOptions.Fatal = false
logger.Log("Config initialized successfully", logOptions)
}
diff --git a/controllers/anime.go b/controllers/anime.go
index 669f415..7c01768 100644
--- a/controllers/anime.go
+++ b/controllers/anime.go
@@ -3,7 +3,6 @@ package controllers
import (
"metachan/database"
animeService "metachan/services/anime"
- "metachan/types"
"metachan/utils/logger"
"metachan/utils/mappers"
@@ -40,8 +39,8 @@ func GetAnimeByMALID(c *fiber.Ctx) error {
service := getAnimeService()
anime, err := service.GetAnimeDetails(mapping)
if err != nil {
- logger.Log("Failed to fetch anime details: "+err.Error(), types.LogOptions{
- Level: types.Error,
+ logger.Log("Failed to fetch anime details: "+err.Error(), logger.LogOptions{
+ Level: logger.Error,
Prefix: "AnimeAPI",
})
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
diff --git a/database/database.go b/database/database.go
index 89bbddc..382cb1d 100644
--- a/database/database.go
+++ b/database/database.go
@@ -29,9 +29,9 @@ func init() {
case types.SQLServer:
dialector = sqlserver.Open(config.Config.DataSourceName)
default:
- logger.Log(fmt.Sprintf("Invalid database driver: %s", config.Config.DatabaseDriver), types.LogOptions{
+ logger.Log(fmt.Sprintf("Invalid database driver: %s", config.Config.DatabaseDriver), logger.LogOptions{
Prefix: "Database",
- Level: types.Error,
+ Level: logger.Error,
Fatal: true,
})
}
@@ -41,15 +41,15 @@ func init() {
Logger: gormlogger.Default.LogMode(gormlogger.Silent),
})
if err != nil {
- logger.Log(fmt.Sprintf("Error connecting to database: %v", err), types.LogOptions{
+ logger.Log(fmt.Sprintf("Error connecting to database: %v", err), logger.LogOptions{
Prefix: "Database",
- Level: types.Error,
+ Level: logger.Error,
Fatal: true,
})
} else {
- logger.Log("Database connection established successfully", types.LogOptions{
+ logger.Log("Database connection established successfully", logger.LogOptions{
Prefix: "Database",
- Level: types.Success,
+ Level: logger.Success,
})
}
}
diff --git a/database/migrate.go b/database/migrate.go
index 0f218b3..a20a211 100644
--- a/database/migrate.go
+++ b/database/migrate.go
@@ -3,7 +3,6 @@ package database
import (
"fmt"
"metachan/entities"
- "metachan/types"
"metachan/utils/logger"
)
@@ -13,15 +12,15 @@ func AutoMigrate() {
&entities.AnimeMapping{},
)
if err != nil {
- logger.Log(fmt.Sprintf("Error during auto migration: %v", err), types.LogOptions{
+ logger.Log(fmt.Sprintf("Error during auto migration: %v", err), logger.LogOptions{
Prefix: "Database",
- Level: types.Error,
+ Level: logger.Error,
Fatal: true,
})
} else {
- logger.Log("Auto migration completed successfully", types.LogOptions{
+ logger.Log("Auto migration completed successfully", logger.LogOptions{
Prefix: "Database",
- Level: types.Success,
+ Level: logger.Success,
})
}
}
diff --git a/database/status.go b/database/status.go
index 14f24ed..48b94d5 100644
--- a/database/status.go
+++ b/database/status.go
@@ -3,7 +3,6 @@ package database
import (
"context"
"fmt"
- "metachan/types"
"metachan/utils/logger"
"time"
)
@@ -15,9 +14,9 @@ func DatabaseConnectionStatus() bool {
sqlDB, err := DB.DB()
if err != nil {
- logger.Log(fmt.Sprintf("Unable to get SQL DB: %v", err), types.LogOptions{
+ logger.Log(fmt.Sprintf("Unable to get SQL DB: %v", err), logger.LogOptions{
Prefix: "Database",
- Level: types.Error,
+ Level: logger.Error,
})
return false
}
@@ -27,9 +26,9 @@ func DatabaseConnectionStatus() bool {
err = sqlDB.PingContext(ctx)
if err != nil {
- logger.Log(fmt.Sprintf("Database connection error: %v", err), types.LogOptions{
+ logger.Log(fmt.Sprintf("Database connection error: %v", err), logger.LogOptions{
Prefix: "Database",
- Level: types.Error,
+ Level: logger.Error,
})
return false
}
diff --git a/metachan/main.go b/metachan/main.go
index d0fb826..9d57ce7 100644
--- a/metachan/main.go
+++ b/metachan/main.go
@@ -7,7 +7,6 @@ import (
"metachan/middleware"
"metachan/router"
"metachan/tasks"
- "metachan/types"
"metachan/utils/logger"
"github.com/gofiber/fiber/v2"
@@ -36,15 +35,15 @@ func main() {
// Start the server
if err := app.Listen(fmt.Sprintf(":%d", config.Config.Port)); err != nil {
- logger.Log(fmt.Sprintf("Failed to the start the server on port %d: %v", config.Config.Port, err), types.LogOptions{
+ logger.Log(fmt.Sprintf("Failed to the start the server on port %d: %v", config.Config.Port, err), logger.LogOptions{
Prefix: "Main",
- Level: types.Error,
+ Level: logger.Error,
Fatal: true,
})
}
- logger.Log(fmt.Sprintf("Server started on port %d", config.Config.Port), types.LogOptions{
+ logger.Log(fmt.Sprintf("Server started on port %d", config.Config.Port), logger.LogOptions{
Prefix: "Main",
- Level: types.Success,
+ Level: logger.Success,
})
}
diff --git a/middleware/logger.go b/middleware/logger.go
index 08a533e..8d2b361 100644
--- a/middleware/logger.go
+++ b/middleware/logger.go
@@ -2,7 +2,6 @@ package middleware
import (
"fmt"
- "metachan/types"
"metachan/utils/logger"
"strconv"
"strings"
@@ -35,13 +34,13 @@ func HTTPLogger() fiber.Handler {
// Format with consistent spacing and alignment
message := fmt.Sprintf("%s %s%-3d%s %-15s %-10s %-s",
paddedMethod,
- messageColor, status, types.Reset,
+ messageColor, status, logger.Reset,
"IP: "+ip,
"TTR: "+formatDuration(duration),
"Path: "+path,
)
- logger.Log(message, types.LogOptions{
+ logger.Log(message, logger.LogOptions{
Prefix: "HTTP",
Level: level,
})
@@ -50,35 +49,35 @@ func HTTPLogger() fiber.Handler {
}
}
-func getLogLevel(status int) types.LogLevel {
+func getLogLevel(status int) logger.LogLevel {
switch {
case status >= 500:
- return types.Error
+ return logger.Error
case status >= 400:
- return types.Warn
+ return logger.Warn
case status >= 300:
- return types.Info
+ return logger.Info
case status >= 200:
- return types.Success
+ return logger.Success
default:
- return types.Info
+ return logger.Info
}
}
-func getMessageColor(level types.LogLevel) string {
+func getMessageColor(level logger.LogLevel) string {
switch level {
- case types.Info:
- return types.MessageColorInfo
- case types.Warn:
- return types.MessageColorWarn
- case types.Error:
- return types.MessageColorError
- case types.Debug:
- return types.MessageColorDebug
- case types.Success:
- return types.MessageColorSuccess
+ case logger.Info:
+ return logger.MessageColorInfo
+ case logger.Warn:
+ return logger.MessageColorWarn
+ case logger.Error:
+ return logger.MessageColorError
+ case logger.Debug:
+ return logger.MessageColorDebug
+ case logger.Success:
+ return logger.MessageColorSuccess
default:
- return types.MessageColorInfo
+ return logger.MessageColorInfo
}
}
diff --git a/services/anime/helpers.go b/services/anime/helpers.go
index d5e9a67..fb8e82b 100644
--- a/services/anime/helpers.go
+++ b/services/anime/helpers.go
@@ -4,7 +4,11 @@ import (
"crypto/tls"
"fmt"
"metachan/types"
- "metachan/utils/api"
+ "metachan/utils/api/anilist"
+ "metachan/utils/api/jikan"
+ "metachan/utils/api/malsync"
+ "metachan/utils/api/tmdb"
+ api "metachan/utils/api/tvdb"
"metachan/utils/logger"
"net/http"
"strings"
@@ -12,7 +16,7 @@ import (
)
// generateBasicEpisodes creates a basic list of episode data from Jikan episodes
-func generateBasicEpisodes(episodes []types.JikanAnimeEpisode) []types.AnimeSingleEpisode {
+func generateBasicEpisodes(episodes []jikan.JikanAnimeEpisode) []types.AnimeSingleEpisode {
var animeEpisodes []types.AnimeSingleEpisode
for _, episode := range episodes {
@@ -37,7 +41,7 @@ func generateBasicEpisodes(episodes []types.JikanAnimeEpisode) []types.AnimeSing
}
// getEpisodeCount determines the highest episode count from different sources
-func getEpisodeCount(malAnime *types.JikanAnimeResponse, anilistAnime *types.AnilistAnimeResponse) int {
+func getEpisodeCount(malAnime *jikan.JikanAnimeResponse, anilistAnime *anilist.AnilistAnimeResponse) int {
if anilistAnime == nil {
return malAnime.Data.Episodes
}
@@ -97,7 +101,7 @@ func sortSeasonsByAirDate(seasons *[]types.AnimeSeason) {
}
// generateGenres converts Jikan genre structures to our format
-func generateGenres(genres, explicitGenres []types.JikanGenericMALStructure) []types.AnimeGenres {
+func generateGenres(genres, explicitGenres []jikan.JikanGenericMALStructure) []types.AnimeGenres {
var animeGenres []types.AnimeGenres
// Add regular genres
@@ -122,7 +126,7 @@ func generateGenres(genres, explicitGenres []types.JikanGenericMALStructure) []t
}
// generateStudios converts Jikan studio structures to our format
-func generateStudios(studios []types.JikanGenericMALStructure) []types.AnimeStudio {
+func generateStudios(studios []jikan.JikanGenericMALStructure) []types.AnimeStudio {
var animeStudios []types.AnimeStudio
for _, studio := range studios {
@@ -137,7 +141,7 @@ func generateStudios(studios []types.JikanGenericMALStructure) []types.AnimeStud
}
// generateProducers converts Jikan producer structures to our format
-func generateProducers(producers []types.JikanGenericMALStructure) []types.AnimeProducer {
+func generateProducers(producers []jikan.JikanGenericMALStructure) []types.AnimeProducer {
var animeProducers []types.AnimeProducer
for _, producer := range producers {
@@ -152,7 +156,7 @@ func generateProducers(producers []types.JikanGenericMALStructure) []types.Anime
}
// generateLicensors converts Jikan licensor structures to our format
-func generateLicensors(licensors []types.JikanGenericMALStructure) []types.AnimeLicensor {
+func generateLicensors(licensors []jikan.JikanGenericMALStructure) []types.AnimeLicensor {
var animeLicensors []types.AnimeLicensor
for _, licensor := range licensors {
@@ -167,7 +171,7 @@ func generateLicensors(licensors []types.JikanGenericMALStructure) []types.Anime
}
// getAnimeCharacters processes character data from Jikan
-func getAnimeCharacters(characterResponse *types.JikanAnimeCharacterResponse) []types.AnimeCharacter {
+func getAnimeCharacters(characterResponse *jikan.JikanAnimeCharacterResponse) []types.AnimeCharacter {
var characters []types.AnimeCharacter
for _, entry := range characterResponse.Data {
@@ -196,10 +200,10 @@ func getAnimeCharacters(characterResponse *types.JikanAnimeCharacterResponse) []
}
// getNextAiringEpisode extracts next airing episode data from AniList
-func getNextAiringEpisode(anilistAnime *types.AnilistAnimeResponse) types.AnimeAiringEpisode {
+func getNextAiringEpisode(anilistAnime *anilist.AnilistAnimeResponse) types.AnimeAiringEpisode {
if anilistAnime == nil || anilistAnime.Data.Media.ID == 0 {
- logger.Log("No valid AniList data for next airing episode", types.LogOptions{
- Level: types.Debug,
+ logger.Log("No valid AniList data for next airing episode", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return types.AnimeAiringEpisode{}
@@ -210,16 +214,16 @@ func getNextAiringEpisode(anilistAnime *types.AnilistAnimeResponse) types.AnimeA
// Check if there is valid data
if nextEpisode.AiringAt == 0 && nextEpisode.Episode == 0 {
- logger.Log(fmt.Sprintf("Anime ID %d has no next airing episode", anilistAnime.Data.Media.ID), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Anime ID %d has no next airing episode", anilistAnime.Data.Media.ID), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return types.AnimeAiringEpisode{}
}
logger.Log(fmt.Sprintf("Found next airing episode %d at timestamp %d (in %d seconds)",
- nextEpisode.Episode, nextEpisode.AiringAt, nextEpisode.TimeUntilAiring), types.LogOptions{
- Level: types.Debug,
+ nextEpisode.Episode, nextEpisode.AiringAt, nextEpisode.TimeUntilAiring), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -231,7 +235,7 @@ func getNextAiringEpisode(anilistAnime *types.AnilistAnimeResponse) types.AnimeA
}
// getAnimeSchedule extracts airing schedule data from AniList
-func getAnimeSchedule(anilistAnime *types.AnilistAnimeResponse) []types.AnimeAiringEpisode {
+func getAnimeSchedule(anilistAnime *anilist.AnilistAnimeResponse) []types.AnimeAiringEpisode {
if anilistAnime == nil {
return []types.AnimeAiringEpisode{}
}
@@ -240,8 +244,8 @@ func getAnimeSchedule(anilistAnime *types.AnilistAnimeResponse) []types.AnimeAir
// The nodes might be nil if there's no schedule
if anilistAnime.Data.Media.AiringSchedule.Nodes == nil {
- logger.Log("No airing schedule found in AniList data", types.LogOptions{
- Level: types.Debug,
+ logger.Log("No airing schedule found in AniList data", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return []types.AnimeAiringEpisode{}
@@ -255,8 +259,8 @@ func getAnimeSchedule(anilistAnime *types.AnilistAnimeResponse) []types.AnimeAir
})
}
- logger.Log(fmt.Sprintf("Found %d episodes in airing schedule", len(schedule)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Found %d episodes in airing schedule", len(schedule)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -265,10 +269,10 @@ func getAnimeSchedule(anilistAnime *types.AnilistAnimeResponse) []types.AnimeAir
// AttachEpisodeDescriptions enhances episode information with external data
// Imports the function from the anime utils to use in our service
-var AttachEpisodeDescriptions = api.AttachEpisodeDescriptions
+var AttachEpisodeDescriptions = tmdb.AttachEpisodeDescriptions
// extractLogosFromMALSync extracts logo images from MALSync data
-func extractLogosFromMALSync(malSyncData *types.MALSyncAnimeResponse) types.AnimeLogos {
+func extractLogosFromMALSync(malSyncData *malsync.MALSyncAnimeResponse) types.AnimeLogos {
logos := types.AnimeLogos{}
// Early return if no data
@@ -279,8 +283,8 @@ func extractLogosFromMALSync(malSyncData *types.MALSyncAnimeResponse) types.Anim
// Check if Crunchyroll data exists in the MALSync response
crunchyrollSites, exists := malSyncData.Sites["Crunchyroll"]
if !exists || len(crunchyrollSites) == 0 {
- logger.Log("No Crunchyroll data found in MALSync response", types.LogOptions{
- Level: types.Debug,
+ logger.Log("No Crunchyroll data found in MALSync response", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return logos
@@ -294,8 +298,8 @@ func extractLogosFromMALSync(malSyncData *types.MALSyncAnimeResponse) types.Anim
}
if crURL == "" {
- logger.Log("No valid Crunchyroll URL found", types.LogOptions{
- Level: types.Debug,
+ logger.Log("No valid Crunchyroll URL found", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return logos
@@ -323,8 +327,8 @@ func extractLogosFromMALSync(malSyncData *types.MALSyncAnimeResponse) types.Anim
logos.XLarge = fmt.Sprintf("https://imgsrv.crunchyroll.com/cdn-cgi/image/fit=contain,format=auto,quality=85,width=%d/keyart/%s-title_logo-en-us", logoSizes["XLarge"], seriesID)
logos.Original = fmt.Sprintf("https://imgsrv.crunchyroll.com/cdn-cgi/image/fit=contain,format=auto,quality=85,width=%d/keyart/%s-title_logo-en-us", logoSizes["Original"], seriesID)
- logger.Log(fmt.Sprintf("Successfully generated logo URLs for series ID: %s", seriesID), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Successfully generated logo URLs for series ID: %s", seriesID), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -333,8 +337,8 @@ func extractLogosFromMALSync(malSyncData *types.MALSyncAnimeResponse) types.Anim
// extractCrunchyrollSeriesID extracts the series ID from a Crunchyroll URL
func extractCrunchyrollSeriesID(crURL string) string {
- logger.Log(fmt.Sprintf("Attempting to extract series ID from URL: %s", crURL), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Attempting to extract series ID from URL: %s", crURL), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -342,8 +346,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
if strings.Contains(crURL, "/series/") {
parts := strings.Split(crURL, "/series/")
if len(parts) < 2 {
- logger.Log("URL contains /series/ but couldn't extract ID part", types.LogOptions{
- Level: types.Debug,
+ logger.Log("URL contains /series/ but couldn't extract ID part", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return ""
@@ -351,23 +355,23 @@ func extractCrunchyrollSeriesID(crURL string) string {
idParts := strings.Split(parts[1], "/")
if len(idParts) < 1 {
- logger.Log("Couldn't extract ID from path segments", types.LogOptions{
- Level: types.Debug,
+ logger.Log("Couldn't extract ID from path segments", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return ""
}
- logger.Log(fmt.Sprintf("Found series ID directly in URL: %s", idParts[0]), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Found series ID directly in URL: %s", idParts[0]), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return idParts[0]
}
// Need to follow redirect to get series ID
- logger.Log("URL doesn't contain /series/, following redirect...", types.LogOptions{
- Level: types.Debug,
+ logger.Log("URL doesn't contain /series/, following redirect...", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -391,8 +395,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
// Update HTTP to HTTPS for Crunchyroll URLs if needed
if strings.HasPrefix(crURL, "http://www.crunchyroll.com") {
crURL = strings.Replace(crURL, "http://", "https://", 1)
- logger.Log(fmt.Sprintf("Updated URL to HTTPS: %s", crURL), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Updated URL to HTTPS: %s", crURL), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
}
@@ -400,8 +404,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
// Add User-Agent header to mimic a browser
req, err := http.NewRequest("GET", crURL, nil)
if err != nil {
- logger.Log(fmt.Sprintf("Failed to create request for Crunchyroll URL: %v", err), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Failed to create request for Crunchyroll URL: %v", err), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return ""
@@ -412,8 +416,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
resp, err := client.Do(req)
if err != nil {
- logger.Log(fmt.Sprintf("Failed to get Crunchyroll redirect: %v", err), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Failed to get Crunchyroll redirect: %v", err), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return ""
@@ -421,14 +425,14 @@ func extractCrunchyrollSeriesID(crURL string) string {
defer resp.Body.Close()
// Log the status code and response headers for debugging
- logger.Log(fmt.Sprintf("Crunchyroll response status: %d %s", resp.StatusCode, resp.Status), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Crunchyroll response status: %d %s", resp.StatusCode, resp.Status), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
for name, values := range resp.Header {
- logger.Log(fmt.Sprintf("Header %s: %s", name, strings.Join(values, ", ")), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Header %s: %s", name, strings.Join(values, ", ")), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
}
@@ -438,8 +442,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
resp.StatusCode != http.StatusFound &&
resp.StatusCode != http.StatusTemporaryRedirect &&
resp.StatusCode != http.StatusPermanentRedirect {
- logger.Log(fmt.Sprintf("Unexpected status code from Crunchyroll: %d", resp.StatusCode), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Unexpected status code from Crunchyroll: %d", resp.StatusCode), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -451,8 +455,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
urlParts := strings.Split(crURL, "/")
if len(urlParts) > 0 {
potentialId := urlParts[len(urlParts)-1]
- logger.Log(fmt.Sprintf("Extracted potential series ID from original URL: %s", potentialId), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Extracted potential series ID from original URL: %s", potentialId), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return potentialId
@@ -463,15 +467,15 @@ func extractCrunchyrollSeriesID(crURL string) string {
redirectURL := resp.Header.Get("Location")
if redirectURL == "" {
- logger.Log("No redirect URL found in Crunchyroll response", types.LogOptions{
- Level: types.Debug,
+ logger.Log("No redirect URL found in Crunchyroll response", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return ""
}
- logger.Log(fmt.Sprintf("Found redirect URL: %s", redirectURL), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Found redirect URL: %s", redirectURL), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -487,8 +491,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
return ""
}
- logger.Log(fmt.Sprintf("Successfully extracted series ID from redirect: %s", idParts[0]), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Successfully extracted series ID from redirect: %s", idParts[0]), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return idParts[0]
@@ -496,8 +500,8 @@ func extractCrunchyrollSeriesID(crURL string) string {
// For multi-level redirects, try to follow one more time
if strings.Contains(redirectURL, "crunchyroll.com") {
- logger.Log("Trying to follow one more redirect level...", types.LogOptions{
- Level: types.Debug,
+ logger.Log("Trying to follow one more redirect level...", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return extractCrunchyrollSeriesID(redirectURL)
@@ -508,15 +512,15 @@ func extractCrunchyrollSeriesID(crURL string) string {
urlParts := strings.Split(crURL, "/")
if len(urlParts) > 0 {
potentialId := urlParts[len(urlParts)-1]
- logger.Log(fmt.Sprintf("Using fallback: extracted ID from original URL: %s", potentialId), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Using fallback: extracted ID from original URL: %s", potentialId), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return potentialId
}
- logger.Log("Could not extract series ID from Crunchyroll redirect URL", types.LogOptions{
- Level: types.Debug,
+ logger.Log("Could not extract series ID from Crunchyroll redirect URL", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
return ""
diff --git a/services/anime/service.go b/services/anime/service.go
index faaa5cf..63840cd 100644
--- a/services/anime/service.go
+++ b/services/anime/service.go
@@ -4,7 +4,10 @@ import (
"fmt"
"metachan/entities"
"metachan/types"
- "metachan/utils/api"
+ "metachan/utils/api/anilist"
+ "metachan/utils/api/jikan"
+ "metachan/utils/api/malsync"
+ "metachan/utils/api/streaming"
"metachan/utils/concurrency"
"metachan/utils/logger"
"strings"
@@ -13,19 +16,19 @@ import (
// Service provides high-level operations for anime data
type Service struct {
- jikanClient *api.JikanClient
- streamingClient *api.AllAnimeClient
- anilistClient *api.AniListClient
- malsyncClient *api.MALSyncClient
+ jikanClient *jikan.JikanClient
+ streamingClient *streaming.AllAnimeClient
+ anilistClient *anilist.AniListClient
+ malsyncClient *malsync.MALSyncClient
}
// NewService creates a new anime service
func NewService() *Service {
return &Service{
- jikanClient: api.NewJikanClient(),
- streamingClient: api.NewAllAnimeClient(),
- anilistClient: api.NewAniListClient(),
- malsyncClient: api.NewMALSyncClient(),
+ jikanClient: jikan.NewJikanClient(),
+ streamingClient: streaming.NewAllAnimeClient(),
+ anilistClient: anilist.NewAniListClient(),
+ malsyncClient: malsync.NewMALSyncClient(),
}
}
@@ -34,8 +37,8 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
startTime := time.Now()
defer func() {
duration := time.Since(startTime)
- logger.Log(fmt.Sprintf("GetAnimeDetails total execution time: %s", duration), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("GetAnimeDetails total execution time: %s", duration), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
}()
@@ -43,15 +46,15 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
malID := mapping.MAL
// Create the different types of functions for proper Go generic type inference
- animeFunc := func() (*types.JikanAnimeResponse, error) {
+ animeFunc := func() (*jikan.JikanAnimeResponse, error) {
return s.jikanClient.GetFullAnime(malID)
}
- episodesFunc := func() (*types.JikanAnimeEpisodeResponse, error) {
+ episodesFunc := func() (*jikan.JikanAnimeEpisodeResponse, error) {
return s.jikanClient.GetAnimeEpisodes(malID)
}
- charactersFunc := func() (*types.JikanAnimeCharacterResponse, error) {
+ charactersFunc := func() (*jikan.JikanAnimeCharacterResponse, error) {
return s.jikanClient.GetAnimeCharacters(malID)
}
@@ -60,8 +63,8 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
animeResult := concurrency.Parallel(animeFunc)[0]
episodesResult := concurrency.Parallel(episodesFunc)[0]
charactersResult := concurrency.Parallel(charactersFunc)[0]
- logger.Log(fmt.Sprintf("Initial parallel API fetch time: %s", time.Since(fetchStartTime)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Initial parallel API fetch time: %s", time.Since(fetchStartTime)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -82,17 +85,17 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
}
// Get Anilist and MALSync data in parallel if available
- var anilistAnime *types.AnilistAnimeResponse
- var malSyncData *types.MALSyncAnimeResponse
+ var anilistAnime *anilist.AnilistAnimeResponse
+ var malSyncData *malsync.MALSyncAnimeResponse
anilistStartTime := time.Now()
if mapping.Anilist != 0 {
// We need separate functions for each type for proper type inference
- anilistFunc := func() (*types.AnilistAnimeResponse, error) {
+ anilistFunc := func() (*anilist.AnilistAnimeResponse, error) {
return s.anilistClient.GetAnime(mapping.Anilist)
}
- malsyncFunc := func() (*types.MALSyncAnimeResponse, error) {
+ malsyncFunc := func() (*malsync.MALSyncAnimeResponse, error) {
return s.malsyncClient.GetAnimeByMALID(malID)
}
@@ -103,13 +106,13 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
// Extract AniList result
if anilistResult.Error == nil {
anilistAnime = anilistResult.Value
- logger.Log(fmt.Sprintf("Successfully fetched AniList data for ID %d", mapping.Anilist), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Successfully fetched AniList data for ID %d", mapping.Anilist), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
} else {
- logger.Log(fmt.Sprintf("Failed to fetch AniList data: %v", anilistResult.Error), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to fetch AniList data: %v", anilistResult.Error), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "AnimeAPI",
})
}
@@ -118,21 +121,21 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
if malsyncResult.Error == nil {
malSyncData = malsyncResult.Value
} else {
- logger.Log(fmt.Sprintf("Failed to fetch MALSync data: %v", malsyncResult.Error), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to fetch MALSync data: %v", malsyncResult.Error), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "AnimeAPI",
})
}
} else {
- logger.Log(fmt.Sprintf("No AniList ID available for MAL ID %d", malID), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("No AniList ID available for MAL ID %d", malID), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
// If no AniList ID, just fetch MALSync data
malSyncData, _ = s.malsyncClient.GetAnimeByMALID(malID)
}
- logger.Log(fmt.Sprintf("AniList and MALSync fetch time: %s", time.Since(anilistStartTime)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("AniList and MALSync fetch time: %s", time.Since(anilistStartTime)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -148,14 +151,14 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
defer close(dubbedCountChan)
basicEpisodes := generateBasicEpisodes(episodes.Data)
- logger.Log(fmt.Sprintf("Generated basic episodes: %d", len(basicEpisodes)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Generated basic episodes: %d", len(basicEpisodes)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
// Enrich episodes with TMDB data
- logger.Log(fmt.Sprintf("Starting enrichEpisodes for %d episodes", len(basicEpisodes)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Starting enrichEpisodes for %d episodes", len(basicEpisodes)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
enrichStart := time.Now()
@@ -167,8 +170,8 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
searchTitle := anime.Data.Title
startStreamingCheck := time.Now()
- logger.Log("Fetching streaming episode counts...", types.LogOptions{
- Level: types.Debug,
+ logger.Log("Fetching streaming episode counts...", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -187,21 +190,21 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
// Log the final error if all attempts failed
if err != nil {
- logger.Log(fmt.Sprintf("Failed to fetch streaming counts after all attempts: %v", err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to fetch streaming counts after all attempts: %v", err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "AnimeAPI",
})
}
}
logger.Log(fmt.Sprintf("Streaming count check took %s. Subbed: %d, Dubbed: %d",
- time.Since(startStreamingCheck), subCount, dubCount), types.LogOptions{
- Level: types.Debug,
+ time.Since(startStreamingCheck), subCount, dubCount), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
- logger.Log(fmt.Sprintf("enrichEpisodes execution time: %s", time.Since(enrichStart)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("enrichEpisodes execution time: %s", time.Since(enrichStart)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -214,21 +217,21 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
seasonsStartTime := time.Now()
var seasons []types.AnimeSeason
if mapping.TVDB != 0 {
- logger.Log(fmt.Sprintf("Finding season mappings for TVDB ID %d", mapping.TVDB), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Finding season mappings for TVDB ID %d", mapping.TVDB), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "TVDB",
})
seasonMappings, err := FindSeasonMappings(mapping.TVDB)
if err == nil && len(seasonMappings) > 0 {
- logger.Log(fmt.Sprintf("Found %d season mappings for TVDB ID %d", len(seasonMappings), mapping.TVDB), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Found %d season mappings for TVDB ID %d", len(seasonMappings), mapping.TVDB), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "TVDB",
})
seasons = s.getSeasonDetails(&seasonMappings, malID)
}
}
- logger.Log(fmt.Sprintf("Seasons fetch time: %s", time.Since(seasonsStartTime)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Seasons fetch time: %s", time.Since(seasonsStartTime)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -248,8 +251,8 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
}
// Wait for episode data to complete
- logger.Log(fmt.Sprintf("Waiting for episode data processing (started %s ago)", time.Since(episodeProcessingStartTime)), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Waiting for episode data processing (started %s ago)", time.Since(episodeProcessingStartTime)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
episodeWaitStartTime := time.Now()
@@ -258,8 +261,8 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
dubbedCount := <-dubbedCountChan
logger.Log(fmt.Sprintf("Episode data wait time: %s (total episode processing time: %s)",
time.Since(episodeWaitStartTime),
- time.Since(episodeProcessingStartTime)), types.LogOptions{
- Level: types.Debug,
+ time.Since(episodeProcessingStartTime)), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -350,8 +353,8 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
// Add AniList cover images and color if available
if anilistAnime != nil && anilistAnime.Data.Media.ID > 0 {
- logger.Log("Setting covers and color from AniList data", types.LogOptions{
- Level: types.Debug,
+ logger.Log("Setting covers and color from AniList data", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
@@ -368,14 +371,14 @@ func (s *Service) GetAnimeDetails(mapping *entities.AnimeMapping) (*types.Anime,
// For color, also make sure it's not empty
if coverImage.Color != "" {
animeDetails.Color = coverImage.Color
- logger.Log(fmt.Sprintf("Set color to: %s", coverImage.Color), types.LogOptions{
- Level: types.Debug,
+ logger.Log(fmt.Sprintf("Set color to: %s", coverImage.Color), logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
}
} else {
- logger.Log("No valid AniList data available for covers and color", types.LogOptions{
- Level: types.Debug,
+ logger.Log("No valid AniList data available for covers and color", logger.LogOptions{
+ Level: logger.Debug,
Prefix: "AnimeAPI",
})
}
diff --git a/tasks/anisync.task.go b/tasks/anisync.task.go
index 13c3587..b189336 100644
--- a/tasks/anisync.task.go
+++ b/tasks/anisync.task.go
@@ -17,15 +17,15 @@ import (
const fribbURL = "https://raw.githubusercontent.com/Fribb/anime-lists/master/anime-list-full.json"
func AniSync() error {
- logger.Log("Starting Anime Sync", types.LogOptions{
- Level: types.Info,
+ logger.Log("Starting Anime Sync", logger.LogOptions{
+ Level: logger.Info,
Prefix: "AniSync",
})
response, err := http.Get(fribbURL)
if err != nil {
- logger.Log(fmt.Sprintf("Anime Sync failed: %v", err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Anime Sync failed: %v", err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "AniSync",
})
return err
@@ -34,8 +34,8 @@ func AniSync() error {
body, err := io.ReadAll(response.Body)
if err != nil {
- logger.Log(fmt.Sprintf("Failed to read response body: %v", err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Failed to read response body: %v", err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "AniSync",
})
return err
@@ -43,8 +43,8 @@ func AniSync() error {
var mappings []types.AniSyncMapping
if err := json.Unmarshal(body, &mappings); err != nil {
- logger.Log(fmt.Sprintf("Failed to unmarshal JSON: %v", err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Failed to unmarshal JSON: %v", err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "AniSync",
})
return err
@@ -61,14 +61,14 @@ func AniSync() error {
batch := mappings[i:end]
processBatch(batch)
- logger.Log(fmt.Sprintf("Processed %d/%d mappings", end, total), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Processed %d/%d mappings", end, total), logger.LogOptions{
+ Level: logger.Info,
Prefix: "AniSync",
})
}
- logger.Log("Anime Sync completed", types.LogOptions{
- Level: types.Success,
+ logger.Log("Anime Sync completed", logger.LogOptions{
+ Level: logger.Success,
Prefix: "AniSync",
})
@@ -104,14 +104,14 @@ func processBatch(mappings []types.AniSyncMapping) {
MALAnilistComposite: composite,
}
if err := database.DB.Create(&newEntity).Error; err != nil {
- logger.Log(fmt.Sprintf("Unable to process mapping %v: %v", mapping, err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Unable to process mapping %v: %v", mapping, err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "AniSync",
})
}
} else {
- logger.Log(fmt.Sprintf("Error fetching entity: %v", err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Error fetching entity: %v", err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "AniSync",
})
}
@@ -133,8 +133,8 @@ func processBatch(mappings []types.AniSyncMapping) {
entity.Type = entities.MappingType(mapping.Type)
entity.MALAnilistComposite = composite
if err := database.DB.Save(&entity).Error; err != nil {
- logger.Log(fmt.Sprintf("Unable to update mapping %v: %v", mapping, err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Unable to update mapping %v: %v", mapping, err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "AniSync",
})
}
diff --git a/tasks/manager.go b/tasks/manager.go
index 139b172..a420148 100644
--- a/tasks/manager.go
+++ b/tasks/manager.go
@@ -28,8 +28,8 @@ func (tm *TaskManager) RegisterTask(task types.Task) error {
}
tm.Tasks[task.Name] = task
- logger.Log(fmt.Sprintf("Task %s registered", task.Name), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Task %s registered", task.Name), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TaskManager",
})
@@ -60,8 +60,8 @@ func (tm *TaskManager) logTaskExecution(taskName, status, message string) {
}
if err := tm.Database.Create(&logEntry).Error; err != nil {
- logger.Log(fmt.Sprintf("Failed to log task execution for %s: %v", taskName, err), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Failed to log task execution for %s: %v", taskName, err), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TaskManager",
})
}
@@ -72,8 +72,8 @@ func (tm *TaskManager) StartTask(taskName string) {
task, exists := tm.Tasks[taskName]
tm.Mutex.Unlock()
if !exists {
- logger.Log(fmt.Sprintf("Task %s not found", taskName), types.LogOptions{
- Level: types.Warn,
+ logger.Log(fmt.Sprintf("Task %s not found", taskName), logger.LogOptions{
+ Level: logger.Warn,
Prefix: "TaskManager",
})
return
@@ -84,8 +84,8 @@ func (tm *TaskManager) StartTask(taskName string) {
shouldExec, err := tm.shouldExecuteTask(taskName, task.Interval)
if err != nil {
- logger.Log(fmt.Sprintf("Error checking execution condition for task %s: %v", taskName, err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Error checking execution condition for task %s: %v", taskName, err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "TaskManager",
})
return
@@ -101,15 +101,15 @@ func (tm *TaskManager) StartTask(taskName string) {
if shouldExec {
if err := task.Execute(); err != nil {
tm.logTaskExecution(taskName, "error", err.Error())
- logger.Log(fmt.Sprintf("Task %s execution failed: %v", taskName, err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Task %s execution failed: %v", taskName, err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "TaskManager",
})
} else {
task.LastRun = time.Now()
tm.logTaskExecution(taskName, "success", "Task executed successfully")
- logger.Log(fmt.Sprintf("Task %s executed successfully", taskName), types.LogOptions{
- Level: types.Success,
+ logger.Log(fmt.Sprintf("Task %s executed successfully", taskName), logger.LogOptions{
+ Level: logger.Success,
Prefix: "TaskManager",
})
}
@@ -125,8 +125,8 @@ func (tm *TaskManager) StartTask(taskName string) {
}
}
- logger.Log(fmt.Sprintf("Task %s will run in %v", taskName, initialDelay), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Task %s will run in %v", taskName, initialDelay), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TaskManager",
})
@@ -135,15 +135,15 @@ func (tm *TaskManager) StartTask(taskName string) {
case <-time.After(initialDelay):
if err := task.Execute(); err != nil {
tm.logTaskExecution(taskName, "error", err.Error())
- logger.Log(fmt.Sprintf("Task %s execution failed: %v", taskName, err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Task %s execution failed: %v", taskName, err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "TaskManager",
})
} else {
task.LastRun = time.Now()
tm.logTaskExecution(taskName, "success", "Task executed successfully")
- logger.Log(fmt.Sprintf("Task %s executed successfully", taskName), types.LogOptions{
- Level: types.Success,
+ logger.Log(fmt.Sprintf("Task %s executed successfully", taskName), logger.LogOptions{
+ Level: logger.Success,
Prefix: "TaskManager",
})
}
@@ -164,15 +164,15 @@ func (tm *TaskManager) StartTask(taskName string) {
case <-ticker.C:
if err := task.Execute(); err != nil {
tm.logTaskExecution(taskName, "error", err.Error())
- logger.Log(fmt.Sprintf("Task %s execution failed: %v", taskName, err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Task %s execution failed: %v", taskName, err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "TaskManager",
})
} else {
task.LastRun = time.Now()
tm.logTaskExecution(taskName, "success", "Task executed successfully")
- logger.Log(fmt.Sprintf("Task %s executed successfully", taskName), types.LogOptions{
- Level: types.Success,
+ logger.Log(fmt.Sprintf("Task %s executed successfully", taskName), logger.LogOptions{
+ Level: logger.Success,
Prefix: "TaskManager",
})
}
@@ -183,8 +183,8 @@ func (tm *TaskManager) StartTask(taskName string) {
}
}()
- logger.Log(fmt.Sprintf("Task %s scheduled with interval %v", taskName, task.Interval), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Task %s scheduled with interval %v", taskName, task.Interval), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TaskManager",
})
}
@@ -197,8 +197,8 @@ func (tm *TaskManager) StopTask(taskName string) {
close(doneChan)
delete(tm.Done, taskName)
delete(tm.Tickers, taskName)
- logger.Log(fmt.Sprintf("Task %s stopped", taskName), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Task %s stopped", taskName), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TaskManager",
})
}
@@ -228,8 +228,8 @@ func (tm *TaskManager) StopAllTasks() {
ticker.Stop()
delete(tm.Tickers, name)
}
- logger.Log(fmt.Sprintf("Task %s stopped", name), types.LogOptions{
- Level: types.Info,
+ logger.Log(fmt.Sprintf("Task %s stopped", name), logger.LogOptions{
+ Level: logger.Info,
Prefix: "TaskManager",
})
}
@@ -255,8 +255,8 @@ func (tm *TaskManager) GetTaskStatus(taskName string) *types.TaskStatus {
nextRun = &next
}
} else if err != gorm.ErrRecordNotFound {
- logger.Log(fmt.Sprintf("Error fetching task log for %s: %v", taskName, err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Error fetching task log for %s: %v", taskName, err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "TaskManager",
})
}
diff --git a/tasks/tasks.go b/tasks/tasks.go
index dd6685d..57b34bd 100644
--- a/tasks/tasks.go
+++ b/tasks/tasks.go
@@ -27,8 +27,8 @@ func init() {
})
if err != nil {
- logger.Log(fmt.Sprintf("Failed to register task: %v", err), types.LogOptions{
- Level: types.Error,
+ logger.Log(fmt.Sprintf("Failed to register task: %v", err), logger.LogOptions{
+ Level: logger.Error,
Prefix: "TaskManager",
})
}
diff --git a/types/anime.go b/types/anime.go
index ab847f4..8d0c9d3 100644
--- a/types/anime.go
+++ b/types/anime.go
@@ -1,5 +1,6 @@
package types
+// AnimeTitles contains different naming variants for an anime
type AnimeTitles struct {
English string `json:"english"`
Japanese string `json:"japanese"`
@@ -7,6 +8,7 @@ type AnimeTitles struct {
Synonyms []string `json:"synonyms"`
}
+// AnimeMappings contains IDs from various anime databases/services
type AnimeMappings struct {
AniDB int `json:"anidb"`
Anilist int `json:"anilist"`
@@ -22,44 +24,18 @@ type AnimeMappings struct {
TVDB int `json:"tvdb"`
}
+//
+// Episode Related Types
+//
+
+// EpisodeTitles contains different naming variants for an episode
type EpisodeTitles struct {
English string `json:"english"`
Japanese string `json:"japanese"`
Romaji string `json:"romaji"`
}
-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
-}
-
-// EpisodeStreamingResult contains streaming sources for a specific episode
-// Used for parallel streaming source fetching
-type EpisodeStreamingResult struct {
- EpisodeNumber int
- Streaming *AnimeStreaming
-}
-
-type AnimeStreamingSource struct {
- URL string `json:"url"`
- Server string `json:"server"`
- Type string `json:"type"` // MP4 or M3U8
-}
-
-// AnimeStreaming represents streaming sources for an anime episode
-type AnimeStreaming struct {
- Sub []AnimeStreamingSource `json:"sub"`
- Dub []AnimeStreamingSource `json:"dub"`
-}
-
+// AnimeSingleEpisode contains information about a single anime episode
type AnimeSingleEpisode struct {
Titles EpisodeTitles `json:"titles"`
Description string `json:"description"`
@@ -70,9 +46,9 @@ type AnimeSingleEpisode struct {
ForumURL string `json:"forum_url"`
URL string `json:"url"`
ThumbnailURL string `json:"thumbnail_url"`
- // Stream AnimeStreaming `json:"stream"`
}
+// AnimeEpisodes contains information about all episodes of an anime
type AnimeEpisodes struct {
Total int `json:"total"`
Aired int `json:"aired"`
@@ -81,6 +57,11 @@ type AnimeEpisodes struct {
Episodes []AnimeSingleEpisode `json:"episodes"`
}
+//
+// Visual Media Types
+//
+
+// AnimeLogos contains logo images in various sizes
type AnimeLogos struct {
Small string `json:"small,omitempty"`
Medium string `json:"medium,omitempty"`
@@ -89,36 +70,50 @@ type AnimeLogos struct {
Original string `json:"original,omitempty"`
}
+// AnimeImages contains general images in various sizes
type AnimeImages struct {
Small string `json:"small,omitempty"`
Large string `json:"large,omitempty"`
Original string `json:"original,omitempty"`
}
+//
+// Production and Content Types
+//
+
+// AnimeGenres contains genre information
type AnimeGenres struct {
Name string `json:"name"`
GenreID int `json:"genre_id"`
URL string `json:"url"`
}
+// AnimeProducer contains producer information
type AnimeProducer struct {
Name string `json:"name"`
ProducerID int `json:"producer_id"`
URL string `json:"url"`
}
+// AnimeLicensor contains licensor information
type AnimeLicensor struct {
Name string `json:"name"`
ProducerID int `json:"producer_id"`
URL string `json:"url"`
}
+// AnimeStudio contains studio information
type AnimeStudio struct {
Name string `json:"name"`
StudioID int `json:"studio_id"`
URL string `json:"url"`
}
+//
+// Airing and Schedule Types
+//
+
+// AiringStatusDates contains date information for airing
type AiringStatusDates struct {
Day int `json:"day"`
Month int `json:"month"`
@@ -126,21 +121,21 @@ type AiringStatusDates struct {
String string `json:"string"`
}
+// AiringStatus contains full airing status information
type AiringStatus struct {
From AiringStatusDates `json:"from"`
To AiringStatusDates `json:"to"`
String string `json:"string"`
}
-type AnimeScores struct {
- 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"`
+// AnimeAiringEpisode contains information about a single upcoming episode
+type AnimeAiringEpisode struct {
+ AiringAt int `json:"airing_at"`
+ TimeUntilAiring int `json:"time_until_airing"`
+ Episode int `json:"episode"`
}
+// AnimeBroadcast contains broadcast schedule information
type AnimeBroadcast struct {
Day string `json:"day"`
Time string `json:"time"`
@@ -148,23 +143,25 @@ type AnimeBroadcast struct {
String string `json:"string"`
}
-type AnimeSeason struct {
- MALID int `json:"id"`
- Titles AnimeTitles `json:"titles"`
- Synopsis string `json:"synopsis"`
- Type AniSyncType `json:"type"`
- Source string `json:"source"`
- Airing bool `json:"airing"`
- Status string `json:"status"`
- AiringStatus AiringStatus `json:"airing_status"`
- Duration string `json:"duration"`
- Images AnimeImages `json:"images"`
- Scores AnimeScores `json:"scores"`
- Season string `json:"season"`
- Year int `json:"year"`
- Current bool `json:"current"`
+//
+// Community and Metrics Types
+//
+
+// AnimeScores contains popularity and rating information
+type AnimeScores struct {
+ 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"`
}
+//
+// Character Related Types
+//
+
+// AnimeVoiceActor contains voice actor information
type AnimeVoiceActor struct {
MALID int `json:"mal_id"`
URL string `json:"url"`
@@ -173,6 +170,7 @@ type AnimeVoiceActor struct {
Language string `json:"language"`
}
+// AnimeCharacter contains character information
type AnimeCharacter struct {
MALID int `json:"mal_id"`
URL string `json:"url"`
@@ -182,12 +180,33 @@ type AnimeCharacter struct {
VoiceActors []AnimeVoiceActor `json:"voice_actors"`
}
-type AnimeAiringEpisode struct {
- AiringAt int `json:"airing_at"`
- TimeUntilAiring int `json:"time_until_airing"`
- Episode int `json:"episode"`
+//
+// Season Related Types
+//
+
+// AnimeSeason contains information about a single anime season
+type AnimeSeason struct {
+ MALID int `json:"id"`
+ Titles AnimeTitles `json:"titles"`
+ Synopsis string `json:"synopsis"`
+ Type AniSyncType `json:"type"`
+ Source string `json:"source"`
+ Airing bool `json:"airing"`
+ Status string `json:"status"`
+ AiringStatus AiringStatus `json:"airing_status"`
+ Duration string `json:"duration"`
+ Images AnimeImages `json:"images"`
+ Scores AnimeScores `json:"scores"`
+ Season string `json:"season"`
+ Year int `json:"year"`
+ Current bool `json:"current"`
}
+//
+// Main Anime Type
+//
+
+// Anime is the main structure containing all anime information
type Anime struct {
MALID int `json:"id"`
Titles AnimeTitles `json:"titles"`
@@ -217,407 +236,3 @@ type Anime struct {
Characters []AnimeCharacter `json:"characters"`
Mappings AnimeMappings `json:"mappings"`
}
-
-type JikanPagination struct {
- LastVisiblePage int `json:"last_visible_page"`
- HasNextPage bool `json:"has_next_page"`
-}
-
-type JikanGenericMALStructure struct {
- MALID int `json:"mal_id"`
- Type string `json:"type"`
- URL string `json:"url"`
- Name string `json:"name"`
-}
-
-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"`
-}
-
-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"`
-}
-
-type JikanAnimeEpisodeResponse struct {
- Pagination JikanPagination `json:"pagination"`
- Data []JikanAnimeEpisode `json:"data"`
-}
-
-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"`
-}
-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"`
-}
-
-// 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/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/types/tmdb.go b/utils/api/tmdb/types.go
index 092f6c1..8743573 100644
--- a/types/tmdb.go
+++ b/utils/api/tmdb/types.go
@@ -1,4 +1,4 @@
-package types
+package tmdb
// TMDBShowResult represents a TV show result from TMDB search
type TMDBShowResult struct {
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",
})
}
diff --git a/utils/logger/logger.go b/utils/logger/logger.go
index de2d69c..c5788bc 100644
--- a/utils/logger/logger.go
+++ b/utils/logger/logger.go
@@ -5,8 +5,6 @@ import (
"os"
"strings"
"time"
-
- "metachan/types"
)
const prefixWidth = 15
@@ -15,47 +13,47 @@ func getTimestamp() string {
return time.Now().Format(time.RFC3339)
}
-func getLevelColor(level types.LogLevel) string {
+func getLevelColor(level LogLevel) string {
switch level {
- case types.Info:
- return types.LevelColorInfo
- case types.Warn:
- return types.LevelColorWarn
- case types.Error:
- return types.LevelColorError
- case types.Debug:
- return types.LevelColorDebug
- case types.Success:
- return types.LevelColorSuccess
+ case Info:
+ return LevelColorInfo
+ case Warn:
+ return LevelColorWarn
+ case Error:
+ return LevelColorError
+ case Debug:
+ return LevelColorDebug
+ case Success:
+ return LevelColorSuccess
default:
- return types.LevelColorInfo
+ return LevelColorInfo
}
}
-func getMessageColor(level types.LogLevel) string {
+func getMessageColor(level LogLevel) string {
switch level {
- case types.Info:
- return types.MessageColorInfo
- case types.Warn:
- return types.MessageColorWarn
- case types.Error:
- return types.MessageColorError
- case types.Debug:
- return types.MessageColorDebug
- case types.Success:
- return types.MessageColorSuccess
+ case Info:
+ return MessageColorInfo
+ case Warn:
+ return MessageColorWarn
+ case Error:
+ return MessageColorError
+ case Debug:
+ return MessageColorDebug
+ case Success:
+ return MessageColorSuccess
default:
- return types.MessageColorInfo
+ return MessageColorInfo
}
}
-func Log(message interface{}, options types.LogOptions) {
+func Log(message interface{}, options LogOptions) {
var builder strings.Builder
if options.Timestamp {
- builder.WriteString(types.Gray)
+ builder.WriteString(Gray)
builder.WriteString(getTimestamp())
- builder.WriteString(types.Reset)
+ builder.WriteString(Reset)
builder.WriteString(" ")
}
@@ -70,11 +68,11 @@ func Log(message interface{}, options types.LogOptions) {
padding = strings.Repeat(" ", prefixWidth-totalWidth)
}
- builder.WriteString(types.Cyan)
+ builder.WriteString(Cyan)
builder.WriteString("[")
builder.WriteString(options.Prefix)
builder.WriteString("]")
- builder.WriteString(types.Reset)
+ builder.WriteString(Reset)
builder.WriteString(padding)
builder.WriteString(" ")
}
@@ -90,10 +88,10 @@ func Log(message interface{}, options types.LogOptions) {
builder.WriteString(fmt.Sprintf("%v", msg))
}
- builder.WriteString(types.Reset)
+ builder.WriteString(Reset)
builder.WriteString("\n")
- if options.Level == types.Error || options.Level == types.Warn {
+ if options.Level == Error || options.Level == Warn {
os.Stderr.WriteString(builder.String())
} else {
os.Stdout.WriteString(builder.String())
diff --git a/types/logger.go b/utils/logger/types.go
index a913312..5ef310f 100644
--- a/types/logger.go
+++ b/utils/logger/types.go
@@ -1,4 +1,4 @@
-package types
+package logger
type LogLevel string