diff --git a/src/screens/MalSettingsScreen.tsx b/src/screens/MalSettingsScreen.tsx index 79f412b3..15f1fa1c 100644 --- a/src/screens/MalSettingsScreen.tsx +++ b/src/screens/MalSettingsScreen.tsx @@ -40,6 +40,7 @@ const MalSettingsScreen: React.FC = () => { const [autoUpdateEnabled, setAutoUpdateEnabled] = useState(mmkvStorage.getBoolean('mal_auto_update') ?? true); const [autoAddEnabled, setAutoAddEnabled] = useState(mmkvStorage.getBoolean('mal_auto_add') ?? true); const [autoLibrarySyncEnabled, setAutoLibrarySyncEnabled] = useState(mmkvStorage.getBoolean('mal_auto_sync_to_library') ?? false); + const [includeNsfwEnabled, setIncludeNsfwEnabled] = useState(mmkvStorage.getBoolean('mal_include_nsfw') ?? true); const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); @@ -145,6 +146,11 @@ const MalSettingsScreen: React.FC = () => { mmkvStorage.setBoolean('mal_auto_sync_to_library', val); }; + const toggleIncludeNsfw = (val: boolean) => { + setIncludeNsfwEnabled(val); + mmkvStorage.setBoolean('mal_include_nsfw', val); + }; + return ( { /> + + + + + + Include NSFW Content + + + Allow NSFW entries to be returned when fetching your MAL list. + + + + + )} diff --git a/src/services/mal/MalApi.ts b/src/services/mal/MalApi.ts index c1667c5e..bd1c267c 100644 --- a/src/services/mal/MalApi.ts +++ b/src/services/mal/MalApi.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import { mmkvStorage } from '../mmkvStorage'; import { MalAuth } from './MalAuth'; import { MalAnimeNode, MalListStatus, MalUserListResponse, MalSearchResult, MalUser } from '../../types/mal'; @@ -39,7 +40,7 @@ export const MalApiService = { limit, offset, sort: 'list_updated_at', - nsfw: true // Ensure all content is returned + nsfw: mmkvStorage.getBoolean('mal_include_nsfw') ?? true }, }); return response.data; diff --git a/src/services/mal/MalSync.ts b/src/services/mal/MalSync.ts index 8e83b78d..6ab08956 100644 --- a/src/services/mal/MalSync.ts +++ b/src/services/mal/MalSync.ts @@ -372,34 +372,31 @@ export const MalSync = { const response = await MalApiService.getUserList('watching', 0, 50); if (!response.data || response.data.length === 0) return; - for (const item of response.data) { + const currentLibrary = await catalogService.getLibraryItems(); + const libraryIds = new Set(currentLibrary.map(l => l.id)); + + // Process items in parallel + await Promise.all(response.data.map(async (item) => { const malId = item.node.id; const { imdbId } = await MalSync.getIdsFromMalId(malId); - if (imdbId) { + if (imdbId && !libraryIds.has(imdbId)) { const type = item.node.media_type === 'movie' ? 'movie' : 'series'; + logger.log(`[MalSync] Auto-adding to library: ${item.node.title} (${imdbId})`); - // Check if already in library to avoid redundant calls - const currentLibrary = await catalogService.getLibraryItems(); - const exists = currentLibrary.some(l => l.id === imdbId); - - if (!exists) { - logger.log(`[MalSync] Auto-adding to library: ${item.node.title} (${imdbId})`); - - await catalogService.addToLibrary({ - id: imdbId, - type: type, - name: item.node.title, - poster: item.node.main_picture?.large || item.node.main_picture?.medium || '', - posterShape: 'poster', - year: item.node.start_season?.year, - description: '', - genres: [], - inLibrary: true, - }); - } + await catalogService.addToLibrary({ + id: imdbId, + type: type, + name: item.node.title, + poster: item.node.main_picture?.large || item.node.main_picture?.medium || '', + posterShape: 'poster', + year: item.node.start_season?.year, + description: '', + genres: [], + inLibrary: true, + }); } - } + })); } catch (e) { logger.error('[MalSync] syncMalWatchingToLibrary failed:', e); } diff --git a/src/services/watchedService.ts b/src/services/watchedService.ts index dc339bc1..67e3ed52 100644 --- a/src/services/watchedService.ts +++ b/src/services/watchedService.ts @@ -221,7 +221,7 @@ class WatchedService { const malToken = MalAuth.getToken(); if (malToken) { MalSync.scrobbleEpisode( - imdbId, + '', // Don't use IMDb ID as title fallback (unlikely to match) 1, 1, 'movie',