diff --git a/server/api/plextv.ts b/server/api/plextv.ts index 1d474ac40..2fc4523a8 100644 --- a/server/api/plextv.ts +++ b/server/api/plextv.ts @@ -2,6 +2,7 @@ import type { PlexDevice } from '@server/interfaces/api/plexInterfaces'; import cacheManager from '@server/lib/cache'; import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; +import { randomUUID } from 'node:crypto'; import xml2js from 'xml2js'; import ExternalAPI from './externalapi'; @@ -363,6 +364,24 @@ class PlexTvAPI extends ExternalAPI { }; } } + + public async pingToken() { + try { + const response = await this.axios.get('/api/v2/ping', { + headers: { + 'X-Plex-Client-Identifier': randomUUID(), + }, + }); + if (!response?.data?.pong) { + throw new Error('No pong response'); + } + } catch (e) { + logger.error('Failed to ping token', { + label: 'Plex Refresh Token', + errorMessage: e.message, + }); + } + } } export default PlexTvAPI; diff --git a/server/job/schedule.ts b/server/job/schedule.ts index fea1f53d0..bc693dd9b 100644 --- a/server/job/schedule.ts +++ b/server/job/schedule.ts @@ -1,6 +1,7 @@ import availabilitySync from '@server/lib/availabilitySync'; import downloadTracker from '@server/lib/downloadtracker'; import ImageProxy from '@server/lib/imageproxy'; +import refreshToken from '@server/lib/refreshToken'; import { plexFullScanner, plexRecentScanner } from '@server/lib/scanners/plex'; import { radarrScanner } from '@server/lib/scanners/radarr'; import { sonarrScanner } from '@server/lib/scanners/sonarr'; @@ -168,5 +169,19 @@ export const startJobs = (): void => { }), }); + scheduledJobs.push({ + id: 'plex-refresh-token', + name: 'Plex Refresh Token', + type: 'process', + interval: 'fixed', + cronSchedule: jobs['plex-refresh-token'].schedule, + job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => { + logger.info('Starting scheduled job: Plex Refresh Token', { + label: 'Jobs', + }); + refreshToken.run(); + }), + }); + logger.info('Scheduled jobs loaded', { label: 'Jobs' }); }; diff --git a/server/lib/refreshToken.ts b/server/lib/refreshToken.ts new file mode 100644 index 000000000..ac7bd3463 --- /dev/null +++ b/server/lib/refreshToken.ts @@ -0,0 +1,37 @@ +import PlexTvAPI from '@server/api/plextv'; +import { getRepository } from '@server/datasource'; +import { User } from '@server/entity/User'; +import logger from '@server/logger'; + +class RefreshToken { + public async run() { + const userRepository = getRepository(User); + + const users = await userRepository + .createQueryBuilder('user') + .addSelect('user.plexToken') + .where("user.plexToken != ''") + .getMany(); + + for (const user of users) { + await this.refreshUserToken(user); + } + } + + private async refreshUserToken(user: User) { + if (!user.plexToken) { + logger.warn('Skipping user refresh token for user without plex token', { + label: 'Plex Refresh Token', + user: user.displayName, + }); + return; + } + + const plexTvApi = new PlexTvAPI(user.plexToken); + plexTvApi.pingToken(); + } +} + +const refreshToken = new RefreshToken(); + +export default refreshToken; diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 6602f3104..63daf17f3 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -248,6 +248,7 @@ export type JobId = | 'plex-recently-added-scan' | 'plex-full-scan' | 'plex-watchlist-sync' + | 'plex-refresh-token' | 'radarr-scan' | 'sonarr-scan' | 'download-sync' @@ -409,6 +410,9 @@ class Settings { 'plex-watchlist-sync': { schedule: '0 */3 * * * *', }, + 'plex-refresh-token': { + schedule: '0 0 5 * * *', + }, 'radarr-scan': { schedule: '0 0 4 * * *', }, diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx index 72bed7c47..ca3edbd14 100644 --- a/src/components/Settings/SettingsJobsCache/index.tsx +++ b/src/components/Settings/SettingsJobsCache/index.tsx @@ -53,6 +53,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({ 'plex-recently-added-scan': 'Plex Recently Added Scan', 'plex-full-scan': 'Plex Full Library Scan', 'plex-watchlist-sync': 'Plex Watchlist Sync', + 'plex-refresh-token': 'Plex Refresh Token', 'availability-sync': 'Media Availability Sync', 'radarr-scan': 'Radarr Scan', 'sonarr-scan': 'Sonarr Scan', diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 10165c9e1..60a2d46bc 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -768,6 +768,7 @@ "components.Settings.SettingsJobsCache.nextexecution": "Next Execution", "components.Settings.SettingsJobsCache.plex-full-scan": "Plex Full Library Scan", "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex Recently Added Scan", + "components.Settings.SettingsJobsCache.plex-refresh-token": "Plex Refresh Token", "components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Watchlist Sync", "components.Settings.SettingsJobsCache.process": "Process", "components.Settings.SettingsJobsCache.radarr-scan": "Radarr Scan",