pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1import { useCallback } from "react";
2
3// import { SessionResponse } from "@/backend/accounts/auth";
4import { bookmarkMediaToInput } from "@/backend/accounts/bookmarks";
5import {
6 base64ToBuffer,
7 bytesToBase64,
8 bytesToBase64Url,
9 encryptData,
10 // keysFromMnemonic,
11 keysFromSeed,
12 signChallenge,
13} from "@/backend/accounts/crypto";
14import {
15 importBookmarks,
16 importGroupOrder,
17 importProgress,
18 importSettings,
19 importWatchHistory,
20} from "@/backend/accounts/import";
21// import { getLoginChallengeToken, loginAccount } from "@/backend/accounts/login";
22import { progressMediaItemToInputs } from "@/backend/accounts/progress";
23import {
24 getRegisterChallengeToken,
25 registerAccount,
26} from "@/backend/accounts/register";
27import { watchHistoryItemsToInputs } from "@/backend/accounts/watchHistory";
28// import { removeSession } from "@/backend/accounts/sessions";
29// import { getSettings } from "@/backend/accounts/settings";
30// import {
31// UserResponse,
32// getBookmarks,
33// getProgress,
34// getUser,
35// } from "@/backend/accounts/user";
36import { useAuthData } from "@/hooks/auth/useAuthData";
37// import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
38import { AccountWithToken, useAuthStore } from "@/stores/auth";
39import { BookmarkMediaItem, useBookmarkStore } from "@/stores/bookmarks";
40import { useGroupOrderStore } from "@/stores/groupOrder";
41import { usePreferencesStore } from "@/stores/preferences";
42import { ProgressMediaItem, useProgressStore } from "@/stores/progress";
43import { useSubtitleStore } from "@/stores/subtitles";
44import { WatchHistoryItem, useWatchHistoryStore } from "@/stores/watchHistory";
45
46export interface RegistrationData {
47 recaptchaToken?: string;
48 mnemonic: string;
49 userData: {
50 device: string;
51 profile: {
52 colorA: string;
53 colorB: string;
54 icon: string;
55 };
56 };
57}
58
59export interface LoginData {
60 mnemonic: string;
61 userData: {
62 device: string;
63 };
64}
65
66export function useMigration() {
67 const currentAccount = useAuthStore((s) => s.account);
68 const progress = useProgressStore((s) => s.items);
69 const watchHistory = useWatchHistoryStore((s) => s.items);
70 const bookmarks = useBookmarkStore((s) => s.bookmarks);
71 const groupOrder = useGroupOrderStore((s) => s.groupOrder);
72 const preferences = usePreferencesStore.getState();
73 const subtitleLanguage = useSubtitleStore((s) => s.lastSelectedLanguage);
74 const { login: userDataLogin } = useAuthData();
75
76 const migrate = useCallback(
77 async (backendUrl: string, recaptchaToken?: string) => {
78 if (!currentAccount) return;
79
80 const importData = async (
81 backendUrlInner: string,
82 account: AccountWithToken,
83 progressItems: Record<string, ProgressMediaItem>,
84 watchHistoryItems: Record<string, WatchHistoryItem>,
85 bookmarkItems: Record<string, BookmarkMediaItem>,
86 groupOrderItems: string[],
87 ) => {
88 if (
89 Object.keys(progressItems).length === 0 &&
90 Object.keys(watchHistoryItems).length === 0 &&
91 Object.keys(bookmarkItems).length === 0 &&
92 groupOrderItems.length === 0
93 ) {
94 return;
95 }
96
97 const progressInputs = Object.entries(progressItems).flatMap(
98 ([tmdbId, item]) => progressMediaItemToInputs(tmdbId, item),
99 );
100
101 const watchHistoryInputs = watchHistoryItemsToInputs(watchHistoryItems);
102
103 const bookmarkInputs = Object.entries(bookmarkItems).map(
104 ([tmdbId, item]) => bookmarkMediaToInput(tmdbId, item),
105 );
106
107 const importPromises = [
108 importProgress(backendUrlInner, account, progressInputs),
109 importWatchHistory(backendUrlInner, account, watchHistoryInputs),
110 importBookmarks(backendUrlInner, account, bookmarkInputs),
111 ];
112
113 // Import group order if it exists
114 if (groupOrderItems.length > 0) {
115 importPromises.push(
116 importGroupOrder(backendUrlInner, account, groupOrderItems),
117 );
118 }
119
120 // Import settings/preferences
121 importPromises.push(
122 importSettings(backendUrlInner, account, {
123 defaultSubtitleLanguage: subtitleLanguage || undefined,
124 febboxKey: preferences.febboxKey,
125 debridToken: preferences.debridToken,
126 debridService: preferences.debridService,
127 enableThumbnails: preferences.enableThumbnails,
128 enableAutoplay: preferences.enableAutoplay,
129 enableSkipCredits: preferences.enableSkipCredits,
130 enableDiscover: preferences.enableDiscover,
131 enableFeatured: preferences.enableFeatured,
132 enableDetailsModal: preferences.enableDetailsModal,
133 enableImageLogos: preferences.enableImageLogos,
134 enableCarouselView: preferences.enableCarouselView,
135 forceCompactEpisodeView: preferences.forceCompactEpisodeView,
136 sourceOrder:
137 preferences.sourceOrder.length > 0
138 ? preferences.sourceOrder
139 : undefined,
140 enableSourceOrder: preferences.enableSourceOrder,
141 lastSuccessfulSource: preferences.lastSuccessfulSource,
142 enableLastSuccessfulSource: preferences.enableLastSuccessfulSource,
143 embedOrder:
144 preferences.embedOrder.length > 0
145 ? preferences.embedOrder
146 : undefined,
147 enableEmbedOrder: preferences.enableEmbedOrder,
148 proxyTmdb: preferences.proxyTmdb,
149 enableLowPerformanceMode: preferences.enableLowPerformanceMode,
150 enableNativeSubtitles: preferences.enableNativeSubtitles,
151 enableHoldToBoost: preferences.enableHoldToBoost,
152 homeSectionOrder:
153 preferences.homeSectionOrder.length > 0
154 ? preferences.homeSectionOrder
155 : undefined,
156 manualSourceSelection: preferences.manualSourceSelection,
157 enableDoubleClickToSeek: preferences.enableDoubleClickToSeek,
158 enableAutoResumeOnPlaybackError:
159 preferences.enableAutoResumeOnPlaybackError,
160 }),
161 );
162
163 await Promise.all(importPromises);
164 };
165
166 const { challenge } = await getRegisterChallengeToken(
167 backendUrl,
168 recaptchaToken || undefined, // Pass undefined if token is not provided
169 );
170 const keys = await keysFromSeed(base64ToBuffer(currentAccount.seed));
171 const signature = await signChallenge(keys, challenge);
172 const registerResult = await registerAccount(backendUrl, {
173 challenge: {
174 code: challenge,
175 signature,
176 },
177 publicKey: bytesToBase64Url(keys.publicKey),
178 device: await encryptData(currentAccount.deviceName, keys.seed),
179 profile: currentAccount.profile,
180 });
181
182 const account = await userDataLogin(
183 registerResult,
184 registerResult.user,
185 registerResult.session,
186 bytesToBase64(keys.seed),
187 );
188
189 await importData(
190 backendUrl,
191 account,
192 progress,
193 watchHistory,
194 bookmarks,
195 groupOrder,
196 );
197
198 return account;
199 },
200 [
201 currentAccount,
202 userDataLogin,
203 bookmarks,
204 progress,
205 watchHistory,
206 groupOrder,
207 preferences,
208 subtitleLanguage,
209 ],
210 );
211
212 return {
213 migrate,
214 };
215}