feat: add telegram notification support

This commit is contained in:
moonrailgun 2024-01-07 01:36:51 +08:00
parent ce6fd56d51
commit e5a52257ea
8 changed files with 175 additions and 1 deletions

View 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';

View File

@ -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,
},
]; ];

View File

@ -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';

View File

@ -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,
}; };

View 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,
})),
});
}
}
},
};

View File

@ -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();

View File

@ -53,6 +53,7 @@ export class BaseContentTokenizer {
return ''; return '';
}) })
.join(''); .join('')
.trim();
} }
} }

View 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`;
}
}