feat(telegram): add support for individual chat notifications (#1027)
This commit is contained in:
@@ -35,6 +35,7 @@ These variables are usually the target user of the notification.
|
||||
- `{{notifyuser_email}}` Target user's email.
|
||||
- `{{notifyuser_avatar}}` Target user's avatar.
|
||||
- `{{notifyuser_settings_discordId}}` Target user's discord ID (if one is set).
|
||||
- `{{notifyuser_settings_telegramChatId}}` Target user's telegram Chat ID (if one is set).
|
||||
|
||||
### Media
|
||||
|
||||
|
||||
@@ -97,6 +97,10 @@ components:
|
||||
default: true
|
||||
discordId:
|
||||
type: string
|
||||
telegramChatId:
|
||||
type: string
|
||||
telegramSendSilently:
|
||||
type: boolean
|
||||
required:
|
||||
- enableNotifications
|
||||
MainSettings:
|
||||
@@ -1545,6 +1549,12 @@ components:
|
||||
discordId:
|
||||
type: string
|
||||
nullable: true
|
||||
telegramChatId:
|
||||
type: string
|
||||
nullable: true
|
||||
telegramSendSilently:
|
||||
type: boolean
|
||||
nullable: true
|
||||
required:
|
||||
- enableNotifications
|
||||
securitySchemes:
|
||||
|
||||
@@ -26,6 +26,12 @@ export class UserSettings {
|
||||
@Column({ nullable: true })
|
||||
public discordId?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public telegramChatId?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public telegramSendSilently?: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public region?: string;
|
||||
|
||||
|
||||
@@ -6,5 +6,8 @@ export interface UserSettingsGeneralResponse {
|
||||
|
||||
export interface UserSettingsNotificationsResponse {
|
||||
enableNotifications: boolean;
|
||||
telegramBotUsername?: string;
|
||||
discordId?: string;
|
||||
telegramChatId?: string;
|
||||
telegramSendSilently?: boolean;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,21 @@ class TelegramAgent
|
||||
disable_notification: this.getSettings().options.sendSilently,
|
||||
} as TelegramPayload);
|
||||
|
||||
if (
|
||||
payload.notifyUser.settings?.enableNotifications &&
|
||||
payload.notifyUser.settings?.telegramChatId &&
|
||||
payload.notifyUser.settings?.telegramChatId !==
|
||||
this.getSettings().options.chatId
|
||||
) {
|
||||
await axios.post(endpoint, {
|
||||
text: this.buildMessage(type, payload),
|
||||
parse_mode: 'MarkdownV2',
|
||||
chat_id: `${payload.notifyUser.settings.telegramChatId}`,
|
||||
disable_notification:
|
||||
payload.notifyUser.settings.telegramSendSilently,
|
||||
} as TelegramPayload);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.error('Error sending Telegram notification', {
|
||||
|
||||
@@ -20,6 +20,7 @@ const KeyMap: Record<string, string | KeyMapFunction> = {
|
||||
notifyuser_email: 'notifyUser.email',
|
||||
notifyuser_avatar: 'notifyUser.avatar',
|
||||
notifyuser_settings_discordId: 'notifyUser.settings.discordId',
|
||||
notifyuser_settings_telegramChatId: 'notifyUser.settings.telegramChatId',
|
||||
media_tmdbid: 'media.tmdbId',
|
||||
media_imdbid: 'media.imdbId',
|
||||
media_tvdbid: 'media.tvdbId',
|
||||
|
||||
@@ -120,6 +120,7 @@ export interface NotificationAgentEmail extends NotificationAgentConfig {
|
||||
|
||||
export interface NotificationAgentTelegram extends NotificationAgentConfig {
|
||||
options: {
|
||||
botUsername: string;
|
||||
botAPI: string;
|
||||
chatId: string;
|
||||
sendSilently: boolean;
|
||||
@@ -242,6 +243,7 @@ class Settings {
|
||||
enabled: false,
|
||||
types: 0,
|
||||
options: {
|
||||
botUsername: '',
|
||||
botAPI: '',
|
||||
chatId: '',
|
||||
sendSilently: false,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddTelegramSettingsToUserSettings1614334195680
|
||||
implements MigrationInterface {
|
||||
name = 'AddTelegramSettingsToUserSettings1614334195680';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "enableNotifications" boolean NOT NULL DEFAULT (1), "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" 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", "enableNotifications", "discordId", "userId", "region", "originalLanguage") SELECT "id", "enableNotifications", "discordId", "userId", "region", "originalLanguage" 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, "enableNotifications" boolean NOT NULL DEFAULT (1), "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" 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 "user_settings"("id", "enableNotifications", "discordId", "userId", "region", "originalLanguage") SELECT "id", "enableNotifications", "discordId", "userId", "region", "originalLanguage" FROM "temporary_user_settings"`
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_settings"`);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Router } from 'express';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { canMakePermissionsChange } from '.';
|
||||
import { User } from '../../entity/User';
|
||||
import { getSettings } from '../../lib/settings';
|
||||
import { UserSettings } from '../../entity/UserSettings';
|
||||
import {
|
||||
UserSettingsGeneralResponse,
|
||||
@@ -198,6 +199,7 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>(
|
||||
isOwnProfileOrAdmin(),
|
||||
async (req, res, next) => {
|
||||
const userRepository = getRepository(User);
|
||||
const settings = getSettings();
|
||||
|
||||
try {
|
||||
const user = await userRepository.findOne({
|
||||
@@ -210,7 +212,11 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>(
|
||||
|
||||
return res.status(200).json({
|
||||
enableNotifications: user.settings?.enableNotifications ?? true,
|
||||
telegramBotUsername:
|
||||
settings?.notifications.agents.telegram.options.botUsername,
|
||||
discordId: user.settings?.discordId,
|
||||
telegramChatId: user.settings?.telegramChatId,
|
||||
telegramSendSilently: user?.settings?.telegramSendSilently,
|
||||
});
|
||||
} catch (e) {
|
||||
next({ status: 500, message: e.message });
|
||||
@@ -239,10 +245,14 @@ userSettingsRoutes.post<
|
||||
user: req.user,
|
||||
enableNotifications: req.body.enableNotifications,
|
||||
discordId: req.body.discordId,
|
||||
telegramChatId: req.body.telegramChatId,
|
||||
telegramSendSilently: req.body.telegramSendSilently,
|
||||
});
|
||||
} else {
|
||||
user.settings.enableNotifications = req.body.enableNotifications;
|
||||
user.settings.discordId = req.body.discordId;
|
||||
user.settings.telegramChatId = req.body.telegramChatId;
|
||||
user.settings.telegramSendSilently = req.body.telegramSendSilently;
|
||||
}
|
||||
|
||||
userRepository.save(user);
|
||||
@@ -250,6 +260,8 @@ userSettingsRoutes.post<
|
||||
return res.status(200).json({
|
||||
enableNotifications: user.settings.enableNotifications,
|
||||
discordId: user.settings.discordId,
|
||||
telegramChatId: user.settings.telegramChatId,
|
||||
telegramSendSilently: user.settings.telegramSendSilently,
|
||||
});
|
||||
} catch (e) {
|
||||
next({ status: 500, message: e.message });
|
||||
|
||||
@@ -14,6 +14,7 @@ const messages = defineMessages({
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
agentenabled: 'Enable Agent',
|
||||
botUsername: 'Bot Username',
|
||||
botAPI: 'Bot Authentication Token',
|
||||
chatId: 'Chat ID',
|
||||
validationBotAPIRequired: 'You must provide a bot authentication token',
|
||||
@@ -43,9 +44,12 @@ const NotificationsTelegram: React.FC = () => {
|
||||
botAPI: Yup.string().required(
|
||||
intl.formatMessage(messages.validationBotAPIRequired)
|
||||
),
|
||||
chatId: Yup.string().required(
|
||||
intl.formatMessage(messages.validationChatIdRequired)
|
||||
),
|
||||
chatId: Yup.string()
|
||||
.required(intl.formatMessage(messages.validationChatIdRequired))
|
||||
.matches(
|
||||
/^[-]?\d+$/,
|
||||
intl.formatMessage(messages.validationChatIdRequired)
|
||||
),
|
||||
});
|
||||
|
||||
if (!data && !error) {
|
||||
@@ -57,6 +61,7 @@ const NotificationsTelegram: React.FC = () => {
|
||||
initialValues={{
|
||||
enabled: data?.enabled,
|
||||
types: data?.types,
|
||||
botUsername: data?.options.botUsername,
|
||||
botAPI: data?.options.botAPI,
|
||||
chatId: data?.options.chatId,
|
||||
sendSilently: data?.options.sendSilently,
|
||||
@@ -71,6 +76,7 @@ const NotificationsTelegram: React.FC = () => {
|
||||
botAPI: values.botAPI,
|
||||
chatId: values.chatId,
|
||||
sendSilently: values.sendSilently,
|
||||
botUsername: values.botUsername,
|
||||
},
|
||||
});
|
||||
addToast(intl.formatMessage(messages.telegramsettingssaved), {
|
||||
@@ -96,6 +102,7 @@ const NotificationsTelegram: React.FC = () => {
|
||||
botAPI: values.botAPI,
|
||||
chatId: values.chatId,
|
||||
sendSilently: values.sendSilently,
|
||||
botUsername: values.botUsername,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -147,6 +154,24 @@ const NotificationsTelegram: React.FC = () => {
|
||||
<Field type="checkbox" id="enabled" name="enabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="botUsername" className="text-label">
|
||||
{intl.formatMessage(messages.botUsername)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
id="botUsername"
|
||||
name="botUsername"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.botUsername)}
|
||||
/>
|
||||
</div>
|
||||
{errors.botUsername && touched.botUsername && (
|
||||
<div className="error">{errors.botUsername}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="botAPI" className="text-label">
|
||||
{intl.formatMessage(messages.botAPI)}
|
||||
|
||||
@@ -19,6 +19,15 @@ const messages = defineMessages({
|
||||
discordIdTip:
|
||||
'The <FindDiscordIdLink>ID number</FindDiscordIdLink> for your Discord user account',
|
||||
validationDiscordId: 'You must provide a valid Discord user ID',
|
||||
telegramChatId: 'Telegram Chat ID',
|
||||
telegramChatIdTip:
|
||||
'The Chat ID can be aquired by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat.',
|
||||
telegramChatIdTipLong:
|
||||
'Start a chat by clicking <TelegramBotLink>here</TelegramBotLink>.\
|
||||
Then get the group Chat ID by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to that chat and send /my_id to the chat',
|
||||
sendSilently: 'Send Silently',
|
||||
sendSilentlyDescription: 'Send telegram notifications silently',
|
||||
validationTelegramChatId: 'You must provide a valid Telegram Chat ID',
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
plexuser: 'Plex User',
|
||||
@@ -40,6 +49,12 @@ const UserNotificationSettings: React.FC = () => {
|
||||
discordId: Yup.string()
|
||||
.optional()
|
||||
.matches(/^\d{17,18}$/, intl.formatMessage(messages.validationDiscordId)),
|
||||
telegramChatId: Yup.string()
|
||||
.optional()
|
||||
.matches(
|
||||
/^[-]?\d+$/,
|
||||
intl.formatMessage(messages.validationTelegramChatId)
|
||||
),
|
||||
});
|
||||
|
||||
if (!data && !error) {
|
||||
@@ -61,6 +76,8 @@ const UserNotificationSettings: React.FC = () => {
|
||||
initialValues={{
|
||||
enableNotifications: data?.enableNotifications,
|
||||
discordId: data?.discordId,
|
||||
telegramChatId: data?.telegramChatId,
|
||||
telegramSendSilently: data?.telegramSendSilently,
|
||||
}}
|
||||
validationSchema={UserNotificationSettingsSchema}
|
||||
enableReinitialize
|
||||
@@ -71,6 +88,8 @@ const UserNotificationSettings: React.FC = () => {
|
||||
{
|
||||
enableNotifications: values.enableNotifications,
|
||||
discordId: values.discordId,
|
||||
telegramChatId: values.telegramChatId,
|
||||
telegramSendSilently: values.telegramSendSilently,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -135,6 +154,86 @@ const UserNotificationSettings: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="telegramChatId" className="text-label">
|
||||
<span>{intl.formatMessage(messages.telegramChatId)}</span>
|
||||
<span className="label-tip">
|
||||
{data?.telegramBotUsername
|
||||
? intl.formatMessage(messages.telegramChatIdTipLong, {
|
||||
TelegramBotLink: function TelegramBotLink(msg) {
|
||||
return (
|
||||
<a
|
||||
href={`https://telegram.me/${data.telegramBotUsername}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-gray-100 underline transition duration-300 hover:text-white"
|
||||
>
|
||||
{msg}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
GetIdBotLink: function GetIdBotLink(msg) {
|
||||
return (
|
||||
<a
|
||||
href="https://telegram.me/get_id_bot"
|
||||
className="text-gray-100 underline transition duration-300 hover:text-white"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{msg}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
})
|
||||
: intl.formatMessage(messages.telegramChatIdTip, {
|
||||
GetIdBotLink: function GetIdBotLink(msg) {
|
||||
return (
|
||||
<a
|
||||
href="https://telegram.me/get_id_bot"
|
||||
className="text-gray-100 underline transition duration-300 hover:text-white"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{msg}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
})}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
id="telegramChatId"
|
||||
name="telegramChatId"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{errors.telegramChatId && touched.telegramChatId && (
|
||||
<div className="error">{errors.telegramChatId}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="telegramSendSilently"
|
||||
className="checkbox-label"
|
||||
>
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.sendSilently)}
|
||||
</span>
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.sendSilentlyDescription)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="telegramSendSilently"
|
||||
name="telegramSendSilently"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"components.Settings.Notifications.authPass": "SMTP Password",
|
||||
"components.Settings.Notifications.authUser": "SMTP Username",
|
||||
"components.Settings.Notifications.botAPI": "Bot Authentication Token",
|
||||
"components.Settings.Notifications.botUsername": "Bot Username",
|
||||
"components.Settings.Notifications.chatId": "Chat ID",
|
||||
"components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.",
|
||||
"components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!",
|
||||
@@ -718,9 +719,15 @@
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.plexuser": "Plex User",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.save": "Save Changes",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.saving": "Saving…",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Send Silently",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Send telegram notifications silently",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Telegram Chat ID",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTip": "The Chat ID can be aquired by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat.",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "Start a chat by clicking <TelegramBotLink>here</TelegramBotLink>. Then get the group Chat ID by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to that chat and send /my_id to the chat",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.toastSettingsFailure": "Something went wrong while saving settings.",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.toastSettingsSuccess": "Settings successfully saved!",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "You must provide a valid Discord user ID",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid Telegram Chat ID",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Confirm Password",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Current Password",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.newpassword": "New Password",
|
||||
|
||||
Reference in New Issue
Block a user