@serifpersia/dango 3.4.4 → 3.4.6
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-4CEz8Buu.js → AnimeCard-BVdH0EeW.js} +1 -1
- package/client/dist/assets/{AnimeInfo-DyX9qTwg.js → AnimeInfo-Dw2Nop6Q.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-Dvp_c8vK.js → AnimeInfoPage-Cw-Tq3YX.js} +2 -2
- package/client/dist/assets/{Asmr-qg3lMAJn.js → Asmr-BrjzzyOi.js} +1 -1
- package/client/dist/assets/{AsmrResetProgressModal-BpPlg_Vv.js → AsmrResetProgressModal-CYjpcYpb.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-FJllOgOH.js → AvSyncCalibrator-BuHurVgo.js} +3 -3
- package/client/dist/assets/AvSyncCalibrator-D0FYSwiz.css +1 -0
- package/client/dist/assets/{Home-A7gCeEST.js → Home-KdnP7sNM.js} +1 -1
- package/client/dist/assets/{Insights-G9IXepHJ.js → Insights-DDWry-t1.js} +1 -1
- package/client/dist/assets/{LibraryListPage-Bh2qCJPh.js → LibraryListPage-CV8Z9JlO.js} +1 -1
- package/client/dist/assets/{ListeningList-Czv4cTRl.js → ListeningList-DSMbMZT7.js} +1 -1
- package/client/dist/assets/{Manga-qdAAS1z7.js → Manga-J6XoqEKq.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-1rTgZzf2.js → MangaInfoPage-N7TxsN5u.js} +1 -1
- package/client/dist/assets/{MangaReadPage-CeyxPG6w.js → MangaReadPage-D_VzSNWq.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-C_ppotmh.js → MangaResetProgressModal-DyhQYBDy.js} +1 -1
- package/client/dist/assets/{Mature-Bkhxnr3K.js → Mature-Db53R-LR.js} +1 -1
- package/client/dist/assets/{MediaCard-BQZsutEo.js → MediaCard-BcLjlpfR.js} +1 -1
- package/client/dist/assets/{Music-C0rGzSbi.js → Music-DjxQjJOl.js} +1 -1
- package/client/dist/assets/{OptionListMenu-CyfpCBEy.js → OptionListMenu-DngTgHxH.js} +1 -1
- package/client/dist/assets/Player-Be_7Ne5p.js +56 -0
- package/client/dist/assets/{PlayerSettings-icy4SfQa.js → PlayerSettings-D47ItGDR.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-5eloKF5J.js → QueueOptionsButton-ClVTpH8J.js} +1 -1
- package/client/dist/assets/{QueueRail-DaMjlKL4.js → QueueRail-DeJKVRs5.js} +1 -1
- package/client/dist/assets/{Radio-FxGqCxR4.js → Radio-DU0ma3GA.js} +1 -1
- package/client/dist/assets/{ReadingList-CCm3IAbt.js → ReadingList-qXW7Dk5z.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CeI4V3Y5.js → ResetProgressModal-C6ukct_c.js} +1 -1
- package/client/dist/assets/{Search-qpILaAiQ.js → Search-DjjKA_Wu.js} +1 -1
- package/client/dist/assets/{Settings-Bw9M3NGz.js → Settings-DQt_S5Qw.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-0dY_xMq3.js → SkeletonGrid-nx2HEi4P.js} +1 -1
- package/client/dist/assets/{Trackers-Zy5GOs0P.js → Trackers-BcGzeqj2.js} +1 -1
- package/client/dist/assets/Tv-COvyHdeJ.js +4 -0
- package/client/dist/assets/{TvInfo-CHIsVzZ0.js → TvInfo-CUd1agFy.js} +1 -1
- package/client/dist/assets/{TvPopup-D_tVTyZ2.js → TvPopup-CJeu3tgc.js} +1 -1
- package/client/dist/assets/{TvSearch-BVxedBW-.js → TvSearch-BIypjbU3.js} +1 -1
- package/client/dist/assets/{TvWatchlist-BEIwq9R9.js → TvWatchlist-DZrBdiqZ.js} +1 -1
- package/client/dist/assets/{Watchlist-CSngjqlz.js → Watchlist-Dw8cPe0n.js} +1 -1
- package/client/dist/assets/{hls-D48GVTkk.js → hls-B3_1VuDP.js} +1 -1
- package/client/dist/assets/{index-CVB5dYhT.js → index-gMxawd-E.js} +2 -2
- package/client/dist/assets/{useAnimeInfoData-DxdOg_B7.js → useAnimeInfoData-_iOLEAa8.js} +1 -1
- package/client/dist/assets/{useAsmr-ClckxnPe.js → useAsmr-Blk4v41t.js} +1 -1
- package/client/dist/assets/{useAsmrPopup-Bbgxefy_.js → useAsmrPopup-D9SQIgZa.js} +1 -1
- package/client/dist/assets/{useMangaLibrary-B3_BUKVk.js → useMangaLibrary-B60at6ni.js} +1 -1
- package/client/dist/assets/{useMangaPopup-Celk4lzf.js → useMangaPopup-DEfGjWSu.js} +1 -1
- package/client/dist/assets/{useProviders-pwxPZPHh.js → useProviders-Ca0bZWt7.js} +1 -1
- package/client/dist/assets/{useTv-D9sQrDop.js → useTv-WJCDL5_5.js} +1 -1
- package/client/dist/assets/{useTvLibrary-UsQpgBTB.js → useTvLibrary-Fgn5i9rJ.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +4 -2
- package/server/dist/app-hono.js +2 -0
- package/server/dist/hono/asmr.js +10 -1
- package/server/dist/hono/music.js +122 -332
- package/server/dist/hono/proxy.js +105 -99
- package/server/dist/hono/tv.js +5 -58
- package/server/dist/hono/watchlist.js +2 -291
- package/server/dist/lib/anilist.js +0 -3
- package/server/dist/lib/mal.js +4 -41
- package/server/dist/lib/offline-db.js +0 -6
- package/server/dist/lib/potoken.js +135 -0
- package/server/dist/lib/tracker/manga-sync.service.js +3 -22
- package/server/dist/lib/tracker/sync-state.js +21 -0
- package/server/dist/lib/tracker/sync.service.js +3 -22
- package/server/dist/lib/ump.js +106 -0
- package/server/dist/providers/remote-loader.js +17 -27
- package/server/dist/providers/title-matching.js +5 -5
- package/server/dist/repositories/asmr.repository.js +4 -53
- package/server/dist/repositories/library.repository.js +46 -0
- package/server/dist/repositories/manga.repository.js +3 -53
- package/server/dist/repositories/settings.repository.js +8 -0
- package/server/dist/repositories/tv.repository.js +3 -46
- package/server/dist/repositories/watched-episodes.repository.js +0 -2
- package/server/dist/server.js +5 -1
- package/server/dist/sync.js +3 -5
- package/server/dist/trpc/routers/asmr-library.js +2 -12
- package/server/dist/trpc/routers/asmr-progress.js +10 -22
- package/server/dist/trpc/routers/data.js +17 -36
- package/server/dist/trpc/routers/discord.js +1 -8
- package/server/dist/trpc/routers/insights.js +15 -16
- package/server/dist/trpc/routers/manga-library.js +5 -22
- package/server/dist/trpc/routers/manga-progress.js +8 -20
- package/server/dist/trpc/routers/music.js +6 -11
- package/server/dist/trpc/routers/queue.js +1 -1
- package/server/dist/trpc/routers/tracker.js +1 -7
- package/server/dist/trpc/routers/tv-library.js +2 -12
- package/server/dist/trpc/routers/tv-progress.js +16 -28
- package/server/dist/trpc/routers/tv.js +5 -21
- package/server/dist/trpc/routers/watchlist.js +1 -8
- package/server/dist/trpc/validation.js +35 -0
- package/server/dist/utils/db-utils.js +8 -0
- package/server/dist/utils/http.utils.js +12 -0
- package/server/dist/utils/machine-id.js +1 -2
- package/server/dist/utils/megaplay.utils.js +1 -2
- package/server/package.json +4 -1
- package/client/dist/assets/AvSyncCalibrator-C2wu1ydI.css +0 -1
- package/client/dist/assets/Player-yhiTlpYU.js +0 -56
- package/client/dist/assets/Tv-DREwc7BX.js +0 -4
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const MEDIA_HEADER = 20;
|
|
2
|
+
const MEDIA = 21;
|
|
3
|
+
const STREAM_PROTECTION_STATUS = 58;
|
|
4
|
+
function readVarint(buf, pos) {
|
|
5
|
+
if (pos >= buf.length)
|
|
6
|
+
return null;
|
|
7
|
+
const prefix = buf[pos];
|
|
8
|
+
let size = 0;
|
|
9
|
+
for (let shift = 1; shift <= 5; shift++) {
|
|
10
|
+
if ((prefix & (128 >> (shift - 1))) === 0) {
|
|
11
|
+
size = shift;
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (size < 1 || pos + size > buf.length)
|
|
16
|
+
return null;
|
|
17
|
+
if (size === 1)
|
|
18
|
+
return [prefix, pos + 1];
|
|
19
|
+
if (size === 5) {
|
|
20
|
+
const value = buf[pos + 1] + buf[pos + 2] * 256 + buf[pos + 3] * 65536 + buf[pos + 4] * 16777216;
|
|
21
|
+
return [value >>> 0, pos + 5];
|
|
22
|
+
}
|
|
23
|
+
const lowBits = 8 - size;
|
|
24
|
+
let value = prefix & ((1 << lowBits) - 1);
|
|
25
|
+
for (let i = 1; i < size; i++) {
|
|
26
|
+
value += buf[pos + i] * 2 ** (lowBits + 8 * (i - 1));
|
|
27
|
+
}
|
|
28
|
+
return [value, pos + size];
|
|
29
|
+
}
|
|
30
|
+
function readStatusField(payload) {
|
|
31
|
+
let pos = 0;
|
|
32
|
+
while (pos < payload.length) {
|
|
33
|
+
const key = payload[pos++];
|
|
34
|
+
const field = key >> 3;
|
|
35
|
+
const wire = key & 7;
|
|
36
|
+
if (wire !== 0)
|
|
37
|
+
break;
|
|
38
|
+
const decoded = readVarint(payload, pos);
|
|
39
|
+
if (!decoded)
|
|
40
|
+
break;
|
|
41
|
+
if (field === 1)
|
|
42
|
+
return decoded[0];
|
|
43
|
+
pos = decoded[1];
|
|
44
|
+
}
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
export function parseUmpResponse(buf, carry) {
|
|
48
|
+
const mediaChunks = [];
|
|
49
|
+
let status = 1;
|
|
50
|
+
let nextCarry = carry;
|
|
51
|
+
let pos = 0;
|
|
52
|
+
if (nextCarry) {
|
|
53
|
+
const t = readVarint(buf, pos);
|
|
54
|
+
const s = t && readVarint(buf, t[1]);
|
|
55
|
+
if (t && s && t[0] === MEDIA_HEADER) {
|
|
56
|
+
pos = s[1] + s[0];
|
|
57
|
+
}
|
|
58
|
+
const ct = readVarint(buf, pos);
|
|
59
|
+
const cs = ct && readVarint(buf, ct[1]);
|
|
60
|
+
if (ct && cs && ct[0] === nextCarry.pendingType) {
|
|
61
|
+
pos = cs[1];
|
|
62
|
+
const take = Math.min(cs[0], buf.length - pos);
|
|
63
|
+
nextCarry.chunks.push(buf.subarray(pos, pos + take));
|
|
64
|
+
nextCarry.remaining = cs[0] - take;
|
|
65
|
+
pos += take;
|
|
66
|
+
if (nextCarry.remaining <= 0) {
|
|
67
|
+
const full = Buffer.concat(nextCarry.chunks);
|
|
68
|
+
nextCarry = null;
|
|
69
|
+
if (ct[0] === MEDIA && full.length > 1)
|
|
70
|
+
mediaChunks.push(full.subarray(1));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
nextCarry = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
while (pos < buf.length) {
|
|
78
|
+
const t = readVarint(buf, pos);
|
|
79
|
+
if (!t)
|
|
80
|
+
break;
|
|
81
|
+
const s = readVarint(buf, t[1]);
|
|
82
|
+
if (!s)
|
|
83
|
+
break;
|
|
84
|
+
const [type, size] = [t[0], s[0]];
|
|
85
|
+
pos = s[1];
|
|
86
|
+
const available = buf.length - pos;
|
|
87
|
+
if (size > available) {
|
|
88
|
+
nextCarry = {
|
|
89
|
+
pendingType: type,
|
|
90
|
+
remaining: size - available,
|
|
91
|
+
chunks: [buf.subarray(pos)],
|
|
92
|
+
};
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
const payload = buf.subarray(pos, pos + size);
|
|
96
|
+
pos += size;
|
|
97
|
+
if (type === MEDIA) {
|
|
98
|
+
if (payload.length > 1)
|
|
99
|
+
mediaChunks.push(payload.subarray(1));
|
|
100
|
+
}
|
|
101
|
+
else if (type === STREAM_PROTECTION_STATUS) {
|
|
102
|
+
status = readStatusField(payload);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { media: Buffer.concat(mediaChunks), status, carry: nextCarry };
|
|
106
|
+
}
|
|
@@ -9,6 +9,7 @@ import logger from '../logger.js';
|
|
|
9
9
|
import { CONFIG } from '../config.js';
|
|
10
10
|
import { requestContext } from '../utils/request-context.js';
|
|
11
11
|
import { sanitizeCfClearance, buildCfClearanceCookie } from '../utils/cookie.utils.js';
|
|
12
|
+
import { base64UrlEncode } from '../utils/megaplay.utils.js';
|
|
12
13
|
import { decodeMaybeGzip, parseJsonBody } from '../utils/http.utils.js';
|
|
13
14
|
import { buildQueryVariants, pickBestMatch } from './title-matching.js';
|
|
14
15
|
import { anilistRequest, parseMalId, searchAnilistByTitle } from '../lib/anilist.js';
|
|
@@ -71,13 +72,6 @@ function resolveEntryUrl(entry, registryUrl) {
|
|
|
71
72
|
function sha256Hex(data) {
|
|
72
73
|
return crypto.createHash('sha256').update(data).digest('hex');
|
|
73
74
|
}
|
|
74
|
-
function base64UrlEncode(input) {
|
|
75
|
-
return Buffer.from(input)
|
|
76
|
-
.toString('base64')
|
|
77
|
-
.replace(/\+/g, '-')
|
|
78
|
-
.replace(/\//g, '_')
|
|
79
|
-
.replace(/=+$/, '');
|
|
80
|
-
}
|
|
81
75
|
function aes256CbcDecryptJson(b64url, keyUtf8, ivUtf8) {
|
|
82
76
|
let encrypted;
|
|
83
77
|
try {
|
|
@@ -238,7 +232,8 @@ function createCtx(cache) {
|
|
|
238
232
|
? { url: urlOrOptions, ...(maybeOptions ?? {}) }
|
|
239
233
|
: urlOrOptions);
|
|
240
234
|
const { url, ...rest } = opts;
|
|
241
|
-
const res = await gotScraping(
|
|
235
|
+
const res = await gotScraping({
|
|
236
|
+
url: url,
|
|
242
237
|
method: 'GET',
|
|
243
238
|
responseType: 'text',
|
|
244
239
|
throwHttpErrors: false,
|
|
@@ -271,31 +266,26 @@ function createCtx(cache) {
|
|
|
271
266
|
},
|
|
272
267
|
};
|
|
273
268
|
}
|
|
274
|
-
function
|
|
269
|
+
function shapeOf(p) {
|
|
275
270
|
if (!p || typeof p !== 'object')
|
|
276
|
-
return
|
|
271
|
+
return null;
|
|
277
272
|
const o = p;
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
273
|
+
return typeof o.name === 'string' ? o : null;
|
|
274
|
+
}
|
|
275
|
+
function hasFns(o, fns) {
|
|
276
|
+
return fns.every((f) => typeof o[f] === 'function');
|
|
277
|
+
}
|
|
278
|
+
function isValidVideoProvider(p) {
|
|
279
|
+
const o = shapeOf(p);
|
|
280
|
+
return !!o && hasFns(o, ['search', 'getEpisodes', 'getStreamUrls']);
|
|
282
281
|
}
|
|
283
282
|
function isValidMangaProvider(p) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const o = p;
|
|
287
|
-
return (typeof o.name === 'string' &&
|
|
288
|
-
typeof o.search === 'function' &&
|
|
289
|
-
typeof o.getDetail === 'function' &&
|
|
290
|
-
typeof o.getChapters === 'function' &&
|
|
291
|
-
typeof o.getPages === 'function');
|
|
283
|
+
const o = shapeOf(p);
|
|
284
|
+
return !!o && hasFns(o, ['search', 'getDetail', 'getChapters', 'getPages']);
|
|
292
285
|
}
|
|
293
286
|
function isValidTvProvider(p) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const o = p;
|
|
297
|
-
return (typeof o.name === 'string' &&
|
|
298
|
-
(typeof o.getSources === 'function' || typeof o.getEmbedUrl === 'function'));
|
|
287
|
+
const o = shapeOf(p);
|
|
288
|
+
return !!o && (typeof o.getSources === 'function' || typeof o.getEmbedUrl === 'function');
|
|
299
289
|
}
|
|
300
290
|
function makeEmbedProvider(id, base) {
|
|
301
291
|
const root = base.trim().replace(/\/+$/, '');
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
function normalizeCompact(s) {
|
|
2
2
|
return s.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
3
3
|
}
|
|
4
|
-
|
|
4
|
+
function tokenize(s) {
|
|
5
5
|
return s
|
|
6
6
|
.toLowerCase()
|
|
7
7
|
.replace(/[^\p{L}\p{N}\s]/gu, ' ')
|
|
8
8
|
.split(/\s+/)
|
|
9
9
|
.filter((w) => w.length >= 2);
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
function bigramDice(a, b) {
|
|
12
12
|
if (a.length < 2 || b.length < 2)
|
|
13
13
|
return a === b ? 1 : 0;
|
|
14
14
|
const grams = (s) => {
|
|
@@ -129,10 +129,10 @@ function detectSeasonMarker(s) {
|
|
|
129
129
|
return trailingNum[1];
|
|
130
130
|
return '';
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
function distinctiveTokens(s) {
|
|
133
133
|
return [...new Set(tokenize(s).filter((w) => !STOPWORDS.has(w)))];
|
|
134
134
|
}
|
|
135
|
-
|
|
135
|
+
function titleSimilarity(query, candidate) {
|
|
136
136
|
const q = normalizeCompact(query);
|
|
137
137
|
const c = normalizeCompact(candidate);
|
|
138
138
|
if (!q || !c)
|
|
@@ -1,48 +1,13 @@
|
|
|
1
1
|
import { eq, inArray, sql } from 'drizzle-orm';
|
|
2
2
|
import { getDrizzle } from '../db/drizzle.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'Completed',
|
|
7
|
-
'On-Hold',
|
|
8
|
-
'Dropped',
|
|
9
|
-
'Planned',
|
|
10
|
-
];
|
|
3
|
+
import { asmrProgress } from '../db/schema-asmr.js';
|
|
4
|
+
import { makeLibraryRepo } from './library.repository.js';
|
|
5
|
+
export const ASMR_STATUSES = ['Listening', 'Completed', 'On-Hold', 'Dropped', 'Planned'];
|
|
11
6
|
export function buildAsmrId(rjCode) {
|
|
12
7
|
return String(rjCode).trim().toUpperCase();
|
|
13
8
|
}
|
|
14
9
|
export const AsmrLibraryRepository = {
|
|
15
|
-
|
|
16
|
-
const rows = await getDrizzle(db).all(sql `SELECT * FROM asmr_library WHERE id = ${id}`);
|
|
17
|
-
return rows[0];
|
|
18
|
-
},
|
|
19
|
-
exists: async (db, id) => {
|
|
20
|
-
const rows = await getDrizzle(db).all(sql `SELECT EXISTS(SELECT 1 FROM asmr_library WHERE id = ${id}) as inLibrary`);
|
|
21
|
-
return !!(rows[0] && rows[0].inLibrary);
|
|
22
|
-
},
|
|
23
|
-
getAll: (db, status, limit, offset) => {
|
|
24
|
-
const q = sql `SELECT * FROM asmr_library`;
|
|
25
|
-
if (status && status !== 'All') {
|
|
26
|
-
q.append(sql ` WHERE status = ${status}`);
|
|
27
|
-
}
|
|
28
|
-
q.append(sql ` ORDER BY updatedAt DESC`);
|
|
29
|
-
if (limit !== undefined && offset !== undefined) {
|
|
30
|
-
q.append(sql ` LIMIT ${limit} OFFSET ${offset}`);
|
|
31
|
-
}
|
|
32
|
-
return getDrizzle(db).all(q);
|
|
33
|
-
},
|
|
34
|
-
getCount: async (db, status) => {
|
|
35
|
-
if (status && status !== 'All') {
|
|
36
|
-
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM asmr_library WHERE status = ${status}`);
|
|
37
|
-
return rows[0]?.total || 0;
|
|
38
|
-
}
|
|
39
|
-
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM asmr_library`);
|
|
40
|
-
return rows[0]?.total || 0;
|
|
41
|
-
},
|
|
42
|
-
getIds: async (db) => {
|
|
43
|
-
const rows = await getDrizzle(db).all(sql `SELECT id FROM asmr_library`);
|
|
44
|
-
return rows.map((r) => r.id);
|
|
45
|
-
},
|
|
10
|
+
...makeLibraryRepo('asmr_library'),
|
|
46
11
|
upsert: (db, data) => getDrizzle(db).run(sql `
|
|
47
12
|
INSERT INTO asmr_library (id, rjCode, title, thumbnail, status, isAdult, updatedAt)
|
|
48
13
|
VALUES (${data.id}, ${data.rjCode}, ${data.title}, ${data.thumbnail}, ${data.status}, ${data.isAdult ? 1 : 0}, strftime('%s', 'now'))
|
|
@@ -53,22 +18,8 @@ export const AsmrLibraryRepository = {
|
|
|
53
18
|
status = COALESCE(NULLIF(EXCLUDED.status, ''), asmr_library.status),
|
|
54
19
|
isAdult = COALESCE(EXCLUDED.isAdult, asmr_library.isAdult),
|
|
55
20
|
updatedAt = strftime('%s', 'now')`),
|
|
56
|
-
updateStatus: (db, id, status) => getDrizzle(db).run(sql `
|
|
57
|
-
UPDATE asmr_library SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
58
|
-
updateStatusMany: (db, ids, status) => {
|
|
59
|
-
if (ids.length === 0)
|
|
60
|
-
return Promise.resolve();
|
|
61
|
-
return getDrizzle(db).run(sql `
|
|
62
|
-
UPDATE asmr_library SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`);
|
|
63
|
-
},
|
|
64
21
|
touchProgress: (db, id, progress) => getDrizzle(db).run(sql `
|
|
65
22
|
UPDATE asmr_library SET lastTrackIndex = ${progress.trackIndex}, lastTrackLabel = ${progress.trackLabel}, lastPosition = ${progress.position}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
66
|
-
delete: (db, id) => getDrizzle(db).delete(asmrLibrary).where(eq(asmrLibrary.id, id)),
|
|
67
|
-
deleteMany: (db, ids) => {
|
|
68
|
-
if (ids.length === 0)
|
|
69
|
-
return Promise.resolve();
|
|
70
|
-
return getDrizzle(db).delete(asmrLibrary).where(inArray(asmrLibrary.id, ids));
|
|
71
|
-
},
|
|
72
23
|
};
|
|
73
24
|
export const AsmrProgressRepository = {
|
|
74
25
|
getByWork: (db, workId) => getDrizzle(db).all(sql `
|
|
@@ -36,6 +36,52 @@ async function clearTables(db, tables) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
export function makeLibraryRepo(table) {
|
|
40
|
+
const t = sql.raw(`"${table}"`);
|
|
41
|
+
return {
|
|
42
|
+
getById: async (db, id) => {
|
|
43
|
+
const rows = await getDrizzle(db).all(sql `SELECT * FROM ${t} WHERE id = ${id}`);
|
|
44
|
+
return rows[0];
|
|
45
|
+
},
|
|
46
|
+
getAll: (db, status, limit, offset) => {
|
|
47
|
+
const q = sql `SELECT * FROM ${t}`;
|
|
48
|
+
if (status && status !== 'All') {
|
|
49
|
+
q.append(sql ` WHERE status = ${status}`);
|
|
50
|
+
}
|
|
51
|
+
q.append(sql ` ORDER BY updatedAt DESC`);
|
|
52
|
+
if (limit !== undefined && offset !== undefined) {
|
|
53
|
+
q.append(sql ` LIMIT ${limit} OFFSET ${offset}`);
|
|
54
|
+
}
|
|
55
|
+
return getDrizzle(db).all(q);
|
|
56
|
+
},
|
|
57
|
+
getCount: async (db, status) => {
|
|
58
|
+
if (status && status !== 'All') {
|
|
59
|
+
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM ${t} WHERE status = ${status}`);
|
|
60
|
+
return rows[0]?.total || 0;
|
|
61
|
+
}
|
|
62
|
+
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM ${t}`);
|
|
63
|
+
return rows[0]?.total || 0;
|
|
64
|
+
},
|
|
65
|
+
getIds: async (db) => {
|
|
66
|
+
const rows = await getDrizzle(db).all(sql `SELECT id FROM ${t}`);
|
|
67
|
+
return rows.map((r) => r.id);
|
|
68
|
+
},
|
|
69
|
+
updateStatus: (db, id, status) => getDrizzle(db).run(sql `
|
|
70
|
+
UPDATE ${t} SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
71
|
+
updateStatusMany: (db, ids, status) => {
|
|
72
|
+
if (ids.length === 0)
|
|
73
|
+
return Promise.resolve();
|
|
74
|
+
return getDrizzle(db).run(sql `
|
|
75
|
+
UPDATE ${t} SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`);
|
|
76
|
+
},
|
|
77
|
+
delete: (db, id) => getDrizzle(db).run(sql `DELETE FROM ${t} WHERE id = ${id}`),
|
|
78
|
+
deleteMany: (db, ids) => {
|
|
79
|
+
if (ids.length === 0)
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
return getDrizzle(db).run(sql `DELETE FROM ${t} WHERE id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
39
85
|
export const LibraryRepository = {
|
|
40
86
|
countAll: (db) => countTables(db, LIBRARY_TABLES),
|
|
41
87
|
clearAll: (db) => clearTables(db, LIBRARY_TABLES),
|
|
@@ -1,48 +1,13 @@
|
|
|
1
1
|
import { eq, inArray, sql } from 'drizzle-orm';
|
|
2
2
|
import { getDrizzle } from '../db/drizzle.js';
|
|
3
3
|
import { mangaLibrary, mangaProgress } from '../db/schema-manga.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'Completed',
|
|
7
|
-
'On-Hold',
|
|
8
|
-
'Dropped',
|
|
9
|
-
'Planned',
|
|
10
|
-
];
|
|
4
|
+
import { makeLibraryRepo } from './library.repository.js';
|
|
5
|
+
export const MANGA_STATUSES = ['Reading', 'Completed', 'On-Hold', 'Dropped', 'Planned'];
|
|
11
6
|
export function buildMangaId(provider, mangaId) {
|
|
12
7
|
return `${provider.toLowerCase()}:${mangaId}`;
|
|
13
8
|
}
|
|
14
9
|
export const MangaLibraryRepository = {
|
|
15
|
-
|
|
16
|
-
const rows = await getDrizzle(db).all(sql `SELECT * FROM manga_library WHERE id = ${id}`);
|
|
17
|
-
return rows[0];
|
|
18
|
-
},
|
|
19
|
-
exists: async (db, id) => {
|
|
20
|
-
const rows = await getDrizzle(db).all(sql `SELECT EXISTS(SELECT 1 FROM manga_library WHERE id = ${id}) as inLibrary`);
|
|
21
|
-
return !!(rows[0] && rows[0].inLibrary);
|
|
22
|
-
},
|
|
23
|
-
getAll: (db, status, limit, offset) => {
|
|
24
|
-
const q = sql `SELECT * FROM manga_library`;
|
|
25
|
-
if (status && status !== 'All') {
|
|
26
|
-
q.append(sql ` WHERE status = ${status}`);
|
|
27
|
-
}
|
|
28
|
-
q.append(sql ` ORDER BY updatedAt DESC`);
|
|
29
|
-
if (limit !== undefined && offset !== undefined) {
|
|
30
|
-
q.append(sql ` LIMIT ${limit} OFFSET ${offset}`);
|
|
31
|
-
}
|
|
32
|
-
return getDrizzle(db).all(q);
|
|
33
|
-
},
|
|
34
|
-
getCount: async (db, status) => {
|
|
35
|
-
if (status && status !== 'All') {
|
|
36
|
-
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM manga_library WHERE status = ${status}`);
|
|
37
|
-
return rows[0]?.total || 0;
|
|
38
|
-
}
|
|
39
|
-
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM manga_library`);
|
|
40
|
-
return rows[0]?.total || 0;
|
|
41
|
-
},
|
|
42
|
-
getIds: async (db) => {
|
|
43
|
-
const rows = await getDrizzle(db).all(sql `SELECT id FROM manga_library`);
|
|
44
|
-
return rows.map((r) => r.id);
|
|
45
|
-
},
|
|
10
|
+
...makeLibraryRepo('manga_library'),
|
|
46
11
|
upsert: (db, data) => getDrizzle(db).run(sql `
|
|
47
12
|
INSERT INTO manga_library (id, provider, mangaId, title, cover, status, author, altTitle, contentRating, anilistId, anilistIdSource, updatedAt)
|
|
48
13
|
VALUES (${data.id}, ${data.provider}, ${data.mangaId}, ${data.title}, ${data.cover}, ${data.status}, ${data.author || null}, ${data.altTitle || null}, ${data.contentRating || null}, ${data.anilistId ?? null}, ${data.anilistIdSource ?? null}, strftime('%s', 'now'))
|
|
@@ -63,25 +28,10 @@ export const MangaLibraryRepository = {
|
|
|
63
28
|
.set({ anilistId, anilistIdSource: source })
|
|
64
29
|
.where(eq(mangaLibrary.id, id))
|
|
65
30
|
: getDrizzle(db).update(mangaLibrary).set({ anilistId }).where(eq(mangaLibrary.id, id)),
|
|
66
|
-
getByAnilistId: (db, anilistId) => getDrizzle(db).all(sql `SELECT * FROM manga_library WHERE anilistId = ${anilistId}`),
|
|
67
|
-
updateStatus: (db, id, status) => getDrizzle(db).run(sql `
|
|
68
|
-
UPDATE manga_library SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
69
|
-
updateStatusMany: (db, ids, status) => {
|
|
70
|
-
if (ids.length === 0)
|
|
71
|
-
return Promise.resolve();
|
|
72
|
-
return getDrizzle(db).run(sql `
|
|
73
|
-
UPDATE manga_library SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`);
|
|
74
|
-
},
|
|
75
31
|
touchProgress: (db, id, progress) => getDrizzle(db).run(sql `
|
|
76
32
|
UPDATE manga_library SET lastChapterId = ${progress.chapterId}, lastChapterNumber = ${progress.chapterNumber}, lastPage = ${progress.page}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
77
33
|
setProgressPointer: (db, id, chapterNumber) => getDrizzle(db).run(sql `
|
|
78
34
|
UPDATE manga_library SET lastChapterNumber = ${chapterNumber}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
79
|
-
delete: (db, id) => getDrizzle(db).delete(mangaLibrary).where(eq(mangaLibrary.id, id)),
|
|
80
|
-
deleteMany: (db, ids) => {
|
|
81
|
-
if (ids.length === 0)
|
|
82
|
-
return Promise.resolve();
|
|
83
|
-
return getDrizzle(db).delete(mangaLibrary).where(inArray(mangaLibrary.id, ids));
|
|
84
|
-
},
|
|
85
35
|
};
|
|
86
36
|
export const MangaProgressRepository = {
|
|
87
37
|
getByManga: (db, mangaId) => getDrizzle(db).all(sql `
|
|
@@ -11,6 +11,14 @@ export const SettingsRepository = {
|
|
|
11
11
|
.values({ key, value })
|
|
12
12
|
.onConflictDoUpdate({ target: settings.key, set: { value } }),
|
|
13
13
|
deleteByKey: (db, key) => getDrizzle(db).delete(settings).where(eq(settings.key, key)),
|
|
14
|
+
readContinueFlags: async (db, ignoreKey, listOnlyKey) => {
|
|
15
|
+
const ignoreRow = await SettingsRepository.getByKey(db, ignoreKey);
|
|
16
|
+
const listOnlyRow = await SettingsRepository.getByKey(db, listOnlyKey);
|
|
17
|
+
return {
|
|
18
|
+
ignoreAdult: ignoreRow ? ignoreRow.value !== 'false' : true,
|
|
19
|
+
listOnly: listOnlyRow ? listOnlyRow.value === 'true' || listOnlyRow.value === '1' : false,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
14
22
|
clearWatchlist: (db) => getDrizzle(db).delete(watchlist),
|
|
15
23
|
upsertWatchlistBatch: (db, shows) => {
|
|
16
24
|
if (shows.length === 0)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eq, inArray, sql } from 'drizzle-orm';
|
|
2
2
|
import { getDrizzle } from '../db/drizzle.js';
|
|
3
|
-
import {
|
|
3
|
+
import { tvProgress } from '../db/schema-tv.js';
|
|
4
|
+
import { makeLibraryRepo } from './library.repository.js';
|
|
4
5
|
export const TV_STATUSES = ['Watching', 'Completed', 'On-Hold', 'Dropped', 'Planned'];
|
|
5
6
|
export function buildTvId(mediaType, tmdbId) {
|
|
6
7
|
const t = String(mediaType).toLowerCase() === 'movie' ? 'movie' : 'tv';
|
|
@@ -14,37 +15,7 @@ export function normalizeTvMediaId(raw) {
|
|
|
14
15
|
return s;
|
|
15
16
|
}
|
|
16
17
|
export const TvLibraryRepository = {
|
|
17
|
-
|
|
18
|
-
const rows = await getDrizzle(db).all(sql `SELECT * FROM tv_library WHERE id = ${id}`);
|
|
19
|
-
return rows[0];
|
|
20
|
-
},
|
|
21
|
-
exists: async (db, id) => {
|
|
22
|
-
const rows = await getDrizzle(db).all(sql `SELECT EXISTS(SELECT 1 FROM tv_library WHERE id = ${id}) as inLibrary`);
|
|
23
|
-
return !!(rows[0] && rows[0].inLibrary);
|
|
24
|
-
},
|
|
25
|
-
getAll: (db, status, limit, offset) => {
|
|
26
|
-
const q = sql `SELECT * FROM tv_library`;
|
|
27
|
-
if (status && status !== 'All') {
|
|
28
|
-
q.append(sql ` WHERE status = ${status}`);
|
|
29
|
-
}
|
|
30
|
-
q.append(sql ` ORDER BY updatedAt DESC`);
|
|
31
|
-
if (limit !== undefined && offset !== undefined) {
|
|
32
|
-
q.append(sql ` LIMIT ${limit} OFFSET ${offset}`);
|
|
33
|
-
}
|
|
34
|
-
return getDrizzle(db).all(q);
|
|
35
|
-
},
|
|
36
|
-
getCount: async (db, status) => {
|
|
37
|
-
if (status && status !== 'All') {
|
|
38
|
-
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM tv_library WHERE status = ${status}`);
|
|
39
|
-
return rows[0]?.total || 0;
|
|
40
|
-
}
|
|
41
|
-
const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM tv_library`);
|
|
42
|
-
return rows[0]?.total || 0;
|
|
43
|
-
},
|
|
44
|
-
getIds: async (db) => {
|
|
45
|
-
const rows = await getDrizzle(db).all(sql `SELECT id FROM tv_library`);
|
|
46
|
-
return rows.map((r) => r.id);
|
|
47
|
-
},
|
|
18
|
+
...makeLibraryRepo('tv_library'),
|
|
48
19
|
upsert: (db, data) => getDrizzle(db).run(sql `
|
|
49
20
|
INSERT INTO tv_library (id, tmdbId, mediaType, title, poster, backdrop, year, overview, status, adult, updatedAt)
|
|
50
21
|
VALUES (${data.id}, ${data.tmdbId}, ${data.mediaType}, ${data.title}, ${data.poster}, ${data.backdrop || null}, ${data.year || null}, ${data.overview || null}, ${data.status}, ${data.adult ? 1 : 0}, strftime('%s', 'now'))
|
|
@@ -59,22 +30,8 @@ export const TvLibraryRepository = {
|
|
|
59
30
|
status = COALESCE(NULLIF(EXCLUDED.status, ''), tv_library.status),
|
|
60
31
|
adult = COALESCE(EXCLUDED.adult, tv_library.adult),
|
|
61
32
|
updatedAt = strftime('%s', 'now')`),
|
|
62
|
-
updateStatus: (db, id, status) => getDrizzle(db).run(sql `
|
|
63
|
-
UPDATE tv_library SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
64
|
-
updateStatusMany: (db, ids, status) => {
|
|
65
|
-
if (ids.length === 0)
|
|
66
|
-
return Promise.resolve();
|
|
67
|
-
return getDrizzle(db).run(sql `
|
|
68
|
-
UPDATE tv_library SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`);
|
|
69
|
-
},
|
|
70
33
|
touchProgress: (db, id, progress) => getDrizzle(db).run(sql `
|
|
71
34
|
UPDATE tv_library SET lastSeason = ${progress.season}, lastEpisode = ${progress.episode}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
|
|
72
|
-
delete: (db, id) => getDrizzle(db).delete(tvLibrary).where(eq(tvLibrary.id, id)),
|
|
73
|
-
deleteMany: (db, ids) => {
|
|
74
|
-
if (ids.length === 0)
|
|
75
|
-
return Promise.resolve();
|
|
76
|
-
return getDrizzle(db).delete(tvLibrary).where(inArray(tvLibrary.id, ids));
|
|
77
|
-
},
|
|
78
35
|
};
|
|
79
36
|
export const TvProgressRepository = {
|
|
80
37
|
getByMedia: (db, mediaId) => getDrizzle(db).all(sql `
|
|
@@ -79,6 +79,4 @@ export const WatchedEpisodesRepository = {
|
|
|
79
79
|
ORDER BY we.watchedAt DESC
|
|
80
80
|
${sql.raw(limitClause)}`);
|
|
81
81
|
},
|
|
82
|
-
getEpisodesForShows: (db, showIds) => getDrizzle(db).all(sql `
|
|
83
|
-
SELECT showId, episodeNumber, currentTime, duration, watchedAt FROM watched_episodes WHERE showId IN (${sql.join(showIds.map((id) => sql `${id}`), sql `, `)})`),
|
|
84
82
|
};
|
package/server/dist/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import logger from './logger.js';
|
|
|
12
12
|
import { notifyServerExit } from './lib/ipc.js';
|
|
13
13
|
import { CONFIG } from './config.js';
|
|
14
14
|
import { initializeDatabase, initializeMangaDatabase, initializeTvDatabase, initializeAsmrDatabase, runFullSyncSequence, syncUp, waitForSync, KIND_SYNC, } from './sync.js';
|
|
15
|
+
import { isJasmrApi } from './hono/asmr.js';
|
|
15
16
|
import { loadRemoteProviders } from './providers/remote-loader.js';
|
|
16
17
|
import { discordRPCService } from './discord-rpc.js';
|
|
17
18
|
import { discordGatewayService } from './discord-gateway.js';
|
|
@@ -38,7 +39,10 @@ const app = createHonoApp(() => ({ shuttingDown: isShuttingDown, dbReady: !!db }
|
|
|
38
39
|
getProviders: () => providers,
|
|
39
40
|
getMangaProvider: (name) => mangaProviders[name],
|
|
40
41
|
getTvProvider: (name) => tvProviders[name],
|
|
41
|
-
getJasmr: () =>
|
|
42
|
+
getJasmr: () => {
|
|
43
|
+
const mod = providers['jasmr'];
|
|
44
|
+
return isJasmrApi(mod) ? mod : undefined;
|
|
45
|
+
},
|
|
42
46
|
});
|
|
43
47
|
const apiCache = new AppCache({ ttlSeconds: 3600, maxKeys: 5000 });
|
|
44
48
|
const providers = {};
|
package/server/dist/sync.js
CHANGED
|
@@ -151,13 +151,11 @@ class Mutex {
|
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
unlock() {
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
const resolve = this._waiting.shift();
|
|
155
|
+
if (resolve)
|
|
156
156
|
resolve();
|
|
157
|
-
|
|
158
|
-
else {
|
|
157
|
+
else
|
|
159
158
|
this._locked = false;
|
|
160
|
-
}
|
|
161
159
|
}
|
|
162
160
|
}
|
|
163
161
|
const syncMutex = new Mutex();
|
|
@@ -2,23 +2,13 @@ import { TRPCError } from '@trpc/server';
|
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
3
|
import { performAsmrWriteTransactionAsync } from '../../sync.js';
|
|
4
4
|
import { buildAsmrId, ASMR_STATUSES, AsmrLibraryRepository, AsmrProgressRepository, } from '../../repositories/asmr.repository.js';
|
|
5
|
-
import { defineSchema, reqObj, reqStr, optStr, reqStrArray } from '../validation.js';
|
|
5
|
+
import { defineSchema, reqObj, reqStr, optStr, reqStrArray, readPageLimit } from '../validation.js';
|
|
6
6
|
const asmrLibraryListInput = () => defineSchema((value) => {
|
|
7
7
|
const obj = reqObj(value);
|
|
8
|
-
const out = {};
|
|
8
|
+
const out = { ...readPageLimit(obj) };
|
|
9
9
|
const status = optStr(obj, 'status');
|
|
10
10
|
if (status !== undefined)
|
|
11
11
|
out.status = status;
|
|
12
|
-
if (obj.page !== undefined && obj.page !== null && obj.page !== '') {
|
|
13
|
-
const page = Number(obj.page);
|
|
14
|
-
if (Number.isFinite(page))
|
|
15
|
-
out.page = page;
|
|
16
|
-
}
|
|
17
|
-
if (obj.limit !== undefined && obj.limit !== null && obj.limit !== '') {
|
|
18
|
-
const limit = Number(obj.limit);
|
|
19
|
-
if (Number.isFinite(limit))
|
|
20
|
-
out.limit = limit;
|
|
21
|
-
}
|
|
22
12
|
return out;
|
|
23
13
|
});
|
|
24
14
|
const asmrLibraryIdInput = () => defineSchema((value) => ({
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { protectedProcedure, router } from '../index.js';
|
|
2
2
|
import { performAsmrWriteTransactionAsync } from '../../sync.js';
|
|
3
|
-
import {
|
|
3
|
+
import { adultNonListIds } from '../../utils/db-utils.js';
|
|
4
4
|
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
5
5
|
import { AsmrLibraryRepository, AsmrProgressRepository, } from '../../repositories/asmr.repository.js';
|
|
6
|
-
import { defineSchema, reqObj, reqStr, reqStrArray } from '../validation.js';
|
|
7
|
-
|
|
8
|
-
const rows = await dbAll(db, `SELECT DISTINCT p.workId as workId
|
|
9
|
-
FROM asmr_progress p
|
|
10
|
-
LEFT JOIN asmr_library l ON l.id = p.workId
|
|
11
|
-
WHERE COALESCE(l.isAdult, p.isAdult) = 1
|
|
12
|
-
AND l.id IS NULL`);
|
|
13
|
-
return rows.map((r) => r.workId);
|
|
14
|
-
}
|
|
6
|
+
import { defineSchema, reqObj, reqStr, reqStrArray, strOrNull } from '../validation.js';
|
|
7
|
+
const getAdultNonListWorkIds = (db) => adultNonListIds(db, 'asmr_progress', 'asmr_library', 'workId', 'COALESCE(l.isAdult, p.isAdult) = 1');
|
|
15
8
|
const asmrProgressIdsInput = () => defineSchema((value) => ({
|
|
16
9
|
ids: reqStrArray(reqObj(value), 'ids', true),
|
|
17
10
|
}));
|
|
@@ -26,9 +19,9 @@ const asmrProgressSaveInput = () => defineSchema((value) => {
|
|
|
26
19
|
trackLabel: obj.trackLabel,
|
|
27
20
|
currentTime: obj.currentTime,
|
|
28
21
|
duration: obj.duration,
|
|
29
|
-
title: obj
|
|
30
|
-
thumbnail: obj
|
|
31
|
-
rjCode: obj
|
|
22
|
+
title: strOrNull(obj, 'title'),
|
|
23
|
+
thumbnail: strOrNull(obj, 'thumbnail'),
|
|
24
|
+
rjCode: strOrNull(obj, 'rjCode'),
|
|
32
25
|
isAdult: obj.isAdult ?? null,
|
|
33
26
|
};
|
|
34
27
|
});
|
|
@@ -66,9 +59,9 @@ export const asmrProgressRouter = router({
|
|
|
66
59
|
trackLabel: String(input.trackLabel || ''),
|
|
67
60
|
currentTime: timeNum,
|
|
68
61
|
duration: durationNum,
|
|
69
|
-
title:
|
|
70
|
-
thumbnail:
|
|
71
|
-
rjCode:
|
|
62
|
+
title: input.title ?? null,
|
|
63
|
+
thumbnail: input.thumbnail ?? null,
|
|
64
|
+
rjCode: input.rjCode ?? null,
|
|
72
65
|
isAdult: input.isAdult != null ? Number(input.isAdult) : null,
|
|
73
66
|
});
|
|
74
67
|
await AsmrLibraryRepository.touchProgress(tx, String(input.workId), {
|
|
@@ -101,12 +94,7 @@ export const asmrProgressRouter = router({
|
|
|
101
94
|
.input(asmrContinueListeningInput())
|
|
102
95
|
.query(async ({ ctx, input }) => {
|
|
103
96
|
const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
|
|
104
|
-
const
|
|
105
|
-
const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
|
|
106
|
-
const listOnlyRow = await SettingsRepository.getByKey(ctx.db, 'asmrCwWatchlistOnly');
|
|
107
|
-
const listOnly = listOnlyRow
|
|
108
|
-
? listOnlyRow.value === 'true' || listOnlyRow.value === '1'
|
|
109
|
-
: false;
|
|
97
|
+
const { ignoreAdult, listOnly } = await SettingsRepository.readContinueFlags(ctx.db, 'asmrIgnoreAdultContent', 'asmrCwWatchlistOnly');
|
|
110
98
|
const rows = (await AsmrProgressRepository.getContinueListening(ctx.asmrDb, limit)).filter((row) => {
|
|
111
99
|
if (ignoreAdult && row.isAdult === 1)
|
|
112
100
|
return false;
|