@serifpersia/dango 3.6.7 → 3.6.9
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-DKDJK8Mp.js} +1 -1
- package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-CTcvfjva.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_UiacIN.js} +2 -2
- package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-DMSN2vxl.js} +2 -2
- package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
- package/client/dist/assets/{Home-DUkUTTTy.js → Home-pwmK9_Eh.js} +1 -1
- package/client/dist/assets/{Insights-QCh4xW12.js → Insights-m7kFzg2A.js} +1 -1
- package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-C1wpPvu6.js} +1 -1
- package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
- package/client/dist/assets/{Manga-HydR-L7o.js → Manga-ChRCExhK.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DSHJPo2A.js} +1 -1
- package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-CzOVY1wA.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
- package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-CifQMfhs.js} +1 -1
- package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CM10yB9V.js} +1 -1
- package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-6Kovbsqw.js} +1 -1
- package/client/dist/assets/{Player-CsH4x_I_.js → Player-CK0hT-u4.js} +2 -2
- package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-Bb66uqT9.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
- package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-DUK7URAE.js} +1 -1
- package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-Ck9LJThU.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BUphIv0k.js} +1 -1
- package/client/dist/assets/{Search-DFkhuk-x.js → Search-zJyP15h6.js} +1 -1
- package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-BYdoSEUh.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CqgbOhjX.js} +1 -1
- package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-BVjyv6CM.js} +1 -1
- package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-L13ylswR.js} +1 -1
- package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-Cvcm5U2D.js} +1 -1
- package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-DhxfKvYm.js} +1 -1
- package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-P8bFE6_X.js} +1 -1
- package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-D5gVPjtb.js} +1 -1
- package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-Cjq8l3rU.js} +1 -1
- package/client/dist/assets/{hls-BGCX7mJy.js → hls-BkzuANAg.js} +1 -1
- package/client/dist/assets/{index-CnfZwsds.js → index-CLVgzKLk.js} +19 -19
- package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-DZJZu-so.js} +1 -1
- package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CvrMGHuo.js} +1 -1
- package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
- package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary--MiN11uR.js} +1 -1
- package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-CR5XhKKS.js} +1 -1
- package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-DqdT4fA5.js} +1 -1
- package/client/dist/assets/{useTv-DGflCsSI.js → useTv-BL0HMDyP.js} +1 -1
- package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-D4U4iqN8.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +3 -5
- package/server/dist/config.js +1 -174
- package/server/dist/server.js +1068 -289
- package/server/dist/shipped-defaults.js +1 -6
- package/server/package.json +4 -5
- package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
- package/client/dist/assets/ListeningList-DguKjI_t.js +0 -1
- package/client/dist/assets/Music-BGH_0voi.css +0 -1
- package/client/dist/assets/Music-CQpyXTLH.js +0 -1
- package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
- package/client/dist/assets/Radio-DF6g6odd.js +0 -1
- package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
- package/client/dist/assets/useAsmrPopup-SymV1J-b.js +0 -1
- package/server/dist/app-auth.js +0 -157
- package/server/dist/app-hono.js +0 -598
- package/server/dist/db/drizzle.js +0 -16
- package/server/dist/db/schema-anime.js +0 -61
- package/server/dist/db/schema-asmr.js +0 -13
- package/server/dist/db/schema-manga.js +0 -31
- package/server/dist/db/schema-tv.js +0 -18
- package/server/dist/db.js +0 -217
- package/server/dist/discord-gateway.js +0 -385
- package/server/dist/discord-rpc.js +0 -413
- package/server/dist/github-sync.js +0 -508
- package/server/dist/google.js +0 -741
- package/server/dist/hono/asmr.js +0 -10
- package/server/dist/hono/auth.js +0 -49
- package/server/dist/hono/music.js +0 -209
- package/server/dist/hono/proxy.js +0 -827
- package/server/dist/hono/tv.js +0 -200
- package/server/dist/hono/watchlist.js +0 -341
- package/server/dist/lib/anilist.js +0 -1583
- package/server/dist/lib/anischedule.js +0 -191
- package/server/dist/lib/insights.js +0 -185
- package/server/dist/lib/ipc.js +0 -23
- package/server/dist/lib/kitsu.js +0 -517
- package/server/dist/lib/mal-import.js +0 -291
- package/server/dist/lib/mal.js +0 -957
- package/server/dist/lib/migration.js +0 -310
- package/server/dist/lib/offline-db.js +0 -395
- package/server/dist/lib/potoken.js +0 -135
- package/server/dist/lib/recommendations.service.js +0 -216
- package/server/dist/lib/temp-ids.js +0 -3
- package/server/dist/lib/tmdb.js +0 -72
- package/server/dist/lib/tracker/anilist-tracker.js +0 -308
- package/server/dist/lib/tracker/manga-sync.service.js +0 -773
- package/server/dist/lib/tracker/sync-state.js +0 -21
- package/server/dist/lib/tracker/sync.service.js +0 -476
- package/server/dist/lib/translate.js +0 -79
- package/server/dist/lib/ump.js +0 -106
- package/server/dist/lib/ytmusic.js +0 -162
- package/server/dist/logger.js +0 -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 +0 -614
- package/server/dist/providers/remote-types.js +0 -159
- package/server/dist/providers/title-matching.js +0 -226
- package/server/dist/providers/tv.types.js +0 -1
- package/server/dist/rclone.js +0 -120
- package/server/dist/repositories/asmr.repository.js +0 -77
- package/server/dist/repositories/insights.repository.js +0 -106
- package/server/dist/repositories/library.repository.js +0 -94
- package/server/dist/repositories/mal-cache.repository.js +0 -52
- package/server/dist/repositories/manga.repository.js +0 -95
- package/server/dist/repositories/notifications.repository.js +0 -60
- package/server/dist/repositories/queue.repository.js +0 -72
- package/server/dist/repositories/settings.repository.js +0 -30
- package/server/dist/repositories/shows-meta.repository.js +0 -58
- package/server/dist/repositories/temp-show-ids.repository.js +0 -63
- package/server/dist/repositories/tv.repository.js +0 -93
- package/server/dist/repositories/watched-episodes.repository.js +0 -82
- package/server/dist/repositories/watchlist.repository.js +0 -60
- package/server/dist/scripts/backfill-episode-duration.js +0 -42
- package/server/dist/sync-payload.js +0 -91
- package/server/dist/sync.js +0 -657
- package/server/dist/trpc/context.js +0 -7
- package/server/dist/trpc/errors.js +0 -15
- package/server/dist/trpc/index.js +0 -18
- package/server/dist/trpc/router.js +0 -49
- package/server/dist/trpc/routers/asmr-library.js +0 -112
- package/server/dist/trpc/routers/asmr-progress.js +0 -121
- package/server/dist/trpc/routers/asmr.js +0 -87
- package/server/dist/trpc/routers/auth.js +0 -194
- package/server/dist/trpc/routers/continue-watching.js +0 -81
- package/server/dist/trpc/routers/data.js +0 -1026
- package/server/dist/trpc/routers/discord.js +0 -285
- package/server/dist/trpc/routers/discovery.js +0 -11
- package/server/dist/trpc/routers/insights.js +0 -14
- package/server/dist/trpc/routers/manga-library.js +0 -263
- package/server/dist/trpc/routers/manga-progress.js +0 -169
- package/server/dist/trpc/routers/manga.js +0 -201
- package/server/dist/trpc/routers/music.js +0 -655
- package/server/dist/trpc/routers/notifications.js +0 -80
- package/server/dist/trpc/routers/progress.js +0 -109
- package/server/dist/trpc/routers/queue.js +0 -93
- package/server/dist/trpc/routers/radio.js +0 -145
- package/server/dist/trpc/routers/settings.js +0 -96
- package/server/dist/trpc/routers/tracker.js +0 -213
- package/server/dist/trpc/routers/tv-library.js +0 -135
- package/server/dist/trpc/routers/tv-progress.js +0 -159
- package/server/dist/trpc/routers/tv.js +0 -551
- package/server/dist/trpc/routers/watchlist.js +0 -550
- package/server/dist/trpc/validation.js +0 -258
- package/server/dist/utils/cache.utils.js +0 -25
- package/server/dist/utils/cookie.utils.js +0 -13
- package/server/dist/utils/db-utils.js +0 -17
- package/server/dist/utils/env.utils.js +0 -77
- package/server/dist/utils/http.utils.js +0 -82
- package/server/dist/utils/request-context.js +0 -2
- package/server/dist/utils/security.utils.js +0 -76
- /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { eq, sql } from 'drizzle-orm';
|
|
2
|
-
import { getDrizzle } from '../db/drizzle.js';
|
|
3
|
-
import { watchedEpisodes } from '../db/schema-anime.js';
|
|
4
|
-
export const WatchedEpisodesRepository = {
|
|
5
|
-
getByShowAndEpisode: async (db, showId, episodeNumber) => {
|
|
6
|
-
const rows = await getDrizzle(db).all(sql `
|
|
7
|
-
SELECT currentTime, duration FROM watched_episodes WHERE showId = ${showId} AND episodeNumber = ${episodeNumber}`);
|
|
8
|
-
return rows[0];
|
|
9
|
-
},
|
|
10
|
-
getWatchedEpisodeNumbers: async (db, showId) => {
|
|
11
|
-
const rows = await getDrizzle(db).all(sql `SELECT episodeNumber FROM watched_episodes WHERE showId = ${showId}`);
|
|
12
|
-
return rows.map((r) => r.episodeNumber);
|
|
13
|
-
},
|
|
14
|
-
getByShow: (db, showId) => getDrizzle(db).all(sql `
|
|
15
|
-
SELECT showId, episodeNumber, currentTime, duration, watchedAt FROM watched_episodes WHERE showId = ${showId} ORDER BY CAST(episodeNumber AS REAL) ASC`),
|
|
16
|
-
getLatestResumeProgress: async (db, showId) => {
|
|
17
|
-
const rows = await getDrizzle(db).all(sql `
|
|
18
|
-
SELECT showId, episodeNumber, currentTime, duration, watchedAt
|
|
19
|
-
FROM watched_episodes
|
|
20
|
-
WHERE showId = ${showId} AND currentTime > 5 AND (duration <= 0 OR currentTime < duration * 0.8)
|
|
21
|
-
ORDER BY watchedAt DESC
|
|
22
|
-
LIMIT 1`);
|
|
23
|
-
return rows[0];
|
|
24
|
-
},
|
|
25
|
-
upsert: (db, data) => getDrizzle(db)
|
|
26
|
-
.insert(watchedEpisodes)
|
|
27
|
-
.values({
|
|
28
|
-
showId: data.showId,
|
|
29
|
-
episodeNumber: data.episodeNumber,
|
|
30
|
-
watchedAt: sql `CURRENT_TIMESTAMP`,
|
|
31
|
-
currentTime: data.currentTime,
|
|
32
|
-
duration: data.duration,
|
|
33
|
-
})
|
|
34
|
-
.onConflictDoUpdate({
|
|
35
|
-
target: [watchedEpisodes.showId, watchedEpisodes.episodeNumber],
|
|
36
|
-
set: {
|
|
37
|
-
watchedAt: sql `CURRENT_TIMESTAMP`,
|
|
38
|
-
currentTime: data.currentTime,
|
|
39
|
-
duration: data.duration,
|
|
40
|
-
},
|
|
41
|
-
}),
|
|
42
|
-
insertIfMissing: (db, data) => getDrizzle(db)
|
|
43
|
-
.insert(watchedEpisodes)
|
|
44
|
-
.values({
|
|
45
|
-
showId: data.showId,
|
|
46
|
-
episodeNumber: data.episodeNumber,
|
|
47
|
-
watchedAt: data.watchedAt ? sql `${data.watchedAt}` : sql `CURRENT_TIMESTAMP`,
|
|
48
|
-
currentTime: 0,
|
|
49
|
-
duration: 0,
|
|
50
|
-
})
|
|
51
|
-
.onConflictDoNothing(),
|
|
52
|
-
deleteByShow: (db, showId) => getDrizzle(db).delete(watchedEpisodes).where(eq(watchedEpisodes.showId, showId)),
|
|
53
|
-
getContinueWatching: (db, limit) => {
|
|
54
|
-
const limitClause = typeof limit === 'number' ? `LIMIT ${limit}` : '';
|
|
55
|
-
return getDrizzle(db).all(sql `
|
|
56
|
-
SELECT
|
|
57
|
-
we.showId as _id,
|
|
58
|
-
we.showId as id,
|
|
59
|
-
COALESCE(NULLIF(w.name, ''), sm.name) as name,
|
|
60
|
-
COALESCE(NULLIF(w.thumbnail, ''), NULLIF(sm.thumbnail, ''), '') as thumbnail,
|
|
61
|
-
COALESCE(NULLIF(w.nativeName, ''), sm.nativeName) as nativeName,
|
|
62
|
-
COALESCE(NULLIF(w.englishName, ''), sm.englishName) as englishName,
|
|
63
|
-
COALESCE(NULLIF(w.type, ''), sm.type) as type,
|
|
64
|
-
sm.episodeCount,
|
|
65
|
-
sm.type as smType,
|
|
66
|
-
sm.isAdult as isAdult,
|
|
67
|
-
w.status as watchlistStatus,
|
|
68
|
-
(SELECT COUNT(DISTINCT episodeNumber) FROM watched_episodes WHERE showId = we.showId) as watchedCount,
|
|
69
|
-
we.episodeNumber, we.currentTime, we.duration, we.watchedAt
|
|
70
|
-
FROM (
|
|
71
|
-
SELECT *, ROW_NUMBER() OVER(PARTITION BY showId ORDER BY (currentTime > 0 OR duration > 0) DESC, watchedAt DESC) as rn
|
|
72
|
-
FROM watched_episodes
|
|
73
|
-
) we
|
|
74
|
-
LEFT JOIN watchlist w ON we.showId = w.id
|
|
75
|
-
LEFT JOIN shows_meta sm ON we.showId = sm.id
|
|
76
|
-
WHERE we.rn = 1
|
|
77
|
-
AND (w.status IS NULL OR w.status = 'Watching')
|
|
78
|
-
AND (w.id IS NOT NULL OR sm.id IS NOT NULL)
|
|
79
|
-
ORDER BY we.watchedAt DESC
|
|
80
|
-
${sql.raw(limitClause)}`);
|
|
81
|
-
},
|
|
82
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { eq, inArray, sql } from 'drizzle-orm';
|
|
2
|
-
import { getDrizzle } from '../db/drizzle.js';
|
|
3
|
-
import { watchlist } from '../db/schema-anime.js';
|
|
4
|
-
export const WatchlistRepository = {
|
|
5
|
-
getById: async (db, id) => {
|
|
6
|
-
const rows = await getDrizzle(db).all(sql `SELECT * FROM watchlist WHERE id = ${id}`);
|
|
7
|
-
return rows[0];
|
|
8
|
-
},
|
|
9
|
-
exists: async (db, id) => {
|
|
10
|
-
const rows = await getDrizzle(db).all(sql `SELECT EXISTS(SELECT 1 FROM watchlist WHERE id = ${id}) as inWatchlist`);
|
|
11
|
-
return !!(rows[0] && rows[0].inWatchlist);
|
|
12
|
-
},
|
|
13
|
-
getAll: (db, status, limit, offset) => {
|
|
14
|
-
const q = sql `SELECT * FROM watchlist`;
|
|
15
|
-
if (status && status !== 'All') {
|
|
16
|
-
q.append(sql ` WHERE status = ${status}`);
|
|
17
|
-
}
|
|
18
|
-
q.append(sql ` ORDER BY rowid DESC`);
|
|
19
|
-
if (limit !== undefined && offset !== undefined) {
|
|
20
|
-
q.append(sql ` LIMIT ${limit} OFFSET ${offset}`);
|
|
21
|
-
}
|
|
22
|
-
return getDrizzle(db).all(q);
|
|
23
|
-
},
|
|
24
|
-
getCount: async (db, status) => {
|
|
25
|
-
const q = sql `SELECT COUNT(*) as total FROM watchlist`;
|
|
26
|
-
if (status && status !== 'All') {
|
|
27
|
-
q.append(sql ` WHERE status = ${status}`);
|
|
28
|
-
}
|
|
29
|
-
const rows = await getDrizzle(db).all(q);
|
|
30
|
-
return rows[0]?.total || 0;
|
|
31
|
-
},
|
|
32
|
-
upsert: (db, data) => getDrizzle(db).run(sql `
|
|
33
|
-
INSERT INTO watchlist (id, name, thumbnail, status, nativeName, englishName, type) VALUES (${data.id}, ${data.name}, ${data.thumbnail}, ${data.status}, ${data.nativeName}, ${data.englishName}, ${data.type})
|
|
34
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
35
|
-
name = COALESCE(NULLIF(EXCLUDED.name, ''), watchlist.name),
|
|
36
|
-
thumbnail = COALESCE(NULLIF(EXCLUDED.thumbnail, ''), watchlist.thumbnail),
|
|
37
|
-
status = COALESCE(NULLIF(EXCLUDED.status, ''), watchlist.status),
|
|
38
|
-
nativeName = COALESCE(NULLIF(EXCLUDED.nativeName, ''), watchlist.nativeName),
|
|
39
|
-
englishName = COALESCE(NULLIF(EXCLUDED.englishName, ''), watchlist.englishName),
|
|
40
|
-
type = COALESCE(NULLIF(EXCLUDED.type, ''), watchlist.type)`),
|
|
41
|
-
updateStatus: (db, id, status) => getDrizzle(db).update(watchlist).set({ status }).where(eq(watchlist.id, id)),
|
|
42
|
-
updateThumbnail: (db, id, thumbnail) => {
|
|
43
|
-
if (!thumbnail || thumbnail.trim() === '')
|
|
44
|
-
return Promise.resolve();
|
|
45
|
-
return getDrizzle(db).update(watchlist).set({ thumbnail }).where(eq(watchlist.id, id));
|
|
46
|
-
},
|
|
47
|
-
delete: (db, id) => getDrizzle(db).delete(watchlist).where(eq(watchlist.id, id)),
|
|
48
|
-
deleteMany: (db, ids) => {
|
|
49
|
-
if (ids.length === 0)
|
|
50
|
-
return Promise.resolve();
|
|
51
|
-
return getDrizzle(db).delete(watchlist).where(inArray(watchlist.id, ids));
|
|
52
|
-
},
|
|
53
|
-
updateStatusMany: (db, ids, status) => {
|
|
54
|
-
if (ids.length === 0)
|
|
55
|
-
return Promise.resolve();
|
|
56
|
-
return getDrizzle(db).update(watchlist).set({ status }).where(inArray(watchlist.id, ids));
|
|
57
|
-
},
|
|
58
|
-
getWatchingShows: (db) => getDrizzle(db).all(sql `SELECT id, name, thumbnail, nativeName, englishName FROM watchlist WHERE status = 'Watching'`),
|
|
59
|
-
getMissingThumbnails: (db) => getDrizzle(db).all(sql `SELECT id, name FROM watchlist WHERE thumbnail IS NULL OR TRIM(thumbnail) = ''`),
|
|
60
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { CONFIG } from '../config.js';
|
|
3
|
-
import { DatabaseWrapper } from '../db.js';
|
|
4
|
-
import { anilistRequest } from '../lib/anilist.js';
|
|
5
|
-
import { dbAll, dbRun } from '../utils/db-utils.js';
|
|
6
|
-
const BATCH_SIZE = 50;
|
|
7
|
-
async function main() {
|
|
8
|
-
const dbName = CONFIG.IS_DEV ? CONFIG.DB_NAME_DEV : CONFIG.DB_NAME_PROD;
|
|
9
|
-
const db = await DatabaseWrapper.create(path.join(CONFIG.ROOT, dbName));
|
|
10
|
-
db.configure('busyTimeout', 10000);
|
|
11
|
-
const cols = dbAll(db, 'PRAGMA table_info(shows_meta)');
|
|
12
|
-
if (!cols.some((c) => c.name === 'episodeDuration')) {
|
|
13
|
-
db.run('ALTER TABLE shows_meta ADD COLUMN episodeDuration INTEGER');
|
|
14
|
-
}
|
|
15
|
-
const missing = dbAll(db, 'SELECT id, anilistId FROM shows_meta WHERE anilistId IS NOT NULL AND episodeDuration IS NULL');
|
|
16
|
-
if (missing.length === 0) {
|
|
17
|
-
console.log('Nothing to backfill.');
|
|
18
|
-
db.close();
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
let updated = 0;
|
|
22
|
-
for (let i = 0; i < missing.length; i += BATCH_SIZE) {
|
|
23
|
-
const batch = missing.slice(i, i + BATCH_SIZE);
|
|
24
|
-
const res = await anilistRequest(`query ($ids: [Int]) { Page(perPage: ${BATCH_SIZE}) { media(id_in: $ids, type: ANIME) { id duration } } }`, { ids: batch.map((b) => b.anilistId) });
|
|
25
|
-
for (const m of res?.data?.Page?.media ?? []) {
|
|
26
|
-
if (!m.duration || m.duration <= 0)
|
|
27
|
-
continue;
|
|
28
|
-
const row = batch.find((b) => b.anilistId === m.id);
|
|
29
|
-
if (!row)
|
|
30
|
-
continue;
|
|
31
|
-
dbRun(db, 'UPDATE shows_meta SET episodeDuration = ? WHERE id = ?', [m.duration, row.id]);
|
|
32
|
-
updated++;
|
|
33
|
-
}
|
|
34
|
-
console.log(`Progress: ${Math.min(i + BATCH_SIZE, missing.length)}/${missing.length} (updated ${updated})`);
|
|
35
|
-
}
|
|
36
|
-
console.log(`Done. Updated ${updated} of ${missing.length} shows.`);
|
|
37
|
-
db.close();
|
|
38
|
-
}
|
|
39
|
-
main().catch((err) => {
|
|
40
|
-
console.error(err?.message || err);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
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
|
-
}
|