171 lines
4.6 KiB
Go
171 lines
4.6 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"mal/integrations/jikan"
|
|
"mal/internal/domain"
|
|
)
|
|
|
|
type episodePartial struct {
|
|
title string
|
|
filler bool
|
|
recap bool
|
|
sub bool
|
|
dub bool
|
|
}
|
|
|
|
func titleCandidates(anime domain.Anime) []string {
|
|
out := []string{anime.Title}
|
|
if anime.TitleEnglish != "" && anime.TitleEnglish != anime.Title {
|
|
out = append(out, anime.TitleEnglish)
|
|
}
|
|
if anime.TitleJapanese != "" {
|
|
out = append(out, anime.TitleJapanese)
|
|
}
|
|
for _, syn := range anime.TitleSynonyms {
|
|
if syn != "" && syn != anime.Title && syn != anime.TitleEnglish && syn != anime.TitleJapanese {
|
|
out = append(out, syn)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func isCanonicalEpisodePayloadValid(payload domain.CanonicalEpisodeList, expectedCount int) bool {
|
|
if expectedCount <= 0 {
|
|
return providerBackedPayloadHasAvailability(payload)
|
|
}
|
|
if len(payload.Episodes) > expectedCount {
|
|
return false
|
|
}
|
|
for _, episode := range payload.Episodes {
|
|
if episode.Number <= 0 || episode.Number > expectedCount {
|
|
return false
|
|
}
|
|
}
|
|
return providerBackedPayloadHasAvailability(payload)
|
|
}
|
|
|
|
func providerBackedPayloadHasAvailability(payload domain.CanonicalEpisodeList) bool {
|
|
if payload.Source == "" || payload.Source == "jikan_fallback" || payload.Source == "legacy_disabled" {
|
|
return true
|
|
}
|
|
for _, episode := range payload.Episodes {
|
|
if !episode.HasSub && !episode.HasDub {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func mergeEpisodes(jikanEpisodes []jikan.Episode, availability domain.EpisodeAvailability, expectedCount int) []domain.CanonicalEpisode {
|
|
byNumber := map[int]episodePartial{}
|
|
providerNumbers := availableEpisodeNumbers(availability, expectedCount)
|
|
providerBacked := len(providerNumbers) > 0
|
|
|
|
for number := range providerNumbers {
|
|
mergeEpisode(&byNumber, number, func(item *episodePartial) {})
|
|
}
|
|
|
|
mergeJikanEpisodes(&byNumber, jikanEpisodes, providerNumbers, providerBacked, expectedCount)
|
|
mergeAvailability(&byNumber, availability.Sub, expectedCount, func(item *episodePartial) { item.sub = true })
|
|
mergeAvailability(&byNumber, availability.Dub, expectedCount, func(item *episodePartial) { item.dub = true })
|
|
|
|
numbers := make([]int, 0, len(byNumber))
|
|
for number := range byNumber {
|
|
numbers = append(numbers, number)
|
|
}
|
|
sort.Ints(numbers)
|
|
|
|
episodes := make([]domain.CanonicalEpisode, 0, len(numbers))
|
|
for _, number := range numbers {
|
|
item := byNumber[number]
|
|
title := item.title
|
|
if title == "" {
|
|
title = fmt.Sprintf("Episode %d", number)
|
|
}
|
|
episodes = append(episodes, domain.CanonicalEpisode{
|
|
Number: number,
|
|
Title: title,
|
|
HasSub: item.sub,
|
|
HasDub: item.dub,
|
|
SubOnly: item.sub && !item.dub,
|
|
Filler: item.filler,
|
|
Recap: item.recap,
|
|
})
|
|
}
|
|
return episodes
|
|
}
|
|
|
|
func mergeJikanEpisodes(byNumber *map[int]episodePartial, episodes []jikan.Episode, providerNumbers map[int]bool, providerBacked bool, expectedCount int) {
|
|
for i, ep := range episodes {
|
|
if exceedsExpectedCount(i+1, expectedCount) {
|
|
break
|
|
}
|
|
number, ok := jikanEpisodeNumber(ep, i)
|
|
if !ok || exceedsExpectedCount(number, expectedCount) || (providerBacked && !providerNumbers[number]) {
|
|
continue
|
|
}
|
|
mergeEpisode(byNumber, number, func(item *episodePartial) {
|
|
item.title = strings.TrimSpace(ep.Title)
|
|
item.filler = ep.Filler
|
|
item.recap = ep.Recap
|
|
})
|
|
}
|
|
}
|
|
|
|
func availableEpisodeNumbers(availability domain.EpisodeAvailability, expectedCount int) map[int]bool {
|
|
numbers := map[int]bool{}
|
|
for _, number := range availability.Sub {
|
|
if number > 0 && !exceedsExpectedCount(number, expectedCount) {
|
|
numbers[number] = true
|
|
}
|
|
}
|
|
for _, number := range availability.Dub {
|
|
if number > 0 && !exceedsExpectedCount(number, expectedCount) {
|
|
numbers[number] = true
|
|
}
|
|
}
|
|
return numbers
|
|
}
|
|
|
|
func mergeEpisode(byNumber *map[int]episodePartial, number int, update func(*episodePartial)) {
|
|
item := (*byNumber)[number]
|
|
update(&item)
|
|
(*byNumber)[number] = item
|
|
}
|
|
|
|
func mergeAvailability(byNumber *map[int]episodePartial, numbers []int, expectedCount int, update func(*episodePartial)) {
|
|
for _, number := range numbers {
|
|
if number <= 0 || exceedsExpectedCount(number, expectedCount) {
|
|
continue
|
|
}
|
|
mergeEpisode(byNumber, number, update)
|
|
}
|
|
}
|
|
|
|
func jikanEpisodeNumber(ep jikan.Episode, index int) (int, bool) {
|
|
number, err := strconv.Atoi(strings.TrimSpace(ep.Episode))
|
|
if err == nil && number > 0 {
|
|
return number, true
|
|
}
|
|
if index < 0 {
|
|
return 0, false
|
|
}
|
|
return index + 1, true
|
|
}
|
|
|
|
func exceedsExpectedCount(number int, expectedCount int) bool {
|
|
return expectedCount > 0 && number > expectedCount
|
|
}
|
|
|
|
func truncate(value string, maxLen int) string {
|
|
if len(value) <= maxLen {
|
|
return value
|
|
}
|
|
return value[:maxLen]
|
|
}
|