Compare commits
16 Commits
preview-pr
...
preview-ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c195a6f720 | ||
|
|
f7a4efdde1 | ||
|
|
6a475b5e41 | ||
|
|
ae5542b6d3 | ||
|
|
cbf3bbb17d | ||
|
|
14fdd4e293 | ||
|
|
d2f651081a | ||
|
|
822ca690da | ||
|
|
b2291f5125 | ||
|
|
84f7a9795e | ||
|
|
9c5f4fe2f0 | ||
|
|
3af39dd5f0 | ||
|
|
7ad44830d1 | ||
|
|
bd8deedfbe | ||
|
|
548aaede26 | ||
|
|
8c7a08b4e9 |
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
65
pnpm-lock.yaml
generated
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class RottenTomatoes extends ExternalAPI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tvshow || !tvshow.rottenTomatoes) {
|
if (!tvshow) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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) => ({
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
|||||||
@@ -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} />;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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' }}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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' }}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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=""
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
@@ -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' }}
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
|||||||
@@ -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) && (
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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={
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
|||||||
@@ -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=""
|
||||||
|
|||||||
@@ -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=""
|
||||||
|
|||||||
@@ -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=""
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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 {};
|
|
||||||
Reference in New Issue
Block a user