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 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
@@ -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