@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.
- package/README.md +1 -1
- package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DraA_TF9.js} +1 -1
- package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-BJ7pzs-P.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_esUr5C.js} +2 -2
- package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-Ckuh2xfW.js} +1 -1
- package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-DwHCd9yB.js} +1 -1
- package/client/dist/assets/{Home-DUkUTTTy.js → Home-DuM1EuvN.js} +1 -1
- package/client/dist/assets/{Insights-QCh4xW12.js → Insights-D3Pd9c9V.js} +1 -1
- package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-Dm2yVn0-.js} +1 -1
- package/client/dist/assets/{ListeningList-DguKjI_t.js → ListeningList-D-zt6EXZ.js} +1 -1
- package/client/dist/assets/{Manga-HydR-L7o.js → Manga-JwprMKBk.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DaeyJl2p.js} +1 -1
- package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-COg0fHI9.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
- package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-IHDaasrs.js} +1 -1
- package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CpwsrXo5.js} +1 -1
- package/client/dist/assets/{Music-CQpyXTLH.js → Music-Dks4tPeK.js} +1 -1
- package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-11CcAsy8.js} +1 -1
- package/client/dist/assets/{Player-CsH4x_I_.js → Player-DQoEQkHL.js} +2 -2
- package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-D4Ud4kv4.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
- package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-B4Dok7U3.js} +1 -1
- package/client/dist/assets/{Radio-DF6g6odd.js → Radio-BHK6LSk4.js} +1 -1
- package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-BCsxIz57.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BXHxSfy-.js} +1 -1
- package/client/dist/assets/{Search-DFkhuk-x.js → Search-PK1ds07O.js} +1 -1
- package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-Bxk_x55l.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CpJCZSRN.js} +1 -1
- package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-RieCh_84.js} +1 -1
- package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-DLytPJUj.js} +1 -1
- package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-C8djKebR.js} +1 -1
- package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-40JGGJDk.js} +1 -1
- package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-A3hbospf.js} +1 -1
- package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-DuXjqmZM.js} +1 -1
- package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-B9wGBh0u.js} +1 -1
- package/client/dist/assets/{hls-BGCX7mJy.js → hls-CZ5X3Ya-.js} +1 -1
- package/client/dist/assets/{index-CnfZwsds.js → index-dRhc9-ol.js} +2 -2
- package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
- package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CKOdHgol.js} +1 -1
- package/client/dist/assets/{useAsmrPopup-SymV1J-b.js → useAsmrPopup-SFHPhpcl.js} +1 -1
- package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary-CKATe31P.js} +1 -1
- package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-C75QuYgY.js} +1 -1
- package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-CMtvZbEi.js} +1 -1
- package/client/dist/assets/{useTv-DGflCsSI.js → useTv-DUX4zYMr.js} +1 -1
- package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-By4Gt-R_.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +2 -2
- package/server/dist/app-auth.js +1 -157
- package/server/dist/app-hono.js +7 -557
- package/server/dist/config.js +1 -174
- package/server/dist/db/drizzle.js +1 -16
- package/server/dist/db/schema-anime.js +1 -61
- package/server/dist/db/schema-asmr.js +1 -13
- package/server/dist/db/schema-manga.js +1 -31
- package/server/dist/db/schema-tv.js +1 -18
- package/server/dist/db.js +1 -217
- package/server/dist/discord-gateway.js +1 -385
- package/server/dist/discord-rpc.js +1 -413
- package/server/dist/github-sync.js +1 -508
- package/server/dist/google.js +1 -741
- package/server/dist/hono/asmr.js +1 -10
- package/server/dist/hono/auth.js +3 -26
- package/server/dist/hono/music.js +1 -209
- package/server/dist/hono/proxy.js +36 -783
- package/server/dist/hono/tv.js +5 -200
- package/server/dist/hono/watchlist.js +2 -338
- package/server/dist/lib/anilist.js +26 -1458
- package/server/dist/lib/anischedule.js +1 -191
- package/server/dist/lib/insights.js +1 -185
- package/server/dist/lib/ipc.js +1 -23
- package/server/dist/lib/kitsu.js +1 -517
- package/server/dist/lib/mal-import.js +1 -291
- package/server/dist/lib/mal.js +1 -957
- package/server/dist/lib/migration.js +1 -310
- package/server/dist/lib/offline-db.js +4 -384
- package/server/dist/lib/potoken.js +1 -135
- package/server/dist/lib/recommendations.service.js +1 -216
- package/server/dist/lib/temp-ids.js +1 -3
- package/server/dist/lib/tmdb.js +1 -72
- package/server/dist/lib/tracker/anilist-tracker.js +8 -245
- package/server/dist/lib/tracker/manga-sync.service.js +1 -773
- package/server/dist/lib/tracker/sync-state.js +1 -21
- package/server/dist/lib/tracker/sync.service.js +1 -476
- package/server/dist/lib/translate.js +3 -79
- package/server/dist/lib/ump.js +1 -106
- package/server/dist/lib/ytmusic.js +1 -162
- package/server/dist/logger.js +1 -16
- package/server/dist/providers/manga/manga.types.js +0 -1
- package/server/dist/providers/provider.interface.js +0 -1
- package/server/dist/providers/remote-loader.js +2 -614
- package/server/dist/providers/remote-types.js +1 -159
- package/server/dist/providers/title-matching.js +1 -226
- package/server/dist/providers/tv.types.js +0 -1
- package/server/dist/rclone.js +2 -120
- package/server/dist/repositories/asmr.repository.js +11 -46
- package/server/dist/repositories/insights.repository.js +11 -25
- package/server/dist/repositories/library.repository.js +3 -94
- package/server/dist/repositories/mal-cache.repository.js +1 -52
- package/server/dist/repositories/manga.repository.js +14 -58
- package/server/dist/repositories/notifications.repository.js +6 -52
- package/server/dist/repositories/queue.repository.js +5 -58
- package/server/dist/repositories/settings.repository.js +2 -29
- package/server/dist/repositories/shows-meta.repository.js +3 -56
- package/server/dist/repositories/temp-show-ids.repository.js +3 -63
- package/server/dist/repositories/tv.repository.js +11 -54
- package/server/dist/repositories/watched-episodes.repository.js +6 -55
- package/server/dist/repositories/watchlist.repository.js +3 -54
- package/server/dist/scripts/backfill-episode-duration.js +1 -42
- package/server/dist/server.js +1 -310
- package/server/dist/shipped-defaults.js +1 -6
- package/server/dist/sync-payload.js +1 -91
- package/server/dist/sync.js +3 -647
- package/server/dist/trpc/context.js +1 -7
- package/server/dist/trpc/errors.js +1 -15
- package/server/dist/trpc/index.js +1 -18
- package/server/dist/trpc/router.js +1 -49
- package/server/dist/trpc/routers/asmr-library.js +1 -112
- package/server/dist/trpc/routers/asmr-progress.js +1 -121
- package/server/dist/trpc/routers/asmr.js +1 -87
- package/server/dist/trpc/routers/auth.js +1 -194
- package/server/dist/trpc/routers/continue-watching.js +2 -58
- package/server/dist/trpc/routers/data.js +1 -1026
- package/server/dist/trpc/routers/discord.js +1 -285
- package/server/dist/trpc/routers/discovery.js +1 -11
- package/server/dist/trpc/routers/insights.js +1 -14
- package/server/dist/trpc/routers/manga-library.js +1 -263
- package/server/dist/trpc/routers/manga-progress.js +1 -169
- package/server/dist/trpc/routers/manga.js +1 -201
- package/server/dist/trpc/routers/music.js +1 -655
- package/server/dist/trpc/routers/notifications.js +1 -80
- package/server/dist/trpc/routers/progress.js +1 -109
- package/server/dist/trpc/routers/queue.js +1 -93
- package/server/dist/trpc/routers/radio.js +1 -145
- package/server/dist/trpc/routers/settings.js +1 -96
- package/server/dist/trpc/routers/tracker.js +1 -213
- package/server/dist/trpc/routers/tv-library.js +1 -135
- package/server/dist/trpc/routers/tv-progress.js +1 -159
- package/server/dist/trpc/routers/tv.js +1 -551
- package/server/dist/trpc/routers/watchlist.js +2 -549
- package/server/dist/trpc/validation.js +1 -258
- package/server/dist/utils/cache.utils.js +1 -25
- package/server/dist/utils/cookie.utils.js +1 -13
- package/server/dist/utils/db-utils.js +5 -17
- package/server/dist/utils/env.utils.js +5 -77
- package/server/dist/utils/http.utils.js +1 -82
- package/server/dist/utils/request-context.js +1 -2
- package/server/dist/utils/security.utils.js +1 -76
- package/server/package.json +3 -2
package/server/dist/server.js
CHANGED
|
@@ -1,310 +1 @@
|
|
|
1
|
-
process.setMaxListeners(100);
|
|
2
|
-
import { EventEmitter } from 'events';
|
|
3
|
-
EventEmitter.defaultMaxListeners = 100;
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { AppCache } from './utils/cache.utils.js';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import crypto from 'crypto';
|
|
8
|
-
import { serve } from '@hono/node-server';
|
|
9
|
-
import { serveStatic } from '@hono/node-server/serve-static';
|
|
10
|
-
import { getConnInfo } from '@hono/node-server/conninfo';
|
|
11
|
-
import logger from './logger.js';
|
|
12
|
-
import { notifyServerExit } from './lib/ipc.js';
|
|
13
|
-
import { CONFIG } from './config.js';
|
|
14
|
-
import { initializeDatabase, initializeMangaDatabase, initializeTvDatabase, initializeAsmrDatabase, runFullSyncSequence, syncUp, waitForSync, KIND_SYNC, } from './sync.js';
|
|
15
|
-
import { isJasmrApi } from './hono/asmr.js';
|
|
16
|
-
import { loadRemoteProviders } from './providers/remote-loader.js';
|
|
17
|
-
import { discordRPCService } from './discord-rpc.js';
|
|
18
|
-
import { discordGatewayService } from './discord-gateway.js';
|
|
19
|
-
import { SettingsRepository } from './repositories/settings.repository.js';
|
|
20
|
-
import { createHonoApp } from './app-hono.js';
|
|
21
|
-
import { startWatchlistDiscovery, stopWatchlistDiscovery } from './hono/watchlist.js';
|
|
22
|
-
import { checkAnilistStatus } from './lib/anilist.js';
|
|
23
|
-
import { offlineDb } from './lib/offline-db.js';
|
|
24
|
-
const app = createHonoApp(() => ({ shuttingDown: isShuttingDown, dbReady: !!db }), () => ({
|
|
25
|
-
shuttingDown: isShuttingDown,
|
|
26
|
-
dbsReady: !!db && !!mangaDb && !!tvDb && !!asmrDb,
|
|
27
|
-
bootSyncing,
|
|
28
|
-
}), () => ({ db, mangaDb, tvDb, asmrDb }), {
|
|
29
|
-
initializeDatabase,
|
|
30
|
-
setDb: (newDb) => {
|
|
31
|
-
db = newDb;
|
|
32
|
-
},
|
|
33
|
-
}, {
|
|
34
|
-
getCatalog: getProviderCatalog,
|
|
35
|
-
refresh: refreshRemoteProviders,
|
|
36
|
-
}, runSyncSequence, {
|
|
37
|
-
getApiCache: () => apiCache,
|
|
38
|
-
getProviders: () => providers,
|
|
39
|
-
getMangaProvider: (name) => mangaProviders[name],
|
|
40
|
-
getTvProvider: (name) => tvProviders[name],
|
|
41
|
-
getProxyRules: () => proxyRules,
|
|
42
|
-
getJasmr: () => {
|
|
43
|
-
const mod = providers['jasmr'];
|
|
44
|
-
return isJasmrApi(mod) ? mod : undefined;
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
const apiCache = new AppCache({ ttlSeconds: 3600, maxKeys: 5000 });
|
|
48
|
-
const providers = {};
|
|
49
|
-
const mangaProviders = {};
|
|
50
|
-
const tvProviders = {};
|
|
51
|
-
let proxyRules = [];
|
|
52
|
-
let remoteCatalog = [];
|
|
53
|
-
function parseEnabledProviders() {
|
|
54
|
-
return CONFIG.PROVIDER_ENABLED.split(',')
|
|
55
|
-
.map((s) => s.trim().toLowerCase())
|
|
56
|
-
.filter(Boolean);
|
|
57
|
-
}
|
|
58
|
-
function getProviderCatalog() {
|
|
59
|
-
return remoteCatalog;
|
|
60
|
-
}
|
|
61
|
-
async function refreshRemoteProviders() {
|
|
62
|
-
const url = CONFIG.PROVIDER_REPO_URL.trim();
|
|
63
|
-
if (!url)
|
|
64
|
-
return getProviderCatalog();
|
|
65
|
-
try {
|
|
66
|
-
const result = await loadRemoteProviders({
|
|
67
|
-
cache: apiCache,
|
|
68
|
-
registryUrl: url,
|
|
69
|
-
enabledProviders: parseEnabledProviders(),
|
|
70
|
-
});
|
|
71
|
-
if (result.catalog.length === 0) {
|
|
72
|
-
logger.warn('remote providers refresh returned nothing, keeping current set');
|
|
73
|
-
return getProviderCatalog();
|
|
74
|
-
}
|
|
75
|
-
for (const key of Object.keys(providers)) {
|
|
76
|
-
if (!(key in result.providers))
|
|
77
|
-
delete providers[key];
|
|
78
|
-
}
|
|
79
|
-
Object.assign(providers, result.providers);
|
|
80
|
-
for (const key of Object.keys(mangaProviders)) {
|
|
81
|
-
if (!(key in result.mangaProviders))
|
|
82
|
-
delete mangaProviders[key];
|
|
83
|
-
}
|
|
84
|
-
Object.assign(mangaProviders, result.mangaProviders);
|
|
85
|
-
for (const key of Object.keys(tvProviders)) {
|
|
86
|
-
if (!(key in result.tvProviders))
|
|
87
|
-
delete tvProviders[key];
|
|
88
|
-
}
|
|
89
|
-
Object.assign(tvProviders, result.tvProviders);
|
|
90
|
-
proxyRules = result.proxyRules;
|
|
91
|
-
remoteCatalog = result.catalog;
|
|
92
|
-
logger.info({
|
|
93
|
-
count: Object.keys(result.providers).length,
|
|
94
|
-
mangaCount: Object.keys(result.mangaProviders).length,
|
|
95
|
-
tvCount: Object.keys(result.tvProviders).length,
|
|
96
|
-
}, 'remote providers refreshed');
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
logger.error({ err }, 'remote providers refresh failed');
|
|
100
|
-
}
|
|
101
|
-
return getProviderCatalog();
|
|
102
|
-
}
|
|
103
|
-
let db;
|
|
104
|
-
let mangaDb;
|
|
105
|
-
let tvDb;
|
|
106
|
-
let asmrDb;
|
|
107
|
-
let isShuttingDown = false;
|
|
108
|
-
let bootSyncing = true;
|
|
109
|
-
async function runSyncSequence(database, mangaDatabase, preferredProvider) {
|
|
110
|
-
const remoteFolder = CONFIG.IS_DEV ? CONFIG.REMOTE_FOLDER_DEV : CONFIG.REMOTE_FOLDER_PROD;
|
|
111
|
-
await runFullSyncSequence({ db: database, mangaDb: mangaDatabase, tvDb, asmrDb }, { remoteFolder, preferredProvider });
|
|
112
|
-
}
|
|
113
|
-
startWatchlistDiscovery(() => db);
|
|
114
|
-
if (!CONFIG.IS_DEV) {
|
|
115
|
-
const frontendPath = path.join(CONFIG.PACKAGE_ROOT, 'client', 'dist');
|
|
116
|
-
logger.info(`Serving frontend from: ${frontendPath}`);
|
|
117
|
-
app.use('/*', serveStatic({ root: frontendPath }));
|
|
118
|
-
app.get('*', async (c) => {
|
|
119
|
-
if (c.req.path.startsWith('/api/'))
|
|
120
|
-
return c.notFound();
|
|
121
|
-
try {
|
|
122
|
-
const html = await fs.promises.readFile(path.join(frontendPath, 'index.html'), 'utf8');
|
|
123
|
-
return c.html(html);
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
logger.error({ err }, `Failed to serve index.html from ${frontendPath}`);
|
|
127
|
-
return c.text('Server Error: Frontend build not found.', 500);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
async function main() {
|
|
132
|
-
const dbName = CONFIG.IS_DEV ? CONFIG.DB_NAME_DEV : CONFIG.DB_NAME_PROD;
|
|
133
|
-
const dbPath = path.join(CONFIG.ROOT, dbName);
|
|
134
|
-
const remoteFolder = CONFIG.IS_DEV ? CONFIG.REMOTE_FOLDER_DEV : CONFIG.REMOTE_FOLDER_PROD;
|
|
135
|
-
db = await initializeDatabase(dbPath);
|
|
136
|
-
logger.info(`Database initialized at ${dbPath}`);
|
|
137
|
-
const mangaDbName = CONFIG.IS_DEV ? CONFIG.MANGA_DB_NAME_DEV : CONFIG.MANGA_DB_NAME_PROD;
|
|
138
|
-
const mangaDbPath = path.join(CONFIG.ROOT, mangaDbName);
|
|
139
|
-
mangaDb = await initializeMangaDatabase(mangaDbPath);
|
|
140
|
-
logger.info(`Manga database initialized at ${mangaDbPath}`);
|
|
141
|
-
const tvDbName = CONFIG.IS_DEV ? CONFIG.TV_DB_NAME_DEV : CONFIG.TV_DB_NAME_PROD;
|
|
142
|
-
const tvDbPath = path.join(CONFIG.ROOT, tvDbName);
|
|
143
|
-
tvDb = await initializeTvDatabase(tvDbPath);
|
|
144
|
-
logger.info(`TV database initialized at ${tvDbPath}`);
|
|
145
|
-
const asmrDbName = CONFIG.IS_DEV ? CONFIG.ASMR_DB_NAME_DEV : CONFIG.ASMR_DB_NAME_PROD;
|
|
146
|
-
const asmrDbPath = path.join(CONFIG.ROOT, asmrDbName);
|
|
147
|
-
asmrDb = await initializeAsmrDatabase(asmrDbPath);
|
|
148
|
-
logger.info(`ASMR database initialized at ${asmrDbPath}`);
|
|
149
|
-
await offlineDb.init(db);
|
|
150
|
-
if (await offlineDb.checkWeeklyUpdateDue(db)) {
|
|
151
|
-
logger.info('Weekly offline database update is due on startup, starting background update...');
|
|
152
|
-
offlineDb.executeScheduledUpdate(db).catch((err) => {
|
|
153
|
-
logger.warn({ err: err?.message }, 'Startup scheduled offline database update failed');
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
const rpcEnabledSetting = await SettingsRepository.getByKey(db, 'discordRPCEnabled');
|
|
157
|
-
const isRpcEnabled = rpcEnabledSetting ? rpcEnabledSetting.value === 'true' : true;
|
|
158
|
-
await discordRPCService.setEnabled(isRpcEnabled);
|
|
159
|
-
discordGatewayService.setEnabled(isRpcEnabled);
|
|
160
|
-
checkAnilistStatus().catch(() => { });
|
|
161
|
-
if (CONFIG.PROVIDER_REPO_URL.trim()) {
|
|
162
|
-
setInterval(() => {
|
|
163
|
-
refreshRemoteProviders().catch((err) => logger.error({ err }, 'periodic providers refresh failed'));
|
|
164
|
-
}, CONFIG.PROVIDER_REPO_REFRESH_MS).unref();
|
|
165
|
-
}
|
|
166
|
-
const kindDbs = () => ({
|
|
167
|
-
anime: db,
|
|
168
|
-
manga: mangaDb,
|
|
169
|
-
tv: tvDb,
|
|
170
|
-
asmr: asmrDb,
|
|
171
|
-
});
|
|
172
|
-
for (const kind of Object.keys(KIND_SYNC)) {
|
|
173
|
-
if (!fs.existsSync(KIND_SYNC[kind].manifestPath)) {
|
|
174
|
-
fs.writeFileSync(KIND_SYNC[kind].manifestPath, JSON.stringify({ version: 0 }));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const unsynced = {
|
|
178
|
-
anime: false,
|
|
179
|
-
manga: false,
|
|
180
|
-
tv: false,
|
|
181
|
-
asmr: false,
|
|
182
|
-
};
|
|
183
|
-
const watchers = Object.keys(KIND_SYNC).map((kind) => fs.watch(KIND_SYNC[kind].manifestPath, (eventType) => {
|
|
184
|
-
if (eventType === 'change' || eventType === 'rename') {
|
|
185
|
-
unsynced[kind] = true;
|
|
186
|
-
}
|
|
187
|
-
}));
|
|
188
|
-
const server = serve({ fetch: app.fetch, port: CONFIG.PORT }, () => {
|
|
189
|
-
logger.info(`Server running on http://localhost:${CONFIG.PORT}`);
|
|
190
|
-
});
|
|
191
|
-
runSyncSequence(db, mangaDb)
|
|
192
|
-
.catch((err) => logger.error({ err }, 'background boot sync failed'))
|
|
193
|
-
.finally(() => {
|
|
194
|
-
bootSyncing = false;
|
|
195
|
-
});
|
|
196
|
-
refreshRemoteProviders().catch((err) => logger.error({ err }, 'background providers refresh failed'));
|
|
197
|
-
const kindDbLabel = {
|
|
198
|
-
anime: 'database',
|
|
199
|
-
manga: 'manga database',
|
|
200
|
-
tv: 'TV database',
|
|
201
|
-
asmr: 'ASMR database',
|
|
202
|
-
};
|
|
203
|
-
const syncInterval = setInterval(async () => {
|
|
204
|
-
for (const kind of Object.keys(KIND_SYNC)) {
|
|
205
|
-
if (!unsynced[kind])
|
|
206
|
-
continue;
|
|
207
|
-
logger.info(`Uploading accumulated ${kindDbLabel[kind]} changes...`);
|
|
208
|
-
unsynced[kind] = false;
|
|
209
|
-
try {
|
|
210
|
-
await syncUp(kindDbs()[kind], kind, remoteFolder);
|
|
211
|
-
}
|
|
212
|
-
catch (err) {
|
|
213
|
-
logger.error({ err }, `Failed to upload ${kindDbLabel[kind]} changes`);
|
|
214
|
-
unsynced[kind] = true;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}, 300000);
|
|
218
|
-
const offlineDbInterval = setInterval(async () => {
|
|
219
|
-
if (await offlineDb.checkWeeklyUpdateDue(db)) {
|
|
220
|
-
logger.info('Weekly offline database update triggered by periodic schedule...');
|
|
221
|
-
offlineDb.executeScheduledUpdate(db).catch((err) => {
|
|
222
|
-
logger.warn({ err: err?.message }, 'Interval scheduled offline database update failed');
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}, 6 * 60 * 60 * 1000);
|
|
226
|
-
offlineDbInterval.unref();
|
|
227
|
-
const shutdown = async (signal) => {
|
|
228
|
-
if (isShuttingDown)
|
|
229
|
-
return;
|
|
230
|
-
isShuttingDown = true;
|
|
231
|
-
stopWatchlistDiscovery();
|
|
232
|
-
clearInterval(syncInterval);
|
|
233
|
-
clearInterval(offlineDbInterval);
|
|
234
|
-
discordRPCService.disconnect();
|
|
235
|
-
discordGatewayService.shutdown();
|
|
236
|
-
for (const watcher of watchers) {
|
|
237
|
-
await watcher.close();
|
|
238
|
-
}
|
|
239
|
-
if (server) {
|
|
240
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
241
|
-
}
|
|
242
|
-
const kindFinalError = {
|
|
243
|
-
anime: 'Final sync on shutdown failed',
|
|
244
|
-
manga: 'Final manga sync on shutdown failed',
|
|
245
|
-
tv: 'Final TV sync on shutdown failed',
|
|
246
|
-
asmr: 'Final ASMR sync on shutdown failed',
|
|
247
|
-
};
|
|
248
|
-
for (const kind of Object.keys(KIND_SYNC)) {
|
|
249
|
-
if (!unsynced[kind])
|
|
250
|
-
continue;
|
|
251
|
-
logger.info(`Sync on shutdown: uploading final ${kindDbLabel[kind]} changes...`);
|
|
252
|
-
unsynced[kind] = false;
|
|
253
|
-
try {
|
|
254
|
-
await syncUp(kindDbs()[kind], kind, remoteFolder);
|
|
255
|
-
}
|
|
256
|
-
catch (e) {
|
|
257
|
-
logger.error({ err: e }, kindFinalError[kind]);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
await waitForSync();
|
|
261
|
-
asmrDb.close(() => { });
|
|
262
|
-
tvDb.close(() => { });
|
|
263
|
-
mangaDb.close(() => { });
|
|
264
|
-
db.close(() => {
|
|
265
|
-
notifyServerExit();
|
|
266
|
-
if (signal === 'SIGUSR2') {
|
|
267
|
-
process.kill(process.pid, 'SIGUSR2');
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
setTimeout(() => process.exit(0), 100).unref();
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
};
|
|
274
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
275
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
276
|
-
process.on('SIGHUP', () => shutdown('SIGHUP'));
|
|
277
|
-
process.once('SIGUSR2', () => shutdown('SIGUSR2'));
|
|
278
|
-
app.post('/api/internal/shutdown', (c) => {
|
|
279
|
-
let remoteAddress;
|
|
280
|
-
try {
|
|
281
|
-
remoteAddress = getConnInfo(c).remote.address ?? '';
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
remoteAddress = '';
|
|
285
|
-
}
|
|
286
|
-
const isLoopback = remoteAddress === '::1' ||
|
|
287
|
-
remoteAddress === '127.0.0.1' ||
|
|
288
|
-
remoteAddress === '::ffff:127.0.0.1';
|
|
289
|
-
if (!isLoopback) {
|
|
290
|
-
return c.json({ error: 'Forbidden' }, 403);
|
|
291
|
-
}
|
|
292
|
-
const expectedToken = process.env.INTERNAL_SHUTDOWN_TOKEN;
|
|
293
|
-
const providedToken = c.req.header('x-internal-token');
|
|
294
|
-
if (expectedToken) {
|
|
295
|
-
const expBuf = Buffer.from(expectedToken);
|
|
296
|
-
const provBuf = Buffer.from(typeof providedToken === 'string' ? providedToken : '');
|
|
297
|
-
if (expBuf.length !== provBuf.length || !crypto.timingSafeEqual(expBuf, provBuf)) {
|
|
298
|
-
logger.warn('Unauthorized internal shutdown attempt rejected: invalid token');
|
|
299
|
-
return c.json({ error: 'Forbidden' }, 403);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const response = c.json({ message: 'Shutting down' });
|
|
303
|
-
setTimeout(() => shutdown(), 500);
|
|
304
|
-
return response;
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
main().catch((err) => {
|
|
308
|
-
logger.error({ err }, 'Server failed to start');
|
|
309
|
-
process.exit(1);
|
|
310
|
-
});
|
|
1
|
+
process.setMaxListeners(100);import{EventEmitter as Z}from"events";Z.defaultMaxListeners=100;import l from"path";import{AppCache as ee}from"./utils/cache.utils.js";import D from"fs";import te from"crypto";import{serve as re}from"@hono/node-server";import{serveStatic as ae}from"@hono/node-server/serve-static";import{getConnInfo as oe}from"@hono/node-server/conninfo";import o from"./logger.js";import{notifyServerExit as ne}from"./lib/ipc.js";import{CONFIG as t}from"./config.js";import{initializeDatabase as U,initializeMangaDatabase as ie,initializeTvDatabase as se,initializeAsmrDatabase as de,runFullSyncSequence as ce,syncUp as W,waitForSync as fe,KIND_SYNC as c}from"./sync.js";import{isJasmrApi as le}from"./hono/asmr.js";import{loadRemoteProviders as me}from"./providers/remote-loader.js";import{discordRPCService as B}from"./discord-rpc.js";import{discordGatewayService as G}from"./discord-gateway.js";import{SettingsRepository as pe}from"./repositories/settings.repository.js";import{createHonoApp as ue}from"./app-hono.js";import{startWatchlistDiscovery as ge,stopWatchlistDiscovery as ve}from"./hono/watchlist.js";import{checkAnilistStatus as he}from"./lib/anilist.js";import{offlineDb as v}from"./lib/offline-db.js";const y=ue(()=>({shuttingDown:S,dbReady:!!i}),()=>({shuttingDown:S,dbsReady:!!i&&!!m&&!!p&&!!u,bootSyncing:z}),()=>({db:i,mangaDb:m,tvDb:p,asmrDb:u}),{initializeDatabase:U,setDb:r=>{i=r}},{getCatalog:P,refresh:w},q,{getApiCache:()=>K,getProviders:()=>h,getMangaProvider:r=>R[r],getTvProvider:r=>E[r],getProxyRules:()=>L,getJasmr:()=>{const r=h.jasmr;return le(r)?r:void 0}}),K=new ee({ttlSeconds:3600,maxKeys:5e3}),h={},R={},E={};let L=[],$=[];function be(){return t.PROVIDER_ENABLED.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean)}function P(){return $}async function w(){const r=t.PROVIDER_REPO_URL.trim();if(!r)return P();try{const a=await me({cache:K,registryUrl:r,enabledProviders:be()});if(a.catalog.length===0)return o.warn("remote providers refresh returned nothing, keeping current set"),P();for(const n of Object.keys(h))n in a.providers||delete h[n];Object.assign(h,a.providers);for(const n of Object.keys(R))n in a.mangaProviders||delete R[n];Object.assign(R,a.mangaProviders);for(const n of Object.keys(E))n in a.tvProviders||delete E[n];Object.assign(E,a.tvProviders),L=a.proxyRules,$=a.catalog,o.info({count:Object.keys(a.providers).length,mangaCount:Object.keys(a.mangaProviders).length,tvCount:Object.keys(a.tvProviders).length},"remote providers refreshed")}catch(a){o.error({err:a},"remote providers refresh failed")}return P()}let i,m,p,u,S=!1,z=!0;async function q(r,a,n){const O=t.IS_DEV?t.REMOTE_FOLDER_DEV:t.REMOTE_FOLDER_PROD;await ce({db:r,mangaDb:a,tvDb:p,asmrDb:u},{remoteFolder:O,preferredProvider:n})}if(ge(()=>i),!t.IS_DEV){const r=l.join(t.PACKAGE_ROOT,"client","dist");o.info(`Serving frontend from: ${r}`),y.use("/*",ae({root:r})),y.get("*",async a=>{if(a.req.path.startsWith("/api/"))return a.notFound();try{const n=await D.promises.readFile(l.join(r,"index.html"),"utf8");return a.html(n)}catch(n){return o.error({err:n},`Failed to serve index.html from ${r}`),a.text("Server Error: Frontend build not found.",500)}})}async function De(){const r=t.IS_DEV?t.DB_NAME_DEV:t.DB_NAME_PROD,a=l.join(t.ROOT,r),n=t.IS_DEV?t.REMOTE_FOLDER_DEV:t.REMOTE_FOLDER_PROD;i=await U(a),o.info(`Database initialized at ${a}`);const O=t.IS_DEV?t.MANGA_DB_NAME_DEV:t.MANGA_DB_NAME_PROD,I=l.join(t.ROOT,O);m=await ie(I),o.info(`Manga database initialized at ${I}`);const H=t.IS_DEV?t.TV_DB_NAME_DEV:t.TV_DB_NAME_PROD,M=l.join(t.ROOT,H);p=await se(M),o.info(`TV database initialized at ${M}`);const J=t.IS_DEV?t.ASMR_DB_NAME_DEV:t.ASMR_DB_NAME_PROD,k=l.join(t.ROOT,J);u=await de(k),o.info(`ASMR database initialized at ${k}`),await v.init(i),await v.checkWeeklyUpdateDue(i)&&(o.info("Weekly offline database update is due on startup, starting background update..."),v.executeScheduledUpdate(i).catch(e=>{o.warn({err:e?.message},"Startup scheduled offline database update failed")}));const T=await pe.getByKey(i,"discordRPCEnabled"),j=T?T.value==="true":!0;await B.setEnabled(j),G.setEnabled(j),he().catch(()=>{}),t.PROVIDER_REPO_URL.trim()&&setInterval(()=>{w().catch(e=>o.error({err:e},"periodic providers refresh failed"))},t.PROVIDER_REPO_REFRESH_MS).unref();const A=()=>({anime:i,manga:m,tv:p,asmr:u});for(const e of Object.keys(c))D.existsSync(c[e].manifestPath)||D.writeFileSync(c[e].manifestPath,JSON.stringify({version:0}));const f={anime:!1,manga:!1,tv:!1,asmr:!1},Y=Object.keys(c).map(e=>D.watch(c[e].manifestPath,s=>{(s==="change"||s==="rename")&&(f[e]=!0)})),N=re({fetch:y.fetch,port:t.PORT},()=>{o.info(`Server running on http://localhost:${t.PORT}`)});q(i,m).catch(e=>o.error({err:e},"background boot sync failed")).finally(()=>{z=!1}),w().catch(e=>o.error({err:e},"background providers refresh failed"));const _={anime:"database",manga:"manga database",tv:"TV database",asmr:"ASMR database"},Q=setInterval(async()=>{for(const e of Object.keys(c))if(f[e]){o.info(`Uploading accumulated ${_[e]} changes...`),f[e]=!1;try{await W(A()[e],e,n)}catch(s){o.error({err:s},`Failed to upload ${_[e]} changes`),f[e]=!0}}},3e5),F=setInterval(async()=>{await v.checkWeeklyUpdateDue(i)&&(o.info("Weekly offline database update triggered by periodic schedule..."),v.executeScheduledUpdate(i).catch(e=>{o.warn({err:e?.message},"Interval scheduled offline database update failed")}))},360*60*1e3);F.unref();const g=async e=>{if(S)return;S=!0,ve(),clearInterval(Q),clearInterval(F),B.disconnect(),G.shutdown();for(const d of Y)await d.close();N&&await new Promise(d=>N.close(()=>d()));const s={anime:"Final sync on shutdown failed",manga:"Final manga sync on shutdown failed",tv:"Final TV sync on shutdown failed",asmr:"Final ASMR sync on shutdown failed"};for(const d of Object.keys(c))if(f[d]){o.info(`Sync on shutdown: uploading final ${_[d]} changes...`),f[d]=!1;try{await W(A()[d],d,n)}catch(b){o.error({err:b},s[d])}}await fe(),u.close(()=>{}),p.close(()=>{}),m.close(()=>{}),i.close(()=>{ne(),e==="SIGUSR2"?process.kill(process.pid,"SIGUSR2"):setTimeout(()=>process.exit(0),100).unref()})};process.on("SIGINT",()=>g("SIGINT")),process.on("SIGTERM",()=>g("SIGTERM")),process.on("SIGHUP",()=>g("SIGHUP")),process.once("SIGUSR2",()=>g("SIGUSR2")),y.post("/api/internal/shutdown",e=>{let s;try{s=oe(e).remote.address??""}catch{s=""}if(!(s==="::1"||s==="127.0.0.1"||s==="::ffff:127.0.0.1"))return e.json({error:"Forbidden"},403);const b=process.env.INTERNAL_SHUTDOWN_TOKEN,V=e.req.header("x-internal-token");if(b){const x=Buffer.from(b),C=Buffer.from(typeof V=="string"?V:"");if(x.length!==C.length||!te.timingSafeEqual(x,C))return o.warn("Unauthorized internal shutdown attempt rejected: invalid token"),e.json({error:"Forbidden"},403)}const X=e.json({message:"Shutting down"});return setTimeout(()=>g(),500),X})}De().catch(r=>{o.error({err:r},"Server failed to start"),process.exit(1)});
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
"GITHUB_CLIENT_ID": "Ov23liT1ZtPk7XtN9PZk",
|
|
3
|
-
"GOOGLE_AUTH_WORKER_URL": "https://google-auth-client.ramiserifpersia.workers.dev",
|
|
4
|
-
"DISCORD_CLIENT_ID": "1512253778022367426",
|
|
5
|
-
"PROVIDER_REPO_URL": "https://raw.githubusercontent.com/serifpersia/dango-providers/main/registry.json"
|
|
6
|
-
};
|
|
1
|
+
const t={GITHUB_CLIENT_ID:"Ov23liT1ZtPk7XtN9PZk",GOOGLE_AUTH_WORKER_URL:"https://google-auth-client.ramiserifpersia.workers.dev",DISCORD_CLIENT_ID:"1512253778022367426",PROVIDER_REPO_URL:"https://raw.githubusercontent.com/serifpersia/dango-providers/main/registry.json"};export{t as SHIPPED_DEFAULTS};
|
|
@@ -1,91 +1 @@
|
|
|
1
|
-
import path from '
|
|
2
|
-
import logger from './logger.js';
|
|
3
|
-
import { CONFIG } from './config.js';
|
|
4
|
-
import { dbAll, dbGet } from './utils/db-utils.js';
|
|
5
|
-
import { isTempSyncRow } from './lib/temp-ids.js';
|
|
6
|
-
const log = logger.child({ module: 'SyncPayload' });
|
|
7
|
-
export const ANIME_SYNC_TABLES = [
|
|
8
|
-
'watchlist',
|
|
9
|
-
'watched_episodes',
|
|
10
|
-
'queue',
|
|
11
|
-
'settings',
|
|
12
|
-
'shows_meta',
|
|
13
|
-
'sync_metadata',
|
|
14
|
-
'dismissed_notifications',
|
|
15
|
-
'discovered_notifications',
|
|
16
|
-
];
|
|
17
|
-
export const MANGA_SYNC_TABLES = ['manga_library', 'manga_progress', 'sync_metadata'];
|
|
18
|
-
export const TV_SYNC_TABLES = ['tv_library', 'tv_progress', 'sync_metadata'];
|
|
19
|
-
export const ASMR_SYNC_TABLES = ['asmr_library', 'asmr_progress', 'sync_metadata'];
|
|
20
|
-
export function readPayloadVersion(payload) {
|
|
21
|
-
const meta = payload.tables['sync_metadata'];
|
|
22
|
-
const row = meta?.find((r) => r.key === 'db_version');
|
|
23
|
-
const v = row?.value;
|
|
24
|
-
return typeof v === 'number' ? v : Number(v || payload.version || 0);
|
|
25
|
-
}
|
|
26
|
-
export function normalizePayload(input, tables, label) {
|
|
27
|
-
if (!input || typeof input !== 'object' || !('tables' in input)) {
|
|
28
|
-
throw new Error(`Invalid ${label} sync payload.`);
|
|
29
|
-
}
|
|
30
|
-
const payload = input;
|
|
31
|
-
for (const table of tables) {
|
|
32
|
-
if (!Array.isArray(payload.tables?.[table])) {
|
|
33
|
-
throw new Error(`Invalid ${label} sync payload: missing ${String(table)}.`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return payload;
|
|
37
|
-
}
|
|
38
|
-
export function exportTables(db, tables) {
|
|
39
|
-
const out = {};
|
|
40
|
-
for (const table of tables) {
|
|
41
|
-
const rows = dbAll(db, `SELECT * FROM "${String(table).replace(/"/g, '""')}"`);
|
|
42
|
-
out[table] = rows.filter((row) => !isTempSyncRow(row));
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
version: readPayloadVersion({ version: 0, exportedAt: '', tables: out }),
|
|
46
|
-
exportedAt: new Date().toISOString(),
|
|
47
|
-
tables: out,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
export function importTables(db, tables, payload, opts) {
|
|
51
|
-
const asRec = payload.tables;
|
|
52
|
-
const remoteLibraryRows = opts.libraryTables.reduce((n, t) => n + (asRec[t]?.length ?? 0), 0);
|
|
53
|
-
const localLibraryRows = opts.libraryTables.length === 0
|
|
54
|
-
? 1
|
|
55
|
-
: (dbGet(db, `SELECT ${opts.libraryTables
|
|
56
|
-
.map((t) => `(SELECT COUNT(*) FROM "${String(t).replace(/"/g, '""')}")`)
|
|
57
|
-
.join(' + ')} AS n`)?.n ?? 0);
|
|
58
|
-
if (remoteLibraryRows === 0 && localLibraryRows > 0) {
|
|
59
|
-
throw new Error('Sync down refused: remote library is empty while local library has data');
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
db.backup(path.join(CONFIG.ROOT, opts.backupName));
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
log.warn({ err }, 'Pre-sync backup failed, continuing without backup');
|
|
66
|
-
}
|
|
67
|
-
const localColumns = new Map();
|
|
68
|
-
for (const table of tables) {
|
|
69
|
-
const cols = db.all(`PRAGMA table_info("${String(table)}")`);
|
|
70
|
-
localColumns.set(String(table), new Set(cols.map((c) => c.name)));
|
|
71
|
-
}
|
|
72
|
-
db.serialize(() => {
|
|
73
|
-
for (const table of tables) {
|
|
74
|
-
db.run(`DELETE FROM "${String(table).replace(/"/g, '""')}"`);
|
|
75
|
-
}
|
|
76
|
-
for (const table of tables) {
|
|
77
|
-
const known = localColumns.get(String(table));
|
|
78
|
-
for (const row of asRec[String(table)] || []) {
|
|
79
|
-
if (isTempSyncRow(row))
|
|
80
|
-
continue;
|
|
81
|
-
const columns = Object.keys(row).filter((c) => known?.has(c));
|
|
82
|
-
if (columns.length === 0)
|
|
83
|
-
continue;
|
|
84
|
-
const columnSql = columns.map((c) => `"${c.replace(/"/g, '""')}"`).join(', ');
|
|
85
|
-
const placeholders = columns.map(() => '?').join(', ');
|
|
86
|
-
const values = columns.map((c) => row[c]);
|
|
87
|
-
db.run(`INSERT INTO "${String(table).replace(/"/g, '""')}" (${columnSql}) VALUES (${placeholders})`, values);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
1
|
+
import S from"path";import f from"./logger.js";import{CONFIG as u}from"./config.js";import{dbAll as w,dbGet as _}from"./utils/db-utils.js";import{isTempSyncRow as d}from"./lib/temp-ids.js";const R=f.child({module:"SyncPayload"}),$=["watchlist","watched_episodes","queue","settings","shows_meta","sync_metadata","dismissed_notifications","discovered_notifications"],L=["manga_library","manga_progress","sync_metadata"],P=["tv_library","tv_progress","sync_metadata"],k=["asmr_library","asmr_progress","sync_metadata"];function x(e){const n=e.tables.sync_metadata?.find(t=>t.key==="db_version")?.value;return typeof n=="number"?n:Number(n||e.version||0)}function C(e,o,a){if(!e||typeof e!="object"||!("tables"in e))throw new Error(`Invalid ${a} sync payload.`);const n=e;for(const t of o)if(!Array.isArray(n.tables?.[t]))throw new Error(`Invalid ${a} sync payload: missing ${String(t)}.`);return n}function O(e,o){const a={};for(const n of o){const t=w(e,`SELECT * FROM "${String(n).replace(/"/g,'""')}"`);a[n]=t.filter(y=>!d(y))}return{version:x({version:0,exportedAt:"",tables:a}),exportedAt:new Date().toISOString(),tables:a}}function M(e,o,a,n){const t=a.tables,y=n.libraryTables.reduce((r,s)=>r+(t[s]?.length??0),0),p=n.libraryTables.length===0?1:_(e,`SELECT ${n.libraryTables.map(r=>`(SELECT COUNT(*) FROM "${String(r).replace(/"/g,'""')}")`).join(" + ")} AS n`)?.n??0;if(y===0&&p>0)throw new Error("Sync down refused: remote library is empty while local library has data");try{e.backup(S.join(u.ROOT,n.backupName))}catch(r){R.warn({err:r},"Pre-sync backup failed, continuing without backup")}const b=new Map;for(const r of o){const s=e.all(`PRAGMA table_info("${String(r)}")`);b.set(String(r),new Set(s.map(i=>i.name)))}e.serialize(()=>{for(const r of o)e.run(`DELETE FROM "${String(r).replace(/"/g,'""')}"`);for(const r of o){const s=b.get(String(r));for(const i of t[String(r)]||[]){if(d(i))continue;const c=Object.keys(i).filter(l=>s?.has(l));if(c.length===0)continue;const T=c.map(l=>`"${l.replace(/"/g,'""')}"`).join(", "),m=c.map(()=>"?").join(", "),g=c.map(l=>i[l]);e.run(`INSERT INTO "${String(r).replace(/"/g,'""')}" (${T}) VALUES (${m})`,g)}}})}export{$ as ANIME_SYNC_TABLES,k as ASMR_SYNC_TABLES,L as MANGA_SYNC_TABLES,P as TV_SYNC_TABLES,O as exportTables,M as importTables,C as normalizePayload,x as readPayloadVersion};
|