mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 18:59:28 -05:00
Compare commits
69 Commits
forgotPass
...
v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eceedbbaad | ||
|
|
29f06a965c | ||
|
|
9ec05d3ba4 | ||
|
|
ee14ff5a51 | ||
|
|
6b62d4b862 | ||
|
|
706fea0e97 | ||
|
|
80956d1a83 | ||
|
|
6d530d9028 | ||
|
|
f12237565f | ||
|
|
11f5594ed4 | ||
|
|
e4e58bee05 | ||
|
|
13ee3a836c | ||
|
|
3f16a353f5 | ||
|
|
9c43ba95e6 | ||
|
|
13fb6fd1a7 | ||
|
|
16e8e3a38e | ||
|
|
6fecdf094d | ||
|
|
69b271b018 | ||
|
|
d6ebd9a9b9 | ||
|
|
70dad332fc | ||
|
|
a65e430c60 | ||
|
|
18f4b67b72 | ||
|
|
506c31562a | ||
|
|
7a9d7a4834 | ||
|
|
902a033b8a | ||
|
|
00eb20aa5e | ||
|
|
a2c27cfa95 | ||
|
|
7122b4d08b | ||
|
|
b03b9b1dbb | ||
|
|
73672e29f8 | ||
|
|
cc5192209f | ||
|
|
278dcf4b44 | ||
|
|
36e092f225 | ||
|
|
46d5c737a2 | ||
|
|
cba4878db3 | ||
|
|
57cc48a699 | ||
|
|
84f488be06 | ||
|
|
f885f2a0f3 | ||
|
|
eef3e5ea4c | ||
|
|
8db821c1c1 | ||
|
|
a39b882f09 | ||
|
|
754dccc4bf | ||
|
|
f97ee11430 | ||
|
|
54868fd486 | ||
|
|
eea389879f | ||
|
|
5c917f95b4 | ||
|
|
dd4d42fd31 | ||
|
|
e5c6b9cd74 | ||
|
|
508fccae4e | ||
|
|
f77573c838 | ||
|
|
7dfe38001e | ||
|
|
48f55da43e | ||
|
|
1e97503802 | ||
|
|
42ff34bb3d | ||
|
|
107b766c44 | ||
|
|
fb51ce5570 | ||
|
|
3357343d98 | ||
|
|
9d61092f37 | ||
|
|
29274614c3 | ||
|
|
19b51592ea | ||
|
|
757c0fc29e | ||
|
|
3eb48abc14 | ||
|
|
01cd9d3872 | ||
|
|
9582196e1f | ||
|
|
3743edab8d | ||
|
|
d81e7cdbab | ||
|
|
6e1d7f7075 | ||
|
|
91cf2de33a | ||
|
|
a6ec2d5220 |
7
.github/CODEOWNERS
vendored
Normal file
7
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Global code ownership
|
||||
|
||||
- @Fallenbagel
|
||||
|
||||
# i18n locale files
|
||||
|
||||
src/i18n/locale/ @Fallenbagel
|
||||
1996
CHANGELOG.md
1996
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
||||
- Emby Support
|
||||
|
||||
Along with all the existing Overseerr features:
|
||||
|
||||
- Full Plex integration. Authenticate and manage user access with Plex!
|
||||
- Easy integration with your existing services. Currently, Jellyseerr supports Sonarr and Radarr. More to come!
|
||||
- Plex library scan, to keep track of the titles which are already available.
|
||||
@@ -55,4 +56,4 @@ Our [Code of Conduct](https://github.com/fallenbagel/jellyseerr/blob/develop/COD
|
||||
|
||||
## Contributing
|
||||
|
||||
You can help improve Jellyseerr too! Check out our [Contribution Guide](https://github.com/fallenbagel/jellyseerr/blob/develop/CONTRIBUTING.md) to get started.
|
||||
You can help improve Jellyseerr too! Check out our [Contribution Guide](https://github.com/fallenbagel/jellyseerr/blob/develop/CONTRIBUTING.md) to get started.
|
||||
|
||||
@@ -8,7 +8,7 @@ To use Fail2ban with Overseerr, create a new file named `overseerr.local` in you
|
||||
|
||||
```
|
||||
[Definition]
|
||||
failregex = .*\[warn\]\[API\]\: Failed sign-in attempt.*"ip":"<HOST>"
|
||||
failregex = .*\[info\]\[Auth\]\: Failed sign-in attempt.*"ip":"<HOST>"
|
||||
```
|
||||
|
||||
You can then add a jail using this filter in `jail.local`. Please see the [Fail2ban documetation](https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jails) for details on how to configure the jail.
|
||||
|
||||
@@ -5815,36 +5815,6 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Issue'
|
||||
|
||||
/issue/count:
|
||||
get:
|
||||
summary: Gets issue counts
|
||||
description: |
|
||||
Returns the number of open and closed issues, as well as the number of issues of each type.
|
||||
tags:
|
||||
- issue
|
||||
responses:
|
||||
'200':
|
||||
description: Issue counts returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
type: number
|
||||
video:
|
||||
type: number
|
||||
audio:
|
||||
type: number
|
||||
subtitles:
|
||||
type: number
|
||||
others:
|
||||
type: number
|
||||
open:
|
||||
type: number
|
||||
closed:
|
||||
type: number
|
||||
/issue/{issueId}:
|
||||
get:
|
||||
summary: Get issue
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jellyseerr",
|
||||
"version": "0.1.0",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts",
|
||||
|
||||
3
public/preview.jpg:Zone.Identifier
Normal file
3
public/preview.jpg:Zone.Identifier
Normal file
@@ -0,0 +1,3 @@
|
||||
[ZoneTransfer]
|
||||
LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
|
||||
ZoneId=3
|
||||
@@ -129,13 +129,7 @@ class TheMovieDb extends ExternalAPI {
|
||||
}: SingleSearchOptions): Promise<TmdbSearchMovieResponse> => {
|
||||
try {
|
||||
const data = await this.get<TmdbSearchMovieResponse>('/search/movie', {
|
||||
params: {
|
||||
query,
|
||||
page,
|
||||
include_adult: includeAdult,
|
||||
language,
|
||||
primary_release_year: year,
|
||||
},
|
||||
params: { query, page, include_adult: includeAdult, language, year },
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { IssueStatus, IssueType } from '../constants/issue';
|
||||
import { IssueStatus } from '../constants/issue';
|
||||
import Issue from '../entity/Issue';
|
||||
import IssueComment from '../entity/IssueComment';
|
||||
import Media from '../entity/Media';
|
||||
@@ -146,68 +146,6 @@ issueRoutes.post<
|
||||
}
|
||||
);
|
||||
|
||||
issueRoutes.get('/count', async (req, res, next) => {
|
||||
const issueRepository = getRepository(Issue);
|
||||
|
||||
try {
|
||||
const query = issueRepository.createQueryBuilder('issue');
|
||||
|
||||
const totalCount = await query.getCount();
|
||||
|
||||
const videoCount = await query
|
||||
.where('issue.issueType = :issueType', {
|
||||
issueType: IssueType.VIDEO,
|
||||
})
|
||||
.getCount();
|
||||
|
||||
const audioCount = await query
|
||||
.where('issue.issueType = :issueType', {
|
||||
issueType: IssueType.AUDIO,
|
||||
})
|
||||
.getCount();
|
||||
|
||||
const subtitlesCount = await query
|
||||
.where('issue.issueType = :issueType', {
|
||||
issueType: IssueType.SUBTITLES,
|
||||
})
|
||||
.getCount();
|
||||
|
||||
const othersCount = await query
|
||||
.where('issue.issueType = :issueType', {
|
||||
issueType: IssueType.OTHER,
|
||||
})
|
||||
.getCount();
|
||||
|
||||
const openCount = await query
|
||||
.where('issue.status = :issueStatus', {
|
||||
issueStatus: IssueStatus.OPEN,
|
||||
})
|
||||
.getCount();
|
||||
|
||||
const closedCount = await query
|
||||
.where('issue.status = :issueStatus', {
|
||||
issueStatus: IssueStatus.RESOLVED,
|
||||
})
|
||||
.getCount();
|
||||
|
||||
return res.status(200).json({
|
||||
total: totalCount,
|
||||
video: videoCount,
|
||||
audio: audioCount,
|
||||
subtitles: subtitlesCount,
|
||||
others: othersCount,
|
||||
open: openCount,
|
||||
closed: closedCount,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.debug('Something went wrong retrieving issue counts.', {
|
||||
label: 'API',
|
||||
errorMessage: e.message,
|
||||
});
|
||||
next({ status: 500, message: 'Unable to retrieve issue counts.' });
|
||||
}
|
||||
});
|
||||
|
||||
issueRoutes.get<{ issueId: string }>(
|
||||
'/:issueId',
|
||||
isAuthenticated(
|
||||
|
||||
@@ -110,12 +110,6 @@ const networks: Network[] = [
|
||||
'https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)/nm8d7P7MJNiBLdgIzUK0gkuEA4r.png',
|
||||
url: '/discover/tv/network/16',
|
||||
},
|
||||
{
|
||||
name: 'Paramount+',
|
||||
image:
|
||||
'https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)/fi83B1oztoS47xxcemFdPMhIzK.png',
|
||||
url: '/discover/tv/network/4330',
|
||||
},
|
||||
{
|
||||
name: 'BBC One',
|
||||
image:
|
||||
|
||||
@@ -266,11 +266,8 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
as="a"
|
||||
buttonType="ghost"
|
||||
href={
|
||||
process.env.JELLYFIN_TYPE == 'emby'
|
||||
? settings.currentSettings.jellyfinHost +
|
||||
'/web/index.html#!/startup/forgotpassword.html'
|
||||
: settings.currentSettings.jellyfinHost +
|
||||
'/web/index.html#!/forgotpassword.html'
|
||||
settings.currentSettings.jellyfinHost +
|
||||
'/web/#!/forgotpassword.html'
|
||||
}
|
||||
>
|
||||
{intl.formatMessage(messages.forgotpassword)}
|
||||
|
||||
@@ -210,7 +210,7 @@ const ManageSlideOver: React.FC<
|
||||
{hasPermission(Permission.ADMIN) &&
|
||||
(data.mediaInfo?.serviceUrl ||
|
||||
data.mediaInfo?.tautulliUrl ||
|
||||
!!watchData?.data?.playCount) && (
|
||||
watchData?.data?.playCount) && (
|
||||
<div>
|
||||
<h3 className="mb-2 text-xl font-bold">
|
||||
{intl.formatMessage(messages.manageModalMedia)}
|
||||
@@ -325,7 +325,7 @@ const ManageSlideOver: React.FC<
|
||||
{hasPermission(Permission.ADMIN) &&
|
||||
(data.mediaInfo?.serviceUrl4k ||
|
||||
data.mediaInfo?.tautulliUrl4k ||
|
||||
!!watchData?.data4k?.playCount) && (
|
||||
watchData?.data4k?.playCount) && (
|
||||
<div>
|
||||
<h3 className="mb-2 text-xl font-bold">
|
||||
{intl.formatMessage(messages.manageModalMedia4k)}
|
||||
|
||||
@@ -26,7 +26,6 @@ const messages = defineMessages({
|
||||
server: 'Destination Server',
|
||||
profilechanged: 'Quality Profile',
|
||||
rootfolder: 'Root Folder',
|
||||
languageprofile: 'Language Profile',
|
||||
});
|
||||
|
||||
interface RequestBlockProps {
|
||||
@@ -39,8 +38,7 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
|
||||
const intl = useIntl();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const { profile, rootFolder, server, languageProfile } =
|
||||
useRequestOverride(request);
|
||||
const { profile, rootFolder, server } = useRequestOverride(request);
|
||||
|
||||
const updateRequest = async (type: 'approve' | 'decline'): Promise<void> => {
|
||||
setIsUpdating(true);
|
||||
@@ -211,7 +209,7 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(server || profile || rootFolder || languageProfile) && (
|
||||
{(server || profile !== null || rootFolder) && (
|
||||
<>
|
||||
<div className="mt-4 mb-1 text-sm">
|
||||
{intl.formatMessage(messages.requestoverrides)}
|
||||
@@ -225,12 +223,12 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
|
||||
<span>{server}</span>
|
||||
</li>
|
||||
)}
|
||||
{profile && (
|
||||
{profile !== null && (
|
||||
<li className="flex justify-between px-1 py-2">
|
||||
<span className="font-bold">
|
||||
{intl.formatMessage(messages.profilechanged)}
|
||||
</span>
|
||||
<span>{profile}</span>
|
||||
<span>ID {profile}</span>
|
||||
</li>
|
||||
)}
|
||||
{rootFolder && (
|
||||
@@ -241,14 +239,6 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
|
||||
<span>{rootFolder}</span>
|
||||
</li>
|
||||
)}
|
||||
{languageProfile && (
|
||||
<li className="flex justify-between px-1 py-2">
|
||||
<span className="mr-2 font-bold">
|
||||
{intl.formatMessage(messages.languageprofile)}
|
||||
</span>
|
||||
<span>{languageProfile}</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -82,9 +82,7 @@ const useDiscover = <T extends BaseMedia, S = Record<string, never>>(
|
||||
|
||||
const isEmpty = !isLoadingInitialData && titles?.length === 0;
|
||||
const isReachingEnd =
|
||||
isEmpty ||
|
||||
(!!data && (data[data?.length - 1]?.results.length ?? 0) < 20) ||
|
||||
(!!data && (data[data?.length - 1]?.totalResults ?? 0) < 41);
|
||||
isEmpty || (!!data && (data[data?.length - 1]?.results.length ?? 0) < 20);
|
||||
|
||||
return {
|
||||
isLoadingInitialData,
|
||||
|
||||
@@ -1,61 +1,45 @@
|
||||
import useSWR from 'swr';
|
||||
import { MediaRequest } from '../../server/entity/MediaRequest';
|
||||
import {
|
||||
ServiceCommonServer,
|
||||
ServiceCommonServerWithDetails,
|
||||
} from '../../server/interfaces/api/serviceInterfaces';
|
||||
import { ServiceCommonServer } from '../../server/interfaces/api/serviceInterfaces';
|
||||
|
||||
interface OverrideStatus {
|
||||
server?: string;
|
||||
profile?: string;
|
||||
rootFolder?: string;
|
||||
languageProfile?: string;
|
||||
server: string | null;
|
||||
profile: number | null;
|
||||
rootFolder: string | null;
|
||||
}
|
||||
|
||||
const useRequestOverride = (request: MediaRequest): OverrideStatus => {
|
||||
const { data: allServers } = useSWR<ServiceCommonServer[]>(
|
||||
const { data } = useSWR<ServiceCommonServer[]>(
|
||||
`/api/v1/service/${request.type === 'movie' ? 'radarr' : 'sonarr'}`
|
||||
);
|
||||
|
||||
const { data } = useSWR<ServiceCommonServerWithDetails>(
|
||||
`/api/v1/service/${request.type === 'movie' ? 'radarr' : 'sonarr'}/${
|
||||
request.serverId
|
||||
}`
|
||||
);
|
||||
|
||||
if (!data || !allServers) {
|
||||
return {};
|
||||
if (!data) {
|
||||
return {
|
||||
server: null,
|
||||
profile: null,
|
||||
rootFolder: null,
|
||||
};
|
||||
}
|
||||
|
||||
const defaultServer = allServers.find(
|
||||
const defaultServer = data.find(
|
||||
(server) => server.is4k === request.is4k && server.isDefault
|
||||
);
|
||||
|
||||
const activeServer = allServers.find(
|
||||
(server) => server.id === request.serverId
|
||||
);
|
||||
const activeServer = data.find((server) => server.id === request.serverId);
|
||||
|
||||
return {
|
||||
server:
|
||||
activeServer && request.serverId !== defaultServer?.id
|
||||
? activeServer.name
|
||||
: undefined,
|
||||
: null,
|
||||
profile:
|
||||
defaultServer?.activeProfileId !== request.profileId
|
||||
? data.profiles.find((profile) => profile.id === request.profileId)
|
||||
?.name
|
||||
: undefined,
|
||||
? request.profileId
|
||||
: null,
|
||||
rootFolder:
|
||||
defaultServer?.activeDirectory !== request.rootFolder
|
||||
? request.rootFolder
|
||||
: undefined,
|
||||
languageProfile:
|
||||
request.type === 'tv' &&
|
||||
defaultServer?.activeLanguageProfileId !== request.languageProfileId
|
||||
? data.languageProfiles?.find(
|
||||
(profile) => profile.id === request.languageProfileId
|
||||
)?.name
|
||||
: undefined,
|
||||
: null,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useRouter } from 'next/router';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import type { UrlObject } from 'url';
|
||||
import type { Nullable } from '../utils/typeHelpers';
|
||||
import { useEffect, useState, Dispatch, SetStateAction } from 'react';
|
||||
import useDebouncedState from './useDebouncedState';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { Nullable } from '../utils/typeHelpers';
|
||||
|
||||
type Url = string | UrlObject;
|
||||
|
||||
@@ -48,7 +48,7 @@ const useSearchInput = (): SearchObject => {
|
||||
* in a new route. If we are, then we only replace the history.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (debouncedValue !== '' && searchOpen) {
|
||||
if (debouncedValue !== '') {
|
||||
if (router.pathname.startsWith('/search')) {
|
||||
router.replace({
|
||||
pathname: router.pathname,
|
||||
|
||||
@@ -540,4 +540,4 @@
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "ID chatu",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Potvrďte heslo",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Aktuální heslo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,6 @@
|
||||
"components.QuotaSelector.unlimited": "Unlimited",
|
||||
"components.RegionSelector.regionDefault": "All Regions",
|
||||
"components.RegionSelector.regionServerDefault": "Default ({region})",
|
||||
"components.RequestBlock.languageprofile": "Language Profile",
|
||||
"components.RequestBlock.profilechanged": "Quality Profile",
|
||||
"components.RequestBlock.requestoverrides": "Request Overrides",
|
||||
"components.RequestBlock.rootfolder": "Root Folder",
|
||||
|
||||
Reference in New Issue
Block a user