Compare commits
7 Commits
feat-dns-c
...
test-fix-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
166796804e | ||
|
|
9aeb3604e6 | ||
|
|
6eb88f8674 | ||
|
|
46ee8a4ca1 | ||
|
|
f52939e4cd | ||
|
|
d31a2c37e6 | ||
|
|
20863d4a8d |
108
.github/workflows/release.yml
vendored
108
.github/workflows/release.yml
vendored
@@ -35,60 +35,60 @@ jobs:
|
|||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
run: npx semantic-release
|
run: npx semantic-release
|
||||||
|
|
||||||
build-snap:
|
# build-snap:
|
||||||
name: Build Snap Package (${{ matrix.architecture }})
|
# name: Build Snap Package (${{ matrix.architecture }})
|
||||||
needs: semantic-release
|
# needs: semantic-release
|
||||||
runs-on: ubuntu-22.04
|
# runs-on: ubuntu-22.04
|
||||||
strategy:
|
# strategy:
|
||||||
fail-fast: false
|
# fail-fast: false
|
||||||
matrix:
|
# matrix:
|
||||||
architecture:
|
# architecture:
|
||||||
- amd64
|
# - amd64
|
||||||
- arm64
|
# - arm64
|
||||||
- armhf
|
# - armhf
|
||||||
steps:
|
# steps:
|
||||||
- name: Checkout Code
|
# - name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
# uses: actions/checkout@v4
|
||||||
with:
|
# with:
|
||||||
fetch-depth: 0
|
# fetch-depth: 0
|
||||||
- name: Switch to main branch
|
# - name: Switch to main branch
|
||||||
run: git checkout main
|
# run: git checkout main
|
||||||
- name: Pull latest changes
|
# - name: Pull latest changes
|
||||||
run: git pull
|
# run: git pull
|
||||||
- name: Prepare
|
# - name: Prepare
|
||||||
id: prepare
|
# id: prepare
|
||||||
run: |
|
# run: |
|
||||||
git fetch --prune --tags
|
# git fetch --prune --tags
|
||||||
if [[ $GITHUB_REF == refs/tags/* || $GITHUB_REF == refs/heads/master ]]; then
|
# if [[ $GITHUB_REF == refs/tags/* || $GITHUB_REF == refs/heads/master ]]; then
|
||||||
echo "RELEASE=stable" >> $GITHUB_OUTPUT
|
# echo "RELEASE=stable" >> $GITHUB_OUTPUT
|
||||||
else
|
# else
|
||||||
echo "RELEASE=edge" >> $GITHUB_OUTPUT
|
# echo "RELEASE=edge" >> $GITHUB_OUTPUT
|
||||||
fi
|
# fi
|
||||||
- name: Set Up QEMU
|
# - name: Set Up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
# uses: docker/setup-qemu-action@v3
|
||||||
with:
|
# with:
|
||||||
image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde
|
# image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde
|
||||||
- name: Build Snap Package
|
# - name: Build Snap Package
|
||||||
uses: diddlesnaps/snapcraft-multiarch-action@v1
|
# uses: diddlesnaps/snapcraft-multiarch-action@v1
|
||||||
id: build
|
# id: build
|
||||||
with:
|
# with:
|
||||||
architecture: ${{ matrix.architecture }}
|
# architecture: ${{ matrix.architecture }}
|
||||||
- name: Upload Snap Package
|
# - name: Upload Snap Package
|
||||||
uses: actions/upload-artifact@v4
|
# uses: actions/upload-artifact@v4
|
||||||
with:
|
# with:
|
||||||
name: jellyseerr-snap-package-${{ matrix.architecture }}
|
# name: jellyseerr-snap-package-${{ matrix.architecture }}
|
||||||
path: ${{ steps.build.outputs.snap }}
|
# path: ${{ steps.build.outputs.snap }}
|
||||||
- name: Review Snap Package
|
# - name: Review Snap Package
|
||||||
uses: diddlesnaps/snapcraft-review-tools-action@v1
|
# uses: diddlesnaps/snapcraft-review-tools-action@v1
|
||||||
with:
|
# with:
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
# snap: ${{ steps.build.outputs.snap }}
|
||||||
- name: Publish Snap Package
|
# - name: Publish Snap Package
|
||||||
uses: snapcore/action-publish@v1
|
# uses: snapcore/action-publish@v1
|
||||||
env:
|
# env:
|
||||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_LOGIN }}
|
# SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_LOGIN }}
|
||||||
with:
|
# with:
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
# snap: ${{ steps.build.outputs.snap }}
|
||||||
release: ${{ steps.prepare.outputs.RELEASE }}
|
# release: ${{ steps.prepare.outputs.RELEASE }}
|
||||||
|
|
||||||
discord:
|
discord:
|
||||||
name: Send Discord Notification
|
name: Send Discord Notification
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"axios-rate-limit": "1.3.0",
|
"axios-rate-limit": "1.3.0",
|
||||||
"bcrypt": "5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"bowser": "2.11.0",
|
"bowser": "2.11.0",
|
||||||
|
"cacheable-lookup": "^7.0.0",
|
||||||
"connect-typeorm": "1.1.4",
|
"connect-typeorm": "1.1.4",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.6",
|
||||||
"copy-to-clipboard": "3.3.3",
|
"copy-to-clipboard": "3.3.3",
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ class JellyfinAPI extends ExternalAPI {
|
|||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
logger.debug(`Logging in to Jellyfin server: ${this.jellyfinHost}`, {
|
||||||
|
label: 'Jellyfin API',
|
||||||
|
clientIp: ClientIP,
|
||||||
|
});
|
||||||
|
|
||||||
const authResponse = await this.post<JellyfinLoginResponse>(
|
const authResponse = await this.post<JellyfinLoginResponse>(
|
||||||
'/Users/AuthenticateByName',
|
'/Users/AuthenticateByName',
|
||||||
{
|
{
|
||||||
@@ -146,6 +151,12 @@ class JellyfinAPI extends ExternalAPI {
|
|||||||
|
|
||||||
return authResponse;
|
return authResponse;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error('Failed to login to Jellyfin server', {
|
||||||
|
label: 'Jellyfin API',
|
||||||
|
clientIp: ClientIP,
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
|
||||||
const status = e.response?.status;
|
const status = e.response?.status;
|
||||||
|
|
||||||
const networkErrorCodes = new Set([
|
const networkErrorCodes = new Set([
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import imageproxy from '@server/routes/imageproxy';
|
|||||||
import { getAppVersion } from '@server/utils/appVersion';
|
import { getAppVersion } from '@server/utils/appVersion';
|
||||||
import restartFlag from '@server/utils/restartFlag';
|
import restartFlag from '@server/utils/restartFlag';
|
||||||
import { getClientIp } from '@supercharge/request-ip';
|
import { getClientIp } from '@supercharge/request-ip';
|
||||||
|
import type CacheableLookupType from 'cacheable-lookup';
|
||||||
import { TypeormStore } from 'connect-typeorm/out';
|
import { TypeormStore } from 'connect-typeorm/out';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import csurf from 'csurf';
|
import csurf from 'csurf';
|
||||||
@@ -32,10 +33,14 @@ import * as OpenApiValidator from 'express-openapi-validator';
|
|||||||
import type { Store } from 'express-session';
|
import type { Store } from 'express-session';
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import next from 'next';
|
import next from 'next';
|
||||||
|
import http from 'node:http';
|
||||||
|
import https from 'node:https';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import swaggerUi from 'swagger-ui-express';
|
import swaggerUi from 'swagger-ui-express';
|
||||||
import YAML from 'yamljs';
|
import YAML from 'yamljs';
|
||||||
|
|
||||||
|
const _importDynamic = new Function('modulePath', 'return import(modulePath)');
|
||||||
|
|
||||||
const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml');
|
const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml');
|
||||||
|
|
||||||
logger.info(`Starting Overseerr version ${getAppVersion()}`);
|
logger.info(`Starting Overseerr version ${getAppVersion()}`);
|
||||||
@@ -46,6 +51,12 @@ const handle = app.getRequestHandler();
|
|||||||
app
|
app
|
||||||
.prepare()
|
.prepare()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
const CacheableLookup = (await _importDynamic('cacheable-lookup'))
|
||||||
|
.default as typeof CacheableLookupType;
|
||||||
|
const cacheable = new CacheableLookup();
|
||||||
|
cacheable.install(http.globalAgent);
|
||||||
|
cacheable.install(https.globalAgent);
|
||||||
|
|
||||||
const dbConnection = await dataSource.initialize();
|
const dbConnection = await dataSource.initialize();
|
||||||
|
|
||||||
// Run migrations in production
|
// Run migrations in production
|
||||||
|
|||||||
@@ -83,13 +83,17 @@ class JellyfinScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const has4k = metadata.MediaSources?.some((MediaSource) => {
|
const has4k = metadata.MediaSources?.some((MediaSource) => {
|
||||||
return MediaSource.MediaStreams.some((MediaStream) => {
|
return MediaSource.MediaStreams.filter(
|
||||||
|
(MediaStream) => MediaStream.Type === 'Video'
|
||||||
|
).some((MediaStream) => {
|
||||||
return (MediaStream.Width ?? 0) > 2000;
|
return (MediaStream.Width ?? 0) > 2000;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasOtherResolution = metadata.MediaSources?.some((MediaSource) => {
|
const hasOtherResolution = metadata.MediaSources?.some((MediaSource) => {
|
||||||
return MediaSource.MediaStreams.some((MediaStream) => {
|
return MediaSource.MediaStreams.filter(
|
||||||
|
(MediaStream) => MediaStream.Type === 'Video'
|
||||||
|
).some((MediaStream) => {
|
||||||
return (MediaStream.Width ?? 0) <= 2000;
|
return (MediaStream.Width ?? 0) <= 2000;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ApiError } from '@server/types/error';
|
|||||||
import * as EmailValidator from 'email-validator';
|
import * as EmailValidator from 'email-validator';
|
||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import gravatarUrl from 'gravatar-url';
|
import gravatarUrl from 'gravatar-url';
|
||||||
|
import net from 'net';
|
||||||
|
|
||||||
const authRoutes = Router();
|
const authRoutes = Router();
|
||||||
|
|
||||||
@@ -271,11 +272,21 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
|||||||
? jellyfinHost.slice(0, -1)
|
? jellyfinHost.slice(0, -1)
|
||||||
: jellyfinHost;
|
: jellyfinHost;
|
||||||
|
|
||||||
const ip = req.ip ? req.ip.split(':').reverse()[0] : undefined;
|
const ip = req.ip;
|
||||||
|
let clientIp;
|
||||||
|
|
||||||
|
if (ip) {
|
||||||
|
if (net.isIPv4(ip)) {
|
||||||
|
clientIp = ip;
|
||||||
|
} else if (net.isIPv6(ip)) {
|
||||||
|
clientIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const account = await jellyfinserver.login(
|
const account = await jellyfinserver.login(
|
||||||
body.username,
|
body.username,
|
||||||
body.password,
|
body.password,
|
||||||
ip
|
clientIp
|
||||||
);
|
);
|
||||||
|
|
||||||
// Next let's see if the user already exists
|
// Next let's see if the user already exists
|
||||||
|
|||||||
@@ -434,33 +434,38 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && (
|
{hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||||
<Tooltip content={intl.formatMessage(messages.managemovie)}>
|
data.mediaInfo &&
|
||||||
<Button
|
(data.mediaInfo.jellyfinMediaId ||
|
||||||
buttonType="ghost"
|
data.mediaInfo.jellyfinMediaId4k ||
|
||||||
onClick={() => setShowManager(true)}
|
data.mediaInfo.status !== MediaStatus.UNKNOWN ||
|
||||||
className="relative ml-2 first:ml-0"
|
data.mediaInfo.status4k !== MediaStatus.UNKNOWN) && (
|
||||||
>
|
<Tooltip content={intl.formatMessage(messages.managemovie)}>
|
||||||
<CogIcon className="!mr-0" />
|
<Button
|
||||||
{hasPermission(
|
buttonType="ghost"
|
||||||
[Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
|
onClick={() => setShowManager(true)}
|
||||||
{
|
className="relative ml-2 first:ml-0"
|
||||||
type: 'or',
|
>
|
||||||
}
|
<CogIcon className="!mr-0" />
|
||||||
) &&
|
{hasPermission(
|
||||||
(
|
[Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
|
||||||
data.mediaInfo?.issues.filter(
|
{
|
||||||
(issue) => issue.status === IssueStatus.OPEN
|
type: 'or',
|
||||||
) ?? []
|
}
|
||||||
).length > 0 && (
|
) &&
|
||||||
<>
|
(
|
||||||
<div className="absolute -right-1 -top-1 h-3 w-3 rounded-full bg-red-600" />
|
data.mediaInfo?.issues.filter(
|
||||||
<div className="absolute -right-1 -top-1 h-3 w-3 animate-ping rounded-full bg-red-600" />
|
(issue) => issue.status === IssueStatus.OPEN
|
||||||
</>
|
) ?? []
|
||||||
)}
|
).length > 0 && (
|
||||||
</Button>
|
<>
|
||||||
</Tooltip>
|
<div className="absolute -right-1 -top-1 h-3 w-3 rounded-full bg-red-600" />
|
||||||
)}
|
<div className="absolute -right-1 -top-1 h-3 w-3 animate-ping rounded-full bg-red-600" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="media-overview">
|
<div className="media-overview">
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ const messages = defineMessages({
|
|||||||
discordId: 'Discord User ID',
|
discordId: 'Discord User ID',
|
||||||
discordIdTip:
|
discordIdTip:
|
||||||
'The <FindDiscordIdLink>multi-digit ID number</FindDiscordIdLink> associated with your Discord user account',
|
'The <FindDiscordIdLink>multi-digit ID number</FindDiscordIdLink> associated with your Discord user account',
|
||||||
|
validationemailrequired: 'Email required',
|
||||||
|
validationemailformat: 'Valid email required',
|
||||||
validationDiscordId: 'You must provide a valid Discord user ID',
|
validationDiscordId: 'You must provide a valid Discord user ID',
|
||||||
plexwatchlistsyncmovies: 'Auto-Request Movies',
|
plexwatchlistsyncmovies: 'Auto-Request Movies',
|
||||||
plexwatchlistsyncmoviestip:
|
plexwatchlistsyncmoviestip:
|
||||||
@@ -88,6 +90,9 @@ const UserGeneralSettings = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const UserGeneralSettingsSchema = Yup.object().shape({
|
const UserGeneralSettingsSchema = Yup.object().shape({
|
||||||
|
email: Yup.string()
|
||||||
|
.email(intl.formatMessage(messages.validationemailformat))
|
||||||
|
.required(intl.formatMessage(messages.validationemailrequired)),
|
||||||
discordId: Yup.string()
|
discordId: Yup.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.matches(/^\d{17,19}$/, intl.formatMessage(messages.validationDiscordId)),
|
.matches(/^\d{17,19}$/, intl.formatMessage(messages.validationDiscordId)),
|
||||||
|
|||||||
@@ -1177,6 +1177,8 @@
|
|||||||
"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",
|
||||||
|
"components.UserProfile.UserSettings.UserGeneralSettings.validationemailformat": "Valid email required",
|
||||||
|
"components.UserProfile.UserSettings.UserGeneralSettings.validationemailrequired": "Email required",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Device Default",
|
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Device Default",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.discordId": "User ID",
|
"components.UserProfile.UserSettings.UserNotificationSettings.discordId": "User ID",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "The <FindDiscordIdLink>multi-digit ID number</FindDiscordIdLink> associated with your user account",
|
"components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "The <FindDiscordIdLink>multi-digit ID number</FindDiscordIdLink> associated with your user account",
|
||||||
|
|||||||
@@ -5033,6 +5033,11 @@ cacache@^16.0.0, cacache@^16.1.0, cacache@^16.1.3:
|
|||||||
tar "^6.1.11"
|
tar "^6.1.11"
|
||||||
unique-filename "^2.0.0"
|
unique-filename "^2.0.0"
|
||||||
|
|
||||||
|
cacheable-lookup@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27"
|
||||||
|
integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==
|
||||||
|
|
||||||
cachedir@2.3.0, cachedir@^2.3.0:
|
cachedir@2.3.0, cachedir@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
|
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
|
||||||
|
|||||||
Reference in New Issue
Block a user