@serifpersia/dango 3.0.3
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/LICENSE +21 -0
- package/README.md +247 -0
- package/client/dist/assets/AnimeInfo-CMxs0nRH.css +1 -0
- package/client/dist/assets/AnimeInfo-DVdaocCl.js +1 -0
- package/client/dist/assets/AnimeInfoPage-Bx5EvGt-.js +2 -0
- package/client/dist/assets/Asmr-C2i9GKD4.css +1 -0
- package/client/dist/assets/Asmr-CIjmFtRH.js +2 -0
- package/client/dist/assets/Asmr.module-DPG2JhDJ.js +1 -0
- package/client/dist/assets/AvSyncCalibrator-CIYcyon5.css +1 -0
- package/client/dist/assets/AvSyncCalibrator-vqErH7Cm.js +10 -0
- package/client/dist/assets/ErrorMessage-BwSjzblw.js +1 -0
- package/client/dist/assets/ErrorMessage-CXoRbrLN.css +1 -0
- package/client/dist/assets/Home-D7YfGBUk.js +1 -0
- package/client/dist/assets/Home-DKCfEiwl.css +1 -0
- package/client/dist/assets/Insights-BljI64Ua.css +1 -0
- package/client/dist/assets/Insights-CLQGrlpX.js +1 -0
- package/client/dist/assets/Manga-DQ9HYnpk.js +1 -0
- package/client/dist/assets/Manga-H8Wb04w3.css +1 -0
- package/client/dist/assets/Map-CXFqXmsZ.css +1 -0
- package/client/dist/assets/Map-L_ryVBTq.js +1 -0
- package/client/dist/assets/Mature-BeWzAJ7B.js +1 -0
- package/client/dist/assets/Mature-DJmUUtlF.css +1 -0
- package/client/dist/assets/Player-CYpd__FP.js +30 -0
- package/client/dist/assets/Player-DreAhybc.css +1 -0
- package/client/dist/assets/PlayerRedirect-fxUAp9JV.js +1 -0
- package/client/dist/assets/PlayerSettings-BcsotxP9.js +1 -0
- package/client/dist/assets/PlayerSettings-Cqua1CDD.css +1 -0
- package/client/dist/assets/QueueRail-B9Pgbqc3.js +1 -0
- package/client/dist/assets/QueueRail-BnaltOP8.css +1 -0
- package/client/dist/assets/Radio-BvdY5twD.js +1 -0
- package/client/dist/assets/Radio-CUuYy3DY.css +1 -0
- package/client/dist/assets/Search-BzO-aRP7.css +1 -0
- package/client/dist/assets/Search-R9YLAXS1.js +1 -0
- package/client/dist/assets/Search.module-D8ZbQAiK.js +1 -0
- package/client/dist/assets/Settings-9kYcXdTR.js +21 -0
- package/client/dist/assets/Settings-DF8JThUL.css +1 -0
- package/client/dist/assets/SkeletonGrid-D5vRvX5q.js +1 -0
- package/client/dist/assets/SkeletonGrid-DEbYtO4H.css +1 -0
- package/client/dist/assets/SynopsisText-BlFw0R-0.js +1 -0
- package/client/dist/assets/SynopsisText-CUgOjOxt.css +1 -0
- package/client/dist/assets/Trackers-DfZlnnVq.js +2 -0
- package/client/dist/assets/Trackers-Dt9lR_uP.css +1 -0
- package/client/dist/assets/Tv-CVKC2YBs.css +1 -0
- package/client/dist/assets/Tv-Db1Wx_b7.js +15 -0
- package/client/dist/assets/Watchlist-B2nkNZqM.css +1 -0
- package/client/dist/assets/Watchlist-a77CYtzu.js +1 -0
- package/client/dist/assets/hls-6Owgw2Aj.js +1 -0
- package/client/dist/assets/hls-BOLXTnjL.js +42 -0
- package/client/dist/assets/index-B6qdXFtl.css +1 -0
- package/client/dist/assets/index-B8TPVaIS.js +30354 -0
- package/client/dist/assets/index-CpjgU8ou.js +178 -0
- package/client/dist/assets/useAnimeInfoData-BOfyi9UJ.js +1 -0
- package/client/dist/assets/useIsMobile-CM15uQn8.css +1 -0
- package/client/dist/assets/useIsMobile-Cb8HcKRj.js +1 -0
- package/client/dist/assets/useMatureConsent-CZMYBVXd.js +1 -0
- package/client/dist/assets/vendor-rLNfZm7Y.js +1 -0
- package/client/dist/av-sync-test.mp4 +0 -0
- package/client/dist/favicon.ico +0 -0
- package/client/dist/index.html +31 -0
- package/client/dist/logo-mochi.png +0 -0
- package/client/dist/logo-sakura.png +0 -0
- package/client/dist/logo.png +0 -0
- package/client/dist/placeholder.svg +4 -0
- package/client/dist/robots.txt +3 -0
- package/orchestrator.js +416 -0
- package/package.json +89 -0
- package/server/dist/app-auth.js +231 -0
- package/server/dist/config.js +133 -0
- package/server/dist/controllers/auth.controller.js +247 -0
- package/server/dist/controllers/data.controller.js +785 -0
- package/server/dist/controllers/insights.controller.js +197 -0
- package/server/dist/controllers/proxy.controller.js +665 -0
- package/server/dist/controllers/settings.controller.js +267 -0
- package/server/dist/controllers/watchlist.controller.js +967 -0
- package/server/dist/db.js +152 -0
- package/server/dist/discord-gateway.js +381 -0
- package/server/dist/discord-rpc.js +424 -0
- package/server/dist/github-sync.js +403 -0
- package/server/dist/google.js +549 -0
- package/server/dist/lib/anilist.js +1519 -0
- package/server/dist/lib/anischedule.js +202 -0
- package/server/dist/lib/ipc.js +32 -0
- package/server/dist/lib/kitsu.js +552 -0
- package/server/dist/lib/mal.js +886 -0
- package/server/dist/lib/migration.js +335 -0
- package/server/dist/lib/temp-ids.js +11 -0
- package/server/dist/lib/tmdb.js +71 -0
- package/server/dist/lib/tracker/anilist-tracker.js +250 -0
- package/server/dist/lib/tracker/sync.service.js +312 -0
- package/server/dist/logger.js +19 -0
- package/server/dist/providers/123anime.provider.js +240 -0
- package/server/dist/providers/anilight.provider.js +366 -0
- package/server/dist/providers/animepahe.provider.js +531 -0
- package/server/dist/providers/animeya.provider.js +746 -0
- package/server/dist/providers/base-provider.js +45 -0
- package/server/dist/providers/hn.provider.js +473 -0
- package/server/dist/providers/ht.provider.js +439 -0
- package/server/dist/providers/jasmr.provider.js +504 -0
- package/server/dist/providers/kaa.provider.js +388 -0
- package/server/dist/providers/manga/manga.types.js +10 -0
- package/server/dist/providers/manga/mangadex.provider.js +227 -0
- package/server/dist/providers/manga/mangapill.provider.js +232 -0
- package/server/dist/providers/megaplay.provider.js +583 -0
- package/server/dist/providers/op.provider.js +440 -0
- package/server/dist/providers/provider.interface.js +2 -0
- package/server/dist/providers/title-matching.js +234 -0
- package/server/dist/providers/wh.provider.js +600 -0
- package/server/dist/rclone.js +126 -0
- package/server/dist/repositories/insights.repository.js +43 -0
- package/server/dist/repositories/mal-cache.repository.js +66 -0
- package/server/dist/repositories/notifications.repository.js +40 -0
- package/server/dist/repositories/queue.repository.js +64 -0
- package/server/dist/repositories/settings.repository.js +15 -0
- package/server/dist/repositories/shows-meta.repository.js +43 -0
- package/server/dist/repositories/temp-show-ids.repository.js +54 -0
- package/server/dist/repositories/watched-episodes.repository.js +54 -0
- package/server/dist/repositories/watchlist.repository.js +74 -0
- package/server/dist/routes/asmr.routes.js +72 -0
- package/server/dist/routes/auth.routes.js +28 -0
- package/server/dist/routes/data.routes.js +50 -0
- package/server/dist/routes/discord-gateway.routes.js +57 -0
- package/server/dist/routes/insights.routes.js +12 -0
- package/server/dist/routes/lan-auth.routes.js +49 -0
- package/server/dist/routes/manga.routes.js +115 -0
- package/server/dist/routes/proxy.routes.js +15 -0
- package/server/dist/routes/radio.routes.js +143 -0
- package/server/dist/routes/settings.routes.js +27 -0
- package/server/dist/routes/tracker.routes.js +143 -0
- package/server/dist/routes/translate.routes.js +90 -0
- package/server/dist/routes/tv.routes.js +843 -0
- package/server/dist/routes/watchlist.routes.js +312 -0
- package/server/dist/server.js +310 -0
- package/server/dist/shipped-defaults.js +8 -0
- package/server/dist/sync-config.js +32 -0
- package/server/dist/sync.js +506 -0
- package/server/dist/utils/cookie.utils.js +17 -0
- package/server/dist/utils/db-utils.js +15 -0
- package/server/dist/utils/env.utils.js +83 -0
- package/server/dist/utils/machine-id.js +46 -0
- package/server/dist/utils/request-context.js +5 -0
- package/server/dist/utils/security.utils.js +99 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWatchlistRouter = createWatchlistRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const watchlist_controller_1 = require("../controllers/watchlist.controller");
|
|
6
|
+
const discord_rpc_1 = require("../discord-rpc");
|
|
7
|
+
const title_matching_1 = require("../providers/title-matching");
|
|
8
|
+
const dlsitePosterCache = new Map();
|
|
9
|
+
const mangadexCoverCache = new Map();
|
|
10
|
+
async function getMangadexCover(title) {
|
|
11
|
+
const key = title.trim().toLowerCase();
|
|
12
|
+
if (!key)
|
|
13
|
+
return null;
|
|
14
|
+
const cached = mangadexCoverCache.get(key);
|
|
15
|
+
if (cached && Date.now() - cached.ts < 3600_000)
|
|
16
|
+
return cached.url;
|
|
17
|
+
try {
|
|
18
|
+
const params = new URLSearchParams({
|
|
19
|
+
title: title.trim(),
|
|
20
|
+
limit: '5',
|
|
21
|
+
'includes[]': 'cover_art',
|
|
22
|
+
'order[relevance]': 'desc',
|
|
23
|
+
});
|
|
24
|
+
for (const r of ['safe', 'suggestive', 'erotica', 'pornographic']) {
|
|
25
|
+
params.append('contentRating[]', r);
|
|
26
|
+
}
|
|
27
|
+
const res = await fetch(`https://api.mangadex.org/manga?${params.toString()}`, {
|
|
28
|
+
headers: { 'User-Agent': 'Dango/3.0', Accept: 'application/json' },
|
|
29
|
+
signal: AbortSignal.timeout(10000),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok)
|
|
32
|
+
return null;
|
|
33
|
+
const data = (await res.json());
|
|
34
|
+
const candidates = [];
|
|
35
|
+
for (const m of data.data || []) {
|
|
36
|
+
const fileName = m.relationships?.find((r) => r.type === 'cover_art')?.attributes?.fileName;
|
|
37
|
+
if (!fileName)
|
|
38
|
+
continue;
|
|
39
|
+
const cover = `https://uploads.mangadex.org/covers/${m.id}/${fileName}.256.jpg`;
|
|
40
|
+
const main = m.attributes?.title;
|
|
41
|
+
const mainTitle = main?.en || main?.['ja-ro'] || Object.values(main || {})[0];
|
|
42
|
+
if (mainTitle)
|
|
43
|
+
candidates.push({ title: mainTitle, cover });
|
|
44
|
+
for (const alt of m.attributes?.altTitles || []) {
|
|
45
|
+
const t = Object.values(alt)[0];
|
|
46
|
+
if (t)
|
|
47
|
+
candidates.push({ title: t, cover });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const match = (0, title_matching_1.pickBestMatch)(candidates, [title]);
|
|
51
|
+
const url = match ? match.item.cover : null;
|
|
52
|
+
mangadexCoverCache.set(key, { url, ts: Date.now() });
|
|
53
|
+
return url;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function getDlsitePoster(rjCode) {
|
|
60
|
+
const key = String(rjCode).trim().toUpperCase();
|
|
61
|
+
if (!/^RJ\d{5,}$/.test(key))
|
|
62
|
+
return null;
|
|
63
|
+
const cached = dlsitePosterCache.get(key);
|
|
64
|
+
if (cached && Date.now() - cached.ts < 3600_000)
|
|
65
|
+
return cached.url;
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`https://www.dlsite.com/maniax/product/info/ajax?product_id=${key}`, {
|
|
68
|
+
headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' },
|
|
69
|
+
signal: AbortSignal.timeout(5000),
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok)
|
|
72
|
+
return null;
|
|
73
|
+
const raw = (await res.json());
|
|
74
|
+
const data = raw;
|
|
75
|
+
const entryRaw = (data[key] ?? data);
|
|
76
|
+
const entry = entryRaw;
|
|
77
|
+
const img = typeof entry?.work_image === 'string' ? entry.work_image : undefined;
|
|
78
|
+
if (!img)
|
|
79
|
+
return null;
|
|
80
|
+
const url = img.startsWith('//') ? `https:${img}` : img;
|
|
81
|
+
dlsitePosterCache.set(key, { url, ts: Date.now() });
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function createWatchlistRouter(getDb) {
|
|
89
|
+
const router = (0, express_1.Router)();
|
|
90
|
+
const controller = new watchlist_controller_1.WatchlistController();
|
|
91
|
+
controller.startNotificationDiscovery(getDb);
|
|
92
|
+
router.get('/continue-watching/all', controller.getAllContinueWatching);
|
|
93
|
+
router.get('/continue-watching/this-week', controller.getThisWeekSchedule);
|
|
94
|
+
router.post('/continue-watching/remove', controller.removeContinueWatching);
|
|
95
|
+
router.post('/continue-watching/remove-many', controller.batchRemoveContinueWatching);
|
|
96
|
+
router.get('/continue-watching/adult-count', controller.getAdultContinueWatchingCount);
|
|
97
|
+
router.post('/continue-watching/purge-adult', controller.purgeAdultContinueWatching);
|
|
98
|
+
router.post('/update-progress', controller.updateProgress);
|
|
99
|
+
router.get('/watchlist', controller.getWatchlist);
|
|
100
|
+
router.get('/watchlist/check/:showId', controller.checkWatchlist);
|
|
101
|
+
router.post('/watchlist/add', controller.addToWatchlist);
|
|
102
|
+
router.post('/watchlist/remove', controller.removeFromWatchlist);
|
|
103
|
+
router.post('/watchlist/remove-many', controller.batchRemoveFromWatchlist);
|
|
104
|
+
router.post('/watchlist/status', controller.updateWatchlistStatus);
|
|
105
|
+
router.post('/watchlist/batch-status', controller.batchUpdateWatchlistStatus);
|
|
106
|
+
router.get('/queue', controller.getQueue);
|
|
107
|
+
router.get('/queue/suggested/:showId', controller.getSuggestedQueueEpisode);
|
|
108
|
+
router.get('/queue/remaining/:showId', controller.getQueueRemainingEpisodes);
|
|
109
|
+
router.post('/queue/add', controller.addToQueue);
|
|
110
|
+
router.post('/queue/batch', controller.addToQueueBatch);
|
|
111
|
+
router.post('/queue/remove', controller.removeFromQueue);
|
|
112
|
+
router.post('/queue/remove-many', controller.removeFromQueueBatch);
|
|
113
|
+
router.post('/queue/clear', controller.clearQueue);
|
|
114
|
+
router.post('/queue/reorder', controller.reorderQueue);
|
|
115
|
+
router.get('/episode-progress/:showId/:episodeNumber', controller.getEpisodeProgress);
|
|
116
|
+
router.get('/watched-episodes/:showId', controller.getWatchedEpisodes);
|
|
117
|
+
router.get('/notifications', controller.getNotifications);
|
|
118
|
+
router.post('/notifications/dismiss', controller.dismissNotification);
|
|
119
|
+
router.post('/notifications/clear-all', controller.clearAllNotifications);
|
|
120
|
+
router.get('/discovery/status', (req, res) => {
|
|
121
|
+
res.json(controller.getDiscoveryStatus());
|
|
122
|
+
});
|
|
123
|
+
router.post('/discovery/nudge', (req, res) => {
|
|
124
|
+
const started = controller.triggerDiscovery?.(false) ?? false;
|
|
125
|
+
res.json({ success: true, started, ...controller.getDiscoveryStatus() });
|
|
126
|
+
});
|
|
127
|
+
router.post('/discovery/refresh', (req, res) => {
|
|
128
|
+
const started = controller.triggerDiscovery?.(true) ?? false;
|
|
129
|
+
res.json({ success: true, started, ...controller.getDiscoveryStatus() });
|
|
130
|
+
});
|
|
131
|
+
router.post('/discord/clear', (req, res) => {
|
|
132
|
+
const { sessionId } = req.body;
|
|
133
|
+
if (!discord_rpc_1.discordRPCService.isServiceEnabled) {
|
|
134
|
+
return res.json({ success: true });
|
|
135
|
+
}
|
|
136
|
+
discord_rpc_1.discordRPCService.clearPresence(sessionId);
|
|
137
|
+
res.json({ success: true });
|
|
138
|
+
});
|
|
139
|
+
router.post('/discord/heartbeat', (req, res) => {
|
|
140
|
+
const { sessionId, bye } = req.body ?? {};
|
|
141
|
+
if (typeof sessionId !== 'string' || !discord_rpc_1.discordRPCService.isServiceEnabled) {
|
|
142
|
+
return res.json({ success: true });
|
|
143
|
+
}
|
|
144
|
+
if (bye) {
|
|
145
|
+
discord_rpc_1.discordRPCService.removeHeartbeat(sessionId);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
discord_rpc_1.discordRPCService.heartbeat(sessionId);
|
|
149
|
+
}
|
|
150
|
+
res.json({ success: true });
|
|
151
|
+
});
|
|
152
|
+
router.post('/discord/status', (req, res) => {
|
|
153
|
+
const { page } = req.body;
|
|
154
|
+
if (typeof page !== 'string' || !discord_rpc_1.discordRPCService.isServiceEnabled) {
|
|
155
|
+
return res.json({ success: true });
|
|
156
|
+
}
|
|
157
|
+
discord_rpc_1.discordRPCService.setIdleStatus(page);
|
|
158
|
+
res.json({ success: true });
|
|
159
|
+
});
|
|
160
|
+
router.post('/discord/asmr', async (req, res) => {
|
|
161
|
+
const { title, trackLabel, isPlaying, thumbnail, thumbnails, currentTime, duration, isAdult, sessionId, rjCode, } = req.body ?? {};
|
|
162
|
+
if (!discord_rpc_1.discordRPCService.isServiceEnabled)
|
|
163
|
+
return res.json({ success: true });
|
|
164
|
+
if (typeof sessionId === 'string')
|
|
165
|
+
discord_rpc_1.discordRPCService.heartbeat(sessionId);
|
|
166
|
+
let thumb = String(thumbnail || '');
|
|
167
|
+
const thumbs = Array.isArray(thumbnails) ? thumbnails.map(String).filter(Boolean) : undefined;
|
|
168
|
+
// ASMR thumbs from japaneseasmr/weeabo0 require Referer and will 403 on Discord's fetch.
|
|
169
|
+
// Try DLSite's direct img.dlsite.jp poster which is public (no Referer needed).
|
|
170
|
+
const needsDlsite = !thumb || thumb.includes('weeabo0.xyz') || thumb.includes('japaneseasmr.com');
|
|
171
|
+
if (needsDlsite && rjCode) {
|
|
172
|
+
const dlsiteThumb = await getDlsitePoster(String(rjCode));
|
|
173
|
+
if (dlsiteThumb)
|
|
174
|
+
thumb = dlsiteThumb;
|
|
175
|
+
}
|
|
176
|
+
// fallback to logo if still proxied/weeabo0 (Discord can't fetch localhost or 403)
|
|
177
|
+
if (thumb.includes('/api/image-proxy') || thumb.includes('weeabo0.xyz'))
|
|
178
|
+
thumb = '';
|
|
179
|
+
discord_rpc_1.discordRPCService.updatePresence({
|
|
180
|
+
title: String(title || 'ASMR').slice(0, 128),
|
|
181
|
+
episode: String(trackLabel || '').slice(0, 64),
|
|
182
|
+
totalEpisodes: '',
|
|
183
|
+
currentTime: Number(currentTime) || 0,
|
|
184
|
+
duration: Number(duration) || 0,
|
|
185
|
+
thumbnail: thumb,
|
|
186
|
+
thumbnails: thumbs,
|
|
187
|
+
isPlaying: !!isPlaying,
|
|
188
|
+
providerName: 'ASMR',
|
|
189
|
+
sessionId: typeof sessionId === 'string' ? sessionId : undefined,
|
|
190
|
+
isAdult: !!isAdult,
|
|
191
|
+
});
|
|
192
|
+
res.json({ success: true });
|
|
193
|
+
});
|
|
194
|
+
router.post('/discord/radio', (req, res) => {
|
|
195
|
+
const { title, stationLabel, isPlaying, thumbnail, currentTime, sessionId } = req.body ?? {};
|
|
196
|
+
if (!discord_rpc_1.discordRPCService.isServiceEnabled)
|
|
197
|
+
return res.json({ success: true });
|
|
198
|
+
if (typeof sessionId === 'string')
|
|
199
|
+
discord_rpc_1.discordRPCService.heartbeat(sessionId);
|
|
200
|
+
let thumb = String(thumbnail || '');
|
|
201
|
+
if (thumb.includes('/api/image-proxy')) {
|
|
202
|
+
const match = thumb.match(/url=([^&]+)/);
|
|
203
|
+
if (match)
|
|
204
|
+
thumb = decodeURIComponent(match[1]);
|
|
205
|
+
}
|
|
206
|
+
if (thumb && !thumb.startsWith('https://'))
|
|
207
|
+
thumb = '';
|
|
208
|
+
if (thumb.includes('localhost') || thumb.includes('127.0.0.1'))
|
|
209
|
+
thumb = '';
|
|
210
|
+
discord_rpc_1.discordRPCService.updatePresence({
|
|
211
|
+
title: String(title || 'Radio').slice(0, 128),
|
|
212
|
+
episode: String(stationLabel || '').slice(0, 64),
|
|
213
|
+
totalEpisodes: '',
|
|
214
|
+
stateLine: String(stationLabel || 'Radio').slice(0, 64),
|
|
215
|
+
currentTime: Number(currentTime) || 0,
|
|
216
|
+
duration: 0,
|
|
217
|
+
thumbnail: thumb,
|
|
218
|
+
isPlaying: !!isPlaying,
|
|
219
|
+
providerName: 'Radio',
|
|
220
|
+
sessionId: typeof sessionId === 'string' ? sessionId : undefined,
|
|
221
|
+
isAdult: false,
|
|
222
|
+
});
|
|
223
|
+
res.json({ success: true });
|
|
224
|
+
});
|
|
225
|
+
router.post('/discord/manga', async (req, res) => {
|
|
226
|
+
const { title, chapterLabel, isPlaying, thumbnail, thumbnails, sessionId, isAdult } = req.body ?? {};
|
|
227
|
+
if (!discord_rpc_1.discordRPCService.isServiceEnabled)
|
|
228
|
+
return res.json({ success: true });
|
|
229
|
+
if (typeof sessionId === 'string')
|
|
230
|
+
discord_rpc_1.discordRPCService.heartbeat(sessionId);
|
|
231
|
+
const cleanThumb = (value) => {
|
|
232
|
+
let thumb = String(value || '');
|
|
233
|
+
if (thumb.includes('/api/proxy') || thumb.includes('/api/image-proxy')) {
|
|
234
|
+
const match = thumb.match(/url=([^&]+)/);
|
|
235
|
+
if (match) {
|
|
236
|
+
try {
|
|
237
|
+
thumb = decodeURIComponent(match[1]);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return '';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!thumb.startsWith('https://'))
|
|
245
|
+
return '';
|
|
246
|
+
if (thumb.includes('localhost') || thumb.includes('127.0.0.1'))
|
|
247
|
+
return '';
|
|
248
|
+
if (thumb.includes('readdetectiveconan.com'))
|
|
249
|
+
return '';
|
|
250
|
+
return thumb;
|
|
251
|
+
};
|
|
252
|
+
let thumb = cleanThumb(thumbnail);
|
|
253
|
+
let thumbs = Array.isArray(thumbnails)
|
|
254
|
+
? thumbnails.map(cleanThumb).filter(Boolean)
|
|
255
|
+
: undefined;
|
|
256
|
+
if (!thumb && title) {
|
|
257
|
+
const fallback = await getMangadexCover(String(title));
|
|
258
|
+
if (fallback) {
|
|
259
|
+
thumb = fallback;
|
|
260
|
+
thumbs = [fallback, ...(thumbs || [])];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
discord_rpc_1.discordRPCService.updatePresence({
|
|
264
|
+
title: String(title || 'Manga').slice(0, 128),
|
|
265
|
+
episode: String(chapterLabel || '').slice(0, 64),
|
|
266
|
+
totalEpisodes: '',
|
|
267
|
+
stateLine: (chapterLabel ? `Reading ${chapterLabel}` : 'Reading').slice(0, 64),
|
|
268
|
+
currentTime: 0,
|
|
269
|
+
duration: 0,
|
|
270
|
+
thumbnail: thumb,
|
|
271
|
+
thumbnails: thumbs,
|
|
272
|
+
isPlaying: isPlaying !== false,
|
|
273
|
+
providerName: 'Manga',
|
|
274
|
+
sessionId: typeof sessionId === 'string' ? sessionId : undefined,
|
|
275
|
+
isAdult: isAdult === true,
|
|
276
|
+
});
|
|
277
|
+
res.json({ success: true });
|
|
278
|
+
});
|
|
279
|
+
router.post('/discord/tv', (req, res) => {
|
|
280
|
+
const { title, episodeLabel, isPlaying, thumbnail, currentTime, duration, sessionId, isAdult } = req.body ?? {};
|
|
281
|
+
if (!discord_rpc_1.discordRPCService.isServiceEnabled)
|
|
282
|
+
return res.json({ success: true });
|
|
283
|
+
if (typeof sessionId === 'string')
|
|
284
|
+
discord_rpc_1.discordRPCService.heartbeat(sessionId);
|
|
285
|
+
let thumb = String(thumbnail || '');
|
|
286
|
+
if (thumb.includes('/api/image-proxy')) {
|
|
287
|
+
const match = thumb.match(/url=([^&]+)/);
|
|
288
|
+
if (match)
|
|
289
|
+
thumb = decodeURIComponent(match[1]);
|
|
290
|
+
}
|
|
291
|
+
if (thumb.includes('localhost') || thumb.includes('127.0.0.1'))
|
|
292
|
+
thumb = '';
|
|
293
|
+
discord_rpc_1.discordRPCService.updatePresence({
|
|
294
|
+
title: String(title || 'TV').slice(0, 128),
|
|
295
|
+
episode: String(episodeLabel || '').slice(0, 64),
|
|
296
|
+
totalEpisodes: '',
|
|
297
|
+
stateLine: String(episodeLabel || 'Movie').slice(0, 64),
|
|
298
|
+
currentTime: Number(currentTime) || 0,
|
|
299
|
+
duration: Number(duration) || 0,
|
|
300
|
+
thumbnail: thumb,
|
|
301
|
+
isPlaying: !!isPlaying,
|
|
302
|
+
providerName: 'TV',
|
|
303
|
+
sessionId: typeof sessionId === 'string' ? sessionId : undefined,
|
|
304
|
+
isAdult: isAdult === true,
|
|
305
|
+
});
|
|
306
|
+
res.json({ success: true });
|
|
307
|
+
});
|
|
308
|
+
return {
|
|
309
|
+
router,
|
|
310
|
+
stopDiscovery: () => controller.stopNotificationDiscovery(),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
process.setMaxListeners(100);
|
|
7
|
+
const events_1 = require("events");
|
|
8
|
+
events_1.EventEmitter.defaultMaxListeners = 100;
|
|
9
|
+
const express_1 = __importDefault(require("express"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const cors_1 = __importDefault(require("cors"));
|
|
12
|
+
const compression_1 = __importDefault(require("compression"));
|
|
13
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
16
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
17
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
18
|
+
const ipc_1 = require("./lib/ipc");
|
|
19
|
+
const security_utils_1 = require("./utils/security.utils");
|
|
20
|
+
const _123anime_provider_1 = require("./providers/123anime.provider");
|
|
21
|
+
const animeya_provider_1 = require("./providers/animeya.provider");
|
|
22
|
+
const megaplay_provider_1 = require("./providers/megaplay.provider");
|
|
23
|
+
const animepahe_provider_1 = require("./providers/animepahe.provider");
|
|
24
|
+
const wh_provider_1 = require("./providers/wh.provider");
|
|
25
|
+
const hn_provider_1 = require("./providers/hn.provider");
|
|
26
|
+
const anilight_provider_1 = require("./providers/anilight.provider");
|
|
27
|
+
const kaa_provider_1 = require("./providers/kaa.provider");
|
|
28
|
+
const ht_provider_1 = require("./providers/ht.provider");
|
|
29
|
+
const jasmr_provider_1 = require("./providers/jasmr.provider");
|
|
30
|
+
const op_provider_1 = require("./providers/op.provider");
|
|
31
|
+
const github_sync_1 = require("./github-sync");
|
|
32
|
+
const config_1 = require("./config");
|
|
33
|
+
const sync_1 = require("./sync");
|
|
34
|
+
const auth_routes_1 = require("./routes/auth.routes");
|
|
35
|
+
const lan_auth_routes_1 = require("./routes/lan-auth.routes");
|
|
36
|
+
const app_auth_1 = require("./app-auth");
|
|
37
|
+
const watchlist_routes_1 = require("./routes/watchlist.routes");
|
|
38
|
+
const data_routes_1 = require("./routes/data.routes");
|
|
39
|
+
const asmr_routes_1 = require("./routes/asmr.routes");
|
|
40
|
+
const manga_routes_1 = require("./routes/manga.routes");
|
|
41
|
+
const radio_routes_1 = require("./routes/radio.routes");
|
|
42
|
+
const tv_routes_1 = require("./routes/tv.routes");
|
|
43
|
+
const proxy_routes_1 = require("./routes/proxy.routes");
|
|
44
|
+
const settings_routes_1 = require("./routes/settings.routes");
|
|
45
|
+
const insights_routes_1 = require("./routes/insights.routes");
|
|
46
|
+
const translate_routes_1 = require("./routes/translate.routes");
|
|
47
|
+
const discord_gateway_routes_1 = require("./routes/discord-gateway.routes");
|
|
48
|
+
const tracker_routes_1 = require("./routes/tracker.routes");
|
|
49
|
+
const discord_rpc_1 = require("./discord-rpc");
|
|
50
|
+
const discord_gateway_1 = require("./discord-gateway");
|
|
51
|
+
const settings_repository_1 = require("./repositories/settings.repository");
|
|
52
|
+
const request_context_1 = require("./utils/request-context");
|
|
53
|
+
const anilist_1 = require("./lib/anilist");
|
|
54
|
+
const app = (0, express_1.default)();
|
|
55
|
+
app.use((req, res, next) => {
|
|
56
|
+
const store = new Map();
|
|
57
|
+
if (req.headers['x-animepahe-ua']) {
|
|
58
|
+
store.set('ua', req.headers['x-animepahe-ua']);
|
|
59
|
+
}
|
|
60
|
+
if (req.headers['x-animepahe-cookie']) {
|
|
61
|
+
store.set('cookie', req.headers['x-animepahe-cookie']);
|
|
62
|
+
}
|
|
63
|
+
if (req.headers['x-jasmr-ua']) {
|
|
64
|
+
store.set('jasmr_ua', req.headers['x-jasmr-ua']);
|
|
65
|
+
}
|
|
66
|
+
if (req.headers['x-jasmr-cookie']) {
|
|
67
|
+
store.set('jasmr_cookie', req.headers['x-jasmr-cookie']);
|
|
68
|
+
}
|
|
69
|
+
request_context_1.requestContext.run(store, next);
|
|
70
|
+
});
|
|
71
|
+
const apiCache = new node_cache_1.default({ stdTTL: 3600 });
|
|
72
|
+
const _123AnimeProvider = new _123anime_provider_1._123AnimeProvider(apiCache);
|
|
73
|
+
const animeyaProvider = new animeya_provider_1.AnimeyaProvider(apiCache);
|
|
74
|
+
const megaPlayProvider = new megaplay_provider_1.MegaPlayProvider(apiCache);
|
|
75
|
+
const animepaheProvider = new animepahe_provider_1.AnimePaheProvider(apiCache);
|
|
76
|
+
const whProvider = new wh_provider_1.WhProvider(apiCache);
|
|
77
|
+
const hnProvider = new hn_provider_1.HnProvider();
|
|
78
|
+
const anilightProvider = new anilight_provider_1.AnilightProvider(apiCache);
|
|
79
|
+
const kaaProvider = new kaa_provider_1.KaaProvider(apiCache);
|
|
80
|
+
const htProvider = new ht_provider_1.HtProvider(apiCache);
|
|
81
|
+
const jasmrProvider = new jasmr_provider_1.JasmrProvider(apiCache);
|
|
82
|
+
const opProvider = new op_provider_1.OpProvider(apiCache);
|
|
83
|
+
const providers = {
|
|
84
|
+
'123anime': _123AnimeProvider,
|
|
85
|
+
animeya: animeyaProvider,
|
|
86
|
+
megaplay: megaPlayProvider,
|
|
87
|
+
animepahe: animepaheProvider,
|
|
88
|
+
wh: whProvider,
|
|
89
|
+
hn: hnProvider,
|
|
90
|
+
anilight: anilightProvider,
|
|
91
|
+
kaa: kaaProvider,
|
|
92
|
+
ht: htProvider,
|
|
93
|
+
jasmr: jasmrProvider,
|
|
94
|
+
op: opProvider,
|
|
95
|
+
};
|
|
96
|
+
let db;
|
|
97
|
+
let isShuttingDown = false;
|
|
98
|
+
async function runSyncSequence(database, preferredProvider) {
|
|
99
|
+
const dbName = config_1.CONFIG.IS_DEV ? config_1.CONFIG.DB_NAME_DEV : config_1.CONFIG.DB_NAME_PROD;
|
|
100
|
+
const dbPath = path_1.default.join(config_1.CONFIG.ROOT, dbName);
|
|
101
|
+
const remoteFolder = config_1.CONFIG.IS_DEV ? config_1.CONFIG.REMOTE_FOLDER_DEV : config_1.CONFIG.REMOTE_FOLDER_PROD;
|
|
102
|
+
await (0, sync_1.initSyncProvider)(preferredProvider);
|
|
103
|
+
if ((0, sync_1.getActiveProvider)() === 'github' && github_sync_1.githubSyncService.isAuthenticated()) {
|
|
104
|
+
try {
|
|
105
|
+
await github_sync_1.githubSyncService.migrateFromAniWebSync();
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
logger_1.default.error({ err }, 'GitHub sync migration from ani-web failed');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const didDownload = await (0, sync_1.syncDownOnBoot)(database, dbPath, remoteFolder, () => {
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
if (database && !database.isClosedCheck()) {
|
|
114
|
+
database.checkpoint();
|
|
115
|
+
database.close(() => resolve());
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
resolve();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
let currentDb = database;
|
|
123
|
+
if (didDownload) {
|
|
124
|
+
db = await (0, sync_1.initializeDatabase)(dbPath);
|
|
125
|
+
currentDb = db;
|
|
126
|
+
logger_1.default.info('Database re-initialized after sync.');
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await (0, sync_1.syncUp)(currentDb, dbPath, remoteFolder);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
logger_1.default.error({ err }, 'Sync up on boot failed');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
app.use((req, res, next) => {
|
|
136
|
+
if (isShuttingDown) {
|
|
137
|
+
return res.status(503).send('Server is shutting down...');
|
|
138
|
+
}
|
|
139
|
+
if (!db) {
|
|
140
|
+
return res.status(503).send('Database initializing...');
|
|
141
|
+
}
|
|
142
|
+
req.db = db;
|
|
143
|
+
next();
|
|
144
|
+
});
|
|
145
|
+
app.use((0, compression_1.default)({
|
|
146
|
+
level: 2,
|
|
147
|
+
threshold: 1024,
|
|
148
|
+
filter: (req, res) => {
|
|
149
|
+
if (req.headers['x-no-compression']) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return compression_1.default.filter(req, res);
|
|
153
|
+
},
|
|
154
|
+
}));
|
|
155
|
+
app.use((0, cors_1.default)({
|
|
156
|
+
origin: (origin, callback) => {
|
|
157
|
+
callback(null, (0, security_utils_1.isAllowedOrigin)(origin));
|
|
158
|
+
},
|
|
159
|
+
credentials: true,
|
|
160
|
+
}));
|
|
161
|
+
app.use(security_utils_1.crossSiteProtectionMiddleware);
|
|
162
|
+
app.use(express_1.default.json({ limit: '10mb' }));
|
|
163
|
+
app.use('/api/auth', (0, lan_auth_routes_1.createLanAuthRouter)());
|
|
164
|
+
app.use(app_auth_1.lanAuthMiddleware);
|
|
165
|
+
app.use('/api/auth', (0, auth_routes_1.createAuthRouter)((database) => runSyncSequence(database)));
|
|
166
|
+
const { router: watchlistRouter, stopDiscovery } = (0, watchlist_routes_1.createWatchlistRouter)(() => db);
|
|
167
|
+
app.use('/api', watchlistRouter);
|
|
168
|
+
app.use('/api', (0, data_routes_1.createDataRouter)(apiCache, providers));
|
|
169
|
+
app.use('/api', (0, asmr_routes_1.createAsmrRouter)(apiCache, jasmrProvider));
|
|
170
|
+
app.use('/api', (0, manga_routes_1.createMangaRouter)(apiCache));
|
|
171
|
+
app.use('/api', (0, radio_routes_1.createRadioRouter)(apiCache));
|
|
172
|
+
app.use('/api', (0, tv_routes_1.createTvRouter)(apiCache));
|
|
173
|
+
app.use('/api', (0, proxy_routes_1.createProxyRouter)());
|
|
174
|
+
app.use('/api', (0, insights_routes_1.createInsightsRouter)());
|
|
175
|
+
app.use('/api', (0, translate_routes_1.createTranslateRouter)());
|
|
176
|
+
app.use('/api', (0, discord_gateway_routes_1.createDiscordGatewayRouter)());
|
|
177
|
+
app.use('/api', (0, tracker_routes_1.createTrackerRouter)());
|
|
178
|
+
app.use('/api', (0, settings_routes_1.createSettingsRouter)(() => db, sync_1.initializeDatabase, (newDb) => {
|
|
179
|
+
db = newDb;
|
|
180
|
+
}));
|
|
181
|
+
if (!config_1.CONFIG.IS_DEV) {
|
|
182
|
+
const frontendPath = path_1.default.join(config_1.CONFIG.PACKAGE_ROOT, 'client', 'dist');
|
|
183
|
+
logger_1.default.info(`Serving frontend from: ${frontendPath}`);
|
|
184
|
+
app.use(express_1.default.static(frontendPath));
|
|
185
|
+
app.get(/^(?!\/api).+/, (req, res) => {
|
|
186
|
+
res.sendFile('index.html', { root: frontendPath }, (err) => {
|
|
187
|
+
if (err) {
|
|
188
|
+
logger_1.default.error({ err }, `Failed to serve index.html from ${frontendPath}`);
|
|
189
|
+
if (!res.headersSent) {
|
|
190
|
+
res.status(500).send('Server Error: Frontend build not found.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
app.use((err, req, res, next) => {
|
|
197
|
+
if (err instanceof SyntaxError &&
|
|
198
|
+
'body' in err &&
|
|
199
|
+
req.headers['content-type']?.includes('application/json')) {
|
|
200
|
+
if (!res.headersSent) {
|
|
201
|
+
return res.status(400).json({ error: 'Invalid JSON', status: 400 });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
logger_1.default.error({ err, url: req.url, method: req.method }, 'Unhandled error');
|
|
205
|
+
if (res.headersSent) {
|
|
206
|
+
return next(err);
|
|
207
|
+
}
|
|
208
|
+
res.status(err.status || 500).json({
|
|
209
|
+
error: err.message || 'Internal Server Error',
|
|
210
|
+
status: err.status || 500,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
async function main() {
|
|
214
|
+
const dbName = config_1.CONFIG.IS_DEV ? config_1.CONFIG.DB_NAME_DEV : config_1.CONFIG.DB_NAME_PROD;
|
|
215
|
+
const dbPath = path_1.default.join(config_1.CONFIG.ROOT, dbName);
|
|
216
|
+
const remoteFolder = config_1.CONFIG.IS_DEV ? config_1.CONFIG.REMOTE_FOLDER_DEV : config_1.CONFIG.REMOTE_FOLDER_PROD;
|
|
217
|
+
db = await (0, sync_1.initializeDatabase)(dbPath);
|
|
218
|
+
logger_1.default.info(`Database initialized at ${dbPath}`);
|
|
219
|
+
const rpcEnabledSetting = await settings_repository_1.SettingsRepository.getByKey(db, 'discordRPCEnabled');
|
|
220
|
+
const isRpcEnabled = rpcEnabledSetting ? rpcEnabledSetting.value === 'true' : true;
|
|
221
|
+
await discord_rpc_1.discordRPCService.setEnabled(isRpcEnabled);
|
|
222
|
+
discord_gateway_1.discordGatewayService.setEnabled(isRpcEnabled);
|
|
223
|
+
(0, anilist_1.checkAnilistStatus)().catch(() => { });
|
|
224
|
+
await runSyncSequence(db);
|
|
225
|
+
if (!fs_1.default.existsSync(config_1.CONFIG.LOCAL_MANIFEST_PATH)) {
|
|
226
|
+
fs_1.default.writeFileSync(config_1.CONFIG.LOCAL_MANIFEST_PATH, JSON.stringify({ version: 0 }));
|
|
227
|
+
}
|
|
228
|
+
let hasUnsyncedChanges = false;
|
|
229
|
+
const watcher = chokidar_1.default.watch(config_1.CONFIG.LOCAL_MANIFEST_PATH, {
|
|
230
|
+
persistent: true,
|
|
231
|
+
ignoreInitial: true,
|
|
232
|
+
});
|
|
233
|
+
const expressServer = app.listen(config_1.CONFIG.PORT, () => {
|
|
234
|
+
logger_1.default.info(`Server running on http://localhost:${config_1.CONFIG.PORT}`);
|
|
235
|
+
});
|
|
236
|
+
watcher.on('change', () => {
|
|
237
|
+
hasUnsyncedChanges = true;
|
|
238
|
+
});
|
|
239
|
+
const syncInterval = setInterval(async () => {
|
|
240
|
+
if (hasUnsyncedChanges) {
|
|
241
|
+
logger_1.default.info('Uploading accumulated database changes...');
|
|
242
|
+
hasUnsyncedChanges = false;
|
|
243
|
+
try {
|
|
244
|
+
await (0, sync_1.syncUp)(db, dbPath, remoteFolder);
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
logger_1.default.error({ err }, 'Failed to upload database changes');
|
|
248
|
+
hasUnsyncedChanges = true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}, 300000);
|
|
252
|
+
const shutdown = async (signal) => {
|
|
253
|
+
if (isShuttingDown)
|
|
254
|
+
return;
|
|
255
|
+
isShuttingDown = true;
|
|
256
|
+
stopDiscovery();
|
|
257
|
+
clearInterval(syncInterval);
|
|
258
|
+
discord_rpc_1.discordRPCService.disconnect();
|
|
259
|
+
discord_gateway_1.discordGatewayService.disconnect();
|
|
260
|
+
await watcher.close();
|
|
261
|
+
if (expressServer) {
|
|
262
|
+
expressServer.close();
|
|
263
|
+
}
|
|
264
|
+
if (hasUnsyncedChanges) {
|
|
265
|
+
logger_1.default.info('Sync on shutdown: uploading final database changes...');
|
|
266
|
+
hasUnsyncedChanges = false;
|
|
267
|
+
try {
|
|
268
|
+
await (0, sync_1.syncUp)(db, dbPath, remoteFolder);
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
logger_1.default.error({ err: e }, 'Final sync on shutdown failed');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
await (0, sync_1.waitForSync)();
|
|
275
|
+
db.close(() => {
|
|
276
|
+
(0, ipc_1.notifyServerExit)();
|
|
277
|
+
if (signal === 'SIGUSR2') {
|
|
278
|
+
process.kill(process.pid, 'SIGUSR2');
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
283
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
284
|
+
process.on('SIGHUP', () => shutdown('SIGHUP'));
|
|
285
|
+
process.once('SIGUSR2', () => shutdown('SIGUSR2'));
|
|
286
|
+
app.post('/api/internal/shutdown', (req, res) => {
|
|
287
|
+
const isLoopback = req.ip === '::1' || req.ip === '127.0.0.1' || req.ip === '::ffff:127.0.0.1';
|
|
288
|
+
if (!isLoopback) {
|
|
289
|
+
res.status(403).json({ error: 'Forbidden' });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const expectedToken = process.env.INTERNAL_SHUTDOWN_TOKEN;
|
|
293
|
+
const providedToken = req.headers['x-internal-token'];
|
|
294
|
+
if (expectedToken) {
|
|
295
|
+
const expBuf = Buffer.from(expectedToken);
|
|
296
|
+
const provBuf = Buffer.from(typeof providedToken === 'string' ? providedToken : '');
|
|
297
|
+
if (expBuf.length !== provBuf.length || !crypto_1.default.timingSafeEqual(expBuf, provBuf)) {
|
|
298
|
+
logger_1.default.warn('Unauthorized internal shutdown attempt rejected: invalid token');
|
|
299
|
+
res.status(403).json({ error: 'Forbidden' });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
res.status(200).json({ message: 'Shutting down' });
|
|
304
|
+
setTimeout(() => shutdown(), 500);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
main().catch((err) => {
|
|
308
|
+
logger_1.default.error({ err }, 'Server failed to start');
|
|
309
|
+
process.exit(1);
|
|
310
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHIPPED_DEFAULTS = void 0;
|
|
4
|
+
exports.SHIPPED_DEFAULTS = {
|
|
5
|
+
"GITHUB_CLIENT_ID": "Ov23liT1ZtPk7XtN9PZk",
|
|
6
|
+
"GOOGLE_AUTH_WORKER_URL": "https://google-auth-client.ramiserifpersia.workers.dev",
|
|
7
|
+
"DISCORD_CLIENT_ID": "1512253778022367426"
|
|
8
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setActiveRemote = setActiveRemote;
|
|
7
|
+
exports.getActiveRemote = getActiveRemote;
|
|
8
|
+
exports.initialize = initialize;
|
|
9
|
+
exports.getRemoteString = getRemoteString;
|
|
10
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
11
|
+
const log = logger_1.default.child({ module: 'SyncConfig' });
|
|
12
|
+
let activeRemote;
|
|
13
|
+
function setActiveRemote(remote) {
|
|
14
|
+
log.info({ remote }, 'Setting active sync remote');
|
|
15
|
+
activeRemote = remote;
|
|
16
|
+
}
|
|
17
|
+
function getActiveRemote() {
|
|
18
|
+
return activeRemote;
|
|
19
|
+
}
|
|
20
|
+
async function initialize() {
|
|
21
|
+
if (activeRemote !== 'gdrive') {
|
|
22
|
+
log.info('Active remote is not gdrive, skipping gdrive-specific initialization.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
log.info('gdrive is the active remote.');
|
|
26
|
+
}
|
|
27
|
+
function getRemoteString(remoteDir) {
|
|
28
|
+
if (!activeRemote) {
|
|
29
|
+
throw new Error('Cannot get remote string: active remote is not set.');
|
|
30
|
+
}
|
|
31
|
+
return `${activeRemote}:${remoteDir}`;
|
|
32
|
+
}
|