diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 4e7f77278..560f04d57 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -262,8 +262,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => { urlBase: body.urlBase, }); - const { externalHostname } = getSettings().jellyfin; - // Try to find deviceId that corresponds to jellyfin user, else generate a new one let user = await userRepository.findOne({ where: { jellyfinUsername: body.username }, @@ -281,11 +279,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => { // First we need to attempt to log the user in to jellyfin const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId); - const jellyfinHost = - externalHostname && externalHostname.length > 0 - ? externalHostname - : hostname; - const ip = req.ip; let clientIp; @@ -336,7 +329,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { jellyfinAuthToken: account.AccessToken, permissions: Permission.ADMIN, avatar: account.User.PrimaryImageTag - ? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` + ? `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` : gravatarUrl(body.email || account.User.Name, { default: 'mm', size: 200, @@ -355,7 +348,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { jellyfinAuthToken: account.AccessToken, permissions: Permission.ADMIN, avatar: account.User.PrimaryImageTag - ? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` + ? `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` : gravatarUrl(body.email || account.User.Name, { default: 'mm', size: 200, @@ -410,7 +403,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { ); // Update the users avatar with their jellyfin profile pic (incase it changed) if (account.User.PrimaryImageTag) { - const avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`; + const avatar = `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`; if (avatar !== user.avatar) { const avatarProxy = new ImageProxy('avatar', ''); avatarProxy.clearCachedImage(user.avatar); @@ -467,7 +460,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { jellyfinDeviceId: deviceId, permissions: settings.main.defaultPermissions, avatar: account.User.PrimaryImageTag - ? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` + ? `/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` : gravatarUrl(body.email || account.User.Name, { default: 'mm', size: 200, diff --git a/server/routes/avatarproxy.ts b/server/routes/avatarproxy.ts index 65638df2b..e6f6f3b54 100644 --- a/server/routes/avatarproxy.ts +++ b/server/routes/avatarproxy.ts @@ -1,5 +1,8 @@ +import { MediaServerType } from '@server/constants/server'; import ImageProxy from '@server/lib/imageproxy'; +import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; +import { getHostname } from '@server/utils/getHostname'; import { Router } from 'express'; const router = Router(); @@ -7,9 +10,25 @@ const router = Router(); const avatarImageProxy = new ImageProxy('avatar', ''); // Proxy avatar images router.get('/*', async (req, res) => { - const imagePath = req.url.startsWith('/') ? req.url.slice(1) : req.url; - + let imagePath = ''; 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); res.writeHead(200, { diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 30c854af9..3d6b6b0d3 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -377,11 +377,6 @@ settingsRoutes.get('/jellyfin/library', async (req, res, next) => { settingsRoutes.get('/jellyfin/users', async (req, res) => { const settings = getSettings(); - const { externalHostname } = settings.jellyfin; - const jellyfinHost = - externalHostname && externalHostname.length > 0 - ? externalHostname - : getHostname(); const userRepository = getRepository(User); const admin = await userRepository.findOneOrFail({ @@ -401,7 +396,7 @@ settingsRoutes.get('/jellyfin/users', async (req, res) => { username: user.Name, id: user.Id, thumb: user.PrimaryImageTag - ? `${jellyfinHost}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90` + ? `/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90` : gravatarUrl(user.Name, { default: 'mm', size: 200 }), email: user.Name, })); diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index f8a0d41a2..83ad0910b 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -516,12 +516,6 @@ router.post( //const jellyfinUsersResponse = await jellyfinClient.getUsers(); const createdUsers: User[] = []; - const { externalHostname } = getSettings().jellyfin; - - const jellyfinHost = - externalHostname && externalHostname.length > 0 - ? externalHostname - : hostname; jellyfinClient.setUserId(admin.jellyfinUserId ?? ''); const jellyfinUsers = await jellyfinClient.getUsers(); @@ -546,7 +540,7 @@ router.post( email: jellyfinUser?.Name, permissions: settings.main.defaultPermissions, avatar: jellyfinUser?.PrimaryImageTag - ? `${jellyfinHost}/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90` + ? `/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90` : gravatarUrl(jellyfinUser?.Name ?? '', { default: 'mm', size: 200, diff --git a/src/components/Blacklist/index.tsx b/src/components/Blacklist/index.tsx index 217f4cefd..a752e95f8 100644 --- a/src/components/Blacklist/index.tsx +++ b/src/components/Blacklist/index.tsx @@ -268,6 +268,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => { {title && title.backdropPath && (
{ className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105" > { { {data.backdropPath && (
{
src; +export type CachedImageProps = ImageProps & { + src: string; + type: 'tmdb' | 'avatar'; +}; + /** * The CachedImage component should be used wherever * we want to offer the option to locally cache images. **/ -const CachedImage = ({ src, ...props }: ImageProps) => { +const CachedImage = ({ src, type, ...props }: CachedImageProps) => { const { currentSettings } = useSettings(); - let imageUrl = src; - - if (typeof imageUrl === 'string' && imageUrl.startsWith('http')) { - const parsedUrl = new URL(imageUrl); + let imageUrl: string; - if (parsedUrl.host === 'image.tmdb.org') { - if (currentSettings.cacheImages) - imageUrl = imageUrl.replace('https://image.tmdb.org', '/imageproxy'); - } else if (parsedUrl.host !== 'gravatar.com') { - imageUrl = '/avatarproxy/' + imageUrl; - } + if (type === 'tmdb') { + // tmdb stuff + imageUrl = + currentSettings.cacheImages && !src.startsWith('/') + ? src.replace(/^https:\/\/image\.tmdb\.org\//, '/imageproxy/') + : src; + } else if (type === 'avatar') { + // jellyfin avatar (in any) + const jellyfinAvatar = src.match( + /(\/Users\/\w+\/Images\/Primary\/?\?tag=\w+&quality=90)$/ + )?.[1]; + imageUrl = jellyfinAvatar ? `/avatarproxy` + jellyfinAvatar : src; + } else { + return null; } return ; diff --git a/src/components/Common/ImageFader/index.tsx b/src/components/Common/ImageFader/index.tsx index 20ccb6985..930471e94 100644 --- a/src/components/Common/ImageFader/index.tsx +++ b/src/components/Common/ImageFader/index.tsx @@ -61,6 +61,7 @@ const ImageFader: ForwardRefRenderFunction = ( {...props} > ( {backdrop && (
{ >
{ tabIndex={0} > { {data.backdropPath && (
{
{ className="group ml-1 inline-flex h-full items-center xl:ml-1.5" > { {title.backdropPath && (
{ className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105" > { className="group flex items-center truncate" > { data-testid="user-menu" > {
{ {data.backdropPath && (
{
{
{ {data.profilePath && (
{ > { {title.backdropPath && (
{ > { 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" > { {title.backdropPath && (
{ className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105" > { > { >
{ {data.backdropPath && (
{
= ({
{ className="h-10 w-10 flex-shrink-0" > {