ani-web 2.0.7

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +220 -0
  3. package/client/dist/assets/AnimeInfo-B88ZA3gl.js +1 -0
  4. package/client/dist/assets/AnimeInfo-R63luGTP.css +1 -0
  5. package/client/dist/assets/AnimeInfoPage-xGVarrXG.js +2 -0
  6. package/client/dist/assets/Button-Fq9KaUOg.css +1 -0
  7. package/client/dist/assets/Button-lkEUHIDS.js +1 -0
  8. package/client/dist/assets/ErrorMessage-BVWNgHMx.css +1 -0
  9. package/client/dist/assets/ErrorMessage-BXKDLzn8.js +1 -0
  10. package/client/dist/assets/Home-O1FbN8t_.js +1 -0
  11. package/client/dist/assets/Home-r0eYbWNc.css +1 -0
  12. package/client/dist/assets/Insights-CF4K-oO5.css +1 -0
  13. package/client/dist/assets/Insights-opcB-aTo.js +1 -0
  14. package/client/dist/assets/MAL-BGM33N5c.js +1 -0
  15. package/client/dist/assets/MAL-DeQNXXPx.css +1 -0
  16. package/client/dist/assets/Player-BarbgKjI.css +1 -0
  17. package/client/dist/assets/Player-CSyGax33.js +9 -0
  18. package/client/dist/assets/PlayerSettings-BaCVQyw6.js +1 -0
  19. package/client/dist/assets/PlayerSettings-l6aLKrHh.css +1 -0
  20. package/client/dist/assets/QueueRail-Cxn5U8kE.js +1 -0
  21. package/client/dist/assets/QueueRail-DOM_pWkE.css +1 -0
  22. package/client/dist/assets/RemoveConfirmationModal-BBiogSdf.css +1 -0
  23. package/client/dist/assets/RemoveConfirmationModal-DatCZQKq.js +1 -0
  24. package/client/dist/assets/Search-BzO-aRP7.css +1 -0
  25. package/client/dist/assets/Search-DJxo3BYH.js +1 -0
  26. package/client/dist/assets/SearchableSelect-BkCrf6E8.css +1 -0
  27. package/client/dist/assets/SearchableSelect-BzYsMz8B.js +1 -0
  28. package/client/dist/assets/Settings-Bv9fX-x3.css +1 -0
  29. package/client/dist/assets/Settings-C5adinOe.js +1 -0
  30. package/client/dist/assets/SynopsisText-C3AK-aRc.js +1 -0
  31. package/client/dist/assets/SynopsisText-DsI3mW5v.css +1 -0
  32. package/client/dist/assets/ToggleSwitch-BIlQxIjg.css +1 -0
  33. package/client/dist/assets/ToggleSwitch-CrXim14A.js +1 -0
  34. package/client/dist/assets/Watchlist-CXw0vbNx.js +1 -0
  35. package/client/dist/assets/Watchlist-a2RHQogs.css +1 -0
  36. package/client/dist/assets/hls.light-DcbkToIY.js +27 -0
  37. package/client/dist/assets/index-BzX_xmnf.css +1 -0
  38. package/client/dist/assets/index-Ciivz6fh.js +178 -0
  39. package/client/dist/assets/useAnimeInfoData-Dqthchpa.js +1 -0
  40. package/client/dist/assets/useIsMobile-BviODivc.js +1 -0
  41. package/client/dist/assets/vendor-Bc4EraM_.js +3 -0
  42. package/client/dist/favicon.ico +0 -0
  43. package/client/dist/index.html +35 -0
  44. package/client/dist/logo.png +0 -0
  45. package/client/dist/placeholder.svg +4 -0
  46. package/client/dist/robots.txt +3 -0
  47. package/client/package.json +58 -0
  48. package/orchestrator.js +323 -0
  49. package/package.json +88 -0
  50. package/server/.env +1 -0
  51. package/server/dist/config.js +89 -0
  52. package/server/dist/constants.json +1359 -0
  53. package/server/dist/controllers/auth.controller.js +215 -0
  54. package/server/dist/controllers/data.controller.js +232 -0
  55. package/server/dist/controllers/insights.controller.js +200 -0
  56. package/server/dist/controllers/proxy.controller.js +353 -0
  57. package/server/dist/controllers/settings.controller.js +159 -0
  58. package/server/dist/controllers/watchlist.controller.js +749 -0
  59. package/server/dist/db.js +152 -0
  60. package/server/dist/discord-rpc.js +279 -0
  61. package/server/dist/github-sync.js +342 -0
  62. package/server/dist/google.js +310 -0
  63. package/server/dist/logger.js +21 -0
  64. package/server/dist/providers/123anime.provider.js +226 -0
  65. package/server/dist/providers/allanime.provider.js +736 -0
  66. package/server/dist/providers/animepahe.provider.js +457 -0
  67. package/server/dist/providers/animeya.provider.js +787 -0
  68. package/server/dist/providers/megaplay.provider.js +264 -0
  69. package/server/dist/providers/provider.interface.js +2 -0
  70. package/server/dist/rclone.js +126 -0
  71. package/server/dist/repositories/insights.repository.js +42 -0
  72. package/server/dist/repositories/notifications.repository.js +30 -0
  73. package/server/dist/repositories/queue.repository.js +38 -0
  74. package/server/dist/repositories/settings.repository.js +14 -0
  75. package/server/dist/repositories/shows-meta.repository.js +41 -0
  76. package/server/dist/repositories/watched-episodes.repository.js +67 -0
  77. package/server/dist/repositories/watchlist.repository.js +80 -0
  78. package/server/dist/routes/auth.routes.js +26 -0
  79. package/server/dist/routes/data.routes.js +42 -0
  80. package/server/dist/routes/insights.routes.js +12 -0
  81. package/server/dist/routes/proxy.routes.js +14 -0
  82. package/server/dist/routes/settings.routes.js +27 -0
  83. package/server/dist/routes/watchlist.routes.js +46 -0
  84. package/server/dist/server.js +229 -0
  85. package/server/dist/sync-config.js +28 -0
  86. package/server/dist/sync.js +427 -0
  87. package/server/dist/utils/db-utils.js +15 -0
  88. package/server/dist/utils/env.utils.js +79 -0
  89. package/server/dist/utils/machine-id.js +46 -0
  90. package/server/dist/utils/request-context.js +5 -0
  91. package/server/package.json +19 -0
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WatchlistRepository = void 0;
4
+ const db_utils_1 = require("../utils/db-utils");
5
+ exports.WatchlistRepository = {
6
+ getById: (db, id) => (0, db_utils_1.dbGet)(db, 'SELECT * FROM watchlist WHERE id = ?', [id]),
7
+ exists: (db, id) => {
8
+ const row = (0, db_utils_1.dbGet)(db, 'SELECT EXISTS(SELECT 1 FROM watchlist WHERE id = ?) as inWatchlist', [id]);
9
+ return !!(row && row.inWatchlist);
10
+ },
11
+ getAll: (db, status, limit, offset) => {
12
+ let query = 'SELECT * FROM watchlist';
13
+ const params = [];
14
+ if (status && status !== 'All') {
15
+ query += ' WHERE status = ?';
16
+ params.push(status);
17
+ }
18
+ query += ' ORDER BY rowid DESC';
19
+ if (limit !== undefined && offset !== undefined) {
20
+ query += ' LIMIT ? OFFSET ?';
21
+ params.push(limit, offset);
22
+ }
23
+ return (0, db_utils_1.dbAll)(db, query, params);
24
+ },
25
+ getCount: (db, status) => {
26
+ let query = 'SELECT COUNT(*) as total FROM watchlist';
27
+ const params = [];
28
+ if (status && status !== 'All') {
29
+ query += ' WHERE status = ?';
30
+ params.push(status);
31
+ }
32
+ const row = (0, db_utils_1.dbGet)(db, query, params);
33
+ return row?.total || 0;
34
+ },
35
+ upsert: (db, data) => (0, db_utils_1.dbRun)(db, 'INSERT OR REPLACE INTO watchlist (id, name, thumbnail, status, nativeName, englishName, type) VALUES (?, ?, ?, ?, ?, ?, ?)', [
36
+ data.id,
37
+ data.name,
38
+ data.thumbnail,
39
+ data.status,
40
+ data.nativeName,
41
+ data.englishName,
42
+ data.type,
43
+ ]),
44
+ updateStatus: (db, id, status) => (0, db_utils_1.dbRun)(db, 'UPDATE watchlist SET status = ? WHERE id = ?', [status, id]),
45
+ updateType: (db, id, type) => (0, db_utils_1.dbRun)(db, 'UPDATE watchlist SET type = ? WHERE id = ?', [type, id]),
46
+ updateThumbnail: (db, id, thumbnail) => (0, db_utils_1.dbRun)(db, 'UPDATE watchlist SET thumbnail = ? WHERE id = ?', [thumbnail, id]),
47
+ delete: (db, id) => (0, db_utils_1.dbRun)(db, 'DELETE FROM watchlist WHERE id = ?', [id]),
48
+ getWatchingShows: (db) => (0, db_utils_1.dbAll)(db, "SELECT id, name, thumbnail, nativeName, englishName FROM watchlist WHERE status = 'Watching'"),
49
+ getShowsWithNewEpisodes: (db) => (0, db_utils_1.dbAll)(db, `SELECT
50
+ w.id,
51
+ w.name,
52
+ w.thumbnail,
53
+ w.nativeName,
54
+ w.englishName,
55
+ w.type,
56
+ sm.episodeCount,
57
+ sm.type as smType,
58
+ sm.status as smStatus,
59
+ dn.episodeNumber as latestDiscoveredEpisode
60
+ FROM watchlist w
61
+ JOIN discovered_notifications dn ON w.id = dn.showId
62
+ LEFT JOIN shows_meta sm ON w.id = sm.id
63
+ WHERE w.status = 'Watching'
64
+ AND dn.discoveredAt >= date('now', '-7 days')
65
+ AND NOT EXISTS (
66
+ SELECT 1 FROM watched_episodes we
67
+ WHERE we.showId = w.id AND we.episodeNumber = dn.episodeNumber
68
+ )
69
+ AND dn.discoveredAt = (
70
+ SELECT MAX(dn2.discoveredAt)
71
+ FROM discovered_notifications dn2
72
+ WHERE dn2.showId = dn.showId
73
+ AND dn2.discoveredAt >= date('now', '-7 days')
74
+ AND NOT EXISTS (
75
+ SELECT 1 FROM watched_episodes we2
76
+ WHERE we2.showId = dn2.showId AND we2.episodeNumber = dn2.episodeNumber
77
+ )
78
+ )
79
+ ORDER BY dn.discoveredAt DESC`),
80
+ };
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAuthRouter = createAuthRouter;
4
+ const express_1 = require("express");
5
+ const auth_controller_1 = require("../controllers/auth.controller");
6
+ function createAuthRouter(runSyncSequence) {
7
+ const router = (0, express_1.Router)();
8
+ const controller = new auth_controller_1.AuthController(runSyncSequence);
9
+ router.get('/config-status', controller.getConfigStatus);
10
+ router.get('/google-auth', controller.getGoogleAuthSettings);
11
+ router.post('/google-auth', controller.updateGoogleAuthSettings);
12
+ router.get('/github/status', controller.getGitHubAuthStatus);
13
+ router.post('/github/start', controller.startGitHubDeviceAuth);
14
+ router.get('/github/poll', controller.pollGitHubDeviceAuth);
15
+ router.post('/github/logout', controller.logoutGitHub);
16
+ router.get('/settings/rclone', controller.getRcloneSettings);
17
+ router.post('/settings/rclone', controller.updateRcloneSettings);
18
+ router.get('/settings/sync', controller.getSyncSettings);
19
+ router.post('/settings/sync', controller.updateSyncProvider);
20
+ router.get('/google', controller.getAuthUrl);
21
+ router.post('/google/login', controller.loginGoogle);
22
+ router.get('/google/callback', controller.handleCallback);
23
+ router.get('/user', controller.getUserProfile);
24
+ router.post('/logout', controller.logout);
25
+ return router;
26
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDataRouter = createDataRouter;
4
+ const express_1 = require("express");
5
+ const data_controller_1 = require("../controllers/data.controller");
6
+ function makeCacheMiddleware(cache, keyFn, ttl, validate = (d) => Array.isArray(d) && d.length > 0) {
7
+ return (req, res, next) => {
8
+ const cacheKey = keyFn(req);
9
+ const cached = cache.get(cacheKey);
10
+ if (cached)
11
+ return res.json(cached);
12
+ const originalJson = res.json.bind(res);
13
+ res.json = (data) => {
14
+ if (validate(data)) {
15
+ if (ttl !== undefined) {
16
+ cache.set(cacheKey, data, ttl);
17
+ }
18
+ else {
19
+ cache.set(cacheKey, data);
20
+ }
21
+ }
22
+ return originalJson(data);
23
+ };
24
+ next();
25
+ };
26
+ }
27
+ function createDataRouter(apiCache, providers) {
28
+ const router = (0, express_1.Router)();
29
+ const controller = new data_controller_1.DataController(providers);
30
+ router.get('/popular/:timeframe', makeCacheMiddleware(apiCache, (req) => `popular-${req.params.timeframe.toLowerCase()}-${req.query.page || 1}-${req.query.size || 10}`), controller.getPopular);
31
+ router.get('/schedule/:date', makeCacheMiddleware(apiCache, (req) => `schedule-${req.params.date}`), controller.getSchedule);
32
+ router.get('/latest-releases', makeCacheMiddleware(apiCache, (req) => `latest-releases-${req.query.page || 1}-${req.query.size || 14}`, 300), controller.getLatestReleases);
33
+ router.get('/search', makeCacheMiddleware(apiCache, (req) => `search-${JSON.stringify(req.query)}`, 1800), controller.search);
34
+ router.get('/skip-times/:showId/:episodeNumber', controller.getSkipTimes);
35
+ router.get('/video', controller.getVideo);
36
+ router.get('/episodes', controller.getEpisodes);
37
+ router.get('/seasonal', makeCacheMiddleware(apiCache, (req) => `seasonal-14-${req.query.page || 1}`, 1800), controller.getSeasonal);
38
+ router.get('/show-meta/:id', makeCacheMiddleware(apiCache, (req) => `meta-${req.params.id}-${req.query.provider || 'allanime'}`, 3600, (d) => !!d &&
39
+ (typeof d.bannerImage === 'string' || typeof d.description === 'string')), controller.getShowMeta);
40
+ router.get('/genres-and-tags', controller.getGenresAndTags);
41
+ return router;
42
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createInsightsRouter = createInsightsRouter;
4
+ const express_1 = require("express");
5
+ const insights_controller_1 = require("../controllers/insights.controller");
6
+ function createInsightsRouter(provider) {
7
+ const router = (0, express_1.Router)();
8
+ const controller = new insights_controller_1.InsightsController(provider);
9
+ router.get('/insights', controller.getWatchInsights);
10
+ router.get('/insights/genre-cards', controller.getGenreCards);
11
+ return router;
12
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createProxyRouter = createProxyRouter;
4
+ const express_1 = require("express");
5
+ const proxy_controller_1 = require("../controllers/proxy.controller");
6
+ function createProxyRouter() {
7
+ const router = (0, express_1.Router)();
8
+ const controller = new proxy_controller_1.ProxyController();
9
+ router.get('/proxy', controller.handleProxy);
10
+ router.get('/embed-proxy', controller.handleEmbedProxy);
11
+ router.get('/subtitle-proxy', controller.handleSubtitleProxy);
12
+ router.get('/image-proxy', controller.handleImageProxy);
13
+ return router;
14
+ }
@@ -0,0 +1,27 @@
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.createSettingsRouter = createSettingsRouter;
7
+ const express_1 = require("express");
8
+ const settings_controller_1 = require("../controllers/settings.controller");
9
+ const multer_1 = __importDefault(require("multer"));
10
+ const config_1 = require("../config");
11
+ function createSettingsRouter(provider, getDb, initializeDatabase, setDb) {
12
+ const router = (0, express_1.Router)();
13
+ const controller = new settings_controller_1.SettingsController(provider);
14
+ router.get('/settings', controller.getSettings);
15
+ router.post('/settings', controller.updateSettings);
16
+ router.get('/backup-db', controller.backupDatabase);
17
+ router.get('/installation-id', controller.getInstallationId);
18
+ const restoreStorage = (0, multer_1.default)({
19
+ storage: multer_1.default.diskStorage({
20
+ destination: (_req, _f, cb) => cb(null, config_1.CONFIG.ROOT),
21
+ filename: (_r, _f, cb) => cb(null, `restore_temp.db`),
22
+ }),
23
+ });
24
+ router.post('/restore-db', restoreStorage.single('dbfile'), (req, res) => controller.restoreDatabase(req, res, getDb(), initializeDatabase, setDb));
25
+ router.post('/import/mal-xml', (0, multer_1.default)().single('xmlfile'), controller.importMalXml);
26
+ return router;
27
+ }
@@ -0,0 +1,46 @@
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
+ function createWatchlistRouter(allAnime, animePahe) {
8
+ const router = (0, express_1.Router)();
9
+ const controller = new watchlist_controller_1.WatchlistController({ allAnime, animePahe });
10
+ router.get('/continue-watching', controller.getContinueWatching);
11
+ router.get('/continue-watching/fast', controller.getContinueWatchingFast);
12
+ router.get('/continue-watching/up-next', controller.getContinueWatchingUpNext);
13
+ router.get('/continue-watching/all', controller.getAllContinueWatching);
14
+ router.get('/continue-watching/this-week', controller.getThisWeekSchedule);
15
+ router.post('/continue-watching/remove', controller.removeContinueWatching);
16
+ router.post('/update-progress', controller.updateProgress);
17
+ router.get('/watchlist', controller.getWatchlist);
18
+ router.get('/watchlist/check/:showId', controller.checkWatchlist);
19
+ router.post('/watchlist/add', controller.addToWatchlist);
20
+ router.post('/watchlist/remove', controller.removeFromWatchlist);
21
+ router.post('/watchlist/status', controller.updateWatchlistStatus);
22
+ router.get('/queue', controller.getQueue);
23
+ router.get('/queue/suggested/:showId', controller.getSuggestedQueueEpisode);
24
+ router.post('/queue/add', controller.addToQueue);
25
+ router.post('/queue/remove', controller.removeFromQueue);
26
+ router.post('/queue/clear', controller.clearQueue);
27
+ router.post('/queue/reorder', controller.reorderQueue);
28
+ router.get('/episode-progress/:showId/:episodeNumber', controller.getEpisodeProgress);
29
+ router.get('/watched-episodes/:showId', controller.getWatchedEpisodes);
30
+ router.get('/notifications', controller.getNotifications);
31
+ router.post('/notifications/dismiss', controller.dismissNotification);
32
+ router.post('/notifications/clear-all', controller.clearAllNotifications);
33
+ router.post('/discord/clear', (req, res) => {
34
+ const { sessionId } = req.body;
35
+ discord_rpc_1.discordRPCService.clearPresence(sessionId);
36
+ res.json({ success: true });
37
+ });
38
+ router.post('/discord/status', (req, res) => {
39
+ const { page } = req.body;
40
+ if (typeof page === 'string') {
41
+ discord_rpc_1.discordRPCService.setIdleStatus(page);
42
+ }
43
+ res.json({ success: true });
44
+ });
45
+ return router;
46
+ }
@@ -0,0 +1,229 @@
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 chokidar_1 = __importDefault(require("chokidar"));
16
+ const logger_1 = __importDefault(require("./logger"));
17
+ const allanime_provider_1 = require("./providers/allanime.provider");
18
+ const _123anime_provider_1 = require("./providers/123anime.provider");
19
+ const animeya_provider_1 = require("./providers/animeya.provider");
20
+ const megaplay_provider_1 = require("./providers/megaplay.provider");
21
+ const animepahe_provider_1 = require("./providers/animepahe.provider");
22
+ const config_1 = require("./config");
23
+ const sync_1 = require("./sync");
24
+ const auth_routes_1 = require("./routes/auth.routes");
25
+ const watchlist_routes_1 = require("./routes/watchlist.routes");
26
+ const data_routes_1 = require("./routes/data.routes");
27
+ const proxy_routes_1 = require("./routes/proxy.routes");
28
+ const settings_routes_1 = require("./routes/settings.routes");
29
+ const insights_routes_1 = require("./routes/insights.routes");
30
+ const discord_rpc_1 = require("./discord-rpc");
31
+ const settings_repository_1 = require("./repositories/settings.repository");
32
+ const request_context_1 = require("./utils/request-context");
33
+ const app = (0, express_1.default)();
34
+ app.use((req, res, next) => {
35
+ const store = new Map();
36
+ if (req.headers['x-animepahe-ua']) {
37
+ store.set('ua', req.headers['x-animepahe-ua']);
38
+ }
39
+ if (req.headers['x-animepahe-cookie']) {
40
+ store.set('cookie', req.headers['x-animepahe-cookie']);
41
+ }
42
+ request_context_1.requestContext.run(store, next);
43
+ });
44
+ const apiCache = new node_cache_1.default({ stdTTL: 3600 });
45
+ const allAnimeProvider = new allanime_provider_1.AllAnimeProvider(apiCache);
46
+ const _123AnimeProvider = new _123anime_provider_1._123AnimeProvider(apiCache);
47
+ const animeyaProvider = new animeya_provider_1.AnimeyaProvider(apiCache);
48
+ const megaPlayProvider = new megaplay_provider_1.MegaPlayProvider(apiCache);
49
+ const animepaheProvider = new animepahe_provider_1.AnimePaheProvider(apiCache);
50
+ const providers = {
51
+ allanime: allAnimeProvider,
52
+ '123anime': _123AnimeProvider,
53
+ animeya: animeyaProvider,
54
+ megaplay: megaPlayProvider,
55
+ animepahe: animepaheProvider,
56
+ };
57
+ let db;
58
+ let isShuttingDown = false;
59
+ async function runSyncSequence(database, preferredProvider) {
60
+ const dbName = config_1.CONFIG.IS_DEV ? config_1.CONFIG.DB_NAME_DEV : config_1.CONFIG.DB_NAME_PROD;
61
+ const dbPath = path_1.default.join(config_1.CONFIG.ROOT, dbName);
62
+ const remoteFolder = config_1.CONFIG.IS_DEV ? config_1.CONFIG.REMOTE_FOLDER_DEV : config_1.CONFIG.REMOTE_FOLDER_PROD;
63
+ await (0, sync_1.initSyncProvider)(preferredProvider);
64
+ const didDownload = await (0, sync_1.syncDownOnBoot)(database, dbPath, remoteFolder, () => {
65
+ return new Promise((resolve) => {
66
+ if (database && !database.isClosedCheck()) {
67
+ database.checkpoint();
68
+ database.close(() => resolve());
69
+ }
70
+ else {
71
+ resolve();
72
+ }
73
+ });
74
+ });
75
+ let currentDb = database;
76
+ if (didDownload) {
77
+ db = await (0, sync_1.initializeDatabase)(dbPath);
78
+ currentDb = db;
79
+ logger_1.default.info('Database re-initialized after sync.');
80
+ }
81
+ try {
82
+ await (0, sync_1.syncUp)(currentDb, dbPath, remoteFolder);
83
+ }
84
+ catch (err) {
85
+ logger_1.default.error({ err }, 'Sync up on boot failed');
86
+ }
87
+ }
88
+ app.use((req, res, next) => {
89
+ if (isShuttingDown) {
90
+ return res.status(503).send('Server is shutting down...');
91
+ }
92
+ if (!db) {
93
+ return res.status(503).send('Database initializing...');
94
+ }
95
+ req.db = db;
96
+ next();
97
+ });
98
+ app.use((0, compression_1.default)({
99
+ level: 2,
100
+ threshold: 1024,
101
+ filter: (req, res) => {
102
+ if (req.headers['x-no-compression']) {
103
+ return false;
104
+ }
105
+ return compression_1.default.filter(req, res);
106
+ },
107
+ }));
108
+ app.use((0, cors_1.default)());
109
+ app.use(express_1.default.json({ limit: '10mb' }));
110
+ app.use('/api/auth', (0, auth_routes_1.createAuthRouter)((database) => runSyncSequence(database)));
111
+ app.use('/api', (0, watchlist_routes_1.createWatchlistRouter)(allAnimeProvider, animepaheProvider));
112
+ app.use('/api', (0, data_routes_1.createDataRouter)(apiCache, providers));
113
+ app.use('/api', (0, proxy_routes_1.createProxyRouter)());
114
+ app.use('/api', (0, insights_routes_1.createInsightsRouter)(allAnimeProvider));
115
+ app.use('/api', (0, settings_routes_1.createSettingsRouter)(allAnimeProvider, () => db, sync_1.initializeDatabase, (newDb) => {
116
+ db = newDb;
117
+ }));
118
+ if (!config_1.CONFIG.IS_DEV) {
119
+ const frontendPath = path_1.default.join(config_1.CONFIG.PACKAGE_ROOT, 'client', 'dist');
120
+ logger_1.default.info(`Serving frontend from: ${frontendPath}`);
121
+ app.use(express_1.default.static(frontendPath));
122
+ app.get(/^(?!\/api).+/, (req, res) => {
123
+ res.sendFile('index.html', { root: frontendPath }, (err) => {
124
+ if (err) {
125
+ logger_1.default.error({ err }, `Failed to serve index.html from ${frontendPath}`);
126
+ if (!res.headersSent) {
127
+ res.status(500).send('Server Error: Frontend build not found.');
128
+ }
129
+ }
130
+ });
131
+ });
132
+ }
133
+ app.use((err, req, res, next) => {
134
+ logger_1.default.error({ err, url: req.url, method: req.method }, 'Unhandled error');
135
+ if (res.headersSent) {
136
+ return next(err);
137
+ }
138
+ res.status(err.status || 500).json({
139
+ error: err.message || 'Internal Server Error',
140
+ status: err.status || 500,
141
+ });
142
+ });
143
+ async function main() {
144
+ const dbName = config_1.CONFIG.IS_DEV ? config_1.CONFIG.DB_NAME_DEV : config_1.CONFIG.DB_NAME_PROD;
145
+ const dbPath = path_1.default.join(config_1.CONFIG.ROOT, dbName);
146
+ const remoteFolder = config_1.CONFIG.IS_DEV ? config_1.CONFIG.REMOTE_FOLDER_DEV : config_1.CONFIG.REMOTE_FOLDER_PROD;
147
+ db = await (0, sync_1.initializeDatabase)(dbPath);
148
+ logger_1.default.info(`Database initialized at ${dbPath}`);
149
+ const rpcEnabledSetting = await settings_repository_1.SettingsRepository.getByKey(db, 'discordRPCEnabled');
150
+ const isRpcEnabled = rpcEnabledSetting ? rpcEnabledSetting.value === 'true' : true;
151
+ discord_rpc_1.discordRPCService.setEnabled(isRpcEnabled);
152
+ await runSyncSequence(db);
153
+ if (!fs_1.default.existsSync(config_1.CONFIG.LOCAL_MANIFEST_PATH)) {
154
+ fs_1.default.writeFileSync(config_1.CONFIG.LOCAL_MANIFEST_PATH, JSON.stringify({ version: 0 }));
155
+ }
156
+ let hasUnsyncedChanges = false;
157
+ const watcher = chokidar_1.default.watch(config_1.CONFIG.LOCAL_MANIFEST_PATH, {
158
+ persistent: true,
159
+ ignoreInitial: true,
160
+ });
161
+ const expressServer = app.listen(config_1.CONFIG.PORT, () => {
162
+ logger_1.default.info(`Server running on http://localhost:${config_1.CONFIG.PORT}`);
163
+ });
164
+ watcher.on('change', () => {
165
+ hasUnsyncedChanges = true;
166
+ });
167
+ const syncInterval = setInterval(async () => {
168
+ if (hasUnsyncedChanges) {
169
+ logger_1.default.info('Uploading accumulated database changes...');
170
+ hasUnsyncedChanges = false;
171
+ try {
172
+ await (0, sync_1.syncUp)(db, dbPath, remoteFolder);
173
+ }
174
+ catch (err) {
175
+ logger_1.default.error({ err }, 'Failed to upload database changes');
176
+ hasUnsyncedChanges = true;
177
+ }
178
+ }
179
+ }, 300000);
180
+ const shutdown = async (signal) => {
181
+ if (isShuttingDown)
182
+ return;
183
+ isShuttingDown = true;
184
+ clearInterval(syncInterval);
185
+ discord_rpc_1.discordRPCService.disconnect();
186
+ await watcher.close();
187
+ if (expressServer) {
188
+ expressServer.close();
189
+ }
190
+ if (hasUnsyncedChanges) {
191
+ logger_1.default.info('Sync on shutdown: uploading final database changes...');
192
+ hasUnsyncedChanges = false;
193
+ try {
194
+ await (0, sync_1.syncUp)(db, dbPath, remoteFolder);
195
+ }
196
+ catch (e) {
197
+ console.error('Final sync on shutdown failed:', e);
198
+ }
199
+ }
200
+ await (0, sync_1.waitForSync)();
201
+ db.close(() => {
202
+ console.log('[SERVER_EXIT]');
203
+ setTimeout(() => {
204
+ if (signal === 'SIGUSR2') {
205
+ process.kill(process.pid, 'SIGUSR2');
206
+ }
207
+ else {
208
+ process.exit(0);
209
+ }
210
+ }, 600);
211
+ });
212
+ };
213
+ process.on('SIGINT', () => shutdown('SIGINT'));
214
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
215
+ process.once('SIGUSR2', () => shutdown('SIGUSR2'));
216
+ app.post('/api/internal/shutdown', (req, res) => {
217
+ if (req.ip === '::1' || req.ip === '127.0.0.1' || req.ip === '::ffff:127.0.0.1') {
218
+ res.status(200).json({ message: 'Shutting down' });
219
+ setTimeout(() => shutdown(), 500);
220
+ }
221
+ else {
222
+ res.status(403).send('Forbidden');
223
+ }
224
+ });
225
+ }
226
+ main().catch((err) => {
227
+ console.error('Server failed to start:', err);
228
+ process.exit(1);
229
+ });
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setActiveRemote = setActiveRemote;
4
+ exports.getActiveRemote = getActiveRemote;
5
+ exports.initialize = initialize;
6
+ exports.getRemoteString = getRemoteString;
7
+ const log = (message) => console.log(`[Sync Config] ${new Date().toISOString()} - ${message}`);
8
+ let activeRemote;
9
+ function setActiveRemote(remote) {
10
+ log(`Setting active sync remote to: ${remote}`);
11
+ activeRemote = remote;
12
+ }
13
+ function getActiveRemote() {
14
+ return activeRemote;
15
+ }
16
+ async function initialize() {
17
+ if (activeRemote !== 'gdrive') {
18
+ log('Active remote is not gdrive, skipping gdrive-specific initialization.');
19
+ return;
20
+ }
21
+ log('gdrive is the active remote.');
22
+ }
23
+ function getRemoteString(remoteDir) {
24
+ if (!activeRemote) {
25
+ throw new Error('Cannot get remote string: active remote is not set.');
26
+ }
27
+ return `${activeRemote}:${remoteDir}`;
28
+ }