piclist 1.2.2 → 1.3.1
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/README.md +1 -0
- package/bin/picgo-server +335 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Based on Picgo-Core and add more features.
|
|
|
20
20
|
- Add support for advanced rename, you can set the rename rule through `picgo set buildin rename` under the CLI command
|
|
21
21
|
- Add new built-in picbed: WebDAV, SFTP, Local path
|
|
22
22
|
- Adds support for imgur account uploads
|
|
23
|
+
- Built-in server just like PicList-Desktop server, you can use `picgo-server` to start the server
|
|
23
24
|
- Fix several bugs of PicGo-Core
|
|
24
25
|
|
|
25
26
|
## Installation
|
package/bin/picgo-server
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
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
|
+
let configPath = argv.c || argv.config || ''
|
|
15
|
+
if (configPath !== true && configPath !== '') {
|
|
16
|
+
configPath = path.resolve(configPath)
|
|
17
|
+
} else {
|
|
18
|
+
configPath = ''
|
|
19
|
+
}
|
|
20
|
+
const { PicGo } = require('..')
|
|
21
|
+
const picgo = new PicGo(configPath)
|
|
22
|
+
const errorMessage = 'Upload failed, please check your network and config'
|
|
23
|
+
|
|
24
|
+
handleResponse = ({
|
|
25
|
+
response,
|
|
26
|
+
statusCode = 200,
|
|
27
|
+
header = {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
'access-control-allow-headers': '*',
|
|
30
|
+
'access-control-allow-methods': 'POST, GET, OPTIONS',
|
|
31
|
+
'access-control-allow-origin': '*'
|
|
32
|
+
},
|
|
33
|
+
body = {
|
|
34
|
+
success: false
|
|
35
|
+
}
|
|
36
|
+
}) => {
|
|
37
|
+
if (body && body.success === false) {
|
|
38
|
+
console.log('[PicList Server] upload failed, see log for more detail ↑')
|
|
39
|
+
}
|
|
40
|
+
response.writeHead(statusCode, header)
|
|
41
|
+
response.write(JSON.stringify(body))
|
|
42
|
+
response.end()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ensureHTTPLink = url => {
|
|
46
|
+
return url.startsWith('http') ? url : `http://${url}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class Router {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.router = new Map()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get(url, callback, urlparams) {
|
|
55
|
+
this.router.set(url, { handler: callback, urlparams })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
post(url, callback, urlparams) {
|
|
59
|
+
this.router.set(url, { handler: callback, urlparams })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getHandler(url) {
|
|
63
|
+
if (this.router.has(url)) {
|
|
64
|
+
return this.router.get(url)
|
|
65
|
+
} else {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let router = new Router()
|
|
72
|
+
|
|
73
|
+
const multerStorage = multer.diskStorage({
|
|
74
|
+
destination: function (_req, _file, cb) {
|
|
75
|
+
if (!fs.existsSync(tempDir)) {
|
|
76
|
+
fs.mkdirSync(tempDir)
|
|
77
|
+
}
|
|
78
|
+
cb(null, tempDir)
|
|
79
|
+
},
|
|
80
|
+
filename: function (_req, file, cb) {
|
|
81
|
+
cb(null, file.originalname)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const uploadMulter = multer({
|
|
86
|
+
storage: multerStorage
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
router.post('/upload', async ({response, list = [], urlparams }) => {
|
|
90
|
+
try {
|
|
91
|
+
const picbed = urlparams?.get('picbed')
|
|
92
|
+
let currentPicBedType = ''
|
|
93
|
+
let needRestore = false
|
|
94
|
+
if (picbed) {
|
|
95
|
+
const currentPicBed = picgo.getConfig('picBed') || {}
|
|
96
|
+
currentPicBedType = currentPicBed?.uploader || currentPicBed?.current || 'smms'
|
|
97
|
+
if (picbed === currentPicBedType) {
|
|
98
|
+
// do nothing
|
|
99
|
+
} else {
|
|
100
|
+
needRestore = true
|
|
101
|
+
picgo.setConfig({
|
|
102
|
+
'picBed.current': picbed
|
|
103
|
+
})
|
|
104
|
+
picgo.setConfig({
|
|
105
|
+
'picBed.uploader': picbed
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (list.length === 0) {
|
|
110
|
+
// upload with clipboard
|
|
111
|
+
console.log('[PicList Server] upload clipboard file')
|
|
112
|
+
const result = await picgo.upload()
|
|
113
|
+
const res = result.imgUrl
|
|
114
|
+
console.log('[PicList Server] upload result:', res)
|
|
115
|
+
if (res) {
|
|
116
|
+
handleResponse({
|
|
117
|
+
response,
|
|
118
|
+
body: {
|
|
119
|
+
success: true,
|
|
120
|
+
result: [res]
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
} else {
|
|
124
|
+
handleResponse({
|
|
125
|
+
response,
|
|
126
|
+
body: {
|
|
127
|
+
success: false,
|
|
128
|
+
message: errorMessage
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
console.log('[PicList Server] upload files in list')
|
|
134
|
+
// upload with files
|
|
135
|
+
const result = await picgo.upload(list)
|
|
136
|
+
const res = result.map(item => {
|
|
137
|
+
return item.imgUrl
|
|
138
|
+
})
|
|
139
|
+
console.log('[PicList Server] upload result\n', res.join('\n'))
|
|
140
|
+
if (res.length) {
|
|
141
|
+
handleResponse({
|
|
142
|
+
response,
|
|
143
|
+
body: {
|
|
144
|
+
success: true,
|
|
145
|
+
result: res
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
} else {
|
|
149
|
+
handleResponse({
|
|
150
|
+
response,
|
|
151
|
+
body: {
|
|
152
|
+
success: false,
|
|
153
|
+
message: errorMessage
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
fs.emptyDirSync(tempDir)
|
|
159
|
+
if (needRestore) {
|
|
160
|
+
picgo.setConfig({
|
|
161
|
+
'picBed.current': currentPicBedType
|
|
162
|
+
})
|
|
163
|
+
picgo.setConfig({
|
|
164
|
+
'picBed.uploader': currentPicBedType
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.log(err)
|
|
169
|
+
handleResponse({
|
|
170
|
+
response,
|
|
171
|
+
body: {
|
|
172
|
+
success: false,
|
|
173
|
+
message: errorMessage
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
router.post('/heartbeat', async ({ response }) => {
|
|
180
|
+
handleResponse({
|
|
181
|
+
response,
|
|
182
|
+
body: {
|
|
183
|
+
success: true,
|
|
184
|
+
result: 'alive'
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
class Server {
|
|
190
|
+
constructor() {
|
|
191
|
+
let config = picgo.getConfig('settings.server')
|
|
192
|
+
const result = this.checkIfConfigIsValid(config)
|
|
193
|
+
if (result) {
|
|
194
|
+
this.config = config
|
|
195
|
+
} else {
|
|
196
|
+
config = {
|
|
197
|
+
port: 36677,
|
|
198
|
+
host: '127.0.0.1',
|
|
199
|
+
enable: true
|
|
200
|
+
}
|
|
201
|
+
this.config = config
|
|
202
|
+
picgo.saveConfig({
|
|
203
|
+
'settings.server': config
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
this.httpServer = http.createServer(this.handleRequest)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
checkIfConfigIsValid(config) {
|
|
210
|
+
return config && config.port && config.host && config.enable !== undefined
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
handleRequest(request, response) {
|
|
214
|
+
if (request.method === 'OPTIONS') {
|
|
215
|
+
handleResponse({ response })
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (request.method === 'POST') {
|
|
220
|
+
const [url, query] = (request.url || '').split('?')
|
|
221
|
+
if (!router.getHandler(url)) {
|
|
222
|
+
console.log(`[PicList Server] don't support [${url}] url`)
|
|
223
|
+
handleResponse({
|
|
224
|
+
response,
|
|
225
|
+
statusCode: 404,
|
|
226
|
+
body: {
|
|
227
|
+
success: false
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
} else {
|
|
231
|
+
if (request.headers['content-type'] && request.headers['content-type'].startsWith('multipart/form-data')) {
|
|
232
|
+
uploadMulter.any()(request, response, err => {
|
|
233
|
+
if (err) {
|
|
234
|
+
console.log('[PicList Server]', err)
|
|
235
|
+
return handleResponse({
|
|
236
|
+
response,
|
|
237
|
+
body: {
|
|
238
|
+
success: false,
|
|
239
|
+
message: 'Error processing formData'
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const list = request.files.map(file => file.path)
|
|
245
|
+
|
|
246
|
+
const handler = router.getHandler(url)?.handler
|
|
247
|
+
if (handler) {
|
|
248
|
+
handler({
|
|
249
|
+
list: list,
|
|
250
|
+
response,
|
|
251
|
+
urlparams: query ? new URLSearchParams(query) : undefined
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
} else {
|
|
256
|
+
let body = ''
|
|
257
|
+
let postObj
|
|
258
|
+
request.on('data', chunk => {
|
|
259
|
+
body += chunk
|
|
260
|
+
})
|
|
261
|
+
request.on('end', () => {
|
|
262
|
+
try {
|
|
263
|
+
postObj = body === '' ? {} : JSON.parse(body)
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.log('[PicList Server]', err)
|
|
266
|
+
return handleResponse({
|
|
267
|
+
response,
|
|
268
|
+
body: {
|
|
269
|
+
success: false,
|
|
270
|
+
message: 'Not sending data in JSON format'
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
console.log('[PicList Server] get the request', body)
|
|
275
|
+
const handler = router.getHandler(url)?.handler
|
|
276
|
+
if (handler) {
|
|
277
|
+
handler({
|
|
278
|
+
...postObj,
|
|
279
|
+
response,
|
|
280
|
+
urlparams: query ? new URLSearchParams(query) : undefined
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
console.log(`[PicList Server] don't support [${request.method}] method`)
|
|
288
|
+
response.statusCode = 404
|
|
289
|
+
response.end()
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
listen(port) {
|
|
294
|
+
console.log(`[PicList Server] is listening at ${port}`)
|
|
295
|
+
if (typeof port === 'string') {
|
|
296
|
+
port = parseInt(port, 10)
|
|
297
|
+
}
|
|
298
|
+
this.httpServer.listen(port, this.config.host).on('error', async err => {
|
|
299
|
+
if (err.errno === 'EADDRINUSE') {
|
|
300
|
+
try {
|
|
301
|
+
await axios.post(ensureHTTPLink(`${this.config.host}:${port}/heartbeat`))
|
|
302
|
+
this.shutdown(true)
|
|
303
|
+
} catch (e) {
|
|
304
|
+
console.log(`[PicList Server] ${port} is busy, trying with port ${port + 1}`)
|
|
305
|
+
this.listen(port + 1)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
startup() {
|
|
312
|
+
console.log('startup', this.config.enable)
|
|
313
|
+
if (this.config.enable) {
|
|
314
|
+
this.listen(this.config.port)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
shutdown(hasStarted) {
|
|
319
|
+
this.httpServer.close()
|
|
320
|
+
if (!hasStarted) {
|
|
321
|
+
console.log('[PicList Server] shutdown')
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
restart() {
|
|
326
|
+
this.config = picgo.getConfig('settings.server')
|
|
327
|
+
this.shutdown()
|
|
328
|
+
this.startup()
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const server = new Server()
|
|
333
|
+
server.startup()
|
|
334
|
+
|
|
335
|
+
module.exports = server
|