mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-05-23 20:02:15 +00:00
Changes: - Add season-keyword regex (staffel, season, saison, temporada) and episode-keyword regex (folge, episode, ep.) to reliably extract the correct number regardless of name format - parseChapterNumber() now encodes season context into the sort key (season * 100000 + episode) so multi-season anime sort correctly across seasons without mixing episode numbers - Add parseEpisodeNumber() which strips season context and returns only the episode number within a season; use this for tracker updates (MAL/AniList/Kitsu) and AniSkip lookups, where the tracker entry is already season-specific - Switch updateTrackChapterRead and getAniSkipResults to parseEpisodeNumber to fix incorrect episode reporting for multi-season anime - Compile all RegExp objects as static finals instead of per-call instantiation - Refactor duplicated parse logic into a single private _parse() method with an applySeason flag
103 lines
3.4 KiB
Dart
103 lines
3.4 KiB
Dart
class ChapterRecognition {
|
|
static final _unwanted = RegExp(
|
|
r"\b(?:v|ver|vol|version|volume|season|staffel|saison|temporada|s)[^a-z]?[0-9]+",
|
|
);
|
|
static final _unwantedWhiteSpace = RegExp(r"\s(?=extra|special|omake)");
|
|
static final _seasonKeyword = RegExp(
|
|
r"\b(?:staffel|season|saison|temporada)\s*([0-9]+)",
|
|
);
|
|
static final _episodeKeyword = RegExp(
|
|
r"\b(?:folge|episode|ep\.?)\s*([0-9]+(?:\.[0-9]+)?)",
|
|
);
|
|
// lookbehind for "ch." then zero or more spaces.
|
|
static final _chNotation = RegExp(
|
|
r"(?<=ch\.) *([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?",
|
|
);
|
|
static final _bareNumber = RegExp(r"([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?");
|
|
|
|
/// Sort key for the UI list. Encodes season into the key so multi-season
|
|
/// anime sort correctly: key = season * 100000 + episode.
|
|
int parseChapterNumber(String mangaTitle, String chapterName) =>
|
|
_parse(mangaTitle, chapterName, applySeason: true);
|
|
|
|
/// Episode number within a season, for tracker updates (MAL/AniList/Kitsu)
|
|
/// and AniSkip results. The tracker entry is already season-specific,
|
|
/// so season is stripped.
|
|
int parseEpisodeNumber(String mangaTitle, String chapterName) =>
|
|
_parse(mangaTitle, chapterName, applySeason: false);
|
|
|
|
int _parse(
|
|
String mangaTitle,
|
|
String chapterName, {
|
|
required bool applySeason,
|
|
}) {
|
|
// Normalize the chapter name by removing title, punctuation noise, etc.
|
|
final name = chapterName
|
|
.toLowerCase()
|
|
.replaceAll(mangaTitle.toLowerCase(), '')
|
|
.trim()
|
|
.replaceAll(',', '.')
|
|
.replaceAll('-', '.')
|
|
.replaceAll(_unwantedWhiteSpace, '');
|
|
|
|
final season = applySeason
|
|
? int.tryParse(_seasonKeyword.firstMatch(name)?.group(1) ?? '') ?? 0
|
|
: 0;
|
|
|
|
final epMatch = _episodeKeyword.firstMatch(name);
|
|
if (epMatch != null) {
|
|
final ep = double.parse(epMatch.group(1)!).toInt();
|
|
return _withSeason(season, ep);
|
|
}
|
|
|
|
// strip season/volume noise, then look for ch. or bare number.
|
|
final stripped = name.replaceAll(_unwanted, '');
|
|
final ep = _extractNumber(stripped);
|
|
return ep != null ? _withSeason(season, ep) : 0;
|
|
}
|
|
|
|
// Combines season + episode into a sortable integer.
|
|
int _withSeason(int season, int ep) => season > 0 ? season * 100000 + ep : ep;
|
|
|
|
int? _extractNumber(String name) {
|
|
final chMatch = _chNotation.firstMatch(name);
|
|
if (chMatch != null) return _fromMatch(chMatch).toInt();
|
|
|
|
final numMatch = _bareNumber.firstMatch(name);
|
|
if (numMatch != null) return _fromMatch(numMatch).toInt();
|
|
|
|
return null;
|
|
}
|
|
|
|
double _fromMatch(Match match) {
|
|
final base = double.parse(match.group(1)!);
|
|
return base + _decimalAddition(match.group(2), match.group(3));
|
|
}
|
|
|
|
double _decimalAddition(String? decimal, String? alpha) {
|
|
if (decimal != null && decimal.isNotEmpty) return double.parse(decimal);
|
|
if (alpha != null && alpha.isNotEmpty) {
|
|
if (alpha.contains("extra")) {
|
|
return 0.99;
|
|
}
|
|
if (alpha.contains("omake")) {
|
|
return 0.98;
|
|
}
|
|
if (alpha.contains("special")) {
|
|
return 0.97;
|
|
}
|
|
final trimmedAlpha = alpha.replaceFirst('.', '');
|
|
if (trimmedAlpha.length == 1) {
|
|
return _parseAlphaPostFix(trimmedAlpha[0]);
|
|
}
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
double _parseAlphaPostFix(String alpha) {
|
|
final number = alpha.codeUnitAt(0) - ('a'.codeUnitAt(0) - 1);
|
|
if (number >= 10) return 0.0;
|
|
return number / 10.0;
|
|
}
|
|
}
|