Skip to content

Commit

Permalink
feat(config): add option to anonymize other users (#263)
Browse files Browse the repository at this point in the history
resolves #262
  • Loading branch information
RaunoT authored Oct 9, 2024
1 parent 5773b4b commit 4f96214
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 8 deletions.
7 changes: 6 additions & 1 deletion src/app/dashboard/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}

Expand Down
2 changes: 2 additions & 0 deletions src/app/settings/general/_actions/updateGeneralSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const schema = z.object({
isPostersTmdbOnly: z.boolean(),
googleAnalyticsId: z.string(),
isOutsideAccess: z.boolean(),
isAnonymized: z.boolean(),
complete: z.boolean(),
})

Expand All @@ -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,
}

Expand Down
11 changes: 11 additions & 0 deletions src/app/settings/general/_components/GeneralSettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ export default function GeneralSettingsForm({ settings, libraries }: Props) {
<small>Access without login.</small>
</span>
</Switch>
<Switch
className='switch items-start'
name='isAnonymized'
defaultSelected={generalSettings.isAnonymized}
>
<div className='indicator'></div>
<span className='label'>
<span className='label-wrapper'>Anonymize</span>
<small>Hide usernames for other users.</small>
</span>
</Switch>
</section>
<section className='group-settings group'>
<h2 className='heading-settings'>Analytics</h2>
Expand Down
18 changes: 13 additions & 5 deletions src/components/MediaItem/MediaItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<string>(initialImageSrc)
const [dataKey, setDataKey] = useState<number>(0)
const titleContainerRef = useRef<HTMLDivElement>(null)
const isOverseerrActive =
settings.connection.overseerrUrl && settings.connection.overseerrApiKey
const [imageSrc, setImageSrc] = useState<string>(posterSrc)

useEffect(() => {
setDataKey((prevDataKey) => prevDataKey + 1)
setImageSrc(posterSrc)
}, [data, type, posterSrc])
setImageSrc(initialImageSrc)
}, [data, type, initialImageSrc])

return (
<motion.li
Expand All @@ -89,7 +93,11 @@ export default function MediaItem({
<Image
fill
className='object-cover object-top'
alt={isUserDashboard ? data.user + ' avatar' : data.title + ' poster'}
alt={
isUserDashboard
? data.friendly_name + ' avatar'
: data.title + ' poster'
}
src={imageSrc}
sizes='10rem'
onError={() => setImageSrc(placeholderSvg)}
Expand Down
8 changes: 8 additions & 0 deletions src/components/MediaItem/anonymous.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/types/settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type GeneralSettings = {
isPostersTmdbOnly: boolean
googleAnalyticsId: string
isOutsideAccess: boolean
isAnonymized: boolean
complete: boolean
}

Expand Down
1 change: 1 addition & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const DEFAULT_SETTINGS: Settings = {
isPostersTmdbOnly: false,
googleAnalyticsId: '',
isOutsideAccess: false,
isAnonymized: false,
complete: false,
},
rewind: {
Expand Down
22 changes: 20 additions & 2 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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)
Expand All @@ -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,
}
})
}

0 comments on commit 4f96214

Please sign in to comment.