@serifpersia/dango 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/client/dist/assets/AnimeInfo-CMxs0nRH.css +1 -0
- package/client/dist/assets/AnimeInfo-DVdaocCl.js +1 -0
- package/client/dist/assets/AnimeInfoPage-Bx5EvGt-.js +2 -0
- package/client/dist/assets/Asmr-C2i9GKD4.css +1 -0
- package/client/dist/assets/Asmr-CIjmFtRH.js +2 -0
- package/client/dist/assets/Asmr.module-DPG2JhDJ.js +1 -0
- package/client/dist/assets/AvSyncCalibrator-CIYcyon5.css +1 -0
- package/client/dist/assets/AvSyncCalibrator-vqErH7Cm.js +10 -0
- package/client/dist/assets/ErrorMessage-BwSjzblw.js +1 -0
- package/client/dist/assets/ErrorMessage-CXoRbrLN.css +1 -0
- package/client/dist/assets/Home-D7YfGBUk.js +1 -0
- package/client/dist/assets/Home-DKCfEiwl.css +1 -0
- package/client/dist/assets/Insights-BljI64Ua.css +1 -0
- package/client/dist/assets/Insights-CLQGrlpX.js +1 -0
- package/client/dist/assets/Manga-DQ9HYnpk.js +1 -0
- package/client/dist/assets/Manga-H8Wb04w3.css +1 -0
- package/client/dist/assets/Map-CXFqXmsZ.css +1 -0
- package/client/dist/assets/Map-L_ryVBTq.js +1 -0
- package/client/dist/assets/Mature-BeWzAJ7B.js +1 -0
- package/client/dist/assets/Mature-DJmUUtlF.css +1 -0
- package/client/dist/assets/Player-CYpd__FP.js +30 -0
- package/client/dist/assets/Player-DreAhybc.css +1 -0
- package/client/dist/assets/PlayerRedirect-fxUAp9JV.js +1 -0
- package/client/dist/assets/PlayerSettings-BcsotxP9.js +1 -0
- package/client/dist/assets/PlayerSettings-Cqua1CDD.css +1 -0
- package/client/dist/assets/QueueRail-B9Pgbqc3.js +1 -0
- package/client/dist/assets/QueueRail-BnaltOP8.css +1 -0
- package/client/dist/assets/Radio-BvdY5twD.js +1 -0
- package/client/dist/assets/Radio-CUuYy3DY.css +1 -0
- package/client/dist/assets/Search-BzO-aRP7.css +1 -0
- package/client/dist/assets/Search-R9YLAXS1.js +1 -0
- package/client/dist/assets/Search.module-D8ZbQAiK.js +1 -0
- package/client/dist/assets/Settings-9kYcXdTR.js +21 -0
- package/client/dist/assets/Settings-DF8JThUL.css +1 -0
- package/client/dist/assets/SkeletonGrid-D5vRvX5q.js +1 -0
- package/client/dist/assets/SkeletonGrid-DEbYtO4H.css +1 -0
- package/client/dist/assets/SynopsisText-BlFw0R-0.js +1 -0
- package/client/dist/assets/SynopsisText-CUgOjOxt.css +1 -0
- package/client/dist/assets/Trackers-DfZlnnVq.js +2 -0
- package/client/dist/assets/Trackers-Dt9lR_uP.css +1 -0
- package/client/dist/assets/Tv-CVKC2YBs.css +1 -0
- package/client/dist/assets/Tv-Db1Wx_b7.js +15 -0
- package/client/dist/assets/Watchlist-B2nkNZqM.css +1 -0
- package/client/dist/assets/Watchlist-a77CYtzu.js +1 -0
- package/client/dist/assets/hls-6Owgw2Aj.js +1 -0
- package/client/dist/assets/hls-BOLXTnjL.js +42 -0
- package/client/dist/assets/index-B6qdXFtl.css +1 -0
- package/client/dist/assets/index-B8TPVaIS.js +30354 -0
- package/client/dist/assets/index-CpjgU8ou.js +178 -0
- package/client/dist/assets/useAnimeInfoData-BOfyi9UJ.js +1 -0
- package/client/dist/assets/useIsMobile-CM15uQn8.css +1 -0
- package/client/dist/assets/useIsMobile-Cb8HcKRj.js +1 -0
- package/client/dist/assets/useMatureConsent-CZMYBVXd.js +1 -0
- package/client/dist/assets/vendor-rLNfZm7Y.js +1 -0
- package/client/dist/av-sync-test.mp4 +0 -0
- package/client/dist/favicon.ico +0 -0
- package/client/dist/index.html +31 -0
- package/client/dist/logo-mochi.png +0 -0
- package/client/dist/logo-sakura.png +0 -0
- package/client/dist/logo.png +0 -0
- package/client/dist/placeholder.svg +4 -0
- package/client/dist/robots.txt +3 -0
- package/orchestrator.js +416 -0
- package/package.json +89 -0
- package/server/dist/app-auth.js +231 -0
- package/server/dist/config.js +133 -0
- package/server/dist/controllers/auth.controller.js +247 -0
- package/server/dist/controllers/data.controller.js +785 -0
- package/server/dist/controllers/insights.controller.js +197 -0
- package/server/dist/controllers/proxy.controller.js +665 -0
- package/server/dist/controllers/settings.controller.js +267 -0
- package/server/dist/controllers/watchlist.controller.js +967 -0
- package/server/dist/db.js +152 -0
- package/server/dist/discord-gateway.js +381 -0
- package/server/dist/discord-rpc.js +424 -0
- package/server/dist/github-sync.js +403 -0
- package/server/dist/google.js +549 -0
- package/server/dist/lib/anilist.js +1519 -0
- package/server/dist/lib/anischedule.js +202 -0
- package/server/dist/lib/ipc.js +32 -0
- package/server/dist/lib/kitsu.js +552 -0
- package/server/dist/lib/mal.js +886 -0
- package/server/dist/lib/migration.js +335 -0
- package/server/dist/lib/temp-ids.js +11 -0
- package/server/dist/lib/tmdb.js +71 -0
- package/server/dist/lib/tracker/anilist-tracker.js +250 -0
- package/server/dist/lib/tracker/sync.service.js +312 -0
- package/server/dist/logger.js +19 -0
- package/server/dist/providers/123anime.provider.js +240 -0
- package/server/dist/providers/anilight.provider.js +366 -0
- package/server/dist/providers/animepahe.provider.js +531 -0
- package/server/dist/providers/animeya.provider.js +746 -0
- package/server/dist/providers/base-provider.js +45 -0
- package/server/dist/providers/hn.provider.js +473 -0
- package/server/dist/providers/ht.provider.js +439 -0
- package/server/dist/providers/jasmr.provider.js +504 -0
- package/server/dist/providers/kaa.provider.js +388 -0
- package/server/dist/providers/manga/manga.types.js +10 -0
- package/server/dist/providers/manga/mangadex.provider.js +227 -0
- package/server/dist/providers/manga/mangapill.provider.js +232 -0
- package/server/dist/providers/megaplay.provider.js +583 -0
- package/server/dist/providers/op.provider.js +440 -0
- package/server/dist/providers/provider.interface.js +2 -0
- package/server/dist/providers/title-matching.js +234 -0
- package/server/dist/providers/wh.provider.js +600 -0
- package/server/dist/rclone.js +126 -0
- package/server/dist/repositories/insights.repository.js +43 -0
- package/server/dist/repositories/mal-cache.repository.js +66 -0
- package/server/dist/repositories/notifications.repository.js +40 -0
- package/server/dist/repositories/queue.repository.js +64 -0
- package/server/dist/repositories/settings.repository.js +15 -0
- package/server/dist/repositories/shows-meta.repository.js +43 -0
- package/server/dist/repositories/temp-show-ids.repository.js +54 -0
- package/server/dist/repositories/watched-episodes.repository.js +54 -0
- package/server/dist/repositories/watchlist.repository.js +74 -0
- package/server/dist/routes/asmr.routes.js +72 -0
- package/server/dist/routes/auth.routes.js +28 -0
- package/server/dist/routes/data.routes.js +50 -0
- package/server/dist/routes/discord-gateway.routes.js +57 -0
- package/server/dist/routes/insights.routes.js +12 -0
- package/server/dist/routes/lan-auth.routes.js +49 -0
- package/server/dist/routes/manga.routes.js +115 -0
- package/server/dist/routes/proxy.routes.js +15 -0
- package/server/dist/routes/radio.routes.js +143 -0
- package/server/dist/routes/settings.routes.js +27 -0
- package/server/dist/routes/tracker.routes.js +143 -0
- package/server/dist/routes/translate.routes.js +90 -0
- package/server/dist/routes/tv.routes.js +843 -0
- package/server/dist/routes/watchlist.routes.js +312 -0
- package/server/dist/server.js +310 -0
- package/server/dist/shipped-defaults.js +8 -0
- package/server/dist/sync-config.js +32 -0
- package/server/dist/sync.js +506 -0
- package/server/dist/utils/cookie.utils.js +17 -0
- package/server/dist/utils/db-utils.js +15 -0
- package/server/dist/utils/env.utils.js +83 -0
- package/server/dist/utils/machine-id.js +46 -0
- package/server/dist/utils/request-context.js +5 -0
- package/server/dist/utils/security.utils.js +99 -0
package/orchestrator.js
ADDED
|
@@ -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
|
+
}
|