feat(notif): auto-request notif type (#2956)

pull/2957/head
TheCatLady 2 years ago committed by GitHub
parent 301f2bf7ab
commit 6c0fd40877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -480,6 +480,10 @@ export class MediaRequest {
} }
this.sendNotification(media, Notification.MEDIA_PENDING); this.sendNotification(media, Notification.MEDIA_PENDING);
if (this.isAutoRequest) {
this.sendNotification(media, Notification.MEDIA_AUTO_REQUESTED);
}
} }
} }
@ -524,6 +528,14 @@ export class MediaRequest {
: Notification.MEDIA_APPROVED : Notification.MEDIA_APPROVED
: Notification.MEDIA_DECLINED : Notification.MEDIA_DECLINED
); );
if (
this.status === MediaRequestStatus.APPROVED &&
autoApproved &&
this.isAutoRequest
) {
this.sendNotification(media, Notification.MEDIA_AUTO_REQUESTED);
}
} }
} }
@ -1062,6 +1074,7 @@ export class MediaRequest {
const mediaType = this.type === MediaType.MOVIE ? 'Movie' : 'Series'; const mediaType = this.type === MediaType.MOVIE ? 'Movie' : 'Series';
let event: string | undefined; let event: string | undefined;
let notifyAdmin = true; let notifyAdmin = true;
let notifySystem = true;
switch (type) { switch (type) {
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
@ -1075,6 +1088,13 @@ export class MediaRequest {
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
event = `New ${this.is4k ? '4K ' : ''}${mediaType} Request`; event = `New ${this.is4k ? '4K ' : ''}${mediaType} Request`;
break; break;
case Notification.MEDIA_AUTO_REQUESTED:
event = `${
this.is4k ? '4K ' : ''
}${mediaType} Request Automatically Submitted`;
notifyAdmin = false;
notifySystem = false;
break;
case Notification.MEDIA_AUTO_APPROVED: case Notification.MEDIA_AUTO_APPROVED:
event = `${ event = `${
this.is4k ? '4K ' : '' this.is4k ? '4K ' : ''
@ -1091,6 +1111,7 @@ export class MediaRequest {
media, media,
request: this, request: this,
notifyAdmin, notifyAdmin,
notifySystem,
notifyUser: notifyAdmin ? undefined : this.requestedBy, notifyUser: notifyAdmin ? undefined : this.requestedBy,
event, event,
subject: `${movie.title}${ subject: `${movie.title}${
@ -1109,6 +1130,7 @@ export class MediaRequest {
media, media,
request: this, request: this,
notifyAdmin, notifyAdmin,
notifySystem,
notifyUser: notifyAdmin ? undefined : this.requestedBy, notifyUser: notifyAdmin ? undefined : this.requestedBy,
event, event,
subject: `${tv.name}${ subject: `${tv.name}${

@ -9,6 +9,7 @@ import type { NotificationAgentConfig } from '../../settings';
export interface NotificationPayload { export interface NotificationPayload {
event?: string; event?: string;
subject: string; subject: string;
notifySystem: boolean;
notifyAdmin: boolean; notifyAdmin: boolean;
notifyUser?: User; notifyUser?: User;
media?: Media; media?: Media;

@ -243,7 +243,10 @@ class DiscordAgent
): Promise<boolean> { ): Promise<boolean> {
const settings = this.getSettings(); const settings = this.getSettings();
if (!hasNotificationType(type, settings.types ?? 0)) { if (
!payload.notifySystem ||
!hasNotificationType(type, settings.types ?? 0)
) {
return true; return true;
} }

@ -81,6 +81,11 @@ class EmailAgent
is4k ? 'in 4K ' : '' is4k ? 'in 4K ' : ''
}is pending approval:`; }is pending approval:`;
break; break;
case Notification.MEDIA_AUTO_REQUESTED:
body = `A new request for the following ${mediaType} ${
is4k ? 'in 4K ' : ''
}was automatically submitted:`;
break;
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
body = `Your request for the following ${mediaType} ${ body = `Your request for the following ${mediaType} ${
is4k ? 'in 4K ' : '' is4k ? 'in 4K ' : ''

@ -117,7 +117,10 @@ class GotifyAgent
): Promise<boolean> { ): Promise<boolean> {
const settings = this.getSettings(); const settings = this.getSettings();
if (!hasNotificationType(type, settings.types ?? 0)) { if (
!payload.notifySystem ||
!hasNotificationType(type, settings.types ?? 0)
) {
return true; return true;
} }

@ -87,7 +87,10 @@ class LunaSeaAgent
): Promise<boolean> { ): Promise<boolean> {
const settings = this.getSettings(); const settings = this.getSettings();
if (!hasNotificationType(type, settings.types ?? 0)) { if (
!payload.notifySystem ||
!hasNotificationType(type, settings.types ?? 0)
) {
return true; return true;
} }

@ -5,6 +5,7 @@ import {
shouldSendAdminNotification, shouldSendAdminNotification,
} from '..'; } from '..';
import { IssueStatus, IssueTypeName } from '../../../constants/issue'; import { IssueStatus, IssueTypeName } from '../../../constants/issue';
import { MediaStatus } from '../../../constants/media';
import { getRepository } from '../../../datasource'; import { getRepository } from '../../../datasource';
import { User } from '../../../entity/User'; import { User } from '../../../entity/User';
import logger from '../../../logger'; import logger from '../../../logger';
@ -52,6 +53,12 @@ class PushbulletAgent
let status = ''; let status = '';
switch (type) { switch (type) {
case Notification.MEDIA_AUTO_REQUESTED:
status =
payload.media?.status === MediaStatus.PENDING
? 'Pending Approval'
: 'Processing';
break;
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
status = 'Pending Approval'; status = 'Pending Approval';
break; break;
@ -104,6 +111,7 @@ class PushbulletAgent
// Send system notification // Send system notification
if ( if (
payload.notifySystem &&
hasNotificationType(type, settings.types ?? 0) && hasNotificationType(type, settings.types ?? 0) &&
settings.enabled && settings.enabled &&
settings.options.accessToken settings.options.accessToken

@ -5,6 +5,7 @@ import {
shouldSendAdminNotification, shouldSendAdminNotification,
} from '..'; } from '..';
import { IssueStatus, IssueTypeName } from '../../../constants/issue'; import { IssueStatus, IssueTypeName } from '../../../constants/issue';
import { MediaStatus } from '../../../constants/media';
import { getRepository } from '../../../datasource'; import { getRepository } from '../../../datasource';
import { User } from '../../../entity/User'; import { User } from '../../../entity/User';
import logger from '../../../logger'; import logger from '../../../logger';
@ -61,6 +62,12 @@ class PushoverAgent
let status = ''; let status = '';
switch (type) { switch (type) {
case Notification.MEDIA_AUTO_REQUESTED:
status =
payload.media?.status === MediaStatus.PENDING
? 'Pending Approval'
: 'Processing';
break;
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
status = 'Pending Approval'; status = 'Pending Approval';
break; break;
@ -135,6 +142,7 @@ class PushoverAgent
// Send system notification // Send system notification
if ( if (
payload.notifySystem &&
hasNotificationType(type, settings.types ?? 0) && hasNotificationType(type, settings.types ?? 0) &&
settings.enabled && settings.enabled &&
settings.options.accessToken && settings.options.accessToken &&

@ -225,7 +225,10 @@ class SlackAgent
): Promise<boolean> { ): Promise<boolean> {
const settings = this.getSettings(); const settings = this.getSettings();
if (!hasNotificationType(type, settings.types ?? 0)) { if (
!payload.notifySystem ||
!hasNotificationType(type, settings.types ?? 0)
) {
return true; return true;
} }

@ -5,6 +5,7 @@ import {
shouldSendAdminNotification, shouldSendAdminNotification,
} from '..'; } from '..';
import { IssueStatus, IssueTypeName } from '../../../constants/issue'; import { IssueStatus, IssueTypeName } from '../../../constants/issue';
import { MediaStatus } from '../../../constants/media';
import { getRepository } from '../../../datasource'; import { getRepository } from '../../../datasource';
import { User } from '../../../entity/User'; import { User } from '../../../entity/User';
import logger from '../../../logger'; import logger from '../../../logger';
@ -79,6 +80,12 @@ class TelegramAgent
let status = ''; let status = '';
switch (type) { switch (type) {
case Notification.MEDIA_AUTO_REQUESTED:
status =
payload.media?.status === MediaStatus.PENDING
? 'Pending Approval'
: 'Processing';
break;
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
status = 'Pending Approval'; status = 'Pending Approval';
break; break;
@ -157,6 +164,7 @@ class TelegramAgent
// Send system notification // Send system notification
if ( if (
payload.notifySystem &&
hasNotificationType(type, settings.types ?? 0) && hasNotificationType(type, settings.types ?? 0) &&
settings.options.chatId settings.options.chatId
) { ) {

@ -164,7 +164,10 @@ class WebhookAgent
): Promise<boolean> { ): Promise<boolean> {
const settings = this.getSettings(); const settings = this.getSettings();
if (!hasNotificationType(type, settings.types ?? 0)) { if (
!payload.notifySystem ||
!hasNotificationType(type, settings.types ?? 0)
) {
return true; return true;
} }

@ -57,6 +57,11 @@ class WebPushAgent
case Notification.TEST_NOTIFICATION: case Notification.TEST_NOTIFICATION:
message = payload.message; message = payload.message;
break; break;
case Notification.MEDIA_AUTO_REQUESTED:
message = `Automatically submitted a new ${
is4k ? '4K ' : ''
}${mediaType} request.`;
break;
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
message = `Your ${ message = `Your ${
is4k ? '4K ' : '' is4k ? '4K ' : ''

@ -16,6 +16,7 @@ export enum Notification {
ISSUE_COMMENT = 512, ISSUE_COMMENT = 512,
ISSUE_RESOLVED = 1024, ISSUE_RESOLVED = 1024,
ISSUE_REOPENED = 2048, ISSUE_REOPENED = 2048,
MEDIA_AUTO_REQUESTED = 4096,
} }
export const hasNotificationType = ( export const hasNotificationType = (

@ -1,4 +1,3 @@
import { Not } from 'typeorm';
import PlexTvAPI from '../api/plextv'; import PlexTvAPI from '../api/plextv';
import { User } from '../entity/User'; import { User } from '../entity/User';
import Media from '../entity/Media'; import Media from '../entity/Media';
@ -20,12 +19,12 @@ class WatchlistSync {
const userRepository = getRepository(User); const userRepository = getRepository(User);
// Get users who actually have plex tokens // Get users who actually have plex tokens
const users = await userRepository.find({ const users = await userRepository
select: { id: true, plexToken: true, permissions: true }, .createQueryBuilder('user')
where: { .addSelect('user.plexToken')
plexToken: Not(''), .leftJoinAndSelect('user.settings', 'settings')
}, .where("user.plexToken != ''")
}); .getMany();
for (const user of users) { for (const user of users) {
await this.syncUserWatchlist(user); await this.syncUserWatchlist(user);
@ -36,7 +35,7 @@ class WatchlistSync {
if (!user.plexToken) { if (!user.plexToken) {
logger.warn('Skipping user watchlist sync for user without plex token', { logger.warn('Skipping user watchlist sync for user without plex token', {
label: 'Plex Watchlist Sync', label: 'Plex Watchlist Sync',
userId: user.id, user: user.displayName,
}); });
return; return;
} }

@ -18,6 +18,7 @@ const notificationRoutes = Router();
const sendTestNotification = async (agent: NotificationAgent, user: User) => const sendTestNotification = async (agent: NotificationAgent, user: User) =>
await agent.send(Notification.TEST_NOTIFICATION, { await agent.send(Notification.TEST_NOTIFICATION, {
notifySystem: true,
notifyAdmin: false, notifyAdmin: false,
notifyUser: user, notifyUser: user,
subject: 'Test Notification', subject: 'Test Notification',
@ -247,7 +248,7 @@ notificationRoutes.post('/webpush/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
@ -363,7 +364,7 @@ notificationRoutes.post('/lunasea/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
@ -384,34 +385,26 @@ notificationRoutes.get('/gotify', (_req, res) => {
res.status(200).json(settings.notifications.agents.gotify); res.status(200).json(settings.notifications.agents.gotify);
}); });
notificationRoutes.post('/gotify', (req, rest) => { notificationRoutes.post('/gotify', (req, res) => {
const settings = getSettings(); const settings = getSettings();
settings.notifications.agents.gotify = req.body; settings.notifications.agents.gotify = req.body;
settings.save(); settings.save();
rest.status(200).json(settings.notifications.agents.gotify); res.status(200).json(settings.notifications.agents.gotify);
}); });
notificationRoutes.post('/gotify/test', async (req, rest, next) => { notificationRoutes.post('/gotify/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information is missing from request', message: 'User information is missing from the request.',
}); });
} }
const gotifyAgent = new GotifyAgent(req.body); const gotifyAgent = new GotifyAgent(req.body);
if ( if (await sendTestNotification(gotifyAgent, req.user)) {
await gotifyAgent.send(Notification.TEST_NOTIFICATION, { return res.status(204).send();
notifyAdmin: false,
notifyUser: req.user,
subject: 'Test Notification',
message:
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
) {
return rest.status(204).send();
} else { } else {
return next({ return next({
status: 500, status: 500,

@ -69,6 +69,7 @@ export class IssueCommentSubscriber
media, media,
image, image,
notifyAdmin: true, notifyAdmin: true,
notifySystem: true,
notifyUser: notifyUser:
!issue.createdBy.hasPermission(Permission.MANAGE_ISSUES) && !issue.createdBy.hasPermission(Permission.MANAGE_ISSUES) &&
issue.createdBy.id !== entity.user.id issue.createdBy.id !== entity.user.id

@ -84,6 +84,7 @@ export class IssueSubscriber implements EntitySubscriberInterface<Issue> {
image, image,
extra, extra,
notifyAdmin: true, notifyAdmin: true,
notifySystem: true,
notifyUser: notifyUser:
!entity.createdBy.hasPermission(Permission.MANAGE_ISSUES) && !entity.createdBy.hasPermission(Permission.MANAGE_ISSUES) &&
(type === Notification.ISSUE_RESOLVED || (type === Notification.ISSUE_RESOLVED ||

@ -45,6 +45,7 @@ export class MediaSubscriber implements EntitySubscriberInterface<Media> {
{ {
event: `${is4k ? '4K ' : ''}Movie Request Now Available`, event: `${is4k ? '4K ' : ''}Movie Request Now Available`,
notifyAdmin: false, notifyAdmin: false,
notifySystem: true,
notifyUser: request.requestedBy, notifyUser: request.requestedBy,
subject: `${movie.title}${ subject: `${movie.title}${
movie.release_date movie.release_date
@ -143,6 +144,7 @@ export class MediaSubscriber implements EntitySubscriberInterface<Media> {
omission: '…', omission: '…',
}), }),
notifyAdmin: false, notifyAdmin: false,
notifySystem: true,
notifyUser: request.requestedBy, notifyUser: request.requestedBy,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`, image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
media: entity, media: entity,

@ -60,6 +60,9 @@ const messages = defineMessages({
'Get notified when issues you reported are reopened.', 'Get notified when issues you reported are reopened.',
adminissuereopenedDescription: adminissuereopenedDescription:
'Get notified when issues are reopened by other users.', 'Get notified when issues are reopened by other users.',
mediaautorequested: 'Request Automatically Submitted',
mediaautorequestedDescription:
'Get notified when new media requests are automatically submitted for items on your Plex Watchlist.',
}); });
export const hasNotificationType = ( export const hasNotificationType = (
@ -101,6 +104,7 @@ export enum Notification {
ISSUE_COMMENT = 512, ISSUE_COMMENT = 512,
ISSUE_RESOLVED = 1024, ISSUE_RESOLVED = 1024,
ISSUE_REOPENED = 2048, ISSUE_REOPENED = 2048,
MEDIA_AUTO_REQUESTED = 4096,
} }
export const ALL_NOTIFICATIONS = Object.values(Notification) export const ALL_NOTIFICATIONS = Object.values(Notification)
@ -191,6 +195,25 @@ const NotificationTypeSelector = ({
)))); ))));
const types: NotificationItem[] = [ const types: NotificationItem[] = [
{
id: 'media-auto-requested',
name: intl.formatMessage(messages.mediaautorequested),
description: intl.formatMessage(messages.mediaautorequestedDescription),
value: Notification.MEDIA_AUTO_REQUESTED,
hidden:
!user ||
(!user.settings?.watchlistSyncMovies &&
!user.settings?.watchlistSyncTv) ||
!hasPermission(
[
Permission.AUTO_REQUEST,
Permission.AUTO_REQUEST_MOVIE,
Permission.AUTO_REQUEST_TV,
],
{ type: 'or' }
),
hasNotifyUser: true,
},
{ {
id: 'media-requested', id: 'media-requested',
name: intl.formatMessage(messages.mediarequested), name: intl.formatMessage(messages.mediarequested),

@ -31,6 +31,8 @@ export interface UserSettings {
originalLanguage?: string; originalLanguage?: string;
locale?: string; locale?: string;
notificationTypes: Partial<NotificationAgentTypes>; notificationTypes: Partial<NotificationAgentTypes>;
watchlistSyncMovies?: boolean;
watchlistSyncTv?: boolean;
} }
interface UserHookResponse { interface UserHookResponse {

@ -197,6 +197,8 @@
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Send notifications when users submit new media requests which are automatically approved.", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Send notifications when users submit new media requests which are automatically approved.",
"components.NotificationTypeSelector.mediaapproved": "Request Approved", "components.NotificationTypeSelector.mediaapproved": "Request Approved",
"components.NotificationTypeSelector.mediaapprovedDescription": "Send notifications when media requests are manually approved.", "components.NotificationTypeSelector.mediaapprovedDescription": "Send notifications when media requests are manually approved.",
"components.NotificationTypeSelector.mediaautorequested": "Request Automatically Submitted",
"components.NotificationTypeSelector.mediaautorequestedDescription": "Get notified when new media requests are automatically submitted for items on your Plex Watchlist.",
"components.NotificationTypeSelector.mediaavailable": "Request Available", "components.NotificationTypeSelector.mediaavailable": "Request Available",
"components.NotificationTypeSelector.mediaavailableDescription": "Send notifications when media requests become available.", "components.NotificationTypeSelector.mediaavailableDescription": "Send notifications when media requests become available.",
"components.NotificationTypeSelector.mediadeclined": "Request Declined", "components.NotificationTypeSelector.mediadeclined": "Request Declined",

Loading…
Cancel
Save