refactor(api): replace plex-api package with internal implementation (#2335)

Removes plex-api dependency and its type declarations. Then extends the ExternalApi class for
PlexAPI implementation to mimick the exact same old behaviour. This should resolve the security
vulnerabilities in transitive dependencies: form-data(critical), request (moderate, deprecated),
tough-cookie (moderate), xml2js (moderate). Plex-api itself is also no longer maintained.
This commit is contained in:
fallenbagel
2026-01-27 00:52:44 +05:00
committed by GitHub
parent 6031fab3b4
commit f627a8e9db
5 changed files with 51 additions and 294 deletions

View File

@@ -76,7 +76,6 @@
"nodemailer": "6.10.0", "nodemailer": "6.10.0",
"openpgp": "6.3.0", "openpgp": "6.3.0",
"pg": "8.16.3", "pg": "8.16.3",
"plex-api": "5.3.2",
"pug": "3.0.3", "pug": "3.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-ace": "10.1.0", "react-ace": "10.1.0",

205
pnpm-lock.yaml generated
View File

@@ -140,9 +140,6 @@ importers:
pg: pg:
specifier: 8.16.3 specifier: 8.16.3
version: 8.16.3 version: 8.16.3
plex-api:
specifier: 5.3.2
version: 5.3.2
pug: pug:
specifier: 3.0.3 specifier: 3.0.3
version: 3.0.3 version: 3.0.3
@@ -5434,10 +5431,6 @@ packages:
forever-agent@0.6.1: forever-agent@0.6.1:
resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==}
form-data@2.3.3:
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
engines: {node: '>= 0.12'}
form-data@4.0.5: form-data@4.0.5:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -5659,15 +5652,6 @@ packages:
engines: {node: '>=0.4.7'} engines: {node: '>=0.4.7'}
hasBin: true hasBin: true
har-schema@2.0.0:
resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
engines: {node: '>=4'}
har-validator@5.1.5:
resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==}
engines: {node: '>=6'}
deprecated: this library is no longer supported
hard-rejection@2.1.0: hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -5790,10 +5774,6 @@ packages:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
http-signature@1.2.0:
resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
engines: {node: '>=0.8', npm: '>=1.3.7'}
http-signature@1.4.0: http-signature@1.4.0:
resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@@ -6375,10 +6355,6 @@ packages:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0} engines: {'0': node >= 0.2.0}
jsprim@1.4.2:
resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
engines: {node: '>=0.6.0'}
jsprim@2.0.2: jsprim@2.0.2:
resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==}
engines: {'0': node >=0.6.0} engines: {'0': node >=0.6.0}
@@ -7213,9 +7189,6 @@ packages:
nullthrows@1.1.1: nullthrows@1.1.1:
resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
oauth-sign@0.9.0:
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
ob1@0.80.12: ob1@0.80.12:
resolution: {integrity: sha512-VMArClVT6LkhUGpnuEoBuyjG9rzUyEzg4PDkav6wK1cLhOK02gPCYFxoiB4mqVnrMhDpIzJcrGNAMVi9P+hXrw==} resolution: {integrity: sha512-VMArClVT6LkhUGpnuEoBuyjG9rzUyEzg4PDkav6wK1cLhOK02gPCYFxoiB4mqVnrMhDpIzJcrGNAMVi9P+hXrw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -7520,18 +7493,6 @@ packages:
resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
engines: {node: '>=6'} engines: {node: '>=6'}
plex-api-credentials@3.0.1:
resolution: {integrity: sha512-E0PdSVSqE5rmdEFNsIvFPDJQZPdBX7UR4sgkm9HF4V8VNbX0N4elASnMuoste8i9eTh4hCIqt761NQfzl45XnQ==}
engines: {node: '>=4.0'}
plex-api-headers@1.1.0:
resolution: {integrity: sha512-Igl37++MSa+4H8LNP3Ene9GU0e1YypmXvFVNvVUwoAx44e74jbUlJXy4Q5rLSBisn0O2lBKdE6VkFIwrDl+UnQ==}
engines: {node: '>=0.10'}
plex-api@5.3.2:
resolution: {integrity: sha512-RCFMQKu1cx+G4Y/8NfaifWEWEyhFFUV/d1/qAD4O1Si/IeA1S4hueC9py0uzFKR2iz+knuEPtVXtq9Upc9GImg==}
engines: {node: '>=8.0'}
plimit-lit@1.6.1: plimit-lit@1.6.1:
resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -7752,9 +7713,6 @@ packages:
proxy-from-env@1.1.0: proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
psl@1.15.0:
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
pstree.remy@1.1.8: pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
@@ -7827,10 +7785,6 @@ packages:
resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
qs@6.5.3:
resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
engines: {node: '>=0.6'}
querystring@0.2.1: querystring@0.2.1:
resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==} resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==}
engines: {node: '>=0.4.x'} engines: {node: '>=0.4.x'}
@@ -8145,24 +8099,6 @@ packages:
request-progress@3.0.0: request-progress@3.0.0:
resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==}
request-promise-core@1.1.2:
resolution: {integrity: sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==}
engines: {node: '>=0.10.0'}
peerDependencies:
request: ^2.34
request-promise@4.2.4:
resolution: {integrity: sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==}
engines: {node: '>=0.10.0'}
deprecated: request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
peerDependencies:
request: ^2.34
request@2.88.2:
resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
engines: {node: '>= 6'}
deprecated: request has been deprecated, see https://github.com/request/request/issues/3142
require-directory@2.1.1: require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -8599,10 +8535,6 @@ packages:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
stealthy-require@1.1.1:
resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==}
engines: {node: '>=0.10.0'}
stop-iteration-iterator@1.1.0: stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -8899,10 +8831,6 @@ packages:
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
hasBin: true hasBin: true
tough-cookie@2.5.0:
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
engines: {node: '>=0.8'}
tough-cookie@5.1.2: tough-cookie@5.1.2:
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -9289,11 +9217,6 @@ packages:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true hasBin: true
uuid@3.4.0:
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
hasBin: true
uuid@8.3.2: uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true hasBin: true
@@ -9488,12 +9411,6 @@ packages:
utf-8-validate: utf-8-validate:
optional: true optional: true
xml2js@0.4.16:
resolution: {integrity: sha512-9rH7UTUNphxeDRCeJBi4Fxp/z0fd92WeXNQ1dtUYMpqO3PaK59hVDCuUmOGHRZvufJDzcX8TG+Kdty7ylM0t2w==}
xml2js@0.4.19:
resolution: {integrity: sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==}
xml2js@0.4.23: xml2js@0.4.23:
resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==}
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
@@ -9502,14 +9419,6 @@ packages:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
xmlbuilder@4.2.1:
resolution: {integrity: sha512-oEePiEefhQhAeUnwRnIBLBWmk/fsWWbQ53EEWsRuzECbQ3m5o/Esmq6H47CYYwSLW+Ynt0rS9hd0pd2ogMAWjg==}
engines: {node: '>=0.8.0'}
xmlbuilder@9.0.7:
resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==}
engines: {node: '>=4.0'}
xtend@4.0.2: xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}
@@ -16259,12 +16168,6 @@ snapshots:
forever-agent@0.6.1: {} forever-agent@0.6.1: {}
form-data@2.3.3:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
form-data@4.0.5: form-data@4.0.5:
dependencies: dependencies:
asynckit: 0.4.0 asynckit: 0.4.0
@@ -16552,13 +16455,6 @@ snapshots:
uglify-js: 3.19.3 uglify-js: 3.19.3
optional: true optional: true
har-schema@2.0.0: {}
har-validator@5.1.5:
dependencies:
ajv: 6.12.6
har-schema: 2.0.0
hard-rejection@2.1.0: {} hard-rejection@2.1.0: {}
has-bigints@1.0.2: {} has-bigints@1.0.2: {}
@@ -16702,12 +16598,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
http-signature@1.2.0:
dependencies:
assert-plus: 1.0.0
jsprim: 1.4.2
sshpk: 1.18.0
http-signature@1.4.0: http-signature@1.4.0:
dependencies: dependencies:
assert-plus: 1.0.0 assert-plus: 1.0.0
@@ -17306,13 +17196,6 @@ snapshots:
jsonparse@1.3.1: {} jsonparse@1.3.1: {}
jsprim@1.4.2:
dependencies:
assert-plus: 1.0.0
extsprintf: 1.3.0
json-schema: 0.4.0
verror: 1.10.0
jsprim@2.0.2: jsprim@2.0.2:
dependencies: dependencies:
assert-plus: 1.0.0 assert-plus: 1.0.0
@@ -18416,8 +18299,6 @@ snapshots:
nullthrows@1.1.1: {} nullthrows@1.1.1: {}
oauth-sign@0.9.0: {}
ob1@0.80.12: ob1@0.80.12:
dependencies: dependencies:
flow-enums-runtime: 0.0.6 flow-enums-runtime: 0.0.6
@@ -18718,25 +18599,6 @@ snapshots:
dependencies: dependencies:
find-up: 3.0.0 find-up: 3.0.0
plex-api-credentials@3.0.1(request@2.88.2):
dependencies:
bluebird: 3.7.2
plex-api-headers: 1.1.0
request-promise: 4.2.4(request@2.88.2)
xml2js: 0.4.19
transitivePeerDependencies:
- request
plex-api-headers@1.1.0: {}
plex-api@5.3.2:
dependencies:
plex-api-credentials: 3.0.1(request@2.88.2)
plex-api-headers: 1.1.0
request: 2.88.2
uuid: 3.4.0
xml2js: 0.4.16
plimit-lit@1.6.1: plimit-lit@1.6.1:
dependencies: dependencies:
queue-lit: 1.5.2 queue-lit: 1.5.2
@@ -18909,10 +18771,6 @@ snapshots:
proxy-from-env@1.1.0: {} proxy-from-env@1.1.0: {}
psl@1.15.0:
dependencies:
punycode: 2.3.1
pstree.remy@1.1.8: {} pstree.remy@1.1.8: {}
pug-attrs@3.0.0: pug-attrs@3.0.0:
@@ -19011,8 +18869,6 @@ snapshots:
dependencies: dependencies:
side-channel: 1.1.0 side-channel: 1.1.0
qs@6.5.3: {}
querystring@0.2.1: {} querystring@0.2.1: {}
queue-lit@1.5.2: {} queue-lit@1.5.2: {}
@@ -19523,42 +19379,6 @@ snapshots:
dependencies: dependencies:
throttleit: 1.0.1 throttleit: 1.0.1
request-promise-core@1.1.2(request@2.88.2):
dependencies:
lodash: 4.17.21
request: 2.88.2
request-promise@4.2.4(request@2.88.2):
dependencies:
bluebird: 3.7.2
request: 2.88.2
request-promise-core: 1.1.2(request@2.88.2)
stealthy-require: 1.1.1
tough-cookie: 2.5.0
request@2.88.2:
dependencies:
aws-sign2: 0.7.0
aws4: 1.13.2
caseless: 0.12.0
combined-stream: 1.0.8
extend: 3.0.2
forever-agent: 0.6.1
form-data: 2.3.3
har-validator: 5.1.5
http-signature: 1.2.0
is-typedarray: 1.0.0
isstream: 0.1.2
json-stringify-safe: 5.0.1
mime-types: 2.1.35
oauth-sign: 0.9.0
performance-now: 2.1.0
qs: 6.5.3
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
uuid: 3.4.0
require-directory@2.1.1: {} require-directory@2.1.1: {}
require-from-string@2.0.2: {} require-from-string@2.0.2: {}
@@ -20070,8 +19890,6 @@ snapshots:
statuses@2.0.2: {} statuses@2.0.2: {}
stealthy-require@1.1.1: {}
stop-iteration-iterator@1.1.0: stop-iteration-iterator@1.1.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@@ -20392,11 +20210,6 @@ snapshots:
touch@3.1.1: {} touch@3.1.1: {}
tough-cookie@2.5.0:
dependencies:
psl: 1.15.0
punycode: 2.3.1
tough-cookie@5.1.2: tough-cookie@5.1.2:
dependencies: dependencies:
tldts: 6.1.86 tldts: 6.1.86
@@ -20776,8 +20589,6 @@ snapshots:
uuid@11.1.0: {} uuid@11.1.0: {}
uuid@3.4.0: {}
uuid@8.3.2: {} uuid@8.3.2: {}
uuid@9.0.1: uuid@9.0.1:
@@ -21044,16 +20855,6 @@ snapshots:
ws@7.5.10: {} ws@7.5.10: {}
xml2js@0.4.16:
dependencies:
sax: 1.4.1
xmlbuilder: 4.2.1
xml2js@0.4.19:
dependencies:
sax: 1.4.1
xmlbuilder: 9.0.7
xml2js@0.4.23: xml2js@0.4.23:
dependencies: dependencies:
sax: 1.4.1 sax: 1.4.1
@@ -21061,12 +20862,6 @@ snapshots:
xmlbuilder@11.0.1: {} xmlbuilder@11.0.1: {}
xmlbuilder@4.2.1:
dependencies:
lodash: 4.17.21
xmlbuilder@9.0.7: {}
xtend@4.0.2: {} xtend@4.0.2: {}
y18n@4.0.3: {} y18n@4.0.3: {}

View File

@@ -13,6 +13,7 @@ const DEFAULT_ROLLING_BUFFER = 10000;
export interface ExternalAPIOptions { export interface ExternalAPIOptions {
nodeCache?: NodeCache; nodeCache?: NodeCache;
headers?: Record<string, unknown>; headers?: Record<string, unknown>;
timeout?: number;
rateLimit?: { rateLimit?: {
maxRPS: number; maxRPS: number;
maxRequests: number; maxRequests: number;
@@ -32,6 +33,7 @@ class ExternalAPI {
this.axios = axios.create({ this.axios = axios.create({
baseURL: baseUrl, baseURL: baseUrl,
params, params,
timeout: options.timeout,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Accept: 'application/json', Accept: 'application/json',

View File

@@ -1,7 +1,14 @@
import ExternalAPI from '@server/api/externalapi';
import type { Library, PlexSettings } from '@server/lib/settings'; import type { Library, PlexSettings } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings';
import logger from '@server/logger'; import logger from '@server/logger';
import NodePlexAPI from 'plex-api';
interface PlexStatusResponse {
MediaContainer: {
machineIdentifier: string;
friendlyName: string;
};
}
export interface PlexLibraryItem { export interface PlexLibraryItem {
ratingKey: string; ratingKey: string;
@@ -84,9 +91,7 @@ interface PlexMetadataResponse {
}; };
} }
class PlexAPI { class PlexAPI extends ExternalAPI {
private plexClient: NodePlexAPI;
constructor({ constructor({
plexToken, plexToken,
plexSettings, plexSettings,
@@ -97,48 +102,33 @@ class PlexAPI {
timeout?: number; timeout?: number;
}) { }) {
const settings = getSettings(); const settings = getSettings();
let settingsPlex: PlexSettings | undefined; const settingsPlex = plexSettings ?? settings.plex;
plexSettings
? (settingsPlex = plexSettings)
: (settingsPlex = getSettings().plex);
this.plexClient = new NodePlexAPI({ const protocol = settingsPlex.useSsl ? 'https' : 'http';
hostname: settingsPlex.ip, const baseUrl = `${protocol}://${settingsPlex.ip}:${settingsPlex.port}`;
port: settingsPlex.port,
https: settingsPlex.useSsl, super(
timeout: timeout, baseUrl,
token: plexToken ?? undefined, {},
authenticator: { {
authenticate: ( timeout,
_plexApi, headers: {
cb: (err?: string, token?: string) => void 'X-Plex-Token': plexToken ?? '',
) => { 'X-Plex-Client-Identifier': settings.clientId,
if (!plexToken) { 'X-Plex-Product': 'Seerr',
return cb('Plex Token not found!'); 'X-Plex-Device-Name': 'Seerr',
} 'X-Plex-Platform': 'Seerr',
cb(undefined, plexToken);
}, },
}, }
// requestOptions: { );
// includeChildren: 1,
// },
options: {
identifier: settings.clientId,
product: 'Seerr',
deviceName: 'Seerr',
platform: 'Seerr',
},
});
} }
public async getStatus() { public async getStatus(): Promise<PlexStatusResponse> {
return await this.plexClient.query('/'); return await this.get('/');
} }
public async getLibraries(): Promise<PlexLibrary[]> { public async getLibraries(): Promise<PlexLibrary[]> {
const response = await this.plexClient.query<PlexLibrariesResponse>( const response = await this.get<PlexLibrariesResponse>('/library/sections');
'/library/sections'
);
return response.MediaContainer.Directory; return response.MediaContainer.Directory;
} }
@@ -187,13 +177,15 @@ class PlexAPI {
id: string, id: string,
{ offset = 0, size = 50 }: { offset?: number; size?: number } = {} { offset = 0, size = 50 }: { offset?: number; size?: number } = {}
): Promise<{ totalSize: number; items: PlexLibraryItem[] }> { ): Promise<{ totalSize: number; items: PlexLibraryItem[] }> {
const response = await this.plexClient.query<PlexLibraryResponse>({ const response = await this.get<PlexLibraryResponse>(
uri: `/library/sections/${id}/all?includeGuids=1`, `/library/sections/${id}/all?includeGuids=1`,
extraHeaders: { {
'X-Plex-Container-Start': `${offset}`, headers: {
'X-Plex-Container-Size': `${size}`, 'X-Plex-Container-Start': `${offset}`,
}, 'X-Plex-Container-Size': `${size}`,
}); },
}
);
return { return {
totalSize: response.MediaContainer.totalSize, totalSize: response.MediaContainer.totalSize,
@@ -205,7 +197,7 @@ class PlexAPI {
key: string, key: string,
options: { includeChildren?: boolean } = {} options: { includeChildren?: boolean } = {}
): Promise<PlexMetadata> { ): Promise<PlexMetadata> {
const response = await this.plexClient.query<PlexMetadataResponse>( const response = await this.get<PlexMetadataResponse>(
`/library/metadata/${key}${ `/library/metadata/${key}${
options.includeChildren ? '?includeChildren=1' : '' options.includeChildren ? '?includeChildren=1' : ''
}` }`
@@ -215,7 +207,7 @@ class PlexAPI {
} }
public async getChildrenMetadata(key: string): Promise<PlexMetadata[]> { public async getChildrenMetadata(key: string): Promise<PlexMetadata[]> {
const response = await this.plexClient.query<PlexMetadataResponse>( const response = await this.get<PlexMetadataResponse>(
`/library/metadata/${key}/children` `/library/metadata/${key}/children`
); );
@@ -229,15 +221,17 @@ class PlexAPI {
}, },
mediaType: 'movie' | 'show' mediaType: 'movie' | 'show'
): Promise<PlexLibraryItem[]> { ): Promise<PlexLibraryItem[]> {
const response = await this.plexClient.query<PlexLibraryResponse>({ const response = await this.get<PlexLibraryResponse>(
uri: `/library/sections/${id}/all?type=${ `/library/sections/${id}/all?type=${
mediaType === 'show' ? '4' : '1' mediaType === 'show' ? '4' : '1'
}&sort=addedAt%3Adesc&addedAt>>=${Math.floor(options.addedAt / 1000)}`, }&sort=addedAt%3Adesc&addedAt>>=${Math.floor(options.addedAt / 1000)}`,
extraHeaders: { {
'X-Plex-Container-Start': `0`, headers: {
'X-Plex-Container-Size': `500`, 'X-Plex-Container-Start': '0',
}, 'X-Plex-Container-Size': '500',
}); },
}
);
return response.MediaContainer.Metadata; return response.MediaContainer.Metadata;
} }

View File

@@ -1,33 +0,0 @@
declare module 'plex-api' {
export default class PlexAPI {
constructor(intiialOptions: {
hostname: string;
port: number;
token?: string;
https?: boolean;
timeout?: number;
authenticator: {
authenticate: (
_plexApi: PlexAPI,
cb: (err?: string, token?: string) => void
) => void;
};
options: {
identifier: string;
product: string;
deviceName: string;
platform: string;
};
requestOptions?: Record<string, string | number>;
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
query: <T extends Record<string, any>>(
endpoint:
| string
| {
uri: string;
extraHeaders?: Record<string, string | number>;
}
) => Promise<T>;
}
}