mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Write metadata file option, rate limiting login attempts, generic failed login message
This commit is contained in:
		
							parent
							
								
									07207b091d
								
							
						
					
					
						commit
						c82d888b5a
					
				| @ -55,6 +55,11 @@ | |||||||
|       <div class="absolute bottom-0 left-0 w-full py-4 bg-bg" :class="isScrollable ? 'box-shadow-md-up' : 'box-shadow-sm-up border-t border-primary border-opacity-50'"> |       <div class="absolute bottom-0 left-0 w-full py-4 bg-bg" :class="isScrollable ? 'box-shadow-md-up' : 'box-shadow-sm-up border-t border-primary border-opacity-50'"> | ||||||
|         <div class="flex px-4"> |         <div class="flex px-4"> | ||||||
|           <ui-btn v-if="userCanDelete" color="error" type="button" small @click.stop.prevent="deleteAudiobook">Remove</ui-btn> |           <ui-btn v-if="userCanDelete" color="error" type="button" small @click.stop.prevent="deleteAudiobook">Remove</ui-btn> | ||||||
|  | 
 | ||||||
|  |           <ui-tooltip text="(Root User Only) Save a NFO metadata file in your audiobooks directory" class="mx-4"> | ||||||
|  |             <ui-btn v-if="isRootUser" :loading="savingMetadata" color="bg" type="button" class="h-full" small @click.stop.prevent="saveMetadata">Save Metadata</ui-btn> | ||||||
|  |           </ui-tooltip> | ||||||
|  | 
 | ||||||
|           <div class="flex-grow" /> |           <div class="flex-grow" /> | ||||||
|           <ui-btn type="submit">Submit</ui-btn> |           <ui-btn type="submit">Submit</ui-btn> | ||||||
|         </div> |         </div> | ||||||
| @ -87,7 +92,8 @@ export default { | |||||||
|       }, |       }, | ||||||
|       newTags: [], |       newTags: [], | ||||||
|       resettingProgress: false, |       resettingProgress: false, | ||||||
|       isScrollable: false |       isScrollable: false, | ||||||
|  |       savingMetadata: false | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   watch: { |   watch: { | ||||||
| @ -107,6 +113,9 @@ export default { | |||||||
|         this.$emit('update:processing', val) |         this.$emit('update:processing', val) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     isRootUser() { | ||||||
|  |       return this.$store.getters['user/getIsRoot'] | ||||||
|  |     }, | ||||||
|     audiobookId() { |     audiobookId() { | ||||||
|       return this.audiobook ? this.audiobook.id : null |       return this.audiobook ? this.audiobook.id : null | ||||||
|     }, |     }, | ||||||
| @ -127,6 +136,24 @@ export default { | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  |     saveMetadataComplete(result) { | ||||||
|  |       this.savingMetadata = false | ||||||
|  |       if (result.error) { | ||||||
|  |         this.$toast.error(result.error) | ||||||
|  |       } else if (result.audiobookId) { | ||||||
|  |         var { savedPath } = result | ||||||
|  |         if (!savedPath) { | ||||||
|  |           this.$toast.error(`Failed to save metadata file (${result.audiobookId})`) | ||||||
|  |         } else { | ||||||
|  |           this.$toast.success(`Metadata file saved "${result.audiobookTitle}"`) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     saveMetadata() { | ||||||
|  |       this.savingMetadata = true | ||||||
|  |       this.$root.socket.once('save_metadata_complete', this.saveMetadataComplete) | ||||||
|  |       this.$root.socket.emit('save_metadata', this.audiobookId) | ||||||
|  |     }, | ||||||
|     async submitForm() { |     async submitForm() { | ||||||
|       if (this.isProcessing) { |       if (this.isProcessing) { | ||||||
|         return |         return | ||||||
|  | |||||||
| @ -127,6 +127,21 @@ export default { | |||||||
|         this.$store.commit('setScanProgress', progress) |         this.$store.commit('setScanProgress', progress) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     saveMetadataComplete(result) { | ||||||
|  |       if (result.error) { | ||||||
|  |         this.$toast.error(result.error) | ||||||
|  |       } else if (result.audiobookId) { | ||||||
|  |         var { savedPath } = result | ||||||
|  |         if (!savedPath) { | ||||||
|  |           this.$toast.error(`Failed to save metadata file (${result.audiobookId})`) | ||||||
|  |         } else { | ||||||
|  |           this.$toast.success(`Metadata file saved (${result.audiobookId})`) | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         var { success, failed } = result | ||||||
|  |         this.$toast.success(`Metadata save complete\n${success} Succeeded\n${failed} Failed`) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     userUpdated(user) { |     userUpdated(user) { | ||||||
|       if (this.$store.state.user.user.id === user.id) { |       if (this.$store.state.user.user.id === user.id) { | ||||||
|         this.$store.commit('user/setUser', user) |         this.$store.commit('user/setUser', user) | ||||||
| @ -230,6 +245,7 @@ export default { | |||||||
|       this.socket.on('scan_start', this.scanStart) |       this.socket.on('scan_start', this.scanStart) | ||||||
|       this.socket.on('scan_complete', this.scanComplete) |       this.socket.on('scan_complete', this.scanComplete) | ||||||
|       this.socket.on('scan_progress', this.scanProgress) |       this.socket.on('scan_progress', this.scanProgress) | ||||||
|  |       // this.socket.on('save_metadata_complete', this.saveMetadataComplete) | ||||||
| 
 | 
 | ||||||
|       // Download Listeners |       // Download Listeners | ||||||
|       this.socket.on('download_started', this.downloadStarted) |       this.socket.on('download_started', this.downloadStarted) | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "audiobookshelf-client", |   "name": "audiobookshelf-client", | ||||||
|   "version": "1.2.7", |   "version": "1.2.8", | ||||||
|   "description": "Audiobook manager and player", |   "description": "Audiobook manager and player", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
| @ -50,11 +50,13 @@ | |||||||
|           <div class="w-40 flex flex-col"> |           <div class="w-40 flex flex-col"> | ||||||
|             <ui-btn color="success" class="mb-4" :loading="isScanning" :disabled="isScanningCovers" @click="scan">Scan</ui-btn> |             <ui-btn color="success" class="mb-4" :loading="isScanning" :disabled="isScanningCovers" @click="scan">Scan</ui-btn> | ||||||
| 
 | 
 | ||||||
|             <div class="w-full"> |             <div class="w-full mb-4"> | ||||||
|               <ui-tooltip direction="bottom" text="Only scans audiobooks without a cover. Covers will be applied if a close match is found." class="w-full"> |               <ui-tooltip direction="bottom" text="Only scans audiobooks without a cover. Covers will be applied if a close match is found." class="w-full"> | ||||||
|                 <ui-btn color="primary" class="w-full" small :padding-x="2" :loading="isScanningCovers" :disabled="isScanning" @click="scanCovers">Scan for Covers</ui-btn> |                 <ui-btn color="primary" class="w-full" small :padding-x="2" :loading="isScanningCovers" :disabled="isScanning" @click="scanCovers">Scan for Covers</ui-btn> | ||||||
|               </ui-tooltip> |               </ui-tooltip> | ||||||
|             </div> |             </div> | ||||||
|  | 
 | ||||||
|  |             <!-- <ui-btn color="primary" small @click="saveMetadataFiles">Save Metadata</ui-btn> --> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @ -152,6 +154,9 @@ export default { | |||||||
|     scanCovers() { |     scanCovers() { | ||||||
|       this.$root.socket.emit('scan_covers') |       this.$root.socket.emit('scan_covers') | ||||||
|     }, |     }, | ||||||
|  |     saveMetadataFiles() { | ||||||
|  |       this.$root.socket.emit('save_metadata') | ||||||
|  |     }, | ||||||
|     loadUsers() { |     loadUsers() { | ||||||
|       this.$axios |       this.$axios | ||||||
|         .$get('/api/users') |         .$get('/api/users') | ||||||
|  | |||||||
| @ -57,15 +57,14 @@ export default { | |||||||
|         password: this.password || '' |         password: this.password || '' | ||||||
|       } |       } | ||||||
|       var authRes = await this.$axios.$post('/login', payload).catch((error) => { |       var authRes = await this.$axios.$post('/login', payload).catch((error) => { | ||||||
|         console.error('Failed', error) |         console.error('Failed', error.response) | ||||||
|  |         if (error.response) this.error = error.response.data | ||||||
|  |         else this.error = 'Unknown Error' | ||||||
|         return false |         return false | ||||||
|       }) |       }) | ||||||
|       console.log('Auth res', authRes) |       if (authRes && authRes.error) { | ||||||
|       if (!authRes) { |  | ||||||
|         this.error = 'Unknown Failure' |  | ||||||
|       } else if (authRes.error) { |  | ||||||
|         this.error = authRes.error |         this.error = authRes.error | ||||||
|       } else { |       } else if (authRes) { | ||||||
|         this.$store.commit('user/setUser', authRes.user) |         this.$store.commit('user/setUser', authRes.user) | ||||||
|       } |       } | ||||||
|       this.processing = false |       this.processing = false | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ export default function ({ $axios, store }) { | |||||||
| 
 | 
 | ||||||
|   $axios.onError(error => { |   $axios.onError(error => { | ||||||
|     const code = parseInt(error.response && error.response.status) |     const code = parseInt(error.response && error.response.status) | ||||||
|     console.error('Axios error code', code) |     const message = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error' | ||||||
|  |     console.error('Axios error', code, message) | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
							
								
								
									
										432
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										432
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "audiobookshelf", |   "name": "audiobookshelf", | ||||||
|   "version": "1.2.4", |   "version": "1.2.7", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
| @ -69,12 +69,6 @@ | |||||||
|         "@types/node": "*" |         "@types/node": "*" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "abbrev": { |  | ||||||
|       "version": "1.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", |  | ||||||
|       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "aborter": { |     "aborter": { | ||||||
|       "version": "1.1.0", |       "version": "1.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/aborter/-/aborter-1.1.0.tgz", |       "resolved": "https://registry.npmjs.org/aborter/-/aborter-1.1.0.tgz", | ||||||
| @ -89,23 +83,6 @@ | |||||||
|         "negotiator": "0.6.2" |         "negotiator": "0.6.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "adm-zip": { |  | ||||||
|       "version": "0.4.16", |  | ||||||
|       "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", |  | ||||||
|       "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==" |  | ||||||
|     }, |  | ||||||
|     "ansi-regex": { |  | ||||||
|       "version": "2.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", |  | ||||||
|       "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "aproba": { |  | ||||||
|       "version": "1.2.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", |  | ||||||
|       "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "archiver": { |     "archiver": { | ||||||
|       "version": "5.3.0", |       "version": "5.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", |       "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", | ||||||
| @ -161,33 +138,6 @@ | |||||||
|         "is-primitive": "^3.0.1" |         "is-primitive": "^3.0.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "are-we-there-yet": { |  | ||||||
|       "version": "1.1.7", |  | ||||||
|       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", |  | ||||||
|       "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "delegates": "^1.0.0", |  | ||||||
|         "readable-stream": "^2.0.6" |  | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "readable-stream": { |  | ||||||
|           "version": "2.3.7", |  | ||||||
|           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", |  | ||||||
|           "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", |  | ||||||
|           "optional": true, |  | ||||||
|           "requires": { |  | ||||||
|             "core-util-is": "~1.0.0", |  | ||||||
|             "inherits": "~2.0.3", |  | ||||||
|             "isarray": "~1.0.0", |  | ||||||
|             "process-nextick-args": "~2.0.0", |  | ||||||
|             "safe-buffer": "~5.1.1", |  | ||||||
|             "string_decoder": "~1.1.1", |  | ||||||
|             "util-deprecate": "~1.0.1" |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "array-back": { |     "array-back": { | ||||||
|       "version": "3.1.0", |       "version": "3.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", |       "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", | ||||||
| @ -335,12 +285,6 @@ | |||||||
|         "responselike": "^2.0.0" |         "responselike": "^2.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "chownr": { |  | ||||||
|       "version": "1.1.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", |  | ||||||
|       "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "clone-response": { |     "clone-response": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", | ||||||
| @ -349,12 +293,6 @@ | |||||||
|         "mimic-response": "^1.0.0" |         "mimic-response": "^1.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "code-point-at": { |  | ||||||
|       "version": "1.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", |  | ||||||
|       "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "command-line-args": { |     "command-line-args": { | ||||||
|       "version": "5.2.0", |       "version": "5.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.0.tgz", |       "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.0.tgz", | ||||||
| @ -387,12 +325,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", |       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | ||||||
|       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" |       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" | ||||||
|     }, |     }, | ||||||
|     "console-control-strings": { |  | ||||||
|       "version": "1.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", |  | ||||||
|       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "content-disposition": { |     "content-disposition": { | ||||||
|       "version": "0.5.3", |       "version": "0.5.3", | ||||||
|       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", |       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", | ||||||
| @ -485,23 +417,11 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "deep-extend": { |  | ||||||
|       "version": "0.6.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", |  | ||||||
|       "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "defer-to-connect": { |     "defer-to-connect": { | ||||||
|       "version": "2.0.1", |       "version": "2.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", |       "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", | ||||||
|       "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" |       "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" | ||||||
|     }, |     }, | ||||||
|     "delegates": { |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", |  | ||||||
|       "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "depd": { |     "depd": { | ||||||
|       "version": "1.1.2", |       "version": "1.1.2", | ||||||
|       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", |       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", | ||||||
| @ -512,12 +432,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", |       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", | ||||||
|       "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" |       "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" | ||||||
|     }, |     }, | ||||||
|     "detect-libc": { |  | ||||||
|       "version": "1.0.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", |  | ||||||
|       "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "dicer": { |     "dicer": { | ||||||
|       "version": "0.3.0", |       "version": "0.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", |       "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", | ||||||
| @ -654,6 +568,11 @@ | |||||||
|         "busboy": "^0.3.1" |         "busboy": "^0.3.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "express-rate-limit": { | ||||||
|  |       "version": "5.3.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.3.0.tgz", | ||||||
|  |       "integrity": "sha512-qJhfEgCnmteSeZAeuOKQ2WEIFTX5ajrzE0xS6gCOBCoRQcU+xEzQmgYQQTpzCcqUAAzTEtu4YEih4pnLfvNtew==" | ||||||
|  |     }, | ||||||
|     "finalhandler": { |     "finalhandler": { | ||||||
|       "version": "1.1.2", |       "version": "1.1.2", | ||||||
|       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", |       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", | ||||||
| @ -715,36 +634,11 @@ | |||||||
|         "universalify": "^2.0.0" |         "universalify": "^2.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "fs-minipass": { |  | ||||||
|       "version": "1.2.7", |  | ||||||
|       "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", |  | ||||||
|       "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "minipass": "^2.6.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "fs.realpath": { |     "fs.realpath": { | ||||||
|       "version": "1.0.0", |       "version": "1.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", |       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | ||||||
|       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" |       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" | ||||||
|     }, |     }, | ||||||
|     "gauge": { |  | ||||||
|       "version": "2.7.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", |  | ||||||
|       "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "aproba": "^1.0.3", |  | ||||||
|         "console-control-strings": "^1.0.0", |  | ||||||
|         "has-unicode": "^2.0.0", |  | ||||||
|         "object-assign": "^4.1.0", |  | ||||||
|         "signal-exit": "^3.0.0", |  | ||||||
|         "string-width": "^1.0.1", |  | ||||||
|         "strip-ansi": "^3.0.1", |  | ||||||
|         "wide-align": "^1.1.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "get-stream": { |     "get-stream": { | ||||||
|       "version": "5.2.0", |       "version": "5.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", |       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", | ||||||
| @ -790,12 +684,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", |       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", | ||||||
|       "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" |       "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" | ||||||
|     }, |     }, | ||||||
|     "has-unicode": { |  | ||||||
|       "version": "2.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", |  | ||||||
|       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "http-cache-semantics": { |     "http-cache-semantics": { | ||||||
|       "version": "4.1.0", |       "version": "4.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", |       "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", | ||||||
| @ -835,15 +723,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", |       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", | ||||||
|       "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" |       "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" | ||||||
|     }, |     }, | ||||||
|     "ignore-walk": { |  | ||||||
|       "version": "3.0.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", |  | ||||||
|       "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "minimatch": "^3.0.4" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "inflight": { |     "inflight": { | ||||||
|       "version": "1.0.6", |       "version": "1.0.6", | ||||||
|       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", |       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", | ||||||
| @ -858,12 +737,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", |       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", | ||||||
|       "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" |       "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" | ||||||
|     }, |     }, | ||||||
|     "ini": { |  | ||||||
|       "version": "1.3.8", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", |  | ||||||
|       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "ip": { |     "ip": { | ||||||
|       "version": "1.1.5", |       "version": "1.1.5", | ||||||
|       "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", |       "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", | ||||||
| @ -874,15 +747,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", |       "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", | ||||||
|       "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" |       "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" | ||||||
|     }, |     }, | ||||||
|     "is-fullwidth-code-point": { |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", |  | ||||||
|       "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "number-is-nan": "^1.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "is-primitive": { |     "is-primitive": { | ||||||
|       "version": "3.0.1", |       "version": "3.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", |       "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", | ||||||
| @ -1106,79 +970,11 @@ | |||||||
|         "brace-expansion": "^1.1.7" |         "brace-expansion": "^1.1.7" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "minimist": { |  | ||||||
|       "version": "1.2.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", |  | ||||||
|       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "minipass": { |  | ||||||
|       "version": "2.9.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", |  | ||||||
|       "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "safe-buffer": "^5.1.2", |  | ||||||
|         "yallist": "^3.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "minizlib": { |  | ||||||
|       "version": "1.3.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", |  | ||||||
|       "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "minipass": "^2.9.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "mkdirp": { |  | ||||||
|       "version": "0.5.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", |  | ||||||
|       "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "minimist": "^1.2.5" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "ms": { |     "ms": { | ||||||
|       "version": "2.0.0", |       "version": "2.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", |       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||||
|       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" |       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||||
|     }, |     }, | ||||||
|     "nan": { |  | ||||||
|       "version": "2.10.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", |  | ||||||
|       "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "needle": { |  | ||||||
|       "version": "2.9.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", |  | ||||||
|       "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "debug": "^3.2.6", |  | ||||||
|         "iconv-lite": "^0.4.4", |  | ||||||
|         "sax": "^1.2.4" |  | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "debug": { |  | ||||||
|           "version": "3.2.7", |  | ||||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", |  | ||||||
|           "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", |  | ||||||
|           "optional": true, |  | ||||||
|           "requires": { |  | ||||||
|             "ms": "^2.1.1" |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         "ms": { |  | ||||||
|           "version": "2.1.3", |  | ||||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", |  | ||||||
|           "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", |  | ||||||
|           "optional": true |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "negotiator": { |     "negotiator": { | ||||||
|       "version": "0.6.2", |       "version": "0.6.2", | ||||||
|       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", |       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", | ||||||
| @ -1200,34 +996,6 @@ | |||||||
|         "minimatch": "^3.0.2" |         "minimatch": "^3.0.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node-pre-gyp": { |  | ||||||
|       "version": "0.10.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", |  | ||||||
|       "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "detect-libc": "^1.0.2", |  | ||||||
|         "mkdirp": "^0.5.1", |  | ||||||
|         "needle": "^2.2.1", |  | ||||||
|         "nopt": "^4.0.1", |  | ||||||
|         "npm-packlist": "^1.1.6", |  | ||||||
|         "npmlog": "^4.0.2", |  | ||||||
|         "rc": "^1.2.7", |  | ||||||
|         "rimraf": "^2.6.1", |  | ||||||
|         "semver": "^5.3.0", |  | ||||||
|         "tar": "^4" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "nopt": { |  | ||||||
|       "version": "4.0.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", |  | ||||||
|       "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "abbrev": "1", |  | ||||||
|         "osenv": "^0.1.4" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "normalize-path": { |     "normalize-path": { | ||||||
|       "version": "3.0.0", |       "version": "3.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", |       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", | ||||||
| @ -1238,50 +1006,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", |       "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", | ||||||
|       "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" |       "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" | ||||||
|     }, |     }, | ||||||
|     "npm-bundled": { |  | ||||||
|       "version": "1.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", |  | ||||||
|       "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "npm-normalize-package-bin": "^1.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "npm-normalize-package-bin": { |  | ||||||
|       "version": "1.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", |  | ||||||
|       "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "npm-packlist": { |  | ||||||
|       "version": "1.4.8", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", |  | ||||||
|       "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "ignore-walk": "^3.0.1", |  | ||||||
|         "npm-bundled": "^1.0.1", |  | ||||||
|         "npm-normalize-package-bin": "^1.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "npmlog": { |  | ||||||
|       "version": "4.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", |  | ||||||
|       "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "are-we-there-yet": "~1.1.2", |  | ||||||
|         "console-control-strings": "~1.1.0", |  | ||||||
|         "gauge": "~2.7.3", |  | ||||||
|         "set-blocking": "~2.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "number-is-nan": { |  | ||||||
|       "version": "1.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", |  | ||||||
|       "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "object-assign": { |     "object-assign": { | ||||||
|       "version": "4.1.1", |       "version": "4.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", |       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", | ||||||
| @ -1303,28 +1027,6 @@ | |||||||
|         "wrappy": "1" |         "wrappy": "1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "os-homedir": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "os-tmpdir": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "osenv": { |  | ||||||
|       "version": "0.1.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", |  | ||||||
|       "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "os-homedir": "^1.0.0", |  | ||||||
|         "os-tmpdir": "^1.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "p-cancelable": { |     "p-cancelable": { | ||||||
|       "version": "2.1.1", |       "version": "2.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", |       "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", | ||||||
| @ -1422,18 +1124,6 @@ | |||||||
|         "unpipe": "1.0.0" |         "unpipe": "1.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "rc": { |  | ||||||
|       "version": "1.2.8", |  | ||||||
|       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", |  | ||||||
|       "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "deep-extend": "^0.6.0", |  | ||||||
|         "ini": "~1.3.0", |  | ||||||
|         "minimist": "^1.2.0", |  | ||||||
|         "strip-json-comments": "~2.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "readable-stream": { |     "readable-stream": { | ||||||
|       "version": "3.6.0", |       "version": "3.6.0", | ||||||
|       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", |       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", | ||||||
| @ -1470,15 +1160,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", |       "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", | ||||||
|       "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" |       "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" | ||||||
|     }, |     }, | ||||||
|     "rimraf": { |  | ||||||
|       "version": "2.7.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", |  | ||||||
|       "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "glob": "^7.1.3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "ripstat": { |     "ripstat": { | ||||||
|       "version": "1.1.1", |       "version": "1.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ripstat/-/ripstat-1.1.1.tgz", |       "resolved": "https://registry.npmjs.org/ripstat/-/ripstat-1.1.1.tgz", | ||||||
| @ -1521,11 +1202,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", |       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", | ||||||
|       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" |       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" | ||||||
|     }, |     }, | ||||||
|     "sax": { |  | ||||||
|       "version": "1.2.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", |  | ||||||
|       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" |  | ||||||
|     }, |  | ||||||
|     "semver": { |     "semver": { | ||||||
|       "version": "5.7.1", |       "version": "5.7.1", | ||||||
|       "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", |       "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", | ||||||
| @ -1569,12 +1245,6 @@ | |||||||
|         "send": "0.17.1" |         "send": "0.17.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "set-blocking": { |  | ||||||
|       "version": "2.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", |  | ||||||
|       "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "setprototypeof": { |     "setprototypeof": { | ||||||
|       "version": "1.1.1", |       "version": "1.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", |       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", | ||||||
| @ -1661,17 +1331,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/string-indexes/-/string-indexes-1.0.0.tgz", |       "resolved": "https://registry.npmjs.org/string-indexes/-/string-indexes-1.0.0.tgz", | ||||||
|       "integrity": "sha512-RUlx+2YydZJNlRAvoh1siPYWj/Xfk6t1sQLkA5n1tMGRCKkRLzkRtJhHk4qRmKergEBh8R3pWhsUsDqia/bolw==" |       "integrity": "sha512-RUlx+2YydZJNlRAvoh1siPYWj/Xfk6t1sQLkA5n1tMGRCKkRLzkRtJhHk4qRmKergEBh8R3pWhsUsDqia/bolw==" | ||||||
|     }, |     }, | ||||||
|     "string-width": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "code-point-at": "^1.0.0", |  | ||||||
|         "is-fullwidth-code-point": "^1.0.0", |  | ||||||
|         "strip-ansi": "^3.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "string_decoder": { |     "string_decoder": { | ||||||
|       "version": "1.1.1", |       "version": "1.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", |       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||||||
| @ -1680,44 +1339,6 @@ | |||||||
|         "safe-buffer": "~5.1.0" |         "safe-buffer": "~5.1.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "strip-ansi": { |  | ||||||
|       "version": "3.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", |  | ||||||
|       "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "ansi-regex": "^2.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "strip-json-comments": { |  | ||||||
|       "version": "2.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", |  | ||||||
|       "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "tar": { |  | ||||||
|       "version": "4.4.19", |  | ||||||
|       "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", |  | ||||||
|       "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "chownr": "^1.1.4", |  | ||||||
|         "fs-minipass": "^1.2.7", |  | ||||||
|         "minipass": "^2.9.0", |  | ||||||
|         "minizlib": "^1.3.3", |  | ||||||
|         "mkdirp": "^0.5.5", |  | ||||||
|         "safe-buffer": "^5.2.1", |  | ||||||
|         "yallist": "^3.1.1" |  | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "safe-buffer": { |  | ||||||
|           "version": "5.2.1", |  | ||||||
|           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", |  | ||||||
|           "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", |  | ||||||
|           "optional": true |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "tar-stream": { |     "tar-stream": { | ||||||
|       "version": "2.2.0", |       "version": "2.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", |       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", | ||||||
| @ -1803,15 +1424,6 @@ | |||||||
|         "isexe": "^2.0.0" |         "isexe": "^2.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "wide-align": { |  | ||||||
|       "version": "1.1.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", |  | ||||||
|       "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "string-width": "^1.0.2 || 2" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "wrappy": { |     "wrappy": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||||||
| @ -1827,26 +1439,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", |       "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", | ||||||
|       "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" |       "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" | ||||||
|     }, |     }, | ||||||
|     "xml2js": { |  | ||||||
|       "version": "0.4.23", |  | ||||||
|       "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", |  | ||||||
|       "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", |  | ||||||
|       "requires": { |  | ||||||
|         "sax": ">=0.6.0", |  | ||||||
|         "xmlbuilder": "~11.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "xmlbuilder": { |  | ||||||
|       "version": "11.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", |  | ||||||
|       "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" |  | ||||||
|     }, |  | ||||||
|     "yallist": { |  | ||||||
|       "version": "3.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", |  | ||||||
|       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", |  | ||||||
|       "optional": true |  | ||||||
|     }, |  | ||||||
|     "zip-stream": { |     "zip-stream": { | ||||||
|       "version": "4.1.0", |       "version": "4.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", |       "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", | ||||||
| @ -1856,16 +1448,6 @@ | |||||||
|         "compress-commons": "^4.1.0", |         "compress-commons": "^4.1.0", | ||||||
|         "readable-stream": "^3.6.0" |         "readable-stream": "^3.6.0" | ||||||
|       } |       } | ||||||
|     }, |  | ||||||
|     "zipfile": { |  | ||||||
|       "version": "0.5.12", |  | ||||||
|       "resolved": "https://registry.npmjs.org/zipfile/-/zipfile-0.5.12.tgz", |  | ||||||
|       "integrity": "sha512-zA60gW+XgQBu/Q4qV3BCXNIDRald6Xi5UOPj3jWGlnkjmBHaKDwIz7kyXWV3kq7VEsQN/2t/IWjdXdKeVNm6Eg==", |  | ||||||
|       "optional": true, |  | ||||||
|       "requires": { |  | ||||||
|         "nan": "~2.10.0", |  | ||||||
|         "node-pre-gyp": "~0.10.2" |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "audiobookshelf", |   "name": "audiobookshelf", | ||||||
|   "version": "1.2.7", |   "version": "1.2.8", | ||||||
|   "description": "Self-hosted audiobook server for managing and playing audiobooks", |   "description": "Self-hosted audiobook server for managing and playing audiobooks", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
| @ -29,6 +29,7 @@ | |||||||
|     "cookie-parser": "^1.4.5", |     "cookie-parser": "^1.4.5", | ||||||
|     "express": "^4.17.1", |     "express": "^4.17.1", | ||||||
|     "express-fileupload": "^1.2.1", |     "express-fileupload": "^1.2.1", | ||||||
|  |     "express-rate-limit": "^5.3.0", | ||||||
|     "fluent-ffmpeg": "^2.1.2", |     "fluent-ffmpeg": "^2.1.2", | ||||||
|     "fs-extra": "^10.0.0", |     "fs-extra": "^10.0.0", | ||||||
|     "ip": "^1.1.5", |     "ip": "^1.1.5", | ||||||
|  | |||||||
| @ -103,18 +103,18 @@ class Auth { | |||||||
| 
 | 
 | ||||||
|     var user = this.users.find(u => u.username === username) |     var user = this.users.find(u => u.username === username) | ||||||
| 
 | 
 | ||||||
|     if (!user) { |     if (!user || !user.isActive) { | ||||||
|       return res.json({ error: 'User not found' }) |       Logger.debug(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit}`) | ||||||
|     } |       if (req.rateLimit.remaining <= 2) { | ||||||
| 
 |         return res.status(401).send(`Invalid user or password (${req.rateLimit.remaining === 0 ? '1 attempt remaining' : `${req.rateLimit.remaining + 1} attempts remaining`})`) | ||||||
|     if (!user.isActive) { |       } | ||||||
|       return res.json({ error: 'User unavailable' }) |       return res.status(401).send('Invalid user or password') | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Check passwordless root user
 |     // Check passwordless root user
 | ||||||
|     if (user.id === 'root' && (!user.pash || user.pash === '')) { |     if (user.id === 'root' && (!user.pash || user.pash === '')) { | ||||||
|       if (password) { |       if (password) { | ||||||
|         return res.json({ error: 'Invalid root password (hint: there is none)' }) |         return res.status(401).send('Invalid root password (hint: there is none)') | ||||||
|       } else { |       } else { | ||||||
|         return res.json({ user: user.toJSONForBrowser() }) |         return res.json({ user: user.toJSONForBrowser() }) | ||||||
|       } |       } | ||||||
| @ -127,12 +127,24 @@ class Auth { | |||||||
|         user: user.toJSONForBrowser() |         user: user.toJSONForBrowser() | ||||||
|       }) |       }) | ||||||
|     } else { |     } else { | ||||||
|       res.json({ |       Logger.debug(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit}`) | ||||||
|         error: 'Invalid Password' |       if (req.rateLimit.remaining <= 2) { | ||||||
|       }) |         Logger.error(`[Auth] Failed login attempt for user ${user.username}. Attempts: ${req.rateLimit.current}`) | ||||||
|  |         return res.status(401).send(`Invalid user or password (${req.rateLimit.remaining === 0 ? '1 attempt remaining' : `${req.rateLimit.remaining + 1} attempts remaining`})`) | ||||||
|  |       } | ||||||
|  |       return res.status(401).send('Invalid user or password') | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // Not in use now
 | ||||||
|  |   lockUser(user) { | ||||||
|  |     user.isLocked = true | ||||||
|  |     return this.db.updateEntity('user', user).catch((error) => { | ||||||
|  |       Logger.error('[Auth] Failed to lock user', user.username, error) | ||||||
|  |       return false | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   comparePassword(password, user) { |   comparePassword(password, user) { | ||||||
|     if (user.type === 'root' && !password && !user.pash) return true |     if (user.type === 'root' && !password && !user.pash) return true | ||||||
|     if (!password || !user.pash) return false |     if (!password || !user.pash) return false | ||||||
|  | |||||||
| @ -13,6 +13,8 @@ class Scanner { | |||||||
|   constructor(AUDIOBOOK_PATH, METADATA_PATH, db, emitter) { |   constructor(AUDIOBOOK_PATH, METADATA_PATH, db, emitter) { | ||||||
|     this.AudiobookPath = AUDIOBOOK_PATH |     this.AudiobookPath = AUDIOBOOK_PATH | ||||||
|     this.MetadataPath = METADATA_PATH |     this.MetadataPath = METADATA_PATH | ||||||
|  |     this.BookMetadataPath = Path.join(this.MetadataPath, 'books') | ||||||
|  | 
 | ||||||
|     this.db = db |     this.db = db | ||||||
|     this.emitter = emitter |     this.emitter = emitter | ||||||
| 
 | 
 | ||||||
| @ -387,6 +389,39 @@ class Scanner { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async saveMetadata(audiobookId) { | ||||||
|  |     if (audiobookId) { | ||||||
|  |       var audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId) | ||||||
|  |       if (!audiobook) { | ||||||
|  |         return { | ||||||
|  |           error: 'Audiobook not found' | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       var savedPath = await audiobook.writeNfoFile() | ||||||
|  |       return { | ||||||
|  |         audiobookId, | ||||||
|  |         audiobookTitle: audiobook.title, | ||||||
|  |         savedPath | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       var response = { | ||||||
|  |         success: 0, | ||||||
|  |         failed: 0 | ||||||
|  |       } | ||||||
|  |       for (let i = 0; i < this.db.audiobooks.length; i++) { | ||||||
|  |         var audiobook = this.db.audiobooks[i] | ||||||
|  |         var savedPath = await audiobook.writeNfoFile() | ||||||
|  |         if (savedPath) { | ||||||
|  |           Logger.info(`[Scanner] Saved metadata nfo ${savedPath}`) | ||||||
|  |           response.success++ | ||||||
|  |         } else { | ||||||
|  |           response.failed++ | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return response | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async find(req, res) { |   async find(req, res) { | ||||||
|     var method = req.params.method |     var method = req.params.method | ||||||
|     var query = req.query |     var query = req.query | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ const http = require('http') | |||||||
| const SocketIO = require('socket.io') | const SocketIO = require('socket.io') | ||||||
| const fs = require('fs-extra') | const fs = require('fs-extra') | ||||||
| const fileUpload = require('express-fileupload') | const fileUpload = require('express-fileupload') | ||||||
|  | const rateLimit = require('express-rate-limit') | ||||||
| 
 | 
 | ||||||
| const Auth = require('./Auth') | const Auth = require('./Auth') | ||||||
| const Watcher = require('./Watcher') | const Watcher = require('./Watcher') | ||||||
| @ -110,6 +111,14 @@ class Server { | |||||||
|     this.scanner.cancelScan = true |     this.scanner.cancelScan = true | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // Generates an NFO metadata file, if no audiobookId is passed then all audiobooks are done
 | ||||||
|  |   async saveMetadata(socket, audiobookId = null) { | ||||||
|  |     Logger.info('[Server] Starting save metadata files') | ||||||
|  |     var response = await this.scanner.saveMetadata(audiobookId) | ||||||
|  |     Logger.info(`[Server] Finished saving metadata files Successful: ${response.success}, Failed: ${response.failed}`) | ||||||
|  |     socket.emit('save_metadata_complete', response) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async init() { |   async init() { | ||||||
|     Logger.info('[Server] Init') |     Logger.info('[Server] Init') | ||||||
|     await this.streamManager.ensureStreamsDir() |     await this.streamManager.ensureStreamsDir() | ||||||
| @ -172,6 +181,21 @@ class Server { | |||||||
|     res.sendStatus(200) |     res.sendStatus(200) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // First time login rate limit is hit
 | ||||||
|  |   loginLimitReached(req, res, options) { | ||||||
|  |     Logger.error(`[Server] Login rate limit (${options.max}) was hit for ip ${req.ip}`) | ||||||
|  |     options.message = 'Too many attempts. Login temporarily locked.' | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getLoginRateLimiter() { | ||||||
|  |     return rateLimit({ | ||||||
|  |       windowMs: this.db.serverSettings.rateLimitLoginWindow, // 5 minutes
 | ||||||
|  |       max: this.db.serverSettings.rateLimitLoginRequests, | ||||||
|  |       skipSuccessfulRequests: true, | ||||||
|  |       onLimitReached: this.loginLimitReached | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async start() { |   async start() { | ||||||
|     Logger.info('=== Starting Server ===') |     Logger.info('=== Starting Server ===') | ||||||
|     await this.init() |     await this.init() | ||||||
| @ -206,13 +230,18 @@ class Server { | |||||||
| 
 | 
 | ||||||
|     app.use('/api', this.authMiddleware.bind(this), this.apiController.router) |     app.use('/api', this.authMiddleware.bind(this), this.apiController.router) | ||||||
|     app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router) |     app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router) | ||||||
|  | 
 | ||||||
|  |     // Incomplete work in progress
 | ||||||
|     // app.use('/ebook', this.ebookReader.router)
 |     // app.use('/ebook', this.ebookReader.router)
 | ||||||
|     app.use('/feeds', this.rssFeeds.router) |     // app.use('/feeds', this.rssFeeds.router)
 | ||||||
| 
 | 
 | ||||||
|     app.post('/upload', this.authMiddleware.bind(this), this.handleUpload.bind(this)) |     app.post('/upload', this.authMiddleware.bind(this), this.handleUpload.bind(this)) | ||||||
| 
 | 
 | ||||||
|     app.post('/login', (req, res) => this.auth.login(req, res)) |     var loginRateLimiter = this.getLoginRateLimiter() | ||||||
|  |     app.post('/login', loginRateLimiter, (req, res) => this.auth.login(req, res)) | ||||||
|  | 
 | ||||||
|     app.post('/logout', this.logout.bind(this)) |     app.post('/logout', this.logout.bind(this)) | ||||||
|  | 
 | ||||||
|     app.get('/ping', (req, res) => { |     app.get('/ping', (req, res) => { | ||||||
|       Logger.info('Recieved ping') |       Logger.info('Recieved ping') | ||||||
|       res.json({ success: true }) |       res.json({ success: true }) | ||||||
| @ -231,7 +260,6 @@ class Server { | |||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     this.server.listen(this.Port, this.Host, () => { |     this.server.listen(this.Port, this.Host, () => { | ||||||
|       Logger.info(`Running on http://${this.Host}:${this.Port}`) |       Logger.info(`Running on http://${this.Host}:${this.Port}`) | ||||||
|     }) |     }) | ||||||
| @ -259,6 +287,7 @@ class Server { | |||||||
|       socket.on('scan', this.scan.bind(this)) |       socket.on('scan', this.scan.bind(this)) | ||||||
|       socket.on('scan_covers', this.scanCovers.bind(this)) |       socket.on('scan_covers', this.scanCovers.bind(this)) | ||||||
|       socket.on('cancel_scan', this.cancelScan.bind(this)) |       socket.on('cancel_scan', this.cancelScan.bind(this)) | ||||||
|  |       socket.on('save_metadata', (audiobookId) => this.saveMetadata(socket, audiobookId)) | ||||||
| 
 | 
 | ||||||
|       // Streaming
 |       // Streaming
 | ||||||
|       socket.on('open_stream', (audiobookId) => this.streamManager.openStreamSocketRequest(socket, audiobookId)) |       socket.on('open_stream', (audiobookId) => this.streamManager.openStreamSocketRequest(socket, audiobookId)) | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| const Path = require('path') | const Path = require('path') | ||||||
| const { bytesPretty, elapsedPretty } = require('../utils/fileUtils') | const { bytesPretty, elapsedPretty } = require('../utils/fileUtils') | ||||||
| const { comparePaths, getIno } = require('../utils/index') | const { comparePaths, getIno } = require('../utils/index') | ||||||
|  | const nfoGenerator = require('../utils/nfoGenerator') | ||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
| const Book = require('./Book') | const Book = require('./Book') | ||||||
| const AudioTrack = require('./AudioTrack') | const AudioTrack = require('./AudioTrack') | ||||||
| @ -530,5 +531,9 @@ class Audiobook { | |||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   writeNfoFile(nfoFilename = 'metadata.nfo') { | ||||||
|  |     return nfoGenerator(this, nfoFilename) | ||||||
|  |   } | ||||||
| } | } | ||||||
| module.exports = Audiobook | module.exports = Audiobook | ||||||
| @ -1,3 +1,4 @@ | |||||||
|  | const fs = require('fs-extra') | ||||||
| const Path = require('path') | const Path = require('path') | ||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
| const parseAuthors = require('../utils/parseAuthors') | const parseAuthors = require('../utils/parseAuthors') | ||||||
|  | |||||||
| @ -3,10 +3,14 @@ const { CoverDestination } = require('../utils/constants') | |||||||
| class ServerSettings { | class ServerSettings { | ||||||
|   constructor(settings) { |   constructor(settings) { | ||||||
|     this.id = 'server-settings' |     this.id = 'server-settings' | ||||||
|  | 
 | ||||||
|     this.autoTagNew = false |     this.autoTagNew = false | ||||||
|     this.newTagExpireDays = 15 |     this.newTagExpireDays = 15 | ||||||
|     this.scannerParseSubtitle = false |     this.scannerParseSubtitle = false | ||||||
|     this.coverDestination = CoverDestination.METADATA |     this.coverDestination = CoverDestination.METADATA | ||||||
|  |     this.saveMetadataFile = false | ||||||
|  |     this.rateLimitLoginRequests = 10 | ||||||
|  |     this.rateLimitLoginWindow = 10 * 60 * 1000 // 10 Minutes
 | ||||||
| 
 | 
 | ||||||
|     if (settings) { |     if (settings) { | ||||||
|       this.construct(settings) |       this.construct(settings) | ||||||
| @ -18,6 +22,9 @@ class ServerSettings { | |||||||
|     this.newTagExpireDays = settings.newTagExpireDays |     this.newTagExpireDays = settings.newTagExpireDays | ||||||
|     this.scannerParseSubtitle = settings.scannerParseSubtitle |     this.scannerParseSubtitle = settings.scannerParseSubtitle | ||||||
|     this.coverDestination = settings.coverDestination || CoverDestination.METADATA |     this.coverDestination = settings.coverDestination || CoverDestination.METADATA | ||||||
|  |     this.saveMetadataFile = !!settings.saveMetadataFile | ||||||
|  |     this.rateLimitLoginRequests = !isNaN(settings.rateLimitLoginRequests) ? Number(settings.rateLimitLoginRequests) : 10 | ||||||
|  |     this.rateLimitLoginWindow = !isNaN(settings.rateLimitLoginWindow) ? Number(settings.rateLimitLoginWindow) : 10 * 60 * 1000 // 10 Minutes
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toJSON() { |   toJSON() { | ||||||
| @ -26,7 +33,10 @@ class ServerSettings { | |||||||
|       autoTagNew: this.autoTagNew, |       autoTagNew: this.autoTagNew, | ||||||
|       newTagExpireDays: this.newTagExpireDays, |       newTagExpireDays: this.newTagExpireDays, | ||||||
|       scannerParseSubtitle: this.scannerParseSubtitle, |       scannerParseSubtitle: this.scannerParseSubtitle, | ||||||
|       coverDestination: this.coverDestination |       coverDestination: this.coverDestination, | ||||||
|  |       saveMetadataFile: !!this.saveMetadataFile, | ||||||
|  |       rateLimitLoginRequests: this.rateLimitLoginRequests, | ||||||
|  |       rateLimitLoginWindow: this.rateLimitLoginWindow | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ class User { | |||||||
|     this.stream = null |     this.stream = null | ||||||
|     this.token = null |     this.token = null | ||||||
|     this.isActive = true |     this.isActive = true | ||||||
|  |     this.isLocked = false | ||||||
|     this.createdAt = null |     this.createdAt = null | ||||||
|     this.audiobooks = null |     this.audiobooks = null | ||||||
| 
 | 
 | ||||||
| @ -76,6 +77,7 @@ class User { | |||||||
|       token: this.token, |       token: this.token, | ||||||
|       audiobooks: this.audiobooksToJSON(), |       audiobooks: this.audiobooksToJSON(), | ||||||
|       isActive: this.isActive, |       isActive: this.isActive, | ||||||
|  |       isLocked: this.isLocked, | ||||||
|       createdAt: this.createdAt, |       createdAt: this.createdAt, | ||||||
|       settings: this.settings, |       settings: this.settings, | ||||||
|       permissions: this.permissions |       permissions: this.permissions | ||||||
| @ -91,6 +93,7 @@ class User { | |||||||
|       token: this.token, |       token: this.token, | ||||||
|       audiobooks: this.audiobooksToJSON(), |       audiobooks: this.audiobooksToJSON(), | ||||||
|       isActive: this.isActive, |       isActive: this.isActive, | ||||||
|  |       isLocked: this.isLocked, | ||||||
|       createdAt: this.createdAt, |       createdAt: this.createdAt, | ||||||
|       settings: this.settings, |       settings: this.settings, | ||||||
|       permissions: this.permissions |       permissions: this.permissions | ||||||
| @ -112,7 +115,8 @@ class User { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     this.isActive = (user.isActive === undefined || user.id === 'root') ? true : !!user.isActive |     this.isActive = (user.isActive === undefined || user.type === 'root') ? true : !!user.isActive | ||||||
|  |     this.isLocked = user.type === 'root' ? false : !!user.isLocked | ||||||
|     this.createdAt = user.createdAt || Date.now() |     this.createdAt = user.createdAt || Date.now() | ||||||
|     this.settings = user.settings || this.getDefaultUserSettings() |     this.settings = user.settings || this.getDefaultUserSettings() | ||||||
|     this.permissions = user.permissions || this.getDefaultUserPermissions() |     this.permissions = user.permissions || this.getDefaultUserPermissions() | ||||||
|  | |||||||
| @ -29,9 +29,10 @@ function bytesPretty(bytes, decimals = 0) { | |||||||
|     return '0 Bytes' |     return '0 Bytes' | ||||||
|   } |   } | ||||||
|   const k = 1024 |   const k = 1024 | ||||||
|   const dm = decimals < 0 ? 0 : decimals |   var dm = decimals < 0 ? 0 : decimals | ||||||
|   const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] |   const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] | ||||||
|   const i = Math.floor(Math.log(bytes) / Math.log(k)) |   const i = Math.floor(Math.log(bytes) / Math.log(k)) | ||||||
|  |   if (i > 2 && dm === 0) dm = 1 | ||||||
|   return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] |   return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] | ||||||
| } | } | ||||||
| module.exports.bytesPretty = bytesPretty | module.exports.bytesPretty = bytesPretty | ||||||
|  | |||||||
							
								
								
									
										91
									
								
								server/utils/nfoGenerator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								server/utils/nfoGenerator.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | |||||||
|  | const fs = require('fs-extra') | ||||||
|  | const Path = require('path') | ||||||
|  | const { bytesPretty } = require('./fileUtils') | ||||||
|  | const Logger = require('../Logger') | ||||||
|  | 
 | ||||||
|  | const LEFT_COL_LEN = 25 | ||||||
|  | 
 | ||||||
|  | function sectionHeaderLines(title) { | ||||||
|  |   return [title, ''.padEnd(10, '=')] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function generateSection(sectionTitle, sectionData) { | ||||||
|  |   var lines = sectionHeaderLines(sectionTitle) | ||||||
|  |   for (const key in sectionData) { | ||||||
|  |     var line = key.padEnd(LEFT_COL_LEN) + (sectionData[key] || '') | ||||||
|  |     lines.push(line) | ||||||
|  |   } | ||||||
|  |   return lines | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function generate(audiobook, nfoFilename = 'metadata.nfo') { | ||||||
|  |   var jsonObj = audiobook.toJSON() | ||||||
|  |   var book = jsonObj.book | ||||||
|  | 
 | ||||||
|  |   var generalSectionData = { | ||||||
|  |     'Title': book.title, | ||||||
|  |     'Subtitle': book.subtitle, | ||||||
|  |     'Author': book.author, | ||||||
|  |     'Narrator': book.narrarator, | ||||||
|  |     'Series': book.series, | ||||||
|  |     'Volume Number': book.volumeNumber, | ||||||
|  |     'Publish Year': book.publishYear, | ||||||
|  |     'Genre': book.genres ? book.genres.join(', ') : '', | ||||||
|  |     'Duration': audiobook.durationPretty, | ||||||
|  |     'Chapters': jsonObj.chapters.length | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!book.subtitle) { | ||||||
|  |     delete generalSectionData['Subtitle'] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!book.series) { | ||||||
|  |     delete generalSectionData['Series'] | ||||||
|  |     delete generalSectionData['Volume Number'] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var tracks = audiobook.tracks | ||||||
|  |   var audioTrack = tracks.length ? audiobook.tracks[0] : {} | ||||||
|  | 
 | ||||||
|  |   var totalBitrate = 0 | ||||||
|  |   var numBitrates = 0 | ||||||
|  |   for (let i = 0; i < tracks.length; i++) { | ||||||
|  |     if (tracks[i].bitRate) { | ||||||
|  |       totalBitrate += tracks[i].bitRate | ||||||
|  |       numBitrates++ | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   var averageBitrate = numBitrates ? totalBitrate / numBitrates : 0 | ||||||
|  | 
 | ||||||
|  |   var mediaSectionData = { | ||||||
|  |     'Tracks': jsonObj.tracks.length, | ||||||
|  |     'Size': audiobook.sizePretty, | ||||||
|  |     'Codec': audioTrack.codec, | ||||||
|  |     'Ext': audioTrack.ext, | ||||||
|  |     'Channels': audioTrack.channels, | ||||||
|  |     'Channel Layout': audioTrack.channelLayout, | ||||||
|  |     'Average Bitrate': bytesPretty(averageBitrate) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var bookSection = generateSection('Book Info', generalSectionData) | ||||||
|  | 
 | ||||||
|  |   var descriptionSection = null | ||||||
|  |   if (book.description) { | ||||||
|  |     descriptionSection = sectionHeaderLines('Book Description') | ||||||
|  |     descriptionSection.push(book.description) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var mediaSection = generateSection('Media Info', mediaSectionData) | ||||||
|  | 
 | ||||||
|  |   var fullFile = bookSection.join('\n') + '\n\n' | ||||||
|  |   if (descriptionSection) fullFile += descriptionSection.join('\n') + '\n\n' | ||||||
|  |   fullFile += mediaSection.join('\n') | ||||||
|  | 
 | ||||||
|  |   var nfoPath = Path.join(audiobook.fullPath, nfoFilename) | ||||||
|  |   var relativePath = Path.join(audiobook.path, nfoFilename) | ||||||
|  |   return fs.writeFile(nfoPath, fullFile).then(() => relativePath).catch((error) => { | ||||||
|  |     Logger.error(`Failed to write nfo file ${error}`) | ||||||
|  |     return false | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | module.exports = generate | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user