Features: - Server: Get identity, info, preferences, sessions, butler tasks - Library: Get all, get contents, recently added, on deck, refresh, analyze - Media: Get, update, delete, refresh, mark watched/unwatched - Playlist: CRUD operations, add/remove items - Collection: CRUD operations, add/remove items - Search: Global and library-specific search - Session: Get active sessions, history, terminate Includes CI/CD pipeline for automatic npm publishing on release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint --if-present
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: Publish to npm
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Publish to npm
|
||||||
|
run: npm publish --access public
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# n8n-nodes-plex-media-server
|
||||||
|
|
||||||
|
This is an n8n community node that lets you interact with the Plex Media Server API in your n8n workflows.
|
||||||
|
|
||||||
|
[Plex](https://www.plex.tv/) is a media server that organizes video, music, and photos from personal media libraries and streams them to smart TVs, streaming boxes, and mobile devices.
|
||||||
|
|
||||||
|
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install n8n-nodes-plex-media-server
|
||||||
|
```
|
||||||
|
|
||||||
|
## Credentials
|
||||||
|
|
||||||
|
To use this node, you need to configure the following credentials:
|
||||||
|
|
||||||
|
- **Server URL**: The URL of your Plex Media Server (e.g., `http://localhost:32400`)
|
||||||
|
- **X-Plex-Token**: Your Plex authentication token
|
||||||
|
|
||||||
|
### Finding Your Plex Token
|
||||||
|
|
||||||
|
1. Sign in to your Plex account in a web browser
|
||||||
|
2. Open any media item and click "Get Info"
|
||||||
|
3. Click "View XML"
|
||||||
|
4. Look for `X-Plex-Token` in the URL
|
||||||
|
|
||||||
|
Or find it in your Plex preferences file:
|
||||||
|
- **Windows**: `%LOCALAPPDATA%\Plex Media Server\Preferences.xml`
|
||||||
|
- **macOS**: `~/Library/Application Support/Plex Media Server/Preferences.xml`
|
||||||
|
- **Linux**: `$PLEX_HOME/Library/Application Support/Plex Media Server/Preferences.xml`
|
||||||
|
|
||||||
|
## Resources & Operations
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- **Get Identity**: Get server identity information
|
||||||
|
- **Get Info**: Get server information
|
||||||
|
- **Get Preferences**: Get server preferences
|
||||||
|
- **Get Active Sessions**: Get all active playback sessions
|
||||||
|
- **Get Transcode Sessions**: Get all active transcode sessions
|
||||||
|
- **Get Butler Tasks**: Get scheduled butler tasks
|
||||||
|
- **Run Butler Task**: Run a specific butler task
|
||||||
|
- **Stop Butler Task**: Stop a specific butler task
|
||||||
|
|
||||||
|
### Library
|
||||||
|
- **Get All**: Get all library sections
|
||||||
|
- **Get**: Get a specific library section
|
||||||
|
- **Get Contents**: Get all items in a library section
|
||||||
|
- **Get Recently Added**: Get recently added items in a library
|
||||||
|
- **Get On Deck**: Get on deck items in a library
|
||||||
|
- **Refresh**: Refresh/scan a library section
|
||||||
|
- **Empty Trash**: Empty the trash for a library section
|
||||||
|
- **Analyze**: Analyze all items in a library section
|
||||||
|
|
||||||
|
### Media
|
||||||
|
- **Get**: Get metadata for a specific media item
|
||||||
|
- **Get Children**: Get children of a media item (e.g., episodes of a show)
|
||||||
|
- **Get Related**: Get related media items
|
||||||
|
- **Get Similar**: Get similar media items
|
||||||
|
- **Update**: Update metadata for a media item
|
||||||
|
- **Delete**: Delete a media item
|
||||||
|
- **Refresh**: Refresh metadata for a media item
|
||||||
|
- **Analyze**: Analyze a media item
|
||||||
|
- **Mark Watched**: Mark a media item as watched (scrobble)
|
||||||
|
- **Mark Unwatched**: Mark a media item as unwatched (unscrobble)
|
||||||
|
|
||||||
|
### Playlist
|
||||||
|
- **Get All**: Get all playlists
|
||||||
|
- **Get**: Get a specific playlist
|
||||||
|
- **Get Items**: Get items in a playlist
|
||||||
|
- **Create**: Create a new playlist
|
||||||
|
- **Update**: Update a playlist
|
||||||
|
- **Delete**: Delete a playlist
|
||||||
|
- **Add Items**: Add items to a playlist
|
||||||
|
- **Remove Items**: Remove items from a playlist
|
||||||
|
|
||||||
|
### Collection
|
||||||
|
- **Get All**: Get all collections in a library
|
||||||
|
- **Get**: Get a specific collection
|
||||||
|
- **Get Items**: Get items in a collection
|
||||||
|
- **Add Items**: Add items to a collection
|
||||||
|
- **Remove Items**: Remove items from a collection
|
||||||
|
- **Delete**: Delete a collection
|
||||||
|
|
||||||
|
### Search
|
||||||
|
- **Search**: Search across all libraries
|
||||||
|
- **Search in Library**: Search within a specific library
|
||||||
|
|
||||||
|
### Session
|
||||||
|
- **Get All**: Get all active playback sessions
|
||||||
|
- **Get History**: Get playback history
|
||||||
|
- **Terminate**: Terminate a playback session
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
- Requires n8n version 1.0.0 or later
|
||||||
|
- Tested with Plex Media Server 1.40.x
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Build the node
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Watch for changes during development
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Lint the code
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
IAuthenticateGeneric,
|
||||||
|
ICredentialTestRequest,
|
||||||
|
ICredentialType,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class PlexApi implements ICredentialType {
|
||||||
|
name = 'plexApi';
|
||||||
|
displayName = 'Plex API';
|
||||||
|
documentationUrl = 'https://developer.plex.tv/pms/';
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Server URL',
|
||||||
|
name: 'serverUrl',
|
||||||
|
type: 'string',
|
||||||
|
default: 'http://localhost:32400',
|
||||||
|
placeholder: 'http://localhost:32400',
|
||||||
|
description: 'The URL of your Plex Media Server',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'X-Plex-Token',
|
||||||
|
name: 'plexToken',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Your Plex authentication token. Find it in Plex settings or browser developer tools.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'X-Plex-Client-Identifier',
|
||||||
|
name: 'clientIdentifier',
|
||||||
|
type: 'string',
|
||||||
|
default: 'n8n-plex-node',
|
||||||
|
description: 'A unique identifier for this client',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'X-Plex-Product',
|
||||||
|
name: 'product',
|
||||||
|
type: 'string',
|
||||||
|
default: 'n8n',
|
||||||
|
description: 'The name of your application',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'X-Plex-Version',
|
||||||
|
name: 'version',
|
||||||
|
type: 'string',
|
||||||
|
default: '1.0.0',
|
||||||
|
description: 'The version of your application',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'X-Plex-Platform',
|
||||||
|
name: 'platform',
|
||||||
|
type: 'string',
|
||||||
|
default: 'Node.js',
|
||||||
|
description: 'The platform running your application',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
authenticate: IAuthenticateGeneric = {
|
||||||
|
type: 'generic',
|
||||||
|
properties: {
|
||||||
|
headers: {
|
||||||
|
'X-Plex-Token': '={{$credentials.plexToken}}',
|
||||||
|
'X-Plex-Client-Identifier': '={{$credentials.clientIdentifier}}',
|
||||||
|
'X-Plex-Product': '={{$credentials.product}}',
|
||||||
|
'X-Plex-Version': '={{$credentials.version}}',
|
||||||
|
'X-Plex-Platform': '={{$credentials.platform}}',
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test: ICredentialTestRequest = {
|
||||||
|
request: {
|
||||||
|
baseURL: '={{$credentials.serverUrl}}',
|
||||||
|
url: '/identity',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const { src, dest } = require('gulp');
|
||||||
|
|
||||||
|
function buildIcons() {
|
||||||
|
return src('nodes/**/*.{png,svg}').pipe(dest('dist/nodes'));
|
||||||
|
}
|
||||||
|
|
||||||
|
exports['build:icons'] = buildIcons;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './nodes/Plex/Plex.node';
|
||||||
|
export * from './credentials/PlexApi.credentials';
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IDataObject,
|
||||||
|
IHttpRequestMethods,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
JsonObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function plexApiRequest(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
method: IHttpRequestMethods,
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject = {},
|
||||||
|
qs: IDataObject = {},
|
||||||
|
): Promise<IDataObject | IDataObject[]> {
|
||||||
|
const credentials = await this.getCredentials('plexApi');
|
||||||
|
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method,
|
||||||
|
url: `${credentials.serverUrl}${endpoint}`,
|
||||||
|
headers: {
|
||||||
|
'X-Plex-Token': credentials.plexToken as string,
|
||||||
|
'X-Plex-Client-Identifier': credentials.clientIdentifier as string,
|
||||||
|
'X-Plex-Product': credentials.product as string,
|
||||||
|
'X-Plex-Version': credentials.version as string,
|
||||||
|
'X-Plex-Platform': credentials.platform as string,
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
qs,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(body).length > 0) {
|
||||||
|
options.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.helpers.httpRequest(options);
|
||||||
|
return response as IDataObject;
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function plexApiRequestAllItems(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
method: IHttpRequestMethods,
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject = {},
|
||||||
|
qs: IDataObject = {},
|
||||||
|
containerKey: string = 'MediaContainer',
|
||||||
|
itemsKey: string = 'Metadata',
|
||||||
|
): Promise<IDataObject[]> {
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
let responseData: IDataObject;
|
||||||
|
|
||||||
|
let start = 0;
|
||||||
|
const size = 100;
|
||||||
|
|
||||||
|
do {
|
||||||
|
qs['X-Plex-Container-Start'] = start;
|
||||||
|
qs['X-Plex-Container-Size'] = size;
|
||||||
|
|
||||||
|
responseData = (await plexApiRequest.call(this, method, endpoint, body, qs)) as IDataObject;
|
||||||
|
|
||||||
|
const container = responseData[containerKey] as IDataObject;
|
||||||
|
if (container && container[itemsKey]) {
|
||||||
|
const items = container[itemsKey] as IDataObject[];
|
||||||
|
returnData.push(...items);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalSize = (container?.totalSize as number) || 0;
|
||||||
|
|
||||||
|
if (returnData.length >= totalSize || !container || !container[itemsKey]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
start += size;
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPlexUrl(baseUrl: string, endpoint: string, params: IDataObject = {}): string {
|
||||||
|
const url = new URL(endpoint, baseUrl);
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
url.searchParams.append(key, String(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
@@ -0,0 +1,700 @@
|
|||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { plexApiRequest, plexApiRequestAllItems } from './GenericFunctions';
|
||||||
|
import {
|
||||||
|
serverOperations,
|
||||||
|
serverFields,
|
||||||
|
libraryOperations,
|
||||||
|
libraryFields,
|
||||||
|
mediaOperations,
|
||||||
|
mediaFields,
|
||||||
|
playlistOperations,
|
||||||
|
playlistFields,
|
||||||
|
searchOperations,
|
||||||
|
searchFields,
|
||||||
|
sessionOperations,
|
||||||
|
sessionFields,
|
||||||
|
collectionOperations,
|
||||||
|
collectionFields,
|
||||||
|
} from './descriptions';
|
||||||
|
|
||||||
|
export class Plex implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Plex',
|
||||||
|
name: 'plex',
|
||||||
|
icon: 'file:plex.svg',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Interact with Plex Media Server API',
|
||||||
|
defaults: {
|
||||||
|
name: 'Plex',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'plexApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Collection',
|
||||||
|
value: 'collection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Library',
|
||||||
|
value: 'library',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Media',
|
||||||
|
value: 'media',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Playlist',
|
||||||
|
value: 'playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Search',
|
||||||
|
value: 'search',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Server',
|
||||||
|
value: 'server',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Session',
|
||||||
|
value: 'session',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'library',
|
||||||
|
},
|
||||||
|
// Operations
|
||||||
|
...serverOperations,
|
||||||
|
...libraryOperations,
|
||||||
|
...mediaOperations,
|
||||||
|
...playlistOperations,
|
||||||
|
...searchOperations,
|
||||||
|
...sessionOperations,
|
||||||
|
...collectionOperations,
|
||||||
|
// Fields
|
||||||
|
...serverFields,
|
||||||
|
...libraryFields,
|
||||||
|
...mediaFields,
|
||||||
|
...playlistFields,
|
||||||
|
...searchFields,
|
||||||
|
...sessionFields,
|
||||||
|
...collectionFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
try {
|
||||||
|
let responseData: IDataObject | IDataObject[];
|
||||||
|
|
||||||
|
// ==================== SERVER ====================
|
||||||
|
if (resource === 'server') {
|
||||||
|
if (operation === 'getIdentity') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/identity');
|
||||||
|
} else if (operation === 'getInfo') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/');
|
||||||
|
} else if (operation === 'getPreferences') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/prefs');
|
||||||
|
} else if (operation === 'getActiveSessions') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/status/sessions');
|
||||||
|
} else if (operation === 'getTranscodeSessions') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/transcode/sessions');
|
||||||
|
} else if (operation === 'getButlerTasks') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/butler');
|
||||||
|
} else if (operation === 'runButlerTask') {
|
||||||
|
const taskName = this.getNodeParameter('taskName', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(this, 'POST', `/butler/${taskName}`);
|
||||||
|
} else if (operation === 'stopButlerTask') {
|
||||||
|
const taskName = this.getNodeParameter('taskName', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(this, 'DELETE', `/butler/${taskName}`);
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The operation "${operation}" is not supported for resource "${resource}"`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== LIBRARY ====================
|
||||||
|
else if (resource === 'library') {
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/library/sections');
|
||||||
|
} else if (operation === 'get') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', `/library/sections/${libraryKey}`);
|
||||||
|
} else if (operation === 'getContents') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
if (options.type) {
|
||||||
|
qs.type = options.type;
|
||||||
|
}
|
||||||
|
if (options.sort) {
|
||||||
|
qs.sort = options.sort;
|
||||||
|
}
|
||||||
|
if (options.unwatched) {
|
||||||
|
qs.unwatched = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await plexApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/all`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
qs['X-Plex-Container-Size'] = limit;
|
||||||
|
const response = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/all`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
const container = (response as IDataObject).MediaContainer as IDataObject;
|
||||||
|
responseData = (container?.Metadata as IDataObject[]) || [];
|
||||||
|
}
|
||||||
|
} else if (operation === 'getRecentlyAdded') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await plexApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/recentlyAdded`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
qs['X-Plex-Container-Size'] = limit;
|
||||||
|
const response = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/recentlyAdded`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
const container = (response as IDataObject).MediaContainer as IDataObject;
|
||||||
|
responseData = (container?.Metadata as IDataObject[]) || [];
|
||||||
|
}
|
||||||
|
} else if (operation === 'getOnDeck') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/onDeck`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'refresh') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
const refreshOptions = this.getNodeParameter('refreshOptions', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
if (refreshOptions.force) {
|
||||||
|
qs.force = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/refresh`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else if (operation === 'emptyTrash') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/library/sections/${libraryKey}/emptyTrash`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'analyze') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/library/sections/${libraryKey}/analyze`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The operation "${operation}" is not supported for resource "${resource}"`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== MEDIA ====================
|
||||||
|
else if (resource === 'media') {
|
||||||
|
const ratingKey = this.getNodeParameter('ratingKey', i) as string;
|
||||||
|
|
||||||
|
if (operation === 'get') {
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/metadata/${ratingKey}`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'getChildren') {
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/metadata/${ratingKey}/children`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'getRelated') {
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
if (options.limit) {
|
||||||
|
qs.count = options.limit;
|
||||||
|
}
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/metadata/${ratingKey}/related`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else if (operation === 'getSimilar') {
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
if (options.limit) {
|
||||||
|
qs.limit = options.limit;
|
||||||
|
}
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/metadata/${ratingKey}/similar`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else if (operation === 'update') {
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
if (updateFields.title) {
|
||||||
|
qs['title.value'] = updateFields.title;
|
||||||
|
}
|
||||||
|
if (updateFields.titleSort) {
|
||||||
|
qs['titleSort.value'] = updateFields.titleSort;
|
||||||
|
}
|
||||||
|
if (updateFields.originallyAvailableAt) {
|
||||||
|
qs['originallyAvailableAt.value'] = updateFields.originallyAvailableAt;
|
||||||
|
}
|
||||||
|
if (updateFields.studio) {
|
||||||
|
qs['studio.value'] = updateFields.studio;
|
||||||
|
}
|
||||||
|
if (updateFields.contentRating) {
|
||||||
|
qs['contentRating.value'] = updateFields.contentRating;
|
||||||
|
}
|
||||||
|
if (updateFields.summary) {
|
||||||
|
qs['summary.value'] = updateFields.summary;
|
||||||
|
}
|
||||||
|
if (updateFields.tagline) {
|
||||||
|
qs['tagline.value'] = updateFields.tagline;
|
||||||
|
}
|
||||||
|
if (updateFields.rating !== undefined) {
|
||||||
|
qs['rating.value'] = updateFields.rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/library/metadata/${ratingKey}`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else if (operation === 'delete') {
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'DELETE',
|
||||||
|
`/library/metadata/${ratingKey}`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'refresh') {
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/library/metadata/${ratingKey}/refresh`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'analyze') {
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/library/metadata/${ratingKey}/analyze`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'markWatched') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/:/scrobble', {}, {
|
||||||
|
key: ratingKey,
|
||||||
|
identifier: 'com.plexapp.plugins.library',
|
||||||
|
});
|
||||||
|
} else if (operation === 'markUnwatched') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/:/unscrobble', {}, {
|
||||||
|
key: ratingKey,
|
||||||
|
identifier: 'com.plexapp.plugins.library',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The operation "${operation}" is not supported for resource "${resource}"`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== PLAYLIST ====================
|
||||||
|
else if (resource === 'playlist') {
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
if (options.playlistType) {
|
||||||
|
qs.playlistType = options.playlistType;
|
||||||
|
}
|
||||||
|
if (options.smart) {
|
||||||
|
qs.smart = options.smart;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/playlists', {}, qs);
|
||||||
|
} else if (operation === 'get') {
|
||||||
|
const playlistRatingKey = this.getNodeParameter('playlistRatingKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/playlists/${playlistRatingKey}`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'getItems') {
|
||||||
|
const playlistRatingKey = this.getNodeParameter('playlistRatingKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/playlists/${playlistRatingKey}/items`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'create') {
|
||||||
|
const title = this.getNodeParameter('title', i) as string;
|
||||||
|
const playlistType = this.getNodeParameter('playlistType', i) as string;
|
||||||
|
const uri = this.getNodeParameter('uri', i) as string;
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(this, 'POST', '/playlists', {}, {
|
||||||
|
title,
|
||||||
|
type: playlistType,
|
||||||
|
uri,
|
||||||
|
smart: '0',
|
||||||
|
});
|
||||||
|
} else if (operation === 'update') {
|
||||||
|
const playlistRatingKey = this.getNodeParameter('playlistRatingKey', i) as string;
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
if (updateFields.title) {
|
||||||
|
qs.title = updateFields.title;
|
||||||
|
}
|
||||||
|
if (updateFields.summary) {
|
||||||
|
qs.summary = updateFields.summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/playlists/${playlistRatingKey}`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else if (operation === 'delete') {
|
||||||
|
const playlistRatingKey = this.getNodeParameter('playlistRatingKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'DELETE',
|
||||||
|
`/playlists/${playlistRatingKey}`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'addItems') {
|
||||||
|
const playlistRatingKey = this.getNodeParameter('playlistRatingKey', i) as string;
|
||||||
|
const itemUri = this.getNodeParameter('itemUri', i) as string;
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/playlists/${playlistRatingKey}/items`,
|
||||||
|
{},
|
||||||
|
{ uri: itemUri },
|
||||||
|
);
|
||||||
|
} else if (operation === 'removeItems') {
|
||||||
|
const playlistRatingKey = this.getNodeParameter('playlistRatingKey', i) as string;
|
||||||
|
const playlistItemId = this.getNodeParameter('playlistItemId', i) as string;
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'DELETE',
|
||||||
|
`/playlists/${playlistRatingKey}/items/${playlistItemId}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The operation "${operation}" is not supported for resource "${resource}"`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== SEARCH ====================
|
||||||
|
else if (resource === 'search') {
|
||||||
|
if (operation === 'search') {
|
||||||
|
const query = this.getNodeParameter('query', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const qs: IDataObject = { query };
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await plexApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'/search',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
qs['X-Plex-Container-Size'] = limit;
|
||||||
|
const response = await plexApiRequest.call(this, 'GET', '/search', {}, qs);
|
||||||
|
const container = (response as IDataObject).MediaContainer as IDataObject;
|
||||||
|
responseData = (container?.Metadata as IDataObject[]) || [];
|
||||||
|
}
|
||||||
|
} else if (operation === 'searchInLibrary') {
|
||||||
|
const query = this.getNodeParameter('query', i) as string;
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const qs: IDataObject = { query };
|
||||||
|
|
||||||
|
if (options.type) {
|
||||||
|
qs.type = options.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await plexApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/search`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
qs['X-Plex-Container-Size'] = limit;
|
||||||
|
const response = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/search`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
const container = (response as IDataObject).MediaContainer as IDataObject;
|
||||||
|
responseData = (container?.Metadata as IDataObject[]) || [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The operation "${operation}" is not supported for resource "${resource}"`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== SESSION ====================
|
||||||
|
else if (resource === 'session') {
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
responseData = await plexApiRequest.call(this, 'GET', '/status/sessions');
|
||||||
|
} else if (operation === 'getHistory') {
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
if (options.sort) {
|
||||||
|
qs.sort = options.sort;
|
||||||
|
}
|
||||||
|
if (options.accountID) {
|
||||||
|
qs.accountID = options.accountID;
|
||||||
|
}
|
||||||
|
if (options.librarySectionID) {
|
||||||
|
qs.librarySectionID = options.librarySectionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await plexApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'/status/sessions/history/all',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
qs['X-Plex-Container-Size'] = limit;
|
||||||
|
const response = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'/status/sessions/history/all',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
const container = (response as IDataObject).MediaContainer as IDataObject;
|
||||||
|
responseData = (container?.Metadata as IDataObject[]) || [];
|
||||||
|
}
|
||||||
|
} else if (operation === 'terminate') {
|
||||||
|
const sessionId = this.getNodeParameter('sessionId', i) as string;
|
||||||
|
const reason = this.getNodeParameter('reason', i, '') as string;
|
||||||
|
const qs: IDataObject = { sessionId };
|
||||||
|
|
||||||
|
if (reason) {
|
||||||
|
qs.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'/status/sessions/terminate',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The operation "${operation}" is not supported for resource "${resource}"`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== COLLECTION ====================
|
||||||
|
else if (resource === 'collection') {
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
const libraryKey = this.getNodeParameter('libraryKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/sections/${libraryKey}/collections`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'get') {
|
||||||
|
const collectionRatingKey = this.getNodeParameter('collectionRatingKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/collections/${collectionRatingKey}`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'getItems') {
|
||||||
|
const collectionRatingKey = this.getNodeParameter('collectionRatingKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/library/collections/${collectionRatingKey}/children`,
|
||||||
|
);
|
||||||
|
} else if (operation === 'addItems') {
|
||||||
|
const collectionRatingKey = this.getNodeParameter('collectionRatingKey', i) as string;
|
||||||
|
const machineIdentifier = this.getNodeParameter('machineIdentifier', i) as string;
|
||||||
|
const itemRatingKeys = this.getNodeParameter('itemRatingKeys', i) as string;
|
||||||
|
|
||||||
|
const uri = itemRatingKeys
|
||||||
|
.split(',')
|
||||||
|
.map((key) => key.trim())
|
||||||
|
.map(
|
||||||
|
(key) =>
|
||||||
|
`server://${machineIdentifier}/com.plexapp.plugins.library/library/metadata/${key}`,
|
||||||
|
)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/library/collections/${collectionRatingKey}/items`,
|
||||||
|
{},
|
||||||
|
{ uri },
|
||||||
|
);
|
||||||
|
} else if (operation === 'removeItems') {
|
||||||
|
const collectionRatingKey = this.getNodeParameter('collectionRatingKey', i) as string;
|
||||||
|
const itemRatingKeys = this.getNodeParameter('itemRatingKeys', i) as string;
|
||||||
|
|
||||||
|
const excludeKeys = itemRatingKeys
|
||||||
|
.split(',')
|
||||||
|
.map((key) => key.trim())
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PUT',
|
||||||
|
`/library/collections/${collectionRatingKey}/items/delete`,
|
||||||
|
{},
|
||||||
|
{ excludeRatingKeys: excludeKeys },
|
||||||
|
);
|
||||||
|
} else if (operation === 'delete') {
|
||||||
|
const collectionRatingKey = this.getNodeParameter('collectionRatingKey', i) as string;
|
||||||
|
responseData = await plexApiRequest.call(
|
||||||
|
this,
|
||||||
|
'DELETE',
|
||||||
|
`/library/collections/${collectionRatingKey}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The operation "${operation}" is not supported for resource "${resource}"`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The resource "${resource}" is not supported`,
|
||||||
|
{ itemIndex: i },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle array or single response
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: (error as Error).message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [returnData];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const collectionOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['collection'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all collections in a library',
|
||||||
|
action: 'Get all collections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a specific collection',
|
||||||
|
action: 'Get a collection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Items',
|
||||||
|
value: 'getItems',
|
||||||
|
description: 'Get items in a collection',
|
||||||
|
action: 'Get collection items',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Add Items',
|
||||||
|
value: 'addItems',
|
||||||
|
description: 'Add items to a collection',
|
||||||
|
action: 'Add items to collection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Remove Items',
|
||||||
|
value: 'removeItems',
|
||||||
|
description: 'Remove items from a collection',
|
||||||
|
action: 'Remove items from collection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a collection',
|
||||||
|
action: 'Delete a collection',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const collectionFields: INodeProperties[] = [
|
||||||
|
// Library Key for getAll
|
||||||
|
{
|
||||||
|
displayName: 'Library Key',
|
||||||
|
name: 'libraryKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['collection'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The key/ID of the library section',
|
||||||
|
},
|
||||||
|
// Collection Rating Key
|
||||||
|
{
|
||||||
|
displayName: 'Collection Rating Key',
|
||||||
|
name: 'collectionRatingKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['collection'],
|
||||||
|
operation: ['get', 'getItems', 'addItems', 'removeItems', 'delete'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The rating key (ID) of the collection',
|
||||||
|
},
|
||||||
|
// Machine Identifier (for add/remove items)
|
||||||
|
{
|
||||||
|
displayName: 'Machine Identifier',
|
||||||
|
name: 'machineIdentifier',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['collection'],
|
||||||
|
operation: ['addItems', 'removeItems'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The machine identifier of the Plex server (found in server identity)',
|
||||||
|
},
|
||||||
|
// Item Rating Keys for add/remove
|
||||||
|
{
|
||||||
|
displayName: 'Item Rating Keys',
|
||||||
|
name: 'itemRatingKeys',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['collection'],
|
||||||
|
operation: ['addItems', 'removeItems'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: '12345,67890',
|
||||||
|
description: 'Comma-separated list of rating keys (IDs) of items to add or remove',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const libraryOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['library'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all library sections',
|
||||||
|
action: 'Get all libraries',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a specific library section',
|
||||||
|
action: 'Get a library',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Contents',
|
||||||
|
value: 'getContents',
|
||||||
|
description: 'Get all items in a library section',
|
||||||
|
action: 'Get library contents',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Recently Added',
|
||||||
|
value: 'getRecentlyAdded',
|
||||||
|
description: 'Get recently added items in a library',
|
||||||
|
action: 'Get recently added items',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get On Deck',
|
||||||
|
value: 'getOnDeck',
|
||||||
|
description: 'Get on deck items in a library',
|
||||||
|
action: 'Get on deck items',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Refresh',
|
||||||
|
value: 'refresh',
|
||||||
|
description: 'Refresh/scan a library section',
|
||||||
|
action: 'Refresh a library',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Empty Trash',
|
||||||
|
value: 'emptyTrash',
|
||||||
|
description: 'Empty the trash for a library section',
|
||||||
|
action: 'Empty library trash',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Analyze',
|
||||||
|
value: 'analyze',
|
||||||
|
description: 'Analyze all items in a library section',
|
||||||
|
action: 'Analyze library',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const libraryFields: INodeProperties[] = [
|
||||||
|
// Library Key/ID
|
||||||
|
{
|
||||||
|
displayName: 'Library Key',
|
||||||
|
name: 'libraryKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['library'],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
'getContents',
|
||||||
|
'getRecentlyAdded',
|
||||||
|
'getOnDeck',
|
||||||
|
'refresh',
|
||||||
|
'emptyTrash',
|
||||||
|
'analyze',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The key/ID of the library section',
|
||||||
|
},
|
||||||
|
// Return All for getContents
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['library'],
|
||||||
|
operation: ['getContents', 'getRecentlyAdded'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['library'],
|
||||||
|
operation: ['getContents', 'getRecentlyAdded'],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 50,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
},
|
||||||
|
// Additional Options for getContents
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['library'],
|
||||||
|
operation: ['getContents'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Type',
|
||||||
|
name: 'type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Movie',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Show',
|
||||||
|
value: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Season',
|
||||||
|
value: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Episode',
|
||||||
|
value: '4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Trailer',
|
||||||
|
value: '5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Comic',
|
||||||
|
value: '6',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Person',
|
||||||
|
value: '7',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Artist',
|
||||||
|
value: '8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Album',
|
||||||
|
value: '9',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Track',
|
||||||
|
value: '10',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Photo Album',
|
||||||
|
value: '11',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Picture',
|
||||||
|
value: '12',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Photo',
|
||||||
|
value: '13',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clip',
|
||||||
|
value: '14',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Playlist Item',
|
||||||
|
value: '15',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Filter by media type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sort',
|
||||||
|
name: 'sort',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Sort order (e.g., "titleSort:asc", "addedAt:desc")',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Unwatched Only',
|
||||||
|
name: 'unwatched',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to only return unwatched items',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Refresh Options
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'refreshOptions',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['library'],
|
||||||
|
operation: ['refresh'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Force',
|
||||||
|
name: 'force',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to force a full refresh of all metadata',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const mediaOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['media'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get metadata for a specific media item',
|
||||||
|
action: 'Get media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Children',
|
||||||
|
value: 'getChildren',
|
||||||
|
description: 'Get children of a media item (e.g., episodes of a show)',
|
||||||
|
action: 'Get media children',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Related',
|
||||||
|
value: 'getRelated',
|
||||||
|
description: 'Get related media items',
|
||||||
|
action: 'Get related media',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Similar',
|
||||||
|
value: 'getSimilar',
|
||||||
|
description: 'Get similar media items',
|
||||||
|
action: 'Get similar media',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update metadata for a media item',
|
||||||
|
action: 'Update media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a media item',
|
||||||
|
action: 'Delete media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Refresh',
|
||||||
|
value: 'refresh',
|
||||||
|
description: 'Refresh metadata for a media item',
|
||||||
|
action: 'Refresh media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Analyze',
|
||||||
|
value: 'analyze',
|
||||||
|
description: 'Analyze a media item',
|
||||||
|
action: 'Analyze media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mark Watched',
|
||||||
|
value: 'markWatched',
|
||||||
|
description: 'Mark a media item as watched (scrobble)',
|
||||||
|
action: 'Mark as watched',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mark Unwatched',
|
||||||
|
value: 'markUnwatched',
|
||||||
|
description: 'Mark a media item as unwatched (unscrobble)',
|
||||||
|
action: 'Mark as unwatched',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'get',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const mediaFields: INodeProperties[] = [
|
||||||
|
// Rating Key (Media ID)
|
||||||
|
{
|
||||||
|
displayName: 'Rating Key',
|
||||||
|
name: 'ratingKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['media'],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
'getChildren',
|
||||||
|
'getRelated',
|
||||||
|
'getSimilar',
|
||||||
|
'update',
|
||||||
|
'delete',
|
||||||
|
'refresh',
|
||||||
|
'analyze',
|
||||||
|
'markWatched',
|
||||||
|
'markUnwatched',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The rating key (ID) of the media item',
|
||||||
|
},
|
||||||
|
// Update Fields
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['media'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The title of the media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sort Title',
|
||||||
|
name: 'titleSort',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The sort title of the media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Originally Available At',
|
||||||
|
name: 'originallyAvailableAt',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The original release date (YYYY-MM-DD)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Studio',
|
||||||
|
name: 'studio',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The studio that produced the media',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Rating',
|
||||||
|
name: 'contentRating',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The content rating (e.g., PG-13, TV-MA)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Summary',
|
||||||
|
name: 'summary',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The summary/description of the media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tagline',
|
||||||
|
name: 'tagline',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The tagline of the media item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Rating',
|
||||||
|
name: 'rating',
|
||||||
|
type: 'number',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: 10,
|
||||||
|
numberPrecision: 1,
|
||||||
|
},
|
||||||
|
default: 0,
|
||||||
|
description: 'The rating of the media item (0-10)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Options for related/similar
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['media'],
|
||||||
|
operation: ['getRelated', 'getSimilar'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 100,
|
||||||
|
},
|
||||||
|
default: 10,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const playlistOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all playlists',
|
||||||
|
action: 'Get all playlists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a specific playlist',
|
||||||
|
action: 'Get a playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Items',
|
||||||
|
value: 'getItems',
|
||||||
|
description: 'Get items in a playlist',
|
||||||
|
action: 'Get playlist items',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new playlist',
|
||||||
|
action: 'Create a playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a playlist',
|
||||||
|
action: 'Update a playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a playlist',
|
||||||
|
action: 'Delete a playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Add Items',
|
||||||
|
value: 'addItems',
|
||||||
|
description: 'Add items to a playlist',
|
||||||
|
action: 'Add items to playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Remove Items',
|
||||||
|
value: 'removeItems',
|
||||||
|
description: 'Remove items from a playlist',
|
||||||
|
action: 'Remove items from playlist',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const playlistFields: INodeProperties[] = [
|
||||||
|
// Playlist Rating Key
|
||||||
|
{
|
||||||
|
displayName: 'Playlist Rating Key',
|
||||||
|
name: 'playlistRatingKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['get', 'getItems', 'update', 'delete', 'addItems', 'removeItems'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The rating key (ID) of the playlist',
|
||||||
|
},
|
||||||
|
// Create Playlist Fields
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The title of the new playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Playlist Type',
|
||||||
|
name: 'playlistType',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Video',
|
||||||
|
value: 'video',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Audio',
|
||||||
|
value: 'audio',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Photo',
|
||||||
|
value: 'photo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'video',
|
||||||
|
description: 'The type of playlist to create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'URI',
|
||||||
|
name: 'uri',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'server://client-id/com.plexapp.plugins.library/library/metadata/12345',
|
||||||
|
description: 'The URI of the first item to add to the playlist',
|
||||||
|
},
|
||||||
|
// Update Fields
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The new title of the playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Summary',
|
||||||
|
name: 'summary',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The summary/description of the playlist',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Add/Remove Items
|
||||||
|
{
|
||||||
|
displayName: 'Item URI',
|
||||||
|
name: 'itemUri',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['addItems'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'server://client-id/com.plexapp.plugins.library/library/metadata/12345',
|
||||||
|
description: 'The URI of the item to add to the playlist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Playlist Item ID',
|
||||||
|
name: 'playlistItemId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['removeItems'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The playlist item ID to remove',
|
||||||
|
},
|
||||||
|
// Options for getAll
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['playlist'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Playlist Type',
|
||||||
|
name: 'playlistType',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Audio',
|
||||||
|
value: 'audio',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Photo',
|
||||||
|
value: 'photo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Video',
|
||||||
|
value: 'video',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Filter playlists by type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Smart',
|
||||||
|
name: 'smart',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Smart Only',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Regular Only',
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Filter by smart/regular playlists',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const searchOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Search',
|
||||||
|
value: 'search',
|
||||||
|
description: 'Search across all libraries',
|
||||||
|
action: 'Search all libraries',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Search in Library',
|
||||||
|
value: 'searchInLibrary',
|
||||||
|
description: 'Search within a specific library',
|
||||||
|
action: 'Search in library',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'search',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const searchFields: INodeProperties[] = [
|
||||||
|
// Query
|
||||||
|
{
|
||||||
|
displayName: 'Query',
|
||||||
|
name: 'query',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['search'],
|
||||||
|
operation: ['search', 'searchInLibrary'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The search query',
|
||||||
|
},
|
||||||
|
// Library Key for searchInLibrary
|
||||||
|
{
|
||||||
|
displayName: 'Library Key',
|
||||||
|
name: 'libraryKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['search'],
|
||||||
|
operation: ['searchInLibrary'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The key/ID of the library section to search in',
|
||||||
|
},
|
||||||
|
// Return All
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['search'],
|
||||||
|
operation: ['search', 'searchInLibrary'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['search'],
|
||||||
|
operation: ['search', 'searchInLibrary'],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 50,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
},
|
||||||
|
// Search Options
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['search'],
|
||||||
|
operation: ['searchInLibrary'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Type',
|
||||||
|
name: 'type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Movie',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Show',
|
||||||
|
value: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Season',
|
||||||
|
value: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Episode',
|
||||||
|
value: '4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Artist',
|
||||||
|
value: '8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Album',
|
||||||
|
value: '9',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Track',
|
||||||
|
value: '10',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Filter search results by media type',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const serverOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['server'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get Identity',
|
||||||
|
value: 'getIdentity',
|
||||||
|
description: 'Get server identity information',
|
||||||
|
action: 'Get server identity',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Info',
|
||||||
|
value: 'getInfo',
|
||||||
|
description: 'Get server information',
|
||||||
|
action: 'Get server info',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Preferences',
|
||||||
|
value: 'getPreferences',
|
||||||
|
description: 'Get server preferences',
|
||||||
|
action: 'Get server preferences',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Active Sessions',
|
||||||
|
value: 'getActiveSessions',
|
||||||
|
description: 'Get all active playback sessions',
|
||||||
|
action: 'Get active sessions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Transcode Sessions',
|
||||||
|
value: 'getTranscodeSessions',
|
||||||
|
description: 'Get all active transcode sessions',
|
||||||
|
action: 'Get transcode sessions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Butler Tasks',
|
||||||
|
value: 'getButlerTasks',
|
||||||
|
description: 'Get scheduled butler tasks',
|
||||||
|
action: 'Get butler tasks',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Run Butler Task',
|
||||||
|
value: 'runButlerTask',
|
||||||
|
description: 'Run a specific butler task',
|
||||||
|
action: 'Run butler task',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Stop Butler Task',
|
||||||
|
value: 'stopButlerTask',
|
||||||
|
description: 'Stop a specific butler task',
|
||||||
|
action: 'Stop butler task',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getInfo',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const serverFields: INodeProperties[] = [
|
||||||
|
// Butler Task Name
|
||||||
|
{
|
||||||
|
displayName: 'Task Name',
|
||||||
|
name: 'taskName',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['server'],
|
||||||
|
operation: ['runButlerTask', 'stopButlerTask'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Backup Database',
|
||||||
|
value: 'BackupDatabase',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Build Gracenote Collections',
|
||||||
|
value: 'BuildGracenoteCollections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Check For Updates',
|
||||||
|
value: 'CheckForUpdates',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clean Old Bundles',
|
||||||
|
value: 'CleanOldBundles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clean Old Cache Files',
|
||||||
|
value: 'CleanOldCacheFiles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Deep Media Analysis',
|
||||||
|
value: 'DeepMediaAnalysis',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Generate Auto Tags',
|
||||||
|
value: 'GenerateAutoTags',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Generate Chapter Thumbs',
|
||||||
|
value: 'GenerateChapterThumbs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Generate Media Index Files',
|
||||||
|
value: 'GenerateMediaIndexFiles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Optimize Database',
|
||||||
|
value: 'OptimizeDatabase',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Refresh Libraries',
|
||||||
|
value: 'RefreshLibraries',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Refresh Local Media',
|
||||||
|
value: 'RefreshLocalMedia',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Refresh Periodic Metadata',
|
||||||
|
value: 'RefreshPeriodicMetadata',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Upgrade Media Analysis',
|
||||||
|
value: 'UpgradeMediaAnalysis',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'RefreshLibraries',
|
||||||
|
description: 'The butler task to run or stop',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const sessionOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['session'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all active playback sessions',
|
||||||
|
action: 'Get all sessions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get History',
|
||||||
|
value: 'getHistory',
|
||||||
|
description: 'Get playback history',
|
||||||
|
action: 'Get playback history',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Terminate',
|
||||||
|
value: 'terminate',
|
||||||
|
description: 'Terminate a playback session',
|
||||||
|
action: 'Terminate session',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sessionFields: INodeProperties[] = [
|
||||||
|
// Session ID for terminate
|
||||||
|
{
|
||||||
|
displayName: 'Session ID',
|
||||||
|
name: 'sessionId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['session'],
|
||||||
|
operation: ['terminate'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The ID of the session to terminate',
|
||||||
|
},
|
||||||
|
// Terminate Options
|
||||||
|
{
|
||||||
|
displayName: 'Reason',
|
||||||
|
name: 'reason',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['session'],
|
||||||
|
operation: ['terminate'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The reason for terminating the session (shown to user)',
|
||||||
|
},
|
||||||
|
// History Options
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['session'],
|
||||||
|
operation: ['getHistory'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Sort',
|
||||||
|
name: 'sort',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Newest First',
|
||||||
|
value: 'viewedAt:desc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Oldest First',
|
||||||
|
value: 'viewedAt:asc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'viewedAt:desc',
|
||||||
|
description: 'Sort order for history',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Account ID',
|
||||||
|
name: 'accountID',
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
description: 'Filter by account ID (0 for all accounts)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Library Section ID',
|
||||||
|
name: 'librarySectionID',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Filter by library section',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['session'],
|
||||||
|
operation: ['getHistory'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['session'],
|
||||||
|
operation: ['getHistory'],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 50,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export * from './ServerDescription';
|
||||||
|
export * from './LibraryDescription';
|
||||||
|
export * from './MediaDescription';
|
||||||
|
export * from './PlaylistDescription';
|
||||||
|
export * from './SearchDescription';
|
||||||
|
export * from './SessionDescription';
|
||||||
|
export * from './CollectionDescription';
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="plexGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#E5A00D"/>
|
||||||
|
<stop offset="100%" style="stop-color:#CC7B19"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="100" height="100" rx="15" fill="#282828"/>
|
||||||
|
<polygon points="30,20 75,50 30,80" fill="url(#plexGradient)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 418 B |
Generated
+4879
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"name": "n8n-nodes-plex-media-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "n8n nodes for interacting with Plex Media Server API",
|
||||||
|
"keywords": [
|
||||||
|
"n8n-community-node-package",
|
||||||
|
"plex",
|
||||||
|
"media-server",
|
||||||
|
"streaming"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "",
|
||||||
|
"author": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": ""
|
||||||
|
},
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && gulp build:icons",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"format": "prettier nodes credentials --write",
|
||||||
|
"lint": "eslint nodes credentials package.json",
|
||||||
|
"lintfix": "eslint nodes credentials package.json --fix",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"n8n": {
|
||||||
|
"n8nNodesApiVersion": 1,
|
||||||
|
"credentials": [
|
||||||
|
"dist/credentials/PlexApi.credentials.js"
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
"dist/nodes/Plex/Plex.node.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.10.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-plugin-n8n-nodes-base": "^1.16.1",
|
||||||
|
"gulp": "^5.0.1",
|
||||||
|
"n8n-workflow": "^1.17.0",
|
||||||
|
"prettier": "^3.1.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"n8n-workflow": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"declaration": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": ".",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"nodes/**/*",
|
||||||
|
"credentials/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user