feat: add permissions for watchlist sync

feature/watchlist-sync
Ryan Cohen 2 years ago
parent f53a3bc10b
commit 7490bbfc9e

@ -58,7 +58,10 @@ export class UserSettings {
public telegramSendSilently?: boolean;
@Column({ nullable: true })
public watchlistSync?: boolean;
public watchlistSyncMovies?: boolean;
@Column({ nullable: true })
public watchlistSyncTv?: boolean;
@Column({
type: 'text',

@ -14,7 +14,8 @@ export interface UserSettingsGeneralResponse {
globalMovieQuotaLimit?: number;
globalTvQuotaLimit?: number;
globalTvQuotaDays?: number;
watchlistSync?: boolean;
watchlistSyncMovies?: boolean;
watchlistSyncTv?: boolean;
}
export type NotificationAgentTypes = Record<NotificationAgentKey, number>;

@ -21,6 +21,9 @@ export enum Permission {
MANAGE_ISSUES = 1048576,
VIEW_ISSUES = 2097152,
CREATE_ISSUES = 4194304,
AUTO_REQUEST = 8388608,
AUTO_REQUEST_MOVIE = 16777216,
AUTO_REQUEST_TV = 33554432,
RECENT_VIEW = 67108864,
}

@ -12,6 +12,7 @@ import {
QuotaRestrictedError,
RequestPermissionError,
} from '../entity/MediaRequest';
import { Permission } from './permissions';
class WatchlistSync {
public async syncWatchlist() {
@ -37,8 +38,24 @@ class WatchlistSync {
return;
}
// Skip sync if user settings have it disabled
if (!user.settings?.watchlistSync) {
if (
!user.hasPermission(
[
Permission.AUTO_REQUEST,
Permission.AUTO_REQUEST_MOVIE,
Permission.AUTO_APPROVE_TV,
],
{ type: 'or' }
)
) {
return;
}
if (
!user.settings?.watchlistSyncMovies &&
!user.settings?.watchlistSyncTv
) {
// Skip sync if user settings have it disabled
return;
}
@ -74,6 +91,25 @@ class WatchlistSync {
throw new Error('Missing TVDB ID from Plex Metadata');
}
// Check if they have auto-request permissons and watchlist sync
// enabled for the media type
if (
((!user.hasPermission(
[Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_MOVIE],
{ type: 'or' }
) ||
!user.settings?.watchlistSyncMovies) &&
mediaItem.type === 'movie') ||
((!user.hasPermission(
[Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_TV],
{ type: 'or' }
) ||
!user.settings?.watchlistSyncTv) &&
mediaItem.type === 'show')
) {
return;
}
await MediaRequest.request(
{
mediaId: mediaItem.tmdbId,

@ -1,13 +1,13 @@
import type { MigrationInterface, QueryRunner } from 'typeorm';
export class AddWatchlistSyncUserSetting1660627491586
export class AddWatchlistSyncUserSetting1660632269368
implements MigrationInterface
{
name = 'AddWatchlistSyncUserSetting1660627491586';
name = 'AddWatchlistSyncUserSetting1660632269368';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSync" boolean, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
`CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "temporary_user_settings"("id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey" FROM "user_settings"`

@ -63,7 +63,8 @@ userSettingsRoutes.get<{ id: string }, UserSettingsGeneralResponse>(
globalMovieQuotaLimit: defaultQuotas.movie.quotaLimit,
globalTvQuotaDays: defaultQuotas.tv.quotaDays,
globalTvQuotaLimit: defaultQuotas.tv.quotaLimit,
watchlistSync: user.settings?.watchlistSync,
watchlistSyncMovies: user.settings?.watchlistSyncMovies,
watchlistSyncTv: user.settings?.watchlistSyncTv,
});
} catch (e) {
next({ status: 500, message: e.message });
@ -115,14 +116,16 @@ userSettingsRoutes.post<
locale: req.body.locale,
region: req.body.region,
originalLanguage: req.body.originalLanguage,
watchlistSync: req.body.watchlistSync,
watchlistSyncMovies: req.body.watchlistSyncMovies,
watchlistSyncTv: req.body.watchlistSyncTv,
});
} else {
user.settings.discordId = req.body.discordId;
user.settings.locale = req.body.locale;
user.settings.region = req.body.region;
user.settings.originalLanguage = req.body.originalLanguage;
user.settings.watchlistSync = req.body.watchlistSync;
user.settings.watchlistSyncMovies = req.body.watchlistSyncMovies;
user.settings.watchlistSyncTv = req.body.watchlistSyncTv;
}
await userRepository.save(user);
@ -133,7 +136,8 @@ userSettingsRoutes.post<
locale: user.settings.locale,
region: user.settings.region,
originalLanguage: user.settings.originalLanguage,
watchlistSync: user.settings.watchlistSync,
watchlistSyncMovies: user.settings.watchlistSyncMovies,
watchlistSyncTv: user.settings.watchlistSyncTv,
});
} catch (e) {
next({ status: 500, message: e.message });

@ -50,6 +50,15 @@ export const messages = defineMessages({
advancedrequest: 'Advanced Requests',
advancedrequestDescription:
'Grant permission to modify advanced media request options.',
autorequest: 'Auto-Request',
autorequestDescription:
'Grant permission to use Plex Watchlist Sync to automatically request media.',
autorequestMovies: 'Auto-Request Movies',
autorequestMoviesDescription:
'Grant permission to use Plex Watchlist Sync to automatically request movies.',
autorequestSeries: 'Auto-Request Series',
autorequestSeriesDescription:
'Grant permission to use Plex Watchlist Sync to automatically request series.',
viewrequests: 'View Requests',
viewrequestsDescription:
'Grant permission to view media requests submitted by other users.',
@ -176,6 +185,43 @@ export const PermissionEdit = ({
},
],
},
{
id: 'autorequest',
name: intl.formatMessage(messages.autorequest),
description: intl.formatMessage(messages.autorequestDescription),
permission: Permission.AUTO_REQUEST,
requires: [{ permissions: [Permission.REQUEST] }],
children: [
{
id: 'autorequestmovies',
name: intl.formatMessage(messages.autorequestMovies),
description: intl.formatMessage(
messages.autorequestMoviesDescription
),
permission: Permission.AUTO_REQUEST_MOVIE,
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_MOVIE],
type: 'or',
},
],
},
{
id: 'autorequesttv',
name: intl.formatMessage(messages.autorequestSeries),
description: intl.formatMessage(
messages.autorequestSeriesDescription
),
permission: Permission.AUTO_REQUEST_TV,
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_TV],
type: 'or',
},
],
},
],
},
{
id: 'request4k',
name: intl.formatMessage(messages.request4k),

@ -49,8 +49,12 @@ const messages = defineMessages({
discordIdTip:
'The <FindDiscordIdLink>multi-digit ID number</FindDiscordIdLink> associated with your Discord user account',
validationDiscordId: 'You must provide a valid Discord user ID',
plexwatchlistsync: 'Plex Watchlist Auto-Request',
plexwatchlistsynctip: 'Automatically request Plex watchlist items.',
plexwatchlistsyncmovies: 'Auto-Request Movies',
plexwatchlistsyncmoviestip:
'Automatically request movies on your Plex watchlist',
plexwatchlistsyncseries: 'Auto-Request Series',
plexwatchlistsyncseriestip:
'Automatically request series on your Plex watchlist',
});
const UserGeneralSettings = () => {
@ -124,7 +128,8 @@ const UserGeneralSettings = () => {
movieQuotaDays: data?.movieQuotaDays,
tvQuotaLimit: data?.tvQuotaLimit,
tvQuotaDays: data?.tvQuotaDays,
watchlistSync: false,
watchlistSyncMovies: data?.watchlistSyncMovies,
watchlistSyncTv: data?.watchlistSyncTv,
}}
validationSchema={UserGeneralSettingsSchema}
enableReinitialize
@ -142,6 +147,8 @@ const UserGeneralSettings = () => {
movieQuotaDays: movieQuotaEnabled ? values.movieQuotaDays : null,
tvQuotaLimit: tvQuotaEnabled ? values.tvQuotaLimit : null,
tvQuotaDays: tvQuotaEnabled ? values.tvQuotaDays : null,
watchlistSyncMovies: values.watchlistSyncMovies,
watchlistSyncTv: values.watchlistSyncTv,
});
if (currentUser?.id === user?.id && setLocale) {
@ -412,24 +419,65 @@ const UserGeneralSettings = () => {
</div>
</>
)}
<div className="form-row">
<label htmlFor="watchlistSync" className="checkbox-label">
<span>{intl.formatMessage(messages.plexwatchlistsync)}</span>
<span className="label-tip">
{intl.formatMessage(messages.plexwatchlistsynctip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="watchlistSync"
name="watchlistSync"
onChange={() => {
setFieldValue('watchlistSync', !values.watchlistSync);
}}
/>
{hasPermission(
[Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_MOVIE],
{ type: 'or' }
) && (
<div className="form-row">
<label
htmlFor="watchlistSyncMovies"
className="checkbox-label"
>
<span>
{intl.formatMessage(messages.plexwatchlistsyncmovies)}
</span>
<span className="label-tip">
{intl.formatMessage(messages.plexwatchlistsyncmoviestip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="watchlistSyncMovies"
name="watchlistSyncMovies"
onChange={() => {
setFieldValue(
'watchlistSyncMovies',
!values.watchlistSyncMovies
);
}}
/>
</div>
</div>
</div>
)}
{hasPermission(
[Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_TV],
{ type: 'or' }
) && (
<div className="form-row">
<label htmlFor="watchlistSyncTv" className="checkbox-label">
<span>
{intl.formatMessage(messages.plexwatchlistsyncseries)}
</span>
<span className="label-tip">
{intl.formatMessage(messages.plexwatchlistsyncseriestip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="watchlistSyncTv"
name="watchlistSyncTv"
onChange={() => {
setFieldValue(
'watchlistSyncTv',
!values.watchlistSyncTv
);
}}
/>
</div>
</div>
)}
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">

@ -232,6 +232,12 @@
"components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests.",
"components.PermissionEdit.autoapproveSeries": "Auto-Approve Series",
"components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests.",
"components.PermissionEdit.autorequest": "Auto-Request",
"components.PermissionEdit.autorequestDescription": "Grant permission to use Plex Watchlist Sync to automatically request media.",
"components.PermissionEdit.autorequestMovies": "Auto-Request Movies",
"components.PermissionEdit.autorequestMoviesDescription": "Grant permission to use Plex Watchlist Sync to automatically request movies.",
"components.PermissionEdit.autorequestSeries": "Auto-Request Series",
"components.PermissionEdit.autorequestSeriesDescription": "Grant permission to use Plex Watchlist Sync to automatically request series.",
"components.PermissionEdit.createissues": "Report Issues",
"components.PermissionEdit.createissuesDescription": "Grant permission to report media issues.",
"components.PermissionEdit.manageissues": "Manage Issues",
@ -923,6 +929,10 @@
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Filter content by original language",
"components.UserProfile.UserSettings.UserGeneralSettings.owner": "Owner",
"components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex User",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Auto-Request Movies",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatically request movies on your Plex watchlist",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Auto-Request Series",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatically request series on your Plex watchlist",
"components.UserProfile.UserSettings.UserGeneralSettings.region": "Discover Region",
"components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filter content by regional availability",
"components.UserProfile.UserSettings.UserGeneralSettings.role": "Role",

Loading…
Cancel
Save