diff options
| author | Bobby <[email protected]> | 2025-05-09 02:25:54 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-05-09 02:25:54 +0530 |
| commit | cb41c834529f696c2f83adbb41e6b592c89495f3 (patch) | |
| tree | 5180a0c055c2b2f4b55a3374c83405d20e858d78 | |
| parent | 9ebde192fbde6d6d6be2d7d86485eca9a0c5e026 (diff) | |
| download | metachan-cb41c834529f696c2f83adbb41e6b592c89495f3.tar.xz metachan-cb41c834529f696c2f83adbb41e6b592c89495f3.zip | |
refactored types
| -rw-r--r-- | config/config.go | 6 | ||||
| -rw-r--r-- | controllers/anime.go | 5 | ||||
| -rw-r--r-- | database/database.go | 12 | ||||
| -rw-r--r-- | database/migrate.go | 9 | ||||
| -rw-r--r-- | database/status.go | 9 | ||||
| -rw-r--r-- | metachan/main.go | 9 | ||||
| -rw-r--r-- | middleware/logger.go | 41 | ||||
| -rw-r--r-- | services/anime/helpers.go | 132 | ||||
| -rw-r--r-- | services/anime/service.go | 119 | ||||
| -rw-r--r-- | tasks/anisync.task.go | 36 | ||||
| -rw-r--r-- | tasks/manager.go | 60 | ||||
| -rw-r--r-- | tasks/tasks.go | 4 | ||||
| -rw-r--r-- | types/anime.go | 541 | ||||
| -rw-r--r-- | utils/api/anilist/anilist.go (renamed from utils/api/anilist.go) | 23 | ||||
| -rw-r--r-- | utils/api/anilist/types.go | 207 | ||||
| -rw-r--r-- | utils/api/aniskip/aniskip.go (renamed from utils/api/aniskip.go) | 57 | ||||
| -rw-r--r-- | utils/api/aniskip/types.go | 15 | ||||
| -rw-r--r-- | utils/api/jikan/jikan.go (renamed from utils/api/jikan.go) | 23 | ||||
| -rw-r--r-- | utils/api/jikan/types.go | 177 | ||||
| -rw-r--r-- | utils/api/malsync/malsync.go (renamed from utils/api/malsync.go) | 7 | ||||
| -rw-r--r-- | utils/api/malsync/types.go | 31 | ||||
| -rw-r--r-- | utils/api/streaming/streaming.go (renamed from utils/api/streaming.go) | 38 | ||||
| -rw-r--r-- | utils/api/streaming/types.go | 38 | ||||
| -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.go | 64 | ||||
| -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 |
