@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,258 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function defineSchema(parse) {
|
|
3
|
-
return {
|
|
4
|
-
'~standard': {
|
|
5
|
-
version: 1,
|
|
6
|
-
vendor: 'dango',
|
|
7
|
-
types: undefined,
|
|
8
|
-
validate(value) {
|
|
9
|
-
try {
|
|
10
|
-
return { value: parse(value) };
|
|
11
|
-
}
|
|
12
|
-
catch (err) {
|
|
13
|
-
return { issues: [{ message: err instanceof Error ? err.message : 'Invalid input' }] };
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function readKey(value) {
|
|
20
|
-
if (typeof value !== 'object' || value === null)
|
|
21
|
-
throw new Error('Expected an object');
|
|
22
|
-
const key = value.key;
|
|
23
|
-
if (typeof key !== 'string' || key.length === 0)
|
|
24
|
-
throw new Error('key must be a string');
|
|
25
|
-
return key;
|
|
26
|
-
}
|
|
27
|
-
export function reqStr(obj, name) {
|
|
28
|
-
const value = obj[name];
|
|
29
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
30
|
-
throw new Error(`${name} must be a string`);
|
|
31
|
-
}
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
export function optStr(obj, name) {
|
|
35
|
-
const value = obj[name];
|
|
36
|
-
if (value === undefined || value === null)
|
|
37
|
-
return undefined;
|
|
38
|
-
return String(value);
|
|
39
|
-
}
|
|
40
|
-
export function optNum(obj, name) {
|
|
41
|
-
const value = obj[name];
|
|
42
|
-
if (value === undefined || value === null || value === '')
|
|
43
|
-
return undefined;
|
|
44
|
-
const num = Number(value);
|
|
45
|
-
return Number.isFinite(num) ? num : undefined;
|
|
46
|
-
}
|
|
47
|
-
export function optBool(obj, name) {
|
|
48
|
-
const value = obj[name];
|
|
49
|
-
if (typeof value !== 'boolean')
|
|
50
|
-
return undefined;
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
export function badRequest(message) {
|
|
54
|
-
return new TRPCError({ code: 'BAD_REQUEST', message });
|
|
55
|
-
}
|
|
56
|
-
export function failed(message) {
|
|
57
|
-
return new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message });
|
|
58
|
-
}
|
|
59
|
-
export function reqId(obj, name) {
|
|
60
|
-
const value = obj[name];
|
|
61
|
-
if ((typeof value !== 'string' && typeof value !== 'number') || String(value).length === 0) {
|
|
62
|
-
throw new Error(`${name} must be a string`);
|
|
63
|
-
}
|
|
64
|
-
return String(value);
|
|
65
|
-
}
|
|
66
|
-
export function readCount(obj, name, fallback) {
|
|
67
|
-
const raw = obj[name];
|
|
68
|
-
if (raw === undefined || raw === null || raw === '')
|
|
69
|
-
return fallback;
|
|
70
|
-
const n = Math.floor(Number(raw));
|
|
71
|
-
return Number.isFinite(n) ? n : fallback;
|
|
72
|
-
}
|
|
73
|
-
export function strOrNull(obj, name) {
|
|
74
|
-
const value = obj[name];
|
|
75
|
-
return typeof value === 'string' ? value : null;
|
|
76
|
-
}
|
|
77
|
-
export function readPageLimit(obj) {
|
|
78
|
-
const out = {};
|
|
79
|
-
const page = optNum(obj, 'page');
|
|
80
|
-
if (page !== undefined)
|
|
81
|
-
out.page = page;
|
|
82
|
-
const limit = optNum(obj, 'limit');
|
|
83
|
-
if (limit !== undefined)
|
|
84
|
-
out.limit = limit;
|
|
85
|
-
return out;
|
|
86
|
-
}
|
|
87
|
-
function reqEpNum(obj, name) {
|
|
88
|
-
const value = obj[name];
|
|
89
|
-
if ((typeof value !== 'string' && typeof value !== 'number') || String(value).length === 0) {
|
|
90
|
-
throw new Error(`${name} must be a string`);
|
|
91
|
-
}
|
|
92
|
-
return String(value);
|
|
93
|
-
}
|
|
94
|
-
export function reqStrArray(obj, name, nonEmpty) {
|
|
95
|
-
const value = obj[name];
|
|
96
|
-
if (!Array.isArray(value) || (nonEmpty && value.length === 0)) {
|
|
97
|
-
throw new Error(`${name} must be ${nonEmpty ? 'a non-empty ' : 'an '}array`);
|
|
98
|
-
}
|
|
99
|
-
return value.map((ep) => String(ep));
|
|
100
|
-
}
|
|
101
|
-
export function reqObj(value) {
|
|
102
|
-
if (typeof value !== 'object' || value === null)
|
|
103
|
-
throw new Error('Expected an object');
|
|
104
|
-
return value;
|
|
105
|
-
}
|
|
106
|
-
export const keyInput = () => defineSchema((value) => ({ key: readKey(value) }));
|
|
107
|
-
export const idInput = () => defineSchema((value) => ({ id: reqStr(reqObj(value), 'id') }));
|
|
108
|
-
export const showIdInput = () => defineSchema((value) => ({
|
|
109
|
-
showId: reqStr(reqObj(value), 'showId'),
|
|
110
|
-
}));
|
|
111
|
-
export const episodeProgressInput = () => defineSchema((value) => {
|
|
112
|
-
const obj = reqObj(value);
|
|
113
|
-
return { showId: reqStr(obj, 'showId'), episodeNumber: reqEpNum(obj, 'episodeNumber') };
|
|
114
|
-
});
|
|
115
|
-
export const idsInput = () => defineSchema((value) => ({
|
|
116
|
-
ids: [...new Set(reqStrArray(reqObj(value), 'ids', true))],
|
|
117
|
-
}));
|
|
118
|
-
export const watchlistAddInput = () => defineSchema((value) => {
|
|
119
|
-
const obj = reqObj(value);
|
|
120
|
-
const out = { id: reqStr(obj, 'id'), name: reqStr(obj, 'name') };
|
|
121
|
-
const status = optStr(obj, 'status');
|
|
122
|
-
if (status !== undefined)
|
|
123
|
-
out.status = status;
|
|
124
|
-
const thumbnail = optStr(obj, 'thumbnail');
|
|
125
|
-
if (thumbnail !== undefined)
|
|
126
|
-
out.thumbnail = thumbnail;
|
|
127
|
-
const nativeName = optStr(obj, 'nativeName');
|
|
128
|
-
if (nativeName !== undefined)
|
|
129
|
-
out.nativeName = nativeName;
|
|
130
|
-
const englishName = optStr(obj, 'englishName');
|
|
131
|
-
if (englishName !== undefined)
|
|
132
|
-
out.englishName = englishName;
|
|
133
|
-
const type = optStr(obj, 'type');
|
|
134
|
-
if (type !== undefined)
|
|
135
|
-
out.type = type;
|
|
136
|
-
if (typeof obj.isAdult === 'boolean')
|
|
137
|
-
out.isAdult = obj.isAdult;
|
|
138
|
-
return out;
|
|
139
|
-
});
|
|
140
|
-
export const watchlistStatusInput = () => defineSchema((value) => {
|
|
141
|
-
const obj = reqObj(value);
|
|
142
|
-
return { id: reqStr(obj, 'id'), status: reqStr(obj, 'status') };
|
|
143
|
-
});
|
|
144
|
-
export const watchlistBatchStatusInput = () => defineSchema((value) => {
|
|
145
|
-
const obj = reqObj(value);
|
|
146
|
-
return { ids: [...new Set(reqStrArray(obj, 'ids', true))], status: reqStr(obj, 'status') };
|
|
147
|
-
});
|
|
148
|
-
function readMeta(obj) {
|
|
149
|
-
return {
|
|
150
|
-
showName: optStr(obj, 'showName'),
|
|
151
|
-
showThumbnail: optStr(obj, 'showThumbnail'),
|
|
152
|
-
nativeName: optStr(obj, 'nativeName'),
|
|
153
|
-
englishName: optStr(obj, 'englishName'),
|
|
154
|
-
type: optStr(obj, 'type'),
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
export const queueAddInput = () => defineSchema((value) => {
|
|
158
|
-
const obj = reqObj(value);
|
|
159
|
-
return {
|
|
160
|
-
...readMeta(obj),
|
|
161
|
-
showId: reqStr(obj, 'showId'),
|
|
162
|
-
episodeNumber: reqEpNum(obj, 'episodeNumber'),
|
|
163
|
-
};
|
|
164
|
-
});
|
|
165
|
-
export const queueBatchInput = () => defineSchema((value) => {
|
|
166
|
-
const obj = reqObj(value);
|
|
167
|
-
return {
|
|
168
|
-
...readMeta(obj),
|
|
169
|
-
showId: reqStr(obj, 'showId'),
|
|
170
|
-
episodeNumbers: reqStrArray(obj, 'episodeNumbers', true),
|
|
171
|
-
};
|
|
172
|
-
});
|
|
173
|
-
export const queueRemoveInput = () => defineSchema((value) => {
|
|
174
|
-
const obj = reqObj(value);
|
|
175
|
-
return { showId: reqStr(obj, 'showId'), episodeNumber: reqEpNum(obj, 'episodeNumber') };
|
|
176
|
-
});
|
|
177
|
-
export const queueRemoveManyInput = () => defineSchema((value) => {
|
|
178
|
-
const obj = reqObj(value);
|
|
179
|
-
const out = { showId: reqStr(obj, 'showId') };
|
|
180
|
-
if (obj.episodeNumbers !== undefined)
|
|
181
|
-
out.episodeNumbers = reqStrArray(obj, 'episodeNumbers', false);
|
|
182
|
-
return out;
|
|
183
|
-
});
|
|
184
|
-
export const queueReorderInput = () => defineSchema((value) => {
|
|
185
|
-
if (!Array.isArray(value))
|
|
186
|
-
throw new Error('items must be an array');
|
|
187
|
-
return value.map((item) => {
|
|
188
|
-
const entry = reqObj(item);
|
|
189
|
-
const out = {};
|
|
190
|
-
if (typeof entry.id === 'number')
|
|
191
|
-
out.id = entry.id;
|
|
192
|
-
if (entry.showId !== undefined)
|
|
193
|
-
out.showId = String(entry.showId);
|
|
194
|
-
if (entry.episodeNumber !== undefined)
|
|
195
|
-
out.episodeNumber = String(entry.episodeNumber);
|
|
196
|
-
return out;
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
export const setSettingInput = () => defineSchema((value) => {
|
|
200
|
-
if (typeof value !== 'object' || value === null)
|
|
201
|
-
throw new Error('Expected an object');
|
|
202
|
-
return { key: readKey(value), value: value.value };
|
|
203
|
-
});
|
|
204
|
-
export const progressUpdateInput = () => defineSchema((value) => {
|
|
205
|
-
const obj = reqObj(value);
|
|
206
|
-
const out = {
|
|
207
|
-
showId: reqStr(obj, 'showId'),
|
|
208
|
-
episodeNumber: reqEpNum(obj, 'episodeNumber'),
|
|
209
|
-
};
|
|
210
|
-
const currentTime = optNum(obj, 'currentTime');
|
|
211
|
-
if (currentTime !== undefined)
|
|
212
|
-
out.currentTime = currentTime;
|
|
213
|
-
const duration = optNum(obj, 'duration');
|
|
214
|
-
if (duration !== undefined)
|
|
215
|
-
out.duration = duration;
|
|
216
|
-
const showName = optStr(obj, 'showName');
|
|
217
|
-
if (showName !== undefined)
|
|
218
|
-
out.showName = showName;
|
|
219
|
-
const showThumbnail = optStr(obj, 'showThumbnail');
|
|
220
|
-
if (showThumbnail !== undefined)
|
|
221
|
-
out.showThumbnail = showThumbnail;
|
|
222
|
-
const nativeName = optStr(obj, 'nativeName');
|
|
223
|
-
if (nativeName !== undefined)
|
|
224
|
-
out.nativeName = nativeName;
|
|
225
|
-
const englishName = optStr(obj, 'englishName');
|
|
226
|
-
if (englishName !== undefined)
|
|
227
|
-
out.englishName = englishName;
|
|
228
|
-
const genres = obj['genres'];
|
|
229
|
-
if (typeof genres === 'string' || Array.isArray(genres)) {
|
|
230
|
-
out.genres = (Array.isArray(genres) ? genres : [genres]).map(String);
|
|
231
|
-
}
|
|
232
|
-
const popularityScore = optNum(obj, 'popularityScore');
|
|
233
|
-
if (popularityScore !== undefined)
|
|
234
|
-
out.popularityScore = popularityScore;
|
|
235
|
-
const type = optStr(obj, 'type');
|
|
236
|
-
if (type !== undefined)
|
|
237
|
-
out.type = type;
|
|
238
|
-
const status = optStr(obj, 'status');
|
|
239
|
-
if (status !== undefined)
|
|
240
|
-
out.status = status;
|
|
241
|
-
const episodeCount = optNum(obj, 'episodeCount');
|
|
242
|
-
if (episodeCount !== undefined)
|
|
243
|
-
out.episodeCount = episodeCount;
|
|
244
|
-
const isPlaying = optBool(obj, 'isPlaying');
|
|
245
|
-
if (isPlaying !== undefined)
|
|
246
|
-
out.isPlaying = isPlaying;
|
|
247
|
-
const sessionId = optStr(obj, 'sessionId');
|
|
248
|
-
if (sessionId !== undefined)
|
|
249
|
-
out.sessionId = sessionId;
|
|
250
|
-
const isAdult = optBool(obj, 'isAdult');
|
|
251
|
-
if (isAdult !== undefined)
|
|
252
|
-
out.isAdult = isAdult;
|
|
253
|
-
return out;
|
|
254
|
-
});
|
|
255
|
-
export const notificationDismissInput = () => defineSchema((value) => {
|
|
256
|
-
const obj = reqObj(value);
|
|
257
|
-
return { showId: reqStr(obj, 'showId'), episodeNumber: reqEpNum(obj, 'episodeNumber') };
|
|
258
|
-
});
|
|
1
|
+
import{TRPCError as S}from"@trpc/server";function i(t){return{"~standard":{version:1,vendor:"dango",types:void 0,validate(e){try{return{value:t(e)}}catch(n){return{issues:[{message:n instanceof Error?n.message:"Invalid input"}]}}}}}}function v(t){if(typeof t!="object"||t===null)throw new Error("Expected an object");const e=t.key;if(typeof e!="string"||e.length===0)throw new Error("key must be a string");return e}function o(t,e){const n=t[e];if(typeof n!="string"||n.length===0)throw new Error(`${e} must be a string`);return n}function s(t,e){const n=t[e];if(n!=null)return String(n)}function a(t,e){const n=t[e];if(n==null||n==="")return;const r=Number(n);return Number.isFinite(r)?r:void 0}function k(t,e){const n=t[e];if(typeof n=="boolean")return n}function Q(t){return new S({code:"BAD_REQUEST",message:t})}function T(t){return new S({code:"INTERNAL_SERVER_ERROR",message:t})}function P(t,e){const n=t[e];if(typeof n!="string"&&typeof n!="number"||String(n).length===0)throw new Error(`${e} must be a string`);return String(n)}function j(t,e,n){const r=t[e];if(r==null||r==="")return n;const d=Math.floor(Number(r));return Number.isFinite(d)?d:n}function q(t,e){const n=t[e];return typeof n=="string"?n:null}function M(t){const e={},n=a(t,"page");n!==void 0&&(e.page=n);const r=a(t,"limit");return r!==void 0&&(e.limit=r),e}function f(t,e){const n=t[e];if(typeof n!="string"&&typeof n!="number"||String(n).length===0)throw new Error(`${e} must be a string`);return String(n)}function l(t,e,n){const r=t[e];if(!Array.isArray(r)||n&&r.length===0)throw new Error(`${e} must be ${n?"a non-empty ":"an "}array`);return r.map(d=>String(d))}function u(t){if(typeof t!="object"||t===null)throw new Error("Expected an object");return t}const O=()=>i(t=>({key:v(t)})),B=()=>i(t=>({id:o(u(t),"id")})),W=()=>i(t=>({showId:o(u(t),"showId")})),C=()=>i(t=>{const e=u(t);return{showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}}),U=()=>i(t=>({ids:[...new Set(l(u(t),"ids",!0))]})),D=()=>i(t=>{const e=u(t),n={id:o(e,"id"),name:o(e,"name")},r=s(e,"status");r!==void 0&&(n.status=r);const d=s(e,"thumbnail");d!==void 0&&(n.thumbnail=d);const p=s(e,"nativeName");p!==void 0&&(n.nativeName=p);const g=s(e,"englishName");g!==void 0&&(n.englishName=g);const c=s(e,"type");return c!==void 0&&(n.type=c),typeof e.isAdult=="boolean"&&(n.isAdult=e.isAdult),n}),$=()=>i(t=>{const e=u(t);return{id:o(e,"id"),status:o(e,"status")}}),_=()=>i(t=>{const e=u(t);return{ids:[...new Set(l(e,"ids",!0))],status:o(e,"status")}});function A(t){return{showName:s(t,"showName"),showThumbnail:s(t,"showThumbnail"),nativeName:s(t,"nativeName"),englishName:s(t,"englishName"),type:s(t,"type")}}const F=()=>i(t=>{const e=u(t);return{...A(e),showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}}),L=()=>i(t=>{const e=u(t);return{...A(e),showId:o(e,"showId"),episodeNumbers:l(e,"episodeNumbers",!0)}}),K=()=>i(t=>{const e=u(t);return{showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}}),V=()=>i(t=>{const e=u(t),n={showId:o(e,"showId")};return e.episodeNumbers!==void 0&&(n.episodeNumbers=l(e,"episodeNumbers",!1)),n}),z=()=>i(t=>{if(!Array.isArray(t))throw new Error("items must be an array");return t.map(e=>{const n=u(e),r={};return typeof n.id=="number"&&(r.id=n.id),n.showId!==void 0&&(r.showId=String(n.showId)),n.episodeNumber!==void 0&&(r.episodeNumber=String(n.episodeNumber)),r})}),G=()=>i(t=>{if(typeof t!="object"||t===null)throw new Error("Expected an object");return{key:v(t),value:t.value}}),H=()=>i(t=>{const e=u(t),n={showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")},r=a(e,"currentTime");r!==void 0&&(n.currentTime=r);const d=a(e,"duration");d!==void 0&&(n.duration=d);const p=s(e,"showName");p!==void 0&&(n.showName=p);const g=s(e,"showThumbnail");g!==void 0&&(n.showThumbnail=g);const c=s(e,"nativeName");c!==void 0&&(n.nativeName=c);const h=s(e,"englishName");h!==void 0&&(n.englishName=h);const m=e.genres;(typeof m=="string"||Array.isArray(m))&&(n.genres=(Array.isArray(m)?m:[m]).map(String));const I=a(e,"popularityScore");I!==void 0&&(n.popularityScore=I);const w=s(e,"type");w!==void 0&&(n.type=w);const y=s(e,"status");y!==void 0&&(n.status=y);const b=a(e,"episodeCount");b!==void 0&&(n.episodeCount=b);const N=k(e,"isPlaying");N!==void 0&&(n.isPlaying=N);const x=s(e,"sessionId");x!==void 0&&(n.sessionId=x);const R=k(e,"isAdult");return R!==void 0&&(n.isAdult=R),n}),J=()=>i(t=>{const e=u(t);return{showId:o(e,"showId"),episodeNumber:f(e,"episodeNumber")}});export{Q as badRequest,i as defineSchema,C as episodeProgressInput,T as failed,B as idInput,U as idsInput,O as keyInput,J as notificationDismissInput,k as optBool,a as optNum,s as optStr,H as progressUpdateInput,F as queueAddInput,L as queueBatchInput,K as queueRemoveInput,V as queueRemoveManyInput,z as queueReorderInput,j as readCount,M as readPageLimit,P as reqId,u as reqObj,o as reqStr,l as reqStrArray,G as setSettingInput,W as showIdInput,q as strOrNull,D as watchlistAddInput,_ as watchlistBatchStatusInput,$ as watchlistStatusInput};
|
|
@@ -1,25 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export class AppCache {
|
|
3
|
-
cache;
|
|
4
|
-
constructor(options = {}) {
|
|
5
|
-
this.cache = new LRUCache({
|
|
6
|
-
max: options.maxKeys ?? 5000,
|
|
7
|
-
ttl: (options.ttlSeconds ?? 3600) * 1000,
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
get(key) {
|
|
11
|
-
return this.cache.get(key);
|
|
12
|
-
}
|
|
13
|
-
set(key, value, ttlSeconds) {
|
|
14
|
-
const entry = value;
|
|
15
|
-
if (ttlSeconds === undefined) {
|
|
16
|
-
this.cache.set(key, entry);
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
this.cache.set(key, entry, { ttl: ttlSeconds * 1000 });
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
delete(key) {
|
|
23
|
-
this.cache.delete(key);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
1
|
+
import{LRUCache as s}from"lru-cache";class r{cache;constructor(e={}){this.cache=new s({max:e.maxKeys??5e3,ttl:(e.ttlSeconds??3600)*1e3})}get(e){return this.cache.get(e)}set(e,n,t){const c=n;t===void 0?this.cache.set(e,c):this.cache.set(e,c,{ttl:t*1e3})}delete(e){this.cache.delete(e)}}export{r as AppCache};
|
|
@@ -1,13 +1 @@
|
|
|
1
|
-
|
|
2
|
-
if (!raw)
|
|
3
|
-
return '';
|
|
4
|
-
let s = String(raw).trim();
|
|
5
|
-
s = s.replace(/^cf_clearance/i, '');
|
|
6
|
-
s = s.replace(/^[:=]\s*/, '');
|
|
7
|
-
s = s.replace(/["']/g, '').trim();
|
|
8
|
-
return s;
|
|
9
|
-
}
|
|
10
|
-
export function buildCfClearanceCookie(raw) {
|
|
11
|
-
const c = sanitizeCfClearance(raw);
|
|
12
|
-
return c ? `cf_clearance=${c}` : '';
|
|
13
|
-
}
|
|
1
|
+
function r(n){if(!n)return"";let e=String(n).trim();return e=e.replace(/^cf_clearance/i,""),e=e.replace(/^[:=]\s*/,""),e=e.replace(/["']/g,"").trim(),e}function i(n){const e=r(n);return e?`cf_clearance=${e}`:""}export{i as buildCfClearanceCookie,r as sanitizeCfClearance};
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
7
|
-
export const dbRun = (db, sql, params = []) => {
|
|
8
|
-
db.run(sql, params);
|
|
9
|
-
};
|
|
10
|
-
export async function adultNonListIds(db, progressTable, libraryTable, keyCol, adultPred) {
|
|
11
|
-
const rows = await dbAll(db, `SELECT DISTINCT p."${keyCol}" as id
|
|
12
|
-
FROM "${progressTable}" p
|
|
13
|
-
LEFT JOIN "${libraryTable}" l ON l.id = p."${keyCol}"
|
|
14
|
-
WHERE ${adultPred}
|
|
15
|
-
AND l.id IS NULL`);
|
|
16
|
-
return rows.map((r) => r.id);
|
|
17
|
-
}
|
|
1
|
+
const e=(r,n,a=[])=>r.all(n,a),u=(r,n,a=[])=>r.get(n,a),b=(r,n,a=[])=>{r.run(n,a)};async function d(r,n,a,t,s){return(await e(r,`SELECT DISTINCT p."${t}" as id
|
|
2
|
+
FROM "${n}" p
|
|
3
|
+
LEFT JOIN "${a}" l ON l.id = p."${t}"
|
|
4
|
+
WHERE ${s}
|
|
5
|
+
AND l.id IS NULL`)).map(i=>i.id)}export{d as adultNonListIds,e as dbAll,u as dbGet,b as dbRun};
|
|
@@ -1,77 +1,5 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let attempts = 0;
|
|
7
|
-
while (!handle) {
|
|
8
|
-
try {
|
|
9
|
-
handle = await fs.promises.open(lockPath, 'wx');
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
if (++attempts >= 100) {
|
|
13
|
-
fs.promises.unlink(lockPath).catch(() => { });
|
|
14
|
-
throw new Error('Failed to acquire lock on .env file - another process may be holding it');
|
|
15
|
-
}
|
|
16
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return handle;
|
|
20
|
-
}
|
|
21
|
-
function releaseLock(handle) {
|
|
22
|
-
handle.close().catch(() => { });
|
|
23
|
-
fs.promises.unlink(lockPath).catch(() => { });
|
|
24
|
-
}
|
|
25
|
-
export async function updateEnvFile(updates) {
|
|
26
|
-
const lockHandle = await acquireLock();
|
|
27
|
-
try {
|
|
28
|
-
const envPath = CONFIG.ENV_PATH;
|
|
29
|
-
let envContent = '';
|
|
30
|
-
if (fs.existsSync(envPath)) {
|
|
31
|
-
envContent = fs.readFileSync(envPath, 'utf8');
|
|
32
|
-
}
|
|
33
|
-
const lines = envContent.split('\n');
|
|
34
|
-
const newLines = [...lines];
|
|
35
|
-
Object.entries(updates).forEach(([rawKey, rawValue]) => {
|
|
36
|
-
const key = rawKey.trim();
|
|
37
|
-
if (!/^[A-Z0-9_]+$/i.test(key)) {
|
|
38
|
-
throw new Error(`Invalid environment variable key: ${key}`);
|
|
39
|
-
}
|
|
40
|
-
const value = String(rawValue ?? '').replace(/[\r\n]/g, '');
|
|
41
|
-
let found = false;
|
|
42
|
-
for (let i = 0; i < newLines.length; i++) {
|
|
43
|
-
const line = newLines[i];
|
|
44
|
-
if (line && line.startsWith(`${key}=`)) {
|
|
45
|
-
if (value === '') {
|
|
46
|
-
newLines.splice(i, 1);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
newLines[i] = `${key}=${value}`;
|
|
51
|
-
}
|
|
52
|
-
found = true;
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (!found && value !== '') {
|
|
57
|
-
newLines.push(`${key}=${value}`);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
const finalContent = newLines
|
|
61
|
-
.join('\n')
|
|
62
|
-
.replace(/\n{2,}/g, '\n')
|
|
63
|
-
.trim() + '\n';
|
|
64
|
-
fs.writeFileSync(envPath, finalContent);
|
|
65
|
-
Object.entries(updates).forEach(([key, value]) => {
|
|
66
|
-
if (value === '') {
|
|
67
|
-
delete process.env[key];
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
process.env[key] = value;
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
releaseLock(lockHandle);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
1
|
+
import t from"fs";import{CONFIG as u}from"../config.js";const f=`${u.ENV_PATH}.lock`;async function w(){let e,a=0;for(;!e;)try{e=await t.promises.open(f,"wx")}catch{if(++a>=100)throw t.promises.unlink(f).catch(()=>{}),new Error("Failed to acquire lock on .env file - another process may be holding it");await new Promise(i=>setTimeout(i,100))}return e}function k(e){e.close().catch(()=>{}),t.promises.unlink(f).catch(()=>{})}async function $(e){const a=await w();try{const i=u.ENV_PATH;let p="";t.existsSync(i)&&(p=t.readFileSync(i,"utf8"));const n=[...p.split(`
|
|
2
|
+
`)];Object.entries(e).forEach(([r,c])=>{const s=r.trim();if(!/^[A-Z0-9_]+$/i.test(s))throw new Error(`Invalid environment variable key: ${s}`);const l=String(c??"").replace(/[\r\n]/g,"");let m=!1;for(let o=0;o<n.length;o++){const h=n[o];if(h&&h.startsWith(`${s}=`)){if(l===""){n.splice(o,1);continue}else n[o]=`${s}=${l}`;m=!0;break}}!m&&l!==""&&n.push(`${s}=${l}`)});const d=n.join(`
|
|
3
|
+
`).replace(/\n{2,}/g,`
|
|
4
|
+
`).trim()+`
|
|
5
|
+
`;t.writeFileSync(i,d),Object.entries(e).forEach(([r,c])=>{c===""?delete process.env[r]:process.env[r]=c})}finally{k(a)}}export{$ as updateEnvFile};
|
|
@@ -1,82 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export class HttpError extends Error {
|
|
3
|
-
status;
|
|
4
|
-
constructor(status, message) {
|
|
5
|
-
super(message || `Upstream request failed with status ${status}`);
|
|
6
|
-
this.name = 'HttpError';
|
|
7
|
-
this.status = status;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export function decodeMaybeGzip(bytes) {
|
|
11
|
-
if (bytes.length > 2 && bytes[0] === 0x1f && bytes[1] === 0x8b)
|
|
12
|
-
return gunzipSync(bytes);
|
|
13
|
-
return bytes;
|
|
14
|
-
}
|
|
15
|
-
export async function parseJsonBody(res) {
|
|
16
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
17
|
-
const text = decodeMaybeGzip(buf).toString('utf8');
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(text);
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
const attempts = [() => brotliDecompressSync(buf), () => inflateSync(buf)];
|
|
23
|
-
for (const attempt of attempts) {
|
|
24
|
-
try {
|
|
25
|
-
return JSON.parse(attempt().toString('utf8'));
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
// ignore
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
throw err;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export function linkAbort(signal, abort) {
|
|
35
|
-
signal.addEventListener('abort', () => abort.abort(), { once: true });
|
|
36
|
-
}
|
|
37
|
-
export function setProxyHeaders(out, get) {
|
|
38
|
-
for (const name of ['content-length', 'content-range', 'accept-ranges']) {
|
|
39
|
-
const raw = get(name);
|
|
40
|
-
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
41
|
-
if (value)
|
|
42
|
-
out.set(name, value);
|
|
43
|
-
}
|
|
44
|
-
out.set('Access-Control-Allow-Origin', '*');
|
|
45
|
-
}
|
|
46
|
-
export function isAbortError(err) {
|
|
47
|
-
if (!err || typeof err !== 'object')
|
|
48
|
-
return false;
|
|
49
|
-
const name = err.name;
|
|
50
|
-
return name === 'AbortError' || name === 'TimeoutError';
|
|
51
|
-
}
|
|
52
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
53
|
-
function isRetryableStatus(status) {
|
|
54
|
-
return status === 408 || status === 429 || status >= 500;
|
|
55
|
-
}
|
|
56
|
-
export async function fetchWithRetry(input, init = {}, options = {}) {
|
|
57
|
-
const { retries = 0, baseDelayMs = 100, timeoutMs = 30000, signal } = options;
|
|
58
|
-
let attempt = 0;
|
|
59
|
-
for (;;) {
|
|
60
|
-
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
61
|
-
const combined = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
62
|
-
try {
|
|
63
|
-
const response = await fetch(input, { ...init, signal: combined });
|
|
64
|
-
if (!isRetryableStatus(response.status) || attempt >= retries)
|
|
65
|
-
return response;
|
|
66
|
-
try {
|
|
67
|
-
await response.body?.cancel();
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
// ignore
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
if (signal?.aborted || attempt >= retries)
|
|
75
|
-
throw err;
|
|
76
|
-
if (!isAbortError(err) && !(err instanceof TypeError))
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
attempt += 1;
|
|
80
|
-
await sleep(Math.min(baseDelayMs * 2 ** (attempt - 1), 3000));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
1
|
+
import{brotliDecompressSync as l,gunzipSync as m,inflateSync as p}from"node:zlib";class w extends Error{status;constructor(r,e){super(e||`Upstream request failed with status ${r}`),this.name="HttpError",this.status=r}}function b(t){return t.length>2&&t[0]===31&&t[1]===139?m(t):t}async function x(t){const r=Buffer.from(await t.arrayBuffer()),e=b(r).toString("utf8");try{return JSON.parse(e)}catch(n){const s=[()=>l(r),()=>p(r)];for(const i of s)try{return JSON.parse(i().toString("utf8"))}catch{}throw n}}function A(t,r){t.addEventListener("abort",()=>r.abort(),{once:!0})}function S(t,r){for(const e of["content-length","content-range","accept-ranges"]){const n=r(e),s=Array.isArray(n)?n[0]:n;s&&t.set(e,s)}t.set("Access-Control-Allow-Origin","*")}function g(t){if(!t||typeof t!="object")return!1;const r=t.name;return r==="AbortError"||r==="TimeoutError"}const y=t=>new Promise(r=>setTimeout(r,t));function h(t){return t===408||t===429||t>=500}async function E(t,r={},e={}){const{retries:n=0,baseDelayMs:s=100,timeoutMs:i=3e4,signal:c}=e;let a=0;for(;;){const u=AbortSignal.timeout(i),f=c?AbortSignal.any([c,u]):u;try{const o=await fetch(t,{...r,signal:f});if(!h(o.status)||a>=n)return o;try{await o.body?.cancel()}catch{}}catch(o){if(c?.aborted||a>=n||!g(o)&&!(o instanceof TypeError))throw o}a+=1,await y(Math.min(s*2**(a-1),3e3))}}export{w as HttpError,b as decodeMaybeGzip,E as fetchWithRetry,g as isAbortError,A as linkAbort,x as parseJsonBody,S as setProxyHeaders};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const requestContext = new AsyncLocalStorage();
|
|
1
|
+
import{AsyncLocalStorage as t}from"node:async_hooks";const r=new t;export{r as requestContext};
|
|
@@ -1,76 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function isSafeExternalUrl(rawUrl) {
|
|
3
|
-
if (typeof rawUrl !== 'string' || !rawUrl.trim()) {
|
|
4
|
-
return { safe: false, error: 'URL required' };
|
|
5
|
-
}
|
|
6
|
-
let parsed;
|
|
7
|
-
try {
|
|
8
|
-
parsed = new URL(rawUrl.trim());
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return { safe: false, error: 'Invalid URL format' };
|
|
12
|
-
}
|
|
13
|
-
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
14
|
-
return { safe: false, error: 'Only http and https protocols are supported' };
|
|
15
|
-
}
|
|
16
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
17
|
-
if (hostname === 'localhost' ||
|
|
18
|
-
hostname === '127.0.0.1' ||
|
|
19
|
-
hostname === '::1' ||
|
|
20
|
-
hostname === '0.0.0.0' ||
|
|
21
|
-
hostname.endsWith('.localhost') ||
|
|
22
|
-
hostname.endsWith('.local') ||
|
|
23
|
-
hostname.endsWith('.internal')) {
|
|
24
|
-
return { safe: false, error: 'Access to loopback/local addresses is forbidden' };
|
|
25
|
-
}
|
|
26
|
-
const ipv4Match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
27
|
-
if (ipv4Match) {
|
|
28
|
-
const o1 = Number(ipv4Match[1]);
|
|
29
|
-
const o2 = Number(ipv4Match[2]);
|
|
30
|
-
const o3 = Number(ipv4Match[3]);
|
|
31
|
-
const o4 = Number(ipv4Match[4]);
|
|
32
|
-
if ([o1, o2, o3, o4].some((o) => o < 0 || o > 255)) {
|
|
33
|
-
return { safe: false, error: 'Invalid IP address format' };
|
|
34
|
-
}
|
|
35
|
-
if (o1 === 0 ||
|
|
36
|
-
o1 === 10 ||
|
|
37
|
-
o1 === 127 ||
|
|
38
|
-
(o1 === 172 && o2 >= 16 && o2 <= 31) ||
|
|
39
|
-
(o1 === 192 && o2 === 168) ||
|
|
40
|
-
(o1 === 169 && o2 === 254)) {
|
|
41
|
-
return { safe: false, error: 'Access to private or link-local IP addresses is forbidden' };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (hostname.startsWith('[') || hostname.includes(':')) {
|
|
45
|
-
const cleanIpv6 = hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
46
|
-
if (cleanIpv6 === '::1' ||
|
|
47
|
-
cleanIpv6 === '::' ||
|
|
48
|
-
cleanIpv6.startsWith('fe80:') ||
|
|
49
|
-
cleanIpv6.startsWith('fc00:') ||
|
|
50
|
-
cleanIpv6.startsWith('fd00:') ||
|
|
51
|
-
cleanIpv6.startsWith('::ffff:127.') ||
|
|
52
|
-
cleanIpv6.startsWith('::ffff:192.168.') ||
|
|
53
|
-
cleanIpv6.startsWith('::ffff:10.') ||
|
|
54
|
-
cleanIpv6.startsWith('::ffff:172.') ||
|
|
55
|
-
cleanIpv6.startsWith('::ffff:169.254.')) {
|
|
56
|
-
return { safe: false, error: 'Access to private or link-local IPv6 addresses is forbidden' };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return { safe: true, url: parsed };
|
|
60
|
-
}
|
|
61
|
-
export function isAllowedOrigin(origin) {
|
|
62
|
-
if (!origin)
|
|
63
|
-
return true; // Non-browser clients, same-origin, or mobile webview
|
|
64
|
-
try {
|
|
65
|
-
const parsed = new URL(origin);
|
|
66
|
-
const host = parsed.hostname.toLowerCase();
|
|
67
|
-
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
68
|
-
if (CONFIG.HOST === '0.0.0.0') {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
return isLocal;
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
import{CONFIG as l}from"../config.js";function d(s){if(typeof s!="string"||!s.trim())return{safe:!1,error:"URL required"};let o;try{o=new URL(s.trim())}catch{return{safe:!1,error:"Invalid URL format"}}if(o.protocol!=="http:"&&o.protocol!=="https:")return{safe:!1,error:"Only http and https protocols are supported"};const r=o.hostname.toLowerCase();if(r==="localhost"||r==="127.0.0.1"||r==="::1"||r==="0.0.0.0"||r.endsWith(".localhost")||r.endsWith(".local")||r.endsWith(".internal"))return{safe:!1,error:"Access to loopback/local addresses is forbidden"};const e=r.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);if(e){const t=Number(e[1]),a=Number(e[2]),n=Number(e[3]),i=Number(e[4]);if([t,a,n,i].some(f=>f<0||f>255))return{safe:!1,error:"Invalid IP address format"};if(t===0||t===10||t===127||t===172&&a>=16&&a<=31||t===192&&a===168||t===169&&a===254)return{safe:!1,error:"Access to private or link-local IP addresses is forbidden"}}if(r.startsWith("[")||r.includes(":")){const t=r.replace(/^\[|\]$/g,"").toLowerCase();if(t==="::1"||t==="::"||t.startsWith("fe80:")||t.startsWith("fc00:")||t.startsWith("fd00:")||t.startsWith("::ffff:127.")||t.startsWith("::ffff:192.168.")||t.startsWith("::ffff:10.")||t.startsWith("::ffff:172.")||t.startsWith("::ffff:169.254."))return{safe:!1,error:"Access to private or link-local IPv6 addresses is forbidden"}}return{safe:!0,url:o}}function u(s){if(!s)return!0;try{const r=new URL(s).hostname.toLowerCase(),e=r==="localhost"||r==="127.0.0.1"||r==="::1";return l.HOST==="0.0.0.0"?!0:e}catch{return!1}}export{u as isAllowedOrigin,d as isSafeExternalUrl};
|
package/server/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dango-server",
|
|
3
3
|
"private": true,
|
|
4
|
-
"version": "3.6.
|
|
4
|
+
"version": "3.6.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.12.0"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node --max-old-space-size=256 dist/server.js",
|
|
11
11
|
"dev": "tsx watch src/server.ts --dev",
|
|
12
|
-
"build": "tsc",
|
|
12
|
+
"build": "tsc --noEmit && node build.mjs",
|
|
13
13
|
"format": "prettier --write .",
|
|
14
14
|
"lint": "eslint ."
|
|
15
15
|
},
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@types/jsdom": "^30.0.0",
|
|
37
37
|
"@types/node": "^22.5.0",
|
|
38
38
|
"@types/ws": "^8.5.10",
|
|
39
|
+
"esbuild": "0.28.2",
|
|
39
40
|
"pino-pretty": "^13.1.1",
|
|
40
41
|
"tsx": "^4.23.13"
|
|
41
42
|
}
|