mirror of
https://github.com/sct/overseerr.git
synced 2025-12-30 17:49:17 +01:00
feat(rebase): rebase
This commit is contained in:
@@ -71,19 +71,18 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem {
|
||||
|
||||
class JellyfinAPI {
|
||||
private authToken?: string;
|
||||
private userId?: string;
|
||||
private jellyfinHost: string;
|
||||
private axios: AxiosInstance;
|
||||
|
||||
constructor(jellyfinHost: string, authToken?: string) {
|
||||
constructor(jellyfinHost: string, authToken?: string, userId?: string) {
|
||||
this.jellyfinHost = jellyfinHost;
|
||||
this.authToken = authToken;
|
||||
this.userId = userId;
|
||||
|
||||
let authHeaderVal = '';
|
||||
if (this.authToken) {
|
||||
authHeaderVal =
|
||||
'MediaBrowser Client="Overseerr", Device="Axios", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0", Token="' +
|
||||
authToken +
|
||||
'"';
|
||||
authHeaderVal = `MediaBrowser Client="Overseerr", Device="Axios", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0", Token="${authToken}"`;
|
||||
} else {
|
||||
authHeaderVal =
|
||||
'MediaBrowser Client="Overseerr", Device="Axios", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0"';
|
||||
@@ -117,9 +116,26 @@ class JellyfinAPI {
|
||||
}
|
||||
}
|
||||
|
||||
public async getServerName(): Promise<string> {
|
||||
try {
|
||||
const account = await this.axios.get<JellyfinUserResponse>(
|
||||
`/System/Info/Public'}`
|
||||
);
|
||||
return account.data.ServerName;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Something went wrong while getting the server name from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API' }
|
||||
);
|
||||
throw new Error('girl idk');
|
||||
}
|
||||
}
|
||||
|
||||
public async getUser(): Promise<JellyfinUserResponse> {
|
||||
try {
|
||||
const account = await this.axios.get<JellyfinUserResponse>('/Users/Me');
|
||||
const account = await this.axios.get<JellyfinUserResponse>(
|
||||
`/Users/${this.userId ?? 'Me'}`
|
||||
);
|
||||
return account.data;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum MediaServerType {
|
||||
PLEX = 1,
|
||||
JELLYFIN, //also works for emby (identical APIs, etc)
|
||||
JELLYFIN,
|
||||
EMBY,
|
||||
NOT_CONFIGURED,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface SettingsAboutResponse {
|
||||
|
||||
export interface PublicSettingsResponse {
|
||||
jellyfinHost?: string;
|
||||
jellyfinServerName?: string;
|
||||
initialized: boolean;
|
||||
applicationTitle: string;
|
||||
hideAvailable: boolean;
|
||||
|
||||
@@ -552,7 +552,7 @@ class JobJellyfinSync {
|
||||
this.running = true;
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOne({
|
||||
select: ['id', 'jellyfinAuthToken'],
|
||||
select: ['id', 'jellyfinAuthToken', 'jellyfinId'],
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
|
||||
@@ -562,7 +562,8 @@ class JobJellyfinSync {
|
||||
|
||||
this.jfClient = new JellyfinAPI(
|
||||
settings.jellyfin.hostname ?? '',
|
||||
admin.jellyfinAuthToken ?? ''
|
||||
admin.jellyfinAuthToken ?? '',
|
||||
admin.jellyfinId ?? ''
|
||||
);
|
||||
|
||||
this.libraries = settings.jellyfin.libraries.filter(
|
||||
|
||||
@@ -99,6 +99,7 @@ interface FullPublicSettings extends PublicSettings {
|
||||
originalLanguage: string;
|
||||
mediaServerType: number;
|
||||
jellyfinHost?: string;
|
||||
jellyfinServerName?: string;
|
||||
}
|
||||
|
||||
export interface NotificationAgentConfig {
|
||||
@@ -368,17 +369,10 @@ class Settings {
|
||||
series4kEnabled: this.data.sonarr.some(
|
||||
(sonarr) => sonarr.is4k && sonarr.isDefault
|
||||
),
|
||||
<<<<<<< HEAD
|
||||
region: this.data.main.region,
|
||||
originalLanguage: this.data.main.originalLanguage,
|
||||
=======
|
||||
mediaServerType: this.main.mediaServerType,
|
||||
<<<<<<< HEAD
|
||||
jfHost: this.jellyfin.hostname ?? '',
|
||||
>>>>>>> feat(all): add initial Jellyfin/Emby support
|
||||
=======
|
||||
jellyfinHost: this.jellyfin.hostname,
|
||||
>>>>>>> feat(rebase): rebase
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
: body.hostname;
|
||||
// First we need to attempt to log the user in to jellyfin
|
||||
const jellyfinserver = new JellyfinAPI(hostname ?? '');
|
||||
settings.jellyfin.name = await jellyfinserver.getServerName();
|
||||
|
||||
const account = await jellyfinserver.login(body.username, body.password);
|
||||
|
||||
|
||||
@@ -74,10 +74,8 @@ const messages = defineMessages({
|
||||
openradarr: 'Open Movie in Radarr',
|
||||
openradarr4k: 'Open Movie in 4K Radarr',
|
||||
downloadstatus: 'Download Status',
|
||||
playonplex: 'Play on Plex',
|
||||
play4konplex: 'Play 4K on Plex',
|
||||
playonjellyfin: 'Play on Jellyfin',
|
||||
play4konjellyfin: 'Play 4K on Jellyfin',
|
||||
play: 'Play on {mediaServerName}',
|
||||
play4k: 'Play 4K on {mediaServerName}',
|
||||
markavailable: 'Mark as Available',
|
||||
mark4kavailable: 'Mark 4K as Available',
|
||||
});
|
||||
@@ -396,8 +394,8 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
<StatusBadge
|
||||
status={data.mediaInfo?.status}
|
||||
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
||||
plexUrl={data.mediaInfo?.mediaUrl}
|
||||
plexUrl4k={data.mediaInfo?.mediaUrl4k}
|
||||
mediaUrl={data.mediaInfo?.mediaUrl}
|
||||
mediaUrl4k={data.mediaInfo?.mediaUrl4k}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
@@ -406,8 +404,8 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
status={data.mediaInfo?.status4k}
|
||||
is4k
|
||||
inProgress={(data.mediaInfo?.downloadStatus4k ?? []).length > 0}
|
||||
plexUrl={data.mediaInfo?.mediaUrl}
|
||||
plexUrl4k={
|
||||
mediaUrl={data.mediaInfo?.mediaUrl}
|
||||
mediaUrl4k={
|
||||
data.mediaInfo?.mediaUrl4k &&
|
||||
(hasPermission(Permission.REQUEST_4K) ||
|
||||
hasPermission(Permission.REQUEST_4K_MOVIE))
|
||||
@@ -487,21 +485,23 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
</svg>
|
||||
<span>
|
||||
{data.mediaInfo?.mediaUrl || data.mediaInfo?.mediaUrl4k
|
||||
? intl.formatMessage(
|
||||
settings.currentSettings.mediaServerType ==
|
||||
? intl.formatMessage(messages.play, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? messages.playonplex
|
||||
: messages.playonjellyfin
|
||||
)
|
||||
? 'Plex'
|
||||
: settings.currentSettings.jellyfinServerName,
|
||||
})
|
||||
: data.mediaInfo?.mediaUrl4k &&
|
||||
(hasPermission(Permission.REQUEST_4K) ||
|
||||
hasPermission(Permission.REQUEST_4K_MOVIE))
|
||||
? intl.formatMessage(
|
||||
settings.currentSettings.mediaServerType ==
|
||||
? intl.formatMessage(messages.play4k, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? messages.playonplex
|
||||
: messages.playonjellyfin
|
||||
)
|
||||
? 'Plex'
|
||||
: settings.currentSettings.jellyfinServerName,
|
||||
})
|
||||
: intl.formatMessage(messages.watchtrailer)}
|
||||
</span>
|
||||
</>
|
||||
@@ -538,12 +538,13 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
}}
|
||||
buttonType="ghost"
|
||||
>
|
||||
{intl.formatMessage(
|
||||
settings.currentSettings.mediaServerType ==
|
||||
{intl.formatMessage(messages.play4k, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? messages.play4konplex
|
||||
: messages.play4konjellyfin
|
||||
)}
|
||||
? 'Plex'
|
||||
: settings.currentSettings.jellyfinServerName,
|
||||
})}
|
||||
</ButtonWithDropdown.Item>
|
||||
)}
|
||||
{trailerUrl && (
|
||||
|
||||
@@ -14,7 +14,6 @@ import globalMessages from '../../i18n/globalMessages';
|
||||
import PermissionEdit from '../PermissionEdit';
|
||||
import * as Yup from 'yup';
|
||||
import RegionSelector from '../RegionSelector';
|
||||
import { MediaServerType } from '../../../server/constants/server';
|
||||
|
||||
const messages = defineMessages({
|
||||
generalsettings: 'General Settings',
|
||||
@@ -36,7 +35,6 @@ const messages = defineMessages({
|
||||
toastSettingsSuccess: 'Settings successfully saved!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
defaultPermissions: 'Default User Permissions',
|
||||
useJellyfin: 'Use Jellyfin as Media Server',
|
||||
hideAvailable: 'Hide Available Media',
|
||||
csrfProtection: 'Enable CSRF Protection',
|
||||
csrfProtectionTip:
|
||||
@@ -124,8 +122,6 @@ const SettingsMain: React.FC = () => {
|
||||
region: data?.region,
|
||||
originalLanguage: data?.originalLanguage,
|
||||
trustProxy: data?.trustProxy,
|
||||
useJellyfin:
|
||||
data?.mediaServerType == MediaServerType.JELLYFIN ? true : false,
|
||||
}}
|
||||
enableReinitialize
|
||||
validationSchema={MainSettingsSchema}
|
||||
@@ -141,9 +137,6 @@ const SettingsMain: React.FC = () => {
|
||||
region: values.region,
|
||||
originalLanguage: values.originalLanguage,
|
||||
trustProxy: values.trustProxy,
|
||||
mediaServerType: values.useJellyfin
|
||||
? MediaServerType.JELLYFIN
|
||||
: MediaServerType.PLEX,
|
||||
});
|
||||
|
||||
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
|
||||
@@ -367,21 +360,6 @@ const SettingsMain: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="useJellyfin" className="checkbox-label">
|
||||
<span>{intl.formatMessage(messages.useJellyfin)}</span>
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="useJellyfin"
|
||||
name="useJellyfin"
|
||||
onChange={() => {
|
||||
setFieldValue('useJellyfin', !values.useJellyfin);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="group-label"
|
||||
|
||||
@@ -31,7 +31,10 @@ const Setup: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [msSettingsComplete, setMSSettingsComplete] = useState(false);
|
||||
const [
|
||||
mediaServerSettingsComplete,
|
||||
setMediaServerSettingsComplete,
|
||||
] = useState(false);
|
||||
const [mediaServerType, setMediaServerType] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
@@ -114,10 +117,12 @@ const Setup: React.FC = () => {
|
||||
{currentStep === 2 && (
|
||||
<div>
|
||||
{mediaServerType == 'PLEX' ? (
|
||||
<SettingsPlex onComplete={() => setMSSettingsComplete(true)} />
|
||||
<SettingsPlex
|
||||
onComplete={() => setMediaServerSettingsComplete(true)}
|
||||
/>
|
||||
) : (
|
||||
<SettingsJellyfin
|
||||
onComplete={() => setMSSettingsComplete(true)}
|
||||
onComplete={() => setMediaServerSettingsComplete(true)}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 text-sm text-gray-500">
|
||||
@@ -131,7 +136,7 @@ const Setup: React.FC = () => {
|
||||
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
disabled={!msSettingsComplete}
|
||||
disabled={!mediaServerSettingsComplete}
|
||||
onClick={() => setCurrentStep(3)}
|
||||
>
|
||||
<FormattedMessage {...messages.continue} />
|
||||
|
||||
@@ -13,25 +13,25 @@ interface StatusBadgeProps {
|
||||
status?: MediaStatus;
|
||||
is4k?: boolean;
|
||||
inProgress?: boolean;
|
||||
plexUrl?: string;
|
||||
plexUrl4k?: string;
|
||||
mediaUrl?: string;
|
||||
mediaUrl4k?: string;
|
||||
}
|
||||
|
||||
const StatusBadge: React.FC<StatusBadgeProps> = ({
|
||||
status,
|
||||
is4k = false,
|
||||
inProgress = false,
|
||||
plexUrl,
|
||||
plexUrl4k,
|
||||
mediaUrl,
|
||||
mediaUrl4k,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
if (is4k) {
|
||||
switch (status) {
|
||||
case MediaStatus.AVAILABLE:
|
||||
if (plexUrl4k) {
|
||||
if (mediaUrl4k) {
|
||||
return (
|
||||
<a href={plexUrl4k} target="_blank" rel="noopener noreferrer">
|
||||
<a href={mediaUrl4k} target="_blank" rel="noopener noreferrer">
|
||||
<Badge
|
||||
badgeType="success"
|
||||
className="transition cursor-pointer hover:bg-green-400"
|
||||
@@ -52,9 +52,9 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
||||
</Badge>
|
||||
);
|
||||
case MediaStatus.PARTIALLY_AVAILABLE:
|
||||
if (plexUrl4k) {
|
||||
if (mediaUrl4k) {
|
||||
return (
|
||||
<a href={plexUrl4k} target="_blank" rel="noopener noreferrer">
|
||||
<a href={mediaUrl4k} target="_blank" rel="noopener noreferrer">
|
||||
<Badge
|
||||
badgeType="success"
|
||||
className="transition cursor-pointer hover:bg-green-400"
|
||||
@@ -104,9 +104,9 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
||||
|
||||
switch (status) {
|
||||
case MediaStatus.AVAILABLE:
|
||||
if (plexUrl) {
|
||||
if (mediaUrl) {
|
||||
return (
|
||||
<a href={plexUrl} target="_blank" rel="noopener noreferrer">
|
||||
<a href={mediaUrl} target="_blank" rel="noopener noreferrer">
|
||||
<Badge
|
||||
badgeType="success"
|
||||
className="transition cursor-pointer hover:bg-green-400"
|
||||
@@ -129,9 +129,9 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
||||
</Badge>
|
||||
);
|
||||
case MediaStatus.PARTIALLY_AVAILABLE:
|
||||
if (plexUrl) {
|
||||
if (mediaUrl) {
|
||||
return (
|
||||
<a href={plexUrl} target="_blank" rel="noopener noreferrer">
|
||||
<a href={mediaUrl} target="_blank" rel="noopener noreferrer">
|
||||
<Badge
|
||||
badgeType="success"
|
||||
className="transition cursor-pointer hover:bg-green-400"
|
||||
|
||||
@@ -67,10 +67,8 @@ const messages = defineMessages({
|
||||
opensonarr: 'Open Series in Sonarr',
|
||||
opensonarr4k: 'Open Series in 4K Sonarr',
|
||||
downloadstatus: 'Download Status',
|
||||
playonplex: 'Play on Plex',
|
||||
play4konplex: 'Play 4K on Plex',
|
||||
playonjellyfin: 'Play on Jellyfin',
|
||||
play4konjellyfin: 'Play 4K on Jellyfin',
|
||||
play: 'Play on {mediaServerName}',
|
||||
play4k: 'Play 4K on {mediaServerName}',
|
||||
markavailable: 'Mark as Available',
|
||||
mark4kavailable: 'Mark 4K as Available',
|
||||
allseasonsmarkedavailable: '* All seasons will be marked as available.',
|
||||
@@ -413,8 +411,8 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
||||
<StatusBadge
|
||||
status={data.mediaInfo?.status}
|
||||
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
||||
plexUrl={data.mediaInfo?.mediaUrl}
|
||||
plexUrl4k={data.mediaInfo?.mediaUrl4k}
|
||||
mediaUrl={data.mediaInfo?.mediaUrl}
|
||||
mediaUrl4k={data.mediaInfo?.mediaUrl4k}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
@@ -422,8 +420,8 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
||||
<StatusBadge
|
||||
status={data.mediaInfo?.status}
|
||||
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||
plexUrl={data.mediaInfo?.mediaUrl}
|
||||
plexUrl4k={
|
||||
mediaUrl={data.mediaInfo?.mediaUrl}
|
||||
mediaUrl4k={
|
||||
data.mediaInfo?.mediaUrl4k &&
|
||||
(hasPermission(Permission.REQUEST_4K) ||
|
||||
hasPermission(Permission.REQUEST_4K_TV))
|
||||
|
||||
@@ -80,10 +80,8 @@
|
||||
"components.MovieDetails.overview": "Overview",
|
||||
"components.MovieDetails.overviewunavailable": "Overview unavailable.",
|
||||
"components.MovieDetails.pending": "Pending",
|
||||
"components.MovieDetails.play4konjellyfin": "Play 4K on Jellyfin",
|
||||
"components.MovieDetails.play4konplex": "Play 4K on Plex",
|
||||
"components.MovieDetails.playonjellyfin": "Play on Jellyfin",
|
||||
"components.MovieDetails.playonplex": "Play on Plex",
|
||||
"components.MovieDetails.play": "Play on {mediaServerName}",
|
||||
"components.MovieDetails.play4k": "Play 4K on {mediaServerName}",
|
||||
"components.MovieDetails.recommendations": "Recommendations",
|
||||
"components.MovieDetails.recommendationssubtext": "If you liked {title}, you might also like…",
|
||||
"components.MovieDetails.releasedate": "Release Date",
|
||||
@@ -584,7 +582,6 @@
|
||||
"components.Settings.toastSettingsSuccess": "Settings successfully saved!",
|
||||
"components.Settings.trustProxy": "Enable Proxy Support",
|
||||
"components.Settings.trustProxyTip": "Allows Overseerr to correctly register client IP addresses behind a proxy (Overseerr must be reloaded for changes to take effect)",
|
||||
"components.Settings.useJellyfin": "Use Jellyfin as Media Server",
|
||||
"components.Settings.validationApplicationTitle": "You must provide an application title",
|
||||
"components.Settings.validationApplicationUrl": "You must provide a valid URL",
|
||||
"components.Settings.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
|
||||
@@ -639,10 +636,8 @@
|
||||
"components.TvDetails.overview": "Overview",
|
||||
"components.TvDetails.overviewunavailable": "Overview unavailable.",
|
||||
"components.TvDetails.pending": "Pending",
|
||||
"components.TvDetails.play4konjellyfin": "Play 4K on Jellyfin",
|
||||
"components.TvDetails.play4konplex": "Play 4K on Plex",
|
||||
"components.TvDetails.playonjellyfin": "Play on Jellyfin",
|
||||
"components.TvDetails.playonplex": "Play on Plex",
|
||||
"components.TvDetails.play": "Play on {mediaServerName}",
|
||||
"components.TvDetails.play4k": "Play 4K on {mediaServerName}",
|
||||
"components.TvDetails.recommendations": "Recommendations",
|
||||
"components.TvDetails.recommendationssubtext": "If you liked {title}, you might also like…",
|
||||
"components.TvDetails.showtype": "Show Type",
|
||||
|
||||
Reference in New Issue
Block a user