Skip to content

Commit

Permalink
Merge branch '1.x' of github.com:jorenn92/Maintainerr into 1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenn92 committed Jan 8, 2024
2 parents a5a975f + bbf76eb commit 6d0d3b5
Show file tree
Hide file tree
Showing 23 changed files with 676 additions and 478 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## [1.7.1](https://github.com/jorenn92/Maintainerr/compare/v1.7.0...v1.7.1) (2024-01-06)


### Bug Fixes

* **maintenance:** Extended the maintenance task with an action to remove orphaned collection objects ([f5826cc](https://github.com/jorenn92/Maintainerr/commit/f5826cc1f4e2997586ec1fa2cc704d7a85d01e8e))
* **plex:** Fixed an issue where fetching Plex users would fail if connection to plex.tv failed ([2458a8f](https://github.com/jorenn92/Maintainerr/commit/2458a8f62797d3122e2577493f73948c85ab4c9b))
* **rules:** Extended the Plex - rating rule ([ef95481](https://github.com/jorenn92/Maintainerr/commit/ef95481d8653d0d84bf3c00a92bf046b8abc50e6))
* **rules:** Fixed an issue where 'Plex - Present in amount of other collections' wouldn't work as expected ([1c4accd](https://github.com/jorenn92/Maintainerr/commit/1c4accdacf17738878cb60bde60bd176b3dc6426))
* **rules:** Fixed an issue where an item would be stuck inside the internal collection when it was removed manually ([1eae15f](https://github.com/jorenn92/Maintainerr/commit/1eae15f094ad081d20829db638f3cb44789f2137))
* **rules:** Fixed an issue where the "Plex - Last episode added at" rule order was affected by the library's Plex Episode Sorting setting ([67299c4](https://github.com/jorenn92/Maintainerr/commit/67299c4d6f94aa2f104694e4fab265fe4767af70))
* **rules:** Resolved an issue where a nullpointer could occur when fetching playlists. ([a0400b8](https://github.com/jorenn92/Maintainerr/commit/a0400b865999a986cfdcef6bb8603f8f0483e62b))

# [1.7.0](https://github.com/jorenn92/Maintainerr/compare/v1.6.10...v1.7.0) (2023-12-21)


Expand Down
11 changes: 7 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ RUN yarn run docs-generate && \
rm -rf ./docs

RUN \
case "${TARGETPLATFORM}" in ('linux/arm64' | 'linux/amd64') \
case "${TARGETPLATFORM}" in ('linux/arm64' | 'linux/amd64') \
yarn add --save --network-timeout 99999999 sharp \
;; \
esac
;; \
esac

RUN yarn --production --non-interactive --ignore-scripts --prefer-offline --frozen-lockfile --network-timeout 99999999

Expand All @@ -49,6 +49,9 @@ ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

ARG DEBUG=false
ENV DEBUG=${DEBUG}

EXPOSE 80

WORKDIR /opt
Expand All @@ -57,7 +60,7 @@ COPY --from=BUILDER /opt ./
COPY supervisord.conf /etc/supervisord.conf

RUN apk add supervisor && \
rm -rf /tmp/* && \
rm -rf /tmp/* && \
mkdir /opt/data

VOLUME [ "/opt/data" ]
Expand Down
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@
<a href="https://hub.docker.com/r/jorenn92/maintainerr"><img src="https://img.shields.io/docker/pulls/jorenn92/maintainerr" alt="Docker pulls" width="16.5%"></a>
</p>

<b>Maintainerr</b> makes managing your media easy.
- Do you hate being the janitor of your server?
- Do you have a lot of media that never gets watched?
- Do your users constantly request media, and let it sit there afterward never to be touched again?

<b>Maintainerr</b> makes managing your media easy.

- Do you hate being the janitor of your server?
- Do you have a lot of media that never gets watched?
- Do your users constantly request media, and let it sit there afterward never to be touched again?

If you answered yes to any of those questions.. You NEED <b>Maintainerr</b>.
It's a one-stop-shop for handling those outlying shows and movies that take up precious space on your server.

# Features

- Configure rules specific to your needs, based off of several available options from Plex, Overseerr, Radarr, and Sonarr.
- Manually add media to a collection, in case it's not included after rule execution. (one-off items that don't match a rule set)
- Selectively exclude media from being added to a collection, even if it matches a rule.
- Show a collection, containing rule matched media, on the Plex home screen for a specific duration before deletion. Think "Leaving soon".
- Show a collection, containing rule matched media, on the Plex home screen for a specific duration before deletion. Think "Leaving soon".
- Optionally, use a manual Plex collection, in case you don't want <b>Maintainerr</b> to add & remove Plex collections at will.
- Manage media straight from the collection within Plex. <b>Maintainerr</b> will sync and add or exclude media to/from the internal collection.

- Remove or unmonitor media from *arr
- Remove or unmonitor media from \*arr
- Clear requests from Overseerr
- Delete files from disk

Expand All @@ -39,14 +41,13 @@ Currently, <b>Maintainerr</b> supports rule parameters from these apps :
- Overseerr
- Radarr
- Sonarr

# Preview

# Preview

![image](https://github.com/ydkmlt84/Maintainerr/assets/2887742/8edabd29-ed98-4a9f-b41f-251b2e7d309c)
![image](https://github.com/ydkmlt84/Maintainerr/assets/2887742/c9916c90-4c67-4341-a0c1-32613518aa20)
![image](https://github.com/ydkmlt84/Maintainerr/assets/2887742/00740a16-e4fe-4429-a769-64ffcd568cba)



# Installation

Docker images for amd64, arm64 & armv7 are available under jorenn92/maintainerr. <br />
Expand All @@ -55,6 +56,7 @@ Data is saved within the container under /opt/data, it is recommended to tie a p
For more information, visit the [installation guide](docs/2-getting-started/1-installation/Installation.md) or navigate to \<maintainerr_url\>:\<port\>/docs after starting your <b>Maintainerr</b> container.

Docker run:

```Yaml
docker run -d \
--name maintainerr \
Expand All @@ -64,7 +66,9 @@ docker run -d \
--restart unless-stopped \
jorenn92/maintainerr
```
Docker-compose:

Docker-compose:

```Yaml
version: '3'

Expand All @@ -76,12 +80,14 @@ services:
- <persistent-local-volume>:/opt/data
environment:
- TZ=Europe/Brussels
# - DEBUG=true # uncomment to enable verbose logs
ports:
- 8154:80
restart: unless-stopped
```
# Credits
Maintainerr is heavily inspired by Overseerr. Some parts of Maintainerr's code are plain copies. Big thanks to the Overseerr team for creating and maintaining such an amazing app!
Please support them at https://github.com/sct/overseerr
1 change: 1 addition & 0 deletions docs/2-getting-started/1-installation/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ services:
- ./data:/opt/data
environment:
- TZ=Europe/Brussels
# - DEBUG=true # uncomment to enable verbose logs
ports:
- 8154:80
restart: unless-stopped
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "maintainerr",
"version": "1.7.0",
"version": "1.7.1",
"private": true,
"repository": {
"type": "git",
Expand Down
7 changes: 6 additions & 1 deletion server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule, {
logger:
process.env.NODE_ENV !== 'production' || process.env.DEBUG == 'true'
? ['log', 'debug', 'error', 'verbose', 'warn']
: ['error', 'warn', 'log'],
});
app.enableCors();
await app.listen(3001);
}
Expand Down
8 changes: 8 additions & 0 deletions server/src/modules/api/external-api/external-api.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Logger } from '@nestjs/common';
import axios, { AxiosInstance, RawAxiosRequestConfig } from 'axios';
import NodeCache from 'node-cache';

Expand Down Expand Up @@ -53,6 +54,7 @@ export class ExternalApiService {

return response.data;
} catch (err) {
Logger.debug(`GET request failed: ${err}`);
return undefined;
}
}
Expand All @@ -64,6 +66,7 @@ export class ExternalApiService {
try {
return (await this.axios.get<T>(endpoint, config)).data;
} catch (err) {
Logger.debug(`GET request failed: ${err}`);
return undefined;
}
}
Expand All @@ -76,6 +79,7 @@ export class ExternalApiService {
const response = await this.axios.delete<T>(endpoint, config);
return response.data;
} catch (err) {
Logger.debug(`DELETE request failed: ${err}`);
return undefined;
}
}
Expand All @@ -89,6 +93,7 @@ export class ExternalApiService {
const response = await this.axios.put<T>(endpoint, data, config);
return response.data;
} catch (err) {
Logger.debug(`PUT request failed: ${err}`);
return undefined;
}
}
Expand All @@ -102,6 +107,7 @@ export class ExternalApiService {
const response = await this.axios.post<T>(endpoint, data, config);
return response.data;
} catch (err) {
Logger.debug(`POST request failed: ${err}`);
return undefined;
}
}
Expand Down Expand Up @@ -138,6 +144,7 @@ export class ExternalApiService {

return response.data;
} catch (err) {
Logger.debug(`GET request failed: ${err}`);
return undefined;
}
}
Expand All @@ -153,6 +160,7 @@ export class ExternalApiService {

return `${this.baseUrl}${endpoint}${JSON.stringify(params)}`;
} catch (err) {
Logger.debug(`Failed serializing cache key: ${err}`);
return undefined;
}
}
Expand Down
100 changes: 100 additions & 0 deletions server/src/modules/api/lib/plexApi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import NodePlexAPI from 'plex-api';
import cacheManager, { Cache } from './cache';
import { AxiosHeaderValue } from 'axios';
import { PlexLibraryResponse } from '../plex-api/interfaces/library.interfaces';

// NodePlexApi wrapped with a cache
class PlexApi extends NodePlexAPI {
Expand All @@ -16,6 +17,57 @@ class PlexApi extends NodePlexAPI {
return this.queryWithCache(options, true);
}

/**
* Queries all items with the given options, will fetch all pages.
*
* @param {any} options - The options for the query.
* @return {Promise<T[]>} - A promise that resolves to an array of T.
*/
async queryAll<T>(options): Promise<T> {
// vars
let result = undefined;
let next = true;
let page = 0;
const size = 120;
options = {
...options,
extraHeaders: {
...options?.extraHeaders,
'X-Plex-Container-Start': `${page}`,
'X-Plex-Container-Size': `${size}`,
},
};

// loop responses
while (next) {
const query: PlexLibraryResponse = await this.queryWithCache(
options,
true,
);
if (result === undefined) {
// if first response, replace result
result = query;
} else {
// if next response, add to previous result
const items = this.getDataValue(query.MediaContainer);

// if response is an array
if (items) {
this.appendToData(result.MediaContainer, items as any[]);
}
}

// fetch all if more than 120
if (query?.MediaContainer?.totalSize > size * (page + 1)) {
options.extraHeaders['X-Plex-Container-Start'] = `${size * (page + 1)}`;
page++;
} else {
next = false;
}
}
return result as unknown as T;
}

async queryWithCache<T>(options, doCache: boolean): Promise<T> {
if (typeof options === 'string') {
options = {
Expand Down Expand Up @@ -55,6 +107,54 @@ class PlexApi extends NodePlexAPI {
return undefined;
}
}

/**
* Retrieves the first array value from an object.
*
* @param {Record<string, T>} obj - The object to retrieve the value from.
* @returns {T | undefined} - The first array value found in the object, or undefined if no array value is found.
*/
private getDataValue<T>(obj: Record<string, T>): T | undefined {
const keys = Object.keys(obj);

// Find the first key that has an array value
const arrayKey = keys.find((key) => Array.isArray(obj[key]));

// If a key with an array value is found, return the corresponding value
if (arrayKey !== undefined) {
return obj[arrayKey];
} else {
return undefined; // No key with an array value found
}
}

/**
* Appends an array of items to the first array property in a given object.
*
* @param {Record<string, T>} obj - The object to append the items to.
* @param {T[]} newItem - The items to append to the object.
* @returns {Record<string, T>} - The object with the items appended to the specified property.
*/
private appendToData<T>(
obj: Record<string, T>,
newItem: T[],
): Record<string, T> {
const keys = Object.keys(obj);

// Find the first key that has an array value
const arrayKey = keys.find((key) => Array.isArray(obj[key]));

if (arrayKey !== undefined) {
const arrayValue = obj[arrayKey];

// Ensure that the value is an array
if (Array.isArray(arrayValue)) {
// If it's an array, append the new item
obj[arrayKey] = [...arrayValue, ...newItem] as T;
}
}
return obj;
}
}

export default PlexApi;
Loading

0 comments on commit 6d0d3b5

Please sign in to comment.