Delegate desktop sync decisions to core

This commit is contained in:
KhooLy 2026-07-22 16:56:22 +03:00
parent 87d9739956
commit 73b5c5c2ec
3 changed files with 54 additions and 49 deletions

View file

@ -7,6 +7,7 @@ export const CORE_METHODS = [
'addonStoreInputType',
'addonStoreSearchPolicy',
'addonStreamsWithProvider',
'addonUptimeMatchPlan',
'airDateRefreshCandidates',
'airDateRefreshPlan',
'anilistEntriesToSync',
@ -43,11 +44,14 @@ export const CORE_METHODS = [
'catalogHasRequiredExtraExcept',
'catalogRequiresExtra',
'catalogSupportsExtra',
'chapterSkipSegments',
'classifyMetaLinks',
'clearPlaybackProgressItem',
'collectionFolderItemsPlan',
'collectionMergePlan',
'computeContinueWatchingBadges',
'contentBaseId',
'contentImdbId',
'contentMergeKeys',
'contentTraktKeysBatch',
'contentWatchedKeysBatch',
@ -83,6 +87,8 @@ export const CORE_METHODS = [
'episodeTextMatches',
'exportCollections',
'externalProviderActionPlan',
'externalSyncRefreshRetryAction',
'externalSyncResponseAction',
'extractAddonManifestUrl',
'extractAnilistIdFromLinks',
'filterDiscoverResults',
@ -92,6 +98,12 @@ export const CORE_METHODS = [
'folderPageState',
'folderSourcePagePlan',
'formatEpisodeLine',
'homeBillboardCandidateScore',
'homeBillboardEditorialMatchScore',
'homeBillboardHasBackdrop',
'homeBillboardIdentityKey',
'homeBillboardNormalizedTitle',
'homeBillboardVisualScore',
'homeHeroPlan',
'homeOverlapRatio',
'homePersonalizationScore',
@ -99,6 +111,7 @@ export const CORE_METHODS = [
'importCollections',
'isEpisodeReleased',
'isSecureRemoteUrl',
'isTmdbLikeContentId',
'isUpNextContinueWatchingItem',
'itemListDiff',
'itemListNewEntries',
@ -112,6 +125,8 @@ export const CORE_METHODS = [
'librarySortPlan',
'libraryViewPlan',
'libraryWatchlistItems',
'malWatchedUpdate',
'malWatchlistUpdate',
'manifestCandidates',
'manifestFetchDecision',
'manifestFetchPlan',
@ -142,15 +157,22 @@ export const CORE_METHODS = [
'normalizeLibraryDocument',
'normalizeManifestUrl',
'normalizePluginRepositoryUrl',
'normalizeSeriesLookupId',
'normalizeTrailerSubtitleUrl',
'nuvioAddonReconciliationPlan',
'nuvioAddonState',
'nuvioBuildLocalProfiles',
'nuvioCollectionRequest',
'nuvioExportPushPlan',
'nuvioImportMergePlan',
'nuvioLibraryItemRequest',
'nuvioLibraryMutationPlan',
'nuvioLibraryToWatchlist',
'nuvioMapCollections',
'nuvioPlaybackProgressRequest',
'nuvioProgressMetaNeeds',
'nuvioSortAddonsByPriority',
'nuvioWatchedItemsRequest',
'offlineDownloadPlan',
'optimizeHomeRows',
'orderStreamsPlan',
@ -160,6 +182,8 @@ export const CORE_METHODS = [
'parseAndPlanAddonResource',
'parseAnimeSkipResults',
'parseAniskipResults',
'parseCatalogItems',
'parseDirectStreams',
'parseEpisodeLocator',
'parseExtraArgs',
'parseIntroDbSegments',
@ -188,6 +212,7 @@ export const CORE_METHODS = [
'playerShouldSendScrobbleStart',
'playerSourceSidebarPlan',
'playerTrackState',
'pluginExecutionPlan',
'pluginManifestParse',
'pluginStreamResultsParse',
'pluginStreamResultsToStreams',
@ -245,6 +270,7 @@ export const CORE_METHODS = [
'selectStreamIndex',
'setMetadataFeedGroupEnabled',
'shouldAttemptAnimeTracking',
'simklHistoryRequest',
'simklLookupIdForType',
'simklMarkWatchedBody',
'simklMatchEpisode',
@ -255,6 +281,8 @@ export const CORE_METHODS = [
'simklWatchedToIds',
'simklWatchingToItems',
'simklWatchlistBody',
'simklWatchlistRemovalRequest',
'simklWatchlistRequest',
'simklWatchlistToItems',
'stableFeedPart',
'streamDiscoveryCacheKey',
@ -284,6 +312,7 @@ export const CORE_METHODS = [
'tmdbImageUrl',
'tmdbLanguage',
'tmdbMetaToMeta',
'tmdbNumericId',
'tmdbPeopleImagesFromCredits',
'tmdbPeopleRequestPlan',
'tmdbResolveIdHint',
@ -304,6 +333,7 @@ export const CORE_METHODS = [
'traktIdsFromContentId',
'traktMarkWatchedBody',
'traktOAuthErrorCode',
'traktPlaybackDeleteIds',
'traktPlaybackItemsDedup',
'traktPlaybackItemsToLibrary',
'traktPlaybackUrl',
@ -313,6 +343,7 @@ export const CORE_METHODS = [
'traktScrobblePlan',
'traktScrobbleUrl',
'traktShowIdFromEpisodeId',
'traktSyncItemToMeta',
'traktTokenExpiresAt',
'traktWatchedToIds',
'traktWatchlistToItems',

View file

@ -1,4 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { coreInvoke } from './engine';
export interface NuvioSession {
access_token: string;
@ -285,38 +286,25 @@ export async function nuvioReplaceAddons(
addons: Array<{ url: string; name?: string; enabled?: boolean; sort_order?: number }>,
): Promise<void> {
const current = await nuvioPullAddons(token, profileId);
const desiredByUrl = new Map(addons.map((addon, index) => [addon.url, {
url: addon.url,
name: addon.name ?? null,
enabled: addon.enabled ?? true,
sort_order: addon.sort_order ?? index,
}]));
const plan = await coreInvoke<{
deleteIds: string[];
updates: Array<{ id: string; payload: Record<string, unknown> }>;
creates: Record<string, unknown>[];
}>('nuvioAddonReconciliationPlan', JSON.stringify({ current, desired: addons, userId, profileId }));
if (!plan) return;
await Promise.all(current
.filter((addon) => !desiredByUrl.has(addon.url))
.map((addon) => nuvioRequest<void>(
await Promise.all(plan.deleteIds.map((id) => nuvioRequest<void>(
'DELETE',
`/rest/v1/addons?id=eq.${encodeURIComponent(addon.id)}&profile_id=eq.${profileId}`,
`/rest/v1/addons?id=eq.${encodeURIComponent(id)}&profile_id=eq.${profileId}`,
undefined,
token,
)));
await Promise.all([...desiredByUrl.values()].map(async (addon) => {
const existing = current.find((candidate) => candidate.url === addon.url);
if (existing) {
await patch<void>(
`/rest/v1/addons?id=eq.${encodeURIComponent(existing.id)}&profile_id=eq.${profileId}`,
addon,
token,
);
} else {
await post<void>('/rest/v1/addons', {
user_id: userId,
profile_id: profileId,
...addon,
}, token);
}
}));
await Promise.all(plan.updates.map(({ id, payload }) => patch<void>(
`/rest/v1/addons?id=eq.${encodeURIComponent(id)}&profile_id=eq.${profileId}`,
payload,
token,
)));
await Promise.all(plan.creates.map((payload) => post<void>('/rest/v1/addons', payload, token)));
}
export async function nuvioPullPlugins(token: string, profileId: number): Promise<unknown[]> {

View file

@ -1,6 +1,7 @@
import { platformFetch } from './httpClient';
import { loadActiveProfile } from './libraryOps';
import { invoke } from '@tauri-apps/api/core';
import { coreInvoke } from './engine';
export async function getOAuthClientId(service: string): Promise<string> {
try {
@ -35,28 +36,13 @@ export async function dropTraktPlaybackProgress(showId: string): Promise<void> {
const playbackItems = await res.json() as unknown[];
if (!Array.isArray(playbackItems)) return;
const deletePromises: Promise<void>[] = [];
for (const raw of playbackItems) {
const entry = raw as Record<string, unknown>;
const traktNumericId = typeof entry.id === 'number' ? entry.id : null;
if (!traktNumericId) continue;
const show = entry.show as Record<string, unknown> | undefined;
const movie = entry.movie as Record<string, unknown> | undefined;
const source = show ?? movie;
const ids = source?.ids as Record<string, unknown> | undefined;
const imdb = typeof ids?.imdb === 'string' ? ids.imdb : null;
const tmdb = ids?.tmdb != null ? `tmdb:${ids.tmdb}` : null;
if (imdb === showId || tmdb === showId) {
deletePromises.push(
platformFetch(`https://api.trakt.tv/sync/playback/${traktNumericId}`, {
method: 'DELETE',
headers,
}).then(() => undefined).catch(() => undefined),
);
}
}
const deleteIds = await coreInvoke<number[]>('traktPlaybackDeleteIds', JSON.stringify({ contentId: showId, items: playbackItems })) ?? [];
const deletePromises = deleteIds.map((id) =>
platformFetch(`https://api.trakt.tv/sync/playback/${id}`, {
method: 'DELETE',
headers,
}).then(() => undefined).catch(() => undefined),
);
await Promise.all(deletePromises);
} catch {