most-box 0.0.2 → 0.0.6

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 (181) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +222 -156
  3. package/electron/main.js +67 -0
  4. package/out/404/index.html +15 -0
  5. package/out/404.html +15 -0
  6. package/out/__next.__PAGE__.txt +11 -0
  7. package/out/__next._full.txt +21 -0
  8. package/out/__next._head.txt +5 -0
  9. package/out/__next._index.txt +7 -0
  10. package/out/__next._tree.txt +4 -0
  11. package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_buildManifest.js +11 -0
  12. package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_clientMiddlewareManifest.js +1 -0
  13. package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_ssgManifest.js +1 -0
  14. package/out/_next/static/chunks/00s106sbq8t9v.js +1 -0
  15. package/out/_next/static/chunks/0174xh3wfsjm1.js +2 -0
  16. package/out/_next/static/chunks/01xlw8hd842-c.js +1 -0
  17. package/out/_next/static/chunks/02ou_44kkb5dz.js +1 -0
  18. package/out/_next/static/chunks/02pr2b_eos3~h.js +1 -0
  19. package/out/_next/static/chunks/03~yq9q893hmn.js +1 -0
  20. package/out/_next/static/chunks/07lsjkarm1p9f.css +1 -0
  21. package/out/_next/static/chunks/0_-ccbcyh_o30.css +1 -0
  22. package/out/_next/static/chunks/0_b839~4.q324.js +1 -0
  23. package/out/_next/static/chunks/0_sna3wdypbzr.js +1 -0
  24. package/out/_next/static/chunks/0_wia9ofmsi1c.css +2 -0
  25. package/out/_next/static/chunks/0byj66sc-9o0g.js +1 -0
  26. package/out/_next/static/chunks/0bzupvr5gt3k9.js +31 -0
  27. package/out/_next/static/chunks/0d3shmwh5_nmn.js +1 -0
  28. package/out/_next/static/chunks/0du450zbk4kq_.js +1 -0
  29. package/out/_next/static/chunks/0e_h0d3ekzks8.css +1 -0
  30. package/out/_next/static/chunks/0ho~log~~-jwp.css +1 -0
  31. package/out/_next/static/chunks/0ibjp~7qzxfjv.js +5 -0
  32. package/out/_next/static/chunks/0imvn_arv36xt.css +1 -0
  33. package/out/_next/static/chunks/0j9~17180dl8j.js +1 -0
  34. package/out/_next/static/chunks/0ji.28mehrvdp.js +1 -0
  35. package/out/_next/static/chunks/0jl~j62iz2uvr.js +1 -0
  36. package/out/_next/static/chunks/0nct0fubs64d-.js +1 -0
  37. package/out/_next/static/chunks/0n~dq4kpx9xxx.js +1 -0
  38. package/out/_next/static/chunks/0pqt~8bl3ukh4.js +4 -0
  39. package/out/_next/static/chunks/0q7ck9f.90_i9.js +1 -0
  40. package/out/_next/static/chunks/0qub_r0x_r-e9.css +1 -0
  41. package/out/_next/static/chunks/0rr4gwjp9z~9a.js +1 -0
  42. package/out/_next/static/chunks/0ry.po.a~iu4p.js +1 -0
  43. package/out/_next/static/chunks/0slwj0c46k5cu.js +1 -0
  44. package/out/_next/static/chunks/0sorqk.oc6b7j.css +1 -0
  45. package/out/_next/static/chunks/11dalasm30arx.js +1 -0
  46. package/out/_next/static/chunks/turbopack-0a_g3u0ud~jb8.js +1 -0
  47. package/out/_not-found/__next._full.txt +20 -0
  48. package/out/_not-found/__next._head.txt +5 -0
  49. package/out/_not-found/__next._index.txt +7 -0
  50. package/out/_not-found/__next._not-found.__PAGE__.txt +9 -0
  51. package/out/_not-found/__next._not-found.txt +5 -0
  52. package/out/_not-found/__next._tree.txt +2 -0
  53. package/out/_not-found/index.html +15 -0
  54. package/out/_not-found/index.txt +20 -0
  55. package/out/app/__next._full.txt +20 -0
  56. package/out/app/__next._head.txt +5 -0
  57. package/out/app/__next._index.txt +7 -0
  58. package/out/app/__next._tree.txt +2 -0
  59. package/out/app/__next.app.__PAGE__.txt +9 -0
  60. package/out/app/__next.app.txt +5 -0
  61. package/out/app/index.html +15 -0
  62. package/out/app/index.txt +20 -0
  63. package/out/changelog/__next._full.txt +22 -0
  64. package/out/changelog/__next._head.txt +5 -0
  65. package/out/changelog/__next._index.txt +7 -0
  66. package/out/changelog/__next._tree.txt +3 -0
  67. package/out/changelog/__next.changelog.__PAGE__.txt +10 -0
  68. package/out/changelog/__next.changelog.txt +5 -0
  69. package/out/changelog/index.html +15 -0
  70. package/out/changelog/index.txt +22 -0
  71. package/out/chat/__next._full.txt +21 -0
  72. package/out/chat/__next._head.txt +5 -0
  73. package/out/chat/__next._index.txt +7 -0
  74. package/out/chat/__next._tree.txt +3 -0
  75. package/out/chat/__next.chat.__PAGE__.txt +9 -0
  76. package/out/chat/__next.chat.txt +6 -0
  77. package/out/chat/index.html +15 -0
  78. package/out/chat/index.txt +21 -0
  79. package/out/docs/__next._full.txt +22 -0
  80. package/out/docs/__next._head.txt +5 -0
  81. package/out/docs/__next._index.txt +7 -0
  82. package/out/docs/__next._tree.txt +3 -0
  83. package/out/docs/__next.docs.__PAGE__.txt +10 -0
  84. package/out/docs/__next.docs.txt +5 -0
  85. package/out/docs/getting-started/__next._full.txt +22 -0
  86. package/out/docs/getting-started/__next._head.txt +5 -0
  87. package/out/docs/getting-started/__next._index.txt +7 -0
  88. package/out/docs/getting-started/__next._tree.txt +3 -0
  89. package/out/docs/getting-started/__next.docs.getting-started.__PAGE__.txt +10 -0
  90. package/out/docs/getting-started/__next.docs.getting-started.txt +5 -0
  91. package/out/docs/getting-started/__next.docs.txt +5 -0
  92. package/out/docs/getting-started/index.html +15 -0
  93. package/out/docs/getting-started/index.txt +22 -0
  94. package/out/docs/index.html +15 -0
  95. package/out/docs/index.txt +22 -0
  96. package/out/download/__next._full.txt +34 -0
  97. package/out/download/__next._head.txt +5 -0
  98. package/out/download/__next._index.txt +7 -0
  99. package/out/download/__next._tree.txt +4 -0
  100. package/out/download/__next.download.__PAGE__.txt +16 -0
  101. package/out/download/__next.download.txt +5 -0
  102. package/out/download/index.html +15 -0
  103. package/out/download/index.txt +34 -0
  104. package/out/favicon.ico +0 -0
  105. package/out/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  106. package/out/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  107. package/out/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
  108. package/out/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  109. package/out/index.html +15 -0
  110. package/out/index.txt +21 -0
  111. package/out/lottery/__next._full.txt +21 -0
  112. package/out/lottery/__next._head.txt +5 -0
  113. package/out/lottery/__next._index.txt +7 -0
  114. package/out/lottery/__next._tree.txt +3 -0
  115. package/out/lottery/__next.lottery.__PAGE__.txt +9 -0
  116. package/out/lottery/__next.lottery.txt +6 -0
  117. package/out/lottery/index.html +15 -0
  118. package/out/lottery/index.txt +21 -0
  119. package/out/ping/__next._full.txt +21 -0
  120. package/out/ping/__next._head.txt +5 -0
  121. package/out/ping/__next._index.txt +7 -0
  122. package/out/ping/__next._tree.txt +4 -0
  123. package/out/ping/__next.ping.__PAGE__.txt +10 -0
  124. package/out/ping/__next.ping.txt +5 -0
  125. package/out/ping/index.html +15 -0
  126. package/out/ping/index.txt +21 -0
  127. package/out/pwa-512x512.png +0 -0
  128. package/out/web3/__next._full.txt +21 -0
  129. package/out/web3/__next._head.txt +5 -0
  130. package/out/web3/__next._index.txt +7 -0
  131. package/out/web3/__next._tree.txt +3 -0
  132. package/out/web3/__next.web3.__PAGE__.txt +9 -0
  133. package/out/web3/__next.web3.txt +6 -0
  134. package/out/web3/ed25519/__next._full.txt +20 -0
  135. package/out/web3/ed25519/__next._head.txt +5 -0
  136. package/out/web3/ed25519/__next._index.txt +7 -0
  137. package/out/web3/ed25519/__next._tree.txt +3 -0
  138. package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +6 -0
  139. package/out/web3/ed25519/__next.web3.ed25519.txt +5 -0
  140. package/out/web3/ed25519/__next.web3.txt +6 -0
  141. package/out/web3/ed25519/index.html +1 -0
  142. package/out/web3/ed25519/index.txt +20 -0
  143. package/out/web3/index.html +15 -0
  144. package/out/web3/index.txt +21 -0
  145. package/out/web3/tools/__next._full.txt +20 -0
  146. package/out/web3/tools/__next._head.txt +5 -0
  147. package/out/web3/tools/__next._index.txt +7 -0
  148. package/out/web3/tools/__next._tree.txt +3 -0
  149. package/out/web3/tools/__next.web3.tools.__PAGE__.txt +6 -0
  150. package/out/web3/tools/__next.web3.tools.txt +5 -0
  151. package/out/web3/tools/__next.web3.txt +6 -0
  152. package/out/web3/tools/index.html +1 -0
  153. package/out/web3/tools/index.txt +20 -0
  154. package/package.json +162 -48
  155. package/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  156. package/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  157. package/public/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
  158. package/public/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  159. package/public/pwa-512x512.png +0 -0
  160. package/server/cli.js +3 -0
  161. package/server/index.js +963 -0
  162. package/{src → server/src}/config.js +51 -39
  163. package/{src → server/src}/core/cid.js +157 -146
  164. package/{src → server/src}/index.js +1950 -1201
  165. package/server/src/utils/api.js +68 -0
  166. package/server/src/utils/avatar.js +11 -0
  167. package/{src → server/src}/utils/errors.js +70 -66
  168. package/server/src/utils/mostWallet.js +42 -0
  169. package/server/src/utils/mp.js +117 -0
  170. package/{src → server/src}/utils/security.js +173 -169
  171. package/server/src/utils/userIdentity.js +93 -0
  172. package/build.mjs +0 -40
  173. package/cli.js +0 -2
  174. package/public/app.css +0 -1519
  175. package/public/app.jsx +0 -1543
  176. package/public/bundle.css +0 -1
  177. package/public/bundle.js +0 -107
  178. package/public/error-boundary.jsx +0 -50
  179. package/public/index.html +0 -16
  180. package/public/index.jsx +0 -20
  181. package/server.js +0 -698
package/server.js DELETED
@@ -1,698 +0,0 @@
1
- import http from 'node:http'
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import os from 'node:os'
5
- import { fileURLToPath } from 'node:url'
6
- import { spawn } from 'node:child_process'
7
- import Busboy from 'busboy'
8
- import { WebSocketServer } from 'ws'
9
- import { MostBoxEngine } from './src/index.js'
10
- import { parseMostLink } from './src/core/cid.js'
11
- import { MAX_FILE_SIZE } from './src/config.js'
12
-
13
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
14
- const PORT = Number(process.env.MOSTBOX_PORT || process.env.PORT) || 1976
15
- const HOST = process.env.MOSTBOX_HOST || '127.0.0.1'
16
-
17
- const MAX_JSON_BODY_SIZE = 10 * 1024 * 1024 // 10MB
18
- const MAX_UPLOAD_SIZE = MAX_FILE_SIZE
19
- const UPLOAD_TMP_DIR = path.join(os.tmpdir(), 'most-box-uploads')
20
-
21
- const rateLimitMap = new Map()
22
- const RATE_LIMIT_WINDOW = 60 * 1000
23
- const RATE_LIMIT_MAX_REQUESTS = 120
24
-
25
- let engine = null
26
- let serverInstance = null
27
- let wss = null
28
-
29
- // --- 配置 ---
30
- const CONFIG_DIR = path.join(os.homedir(), '.most-box')
31
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
32
-
33
- function loadConfig() {
34
- try {
35
- if (fs.existsSync(CONFIG_FILE)) {
36
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'))
37
- }
38
- } catch (err) {
39
- console.error('[Config] Load error:', err.message)
40
- }
41
- return {}
42
- }
43
-
44
- function saveConfig(config) {
45
- try {
46
- if (!fs.existsSync(CONFIG_DIR)) {
47
- fs.mkdirSync(CONFIG_DIR, { recursive: true })
48
- }
49
- const tmpPath = CONFIG_FILE + '.tmp'
50
- fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), 'utf-8')
51
- fs.renameSync(tmpPath, CONFIG_FILE)
52
- return true
53
- } catch (err) {
54
- console.error('[Config] Save error:', err.message)
55
- return false
56
- }
57
- }
58
-
59
- // --- 存储路径 ---
60
- function getDataPath() {
61
- const config = loadConfig()
62
- return config.dataPath || path.join(os.homedir(), 'most-data')
63
- }
64
-
65
- // --- 速率限制 ---
66
- function checkRateLimit(clientIp) {
67
- const now = Date.now()
68
- if (!rateLimitMap.has(clientIp)) {
69
- rateLimitMap.set(clientIp, [])
70
- }
71
- const requests = rateLimitMap.get(clientIp)
72
- while (requests.length > 0 && requests[0] < now - RATE_LIMIT_WINDOW) {
73
- requests.shift()
74
- }
75
- if (requests.length === 0) {
76
- rateLimitMap.delete(clientIp)
77
- }
78
- if (requests.length >= RATE_LIMIT_MAX_REQUESTS) {
79
- return false
80
- }
81
- requests.push(now)
82
- return true
83
- }
84
-
85
- // --- 静态文件服务 ---
86
- const MIME_TYPES = {
87
- '.html': 'text/html; charset=utf-8',
88
- '.js': 'application/javascript; charset=utf-8',
89
- '.css': 'text/css; charset=utf-8',
90
- '.json': 'application/json',
91
- '.png': 'image/png',
92
- '.jpg': 'image/jpeg',
93
- '.jpeg': 'image/jpeg',
94
- '.gif': 'image/gif',
95
- '.webp': 'image/webp',
96
- '.svg': 'image/svg+xml',
97
- '.ico': 'image/x-icon',
98
- '.mp4': 'video/mp4',
99
- '.webm': 'video/webm',
100
- '.ogg': 'video/ogg',
101
- '.mp3': 'audio/mpeg',
102
- '.wav': 'audio/wav',
103
- '.flac': 'audio/flac',
104
- '.aac': 'audio/aac',
105
- '.m4a': 'audio/mp4',
106
- '.opus': 'audio/opus',
107
- '.woff2': 'font/woff2',
108
- '.woff': 'font/woff'
109
- }
110
-
111
- function getMimeType(fileName) {
112
- const ext = path.extname(fileName).toLowerCase()
113
- return MIME_TYPES[ext] || 'application/octet-stream'
114
- }
115
-
116
- function serveStatic(req, res) {
117
- let filePath = req.url === '/' ? '/index.html' : req.url
118
- filePath = filePath.split('?')[0]
119
-
120
- const fullPath = path.join(__dirname, 'public', filePath)
121
- const ext = path.extname(fullPath)
122
- const publicDir = path.join(__dirname, 'public')
123
-
124
- if (!fullPath.startsWith(publicDir)) {
125
- res.writeHead(403)
126
- res.end('Forbidden')
127
- return
128
- }
129
-
130
- fs.readFile(fullPath, (err, data) => {
131
- if (err) {
132
- res.writeHead(404)
133
- res.end('Not found')
134
- return
135
- }
136
-
137
- res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' })
138
- res.end(data)
139
- })
140
- }
141
-
142
- function decodeFilenameFromHeader(headerStr) {
143
- if (!headerStr) return null
144
-
145
- const filenameStarMatch = headerStr.match(/filename\*=(?:UTF-8''|utf-8'')([^;\r\n]+)/i)
146
- if (filenameStarMatch) {
147
- return decodeURIComponent(filenameStarMatch[1])
148
- }
149
-
150
- const filenameMatch = headerStr.match(/filename="([^"]+)"/)
151
- if (filenameMatch) {
152
- const rawFilename = filenameMatch[1]
153
- try {
154
- const buf = Buffer.from(rawFilename, 'latin1')
155
- const decoded = buf.toString('utf8')
156
- if (decoded.includes('\ufffd')) {
157
- return rawFilename
158
- }
159
- return decoded
160
- } catch {
161
- return rawFilename
162
- }
163
- }
164
-
165
- const filenamePlainMatch = headerStr.match(/filename=([^;\r\n]+)/)
166
- if (filenamePlainMatch) {
167
- return filenamePlainMatch[1].trim()
168
- }
169
- return null
170
- }
171
-
172
- function parseMultipartBusboy(req) {
173
- return new Promise((resolve, reject) => {
174
- if (!fs.existsSync(UPLOAD_TMP_DIR)) {
175
- fs.mkdirSync(UPLOAD_TMP_DIR, { recursive: true })
176
- }
177
-
178
- const busboy = Busboy({
179
- headers: req.headers,
180
- limits: {
181
- fileSize: MAX_UPLOAD_SIZE,
182
- files: 1,
183
- fields: 0
184
- }
185
- })
186
-
187
- const result = { filePath: null, filename: null }
188
- let fileSize = 0
189
- let writeStream = null
190
- let tempPath = null
191
-
192
- busboy.on('file', (name, stream, info) => {
193
- result.filename = decodeFilenameFromHeader(`filename="${info.filename}"`)
194
- tempPath = path.join(UPLOAD_TMP_DIR, `upload_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`)
195
- writeStream = fs.createWriteStream(tempPath)
196
-
197
- stream.on('data', (chunk) => {
198
- fileSize += chunk.length
199
- if (fileSize > MAX_UPLOAD_SIZE) {
200
- stream.destroy()
201
- writeStream.destroy()
202
- fs.unlink(tempPath, () => {})
203
- reject(new Error('File too large'))
204
- return
205
- }
206
- })
207
-
208
- stream.on('error', () => {
209
- if (tempPath) fs.unlink(tempPath, () => {})
210
- })
211
-
212
- stream.pipe(writeStream)
213
-
214
- writeStream.on('finish', () => {
215
- result.filePath = tempPath
216
- resolve(result)
217
- })
218
-
219
- writeStream.on('error', (err) => {
220
- if (tempPath) fs.unlink(tempPath, () => {})
221
- reject(err)
222
- })
223
- })
224
-
225
- busboy.on('error', (err) => {
226
- if (tempPath) fs.unlink(tempPath, () => {})
227
- reject(err)
228
- })
229
-
230
- busboy.on('close', () => {
231
- if (!result.filename) {
232
- resolve(null)
233
- }
234
- })
235
-
236
- req.on('error', (err) => {
237
- if (tempPath) fs.unlink(tempPath, () => {})
238
- reject(err)
239
- })
240
- req.pipe(busboy)
241
- })
242
- }
243
-
244
- // --- JSON 请求体解析器(带大小限制) ---
245
- async function parseJSON(req) {
246
- const chunks = []
247
- let totalSize = 0
248
- for await (const chunk of req) {
249
- totalSize += chunk.length
250
- if (totalSize > MAX_JSON_BODY_SIZE) {
251
- throw new Error('Request body too large')
252
- }
253
- chunks.push(chunk)
254
- }
255
- const text = Buffer.concat(chunks).toString()
256
- if (!text.trim()) {
257
- throw new Error('Empty request body')
258
- }
259
- return JSON.parse(text)
260
- }
261
-
262
- // --- API 路由 ---
263
- async function handleAPI(req, res) {
264
- const url = new URL(req.url, `http://${HOST}:${PORT}`)
265
- const pathname = url.pathname
266
- const method = req.method
267
-
268
- const json = (data, status = 200) => {
269
- res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })
270
- res.end(JSON.stringify(data))
271
- }
272
-
273
- try {
274
- // GET /api/node-id
275
- if (pathname === '/api/node-id' && method === 'GET') {
276
- json({ id: engine.getNodeId() })
277
- return
278
- }
279
-
280
- // GET /api/config
281
- if (pathname === '/api/config' && method === 'GET') {
282
- const config = loadConfig()
283
- json({ dataPath: config.dataPath || '' })
284
- return
285
- }
286
-
287
- // POST /api/config — 更新配置
288
- if (pathname === '/api/config' && method === 'POST') {
289
- const body = await parseJSON(req)
290
- const config = loadConfig()
291
-
292
- if (body.resetStorage) {
293
- config.dataPath = ''
294
- } else if (body.dataPath !== undefined) {
295
- let dataPath = body.dataPath.trim()
296
- let basePath = dataPath
297
-
298
- if (dataPath.match(/^[A-Za-z]:\\$/)) {
299
- basePath = dataPath
300
- dataPath = path.join(dataPath, 'most-data')
301
- }
302
-
303
- if (!fs.existsSync(basePath)) {
304
- json({ error: '目录不存在' }, 400)
305
- return
306
- }
307
-
308
- if (!fs.existsSync(dataPath)) {
309
- fs.mkdirSync(dataPath, { recursive: true })
310
- }
311
-
312
- config.dataPath = dataPath
313
- }
314
-
315
- const success = saveConfig(config)
316
- json({ success, dataPath: getDataPath() })
317
- return
318
- }
319
-
320
- // GET /api/config/data-path
321
- if (pathname === '/api/config/data-path' && method === 'GET') {
322
- const config = loadConfig()
323
- const isDefault = !config.dataPath
324
- const dataPath = getDataPath()
325
- json({ dataPath, isDefault })
326
- return
327
- }
328
-
329
- // GET /api/network-status
330
- if (pathname === '/api/network-status' && method === 'GET') {
331
- json(engine.getNetworkStatus())
332
- return
333
- }
334
-
335
- // GET /api/files
336
- if (pathname === '/api/files' && method === 'GET') {
337
- json(engine.listPublishedFiles())
338
- return
339
- }
340
-
341
- // POST /api/publish — multipart 文件上传
342
- if (pathname === '/api/publish' && method === 'POST') {
343
- const result = await parseMultipartBusboy(req)
344
-
345
- if (!result || !result.filename) {
346
- json({ error: 'No file provided' }, 400)
347
- return
348
- }
349
-
350
- try {
351
- const publishResult = await engine.publishFile(result.filePath, result.filename)
352
- json({ success: true, ...publishResult })
353
- } finally {
354
- fs.unlink(result.filePath, () => {})
355
- }
356
- return
357
- }
358
-
359
- // POST /api/download — 从 P2P 开始异步下载
360
- if (pathname === '/api/download' && method === 'POST') {
361
- const body = await parseJSON(req)
362
- if (!body.link) {
363
- json({ error: 'link is required' }, 400)
364
- return
365
- }
366
-
367
- const taskId = `dl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
368
-
369
- const parsed = parseMostLink(body.link)
370
- if (parsed.error) {
371
- json({ error: parsed.error }, 400)
372
- return
373
- }
374
-
375
- const existingFile = engine.getPublishedFiles().find(f => f.cid === parsed.cid)
376
- if (existingFile) {
377
- console.log(`[MostBox] File already exists: ${existingFile.fileName}`)
378
- json({ success: true, taskId, alreadyExists: true, fileName: existingFile.fileName })
379
- return
380
- }
381
-
382
- engine.downloadFile(body.link, taskId).catch(err => {
383
- if (err.message === 'Download cancelled') {
384
- wsBroadcast('download:cancelled', { taskId })
385
- } else {
386
- wsBroadcast('download:error', { taskId, error: err.message })
387
- }
388
- })
389
-
390
- json({ success: true, taskId })
391
- return
392
- }
393
-
394
- // POST /api/download/cancel — 取消活动下载
395
- if (pathname === '/api/download/cancel' && method === 'POST') {
396
- const body = await parseJSON(req)
397
- if (!body.taskId) {
398
- json({ error: 'taskId is required' }, 400)
399
- return
400
- }
401
- engine.cancelDownload(body.taskId)
402
- json({ success: true })
403
- return
404
- }
405
-
406
- // DELETE /api/files/:cid
407
- if (pathname.startsWith('/api/files/') && method === 'DELETE') {
408
- const cid = pathname.replace('/api/files/', '').replace(/\/$/, '')
409
- const result = await engine.deletePublishedFile(cid)
410
- json(result)
411
- return
412
- }
413
-
414
- // POST /api/move — 重命名/移动已发布文件
415
- if (pathname === '/api/move' && method === 'POST') {
416
- const body = await parseJSON(req)
417
- if (!body.cid || !body.newFileName) {
418
- json({ error: 'cid and newFileName are required' }, 400)
419
- return
420
- }
421
- try {
422
- const result = engine.moveFile(body.cid, body.newFileName)
423
- json({ success: true, ...result })
424
- } catch (err) {
425
- json({ error: err.message }, 400)
426
- }
427
- return
428
- }
429
-
430
- // POST /api/folder/rename — 重命名文件夹
431
- if (pathname === '/api/folder/rename' && method === 'POST') {
432
- const body = await parseJSON(req)
433
- if (!body.oldPath || !body.newPath) {
434
- json({ error: 'oldPath and newPath are required' }, 400)
435
- return
436
- }
437
- try {
438
- const result = engine.renameFolder(body.oldPath, body.newPath)
439
- json({ success: true, ...result })
440
- } catch (err) {
441
- json({ error: err.message }, 400)
442
- }
443
- return
444
- }
445
-
446
- // GET /api/files/:cid/download — 内联服务文件,支持 Range
447
- if (pathname.match(/^\/api\/files\/[^/]+\/download$/) && method === 'GET') {
448
- const cid = pathname.split('/')[3]
449
- const rangeHeader = req.headers['range']
450
-
451
- try {
452
- if (rangeHeader) {
453
- const rangeMatch = rangeHeader.match(/bytes=(\d+)-(\d*)/)
454
- if (rangeMatch) {
455
- const start = parseInt(rangeMatch[1], 10)
456
- const end = rangeMatch[2] ? parseInt(rangeMatch[2], 10) : undefined
457
-
458
- const offset = start
459
- const limit = end !== undefined ? end - start + 1 : undefined
460
-
461
- const result = await engine.readFileRaw(cid, { offset, limit })
462
- const contentType = getMimeType(result.fileName)
463
-
464
- res.writeHead(206, {
465
- 'Content-Type': contentType,
466
- 'Content-Length': result.buffer.length,
467
- 'Content-Range': `bytes ${offset}-${offset + result.buffer.length - 1}/${result.totalSize}`,
468
- 'Accept-Ranges': 'bytes'
469
- })
470
- res.end(result.buffer)
471
- return
472
- }
473
- }
474
-
475
- const result = await engine.readFileRaw(cid)
476
- const contentType = getMimeType(result.fileName)
477
- res.writeHead(200, {
478
- 'Content-Type': contentType,
479
- 'Content-Length': result.totalSize,
480
- 'Accept-Ranges': 'bytes',
481
- 'Content-Disposition': `inline; filename="${encodeURIComponent(result.fileName)}"`
482
- })
483
- res.end(result.buffer)
484
- } catch (err) {
485
- if (err.message === 'File not found') {
486
- json({ error: err.message }, 404)
487
- } else {
488
- json({ error: err.message }, 400)
489
- }
490
- }
491
- return
492
- }
493
-
494
- // POST /api/shutdown — 优雅关闭服务器(仅允许 localhost 连接)
495
- if (pathname === '/api/shutdown' && method === 'POST') {
496
- const clientIp = req.socket.remoteAddress
497
- const isLocalhost = clientIp === '127.0.0.1' || clientIp === '::1' || clientIp === '::ffff:127.0.0.1'
498
- if (!isLocalhost) {
499
- json({ error: 'Forbidden' }, 403)
500
- return
501
- }
502
- json({ success: true })
503
- console.log('[MostBox] Shutdown requested via API...')
504
- setTimeout(async () => {
505
- await engine.stop()
506
- serverInstance.close()
507
- console.log('[MostBox] Server stopped.')
508
- process.exit(0)
509
- }, 100)
510
- return
511
- }
512
-
513
- // GET /api/trash — 列出回收站文件
514
- if (pathname === '/api/trash' && method === 'GET') {
515
- json(engine.listTrashFiles())
516
- return
517
- }
518
-
519
- // POST /api/trash/:cid/restore — 从回收站恢复文件
520
- if (pathname.match(/^\/api\/trash\/[^/]+\/restore$/) && method === 'POST') {
521
- const cid = pathname.split('/')[3]
522
- try {
523
- const result = engine.restoreTrashFile(cid)
524
- json({ success: true, files: result })
525
- } catch (err) {
526
- json({ error: err.message }, 400)
527
- }
528
- return
529
- }
530
-
531
- // DELETE /api/trash/:cid — 永久删除回收站文件
532
- if (pathname.match(/^\/api\/trash\/[^/]+$/) && method === 'DELETE') {
533
- const cid = pathname.split('/')[3]
534
- const result = await engine.permanentDeleteTrashFile(cid)
535
- json({ success: true, trashFiles: result })
536
- return
537
- }
538
-
539
- // DELETE /api/trash — 清空回收站
540
- if (pathname === '/api/trash' && method === 'DELETE') {
541
- const result = await engine.emptyTrash()
542
- json({ success: true, trashFiles: result })
543
- return
544
- }
545
-
546
- // POST /api/files/:cid/star — 切换星标状态
547
- if (pathname.match(/^\/api\/files\/[^/]+\/star$/) && method === 'POST') {
548
- const cid = pathname.split('/')[3]
549
- try {
550
- const result = engine.toggleStarred(cid)
551
- json({ success: true, ...result })
552
- } catch (err) {
553
- json({ error: err.message }, 400)
554
- }
555
- return
556
- }
557
-
558
- // GET /api/storage — 获取存储统计信息
559
- if (pathname === '/api/storage' && method === 'GET') {
560
- const result = await engine.getStorageStats()
561
- json(result)
562
- return
563
- }
564
-
565
- json({ error: 'Not found' }, 404)
566
- } catch (err) {
567
- console.error('[API Error]', err)
568
- json({ error: err.message, code: err.code }, 500)
569
- }
570
- }
571
-
572
- function wsBroadcast(event, data) {
573
- const payload = JSON.stringify({ event, data })
574
- if (wss) {
575
- wss.clients.forEach((client) => {
576
- if (client.readyState === 1) {
577
- try { client.send(payload) } catch {}
578
- }
579
- })
580
- }
581
- }
582
-
583
- // --- 主函数 ---
584
- async function main() {
585
- console.log('[MostBox] Starting core daemon...')
586
-
587
- if (fs.existsSync(UPLOAD_TMP_DIR)) {
588
- const staleFiles = fs.readdirSync(UPLOAD_TMP_DIR)
589
- for (const file of staleFiles) {
590
- try { fs.unlinkSync(path.join(UPLOAD_TMP_DIR, file)) } catch {}
591
- }
592
- console.log(`[MostBox] Cleaned ${staleFiles.length} stale upload temp files`)
593
- }
594
-
595
- const dataPath = getDataPath()
596
- console.log(`[MostBox] Storage: ${dataPath}`)
597
-
598
- engine = new MostBoxEngine({ dataPath })
599
-
600
- engine.on('download:progress', (data) => wsBroadcast('download:progress', data))
601
- engine.on('download:status', (data) => wsBroadcast('download:status', data))
602
- engine.on('download:success', (data) => wsBroadcast('download:success', data))
603
- engine.on('download:cancelled', (data) => wsBroadcast('download:cancelled', data))
604
- engine.on('publish:progress', (data) => wsBroadcast('publish:progress', data))
605
- engine.on('publish:success', (data) => wsBroadcast('publish:success', data))
606
- engine.on('connection', () => {
607
- wsBroadcast('network:status', engine.getNetworkStatus())
608
- })
609
-
610
- await engine.start()
611
- console.log('[MostBox] Engine ready')
612
-
613
- serverInstance = http.createServer((req, res) => {
614
- const allowedOrigin = `http://${HOST}:${PORT}`
615
- res.setHeader('Access-Control-Allow-Origin', allowedOrigin)
616
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
617
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
618
-
619
- if (req.method === 'OPTIONS') {
620
- res.writeHead(204)
621
- res.end()
622
- return
623
- }
624
-
625
- const clientIp = req.socket.remoteAddress || 'unknown'
626
-
627
- if (!checkRateLimit(clientIp)) {
628
- res.writeHead(429, { 'Content-Type': 'application/json' })
629
- res.end(JSON.stringify({ error: 'Too many requests' }))
630
- return
631
- }
632
-
633
- if (req.url.startsWith('/api/')) {
634
- handleAPI(req, res).catch(err => {
635
- console.error('[Unhandled API Error]', err)
636
- if (!res.headersSent) {
637
- res.writeHead(500, { 'Content-Type': 'application/json' })
638
- res.end(JSON.stringify({ error: 'Internal server error' }))
639
- }
640
- })
641
- } else {
642
- serveStatic(req, res)
643
- }
644
- })
645
-
646
- wss = new WebSocketServer({ noServer: true })
647
- wss.on('connection', (ws) => {
648
- ws.on('error', () => {})
649
- })
650
-
651
- serverInstance.on('upgrade', (req, socket, head) => {
652
- if (req.url.startsWith('/ws')) {
653
- wss.handleUpgrade(req, socket, head, (ws) => {
654
- wss.emit('connection', ws, req)
655
- })
656
- } else {
657
- socket.destroy()
658
- }
659
- })
660
-
661
- serverInstance.listen(PORT, HOST, () => {
662
- const url = `http://${HOST}:${PORT}`
663
- console.log(`[MostBox] Server running at ${url}`)
664
-
665
- if (process.platform === 'win32') {
666
- spawn('cmd.exe', ['/c', 'start', '', url], {
667
- detached: true,
668
- stdio: 'ignore'
669
- }).unref()
670
- } else {
671
- const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open'
672
- spawn(cmd, [url], {
673
- detached: true,
674
- stdio: 'ignore'
675
- }).unref()
676
- }
677
- })
678
-
679
- process.on('SIGINT', async () => {
680
- console.log('\n[MostBox] Shutting down...')
681
- await engine.stop()
682
- if (wss) wss.close()
683
- serverInstance.close()
684
- process.exit(0)
685
- })
686
-
687
- process.on('SIGTERM', async () => {
688
- await engine.stop()
689
- if (wss) wss.close()
690
- serverInstance.close()
691
- process.exit(0)
692
- })
693
- }
694
-
695
- main().catch(err => {
696
- console.error('[MostBox] Fatal error:', err)
697
- process.exit(1)
698
- })