@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,416 @@
1
+ #!/usr/bin/env node
2
+ const { spawn, execSync } = require('child_process')
3
+ const readline = require('readline')
4
+ const http = require('http')
5
+ const os = require('os')
6
+ const path = require('path')
7
+ const crypto = require('crypto')
8
+ const axios = require('axios')
9
+
10
+ const shutdownToken = crypto.randomBytes(32).toString('hex')
11
+ const mode = process.argv[2] || 'prod'
12
+ const isWin = os.platform() === 'win32'
13
+ const npmCmd = isWin ? 'npm.cmd' : 'npm'
14
+
15
+ const colors = {
16
+ reset: '\x1b[0m',
17
+ server: '\x1b[36m',
18
+ client: '\x1b[32m',
19
+ system: '\x1b[33m',
20
+ }
21
+
22
+ if (mode === '--version' || mode === '-v') {
23
+ const pkg = require('./package.json')
24
+ console.log(`dango version ${pkg.version}`)
25
+ process.exit(0)
26
+ }
27
+
28
+ if (mode === '--help' || mode === '-h') {
29
+ console.log('Usage: dango [dev|prod] [--no-update] [--version] [--help]')
30
+ console.log(' dev Run with hot-reload (server + client)')
31
+ console.log(' prod Run pre-built version (default)')
32
+ console.log(' --no-update Skip update checks')
33
+ console.log(' --version, -v Print version and exit')
34
+ console.log(' --help, -h Show this help and exit')
35
+ process.exit(0)
36
+ }
37
+
38
+ async function checkForUpdates() {
39
+ if (process.argv.includes('--no-update') || mode === 'dev') return
40
+
41
+ try {
42
+ const npmGlobalPrefix = execSync(`${npmCmd} config get prefix`, {
43
+ encoding: 'utf8',
44
+ }).trim()
45
+ const scriptPath = path.resolve(__dirname)
46
+ const isGlobalInstall = scriptPath.includes(npmGlobalPrefix)
47
+
48
+ const pkg = require('./package.json')
49
+ const current = pkg.version
50
+
51
+ if (isGlobalInstall) {
52
+ const { data } = await axios.get('https://registry.npmjs.org/@serifpersia/dango/latest', {
53
+ timeout: 3000,
54
+ headers: { 'User-Agent': 'dango-cli' },
55
+ })
56
+ const latest = data.version
57
+
58
+ if (current !== latest) {
59
+ console.log(
60
+ `\n${colors.system}[Update]${colors.reset} ` +
61
+ `New version ${colors.client}${latest}${colors.reset} available (current: ${current})`
62
+ )
63
+
64
+ if (process.stdin.isTTY) {
65
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
66
+ const answer = await new Promise((resolve) => {
67
+ rl.question(
68
+ `${colors.system}[Update]${colors.reset} Would you like to perform a clean install now? (y/N) `,
69
+ (ans) => {
70
+ rl.close()
71
+ resolve(ans.toLowerCase())
72
+ }
73
+ )
74
+ })
75
+
76
+ if (answer === 'y' || answer === 'yes') {
77
+ console.log(`${colors.system}[Update]${colors.reset} Updating dango...`)
78
+ try {
79
+ execSync(`${npmCmd} install -g @serifpersia/dango@latest`, {
80
+ stdio: 'inherit',
81
+ })
82
+ console.log(
83
+ `\n${colors.system}[Update]${colors.reset} Update successful! Please restart dango to apply changes.`
84
+ )
85
+ process.exit(0)
86
+ } catch (err) {
87
+ console.error(
88
+ `\n${colors.system}[Update]${colors.reset} Update failed: ${err.message}`
89
+ )
90
+ if (!isWin) {
91
+ console.log(
92
+ `${colors.system}[Update]${colors.reset} Hint: If you encounter EACCES permission errors, configure npm global directory without root:`
93
+ )
94
+ console.log(
95
+ `${colors.system}[Update]${colors.reset} ${colors.client}npm config set prefix ~/.npm-global${colors.reset}\n`
96
+ )
97
+ }
98
+ console.log(
99
+ `${colors.system}[Update]${colors.reset} Continuing with current version...\n`
100
+ )
101
+ }
102
+ } else {
103
+ console.log(
104
+ `${colors.system}[Update]${colors.reset} Continuing with version ${current}...\n`
105
+ )
106
+ }
107
+ if (process.stdin.isTTY) process.stdin.resume()
108
+ } else {
109
+ console.log(
110
+ `${colors.system}[Update]${colors.reset} Run: npm install -g @serifpersia/dango to update.\n`
111
+ )
112
+ }
113
+ }
114
+ } else {
115
+ const { data } = await axios.get(
116
+ 'https://api.github.com/repos/serifpersia/dango/releases/latest',
117
+ { timeout: 3000, headers: { 'User-Agent': 'dango-cli' } }
118
+ )
119
+ const remoteVersion = (data.name && data.name.match(/v(\d+\.\d+\.\d+)/)?.[1]) || null
120
+ if (remoteVersion) {
121
+ if (remoteVersion !== current) {
122
+ console.log(`\n${colors.system}====================================================`)
123
+ console.log(
124
+ `${colors.system}[Update Available]${colors.reset} New version ${remoteVersion} found (current: ${current})!`
125
+ )
126
+ console.log(
127
+ `Please download the latest release: ${colors.client}${data.html_url}${colors.reset}`
128
+ )
129
+ console.log(`Replace your current files with the new ones from the zip.`)
130
+ console.log(
131
+ `${colors.system}====================================================\n${colors.reset}`
132
+ )
133
+ }
134
+ return
135
+ }
136
+ if (!pkg.versionDate) return
137
+
138
+ const latestDate = new Date(data.published_at)
139
+ const pkgDate = new Date(pkg.versionDate)
140
+
141
+ if (latestDate > pkgDate) {
142
+ console.log(`\n${colors.system}====================================================`)
143
+ console.log(`${colors.system}[Update Available]${colors.reset} New version found!`)
144
+ console.log(
145
+ `Please download the latest release: ${colors.client}${data.html_url}${colors.reset}`
146
+ )
147
+ console.log(`Replace your current files with the new ones from the zip.`)
148
+ console.log(
149
+ `${colors.system}====================================================\n${colors.reset}`
150
+ )
151
+ }
152
+ }
153
+ } catch (error) {
154
+ // Silently ignore network/registry errors
155
+ }
156
+ }
157
+
158
+ const SERVER_DIR = path.join(__dirname, 'server')
159
+ const CLIENT_DIR = path.join(__dirname, 'client')
160
+
161
+ let syncSpinner = null
162
+ let syncMessage = ''
163
+ let syncDots = 0
164
+
165
+ const startSpinner = (msg) => {
166
+ stopSpinner()
167
+ syncMessage = msg
168
+ syncDots = 0
169
+ process.stdout.write(`${colors.system}[System]${colors.reset} ${msg}`)
170
+ syncSpinner = setInterval(() => {
171
+ syncDots = (syncDots + 1) % 4
172
+ process.stdout.write(
173
+ `\r${colors.system}[System]${colors.reset} ${msg}${'.'.repeat(syncDots)}${' '.repeat(3 - syncDots)}`
174
+ )
175
+ }, 400)
176
+ }
177
+
178
+ const stopSpinner = () => {
179
+ if (syncSpinner) {
180
+ clearInterval(syncSpinner)
181
+ syncSpinner = null
182
+ process.stdout.write('\n')
183
+ }
184
+ }
185
+
186
+ const log = (prefix, color, data) => {
187
+ const str = data.toString()
188
+
189
+ if (str.includes('[SYNC_START]')) {
190
+ const parts = str.split('[SYNC_START]')
191
+ if (parts[1]) startSpinner(parts[1].split('\n')[0].trim())
192
+ }
193
+ if (str.includes('[SYNC_END]')) {
194
+ stopSpinner()
195
+ }
196
+
197
+ if (str.includes('[SERVER_EXIT]')) {
198
+ isShuttingDown = true
199
+ stopSpinner()
200
+ console.log(
201
+ `${colors.system}[System]${colors.reset} Server sync complete. Shutting down cleanly.`
202
+ )
203
+ terminateProcess(serverProcess)
204
+ terminateProcess(clientProcess)
205
+ setTimeout(() => process.exit(0), 2000)
206
+ return
207
+ }
208
+
209
+ if (str.includes('[SYNC_START]') || str.includes('[SYNC_END]')) {
210
+ // Fall through so remaining buffered lines are not swallowed
211
+ }
212
+
213
+ const lines = str.split('\n').filter((line) => line.trim() !== '')
214
+ if (lines.length === 0) return
215
+
216
+ if (syncSpinner) {
217
+ process.stdout.write('\r\x1b[K')
218
+ for (const line of lines) {
219
+ console.log(`${color}[${prefix}]${colors.reset} ${line}`)
220
+ }
221
+
222
+ process.stdout.write(
223
+ `${colors.system}[System]${colors.reset} ${syncMessage}${'.'.repeat(syncDots)}${' '.repeat(3 - syncDots)}`
224
+ )
225
+ } else {
226
+ for (const line of lines) {
227
+ console.log(`${color}[${prefix}]${colors.reset} ${line}`)
228
+ }
229
+ }
230
+ }
231
+
232
+ const spawnOpts = (cwd, extraEnv, withIpc = false) => ({
233
+ // 'ipc' gives the child a structured message channel (see server/src/lib/ipc.ts).
234
+ // It only works for directly spawned node processes (prod); under `npm run`
235
+ // (dev) there is no IPC forwarding, so stdout tags remain the fallback signal.
236
+ stdio: withIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : 'pipe',
237
+ shell: false,
238
+ cwd,
239
+ detached: !isWin,
240
+ windowsHide: true,
241
+ env: { ...process.env, ...(extraEnv || {}) },
242
+ })
243
+
244
+ const spawnNpm = (args, cwd, env) => {
245
+ if (isWin) {
246
+ return spawn('cmd.exe', ['/c', npmCmd, ...args], spawnOpts(cwd, env))
247
+ }
248
+ return spawn(npmCmd, args, spawnOpts(cwd, env))
249
+ }
250
+
251
+ const terminateProcess = (proc, signal = 'SIGTERM') => {
252
+ if (!proc || !proc.pid) return
253
+ if (isWin) {
254
+ try {
255
+ spawn('taskkill', ['/pid', String(proc.pid), '/f', '/t'], {
256
+ stdio: 'ignore',
257
+ windowsHide: true,
258
+ })
259
+ } catch (err) {
260
+ console.error(
261
+ `${colors.system}[System]${colors.reset} Failed to kill pid ${proc.pid}: ${err.message}`
262
+ )
263
+ }
264
+ return
265
+ }
266
+ try {
267
+ process.kill(-proc.pid, signal)
268
+ } catch (err) {
269
+ // ESRCH just means the process group is already gone — not an error.
270
+ if (err.code !== 'ESRCH') {
271
+ console.error(
272
+ `${colors.system}[System]${colors.reset} Failed to signal ${proc.pid} (${signal}): ${err.message}`
273
+ )
274
+ }
275
+ }
276
+ }
277
+ let serverProcess, clientProcess
278
+ let isShuttingDown = false
279
+
280
+ async function main() {
281
+ console.log(
282
+ `${colors.system}[System]${colors.reset} Starting dango in ${mode.toUpperCase()} mode...`
283
+ )
284
+ console.log(
285
+ `${colors.system}[System]${colors.reset} Press 'q' or 'Ctrl+C' to cleanly exit and sync data.\n`
286
+ )
287
+
288
+ if (mode === 'dev') {
289
+ serverProcess = spawnNpm(['run', 'dev', '--workspace=dango-server'], __dirname, {
290
+ NODE_ENV: 'development',
291
+ INTERNAL_SHUTDOWN_TOKEN: shutdownToken,
292
+ })
293
+ clientProcess = spawnNpm(['run', 'dev', '--workspace=dango-client'], __dirname, {
294
+ NODE_ENV: 'development',
295
+ })
296
+ } else {
297
+ const serverPath = path.join(SERVER_DIR, 'dist', 'server.js')
298
+ serverProcess = spawn(
299
+ 'node',
300
+ ['--max-old-space-size=512', serverPath],
301
+ spawnOpts(SERVER_DIR, {
302
+ NODE_ENV: 'production',
303
+ INTERNAL_SHUTDOWN_TOKEN: shutdownToken,
304
+ })
305
+ )
306
+ }
307
+
308
+ if (serverProcess) {
309
+ serverProcess.stdout.on('data', (data) => log('Server', colors.server, data))
310
+ serverProcess.stderr.on('data', (data) => log('Server', colors.server, data))
311
+ serverProcess.on('exit', (code) => {
312
+ if (!isShuttingDown) {
313
+ log('System', colors.system, `Server crashed or exited prematurely.`)
314
+ terminateProcess(clientProcess)
315
+ setTimeout(() => process.exit(code || 0), 500)
316
+ }
317
+ })
318
+ }
319
+
320
+ if (clientProcess) {
321
+ clientProcess.stdout.on('data', (data) => log('Client', colors.client, data))
322
+ clientProcess.stderr.on('data', (data) => log('Client', colors.client, data))
323
+ }
324
+
325
+ if (process.stdin.isTTY) {
326
+ process.stdin.resume()
327
+ readline.emitKeypressEvents(process.stdin)
328
+ process.stdin.setRawMode(true)
329
+ }
330
+
331
+ process.stdin.on('keypress', (str, key) => {
332
+ if (key && (key.name === 'q' || (key.ctrl && key.name === 'c'))) {
333
+ shutdown()
334
+ }
335
+ })
336
+ }
337
+
338
+ const shutdown = () => {
339
+ if (isShuttingDown) return
340
+ isShuttingDown = true
341
+ console.log(`\n${colors.system}[System]${colors.reset} Initiating clean shutdown...`)
342
+
343
+ if (clientProcess) {
344
+ terminateProcess(clientProcess, 'SIGTERM')
345
+ setTimeout(() => {
346
+ if (clientProcess.exitCode === null && !clientProcess.killed) {
347
+ terminateProcess(clientProcess, 'SIGKILL')
348
+ }
349
+ }, 5000)
350
+ }
351
+
352
+ const serverPort = Number(process.env.PORT) || 3000
353
+ const req = http.request(
354
+ {
355
+ hostname: '127.0.0.1',
356
+ port: serverPort,
357
+ path: '/api/internal/shutdown',
358
+ method: 'POST',
359
+ headers: {
360
+ 'x-internal-token': shutdownToken,
361
+ },
362
+ },
363
+ (res) => {
364
+ if (res.statusCode !== 200) {
365
+ console.log(
366
+ `${colors.system}[System]${colors.reset} Server rejected shutdown request (${res.statusCode}), forcing exit.`
367
+ )
368
+ terminateProcess(serverProcess, 'SIGKILL')
369
+ terminateProcess(clientProcess, 'SIGKILL')
370
+ setTimeout(() => process.exit(0), 1000)
371
+ }
372
+ }
373
+ )
374
+
375
+ req.on('error', () => {
376
+ console.log(`${colors.system}[System]${colors.reset} Server unreachable, forcing exit.`)
377
+ terminateProcess(serverProcess, 'SIGKILL')
378
+ terminateProcess(clientProcess, 'SIGKILL')
379
+ setTimeout(() => process.exit(0), 1000)
380
+ })
381
+
382
+ req.end()
383
+
384
+ setTimeout(() => {
385
+ console.log(`${colors.system}[System]${colors.reset} Force exiting after timeout.`)
386
+ terminateProcess(serverProcess, 'SIGKILL')
387
+ terminateProcess(clientProcess, 'SIGKILL')
388
+ setTimeout(() => process.exit(1), 1000)
389
+ }, 15000)
390
+ }
391
+
392
+ const restoreTerminal = () => {
393
+ try {
394
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === 'function') {
395
+ process.stdin.setRawMode(false)
396
+ process.stdin.pause()
397
+ }
398
+ } catch {
399
+ // ignore terminal restore errors
400
+ }
401
+ }
402
+
403
+ process.on('SIGINT', () => {
404
+ shutdown()
405
+ })
406
+ process.on('SIGTERM', () => {
407
+ shutdown()
408
+ })
409
+ process.on('SIGHUP', () => {
410
+ shutdown()
411
+ })
412
+ process.on('exit', restoreTerminal)
413
+ ;(async () => {
414
+ await checkForUpdates().catch(() => {})
415
+ main()
416
+ })()
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@serifpersia/dango",
3
+ "version": "3.0.3",
4
+ "description": "A web application for watching anime.",
5
+ "main": "orchestrator.js",
6
+ "workspaces": [
7
+ "client",
8
+ "server"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/serifpersia/dango.git"
13
+ },
14
+ "bin": {
15
+ "dango": "orchestrator.js"
16
+ },
17
+ "engines": {
18
+ "node": ">=22.5.0"
19
+ },
20
+ "scripts": {
21
+ "build": "npm run build --workspaces",
22
+ "start": "node orchestrator.js prod",
23
+ "dev": "node orchestrator.js dev",
24
+ "client": "npm run start --workspace=client",
25
+ "server": "npm run start --workspace=server",
26
+ "preview": "node orchestrator.js prod",
27
+ "test": "npm run test --workspace=client",
28
+ "lint": "npm run lint --workspaces --if-present",
29
+ "format": "prettier --write .",
30
+ "prepublishOnly": "npm run build",
31
+ "update-version": "node update-version.js"
32
+ },
33
+ "files": [
34
+ "orchestrator.js",
35
+ "client/dist",
36
+ "server/dist",
37
+ "LICENSE",
38
+ "README.md",
39
+ "package.json"
40
+ ],
41
+ "keywords": [
42
+ "anime",
43
+ "react",
44
+ "express",
45
+ "typescript",
46
+ "streaming",
47
+ "cli"
48
+ ],
49
+ "author": "",
50
+ "license": "MIT",
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "type": "commonjs",
55
+ "dependencies": {
56
+ "@octokit/auth-oauth-device": "^8.0.3",
57
+ "@octokit/rest": "^22.0.1",
58
+ "@xhayper/discord-rpc": "^1.3.4",
59
+ "axios": "^1.15.1",
60
+ "axios-retry": "^4.5.0",
61
+ "cheerio": "^1.2.0",
62
+ "chokidar": "^5.0.0",
63
+ "compression": "^1.8.1",
64
+ "cors": "^2.8.5",
65
+ "dotenv": "^17.4.2",
66
+ "express": "^5.1.0",
67
+ "freekeys": "^0.0.4",
68
+ "got-scraping": "^3.2.9",
69
+ "multer": "^2.0.2",
70
+ "node-cache": "^5.1.2",
71
+ "pino": "^10.3.1",
72
+ "pino-pretty": "^13.1.1",
73
+ "ws": "^8.18.0",
74
+ "xml2js": "^0.6.2"
75
+ },
76
+ "devDependencies": {
77
+ "@eslint/js": "^10.0.1",
78
+ "eslint": "^10.2.1",
79
+ "globals": "^17.5.0",
80
+ "prettier": "^3.8.3",
81
+ "ts-node": "^10.9.2",
82
+ "typescript": "^6.0.3",
83
+ "typescript-eslint": "^8.62.0"
84
+ },
85
+ "allowScripts": {
86
+ "esbuild@0.28.2": true
87
+ },
88
+ "versionDate": "2026-09-12T18:44:44.594Z"
89
+ }