diff --git a/src/app/dashboard/users/page.tsx b/src/app/dashboard/users/page.tsx
index 7973a767..f7af27aa 100644
--- a/src/app/dashboard/users/page.tsx
+++ b/src/app/dashboard/users/page.tsx
@@ -10,6 +10,7 @@ import fetchTautulli, { getLibrariesByType } from '@/utils/fetchTautulli'
import { secondsToTime, timeToSeconds } from '@/utils/formatting'
import getPeriod from '@/utils/getPeriod'
import getSettings from '@/utils/getSettings'
+import { anonymizeUsers } from '@/utils/helpers'
import { Metadata } from 'next'
import { getServerSession } from 'next-auth'
import { notFound } from 'next/navigation'
@@ -51,7 +52,7 @@ async function getUsers(
const allUsersCount = await getUsersCount(settings)
if (!allUsersCount) {
- console.error('[TAUTULLI] - Could not determine the number of users.')
+ console.error('Could not determine the number of users!')
return
}
@@ -165,6 +166,10 @@ async function getUsers(
user.audio_plays_count = usersPlaysAndDurations[i].audio_plays_count
})
+ if (settings.general.isAnonymized) {
+ return anonymizeUsers(listedUsers, loggedInUserId)
+ }
+
return listedUsers
}
diff --git a/src/app/settings/general/_actions/updateGeneralSettings.ts b/src/app/settings/general/_actions/updateGeneralSettings.ts
index ebb44115..e4896f95 100644
--- a/src/app/settings/general/_actions/updateGeneralSettings.ts
+++ b/src/app/settings/general/_actions/updateGeneralSettings.ts
@@ -9,6 +9,7 @@ const schema = z.object({
isPostersTmdbOnly: z.boolean(),
googleAnalyticsId: z.string(),
isOutsideAccess: z.boolean(),
+ isAnonymized: z.boolean(),
complete: z.boolean(),
})
@@ -21,6 +22,7 @@ export default async function saveGeneralSettings(
isPostersTmdbOnly: formData.get('isPostersTmdbOnly') === 'on',
googleAnalyticsId: formData.get('googleAnalyticsId') as string,
isOutsideAccess: formData.get('isOutsideAccess') === 'on',
+ isAnonymized: formData.get('isAnonymized') === 'on',
complete: true,
}
diff --git a/src/app/settings/general/_components/GeneralSettingsForm.tsx b/src/app/settings/general/_components/GeneralSettingsForm.tsx
index bda91bb5..b3a3b570 100644
--- a/src/app/settings/general/_components/GeneralSettingsForm.tsx
+++ b/src/app/settings/general/_components/GeneralSettingsForm.tsx
@@ -120,6 +120,17 @@ export default function GeneralSettingsForm({ settings, libraries }: Props) {
Access without login.
+
+
+
+ Anonymize
+ Hide usernames for other users.
+
+
Analytics
diff --git a/src/components/MediaItem/MediaItem.tsx b/src/components/MediaItem/MediaItem.tsx
index 43f041a5..5122cca9 100644
--- a/src/components/MediaItem/MediaItem.tsx
+++ b/src/components/MediaItem/MediaItem.tsx
@@ -18,9 +18,10 @@ import clsx from 'clsx'
import { motion } from 'framer-motion'
import Image from 'next/image'
import { useEffect, useRef, useState } from 'react'
+import anonymousSvg from './anonymous.svg'
import MediaItemTitle from './MediaItemTitle'
-import PlexDeeplink from './PlexDeeplink'
import placeholderSvg from './placeholder.svg'
+import PlexDeeplink from './PlexDeeplink'
type Props = {
data: TautulliItemRow
@@ -53,16 +54,19 @@ export default function MediaItem({
isUserDashboard ? data.user_thumb : data.thumb
}&width=300`,
)}`
+ const isAnonymized = data.user === 'Anonymous'
+ const initialImageSrc =
+ isUserDashboard && isAnonymized ? anonymousSvg : posterSrc
+ const [imageSrc, setImageSrc] = useState(initialImageSrc)
const [dataKey, setDataKey] = useState(0)
const titleContainerRef = useRef(null)
const isOverseerrActive =
settings.connection.overseerrUrl && settings.connection.overseerrApiKey
- const [imageSrc, setImageSrc] = useState(posterSrc)
useEffect(() => {
setDataKey((prevDataKey) => prevDataKey + 1)
- setImageSrc(posterSrc)
- }, [data, type, posterSrc])
+ setImageSrc(initialImageSrc)
+ }, [data, type, initialImageSrc])
return (
setImageSrc(placeholderSvg)}
diff --git a/src/components/MediaItem/anonymous.svg b/src/components/MediaItem/anonymous.svg
new file mode 100644
index 00000000..3f99b757
--- /dev/null
+++ b/src/components/MediaItem/anonymous.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/types/settings.d.ts b/src/types/settings.d.ts
index cc341cb9..dfd19ca1 100644
--- a/src/types/settings.d.ts
+++ b/src/types/settings.d.ts
@@ -18,6 +18,7 @@ export type GeneralSettings = {
isPostersTmdbOnly: boolean
googleAnalyticsId: string
isOutsideAccess: boolean
+ isAnonymized: boolean
complete: boolean
}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 27e11011..2f3d7ff3 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -51,6 +51,7 @@ export const DEFAULT_SETTINGS: Settings = {
isPostersTmdbOnly: false,
googleAnalyticsId: '',
isOutsideAccess: false,
+ isAnonymized: false,
complete: false,
},
rewind: {
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index 3db3f533..24a3849f 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -1,7 +1,8 @@
import { Settings } from '@/types/settings'
+import { TautulliItemRow } from '@/types/tautulli'
import { PERIODS, SETTINGS_PAGES } from './constants'
-const requiredSettings = [
+const REQUIRED_SETTINGS = [
'connection.tautulliUrl',
'connection.tautulliApiKey',
'connection.plexUrl',
@@ -14,7 +15,7 @@ const requiredSettings = [
]
export function checkRequiredSettings(settings: Settings): string | null {
- for (const key of requiredSettings) {
+ for (const key of REQUIRED_SETTINGS) {
const keys = key.split('.')
// @ts-expect-error - TODO: we know this is safe, but should still look to resolve without exception
const settingValue = keys.reduce((acc, curr) => acc && acc[curr], settings)
@@ -39,3 +40,20 @@ export function getRewindDateRange(settings: Settings) {
return { startDate, endDate }
}
+
+export function anonymizeUsers(
+ users: TautulliItemRow[],
+ loggedInUserId: string,
+): TautulliItemRow[] {
+ return users.map((user) => {
+ const isLoggedIn = user.user_id === Number(loggedInUserId)
+
+ return {
+ ...user,
+ user: isLoggedIn ? user.user : 'Anonymous',
+ friendly_name: isLoggedIn ? user.friendly_name : 'Anonymous',
+ user_thumb: isLoggedIn ? user.user_thumb : '',
+ user_id: isLoggedIn ? user.user_id : 0,
+ }
+ })
+}