disable collection/group syncing

This commit is contained in:
Pas 2026-02-25 11:08:08 -07:00
parent 6e5184501f
commit 387982d2f9

View file

@ -4,42 +4,42 @@ import { useInterval } from "react-use";
import { getPosterForMedia } from "@/backend/metadata/tmdb"; import { getPosterForMedia } from "@/backend/metadata/tmdb";
import { useBookmarkStore } from "@/stores/bookmarks"; import { useBookmarkStore } from "@/stores/bookmarks";
import { useTraktAuthStore } from "@/stores/trakt/store"; import { useTraktAuthStore } from "@/stores/trakt/store";
import { modifyBookmarks } from "@/utils/bookmarkModifications";
import { traktService } from "@/utils/trakt"; import { traktService } from "@/utils/trakt";
import { TraktContentData, TraktList } from "@/utils/traktTypes"; import { TraktContentData } from "@/utils/traktTypes";
const TRAKT_SYNC_INTERVAL_MS = 5 * 60 * 1000; // 5 min const TRAKT_SYNC_INTERVAL_MS = 5 * 60 * 1000; // 5 min
const INITIAL_SYNC_DELAY_MS = 2000; // Re-sync after backend restore const INITIAL_SYNC_DELAY_MS = 2000; // Re-sync after backend restore
function listId(list: TraktList): string { // Collections/groups sync disabled for now - bookmarks only sync to watchlist
return list.ids.slug ?? String(list.ids.trakt); // import { modifyBookmarks } from "@/utils/bookmarkModifications";
} // import { TraktList } from "@/utils/traktTypes";
// function listId(list: TraktList): string {
async function findListByName( // return list.ids.slug ?? String(list.ids.trakt);
username: string, // }
groupName: string, // async function findListByName(
): Promise<TraktList | null> { // username: string,
const lists = await traktService.getLists(username); // groupName: string,
return lists.find((l) => l.name === groupName) ?? null; // ): Promise<TraktList | null> {
} // const lists = await traktService.getLists(username);
// return lists.find((l) => l.name === groupName) ?? null;
async function ensureListExists( // }
username: string, // async function ensureListExists(
groupName: string, // username: string,
): Promise<TraktList | null> { // groupName: string,
const existing = await findListByName(username, groupName); // ): Promise<TraktList | null> {
if (existing) return existing; // const existing = await findListByName(username, groupName);
try { // if (existing) return existing;
return await traktService.createList(username, groupName); // try {
} catch { // return await traktService.createList(username, groupName);
return null; // } catch {
} // return null;
} // }
// }
export function TraktBookmarkSyncer() { export function TraktBookmarkSyncer() {
const { traktUpdateQueue, removeTraktUpdateItem, replaceBookmarks } = const { traktUpdateQueue, removeTraktUpdateItem, replaceBookmarks } =
useBookmarkStore(); useBookmarkStore();
const { accessToken, user } = useTraktAuthStore(); const { accessToken } = useTraktAuthStore();
const isSyncingRef = useRef(false); const isSyncingRef = useRef(false);
const [hydrated, setHydrated] = useState(false); const [hydrated, setHydrated] = useState(false);
@ -51,9 +51,6 @@ export function TraktBookmarkSyncer() {
const queue = [...traktUpdateQueue]; const queue = [...traktUpdateQueue];
if (queue.length === 0) return; if (queue.length === 0) return;
const slug = user?.ids?.slug;
const hasLists = Boolean(slug);
for (const item of queue) { for (const item of queue) {
removeTraktUpdateItem(item.id); removeTraktUpdateItem(item.id);
@ -70,48 +67,50 @@ export function TraktBookmarkSyncer() {
if (item.action === "add") { if (item.action === "add") {
await traktService.addToWatchlist(contentData); await traktService.addToWatchlist(contentData);
if (hasLists) { // Collections sync disabled - bookmarks only sync to watchlist
const newGroups = item.group ?? []; // if (hasLists) {
const prevGroups = item.previousGroup ?? []; // const newGroups = item.group ?? [];
// const prevGroups = item.previousGroup ?? [];
// Remove from Trakt lists that the bookmark no longer belongs to // // Remove from Trakt lists that the bookmark no longer belongs to
const groupsToRemove = prevGroups.filter( // const groupsToRemove = prevGroups.filter(
(g) => !newGroups.includes(g), // (g) => !newGroups.includes(g),
); // );
for (const groupName of groupsToRemove) { // for (const groupName of groupsToRemove) {
const list = await findListByName(slug!, groupName); // const list = await findListByName(slug!, groupName);
if (list) { // if (list) {
await traktService.removeFromList(slug!, listId(list), [ // await traktService.removeFromList(slug!, listId(list), [
contentData, // contentData,
]); // ]);
} // }
} // }
// Add to Trakt lists that are new // // Add to Trakt lists that are new
const groupsToAdd = newGroups.filter( // const groupsToAdd = newGroups.filter(
(g) => !prevGroups.includes(g), // (g) => !prevGroups.includes(g),
); // );
for (const groupName of groupsToAdd) { // for (const groupName of groupsToAdd) {
const list = await ensureListExists(slug!, groupName); // const list = await ensureListExists(slug!, groupName);
if (list) { // if (list) {
await traktService.addToList(slug!, listId(list), [ // await traktService.addToList(slug!, listId(list), [
contentData, // contentData,
]); // ]);
} // }
} // }
} // }
} else if (item.action === "delete") { } else if (item.action === "delete") {
await traktService.removeFromWatchlist(contentData); await traktService.removeFromWatchlist(contentData);
if (hasLists && item.group?.length) { // Collections sync disabled - bookmarks only sync to watchlist
for (const groupName of item.group) { // if (hasLists && item.group?.length) {
const list = await findListByName(slug!, groupName); // for (const groupName of item.group) {
if (list) { // const list = await findListByName(slug!, groupName);
await traktService.removeFromList(slug!, listId(list), [ // if (list) {
contentData, // await traktService.removeFromList(slug!, listId(list), [
]); // contentData,
} // ]);
} // }
} // }
// }
} }
} catch (error) { } catch (error) {
console.error("Failed to sync bookmark to Trakt", error); console.error("Failed to sync bookmark to Trakt", error);
@ -120,19 +119,20 @@ export function TraktBookmarkSyncer() {
}; };
processQueue(); processQueue();
}, [accessToken, user?.ids?.slug, traktUpdateQueue, removeTraktUpdateItem]); }, [accessToken, traktUpdateQueue, removeTraktUpdateItem]);
// Push local bookmarks to Trakt (watchlist + groups) // Push local bookmarks to Trakt watchlist (TODO implement collections/groups sync)
const syncBookmarksToTrakt = useCallback(async () => { const syncBookmarksToTrakt = useCallback(async () => {
if (!accessToken || isSyncingRef.current) return; if (!accessToken || isSyncingRef.current) return;
const slug = useTraktAuthStore.getState().user?.ids?.slug; // const slug = useTraktAuthStore.getState().user?.ids?.slug;
if (!slug) return; // if (!slug) return;
isSyncingRef.current = true; isSyncingRef.current = true;
try { try {
if (!useTraktAuthStore.getState().user) { if (!useTraktAuthStore.getState().user) {
await traktService.getUserProfile(); await traktService.getUserProfile();
} }
const bookmarks = useBookmarkStore.getState().bookmarks; const bookmarks = useBookmarkStore.getState().bookmarks;
for (const [tmdbId, b] of Object.entries(bookmarks)) { for (const [tmdbId, b] of Object.entries(bookmarks)) {
try { try {
const contentData: TraktContentData = { const contentData: TraktContentData = {
@ -142,14 +142,15 @@ export function TraktBookmarkSyncer() {
type: b.type === "movie" ? "movie" : "show", type: b.type === "movie" ? "movie" : "show",
}; };
await traktService.addToWatchlist(contentData); await traktService.addToWatchlist(contentData);
if (b.group?.length) { // Collections sync disabled - bookmarks only sync to watchlist
for (const groupName of b.group) { // if (b.group?.length) {
const list = await ensureListExists(slug, groupName); // for (const groupName of b.group) {
if (list) { // const list = await ensureListExists(slug, groupName);
await traktService.addToList(slug, listId(list), [contentData]); // if (list) {
} // await traktService.addToList(slug, listId(list), [contentData]);
} // }
} // }
// }
} catch (err) { } catch (err) {
console.warn("Failed to push bookmark to Trakt:", tmdbId, err); console.warn("Failed to push bookmark to Trakt:", tmdbId, err);
} }
@ -192,63 +193,64 @@ export function TraktBookmarkSyncer() {
replaceBookmarks(merged); replaceBookmarks(merged);
const slug = useTraktAuthStore.getState().user?.ids?.slug; // Collections sync disabled - only watchlist is synced, no Trakt lists
if (slug) { // const slug = useTraktAuthStore.getState().user?.ids?.slug;
try { // if (slug) {
const lists = await traktService.getLists(slug); // try {
const currentBookmarks = useBookmarkStore.getState().bookmarks; // const lists = await traktService.getLists(slug);
let modifiedBookmarks = { ...currentBookmarks }; // const currentBookmarks = useBookmarkStore.getState().bookmarks;
// let modifiedBookmarks = { ...currentBookmarks };
for (const list of lists) { // for (const list of lists) {
const listTitle = list.name; // const listTitle = list.name;
const items = await traktService.getListItems(slug, listId(list)); // const items = await traktService.getListItems(slug, listId(list));
for (const li of items) { // for (const li of items) {
const media = li.movie || li.show; // const media = li.movie || li.show;
if (!media?.ids?.tmdb) continue; // if (!media?.ids?.tmdb) continue;
const tmdbId = media.ids.tmdb.toString(); // const tmdbId = media.ids.tmdb.toString();
const type = li.movie ? "movie" : "show"; // const type = li.movie ? "movie" : "show";
const bookmark = modifiedBookmarks[tmdbId]; // const bookmark = modifiedBookmarks[tmdbId];
if (!bookmark) { // if (!bookmark) {
const poster = await getPosterForMedia(tmdbId, type); // const poster = await getPosterForMedia(tmdbId, type);
modifiedBookmarks[tmdbId] = { // modifiedBookmarks[tmdbId] = {
type: type as "movie" | "show", // type: type as "movie" | "show",
title: media.title, // title: media.title,
year: media.year, // year: media.year,
poster, // poster,
updatedAt: Date.now(), // updatedAt: Date.now(),
group: [listTitle], // group: [listTitle],
}; // };
} else { // } else {
const groups = bookmark.group ?? []; // const groups = bookmark.group ?? [];
if (!groups.includes(listTitle)) { // if (!groups.includes(listTitle)) {
const { modifiedBookmarks: next } = modifyBookmarks( // const { modifiedBookmarks: next } = modifyBookmarks(
modifiedBookmarks, // modifiedBookmarks,
[tmdbId], // [tmdbId],
{ addGroups: [listTitle] }, // { addGroups: [listTitle] },
); // );
modifiedBookmarks = next; // modifiedBookmarks = next;
} // }
} // }
} // }
} // }
const hasNewBookmarks = // const hasNewBookmarks =
Object.keys(modifiedBookmarks).length !== // Object.keys(modifiedBookmarks).length !==
Object.keys(currentBookmarks).length; // Object.keys(currentBookmarks).length;
const hasGroupChanges = Object.keys(modifiedBookmarks).some( // const hasGroupChanges = Object.keys(modifiedBookmarks).some(
(id) => // (id) =>
JSON.stringify(modifiedBookmarks[id]?.group ?? []) !== // JSON.stringify(modifiedBookmarks[id]?.group ?? []) !==
JSON.stringify(currentBookmarks[id]?.group ?? []), // JSON.stringify(currentBookmarks[id]?.group ?? []),
); // );
if (hasNewBookmarks || hasGroupChanges) { // if (hasNewBookmarks || hasGroupChanges) {
replaceBookmarks(modifiedBookmarks); // replaceBookmarks(modifiedBookmarks);
} // }
} catch (listError) { // } catch (listError) {
console.warn("Failed to sync Trakt lists (groups)", listError); // console.warn("Failed to sync Trakt lists (groups)", listError);
} // }
} // }
} catch (error) { } catch (error) {
console.error("Failed to sync Trakt watchlist to local", error); console.error("Failed to sync Trakt watchlist to local", error);
} finally { } finally {
@ -257,8 +259,9 @@ export function TraktBookmarkSyncer() {
}, [accessToken, replaceBookmarks]); }, [accessToken, replaceBookmarks]);
const fullSync = useCallback(async () => { const fullSync = useCallback(async () => {
await syncWatchlistFromTrakt(); // Pull Trakt → local, merge // Push local → Trakt first so our changes reach Trakt before we pull
await syncBookmarksToTrakt(); // Push local → Trakt await syncBookmarksToTrakt();
await syncWatchlistFromTrakt(); // Then pull Trakt → local, merge
}, [syncWatchlistFromTrakt, syncBookmarksToTrakt]); }, [syncWatchlistFromTrakt, syncBookmarksToTrakt]);
// Wait for Trakt auth store to rehydrate from persist (accessToken may be null on first render) // Wait for Trakt auth store to rehydrate from persist (accessToken may be null on first render)
@ -281,13 +284,12 @@ export function TraktBookmarkSyncer() {
}; };
}, []); }, []);
// On mount (after hydration): pull immediately (Trakt → local) // On mount (after hydration): full sync (push then pull)
useEffect(() => { useEffect(() => {
if (!hydrated || !accessToken) return; if (!hydrated || !accessToken) return;
syncWatchlistFromTrakt();
const t = setTimeout(fullSync, INITIAL_SYNC_DELAY_MS); const t = setTimeout(fullSync, INITIAL_SYNC_DELAY_MS);
return () => clearTimeout(t); return () => clearTimeout(t);
}, [hydrated, accessToken, syncWatchlistFromTrakt, fullSync]); }, [hydrated, accessToken, fullSync]);
// Periodic full sync (pull + push) // Periodic full sync (pull + push)
useInterval(fullSync, TRAKT_SYNC_INTERVAL_MS); useInterval(fullSync, TRAKT_SYNC_INTERVAL_MS);