node build fixed
This commit is contained in:
1014
seanime-2.9.10/internal/library/autodownloader/autodownloader.go
Normal file
1014
seanime-2.9.10/internal/library/autodownloader/autodownloader.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
package autodownloader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
hibiketorrent "seanime/internal/extension/hibike/torrent"
|
||||
"seanime/internal/library/anime"
|
||||
"sync"
|
||||
|
||||
"github.com/5rahim/habari"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type (
|
||||
// NormalizedTorrent is a struct built from torrent from a provider.
|
||||
// It is used to normalize the data from different providers so that it can be used by the AutoDownloader.
|
||||
NormalizedTorrent struct {
|
||||
hibiketorrent.AnimeTorrent
|
||||
ParsedData *habari.Metadata `json:"parsedData"`
|
||||
magnet string // Access using GetMagnet()
|
||||
}
|
||||
)
|
||||
|
||||
func (ad *AutoDownloader) getLatestTorrents(rules []*anime.AutoDownloaderRule) (ret []*NormalizedTorrent, err error) {
|
||||
ad.logger.Debug().Msg("autodownloader: Checking for new episodes")
|
||||
|
||||
providerExtension, ok := ad.torrentRepository.GetDefaultAnimeProviderExtension()
|
||||
if !ok {
|
||||
ad.logger.Warn().Msg("autodownloader: No default torrent provider found")
|
||||
return nil, errors.New("no default torrent provider found")
|
||||
}
|
||||
|
||||
// Get the latest torrents
|
||||
torrents, err := providerExtension.GetProvider().GetLatest()
|
||||
if err != nil {
|
||||
ad.logger.Error().Err(err).Msg("autodownloader: Failed to get latest torrents")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ad.settings.EnableEnhancedQueries {
|
||||
// Get unique release groups
|
||||
uniqueReleaseGroups := GetUniqueReleaseGroups(rules)
|
||||
// Filter the torrents
|
||||
wg := sync.WaitGroup{}
|
||||
mu := sync.Mutex{}
|
||||
wg.Add(len(uniqueReleaseGroups))
|
||||
|
||||
for _, releaseGroup := range uniqueReleaseGroups {
|
||||
go func(releaseGroup string) {
|
||||
defer wg.Done()
|
||||
filteredTorrents, err := providerExtension.GetProvider().Search(hibiketorrent.AnimeSearchOptions{
|
||||
Media: hibiketorrent.Media{},
|
||||
Query: releaseGroup,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
torrents = append(torrents, filteredTorrents...)
|
||||
mu.Unlock()
|
||||
}(releaseGroup)
|
||||
}
|
||||
wg.Wait()
|
||||
// Remove duplicates
|
||||
torrents = lo.UniqBy(torrents, func(t *hibiketorrent.AnimeTorrent) string {
|
||||
return t.Name
|
||||
})
|
||||
}
|
||||
|
||||
// Normalize the torrents
|
||||
ret = make([]*NormalizedTorrent, 0, len(torrents))
|
||||
for _, t := range torrents {
|
||||
parsedData := habari.Parse(t.Name)
|
||||
ret = append(ret, &NormalizedTorrent{
|
||||
AnimeTorrent: *t,
|
||||
ParsedData: parsedData,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetMagnet returns the magnet link for the torrent.
|
||||
func (t *NormalizedTorrent) GetMagnet(providerExtension hibiketorrent.AnimeProvider) (string, error) {
|
||||
if t.magnet == "" {
|
||||
magnet, err := providerExtension.GetTorrentMagnetLink(&t.AnimeTorrent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
t.magnet = magnet
|
||||
return t.magnet, nil
|
||||
}
|
||||
return t.magnet, nil
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package autodownloader
|
||||
|
||||
import (
|
||||
"github.com/5rahim/habari"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/api/metadata"
|
||||
"seanime/internal/database/models"
|
||||
"seanime/internal/library/anime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComparison(t *testing.T) {
|
||||
ad := AutoDownloader{
|
||||
metadataProvider: metadata.GetMockProvider(t),
|
||||
settings: &models.AutoDownloaderSettings{
|
||||
EnableSeasonCheck: true,
|
||||
},
|
||||
}
|
||||
name1 := "[Oshi no Ko] 2nd Season"
|
||||
name2 := "Oshi no Ko Season 2"
|
||||
aniListEntry := &anilist.AnimeListEntry{
|
||||
Media: &anilist.BaseAnime{
|
||||
ID: 166531,
|
||||
Title: &anilist.BaseAnime_Title{
|
||||
Romaji: &name1,
|
||||
English: &name2,
|
||||
},
|
||||
Episodes: lo.ToPtr(13),
|
||||
Format: lo.ToPtr(anilist.MediaFormatTv),
|
||||
},
|
||||
}
|
||||
|
||||
rule := &anime.AutoDownloaderRule{
|
||||
MediaId: 166531,
|
||||
ReleaseGroups: []string{"SubsPlease", "Erai-raws"},
|
||||
Resolutions: []string{"1080p"},
|
||||
TitleComparisonType: "likely",
|
||||
EpisodeType: "recent",
|
||||
EpisodeNumbers: []int{3}, // ignored
|
||||
Destination: "/data/seanime/library/[Oshi no Ko] 2nd Season",
|
||||
ComparisonTitle: "[Oshi no Ko] 2nd Season",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
torrentName string
|
||||
succeedTitleComparison bool
|
||||
succeedSeasonAndEpisodeMatch bool
|
||||
enableSeasonCheck bool
|
||||
}{
|
||||
{
|
||||
torrentName: "[Erai-raws] Oshi no Ko 2nd Season - 03 [720p][Multiple Subtitle] [ENG][FRE]",
|
||||
succeedTitleComparison: true,
|
||||
succeedSeasonAndEpisodeMatch: true,
|
||||
enableSeasonCheck: true,
|
||||
},
|
||||
{
|
||||
torrentName: "[SubsPlease] Oshi no Ko - 16 (1080p)",
|
||||
succeedTitleComparison: true,
|
||||
succeedSeasonAndEpisodeMatch: true,
|
||||
enableSeasonCheck: true,
|
||||
},
|
||||
{
|
||||
torrentName: "[Erai-raws] Oshi no Ko 3rd Season - 03 [720p][Multiple Subtitle] [ENG][FRE]",
|
||||
succeedTitleComparison: true,
|
||||
succeedSeasonAndEpisodeMatch: false,
|
||||
enableSeasonCheck: true,
|
||||
},
|
||||
{
|
||||
torrentName: "[Erai-raws] Oshi no Ko 2nd Season - 03 [720p][Multiple Subtitle] [ENG][FRE]",
|
||||
succeedTitleComparison: true,
|
||||
succeedSeasonAndEpisodeMatch: true,
|
||||
enableSeasonCheck: false,
|
||||
},
|
||||
{
|
||||
torrentName: "[SubsPlease] Oshi no Ko - 16 (1080p)",
|
||||
succeedTitleComparison: true,
|
||||
succeedSeasonAndEpisodeMatch: true,
|
||||
enableSeasonCheck: false,
|
||||
},
|
||||
{
|
||||
torrentName: "[Erai-raws] Oshi no Ko 3rd Season - 03 [720p][Multiple Subtitle] [ENG][FRE]",
|
||||
succeedTitleComparison: true,
|
||||
succeedSeasonAndEpisodeMatch: true,
|
||||
enableSeasonCheck: false,
|
||||
},
|
||||
}
|
||||
|
||||
lfw := anime.NewLocalFileWrapper([]*anime.LocalFile{
|
||||
{
|
||||
Path: "/data/seanime/library/[Oshi no Ko] 2nd Season/[SubsPlease] Oshi no Ko - 12 (1080p).mkv",
|
||||
Name: "Oshi no Ko - 12 (1080p).mkv",
|
||||
ParsedData: &anime.LocalFileParsedData{
|
||||
Original: "Oshi no Ko - 12 (1080p).mkv",
|
||||
Title: "Oshi no Ko",
|
||||
ReleaseGroup: "SubsPlease",
|
||||
},
|
||||
ParsedFolderData: []*anime.LocalFileParsedData{
|
||||
{
|
||||
Original: "[Oshi no Ko] 2nd Season",
|
||||
Title: "[Oshi no Ko]",
|
||||
},
|
||||
},
|
||||
Metadata: &anime.LocalFileMetadata{
|
||||
Episode: 1,
|
||||
AniDBEpisode: "1",
|
||||
Type: "main",
|
||||
},
|
||||
MediaId: 166531,
|
||||
},
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.torrentName, func(t *testing.T) {
|
||||
|
||||
ad.settings.EnableSeasonCheck = tt.enableSeasonCheck
|
||||
|
||||
p := habari.Parse(tt.torrentName)
|
||||
if tt.succeedTitleComparison {
|
||||
require.True(t, ad.isTitleMatch(p, tt.torrentName, rule, aniListEntry))
|
||||
} else {
|
||||
require.False(t, ad.isTitleMatch(p, tt.torrentName, rule, aniListEntry))
|
||||
}
|
||||
lfwe, ok := lfw.GetLocalEntryById(166531)
|
||||
require.True(t, ok)
|
||||
_, ok = ad.isSeasonAndEpisodeMatch(p, rule, aniListEntry, lfwe, []*models.AutoDownloaderItem{})
|
||||
if tt.succeedSeasonAndEpisodeMatch {
|
||||
require.True(t, ok)
|
||||
} else {
|
||||
require.False(t, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestComparison2(t *testing.T) {
|
||||
ad := AutoDownloader{
|
||||
metadataProvider: metadata.GetMockProvider(t),
|
||||
settings: &models.AutoDownloaderSettings{
|
||||
EnableSeasonCheck: true,
|
||||
},
|
||||
}
|
||||
name1 := "DANDADAN"
|
||||
name2 := "Dandadan"
|
||||
aniListEntry := &anilist.AnimeListEntry{
|
||||
Media: &anilist.BaseAnime{
|
||||
Title: &anilist.BaseAnime_Title{
|
||||
Romaji: &name1,
|
||||
English: &name2,
|
||||
},
|
||||
Episodes: lo.ToPtr(12),
|
||||
Status: lo.ToPtr(anilist.MediaStatusFinished),
|
||||
Format: lo.ToPtr(anilist.MediaFormatTv),
|
||||
},
|
||||
}
|
||||
|
||||
rule := &anime.AutoDownloaderRule{
|
||||
MediaId: 166531,
|
||||
ReleaseGroups: []string{},
|
||||
Resolutions: []string{"1080p"},
|
||||
TitleComparisonType: "likely",
|
||||
EpisodeType: "recent",
|
||||
EpisodeNumbers: []int{},
|
||||
Destination: "/data/seanime/library/Dandadan",
|
||||
ComparisonTitle: "Dandadan",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
torrentName string
|
||||
succeedAdditionalTermsMatch bool
|
||||
ruleAdditionalTerms []string
|
||||
}{
|
||||
{
|
||||
torrentName: "[Anime Time] Dandadan - 04 [Dual Audio][1080p][HEVC 10bit x265][AAC][Multi Sub] [Weekly]",
|
||||
ruleAdditionalTerms: []string{},
|
||||
succeedAdditionalTermsMatch: true,
|
||||
},
|
||||
{
|
||||
torrentName: "[Anime Time] Dandadan - 04 [Dual Audio][1080p][HEVC 10bit x265][AAC][Multi Sub] [Weekly]",
|
||||
ruleAdditionalTerms: []string{
|
||||
"H265,H.265, H 265,x265",
|
||||
"10bit,10-bit,10 bit",
|
||||
},
|
||||
succeedAdditionalTermsMatch: true,
|
||||
},
|
||||
{
|
||||
torrentName: "[Raze] Dandadan - 04 x265 10bit 1080p 143.8561fps.mkv",
|
||||
ruleAdditionalTerms: []string{
|
||||
"H265,H.265, H 265,x265",
|
||||
"10bit,10-bit,10 bit",
|
||||
},
|
||||
succeedAdditionalTermsMatch: true,
|
||||
},
|
||||
//{ // DEVNOTE: Doesn't pass because of title
|
||||
// torrentName: "[Sokudo] DAN DA DAN | Dandadan - S01E03 [1080p EAC-3 AV1][Dual Audio] (weekly)",
|
||||
// ruleAdditionalTerms: []string{
|
||||
// "H265,H.265, H 265,x265",
|
||||
// "10bit,10-bit,10 bit",
|
||||
// },
|
||||
// succeedAdditionalTermsMatch: false,
|
||||
//},
|
||||
{
|
||||
torrentName: "[Raze] Dandadan - 04 x265 10bit 1080p 143.8561fps.mkv",
|
||||
ruleAdditionalTerms: []string{
|
||||
"H265,H.265, H 265,x265",
|
||||
"10bit,10-bit,10 bit",
|
||||
"AAC",
|
||||
},
|
||||
succeedAdditionalTermsMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.torrentName, func(t *testing.T) {
|
||||
|
||||
rule.AdditionalTerms = tt.ruleAdditionalTerms
|
||||
|
||||
ok := ad.isTitleMatch(habari.Parse(tt.torrentName), tt.torrentName, rule, aniListEntry)
|
||||
assert.True(t, ok)
|
||||
|
||||
ok = ad.isAdditionalTermsMatch(tt.torrentName, rule)
|
||||
if tt.succeedAdditionalTermsMatch {
|
||||
assert.True(t, ok)
|
||||
} else {
|
||||
assert.False(t, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestComparison3(t *testing.T) {
|
||||
ad := AutoDownloader{
|
||||
metadataProvider: metadata.GetMockProvider(t),
|
||||
settings: &models.AutoDownloaderSettings{
|
||||
EnableSeasonCheck: true,
|
||||
},
|
||||
}
|
||||
name1 := "Dandadan"
|
||||
name2 := "DAN DA DAN"
|
||||
aniListEntry := &anilist.AnimeListEntry{
|
||||
Media: &anilist.BaseAnime{
|
||||
Title: &anilist.BaseAnime_Title{
|
||||
Romaji: &name1,
|
||||
English: &name2,
|
||||
},
|
||||
Status: lo.ToPtr(anilist.MediaStatusFinished),
|
||||
Episodes: lo.ToPtr(12),
|
||||
Format: lo.ToPtr(anilist.MediaFormatTv),
|
||||
},
|
||||
}
|
||||
|
||||
rule := &anime.AutoDownloaderRule{
|
||||
MediaId: 166531,
|
||||
ReleaseGroups: []string{},
|
||||
Resolutions: []string{},
|
||||
TitleComparisonType: "likely",
|
||||
EpisodeType: "recent",
|
||||
EpisodeNumbers: []int{},
|
||||
Destination: "/data/seanime/library/Dandadan",
|
||||
ComparisonTitle: "Dandadan",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
torrentName string
|
||||
succeedTitleComparison bool
|
||||
succeedSeasonAndEpisodeMatch bool
|
||||
enableSeasonCheck bool
|
||||
}{
|
||||
{
|
||||
torrentName: "[Salieri] Zom 100 Bucket List of the Dead - S1 - BD (1080p) (HDR) [Dual Audio]",
|
||||
succeedTitleComparison: false,
|
||||
succeedSeasonAndEpisodeMatch: false,
|
||||
enableSeasonCheck: false,
|
||||
},
|
||||
}
|
||||
|
||||
lfw := anime.NewLocalFileWrapper([]*anime.LocalFile{
|
||||
{
|
||||
Path: "/data/seanime/library/Dandadan/[SubsPlease] Dandadan - 01 (1080p).mkv",
|
||||
Name: "Dandadan - 01 (1080p).mkv",
|
||||
ParsedData: &anime.LocalFileParsedData{
|
||||
Original: "Dandadan - 01 (1080p).mkv",
|
||||
Title: "Dandadan",
|
||||
ReleaseGroup: "SubsPlease",
|
||||
},
|
||||
ParsedFolderData: []*anime.LocalFileParsedData{
|
||||
{
|
||||
Original: "Dandadan",
|
||||
Title: "Dandadan",
|
||||
},
|
||||
},
|
||||
Metadata: &anime.LocalFileMetadata{
|
||||
Episode: 1,
|
||||
AniDBEpisode: "1",
|
||||
Type: "main",
|
||||
},
|
||||
MediaId: 171018,
|
||||
},
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.torrentName, func(t *testing.T) {
|
||||
|
||||
ad.settings.EnableSeasonCheck = tt.enableSeasonCheck
|
||||
|
||||
p := habari.Parse(tt.torrentName)
|
||||
if tt.succeedTitleComparison {
|
||||
require.True(t, ad.isTitleMatch(p, tt.torrentName, rule, aniListEntry))
|
||||
} else {
|
||||
require.False(t, ad.isTitleMatch(p, tt.torrentName, rule, aniListEntry))
|
||||
}
|
||||
lfwe, ok := lfw.GetLocalEntryById(171018)
|
||||
require.True(t, ok)
|
||||
_, ok = ad.isSeasonAndEpisodeMatch(p, rule, aniListEntry, lfwe, []*models.AutoDownloaderItem{})
|
||||
if tt.succeedSeasonAndEpisodeMatch {
|
||||
assert.True(t, ok)
|
||||
} else {
|
||||
assert.False(t, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
21
seanime-2.9.10/internal/library/autodownloader/helpers.go
Normal file
21
seanime-2.9.10/internal/library/autodownloader/helpers.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package autodownloader
|
||||
|
||||
import (
|
||||
"seanime/internal/library/anime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetUniqueReleaseGroups(rules []*anime.AutoDownloaderRule) []string {
|
||||
uniqueReleaseGroups := make(map[string]string)
|
||||
for _, rule := range rules {
|
||||
for _, releaseGroup := range rule.ReleaseGroups {
|
||||
// make it case-insensitive
|
||||
uniqueReleaseGroups[strings.ToLower(releaseGroup)] = releaseGroup
|
||||
}
|
||||
}
|
||||
var result []string
|
||||
for k := range uniqueReleaseGroups {
|
||||
result = append(result, k)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package autodownloader
|
||||
|
||||
import (
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/database/models"
|
||||
"seanime/internal/hook_resolver"
|
||||
"seanime/internal/library/anime"
|
||||
)
|
||||
|
||||
// AutoDownloaderRunStartedEvent is triggered when the autodownloader starts checking for new episodes.
|
||||
// Prevent default to abort the run.
|
||||
type AutoDownloaderRunStartedEvent struct {
|
||||
hook_resolver.Event
|
||||
Rules []*anime.AutoDownloaderRule `json:"rules"`
|
||||
}
|
||||
|
||||
// AutoDownloaderTorrentsFetchedEvent is triggered at the beginning of a run, when the autodownloader fetches torrents from the provider.
|
||||
type AutoDownloaderTorrentsFetchedEvent struct {
|
||||
hook_resolver.Event
|
||||
Torrents []*NormalizedTorrent `json:"torrents"`
|
||||
}
|
||||
|
||||
// AutoDownloaderMatchVerifiedEvent is triggered when a torrent is verified to follow a rule.
|
||||
// Prevent default to abort the download if the match is found.
|
||||
type AutoDownloaderMatchVerifiedEvent struct {
|
||||
hook_resolver.Event
|
||||
// Fetched torrent
|
||||
Torrent *NormalizedTorrent `json:"torrent"`
|
||||
Rule *anime.AutoDownloaderRule `json:"rule"`
|
||||
ListEntry *anilist.AnimeListEntry `json:"listEntry"`
|
||||
LocalEntry *anime.LocalFileWrapperEntry `json:"localEntry"`
|
||||
// The episode number found for the match
|
||||
// If the match failed, this will be 0
|
||||
Episode int `json:"episode"`
|
||||
// Whether the torrent matches the rule
|
||||
// Changing this value to true will trigger a download even if the match failed;
|
||||
MatchFound bool `json:"matchFound"`
|
||||
}
|
||||
|
||||
// AutoDownloaderSettingsUpdatedEvent is triggered when the autodownloader settings are updated
|
||||
type AutoDownloaderSettingsUpdatedEvent struct {
|
||||
hook_resolver.Event
|
||||
Settings *models.AutoDownloaderSettings `json:"settings"`
|
||||
}
|
||||
|
||||
// AutoDownloaderBeforeDownloadTorrentEvent is triggered when the autodownloader is about to download a torrent.
|
||||
// Prevent default to abort the download.
|
||||
type AutoDownloaderBeforeDownloadTorrentEvent struct {
|
||||
hook_resolver.Event
|
||||
Torrent *NormalizedTorrent `json:"torrent"`
|
||||
Rule *anime.AutoDownloaderRule `json:"rule"`
|
||||
Items []*models.AutoDownloaderItem `json:"items"`
|
||||
}
|
||||
|
||||
// AutoDownloaderAfterDownloadTorrentEvent is triggered when the autodownloader has downloaded a torrent.
|
||||
type AutoDownloaderAfterDownloadTorrentEvent struct {
|
||||
hook_resolver.Event
|
||||
Torrent *NormalizedTorrent `json:"torrent"`
|
||||
Rule *anime.AutoDownloaderRule `json:"rule"`
|
||||
}
|
||||
Reference in New Issue
Block a user