aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-02-24 14:02:37 +0530
committerBobby <[email protected]>2026-02-24 14:02:37 +0530
commitde0c8e5b052342bc7aaa3e8e3795ce432208af61 (patch)
tree4ee288d86fc17c4e28a101a35cbdf9b2c1597042
parent45e858c3062eacb2f9d51b65d5680eb211ce5482 (diff)
downloadmetachan-de0c8e5b052342bc7aaa3e8e3795ce432208af61.tar.xz
metachan-de0c8e5b052342bc7aaa3e8e3795ce432208af61.zip
Refactor entities and repositories: add AnimeCharacter and CharacterVoiceActor types, update character handling in anime, and enhance producer enrichment logic
-rw-r--r--database/migrate.go2
-rw-r--r--entities/anime.go2
-rw-r--r--entities/meta.go4
-rw-r--r--entities/persona.go36
-rw-r--r--entities/producer.go3
-rw-r--r--repositories/anime.go100
-rw-r--r--repositories/meta.go24
-rw-r--r--repositories/producer.go56
-rw-r--r--services/anime.go46
-rw-r--r--tasks/manager.go4
-rw-r--r--tasks/producersync.task.go91
-rw-r--r--tasks/tasks.go1
-rw-r--r--types/aniskip.go10
-rw-r--r--types/jikan.go23
-rw-r--r--types/tasks.go1
15 files changed, 312 insertions, 91 deletions
diff --git a/database/migrate.go b/database/migrate.go
index 44a462c..c40b19c 100644
--- a/database/migrate.go
+++ b/database/migrate.go
@@ -48,6 +48,8 @@ func migrate() {
// Character/Persona entities
&entities.Character{},
&entities.VoiceActor{},
+ &entities.AnimeCharacter{},
+ &entities.CharacterVoiceActor{},
)
if err != nil {
logger.Fatalf("Database", "Error during database migration: %v", err)
diff --git a/entities/anime.go b/entities/anime.go
index cdcc563..9c23767 100644
--- a/entities/anime.go
+++ b/entities/anime.go
@@ -46,6 +46,6 @@ type Anime struct {
Studios []Producer `gorm:"many2many:anime_studios;" json:"studios,omitempty"`
Licensors []Producer `gorm:"many2many:anime_licensors;" json:"licensors,omitempty"`
Episodes []Episode `gorm:"foreignKey:AnimeID" json:"episodes,omitempty"`
- Characters []Character `gorm:"foreignKey:AnimeID" json:"characters,omitempty"`
+ Characters []Character `gorm:"-" json:"characters,omitempty"`
Schedule []EpisodeSchedule `gorm:"foreignKey:AnimeID;constraint:OnDelete:CASCADE" json:"airing_schedule,omitempty"`
}
diff --git a/entities/meta.go b/entities/meta.go
index 8c37417..2395ddd 100644
--- a/entities/meta.go
+++ b/entities/meta.go
@@ -67,8 +67,8 @@ type ExternalURL struct {
type SimpleTitle struct {
BaseModel
- Type string `json:"type,omitempty"`
- Title string `json:"title,omitempty"`
+ Type string `gorm:"uniqueIndex:idx_simple_title" json:"type,omitempty"`
+ Title string `gorm:"uniqueIndex:idx_simple_title" json:"title,omitempty"`
}
type SimpleImage struct {
diff --git a/entities/persona.go b/entities/persona.go
index 481f7af..91a0b49 100644
--- a/entities/persona.go
+++ b/entities/persona.go
@@ -2,21 +2,31 @@ package entities
type Character struct {
BaseModel
- AnimeID uint `json:"-"`
- MALID int `json:"mal_id,omitempty"`
- URL string `json:"url,omitempty"`
- ImageURL string `json:"image_url,omitempty"`
- Name string `json:"name,omitempty"`
- Role string `json:"role,omitempty"`
- VoiceActors []VoiceActor `gorm:"foreignKey:CharacterID" json:"voice_actors,omitempty"`
+ MALID int `gorm:"uniqueIndex" json:"mal_id,omitempty"`
+ URL string `json:"url,omitempty"`
+ ImageURL string `json:"image_url,omitempty"`
+ Name string `json:"name,omitempty"`
+ Role string `gorm:"-" json:"role,omitempty"`
+ VoiceActors []CharacterVoiceActor `gorm:"foreignKey:CharacterID" json:"voice_actors,omitempty"`
}
type VoiceActor struct {
BaseModel
- CharacterID uint `json:"-"`
- MALID int `json:"mal_id,omitempty"`
- URL string `json:"url,omitempty"`
- Image string `json:"image_url,omitempty"`
- Name string `json:"name,omitempty"`
- Language string `json:"language,omitempty"`
+ MALID int `gorm:"uniqueIndex" json:"mal_id,omitempty"`
+ URL string `json:"url,omitempty"`
+ Image string `json:"image_url,omitempty"`
+ Name string `json:"name,omitempty"`
+}
+
+type AnimeCharacter struct {
+ AnimeID uint `gorm:"primaryKey"`
+ CharacterID uint `gorm:"primaryKey"`
+ Role string
+}
+
+type CharacterVoiceActor struct {
+ CharacterID uint `gorm:"primaryKey" json:"-"`
+ VoiceActorID uint `gorm:"primaryKey" json:"-"`
+ Language string `json:"language,omitempty"`
+ VoiceActor *VoiceActor `gorm:"foreignKey:ID;references:VoiceActorID" json:"voice_actor,omitempty"`
}
diff --git a/entities/producer.go b/entities/producer.go
index c68dc32..42cada9 100644
--- a/entities/producer.go
+++ b/entities/producer.go
@@ -1,5 +1,7 @@
package entities
+import "time"
+
type Producer struct {
BaseModel
MALID int `gorm:"uniqueIndex" json:"mal_id,omitempty"`
@@ -8,6 +10,7 @@ type Producer struct {
Count int `json:"count,omitempty"`
Established string `json:"established,omitempty"`
About string `gorm:"type:text" json:"about,omitempty"`
+ EnrichedAt *time.Time `json:"-"`
ImageID *uint `json:"-"`
Image *SimpleImage `gorm:"foreignKey:ImageID" json:"image,omitempty"`
Titles []SimpleTitle `gorm:"many2many:producer_titles" json:"titles,omitempty"`
diff --git a/repositories/anime.go b/repositories/anime.go
index 8870f35..b20a256 100644
--- a/repositories/anime.go
+++ b/repositories/anime.go
@@ -51,8 +51,6 @@ func GetAnime[T idType](maptype enums.MappingType, id T) (entities.Anime, error)
Preload("Episodes.StreamInfo").
Preload("Episodes.StreamInfo.SubSources").
Preload("Episodes.StreamInfo.DubSources").
- Preload("Characters").
- Preload("Characters.VoiceActors").
Preload("Schedule").
Preload("Seasons").
Preload("Seasons.Title").
@@ -69,9 +67,82 @@ func GetAnime[T idType](maptype enums.MappingType, id T) (entities.Anime, error)
return entities.Anime{}, errors.New("anime not found")
}
+ loadAnimeCharacters(&anime)
+
return anime, nil
}
+func loadAnimeCharacters(anime *entities.Anime) {
+ var rows []struct {
+ CharacterID uint
+ Role string
+ }
+ DB.Table("anime_characters").
+ Select("character_id, role").
+ Where("anime_id = ?", anime.ID).
+ Scan(&rows)
+
+ for _, row := range rows {
+ var char entities.Character
+ if err := DB.First(&char, row.CharacterID).Error; err != nil {
+ continue
+ }
+ DB.Preload("VoiceActor").
+ Where("character_id = ?", char.ID).
+ Find(&char.VoiceActors)
+ char.Role = row.Role
+ anime.Characters = append(anime.Characters, char)
+ }
+}
+
+func SaveAnimeCharacters(animeID uint, characters []entities.Character) error {
+ for i := range characters {
+ char := &characters[i]
+
+ DB.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "mal_id"}},
+ DoUpdates: clause.AssignmentColumns([]string{"url", "image_url", "name"}),
+ }).Create(char)
+ if char.ID == 0 {
+ DB.Where("mal_id = ?", char.MALID).First(char)
+ }
+
+ DB.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "anime_id"}, {Name: "character_id"}},
+ DoUpdates: clause.AssignmentColumns([]string{"role"}),
+ }).Create(&entities.AnimeCharacter{
+ AnimeID: animeID,
+ CharacterID: char.ID,
+ Role: char.Role,
+ })
+
+ for _, cva := range char.VoiceActors {
+ va := cva.VoiceActor
+ if va == nil {
+ continue
+ }
+
+ DB.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "mal_id"}},
+ DoUpdates: clause.AssignmentColumns([]string{"url", "image", "name"}),
+ }).Create(va)
+ if va.ID == 0 {
+ DB.Where("mal_id = ?", va.MALID).First(va)
+ }
+
+ DB.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "character_id"}, {Name: "voice_actor_id"}},
+ DoUpdates: clause.AssignmentColumns([]string{"language"}),
+ }).Create(&entities.CharacterVoiceActor{
+ CharacterID: char.ID,
+ VoiceActorID: va.ID,
+ Language: cva.Language,
+ })
+ }
+ }
+ return nil
+}
+
func CreateOrUpdateAnime(anime *entities.Anime) error {
if anime == nil {
return fmt.Errorf("anime is nil")
@@ -85,7 +156,7 @@ func CreateOrUpdateAnime(anime *entities.Anime) error {
result = DB.Session(&gorm.Session{FullSaveAssociations: true}).Clauses(clause.OnConflict{
UpdateAll: true,
- }).Save(anime)
+ }).Omit("Characters", "Episodes").Save(anime)
if result.Error != nil {
return fmt.Errorf("failed to save anime: %w", result.Error)
@@ -95,6 +166,29 @@ func CreateOrUpdateAnime(anime *entities.Anime) error {
return nil
}
+func SaveAnimeEpisodes(animeID uint, episodes []entities.Episode) error {
+ for i := range episodes {
+ ep := &episodes[i]
+ ep.AnimeID = animeID
+
+ var existing entities.Episode
+ if DB.Where("episode_id = ?", ep.EpisodeID).First(&existing).Error == nil {
+ ep.ID = existing.ID
+ ep.TitleID = existing.TitleID
+ DB.Model(ep).Omit("SkipTimes", "StreamInfo", "Title").Updates(ep)
+ if ep.Title != nil && existing.TitleID != 0 {
+ ep.Title.ID = existing.TitleID
+ DB.Save(ep.Title)
+ }
+ } else {
+ DB.Session(&gorm.Session{FullSaveAssociations: true}).
+ Omit("SkipTimes", "StreamInfo").
+ Create(ep)
+ }
+ }
+ return nil
+}
+
func SaveEpisodeSkipTimes(episodeID string, skipTimes []entities.EpisodeSkipTime) error {
if len(skipTimes) == 0 {
return nil
diff --git a/repositories/meta.go b/repositories/meta.go
index 34487a4..817a843 100644
--- a/repositories/meta.go
+++ b/repositories/meta.go
@@ -72,6 +72,30 @@ func CreateOrUpdateSimpleTitle(title *entities.SimpleTitle) (uint, error) {
return title.ID, nil
}
+func GetAllImagesMapped() (map[string]uint, error) {
+ var images []entities.SimpleImage
+ if err := DB.Select("id, image_url").Find(&images).Error; err != nil {
+ return nil, err
+ }
+ m := make(map[string]uint, len(images))
+ for _, img := range images {
+ m[img.ImageURL] = img.ID
+ }
+ return m, nil
+}
+
+func GetAllTitlesMapped() (map[string]uint, error) {
+ var titles []entities.SimpleTitle
+ if err := DB.Select("id, type, title").Find(&titles).Error; err != nil {
+ return nil, err
+ }
+ m := make(map[string]uint, len(titles))
+ for _, t := range titles {
+ m[t.Type+":"+t.Title] = t.ID
+ }
+ return m, nil
+}
+
func CreateOrUpdateExternalURL(url *entities.ExternalURL) (uint, error) {
result := DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}, {Name: "url"}},
diff --git a/repositories/producer.go b/repositories/producer.go
index 53deefe..afe9108 100644
--- a/repositories/producer.go
+++ b/repositories/producer.go
@@ -4,26 +4,80 @@ import (
"errors"
"metachan/entities"
"metachan/utils/logger"
+ "time"
"gorm.io/gorm/clause"
)
func CreateOrUpdateProducer(producer *entities.Producer) error {
+ for i := range producer.Titles {
+ t := &producer.Titles[i]
+ DB.Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "type"}, {Name: "title"}},
+ DoNothing: true,
+ }).Create(t)
+ if t.ID == 0 {
+ DB.Where("type = ? AND title = ?", t.Type, t.Title).First(t)
+ }
+ }
+
result := DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "mal_id"}},
DoUpdates: clause.AssignmentColumns([]string{
"url", "favorites", "count", "established", "about", "image_id",
}),
- }).Create(producer)
+ }).Omit("Titles").Create(producer)
if result.Error != nil {
logger.Errorf("Producer", "Failed to create or update producer: %v", result.Error)
return errors.New("failed to create or update producer")
}
+ if len(producer.Titles) > 0 {
+ if err := DB.Model(producer).Association("Titles").Replace(producer.Titles); err != nil {
+ logger.Errorf("Producer", "Failed to associate titles for producer %d: %v", producer.MALID, err)
+ }
+ }
+
return nil
}
+func GetAllProducers() ([]entities.Producer, error) {
+ var producers []entities.Producer
+ if err := DB.Select("id, mal_id, enriched_at").Find(&producers).Error; err != nil {
+ return nil, err
+ }
+ return producers, nil
+}
+
+func GetProducerExternalURLCount(producer *entities.Producer) int64 {
+ return DB.Model(producer).Association("ExternalURLs").Count()
+}
+
+func ReplaceProducerExternalURLs(producer *entities.Producer, urls []entities.ExternalURL) error {
+ return DB.Model(producer).Association("ExternalURLs").Replace(urls)
+}
+
+func UpdateProducerDetails(id uint, url, established, about string, favorites, count int, imageURL string) error {
+ now := time.Now()
+ updates := map[string]interface{}{
+ "url": url,
+ "favorites": favorites,
+ "count": count,
+ "established": established,
+ "about": about,
+ "enriched_at": now,
+ }
+ if imageURL != "" {
+ img := entities.SimpleImage{ImageURL: imageURL}
+ imgID, err := CreateOrUpdateSimpleImage(&img)
+ if err == nil && imgID != 0 {
+ updates["image_id"] = imgID
+ }
+ }
+ return DB.Model(&entities.Producer{}).Where("id = ?", id).Updates(updates).Error
+}
+
func BatchCreateProducers(producers []entities.Producer) error {
if len(producers) == 0 {
return nil
diff --git a/services/anime.go b/services/anime.go
index 2bd35d1..eba303b 100644
--- a/services/anime.go
+++ b/services/anime.go
@@ -282,29 +282,27 @@ func applyJikanData(anime *entities.Anime, jikanAnime *types.JikanAnimeResponse,
if jikanCharacters != nil {
for _, jc := range jikanCharacters.Data {
- character := entities.Character{
- MALID: jc.MALID,
- Name: jc.Name,
+ char := entities.Character{
+ MALID: jc.Character.MALID,
+ Name: jc.Character.Name,
+ URL: jc.Character.URL,
+ ImageURL: jc.Character.Images.JPG.ImageURL,
Role: jc.Role,
- URL: jc.URL,
- ImageURL: jc.Images.JPG.ImageURL,
}
- if len(jc.VoiceActors) > 0 {
- for _, va := range jc.VoiceActors {
- if va.Language == "Japanese" {
- character.VoiceActors = append(character.VoiceActors, entities.VoiceActor{
- MALID: va.MALID,
- Name: va.Name,
- Language: va.Language,
- URL: va.URL,
- Image: va.Images.JPG.ImageURL,
- })
- }
- }
+ for _, va := range jc.VoiceActors {
+ char.VoiceActors = append(char.VoiceActors, entities.CharacterVoiceActor{
+ Language: va.Language,
+ VoiceActor: &entities.VoiceActor{
+ MALID: va.Person.MALID,
+ Name: va.Person.Name,
+ URL: va.Person.URL,
+ Image: va.Person.Images.JPG.ImageURL,
+ },
+ })
}
- anime.Characters = append(anime.Characters, character)
+ anime.Characters = append(anime.Characters, char)
}
}
}
@@ -671,6 +669,18 @@ func saveAnime(anime *entities.Anime, skipTimeMap map[string][]entities.EpisodeS
return fmt.Errorf("failed to save anime: %w", err)
}
+ if len(anime.Episodes) > 0 {
+ if err := repositories.SaveAnimeEpisodes(anime.ID, anime.Episodes); err != nil {
+ logger.Warnf("AnimeService", "Failed to save episodes: %v", err)
+ }
+ }
+
+ if len(anime.Characters) > 0 {
+ if err := repositories.SaveAnimeCharacters(anime.ID, anime.Characters); err != nil {
+ logger.Warnf("AnimeService", "Failed to save characters: %v", err)
+ }
+ }
+
for episodeID, skipTimes := range skipTimeMap {
if err := repositories.SaveEpisodeSkipTimes(episodeID, skipTimes); err != nil {
logger.Warnf("AnimeService", "Failed to save skip times for episode %s: %v", episodeID, err)
diff --git a/tasks/manager.go b/tasks/manager.go
index ac85630..30522a5 100644
--- a/tasks/manager.go
+++ b/tasks/manager.go
@@ -92,6 +92,10 @@ func (tm *TaskManager) StartTask(taskName string) {
tm.Done[taskName] = doneChan
tm.Mutex.Unlock()
+ if task.OnResume != nil {
+ go task.OnResume()
+ }
+
go func() {
if shouldExec {
if !tm.checkDependencies(task) {
diff --git a/tasks/producersync.task.go b/tasks/producersync.task.go
index 5055618..88ea682 100644
--- a/tasks/producersync.task.go
+++ b/tasks/producersync.task.go
@@ -9,6 +9,12 @@ import (
"time"
)
+// ResumeProducerEnrichment is called on startup to resume any background enrichment
+// that was interrupted by a previous shutdown.
+func ResumeProducerEnrichment() {
+ go enrichProducersInBackground()
+}
+
func ProducerSync() error {
logger.Infof("ProducerSync", "Starting producer sync (includes studios and licensors)")
@@ -27,7 +33,7 @@ func ProducerSync() error {
logger.Successf("ProducerSync", "Saved basic data for %d producers, enriching external URLs in background", total)
- go enrichProducersInBackground(response.Data)
+ go enrichProducersInBackground()
return nil
}
@@ -99,25 +105,17 @@ func saveProducerListData(producersData []types.JikanSingleProducer) error {
}
}
- var dbImages []entities.SimpleImage
- if err := repositories.DB.Select("id, image_url").Find(&dbImages).Error; err != nil {
+ imageIDMap, err := repositories.GetAllImagesMapped()
+ if err != nil {
logger.Errorf("ProducerSync", "Failed to query images: %v", err)
return err
}
- imageIDMap := make(map[string]uint)
- for _, img := range dbImages {
- imageIDMap[img.ImageURL] = img.ID
- }
- var dbTitles []entities.SimpleTitle
- if err := repositories.DB.Select("id, type, title").Find(&dbTitles).Error; err != nil {
+ titleIDMap, err := repositories.GetAllTitlesMapped()
+ if err != nil {
logger.Errorf("ProducerSync", "Failed to query titles: %v", err)
return err
}
- titleIDMap := make(map[string]uint)
- for _, t := range dbTitles {
- titleIDMap[t.Type+":"+t.Title] = t.ID
- }
producers := make([]entities.Producer, 0, len(producerItems))
for _, pd := range producerItems {
@@ -147,42 +145,61 @@ func saveProducerListData(producersData []types.JikanSingleProducer) error {
return nil
}
-func enrichProducersInBackground(producersData []types.JikanSingleProducer) {
- total := len(producersData)
- startTime := time.Now()
- enriched := 0
+func enrichProducersInBackground() {
+ producers, err := repositories.GetAllProducers()
+ if err != nil {
+ logger.Errorf("ProducerSync", "Failed to load producers for enrichment: %v", err)
+ return
+ }
- for i, pd := range producersData {
- var existing entities.Producer
- if err := repositories.DB.Where("mal_id = ?", pd.MALID).First(&existing).Error; err == nil {
- threeDaysAgo := time.Now().Add(-3 * 24 * time.Hour)
- if existing.UpdatedAt.After(threeDaysAgo) && len(existing.ExternalURLs) > 0 {
- continue
- }
+ sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour)
+
+ // Pre-check: skip entire run if nothing qualifies
+ hasWork := false
+ for _, p := range producers {
+ if p.EnrichedAt == nil || !p.EnrichedAt.After(sevenDaysAgo) {
+ hasWork = true
+ break
}
+ }
+ if !hasWork {
+ return
+ }
- detail, err := jikan.GetProducerByID(pd.MALID)
- if err != nil {
- logger.Warnf("ProducerSync", "Failed to fetch details for producer %d: %v", pd.MALID, err)
+ logger.Infof("ProducerSync", "Resuming background enrichment for producers missing external URLs")
+
+ total := len(producers)
+ startTime := time.Now()
+ enriched := 0
+
+ for i, p := range producers {
+ if p.EnrichedAt != nil && p.EnrichedAt.After(sevenDaysAgo) {
continue
}
- if len(detail.Data.External) == 0 {
+ detail, err := jikan.GetProducerByID(p.MALID)
+ if err != nil {
+ logger.Warnf("ProducerSync", "Failed to fetch details for producer %d: %v", p.MALID, err)
continue
}
- var producer entities.Producer
- if err := repositories.DB.Where("mal_id = ?", pd.MALID).First(&producer).Error; err != nil {
+ data := detail.Data
+ if err := repositories.UpdateProducerDetails(
+ p.ID, data.URL, data.Established, data.About, data.Favorites, data.Count,
+ data.Images.JPG.ImageURL,
+ ); err != nil {
+ logger.Warnf("ProducerSync", "Failed to update details for producer %d: %v", p.MALID, err)
continue
}
- externalURLs := make([]entities.ExternalURL, 0, len(detail.Data.External))
- for _, ext := range detail.Data.External {
- externalURLs = append(externalURLs, entities.ExternalURL{Name: ext.Name, URL: ext.URL})
- }
- if err := repositories.DB.Model(&producer).Association("ExternalURLs").Replace(externalURLs); err != nil {
- logger.Warnf("ProducerSync", "Failed to update external URLs for producer %d: %v", pd.MALID, err)
- continue
+ if len(data.External) > 0 {
+ externalURLs := make([]entities.ExternalURL, 0, len(data.External))
+ for _, ext := range data.External {
+ externalURLs = append(externalURLs, entities.ExternalURL{Name: ext.Name, URL: ext.URL})
+ }
+ if err := repositories.ReplaceProducerExternalURLs(&p, externalURLs); err != nil {
+ logger.Warnf("ProducerSync", "Failed to update external URLs for producer %d: %v", p.MALID, err)
+ }
}
enriched++
diff --git a/tasks/tasks.go b/tasks/tasks.go
index 8b19a19..8eb22f4 100644
--- a/tasks/tasks.go
+++ b/tasks/tasks.go
@@ -23,6 +23,7 @@ func init() {
Name: "ProducerSync",
Interval: 7 * 24 * time.Hour,
Execute: ProducerSync,
+ OnResume: ResumeProducerEnrichment,
})
if err != nil {
diff --git a/types/aniskip.go b/types/aniskip.go
index 3e25432..0ac94c9 100644
--- a/types/aniskip.go
+++ b/types/aniskip.go
@@ -1,15 +1,15 @@
package types
type AniskipInterval struct {
- StartTime float64 `json:"start_time"`
- EndTime float64 `json:"end_time"`
+ StartTime float64 `json:"startTime"`
+ EndTime float64 `json:"endTime"`
}
type AniskipResult struct {
Interval AniskipInterval `json:"interval"`
- SkipType string `json:"skip_type"`
- SkipID string `json:"skip_id"`
- EpisodeLength float64 `json:"episode_length"`
+ SkipType string `json:"skipType"`
+ SkipID string `json:"skipId"`
+ EpisodeLength float64 `json:"episodeLength"`
}
type AniskipResponse struct {
diff --git a/types/jikan.go b/types/jikan.go
index a91c14a..bbbea8b 100644
--- a/types/jikan.go
+++ b/types/jikan.go
@@ -159,21 +159,22 @@ type JikanAnimeEpisodeResponse struct {
Data []JikanAnimeSingleEpisode `json:"data"`
}
+type JikanCharacterPerson struct {
+ MALID int `json:"mal_id"`
+ URL string `json:"url"`
+ Images JikanGenericImageEntity `json:"images"`
+ Name string `json:"name"`
+}
+
type JikanSingleCharacter struct {
- MALID int `json:"mal_id"`
- URL string `json:"url"`
- Images JikanGenericImageEntity `json:"images"`
- Name string `json:"name"`
- Role string `json:"role"`
- VoiceActors []JikanVoiceActor `json:"voice_actors"`
+ Character JikanCharacterPerson `json:"character"`
+ Role string `json:"role"`
+ VoiceActors []JikanVoiceActor `json:"voice_actors"`
}
type JikanVoiceActor struct {
- MALID int `json:"mal_id"`
- URL string `json:"url"`
- Images JikanGenericImageEntity `json:"images"`
- Name string `json:"name"`
- Language string `json:"language"`
+ Person JikanCharacterPerson `json:"person"`
+ Language string `json:"language"`
}
type JikanAnimeCharacterResponse struct {
diff --git a/types/tasks.go b/types/tasks.go
index 10ba608..f86a497 100644
--- a/types/tasks.go
+++ b/types/tasks.go
@@ -6,6 +6,7 @@ type Task struct {
Name string
Interval time.Duration
Execute func() error
+ OnResume func()
LastRun time.Time
Dependencies []string
}