Skip to content

Commit

Permalink
auto tagging and collection creation works
Browse files Browse the repository at this point in the history
  • Loading branch information
manybothans committed May 16, 2023
1 parent 2c2cf04 commit 8c215e1
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 68 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
### ⚠️ UNDER DEVELOPMENT, INCOMPLETE ⚠️

# Plex Requester Collections

This app automatically creates Collections in Plex Media Server for the content that each user requests in the media request management system Overseerr. It will add a label to all TV Shows and Movies in Plex that a user has requested in Overseerr, using the label format `requester:plex_username`. It will also create Smart Collections for each user containing their requested items. The collections will have labels in the format `owner:plex_username`, which you can use to create sharing restrictions if desired, for example for personalized home screens.
Expand Down
60 changes: 30 additions & 30 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
{
"name": "plex-requester-collections",
"version": "0.0.1",
"description": "Node app to automatically create Plex Collections for content requested by users in Overseerr.",
"main": "dist/index.js",
"scripts": {
"start": "tsc && node dist/index.js",
"lint": "eslint . --ext .ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"plex",
"overseerr",
"homelab",
"selfhost"
],
"author": "Jess Latimer",
"license": "MIT",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.5",
"@typescript-eslint/parser": "^5.59.5",
"eslint": "^8.40.0",
"typescript": "^5.0.4"
},
"dependencies": {
"@types/lodash": "^4.14.194",
"@types/node": "^20.1.3",
"axios": "^1.4.0",
"dotenv": "^16.0.3",
"lodash": "^4.17.21"
}
"name": "plex-requester-collections",
"version": "1.0.0",
"description": "Node app to automatically create Plex Collections for content requested by users in Overseerr.",
"main": "dist/index.js",
"scripts": {
"start": "tsc && node dist/index.js",
"lint": "eslint . --ext .ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"plex",
"overseerr",
"homelab",
"selfhost"
],
"author": "Jess Latimer",
"license": "MIT",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.5",
"@typescript-eslint/parser": "^5.59.5",
"eslint": "^8.40.0",
"typescript": "^5.0.4"
},
"dependencies": {
"@types/lodash": "^4.14.194",
"@types/node": "^20.1.3",
"axios": "^1.4.0",
"dotenv": "^16.0.3",
"lodash": "^4.17.21"
}
}
80 changes: 66 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,81 @@ import _ from "lodash";
// ID of current library section.
const sectionId = parseInt(plexSections[i]?.key);

// Get all the media items, existing collections, and existing labels for this library section.
// Get all the media items and existing collections for this library section.
const mediaItems = await PlexAPI.getAllItems(sectionId);
const collections = await PlexAPI.getCollections(sectionId);
const labels = await PlexAPI.getLabels(sectionId);
let collections = await PlexAPI.getCollections(sectionId);

// Cycle through each media item in the library section, tag it with the requester.
for (let j = 0; j < mediaItems.length; j++) {
const mediaItem = mediaItems[j];
const request = getRequestForPlexMediaId(

// Does a requester entry exist for this media item?
const request = _.find(
requests,
parseInt(mediaItem?.ratingKey)
(item) => item?.media?.ratingKey === mediaItem?.ratingKey
);

// Bingo, this media item has a requester... Do the stuff.
if (mediaItem && request) {
// Init some values we're going to need.
const mediaId = parseInt(mediaItem.ratingKey);
const plexUsername = request?.requestedBy?.plexUsername;

// Tag the media item.
const mediaLabelValue = "requester:" + plexUsername;
const result = await PlexAPI.addLabelToItem(
sectionId,
PlexAPI.getPlexTypeCode(sectionType),
mediaId,
mediaLabelValue
);

// This is what the smart collection should be called.
const collectionTitle =
sectionType == "movie"
? "Movies Requested by " + plexUsername
: "TV Shows Requested by " + plexUsername;

// Does the smart collection already exist?
const collection = _.find(
collections,
(item) => item?.title === collectionTitle
);
// If collection exists with this title, assume it's set up correctly and we don't need to do anything else.
// If collection does not exist with this title, create it and tag is with owner label.
if (!collection) {
// Get the numberic ID of the label we're using right now.
const mediaLabelKey = await PlexAPI.getKeyForLabel(
sectionId,
mediaLabelValue
);

// Create the new smart collection
const createColResult =
await PlexAPI.createSmartCollection({
sectionId: sectionId,
title: collectionTitle,
titleSort: "zzz_" + collectionTitle, // TO DO Move prefix to env option.
itemType: PlexAPI.getPlexTypeCode(sectionType),
sort: "addedAt%3Adesc", //date added descending
query: "label=" + mediaLabelKey
});
// Only continue if creating the collection seems to have worked.
if (createColResult) {
const labelColResult = await PlexAPI.addLabelToItem(
sectionId,
PlexAPI.getPlexTypeCode(createColResult.type),
parseInt(createColResult.ratingKey),
"owner:" + plexUsername
);
}

// Update list of collections we're working with now that we've added one.
collections = await PlexAPI.getCollections(sectionId);
}

console.log(
`${mediaItem.title} requested by ${request?.requestedBy?.displayName}`
`${mediaItem.title} requested by ${plexUsername}`
);
}
}
Expand All @@ -45,11 +105,3 @@ import _ from "lodash";
}
}
})();

const getRequestForPlexMediaId = function (requests, plexId) {
const result = _.find(
requests,
(element) => parseInt(element?.media?.ratingKey) === plexId
);
return result;
};
45 changes: 23 additions & 22 deletions src/plex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ type PlexCollectionOptions = {
sectionId: number;
title: string;
titleSort?: string;
mediaIds?: Array<number>;
itemType: number;
sort?: string;
query: string;
Expand Down Expand Up @@ -258,7 +257,17 @@ const PlexAPI = {
// TO DO This one was kind of a finicky query. Just copying their exact string for now, will come back and simplify.
// Looks like you can't set titleSort in this command
const data = await this.callApi({
url: `/library/collections?type=${options.itemType}&title=${options.title}&smart=1&uri=server%3A%2F%2F${machineId}%2Fcom.plexapp.plugins.library%2Flibrary%2Fsections%2F${options.sectionId}%2Fall%3Ftype%3D${options.itemType}%26sort%3D${options.sort}%26${options.query}&sectionId=${options.sectionId}`,
url: `/library/collections?type=${
options.itemType
}&title=${encodeURIComponent(
options.title
)}&smart=1&uri=server%3A%2F%2F${machineId}%2Fcom.plexapp.plugins.library%2Flibrary%2Fsections%2F${
options.sectionId
}%2Fall%3Ftype%3D${options.itemType}%26sort%3D${
options.sort
}%26${encodeURIComponent(options.query)}&sectionId=${
options.sectionId
}`,
method: "post"
});

Expand All @@ -269,6 +278,9 @@ const PlexAPI = {
? data.MediaContainer.Metadata[0].ratingKey
: undefined;

// If key can't be found, get out of here cause next call might make wrong changes.
if (!collectionKey) return undefined;

// Update the collection's titleSort field.
const result = await this.updateItemDetails(
options.sectionId,
Expand All @@ -282,8 +294,8 @@ const PlexAPI = {
this.debug(result);
}

this.debug(data?.MediaContainer?.Metadata);
return data?.MediaContainer?.Metadata;
this.debug(data?.MediaContainer?.Metadata[0]);
return data?.MediaContainer?.Metadata[0];
},
// The API command described here can return the information for each account setup on the Plex server.
getAccounts: async function () {
Expand Down Expand Up @@ -336,28 +348,17 @@ const PlexAPI = {
this.debug(labelKey);
return labelKey;
},
// Alias helper funtion to simplify adding labels to movies.
addLabelToMovie: async function (
sectionId: number,
movieId: number,
label: string
) {
return this.updateItemDetails(sectionId, movieId, {
"label[0].tag.tag": encodeURIComponent(label),
"label.locked": 1,
type: PlexTypes.MOVIE
});
},
// Alias helper funtion to simplify adding labels to collecitons.
addLabelToCollection: async function (
// Alias helper funtion to simplify adding labels to plex items (Movies, Shows, Collections, etc.).
addLabelToItem: async function (
sectionId: number,
collectionId: number,
itemType: number,
itemId: number,
label: string
) {
return this.updateItemDetails(sectionId, collectionId, {
"label[0].tag.tag": encodeURIComponent(label),
return this.updateItemDetails(sectionId, itemId, {
"label[0].tag.tag": label,
"label.locked": 1,
type: PlexTypes.COLLECTION
type: itemType
});
},
// Returns an array of all the top-level media items (Movies or TV Shows) in a given Section AKA Library.
Expand Down

0 comments on commit 8c215e1

Please sign in to comment.