mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-24 03:27:43 +00:00
feat(mal): improved mapping logic with direct ID support, TMDB-based resolution, and same-day batch support
This commit is contained in:
parent
bd432b438c
commit
b093e4933c
8 changed files with 258 additions and 38 deletions
|
|
@ -34,7 +34,15 @@ interface SeriesContentProps {
|
|||
onSeasonChange: (season: number) => void;
|
||||
onSelectEpisode: (episode: Episode) => void;
|
||||
groupedEpisodes?: { [seasonNumber: number]: Episode[] };
|
||||
metadata?: { poster?: string; id?: string; name?: string };
|
||||
metadata?: {
|
||||
poster?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
mal_id?: number;
|
||||
external_ids?: {
|
||||
mal_id?: number;
|
||||
}
|
||||
};
|
||||
imdbId?: string; // IMDb ID for Trakt sync
|
||||
}
|
||||
|
||||
|
|
@ -574,15 +582,31 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
|
||||
// 3. Background Async Operation
|
||||
const showImdbId = imdbId || metadata.id;
|
||||
const malId = (metadata as any)?.mal_id || (metadata as any)?.external_ids?.mal_id;
|
||||
const tmdbId = (metadata as any)?.tmdbId || (metadata as any)?.external_ids?.tmdb_id;
|
||||
|
||||
// Calculate dayIndex for same-day releases
|
||||
let dayIndex = 0;
|
||||
if (episode.air_date) {
|
||||
const sameDayEpisodes = episodes
|
||||
.filter(ep => ep.air_date === episode.air_date)
|
||||
.sort((a, b) => a.episode_number - b.episode_number);
|
||||
dayIndex = sameDayEpisodes.findIndex(ep => ep.episode_number === episode.episode_number);
|
||||
if (dayIndex < 0) dayIndex = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await watchedService.markEpisodeAsWatched(
|
||||
showImdbId,
|
||||
metadata.id,
|
||||
showImdbId || 'Anime',
|
||||
metadata.id || '',
|
||||
episode.season_number,
|
||||
episode.episode_number,
|
||||
new Date(),
|
||||
episode.air_date,
|
||||
metadata?.name
|
||||
metadata?.name,
|
||||
malId,
|
||||
dayIndex,
|
||||
tmdbId
|
||||
);
|
||||
|
||||
// Reload to ensure consistency (e.g. if optimistic update was slightly off or for other effects)
|
||||
|
|
|
|||
|
|
@ -203,6 +203,21 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
episodeId: episodeId
|
||||
});
|
||||
|
||||
const currentMalId = (metadata as any)?.mal_id || (metadata as any)?.external_ids?.mal_id;
|
||||
const currentTmdbId = (metadata as any)?.tmdbId || (metadata as any)?.external_ids?.tmdb_id;
|
||||
|
||||
// Calculate dayIndex for same-day releases
|
||||
const currentDayIndex = useMemo(() => {
|
||||
if (!releaseDate || !groupedEpisodes) return 0;
|
||||
// Flatten groupedEpisodes to search for same-day releases
|
||||
const allEpisodes = Object.values(groupedEpisodes).flat();
|
||||
const sameDayEpisodes = allEpisodes
|
||||
.filter(ep => ep.air_date === releaseDate)
|
||||
.sort((a, b) => a.episode_number - b.episode_number);
|
||||
const idx = sameDayEpisodes.findIndex(ep => ep.episode_number === episode);
|
||||
return idx >= 0 ? idx : 0;
|
||||
}, [releaseDate, groupedEpisodes, episode]);
|
||||
|
||||
const watchProgress = useWatchProgress(
|
||||
id, type, episodeId,
|
||||
playerState.currentTime,
|
||||
|
|
@ -214,7 +229,10 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
imdbId,
|
||||
season,
|
||||
episode,
|
||||
releaseDate
|
||||
releaseDate,
|
||||
currentMalId,
|
||||
currentDayIndex,
|
||||
currentTmdbId
|
||||
);
|
||||
|
||||
const gestureControls = usePlayerGestureControls({
|
||||
|
|
|
|||
|
|
@ -222,6 +222,21 @@ const KSPlayerCore: React.FC = () => {
|
|||
isMounted
|
||||
});
|
||||
|
||||
const currentMalId = (metadata as any)?.mal_id || (metadata as any)?.external_ids?.mal_id;
|
||||
const currentTmdbId = (metadata as any)?.tmdbId || (metadata as any)?.external_ids?.tmdb_id;
|
||||
|
||||
// Calculate dayIndex for same-day releases
|
||||
const currentDayIndex = useMemo(() => {
|
||||
if (!releaseDate || !groupedEpisodes) return 0;
|
||||
// Flatten groupedEpisodes to search for same-day releases
|
||||
const allEpisodes = Object.values(groupedEpisodes).flat() as any[];
|
||||
const sameDayEpisodes = allEpisodes
|
||||
.filter(ep => ep.air_date === releaseDate)
|
||||
.sort((a, b) => a.episode_number - b.episode_number);
|
||||
const idx = sameDayEpisodes.findIndex(ep => ep.episode_number === episode);
|
||||
return idx >= 0 ? idx : 0;
|
||||
}, [releaseDate, groupedEpisodes, episode]);
|
||||
|
||||
const watchProgress = useWatchProgress(
|
||||
id, type, episodeId,
|
||||
currentTime,
|
||||
|
|
@ -233,7 +248,10 @@ const KSPlayerCore: React.FC = () => {
|
|||
imdbId,
|
||||
season,
|
||||
episode,
|
||||
releaseDate
|
||||
releaseDate,
|
||||
currentMalId,
|
||||
currentDayIndex,
|
||||
currentTmdbId
|
||||
);
|
||||
|
||||
// Gestures
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ export const useWatchProgress = (
|
|||
imdbId?: string,
|
||||
season?: number,
|
||||
episode?: number,
|
||||
releaseDate?: string
|
||||
releaseDate?: string,
|
||||
malId?: number,
|
||||
dayIndex?: number,
|
||||
tmdbId?: number
|
||||
) => {
|
||||
const [resumePosition, setResumePosition] = useState<number | null>(null);
|
||||
const [savedDuration, setSavedDuration] = useState<number | null>(null);
|
||||
|
|
@ -38,6 +41,20 @@ export const useWatchProgress = (
|
|||
const seasonRef = useRef(season);
|
||||
const episodeRef = useRef(episode);
|
||||
const releaseDateRef = useRef(releaseDate);
|
||||
const malIdRef = useRef(malId);
|
||||
const dayIndexRef = useRef(dayIndex);
|
||||
const tmdbIdRef = useRef(tmdbId);
|
||||
|
||||
// Sync refs
|
||||
useEffect(() => {
|
||||
imdbIdRef.current = imdbId;
|
||||
seasonRef.current = season;
|
||||
episodeRef.current = episode;
|
||||
releaseDateRef.current = releaseDate;
|
||||
malIdRef.current = malId;
|
||||
dayIndexRef.current = dayIndex;
|
||||
tmdbIdRef.current = tmdbId;
|
||||
}, [imdbId, season, episode, releaseDate, malId, dayIndex, tmdbId]);
|
||||
|
||||
// Reset scrobble flag when content changes
|
||||
useEffect(() => {
|
||||
|
|
@ -154,6 +171,9 @@ export const useWatchProgress = (
|
|||
const currentSeason = seasonRef.current;
|
||||
const currentEpisode = episodeRef.current;
|
||||
const currentReleaseDate = releaseDateRef.current;
|
||||
const currentMalId = malIdRef.current;
|
||||
const currentDayIndex = dayIndexRef.current;
|
||||
const currentTmdbId = tmdbIdRef.current;
|
||||
|
||||
if (type === 'series' && currentImdbId && currentSeason !== undefined && currentEpisode !== undefined) {
|
||||
watchedService.markEpisodeAsWatched(
|
||||
|
|
@ -162,10 +182,14 @@ export const useWatchProgress = (
|
|||
currentSeason,
|
||||
currentEpisode,
|
||||
new Date(),
|
||||
currentReleaseDate
|
||||
currentReleaseDate,
|
||||
undefined,
|
||||
currentMalId,
|
||||
currentDayIndex,
|
||||
currentTmdbId
|
||||
);
|
||||
} else if (type === 'movie' && currentImdbId) {
|
||||
watchedService.markMovieAsWatched(currentImdbId);
|
||||
watchedService.markMovieAsWatched(currentImdbId, new Date(), currentMalId, currentTmdbId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,13 @@ export interface StreamingContent {
|
|||
[key: string]: any;
|
||||
};
|
||||
imdb_id?: string;
|
||||
mal_id?: number;
|
||||
external_ids?: {
|
||||
mal_id?: number;
|
||||
imdb_id?: string;
|
||||
tmdb_id?: number;
|
||||
tvdb_id?: number;
|
||||
};
|
||||
slug?: string;
|
||||
releaseInfo?: string;
|
||||
traktSource?: 'watchlist' | 'continue-watching' | 'watched';
|
||||
|
|
|
|||
|
|
@ -31,9 +31,10 @@ export const ArmSyncService = {
|
|||
*
|
||||
* @param imdbId The IMDb ID of the show
|
||||
* @param releaseDateStr The air date of the episode (YYYY-MM-DD)
|
||||
* @param dayIndex The 0-based index of this episode among others released on the same day (optional)
|
||||
* @returns {Promise<DateSyncResult | null>} The resolved MAL ID and Episode number
|
||||
*/
|
||||
resolveByDate: async (imdbId: string, releaseDateStr: string): Promise<DateSyncResult | null> => {
|
||||
resolveByDate: async (imdbId: string, releaseDateStr: string, dayIndex?: number): Promise<DateSyncResult | null> => {
|
||||
try {
|
||||
// Basic validation: ensure date is in YYYY-MM-DD format
|
||||
if (!/^\d{4}-\d{2}-\d{2}/.test(releaseDateStr)) {
|
||||
|
|
@ -59,7 +60,54 @@ export const ArmSyncService = {
|
|||
|
||||
logger.log(`[ArmSync] Found candidates: ${malIds.join(', ')}`);
|
||||
|
||||
// 2. Validate Candidates via Jikan Dates
|
||||
// 2. Validate Candidates
|
||||
return await ArmSyncService.resolveFromMalCandidates(malIds, releaseDateStr, dayIndex);
|
||||
} catch (e) {
|
||||
logger.error('[ArmSync] Resolution failed:', e);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves the correct MyAnimeList ID and Episode Number using ARM (for ID mapping)
|
||||
* and Jikan (for Air Date matching) using a TMDB ID.
|
||||
*
|
||||
* @param tmdbId The TMDB ID of the show
|
||||
* @param releaseDateStr The air date of the episode (YYYY-MM-DD)
|
||||
* @param dayIndex The 0-based index of this episode among others released on the same day
|
||||
* @returns {Promise<DateSyncResult | null>} The resolved MAL ID and Episode number
|
||||
*/
|
||||
resolveByTmdb: async (tmdbId: number, releaseDateStr: string, dayIndex?: number): Promise<DateSyncResult | null> => {
|
||||
try {
|
||||
if (!/^\d{4}-\d{2}-\d{2}/.test(releaseDateStr)) return null;
|
||||
|
||||
logger.log(`[ArmSync] Resolving TMDB ${tmdbId} for date ${releaseDateStr}...`);
|
||||
|
||||
// 1. Fetch Candidates from ARM using TMDB ID
|
||||
const armRes = await axios.get<ArmEntry[]>(`${ARM_BASE}/tmdb`, {
|
||||
params: { id: tmdbId }
|
||||
});
|
||||
|
||||
const malIds = armRes.data
|
||||
.map(entry => entry.myanimelist)
|
||||
.filter((id): id is number => !!id);
|
||||
|
||||
if (malIds.length === 0) return null;
|
||||
|
||||
logger.log(`[ArmSync] Found candidates for TMDB ${tmdbId}: ${malIds.join(', ')}`);
|
||||
|
||||
// 2. Validate Candidates
|
||||
return await ArmSyncService.resolveFromMalCandidates(malIds, releaseDateStr, dayIndex);
|
||||
} catch (e) {
|
||||
logger.error('[ArmSync] TMDB resolution failed:', e);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper to find the correct MAL ID from a list of candidates based on date
|
||||
*/
|
||||
resolveFromMalCandidates: async (malIds: number[], releaseDateStr: string, dayIndex?: number): Promise<DateSyncResult | null> => {
|
||||
// Helper to delay (Jikan Rate Limit: 3 req/sec)
|
||||
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
|
||||
|
||||
|
|
@ -96,7 +144,7 @@ export const ArmSyncService = {
|
|||
const epsRes = await axios.get(`${JIKAN_BASE}/anime/${malId}/episodes`);
|
||||
const episodes = epsRes.data.data;
|
||||
|
||||
const matchEp = episodes.find((ep: any) => {
|
||||
const matchingEpisodes = episodes.filter((ep: any) => {
|
||||
if (!ep.aired) return false;
|
||||
try {
|
||||
const epDate = new Date(ep.aired);
|
||||
|
|
@ -113,7 +161,20 @@ export const ArmSyncService = {
|
|||
}
|
||||
});
|
||||
|
||||
if (matchEp) {
|
||||
if (matchingEpisodes.length > 0) {
|
||||
// Sort matching episodes by their mal_id to ensure consistent ordering
|
||||
matchingEpisodes.sort((a: any, b: any) => a.mal_id - b.mal_id);
|
||||
|
||||
let matchEp = matchingEpisodes[0];
|
||||
|
||||
// If multiple episodes match the same day, use dayIndex to pick the correct one
|
||||
if (matchingEpisodes.length > 1 && dayIndex !== undefined) {
|
||||
// If the dayIndex is within bounds, pick it. Otherwise, pick the last one.
|
||||
const idx = Math.min(dayIndex, matchingEpisodes.length - 1);
|
||||
matchEp = matchingEpisodes[idx];
|
||||
logger.log(`[ArmSync] Disambiguated same-day release using dayIndex ${dayIndex} -> picked Ep #${matchEp.mal_id}`);
|
||||
}
|
||||
|
||||
logger.log(`[ArmSync] Episode resolved: #${matchEp.mal_id} (${matchEp.title})`);
|
||||
return {
|
||||
malId,
|
||||
|
|
@ -126,10 +187,6 @@ export const ArmSyncService = {
|
|||
logger.warn(`[ArmSync] Failed to check candidate ${malId}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
logger.error('[ArmSync] Resolution failed:', e);
|
||||
}
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const MalSync = {
|
|||
* Tries to find a MAL ID for a given anime title or IMDb ID.
|
||||
* Caches the result to avoid repeated API calls.
|
||||
*/
|
||||
getMalId: async (title: string, type: 'movie' | 'series' = 'series', year?: number, season?: number, imdbId?: string, episode: number = 1, releaseDate?: string): Promise<number | null> => {
|
||||
getMalId: async (title: string, type: 'movie' | 'series' = 'series', year?: number, season?: number, imdbId?: string, episode: number = 1, releaseDate?: string, dayIndex?: number, tmdbId?: number): Promise<number | null> => {
|
||||
// Safety check: Never perform a MAL search for generic placeholders or empty strings.
|
||||
// This prevents "cache poisoning" where a generic term matches a random anime.
|
||||
const cleanTitle = title.trim();
|
||||
|
|
@ -64,12 +64,25 @@ export const MalSync = {
|
|||
return cachedId;
|
||||
}
|
||||
|
||||
if (isGenericTitle && !imdbId) return null;
|
||||
if (isGenericTitle && !imdbId && !tmdbId) return null;
|
||||
|
||||
// 1. Try ARM + Jikan Sync (Most accurate for perfect season/episode matching)
|
||||
// 1. Try TMDB-based Resolution (High Accuracy)
|
||||
if (tmdbId && releaseDate) {
|
||||
try {
|
||||
const tmdbResult = await ArmSyncService.resolveByTmdb(tmdbId, releaseDate, dayIndex);
|
||||
if (tmdbResult && tmdbResult.malId) {
|
||||
console.log(`[MalSync] Found TMDB match: ${tmdbId} (${releaseDate}) -> MAL ${tmdbResult.malId}`);
|
||||
return tmdbResult.malId;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[MalSync] TMDB Sync failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try ARM + Jikan Sync (IMDb fallback)
|
||||
if (imdbId && type === 'series' && releaseDate) {
|
||||
try {
|
||||
const armResult = await ArmSyncService.resolveByDate(imdbId, releaseDate);
|
||||
const armResult = await ArmSyncService.resolveByDate(imdbId, releaseDate, dayIndex);
|
||||
if (armResult && armResult.malId) {
|
||||
console.log(`[MalSync] Found ARM match: ${imdbId} (${releaseDate}) -> MAL ${armResult.malId} Ep ${armResult.episode}`);
|
||||
// Note: ArmSyncService returns the *absolute* episode number for MAL (e.g. 76)
|
||||
|
|
@ -100,6 +113,10 @@ export const MalSync = {
|
|||
if (type === 'series' && season && season > 1) {
|
||||
// Improve search query: "Attack on Titan Season 2" usually works better than just appending
|
||||
searchQuery = `${cleanTitle} Season ${season}`;
|
||||
} else if (type === 'series' && season === 0) {
|
||||
// Improve Season 0 (Specials) lookup: "Attack on Titan Specials" or "Attack on Titan OVA"
|
||||
// We search for both to find the most likely entry
|
||||
searchQuery = `${cleanTitle} Specials`;
|
||||
}
|
||||
|
||||
const result = await MalApiService.searchAnime(searchQuery, 10);
|
||||
|
|
@ -109,6 +126,17 @@ export const MalSync = {
|
|||
// Filter by type first
|
||||
if (type === 'movie') {
|
||||
candidates = candidates.filter(r => r.node.media_type === 'movie');
|
||||
} else if (season === 0) {
|
||||
// For Season 0, prioritize specials, ovas, and onas
|
||||
candidates = candidates.filter(r => r.node.media_type === 'special' || r.node.media_type === 'ova' || r.node.media_type === 'ona');
|
||||
if (candidates.length === 0) {
|
||||
// If no specific special types found, fallback to anything containing "Special" or "OVA" in title
|
||||
candidates = result.data.filter(r =>
|
||||
r.node.title.toLowerCase().includes('special') ||
|
||||
r.node.title.toLowerCase().includes('ova') ||
|
||||
r.node.title.toLowerCase().includes('ona')
|
||||
);
|
||||
}
|
||||
} else {
|
||||
candidates = candidates.filter(r => r.node.media_type === 'tv' || r.node.media_type === 'ona' || r.node.media_type === 'special' || r.node.media_type === 'ova');
|
||||
}
|
||||
|
|
@ -150,7 +178,10 @@ export const MalSync = {
|
|||
type: 'movie' | 'series' = 'series',
|
||||
season?: number,
|
||||
imdbId?: string,
|
||||
releaseDate?: string
|
||||
releaseDate?: string,
|
||||
providedMalId?: number, // Optional: skip lookup if already known
|
||||
dayIndex?: number, // 0-based index of episode in a same-day release batch
|
||||
tmdbId?: number
|
||||
) => {
|
||||
try {
|
||||
// Requirement 9 & 10: Respect user settings and safety
|
||||
|
|
@ -161,12 +192,22 @@ export const MalSync = {
|
|||
return;
|
||||
}
|
||||
|
||||
let malId: number | null = null;
|
||||
let malId: number | null = providedMalId || null;
|
||||
let finalEpisodeNumber = episodeNumber;
|
||||
|
||||
// Try ARM Sync first to get exact MAL ID and absolute episode number
|
||||
if (imdbId && type === 'series' && releaseDate) {
|
||||
const armResult = await ArmSyncService.resolveByDate(imdbId, releaseDate);
|
||||
// Strategy 1: TMDB-based Resolution (High Accuracy for Specials)
|
||||
if (!malId && tmdbId && releaseDate) {
|
||||
const tmdbResult = await ArmSyncService.resolveByTmdb(tmdbId, releaseDate, dayIndex);
|
||||
if (tmdbResult) {
|
||||
malId = tmdbResult.malId;
|
||||
finalEpisodeNumber = tmdbResult.episode;
|
||||
console.log(`[MalSync] TMDB Resolved: ${animeTitle} -> MAL ${malId} Ep ${finalEpisodeNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: IMDb-based Resolution (Fallback)
|
||||
if (!malId && imdbId && type === 'series' && releaseDate) {
|
||||
const armResult = await ArmSyncService.resolveByDate(imdbId, releaseDate, dayIndex);
|
||||
if (armResult) {
|
||||
malId = armResult.malId;
|
||||
finalEpisodeNumber = armResult.episode;
|
||||
|
|
@ -174,9 +215,9 @@ export const MalSync = {
|
|||
}
|
||||
}
|
||||
|
||||
// Fallback to standard lookup if ARM failed or not applicable
|
||||
// Fallback to standard lookup if ARM/TMDB failed and no ID provided
|
||||
if (!malId) {
|
||||
malId = await MalSync.getMalId(animeTitle, type, undefined, season, imdbId, episodeNumber, releaseDate);
|
||||
malId = await MalSync.getMalId(animeTitle, type, undefined, season, imdbId, episodeNumber, releaseDate, dayIndex, tmdbId);
|
||||
}
|
||||
|
||||
if (!malId) return;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ class WatchedService {
|
|||
*/
|
||||
public async markMovieAsWatched(
|
||||
imdbId: string,
|
||||
watchedAt: Date = new Date()
|
||||
watchedAt: Date = new Date(),
|
||||
malId?: number,
|
||||
tmdbId?: number
|
||||
): Promise<{ success: boolean; syncedToTrakt: boolean }> {
|
||||
try {
|
||||
logger.log(`[WatchedService] Marking movie as watched: ${imdbId}`);
|
||||
|
|
@ -58,7 +60,11 @@ class WatchedService {
|
|||
1,
|
||||
'movie',
|
||||
undefined,
|
||||
imdbId
|
||||
imdbId,
|
||||
undefined,
|
||||
malId,
|
||||
undefined,
|
||||
tmdbId
|
||||
).catch(err => logger.error('[WatchedService] MAL movie sync failed:', err));
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +100,10 @@ class WatchedService {
|
|||
episode: number,
|
||||
watchedAt: Date = new Date(),
|
||||
releaseDate?: string, // Optional release date for precise matching
|
||||
showTitle?: string
|
||||
showTitle?: string,
|
||||
malId?: number,
|
||||
dayIndex?: number,
|
||||
tmdbId?: number
|
||||
): Promise<{ success: boolean; syncedToTrakt: boolean }> {
|
||||
try {
|
||||
logger.log(`[WatchedService] Marking episode as watched: ${showImdbId} S${season}E${episode}`);
|
||||
|
|
@ -115,12 +124,31 @@ class WatchedService {
|
|||
|
||||
// Sync to MAL
|
||||
const malToken = MalAuth.getToken();
|
||||
if (malToken && showImdbId) {
|
||||
// Strategy 1: "Perfect Match" using ARM + Release Date
|
||||
if (malToken && (showImdbId || malId || tmdbId)) {
|
||||
// Strategy 0: Direct Match (if malId is provided)
|
||||
let synced = false;
|
||||
if (releaseDate) {
|
||||
if (malId) {
|
||||
await MalSync.scrobbleDirect(malId, episode);
|
||||
synced = true;
|
||||
}
|
||||
|
||||
// Strategy 1: TMDB-based Resolution (High Accuracy for Specials)
|
||||
if (!synced && releaseDate && tmdbId) {
|
||||
try {
|
||||
const armResult = await ArmSyncService.resolveByDate(showImdbId, releaseDate);
|
||||
const tmdbResult = await ArmSyncService.resolveByTmdb(tmdbId, releaseDate, dayIndex);
|
||||
if (tmdbResult) {
|
||||
await MalSync.scrobbleDirect(tmdbResult.malId, tmdbResult.episode);
|
||||
synced = true;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('[WatchedService] TMDB Sync failed, falling back to IMDb:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: IMDb-based Resolution (Fallback)
|
||||
if (!synced && releaseDate && showImdbId) {
|
||||
try {
|
||||
const armResult = await ArmSyncService.resolveByDate(showImdbId, releaseDate, dayIndex);
|
||||
if (armResult) {
|
||||
await MalSync.scrobbleDirect(armResult.malId, armResult.episode);
|
||||
synced = true;
|
||||
|
|
@ -130,7 +158,7 @@ class WatchedService {
|
|||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Offline Mapping Fallback
|
||||
// Strategy 3: Offline Mapping / Search Fallback
|
||||
if (!synced) {
|
||||
MalSync.scrobbleEpisode(
|
||||
showTitle || showImdbId || 'Anime',
|
||||
|
|
@ -139,7 +167,10 @@ class WatchedService {
|
|||
'series',
|
||||
season,
|
||||
showImdbId,
|
||||
releaseDate // Pass releaseDate for better matching
|
||||
releaseDate,
|
||||
malId,
|
||||
dayIndex,
|
||||
tmdbId
|
||||
).catch(err => logger.error('[WatchedService] MAL sync failed:', err));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue