Skip to content

Commit a825861

Browse files
Merge pull request #152 from alvarorichard/dev
update to 1.8.1
2 parents a5fe46e + a2b6476 commit a825861

34 files changed

Lines changed: 3771 additions & 389 deletions

CHANGELOG.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,47 @@
1-
# GoAnime Release Notes - Version 1.8
1+
# GoAnime Release Notes - Version 1.8.1
22

3-
Release date: 2026-04-18
3+
Release date: 2026-04-20
44

55
## Highlights
66

7-
- **New PT-BR Source**: Added a robust new source specifically for movies, anime, series, and doramas, entirely in Brazilian Portuguese (PT-BR).
7+
- **New PT-BR Source (SuperFlix)**: Added a robust new source for movies, anime, series, and doramas, entirely in Brazilian Portuguese (PT-BR).
88
- **Jellyfin & Plex Compatibility**: Reorganized the media storage structure for movies, series, anime, and doramas, making them out-of-the-box compatible with Jellyfin and Plex.
9-
- **Download Improvements**: Enhanced downloading mechanisms for improved stability and user experience.
9+
- **Automatic Season Inference**: GoAnime now automatically detects the season number from the anime title (e.g., "Mushoku Tensei Season 2"), eliminating manual configuration and ensuring episodes are organized under the correct season.
10+
- **More Robust Downloads with Smart Fallback**: The download system has been completely revamped with intelligent source selection, automatic fallback to alternative URLs (especially for AnimeFire), and improved progress tracking for batch downloads.
11+
- **AllAnime Encrypted API Support**: Implemented AES-256-CTR decryption for AllAnime's new encrypted "tobeparsed" API responses, ensuring continued access to the AllAnime source after their API migration.
12+
- **More Stable Terminal Interface**: Critical fixes to terminal interaction prevent display issues during asynchronous operations such as downloads and updates.
1013

1114
## Features
1215

13-
- Add new PT-BR source covering movies, anime, series, and doramas.
14-
- Implement automated media organization optimized for Jellyfin and Plex playback.
16+
- Add new PT-BR source (SuperFlix) covering movies, anime, series, and doramas.
17+
- Implement automated media organization optimized for Jellyfin and Plex playback with compatible folder naming and external IDs.
18+
- Implement download-all mode and interactive selection menu for anime and TV show downloads.
19+
- Implement automatic season number inference from anime titles using patterns like "Season N" and "Nth Season", with support for English and Romaji titles via AniList.
20+
- Add intelligent AnimeFire download source resolution with preferred quality selection (best, worst, or specific resolution) and automatic candidate ordering.
21+
- Implement fallback mechanism for AnimeFire downloads: when a URL returns 404, the system automatically fetches an alternative source from the video API.
22+
- Add hierarchical progress model (`childProgress`) for batch downloads, enabling accurate tracking of total and received bytes across multiple simultaneous episodes.
23+
- Introduce console output suppression mechanism (`SuppressConsole`) during asynchronous operations to prevent interference with progress bars.
24+
- Implement episode-specific tracking keys to prevent data overlap in the database.
25+
- Implement AES-256-CTR decryption for AllAnime's new encrypted "tobeparsed" source URL blobs, with JSON parsing and regex fallback extraction.
26+
- Integrate images and Discord RPC for the SuperFlix source.
27+
28+
## Bug Fixes
29+
30+
- Fix case-sensitive comparison of source names (AllAnime, AnimeFire, AnimeDrive, Goyabu) in stream selection, preventing failures when the `Source` field had different capitalization.
31+
- Fix episode resolution for anime with selected season greater than 1, ensuring local episodes are correctly mapped within the season instead of using absolute numbering.
32+
- Fix terminal capability queries (CSI response suppression) that caused corrupted output during spinners and progress bars.
33+
- Fix `USERPROFILE` environment variable in download tests for Windows compatibility.
34+
- Ensure all sources appear in scraper search results.
35+
- Fix Windows path compatibility using `filepath.ToSlash` in media naming functions.
36+
- Fix AllAnime source URL decoding by replacing the incomplete hex substitution table with the full cipher from ani-cli's `provider_init`, covering all uppercase/lowercase letters, digits, and special characters.
37+
- Fix AllAnime `extractSourceURLs` to fall through to regex extraction when JSON parsing returns zero URLs, preventing silent failures.
38+
- Fix HTTP2 transport race conditions by replacing the shared fast client with dedicated instances.
1539

1640
## Improvements
1741

18-
- Enhance download speeds and reliability.
42+
- Refactor `RunClean` in the TUI package to encapsulate interactive components (spinners, prompts) and prevent interference with standard output.
43+
- Refactor the AllAnime scraper for improved code readability and maintainability.
44+
- Significantly expand test coverage: +1,900 lines of AllAnime scraper tests, new download regression tests, season propagation tests, and progress aggregation tests.
45+
- Optimize download performance with larger buffers and improved concurrency settings.
46+
- Enhance media type handling and sorting for PT-BR entries, including type disambiguation for FlixHQ/SFlix results.
47+
- Update dependencies: bubbletea v2.0.6, lipgloss v2.0.3, and other packages in `go.mod`.

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ require (
2828
github.com/andybalholm/brotli v1.2.1 // indirect
2929
github.com/atotto/clipboard v0.1.4 // indirect
3030
github.com/catppuccin/go v0.3.0 // indirect
31-
github.com/charmbracelet/ultraviolet v0.0.0-20260416161146-9c68a866306c // indirect
31+
github.com/charmbracelet/ultraviolet v0.0.0-20260420095748-421e4a7fa8d7 // indirect
3232
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
3333
github.com/charmbracelet/x/exp/strings v0.1.0 // indirect
3434
github.com/charmbracelet/x/termios v0.1.1 // indirect
@@ -63,7 +63,7 @@ require (
6363
github.com/cloudflare/circl v1.6.3 // indirect
6464
github.com/davecgh/go-spew v1.1.1 // indirect
6565
github.com/gdamore/encoding v1.0.1 // indirect
66-
github.com/gdamore/tcell/v2 v2.13.8 // indirect
66+
github.com/gdamore/tcell/v2 v2.13.9 // indirect
6767
github.com/ktr0731/go-ansisgr v0.1.0 // indirect
6868
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
6969
github.com/mattn/go-runewidth v0.0.23 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex
3030
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
3131
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
3232
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
33-
github.com/charmbracelet/ultraviolet v0.0.0-20260416161146-9c68a866306c h1:a+Q3cOt8vEb6ETG/st32Qjm8R5fdI9wSKb3tqPISnoY=
34-
github.com/charmbracelet/ultraviolet v0.0.0-20260416161146-9c68a866306c/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
33+
github.com/charmbracelet/ultraviolet v0.0.0-20260420095748-421e4a7fa8d7 h1:PbFxahSfyADcQOp+7WxbeqN3wX37KA/Rk+EXOW1xS9Q=
34+
github.com/charmbracelet/ultraviolet v0.0.0-20260420095748-421e4a7fa8d7/go.mod h1:3YdTxlnV/L0bQ3VN8WOSw8doF7LZV/xawUQ4MuAPDvo=
3535
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
3636
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
3737
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
@@ -81,8 +81,8 @@ github.com/enetx/surf v1.0.198 h1:TJkyEyy5M+GnLZlGKmByeXwG7K2vv7F5L+0SgwlDu7g=
8181
github.com/enetx/surf v1.0.198/go.mod h1:BtLmZDYAny66azybFr9UdFVnwy8WRV4FTAzElsd7bvE=
8282
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
8383
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
84-
github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
85-
github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
84+
github.com/gdamore/tcell/v2 v2.13.9 h1:uI5l3DYPcFvHINKlGft+en23evOKL+dwtD21QR8ejVA=
85+
github.com/gdamore/tcell/v2 v2.13.9/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
8686
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
8787
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
8888
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=

internal/api/enhanced.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ func isStdoutTerminal() bool {
4141
// callback when no terminal is attached.
4242
func runWithSpinner(title string, action func()) {
4343
if isStdoutTerminal() {
44-
_ = spinner.New().
45-
Title(title).
46-
Type(spinner.Dots).
47-
Action(action).
48-
Run()
44+
_ = tui.RunClean(func() error {
45+
return spinner.New().
46+
Title(title).
47+
Type(spinner.Dots).
48+
Action(action).
49+
Run()
50+
})
4951
} else {
5052
action()
5153
}
@@ -440,16 +442,17 @@ func GetEpisodeStreamURL(episode *models.Episode, anime *models.Anime, quality s
440442
var sourceName string
441443

442444
// Priority 1: Check the Source field (most reliable)
443-
if anime.Source == "AllAnime" {
445+
sourceLower := strings.ToLower(anime.Source)
446+
if sourceLower == "allanime" {
444447
scraperType = scraper.AllAnimeType
445448
sourceName = "AllAnime"
446-
} else if strings.Contains(anime.Source, "AnimeFire") {
449+
} else if strings.Contains(sourceLower, "animefire") {
447450
scraperType = scraper.AnimefireType
448451
sourceName = "Animefire.io"
449-
} else if anime.Source == "AnimeDrive" {
452+
} else if sourceLower == "animedrive" {
450453
scraperType = scraper.AnimeDriveType
451454
sourceName = "AnimeDrive"
452-
} else if anime.Source == "Goyabu" {
455+
} else if sourceLower == "goyabu" {
453456
scraperType = scraper.GoyabuType
454457
sourceName = "Goyabu"
455458
} else if strings.Contains(anime.Name, "[English]") {

internal/api/providers/metadata/metadata.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ func (e *Enricher) EnrichAnime(ctx context.Context, anime *models.Anime) ([]Seas
400400
if meta.TotalEpisodes > 0 && anime.Details.Episodes == 0 {
401401
anime.Details.Episodes = meta.TotalEpisodes
402402
}
403+
if anime.CurrentSeason <= 0 {
404+
if season := inferSeasonNumber(anime.Name, meta.TitleEnglish, meta.TitleRomaji); season > 1 {
405+
anime.CurrentSeason = season
406+
util.Debug("inferred anime season from title", "season", season, "anime", anime.Name)
407+
}
408+
}
403409

404410
seasonMap := meta.SeasonMap
405411

@@ -431,6 +437,38 @@ func (e *Enricher) EnrichAnime(ctx context.Context, anime *models.Anime) ([]Seas
431437
return seasonMap, nil
432438
}
433439

440+
func inferSeasonNumber(titles ...string) int {
441+
for _, title := range titles {
442+
if season := inferSeasonNumberFromTitle(title); season > 1 {
443+
return season
444+
}
445+
}
446+
return 0
447+
}
448+
449+
func inferSeasonNumberFromTitle(title string) int {
450+
title = strings.TrimSpace(title)
451+
if title == "" {
452+
return 0
453+
}
454+
455+
patterns := []*regexp.Regexp{
456+
regexp.MustCompile(`(?i)\bseason\s+(\d+)\b`),
457+
regexp.MustCompile(`(?i)\b(\d+)\s*(?:st|nd|rd|th)?\s+season\b`),
458+
}
459+
for _, pattern := range patterns {
460+
matches := pattern.FindStringSubmatch(title)
461+
if len(matches) < 2 {
462+
continue
463+
}
464+
season, err := strconv.Atoi(matches[1])
465+
if err == nil && season > 1 {
466+
return season
467+
}
468+
}
469+
return 0
470+
}
471+
434472
// --- Internal types ---
435473

436474
type aniListResponse struct {

internal/api/providers/metadata/metadata_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,3 +413,75 @@ func TestEnrichAnime_SuperFlixFallback(t *testing.T) {
413413
t.Errorf("ep 55: got S%02dE%02d, want S02E04", s, e)
414414
}
415415
}
416+
417+
func TestInferSeasonNumberFromTitle(t *testing.T) {
418+
tests := []struct {
419+
title string
420+
want int
421+
}{
422+
{title: "JUJUTSU KAISEN Season 2", want: 2},
423+
{title: "Jujutsu Kaisen 2 Season", want: 2},
424+
{title: "Jujutsu Kaisen 2nd Season", want: 2},
425+
{title: "Black Clover", want: 0},
426+
{title: "Season 1", want: 0},
427+
}
428+
429+
for _, tc := range tests {
430+
t.Run(tc.title, func(t *testing.T) {
431+
if got := inferSeasonNumberFromTitle(tc.title); got != tc.want {
432+
t.Fatalf("inferSeasonNumberFromTitle(%q) = %d, want %d", tc.title, got, tc.want)
433+
}
434+
})
435+
}
436+
}
437+
438+
func TestEnrichAnime_JujutsuKaisenSeason2InfersCurrentSeasonFromMockedAniList(t *testing.T) {
439+
media := makeMedia(145064, 51009, "JUJUTSU KAISEN Season 2", "Jujutsu Kaisen 2nd Season", 2023, 23)
440+
relJSON := `{
441+
"edges": [{
442+
"relationType": "SEQUEL",
443+
"node": {
444+
"id": 999999,
445+
"title": {"romaji": "Jujutsu Kaisen Culling Game", "english": "JUJUTSU KAISEN Season 3"},
446+
"episodes": 23,
447+
"format": "TV",
448+
"startDate": {"year": 2026, "month": 1}
449+
}
450+
}]
451+
}`
452+
if err := json.Unmarshal([]byte(relJSON), &media.Relations); err != nil {
453+
t.Fatalf("failed to build mock relations: %v", err)
454+
}
455+
456+
mock := newMockClient()
457+
mock.addAniListResponse(media)
458+
enricher := NewEnricherWithClient(mock)
459+
anime := &models.Anime{
460+
Name: "[PT-BR] Jujutsu Kaisen 2 Season (Dublado)",
461+
URL: "https://goyabu.io/anime/jujutsu-kaisen-2-season-dublado",
462+
Source: "Goyabu",
463+
AnilistID: 145064,
464+
MalID: 51009,
465+
}
466+
467+
seasonMap, err := enricher.EnrichAnime(context.Background(), anime)
468+
if err != nil {
469+
t.Fatalf("unexpected error: %v", err)
470+
}
471+
472+
if anime.CurrentSeason != 2 {
473+
t.Fatalf("CurrentSeason = %d, want 2", anime.CurrentSeason)
474+
}
475+
if anime.OfficialTitle() != "JUJUTSU KAISEN Season 2" {
476+
t.Fatalf("OfficialTitle = %q, want JUJUTSU KAISEN Season 2", anime.OfficialTitle())
477+
}
478+
if len(seasonMap) != 2 {
479+
t.Fatalf("seasonMap len = %d, want 2: %+v", len(seasonMap), seasonMap)
480+
}
481+
if seasonMap[0].Season != 1 || seasonMap[0].StartEp != 1 || seasonMap[0].EndEp != 23 {
482+
t.Fatalf("seasonMap[0] = %+v, want season 1 range 1-23", seasonMap[0])
483+
}
484+
if seasonMap[1].Season != 2 || seasonMap[1].StartEp != 24 || seasonMap[1].EndEp != 46 {
485+
t.Fatalf("seasonMap[1] = %+v, want season 2 range 24-46", seasonMap[1])
486+
}
487+
}

0 commit comments

Comments
 (0)