mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-15 07:26:02 +00:00
refactor: remove legacy offline mappings and switch to ArmSync/Jikan
This commit is contained in:
parent
548a42ffc2
commit
bd1cdeb29e
10 changed files with 48 additions and 133345 deletions
|
|
@ -1,47 +0,0 @@
|
|||
import json
|
||||
|
||||
with open('/data/data/com.termux/files/home/.gemini/tmp/plex_ani_bridge_mappings/mappings.json', 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Find Attack on Titan Season 3 Part 2 (MAL ID 38524)
|
||||
target_mal_id = 38524
|
||||
found_entry = None
|
||||
found_key = None
|
||||
|
||||
for key, value in data.items():
|
||||
mal_ids = value.get('mal_id')
|
||||
if mal_ids:
|
||||
if isinstance(mal_ids, list):
|
||||
if target_mal_id in mal_ids:
|
||||
found_entry = value
|
||||
found_key = key
|
||||
break
|
||||
elif mal_ids == target_mal_id:
|
||||
found_entry = value
|
||||
found_key = key
|
||||
break
|
||||
|
||||
print(f"Entry for MAL ID {target_mal_id}:")
|
||||
print(json.dumps({found_key: found_entry}, indent=2))
|
||||
|
||||
# Check for reverse lookup capability (IMDb -> MAL)
|
||||
print("\nChecking duplicates for IMDb IDs...")
|
||||
imdb_map = {}
|
||||
duplicates = 0
|
||||
for key, value in data.items():
|
||||
imdb_ids = value.get('imdb_id')
|
||||
if not imdb_ids:
|
||||
continue
|
||||
if not isinstance(imdb_ids, list):
|
||||
imdb_ids = [imdb_ids]
|
||||
|
||||
for imdb in imdb_ids:
|
||||
if imdb in imdb_map:
|
||||
duplicates += 1
|
||||
# print(f"Duplicate IMDb: {imdb} -> {imdb_map[imdb]} and {key}")
|
||||
else:
|
||||
imdb_map[imdb] = key
|
||||
|
||||
print(f"Total entries: {len(data)}")
|
||||
print(f"Unique IMDb IDs mapped: {len(imdb_map)}")
|
||||
print(f"Duplicate IMDb references: {duplicates}")
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import sqlite3
|
||||
conn = sqlite3.connect('/data/data/com.termux/files/home/.gemini/tmp/otaku_mappings/anime_mappings.db')
|
||||
cursor = conn.cursor()
|
||||
print("Shingeki no Kyojin entries:")
|
||||
cursor.execute("SELECT mal_id, mal_title, thetvdb_season, thetvdb_part, anime_media_episodes FROM anime WHERE mal_title LIKE '%Shingeki no Kyojin%'")
|
||||
rows = cursor.fetchall()
|
||||
for row in rows:
|
||||
print(row)
|
||||
conn.close()
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import sqlite3
|
||||
conn = sqlite3.connect('/data/data/com.termux/files/home/.gemini/tmp/otaku_mappings/anime_mappings.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
print(f"Tables: {tables}")
|
||||
for table in tables:
|
||||
table_name = table[0]
|
||||
print(f"\nSchema for {table_name}:")
|
||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||
columns = cursor.fetchall()
|
||||
for col in columns:
|
||||
print(col)
|
||||
print(f"\nFirst 5 rows of {table_name}:")
|
||||
cursor.execute(f"SELECT * FROM {table_name} LIMIT 5")
|
||||
rows = cursor.fetchall()
|
||||
for row in rows:
|
||||
print(row)
|
||||
conn.close()
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import sqlite3
|
||||
conn = sqlite3.connect('/data/data/com.termux/files/home/.gemini/tmp/otaku_mappings/anime_mappings.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT imdb_id, COUNT(*) c FROM anime WHERE imdb_id IS NOT NULL GROUP BY imdb_id HAVING c > 1 LIMIT 5")
|
||||
rows = cursor.fetchall()
|
||||
print("Duplicate IMDB IDs:")
|
||||
for row in rows:
|
||||
print(row)
|
||||
# Check details for one
|
||||
cursor.execute(f"SELECT mal_id, mal_title, thetvdb_season, anime_media_episodes FROM anime WHERE imdb_id = '{row[0]}'")
|
||||
details = cursor.fetchall()
|
||||
for d in details:
|
||||
print(f" - {d}")
|
||||
conn.close()
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import sqlite3
|
||||
conn = sqlite3.connect('/data/data/com.termux/files/home/.gemini/tmp/otaku_mappings/anime_mappings.db')
|
||||
cursor = conn.cursor()
|
||||
print("Shingeki no Kyojin Season 3 details:")
|
||||
cursor.execute("SELECT mal_id, mal_title, thetvdb_season, thetvdb_part, anime_media_episodes, global_media_episodes FROM anime WHERE mal_title LIKE 'Shingeki no Kyojin Season 3%'")
|
||||
rows = cursor.fetchall()
|
||||
for row in rows:
|
||||
print(row)
|
||||
|
||||
print("\nOne Piece details:")
|
||||
cursor.execute("SELECT mal_id, mal_title, thetvdb_season, thetvdb_part, anime_media_episodes, global_media_episodes FROM anime WHERE mal_title = 'One Piece'")
|
||||
rows = cursor.fetchall()
|
||||
for row in rows:
|
||||
print(row)
|
||||
conn.close()
|
||||
132969
src/assets/mappings.json
132969
src/assets/mappings.json
File diff suppressed because it is too large
Load diff
|
|
@ -20,9 +20,9 @@ import { MalApiService } from '../services/mal/MalApi';
|
|||
import { MalAnimeNode, MalListStatus } from '../types/mal';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { mappingService } from '../services/MappingService';
|
||||
import { logger } from '../utils/logger';
|
||||
import { MalEditModal } from '../components/mal/MalEditModal';
|
||||
import { MalSync } from '../services/mal/MalSync';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const ITEM_WIDTH = width * 0.35;
|
||||
|
|
@ -102,9 +102,8 @@ const MalLibraryScreen: React.FC = () => {
|
|||
// Requirement 8: Resolve correct Cinemata / TMDB / IMDb ID
|
||||
const malId = item.node.id;
|
||||
|
||||
// Check offline mapping first (reverse lookup)
|
||||
await mappingService.init();
|
||||
const imdbId = mappingService.getImdbIdFromMalId(malId);
|
||||
// Use MalSync API to get external IDs
|
||||
const { imdbId } = await MalSync.getIdsFromMalId(malId);
|
||||
|
||||
if (imdbId) {
|
||||
navigation.navigate('Metadata', {
|
||||
|
|
|
|||
|
|
@ -1,249 +0,0 @@
|
|||
import * as FileSystem from 'expo-file-system';
|
||||
import axios from 'axios';
|
||||
import { Asset } from 'expo-asset';
|
||||
|
||||
// We require the bundled mappings as a fallback.
|
||||
// This ensures the app works immediately upon install without internet.
|
||||
const BUNDLED_MAPPINGS = require('../assets/mappings.json');
|
||||
const MAPPINGS_FILE_URI = FileSystem.documentDirectory + 'mappings.json';
|
||||
const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/eliasbenb/PlexAniBridge-Mappings/master/mappings.json';
|
||||
|
||||
interface MappingEntry {
|
||||
anidb_id?: number;
|
||||
imdb_id?: string | string[];
|
||||
mal_id?: number | number[];
|
||||
tmdb_show_id?: number;
|
||||
tmdb_movie_id?: number | number[];
|
||||
tvdb_id?: number;
|
||||
tvdb_mappings?: { [key: string]: string };
|
||||
}
|
||||
|
||||
interface Mappings {
|
||||
[anilist_id: string]: MappingEntry;
|
||||
}
|
||||
|
||||
class MappingService {
|
||||
private mappings: Mappings = {};
|
||||
private imdbIndex: { [imdbId: string]: string[] } = {}; // Maps IMDb ID to array of AniList IDs
|
||||
private malIndex: { [malId: number]: string } = {}; // Maps MAL ID to AniList ID
|
||||
private isInitialized = false;
|
||||
|
||||
/**
|
||||
* Initialize the service. Loads mappings from local storage if available,
|
||||
* otherwise falls back to the bundled JSON.
|
||||
*/
|
||||
async init() {
|
||||
if (this.isInitialized) return;
|
||||
|
||||
try {
|
||||
const fileInfo = await FileSystem.getInfoAsync(MAPPINGS_FILE_URI);
|
||||
|
||||
if (fileInfo.exists) {
|
||||
console.log('Loading mappings from local storage...');
|
||||
const content = await FileSystem.readAsStringAsync(MAPPINGS_FILE_URI);
|
||||
this.mappings = JSON.parse(content);
|
||||
} else {
|
||||
console.log('Loading bundled mappings...');
|
||||
this.mappings = BUNDLED_MAPPINGS;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load mappings, falling back to bundled:', error);
|
||||
this.mappings = BUNDLED_MAPPINGS;
|
||||
}
|
||||
|
||||
this.buildIndex();
|
||||
this.isInitialized = true;
|
||||
console.log(`MappingService initialized with ${Object.keys(this.mappings).length} entries.`);
|
||||
|
||||
// Trigger background update
|
||||
this.checkForUpdates().catch(err => console.warn('Background mapping update failed:', err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a reverse index for fast IMDb lookups.
|
||||
*/
|
||||
private buildIndex() {
|
||||
this.imdbIndex = {};
|
||||
this.malIndex = {};
|
||||
for (const [anilistId, entry] of Object.entries(this.mappings)) {
|
||||
// IMDb Index
|
||||
if (entry.imdb_id) {
|
||||
const imdbIds = Array.isArray(entry.imdb_id) ? entry.imdb_id : [entry.imdb_id];
|
||||
for (const id of imdbIds) {
|
||||
if (!this.imdbIndex[id]) {
|
||||
this.imdbIndex[id] = [];
|
||||
}
|
||||
this.imdbIndex[id].push(anilistId);
|
||||
}
|
||||
}
|
||||
|
||||
// MAL Index
|
||||
if (entry.mal_id) {
|
||||
const malIds = Array.isArray(entry.mal_id) ? entry.mal_id : [entry.mal_id];
|
||||
for (const id of malIds) {
|
||||
this.malIndex[id] = anilistId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a MAL ID to an IMDb ID.
|
||||
* This is a reverse lookup used by Stremio services.
|
||||
*/
|
||||
getImdbIdFromMalId(malId: number): string | null {
|
||||
if (!this.isInitialized) {
|
||||
console.warn('MappingService not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
const anilistId = this.malIndex[malId];
|
||||
if (anilistId) {
|
||||
const entry = this.mappings[anilistId];
|
||||
if (entry && entry.imdb_id) {
|
||||
return Array.isArray(entry.imdb_id) ? entry.imdb_id[0] : entry.imdb_id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for updates from the GitHub repository and save to local storage.
|
||||
*/
|
||||
async checkForUpdates() {
|
||||
try {
|
||||
console.log('Checking for mapping updates...');
|
||||
const response = await axios.get(GITHUB_RAW_URL);
|
||||
|
||||
if (response.data && typeof response.data === 'object') {
|
||||
const newMappings = response.data;
|
||||
const newCount = Object.keys(newMappings).length;
|
||||
const currentCount = Object.keys(this.mappings).length;
|
||||
|
||||
// Basic sanity check: ensure we didn't download an empty or drastically smaller file
|
||||
if (newCount > 1000) {
|
||||
await FileSystem.writeAsStringAsync(MAPPINGS_FILE_URI, JSON.stringify(newMappings));
|
||||
console.log(`Mappings updated successfully. New count: ${newCount} (Old: ${currentCount})`);
|
||||
|
||||
// Optional: Hot-reload the mappings immediately?
|
||||
// For stability, usually better to wait for next app restart,
|
||||
// but we can update in memory too.
|
||||
this.mappings = newMappings;
|
||||
this.buildIndex();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to update mappings:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an IMDb ID + Season/Episode to a MAL ID.
|
||||
* Handles complex mapping logic (split seasons, episode offsets).
|
||||
*/
|
||||
getMalId(imdbId: string, season: number, episode: number): number | null {
|
||||
if (!this.isInitialized) {
|
||||
console.warn('MappingService not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
const anilistIds = this.imdbIndex[imdbId];
|
||||
if (!anilistIds || anilistIds.length === 0) return null;
|
||||
|
||||
// Iterate through all potential matches (usually just 1, but sometimes splits)
|
||||
for (const anilistId of anilistIds) {
|
||||
const entry = this.mappings[anilistId];
|
||||
if (!entry) continue;
|
||||
|
||||
// If there are no specific mappings, assumes 1:1 match if it's the only entry
|
||||
// But usually, we look for 'tvdb_mappings' (which this repo uses for seasons)
|
||||
// or 'tmdb_mappings'. This repo uses 'tvdb_mappings' for structure.
|
||||
|
||||
if (this.isMatch(entry, season, episode)) {
|
||||
return this.extractMalId(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: If we have exactly one match and no mapping rules defined, return it.
|
||||
if (anilistIds.length === 1) {
|
||||
const entry = this.mappings[anilistIds[0]];
|
||||
// Only return if it doesn't have restrictive mapping rules that failed above
|
||||
if (!entry.tvdb_mappings) {
|
||||
return this.extractMalId(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private extractMalId(entry: MappingEntry): number | null {
|
||||
if (!entry.mal_id) return null;
|
||||
if (Array.isArray(entry.mal_id)) return entry.mal_id[0];
|
||||
return entry.mal_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic to check if a specific Season/Episode fits within the entry's mapping rules.
|
||||
*/
|
||||
private isMatch(entry: MappingEntry, targetSeason: number, targetEpisode: number): boolean {
|
||||
const mappings = entry.tvdb_mappings;
|
||||
if (!mappings) {
|
||||
// If no mappings exist, we can't be sure, but usually strict matching requires them.
|
||||
// However, some entries might be simple movies or single seasons.
|
||||
return true;
|
||||
}
|
||||
|
||||
const seasonKey = `s${targetSeason}`;
|
||||
const rule = mappings[seasonKey];
|
||||
|
||||
if (rule === undefined) return false; // Season not in this entry
|
||||
|
||||
// Empty string means "matches whole season 1:1"
|
||||
if (rule === "") return true;
|
||||
|
||||
// Parse rules: "e1-e12|2,e13-"
|
||||
const parts = rule.split(',');
|
||||
for (const part of parts) {
|
||||
if (this.checkRulePart(part, targetEpisode)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private checkRulePart(part: string, targetEpisode: number): boolean {
|
||||
// Format: e{start}-e{end}|{ratio}
|
||||
// Examples: "e1-e12", "e13-", "e1", "e1-e12|2"
|
||||
|
||||
let [range, ratioStr] = part.split('|');
|
||||
|
||||
// We currently ignore ratio for *matching* purposes (just checking if it's in range)
|
||||
// Ratio is used for calculating the absolute episode number if we were converting TO absolute.
|
||||
|
||||
const [startStr, endStr] = range.split('-');
|
||||
|
||||
const start = parseInt(startStr.replace('e', ''), 10);
|
||||
|
||||
// Single episode mapping: "e5"
|
||||
if (!endStr && !range.includes('-')) {
|
||||
return targetEpisode === start;
|
||||
}
|
||||
|
||||
// Range
|
||||
if (targetEpisode < start) return false;
|
||||
|
||||
// Open ended range: "e13-"
|
||||
if (endStr === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Closed range: "e1-e12"
|
||||
if (endStr) {
|
||||
const end = parseInt(endStr.replace('e', ''), 10);
|
||||
if (targetEpisode > end) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const mappingService = new MappingService();
|
||||
|
|
@ -2,7 +2,7 @@ import { mmkvStorage } from '../mmkvStorage';
|
|||
import { MalApiService } from './MalApi';
|
||||
import { MalListStatus } from '../../types/mal';
|
||||
import { catalogService } from '../catalogService';
|
||||
import { mappingService } from '../MappingService';
|
||||
import { ArmSyncService } from './ArmSyncService';
|
||||
import axios from 'axios';
|
||||
|
||||
const MAPPING_PREFIX = 'mal_map_';
|
||||
|
|
@ -41,7 +41,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): Promise<number | null> => {
|
||||
getMalId: async (title: string, type: 'movie' | 'series' = 'series', year?: number, season?: number, imdbId?: string, episode: number = 1, releaseDate?: string): 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 normalizedTitle = title.trim().toLowerCase();
|
||||
|
|
@ -53,12 +53,22 @@ export const MalSync = {
|
|||
if (!imdbId) return null;
|
||||
}
|
||||
|
||||
// 1. Try Offline Mapping Service (Most accurate for perfect season/episode matching)
|
||||
if (imdbId && type === 'series' && season !== undefined) {
|
||||
const offlineMalId = mappingService.getMalId(imdbId, season, episode);
|
||||
if (offlineMalId) {
|
||||
console.log(`[MalSync] Found offline mapping: ${imdbId} S${season}E${episode} -> MAL ${offlineMalId}`);
|
||||
return offlineMalId;
|
||||
// 1. Try ARM + Jikan Sync (Most accurate for perfect season/episode matching)
|
||||
if (imdbId && type === 'series' && releaseDate) {
|
||||
try {
|
||||
const armResult = await ArmSyncService.resolveByDate(imdbId, releaseDate);
|
||||
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)
|
||||
// but our 'episode' arg is usually relative (e.g. 1).
|
||||
// scrobbleEpisode uses the malId returned here, and potentially the episode number from ArmSync
|
||||
// But getMalId just returns the ID.
|
||||
// Ideally, scrobbleEpisode should call ArmSyncService directly to get both ID and correct Episode number.
|
||||
// For now, we return the ID.
|
||||
return armResult.malId;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[MalSync] ARM Sync failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +137,8 @@ export const MalSync = {
|
|||
totalEpisodes: number = 0,
|
||||
type: 'movie' | 'series' = 'series',
|
||||
season?: number,
|
||||
imdbId?: string
|
||||
imdbId?: string,
|
||||
releaseDate?: string
|
||||
) => {
|
||||
try {
|
||||
// Requirement 9 & 10: Respect user settings and safety
|
||||
|
|
@ -138,7 +149,24 @@ export const MalSync = {
|
|||
return;
|
||||
}
|
||||
|
||||
const malId = await MalSync.getMalId(animeTitle, type, undefined, season, imdbId, episodeNumber);
|
||||
let malId: number | null = 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);
|
||||
if (armResult) {
|
||||
malId = armResult.malId;
|
||||
finalEpisodeNumber = armResult.episode;
|
||||
console.log(`[MalSync] ARM Resolved: ${animeTitle} -> MAL ${malId} Ep ${finalEpisodeNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to standard lookup if ARM failed or not applicable
|
||||
if (!malId) {
|
||||
malId = await MalSync.getMalId(animeTitle, type, undefined, season, imdbId, episodeNumber, releaseDate);
|
||||
}
|
||||
|
||||
if (!malId) return;
|
||||
|
||||
// Check current status on MAL to avoid overwriting completed/dropped shows
|
||||
|
|
@ -164,8 +192,8 @@ export const MalSync = {
|
|||
|
||||
// If we are just starting (ep 1) or resuming (plan_to_watch/on_hold/null), set to watching
|
||||
// Also ensure we don't downgrade episode count (though unlikely with scrobbling forward)
|
||||
if (episodeNumber <= currentEpisodesWatched) {
|
||||
console.log(`[MalSync] Skipping update for ${animeTitle}: Episode ${episodeNumber} <= Current ${currentEpisodesWatched}`);
|
||||
if (finalEpisodeNumber <= currentEpisodesWatched) {
|
||||
console.log(`[MalSync] Skipping update for ${animeTitle}: Episode ${finalEpisodeNumber} <= Current ${currentEpisodesWatched}`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -188,12 +216,12 @@ export const MalSync = {
|
|||
|
||||
// Determine Status
|
||||
let status: MalListStatus = 'watching';
|
||||
if (finalTotalEpisodes > 0 && episodeNumber >= finalTotalEpisodes) {
|
||||
if (finalTotalEpisodes > 0 && finalEpisodeNumber >= finalTotalEpisodes) {
|
||||
status = 'completed';
|
||||
}
|
||||
|
||||
await MalApiService.updateStatus(malId, status, episodeNumber);
|
||||
console.log(`[MalSync] Synced ${animeTitle} Ep ${episodeNumber}/${finalTotalEpisodes || '?'} -> MAL ID ${malId} (${status})`);
|
||||
await MalApiService.updateStatus(malId, status, finalEpisodeNumber);
|
||||
console.log(`[MalSync] Synced ${animeTitle} Ep ${finalEpisodeNumber}/${finalTotalEpisodes || '?'} -> MAL ID ${malId} (${status})`);
|
||||
} catch (e) {
|
||||
console.error('[MalSync] Scrobble failed:', e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { logger } from '../utils/logger';
|
|||
import { MalSync } from './mal/MalSync';
|
||||
import { MalAuth } from './mal/MalAuth';
|
||||
import { ArmSyncService } from './mal/ArmSyncService';
|
||||
import { mappingService } from './MappingService';
|
||||
|
||||
/**
|
||||
* WatchedService - Manages "watched" status for movies, episodes, and seasons.
|
||||
|
|
@ -17,8 +16,6 @@ class WatchedService {
|
|||
|
||||
private constructor() {
|
||||
this.traktService = TraktService.getInstance();
|
||||
// Initialize mapping service
|
||||
mappingService.init().catch(err => logger.error('[WatchedService] MappingService init failed:', err));
|
||||
}
|
||||
|
||||
public static getInstance(): WatchedService {
|
||||
|
|
@ -130,7 +127,8 @@ class WatchedService {
|
|||
0,
|
||||
'series',
|
||||
season,
|
||||
showImdbId
|
||||
showImdbId,
|
||||
releaseDate // Pass releaseDate for better matching
|
||||
).catch(err => logger.error('[WatchedService] MAL sync failed:', err));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue