piclist 1.8.10 → 1.8.11

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 (61) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc.js +50 -29
  3. package/.prettierrc +18 -0
  4. package/CHANGELOG.md +14 -0
  5. package/bin/picgo +25 -25
  6. package/bin/picgo-server +522 -491
  7. package/dist/core/Lifecycle.d.ts +1 -1
  8. package/dist/core/PicGo.d.ts +4 -4
  9. package/dist/i18n/en.d.ts +1 -1
  10. package/dist/i18n/index.d.ts +3 -4
  11. package/dist/i18n/zh-TW.d.ts +1 -1
  12. package/dist/index.cjs.js +2 -2
  13. package/dist/index.esm.js +2 -2
  14. package/dist/lib/Commander.d.ts +2 -2
  15. package/dist/lib/LifecyclePlugins.d.ts +1 -1
  16. package/dist/lib/Logger.d.ts +1 -1
  17. package/dist/lib/PluginHandler.d.ts +1 -1
  18. package/dist/lib/PluginLoader.d.ts +1 -1
  19. package/dist/lib/Request.d.ts +1 -1
  20. package/dist/plugins/beforetransformer/compress.d.ts +1 -1
  21. package/dist/plugins/beforetransformer/watermark.d.ts +1 -1
  22. package/dist/plugins/beforeupload/buildInRename.d.ts +1 -1
  23. package/dist/plugins/commander/config.d.ts +1 -1
  24. package/dist/plugins/commander/i18n.d.ts +1 -1
  25. package/dist/plugins/commander/index.d.ts +1 -1
  26. package/dist/plugins/commander/init.d.ts +1 -1
  27. package/dist/plugins/commander/pluginHandler.d.ts +1 -1
  28. package/dist/plugins/commander/proxy.d.ts +1 -1
  29. package/dist/plugins/commander/setting.d.ts +1 -1
  30. package/dist/plugins/commander/upload.d.ts +1 -1
  31. package/dist/plugins/commander/use.d.ts +1 -1
  32. package/dist/plugins/transformer/base64.d.ts +1 -1
  33. package/dist/plugins/transformer/index.d.ts +1 -1
  34. package/dist/plugins/transformer/path.d.ts +1 -1
  35. package/dist/plugins/uploader/aliyun.d.ts +1 -1
  36. package/dist/plugins/uploader/awss3plist.d.ts +1 -1
  37. package/dist/plugins/uploader/github.d.ts +1 -1
  38. package/dist/plugins/uploader/imgur.d.ts +1 -1
  39. package/dist/plugins/uploader/index.d.ts +1 -1
  40. package/dist/plugins/uploader/local.d.ts +1 -1
  41. package/dist/plugins/uploader/lsky.d.ts +1 -1
  42. package/dist/plugins/uploader/piclist.d.ts +1 -1
  43. package/dist/plugins/uploader/qiniu.d.ts +1 -1
  44. package/dist/plugins/uploader/s3/uploader.d.ts +1 -1
  45. package/dist/plugins/uploader/s3/utils.d.ts +1 -1
  46. package/dist/plugins/uploader/sftp.d.ts +1 -1
  47. package/dist/plugins/uploader/smms.d.ts +1 -1
  48. package/dist/plugins/uploader/tcyun.d.ts +1 -1
  49. package/dist/plugins/uploader/telegraph.d.ts +1 -1
  50. package/dist/plugins/uploader/upyun.d.ts +1 -1
  51. package/dist/plugins/uploader/webdav.d.ts +1 -1
  52. package/dist/types/index.d.ts +5 -5
  53. package/dist/utils/common.d.ts +1 -1
  54. package/dist/utils/createContext.d.ts +1 -1
  55. package/dist/utils/db.d.ts +2 -2
  56. package/dist/utils/getClipboardImage.d.ts +1 -1
  57. package/dist/utils/initUtils.d.ts +1 -1
  58. package/dist/utils/interfaces.d.ts +3 -3
  59. package/dist/utils/sshClient.d.ts +10 -11
  60. package/package.json +6 -4
  61. package/rollup.config.js +93 -93
package/bin/picgo-server CHANGED
@@ -1,491 +1,522 @@
1
- #!/usr/bin/env node
2
- const http = require('http')
3
- const multer = require('multer')
4
- const axios = require('axios')
5
- const minimist = require('minimist')
6
- const fs = require('fs-extra')
7
- const path = require('path')
8
- const os = require('os')
9
-
10
- const tempDir = path.join(os.homedir(), '.piclist', 'serverTemp')
11
- fs.ensureDirSync(tempDir)
12
-
13
- const argv = minimist(process.argv.slice(2))
14
-
15
- let key = ''
16
- let defaultPort = 36677
17
- let defaultHost = '0.0.0.0'
18
-
19
- if (argv.h || argv.help) {
20
- showHelp()
21
- process.exit(0)
22
- }
23
-
24
- if (argv.v || argv.version) {
25
- showVersion()
26
- process.exit(0)
27
- }
28
-
29
- if (argv.k || argv.key) {
30
- key = String(argv.k || argv.key)
31
- }
32
-
33
- if (argv.p || argv.port) {
34
- defaultPort = Number(argv.p || argv.port) || defaultPort
35
- }
36
-
37
- if (argv.host) {
38
- defaultHost = String(argv.host)
39
- }
40
-
41
- function showVersion() {
42
- const pkg = require('../package.json')
43
- console.log(`PicList-Core ${pkg.version}`)
44
- }
45
-
46
- function showHelp() {
47
- console.log(`
48
- Usage: picgo-server [options]
49
- Options:
50
- -h, --help Print this help message
51
- -c, --config Set config path
52
- -p, --port Set port, default port is 36677
53
- --host Set host, default host is 0.0.0.0
54
- -k, --key Set secret key to avoid unauthorized access
55
- -v, --version Print version number
56
-
57
- Example:
58
- picgo-server -c /path/to/config.json
59
- picgo-server -k 123456
60
- picgo-server -c /path/to/config.json -k 123456
61
- `)
62
- }
63
-
64
- let configPath = argv.c || argv.config || ''
65
- if (configPath !== true && configPath !== '') {
66
- configPath = configPath.replace(/^~/, os.homedir())
67
- configPath = path.resolve(configPath)
68
- } else {
69
- configPath = ''
70
- }
71
-
72
- const { PicGo } = require('..')
73
- const picgo = new PicGo(configPath)
74
- const errorMessage = 'Upload failed, please check your network and config'
75
-
76
- handleResponse = ({
77
- response,
78
- statusCode = 200,
79
- header = {
80
- 'Content-Type': 'application/json',
81
- 'access-control-allow-headers': '*',
82
- 'access-control-allow-methods': 'POST, GET, OPTIONS',
83
- 'access-control-allow-origin': '*'
84
- },
85
- body = {
86
- success: false
87
- }
88
- }) => {
89
- if (body && body.success === false) {
90
- console.log('[PicList Server] upload failed, see log for more detail ↑')
91
- }
92
- response.writeHead(statusCode, header)
93
- response.write(JSON.stringify(body))
94
- response.end()
95
- }
96
-
97
- ensureHTTPLink = url => {
98
- return url.startsWith('http') ? url : `http://${url}`
99
- }
100
-
101
- class Router {
102
- constructor() {
103
- this.router = new Map()
104
- }
105
-
106
- addRoute (method, url, callback, urlparams) {
107
- if (!this.router.has(url)) {
108
- this.router.set(url, new Map())
109
- }
110
- this.router.get(url).set(method, { handler: callback, urlparams })
111
- }
112
-
113
- get (url, callback, urlparams) {
114
- this.addRoute('GET', url, callback, urlparams)
115
- }
116
-
117
- post(url, callback, urlparams) {
118
- this.addRoute('POST', url, callback, urlparams)
119
- }
120
-
121
- any(url, callback, urlparams) {
122
- this.addRoute('GET', url, callback, urlparams)
123
- this.addRoute('POST', url, callback, urlparams)
124
- }
125
-
126
- getHandler (url, method) {
127
- if (this.router.has(url)) {
128
- const methods = this.router.get(url)
129
- if (methods.has(method)) {
130
- return methods.get(method)
131
- }
132
- }
133
- return null
134
- }
135
- }
136
-
137
- let router = new Router()
138
-
139
- const multerStorage = multer.diskStorage({
140
- destination: function (_req, _file, cb) {
141
- if (!fs.existsSync(tempDir)) {
142
- fs.mkdirSync(tempDir)
143
- }
144
- cb(null, tempDir)
145
- },
146
- filename: function (_req, file, cb) {
147
- if (!/[^\u0000-\u00ff]/.test(file.originalname)) {
148
- file.originalname = Buffer.from(file.originalname, 'latin1').toString(
149
- 'utf8'
150
- )
151
- }
152
- cb(null, file.originalname)
153
- }
154
- })
155
-
156
- const uploadMulter = multer({
157
- storage: multerStorage
158
- })
159
-
160
- router.get('/', responseForGet)
161
- router.get('/upload', responseForGet)
162
-
163
- router.post('/upload', async ({ response, list = [], urlparams }) => {
164
- try {
165
- const picbed = urlparams?.get('picbed')
166
- const passedKey = urlparams?.get('key')
167
- if (key && key !== passedKey) {
168
- console.log('[PicList Server] Unauthorized access')
169
- return handleResponse({
170
- response,
171
- body: {
172
- success: false,
173
- message: 'Unauthorized access'
174
- }
175
- })
176
- }
177
- let currentPicBedType = ''
178
- let needRestore = false
179
- if (picbed) {
180
- const currentPicBed = picgo.getConfig('picBed') || {}
181
- currentPicBedType = currentPicBed?.uploader || currentPicBed?.current || 'smms'
182
- if (picbed === currentPicBedType) {
183
- // do nothing
184
- } else {
185
- needRestore = true
186
- picgo.setConfig({
187
- 'picBed.current': picbed
188
- })
189
- picgo.setConfig({
190
- 'picBed.uploader': picbed
191
- })
192
- }
193
- }
194
- if (list.length === 0) {
195
- // upload with clipboard
196
- console.log('[PicList Server] upload clipboard file')
197
- const result = await picgo.upload()
198
- const res = result[0].imgUrl
199
- console.log('[PicList Server] upload result:', res)
200
- if (res) {
201
- handleResponse({
202
- response,
203
- body: {
204
- success: true,
205
- result: [res]
206
- }
207
- })
208
- } else {
209
- handleResponse({
210
- response,
211
- body: {
212
- success: false,
213
- message: errorMessage
214
- }
215
- })
216
- }
217
- } else {
218
- console.log('[PicList Server] upload files in list')
219
- // upload with files
220
- const result = await picgo.upload(list)
221
- const res = result.map(item => {
222
- return item.imgUrl
223
- })
224
- console.log('[PicList Server] upload result\n', res.join('\n'))
225
- if (res.length) {
226
- handleResponse({
227
- response,
228
- body: {
229
- success: true,
230
- result: res
231
- }
232
- })
233
- } else {
234
- handleResponse({
235
- response,
236
- body: {
237
- success: false,
238
- message: errorMessage
239
- }
240
- })
241
- }
242
- }
243
- fs.emptyDirSync(tempDir)
244
- if (needRestore) {
245
- picgo.setConfig({
246
- 'picBed.current': currentPicBedType
247
- })
248
- picgo.setConfig({
249
- 'picBed.uploader': currentPicBedType
250
- })
251
- }
252
- } catch (err) {
253
- console.log(err)
254
- handleResponse({
255
- response,
256
- body: {
257
- success: false,
258
- message: errorMessage
259
- }
260
- })
261
- }
262
- })
263
-
264
- async function responseForGet ({ response }) {
265
- response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
266
- response.write(`
267
- <!DOCTYPE html>
268
- <html lang="en">
269
- <head>
270
- <meta charset="UTF-8">
271
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
272
- <title>Picgo-Server Usage</title>
273
- <style>
274
- body { font-family: Arial, sans-serif; margin: 20px; }
275
- pre { background-color: #f4f4f4; padding: 10px; }
276
- .tip { padding: 10px; background-color: #f0f9ff; border-left: 5px solid #2196F3; margin-bottom: 20px; }
277
- </style>
278
- </head>
279
- <body>
280
- <h2>picgo-server的使用</h2>
281
- <p><code>picgo-server</code>命令用于启动web服务,接收来自其他应用或其他主机的HTTP请求来上传图片。</p>
282
- <p>默认监听地址:<code>0.0.0.0</code>,默认监听端口:<code>36677</code></p>
283
-
284
- <h3>接口鉴权</h3>
285
- <p>当将接口暴露于公网时,为了防止恶意上传,可以使用接口鉴权功能。通过在运行<code>picgo-server</code>时添加<code>-k</code>或<code>--key</code>参数来设置一个密钥。</p>
286
- <p>发送请求时添加URL查询参数<code>key</code>即可,例如:<code>http://xxx:36677/upload?key=xxx</code>。</p>
287
-
288
- <h3>表单上传</h3>
289
- <ul>
290
- <li>请求方法: <code>POST</code></li>
291
- <li>url: <code>http://127.0.0.1:36677/upload</code> (此处以默认配置为例)</li>
292
- <li>请求body: <code>multipart/form-data</code>格式,key任选,value为图片文件</li>
293
- </ul>
294
-
295
- <h3>HTTP调用上传剪贴板图片</h3>
296
- <ul>
297
- <li>请求方法: <code>POST</code></li>
298
- <li>url: <code>http://127.0.0.1:36677/upload</code> (此处以默认配置为例)</li>
299
- <li>请求body: <code>{list: ['xxx.jpg']}</code> 必须是JSON格式</li>
300
- </ul>
301
-
302
- <div class="tip">Tip: PicList支持通过设置<code>picbed</code>和<code>configName</code>两个URL查询参数来指定上传图床和配置文件。例如:<code>http://127.0.0.1:36677/upload?picbed=aws-s3&configName=piclist-test</code> 该配置将会使用<code>aws-s3</code>图床,并且使用<code>piclist-test</code>配置文件。</div>
303
-
304
- <p>返回的数据:</p>
305
- <pre>{
306
- "success": true, // or false
307
- "result": ["url"]
308
- }</pre>
309
-
310
- <h3>HTTP调用上传具体路径图片</h3>
311
- <ul>
312
- <li>method: <code>POST</code></li>
313
- <li>url: <code>http://127.0.0.1:36677/upload</code> (此处以默认配置为例)</li>
314
- <li>request body: <code>{list: ['xxx.jpg']}</code> 必须是JSON格式</li>
315
- </ul>
316
-
317
- <p>返回的数据:</p>
318
- <pre>{
319
- "success": true, // or false
320
- "result": ["url"]
321
- }</pre>
322
-
323
- </body>
324
- </html>
325
- `)
326
- response.end()
327
- }
328
-
329
- router.any('/heartbeat', async ({ response }) => {
330
- handleResponse({
331
- response,
332
- body: {
333
- success: true,
334
- result: 'alive'
335
- }
336
- })
337
- })
338
-
339
- class Server {
340
- constructor() {
341
- this.httpServer = http.createServer(this.handleRequest)
342
- }
343
-
344
- handleRequest(request, response) {
345
- if (request.method === 'OPTIONS') {
346
- handleResponse({ response })
347
- return
348
- }
349
-
350
- if (request.method === 'POST') {
351
- const [url, query] = (request.url || '').split('?')
352
- if (!router.getHandler(url, 'POST')) {
353
- console.log(`[PicList Server] don't support [${url}] endpoint`)
354
- handleResponse({
355
- response,
356
- statusCode: 404,
357
- body: {
358
- success: false
359
- }
360
- })
361
- } else {
362
- let urlSP = query ? new URLSearchParams(query) : undefined
363
- console.log('[PicList Server] get the request from IP:', request.socket.remoteAddress)
364
- if (request.socket.remoteAddress === '127.0.0.1' || request.socket.remoteAddress === '::1') {
365
- if (urlSP) {
366
- urlSP.set('key', key)
367
- } else {
368
- urlSP = new URLSearchParams('key=' + key)
369
- }
370
- }
371
- if (request.headers['content-type'] && request.headers['content-type'].startsWith('multipart/form-data')) {
372
- uploadMulter.any()(request, response, err => {
373
- if (err) {
374
- console.log('[PicList Server]', err)
375
- return handleResponse({
376
- response,
377
- body: {
378
- success: false,
379
- message: 'Error processing formData'
380
- }
381
- })
382
- }
383
-
384
- const list = request.files.map(file => file.path)
385
-
386
- const handler = router.getHandler(url, 'POST')?.handler
387
- if (handler) {
388
- handler({
389
- list: list,
390
- response,
391
- urlparams:urlSP
392
- })
393
- }
394
- })
395
- } else {
396
- let body = ''
397
- let postObj
398
- request.on('data', chunk => {
399
- body += chunk
400
- })
401
- request.on('end', () => {
402
- try {
403
- postObj = body === '' ? {} : JSON.parse(body)
404
- } catch (err) {
405
- console.log('[PicList Server]', err)
406
- return handleResponse({
407
- response,
408
- body: {
409
- success: false,
410
- message: 'Not sending data in JSON format'
411
- }
412
- })
413
- }
414
- console.log('[PicList Server] get the request', body)
415
- const handler = router.getHandler(url, 'POST')?.handler
416
- if (handler) {
417
- handler({
418
- ...postObj,
419
- response,
420
- urlparams: urlSP
421
- })
422
- }
423
- })
424
- }
425
- }
426
- } else if (request.method === 'GET') {
427
- const [url, query] = (request.url || '').split('?')
428
- if (!router.getHandler(url, 'GET')) {
429
- console.log(`[PicList Server] don't support [${url}] endpoint`)
430
- response.statusCode = 404
431
- response.end()
432
- } else {
433
- const handler = router.getHandler(url, 'GET')?.handler
434
- if (handler) {
435
- handler({
436
- response,
437
- urlparams: query ? new URLSearchParams(query) : undefined
438
- })
439
- }
440
- }
441
- } else {
442
- console.log(`[PicList Server] don't support [${request.method}] method`)
443
- response.statusCode = 405
444
- response.end()
445
- }
446
- }
447
-
448
- listen(port) {
449
- console.log(`[PicList Server] is listening at ${port}`)
450
- if (typeof port === 'string') {
451
- port = parseInt(port, 10)
452
- }
453
- this.httpServer.listen(port, defaultHost).on('error', async err => {
454
- if (err.code === 'EADDRINUSE') {
455
- try {
456
- await axios.post(ensureHTTPLink(`${defaultHost}:${port}/heartbeat`))
457
- console.log(`[PicList Server] server is already running at ${port}, no need to start again`)
458
- this.shutdown(true)
459
- } catch (e) {
460
- console.log(`[PicList Server] ${port} is busy, trying with port ${port + 1}`)
461
- this.listen(port + 1)
462
- }
463
- } else {
464
- console.log(`[PicList Server] failed to start server at ${port}`)
465
- console.log(`[PicList Server] ${err}`)
466
- }
467
- })
468
- }
469
-
470
- startup() {
471
- console.log('startup')
472
- this.listen(defaultPort)
473
- }
474
-
475
- shutdown(hasStarted) {
476
- this.httpServer.close()
477
- if (!hasStarted) {
478
- console.log('[PicList Server] shutdown')
479
- }
480
- }
481
-
482
- restart() {
483
- this.shutdown()
484
- this.startup()
485
- }
486
- }
487
-
488
- const server = new Server()
489
- server.startup()
490
-
491
- module.exports = server
1
+ #!/usr/bin/env node
2
+ const http = require('http')
3
+ const multer = require('multer')
4
+ const axios = require('axios')
5
+ const minimist = require('minimist')
6
+ const fs = require('fs-extra')
7
+ const path = require('path')
8
+ const os = require('os')
9
+
10
+ const tempDir = path.join(os.homedir(), '.piclist', 'serverTemp')
11
+ fs.ensureDirSync(tempDir)
12
+
13
+ const argv = minimist(process.argv.slice(2))
14
+
15
+ let key = ''
16
+ let defaultPort = 36677
17
+ let defaultHost = '0.0.0.0'
18
+
19
+ if (argv.h || argv.help) {
20
+ showHelp()
21
+ process.exit(0)
22
+ }
23
+
24
+ if (argv.v || argv.version) {
25
+ showVersion()
26
+ process.exit(0)
27
+ }
28
+
29
+ if (argv.k || argv.key) {
30
+ key = String(argv.k || argv.key)
31
+ }
32
+
33
+ if (argv.p || argv.port) {
34
+ defaultPort = Number(argv.p || argv.port) || defaultPort
35
+ }
36
+
37
+ if (argv.host) {
38
+ defaultHost = String(argv.host)
39
+ }
40
+
41
+ function showVersion() {
42
+ const pkg = require('../package.json')
43
+ console.log(`PicList-Core ${pkg.version}`)
44
+ }
45
+
46
+ function showHelp() {
47
+ console.log(`
48
+ Usage: picgo-server [options]
49
+ Options:
50
+ -h, --help Print this help message
51
+ -c, --config Set config path
52
+ -p, --port Set port, default port is 36677
53
+ --host Set host, default host is 0.0.0.0
54
+ -k, --key Set secret key to avoid unauthorized access
55
+ -v, --version Print version number
56
+
57
+ Example:
58
+ picgo-server -c /path/to/config.json
59
+ picgo-server -k 123456
60
+ picgo-server -c /path/to/config.json -k 123456
61
+ `)
62
+ }
63
+
64
+ let configPath = argv.c || argv.config || ''
65
+ if (configPath !== true && configPath !== '') {
66
+ configPath = configPath.replace(/^~/, os.homedir())
67
+ configPath = path.resolve(configPath)
68
+ } else {
69
+ configPath = ''
70
+ }
71
+
72
+ const { PicGo } = require('..')
73
+ const picgo = new PicGo(configPath)
74
+ const errorMessage = 'Upload failed, please check your network and config'
75
+
76
+ handleResponse = ({
77
+ response,
78
+ statusCode = 200,
79
+ header = {
80
+ 'Content-Type': 'application/json',
81
+ 'access-control-allow-headers': '*',
82
+ 'access-control-allow-methods': 'POST, GET, OPTIONS',
83
+ 'access-control-allow-origin': '*'
84
+ },
85
+ body = {
86
+ success: false
87
+ }
88
+ }) => {
89
+ if (body && body.success === false) {
90
+ console.log('[PicList Server] upload failed, see log for more detail ↑')
91
+ }
92
+ response.writeHead(statusCode, header)
93
+ response.write(JSON.stringify(body))
94
+ response.end()
95
+ }
96
+
97
+ ensureHTTPLink = url => {
98
+ return url.startsWith('http') ? url : `http://${url}`
99
+ }
100
+
101
+ class Router {
102
+ constructor() {
103
+ this.router = new Map()
104
+ }
105
+
106
+ addRoute(method, url, callback, urlparams) {
107
+ if (!this.router.has(url)) {
108
+ this.router.set(url, new Map())
109
+ }
110
+ this.router.get(url).set(method, { handler: callback, urlparams })
111
+ }
112
+
113
+ get(url, callback, urlparams) {
114
+ this.addRoute('GET', url, callback, urlparams)
115
+ }
116
+
117
+ post(url, callback, urlparams) {
118
+ this.addRoute('POST', url, callback, urlparams)
119
+ }
120
+
121
+ any(url, callback, urlparams) {
122
+ this.addRoute('GET', url, callback, urlparams)
123
+ this.addRoute('POST', url, callback, urlparams)
124
+ }
125
+
126
+ getHandler(url, method) {
127
+ if (this.router.has(url)) {
128
+ const methods = this.router.get(url)
129
+ if (methods.has(method)) {
130
+ return methods.get(method)
131
+ }
132
+ }
133
+ return null
134
+ }
135
+ }
136
+
137
+ let router = new Router()
138
+
139
+ const multerStorage = multer.diskStorage({
140
+ destination: function (_req, _file, cb) {
141
+ if (!fs.existsSync(tempDir)) {
142
+ fs.mkdirSync(tempDir)
143
+ }
144
+ cb(null, tempDir)
145
+ },
146
+ filename: function (_req, file, cb) {
147
+ if (!/[^\u0000-\u00ff]/.test(file.originalname)) {
148
+ file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
149
+ }
150
+ cb(null, file.originalname)
151
+ }
152
+ })
153
+
154
+ const uploadMulter = multer({
155
+ storage: multerStorage
156
+ })
157
+
158
+ const changeCurrentUploader = (type, config, id) => {
159
+ if (!type) return
160
+ if (id) {
161
+ picgo.saveConfig({
162
+ [`uploader.${type}.defaultId`]: id
163
+ })
164
+ }
165
+ if (config) {
166
+ picgo.saveConfig({
167
+ [`picBed.${type}`]: config
168
+ })
169
+ }
170
+ picgo.saveConfig({
171
+ 'picBed.current': type,
172
+ 'picBed.uploader': type
173
+ })
174
+ }
175
+
176
+ router.get('/', responseForGet)
177
+ router.get('/upload', responseForGet)
178
+
179
+ router.post('/upload', async ({ response, list = [], urlparams }) => {
180
+ try {
181
+ const picbed = urlparams?.get('picbed')
182
+ const passedKey = urlparams?.get('key')
183
+ if (key && key !== passedKey) {
184
+ console.log('[PicList Server] Unauthorized access')
185
+ return handleResponse({
186
+ response,
187
+ body: {
188
+ success: false,
189
+ message: 'Unauthorized access'
190
+ }
191
+ })
192
+ }
193
+ let currentPicBedType = ''
194
+ let currentPicBedConfig = {}
195
+ let currentPicBedConfigId = ''
196
+ const currentPicBed = picgo.getConfig('picBed') || {}
197
+ let needRestore = false
198
+ if (picbed) {
199
+ currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
200
+ currentPicBedConfig = currentPicBed[currentPicBedType] || {}
201
+ currentPicBedConfigId = currentPicBedConfig._id
202
+ const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
203
+ if (picbed === currentPicBedType && currentPicBedConfig._configName === configName) {
204
+ // do nothing
205
+ } else {
206
+ if (picbed !== currentPicBedType) {
207
+ needRestore = true
208
+ picgo.saveConfig({
209
+ 'picBed.current': picbed,
210
+ 'picBed.uploader': picbed
211
+ })
212
+ }
213
+ if (currentPicBed[picbed]?._configName) {
214
+ needRestore = true
215
+ const picBeds = picgo.getConfig('uploader')
216
+ const currentPicBedList = picBeds?.[picbed]?.configList
217
+ if (currentPicBedList) {
218
+ const currentConfig = currentPicBedList?.find((item) => item._configName === configName)
219
+ changeCurrentUploader(picbed, currentConfig, currentConfig._id)
220
+ }
221
+ }
222
+ }
223
+ }
224
+ if (list.length === 0) {
225
+ // upload with clipboard
226
+ console.log('[PicList Server] upload clipboard file')
227
+ const result = await picgo.upload()
228
+ const res = result[0].imgUrl
229
+ console.log('[PicList Server] upload result:', res)
230
+ if (res) {
231
+ handleResponse({
232
+ response,
233
+ body: {
234
+ success: true,
235
+ result: [res]
236
+ }
237
+ })
238
+ } else {
239
+ handleResponse({
240
+ response,
241
+ body: {
242
+ success: false,
243
+ message: errorMessage
244
+ }
245
+ })
246
+ }
247
+ } else {
248
+ console.log('[PicList Server] upload files in list')
249
+ // upload with files
250
+ const result = await picgo.upload(list)
251
+ const res = result.map(item => {
252
+ return item.imgUrl
253
+ })
254
+ console.log('[PicList Server] upload result\n', res.join('\n'))
255
+ if (res.length) {
256
+ handleResponse({
257
+ response,
258
+ body: {
259
+ success: true,
260
+ result: res
261
+ }
262
+ })
263
+ } else {
264
+ handleResponse({
265
+ response,
266
+ body: {
267
+ success: false,
268
+ message: errorMessage
269
+ }
270
+ })
271
+ }
272
+ }
273
+ fs.emptyDirSync(tempDir)
274
+ if (needRestore) {
275
+ picgo.saveConfig({
276
+ 'picBed.current': currentPicBedType,
277
+ 'picBed.uploader': currentPicBedType
278
+ })
279
+ if (currentPicBed[picbed]?._configName) {
280
+ changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
281
+ }
282
+ }
283
+ } catch (err) {
284
+ console.log(err)
285
+ handleResponse({
286
+ response,
287
+ body: {
288
+ success: false,
289
+ message: errorMessage
290
+ }
291
+ })
292
+ }
293
+ })
294
+
295
+ async function responseForGet({ response }) {
296
+ response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
297
+ response.write(`
298
+ <!DOCTYPE html>
299
+ <html lang="en">
300
+ <head>
301
+ <meta charset="UTF-8">
302
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
303
+ <title>Picgo-Server Usage</title>
304
+ <style>
305
+ body { font-family: Arial, sans-serif; margin: 20px; }
306
+ pre { background-color: #f4f4f4; padding: 10px; }
307
+ .tip { padding: 10px; background-color: #f0f9ff; border-left: 5px solid #2196F3; margin-bottom: 20px; }
308
+ </style>
309
+ </head>
310
+ <body>
311
+ <h2>picgo-server的使用</h2>
312
+ <p><code>picgo-server</code>命令用于启动web服务,接收来自其他应用或其他主机的HTTP请求来上传图片。</p>
313
+ <p>默认监听地址:<code>0.0.0.0</code>,默认监听端口:<code>36677</code></p>
314
+
315
+ <h3>接口鉴权</h3>
316
+ <p>当将接口暴露于公网时,为了防止恶意上传,可以使用接口鉴权功能。通过在运行<code>picgo-server</code>时添加<code>-k</code>或<code>--key</code>参数来设置一个密钥。</p>
317
+ <p>发送请求时添加URL查询参数<code>key</code>即可,例如:<code>http://xxx:36677/upload?key=xxx</code>。</p>
318
+
319
+ <h3>表单上传</h3>
320
+ <ul>
321
+ <li>请求方法: <code>POST</code></li>
322
+ <li>url: <code>http://127.0.0.1:36677/upload</code> (此处以默认配置为例)</li>
323
+ <li>请求body: <code>multipart/form-data</code>格式,key任选,value为图片文件</li>
324
+ </ul>
325
+
326
+ <h3>HTTP调用上传剪贴板图片</h3>
327
+ <ul>
328
+ <li>请求方法: <code>POST</code></li>
329
+ <li>url: <code>http://127.0.0.1:36677/upload</code> (此处以默认配置为例)</li>
330
+ <li>请求body: <code>{list: ['xxx.jpg']}</code> 必须是JSON格式</li>
331
+ </ul>
332
+
333
+ <div class="tip">Tip: PicList支持通过设置<code>picbed</code>和<code>configName</code>两个URL查询参数来指定上传图床和配置文件。例如:<code>http://127.0.0.1:36677/upload?picbed=aws-s3&configName=piclist-test</code> 该配置将会使用<code>aws-s3</code>图床,并且使用<code>piclist-test</code>配置文件。</div>
334
+
335
+ <p>返回的数据:</p>
336
+ <pre>{
337
+ "success": true, // or false
338
+ "result": ["url"]
339
+ }</pre>
340
+
341
+ <h3>HTTP调用上传具体路径图片</h3>
342
+ <ul>
343
+ <li>method: <code>POST</code></li>
344
+ <li>url: <code>http://127.0.0.1:36677/upload</code> (此处以默认配置为例)</li>
345
+ <li>request body: <code>{list: ['xxx.jpg']}</code> 必须是JSON格式</li>
346
+ </ul>
347
+
348
+ <p>返回的数据:</p>
349
+ <pre>{
350
+ "success": true, // or false
351
+ "result": ["url"]
352
+ }</pre>
353
+
354
+ </body>
355
+ </html>
356
+ `)
357
+ response.end()
358
+ }
359
+
360
+ router.any('/heartbeat', async ({ response }) => {
361
+ handleResponse({
362
+ response,
363
+ body: {
364
+ success: true,
365
+ result: 'alive'
366
+ }
367
+ })
368
+ })
369
+
370
+ class Server {
371
+ constructor() {
372
+ this.httpServer = http.createServer(this.handleRequest)
373
+ }
374
+
375
+ handleRequest(request, response) {
376
+ if (request.method === 'OPTIONS') {
377
+ handleResponse({ response })
378
+ return
379
+ }
380
+
381
+ if (request.method === 'POST') {
382
+ const [url, query] = (request.url || '').split('?')
383
+ if (!router.getHandler(url, 'POST')) {
384
+ console.log(`[PicList Server] don't support [${url}] endpoint`)
385
+ handleResponse({
386
+ response,
387
+ statusCode: 404,
388
+ body: {
389
+ success: false
390
+ }
391
+ })
392
+ } else {
393
+ let urlSP = query ? new URLSearchParams(query) : undefined
394
+ console.log('[PicList Server] get the request from IP:', request.socket.remoteAddress)
395
+ if (request.socket.remoteAddress === '127.0.0.1' || request.socket.remoteAddress === '::1') {
396
+ if (urlSP) {
397
+ urlSP.set('key', key)
398
+ } else {
399
+ urlSP = new URLSearchParams('key=' + key)
400
+ }
401
+ }
402
+ if (request.headers['content-type'] && request.headers['content-type'].startsWith('multipart/form-data')) {
403
+ uploadMulter.any()(request, response, err => {
404
+ if (err) {
405
+ console.log('[PicList Server]', err)
406
+ return handleResponse({
407
+ response,
408
+ body: {
409
+ success: false,
410
+ message: 'Error processing formData'
411
+ }
412
+ })
413
+ }
414
+
415
+ const list = request.files.map(file => file.path)
416
+
417
+ const handler = router.getHandler(url, 'POST')?.handler
418
+ if (handler) {
419
+ handler({
420
+ list: list,
421
+ response,
422
+ urlparams: urlSP
423
+ })
424
+ }
425
+ })
426
+ } else {
427
+ let body = ''
428
+ let postObj
429
+ request.on('data', chunk => {
430
+ body += chunk
431
+ })
432
+ request.on('end', () => {
433
+ try {
434
+ postObj = body === '' ? {} : JSON.parse(body)
435
+ } catch (err) {
436
+ console.log('[PicList Server]', err)
437
+ return handleResponse({
438
+ response,
439
+ body: {
440
+ success: false,
441
+ message: 'Not sending data in JSON format'
442
+ }
443
+ })
444
+ }
445
+ console.log('[PicList Server] get the request', body)
446
+ const handler = router.getHandler(url, 'POST')?.handler
447
+ if (handler) {
448
+ handler({
449
+ ...postObj,
450
+ response,
451
+ urlparams: urlSP
452
+ })
453
+ }
454
+ })
455
+ }
456
+ }
457
+ } else if (request.method === 'GET') {
458
+ const [url, query] = (request.url || '').split('?')
459
+ if (!router.getHandler(url, 'GET')) {
460
+ console.log(`[PicList Server] don't support [${url}] endpoint`)
461
+ response.statusCode = 404
462
+ response.end()
463
+ } else {
464
+ const handler = router.getHandler(url, 'GET')?.handler
465
+ if (handler) {
466
+ handler({
467
+ response,
468
+ urlparams: query ? new URLSearchParams(query) : undefined
469
+ })
470
+ }
471
+ }
472
+ } else {
473
+ console.log(`[PicList Server] don't support [${request.method}] method`)
474
+ response.statusCode = 405
475
+ response.end()
476
+ }
477
+ }
478
+
479
+ listen(port) {
480
+ console.log(`[PicList Server] is listening at ${port}`)
481
+ if (typeof port === 'string') {
482
+ port = parseInt(port, 10)
483
+ }
484
+ this.httpServer.listen(port, defaultHost).on('error', async err => {
485
+ if (err.code === 'EADDRINUSE') {
486
+ try {
487
+ await axios.post(ensureHTTPLink(`${defaultHost}:${port}/heartbeat`))
488
+ console.log(`[PicList Server] server is already running at ${port}, no need to start again`)
489
+ this.shutdown(true)
490
+ } catch (e) {
491
+ console.log(`[PicList Server] ${port} is busy, trying with port ${port + 1}`)
492
+ this.listen(port + 1)
493
+ }
494
+ } else {
495
+ console.log(`[PicList Server] failed to start server at ${port}`)
496
+ console.log(`[PicList Server] ${err}`)
497
+ }
498
+ })
499
+ }
500
+
501
+ startup() {
502
+ console.log('startup')
503
+ this.listen(defaultPort)
504
+ }
505
+
506
+ shutdown(hasStarted) {
507
+ this.httpServer.close()
508
+ if (!hasStarted) {
509
+ console.log('[PicList Server] shutdown')
510
+ }
511
+ }
512
+
513
+ restart() {
514
+ this.shutdown()
515
+ this.startup()
516
+ }
517
+ }
518
+
519
+ const server = new Server()
520
+ server.startup()
521
+
522
+ module.exports = server