@serifpersia/dango 3.3.4
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.
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/client/dist/assets/AnimeCard-B5A20jAI.js +1 -0
- package/client/dist/assets/AnimeInfo-0huPAq0O.js +1 -0
- package/client/dist/assets/AnimeInfo-BFWtHlPC.css +1 -0
- package/client/dist/assets/AnimeInfoPage-Dqr5QH6R.js +2 -0
- package/client/dist/assets/AnimeMetaDetails-BPvv5xIl.js +1 -0
- package/client/dist/assets/AnimeMetaDetails-BwNQnRCd.css +1 -0
- package/client/dist/assets/Asmr-B6ZiqkmU.js +2 -0
- package/client/dist/assets/Asmr-Cq2ua6Zk.css +1 -0
- package/client/dist/assets/Asmr.module-ChUXDZKx.js +1 -0
- package/client/dist/assets/AsmrResetProgressModal-Cqsoo3XC.js +1 -0
- package/client/dist/assets/AvSyncCalibrator-C1_iBSyL.css +1 -0
- package/client/dist/assets/AvSyncCalibrator-C8ZoIwMY.js +10 -0
- package/client/dist/assets/ErrorMessage-CXoRbrLN.css +1 -0
- package/client/dist/assets/ErrorMessage-z4f4WGgP.js +1 -0
- package/client/dist/assets/Home-DtRtTuI9.css +1 -0
- package/client/dist/assets/Home-thJhPfs1.js +1 -0
- package/client/dist/assets/Insights-DKtoVe6L.css +1 -0
- package/client/dist/assets/Insights-ZlgPrzGD.js +1 -0
- package/client/dist/assets/LibraryListPage-CCiee-Ot.js +1 -0
- package/client/dist/assets/ListeningList-ctAz_6Zi.js +1 -0
- package/client/dist/assets/Manga-BI9WMTx5.js +1 -0
- package/client/dist/assets/Manga-Dp_-l1Pw.css +1 -0
- package/client/dist/assets/Manga.module-BcfOdsNP.js +1 -0
- package/client/dist/assets/MangaInfoPage-DcrEr7Sk.js +1 -0
- package/client/dist/assets/MangaReadPage-SYg63m48.js +1 -0
- package/client/dist/assets/MangaResetProgressModal-DX-1PS4h.js +1 -0
- package/client/dist/assets/Map-CXFqXmsZ.css +1 -0
- package/client/dist/assets/Map-Dj51g5zL.js +1 -0
- package/client/dist/assets/Mature-DJmUUtlF.css +1 -0
- package/client/dist/assets/Mature-Dqh_9SA3.js +1 -0
- package/client/dist/assets/MediaCard-Be1ST9oj.js +1 -0
- package/client/dist/assets/MediaCard-DB6Yjgky.css +1 -0
- package/client/dist/assets/Music-BGH_0voi.css +1 -0
- package/client/dist/assets/Music-zsPwT8_5.js +1 -0
- package/client/dist/assets/OptionListMenu-Drc_vybW.js +1 -0
- package/client/dist/assets/Player-B036Ic3m.css +1 -0
- package/client/dist/assets/Player-BEtAYps2.js +56 -0
- package/client/dist/assets/PlayerRedirect-D70t8VUW.js +1 -0
- package/client/dist/assets/PlayerSettings-Bj6SsmQ2.css +1 -0
- package/client/dist/assets/PlayerSettings-Cwoji-P7.js +1 -0
- package/client/dist/assets/QueueOptionsButton-B6BMq66e.js +1 -0
- package/client/dist/assets/QueueOptionsButton-CM15uQn8.css +1 -0
- package/client/dist/assets/QueueRail-BUYEjdaq.css +1 -0
- package/client/dist/assets/QueueRail-VxpUDBSm.js +1 -0
- package/client/dist/assets/Radio-B2Q_Egj7.js +1 -0
- package/client/dist/assets/Radio-CCHNFN7R.css +1 -0
- package/client/dist/assets/Radio.module-DGSZLpKM.js +1 -0
- package/client/dist/assets/ReadingList-O0QPwXu7.js +1 -0
- package/client/dist/assets/ResetProgressModal-CR_ovLAu.js +1 -0
- package/client/dist/assets/Search-C8e2nlDD.js +1 -0
- package/client/dist/assets/Search-Cs45oAwI.css +1 -0
- package/client/dist/assets/Search.module-CMEai-B-.js +1 -0
- package/client/dist/assets/Settings-AuFxQcJF.css +1 -0
- package/client/dist/assets/Settings-BgPWBl2V.js +21 -0
- package/client/dist/assets/SkeletonGrid-CtHKKMLj.js +1 -0
- package/client/dist/assets/SkeletonGrid-DhPYefXr.css +1 -0
- package/client/dist/assets/SynopsisText-BcpwT5iw.js +1 -0
- package/client/dist/assets/SynopsisText-CYv2W3yR.css +1 -0
- package/client/dist/assets/Trackers-Bfoiyuzy.js +2 -0
- package/client/dist/assets/Trackers-D5XBOCDp.css +1 -0
- package/client/dist/assets/Tv-Ca0fCdko.js +3 -0
- package/client/dist/assets/Tv-CuEl3Iol.css +1 -0
- package/client/dist/assets/TvInfo-Bu36xJyO.css +1 -0
- package/client/dist/assets/TvInfo-D_scgw_h.js +1 -0
- package/client/dist/assets/TvPopup-YTI7dEbJ.js +1 -0
- package/client/dist/assets/TvSearch-CxU33UOf.js +1 -0
- package/client/dist/assets/TvWatchlist-B4X1fWEH.js +1 -0
- package/client/dist/assets/VirtualKeyboard-6ZNTym4R.js +1 -0
- package/client/dist/assets/VirtualKeyboard-KIPc5ZGH.css +1 -0
- package/client/dist/assets/Watchlist-BiDEU0iV.css +1 -0
- package/client/dist/assets/Watchlist-P6YMZUdF.js +1 -0
- package/client/dist/assets/Watchlist.module-XGtVQr1d.js +1 -0
- package/client/dist/assets/hls-BOLXTnjL.js +42 -0
- package/client/dist/assets/hls-C4cG9wQw.js +1 -0
- package/client/dist/assets/index-BOKlZEHq.js +30354 -0
- package/client/dist/assets/index-CUm1meqr.css +1 -0
- package/client/dist/assets/index-IZgh1beL.js +178 -0
- package/client/dist/assets/useAnimeInfoData-DeuwDSnI.js +1 -0
- package/client/dist/assets/useAsmr-CbMVRjmV.js +1 -0
- package/client/dist/assets/useAsmrPopup-D91Q3CBg.js +1 -0
- package/client/dist/assets/useIsMobile-Dm_b6Ob3.js +1 -0
- package/client/dist/assets/useMangaLibrary-BPvFb1lU.js +1 -0
- package/client/dist/assets/useMangaPopup-BOL2mQJs.js +1 -0
- package/client/dist/assets/useMatureConsent-CT7-KfzG.js +1 -0
- package/client/dist/assets/useProviders-BsRGONMA.js +1 -0
- package/client/dist/assets/useTv-D37MfTHA.js +1 -0
- package/client/dist/assets/useTvLibrary-Cwxgy_Bg.js +1 -0
- package/client/dist/assets/vendor-core-8R2fFbyN.js +1 -0
- package/client/dist/assets/vendor-icons-CDMuNDeL.js +1 -0
- package/client/dist/assets/vendor-query-BXRECSI6.js +1 -0
- package/client/dist/assets/vendor-ui-CUDCvRS_.js +1 -0
- package/client/dist/av-sync-test.mp4 +0 -0
- package/client/dist/favicon.ico +0 -0
- package/client/dist/index.html +46 -0
- package/client/dist/logo-mochi.png +0 -0
- package/client/dist/logo-sakura.png +0 -0
- package/client/dist/logo.png +0 -0
- package/client/dist/placeholder.svg +4 -0
- package/client/dist/robots.txt +3 -0
- package/orchestrator.js +534 -0
- package/package.json +85 -0
- package/server/dist/app-auth.js +177 -0
- package/server/dist/config.js +175 -0
- package/server/dist/controllers/asmr-library.controller.js +288 -0
- package/server/dist/controllers/auth.controller.js +225 -0
- package/server/dist/controllers/data.controller.js +766 -0
- package/server/dist/controllers/insights.controller.js +198 -0
- package/server/dist/controllers/manga-library.controller.js +296 -0
- package/server/dist/controllers/music.controller.js +698 -0
- package/server/dist/controllers/proxy.controller.js +820 -0
- package/server/dist/controllers/settings.controller.js +609 -0
- package/server/dist/controllers/tv-library.controller.js +310 -0
- package/server/dist/controllers/watchlist.controller.js +1015 -0
- package/server/dist/db.js +145 -0
- package/server/dist/discord-gateway.js +391 -0
- package/server/dist/discord-rpc.js +427 -0
- package/server/dist/github-sync.js +512 -0
- package/server/dist/google.js +748 -0
- package/server/dist/lib/anilist.js +1549 -0
- package/server/dist/lib/anischedule.js +194 -0
- package/server/dist/lib/discord-roles-sync.service.js +130 -0
- package/server/dist/lib/ipc.js +24 -0
- package/server/dist/lib/kitsu.js +544 -0
- package/server/dist/lib/mal.js +934 -0
- package/server/dist/lib/migration.js +329 -0
- package/server/dist/lib/offline-db.js +401 -0
- package/server/dist/lib/temp-ids.js +3 -0
- package/server/dist/lib/tmdb.js +72 -0
- package/server/dist/lib/tracker/anilist-tracker.js +245 -0
- package/server/dist/lib/tracker/sync.service.js +496 -0
- package/server/dist/lib/ytmusic.js +125 -0
- package/server/dist/logger.js +16 -0
- package/server/dist/providers/manga/manga.types.js +7 -0
- package/server/dist/providers/provider.interface.js +1 -0
- package/server/dist/providers/remote-loader.js +574 -0
- package/server/dist/providers/remote-types.js +117 -0
- package/server/dist/providers/title-matching.js +225 -0
- package/server/dist/providers/tv.types.js +1 -0
- package/server/dist/rclone.js +120 -0
- package/server/dist/repositories/asmr.repository.js +123 -0
- package/server/dist/repositories/insights.repository.js +85 -0
- package/server/dist/repositories/library.repository.js +108 -0
- package/server/dist/repositories/mal-cache.repository.js +55 -0
- package/server/dist/repositories/manga.repository.js +138 -0
- package/server/dist/repositories/notifications.repository.js +41 -0
- package/server/dist/repositories/queue.repository.js +61 -0
- package/server/dist/repositories/settings.repository.js +12 -0
- package/server/dist/repositories/shows-meta.repository.js +42 -0
- package/server/dist/repositories/temp-show-ids.repository.js +51 -0
- package/server/dist/repositories/tv.repository.js +151 -0
- package/server/dist/repositories/watched-episodes.repository.js +52 -0
- package/server/dist/repositories/watchlist.repository.js +71 -0
- package/server/dist/request-logger.js +16 -0
- package/server/dist/routes/asmr-library.routes.js +23 -0
- package/server/dist/routes/asmr.routes.js +81 -0
- package/server/dist/routes/auth.routes.js +25 -0
- package/server/dist/routes/data.routes.js +47 -0
- package/server/dist/routes/discord-gateway.routes.js +51 -0
- package/server/dist/routes/insights.routes.js +34 -0
- package/server/dist/routes/lan-auth.routes.js +46 -0
- package/server/dist/routes/manga-library.routes.js +23 -0
- package/server/dist/routes/manga.routes.js +141 -0
- package/server/dist/routes/music.routes.js +20 -0
- package/server/dist/routes/providers.routes.js +17 -0
- package/server/dist/routes/proxy.routes.js +12 -0
- package/server/dist/routes/radio.routes.js +138 -0
- package/server/dist/routes/settings.routes.js +24 -0
- package/server/dist/routes/tracker.routes.js +164 -0
- package/server/dist/routes/translate.routes.js +88 -0
- package/server/dist/routes/tv-library.routes.js +23 -0
- package/server/dist/routes/tv.routes.js +629 -0
- package/server/dist/routes/watchlist.routes.js +341 -0
- package/server/dist/scripts/backfill-episode-duration.js +42 -0
- package/server/dist/server.js +529 -0
- package/server/dist/shipped-defaults.js +7 -0
- package/server/dist/sync-config.js +23 -0
- package/server/dist/sync-payload.js +91 -0
- package/server/dist/sync.js +1128 -0
- package/server/dist/utils/cache.utils.js +25 -0
- package/server/dist/utils/cookie.utils.js +13 -0
- package/server/dist/utils/db-utils.js +9 -0
- package/server/dist/utils/env.utils.js +77 -0
- package/server/dist/utils/http.utils.js +70 -0
- package/server/dist/utils/machine-id.js +40 -0
- package/server/dist/utils/megaplay.utils.js +41 -0
- package/server/dist/utils/request-context.js +2 -0
- package/server/dist/utils/security.utils.js +94 -0
- package/server/package.json +44 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { performWriteTransaction } from '../sync.js';
|
|
2
|
+
import { searchAnilistByTitle, getShowMetaById } from './anilist.js';
|
|
3
|
+
import { isTempShowId } from './temp-ids.js';
|
|
4
|
+
import { WatchlistRepository } from '../repositories/watchlist.repository.js';
|
|
5
|
+
import { ShowsMetaRepository } from '../repositories/shows-meta.repository.js';
|
|
6
|
+
import { dbGet, dbRun } from '../utils/db-utils.js';
|
|
7
|
+
import logger from '../logger.js';
|
|
8
|
+
const inFlightMigrations = new Map();
|
|
9
|
+
async function consolidateFromNumeric(db, numericId) {
|
|
10
|
+
// Check if this numeric ID might be a MAL alias that should redirect to the canonical AniList ID
|
|
11
|
+
const metaRow = dbGet(db, 'SELECT anilistId FROM shows_meta WHERE id = ? AND anilistId IS NOT NULL', [numericId]);
|
|
12
|
+
// Determine the true anilistId for this numeric ID
|
|
13
|
+
let trueAnilistId;
|
|
14
|
+
if (metaRow?.anilistId != null && String(metaRow.anilistId) !== numericId) {
|
|
15
|
+
// shows_meta already has a different anilistId — this is clearly a MAL alias
|
|
16
|
+
trueAnilistId = metaRow.anilistId;
|
|
17
|
+
}
|
|
18
|
+
else if (!metaRow) {
|
|
19
|
+
// No local shows_meta — only verify via AniList if there's existing data that could be a MAL alias
|
|
20
|
+
if (WatchlistRepository.exists(db, numericId)) {
|
|
21
|
+
const meta = await getShowMetaById(numericId);
|
|
22
|
+
if (meta?.anilistId && meta.anilistId !== parseInt(numericId)) {
|
|
23
|
+
trueAnilistId = meta.anilistId;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (trueAnilistId) {
|
|
28
|
+
const canonicalId = String(trueAnilistId);
|
|
29
|
+
if (WatchlistRepository.exists(db, numericId)) {
|
|
30
|
+
// MAL alias is in the watchlist — migrate all entries to the canonical AniList ID
|
|
31
|
+
logger.info({ aliasId: numericId, canonicalId }, 'Migrating watchlist from MAL alias to canonical ID');
|
|
32
|
+
await performWriteTransaction(db, (tx) => {
|
|
33
|
+
const aliasWatchlist = WatchlistRepository.getById(tx, numericId);
|
|
34
|
+
const aliasShowsMeta = ShowsMetaRepository.getById(tx, numericId);
|
|
35
|
+
tx.run('UPDATE OR IGNORE watchlist SET id = ?, thumbnail = ? WHERE id = ?', [
|
|
36
|
+
canonicalId,
|
|
37
|
+
aliasWatchlist?.thumbnail || '',
|
|
38
|
+
numericId,
|
|
39
|
+
]);
|
|
40
|
+
tx.run('DELETE FROM watchlist WHERE id = ?', [numericId]);
|
|
41
|
+
tx.run('UPDATE OR IGNORE shows_meta SET id = ?, anilistId = ?, thumbnail = ? WHERE id = ?', [
|
|
42
|
+
canonicalId,
|
|
43
|
+
trueAnilistId,
|
|
44
|
+
aliasShowsMeta?.thumbnail || '',
|
|
45
|
+
numericId,
|
|
46
|
+
]);
|
|
47
|
+
tx.run('DELETE FROM shows_meta WHERE id = ?', [numericId]);
|
|
48
|
+
tx.run('UPDATE OR IGNORE watched_episodes SET showId = ? WHERE showId = ?', [
|
|
49
|
+
canonicalId,
|
|
50
|
+
numericId,
|
|
51
|
+
]);
|
|
52
|
+
tx.run('DELETE FROM watched_episodes WHERE showId = ?', [numericId]);
|
|
53
|
+
tx.run('UPDATE OR IGNORE queue SET showId = ? WHERE showId = ?', [canonicalId, numericId]);
|
|
54
|
+
tx.run('DELETE FROM queue WHERE showId = ?', [numericId]);
|
|
55
|
+
tx.run('UPDATE OR IGNORE dismissed_notifications SET showId = ? WHERE showId = ?', [
|
|
56
|
+
canonicalId,
|
|
57
|
+
numericId,
|
|
58
|
+
]);
|
|
59
|
+
tx.run('DELETE FROM dismissed_notifications WHERE showId = ?', [numericId]);
|
|
60
|
+
tx.run('UPDATE OR IGNORE discovered_notifications SET showId = ? WHERE showId = ?', [
|
|
61
|
+
canonicalId,
|
|
62
|
+
numericId,
|
|
63
|
+
]);
|
|
64
|
+
tx.run('DELETE FROM discovered_notifications WHERE showId = ?', [numericId]);
|
|
65
|
+
tx.run('INSERT OR REPLACE INTO legacy_id_mapping (legacyId, numericId) VALUES (?, ?)', [
|
|
66
|
+
numericId,
|
|
67
|
+
canonicalId,
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
db.scheduleSave();
|
|
71
|
+
return canonicalId;
|
|
72
|
+
}
|
|
73
|
+
// Not in watchlist — check if canonical ID is in the watchlist and save a mapping
|
|
74
|
+
if (WatchlistRepository.exists(db, canonicalId)) {
|
|
75
|
+
const existing = dbGet(db, 'SELECT numericId FROM legacy_id_mapping WHERE legacyId = ?', [numericId]);
|
|
76
|
+
if (!existing || existing.numericId !== canonicalId) {
|
|
77
|
+
dbRun(db, 'INSERT OR REPLACE INTO legacy_id_mapping (legacyId, numericId) VALUES (?, ?)', [
|
|
78
|
+
numericId,
|
|
79
|
+
canonicalId,
|
|
80
|
+
]);
|
|
81
|
+
db.scheduleSave();
|
|
82
|
+
logger.info({ aliasId: numericId, canonicalId }, 'Mapped numeric alias to canonical ID');
|
|
83
|
+
}
|
|
84
|
+
return canonicalId;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (WatchlistRepository.exists(db, numericId))
|
|
88
|
+
return numericId;
|
|
89
|
+
let legacyId;
|
|
90
|
+
// 1. Check legacy_id_mapping for a legacy ID in the watchlist that maps to this numeric ID
|
|
91
|
+
const legacyRow = dbGet(db, 'SELECT legacyId FROM legacy_id_mapping WHERE numericId = ? AND legacyId IN (SELECT id FROM watchlist)', [numericId]);
|
|
92
|
+
if (legacyRow) {
|
|
93
|
+
legacyId = legacyRow.legacyId;
|
|
94
|
+
}
|
|
95
|
+
// 2. If not found, check shows_meta by anilistId where the entry's id IS in the watchlist
|
|
96
|
+
if (!legacyId && trueAnilistId) {
|
|
97
|
+
const metaByAnilist = dbGet(db, 'SELECT id FROM shows_meta WHERE anilistId = ? AND id IN (SELECT id FROM watchlist)', [trueAnilistId]);
|
|
98
|
+
if (metaByAnilist) {
|
|
99
|
+
legacyId = metaByAnilist.id;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!legacyId)
|
|
103
|
+
return numericId;
|
|
104
|
+
logger.info({ legacyId, numericId }, 'Consolidating watchlist entry from legacy to numeric ID');
|
|
105
|
+
const aniListId = parseInt(numericId);
|
|
106
|
+
await performWriteTransaction(db, (tx) => {
|
|
107
|
+
const legacyWatchlist = WatchlistRepository.getById(tx, legacyId);
|
|
108
|
+
const legacyShowsMeta = ShowsMetaRepository.getById(tx, legacyId);
|
|
109
|
+
tx.run('UPDATE OR IGNORE watchlist SET id = ?, thumbnail = ? WHERE id = ?', [
|
|
110
|
+
numericId,
|
|
111
|
+
legacyWatchlist?.thumbnail || '',
|
|
112
|
+
legacyId,
|
|
113
|
+
]);
|
|
114
|
+
tx.run('DELETE FROM watchlist WHERE id = ?', [legacyId]);
|
|
115
|
+
tx.run('UPDATE OR IGNORE shows_meta SET id = ?, anilistId = ?, thumbnail = ? WHERE id = ?', [
|
|
116
|
+
numericId,
|
|
117
|
+
aniListId,
|
|
118
|
+
legacyShowsMeta?.thumbnail || '',
|
|
119
|
+
legacyId,
|
|
120
|
+
]);
|
|
121
|
+
tx.run('DELETE FROM shows_meta WHERE id = ?', [legacyId]);
|
|
122
|
+
tx.run('UPDATE OR IGNORE watched_episodes SET showId = ? WHERE showId = ?', [
|
|
123
|
+
numericId,
|
|
124
|
+
legacyId,
|
|
125
|
+
]);
|
|
126
|
+
tx.run('DELETE FROM watched_episodes WHERE showId = ?', [legacyId]);
|
|
127
|
+
tx.run('UPDATE OR IGNORE queue SET showId = ? WHERE showId = ?', [numericId, legacyId]);
|
|
128
|
+
tx.run('DELETE FROM queue WHERE showId = ?', [legacyId]);
|
|
129
|
+
tx.run('UPDATE OR IGNORE dismissed_notifications SET showId = ? WHERE showId = ?', [
|
|
130
|
+
numericId,
|
|
131
|
+
legacyId,
|
|
132
|
+
]);
|
|
133
|
+
tx.run('DELETE FROM dismissed_notifications WHERE showId = ?', [legacyId]);
|
|
134
|
+
tx.run('UPDATE OR IGNORE discovered_notifications SET showId = ? WHERE showId = ?', [
|
|
135
|
+
numericId,
|
|
136
|
+
legacyId,
|
|
137
|
+
]);
|
|
138
|
+
tx.run('DELETE FROM discovered_notifications WHERE showId = ?', [legacyId]);
|
|
139
|
+
tx.run('INSERT OR REPLACE INTO legacy_id_mapping (legacyId, numericId) VALUES (?, ?)', [
|
|
140
|
+
legacyId,
|
|
141
|
+
numericId,
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
db.scheduleSave();
|
|
145
|
+
return numericId;
|
|
146
|
+
}
|
|
147
|
+
async function migrateId(db, legacyId) {
|
|
148
|
+
const isNumeric = /^\d+$/.test(legacyId);
|
|
149
|
+
if (isNumeric) {
|
|
150
|
+
return consolidateFromNumeric(db, legacyId);
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
// 1. Check if mapping already exists
|
|
154
|
+
const mapping = dbGet(db, 'SELECT numericId FROM legacy_id_mapping WHERE legacyId = ?', [legacyId]);
|
|
155
|
+
if (mapping) {
|
|
156
|
+
const mappedMeta = (await ShowsMetaRepository.getById(db, mapping.numericId));
|
|
157
|
+
const canonicalId = mappedMeta?.anilistId ? String(mappedMeta.anilistId) : undefined;
|
|
158
|
+
if (!canonicalId || canonicalId === mapping.numericId) {
|
|
159
|
+
return consolidateFromNumeric(db, mapping.numericId);
|
|
160
|
+
}
|
|
161
|
+
await performWriteTransaction(db, (tx) => {
|
|
162
|
+
const legacyWatchlist = WatchlistRepository.getById(tx, mapping.numericId);
|
|
163
|
+
const legacyShowsMeta = ShowsMetaRepository.getById(tx, mapping.numericId);
|
|
164
|
+
tx.run('UPDATE OR IGNORE watchlist SET id = ?, thumbnail = ? WHERE id = ?', [
|
|
165
|
+
canonicalId,
|
|
166
|
+
legacyWatchlist?.thumbnail || '',
|
|
167
|
+
mapping.numericId,
|
|
168
|
+
]);
|
|
169
|
+
tx.run('DELETE FROM watchlist WHERE id = ?', [mapping.numericId]);
|
|
170
|
+
tx.run('UPDATE OR IGNORE shows_meta SET id = ?, anilistId = ?, thumbnail = ? WHERE id = ?', [
|
|
171
|
+
canonicalId,
|
|
172
|
+
parseInt(canonicalId),
|
|
173
|
+
legacyShowsMeta?.thumbnail || '',
|
|
174
|
+
mapping.numericId,
|
|
175
|
+
]);
|
|
176
|
+
tx.run('DELETE FROM shows_meta WHERE id = ?', [mapping.numericId]);
|
|
177
|
+
tx.run('UPDATE OR IGNORE watched_episodes SET showId = ? WHERE showId = ?', [
|
|
178
|
+
canonicalId,
|
|
179
|
+
mapping.numericId,
|
|
180
|
+
]);
|
|
181
|
+
tx.run('DELETE FROM watched_episodes WHERE showId = ?', [mapping.numericId]);
|
|
182
|
+
tx.run('UPDATE OR IGNORE queue SET showId = ? WHERE showId = ?', [
|
|
183
|
+
canonicalId,
|
|
184
|
+
mapping.numericId,
|
|
185
|
+
]);
|
|
186
|
+
tx.run('DELETE FROM queue WHERE showId = ?', [mapping.numericId]);
|
|
187
|
+
tx.run('UPDATE OR IGNORE dismissed_notifications SET showId = ? WHERE showId = ?', [
|
|
188
|
+
canonicalId,
|
|
189
|
+
mapping.numericId,
|
|
190
|
+
]);
|
|
191
|
+
tx.run('DELETE FROM dismissed_notifications WHERE showId = ?', [mapping.numericId]);
|
|
192
|
+
tx.run('UPDATE OR IGNORE discovered_notifications SET showId = ? WHERE showId = ?', [
|
|
193
|
+
canonicalId,
|
|
194
|
+
mapping.numericId,
|
|
195
|
+
]);
|
|
196
|
+
tx.run('DELETE FROM discovered_notifications WHERE showId = ?', [mapping.numericId]);
|
|
197
|
+
tx.run('UPDATE legacy_id_mapping SET numericId = ? WHERE legacyId = ?', [
|
|
198
|
+
canonicalId,
|
|
199
|
+
legacyId,
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
db.scheduleSave();
|
|
203
|
+
logger.info({ legacyId, previousId: mapping.numericId, newId: canonicalId }, 'Canonicalized migrated show ID');
|
|
204
|
+
return canonicalId;
|
|
205
|
+
}
|
|
206
|
+
// 2. We need to find the title/name of the show
|
|
207
|
+
let showName;
|
|
208
|
+
// Try shows_meta by direct ID or anilistId
|
|
209
|
+
const localMeta = (await ShowsMetaRepository.getById(db, legacyId));
|
|
210
|
+
if (localMeta && localMeta.name) {
|
|
211
|
+
showName = localMeta.name;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const metaByAnilist = dbGet(db, 'SELECT id, name FROM shows_meta WHERE anilistId = ?', [parseInt(legacyId) || 0]);
|
|
215
|
+
if (metaByAnilist && metaByAnilist.name) {
|
|
216
|
+
showName = metaByAnilist.name;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Try watchlist
|
|
220
|
+
const watchlistEntry = await WatchlistRepository.getById(db, legacyId);
|
|
221
|
+
if (watchlistEntry && watchlistEntry.name) {
|
|
222
|
+
showName = watchlistEntry.name;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (!showName) {
|
|
227
|
+
logger.warn({ id: legacyId }, 'No show name found for legacy ID migration');
|
|
228
|
+
return legacyId;
|
|
229
|
+
}
|
|
230
|
+
// 3. Search AniList by title
|
|
231
|
+
const aniListShow = await searchAnilistByTitle(showName);
|
|
232
|
+
if (!aniListShow) {
|
|
233
|
+
logger.warn({ id: legacyId, showName }, 'No AniList match found for legacy ID migration');
|
|
234
|
+
return legacyId;
|
|
235
|
+
}
|
|
236
|
+
const newId = String(aniListShow.id);
|
|
237
|
+
let newThumbnail;
|
|
238
|
+
try {
|
|
239
|
+
const freshMeta = await getShowMetaById(newId);
|
|
240
|
+
newThumbnail = freshMeta?.thumbnail;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
newThumbnail = undefined;
|
|
244
|
+
}
|
|
245
|
+
// 4. Perform the DB updates across all tables atomically
|
|
246
|
+
await performWriteTransaction(db, (tx) => {
|
|
247
|
+
tx.run('INSERT OR REPLACE INTO legacy_id_mapping (legacyId, numericId) VALUES (?, ?)', [
|
|
248
|
+
legacyId,
|
|
249
|
+
newId,
|
|
250
|
+
]);
|
|
251
|
+
const legacyWatchlist = WatchlistRepository.getById(tx, legacyId);
|
|
252
|
+
if (legacyWatchlist) {
|
|
253
|
+
const newWatchlistExists = WatchlistRepository.getById(tx, newId);
|
|
254
|
+
if (newWatchlistExists) {
|
|
255
|
+
WatchlistRepository.delete(tx, legacyId);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
tx.run('UPDATE watchlist SET id = ?, thumbnail = ? WHERE id = ?', [
|
|
259
|
+
newId,
|
|
260
|
+
newThumbnail || legacyWatchlist.thumbnail || '',
|
|
261
|
+
legacyId,
|
|
262
|
+
]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const legacyShowsMeta = ShowsMetaRepository.getById(tx, legacyId);
|
|
266
|
+
if (legacyShowsMeta) {
|
|
267
|
+
const newShowsMetaExists = ShowsMetaRepository.getById(tx, newId);
|
|
268
|
+
if (newShowsMetaExists) {
|
|
269
|
+
tx.run('DELETE FROM shows_meta WHERE id = ?', [legacyId]);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
tx.run('UPDATE shows_meta SET id = ?, anilistId = ?, thumbnail = ? WHERE id = ?', [
|
|
273
|
+
newId,
|
|
274
|
+
aniListShow.id,
|
|
275
|
+
newThumbnail || legacyShowsMeta.thumbnail || '',
|
|
276
|
+
legacyId,
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
tx.run('UPDATE OR IGNORE watched_episodes SET showId = ? WHERE showId = ?', [newId, legacyId]);
|
|
281
|
+
tx.run('DELETE FROM watched_episodes WHERE showId = ?', [legacyId]);
|
|
282
|
+
tx.run('UPDATE OR IGNORE queue SET showId = ? WHERE showId = ?', [newId, legacyId]);
|
|
283
|
+
tx.run('DELETE FROM queue WHERE showId = ?', [legacyId]);
|
|
284
|
+
tx.run('UPDATE OR IGNORE dismissed_notifications SET showId = ? WHERE showId = ?', [
|
|
285
|
+
newId,
|
|
286
|
+
legacyId,
|
|
287
|
+
]);
|
|
288
|
+
tx.run('DELETE FROM dismissed_notifications WHERE showId = ?', [legacyId]);
|
|
289
|
+
tx.run('UPDATE OR IGNORE discovered_notifications SET showId = ? WHERE showId = ?', [
|
|
290
|
+
newId,
|
|
291
|
+
legacyId,
|
|
292
|
+
]);
|
|
293
|
+
tx.run('DELETE FROM discovered_notifications WHERE showId = ?', [legacyId]);
|
|
294
|
+
});
|
|
295
|
+
db.scheduleSave();
|
|
296
|
+
logger.info({ legacyId, newId, showName }, 'Successfully migrated legacy show ID to numeric ID');
|
|
297
|
+
return newId;
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
logger.error({ err, legacyId }, 'Error migrating legacy show ID');
|
|
301
|
+
return legacyId;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
export function getMigratedId(db, legacyId) {
|
|
305
|
+
if (isTempShowId(legacyId))
|
|
306
|
+
return Promise.resolve(legacyId);
|
|
307
|
+
if (/^(mal-\d+|-\d+)$/i.test(legacyId)) {
|
|
308
|
+
const mapping = dbGet(db, 'SELECT numericId FROM legacy_id_mapping WHERE legacyId = ?', [legacyId]);
|
|
309
|
+
if (mapping)
|
|
310
|
+
return consolidateFromNumeric(db, mapping.numericId);
|
|
311
|
+
return Promise.resolve(legacyId);
|
|
312
|
+
}
|
|
313
|
+
if (/^\d+$/.test(legacyId)) {
|
|
314
|
+
const existing = inFlightMigrations.get(legacyId);
|
|
315
|
+
if (existing)
|
|
316
|
+
return existing;
|
|
317
|
+
const migration = migrateId(db, legacyId);
|
|
318
|
+
inFlightMigrations.set(legacyId, migration);
|
|
319
|
+
migration.then(() => inFlightMigrations.delete(legacyId), () => inFlightMigrations.delete(legacyId));
|
|
320
|
+
return migration;
|
|
321
|
+
}
|
|
322
|
+
const existing = inFlightMigrations.get(legacyId);
|
|
323
|
+
if (existing)
|
|
324
|
+
return existing;
|
|
325
|
+
const migration = migrateId(db, legacyId);
|
|
326
|
+
inFlightMigrations.set(legacyId, migration);
|
|
327
|
+
migration.then(() => inFlightMigrations.delete(legacyId), () => inFlightMigrations.delete(legacyId));
|
|
328
|
+
return migration;
|
|
329
|
+
}
|