@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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/client/dist/assets/AnimeInfo-CMxs0nRH.css +1 -0
  4. package/client/dist/assets/AnimeInfo-DVdaocCl.js +1 -0
  5. package/client/dist/assets/AnimeInfoPage-Bx5EvGt-.js +2 -0
  6. package/client/dist/assets/Asmr-C2i9GKD4.css +1 -0
  7. package/client/dist/assets/Asmr-CIjmFtRH.js +2 -0
  8. package/client/dist/assets/Asmr.module-DPG2JhDJ.js +1 -0
  9. package/client/dist/assets/AvSyncCalibrator-CIYcyon5.css +1 -0
  10. package/client/dist/assets/AvSyncCalibrator-vqErH7Cm.js +10 -0
  11. package/client/dist/assets/ErrorMessage-BwSjzblw.js +1 -0
  12. package/client/dist/assets/ErrorMessage-CXoRbrLN.css +1 -0
  13. package/client/dist/assets/Home-D7YfGBUk.js +1 -0
  14. package/client/dist/assets/Home-DKCfEiwl.css +1 -0
  15. package/client/dist/assets/Insights-BljI64Ua.css +1 -0
  16. package/client/dist/assets/Insights-CLQGrlpX.js +1 -0
  17. package/client/dist/assets/Manga-DQ9HYnpk.js +1 -0
  18. package/client/dist/assets/Manga-H8Wb04w3.css +1 -0
  19. package/client/dist/assets/Map-CXFqXmsZ.css +1 -0
  20. package/client/dist/assets/Map-L_ryVBTq.js +1 -0
  21. package/client/dist/assets/Mature-BeWzAJ7B.js +1 -0
  22. package/client/dist/assets/Mature-DJmUUtlF.css +1 -0
  23. package/client/dist/assets/Player-CYpd__FP.js +30 -0
  24. package/client/dist/assets/Player-DreAhybc.css +1 -0
  25. package/client/dist/assets/PlayerRedirect-fxUAp9JV.js +1 -0
  26. package/client/dist/assets/PlayerSettings-BcsotxP9.js +1 -0
  27. package/client/dist/assets/PlayerSettings-Cqua1CDD.css +1 -0
  28. package/client/dist/assets/QueueRail-B9Pgbqc3.js +1 -0
  29. package/client/dist/assets/QueueRail-BnaltOP8.css +1 -0
  30. package/client/dist/assets/Radio-BvdY5twD.js +1 -0
  31. package/client/dist/assets/Radio-CUuYy3DY.css +1 -0
  32. package/client/dist/assets/Search-BzO-aRP7.css +1 -0
  33. package/client/dist/assets/Search-R9YLAXS1.js +1 -0
  34. package/client/dist/assets/Search.module-D8ZbQAiK.js +1 -0
  35. package/client/dist/assets/Settings-9kYcXdTR.js +21 -0
  36. package/client/dist/assets/Settings-DF8JThUL.css +1 -0
  37. package/client/dist/assets/SkeletonGrid-D5vRvX5q.js +1 -0
  38. package/client/dist/assets/SkeletonGrid-DEbYtO4H.css +1 -0
  39. package/client/dist/assets/SynopsisText-BlFw0R-0.js +1 -0
  40. package/client/dist/assets/SynopsisText-CUgOjOxt.css +1 -0
  41. package/client/dist/assets/Trackers-DfZlnnVq.js +2 -0
  42. package/client/dist/assets/Trackers-Dt9lR_uP.css +1 -0
  43. package/client/dist/assets/Tv-CVKC2YBs.css +1 -0
  44. package/client/dist/assets/Tv-Db1Wx_b7.js +15 -0
  45. package/client/dist/assets/Watchlist-B2nkNZqM.css +1 -0
  46. package/client/dist/assets/Watchlist-a77CYtzu.js +1 -0
  47. package/client/dist/assets/hls-6Owgw2Aj.js +1 -0
  48. package/client/dist/assets/hls-BOLXTnjL.js +42 -0
  49. package/client/dist/assets/index-B6qdXFtl.css +1 -0
  50. package/client/dist/assets/index-B8TPVaIS.js +30354 -0
  51. package/client/dist/assets/index-CpjgU8ou.js +178 -0
  52. package/client/dist/assets/useAnimeInfoData-BOfyi9UJ.js +1 -0
  53. package/client/dist/assets/useIsMobile-CM15uQn8.css +1 -0
  54. package/client/dist/assets/useIsMobile-Cb8HcKRj.js +1 -0
  55. package/client/dist/assets/useMatureConsent-CZMYBVXd.js +1 -0
  56. package/client/dist/assets/vendor-rLNfZm7Y.js +1 -0
  57. package/client/dist/av-sync-test.mp4 +0 -0
  58. package/client/dist/favicon.ico +0 -0
  59. package/client/dist/index.html +31 -0
  60. package/client/dist/logo-mochi.png +0 -0
  61. package/client/dist/logo-sakura.png +0 -0
  62. package/client/dist/logo.png +0 -0
  63. package/client/dist/placeholder.svg +4 -0
  64. package/client/dist/robots.txt +3 -0
  65. package/orchestrator.js +416 -0
  66. package/package.json +89 -0
  67. package/server/dist/app-auth.js +231 -0
  68. package/server/dist/config.js +133 -0
  69. package/server/dist/controllers/auth.controller.js +247 -0
  70. package/server/dist/controllers/data.controller.js +785 -0
  71. package/server/dist/controllers/insights.controller.js +197 -0
  72. package/server/dist/controllers/proxy.controller.js +665 -0
  73. package/server/dist/controllers/settings.controller.js +267 -0
  74. package/server/dist/controllers/watchlist.controller.js +967 -0
  75. package/server/dist/db.js +152 -0
  76. package/server/dist/discord-gateway.js +381 -0
  77. package/server/dist/discord-rpc.js +424 -0
  78. package/server/dist/github-sync.js +403 -0
  79. package/server/dist/google.js +549 -0
  80. package/server/dist/lib/anilist.js +1519 -0
  81. package/server/dist/lib/anischedule.js +202 -0
  82. package/server/dist/lib/ipc.js +32 -0
  83. package/server/dist/lib/kitsu.js +552 -0
  84. package/server/dist/lib/mal.js +886 -0
  85. package/server/dist/lib/migration.js +335 -0
  86. package/server/dist/lib/temp-ids.js +11 -0
  87. package/server/dist/lib/tmdb.js +71 -0
  88. package/server/dist/lib/tracker/anilist-tracker.js +250 -0
  89. package/server/dist/lib/tracker/sync.service.js +312 -0
  90. package/server/dist/logger.js +19 -0
  91. package/server/dist/providers/123anime.provider.js +240 -0
  92. package/server/dist/providers/anilight.provider.js +366 -0
  93. package/server/dist/providers/animepahe.provider.js +531 -0
  94. package/server/dist/providers/animeya.provider.js +746 -0
  95. package/server/dist/providers/base-provider.js +45 -0
  96. package/server/dist/providers/hn.provider.js +473 -0
  97. package/server/dist/providers/ht.provider.js +439 -0
  98. package/server/dist/providers/jasmr.provider.js +504 -0
  99. package/server/dist/providers/kaa.provider.js +388 -0
  100. package/server/dist/providers/manga/manga.types.js +10 -0
  101. package/server/dist/providers/manga/mangadex.provider.js +227 -0
  102. package/server/dist/providers/manga/mangapill.provider.js +232 -0
  103. package/server/dist/providers/megaplay.provider.js +583 -0
  104. package/server/dist/providers/op.provider.js +440 -0
  105. package/server/dist/providers/provider.interface.js +2 -0
  106. package/server/dist/providers/title-matching.js +234 -0
  107. package/server/dist/providers/wh.provider.js +600 -0
  108. package/server/dist/rclone.js +126 -0
  109. package/server/dist/repositories/insights.repository.js +43 -0
  110. package/server/dist/repositories/mal-cache.repository.js +66 -0
  111. package/server/dist/repositories/notifications.repository.js +40 -0
  112. package/server/dist/repositories/queue.repository.js +64 -0
  113. package/server/dist/repositories/settings.repository.js +15 -0
  114. package/server/dist/repositories/shows-meta.repository.js +43 -0
  115. package/server/dist/repositories/temp-show-ids.repository.js +54 -0
  116. package/server/dist/repositories/watched-episodes.repository.js +54 -0
  117. package/server/dist/repositories/watchlist.repository.js +74 -0
  118. package/server/dist/routes/asmr.routes.js +72 -0
  119. package/server/dist/routes/auth.routes.js +28 -0
  120. package/server/dist/routes/data.routes.js +50 -0
  121. package/server/dist/routes/discord-gateway.routes.js +57 -0
  122. package/server/dist/routes/insights.routes.js +12 -0
  123. package/server/dist/routes/lan-auth.routes.js +49 -0
  124. package/server/dist/routes/manga.routes.js +115 -0
  125. package/server/dist/routes/proxy.routes.js +15 -0
  126. package/server/dist/routes/radio.routes.js +143 -0
  127. package/server/dist/routes/settings.routes.js +27 -0
  128. package/server/dist/routes/tracker.routes.js +143 -0
  129. package/server/dist/routes/translate.routes.js +90 -0
  130. package/server/dist/routes/tv.routes.js +843 -0
  131. package/server/dist/routes/watchlist.routes.js +312 -0
  132. package/server/dist/server.js +310 -0
  133. package/server/dist/shipped-defaults.js +8 -0
  134. package/server/dist/sync-config.js +32 -0
  135. package/server/dist/sync.js +506 -0
  136. package/server/dist/utils/cookie.utils.js +17 -0
  137. package/server/dist/utils/db-utils.js +15 -0
  138. package/server/dist/utils/env.utils.js +83 -0
  139. package/server/dist/utils/machine-id.js +46 -0
  140. package/server/dist/utils/request-context.js +5 -0
  141. package/server/dist/utils/security.utils.js +99 -0
@@ -0,0 +1,267 @@
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.SettingsController = void 0;
7
+ const sync_1 = require("../sync");
8
+ const anilist_1 = require("../lib/anilist");
9
+ const kitsu_1 = require("../lib/kitsu");
10
+ const xml2js_1 = require("xml2js");
11
+ const logger_1 = __importDefault(require("../logger"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const config_1 = require("../config");
15
+ const settings_repository_1 = require("../repositories/settings.repository");
16
+ const shows_meta_repository_1 = require("../repositories/shows-meta.repository");
17
+ const machine_id_1 = require("../utils/machine-id");
18
+ const discord_rpc_1 = require("../discord-rpc");
19
+ function mapMalStatus(malStatus) {
20
+ switch (malStatus) {
21
+ case 'Plan to Watch':
22
+ return 'Planned';
23
+ case 'On Hold':
24
+ return 'On-Hold';
25
+ default:
26
+ return malStatus;
27
+ }
28
+ }
29
+ async function searchByTitleForMal(title) {
30
+ const result = await (0, anilist_1.searchAnilistByTitle)(title);
31
+ if (result)
32
+ return { ...result, source: 'anilist' };
33
+ if ((0, anilist_1.isAnilistRateLimited)()) {
34
+ logger_1.default.debug({ title }, 'AniList rate limited, trying Kitsu fallback for MAL import');
35
+ const fb = await (0, kitsu_1.kitsuSearchAnime)({ query: title, page: 1, perPage: 5 });
36
+ if (fb.length > 0) {
37
+ const lowerTitle = title.toLowerCase();
38
+ const exactMatch = fb.find((m) => m.title?.romaji?.toLowerCase() === lowerTitle ||
39
+ m.title?.english?.toLowerCase() === lowerTitle);
40
+ const best = exactMatch || fb[0];
41
+ if (best.id > 0) {
42
+ return { id: best.id, title: best.title ?? {}, source: 'kitsu' };
43
+ }
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ class SettingsController {
49
+ getSettings = async (req, res) => {
50
+ try {
51
+ const row = await settings_repository_1.SettingsRepository.getByKey(req.db, req.query.key);
52
+ let value = row ? row.value : null;
53
+ if (value === null && req.query.key === 'discordRPCEnabled') {
54
+ value = 'true';
55
+ }
56
+ if (value === null && req.query.key === 'discordRPCHideMature') {
57
+ value = 'true';
58
+ }
59
+ if (value === null && req.query.key === 'ignoreAdultContent') {
60
+ value = 'true';
61
+ }
62
+ res.json({ value: value });
63
+ }
64
+ catch {
65
+ res.status(500).json({ error: 'DB error' });
66
+ }
67
+ };
68
+ updateSettings = async (req, res) => {
69
+ try {
70
+ const key = String(req.body.key);
71
+ const value = String(req.body.value ?? '');
72
+ const shouldDelete = value === '' && key === 'tracker_anilist_client_id';
73
+ await (0, sync_1.performWriteTransaction)(req.db, (tx) => {
74
+ if (shouldDelete)
75
+ settings_repository_1.SettingsRepository.deleteByKey(tx, key);
76
+ else
77
+ settings_repository_1.SettingsRepository.upsert(tx, key, value);
78
+ });
79
+ if (req.body.key === 'discordRPCEnabled') {
80
+ discord_rpc_1.discordRPCService.setEnabled(req.body.value === 'true' || req.body.value === true);
81
+ }
82
+ if (req.body.key === 'discordRPCHideMature') {
83
+ discord_rpc_1.discordRPCService.setHideMature(req.body.value === 'true' || req.body.value === true);
84
+ }
85
+ res.json({ success: true });
86
+ }
87
+ catch {
88
+ res.status(500).json({ error: 'DB error' });
89
+ }
90
+ };
91
+ backupDatabase = (req, res) => {
92
+ const backupPath = path_1.default.join(config_1.CONFIG.ROOT, 'dango-backup.db');
93
+ try {
94
+ req.db.backup(backupPath);
95
+ res.download(backupPath, 'dango-backup.db', (err) => {
96
+ if (err) {
97
+ logger_1.default.error({ err }, 'res.download failed during database backup');
98
+ if (!res.headersSent) {
99
+ res.status(500).json({ error: 'Download failed' });
100
+ }
101
+ }
102
+ fs_1.default.unlink(backupPath, () => { });
103
+ });
104
+ }
105
+ catch (err) {
106
+ logger_1.default.error({ err }, 'Manual backup failed');
107
+ return res.status(500).json({ error: 'Backup failed' });
108
+ }
109
+ };
110
+ restoreDatabase = (req, res, db, initializeDatabase, setDb) => {
111
+ if (!req.file)
112
+ return res.status(400).json({ error: 'No file uploaded.' });
113
+ const dbName = config_1.CONFIG.IS_DEV ? config_1.CONFIG.DB_NAME_DEV : config_1.CONFIG.DB_NAME_PROD;
114
+ const tempPath = path_1.default.join(config_1.CONFIG.ROOT, `restore_temp.db`);
115
+ const dbPath = path_1.default.join(config_1.CONFIG.ROOT, dbName);
116
+ db.close((closeErr) => {
117
+ if (closeErr)
118
+ return res.status(500).json({ error: 'Failed to close database.' });
119
+ try {
120
+ req.db.checkpoint();
121
+ }
122
+ catch (checkpointErr) {
123
+ logger_1.default.warn({ err: checkpointErr }, 'WAL checkpoint failed');
124
+ }
125
+ try {
126
+ if (fs_1.default.existsSync(`${dbPath}-wal`))
127
+ fs_1.default.unlinkSync(`${dbPath}-wal`);
128
+ if (fs_1.default.existsSync(`${dbPath}-shm`))
129
+ fs_1.default.unlinkSync(`${dbPath}-shm`);
130
+ }
131
+ catch (cleanupErr) {
132
+ logger_1.default.warn({ err: cleanupErr }, 'Failed to clean up WAL files');
133
+ }
134
+ fs_1.default.rename(tempPath, dbPath, async (renameErr) => {
135
+ if (renameErr) {
136
+ try {
137
+ const reopenedDb = await initializeDatabase(dbPath);
138
+ setDb(reopenedDb);
139
+ req.db = reopenedDb;
140
+ }
141
+ catch (e) {
142
+ logger_1.default.error({ err: e }, 'Failed to reopen DB after rename failure');
143
+ }
144
+ return res.status(500).json({ error: 'Failed to replace database file.' });
145
+ }
146
+ try {
147
+ const newDb = await initializeDatabase(dbPath);
148
+ setDb(newDb);
149
+ req.db = newDb;
150
+ res.json({ success: true, message: 'Database restored.' });
151
+ }
152
+ catch (e) {
153
+ logger_1.default.error({ err: e }, 'Failed to initialize restored database');
154
+ res.status(500).json({ error: 'Failed to initialize restored database.' });
155
+ }
156
+ });
157
+ });
158
+ };
159
+ importMalXml = async (req, res) => {
160
+ if (!req.file)
161
+ return res.status(400).json({ error: 'No file' });
162
+ const { erase } = req.body;
163
+ let result;
164
+ try {
165
+ result = await (0, xml2js_1.parseStringPromise)(req.file.buffer.toString());
166
+ }
167
+ catch {
168
+ return res.status(400).json({ error: 'Invalid XML' });
169
+ }
170
+ const animeList = result?.myanimelist?.anime || [];
171
+ if (animeList.length === 0) {
172
+ return res.status(400).json({ error: 'No anime found in XML' });
173
+ }
174
+ res.setHeader('Content-Type', 'text/event-stream');
175
+ res.setHeader('Cache-Control', 'no-cache');
176
+ res.setHeader('Connection', 'keep-alive');
177
+ res.flushHeaders();
178
+ const sendEvent = (event, data) => {
179
+ res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
180
+ if (typeof res.flush === 'function')
181
+ res.flush();
182
+ };
183
+ let skippedCount = 0;
184
+ const showsToInsert = [];
185
+ const metaToSave = [];
186
+ const BATCH_SIZE = 5;
187
+ const total = animeList.length;
188
+ for (let i = 0; i < animeList.length; i += BATCH_SIZE) {
189
+ const batch = animeList.slice(i, i + BATCH_SIZE);
190
+ const batchResults = await Promise.allSettled(batch.map((item) => searchByTitleForMal(item.series_title[0])));
191
+ const metaPromises = [];
192
+ batchResults.forEach((r, idx) => {
193
+ const malTitle = batch[idx].series_title[0];
194
+ const currentIdx = i + idx + 1;
195
+ if (r.status === 'fulfilled' && r.value) {
196
+ const show = r.value;
197
+ const title = show.title?.english || show.title?.romaji || malTitle;
198
+ const status = mapMalStatus(batch[idx].my_status[0]);
199
+ showsToInsert.push({
200
+ id: String(show.id),
201
+ name: title,
202
+ status,
203
+ });
204
+ sendEvent('progress', {
205
+ current: currentIdx,
206
+ total,
207
+ title: malTitle,
208
+ matchedTitle: title,
209
+ status,
210
+ source: show.source,
211
+ found: true,
212
+ });
213
+ metaPromises.push((0, anilist_1.getShowMetaById)(String(show.id))
214
+ .then((meta) => {
215
+ if (meta) {
216
+ metaToSave.push({
217
+ id: String(show.id),
218
+ thumbnail: meta.thumbnail || undefined,
219
+ type: meta.type || undefined,
220
+ });
221
+ }
222
+ })
223
+ .catch(() => { }));
224
+ }
225
+ else {
226
+ skippedCount++;
227
+ sendEvent('progress', {
228
+ current: currentIdx,
229
+ total,
230
+ title: malTitle,
231
+ matchedTitle: null,
232
+ status: mapMalStatus(batch[idx].my_status[0]),
233
+ source: null,
234
+ found: false,
235
+ });
236
+ }
237
+ });
238
+ await Promise.allSettled(metaPromises);
239
+ }
240
+ await (0, sync_1.performWriteTransaction)(req.db, (tx) => {
241
+ if (erase)
242
+ settings_repository_1.SettingsRepository.clearWatchlist(tx);
243
+ settings_repository_1.SettingsRepository.upsertWatchlistBatch(tx, showsToInsert);
244
+ for (const meta of metaToSave) {
245
+ if (meta.thumbnail) {
246
+ shows_meta_repository_1.ShowsMetaRepository.upsert(tx, {
247
+ id: meta.id,
248
+ thumbnail: meta.thumbnail,
249
+ type: meta.type,
250
+ });
251
+ }
252
+ }
253
+ });
254
+ sendEvent('complete', { imported: showsToInsert.length, skipped: skippedCount });
255
+ res.end();
256
+ };
257
+ getInstallationId = (_req, res) => {
258
+ try {
259
+ res.json({ id: (0, machine_id_1.getMachineId)() });
260
+ }
261
+ catch (err) {
262
+ logger_1.default.error({ err }, 'Failed to get machine ID');
263
+ res.status(500).json({ error: 'Failed to get machine ID' });
264
+ }
265
+ };
266
+ }
267
+ exports.SettingsController = SettingsController;