diff --git a/src/app/dashboard/_components/Dashboard.tsx b/src/app/dashboard/_components/Dashboard.tsx index 86c0f9f7..415514b0 100644 --- a/src/app/dashboard/_components/Dashboard.tsx +++ b/src/app/dashboard/_components/Dashboard.tsx @@ -15,7 +15,7 @@ import DashboardFilters from './DashboardFilters' type Props = { title: string - items?: TautulliItemRow[] + items?: TautulliItemRow[] | null totalDuration?: string totalSize?: string | number type: 'movie' | 'show' | 'artist' | 'users' diff --git a/src/app/dashboard/users/page.tsx b/src/app/dashboard/users/page.tsx index 7973a767..0fcf0de3 100644 --- a/src/app/dashboard/users/page.tsx +++ b/src/app/dashboard/users/page.tsx @@ -1,15 +1,12 @@ import { authOptions } from '@/lib/auth' import { DashboardSearchParams } from '@/types/dashboard' import { Settings } from '@/types/settings' -import { TautulliItem, TautulliItemRow, TautulliUser } from '@/types/tautulli' -import { - fetchOverseerrStats, - fetchOverseerrUserId, -} from '@/utils/fetchOverseerr' -import fetchTautulli, { getLibrariesByType } from '@/utils/fetchTautulli' +import { fetchOverseerrStats } from '@/utils/fetchOverseerr' +import fetchTautulli, { getUsersCount } from '@/utils/fetchTautulli' import { secondsToTime, timeToSeconds } from '@/utils/formatting' import getPeriod from '@/utils/getPeriod' import getSettings from '@/utils/getSettings' +import getUsersTop from '@/utils/getUsersTop' import { Metadata } from 'next' import { getServerSession } from 'next-auth' import { notFound } from 'next/navigation' @@ -21,176 +18,6 @@ export const metadata: Metadata = { title: 'Users', } -type UserRequestCounts = - | { - requests: number - } - | undefined - -async function getInactiveUserInTimePeriod( - id: string, -): Promise { - const user = await fetchTautulli('get_user', { - user_id: id, - }) - const nonActive = user?.response?.data ?? ({} as TautulliItemRow) - - nonActive.total_duration = 0 - - return nonActive -} - -async function getUsers( - loggedInUserId: string, - period: number, - requestsPeriod: string, - periodString: string, - settings: Settings, -) { - const numberOfUsers = 6 - const allUsersCount = await getUsersCount(settings) - - if (!allUsersCount) { - console.error('[TAUTULLI] - Could not determine the number of users.') - - return - } - - const usersRes = await fetchTautulli('get_home_stats', { - stat_id: 'top_users', - stats_count: allUsersCount, - stats_type: 'duration', - time_range: period, - }) - const users = usersRes?.response?.data?.rows - - if (!users) { - return - } - - const isAnonymousAccess = settings.general.isOutsideAccess && !loggedInUserId - const listedUsers = isAnonymousAccess - ? users.slice(0, numberOfUsers) - : await getStatsWithLoggedInUser(loggedInUserId, users, numberOfUsers) - const [moviesLib, showsLib, audioLib] = await Promise.all([ - getLibrariesByType('movie'), - getLibrariesByType('show'), - getLibrariesByType('artist'), - ]) - const isOverseerrActive = - settings.connection.overseerrUrl && settings.connection.overseerrApiKey - - let usersRequestsCounts: UserRequestCounts[] = [] - - if (isOverseerrActive) { - const overseerrUserIds = await Promise.all( - listedUsers.map( - async (user) => await fetchOverseerrUserId(String(user.user_id)), - ), - ) - - usersRequestsCounts = await Promise.all( - overseerrUserIds.map(async (overseerrId) => { - if (overseerrId) { - const userTotal = await fetchOverseerrStats( - `user/${overseerrId}/requests`, - requestsPeriod, - ) - - return { - requests: userTotal.length, - } - } - }), - ) - } - - const usersPlaysAndDurations = await Promise.all( - listedUsers.map(async (user) => { - let moviesPlaysCount = 0 - let showsPlaysCount = 0 - let audioPlaysCount = 0 - - for (const movieLib of moviesLib) { - const userMovies = await fetchTautulli<{ recordsFiltered: number }>( - 'get_history', - { - user_id: user.user_id, - after: periodString, - section_id: movieLib.section_id, - }, - ) - - moviesPlaysCount += userMovies?.response?.data?.recordsFiltered || 0 - } - - for (const showLib of showsLib) { - const userShows = await fetchTautulli<{ recordsFiltered: number }>( - 'get_history', - { - user_id: user.user_id, - after: periodString, - section_id: showLib.section_id, - }, - ) - - showsPlaysCount += userShows?.response?.data?.recordsFiltered || 0 - } - - for (const audioLibItem of audioLib) { - const userAudio = await fetchTautulli<{ recordsFiltered: number }>( - 'get_history', - { - user_id: user.user_id, - after: periodString, - section_id: audioLibItem.section_id, - }, - ) - - audioPlaysCount += userAudio?.response?.data?.recordsFiltered || 0 - } - - return { - movies_plays_count: moviesPlaysCount, - shows_plays_count: showsPlaysCount, - audio_plays_count: audioPlaysCount, - } - }), - ) - - listedUsers.map((user, i) => { - user.requests = usersRequestsCounts[i]?.requests || 0 - user.movies_plays_count = usersPlaysAndDurations[i].movies_plays_count - user.shows_plays_count = usersPlaysAndDurations[i].shows_plays_count - user.audio_plays_count = usersPlaysAndDurations[i].audio_plays_count - }) - - return listedUsers -} - -async function getStatsWithLoggedInUser( - userId: string, - users: TautulliItemRow[], - numberOfUsers: number, -) { - const loggedInUserRank = users.findIndex( - (user) => String(user.user_id) == userId, - ) - const loggedInUser = - users[loggedInUserRank] || (await getInactiveUserInTimePeriod(userId)) - - let slicedUsers = users.slice(0, numberOfUsers) - - if (loggedInUserRank === -1 || loggedInUserRank >= numberOfUsers) { - slicedUsers = users.slice(0, numberOfUsers - 1) - loggedInUser.rank = - loggedInUserRank === -1 ? users.length : loggedInUserRank - slicedUsers.push(loggedInUser) - } - - return slicedUsers -} - async function getTotalDuration(period: string, settings: Settings) { if (settings.dashboard.activeTotalStatistics.includes('duration')) { const totalDuration = await fetchTautulli<{ total_duration: string }>( @@ -209,24 +36,6 @@ async function getTotalDuration(period: string, settings: Settings) { return undefined } -async function getUsersCount(settings: Settings) { - if (settings.dashboard.activeTotalStatistics.includes('count')) { - const usersRes = await fetchTautulli('get_users') - - let users = usersRes?.response?.data - - if (users) { - users = users.filter( - (user) => user.is_active && user.username !== 'Local', - ) - } - - return users?.length - } - - return undefined -} - async function getTotalRequests(period: string, settings: Settings) { const isOverseerrActive = settings.connection.overseerrUrl && settings.connection.overseerrApiKey @@ -259,13 +68,7 @@ async function DashboardUsersContent({ searchParams }: Props) { const period = getPeriod(searchParams, settings) const [usersData, totalDuration, usersCount, totalRequests] = await Promise.all([ - getUsers( - loggedInUserId, - period.daysAgo, - period.date, - period.string, - settings, - ), + getUsersTop(loggedInUserId, period.string, period.daysAgo), getTotalDuration(period.string, settings), getUsersCount(settings), getTotalRequests(period.date, settings), diff --git a/src/app/rewind/_components/RewindStories.tsx b/src/app/rewind/_components/RewindStories.tsx index 7d7135e7..6abd6163 100644 --- a/src/app/rewind/_components/RewindStories.tsx +++ b/src/app/rewind/_components/RewindStories.tsx @@ -15,6 +15,7 @@ import StoryRequests from './Stories/Requests' import StoryShows from './Stories/Shows' import StoryShowsTop from './Stories/ShowsTop' import StoryTotal from './Stories/Total' +import StoryUsersTop from './Stories/UsersTop' import StoryWelcome from './Stories/Welcome' type Story = { @@ -66,6 +67,9 @@ export default function RewindStories({ userRewind, settings }: Props) { const stories = [ createStory(StoryWelcome, 7000), createStory(StoryTotal, 8000), + ...(userRewind.usersTop && userRewind.usersTop.length > 1 + ? [createStory(StoryUsersTop, 9000)] + : []), ...(userRewind.libraries_total_size && isLibrariesSizeAndCountActive ? [createStory(StoryLibraries, 9000)] : []), diff --git a/src/app/rewind/_components/Stories/Libraries.tsx b/src/app/rewind/_components/Stories/Libraries.tsx index 6abb0a84..947c6f01 100644 --- a/src/app/rewind/_components/Stories/Libraries.tsx +++ b/src/app/rewind/_components/Stories/Libraries.tsx @@ -20,7 +20,7 @@ export default function StoryLibraries({

- But, did you know the{' '} + Did you know the{' '} Filesize diff --git a/src/app/rewind/_components/Stories/UsersTop.tsx b/src/app/rewind/_components/Stories/UsersTop.tsx new file mode 100644 index 00000000..55eece57 --- /dev/null +++ b/src/app/rewind/_components/Stories/UsersTop.tsx @@ -0,0 +1,38 @@ +import MediaItems from '@/components/MediaItem/MediaItems' +import { RewindStory } from '@/types/rewind' +import { UsersIcon } from '@heroicons/react/24/outline' +import RewindStat from '../RewindStat' +import StoryWrapper from '../StoryWrapper' + +export default function StoryUsersTop({ + userRewind, + isPaused, + pause, + resume, + settings, +}: RewindStory) { + return ( + + +

+ Here's how you compared to other{''} + + Users + + +

+ +
+ +
+
+
+ ) +} diff --git a/src/app/rewind/page.tsx b/src/app/rewind/page.tsx index 319a2114..553f0e9c 100644 --- a/src/app/rewind/page.tsx +++ b/src/app/rewind/page.tsx @@ -12,6 +12,8 @@ import { getlibrariesTotalSize, } from '@/utils/getRewind' import getSettings from '@/utils/getSettings' +import getUsersTop from '@/utils/getUsersTop' +import { getRewindDateRange } from '@/utils/helpers' import { getServerSession } from 'next-auth' import { notFound } from 'next/navigation' import { Suspense } from 'react' @@ -59,6 +61,7 @@ async function RewindContent({ searchParams }: Props) { } const libraries = await getLibraries() + const { startDate, endDate } = getRewindDateRange(settings) const [ topMediaItems, topMediaStats, @@ -66,6 +69,7 @@ async function RewindContent({ searchParams }: Props) { librariesTotalSize, librariesTotalDuration, serverId, + usersTop, ] = await Promise.all([ getTopMediaItems(user.id, libraries), getTopMediaStats(user.id, libraries), @@ -73,6 +77,7 @@ async function RewindContent({ searchParams }: Props) { getlibrariesTotalSize(libraries), getLibrariesTotalDuration(libraries), getServerId(), + getUsersTop(user.id, startDate, 0, endDate), ]) const userRewind: UserRewind = { duration: { @@ -82,6 +87,7 @@ async function RewindContent({ searchParams }: Props) { )}%`, total: secondsToTime(librariesTotalDuration), }, + usersTop: usersTop, shows: { top: topMediaItems.shows, count: topMediaStats.shows.count, 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..119d405a 100644 --- a/src/components/MediaItem/MediaItem.tsx +++ b/src/components/MediaItem/MediaItem.tsx @@ -18,19 +18,21 @@ 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 i: number - type: string + type: 'movie' | 'show' | 'artist' | 'users' serverId: string activeStats: string[] settings: Settings loggedInUserId?: string items: TautulliItemRow[] + rows?: boolean } export default function MediaItem({ @@ -42,44 +44,45 @@ export default function MediaItem({ settings, loggedInUserId, items, + rows, }: Props) { const tautulliUrl = settings.connection.tautulliUrl const isTmdbPoster = data.thumb?.startsWith('https://image.tmdb.org') - const isUserDashboard = type === 'users' + const isUsers = type === 'users' const posterSrc = isTmdbPoster ? data.thumb : `/api/image?url=${encodeURIComponent( `${tautulliUrl}/pms_image_proxy?img=${ - isUserDashboard ? data.user_thumb : data.thumb + isUsers ? data.user_thumb : data.thumb }&width=300`, )}` + const isAnonymized = data.user === 'Anonymous' + const initialImageSrc = isUsers && 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) + // Hidden conditional + const isLoggedInUser = String(data.user_id) === loggedInUserId + const isHiddenUser = isUsers && i > 4 && !isLoggedInUser + const isHiddenItem = !isUsers && i > 4 + const isSpecialCase = + i === 4 && items[5] && String(items[5].user_id) === loggedInUserId useEffect(() => { setDataKey((prevDataKey) => prevDataKey + 1) - setImageSrc(posterSrc) - }, [data, type, posterSrc]) + setImageSrc(initialImageSrc) + }, [data, type, initialImageSrc]) return ( 4 && - String(data.user_id) !== loggedInUserId && - 'hidden lg:flex', - isUserDashboard - ? i === 4 && - items[5] && - String(items[5].user_id) === loggedInUserId && - 'hidden lg:flex' - : i > 4 && 'hidden lg:flex', - )} + className={clsx('flex gap-3 2xl:items-center', { + 'hidden lg:flex': + !rows && (isHiddenUser || isHiddenItem || isSpecialCase), + hidden: rows && (isHiddenUser || isSpecialCase), + })} variants={slideDown} initial='hidden' animate='show' @@ -89,7 +92,9 @@ export default function MediaItem({ {isUserDashboard setImageSrc(placeholderSvg)} @@ -165,7 +170,7 @@ export default function MediaItem({ )} {/* Plays */} {activeStats.includes('plays') && - (isUserDashboard ? ( + (isUsers ? ( <> {data.shows_plays_count > 0 && (
  • @@ -206,14 +211,12 @@ export default function MediaItem({
  • )} {/* Requests */} - {activeStats.includes('requests') && - isUserDashboard && - data.requests > 0 && ( -
  • - - {pluralize(data.requests, 'request')} -
  • - )} + {activeStats.includes('requests') && isUsers && data.requests > 0 && ( +
  • + + {pluralize(data.requests, 'request')} +
  • + )}
    diff --git a/src/components/MediaItem/MediaItems.tsx b/src/components/MediaItem/MediaItems.tsx index 900a5d71..be614cb8 100644 --- a/src/components/MediaItem/MediaItems.tsx +++ b/src/components/MediaItem/MediaItems.tsx @@ -5,7 +5,7 @@ import MediaItem from './MediaItem' type Props = { items: TautulliItemRow[] - type: string + type: 'movie' | 'show' | 'artist' | 'users' serverId: string rows?: boolean settings: Settings @@ -24,10 +24,8 @@ export default function MediaItems({
      {items.map((itemData, i) => ( @@ -41,6 +39,7 @@ export default function MediaItems({ settings={settings} loggedInUserId={loggedInUserId} items={items} + rows={rows} /> ))}
    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/rewind.d.ts b/src/types/rewind.d.ts index 1a40b5e0..1e3f1103 100644 --- a/src/types/rewind.d.ts +++ b/src/types/rewind.d.ts @@ -7,6 +7,7 @@ type LibraryRewind = { } export type UserRewind = { + usersTop: TautulliItemRow[] | null libraries: TautulliLibrary[] libraries_total_size: number requests?: { 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/fetchTautulli.ts b/src/utils/fetchTautulli.ts index 4552dcaf..42ea76d0 100644 --- a/src/utils/fetchTautulli.ts +++ b/src/utils/fetchTautulli.ts @@ -170,3 +170,21 @@ export async function getLibrariesByType( return libraries.filter((library) => library.section_type === type) } + +export async function getUsersCount(settings: Settings) { + if (settings.dashboard.activeTotalStatistics.includes('count')) { + const usersRes = await fetchTautulli('get_users') + + let users = usersRes?.response?.data + + if (users) { + users = users.filter( + (user) => user.is_active && user.username !== 'Local', + ) + } + + return users?.length + } + + return undefined +} diff --git a/src/utils/getUsersTop.ts b/src/utils/getUsersTop.ts new file mode 100644 index 00000000..c74b3e1c --- /dev/null +++ b/src/utils/getUsersTop.ts @@ -0,0 +1,186 @@ +import { TautulliItem, TautulliItemRow } from '@/types/tautulli' +import { fetchOverseerrStats, fetchOverseerrUserId } from './fetchOverseerr' +import fetchTautulli, { + getLibrariesByType, + getUsersCount, +} from './fetchTautulli' +import getSettings from './getSettings' +import { anonymizeUsers } from './helpers' + +type UserRequestCounts = + | { + requests: number + } + | undefined + +export default async function getUsersTop( + loggedInUserId: string, + after: string, + period?: number, + before?: string, +): Promise { + const numberOfUsers = 6 + const settings = getSettings() + const allUsersCount = await getUsersCount(settings) + + if (!allUsersCount) { + console.error('Could not determine the number of users!') + + return null + } + + const usersRes = await fetchTautulli('get_home_stats', { + stat_id: 'top_users', + stats_count: allUsersCount, + stats_type: 'duration', + ...(after && before ? { after, before } : { time_range: period || '30' }), + }) + const users = usersRes?.response?.data?.rows + + if (!users) { + return null + } + + const isAnonymousAccess = settings.general.isOutsideAccess && !loggedInUserId + const listedUsers = isAnonymousAccess + ? users.slice(0, numberOfUsers) + : await getStatsWithLoggedInUser(loggedInUserId, users, numberOfUsers) + const [moviesLib, showsLib, audioLib] = await Promise.all([ + getLibrariesByType('movie'), + getLibrariesByType('show'), + getLibrariesByType('artist'), + ]) + const isOverseerrActive = + settings.connection.overseerrUrl && settings.connection.overseerrApiKey + + let usersRequestsCounts: UserRequestCounts[] = [] + + if (isOverseerrActive) { + const overseerrUserIds = await Promise.all( + listedUsers.map( + async (user) => await fetchOverseerrUserId(String(user.user_id)), + ), + ) + + usersRequestsCounts = await Promise.all( + overseerrUserIds.map(async (overseerrId) => { + if (overseerrId) { + const userTotal = await fetchOverseerrStats( + `user/${overseerrId}/requests`, + after, + ...(before ? [before] : []), + ) + + return { + requests: userTotal.length, + } + } + }), + ) + } + + const usersPlaysAndDurations = await Promise.all( + listedUsers.map(async (user) => { + let moviesPlaysCount = 0 + let showsPlaysCount = 0 + let audioPlaysCount = 0 + + for (const movieLib of moviesLib) { + const userMovies = await fetchTautulli<{ recordsFiltered: number }>( + 'get_history', + { + user_id: user.user_id, + after: after, + section_id: movieLib.section_id, + ...(before && { before }), + }, + ) + + moviesPlaysCount += userMovies?.response?.data?.recordsFiltered || 0 + } + + for (const showLib of showsLib) { + const userShows = await fetchTautulli<{ recordsFiltered: number }>( + 'get_history', + { + user_id: user.user_id, + after: after, + section_id: showLib.section_id, + ...(before && { before }), + }, + ) + + showsPlaysCount += userShows?.response?.data?.recordsFiltered || 0 + } + + for (const audioLibItem of audioLib) { + const userAudio = await fetchTautulli<{ recordsFiltered: number }>( + 'get_history', + { + user_id: user.user_id, + after: after, + section_id: audioLibItem.section_id, + ...(before && { before }), + }, + ) + + audioPlaysCount += userAudio?.response?.data?.recordsFiltered || 0 + } + + return { + movies_plays_count: moviesPlaysCount, + shows_plays_count: showsPlaysCount, + audio_plays_count: audioPlaysCount, + } + }), + ) + + listedUsers.map((user, i) => { + user.requests = usersRequestsCounts[i]?.requests || 0 + user.movies_plays_count = usersPlaysAndDurations[i].movies_plays_count + user.shows_plays_count = usersPlaysAndDurations[i].shows_plays_count + user.audio_plays_count = usersPlaysAndDurations[i].audio_plays_count + }) + + if (settings.general.isAnonymized) { + return anonymizeUsers(listedUsers, loggedInUserId) + } + + return listedUsers +} + +async function getStatsWithLoggedInUser( + userId: string, + users: TautulliItemRow[], + numberOfUsers: number, +) { + const loggedInUserRank = users.findIndex( + (user) => String(user.user_id) == userId, + ) + const loggedInUser = + users[loggedInUserRank] || (await getInactiveUserInTimePeriod(userId)) + + let slicedUsers = users.slice(0, numberOfUsers) + + if (loggedInUserRank === -1 || loggedInUserRank >= numberOfUsers) { + slicedUsers = users.slice(0, numberOfUsers - 1) + loggedInUser.rank = + loggedInUserRank === -1 ? users.length : loggedInUserRank + slicedUsers.push(loggedInUser) + } + + return slicedUsers +} + +async function getInactiveUserInTimePeriod( + id: string, +): Promise { + const user = await fetchTautulli('get_user', { + user_id: id, + }) + const nonActive = user?.response?.data ?? ({} as TautulliItemRow) + + nonActive.total_duration = 0 + + return nonActive +} 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, + } + }) +}