@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
|
@@ -1,63 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { TEMP_SHOW_ID_PREFIX } from '../lib/temp-ids.js';
|
|
5
|
-
export const TempShowIdsRepository = {
|
|
6
|
-
getById: async (db, id) => {
|
|
7
|
-
const rows = await getDrizzle(db).all(sql `SELECT * FROM temp_show_ids WHERE id = ${id}`);
|
|
8
|
-
return rows[0];
|
|
9
|
-
},
|
|
10
|
-
getByProviderNative: async (db, provider, nativeId) => {
|
|
11
|
-
const rows = await getDrizzle(db).all(sql `SELECT * FROM temp_show_ids WHERE provider = ${provider} AND nativeId = ${nativeId}`);
|
|
12
|
-
return rows[0];
|
|
13
|
-
},
|
|
14
|
-
count: async (db) => {
|
|
15
|
-
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM temp_show_ids`);
|
|
16
|
-
return rows[0]?.total || 0;
|
|
17
|
-
},
|
|
18
|
-
allocate: async (db, data) => {
|
|
19
|
-
const existing = await TempShowIdsRepository.getByProviderNative(db, data.provider, data.nativeId);
|
|
20
|
-
if (existing)
|
|
21
|
-
return existing;
|
|
22
|
-
const prefixLen = TEMP_SHOW_ID_PREFIX.length + 1;
|
|
23
|
-
const maxRows = await getDrizzle(db).all(sql `
|
|
24
|
-
SELECT MAX(CAST(SUBSTR(id, ${prefixLen}) AS INTEGER)) as maxId FROM temp_show_ids WHERE id LIKE ${`${TEMP_SHOW_ID_PREFIX}%`}`);
|
|
25
|
-
const next = (maxRows[0]?.maxId || 0) + 1;
|
|
26
|
-
const id = `${TEMP_SHOW_ID_PREFIX}${next}`;
|
|
27
|
-
try {
|
|
28
|
-
await getDrizzle(db)
|
|
29
|
-
.insert(tempShowIds)
|
|
30
|
-
.values({
|
|
31
|
-
id,
|
|
32
|
-
provider: data.provider,
|
|
33
|
-
nativeId: data.nativeId,
|
|
34
|
-
title: data.title,
|
|
35
|
-
thumbnail: data.thumbnail || '',
|
|
36
|
-
createdAt: Date.now(),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
const winner = await TempShowIdsRepository.getByProviderNative(db, data.provider, data.nativeId);
|
|
41
|
-
if (winner)
|
|
42
|
-
return winner;
|
|
43
|
-
throw new Error('temp id allocation failed');
|
|
44
|
-
}
|
|
45
|
-
return (await TempShowIdsRepository.getById(db, id));
|
|
46
|
-
},
|
|
47
|
-
purge: async (db) => {
|
|
48
|
-
const total = await TempShowIdsRepository.count(db);
|
|
49
|
-
if (total === 0)
|
|
50
|
-
return 0;
|
|
51
|
-
const likePattern = `${TEMP_SHOW_ID_PREFIX}%`;
|
|
52
|
-
await getDrizzle(db).run(sql `DELETE FROM watchlist WHERE id LIKE ${likePattern}`);
|
|
53
|
-
await getDrizzle(db).run(sql `DELETE FROM watched_episodes WHERE showId LIKE ${likePattern}`);
|
|
54
|
-
await getDrizzle(db).run(sql `DELETE FROM queue WHERE showId LIKE ${likePattern}`);
|
|
55
|
-
await getDrizzle(db).run(sql `DELETE FROM shows_meta WHERE id LIKE ${likePattern}`);
|
|
56
|
-
await getDrizzle(db).run(sql `DELETE FROM dismissed_notifications WHERE showId LIKE ${likePattern}`);
|
|
57
|
-
await getDrizzle(db).run(sql `DELETE FROM discovered_notifications WHERE showId LIKE ${likePattern}`);
|
|
58
|
-
await getDrizzle(db).run(sql `
|
|
59
|
-
DELETE FROM legacy_id_mapping WHERE legacyId LIKE ${likePattern} OR numericId LIKE ${likePattern}`);
|
|
60
|
-
await getDrizzle(db).delete(tempShowIds);
|
|
61
|
-
return total;
|
|
62
|
-
},
|
|
63
|
-
};
|
|
1
|
+
import{sql as a}from"drizzle-orm";import{getDrizzle as i}from"../db/drizzle.js";import{tempShowIds as p}from"../db/schema-anime.js";import{TEMP_SHOW_ID_PREFIX as o}from"../lib/temp-ids.js";const n={getById:async(t,e)=>(await i(t).all(a`SELECT * FROM temp_show_ids WHERE id = ${e}`))[0],getByProviderNative:async(t,e,r)=>(await i(t).all(a`SELECT * FROM temp_show_ids WHERE provider = ${e} AND nativeId = ${r}`))[0],count:async t=>(await i(t).all(a`SELECT COUNT(*) as total FROM temp_show_ids`))[0]?.total||0,allocate:async(t,e)=>{const r=await n.getByProviderNative(t,e.provider,e.nativeId);if(r)return r;const s=o.length+1,m=((await i(t).all(a`
|
|
2
|
+
SELECT MAX(CAST(SUBSTR(id, ${s}) AS INTEGER)) as maxId FROM temp_show_ids WHERE id LIKE ${`${o}%`}`))[0]?.maxId||0)+1,E=`${o}${m}`;try{await i(t).insert(p).values({id:E,provider:e.provider,nativeId:e.nativeId,title:e.title,thumbnail:e.thumbnail||"",createdAt:Date.now()})}catch{const w=await n.getByProviderNative(t,e.provider,e.nativeId);if(w)return w;throw new Error("temp id allocation failed")}return await n.getById(t,E)},purge:async t=>{const e=await n.count(t);if(e===0)return 0;const r=`${o}%`;return await i(t).run(a`DELETE FROM watchlist WHERE id LIKE ${r}`),await i(t).run(a`DELETE FROM watched_episodes WHERE showId LIKE ${r}`),await i(t).run(a`DELETE FROM queue WHERE showId LIKE ${r}`),await i(t).run(a`DELETE FROM shows_meta WHERE id LIKE ${r}`),await i(t).run(a`DELETE FROM dismissed_notifications WHERE showId LIKE ${r}`),await i(t).run(a`DELETE FROM discovered_notifications WHERE showId LIKE ${r}`),await i(t).run(a`
|
|
3
|
+
DELETE FROM legacy_id_mapping WHERE legacyId LIKE ${r} OR numericId LIKE ${r}`),await i(t).delete(p),e}};export{n as TempShowIdsRepository};
|
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getDrizzle } from '../db/drizzle.js';
|
|
3
|
-
import { tvProgress } from '../db/schema-tv.js';
|
|
4
|
-
import { makeLibraryRepo } from './library.repository.js';
|
|
5
|
-
export const TV_STATUSES = ['Watching', 'Completed', 'On-Hold', 'Dropped', 'Planned'];
|
|
6
|
-
export function buildTvId(mediaType, tmdbId) {
|
|
7
|
-
const t = String(mediaType).toLowerCase() === 'movie' ? 'movie' : 'tv';
|
|
8
|
-
return `${t}:${tmdbId}`;
|
|
9
|
-
}
|
|
10
|
-
export function normalizeTvMediaId(raw) {
|
|
11
|
-
const s = String(raw ?? '').trim();
|
|
12
|
-
const m = /^(movie|tv)[:-](\d+)$/i.exec(s);
|
|
13
|
-
if (m)
|
|
14
|
-
return `${m[1].toLowerCase()}:${m[2]}`;
|
|
15
|
-
return s;
|
|
16
|
-
}
|
|
17
|
-
export const TvLibraryRepository = {
|
|
18
|
-
...makeLibraryRepo('tv_library'),
|
|
19
|
-
upsert: (db, data) => getDrizzle(db).run(sql `
|
|
1
|
+
import{eq as n,inArray as d,sql as i}from"drizzle-orm";import{getDrizzle as s}from"../db/drizzle.js";import{tvProgress as a}from"../db/schema-tv.js";import{makeLibraryRepo as p}from"./library.repository.js";const g=["Watching","Completed","On-Hold","Dropped","Planned"];function v(r,e){return`${String(r).toLowerCase()==="movie"?"movie":"tv"}:${e}`}function D(r){const e=String(r??"").trim(),t=/^(movie|tv)[:-](\d+)$/i.exec(e);return t?`${t[1].toLowerCase()}:${t[2]}`:e}const T={...p("tv_library"),upsert:(r,e)=>s(r).run(i`
|
|
20
2
|
INSERT INTO tv_library (id, tmdbId, mediaType, title, poster, backdrop, year, overview, status, adult, updatedAt)
|
|
21
|
-
VALUES (${
|
|
3
|
+
VALUES (${e.id}, ${e.tmdbId}, ${e.mediaType}, ${e.title}, ${e.poster}, ${e.backdrop||null}, ${e.year||null}, ${e.overview||null}, ${e.status}, ${e.adult?1:0}, strftime('%s', 'now'))
|
|
22
4
|
ON CONFLICT(id) DO UPDATE SET
|
|
23
5
|
tmdbId = EXCLUDED.tmdbId,
|
|
24
6
|
mediaType = EXCLUDED.mediaType,
|
|
@@ -29,26 +11,13 @@ export const TvLibraryRepository = {
|
|
|
29
11
|
overview = COALESCE(NULLIF(EXCLUDED.overview, ''), tv_library.overview),
|
|
30
12
|
status = COALESCE(NULLIF(EXCLUDED.status, ''), tv_library.status),
|
|
31
13
|
adult = COALESCE(EXCLUDED.adult, tv_library.adult),
|
|
32
|
-
updatedAt = strftime('%s', 'now')`),
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getByMedia: (db, mediaId) => getDrizzle(db).all(sql `
|
|
38
|
-
SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${mediaId} ORDER BY updatedAt DESC, rowid DESC`),
|
|
39
|
-
getEpisode: async (db, mediaId, season, episode) => {
|
|
40
|
-
const rows = await getDrizzle(db).all(sql `
|
|
41
|
-
SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${mediaId} AND season = ${season} AND episode = ${episode}`);
|
|
42
|
-
return rows[0];
|
|
43
|
-
},
|
|
44
|
-
getLatest: async (db, mediaId) => {
|
|
45
|
-
const rows = await getDrizzle(db).all(sql `
|
|
46
|
-
SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${mediaId} ORDER BY updatedAt DESC, rowid DESC LIMIT 1`);
|
|
47
|
-
return rows[0];
|
|
48
|
-
},
|
|
49
|
-
upsert: (db, data) => getDrizzle(db).run(sql `
|
|
14
|
+
updatedAt = strftime('%s', 'now')`),touchProgress:(r,e,t)=>s(r).run(i`
|
|
15
|
+
UPDATE tv_library SET lastSeason = ${t.season}, lastEpisode = ${t.episode}, updatedAt = strftime('%s', 'now') WHERE id = ${e}`)},I={getByMedia:(r,e)=>s(r).all(i`
|
|
16
|
+
SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${e} ORDER BY updatedAt DESC, rowid DESC`),getEpisode:async(r,e,t,o)=>(await s(r).all(i`
|
|
17
|
+
SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${e} AND season = ${t} AND episode = ${o}`))[0],getLatest:async(r,e)=>(await s(r).all(i`
|
|
18
|
+
SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${e} ORDER BY updatedAt DESC, rowid DESC LIMIT 1`))[0],upsert:(r,e)=>s(r).run(i`
|
|
50
19
|
INSERT INTO tv_progress (mediaId, season, episode, currentTime, duration, completed, title, poster, backdrop, year, overview, tmdbId, mediaType, adult, updatedAt)
|
|
51
|
-
VALUES (${
|
|
20
|
+
VALUES (${e.mediaId}, ${e.season}, ${e.episode}, ${e.currentTime}, ${e.duration}, ${e.completed}, ${e.title??null}, ${e.poster??null}, ${e.backdrop??null}, ${e.year??null}, ${e.overview??null}, ${e.tmdbId??null}, ${e.mediaType??null}, ${e.adult??null}, strftime('%s', 'now'))
|
|
52
21
|
ON CONFLICT(mediaId, season, episode) DO UPDATE SET
|
|
53
22
|
currentTime = EXCLUDED.currentTime,
|
|
54
23
|
duration = EXCLUDED.duration,
|
|
@@ -61,18 +30,8 @@ export const TvProgressRepository = {
|
|
|
61
30
|
tmdbId = COALESCE(EXCLUDED.tmdbId, tv_progress.tmdbId),
|
|
62
31
|
mediaType = COALESCE(EXCLUDED.mediaType, tv_progress.mediaType),
|
|
63
32
|
adult = COALESCE(EXCLUDED.adult, tv_progress.adult),
|
|
64
|
-
updatedAt = strftime('%s', 'now')`),
|
|
65
|
-
|
|
66
|
-
deleteMany: (db, ids) => {
|
|
67
|
-
if (ids.length === 0)
|
|
68
|
-
return Promise.resolve();
|
|
69
|
-
return getDrizzle(db).delete(tvProgress).where(inArray(tvProgress.mediaId, ids));
|
|
70
|
-
},
|
|
71
|
-
deleteEpisode: (db, mediaId, season, episode) => getDrizzle(db).run(sql `
|
|
72
|
-
DELETE FROM tv_progress WHERE mediaId = ${mediaId} AND season = ${season} AND episode = ${episode}`),
|
|
73
|
-
getContinueWatching: (db, limit) => {
|
|
74
|
-
const limitClause = typeof limit === 'number' ? `LIMIT ${limit}` : '';
|
|
75
|
-
return getDrizzle(db).all(sql `
|
|
33
|
+
updatedAt = strftime('%s', 'now')`),deleteByMedia:(r,e)=>s(r).delete(a).where(n(a.mediaId,e)),deleteMany:(r,e)=>e.length===0?Promise.resolve():s(r).delete(a).where(d(a.mediaId,e)),deleteEpisode:(r,e,t,o)=>s(r).run(i`
|
|
34
|
+
DELETE FROM tv_progress WHERE mediaId = ${e} AND season = ${t} AND episode = ${o}`),getContinueWatching:(r,e)=>{const t=typeof e=="number"?`LIMIT ${e}`:"";return s(r).all(i`
|
|
76
35
|
SELECT p.mediaId as id,
|
|
77
36
|
COALESCE(l.tmdbId, p.tmdbId, CAST(SUBSTR(REPLACE(p.mediaId, '-', ':'), INSTR(REPLACE(p.mediaId, '-', ':'), ':') + 1) AS INTEGER)) as tmdbId,
|
|
78
37
|
COALESCE(l.mediaType, p.mediaType, SUBSTR(REPLACE(p.mediaId, '-', ':'), 1, INSTR(REPLACE(p.mediaId, '-', ':'), ':') - 1)) as mediaType,
|
|
@@ -88,6 +47,4 @@ export const TvProgressRepository = {
|
|
|
88
47
|
WHERE p.rn = 1
|
|
89
48
|
AND (l.status IS NULL OR l.status = 'Watching')
|
|
90
49
|
ORDER BY p.updatedAt DESC, p.progressRowid DESC
|
|
91
|
-
${
|
|
92
|
-
},
|
|
93
|
-
};
|
|
50
|
+
${i.raw(t)}`)}};export{g as TV_STATUSES,T as TvLibraryRepository,I as TvProgressRepository,v as buildTvId,D as normalizeTvMediaId};
|
|
@@ -1,58 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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 `
|
|
1
|
+
import{eq as o,sql as s}from"drizzle-orm";import{getDrizzle as r}from"../db/drizzle.js";import{watchedEpisodes as i}from"../db/schema-anime.js";const p={getByShowAndEpisode:async(t,e,a)=>(await r(t).all(s`
|
|
2
|
+
SELECT currentTime, duration FROM watched_episodes WHERE showId = ${e} AND episodeNumber = ${a}`))[0],getWatchedEpisodeNumbers:async(t,e)=>(await r(t).all(s`SELECT episodeNumber FROM watched_episodes WHERE showId = ${e}`)).map(n=>n.episodeNumber),getByShow:(t,e)=>r(t).all(s`
|
|
3
|
+
SELECT showId, episodeNumber, currentTime, duration, watchedAt FROM watched_episodes WHERE showId = ${e} ORDER BY CAST(episodeNumber AS REAL) ASC`),getLatestResumeProgress:async(t,e)=>(await r(t).all(s`
|
|
18
4
|
SELECT showId, episodeNumber, currentTime, duration, watchedAt
|
|
19
5
|
FROM watched_episodes
|
|
20
|
-
WHERE showId = ${
|
|
6
|
+
WHERE showId = ${e} AND currentTime > 5 AND (duration <= 0 OR currentTime < duration * 0.8)
|
|
21
7
|
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 `
|
|
8
|
+
LIMIT 1`))[0],upsert:(t,e)=>r(t).insert(i).values({showId:e.showId,episodeNumber:e.episodeNumber,watchedAt:s`CURRENT_TIMESTAMP`,currentTime:e.currentTime,duration:e.duration}).onConflictDoUpdate({target:[i.showId,i.episodeNumber],set:{watchedAt:s`CURRENT_TIMESTAMP`,currentTime:e.currentTime,duration:e.duration}}),insertIfMissing:(t,e)=>r(t).insert(i).values({showId:e.showId,episodeNumber:e.episodeNumber,watchedAt:e.watchedAt?s`${e.watchedAt}`:s`CURRENT_TIMESTAMP`,currentTime:0,duration:0}).onConflictDoNothing(),deleteByShow:(t,e)=>r(t).delete(i).where(o(i.showId,e)),getContinueWatching:(t,e)=>{const a=typeof e=="number"?`LIMIT ${e}`:"";return r(t).all(s`
|
|
56
9
|
SELECT
|
|
57
10
|
we.showId as _id,
|
|
58
11
|
we.showId as id,
|
|
@@ -77,6 +30,4 @@ export const WatchedEpisodesRepository = {
|
|
|
77
30
|
AND (w.status IS NULL OR w.status = 'Watching')
|
|
78
31
|
AND (w.id IS NOT NULL OR sm.id IS NOT NULL)
|
|
79
32
|
ORDER BY we.watchedAt DESC
|
|
80
|
-
${
|
|
81
|
-
},
|
|
82
|
-
};
|
|
33
|
+
${s.raw(a)}`)}};export{p as WatchedEpisodesRepository};
|
|
@@ -1,60 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
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})
|
|
1
|
+
import{eq as E,inArray as m,sql as n}from"drizzle-orm";import{getDrizzle as i}from"../db/drizzle.js";import{watchlist as s}from"../db/schema-anime.js";const L={getById:async(e,t)=>(await i(e).all(n`SELECT * FROM watchlist WHERE id = ${t}`))[0],exists:async(e,t)=>{const a=await i(e).all(n`SELECT EXISTS(SELECT 1 FROM watchlist WHERE id = ${t}) as inWatchlist`);return!!(a[0]&&a[0].inWatchlist)},getAll:(e,t,a,l)=>{const r=n`SELECT * FROM watchlist`;return t&&t!=="All"&&r.append(n` WHERE status = ${t}`),r.append(n` ORDER BY rowid DESC`),a!==void 0&&l!==void 0&&r.append(n` LIMIT ${a} OFFSET ${l}`),i(e).all(r)},getCount:async(e,t)=>{const a=n`SELECT COUNT(*) as total FROM watchlist`;return t&&t!=="All"&&a.append(n` WHERE status = ${t}`),(await i(e).all(a))[0]?.total||0},upsert:(e,t)=>i(e).run(n`
|
|
2
|
+
INSERT INTO watchlist (id, name, thumbnail, status, nativeName, englishName, type) VALUES (${t.id}, ${t.name}, ${t.thumbnail}, ${t.status}, ${t.nativeName}, ${t.englishName}, ${t.type})
|
|
34
3
|
ON CONFLICT(id) DO UPDATE SET
|
|
35
4
|
name = COALESCE(NULLIF(EXCLUDED.name, ''), watchlist.name),
|
|
36
5
|
thumbnail = COALESCE(NULLIF(EXCLUDED.thumbnail, ''), watchlist.thumbnail),
|
|
37
6
|
status = COALESCE(NULLIF(EXCLUDED.status, ''), watchlist.status),
|
|
38
7
|
nativeName = COALESCE(NULLIF(EXCLUDED.nativeName, ''), watchlist.nativeName),
|
|
39
8
|
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
|
-
};
|
|
9
|
+
type = COALESCE(NULLIF(EXCLUDED.type, ''), watchlist.type)`),updateStatus:(e,t,a)=>i(e).update(s).set({status:a}).where(E(s.id,t)),updateThumbnail:(e,t,a)=>!a||a.trim()===""?Promise.resolve():i(e).update(s).set({thumbnail:a}).where(E(s.id,t)),delete:(e,t)=>i(e).delete(s).where(E(s.id,t)),deleteMany:(e,t)=>t.length===0?Promise.resolve():i(e).delete(s).where(m(s.id,t)),updateStatusMany:(e,t,a)=>t.length===0?Promise.resolve():i(e).update(s).set({status:a}).where(m(s.id,t)),getWatchingShows:e=>i(e).all(n`SELECT id, name, thumbnail, nativeName, englishName FROM watchlist WHERE status = 'Watching'`),getMissingThumbnails:e=>i(e).all(n`SELECT id, name FROM watchlist WHERE thumbnail IS NULL OR TRIM(thumbnail) = ''`)};export{L as WatchlistRepository};
|
|
@@ -1,42 +1 @@
|
|
|
1
|
-
import
|
|
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
|
+
import g from"node:path";import{CONFIG as s}from"../config.js";import{DatabaseWrapper as p}from"../db.js";import{anilistRequest as f}from"../lib/anilist.js";import{dbAll as l,dbRun as E}from"../utils/db-utils.js";const a=50;async function h(){const n=s.IS_DEV?s.DB_NAME_DEV:s.DB_NAME_PROD,o=await p.create(g.join(s.ROOT,n));o.configure("busyTimeout",1e4),l(o,"PRAGMA table_info(shows_meta)").some(i=>i.name==="episodeDuration")||o.run("ALTER TABLE shows_meta ADD COLUMN episodeDuration INTEGER");const e=l(o,"SELECT id, anilistId FROM shows_meta WHERE anilistId IS NOT NULL AND episodeDuration IS NULL");if(e.length===0){console.log("Nothing to backfill."),o.close();return}let r=0;for(let i=0;i<e.length;i+=a){const d=e.slice(i,i+a),c=await f(`query ($ids: [Int]) { Page(perPage: ${a}) { media(id_in: $ids, type: ANIME) { id duration } } }`,{ids:d.map(t=>t.anilistId)});for(const t of c?.data?.Page?.media??[]){if(!t.duration||t.duration<=0)continue;const m=d.find(u=>u.anilistId===t.id);m&&(E(o,"UPDATE shows_meta SET episodeDuration = ? WHERE id = ?",[t.duration,m.id]),r++)}console.log(`Progress: ${Math.min(i+a,e.length)}/${e.length} (updated ${r})`)}console.log(`Done. Updated ${r} of ${e.length} shows.`),o.close()}h().catch(n=>{console.error(n?.message||n),process.exit(1)});
|