@serifpersia/dango 3.4.4 → 3.4.5
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-I3S6qw9d.js} +1 -1
- package/client/dist/assets/{AnimeInfo-DyX9qTwg.js → AnimeInfo-CLU2NSBR.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-Dvp_c8vK.js → AnimeInfoPage-4Mw9ArDy.js} +2 -2
- package/client/dist/assets/{Asmr-qg3lMAJn.js → Asmr-DN9XBc5Y.js} +1 -1
- package/client/dist/assets/{AsmrResetProgressModal-BpPlg_Vv.js → AsmrResetProgressModal-DyU1Z2Gw.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-FJllOgOH.js → AvSyncCalibrator-DxL0mr_4.js} +1 -1
- package/client/dist/assets/{Home-A7gCeEST.js → Home-CeTeJ-gM.js} +1 -1
- package/client/dist/assets/{Insights-G9IXepHJ.js → Insights-i34-qV4C.js} +1 -1
- package/client/dist/assets/{LibraryListPage-Bh2qCJPh.js → LibraryListPage-DE-ASOX2.js} +1 -1
- package/client/dist/assets/{ListeningList-Czv4cTRl.js → ListeningList-DrNce5ll.js} +1 -1
- package/client/dist/assets/{Manga-qdAAS1z7.js → Manga-DOrf7Fk6.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-1rTgZzf2.js → MangaInfoPage-CAZMLMGw.js} +1 -1
- package/client/dist/assets/{MangaReadPage-CeyxPG6w.js → MangaReadPage-DQVEo8_F.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-C_ppotmh.js → MangaResetProgressModal-DOjpLAMp.js} +1 -1
- package/client/dist/assets/{Mature-Bkhxnr3K.js → Mature-D2xb9EGB.js} +1 -1
- package/client/dist/assets/{MediaCard-BQZsutEo.js → MediaCard-CKUEcFTd.js} +1 -1
- package/client/dist/assets/{Music-C0rGzSbi.js → Music-w_a34r7r.js} +1 -1
- package/client/dist/assets/{OptionListMenu-CyfpCBEy.js → OptionListMenu-DxZHBMIZ.js} +1 -1
- package/client/dist/assets/{Player-yhiTlpYU.js → Player-B8YHeATv.js} +2 -2
- package/client/dist/assets/{PlayerSettings-icy4SfQa.js → PlayerSettings-Ddlg-MvW.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-5eloKF5J.js → QueueOptionsButton-BMvAC6Ut.js} +1 -1
- package/client/dist/assets/{QueueRail-DaMjlKL4.js → QueueRail-CAoGJvHP.js} +1 -1
- package/client/dist/assets/{Radio-FxGqCxR4.js → Radio-6O7rAUv1.js} +1 -1
- package/client/dist/assets/{ReadingList-CCm3IAbt.js → ReadingList-k3g8oHvs.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CeI4V3Y5.js → ResetProgressModal-vFC6N-Qw.js} +1 -1
- package/client/dist/assets/{Search-qpILaAiQ.js → Search-Bh9Y6nIJ.js} +1 -1
- package/client/dist/assets/{Settings-Bw9M3NGz.js → Settings-DRumujfh.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-0dY_xMq3.js → SkeletonGrid-BtzUnQE0.js} +1 -1
- package/client/dist/assets/{Trackers-Zy5GOs0P.js → Trackers-D0nqy8wu.js} +1 -1
- package/client/dist/assets/{Tv-DREwc7BX.js → Tv-DVebLMNS.js} +1 -1
- package/client/dist/assets/{TvInfo-CHIsVzZ0.js → TvInfo-BkECbOKN.js} +1 -1
- package/client/dist/assets/{TvPopup-D_tVTyZ2.js → TvPopup-D8WWY6o4.js} +1 -1
- package/client/dist/assets/{TvSearch-BVxedBW-.js → TvSearch-kWPep3gL.js} +1 -1
- package/client/dist/assets/{TvWatchlist-BEIwq9R9.js → TvWatchlist-dD4emIHx.js} +1 -1
- package/client/dist/assets/{Watchlist-CSngjqlz.js → Watchlist-cWxw38nU.js} +1 -1
- package/client/dist/assets/{hls-D48GVTkk.js → hls-uG_Y3i5s.js} +1 -1
- package/client/dist/assets/{index-CVB5dYhT.js → index-BWIWE3Qy.js} +2 -2
- package/client/dist/assets/{useAnimeInfoData-DxdOg_B7.js → useAnimeInfoData-DjPBg24u.js} +1 -1
- package/client/dist/assets/{useAsmr-ClckxnPe.js → useAsmr-DQyobNQX.js} +1 -1
- package/client/dist/assets/{useAsmrPopup-Bbgxefy_.js → useAsmrPopup-CDoroMBD.js} +1 -1
- package/client/dist/assets/{useMangaLibrary-B3_BUKVk.js → useMangaLibrary-C6L8mrnS.js} +1 -1
- package/client/dist/assets/{useMangaPopup-Celk4lzf.js → useMangaPopup-BSuMa_vA.js} +1 -1
- package/client/dist/assets/{useProviders-pwxPZPHh.js → useProviders-XIdvDof6.js} +1 -1
- package/client/dist/assets/{useTv-D9sQrDop.js → useTv-DzSYasru.js} +1 -1
- package/client/dist/assets/{useTvLibrary-UsQpgBTB.js → useTvLibrary-BnbHS-gg.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 +3 -24
- package/server/dist/hono/tv.js +3 -56
- package/server/dist/lib/mal.js +4 -3
- 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 +2 -1
- 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 -52
- package/server/dist/repositories/settings.repository.js +8 -0
- package/server/dist/repositories/tv.repository.js +3 -46
- 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/package.json +4 -1
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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { defineSchema, optStr, reqObj, reqStr } from '../validation.js';
|
|
3
|
+
import { badRequest, defineSchema, failed, optNum, optStr, readCount, reqObj, reqStr, } from '../validation.js';
|
|
4
4
|
import { parseJsonBody } from '../../utils/http.utils.js';
|
|
5
5
|
import { getTrending, getLatestReleases, getSeasonal, getSchedule, searchAnilist, searchAnilistByTitle, getSpotlightBanners, getBatchedHomeData, getGenreTagLists, anilistUnavailable, wasAnilistDownAtBoot, checkAnilistStatus, getShowMetaById, getAnilistEpisodes, parseMalId, getShowMetaByMalId, fromAnilistMedia, } from '../../lib/anilist.js';
|
|
6
6
|
import { malAnimeDetail, malSearchMedia, malSearchTitle } from '../../lib/mal.js';
|
|
@@ -14,25 +14,6 @@ import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js
|
|
|
14
14
|
import { WatchlistRepository } from '../../repositories/watchlist.repository.js';
|
|
15
15
|
import { dbRun } from '../../utils/db-utils.js';
|
|
16
16
|
import logger from '../../logger.js';
|
|
17
|
-
function failed(message) {
|
|
18
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
19
|
-
}
|
|
20
|
-
function badRequest(message) {
|
|
21
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
22
|
-
}
|
|
23
|
-
function optInt(obj, name, fallback) {
|
|
24
|
-
const value = obj[name];
|
|
25
|
-
if (value === undefined || value === null || value === '')
|
|
26
|
-
return fallback;
|
|
27
|
-
return parseInt(String(value), 10) || fallback;
|
|
28
|
-
}
|
|
29
|
-
function optIntOrUndefined(obj, name) {
|
|
30
|
-
const value = obj[name];
|
|
31
|
-
if (value === undefined || value === null || value === '')
|
|
32
|
-
return undefined;
|
|
33
|
-
const parsed = parseInt(String(value), 10);
|
|
34
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
35
|
-
}
|
|
36
17
|
async function cached(ctx, key, ttl, validate, produce) {
|
|
37
18
|
const hit = ctx.apiCache.get(key);
|
|
38
19
|
if (hit)
|
|
@@ -62,9 +43,9 @@ const dataLatestReleasesInput = () => defineSchema((value) => {
|
|
|
62
43
|
if (format !== undefined)
|
|
63
44
|
out.format = format;
|
|
64
45
|
if (obj.page !== undefined)
|
|
65
|
-
out.page =
|
|
46
|
+
out.page = readCount(obj, 'page', 1);
|
|
66
47
|
if (obj.size !== undefined)
|
|
67
|
-
out.size =
|
|
48
|
+
out.size = readCount(obj, 'size', 12);
|
|
68
49
|
return out;
|
|
69
50
|
});
|
|
70
51
|
const dataSearchInput = () => defineSchema((value) => {
|
|
@@ -74,9 +55,9 @@ const dataSearchInput = () => defineSchema((value) => {
|
|
|
74
55
|
if (query !== undefined)
|
|
75
56
|
out.query = query;
|
|
76
57
|
if (obj.page !== undefined)
|
|
77
|
-
out.page =
|
|
58
|
+
out.page = readCount(obj, 'page', 1);
|
|
78
59
|
if (obj.limit !== undefined)
|
|
79
|
-
out.limit =
|
|
60
|
+
out.limit = readCount(obj, 'limit', 14);
|
|
80
61
|
const sortBy = optStr(obj, 'sortBy');
|
|
81
62
|
if (sortBy !== undefined)
|
|
82
63
|
out.sortBy = sortBy;
|
|
@@ -89,7 +70,7 @@ const dataSearchInput = () => defineSchema((value) => {
|
|
|
89
70
|
const season = optStr(obj, 'season');
|
|
90
71
|
if (season !== undefined)
|
|
91
72
|
out.season = season;
|
|
92
|
-
const year =
|
|
73
|
+
const year = optNum(obj, 'year');
|
|
93
74
|
if (year !== undefined)
|
|
94
75
|
out.year = year;
|
|
95
76
|
const country = optStr(obj, 'country');
|
|
@@ -104,10 +85,10 @@ const dataSearchInput = () => defineSchema((value) => {
|
|
|
104
85
|
const excludeTags = optStr(obj, 'excludeTags');
|
|
105
86
|
if (excludeTags !== undefined)
|
|
106
87
|
out.excludeTags = excludeTags;
|
|
107
|
-
const minScore =
|
|
88
|
+
const minScore = optNum(obj, 'minScore');
|
|
108
89
|
if (minScore !== undefined)
|
|
109
90
|
out.minScore = minScore;
|
|
110
|
-
const minEpisodes =
|
|
91
|
+
const minEpisodes = optNum(obj, 'minEpisodes');
|
|
111
92
|
if (minEpisodes !== undefined)
|
|
112
93
|
out.minEpisodes = minEpisodes;
|
|
113
94
|
const adult = optStr(obj, 'adult');
|
|
@@ -138,9 +119,9 @@ const dataSeasonalInput = () => defineSchema((value) => {
|
|
|
138
119
|
const obj = reqObj(value);
|
|
139
120
|
const out = {};
|
|
140
121
|
if (obj.page !== undefined)
|
|
141
|
-
out.page =
|
|
122
|
+
out.page = readCount(obj, 'page', 1);
|
|
142
123
|
if (obj.size !== undefined)
|
|
143
|
-
out.size =
|
|
124
|
+
out.size = readCount(obj, 'size', 14);
|
|
144
125
|
const format = optStr(obj, 'format');
|
|
145
126
|
if (format !== undefined)
|
|
146
127
|
out.format = format;
|
|
@@ -153,9 +134,9 @@ const dataPopularListInput = () => defineSchema((value) => {
|
|
|
153
134
|
if (sort !== undefined)
|
|
154
135
|
out.sort = sort;
|
|
155
136
|
if (obj.page !== undefined)
|
|
156
|
-
out.page =
|
|
137
|
+
out.page = readCount(obj, 'page', 1);
|
|
157
138
|
if (obj.size !== undefined)
|
|
158
|
-
out.size =
|
|
139
|
+
out.size = readCount(obj, 'size', 20);
|
|
159
140
|
return out;
|
|
160
141
|
});
|
|
161
142
|
const dataHomeInput = () => defineSchema((value) => {
|
|
@@ -229,16 +210,16 @@ const dataMatureSearchInput = () => defineSchema((value) => {
|
|
|
229
210
|
out[k] = v;
|
|
230
211
|
}
|
|
231
212
|
if (obj.page !== undefined)
|
|
232
|
-
out.page =
|
|
213
|
+
out.page = readCount(obj, 'page', 1);
|
|
233
214
|
if (obj.limit !== undefined)
|
|
234
|
-
out.limit =
|
|
235
|
-
const year =
|
|
215
|
+
out.limit = readCount(obj, 'limit', 14);
|
|
216
|
+
const year = optNum(obj, 'year');
|
|
236
217
|
if (year !== undefined)
|
|
237
218
|
out.year = year;
|
|
238
|
-
const minScore =
|
|
219
|
+
const minScore = optNum(obj, 'minScore');
|
|
239
220
|
if (minScore !== undefined)
|
|
240
221
|
out.minScore = minScore;
|
|
241
|
-
const minEpisodes =
|
|
222
|
+
const minEpisodes = optNum(obj, 'minEpisodes');
|
|
242
223
|
if (minEpisodes !== undefined)
|
|
243
224
|
out.minEpisodes = minEpisodes;
|
|
244
225
|
return out;
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import { TRPCError } from '@trpc/server';
|
|
2
1
|
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { defineSchema, optStr, reqObj } from '../validation.js';
|
|
2
|
+
import { badRequest, defineSchema, failed, optStr, reqObj } from '../validation.js';
|
|
4
3
|
import { discordRPCService } from '../../discord-rpc.js';
|
|
5
4
|
import { discordGatewayService } from '../../discord-gateway.js';
|
|
6
5
|
import { getDlsitePoster, getMangadexCover } from '../../hono/watchlist.js';
|
|
7
6
|
import { updateEnvFile } from '../../utils/env.utils.js';
|
|
8
7
|
import { CONFIG } from '../../config.js';
|
|
9
8
|
import logger from '../../logger.js';
|
|
10
|
-
function failed(message) {
|
|
11
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
12
|
-
}
|
|
13
|
-
function badRequest(message) {
|
|
14
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
15
|
-
}
|
|
16
9
|
function maskToken(t) {
|
|
17
10
|
if (!t)
|
|
18
11
|
return 'none';
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { TRPCError } from '@trpc/server';
|
|
2
1
|
import { protectedProcedure, router } from '../index.js';
|
|
3
2
|
import { getWatchInsights, getGenreCards } from '../../lib/insights.js';
|
|
4
3
|
import { computeDiscordSyncStats, getCachedRecommendations, getLinkedDiscordUser, setLinkedDiscordUser, syncDiscordRoles, } from '../../lib/discord-roles-sync.service.js';
|
|
5
|
-
import { defineSchema, reqObj } from '../validation.js';
|
|
4
|
+
import { defineSchema, optStr, reqObj, reqStr } from '../validation.js';
|
|
6
5
|
const discordUserInput = () => defineSchema((value) => {
|
|
7
6
|
if (value === undefined || value === null)
|
|
8
7
|
return {};
|
|
9
|
-
|
|
8
|
+
const raw = reqObj(value).user;
|
|
9
|
+
if (raw === undefined || raw === null)
|
|
10
|
+
return { user: null };
|
|
11
|
+
const obj = reqObj(raw);
|
|
12
|
+
return {
|
|
13
|
+
user: {
|
|
14
|
+
id: reqStr(obj, 'id'),
|
|
15
|
+
username: optStr(obj, 'username') ?? '',
|
|
16
|
+
avatar: optStr(obj, 'avatar') ?? null,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
10
19
|
});
|
|
11
20
|
export const insightsRouter = router({
|
|
12
21
|
summary: protectedProcedure.query(async ({ ctx }) => {
|
|
@@ -25,19 +34,9 @@ export const insightsRouter = router({
|
|
|
25
34
|
return { user: await getLinkedDiscordUser(ctx.db) };
|
|
26
35
|
}),
|
|
27
36
|
setDiscordUser: protectedProcedure.input(discordUserInput()).mutation(async ({ ctx, input }) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
(typeof user !== 'object' ||
|
|
32
|
-
typeof user.id !== 'string' ||
|
|
33
|
-
!user.id)) {
|
|
34
|
-
throw new TRPCError({
|
|
35
|
-
code: 'BAD_REQUEST',
|
|
36
|
-
message: 'Invalid user payload: expected { id, ... } or null',
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
await setLinkedDiscordUser(ctx.db, user || null);
|
|
40
|
-
return { success: true, user: user || null };
|
|
37
|
+
const user = input.user ?? null;
|
|
38
|
+
await setLinkedDiscordUser(ctx.db, user);
|
|
39
|
+
return { success: true, user };
|
|
41
40
|
}),
|
|
42
41
|
discordSyncNow: protectedProcedure.mutation(async ({ ctx }) => {
|
|
43
42
|
return syncDiscordRoles(ctx.db, true);
|
|
@@ -2,7 +2,7 @@ import { TRPCError } from '@trpc/server';
|
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
3
|
import { performMangaWriteTransactionAsync } from '../../sync.js';
|
|
4
4
|
import { buildMangaId, MANGA_STATUSES, MangaLibraryRepository, MangaProgressRepository, } from '../../repositories/manga.repository.js';
|
|
5
|
-
import { defineSchema, optStr, reqObj } from '../validation.js';
|
|
5
|
+
import { badRequest, defineSchema, failed, optStr, readCount, reqObj, reqStr, } from '../validation.js';
|
|
6
6
|
function mangaDb(ctx) {
|
|
7
7
|
const db = ctx.mangaDb;
|
|
8
8
|
if (!db || db.isClosedCheck())
|
|
@@ -14,19 +14,6 @@ function normalizeId(provider, mangaId, idRaw) {
|
|
|
14
14
|
return idRaw;
|
|
15
15
|
return buildMangaId(provider, mangaId || idRaw || '');
|
|
16
16
|
}
|
|
17
|
-
function badRequest(message) {
|
|
18
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
19
|
-
}
|
|
20
|
-
function failed(message) {
|
|
21
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
22
|
-
}
|
|
23
|
-
function readCount(obj, name, fallback) {
|
|
24
|
-
const raw = obj[name];
|
|
25
|
-
if (raw === undefined || raw === null || raw === '')
|
|
26
|
-
return fallback;
|
|
27
|
-
const n = Math.floor(Number(raw));
|
|
28
|
-
return Number.isFinite(n) ? n : fallback;
|
|
29
|
-
}
|
|
30
17
|
const mangaLibraryListInput = () => defineSchema((value) => {
|
|
31
18
|
const obj = reqObj(value);
|
|
32
19
|
const status = optStr(obj, 'status');
|
|
@@ -34,13 +21,9 @@ const mangaLibraryListInput = () => defineSchema((value) => {
|
|
|
34
21
|
const limit = Math.min(Math.max(readCount(obj, 'limit', 24), 1), 100);
|
|
35
22
|
return { status, page, limit };
|
|
36
23
|
});
|
|
37
|
-
const mangaLibraryIdInput = () => defineSchema((value) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (typeof id !== 'string')
|
|
41
|
-
throw new Error('id must be a string');
|
|
42
|
-
return { id };
|
|
43
|
-
});
|
|
24
|
+
const mangaLibraryIdInput = () => defineSchema((value) => ({
|
|
25
|
+
id: reqStr(reqObj(value), 'id'),
|
|
26
|
+
}));
|
|
44
27
|
const mangaLibraryAddInput = () => defineSchema((value) => {
|
|
45
28
|
const obj = reqObj(value);
|
|
46
29
|
const out = {
|
|
@@ -179,7 +162,7 @@ export const mangaLibraryRouter = router({
|
|
|
179
162
|
setStatus: protectedProcedure
|
|
180
163
|
.input(mangaLibraryStatusInput())
|
|
181
164
|
.mutation(async ({ ctx, input }) => {
|
|
182
|
-
if (!input.id || !MANGA_STATUSES.includes(input.status)) {
|
|
165
|
+
if (!input.id || !input.status || !MANGA_STATUSES.includes(input.status)) {
|
|
183
166
|
throw badRequest('id and a valid status are required');
|
|
184
167
|
}
|
|
185
168
|
try {
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { protectedProcedure, router } from '../index.js';
|
|
2
2
|
import { defineSchema, optStr, reqObj, reqStr, reqStrArray } from '../validation.js';
|
|
3
3
|
import { performMangaWriteTransactionAsync } from '../../sync.js';
|
|
4
|
-
import {
|
|
4
|
+
import { adultNonListIds } from '../../utils/db-utils.js';
|
|
5
5
|
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
6
6
|
import { MangaLibraryRepository, MangaProgressRepository, } from '../../repositories/manga.repository.js';
|
|
7
7
|
const MANGA_ADULT_RATINGS = ['erotica', 'pornographic'];
|
|
8
8
|
const isMangaContinueAdult = (row) => !!row.contentRating && MANGA_ADULT_RATINGS.includes(row.contentRating);
|
|
9
|
-
|
|
10
|
-
const rows = await dbAll(db, `SELECT DISTINCT p.mangaId as mangaId
|
|
11
|
-
FROM manga_progress p
|
|
12
|
-
LEFT JOIN manga_library l ON l.id = p.mangaId
|
|
13
|
-
WHERE COALESCE(l.contentRating, p.contentRating) IN ('erotica', 'pornographic')
|
|
14
|
-
AND l.id IS NULL`);
|
|
15
|
-
return rows.map((r) => r.mangaId);
|
|
16
|
-
}
|
|
9
|
+
const getAdultNonListMangaIds = (db) => adultNonListIds(db, 'manga_progress', 'manga_library', 'mangaId', "COALESCE(l.contentRating, p.contentRating) IN ('erotica', 'pornographic')");
|
|
17
10
|
const mangaIdInput = () => defineSchema((value) => ({
|
|
18
11
|
mangaId: reqStr(reqObj(value), 'mangaId'),
|
|
19
12
|
}));
|
|
@@ -102,11 +95,11 @@ export const mangaProgressRouter = router({
|
|
|
102
95
|
chapterNumber: String(input.chapterNumber || ''),
|
|
103
96
|
page: pageNum,
|
|
104
97
|
pageCount: pageCountNum,
|
|
105
|
-
title:
|
|
106
|
-
cover:
|
|
107
|
-
provider:
|
|
108
|
-
altTitle:
|
|
109
|
-
contentRating:
|
|
98
|
+
title: input.title ?? null,
|
|
99
|
+
cover: input.cover ?? null,
|
|
100
|
+
provider: input.provider ?? null,
|
|
101
|
+
altTitle: input.altTitle ?? null,
|
|
102
|
+
contentRating: input.contentRating ?? null,
|
|
110
103
|
});
|
|
111
104
|
await MangaLibraryRepository.touchProgress(tx, String(input.mangaId), {
|
|
112
105
|
chapterId: String(input.chapterId),
|
|
@@ -141,12 +134,7 @@ export const mangaProgressRouter = router({
|
|
|
141
134
|
.query(async ({ ctx, input }) => {
|
|
142
135
|
try {
|
|
143
136
|
const limit = Math.min(Math.max(parseInt(String(input.limit)) || 24, 1), 100);
|
|
144
|
-
const
|
|
145
|
-
const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
|
|
146
|
-
const listOnlyRow = await SettingsRepository.getByKey(ctx.db, 'mangaCwWatchlistOnly');
|
|
147
|
-
const listOnly = listOnlyRow
|
|
148
|
-
? listOnlyRow.value === 'true' || listOnlyRow.value === '1'
|
|
149
|
-
: false;
|
|
137
|
+
const { ignoreAdult, listOnly } = await SettingsRepository.readContinueFlags(ctx.db, 'mangaIgnoreAdultContent', 'mangaCwWatchlistOnly');
|
|
150
138
|
const rows = (await MangaProgressRepository.getContinueReading(ctx.mangaDb, limit)).filter((row) => {
|
|
151
139
|
if (ignoreAdult && isMangaContinueAdult(row))
|
|
152
140
|
return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { defineSchema, optStr, reqObj } from '../validation.js';
|
|
3
|
+
import { badRequest, defineSchema, failed, optStr, reqObj } from '../validation.js';
|
|
4
4
|
import logger from '../../logger.js';
|
|
5
5
|
import { getInnertube, getAuthedInnertube, refreshAuthedInnertube, saveMusicCookie, getMusicAuthStatus, signOutMusic, isParserVariantError, } from '../../lib/ytmusic.js';
|
|
6
6
|
const NAME_SUFFIXES = [
|
|
@@ -152,7 +152,8 @@ function upnextToTrack(node, trustLike = false) {
|
|
|
152
152
|
}
|
|
153
153
|
function parseUpnext(panel, trustLike) {
|
|
154
154
|
const tracks = [];
|
|
155
|
-
const
|
|
155
|
+
const shaped = panel;
|
|
156
|
+
const contents = shaped.contents ?? [];
|
|
156
157
|
for (const item of contents) {
|
|
157
158
|
const track = upnextToTrack(item, trustLike);
|
|
158
159
|
if (track)
|
|
@@ -160,7 +161,7 @@ function parseUpnext(panel, trustLike) {
|
|
|
160
161
|
if (tracks.length >= 50)
|
|
161
162
|
break;
|
|
162
163
|
}
|
|
163
|
-
const playlistId =
|
|
164
|
+
const playlistId = shaped.playlist_id ?? null;
|
|
164
165
|
return { tracks, playlistId };
|
|
165
166
|
}
|
|
166
167
|
function asMenuItems(menu) {
|
|
@@ -285,15 +286,9 @@ async function readLibrary(yt) {
|
|
|
285
286
|
logger.info(`[music] library parsed tracks=${tracks.length} playlists=${playlists.length} gridItems=${gridItems.length}`);
|
|
286
287
|
return { tracks, playlists };
|
|
287
288
|
}
|
|
288
|
-
function badRequest(message) {
|
|
289
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
290
|
-
}
|
|
291
289
|
function unauthorized(message) {
|
|
292
290
|
return new TRPCError({ code: 'UNAUTHORIZED', message });
|
|
293
291
|
}
|
|
294
|
-
function failed(message) {
|
|
295
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
296
|
-
}
|
|
297
292
|
const musicSearchInput = () => defineSchema((value) => {
|
|
298
293
|
const obj = reqObj(value);
|
|
299
294
|
const out = {};
|
|
@@ -598,8 +593,8 @@ export const musicRouter = router({
|
|
|
598
593
|
throw unauthorized('MUSIC_AUTH_REQUIRED');
|
|
599
594
|
}
|
|
600
595
|
const attempt = async (session) => {
|
|
601
|
-
const
|
|
602
|
-
|
|
596
|
+
const shaped = session;
|
|
597
|
+
const actions = shaped.actions ?? shaped.session?.actions;
|
|
603
598
|
if (!actions)
|
|
604
599
|
throw new Error('Actions unavailable');
|
|
605
600
|
const panel = await session.music.getUpNext(id);
|
|
@@ -68,7 +68,7 @@ export const queueRouter = router({
|
|
|
68
68
|
}),
|
|
69
69
|
removeMany: protectedProcedure.input(queueRemoveManyInput()).mutation(async ({ ctx, input }) => {
|
|
70
70
|
const showId = await getMigratedId(ctx.db, input.showId);
|
|
71
|
-
let removed;
|
|
71
|
+
let removed = [];
|
|
72
72
|
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
73
73
|
removed = ((await QueueRepository.getByShow(tx, showId)) || []).map((ep) => ep.episodeNumber);
|
|
74
74
|
const toRemove = Array.isArray(input.episodeNumbers) && input.episodeNumbers.length
|
|
@@ -5,15 +5,9 @@ import { performWriteTransactionAsync } from '../../sync.js';
|
|
|
5
5
|
import { AniListTracker } from '../../lib/tracker/anilist-tracker.js';
|
|
6
6
|
import { syncAniList, importFromMalUsername, importFromUsername, } from '../../lib/tracker/sync.service.js';
|
|
7
7
|
import { syncAniListManga, importFromUsernameManga, importMangaFromMalUsername, } from '../../lib/tracker/manga-sync.service.js';
|
|
8
|
-
import { defineSchema, reqObj } from '../validation.js';
|
|
8
|
+
import { badRequest, defineSchema, failed, reqObj } from '../validation.js';
|
|
9
9
|
const TRACKER_TOKEN_KEY = 'tracker_anilist_token';
|
|
10
10
|
const TRACKER_USER_KEY = 'tracker_anilist_user';
|
|
11
|
-
function badRequest(message) {
|
|
12
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
13
|
-
}
|
|
14
|
-
function failed(message) {
|
|
15
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
16
|
-
}
|
|
17
11
|
function unavailable(message) {
|
|
18
12
|
return new TRPCError({ code: 'SERVICE_UNAVAILABLE', message });
|
|
19
13
|
}
|
|
@@ -2,26 +2,16 @@ import { TRPCError } from '@trpc/server';
|
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
3
|
import { performTvWriteTransactionAsync } from '../../sync.js';
|
|
4
4
|
import { buildTvId, TV_STATUSES, TvLibraryRepository, TvProgressRepository, } from '../../repositories/tv.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
|
function normalizeMediaType(raw) {
|
|
7
7
|
return String(raw).toLowerCase() === 'movie' ? 'movie' : 'tv';
|
|
8
8
|
}
|
|
9
9
|
const tvLibraryListInput = () => defineSchema((value) => {
|
|
10
10
|
const obj = reqObj(value);
|
|
11
|
-
const out = {};
|
|
11
|
+
const out = { ...readPageLimit(obj) };
|
|
12
12
|
const status = optStr(obj, 'status');
|
|
13
13
|
if (status !== undefined)
|
|
14
14
|
out.status = status;
|
|
15
|
-
if (obj.page !== undefined && obj.page !== null && obj.page !== '') {
|
|
16
|
-
const page = Number(obj.page);
|
|
17
|
-
if (Number.isFinite(page))
|
|
18
|
-
out.page = page;
|
|
19
|
-
}
|
|
20
|
-
if (obj.limit !== undefined && obj.limit !== null && obj.limit !== '') {
|
|
21
|
-
const limit = Number(obj.limit);
|
|
22
|
-
if (Number.isFinite(limit))
|
|
23
|
-
out.limit = limit;
|
|
24
|
-
}
|
|
25
15
|
return out;
|
|
26
16
|
});
|
|
27
17
|
const tvLibraryIdInput = () => defineSchema((value) => ({
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { protectedProcedure, router } from '../index.js';
|
|
3
3
|
import { performTvWriteTransactionAsync } from '../../sync.js';
|
|
4
|
-
import {
|
|
4
|
+
import { adultNonListIds } from '../../utils/db-utils.js';
|
|
5
5
|
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
6
6
|
import { normalizeTvMediaId, TvLibraryRepository, TvProgressRepository, } from '../../repositories/tv.repository.js';
|
|
7
|
-
import { defineSchema, reqObj, reqStr } from '../validation.js';
|
|
7
|
+
import { defineSchema, reqObj, reqStr, strOrNull } from '../validation.js';
|
|
8
8
|
const isTvContinueAdult = (row) => row.adult === true || row.adult === 1;
|
|
9
|
-
|
|
10
|
-
const rows = await dbAll(db, `SELECT DISTINCT p.mediaId as mediaId
|
|
11
|
-
FROM tv_progress p
|
|
12
|
-
LEFT JOIN tv_library l ON l.id = p.mediaId
|
|
13
|
-
WHERE COALESCE(l.adult, p.adult) = 1
|
|
14
|
-
AND l.id IS NULL`);
|
|
15
|
-
return rows.map((r) => r.mediaId);
|
|
16
|
-
}
|
|
9
|
+
const getAdultNonListMediaIds = (db) => adultNonListIds(db, 'tv_progress', 'tv_library', 'mediaId', 'COALESCE(l.adult, p.adult) = 1');
|
|
17
10
|
const tvProgressMediaInput = () => defineSchema((value) => ({
|
|
18
11
|
mediaId: reqStr(reqObj(value), 'mediaId'),
|
|
19
12
|
}));
|
|
@@ -30,13 +23,13 @@ const tvProgressSaveInput = () => defineSchema((value) => {
|
|
|
30
23
|
currentTime: obj.currentTime,
|
|
31
24
|
duration: obj.duration,
|
|
32
25
|
completed: obj.completed,
|
|
33
|
-
title: obj
|
|
34
|
-
poster: obj
|
|
35
|
-
backdrop: obj
|
|
36
|
-
year: obj
|
|
37
|
-
overview: obj
|
|
26
|
+
title: strOrNull(obj, 'title'),
|
|
27
|
+
poster: strOrNull(obj, 'poster'),
|
|
28
|
+
backdrop: strOrNull(obj, 'backdrop'),
|
|
29
|
+
year: strOrNull(obj, 'year'),
|
|
30
|
+
overview: strOrNull(obj, 'overview'),
|
|
38
31
|
tmdbId: obj.tmdbId ?? null,
|
|
39
|
-
mediaType: obj
|
|
32
|
+
mediaType: strOrNull(obj, 'mediaType'),
|
|
40
33
|
adult: obj.adult ?? null,
|
|
41
34
|
};
|
|
42
35
|
});
|
|
@@ -95,13 +88,13 @@ export const tvProgressRouter = router({
|
|
|
95
88
|
currentTime: storedTime,
|
|
96
89
|
duration: durationNum,
|
|
97
90
|
completed: completedNow,
|
|
98
|
-
title:
|
|
99
|
-
poster:
|
|
100
|
-
backdrop:
|
|
101
|
-
year:
|
|
102
|
-
overview:
|
|
91
|
+
title: input.title ?? null,
|
|
92
|
+
poster: input.poster ?? null,
|
|
93
|
+
backdrop: input.backdrop ?? null,
|
|
94
|
+
year: input.year ?? null,
|
|
95
|
+
overview: input.overview ?? null,
|
|
103
96
|
tmdbId: input.tmdbId != null ? Number(input.tmdbId) : null,
|
|
104
|
-
mediaType:
|
|
97
|
+
mediaType: input.mediaType ?? null,
|
|
105
98
|
adult: input.adult != null ? Number(input.adult) : null,
|
|
106
99
|
});
|
|
107
100
|
await TvLibraryRepository.touchProgress(tx, String(mediaId), {
|
|
@@ -139,12 +132,7 @@ export const tvProgressRouter = router({
|
|
|
139
132
|
.input(tvContinueWatchingInput())
|
|
140
133
|
.query(async ({ ctx, input }) => {
|
|
141
134
|
const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
|
|
142
|
-
const
|
|
143
|
-
const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
|
|
144
|
-
const listOnlyRow = await SettingsRepository.getByKey(ctx.db, 'tvCwWatchlistOnly');
|
|
145
|
-
const listOnly = listOnlyRow
|
|
146
|
-
? listOnlyRow.value === 'true' || listOnlyRow.value === '1'
|
|
147
|
-
: false;
|
|
135
|
+
const { ignoreAdult, listOnly } = await SettingsRepository.readContinueFlags(ctx.db, 'tvIgnoreAdultContent', 'tvCwWatchlistOnly');
|
|
148
136
|
const rows = (await TvProgressRepository.getContinueWatching(ctx.tvDb, limit)).filter((row) => {
|
|
149
137
|
if (ignoreAdult && isTvContinueAdult(row))
|
|
150
138
|
return false;
|