Compare commits

..

16 Commits

Author SHA1 Message Date
JoaquinOlivero
c195a6f720 style: formatting 2024-09-18 03:02:41 +00:00
JoaquinOlivero
f7a4efdde1 fix: avatar images not showing on issues page 2024-09-18 02:45:55 +00:00
JoaquinOlivero
6a475b5e41 fix: remove log and correctly set the if statement for the cached image component 2024-09-18 02:45:43 +00:00
JoaquinOlivero
ae5542b6d3 refactor: only cache avatar with http url protocol 2024-09-18 02:45:41 +00:00
JoaquinOlivero
cbf3bbb17d fix: fix incomplete URL substring sanitization 2024-09-18 02:45:40 +00:00
JoaquinOlivero
14fdd4e293 fix: fix vulnerability 2024-09-18 02:45:39 +00:00
JoaquinOlivero
d2f651081a refactor: checks if the default avatar is cached to avoid creating duplicates for different users 2024-09-18 02:45:35 +00:00
JoaquinOlivero
822ca690da style: grammar 2024-09-18 02:44:04 +00:00
JoaquinOlivero
b2291f5125 refactor: use 'mime' package to detmerine file extension 2024-09-18 02:44:02 +00:00
JoaquinOlivero
84f7a9795e fix: requested changes 2024-09-18 02:44:01 +00:00
JoaquinOlivero
9c5f4fe2f0 fix: remove unexpired unused image when a user changes their avatar 2024-09-18 02:44:00 +00:00
JoaquinOlivero
3af39dd5f0 fix(s): set correct src URL for cached image 2024-09-18 02:43:58 +00:00
JoaquinOlivero
7ad44830d1 fix: show the correct avatar in the list of available users in advanced request 2024-09-18 02:43:57 +00:00
JoaquinOlivero
bd8deedfbe fix: set avatar image URL 2024-09-18 02:43:56 +00:00
JoaquinOlivero
548aaede26 fix: extract keys 2024-09-18 02:43:55 +00:00
JoaquinOlivero
8c7a08b4e9 refactor: proxy and cache user avatar images 2024-09-18 02:43:50 +00:00
48 changed files with 176 additions and 341 deletions

View File

@@ -439,15 +439,6 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "M0NsTeRRR",
"name": "Ludovic Ortega",
"avatar_url": "https://avatars.githubusercontent.com/u/37785089?v=4",
"profile": "https://github.com/M0NsTeRRR",
"contributions": [
"security"
]
} }
] ]
} }

View File

@@ -11,7 +11,7 @@
<a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a> <a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
<a href="https://github.com/fallenbagel/jellyseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/fallenbagel/jellyseerr"></a> <a href="https://github.com/fallenbagel/jellyseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/fallenbagel/jellyseerr"></a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-48-orange.svg"/></a> <a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-47-orange.svg"/></a>
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
**Jellyseerr** is a free and open source software application for managing requests for your media library. **Jellyseerr** is a free and open source software application for managing requests for your media library.
@@ -146,7 +146,6 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XDark187"><img src="https://avatars.githubusercontent.com/u/39034192?v=4?s=100" width="100px;" alt="Baraa"/><br /><sub><b>Baraa</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=XDark187" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/XDark187"><img src="https://avatars.githubusercontent.com/u/39034192?v=4?s=100" width="100px;" alt="Baraa"/><br /><sub><b>Baraa</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=XDark187" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franciscofsales"><img src="https://avatars.githubusercontent.com/u/7977645?v=4?s=100" width="100px;" alt="Francisco Sales"/><br /><sub><b>Francisco Sales</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=franciscofsales" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/franciscofsales"><img src="https://avatars.githubusercontent.com/u/7977645?v=4?s=100" width="100px;" alt="Francisco Sales"/><br /><sub><b>Francisco Sales</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=franciscofsales" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myselfolli"><img src="https://avatars.githubusercontent.com/u/37535998?v=4?s=100" width="100px;" alt="Oliver Laing"/><br /><sub><b>Oliver Laing</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=myselfolli" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/myselfolli"><img src="https://avatars.githubusercontent.com/u/37535998?v=4?s=100" width="100px;" alt="Oliver Laing"/><br /><sub><b>Oliver Laing</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=myselfolli" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/M0NsTeRRR"><img src="https://avatars.githubusercontent.com/u/37785089?v=4?s=100" width="100px;" alt="Ludovic Ortega"/><br /><sub><b>Ludovic Ortega</b></sub></a><br /><a href="#security-M0NsTeRRR" title="Security">🛡️</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -12,12 +12,49 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem'; import TabItem from '@theme/TabItem';
### Prerequisites ### Prerequisites
<Tabs groupId="versions" queryString>
<TabItem value="latest" label="Latest">
- [Node.js 18.x](https://nodejs.org/en/download/)
- [Yarn 1.x](https://classic.yarnpkg.com/lang/en/docs/install)
- [Git](https://git-scm.com/downloads)
</TabItem>
<TabItem value="develop" label="Develop">
- [Node.js 20.x](https://nodejs.org/en/download/) - [Node.js 20.x](https://nodejs.org/en/download/)
- [Pnpm 9.x](https://pnpm.io/installation) - [Pnpm 9.x](https://pnpm.io/installation)
- [Git](https://git-scm.com/downloads) - [Git](https://git-scm.com/downloads)
</TabItem>
</Tabs>
## Unix (Linux, macOS) ## Unix (Linux, macOS)
### Installation ### Installation
<Tabs groupId="versions" queryString>
<TabItem value="latest" label="latest">
1. Assuming you want the working directory to be `/opt/jellyseerr`, create the directory and navigate to it:
```bash
sudo mkdir -p /opt/jellyseerr && cd /opt/jellyseerr
```
2. Clone the Jellyseerr repository and checkout the latest release:
```bash
git clone https://github.com/Fallenbagel/jellyseerr.git
cd jellyseerr
git checkout main
```
3. Install the dependencies:
```bash
CYPRESS_INSTALL_BINARY=0 yarn install --frozen-lockfile --network-timeout 1000000
```
4. Build the project:
```bash
yarn build
```
5. Start Jellyseerr:
```bash
yarn start
```
</TabItem>
<TabItem value="develop" label="develop">
1. Assuming you want the working directory to be `/opt/jellyseerr`, create the directory and navigate to it: 1. Assuming you want the working directory to be `/opt/jellyseerr`, create the directory and navigate to it:
```bash ```bash
sudo mkdir -p /opt/jellyseerr && cd /opt/jellyseerr sudo mkdir -p /opt/jellyseerr && cd /opt/jellyseerr
@@ -40,6 +77,8 @@ pnpm build
```bash ```bash
pnpm start pnpm start
``` ```
</TabItem>
</Tabs>
:::info :::info
You can now access Jellyseerr by visiting `http://localhost:5055` in your web browser. You can now access Jellyseerr by visiting `http://localhost:5055` in your web browser.
@@ -195,6 +234,33 @@ pm2 status jellyseerr
## Windows ## Windows
### Installation ### Installation
<Tabs groupId="versions" queryString>
<TabItem value="latest" label="latest">
1. Assuming you want the working directory to be `C:\jellyseerr`, create the directory and navigate to it:
```powershell
mkdir C:\jellyseerr
cd C:\jellyseerr
```
2. Clone the Jellyseerr repository and checkout the latest release:
```powershell
git clone https://github.com/Fallenbagel/jellyseerr.git .
git checkout main
```
3. Install the dependencies:
```powershell
npm install -g win-node-env
set CYPRESS_INSTALL_BINARY=0 && yarn install --frozen-lockfile --network-timeout 1000000
```
4. Build the project:
```powershell
yarn build
```
5. Start Jellyseerr:
```powershell
yarn start
```
</TabItem>
<TabItem value="develop" label="develop">
1. Assuming you want the working directory to be `C:\jellyseerr`, create the directory and navigate to it: 1. Assuming you want the working directory to be `C:\jellyseerr`, create the directory and navigate to it:
```powershell ```powershell
mkdir C:\jellyseerr mkdir C:\jellyseerr
@@ -218,6 +284,8 @@ pnpm build
```powershell ```powershell
pnpm start pnpm start
``` ```
</TabItem>
</Tabs>
:::tip :::tip
You can add the environment variables to a `.env` file in the Jellyseerr directory. You can add the environment variables to a `.env` file in the Jellyseerr directory.
@@ -245,7 +313,6 @@ node dist/index.js
- Set the trigger to "When the computer starts" - Set the trigger to "When the computer starts"
- Set the action to "Start a program" - Set the action to "Start a program"
- Set the program/script to the path of the `start-jellyseerr.bat` file - Set the program/script to the path of the `start-jellyseerr.bat` file
- Set the "Start in" to the jellyseerr directory.
- Click "Finish" - Click "Finish"
Now, Jellyseerr will start when the computer boots up in the background. Now, Jellyseerr will start when the computer boots up in the background.

View File

@@ -93,8 +93,7 @@
"sqlite3": "5.1.4", "sqlite3": "5.1.4",
"swagger-ui-express": "4.6.2", "swagger-ui-express": "4.6.2",
"swr": "2.2.5", "swr": "2.2.5",
"typeorm": "0.3.11", "typeorm": "0.3.12",
"undici": "^6.20.1",
"web-push": "3.5.0", "web-push": "3.5.0",
"winston": "3.8.2", "winston": "3.8.2",
"winston-daily-rotate-file": "4.7.1", "winston-daily-rotate-file": "4.7.1",

65
pnpm-lock.yaml generated
View File

@@ -49,7 +49,7 @@ importers:
version: 2.11.0 version: 2.11.0
connect-typeorm: connect-typeorm:
specifier: 1.1.4 specifier: 1.1.4
version: 1.1.4(typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))) version: 1.1.4(typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)))
cookie-parser: cookie-parser:
specifier: 1.4.6 specifier: 1.4.6
version: 1.4.6 version: 1.4.6
@@ -192,11 +192,8 @@ importers:
specifier: 2.2.5 specifier: 2.2.5
version: 2.2.5(react@18.3.1) version: 2.2.5(react@18.3.1)
typeorm: typeorm:
specifier: 0.3.11 specifier: 0.3.12
version: 0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)) version: 0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))
undici:
specifier: ^6.20.1
version: 6.20.1
web-push: web-push:
specifier: 3.5.0 specifier: 3.5.0
version: 3.5.0 version: 3.5.0
@@ -4267,6 +4264,10 @@ packages:
resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
engines: {node: '>=0.11'} engines: {node: '>=0.11'}
date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dateformat@3.0.3: dateformat@3.0.3:
resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
@@ -5388,8 +5389,8 @@ packages:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
https-proxy-agent@7.0.5: https-proxy-agent@7.0.4:
resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
human-signals@1.1.1: human-signals@1.1.1:
@@ -6553,6 +6554,11 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
mkdirp@2.1.6:
resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==}
engines: {node: '>=10'}
hasBin: true
modify-values@1.0.1: modify-values@1.0.1:
resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -7724,6 +7730,9 @@ packages:
reflect-metadata@0.1.13: reflect-metadata@0.1.13:
resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==}
reflect-metadata@0.1.14:
resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}
reflect.getprototypeof@1.0.6: reflect.getprototypeof@1.0.6:
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -8661,8 +8670,8 @@ packages:
typedarray@0.0.6: typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
typeorm@0.3.11: typeorm@0.3.12:
resolution: {integrity: sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==} resolution: {integrity: sha512-sYSxBmCf1nJLLTcYtwqZ+lQIRtLPyUoO93rHTOKk9vJCyT4UfRtU7oRsJvfvKP3nnZTD1hzz2SEy2zwPEN6OyA==}
engines: {node: '>= 12.9.0'} engines: {node: '>= 12.9.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -8673,7 +8682,7 @@ packages:
ioredis: ^5.0.4 ioredis: ^5.0.4
mongodb: ^3.6.0 mongodb: ^3.6.0
mssql: ^7.3.0 mssql: ^7.3.0
mysql2: ^2.2.5 mysql2: ^2.2.5 || ^3.0.1
oracledb: ^5.1.0 oracledb: ^5.1.0
pg: ^8.5.1 pg: ^8.5.1
pg-native: ^3.0.0 pg-native: ^3.0.0
@@ -8759,10 +8768,6 @@ packages:
undici-types@5.26.5: undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici@6.20.1:
resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==}
engines: {node: '>=18.17'}
unicode-canonical-property-names-ecmascript@2.0.0: unicode-canonical-property-names-ecmascript@2.0.0:
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -12305,7 +12310,7 @@ snapshots:
fs-extra: 11.2.0 fs-extra: 11.2.0
globby: 11.1.0 globby: 11.1.0
http-proxy-agent: 7.0.2 http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5 https-proxy-agent: 7.0.4
issue-parser: 6.0.0 issue-parser: 6.0.0
lodash: 4.17.21 lodash: 4.17.21
mime: 3.0.0 mime: 3.0.0
@@ -13819,13 +13824,13 @@ snapshots:
ini: 1.3.8 ini: 1.3.8
proto-list: 1.2.4 proto-list: 1.2.4
connect-typeorm@1.1.4(typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))): connect-typeorm@1.1.4(typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))):
dependencies: dependencies:
'@types/debug': 0.0.31 '@types/debug': 0.0.31
'@types/express-session': 1.17.6 '@types/express-session': 1.17.6
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
express-session: 1.18.0 express-session: 1.18.0
typeorm: 0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)) typeorm: 0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -14176,6 +14181,10 @@ snapshots:
date-fns@2.29.3: {} date-fns@2.29.3: {}
date-fns@2.30.0:
dependencies:
'@babel/runtime': 7.24.7
dateformat@3.0.3: {} dateformat@3.0.3: {}
dayjs@1.11.11: {} dayjs@1.11.11: {}
@@ -15730,7 +15739,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
https-proxy-agent@7.0.5: https-proxy-agent@7.0.4:
dependencies: dependencies:
agent-base: 7.1.1 agent-base: 7.1.1
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
@@ -17140,6 +17149,8 @@ snapshots:
mkdirp@1.0.4: {} mkdirp@1.0.4: {}
mkdirp@2.1.6: {}
modify-values@1.0.1: {} modify-values@1.0.1: {}
moment@2.30.1: {} moment@2.30.1: {}
@@ -18361,6 +18372,8 @@ snapshots:
reflect-metadata@0.1.13: {} reflect-metadata@0.1.13: {}
reflect-metadata@0.1.14: {}
reflect.getprototypeof@1.0.6: reflect.getprototypeof@1.0.6:
dependencies: dependencies:
call-bind: 1.0.7 call-bind: 1.0.7
@@ -19418,23 +19431,23 @@ snapshots:
typedarray@0.0.6: {} typedarray@0.0.6: {}
typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)): typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)):
dependencies: dependencies:
'@sqltools/formatter': 1.2.5 '@sqltools/formatter': 1.2.5
app-root-path: 3.1.0 app-root-path: 3.1.0
buffer: 6.0.3 buffer: 6.0.3
chalk: 4.1.2 chalk: 4.1.2
cli-highlight: 2.1.11 cli-highlight: 2.1.11
date-fns: 2.29.3 date-fns: 2.30.0
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
dotenv: 16.4.5 dotenv: 16.4.5
glob: 7.2.3 glob: 8.1.0
js-yaml: 4.1.0 js-yaml: 4.1.0
mkdirp: 1.0.4 mkdirp: 2.1.6
reflect-metadata: 0.1.13 reflect-metadata: 0.1.14
sha.js: 2.4.11 sha.js: 2.4.11
tslib: 2.6.3 tslib: 2.6.3
uuid: 8.3.2 uuid: 9.0.1
xml2js: 0.4.23 xml2js: 0.4.23
yargs: 17.7.2 yargs: 17.7.2
optionalDependencies: optionalDependencies:
@@ -19473,8 +19486,6 @@ snapshots:
undici-types@5.26.5: {} undici-types@5.26.5: {}
undici@6.20.1: {}
unicode-canonical-property-names-ecmascript@2.0.0: {} unicode-canonical-property-names-ecmascript@2.0.0: {}
unicode-emoji-utils@1.2.0: unicode-emoji-utils@1.2.0:

View File

@@ -76,7 +76,7 @@ class ExternalAPI {
} }
const data = await this.getDataFromResponse(response); const data = await this.getDataFromResponse(response);
if (this.cache && ttl !== 0) { if (this.cache) {
this.cache.set(cacheKey, data, ttl ?? DEFAULT_TTL); this.cache.set(cacheKey, data, ttl ?? DEFAULT_TTL);
} }
@@ -120,7 +120,7 @@ class ExternalAPI {
} }
const resData = await this.getDataFromResponse(response); const resData = await this.getDataFromResponse(response);
if (this.cache && ttl !== 0) { if (this.cache) {
this.cache.set(cacheKey, resData, ttl ?? DEFAULT_TTL); this.cache.set(cacheKey, resData, ttl ?? DEFAULT_TTL);
} }
@@ -164,7 +164,7 @@ class ExternalAPI {
} }
const resData = await this.getDataFromResponse(response); const resData = await this.getDataFromResponse(response);
if (this.cache && ttl !== 0) { if (this.cache) {
this.cache.set(cacheKey, resData, ttl ?? DEFAULT_TTL); this.cache.set(cacheKey, resData, ttl ?? DEFAULT_TTL);
} }

View File

@@ -182,7 +182,7 @@ class RottenTomatoes extends ExternalAPI {
); );
} }
if (!tvshow || !tvshow.rottenTomatoes) { if (!tvshow) {
return null; return null;
} }

View File

@@ -157,13 +157,9 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
public getQueue = async (): Promise<(QueueItem & QueueItemAppendT)[]> => { public getQueue = async (): Promise<(QueueItem & QueueItemAppendT)[]> => {
try { try {
const data = await this.get<QueueResponse<QueueItemAppendT>>( const data = await this.get<QueueResponse<QueueItemAppendT>>(`/queue`, {
`/queue`, includeEpisode: 'true',
{ });
includeEpisode: 'true',
},
0
);
return data.records; return data.records;
} catch (e) { } catch (e) {
@@ -197,24 +193,15 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
} }
}; };
async refreshMonitoredDownloads(): Promise<void> {
await this.runCommand('RefreshMonitoredDownloads', {});
}
protected async runCommand( protected async runCommand(
commandName: string, commandName: string,
options: Record<string, unknown> options: Record<string, unknown>
): Promise<void> { ): Promise<void> {
try { try {
await this.post( await this.post(`/command`, {
`/command`, name: commandName,
{ ...options,
name: commandName, });
...options,
},
{},
0
);
} catch (e) { } catch (e) {
throw new Error(`[${this.apiName}] Failed to run command: ${e.message}`); throw new Error(`[${this.apiName}] Failed to run command: ${e.message}`);
} }

View File

@@ -231,7 +231,7 @@ class Media {
this.mediaUrl = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId}&context=home&serverId=${serverId}`; this.mediaUrl = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId}&context=home&serverId=${serverId}`;
} }
if (this.jellyfinMediaId4k) { if (this.jellyfinMediaId4k) {
this.mediaUrl4k = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId4k}&context=home&serverId=${serverId}`; this.mediaUrl4k = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId}&context=home&serverId=${serverId}`;
} }
} }
} }

View File

@@ -37,7 +37,6 @@ import dns from 'node:dns';
import net from 'node:net'; import net from 'node:net';
import path from 'path'; import path from 'path';
import swaggerUi from 'swagger-ui-express'; import swaggerUi from 'swagger-ui-express';
import { ProxyAgent, setGlobalDispatcher } from 'undici';
import YAML from 'yamljs'; import YAML from 'yamljs';
if (process.env.forceIpv4First === 'true') { if (process.env.forceIpv4First === 'true') {
@@ -68,11 +67,6 @@ app
const settings = await getSettings().load(); const settings = await getSettings().load();
restartFlag.initializeSettings(settings.main); restartFlag.initializeSettings(settings.main);
// Register HTTP proxy
if (settings.main.httpProxy) {
setGlobalDispatcher(new ProxyAgent(settings.main.httpProxy));
}
// Migrate library types // Migrate library types
if ( if (
settings.plex.libraries.length > 1 && settings.plex.libraries.length > 1 &&
@@ -181,7 +175,7 @@ app
}, },
store: new TypeormStore({ store: new TypeormStore({
cleanupLimit: 2, cleanupLimit: 2,
ttl: 60 * 60 * 24 * 30, ttl: 1000 * 60 * 60 * 24 * 30,
}).connect(sessionRespository) as Store, }).connect(sessionRespository) as Store,
}) })
); );

View File

@@ -85,7 +85,6 @@ class DownloadTracker {
}); });
try { try {
await radarr.refreshMonitoredDownloads();
const queueItems = await radarr.getQueue(); const queueItems = await radarr.getQueue();
this.radarrServers[server.id] = queueItems.map((item) => ({ this.radarrServers[server.id] = queueItems.map((item) => ({
@@ -163,7 +162,6 @@ class DownloadTracker {
}); });
try { try {
await sonarr.refreshMonitoredDownloads();
const queueItems = await sonarr.getQueue(); const queueItems = await sonarr.getQueue();
this.sonarrServers[server.id] = queueItems.map((item) => ({ this.sonarrServers[server.id] = queueItems.map((item) => ({

View File

@@ -119,7 +119,6 @@ export interface MainSettings {
mediaServerType: number; mediaServerType: number;
partialRequestsEnabled: boolean; partialRequestsEnabled: boolean;
locale: string; locale: string;
httpProxy: string;
} }
interface PublicSettings { interface PublicSettings {
@@ -326,7 +325,6 @@ class Settings {
mediaServerType: MediaServerType.NOT_CONFIGURED, mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true, partialRequestsEnabled: true,
locale: 'en', locale: 'en',
httpProxy: '',
}, },
plex: { plex: {
name: '', name: '',
@@ -650,7 +648,7 @@ class Settings {
if (data) { if (data) {
const parsedJson = JSON.parse(data); const parsedJson = JSON.parse(data);
this.data = await runMigrations(parsedJson, SETTINGS_PATH); this.data = await runMigrations(parsedJson);
this.data = merge(this.data, parsedJson); this.data = merge(this.data, parsedJson);

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-console */
import type { AllSettings } from '@server/lib/settings'; import type { AllSettings } from '@server/lib/settings';
import logger from '@server/logger'; import logger from '@server/logger';
import fs from 'fs'; import fs from 'fs';
@@ -7,8 +6,7 @@ import path from 'path';
const migrationsDir = path.join(__dirname, 'migrations'); const migrationsDir = path.join(__dirname, 'migrations');
export const runMigrations = async ( export const runMigrations = async (
settings: AllSettings, settings: AllSettings
SETTINGS_PATH: string
): Promise<AllSettings> => { ): Promise<AllSettings> => {
const migrations = fs const migrations = fs
.readdirSync(migrationsDir) .readdirSync(migrationsDir)
@@ -19,43 +17,14 @@ export const runMigrations = async (
let migrated = settings; let migrated = settings;
try { try {
const settingsBefore = JSON.stringify(migrated);
for (const migration of migrations) { for (const migration of migrations) {
migrated = await migration(migrated); migrated = await migration(migrated);
} }
const settingsAfter = JSON.stringify(migrated);
if (settingsBefore !== settingsAfter) {
// a migration occured
// we check that the new config will be saved
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(migrated, undefined, ' '));
const fileSaved = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8'));
if (JSON.stringify(fileSaved) !== settingsAfter) {
// something went wrong while saving file
throw new Error('Unable to save settings after migration.');
}
}
} catch (e) { } catch (e) {
logger.error( logger.error(
`Something went wrong while running settings migrations: ${e.message}`, `Something went wrong while running settings migrations: ${e.message}`,
{ label: 'Settings Migrator' } { label: 'Settings Migrator' }
); );
// we stop jellyseerr if the migration failed
console.log(
'===================================================================='
);
console.log(
' SOMETHING WENT WRONG WHILE RUNNING SETTINGS MIGRATIONS '
);
console.log(
' Please check that your configuration folder is properly set up '
);
console.log(
'===================================================================='
);
process.exit();
} }
return migrated; return migrated;

View File

@@ -262,6 +262,8 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
urlBase: body.urlBase, urlBase: body.urlBase,
}); });
const { externalHostname } = getSettings().jellyfin;
// Try to find deviceId that corresponds to jellyfin user, else generate a new one // Try to find deviceId that corresponds to jellyfin user, else generate a new one
let user = await userRepository.findOne({ let user = await userRepository.findOne({
where: { jellyfinUsername: body.username }, where: { jellyfinUsername: body.username },
@@ -279,6 +281,11 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
// First we need to attempt to log the user in to jellyfin // First we need to attempt to log the user in to jellyfin
const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId); const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId);
const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
: hostname;
const ip = req.ip; const ip = req.ip;
let clientIp; let clientIp;
@@ -329,7 +336,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinAuthToken: account.AccessToken, jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN, permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag avatar: account.User.PrimaryImageTag
? `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` ? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: gravatarUrl(body.email || account.User.Name, { : gravatarUrl(body.email || account.User.Name, {
default: 'mm', default: 'mm',
size: 200, size: 200,
@@ -348,7 +355,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinAuthToken: account.AccessToken, jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN, permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag avatar: account.User.PrimaryImageTag
? `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` ? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: gravatarUrl(body.email || account.User.Name, { : gravatarUrl(body.email || account.User.Name, {
default: 'mm', default: 'mm',
size: 200, size: 200,
@@ -403,7 +410,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
); );
// Update the users avatar with their jellyfin profile pic (incase it changed) // Update the users avatar with their jellyfin profile pic (incase it changed)
if (account.User.PrimaryImageTag) { if (account.User.PrimaryImageTag) {
const avatar = `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`; const avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
if (avatar !== user.avatar) { if (avatar !== user.avatar) {
const avatarProxy = new ImageProxy('avatar', ''); const avatarProxy = new ImageProxy('avatar', '');
avatarProxy.clearCachedImage(user.avatar); avatarProxy.clearCachedImage(user.avatar);
@@ -460,7 +467,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinDeviceId: deviceId, jellyfinDeviceId: deviceId,
permissions: settings.main.defaultPermissions, permissions: settings.main.defaultPermissions,
avatar: account.User.PrimaryImageTag avatar: account.User.PrimaryImageTag
? `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` ? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: gravatarUrl(body.email || account.User.Name, { : gravatarUrl(body.email || account.User.Name, {
default: 'mm', default: 'mm',
size: 200, size: 200,

View File

@@ -1,8 +1,5 @@
import { MediaServerType } from '@server/constants/server';
import ImageProxy from '@server/lib/imageproxy'; import ImageProxy from '@server/lib/imageproxy';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger'; import logger from '@server/logger';
import { getHostname } from '@server/utils/getHostname';
import { Router } from 'express'; import { Router } from 'express';
const router = Router(); const router = Router();
@@ -10,25 +7,9 @@ const router = Router();
const avatarImageProxy = new ImageProxy('avatar', ''); const avatarImageProxy = new ImageProxy('avatar', '');
// Proxy avatar images // Proxy avatar images
router.get('/*', async (req, res) => { router.get('/*', async (req, res) => {
let imagePath = ''; const imagePath = req.url.startsWith('/') ? req.url.slice(1) : req.url;
try { try {
const jellyfinAvatar = req.url.match(
/(\/Users\/\w+\/Images\/Primary\/?\?tag=\w+&quality=90)$/
)?.[1];
if (!jellyfinAvatar) {
const mediaServerType = getSettings().main.mediaServerType;
throw new Error(
`Provided URL is not ${
mediaServerType === MediaServerType.JELLYFIN
? 'a Jellyfin'
: 'an Emby'
} avatar.`
);
}
const imageUrl = new URL(jellyfinAvatar, getHostname());
imagePath = imageUrl.toString();
const imageData = await avatarImageProxy.getImage(imagePath); const imageData = await avatarImageProxy.getImage(imagePath);
res.writeHead(200, { res.writeHead(200, {

View File

@@ -377,6 +377,11 @@ settingsRoutes.get('/jellyfin/library', async (req, res, next) => {
settingsRoutes.get('/jellyfin/users', async (req, res) => { settingsRoutes.get('/jellyfin/users', async (req, res) => {
const settings = getSettings(); const settings = getSettings();
const { externalHostname } = settings.jellyfin;
const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
: getHostname();
const userRepository = getRepository(User); const userRepository = getRepository(User);
const admin = await userRepository.findOneOrFail({ const admin = await userRepository.findOneOrFail({
@@ -396,7 +401,7 @@ settingsRoutes.get('/jellyfin/users', async (req, res) => {
username: user.Name, username: user.Name,
id: user.Id, id: user.Id,
thumb: user.PrimaryImageTag thumb: user.PrimaryImageTag
? `/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90` ? `${jellyfinHost}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90`
: gravatarUrl(user.Name, { default: 'mm', size: 200 }), : gravatarUrl(user.Name, { default: 'mm', size: 200 }),
email: user.Name, email: user.Name,
})); }));

View File

@@ -516,6 +516,12 @@ router.post(
//const jellyfinUsersResponse = await jellyfinClient.getUsers(); //const jellyfinUsersResponse = await jellyfinClient.getUsers();
const createdUsers: User[] = []; const createdUsers: User[] = [];
const { externalHostname } = getSettings().jellyfin;
const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
: hostname;
jellyfinClient.setUserId(admin.jellyfinUserId ?? ''); jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
const jellyfinUsers = await jellyfinClient.getUsers(); const jellyfinUsers = await jellyfinClient.getUsers();
@@ -540,7 +546,7 @@ router.post(
email: jellyfinUser?.Name, email: jellyfinUser?.Name,
permissions: settings.main.defaultPermissions, permissions: settings.main.defaultPermissions,
avatar: jellyfinUser?.PrimaryImageTag avatar: jellyfinUser?.PrimaryImageTag
? `/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90` ? `${jellyfinHost}/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90`
: gravatarUrl(jellyfinUser?.Name ?? '', { : gravatarUrl(jellyfinUser?.Name ?? '', {
default: 'mm', default: 'mm',
size: 200, size: 200,

View File

@@ -13,8 +13,7 @@ class RestartFlag {
return ( return (
this.settings.csrfProtection !== settings.csrfProtection || this.settings.csrfProtection !== settings.csrfProtection ||
this.settings.trustProxy !== settings.trustProxy || this.settings.trustProxy !== settings.trustProxy
this.settings.httpProxy !== settings.httpProxy
); );
} }
} }

View File

@@ -268,7 +268,6 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
{title && title.backdropPath && ( {title && title.backdropPath && (
<div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3"> <div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3">
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
alt="" alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -294,7 +293,6 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105" className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
> >
<CachedImage <CachedImage
type="tmdb"
src={ src={
title?.posterPath title?.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
@@ -357,7 +355,6 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
<Link href={`/users/${item.user.id}`}> <Link href={`/users/${item.user.id}`}>
<span className="group flex items-center truncate"> <span className="group flex items-center truncate">
<CachedImage <CachedImage
type="avatar"
src={item.user.avatar} src={item.user.avatar}
alt="" alt=""
className="avatar-sm ml-1.5" className="avatar-sm ml-1.5"

View File

@@ -198,7 +198,6 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
{data.backdropPath && ( {data.backdropPath && (
<div className="media-page-bg-image"> <div className="media-page-bg-image">
<CachedImage <CachedImage
type="tmdb"
alt="" alt=""
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -229,7 +228,6 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
<div className="media-header"> <div className="media-header">
<div className="media-poster"> <div className="media-poster">
<CachedImage <CachedImage
type="tmdb"
src={ src={
data.posterPath data.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`

View File

@@ -4,34 +4,24 @@ import Image from 'next/image';
const imageLoader: ImageLoader = ({ src }) => src; const imageLoader: ImageLoader = ({ src }) => src;
export type CachedImageProps = ImageProps & {
src: string;
type: 'tmdb' | 'avatar';
};
/** /**
* The CachedImage component should be used wherever * The CachedImage component should be used wherever
* we want to offer the option to locally cache images. * we want to offer the option to locally cache images.
**/ **/
const CachedImage = ({ src, type, ...props }: CachedImageProps) => { const CachedImage = ({ src, ...props }: ImageProps) => {
const { currentSettings } = useSettings(); const { currentSettings } = useSettings();
let imageUrl: string; let imageUrl = src;
if (type === 'tmdb') { if (typeof imageUrl === 'string' && imageUrl.startsWith('http')) {
// tmdb stuff const parsedUrl = new URL(imageUrl);
imageUrl =
currentSettings.cacheImages && !src.startsWith('/') if (parsedUrl.host === 'image.tmdb.org') {
? src.replace(/^https:\/\/image\.tmdb\.org\//, '/imageproxy/') if (currentSettings.cacheImages)
: src; imageUrl = imageUrl.replace('https://image.tmdb.org', '/imageproxy');
} else if (type === 'avatar') { } else if (parsedUrl.host !== 'gravatar.com') {
// jellyfin avatar (in any) imageUrl = '/avatarproxy/' + imageUrl;
const jellyfinAvatar = src.match( }
/(\/Users\/\w+\/Images\/Primary\/?\?tag=\w+&quality=90)$/
)?.[1];
imageUrl = jellyfinAvatar ? `/avatarproxy` + jellyfinAvatar : src;
} else {
return null;
} }
return <Image unoptimized loader={imageLoader} src={imageUrl} {...props} />; return <Image unoptimized loader={imageLoader} src={imageUrl} {...props} />;

View File

@@ -61,7 +61,6 @@ const ImageFader: ForwardRefRenderFunction<HTMLDivElement, ImageFaderProps> = (
{...props} {...props}
> >
<CachedImage <CachedImage
type="tmdb"
className="absolute inset-0 h-full w-full" className="absolute inset-0 h-full w-full"
alt="" alt=""
src={imageUrl} src={imageUrl}

View File

@@ -123,7 +123,6 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
{backdrop && ( {backdrop && (
<div className="absolute top-0 left-0 right-0 z-0 h-64 max-h-full w-full"> <div className="absolute top-0 left-0 right-0 z-0 h-64 max-h-full w-full">
<CachedImage <CachedImage
type="tmdb"
alt="" alt=""
src={backdrop} src={backdrop}
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}

View File

@@ -33,7 +33,6 @@ const CompanyCard = ({ image, url, name }: CompanyCardProps) => {
> >
<div className="relative h-full w-full"> <div className="relative h-full w-full">
<CachedImage <CachedImage
type="tmdb"
src={image} src={image}
alt={name} alt={name}
className="relative z-40 h-full w-full" className="relative z-40 h-full w-full"

View File

@@ -36,7 +36,6 @@ const GenreCard = ({ image, url, name, canExpand = false }: GenreCardProps) => {
tabIndex={0} tabIndex={0}
> >
<CachedImage <CachedImage
type="tmdb"
src={image} src={image}
alt="" alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}

View File

@@ -89,8 +89,7 @@ const IssueComment = ({
</Transition> </Transition>
<Link href={isActiveUser ? '/profile' : `/users/${comment.user.id}`}> <Link href={isActiveUser ? '/profile' : `/users/${comment.user.id}`}>
<CachedImage <CachedImage
type="avatar" src={`${comment.user.avatar}`}
src={comment.user.avatar}
alt="" alt=""
className="h-10 w-10 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105" className="h-10 w-10 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
width={40} width={40}

View File

@@ -217,7 +217,6 @@ const IssueDetails = () => {
{data.backdropPath && ( {data.backdropPath && (
<div className="media-page-bg-image"> <div className="media-page-bg-image">
<CachedImage <CachedImage
type="tmdb"
alt="" alt=""
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -236,7 +235,6 @@ const IssueDetails = () => {
<div className="media-header"> <div className="media-header">
<div className="media-poster"> <div className="media-poster">
<CachedImage <CachedImage
type="tmdb"
src={ src={
data.posterPath data.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
@@ -289,8 +287,7 @@ const IssueDetails = () => {
className="group ml-1 inline-flex h-full items-center xl:ml-1.5" className="group ml-1 inline-flex h-full items-center xl:ml-1.5"
> >
<CachedImage <CachedImage
type="avatar" src={`${issueData.createdBy.avatar}`}
src={issueData.createdBy.avatar}
alt="" alt=""
className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full object-cover transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6" className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full object-cover transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6"
width={20} width={20}

View File

@@ -112,7 +112,6 @@ const IssueItem = ({ issue }: IssueItemProps) => {
{title.backdropPath && ( {title.backdropPath && (
<div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3"> <div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3">
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
alt="" alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -138,7 +137,6 @@ const IssueItem = ({ issue }: IssueItemProps) => {
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105" className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
> >
<CachedImage <CachedImage
type="tmdb"
src={ src={
title.posterPath title.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
@@ -228,8 +226,7 @@ const IssueItem = ({ issue }: IssueItemProps) => {
className="group flex items-center truncate" className="group flex items-center truncate"
> >
<CachedImage <CachedImage
type="avatar" src={'/avatarproxy/' + issue.createdBy.avatar}
src={issue.createdBy.avatar}
alt="" alt=""
className="avatar-sm ml-1.5 object-cover" className="avatar-sm ml-1.5 object-cover"
width={20} width={20}

View File

@@ -7,7 +7,6 @@ import {
CogIcon, CogIcon,
EllipsisHorizontalIcon, EllipsisHorizontalIcon,
ExclamationTriangleIcon, ExclamationTriangleIcon,
EyeSlashIcon,
FilmIcon, FilmIcon,
SparklesIcon, SparklesIcon,
TvIcon, TvIcon,
@@ -17,7 +16,6 @@ import {
ClockIcon as FilledClockIcon, ClockIcon as FilledClockIcon,
CogIcon as FilledCogIcon, CogIcon as FilledCogIcon,
ExclamationTriangleIcon as FilledExclamationTriangleIcon, ExclamationTriangleIcon as FilledExclamationTriangleIcon,
EyeSlashIcon as FilledEyeSlashIcon,
FilmIcon as FilledFilmIcon, FilmIcon as FilledFilmIcon,
SparklesIcon as FilledSparklesIcon, SparklesIcon as FilledSparklesIcon,
TvIcon as FilledTvIcon, TvIcon as FilledTvIcon,
@@ -86,18 +84,6 @@ const MobileMenu = () => {
svgIconSelected: <FilledClockIcon className="h-6 w-6" />, svgIconSelected: <FilledClockIcon className="h-6 w-6" />,
activeRegExp: /^\/requests/, activeRegExp: /^\/requests/,
}, },
{
href: '/blacklist',
content: intl.formatMessage(menuMessages.blacklist),
svgIcon: <EyeSlashIcon className="h-6 w-6" />,
svgIconSelected: <FilledEyeSlashIcon className="h-6 w-6" />,
activeRegExp: /^\/blacklist/,
requiredPermission: [
Permission.MANAGE_BLACKLIST,
Permission.VIEW_BLACKLIST,
],
permissionType: 'or',
},
{ {
href: '/issues', href: '/issues',
content: intl.formatMessage(menuMessages.issues), content: intl.formatMessage(menuMessages.issues),

View File

@@ -57,7 +57,6 @@ const UserDropdown = () => {
data-testid="user-menu" data-testid="user-menu"
> >
<CachedImage <CachedImage
type="avatar"
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10" className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
src={user ? user.avatar : ''} src={user ? user.avatar : ''}
alt="" alt=""
@@ -81,7 +80,6 @@ const UserDropdown = () => {
<div className="flex flex-col space-y-4 px-4 py-4"> <div className="flex flex-col space-y-4 px-4 py-4">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<CachedImage <CachedImage
type="avatar"
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10" className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
src={user ? user.avatar : ''} src={user ? user.avatar : ''}
alt="" alt=""

View File

@@ -369,7 +369,6 @@ const ManageSlideOver = ({
content={user.displayName} content={user.displayName}
> >
<CachedImage <CachedImage
type="avatar"
src={user.avatar} src={user.avatar}
alt={user.displayName} alt={user.displayName}
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105" className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
@@ -531,7 +530,6 @@ const ManageSlideOver = ({
content={user.displayName} content={user.displayName}
> >
<CachedImage <CachedImage
type="avatar"
src={user.avatar} src={user.avatar}
alt={user.displayName} alt={user.displayName}
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105" className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"

View File

@@ -448,7 +448,6 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
{data.backdropPath && ( {data.backdropPath && (
<div className="media-page-bg-image"> <div className="media-page-bg-image">
<CachedImage <CachedImage
type="tmdb"
alt="" alt=""
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -495,7 +494,6 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
<div className="media-header"> <div className="media-header">
<div className="media-poster"> <div className="media-poster">
<CachedImage <CachedImage
type="tmdb"
src={ src={
data.posterPath data.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
@@ -743,7 +741,6 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
<div className="group relative z-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-lg bg-gray-800 bg-cover bg-center shadow-md ring-1 ring-gray-700 transition duration-300 hover:scale-105 hover:ring-gray-500"> <div className="group relative z-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-lg bg-gray-800 bg-cover bg-center shadow-md ring-1 ring-gray-700 transition duration-300 hover:scale-105 hover:ring-gray-500">
<div className="absolute inset-0 z-0"> <div className="absolute inset-0 z-0">
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/w1440_and_h320_multi_faces/${data.collection.backdropPath}`} src={`https://image.tmdb.org/t/p/w1440_and_h320_multi_faces/${data.collection.backdropPath}`}
alt="" alt=""
style={{ style={{

View File

@@ -51,7 +51,6 @@ const PersonCard = ({
{profilePath ? ( {profilePath ? (
<div className="relative h-full w-3/4 overflow-hidden rounded-full ring-1 ring-gray-700"> <div className="relative h-full w-3/4 overflow-hidden rounded-full ring-1 ring-gray-700">
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${profilePath}`} src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${profilePath}`}
alt="" alt=""
style={{ style={{

View File

@@ -227,7 +227,6 @@ const PersonDetails = () => {
{data.profilePath && ( {data.profilePath && (
<div className="relative mb-6 mr-0 h-36 w-36 flex-shrink-0 overflow-hidden rounded-full ring-1 ring-gray-700 lg:mb-0 lg:mr-6 lg:h-44 lg:w-44"> <div className="relative mb-6 mr-0 h-36 w-36 flex-shrink-0 overflow-hidden rounded-full ring-1 ring-gray-700 lg:mb-0 lg:mr-6 lg:h-44 lg:w-44">
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.profilePath}`} src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.profilePath}`}
alt="" alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}

View File

@@ -116,7 +116,6 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
> >
<span className="avatar-sm"> <span className="avatar-sm">
<CachedImage <CachedImage
type="avatar"
src={requestData.requestedBy.avatar} src={requestData.requestedBy.avatar}
alt="" alt=""
className="avatar-sm object-cover" className="avatar-sm object-cover"
@@ -346,7 +345,6 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
{title.backdropPath && ( {title.backdropPath && (
<div className="absolute inset-0 z-0"> <div className="absolute inset-0 z-0">
<CachedImage <CachedImage
type="tmdb"
alt="" alt=""
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -392,7 +390,6 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
> >
<span className="avatar-sm"> <span className="avatar-sm">
<CachedImage <CachedImage
type="avatar"
src={requestData.requestedBy.avatar} src={requestData.requestedBy.avatar}
alt="" alt=""
className="avatar-sm object-cover" className="avatar-sm object-cover"
@@ -605,7 +602,6 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
className="w-20 flex-shrink-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-md shadow-sm transition duration-300 hover:scale-105 hover:shadow-md sm:w-28" className="w-20 flex-shrink-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-md shadow-sm transition duration-300 hover:scale-105 hover:shadow-md sm:w-28"
> >
<CachedImage <CachedImage
type="tmdb"
src={ src={
title.posterPath title.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`

View File

@@ -42,7 +42,6 @@ const messages = defineMessages('components.RequestList.RequestItem', {
tmdbid: 'TMDB ID', tmdbid: 'TMDB ID',
tvdbid: 'TheTVDB ID', tvdbid: 'TheTVDB ID',
unknowntitle: 'Unknown Title', unknowntitle: 'Unknown Title',
removearr: 'Remove from {arr}',
profileName: 'Profile', profileName: 'Profile',
}); });
@@ -191,7 +190,6 @@ const RequestItemError = ({
> >
<span className="avatar-sm ml-1.5"> <span className="avatar-sm ml-1.5">
<CachedImage <CachedImage
type="avatar"
src={requestData.requestedBy.avatar} src={requestData.requestedBy.avatar}
alt="" alt=""
className="avatar-sm object-cover" className="avatar-sm object-cover"
@@ -251,7 +249,6 @@ const RequestItemError = ({
> >
<span className="avatar-sm ml-1.5"> <span className="avatar-sm ml-1.5">
<CachedImage <CachedImage
type="avatar"
src={requestData.modifiedBy.avatar} src={requestData.modifiedBy.avatar}
alt="" alt=""
className="avatar-sm object-cover" className="avatar-sm object-cover"
@@ -344,18 +341,6 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
revalidateList(); revalidateList();
}; };
const deleteMediaFile = async () => {
if (request.media) {
await fetch(`/api/v1/media/${request.media.id}/file`, {
method: 'DELETE',
});
await fetch(`/api/v1/media/${request.media.id}`, {
method: 'DELETE',
});
revalidateList();
}
};
const retryRequest = async () => { const retryRequest = async () => {
setRetrying(true); setRetrying(true);
@@ -420,7 +405,6 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
{title.backdropPath && ( {title.backdropPath && (
<div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3"> <div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3">
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
alt="" alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -446,7 +430,6 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105" className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
> >
<CachedImage <CachedImage
type="tmdb"
src={ src={
title.posterPath title.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
@@ -574,7 +557,6 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
> >
<span className="avatar-sm ml-1.5"> <span className="avatar-sm ml-1.5">
<CachedImage <CachedImage
type="avatar"
src={requestData.requestedBy.avatar} src={requestData.requestedBy.avatar}
alt="" alt=""
className="avatar-sm object-cover" className="avatar-sm object-cover"
@@ -634,8 +616,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
> >
<span className="avatar-sm ml-1.5"> <span className="avatar-sm ml-1.5">
<CachedImage <CachedImage
type="avatar" src={requestData.requestedBy.avatar}
src={requestData.modifiedBy.avatar}
alt="" alt=""
className="avatar-sm object-cover" className="avatar-sm object-cover"
width={20} width={20}
@@ -685,28 +666,14 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
)} )}
{requestData.status !== MediaRequestStatus.PENDING && {requestData.status !== MediaRequestStatus.PENDING &&
hasPermission(Permission.MANAGE_REQUESTS) && ( hasPermission(Permission.MANAGE_REQUESTS) && (
<> <ConfirmButton
<ConfirmButton onClick={() => deleteRequest()}
onClick={() => deleteRequest()} confirmText={intl.formatMessage(globalMessages.areyousure)}
confirmText={intl.formatMessage(globalMessages.areyousure)} className="w-full"
className="w-full" >
> <TrashIcon />
<TrashIcon /> <span>{intl.formatMessage(messages.deleterequest)}</span>
<span>{intl.formatMessage(messages.deleterequest)}</span> </ConfirmButton>
</ConfirmButton>
<ConfirmButton
onClick={() => deleteMediaFile()}
confirmText={intl.formatMessage(globalMessages.areyousure)}
className="w-full"
>
<TrashIcon />
<span>
{intl.formatMessage(messages.removearr, {
arr: request.type === 'movie' ? 'Radarr' : 'Sonarr',
})}
</span>
</ConfirmButton>
</>
)} )}
{requestData.status === MediaRequestStatus.PENDING && {requestData.status === MediaRequestStatus.PENDING &&
hasPermission(Permission.MANAGE_REQUESTS) && ( hasPermission(Permission.MANAGE_REQUESTS) && (

View File

@@ -562,7 +562,6 @@ const AdvancedRequester = ({
<Listbox.Button className="focus:shadow-outline-blue relative w-full cursor-default rounded-md border border-gray-700 bg-gray-800 py-2 pl-3 pr-10 text-left text-white transition duration-150 ease-in-out focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5"> <Listbox.Button className="focus:shadow-outline-blue relative w-full cursor-default rounded-md border border-gray-700 bg-gray-800 py-2 pl-3 pr-10 text-left text-white transition duration-150 ease-in-out focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5">
<span className="flex items-center"> <span className="flex items-center">
<CachedImage <CachedImage
type="avatar"
src={selectedUser.avatar} src={selectedUser.avatar}
alt="" alt=""
className="h-6 w-6 flex-shrink-0 rounded-full object-cover" className="h-6 w-6 flex-shrink-0 rounded-full object-cover"
@@ -615,7 +614,6 @@ const AdvancedRequester = ({
} flex items-center`} } flex items-center`}
> >
<CachedImage <CachedImage
type="avatar"
src={user.avatar} src={user.avatar}
alt="" alt=""
className="h-6 w-6 flex-shrink-0 rounded-full object-cover" className="h-6 w-6 flex-shrink-0 rounded-full object-cover"

View File

@@ -437,7 +437,6 @@ const CollectionRequestModal = ({
> >
<div className="relative h-auto w-10 flex-shrink-0 overflow-hidden rounded-md"> <div className="relative h-auto w-10 flex-shrink-0 overflow-hidden rounded-md">
<CachedImage <CachedImage
type="tmdb"
src={ src={
part.posterPath part.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${part.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${part.posterPath}`

View File

@@ -452,7 +452,6 @@ export const WatchProviderSelector = ({
tabIndex={0} tabIndex={0}
> >
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/original${provider.logoPath}`} src={`https://image.tmdb.org/t/p/original${provider.logoPath}`}
alt="" alt=""
style={{ style={{
@@ -498,7 +497,6 @@ export const WatchProviderSelector = ({
tabIndex={0} tabIndex={0}
> >
<CachedImage <CachedImage
type="tmdb"
src={`https://image.tmdb.org/t/p/original${provider.logoPath}`} src={`https://image.tmdb.org/t/p/original${provider.logoPath}`}
alt="" alt=""
style={{ style={{

View File

@@ -55,8 +55,6 @@ const messages = defineMessages('components.Settings.SettingsMain', {
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
partialRequestsEnabled: 'Allow Partial Series Requests', partialRequestsEnabled: 'Allow Partial Series Requests',
locale: 'Display Language', locale: 'Display Language',
httpProxy: 'HTTP Proxy',
httpProxyTip: 'Tooltip to write',
}); });
const SettingsMain = () => { const SettingsMain = () => {
@@ -84,9 +82,6 @@ const SettingsMain = () => {
intl.formatMessage(messages.validationApplicationUrlTrailingSlash), intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
(value) => !value || !value.endsWith('/') (value) => !value || !value.endsWith('/')
), ),
httpProxy: Yup.string().url(
intl.formatMessage(messages.validationApplicationUrl)
),
}); });
const regenerate = async () => { const regenerate = async () => {
@@ -142,7 +137,6 @@ const SettingsMain = () => {
partialRequestsEnabled: data?.partialRequestsEnabled, partialRequestsEnabled: data?.partialRequestsEnabled,
trustProxy: data?.trustProxy, trustProxy: data?.trustProxy,
cacheImages: data?.cacheImages, cacheImages: data?.cacheImages,
httpProxy: data?.httpProxy,
}} }}
enableReinitialize enableReinitialize
validationSchema={MainSettingsSchema} validationSchema={MainSettingsSchema}
@@ -164,7 +158,6 @@ const SettingsMain = () => {
partialRequestsEnabled: values.partialRequestsEnabled, partialRequestsEnabled: values.partialRequestsEnabled,
trustProxy: values.trustProxy, trustProxy: values.trustProxy,
cacheImages: values.cacheImages, cacheImages: values.cacheImages,
httpProxy: values.httpProxy,
}), }),
}); });
if (!res.ok) throw new Error(); if (!res.ok) throw new Error();
@@ -444,28 +437,6 @@ const SettingsMain = () => {
/> />
</div> </div>
</div> </div>
<div className="form-row">
<label htmlFor="httpProxy" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.httpProxy)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.httpProxyTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field id="httpProxy" name="httpProxy" type="text" />
</div>
{errors.httpProxy &&
touched.httpProxy &&
typeof errors.httpProxy === 'string' && (
<div className="error">{errors.httpProxy}</div>
)}
</div>
</div>
<div className="actions"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm"> <span className="ml-3 inline-flex rounded-md shadow-sm">

View File

@@ -346,7 +346,6 @@ const TitleCard = ({
> >
<div className="absolute inset-0 h-full w-full overflow-hidden"> <div className="absolute inset-0 h-full w-full overflow-hidden">
<CachedImage <CachedImage
type="tmdb"
className="absolute inset-0 h-full w-full" className="absolute inset-0 h-full w-full"
alt="" alt=""
src={ src={

View File

@@ -471,7 +471,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
{data.backdropPath && ( {data.backdropPath && (
<div className="media-page-bg-image"> <div className="media-page-bg-image">
<CachedImage <CachedImage
type="tmdb"
alt="" alt=""
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`} src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover' }}
@@ -528,7 +527,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
<div className="media-header"> <div className="media-header">
<div className="media-poster"> <div className="media-poster">
<CachedImage <CachedImage
type="tmdb"
src={ src={
data.posterPath data.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`

View File

@@ -250,7 +250,6 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
<td className="whitespace-nowrap px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6"> <td className="whitespace-nowrap px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6">
<div className="flex items-center"> <div className="flex items-center">
<CachedImage <CachedImage
type="avatar"
className="h-10 w-10 flex-shrink-0 rounded-full" className="h-10 w-10 flex-shrink-0 rounded-full"
src={user.thumb} src={user.thumb}
alt="" alt=""

View File

@@ -634,7 +634,6 @@ const UserList = () => {
className="h-10 w-10 flex-shrink-0" className="h-10 w-10 flex-shrink-0"
> >
<CachedImage <CachedImage
type="avatar"
className="h-10 w-10 rounded-full object-cover" className="h-10 w-10 rounded-full object-cover"
src={user.avatar} src={user.avatar}
alt="" alt=""

View File

@@ -43,7 +43,6 @@ const ProfileHeader = ({ user, isSettingsPage }: ProfileHeaderProps) => {
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<div className="relative"> <div className="relative">
<CachedImage <CachedImage
type="avatar"
className="h-24 w-24 rounded-full bg-gray-600 object-cover ring-1 ring-gray-700" className="h-24 w-24 rounded-full bg-gray-600 object-cover ring-1 ring-gray-700"
src={user.avatar} src={user.avatar}
alt="" alt=""

View File

@@ -298,7 +298,6 @@
"components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {play} other {plays}}", "components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {play} other {plays}}",
"components.ManageSlideOver.removearr": "Remove from {arr}", "components.ManageSlideOver.removearr": "Remove from {arr}",
"components.ManageSlideOver.removearr4k": "Remove from 4K {arr}", "components.ManageSlideOver.removearr4k": "Remove from 4K {arr}",
"components.RequestList.RequestItem.removearr": "Remove from {arr}",
"components.ManageSlideOver.tvshow": "series", "components.ManageSlideOver.tvshow": "series",
"components.MediaSlider.ShowMoreCard.seemore": "See More", "components.MediaSlider.ShowMoreCard.seemore": "See More",
"components.MovieDetails.MovieCast.fullcast": "Full Cast", "components.MovieDetails.MovieCast.fullcast": "Full Cast",
@@ -1031,7 +1030,6 @@
"components.Settings.save": "Save Changes", "components.Settings.save": "Save Changes",
"components.Settings.saving": "Saving…", "components.Settings.saving": "Saving…",
"components.Settings.scan": "Sync Libraries", "components.Settings.scan": "Sync Libraries",
"components.Settings.scanbackground": "Scanning will run in the background. You can continue the setup process in the meantime.",
"components.Settings.scanning": "Syncing…", "components.Settings.scanning": "Syncing…",
"components.Settings.serverLocal": "local", "components.Settings.serverLocal": "local",
"components.Settings.serverRemote": "remote", "components.Settings.serverRemote": "remote",
@@ -1052,7 +1050,6 @@
"components.Settings.tautulliSettings": "Tautulli Settings", "components.Settings.tautulliSettings": "Tautulli Settings",
"components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Jellyseerr fetches watch history data for your Plex media from Tautulli.", "components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Jellyseerr fetches watch history data for your Plex media from Tautulli.",
"components.Settings.timeout": "Timeout", "components.Settings.timeout": "Timeout",
"components.Settings.tip": "Tip",
"components.Settings.toastPlexConnecting": "Attempting to connect to Plex…", "components.Settings.toastPlexConnecting": "Attempting to connect to Plex…",
"components.Settings.toastPlexConnectingFailure": "Failed to connect to Plex.", "components.Settings.toastPlexConnectingFailure": "Failed to connect to Plex.",
"components.Settings.toastPlexConnectingSuccess": "Plex connection established successfully!", "components.Settings.toastPlexConnectingSuccess": "Plex connection established successfully!",
@@ -1082,14 +1079,16 @@
"components.Setup.continue": "Continue", "components.Setup.continue": "Continue",
"components.Setup.finish": "Finish Setup", "components.Setup.finish": "Finish Setup",
"components.Setup.finishing": "Finishing…", "components.Setup.finishing": "Finishing…",
"components.Setup.scanbackground": "Scanning will run in the background. You can continue the setup process in the meantime.",
"components.Setup.servertype": "Choose Server Type", "components.Setup.servertype": "Choose Server Type",
"components.Setup.setup": "Setup", "components.Setup.setup": "Setup",
"components.Setup.signin": "Sign in to your account", "components.Setup.signin": "Sign In",
"components.Setup.signinMessage": "Get started by signing in", "components.Setup.signinMessage": "Get started by signing in",
"components.Setup.signinWithEmby": "Enter your Emby details", "components.Setup.signinWithEmby": "Enter your Emby details",
"components.Setup.signinWithJellyfin": "Enter your Jellyfin details", "components.Setup.signinWithJellyfin": "Enter your Jellyfin details",
"components.Setup.signinWithPlex": "Enter your Plex details", "components.Setup.signinWithPlex": "Enter your Plex details",
"components.Setup.subtitle": "Get started by choosing your media server", "components.Setup.subtitle": "Get started by choosing your media server",
"components.Setup.tip": "Tip",
"components.Setup.welcome": "Welcome to Jellyseerr", "components.Setup.welcome": "Welcome to Jellyseerr",
"components.StatusBadge.managemedia": "Manage {mediaType}", "components.StatusBadge.managemedia": "Manage {mediaType}",
"components.StatusBadge.openinarr": "Open in {arr}", "components.StatusBadge.openinarr": "Open in {arr}",
@@ -1234,7 +1233,6 @@
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Saving…", "components.UserProfile.UserSettings.UserGeneralSettings.saving": "Saving…",
"components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Series Request Limit", "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Series Request Limit",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Something went wrong while saving settings.", "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Something went wrong while saving settings.",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "This email is already taken!",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Settings saved successfully!", "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Settings saved successfully!",
"components.UserProfile.UserSettings.UserGeneralSettings.user": "User", "components.UserProfile.UserSettings.UserGeneralSettings.user": "User",
"components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "You must provide a valid Discord user ID", "components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "You must provide a valid Discord user ID",
@@ -1314,7 +1312,6 @@
"components.UserProfile.seriesrequest": "Series Requests", "components.UserProfile.seriesrequest": "Series Requests",
"components.UserProfile.totalrequests": "Total Requests", "components.UserProfile.totalrequests": "Total Requests",
"components.UserProfile.unlimited": "Unlimited", "components.UserProfile.unlimited": "Unlimited",
"i18n.addToBlacklist": "Add to Blacklist",
"i18n.advanced": "Advanced", "i18n.advanced": "Advanced",
"i18n.all": "All", "i18n.all": "All",
"i18n.approve": "Approve", "i18n.approve": "Approve",

View File

@@ -12,7 +12,6 @@ import { SettingsProvider } from '@app/context/SettingsContext';
import { UserContext } from '@app/context/UserContext'; import { UserContext } from '@app/context/UserContext';
import type { User } from '@app/hooks/useUser'; import type { User } from '@app/hooks/useUser';
import '@app/styles/globals.css'; import '@app/styles/globals.css';
import '@app/utils/fetchOverride';
import { polyfillIntl } from '@app/utils/polyfillIntl'; import { polyfillIntl } from '@app/utils/polyfillIntl';
import { MediaServerType } from '@server/constants/server'; import { MediaServerType } from '@server/constants/server';
import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces'; import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces';

View File

@@ -1,46 +0,0 @@
const getCsrfToken = (): string | null => {
if (typeof window !== 'undefined') {
const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/);
return match ? decodeURIComponent(match[1]) : null;
}
return null;
};
const isSameOrigin = (url: RequestInfo | URL): boolean => {
const parsedUrl = new URL(
url instanceof Request ? url.url : url.toString(),
window.location.origin
);
return parsedUrl.origin === window.location.origin;
};
// We are using a custom fetch implementation to add the X-XSRF-TOKEN heade
// to all requests. This is required when CSRF protection is enabled.
if (typeof window !== 'undefined') {
const originalFetch: typeof fetch = window.fetch;
(window as typeof globalThis).fetch = async (
input: RequestInfo | URL,
init?: RequestInit
): Promise<Response> => {
if (!isSameOrigin(input)) {
return originalFetch(input, init);
}
const csrfToken = getCsrfToken();
const headers = {
...(init?.headers || {}),
...(csrfToken ? { 'XSRF-TOKEN': csrfToken } : {}),
};
const newInit: RequestInit = {
...init,
headers,
};
return originalFetch(input, newInit);
};
}
export {};