@serifpersia/dango 3.6.7 → 3.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-DwHCd9yB.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-DguKjI_t.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-CQpyXTLH.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-CsH4x_I_.js → Player-DQoEQkHL.js} +2 -2
  21. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-DF6g6odd.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DFkhuk-x.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-DLytPJUj.js} +1 -1
  32. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-BGCX7mJy.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-CnfZwsds.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-SymV1J-b.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-By4Gt-R_.js} +1 -1
  47. package/client/dist/index.html +1 -1
  48. package/package.json +2 -2
  49. package/server/dist/app-auth.js +1 -157
  50. package/server/dist/app-hono.js +7 -557
  51. package/server/dist/config.js +1 -174
  52. package/server/dist/db/drizzle.js +1 -16
  53. package/server/dist/db/schema-anime.js +1 -61
  54. package/server/dist/db/schema-asmr.js +1 -13
  55. package/server/dist/db/schema-manga.js +1 -31
  56. package/server/dist/db/schema-tv.js +1 -18
  57. package/server/dist/db.js +1 -217
  58. package/server/dist/discord-gateway.js +1 -385
  59. package/server/dist/discord-rpc.js +1 -413
  60. package/server/dist/github-sync.js +1 -508
  61. package/server/dist/google.js +1 -741
  62. package/server/dist/hono/asmr.js +1 -10
  63. package/server/dist/hono/auth.js +3 -26
  64. package/server/dist/hono/music.js +1 -209
  65. package/server/dist/hono/proxy.js +36 -783
  66. package/server/dist/hono/tv.js +5 -200
  67. package/server/dist/hono/watchlist.js +2 -338
  68. package/server/dist/lib/anilist.js +26 -1458
  69. package/server/dist/lib/anischedule.js +1 -191
  70. package/server/dist/lib/insights.js +1 -185
  71. package/server/dist/lib/ipc.js +1 -23
  72. package/server/dist/lib/kitsu.js +1 -517
  73. package/server/dist/lib/mal-import.js +1 -291
  74. package/server/dist/lib/mal.js +1 -957
  75. package/server/dist/lib/migration.js +1 -310
  76. package/server/dist/lib/offline-db.js +4 -384
  77. package/server/dist/lib/potoken.js +1 -135
  78. package/server/dist/lib/recommendations.service.js +1 -216
  79. package/server/dist/lib/temp-ids.js +1 -3
  80. package/server/dist/lib/tmdb.js +1 -72
  81. package/server/dist/lib/tracker/anilist-tracker.js +8 -245
  82. package/server/dist/lib/tracker/manga-sync.service.js +1 -773
  83. package/server/dist/lib/tracker/sync-state.js +1 -21
  84. package/server/dist/lib/tracker/sync.service.js +1 -476
  85. package/server/dist/lib/translate.js +3 -79
  86. package/server/dist/lib/ump.js +1 -106
  87. package/server/dist/lib/ytmusic.js +1 -162
  88. package/server/dist/logger.js +1 -16
  89. package/server/dist/providers/manga/manga.types.js +0 -1
  90. package/server/dist/providers/provider.interface.js +0 -1
  91. package/server/dist/providers/remote-loader.js +2 -614
  92. package/server/dist/providers/remote-types.js +1 -159
  93. package/server/dist/providers/title-matching.js +1 -226
  94. package/server/dist/providers/tv.types.js +0 -1
  95. package/server/dist/rclone.js +2 -120
  96. package/server/dist/repositories/asmr.repository.js +11 -46
  97. package/server/dist/repositories/insights.repository.js +11 -25
  98. package/server/dist/repositories/library.repository.js +3 -94
  99. package/server/dist/repositories/mal-cache.repository.js +1 -52
  100. package/server/dist/repositories/manga.repository.js +14 -58
  101. package/server/dist/repositories/notifications.repository.js +6 -52
  102. package/server/dist/repositories/queue.repository.js +5 -58
  103. package/server/dist/repositories/settings.repository.js +2 -29
  104. package/server/dist/repositories/shows-meta.repository.js +3 -56
  105. package/server/dist/repositories/temp-show-ids.repository.js +3 -63
  106. package/server/dist/repositories/tv.repository.js +11 -54
  107. package/server/dist/repositories/watched-episodes.repository.js +6 -55
  108. package/server/dist/repositories/watchlist.repository.js +3 -54
  109. package/server/dist/scripts/backfill-episode-duration.js +1 -42
  110. package/server/dist/server.js +1 -310
  111. package/server/dist/shipped-defaults.js +1 -6
  112. package/server/dist/sync-payload.js +1 -91
  113. package/server/dist/sync.js +3 -647
  114. package/server/dist/trpc/context.js +1 -7
  115. package/server/dist/trpc/errors.js +1 -15
  116. package/server/dist/trpc/index.js +1 -18
  117. package/server/dist/trpc/router.js +1 -49
  118. package/server/dist/trpc/routers/asmr-library.js +1 -112
  119. package/server/dist/trpc/routers/asmr-progress.js +1 -121
  120. package/server/dist/trpc/routers/asmr.js +1 -87
  121. package/server/dist/trpc/routers/auth.js +1 -194
  122. package/server/dist/trpc/routers/continue-watching.js +2 -58
  123. package/server/dist/trpc/routers/data.js +1 -1026
  124. package/server/dist/trpc/routers/discord.js +1 -285
  125. package/server/dist/trpc/routers/discovery.js +1 -11
  126. package/server/dist/trpc/routers/insights.js +1 -14
  127. package/server/dist/trpc/routers/manga-library.js +1 -263
  128. package/server/dist/trpc/routers/manga-progress.js +1 -169
  129. package/server/dist/trpc/routers/manga.js +1 -201
  130. package/server/dist/trpc/routers/music.js +1 -655
  131. package/server/dist/trpc/routers/notifications.js +1 -80
  132. package/server/dist/trpc/routers/progress.js +1 -109
  133. package/server/dist/trpc/routers/queue.js +1 -93
  134. package/server/dist/trpc/routers/radio.js +1 -145
  135. package/server/dist/trpc/routers/settings.js +1 -96
  136. package/server/dist/trpc/routers/tracker.js +1 -213
  137. package/server/dist/trpc/routers/tv-library.js +1 -135
  138. package/server/dist/trpc/routers/tv-progress.js +1 -159
  139. package/server/dist/trpc/routers/tv.js +1 -551
  140. package/server/dist/trpc/routers/watchlist.js +2 -549
  141. package/server/dist/trpc/validation.js +1 -258
  142. package/server/dist/utils/cache.utils.js +1 -25
  143. package/server/dist/utils/cookie.utils.js +1 -13
  144. package/server/dist/utils/db-utils.js +5 -17
  145. package/server/dist/utils/env.utils.js +5 -77
  146. package/server/dist/utils/http.utils.js +1 -82
  147. package/server/dist/utils/request-context.js +1 -2
  148. package/server/dist/utils/security.utils.js +1 -76
  149. package/server/package.json +3 -2
@@ -1,291 +1 @@
1
- import { XMLParser } from 'fast-xml-parser';
2
- import { searchAnilistByTitle, isAnilistRateLimited, getShowMetaById } from './anilist.js';
3
- import { kitsuSearchAnime } from './kitsu.js';
4
- import { offlineDb } from './offline-db.js';
5
- import { SettingsRepository } from '../repositories/settings.repository.js';
6
- import { ShowsMetaRepository } from '../repositories/shows-meta.repository.js';
7
- import { performWriteTransactionAsync } from '../sync.js';
8
- import logger from '../logger.js';
9
- let activeImportStatus = {
10
- running: false,
11
- current: 0,
12
- total: 0,
13
- phase: 'done',
14
- source: null,
15
- };
16
- let activeImportCancelled = false;
17
- export function getMalImportStatus() {
18
- return activeImportStatus;
19
- }
20
- export function requestMalImportCancel() {
21
- if (!activeImportStatus.running) {
22
- return { success: true, message: 'No import running' };
23
- }
24
- activeImportCancelled = true;
25
- return { success: true, message: 'Import cancellation requested' };
26
- }
27
- export function prepareMalImport(fileBuffer, flags) {
28
- if (activeImportStatus.running) {
29
- return { ok: false, status: 409, body: { error: 'An import is already in progress' } };
30
- }
31
- if (!fileBuffer)
32
- return { ok: false, status: 400, body: { error: 'No file' } };
33
- const { erase, useOfflineDb, skipFallback } = flags;
34
- const offlineEnabled = useOfflineDb !== 'false' && useOfflineDb !== false && useOfflineDb !== '0';
35
- const shouldSkipFallback = skipFallback === 'true' || skipFallback === true || skipFallback === '1';
36
- let result;
37
- try {
38
- result = malXmlParser.parse(fileBuffer.toString());
39
- }
40
- catch {
41
- return { ok: false, status: 400, body: { error: 'Invalid XML' } };
42
- }
43
- const animeList = result?.myanimelist?.anime || [];
44
- if (animeList.length === 0) {
45
- return { ok: false, status: 400, body: { error: 'No anime found in XML' } };
46
- }
47
- activeImportCancelled = false;
48
- activeImportStatus = {
49
- running: true,
50
- current: 0,
51
- total: animeList.length,
52
- phase: 'offline',
53
- source: 'offline',
54
- };
55
- return { ok: true, import: { animeList, erase, offlineEnabled, shouldSkipFallback } };
56
- }
57
- export async function executeMalImport(db, prepared, sendEvent) {
58
- const { animeList, erase, offlineEnabled, shouldSkipFallback } = prepared;
59
- const total = animeList.length;
60
- let skippedCount = 0;
61
- const offlineShows = [];
62
- const offlineMeta = [];
63
- const fallbackQueue = [];
64
- for (const item of animeList) {
65
- const malTitle = xmlText(item.series_title);
66
- const status = mapMalStatus(xmlText(item.my_status));
67
- let matched = false;
68
- if (offlineEnabled) {
69
- const malId = parseInt(xmlText(item.series_animedb_id), 10);
70
- if (malId && !Number.isNaN(malId)) {
71
- const entry = offlineDb.getByMalId(malId);
72
- if (entry) {
73
- matched = true;
74
- const showId = String(entry.anilistId);
75
- const title = entry.title || malTitle;
76
- offlineShows.push({ id: showId, name: title, thumbnail: entry.thumbnail, status });
77
- if (entry.thumbnail || entry.type || entry.genres) {
78
- offlineMeta.push({
79
- id: showId,
80
- thumbnail: entry.thumbnail,
81
- type: entry.type,
82
- genres: entry.genres,
83
- });
84
- }
85
- activeImportStatus = {
86
- running: true,
87
- current: offlineShows.length,
88
- total,
89
- phase: 'offline',
90
- source: 'offline',
91
- title: malTitle,
92
- matchedTitle: title,
93
- status,
94
- found: true,
95
- imported: offlineShows.length,
96
- };
97
- sendEvent('progress', {
98
- current: offlineShows.length,
99
- total,
100
- title: malTitle,
101
- matchedTitle: title,
102
- status,
103
- source: 'offline',
104
- found: true,
105
- });
106
- }
107
- }
108
- }
109
- if (!matched)
110
- fallbackQueue.push(item);
111
- }
112
- if (offlineShows.length > 0 || erase) {
113
- try {
114
- await performWriteTransactionAsync(db, async (tx) => {
115
- if (erase)
116
- await SettingsRepository.clearWatchlist(tx);
117
- await SettingsRepository.upsertWatchlistBatch(tx, offlineShows);
118
- for (const meta of offlineMeta) {
119
- if (meta.thumbnail || meta.type || meta.genres) {
120
- await ShowsMetaRepository.upsert(tx, {
121
- id: meta.id,
122
- thumbnail: meta.thumbnail,
123
- type: meta.type,
124
- genres: meta.genres,
125
- });
126
- }
127
- }
128
- });
129
- }
130
- catch (dbErr) {
131
- logger.error({ err: dbErr }, 'Failed to commit offline import matches');
132
- }
133
- }
134
- let fallbackImported = 0;
135
- if (shouldSkipFallback || activeImportCancelled) {
136
- skippedCount += fallbackQueue.length;
137
- }
138
- else if (fallbackQueue.length > 0) {
139
- activeImportStatus = { ...activeImportStatus, phase: 'fallback', source: null };
140
- const BATCH_SIZE = 5;
141
- for (let i = 0; i < fallbackQueue.length; i += BATCH_SIZE) {
142
- if (activeImportCancelled) {
143
- skippedCount += fallbackQueue.length - i;
144
- break;
145
- }
146
- const batch = fallbackQueue.slice(i, i + BATCH_SIZE);
147
- const batchResults = await Promise.allSettled(batch.map((item) => searchByTitleForMal(xmlText(item.series_title))));
148
- const batchShows = [];
149
- const batchMeta = [];
150
- const metaPromises = [];
151
- batchResults.forEach((r, idx) => {
152
- const malTitle = xmlText(batch[idx].series_title);
153
- const status = mapMalStatus(xmlText(batch[idx].my_status));
154
- const current = offlineShows.length + fallbackImported + idx + 1;
155
- if (r.status === 'fulfilled' && r.value) {
156
- const show = r.value;
157
- const title = show.title?.english || show.title?.romaji || malTitle;
158
- batchShows.push({ id: String(show.id), name: title, status });
159
- activeImportStatus = {
160
- running: true,
161
- current,
162
- total,
163
- phase: 'fallback',
164
- source: show.source,
165
- title: malTitle,
166
- matchedTitle: title,
167
- status,
168
- found: true,
169
- imported: offlineShows.length + fallbackImported + 1,
170
- };
171
- sendEvent('progress', {
172
- current,
173
- total,
174
- title: malTitle,
175
- matchedTitle: title,
176
- status,
177
- source: show.source,
178
- found: true,
179
- });
180
- metaPromises.push(getShowMetaById(String(show.id))
181
- .then((meta) => {
182
- if (meta) {
183
- batchMeta.push({
184
- id: String(show.id),
185
- thumbnail: meta.thumbnail || undefined,
186
- type: meta.type || undefined,
187
- });
188
- }
189
- })
190
- .catch(() => { }));
191
- }
192
- else {
193
- skippedCount++;
194
- activeImportStatus = {
195
- running: true,
196
- current,
197
- total,
198
- phase: 'fallback',
199
- source: null,
200
- title: malTitle,
201
- matchedTitle: null,
202
- status,
203
- found: false,
204
- imported: offlineShows.length + fallbackImported,
205
- };
206
- sendEvent('progress', {
207
- current,
208
- total,
209
- title: malTitle,
210
- matchedTitle: null,
211
- status,
212
- source: null,
213
- found: false,
214
- });
215
- }
216
- });
217
- await Promise.allSettled(metaPromises);
218
- if (batchShows.length > 0) {
219
- try {
220
- await performWriteTransactionAsync(db, async (tx) => {
221
- await SettingsRepository.upsertWatchlistBatch(tx, batchShows);
222
- for (const meta of batchMeta) {
223
- if (meta.thumbnail) {
224
- await ShowsMetaRepository.upsert(tx, {
225
- id: meta.id,
226
- thumbnail: meta.thumbnail,
227
- type: meta.type,
228
- });
229
- }
230
- }
231
- });
232
- fallbackImported += batchShows.length;
233
- }
234
- catch (dbErr) {
235
- logger.error({ err: dbErr }, 'Failed to commit fallback import batch');
236
- skippedCount += batchShows.length;
237
- }
238
- }
239
- }
240
- }
241
- const imported = offlineShows.length + fallbackImported;
242
- activeImportStatus = {
243
- running: false,
244
- current: total,
245
- total,
246
- phase: 'done',
247
- source: null,
248
- imported,
249
- skipped: skippedCount,
250
- };
251
- sendEvent('complete', { imported, skipped: skippedCount });
252
- }
253
- function xmlText(value) {
254
- if (Array.isArray(value))
255
- return String(value[0] ?? '');
256
- return String(value ?? '');
257
- }
258
- function mapMalStatus(malStatus) {
259
- switch (malStatus) {
260
- case 'Plan to Watch':
261
- return 'Planned';
262
- case 'On Hold':
263
- return 'On-Hold';
264
- default:
265
- return malStatus;
266
- }
267
- }
268
- const malXmlParser = new XMLParser({
269
- isArray: (name) => name === 'anime',
270
- parseTagValue: false,
271
- trimValues: true,
272
- });
273
- async function searchByTitleForMal(title) {
274
- const result = await searchAnilistByTitle(title);
275
- if (result)
276
- return { ...result, source: 'anilist' };
277
- if (isAnilistRateLimited()) {
278
- logger.debug({ title }, 'AniList rate limited, trying Kitsu fallback for MAL import');
279
- const fb = await kitsuSearchAnime({ query: title, page: 1, perPage: 5 });
280
- if (fb.length > 0) {
281
- const lowerTitle = title.toLowerCase();
282
- const exactMatch = fb.find((m) => m.title?.romaji?.toLowerCase() === lowerTitle ||
283
- m.title?.english?.toLowerCase() === lowerTitle);
284
- const best = exactMatch || fb[0];
285
- if (best.id > 0) {
286
- return { id: best.id, title: best.title ?? {}, source: 'kitsu' };
287
- }
288
- }
289
- }
290
- return null;
291
- }
1
+ import{XMLParser as D}from"fast-xml-parser";import{searchAnilistByTitle as j,isAnilistRateLimited as E,getShowMetaById as O}from"./anilist.js";import{kitsuSearchAnime as R}from"./kitsu.js";import{offlineDb as V}from"./offline-db.js";import{SettingsRepository as v}from"../repositories/settings.repository.js";import{ShowsMetaRepository as x}from"../repositories/shows-meta.repository.js";import{performWriteTransactionAsync as N}from"../sync.js";import B from"../logger.js";let u={running:!1,current:0,total:0,phase:"done",source:null},L=!1;function $(){return u}function tt(){return u.running?(L=!0,{success:!0,message:"Import cancellation requested"}):{success:!0,message:"No import running"}}function et(t,b){if(u.running)return{ok:!1,status:409,body:{error:"An import is already in progress"}};if(!t)return{ok:!1,status:400,body:{error:"No file"}};const{erase:a,useOfflineDb:c,skipFallback:p}=b,h=c!=="false"&&c!==!1&&c!=="0",y=p==="true"||p===!0||p==="1";let s;try{s=q.parse(t.toString())}catch{return{ok:!1,status:400,body:{error:"Invalid XML"}}}const l=s?.myanimelist?.anime||[];return l.length===0?{ok:!1,status:400,body:{error:"No anime found in XML"}}:(L=!1,u={running:!0,current:0,total:l.length,phase:"offline",source:"offline"},{ok:!0,import:{animeList:l,erase:a,offlineEnabled:h,shouldSkipFallback:y}})}async function nt(t,b,a){const{animeList:c,erase:p,offlineEnabled:h,shouldSkipFallback:y}=b,s=c.length;let l=0;const o=[],C=[],k=[];for(const r of c){const e=I(r.series_title),g=X(I(r.my_status));let T=!1;if(h){const m=parseInt(I(r.series_animedb_id),10);if(m&&!Number.isNaN(m)){const i=V.getByMalId(m);if(i){T=!0;const M=String(i.anilistId),n=i.title||e;o.push({id:M,name:n,thumbnail:i.thumbnail,status:g}),(i.thumbnail||i.type||i.genres)&&C.push({id:M,thumbnail:i.thumbnail,type:i.type,genres:i.genres}),u={running:!0,current:o.length,total:s,phase:"offline",source:"offline",title:e,matchedTitle:n,status:g,found:!0,imported:o.length},a("progress",{current:o.length,total:s,title:e,matchedTitle:n,status:g,source:"offline",found:!0})}}}T||k.push(r)}if(o.length>0||p)try{await N(t,async r=>{p&&await v.clearWatchlist(r),await v.upsertWatchlistBatch(r,o);for(const e of C)(e.thumbnail||e.type||e.genres)&&await x.upsert(r,{id:e.id,thumbnail:e.thumbnail,type:e.type,genres:e.genres})})}catch(r){B.error({err:r},"Failed to commit offline import matches")}let w=0;if(y||L)l+=k.length;else if(k.length>0){u={...u,phase:"fallback",source:null};const r=5;for(let e=0;e<k.length;e+=r){if(L){l+=k.length-e;break}const g=k.slice(e,e+r),T=await Promise.allSettled(g.map(n=>H(I(n.series_title)))),m=[],i=[],M=[];if(T.forEach((n,f)=>{const S=I(g[f].series_title),A=X(I(g[f].my_status)),P=o.length+w+f+1;if(n.status==="fulfilled"&&n.value){const d=n.value,_=d.title?.english||d.title?.romaji||S;m.push({id:String(d.id),name:_,status:A}),u={running:!0,current:P,total:s,phase:"fallback",source:d.source,title:S,matchedTitle:_,status:A,found:!0,imported:o.length+w+1},a("progress",{current:P,total:s,title:S,matchedTitle:_,status:A,source:d.source,found:!0}),M.push(O(String(d.id)).then(F=>{F&&i.push({id:String(d.id),thumbnail:F.thumbnail||void 0,type:F.type||void 0})}).catch(()=>{}))}else l++,u={running:!0,current:P,total:s,phase:"fallback",source:null,title:S,matchedTitle:null,status:A,found:!1,imported:o.length+w},a("progress",{current:P,total:s,title:S,matchedTitle:null,status:A,source:null,found:!1})}),await Promise.allSettled(M),m.length>0)try{await N(t,async n=>{await v.upsertWatchlistBatch(n,m);for(const f of i)f.thumbnail&&await x.upsert(n,{id:f.id,thumbnail:f.thumbnail,type:f.type})}),w+=m.length}catch(n){B.error({err:n},"Failed to commit fallback import batch"),l+=m.length}}}const W=o.length+w;u={running:!1,current:s,total:s,phase:"done",source:null,imported:W,skipped:l},a("complete",{imported:W,skipped:l})}function I(t){return Array.isArray(t)?String(t[0]??""):String(t??"")}function X(t){switch(t){case"Plan to Watch":return"Planned";case"On Hold":return"On-Hold";default:return t}}const q=new D({isArray:t=>t==="anime",parseTagValue:!1,trimValues:!0});async function H(t){const b=await j(t);if(b)return{...b,source:"anilist"};if(E()){B.debug({title:t},"AniList rate limited, trying Kitsu fallback for MAL import");const a=await R({query:t,page:1,perPage:5});if(a.length>0){const c=t.toLowerCase(),h=a.find(y=>y.title?.romaji?.toLowerCase()===c||y.title?.english?.toLowerCase()===c)||a[0];if(h.id>0)return{id:h.id,title:h.title??{},source:"kitsu"}}}return null}export{nt as executeMalImport,$ as getMalImportStatus,et as prepareMalImport,tt as requestMalImportCancel};