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 { NotificationSMTP } from './smtp';
|
||||
import { NotificationTelegram } from './telegram';
|
||||
|
||||
interface NotificationStrategy {
|
||||
label: string;
|
||||
@ -13,4 +14,9 @@ export const notificationStrategies: NotificationStrategy[] = [
|
||||
name: 'smtp',
|
||||
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 { telegram } from './telegram';
|
||||
import type { NotificationProvider } from './type';
|
||||
|
||||
export const notificationProviders: Record<string, NotificationProvider> = {
|
||||
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 { HTMLContentTokenizer } from './tokenizer/html';
|
||||
import { MarkdownContentTokenizer } from './tokenizer/markdown';
|
||||
import { TelegramContentTokenizer } from './tokenizer/telegram';
|
||||
import {
|
||||
ContentToken,
|
||||
ImageContentToken,
|
||||
@ -38,3 +39,4 @@ export const token = {
|
||||
export const baseContentTokenizer = new BaseContentTokenizer();
|
||||
export const htmlContentTokenizer = new HTMLContentTokenizer();
|
||||
export const markdownContentTokenizer = new MarkdownContentTokenizer();
|
||||
export const telegramContentTokenizer = new TelegramContentTokenizer();
|
||||
|
@ -53,6 +53,7 @@ export class BaseContentTokenizer {
|
||||
|
||||
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