feat: add telegram notification support
This commit is contained in:
parent
ce6fd56d51
commit
e5a52257ea
17
src/client/components/AutoLoadingButton.tsx
Normal file
17
src/client/components/AutoLoadingButton.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Button, ButtonProps } from 'antd';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useEvent } from '../hooks/useEvent';
|
||||||
|
|
||||||
|
export const AutoLoadingButton: React.FC<ButtonProps> = React.memo((props) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = useEvent(
|
||||||
|
async (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
|
setLoading(true);
|
||||||
|
await props.onClick?.(e);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return <Button loading={loading} {...props} onClick={handleClick} />;
|
||||||
|
});
|
||||||
|
AutoLoadingButton.displayName = 'AutoLoadingButton';
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NotificationSMTP } from './smtp';
|
import { NotificationSMTP } from './smtp';
|
||||||
|
import { NotificationTelegram } from './telegram';
|
||||||
|
|
||||||
interface NotificationStrategy {
|
interface NotificationStrategy {
|
||||||
label: string;
|
label: string;
|
||||||
@ -13,4 +14,9 @@ export const notificationStrategies: NotificationStrategy[] = [
|
|||||||
name: 'smtp',
|
name: 'smtp',
|
||||||
form: NotificationSMTP,
|
form: NotificationSMTP,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Telegram',
|
||||||
|
name: 'telegram',
|
||||||
|
form: NotificationTelegram,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import { Form, Input, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useEvent } from '../../../../hooks/useEvent';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { AutoLoadingButton } from '../../../AutoLoadingButton';
|
||||||
|
import { get, last } from 'lodash-es';
|
||||||
|
|
||||||
|
export const NotificationTelegram: React.FC = React.memo(() => {
|
||||||
|
const token = Form.useWatch(['payload', 'botToken']);
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
|
||||||
|
const getUpdatesUrl = (t: string = '<YOUR BOT TOKEN HERE>') =>
|
||||||
|
`https://api.telegram.org/bot${t}/getUpdates`;
|
||||||
|
|
||||||
|
const handleAutoGet = useEvent(async () => {
|
||||||
|
const res = await axios.get(getUpdatesUrl(token));
|
||||||
|
|
||||||
|
const update = last(get(res, ['data', 'result']));
|
||||||
|
|
||||||
|
let chatId;
|
||||||
|
if (get(update, 'channel_post')) {
|
||||||
|
chatId = get(update, 'channel_post.chat.id');
|
||||||
|
} else if (get(update, 'message')) {
|
||||||
|
chatId = get(update, 'message.chat.id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatId) {
|
||||||
|
form.setFieldValue(['payload', 'chatId'], String(chatId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label="Bot Token"
|
||||||
|
name={['payload', 'botToken']}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<Input.Password />
|
||||||
|
</Form.Item>
|
||||||
|
<Typography.Paragraph className="text-neutral-500">
|
||||||
|
You can get a token from https://t.me/BotFather.
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<Form.Item label="Chat ID" required={true}>
|
||||||
|
<div className="flex gap-2 overflow-hidden">
|
||||||
|
<Form.Item
|
||||||
|
className="flex-1"
|
||||||
|
name={['payload', 'chatId']}
|
||||||
|
noStyle={true}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{token && (
|
||||||
|
<AutoLoadingButton onClick={handleAutoGet}>
|
||||||
|
Auto Fetch
|
||||||
|
</AutoLoadingButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
<Typography.Paragraph className="text-neutral-500">
|
||||||
|
Support Direct Chat / Group / Channel's Chat ID
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<Typography.Paragraph className="text-neutral-500">
|
||||||
|
You can get your chat ID by sending a message to the bot and going to
|
||||||
|
this URL to view the chat_id:
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<Typography.Link href={getUpdatesUrl(token)} target="_blank">
|
||||||
|
{getUpdatesUrl('*'.repeat(token?.length ?? 0))}
|
||||||
|
</Typography.Link>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
NotificationTelegram.displayName = 'NotificationTelegram';
|
@ -1,6 +1,8 @@
|
|||||||
import { smtp } from './smtp';
|
import { smtp } from './smtp';
|
||||||
|
import { telegram } from './telegram';
|
||||||
import type { NotificationProvider } from './type';
|
import type { NotificationProvider } from './type';
|
||||||
|
|
||||||
export const notificationProviders: Record<string, NotificationProvider> = {
|
export const notificationProviders: Record<string, NotificationProvider> = {
|
||||||
smtp,
|
smtp,
|
||||||
|
telegram,
|
||||||
};
|
};
|
||||||
|
49
src/server/model/notification/provider/telegram.ts
Normal file
49
src/server/model/notification/provider/telegram.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { NotificationProvider } from './type';
|
||||||
|
import { telegramContentTokenizer, token } from '../token';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ImageContentToken } from '../token/type';
|
||||||
|
|
||||||
|
interface TelegramPayload {
|
||||||
|
botToken: string;
|
||||||
|
chatId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const telegram: NotificationProvider = {
|
||||||
|
send: async (notification, title, message) => {
|
||||||
|
const payload = notification.payload as unknown as TelegramPayload;
|
||||||
|
const { botToken, chatId } = payload;
|
||||||
|
|
||||||
|
const text = telegramContentTokenizer.parse([
|
||||||
|
token.title(title, 1),
|
||||||
|
...message,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// send text part
|
||||||
|
await axios.post(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text,
|
||||||
|
parse_mode: 'MarkdownV2',
|
||||||
|
});
|
||||||
|
|
||||||
|
// send image
|
||||||
|
const imageTokens = message.filter(
|
||||||
|
(m): m is ImageContentToken => m.type === 'image'
|
||||||
|
);
|
||||||
|
if (imageTokens.length > 0) {
|
||||||
|
if (imageTokens.length === 1) {
|
||||||
|
await axios.post(`https://api.telegram.org/bot${botToken}/sendPhoto`, {
|
||||||
|
chat_id: chatId,
|
||||||
|
photo: imageTokens[0].url,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await axios.post(`https://api.telegram.org/bot${botToken}/sendPhoto`, {
|
||||||
|
chat_id: chatId,
|
||||||
|
media: imageTokens.map((t) => ({
|
||||||
|
type: 'photo',
|
||||||
|
media: t.url,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
import { BaseContentTokenizer } from './tokenizer/base';
|
import { BaseContentTokenizer } from './tokenizer/base';
|
||||||
import { HTMLContentTokenizer } from './tokenizer/html';
|
import { HTMLContentTokenizer } from './tokenizer/html';
|
||||||
import { MarkdownContentTokenizer } from './tokenizer/markdown';
|
import { MarkdownContentTokenizer } from './tokenizer/markdown';
|
||||||
|
import { TelegramContentTokenizer } from './tokenizer/telegram';
|
||||||
import {
|
import {
|
||||||
ContentToken,
|
ContentToken,
|
||||||
ImageContentToken,
|
ImageContentToken,
|
||||||
@ -38,3 +39,4 @@ export const token = {
|
|||||||
export const baseContentTokenizer = new BaseContentTokenizer();
|
export const baseContentTokenizer = new BaseContentTokenizer();
|
||||||
export const htmlContentTokenizer = new HTMLContentTokenizer();
|
export const htmlContentTokenizer = new HTMLContentTokenizer();
|
||||||
export const markdownContentTokenizer = new MarkdownContentTokenizer();
|
export const markdownContentTokenizer = new MarkdownContentTokenizer();
|
||||||
|
export const telegramContentTokenizer = new TelegramContentTokenizer();
|
||||||
|
@ -53,6 +53,7 @@ export class BaseContentTokenizer {
|
|||||||
|
|
||||||
return '';
|
return '';
|
||||||
})
|
})
|
||||||
.join('');
|
.join('')
|
||||||
|
.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
src/server/model/notification/token/tokenizer/telegram.ts
Normal file
22
src/server/model/notification/token/tokenizer/telegram.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ImageContentToken, TitleContentToken } from '../type';
|
||||||
|
import { MarkdownContentTokenizer } from './markdown';
|
||||||
|
|
||||||
|
export class TelegramContentTokenizer extends MarkdownContentTokenizer {
|
||||||
|
parseImage(token: ImageContentToken) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTitle(token: TitleContentToken) {
|
||||||
|
if (token.level === 1) {
|
||||||
|
return `\n\\# ${token.content}\n`;
|
||||||
|
}
|
||||||
|
if (token.level === 2) {
|
||||||
|
return `\n\\#\\# ${token.content}\n`;
|
||||||
|
}
|
||||||
|
if (token.level === 3) {
|
||||||
|
return `\n\\#\\#\\# ${token.content}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `\n${token.content}\n`;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user