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="flex px-4"> | ||||
|           <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" /> | ||||
|           <ui-btn type="submit">Submit</ui-btn> | ||||
|         </div> | ||||
| @ -87,7 +92,8 @@ export default { | ||||
|       }, | ||||
|       newTags: [], | ||||
|       resettingProgress: false, | ||||
|       isScrollable: false | ||||
|       isScrollable: false, | ||||
|       savingMetadata: false | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
| @ -107,6 +113,9 @@ export default { | ||||
|         this.$emit('update:processing', val) | ||||
|       } | ||||
|     }, | ||||
|     isRootUser() { | ||||
|       return this.$store.getters['user/getIsRoot'] | ||||
|     }, | ||||
|     audiobookId() { | ||||
|       return this.audiobook ? this.audiobook.id : null | ||||
|     }, | ||||
| @ -127,6 +136,24 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   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() { | ||||
|       if (this.isProcessing) { | ||||
|         return | ||||
|  | ||||
| @ -127,6 +127,21 @@ export default { | ||||
|         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) { | ||||
|       if (this.$store.state.user.user.id === user.id) { | ||||
|         this.$store.commit('user/setUser', user) | ||||
| @ -230,6 +245,7 @@ export default { | ||||
|       this.socket.on('scan_start', this.scanStart) | ||||
|       this.socket.on('scan_complete', this.scanComplete) | ||||
|       this.socket.on('scan_progress', this.scanProgress) | ||||
|       // this.socket.on('save_metadata_complete', this.saveMetadataComplete) | ||||
| 
 | ||||
|       // Download Listeners | ||||
|       this.socket.on('download_started', this.downloadStarted) | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "audiobookshelf-client", | ||||
|   "version": "1.2.7", | ||||
|   "version": "1.2.8", | ||||
|   "description": "Audiobook manager and player", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|  | ||||
| @ -50,11 +50,13 @@ | ||||
|           <div class="w-40 flex flex-col"> | ||||
|             <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-btn color="primary" class="w-full" small :padding-x="2" :loading="isScanningCovers" :disabled="isScanning" @click="scanCovers">Scan for Covers</ui-btn> | ||||
|               </ui-tooltip> | ||||
|             </div> | ||||
| 
 | ||||
|             <!-- <ui-btn color="primary" small @click="saveMetadataFiles">Save Metadata</ui-btn> --> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| @ -152,6 +154,9 @@ export default { | ||||
|     scanCovers() { | ||||
|       this.$root.socket.emit('scan_covers') | ||||
|     }, | ||||
|     saveMetadataFiles() { | ||||
|       this.$root.socket.emit('save_metadata') | ||||
|     }, | ||||
|     loadUsers() { | ||||
|       this.$axios | ||||
|         .$get('/api/users') | ||||
|  | ||||
| @ -57,15 +57,14 @@ export default { | ||||
|         password: this.password || '' | ||||
|       } | ||||
|       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 | ||||
|       }) | ||||
|       console.log('Auth res', authRes) | ||||
|       if (!authRes) { | ||||
|         this.error = 'Unknown Failure' | ||||
|       } else if (authRes.error) { | ||||
|       if (authRes && authRes.error) { | ||||
|         this.error = authRes.error | ||||
|       } else { | ||||
|       } else if (authRes) { | ||||
|         this.$store.commit('user/setUser', authRes.user) | ||||
|       } | ||||
|       this.processing = false | ||||
|  | ||||
| @ -16,6 +16,7 @@ export default function ({ $axios, store }) { | ||||
| 
 | ||||
|   $axios.onError(error => { | ||||
|     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) | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										430
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										430
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "audiobookshelf", | ||||
|   "version": "1.2.4", | ||||
|   "version": "1.2.7", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @ -69,12 +69,6 @@ | ||||
|         "@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": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/aborter/-/aborter-1.1.0.tgz", | ||||
| @ -89,23 +83,6 @@ | ||||
|         "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": { | ||||
|       "version": "5.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", | ||||
| @ -161,33 +138,6 @@ | ||||
|         "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": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", | ||||
| @ -335,12 +285,6 @@ | ||||
|         "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": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", | ||||
| @ -349,12 +293,6 @@ | ||||
|         "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": { | ||||
|       "version": "5.2.0", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "0.5.3", | ||||
|       "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": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", | ||||
|       "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": { | ||||
|       "version": "1.1.2", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "0.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", | ||||
| @ -654,6 +568,11 @@ | ||||
|         "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": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", | ||||
| @ -715,36 +634,11 @@ | ||||
|         "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": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | ||||
|       "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": { | ||||
|       "version": "5.2.0", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "4.1.0", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "1.0.6", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "1.1.5", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "3.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", | ||||
| @ -1106,79 +970,11 @@ | ||||
|         "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": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|       "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": { | ||||
|       "version": "0.6.2", | ||||
|       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", | ||||
| @ -1200,34 +996,6 @@ | ||||
|         "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": { | ||||
|       "version": "3.0.0", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", | ||||
| @ -1303,28 +1027,6 @@ | ||||
|         "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": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", | ||||
| @ -1422,18 +1124,6 @@ | ||||
|         "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": { | ||||
|       "version": "3.6.0", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "1.1.1", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "5.7.1", | ||||
|       "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", | ||||
| @ -1569,12 +1245,6 @@ | ||||
|         "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": { | ||||
|       "version": "1.1.1", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||||
| @ -1680,44 +1339,6 @@ | ||||
|         "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": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", | ||||
| @ -1803,15 +1424,6 @@ | ||||
|         "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": { | ||||
|       "version": "1.0.2", | ||||
|       "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", | ||||
|       "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": { | ||||
|       "version": "4.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", | ||||
| @ -1856,16 +1448,6 @@ | ||||
|         "compress-commons": "^4.1.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", | ||||
|   "version": "1.2.7", | ||||
|   "version": "1.2.8", | ||||
|   "description": "Self-hosted audiobook server for managing and playing audiobooks", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
| @ -29,6 +29,7 @@ | ||||
|     "cookie-parser": "^1.4.5", | ||||
|     "express": "^4.17.1", | ||||
|     "express-fileupload": "^1.2.1", | ||||
|     "express-rate-limit": "^5.3.0", | ||||
|     "fluent-ffmpeg": "^2.1.2", | ||||
|     "fs-extra": "^10.0.0", | ||||
|     "ip": "^1.1.5", | ||||
|  | ||||
| @ -103,18 +103,18 @@ class Auth { | ||||
| 
 | ||||
|     var user = this.users.find(u => u.username === username) | ||||
| 
 | ||||
|     if (!user) { | ||||
|       return res.json({ error: 'User not found' }) | ||||
|     if (!user || !user.isActive) { | ||||
|       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
 | ||||
|     if (user.id === 'root' && (!user.pash || user.pash === '')) { | ||||
|       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 { | ||||
|         return res.json({ user: user.toJSONForBrowser() }) | ||||
|       } | ||||
| @ -127,10 +127,22 @@ class Auth { | ||||
|         user: user.toJSONForBrowser() | ||||
|       }) | ||||
|     } else { | ||||
|       res.json({ | ||||
|         error: 'Invalid Password' | ||||
|       }) | ||||
|       Logger.debug(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit}`) | ||||
|       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) { | ||||
|  | ||||
| @ -13,6 +13,8 @@ class Scanner { | ||||
|   constructor(AUDIOBOOK_PATH, METADATA_PATH, db, emitter) { | ||||
|     this.AudiobookPath = AUDIOBOOK_PATH | ||||
|     this.MetadataPath = METADATA_PATH | ||||
|     this.BookMetadataPath = Path.join(this.MetadataPath, 'books') | ||||
| 
 | ||||
|     this.db = db | ||||
|     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) { | ||||
|     var method = req.params.method | ||||
|     var query = req.query | ||||
|  | ||||
| @ -4,6 +4,7 @@ const http = require('http') | ||||
| const SocketIO = require('socket.io') | ||||
| const fs = require('fs-extra') | ||||
| const fileUpload = require('express-fileupload') | ||||
| const rateLimit = require('express-rate-limit') | ||||
| 
 | ||||
| const Auth = require('./Auth') | ||||
| const Watcher = require('./Watcher') | ||||
| @ -110,6 +111,14 @@ class Server { | ||||
|     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() { | ||||
|     Logger.info('[Server] Init') | ||||
|     await this.streamManager.ensureStreamsDir() | ||||
| @ -172,6 +181,21 @@ class Server { | ||||
|     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() { | ||||
|     Logger.info('=== Starting Server ===') | ||||
|     await this.init() | ||||
| @ -206,13 +230,18 @@ class Server { | ||||
| 
 | ||||
|     app.use('/api', this.authMiddleware.bind(this), this.apiController.router) | ||||
|     app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router) | ||||
| 
 | ||||
|     // Incomplete work in progress
 | ||||
|     // 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('/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.get('/ping', (req, res) => { | ||||
|       Logger.info('Recieved ping') | ||||
|       res.json({ success: true }) | ||||
| @ -231,7 +260,6 @@ class Server { | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     this.server.listen(this.Port, this.Host, () => { | ||||
|       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_covers', this.scanCovers.bind(this)) | ||||
|       socket.on('cancel_scan', this.cancelScan.bind(this)) | ||||
|       socket.on('save_metadata', (audiobookId) => this.saveMetadata(socket, audiobookId)) | ||||
| 
 | ||||
|       // Streaming
 | ||||
|       socket.on('open_stream', (audiobookId) => this.streamManager.openStreamSocketRequest(socket, audiobookId)) | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| const Path = require('path') | ||||
| const { bytesPretty, elapsedPretty } = require('../utils/fileUtils') | ||||
| const { comparePaths, getIno } = require('../utils/index') | ||||
| const nfoGenerator = require('../utils/nfoGenerator') | ||||
| const Logger = require('../Logger') | ||||
| const Book = require('./Book') | ||||
| const AudioTrack = require('./AudioTrack') | ||||
| @ -530,5 +531,9 @@ class Audiobook { | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   writeNfoFile(nfoFilename = 'metadata.nfo') { | ||||
|     return nfoGenerator(this, nfoFilename) | ||||
|   } | ||||
| } | ||||
| module.exports = Audiobook | ||||
| @ -1,3 +1,4 @@ | ||||
| const fs = require('fs-extra') | ||||
| const Path = require('path') | ||||
| const Logger = require('../Logger') | ||||
| const parseAuthors = require('../utils/parseAuthors') | ||||
|  | ||||
| @ -3,10 +3,14 @@ const { CoverDestination } = require('../utils/constants') | ||||
| class ServerSettings { | ||||
|   constructor(settings) { | ||||
|     this.id = 'server-settings' | ||||
| 
 | ||||
|     this.autoTagNew = false | ||||
|     this.newTagExpireDays = 15 | ||||
|     this.scannerParseSubtitle = false | ||||
|     this.coverDestination = CoverDestination.METADATA | ||||
|     this.saveMetadataFile = false | ||||
|     this.rateLimitLoginRequests = 10 | ||||
|     this.rateLimitLoginWindow = 10 * 60 * 1000 // 10 Minutes
 | ||||
| 
 | ||||
|     if (settings) { | ||||
|       this.construct(settings) | ||||
| @ -18,6 +22,9 @@ class ServerSettings { | ||||
|     this.newTagExpireDays = settings.newTagExpireDays | ||||
|     this.scannerParseSubtitle = settings.scannerParseSubtitle | ||||
|     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() { | ||||
| @ -26,7 +33,10 @@ class ServerSettings { | ||||
|       autoTagNew: this.autoTagNew, | ||||
|       newTagExpireDays: this.newTagExpireDays, | ||||
|       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.token = null | ||||
|     this.isActive = true | ||||
|     this.isLocked = false | ||||
|     this.createdAt = null | ||||
|     this.audiobooks = null | ||||
| 
 | ||||
| @ -76,6 +77,7 @@ class User { | ||||
|       token: this.token, | ||||
|       audiobooks: this.audiobooksToJSON(), | ||||
|       isActive: this.isActive, | ||||
|       isLocked: this.isLocked, | ||||
|       createdAt: this.createdAt, | ||||
|       settings: this.settings, | ||||
|       permissions: this.permissions | ||||
| @ -91,6 +93,7 @@ class User { | ||||
|       token: this.token, | ||||
|       audiobooks: this.audiobooksToJSON(), | ||||
|       isActive: this.isActive, | ||||
|       isLocked: this.isLocked, | ||||
|       createdAt: this.createdAt, | ||||
|       settings: this.settings, | ||||
|       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.settings = user.settings || this.getDefaultUserSettings() | ||||
|     this.permissions = user.permissions || this.getDefaultUserPermissions() | ||||
|  | ||||
| @ -29,9 +29,10 @@ function bytesPretty(bytes, decimals = 0) { | ||||
|     return '0 Bytes' | ||||
|   } | ||||
|   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 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] | ||||
| } | ||||
| 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