mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(notif): add Pushbullet and Pushover agents to user notification settings (#1740)
* feat(notif): add Pushbullet and Pushover agents to user notification settings * docs(notif): add "hint" about user notifications to Pushbullet and Pushover pages * fix: regenerate DB migration
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Pushbullet
|
# Pushbullet
|
||||||
|
|
||||||
|
{% hint style="info" %}
|
||||||
|
Users can optionally configure personal notifications in their user settings.
|
||||||
|
|
||||||
|
User notifications are separate from system notifications, and the available notification types are dependent on user permissions.
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Access Token
|
### Access Token
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
# Pushover
|
# Pushover
|
||||||
|
|
||||||
|
{% hint style="info" %}
|
||||||
|
Users can optionally configure personal notifications in their user settings.
|
||||||
|
|
||||||
|
User notifications are separate from system notifications, and the available notification types are dependent on user permissions.
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Application/API Token
|
### Application/API Token
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
# Telegram
|
# Telegram
|
||||||
|
|
||||||
{% hint style="info" %}
|
{% hint style="info" %}
|
||||||
Users can optionally configure their own notifications in their user settings.
|
Users can optionally configure personal notifications in their user settings.
|
||||||
|
|
||||||
|
User notifications are separate from system notifications, and the available notification types are dependent on user permissions.
|
||||||
{% endhint %}
|
{% endhint %}
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
@@ -1630,6 +1630,15 @@ components:
|
|||||||
discordId:
|
discordId:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
pushbulletAccessToken:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
pushoverApplicationToken:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
pushoverUserKey:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
telegramEnabled:
|
telegramEnabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
telegramBotUsername:
|
telegramBotUsername:
|
||||||
|
@@ -42,6 +42,15 @@ export class UserSettings {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
public discordId?: string;
|
public discordId?: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
public pushbulletAccessToken?: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
public pushoverApplicationToken?: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
public pushoverUserKey?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
public telegramChatId?: string;
|
public telegramChatId?: string;
|
||||||
|
|
||||||
|
@@ -22,6 +22,9 @@ export interface UserSettingsNotificationsResponse {
|
|||||||
discordEnabled?: boolean;
|
discordEnabled?: boolean;
|
||||||
discordEnabledTypes?: number;
|
discordEnabledTypes?: number;
|
||||||
discordId?: string;
|
discordId?: string;
|
||||||
|
pushbulletAccessToken?: string;
|
||||||
|
pushoverApplicationToken?: string;
|
||||||
|
pushoverUserKey?: string;
|
||||||
telegramEnabled?: boolean;
|
telegramEnabled?: boolean;
|
||||||
telegramBotUsername?: string;
|
telegramBotUsername?: string;
|
||||||
telegramChatId?: string;
|
telegramChatId?: string;
|
||||||
|
@@ -1,11 +1,19 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { getRepository } from 'typeorm';
|
||||||
import { hasNotificationType, Notification } from '..';
|
import { hasNotificationType, Notification } from '..';
|
||||||
import { MediaType } from '../../../constants/media';
|
import { MediaType } from '../../../constants/media';
|
||||||
|
import { User } from '../../../entity/User';
|
||||||
import logger from '../../../logger';
|
import logger from '../../../logger';
|
||||||
import { getSettings, NotificationAgentPushbullet } from '../../settings';
|
import { Permission } from '../../permissions';
|
||||||
|
import {
|
||||||
|
getSettings,
|
||||||
|
NotificationAgentKey,
|
||||||
|
NotificationAgentPushbullet,
|
||||||
|
} from '../../settings';
|
||||||
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
||||||
|
|
||||||
interface PushbulletPayload {
|
interface PushbulletPayload {
|
||||||
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
@@ -25,22 +33,13 @@ class PushbulletAgent
|
|||||||
}
|
}
|
||||||
|
|
||||||
public shouldSend(): boolean {
|
public shouldSend(): boolean {
|
||||||
const settings = this.getSettings();
|
return true;
|
||||||
|
|
||||||
if (settings.enabled && settings.options.accessToken) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructMessageDetails(
|
private getNotificationPayload(
|
||||||
type: Notification,
|
type: Notification,
|
||||||
payload: NotificationPayload
|
payload: NotificationPayload
|
||||||
): {
|
): PushbulletPayload {
|
||||||
title: string;
|
|
||||||
body: string;
|
|
||||||
} {
|
|
||||||
let messageTitle = '';
|
let messageTitle = '';
|
||||||
let message = '';
|
let message = '';
|
||||||
|
|
||||||
@@ -126,6 +125,7 @@ class PushbulletAgent
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
type: 'note',
|
||||||
title: messageTitle,
|
title: messageTitle,
|
||||||
body: message,
|
body: message,
|
||||||
};
|
};
|
||||||
@@ -136,46 +136,132 @@ class PushbulletAgent
|
|||||||
payload: NotificationPayload
|
payload: NotificationPayload
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const settings = this.getSettings();
|
const settings = this.getSettings();
|
||||||
|
const endpoint = 'https://api.pushbullet.com/v2/pushes';
|
||||||
|
const notificationPayload = this.getNotificationPayload(type, payload);
|
||||||
|
|
||||||
if (!hasNotificationType(type, settings.types ?? 0)) {
|
// Send system notification
|
||||||
return true;
|
if (
|
||||||
}
|
hasNotificationType(type, settings.types ?? 0) &&
|
||||||
|
settings.enabled &&
|
||||||
logger.debug('Sending Pushbullet notification', {
|
settings.options.accessToken
|
||||||
label: 'Notifications',
|
) {
|
||||||
type: Notification[type],
|
logger.debug('Sending Pushbullet notification', {
|
||||||
subject: payload.subject,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { title, body } = this.constructMessageDetails(type, payload);
|
|
||||||
|
|
||||||
await axios.post(
|
|
||||||
'https://api.pushbullet.com/v2/pushes',
|
|
||||||
{
|
|
||||||
type: 'note',
|
|
||||||
title: title,
|
|
||||||
body: body,
|
|
||||||
} as PushbulletPayload,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Access-Token': settings.options.accessToken,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('Error sending Pushbullet notification', {
|
|
||||||
label: 'Notifications',
|
label: 'Notifications',
|
||||||
type: Notification[type],
|
type: Notification[type],
|
||||||
subject: payload.subject,
|
subject: payload.subject,
|
||||||
errorMessage: e.message,
|
|
||||||
response: e.response?.data,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
try {
|
||||||
|
await axios.post(endpoint, notificationPayload, {
|
||||||
|
headers: {
|
||||||
|
'Access-Token': settings.options.accessToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Error sending Pushbullet notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
errorMessage: e.message,
|
||||||
|
response: e.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.notifyUser) {
|
||||||
|
// Send notification to the user who submitted the request
|
||||||
|
if (
|
||||||
|
payload.notifyUser.settings?.hasNotificationType(
|
||||||
|
NotificationAgentKey.PUSHBULLET,
|
||||||
|
type
|
||||||
|
) &&
|
||||||
|
payload.notifyUser.settings?.pushbulletAccessToken &&
|
||||||
|
payload.notifyUser.settings.pushbulletAccessToken !==
|
||||||
|
settings.options.accessToken
|
||||||
|
) {
|
||||||
|
logger.debug('Sending Pushbullet notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: payload.notifyUser.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(endpoint, notificationPayload, {
|
||||||
|
headers: {
|
||||||
|
'Access-Token': payload.notifyUser.settings.pushbulletAccessToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Error sending Pushbullet notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: payload.notifyUser.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
errorMessage: e.message,
|
||||||
|
response: e.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Send notifications to all users with the Manage Requests permission
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
const users = await userRepository.find();
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
users
|
||||||
|
.filter(
|
||||||
|
(user) =>
|
||||||
|
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||||
|
user.settings?.hasNotificationType(
|
||||||
|
NotificationAgentKey.PUSHBULLET,
|
||||||
|
type
|
||||||
|
) &&
|
||||||
|
// Check if it's the user's own auto-approved request
|
||||||
|
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||||
|
user.id !== payload.request?.requestedBy.id)
|
||||||
|
)
|
||||||
|
.map(async (user) => {
|
||||||
|
if (
|
||||||
|
user.settings?.pushbulletAccessToken &&
|
||||||
|
user.settings.pushbulletAccessToken !==
|
||||||
|
settings.options.accessToken
|
||||||
|
) {
|
||||||
|
logger.debug('Sending Pushbullet notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: user.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(endpoint, notificationPayload, {
|
||||||
|
headers: {
|
||||||
|
'Access-Token': user.settings.pushbulletAccessToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Error sending Pushbullet notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: user.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
errorMessage: e.message,
|
||||||
|
response: e.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,15 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { getRepository } from 'typeorm';
|
||||||
import { hasNotificationType, Notification } from '..';
|
import { hasNotificationType, Notification } from '..';
|
||||||
import { MediaType } from '../../../constants/media';
|
import { MediaType } from '../../../constants/media';
|
||||||
|
import { User } from '../../../entity/User';
|
||||||
import logger from '../../../logger';
|
import logger from '../../../logger';
|
||||||
import { getSettings, NotificationAgentPushover } from '../../settings';
|
import { Permission } from '../../permissions';
|
||||||
|
import {
|
||||||
|
getSettings,
|
||||||
|
NotificationAgentKey,
|
||||||
|
NotificationAgentPushover,
|
||||||
|
} from '../../settings';
|
||||||
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
||||||
|
|
||||||
interface PushoverPayload {
|
interface PushoverPayload {
|
||||||
@@ -31,29 +38,13 @@ class PushoverAgent
|
|||||||
}
|
}
|
||||||
|
|
||||||
public shouldSend(): boolean {
|
public shouldSend(): boolean {
|
||||||
const settings = this.getSettings();
|
return true;
|
||||||
|
|
||||||
if (
|
|
||||||
settings.enabled &&
|
|
||||||
settings.options.accessToken &&
|
|
||||||
settings.options.userToken
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructMessageDetails(
|
private getNotificationPayload(
|
||||||
type: Notification,
|
type: Notification,
|
||||||
payload: NotificationPayload
|
payload: NotificationPayload
|
||||||
): {
|
): Partial<PushoverPayload> {
|
||||||
title: string;
|
|
||||||
message: string;
|
|
||||||
url: string | undefined;
|
|
||||||
url_title: string | undefined;
|
|
||||||
priority: number;
|
|
||||||
} {
|
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
let messageTitle = '';
|
let messageTitle = '';
|
||||||
let message = '';
|
let message = '';
|
||||||
@@ -155,6 +146,7 @@ class PushoverAgent
|
|||||||
url,
|
url,
|
||||||
url_title,
|
url_title,
|
||||||
priority,
|
priority,
|
||||||
|
html: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,45 +155,138 @@ class PushoverAgent
|
|||||||
payload: NotificationPayload
|
payload: NotificationPayload
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const settings = this.getSettings();
|
const settings = this.getSettings();
|
||||||
|
const endpoint = 'https://api.pushover.net/1/messages.json';
|
||||||
|
const notificationPayload = this.getNotificationPayload(type, payload);
|
||||||
|
|
||||||
if (!hasNotificationType(type, settings.types ?? 0)) {
|
// Send system notification
|
||||||
return true;
|
if (
|
||||||
}
|
hasNotificationType(type, settings.types ?? 0) &&
|
||||||
|
settings.enabled &&
|
||||||
logger.debug('Sending Pushover notification', {
|
settings.options.accessToken &&
|
||||||
label: 'Notifications',
|
settings.options.userToken
|
||||||
type: Notification[type],
|
) {
|
||||||
subject: payload.subject,
|
logger.debug('Sending Pushover notification', {
|
||||||
});
|
|
||||||
try {
|
|
||||||
const endpoint = 'https://api.pushover.net/1/messages.json';
|
|
||||||
|
|
||||||
const { title, message, url, url_title, priority } =
|
|
||||||
this.constructMessageDetails(type, payload);
|
|
||||||
|
|
||||||
await axios.post(endpoint, {
|
|
||||||
token: settings.options.accessToken,
|
|
||||||
user: settings.options.userToken,
|
|
||||||
title: title,
|
|
||||||
message: message,
|
|
||||||
url: url,
|
|
||||||
url_title: url_title,
|
|
||||||
priority: priority,
|
|
||||||
html: 1,
|
|
||||||
} as PushoverPayload);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('Error sending Pushover notification', {
|
|
||||||
label: 'Notifications',
|
label: 'Notifications',
|
||||||
type: Notification[type],
|
type: Notification[type],
|
||||||
subject: payload.subject,
|
subject: payload.subject,
|
||||||
errorMessage: e.message,
|
|
||||||
response: e.response?.data,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
try {
|
||||||
|
await axios.post(endpoint, {
|
||||||
|
...notificationPayload,
|
||||||
|
token: settings.options.accessToken,
|
||||||
|
user: settings.options.userToken,
|
||||||
|
} as PushoverPayload);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Error sending Pushover notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
errorMessage: e.message,
|
||||||
|
response: e.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.notifyUser) {
|
||||||
|
// Send notification to the user who submitted the request
|
||||||
|
if (
|
||||||
|
payload.notifyUser.settings?.hasNotificationType(
|
||||||
|
NotificationAgentKey.PUSHOVER,
|
||||||
|
type
|
||||||
|
) &&
|
||||||
|
payload.notifyUser.settings?.pushoverApplicationToken &&
|
||||||
|
payload.notifyUser.settings?.pushoverUserKey &&
|
||||||
|
payload.notifyUser.settings.pushoverApplicationToken !==
|
||||||
|
settings.options.accessToken &&
|
||||||
|
payload.notifyUser.settings?.pushoverUserKey !==
|
||||||
|
settings.options.userToken
|
||||||
|
) {
|
||||||
|
logger.debug('Sending Pushover notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: payload.notifyUser.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(endpoint, {
|
||||||
|
...notificationPayload,
|
||||||
|
token: payload.notifyUser.settings.pushoverApplicationToken,
|
||||||
|
user: payload.notifyUser.settings.pushoverUserKey,
|
||||||
|
} as PushoverPayload);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Error sending Pushover notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: payload.notifyUser.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
errorMessage: e.message,
|
||||||
|
response: e.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Send notifications to all users with the Manage Requests permission
|
||||||
|
const userRepository = getRepository(User);
|
||||||
|
const users = await userRepository.find();
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
users
|
||||||
|
.filter(
|
||||||
|
(user) =>
|
||||||
|
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||||
|
user.settings?.hasNotificationType(
|
||||||
|
NotificationAgentKey.PUSHOVER,
|
||||||
|
type
|
||||||
|
) &&
|
||||||
|
// Check if it's the user's own auto-approved request
|
||||||
|
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||||
|
user.id !== payload.request?.requestedBy.id)
|
||||||
|
)
|
||||||
|
.map(async (user) => {
|
||||||
|
if (
|
||||||
|
user.settings?.pushoverApplicationToken &&
|
||||||
|
user.settings?.pushoverUserKey &&
|
||||||
|
user.settings.pushoverApplicationToken !==
|
||||||
|
settings.options.accessToken &&
|
||||||
|
user.settings.pushoverUserKey !== settings.options.userToken
|
||||||
|
) {
|
||||||
|
logger.debug('Sending Pushover notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: user.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(endpoint, {
|
||||||
|
...notificationPayload,
|
||||||
|
token: user.settings.pushoverApplicationToken,
|
||||||
|
user: user.settings.pushoverUserKey,
|
||||||
|
} as PushoverPayload);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Error sending Pushover notification', {
|
||||||
|
label: 'Notifications',
|
||||||
|
recipient: user.displayName,
|
||||||
|
type: Notification[type],
|
||||||
|
subject: payload.subject,
|
||||||
|
errorMessage: e.message,
|
||||||
|
response: e.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -46,11 +46,7 @@ class TelegramAgent
|
|||||||
public shouldSend(): boolean {
|
public shouldSend(): boolean {
|
||||||
const settings = this.getSettings();
|
const settings = this.getSettings();
|
||||||
|
|
||||||
if (
|
if (settings.enabled && settings.options.botAPI) {
|
||||||
settings.enabled &&
|
|
||||||
settings.options.botAPI &&
|
|
||||||
settings.options.chatId
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +57,10 @@ class TelegramAgent
|
|||||||
return text ? text.replace(/[_*[\]()~>#+=|{}.!-]/gi, (x) => '\\' + x) : '';
|
return text ? text.replace(/[_*[\]()~>#+=|{}.!-]/gi, (x) => '\\' + x) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildMessage(
|
private getNotificationPayload(
|
||||||
type: Notification,
|
type: Notification,
|
||||||
payload: NotificationPayload,
|
payload: NotificationPayload
|
||||||
chatId: string,
|
): Partial<TelegramMessagePayload | TelegramPhotoPayload> {
|
||||||
sendSilently: boolean
|
|
||||||
): TelegramMessagePayload | TelegramPhotoPayload {
|
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
let message = '';
|
let message = '';
|
||||||
|
|
||||||
@@ -160,19 +154,15 @@ class TelegramAgent
|
|||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
return payload.image
|
return payload.image
|
||||||
? ({
|
? {
|
||||||
photo: payload.image,
|
photo: payload.image,
|
||||||
caption: message,
|
caption: message,
|
||||||
parse_mode: 'MarkdownV2',
|
parse_mode: 'MarkdownV2',
|
||||||
chat_id: chatId,
|
}
|
||||||
disable_notification: !!sendSilently,
|
: {
|
||||||
} as TelegramPhotoPayload)
|
|
||||||
: ({
|
|
||||||
text: message,
|
text: message,
|
||||||
parse_mode: 'MarkdownV2',
|
parse_mode: 'MarkdownV2',
|
||||||
chat_id: chatId,
|
};
|
||||||
disable_notification: !!sendSilently,
|
|
||||||
} as TelegramMessagePayload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async send(
|
public async send(
|
||||||
@@ -180,13 +170,16 @@ class TelegramAgent
|
|||||||
payload: NotificationPayload
|
payload: NotificationPayload
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const settings = this.getSettings();
|
const settings = this.getSettings();
|
||||||
|
|
||||||
const endpoint = `${this.baseUrl}bot${settings.options.botAPI}/${
|
const endpoint = `${this.baseUrl}bot${settings.options.botAPI}/${
|
||||||
payload.image ? 'sendPhoto' : 'sendMessage'
|
payload.image ? 'sendPhoto' : 'sendMessage'
|
||||||
}`;
|
}`;
|
||||||
|
const notificationPayload = this.getNotificationPayload(type, payload);
|
||||||
|
|
||||||
// Send system notification
|
// Send system notification
|
||||||
if (hasNotificationType(type, settings.types ?? 0)) {
|
if (
|
||||||
|
hasNotificationType(type, settings.types ?? 0) &&
|
||||||
|
settings.options.chatId
|
||||||
|
) {
|
||||||
logger.debug('Sending Telegram notification', {
|
logger.debug('Sending Telegram notification', {
|
||||||
label: 'Notifications',
|
label: 'Notifications',
|
||||||
type: Notification[type],
|
type: Notification[type],
|
||||||
@@ -194,15 +187,11 @@ class TelegramAgent
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(endpoint, {
|
||||||
endpoint,
|
...notificationPayload,
|
||||||
this.buildMessage(
|
chat_id: settings.options.chatId,
|
||||||
type,
|
disable_notification: !!settings.options.sendSilently,
|
||||||
payload,
|
} as TelegramMessagePayload | TelegramPhotoPayload);
|
||||||
settings.options.chatId,
|
|
||||||
settings.options.sendSilently
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error sending Telegram notification', {
|
logger.error('Error sending Telegram notification', {
|
||||||
label: 'Notifications',
|
label: 'Notifications',
|
||||||
@@ -224,7 +213,7 @@ class TelegramAgent
|
|||||||
type
|
type
|
||||||
) &&
|
) &&
|
||||||
payload.notifyUser.settings?.telegramChatId &&
|
payload.notifyUser.settings?.telegramChatId &&
|
||||||
payload.notifyUser.settings?.telegramChatId !== settings.options.chatId
|
payload.notifyUser.settings.telegramChatId !== settings.options.chatId
|
||||||
) {
|
) {
|
||||||
logger.debug('Sending Telegram notification', {
|
logger.debug('Sending Telegram notification', {
|
||||||
label: 'Notifications',
|
label: 'Notifications',
|
||||||
@@ -234,15 +223,12 @@ class TelegramAgent
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(endpoint, {
|
||||||
endpoint,
|
...notificationPayload,
|
||||||
this.buildMessage(
|
chat_id: payload.notifyUser.settings.telegramChatId,
|
||||||
type,
|
disable_notification:
|
||||||
payload,
|
!!payload.notifyUser.settings.telegramSendSilently,
|
||||||
payload.notifyUser.settings.telegramChatId,
|
} as TelegramMessagePayload | TelegramPhotoPayload);
|
||||||
!!payload.notifyUser.settings.telegramSendSilently
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error sending Telegram notification', {
|
logger.error('Error sending Telegram notification', {
|
||||||
label: 'Notifications',
|
label: 'Notifications',
|
||||||
@@ -287,15 +273,11 @@ class TelegramAgent
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(endpoint, {
|
||||||
endpoint,
|
...notificationPayload,
|
||||||
this.buildMessage(
|
chat_id: user.settings.telegramChatId,
|
||||||
type,
|
disable_notification: !!user.settings?.telegramSendSilently,
|
||||||
payload,
|
} as TelegramMessagePayload | TelegramPhotoPayload);
|
||||||
user.settings.telegramChatId,
|
|
||||||
!!user.settings?.telegramSendSilently
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error sending Telegram notification', {
|
logger.error('Error sending Telegram notification', {
|
||||||
label: 'Notifications',
|
label: 'Notifications',
|
||||||
|
@@ -206,6 +206,11 @@ class WebPushAgent
|
|||||||
settings.vapidPrivate
|
settings.vapidPrivate
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const notificationPayload = Buffer.from(
|
||||||
|
JSON.stringify(this.getNotificationPayload(type, payload)),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pushSubs.map(async (sub) => {
|
pushSubs.map(async (sub) => {
|
||||||
logger.debug('Sending web push notification', {
|
logger.debug('Sending web push notification', {
|
||||||
@@ -224,10 +229,7 @@ class WebPushAgent
|
|||||||
p256dh: sub.p256dh,
|
p256dh: sub.p256dh,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Buffer.from(
|
notificationPayload
|
||||||
JSON.stringify(this.getNotificationPayload(type, payload)),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddPushbulletPushoverUserSettings1635079863457
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = 'AddPushbulletPushoverUserSettings1635079863457';
|
||||||
|
|
||||||
|
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, 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") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale" FROM "user_settings"`
|
||||||
|
);
|
||||||
|
await queryRunner.query(`DROP TABLE "user_settings"`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "temporary_user_settings" RENAME TO "user_settings"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "user_settings" RENAME TO "temporary_user_settings"`
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "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 (''), 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 "user_settings"("id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale" FROM "temporary_user_settings"`
|
||||||
|
);
|
||||||
|
await queryRunner.query(`DROP TABLE "temporary_user_settings"`);
|
||||||
|
}
|
||||||
|
}
|
@@ -257,6 +257,9 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>(
|
|||||||
? settings?.discord.types
|
? settings?.discord.types
|
||||||
: 0,
|
: 0,
|
||||||
discordId: user.settings?.discordId,
|
discordId: user.settings?.discordId,
|
||||||
|
pushbulletAccessToken: user.settings?.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: user.settings?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: user.settings?.pushoverUserKey,
|
||||||
telegramEnabled: settings?.telegram.enabled,
|
telegramEnabled: settings?.telegram.enabled,
|
||||||
telegramBotUsername: settings?.telegram.options.botUsername,
|
telegramBotUsername: settings?.telegram.options.botUsername,
|
||||||
telegramChatId: user.settings?.telegramChatId,
|
telegramChatId: user.settings?.telegramChatId,
|
||||||
@@ -298,6 +301,9 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>(
|
|||||||
user: req.user,
|
user: req.user,
|
||||||
pgpKey: req.body.pgpKey,
|
pgpKey: req.body.pgpKey,
|
||||||
discordId: req.body.discordId,
|
discordId: req.body.discordId,
|
||||||
|
pushbulletAccessToken: req.body.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: req.body.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: req.body.pushoverUserKey,
|
||||||
telegramChatId: req.body.telegramChatId,
|
telegramChatId: req.body.telegramChatId,
|
||||||
telegramSendSilently: req.body.telegramSendSilently,
|
telegramSendSilently: req.body.telegramSendSilently,
|
||||||
notificationTypes: req.body.notificationTypes,
|
notificationTypes: req.body.notificationTypes,
|
||||||
@@ -305,6 +311,10 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>(
|
|||||||
} else {
|
} else {
|
||||||
user.settings.pgpKey = req.body.pgpKey;
|
user.settings.pgpKey = req.body.pgpKey;
|
||||||
user.settings.discordId = req.body.discordId;
|
user.settings.discordId = req.body.discordId;
|
||||||
|
user.settings.pushbulletAccessToken = req.body.pushbulletAccessToken;
|
||||||
|
user.settings.pushoverApplicationToken =
|
||||||
|
req.body.pushoverApplicationToken;
|
||||||
|
user.settings.pushoverUserKey = req.body.pushoverUserKey;
|
||||||
user.settings.telegramChatId = req.body.telegramChatId;
|
user.settings.telegramChatId = req.body.telegramChatId;
|
||||||
user.settings.telegramSendSilently = req.body.telegramSendSilently;
|
user.settings.telegramSendSilently = req.body.telegramSendSilently;
|
||||||
user.settings.notificationTypes = Object.assign(
|
user.settings.notificationTypes = Object.assign(
|
||||||
@@ -319,6 +329,9 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>(
|
|||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
pgpKey: user.settings?.pgpKey,
|
pgpKey: user.settings?.pgpKey,
|
||||||
discordId: user.settings?.discordId,
|
discordId: user.settings?.discordId,
|
||||||
|
pushbulletAccessToken: user.settings?.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: user.settings?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: user.settings?.pushoverUserKey,
|
||||||
telegramChatId: user.settings?.telegramChatId,
|
telegramChatId: user.settings?.telegramChatId,
|
||||||
telegramSendSilently: user?.settings?.telegramSendSilently,
|
telegramSendSilently: user?.settings?.telegramSendSilently,
|
||||||
notificationTypes: user.settings.notificationTypes,
|
notificationTypes: user.settings.notificationTypes,
|
||||||
|
@@ -51,8 +51,8 @@ const NotificationsTelegram: React.FC = () => {
|
|||||||
otherwise: Yup.string().nullable(),
|
otherwise: Yup.string().nullable(),
|
||||||
}),
|
}),
|
||||||
chatId: Yup.string()
|
chatId: Yup.string()
|
||||||
.when('enabled', {
|
.when(['enabled', 'types'], {
|
||||||
is: true,
|
is: (enabled: boolean, types: number) => enabled && !!types,
|
||||||
then: Yup.string()
|
then: Yup.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.required(intl.formatMessage(messages.validationChatIdRequired)),
|
.required(intl.formatMessage(messages.validationChatIdRequired)),
|
||||||
|
@@ -35,7 +35,7 @@ const UserNotificationsDiscord: React.FC = () => {
|
|||||||
const UserNotificationsDiscordSchema = Yup.object().shape({
|
const UserNotificationsDiscordSchema = Yup.object().shape({
|
||||||
discordId: Yup.string()
|
discordId: Yup.string()
|
||||||
.when('types', {
|
.when('types', {
|
||||||
is: (value: unknown) => !!value,
|
is: (types: number) => !!types,
|
||||||
then: Yup.string()
|
then: Yup.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.required(intl.formatMessage(messages.validationDiscordId)),
|
.required(intl.formatMessage(messages.validationDiscordId)),
|
||||||
@@ -63,6 +63,9 @@ const UserNotificationsDiscord: React.FC = () => {
|
|||||||
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
||||||
pgpKey: data?.pgpKey,
|
pgpKey: data?.pgpKey,
|
||||||
discordId: values.discordId,
|
discordId: values.discordId,
|
||||||
|
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: data?.pushoverUserKey,
|
||||||
telegramChatId: data?.telegramChatId,
|
telegramChatId: data?.telegramChatId,
|
||||||
telegramSendSilently: data?.telegramSendSilently,
|
telegramSendSilently: data?.telegramSendSilently,
|
||||||
notificationTypes: {
|
notificationTypes: {
|
||||||
|
@@ -63,6 +63,9 @@ const UserEmailSettings: React.FC = () => {
|
|||||||
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
||||||
pgpKey: values.pgpKey,
|
pgpKey: values.pgpKey,
|
||||||
discordId: data?.discordId,
|
discordId: data?.discordId,
|
||||||
|
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: data?.pushoverUserKey,
|
||||||
telegramChatId: data?.telegramChatId,
|
telegramChatId: data?.telegramChatId,
|
||||||
telegramSendSilently: data?.telegramSendSilently,
|
telegramSendSilently: data?.telegramSendSilently,
|
||||||
notificationTypes: {
|
notificationTypes: {
|
||||||
|
@@ -0,0 +1,172 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { Form, Formik } from 'formik';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
|
||||||
|
import { useUser } from '../../../../hooks/useUser';
|
||||||
|
import globalMessages from '../../../../i18n/globalMessages';
|
||||||
|
import Button from '../../../Common/Button';
|
||||||
|
import LoadingSpinner from '../../../Common/LoadingSpinner';
|
||||||
|
import SensitiveInput from '../../../Common/SensitiveInput';
|
||||||
|
import NotificationTypeSelector from '../../../NotificationTypeSelector';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
pushbulletsettingssaved:
|
||||||
|
'Pushbullet notification settings saved successfully!',
|
||||||
|
pushbulletsettingsfailed: 'Pushbullet notification settings failed to save.',
|
||||||
|
pushbulletAccessToken: 'Access Token',
|
||||||
|
pushbulletAccessTokenTip:
|
||||||
|
'Create a token from your <PushbulletSettingsLink>Account Settings</PushbulletSettingsLink>',
|
||||||
|
validationPushbulletAccessToken: 'You must provide an access token',
|
||||||
|
});
|
||||||
|
|
||||||
|
const UserPushbulletSettings: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { addToast } = useToasts();
|
||||||
|
const router = useRouter();
|
||||||
|
const { user } = useUser({ id: Number(router.query.userId) });
|
||||||
|
const { data, error, revalidate } = useSWR<UserSettingsNotificationsResponse>(
|
||||||
|
user ? `/api/v1/user/${user?.id}/settings/notifications` : null
|
||||||
|
);
|
||||||
|
|
||||||
|
const UserNotificationsPushbulletSchema = Yup.object().shape({
|
||||||
|
pushbulletAccessToken: Yup.string().when('types', {
|
||||||
|
is: (types: number) => !!types,
|
||||||
|
then: Yup.string()
|
||||||
|
.nullable()
|
||||||
|
.required(intl.formatMessage(messages.validationPushbulletAccessToken)),
|
||||||
|
otherwise: Yup.string().nullable(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data && !error) {
|
||||||
|
return <LoadingSpinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||||
|
types: data?.notificationTypes.pushbullet ?? 0,
|
||||||
|
}}
|
||||||
|
validationSchema={UserNotificationsPushbulletSchema}
|
||||||
|
enableReinitialize
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
try {
|
||||||
|
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
||||||
|
pgpKey: data?.pgpKey,
|
||||||
|
discordId: data?.discordId,
|
||||||
|
pushbulletAccessToken: values.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: data?.pushoverUserKey,
|
||||||
|
telegramChatId: data?.telegramChatId,
|
||||||
|
telegramSendSilently: data?.telegramSendSilently,
|
||||||
|
notificationTypes: {
|
||||||
|
pushbullet: values.types,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
addToast(intl.formatMessage(messages.pushbulletsettingssaved), {
|
||||||
|
appearance: 'success',
|
||||||
|
autoDismiss: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
addToast(intl.formatMessage(messages.pushbulletsettingsfailed), {
|
||||||
|
appearance: 'error',
|
||||||
|
autoDismiss: true,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
isSubmitting,
|
||||||
|
isValid,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
setFieldTouched,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Form className="section">
|
||||||
|
<div className="form-row">
|
||||||
|
<label htmlFor="pushbulletAccessToken" className="text-label">
|
||||||
|
{intl.formatMessage(messages.pushbulletAccessToken)}
|
||||||
|
<span className="label-required">*</span>
|
||||||
|
{data?.pushbulletAccessToken && (
|
||||||
|
<span className="label-tip">
|
||||||
|
{intl.formatMessage(messages.pushbulletAccessTokenTip, {
|
||||||
|
PushbulletSettingsLink: function PushbulletSettingsLink(
|
||||||
|
msg
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://www.pushbullet.com/#settings/account"
|
||||||
|
className="text-white transition duration-300 hover:underline"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{msg}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<div className="form-input">
|
||||||
|
<div className="form-input-field">
|
||||||
|
<SensitiveInput
|
||||||
|
as="field"
|
||||||
|
id="pushbulletAccessToken"
|
||||||
|
name="pushbulletAccessToken"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.pushbulletAccessToken &&
|
||||||
|
touched.pushbulletAccessToken && (
|
||||||
|
<div className="error">{errors.pushbulletAccessToken}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NotificationTypeSelector
|
||||||
|
user={user}
|
||||||
|
currentTypes={values.types}
|
||||||
|
onUpdate={(newTypes) => {
|
||||||
|
setFieldValue('types', newTypes);
|
||||||
|
setFieldTouched('types');
|
||||||
|
}}
|
||||||
|
error={
|
||||||
|
errors.types && touched.types
|
||||||
|
? (errors.types as string)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="actions">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||||
|
<Button
|
||||||
|
buttonType="primary"
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isValid}
|
||||||
|
>
|
||||||
|
{isSubmitting
|
||||||
|
? intl.formatMessage(globalMessages.saving)
|
||||||
|
: intl.formatMessage(globalMessages.save)}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserPushbulletSettings;
|
@@ -0,0 +1,228 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { Field, Form, Formik } from 'formik';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
|
||||||
|
import { useUser } from '../../../../hooks/useUser';
|
||||||
|
import globalMessages from '../../../../i18n/globalMessages';
|
||||||
|
import Button from '../../../Common/Button';
|
||||||
|
import LoadingSpinner from '../../../Common/LoadingSpinner';
|
||||||
|
import NotificationTypeSelector from '../../../NotificationTypeSelector';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
pushoversettingssaved: 'Pushover notification settings saved successfully!',
|
||||||
|
pushoversettingsfailed: 'Pushover notification settings failed to save.',
|
||||||
|
pushoverApplicationToken: 'Application API Token',
|
||||||
|
pushoverApplicationTokenTip:
|
||||||
|
'<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr',
|
||||||
|
pushoverUserKey: 'User or Group Key',
|
||||||
|
pushoverUserKeyTip:
|
||||||
|
'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>',
|
||||||
|
validationPushoverApplicationToken:
|
||||||
|
'You must provide a valid application token',
|
||||||
|
validationPushoverUserKey: 'You must provide a valid user or group key',
|
||||||
|
});
|
||||||
|
|
||||||
|
const UserPushoverSettings: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { addToast } = useToasts();
|
||||||
|
const router = useRouter();
|
||||||
|
const { user } = useUser({ id: Number(router.query.userId) });
|
||||||
|
const { data, error, revalidate } = useSWR<UserSettingsNotificationsResponse>(
|
||||||
|
user ? `/api/v1/user/${user?.id}/settings/notifications` : null
|
||||||
|
);
|
||||||
|
|
||||||
|
const UserNotificationsPushoverSchema = Yup.object().shape({
|
||||||
|
pushoverApplicationToken: Yup.string()
|
||||||
|
.when('types', {
|
||||||
|
is: (types: number) => !!types,
|
||||||
|
then: Yup.string()
|
||||||
|
.nullable()
|
||||||
|
.required(
|
||||||
|
intl.formatMessage(messages.validationPushoverApplicationToken)
|
||||||
|
),
|
||||||
|
otherwise: Yup.string().nullable(),
|
||||||
|
})
|
||||||
|
.matches(
|
||||||
|
/^[a-z\d]{30}$/i,
|
||||||
|
intl.formatMessage(messages.validationPushoverApplicationToken)
|
||||||
|
),
|
||||||
|
pushoverUserKey: Yup.string()
|
||||||
|
.when('types', {
|
||||||
|
is: (types: number) => !!types,
|
||||||
|
then: Yup.string()
|
||||||
|
.nullable()
|
||||||
|
.required(intl.formatMessage(messages.validationPushoverUserKey)),
|
||||||
|
otherwise: Yup.string().nullable(),
|
||||||
|
})
|
||||||
|
.matches(
|
||||||
|
/^[a-z\d]{30}$/i,
|
||||||
|
intl.formatMessage(messages.validationPushoverUserKey)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data && !error) {
|
||||||
|
return <LoadingSpinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: data?.pushoverUserKey,
|
||||||
|
types: data?.notificationTypes.pushover ?? 0,
|
||||||
|
}}
|
||||||
|
validationSchema={UserNotificationsPushoverSchema}
|
||||||
|
enableReinitialize
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
try {
|
||||||
|
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
||||||
|
pgpKey: data?.pgpKey,
|
||||||
|
discordId: data?.discordId,
|
||||||
|
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: values.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: values.pushoverUserKey,
|
||||||
|
telegramChatId: data?.telegramChatId,
|
||||||
|
telegramSendSilently: data?.telegramSendSilently,
|
||||||
|
notificationTypes: {
|
||||||
|
pushover: values.types,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
addToast(intl.formatMessage(messages.pushoversettingssaved), {
|
||||||
|
appearance: 'success',
|
||||||
|
autoDismiss: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
addToast(intl.formatMessage(messages.pushoversettingsfailed), {
|
||||||
|
appearance: 'error',
|
||||||
|
autoDismiss: true,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
isSubmitting,
|
||||||
|
isValid,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
setFieldTouched,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Form className="section">
|
||||||
|
<div className="form-row">
|
||||||
|
<label htmlFor="pushoverApplicationToken" className="text-label">
|
||||||
|
{intl.formatMessage(messages.pushoverApplicationToken)}
|
||||||
|
<span className="label-required">*</span>
|
||||||
|
{data?.pushoverApplicationToken && (
|
||||||
|
<span className="label-tip">
|
||||||
|
{intl.formatMessage(messages.pushoverApplicationTokenTip, {
|
||||||
|
ApplicationRegistrationLink:
|
||||||
|
function ApplicationRegistrationLink(msg) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://pushover.net/api#registration"
|
||||||
|
className="text-white transition duration-300 hover:underline"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{msg}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<div className="form-input">
|
||||||
|
<div className="form-input-field">
|
||||||
|
<Field
|
||||||
|
id="pushoverApplicationToken"
|
||||||
|
name="pushoverApplicationToken"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.pushoverApplicationToken &&
|
||||||
|
touched.pushoverApplicationToken && (
|
||||||
|
<div className="error">
|
||||||
|
{errors.pushoverApplicationToken}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-row">
|
||||||
|
<label htmlFor="pushoverUserKey" className="checkbox-label">
|
||||||
|
{intl.formatMessage(messages.pushoverUserKey)}
|
||||||
|
<span className="label-tip">
|
||||||
|
{intl.formatMessage(messages.pushoverUserKeyTip, {
|
||||||
|
UsersGroupsLink: function UsersGroupsLink(msg) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://pushover.net/api#identifiers"
|
||||||
|
className="text-white transition duration-300 hover:underline"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{msg}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div className="form-input">
|
||||||
|
<div className="form-input-field">
|
||||||
|
<Field
|
||||||
|
id="pushoverUserKey"
|
||||||
|
name="pushoverUserKey"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.pushoverUserKey && touched.pushoverUserKey && (
|
||||||
|
<div className="error">{errors.pushoverUserKey}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NotificationTypeSelector
|
||||||
|
user={user}
|
||||||
|
currentTypes={values.types}
|
||||||
|
onUpdate={(newTypes) => {
|
||||||
|
setFieldValue('types', newTypes);
|
||||||
|
setFieldTouched('types');
|
||||||
|
}}
|
||||||
|
error={
|
||||||
|
errors.types && touched.types
|
||||||
|
? (errors.types as string)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="actions">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||||
|
<Button
|
||||||
|
buttonType="primary"
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isValid}
|
||||||
|
>
|
||||||
|
{isSubmitting
|
||||||
|
? intl.formatMessage(globalMessages.saving)
|
||||||
|
: intl.formatMessage(globalMessages.save)}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserPushoverSettings;
|
@@ -37,7 +37,7 @@ const UserTelegramSettings: React.FC = () => {
|
|||||||
const UserNotificationsTelegramSchema = Yup.object().shape({
|
const UserNotificationsTelegramSchema = Yup.object().shape({
|
||||||
telegramChatId: Yup.string()
|
telegramChatId: Yup.string()
|
||||||
.when('types', {
|
.when('types', {
|
||||||
is: (value: unknown) => !!value,
|
is: (types: number) => !!types,
|
||||||
then: Yup.string()
|
then: Yup.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.required(intl.formatMessage(messages.validationTelegramChatId)),
|
.required(intl.formatMessage(messages.validationTelegramChatId)),
|
||||||
@@ -67,6 +67,9 @@ const UserTelegramSettings: React.FC = () => {
|
|||||||
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
||||||
pgpKey: data?.pgpKey,
|
pgpKey: data?.pgpKey,
|
||||||
discordId: data?.discordId,
|
discordId: data?.discordId,
|
||||||
|
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: data?.pushoverUserKey,
|
||||||
telegramChatId: values.telegramChatId,
|
telegramChatId: values.telegramChatId,
|
||||||
telegramSendSilently: values.telegramSendSilently,
|
telegramSendSilently: values.telegramSendSilently,
|
||||||
notificationTypes: {
|
notificationTypes: {
|
||||||
|
@@ -44,6 +44,9 @@ const UserWebPushSettings: React.FC = () => {
|
|||||||
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
|
||||||
pgpKey: data?.pgpKey,
|
pgpKey: data?.pgpKey,
|
||||||
discordId: data?.discordId,
|
discordId: data?.discordId,
|
||||||
|
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||||
|
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||||
|
pushoverUserKey: data?.pushoverUserKey,
|
||||||
telegramChatId: data?.telegramChatId,
|
telegramChatId: data?.telegramChatId,
|
||||||
telegramSendSilently: data?.telegramSendSilently,
|
telegramSendSilently: data?.telegramSendSilently,
|
||||||
notificationTypes: {
|
notificationTypes: {
|
||||||
|
@@ -5,6 +5,8 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
|
import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
|
||||||
import DiscordLogo from '../../../../assets/extlogos/discord.svg';
|
import DiscordLogo from '../../../../assets/extlogos/discord.svg';
|
||||||
|
import PushbulletLogo from '../../../../assets/extlogos/pushbullet.svg';
|
||||||
|
import PushoverLogo from '../../../../assets/extlogos/pushover.svg';
|
||||||
import TelegramLogo from '../../../../assets/extlogos/telegram.svg';
|
import TelegramLogo from '../../../../assets/extlogos/telegram.svg';
|
||||||
import { useUser } from '../../../../hooks/useUser';
|
import { useUser } from '../../../../hooks/useUser';
|
||||||
import globalMessages from '../../../../i18n/globalMessages';
|
import globalMessages from '../../../../i18n/globalMessages';
|
||||||
@@ -64,6 +66,28 @@ const UserNotificationSettings: React.FC = ({ children }) => {
|
|||||||
route: '/settings/notifications/discord',
|
route: '/settings/notifications/discord',
|
||||||
regex: /\/settings\/notifications\/discord/,
|
regex: /\/settings\/notifications\/discord/,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Pushbullet',
|
||||||
|
content: (
|
||||||
|
<span className="flex items-center">
|
||||||
|
<PushbulletLogo className="h-4 mr-2" />
|
||||||
|
Pushbullet
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
route: '/settings/notifications/pushbullet',
|
||||||
|
regex: /\/settings\/notifications\/pushbullet/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Pushover',
|
||||||
|
content: (
|
||||||
|
<span className="flex items-center">
|
||||||
|
<PushoverLogo className="h-4 mr-2" />
|
||||||
|
Pushover
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
route: '/settings/notifications/pushover',
|
||||||
|
regex: /\/settings\/notifications\/pushover/,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Telegram',
|
text: 'Telegram',
|
||||||
content: (
|
content: (
|
||||||
|
@@ -874,6 +874,16 @@
|
|||||||
"components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Notification Settings",
|
"components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Notification Settings",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "PGP Public Key",
|
"components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "PGP Public Key",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Encrypt email messages using <OpenPgpLink>OpenPGP</OpenPgpLink>",
|
"components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Encrypt email messages using <OpenPgpLink>OpenPGP</OpenPgpLink>",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Access Token",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Create a token from your <PushbulletSettingsLink>Account Settings</PushbulletSettingsLink>",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Pushbullet notification settings failed to save.",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet notification settings saved successfully!",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Application API Token",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "User or Group Key",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover notification settings failed to save.",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Pushover notification settings saved successfully!",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Send Silently",
|
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Send Silently",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Send notifications with no sound",
|
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Send notifications with no sound",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Chat ID",
|
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Chat ID",
|
||||||
@@ -882,6 +892,9 @@
|
|||||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram notification settings saved successfully!",
|
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram notification settings saved successfully!",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "You must provide a valid user ID",
|
"components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "You must provide a valid user ID",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "You must provide a valid PGP public key",
|
"components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "You must provide a valid PGP public key",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "You must provide an access token",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "You must provide a valid application token",
|
||||||
|
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "You must provide a valid user or group key",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid chat ID",
|
"components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid chat ID",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push",
|
"components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push",
|
||||||
"components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Web push notification settings failed to save.",
|
"components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Web push notification settings failed to save.",
|
||||||
|
17
src/pages/profile/settings/notifications/pushbullet.tsx
Normal file
17
src/pages/profile/settings/notifications/pushbullet.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NextPage } from 'next';
|
||||||
|
import React from 'react';
|
||||||
|
import UserSettings from '../../../../components/UserProfile/UserSettings';
|
||||||
|
import UserNotificationSettings from '../../../../components/UserProfile/UserSettings/UserNotificationSettings';
|
||||||
|
import UserNotificationsPushbullet from '../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet';
|
||||||
|
|
||||||
|
const NotificationsPage: NextPage = () => {
|
||||||
|
return (
|
||||||
|
<UserSettings>
|
||||||
|
<UserNotificationSettings>
|
||||||
|
<UserNotificationsPushbullet />
|
||||||
|
</UserNotificationSettings>
|
||||||
|
</UserSettings>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationsPage;
|
17
src/pages/profile/settings/notifications/pushover.tsx
Normal file
17
src/pages/profile/settings/notifications/pushover.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NextPage } from 'next';
|
||||||
|
import React from 'react';
|
||||||
|
import UserSettings from '../../../../components/UserProfile/UserSettings';
|
||||||
|
import UserNotificationSettings from '../../../../components/UserProfile/UserSettings/UserNotificationSettings';
|
||||||
|
import UserNotificationsPushover from '../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover';
|
||||||
|
|
||||||
|
const NotificationsPage: NextPage = () => {
|
||||||
|
return (
|
||||||
|
<UserSettings>
|
||||||
|
<UserNotificationSettings>
|
||||||
|
<UserNotificationsPushover />
|
||||||
|
</UserNotificationSettings>
|
||||||
|
</UserSettings>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationsPage;
|
@@ -0,0 +1,20 @@
|
|||||||
|
import { NextPage } from 'next';
|
||||||
|
import React from 'react';
|
||||||
|
import UserSettings from '../../../../../components/UserProfile/UserSettings';
|
||||||
|
import UserNotificationSettings from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings';
|
||||||
|
import UserNotificationsPushbullet from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet';
|
||||||
|
import useRouteGuard from '../../../../../hooks/useRouteGuard';
|
||||||
|
import { Permission } from '../../../../../hooks/useUser';
|
||||||
|
|
||||||
|
const NotificationsPage: NextPage = () => {
|
||||||
|
useRouteGuard(Permission.MANAGE_USERS);
|
||||||
|
return (
|
||||||
|
<UserSettings>
|
||||||
|
<UserNotificationSettings>
|
||||||
|
<UserNotificationsPushbullet />
|
||||||
|
</UserNotificationSettings>
|
||||||
|
</UserSettings>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationsPage;
|
20
src/pages/users/[userId]/settings/notifications/pushover.tsx
Normal file
20
src/pages/users/[userId]/settings/notifications/pushover.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { NextPage } from 'next';
|
||||||
|
import React from 'react';
|
||||||
|
import UserSettings from '../../../../../components/UserProfile/UserSettings';
|
||||||
|
import UserNotificationSettings from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings';
|
||||||
|
import UserNotificationsPushover from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover';
|
||||||
|
import useRouteGuard from '../../../../../hooks/useRouteGuard';
|
||||||
|
import { Permission } from '../../../../../hooks/useUser';
|
||||||
|
|
||||||
|
const NotificationsPage: NextPage = () => {
|
||||||
|
useRouteGuard(Permission.MANAGE_USERS);
|
||||||
|
return (
|
||||||
|
<UserSettings>
|
||||||
|
<UserNotificationSettings>
|
||||||
|
<UserNotificationsPushover />
|
||||||
|
</UserNotificationSettings>
|
||||||
|
</UserSettings>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationsPage;
|
Reference in New Issue
Block a user