@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,231 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.LAN_AUTH_COOKIE = void 0;
40
+ exports.getAppPasswordHash = getAppPasswordHash;
41
+ exports.hasAppPassword = hasAppPassword;
42
+ exports.hashAppPassword = hashAppPassword;
43
+ exports.verifyAppPassword = verifyAppPassword;
44
+ exports.setAppPassword = setAppPassword;
45
+ exports.createLanSession = createLanSession;
46
+ exports.validateLanSession = validateLanSession;
47
+ exports.revokeLanSession = revokeLanSession;
48
+ exports.clearAllLanSessions = clearAllLanSessions;
49
+ exports.isLoopbackRequest = isLoopbackRequest;
50
+ exports.getRequestToken = getRequestToken;
51
+ exports.isLanAuthenticated = isLanAuthenticated;
52
+ exports.lanAuthMiddleware = lanAuthMiddleware;
53
+ exports.buildLanCookie = buildLanCookie;
54
+ exports.clearLanCookie = clearLanCookie;
55
+ const crypto_1 = __importDefault(require("crypto"));
56
+ const fs_1 = __importDefault(require("fs"));
57
+ const path_1 = __importDefault(require("path"));
58
+ const config_1 = require("./config");
59
+ exports.LAN_AUTH_COOKIE = 'dango_lan_auth';
60
+ const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
61
+ function sessionsFile() {
62
+ return path_1.default.join(config_1.CONFIG.ROOT, 'lan_sessions.json');
63
+ }
64
+ function loadSessions() {
65
+ try {
66
+ if (!fs_1.default.existsSync(sessionsFile()))
67
+ return {};
68
+ const raw = fs_1.default.readFileSync(sessionsFile(), 'utf8');
69
+ const parsed = JSON.parse(raw);
70
+ const now = Date.now();
71
+ let dirty = false;
72
+ for (const [token, expiry] of Object.entries(parsed)) {
73
+ if (typeof expiry !== 'number' || expiry < now) {
74
+ delete parsed[token];
75
+ dirty = true;
76
+ }
77
+ }
78
+ if (dirty)
79
+ saveSessions(parsed);
80
+ return parsed;
81
+ }
82
+ catch {
83
+ return {};
84
+ }
85
+ }
86
+ function saveSessions(sessions) {
87
+ try {
88
+ fs_1.default.mkdirSync(config_1.CONFIG.ROOT, { recursive: true });
89
+ fs_1.default.writeFileSync(sessionsFile(), JSON.stringify(sessions));
90
+ }
91
+ catch {
92
+ // sessions won't be saved if disk write fails
93
+ }
94
+ }
95
+ function getAppPasswordHash() {
96
+ return process.env.APP_PASSWORD_HASH || config_1.CONFIG.APP_PASSWORD_HASH || '';
97
+ }
98
+ function hasAppPassword() {
99
+ return !!getAppPasswordHash();
100
+ }
101
+ function hashAppPassword(password) {
102
+ const salt = crypto_1.default.randomBytes(16).toString('hex');
103
+ const hash = crypto_1.default.scryptSync(password, salt, 32).toString('hex');
104
+ return `scrypt:${salt}:${hash}`;
105
+ }
106
+ function verifyAppPassword(password) {
107
+ const stored = getAppPasswordHash();
108
+ if (!stored)
109
+ return false;
110
+ const parts = stored.split(':');
111
+ if (parts.length !== 3 || parts[0] !== 'scrypt')
112
+ return false;
113
+ const [, salt, expectedHex] = parts;
114
+ try {
115
+ const derived = crypto_1.default.scryptSync(password, salt, 32);
116
+ const expected = Buffer.from(expectedHex, 'hex');
117
+ if (derived.length !== expected.length)
118
+ return false;
119
+ return crypto_1.default.timingSafeEqual(derived, expected);
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ async function setAppPassword(password) {
126
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('./utils/env.utils')));
127
+ if (!password) {
128
+ await updateEnvFile({ APP_PASSWORD_HASH: '' });
129
+ config_1.CONFIG.APP_PASSWORD_HASH = '';
130
+ return;
131
+ }
132
+ const hash = hashAppPassword(password);
133
+ await updateEnvFile({ APP_PASSWORD_HASH: hash });
134
+ config_1.CONFIG.APP_PASSWORD_HASH = hash;
135
+ }
136
+ function createLanSession() {
137
+ const token = crypto_1.default.randomBytes(32).toString('hex');
138
+ const expiry = Date.now() + SESSION_TTL_MS;
139
+ const sessions = loadSessions();
140
+ sessions[token] = expiry;
141
+ saveSessions(sessions);
142
+ return { token, expiry };
143
+ }
144
+ function validateLanSession(token) {
145
+ if (!token)
146
+ return false;
147
+ const sessions = loadSessions();
148
+ const expiry = sessions[token];
149
+ if (!expiry)
150
+ return false;
151
+ if (expiry < Date.now()) {
152
+ delete sessions[token];
153
+ saveSessions(sessions);
154
+ return false;
155
+ }
156
+ return true;
157
+ }
158
+ function revokeLanSession(token) {
159
+ if (!token)
160
+ return;
161
+ const sessions = loadSessions();
162
+ if (sessions[token]) {
163
+ delete sessions[token];
164
+ saveSessions(sessions);
165
+ }
166
+ }
167
+ function clearAllLanSessions() {
168
+ saveSessions({});
169
+ }
170
+ function normalizeIp(ip) {
171
+ if (!ip)
172
+ return '';
173
+ if (ip.startsWith('::ffff:'))
174
+ return ip.slice('::ffff:'.length);
175
+ return ip;
176
+ }
177
+ function isLoopbackRequest(req) {
178
+ const candidates = [req.ip, req.socket?.remoteAddress].map((v) => normalizeIp(v));
179
+ return candidates.includes('127.0.0.1') || candidates.includes('::1');
180
+ }
181
+ function getRequestToken(req) {
182
+ const auth = req.headers.authorization;
183
+ if (auth && auth.toLowerCase().startsWith('bearer ')) {
184
+ return auth.slice(7).trim() || null;
185
+ }
186
+ const cookieHeader = req.headers.cookie;
187
+ if (cookieHeader) {
188
+ const parts = cookieHeader.split(';');
189
+ for (const part of parts) {
190
+ const idx = part.indexOf('=');
191
+ if (idx === -1)
192
+ continue;
193
+ const name = part.slice(0, idx).trim();
194
+ if (name === exports.LAN_AUTH_COOKIE) {
195
+ return decodeURIComponent(part.slice(idx + 1).trim()) || null;
196
+ }
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ function isLanAuthenticated(req) {
202
+ if (!hasAppPassword())
203
+ return true;
204
+ return validateLanSession(getRequestToken(req));
205
+ }
206
+ const PUBLIC_PATHS = new Set([
207
+ '/api/auth/app-status',
208
+ '/api/auth/app-login',
209
+ '/api/auth/app-logout',
210
+ '/api/internal/shutdown',
211
+ ]);
212
+ function lanAuthMiddleware(req, res, next) {
213
+ if (!req.path.startsWith('/api/'))
214
+ return next();
215
+ if (PUBLIC_PATHS.has(req.path))
216
+ return next();
217
+ if (req.path.startsWith('/api/internal/') && isLoopbackRequest(req))
218
+ return next();
219
+ if (!hasAppPassword())
220
+ return next();
221
+ if (validateLanSession(getRequestToken(req)))
222
+ return next();
223
+ return res.status(401).json({ error: 'LAN_AUTH_REQUIRED' });
224
+ }
225
+ function buildLanCookie(token, expiry) {
226
+ const maxAge = Math.max(1, Math.floor((expiry - Date.now()) / 1000));
227
+ return `${exports.LAN_AUTH_COOKIE}=${encodeURIComponent(token)}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax`;
228
+ }
229
+ function clearLanCookie() {
230
+ return `${exports.LAN_AUTH_COOKIE}=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax`;
231
+ }
@@ -0,0 +1,133 @@
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.CONFIG = exports.SERVER_ROOT = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const dotenv_1 = __importDefault(require("dotenv"));
11
+ const shipped_defaults_1 = require("./shipped-defaults");
12
+ exports.SERVER_ROOT = path_1.default.resolve(__dirname, '..');
13
+ const PACKAGE_ROOT = path_1.default.resolve(exports.SERVER_ROOT, '..');
14
+ function resolveDataRoot() {
15
+ if (process.platform === 'win32' && process.env.APPDATA) {
16
+ return path_1.default.join(process.env.APPDATA, 'dango');
17
+ }
18
+ if (process.platform === 'darwin') {
19
+ return path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'dango');
20
+ }
21
+ if (process.env.XDG_DATA_HOME) {
22
+ return path_1.default.join(process.env.XDG_DATA_HOME, 'dango');
23
+ }
24
+ return path_1.default.join(os_1.default.homedir(), '.local', 'share', 'dango');
25
+ }
26
+ function moveFileIfNeeded(sourcePath, destinationPath) {
27
+ if (!fs_1.default.existsSync(sourcePath) || fs_1.default.existsSync(destinationPath)) {
28
+ return;
29
+ }
30
+ try {
31
+ fs_1.default.renameSync(sourcePath, destinationPath);
32
+ }
33
+ catch {
34
+ fs_1.default.copyFileSync(sourcePath, destinationPath);
35
+ fs_1.default.unlinkSync(sourcePath);
36
+ }
37
+ }
38
+ function migrateLegacyData(packageServerRoot, dataRoot) {
39
+ const legacyFiles = [
40
+ '.env',
41
+ 'google_tokens.json',
42
+ 'sync_manifest.json',
43
+ 'sync_manifest.dev.json',
44
+ 'anime.db',
45
+ 'anime.db-shm',
46
+ 'anime.db-wal',
47
+ 'anime.dev.db',
48
+ 'anime.dev.db-shm',
49
+ 'anime.dev.db-wal',
50
+ ];
51
+ fs_1.default.mkdirSync(dataRoot, { recursive: true });
52
+ for (const filename of legacyFiles) {
53
+ moveFileIfNeeded(path_1.default.join(packageServerRoot, filename), path_1.default.join(dataRoot, filename));
54
+ }
55
+ }
56
+ function resolveLegacyDataRoot() {
57
+ if (process.platform === 'win32' && process.env.APPDATA) {
58
+ return path_1.default.join(process.env.APPDATA, 'ani-web');
59
+ }
60
+ if (process.platform === 'darwin') {
61
+ return path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'ani-web');
62
+ }
63
+ if (process.env.XDG_DATA_HOME) {
64
+ return path_1.default.join(process.env.XDG_DATA_HOME, 'ani-web');
65
+ }
66
+ return path_1.default.join(os_1.default.homedir(), '.local', 'share', 'ani-web');
67
+ }
68
+ function migrateFromAniWeb(dataRoot) {
69
+ const legacyRoot = resolveLegacyDataRoot();
70
+ if (legacyRoot === dataRoot)
71
+ return;
72
+ if (!fs_1.default.existsSync(legacyRoot))
73
+ return;
74
+ const filesToMigrate = [
75
+ '.env',
76
+ 'google_tokens.json',
77
+ 'sync_manifest.json',
78
+ 'sync_manifest.dev.json',
79
+ 'anime.db',
80
+ 'anime.db-shm',
81
+ 'anime.db-wal',
82
+ 'anime.dev.db',
83
+ 'anime.dev.db-shm',
84
+ 'anime.dev.db-wal',
85
+ ];
86
+ fs_1.default.mkdirSync(dataRoot, { recursive: true });
87
+ for (const filename of filesToMigrate) {
88
+ moveFileIfNeeded(path_1.default.join(legacyRoot, filename), path_1.default.join(dataRoot, filename));
89
+ }
90
+ }
91
+ const DATA_ROOT = resolveDataRoot();
92
+ const ENV_PATH = path_1.default.join(DATA_ROOT, '.env');
93
+ migrateLegacyData(exports.SERVER_ROOT, DATA_ROOT);
94
+ migrateFromAniWeb(DATA_ROOT);
95
+ dotenv_1.default.config({ path: path_1.default.join(exports.SERVER_ROOT, '.env') });
96
+ dotenv_1.default.config({ path: ENV_PATH, override: true });
97
+ const IS_DEV = process.argv.includes('--dev');
98
+ const PORT = 3000;
99
+ const HOST = process.env.HOST || '0.0.0.0';
100
+ const GOOGLE_REDIRECT_URI = IS_DEV
101
+ ? 'http://localhost:5173/api/auth/google/callback'
102
+ : `http://localhost:${PORT}/api/auth/google/callback`;
103
+ exports.CONFIG = {
104
+ ROOT: DATA_ROOT,
105
+ SERVER_ROOT: exports.SERVER_ROOT,
106
+ PACKAGE_ROOT,
107
+ ENV_PATH,
108
+ TOKEN_PATH: path_1.default.join(DATA_ROOT, 'google_tokens.json'),
109
+ LOCAL_MANIFEST_PATH: path_1.default.join(DATA_ROOT, IS_DEV ? 'sync_manifest.dev.json' : 'sync_manifest.json'),
110
+ DB_NAME_PROD: 'anime.db',
111
+ DB_NAME_DEV: 'anime.dev.db',
112
+ REMOTE_FOLDER_PROD: 'dango_db',
113
+ REMOTE_FOLDER_DEV: 'dango_dev_db',
114
+ MANIFEST_FILENAME: IS_DEV ? 'sync_manifest.dev.json' : 'sync_manifest.json',
115
+ GOOGLE_SCOPES: [
116
+ 'https://www.googleapis.com/auth/drive.appdata',
117
+ 'https://www.googleapis.com/auth/drive.file',
118
+ 'https://www.googleapis.com/auth/userinfo.email',
119
+ ],
120
+ GOOGLE_SYNC_FILENAME: IS_DEV ? 'sync.dev.json' : 'sync.json',
121
+ RCLONE_SYNC_FILENAME: IS_DEV ? 'sync.dev.json' : 'sync.json',
122
+ IS_DEV,
123
+ PORT,
124
+ HOST,
125
+ GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
126
+ GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
127
+ GOOGLE_REDIRECT_URI: GOOGLE_REDIRECT_URI,
128
+ GOOGLE_AUTH_WORKER_URL: process.env.GOOGLE_AUTH_WORKER_URL || shipped_defaults_1.SHIPPED_DEFAULTS.GOOGLE_AUTH_WORKER_URL || '',
129
+ RCLONE_REMOTE: process.env.RCLONE_REMOTE,
130
+ SYNC_PROVIDER: process.env.SYNC_PROVIDER,
131
+ DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID || shipped_defaults_1.SHIPPED_DEFAULTS.DISCORD_CLIENT_ID || undefined,
132
+ APP_PASSWORD_HASH: process.env.APP_PASSWORD_HASH || '',
133
+ };
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.AuthController = void 0;
40
+ const logger_1 = __importDefault(require("../logger"));
41
+ const google_1 = require("../google");
42
+ const github_sync_1 = require("../github-sync");
43
+ const sync_1 = require("../sync");
44
+ const config_1 = require("../config");
45
+ const rclone_1 = require("../rclone");
46
+ class AuthController {
47
+ runSyncSequence;
48
+ constructor(runSyncSequence) {
49
+ this.runSyncSequence = runSyncSequence;
50
+ }
51
+ getConfigStatus = (_req, res) => {
52
+ // New flow: Worker holds ID+secret, no local .env needed.
53
+ // Legacy fallback: user-owned GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET.
54
+ const useWorker = !!config_1.CONFIG.GOOGLE_AUTH_WORKER_URL;
55
+ const hasLegacyConfig = !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET;
56
+ res.json({ hasConfig: useWorker || hasLegacyConfig, useWorker });
57
+ };
58
+ getGoogleAuthSettings = (_req, res) => {
59
+ res.json({
60
+ useWorker: !!config_1.CONFIG.GOOGLE_AUTH_WORKER_URL,
61
+ hasCustomWorkerUrl: !!process.env.GOOGLE_AUTH_WORKER_URL,
62
+ hasCustomClientId: !!process.env.GOOGLE_CLIENT_ID,
63
+ hasClientSecret: !!process.env.GOOGLE_CLIENT_SECRET,
64
+ });
65
+ };
66
+ updateGoogleAuthSettings = async (req, res) => {
67
+ const { clientId, clientSecret, workerUrl } = req.body;
68
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
69
+ const updates = {};
70
+ if (typeof clientId === 'string') {
71
+ updates.GOOGLE_CLIENT_ID = clientId;
72
+ }
73
+ if (typeof clientSecret === 'string') {
74
+ updates.GOOGLE_CLIENT_SECRET = clientSecret;
75
+ }
76
+ if (typeof workerUrl === 'string') {
77
+ updates.GOOGLE_AUTH_WORKER_URL = workerUrl;
78
+ }
79
+ await updateEnvFile(updates);
80
+ if (typeof workerUrl === 'string') {
81
+ ;
82
+ config_1.CONFIG.GOOGLE_AUTH_WORKER_URL = workerUrl;
83
+ }
84
+ if (typeof clientId === 'string') {
85
+ ;
86
+ config_1.CONFIG.GOOGLE_CLIENT_ID = clientId || undefined;
87
+ }
88
+ if (typeof clientSecret === 'string') {
89
+ ;
90
+ config_1.CONFIG.GOOGLE_CLIENT_SECRET =
91
+ clientSecret || undefined;
92
+ }
93
+ await (0, sync_1.initSyncProvider)();
94
+ res.json({ success: true });
95
+ };
96
+ getGitHubAuthOverride = (_req, res) => {
97
+ res.json({ hasCustomClientId: !!process.env.GITHUB_CLIENT_ID });
98
+ };
99
+ updateGitHubAuthSettings = async (req, res) => {
100
+ const { clientId } = req.body;
101
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
102
+ if (typeof clientId !== 'string') {
103
+ return res.status(400).json({ error: 'clientId required' });
104
+ }
105
+ await updateEnvFile({ GITHUB_CLIENT_ID: clientId });
106
+ res.json({ success: true });
107
+ };
108
+ getRcloneSettings = async (_req, res) => {
109
+ const remotes = await rclone_1.rcloneService.listRemotes();
110
+ res.json({
111
+ remote: config_1.CONFIG.RCLONE_REMOTE || '',
112
+ availableRemotes: remotes,
113
+ activeRemote: rclone_1.rcloneService.isActive() ? rclone_1.rcloneService.getRemoteName() : null,
114
+ });
115
+ };
116
+ getSyncSettings = async (_req, res) => {
117
+ const { getActiveProvider } = await Promise.resolve().then(() => __importStar(require('../sync')));
118
+ res.json({
119
+ activeProvider: process.env.SYNC_PROVIDER || 'default',
120
+ actualActiveProvider: getActiveProvider(),
121
+ authenticatedProviders: {
122
+ github: github_sync_1.githubSyncService.isAuthenticated(),
123
+ google: google_1.googleDriveService.isAuthenticated(),
124
+ rclone: rclone_1.rcloneService.isActive(),
125
+ },
126
+ });
127
+ };
128
+ updateSyncProvider = async (req, res) => {
129
+ const { provider } = req.body;
130
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
131
+ const value = provider === 'default' ? '' : provider;
132
+ await updateEnvFile({ SYNC_PROVIDER: value });
133
+ await (0, sync_1.initSyncProvider)();
134
+ res.json({ success: true, activeProvider: process.env.SYNC_PROVIDER || 'default' });
135
+ };
136
+ getGitHubAuthStatus = async (_req, res) => {
137
+ try {
138
+ const user = await github_sync_1.githubSyncService.getUserProfile();
139
+ res.json({
140
+ authenticated: !!user,
141
+ user,
142
+ device: github_sync_1.githubSyncService.getDeviceState(),
143
+ hasCustomClientId: !!process.env.GITHUB_CLIENT_ID,
144
+ });
145
+ }
146
+ catch (error) {
147
+ logger_1.default.error({ err: error }, 'Failed to fetch GitHub auth status');
148
+ res.json({
149
+ authenticated: false,
150
+ user: null,
151
+ device: github_sync_1.githubSyncService.getDeviceState(),
152
+ hasCustomClientId: !!process.env.GITHUB_CLIENT_ID,
153
+ });
154
+ }
155
+ };
156
+ startGitHubDeviceAuth = async (req, res) => {
157
+ const state = await github_sync_1.githubSyncService.startDeviceAuth(req.db, this.runSyncSequence);
158
+ res.json(state);
159
+ };
160
+ pollGitHubDeviceAuth = (_req, res) => {
161
+ res.json(github_sync_1.githubSyncService.getDeviceState());
162
+ };
163
+ logoutGitHub = async (_req, res) => {
164
+ await github_sync_1.githubSyncService.logout();
165
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
166
+ await updateEnvFile({ SYNC_PROVIDER: '' });
167
+ await (0, sync_1.initSyncProvider)();
168
+ res.json({ success: true });
169
+ };
170
+ updateRcloneSettings = async (req, res) => {
171
+ const { remote } = req.body;
172
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
173
+ await updateEnvFile({
174
+ RCLONE_REMOTE: remote,
175
+ SYNC_PROVIDER: 'rclone',
176
+ });
177
+ await this.runSyncSequence(req.db, 'rclone');
178
+ res.json({ success: true });
179
+ };
180
+ getAuthUrl = async (_req, res) => {
181
+ const url = await google_1.googleDriveService.getAuthUrl();
182
+ res.json({ url });
183
+ };
184
+ loginGoogle = async (req, res) => {
185
+ if (google_1.googleDriveService.isAuthenticated()) {
186
+ const user = await google_1.googleDriveService.getUserProfile();
187
+ if (user) {
188
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
189
+ await updateEnvFile({ SYNC_PROVIDER: 'google' });
190
+ await this.runSyncSequence(req.db, 'google');
191
+ return res.json({ url: null, authenticated: true });
192
+ }
193
+ else {
194
+ logger_1.default.warn('Google tokens found but invalid. Clearing and requesting new auth.');
195
+ await google_1.googleDriveService.logout();
196
+ }
197
+ }
198
+ const url = await google_1.googleDriveService.getAuthUrl();
199
+ res.json({ url, authenticated: false });
200
+ };
201
+ handleCallback = async (req, res) => {
202
+ const code = req.query.code;
203
+ if (!code) {
204
+ return res.status(400).send('No code provided');
205
+ }
206
+ await google_1.googleDriveService.handleCallback(code);
207
+ const user = await google_1.googleDriveService.getUserProfile();
208
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
209
+ await updateEnvFile({ SYNC_PROVIDER: 'google' });
210
+ logger_1.default.info('User logged in. Syncing database (please wait)...');
211
+ try {
212
+ await this.runSyncSequence(req.db, 'google');
213
+ }
214
+ catch (err) {
215
+ logger_1.default.error({ err }, 'Post-login sync failed');
216
+ }
217
+ const responseHtml = `
218
+ <html>
219
+ <body>
220
+ <h1>Authentication Successful</h1>
221
+ <p>Database synced. Closing window...</p>
222
+ <script>
223
+ if (window.opener) {
224
+ window.opener.postMessage({ type: 'GOOGLE_AUTH_SUCCESS', user: ${JSON.stringify(user)} }, window.location.origin);
225
+ window.close();
226
+ } else {
227
+ window.location.href = '/';
228
+ }
229
+ </script>
230
+ </body>
231
+ </html>
232
+ `;
233
+ res.send(responseHtml);
234
+ };
235
+ getUserProfile = async (_req, res) => {
236
+ const user = await google_1.googleDriveService.getUserProfile();
237
+ res.json(user);
238
+ };
239
+ logout = async (_req, res) => {
240
+ await google_1.googleDriveService.logout();
241
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
242
+ await updateEnvFile({ SYNC_PROVIDER: '' });
243
+ await (0, sync_1.initSyncProvider)();
244
+ res.json({ success: true });
245
+ };
246
+ }
247
+ exports.AuthController = AuthController;