Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/using-seerr/plex/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Seerr provides integration features that connect with your Plex media server to
## Available Features

- [Watchlist Auto Request](./watchlist-auto-request) - Automatically request media from your Plex Watchlist
- [Recently Added Processing](#recently-added-processing) - Process newly added Plex media from external tools
- More features coming soon!

## Prerequisites
Expand All @@ -34,3 +35,33 @@ To use any Plex integration features, you must have logged into Seerr at least o
:::note Server Configuration
Plex server configuration is handled by your administrator. If you cannot log in with your Plex account, contact your administrator to verify the server setup.
:::

## Recently Added Processing

Seerr can process a recently added Plex item from an external tool such as Tautulli. Configure the tool to send a `POST` request to:

```text
https://seerr.example.com/api/v1/plex/recently-added
```

Include your Seerr API key as an HTTP header:

```text
X-Api-Key: YOUR_SEERR_API_KEY
```

The webhook body must be JSON and include the Plex rating key:

```json
{
"ratingKey": "12345"
}
```

For Tautulli recently added notifications, configure a webhook notification using this custom JSON body:

```json
{
"ratingKey": "{rating_key}"
}
```
35 changes: 35 additions & 0 deletions seerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2455,6 +2455,41 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/PlexSettings'
/plex/recently-added:
post:
summary: Process recently added Plex media
description: Processes a recently added Plex media item by rating key. This endpoint can be called by external tools such as Tautulli.
tags:
- plex
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
ratingKey:
type: string
required:
- ratingKey
responses:
'200':
description: Plex media was processed
content:
application/json:
schema:
type: object
properties:
ratingKey:
type: string
type:
type: string
title:
type: string
'400':
description: Plex rating key is missing
'500':
description: Plex media could not be processed
/settings/plex/library:
get:
summary: Get Plex libraries
Expand Down
5 changes: 4 additions & 1 deletion server/api/plexapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ interface PlexLibrariesResponse {
export interface PlexMetadata {
ratingKey: string;
parentRatingKey?: string;
grandparentRatingKey?: string;
guid: string;
type: 'movie' | 'show' | 'season';
parentGuid?: string;
grandparentGuid?: string;
type: 'movie' | 'show' | 'season' | 'episode';
title: string;
Guid: {
id: string;
Expand Down
28 changes: 28 additions & 0 deletions server/lib/scanners/plex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,34 @@ class PlexScanner
}
}

public async processRatingKey(ratingKey: string): Promise<PlexMetadata> {
const userRepository = getRepository(User);
const admin = await userRepository.findOne({
select: { id: true, plexToken: true },
where: { id: 1 },
});

if (!admin) {
throw new Error('No admin configured. Plex media processing skipped.');
}

const settings = getSettings();
this.plexClient = new PlexAPI({ plexToken: admin.plexToken });
this.libraries = settings.plex.libraries.filter(
(library) => library.enabled
);

const hasHama = await this.hasHamaAgent();
if (hasHama) {
await animeList.sync();
}

const metadata = await this.plexClient.getMetadata(ratingKey);
await this.processItem(metadata);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
return metadata;
}

private async paginateLibrary(
library: Library,
{ start = 0, sessionId }: { start?: number; sessionId: string }
Expand Down
2 changes: 2 additions & 0 deletions server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import issueCommentRoutes from './issueComment';
import mediaRoutes from './media';
import movieRoutes from './movie';
import personRoutes from './person';
import plexRoutes from './plex';
import requestRoutes from './request';
import searchRoutes from './search';
import serviceRoutes from './service';
Expand Down Expand Up @@ -148,6 +149,7 @@ router.get(
}
);
router.use('/settings', isAuthenticated(Permission.ADMIN), settingsRoutes);
router.use('/plex', isAuthenticated(Permission.ADMIN), plexRoutes);
router.use('/search', isAuthenticated(), searchRoutes);
router.use('/discover', isAuthenticated(), discoverRoutes);
router.use('/request', isAuthenticated(), requestRoutes);
Expand Down
39 changes: 39 additions & 0 deletions server/routes/plex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { plexRecentScanner } from '@server/lib/scanners/plex';
import logger from '@server/logger';
import { Router } from 'express';

const plexRoutes = Router();

plexRoutes.post('/recently-added', async (req, res, next) => {
const ratingKey = req.body?.ratingKey;

if (!ratingKey || typeof ratingKey !== 'string') {
return next({
status: 400,
message: 'Plex ratingKey is required.',
});
}

try {
const metadata = await plexRecentScanner.processRatingKey(ratingKey);

return res.status(200).json({
ratingKey: metadata.ratingKey,
type: metadata.type,
title: metadata.title,
});
} catch (e) {
logger.error('Failed to process pushed Plex recently added media', {
label: 'Plex Scan',
ratingKey,
errorMessage: e.message,
});

return next({
status: 500,
message: 'Unable to process Plex media.',
});
}
});

export default plexRoutes;
Loading