Добавлена api функция для отправки сообщений в telegram bot

/api/telegram-bot/send-message (POST)
This commit is contained in:
Pavel Gnedov 2024-05-02 09:22:11 +07:00
parent 5fb996a12e
commit b2ba939323
5 changed files with 1431 additions and 580 deletions

1854
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,7 @@
"hbs": "^4.2.0",
"imap-simple": "^5.1.0",
"jsonc-parser": "^3.2.0",
"jsonpath-plus": "^8.1.0",
"luxon": "^3.1.0",
"moo": "^0.5.2",
"nano": "^10.0.0",
@ -54,6 +55,7 @@
"@types/cron": "^2.0.0",
"@types/express": "^4.17.13",
"@types/jest": "27.4.0",
"@types/jsonpath": "^0.2.4",
"@types/luxon": "^3.1.0",
"@types/moo": "^0.5.6",
"@types/node": "^16.0.0",

View file

@ -53,6 +53,7 @@ import { CreateTagManagerServiceProvider } from './tags-manager/tags-manager.ser
import { CalendarEnhancer } from '@app/event-emitter/issue-enhancers/calendar-enhancer';
import { Dashboards as DashboardsDs } from '@app/event-emitter/couchdb-datasources/dashboards';
import { DashboardInitService } from './dashboards/dashboard-init.service';
import { TelegramBotController } from './telegram-bot/telegram-bot.controller';
@Module({
imports: [
@ -76,6 +77,7 @@ import { DashboardInitService } from './dashboards/dashboard-init.service';
SimpleKanbanBoardController,
SimpleIssuesListController,
TagsManagerController,
TelegramBotController,
],
providers: [
AppService,

View file

@ -0,0 +1,18 @@
import { Body, Controller, Post } from '@nestjs/common';
import { SendMessageParams, TelegramBotService } from './telegram-bot.service';
import {
BadRequestErrorHandler,
getOrAppErrorOrThrow,
} from '@app/event-emitter/utils/result';
@Controller('/api/telegram-bot')
export class TelegramBotController {
constructor(private telegramBotService: TelegramBotService) {}
@Post('send-message')
async sendMessage(@Body() params: SendMessageParams): Promise<void> {
await getOrAppErrorOrThrow(async () => {
await this.telegramBotService.sendMessageByParams(params);
}, BadRequestErrorHandler);
}
}

View file

@ -8,6 +8,10 @@ import { UserMetaInfoModel } from 'src/models/user-meta-info.model';
import { CurrentIssuesEccmBotHandlerService } from './handlers/current-issues-eccm.bot-handler.service';
import { TelegramBotHandlerInterface } from './telegram.bot-handler.interface';
import { SetDailyEccmUserCommentBotHandlerService } from './handlers/set-daily-eccm-user-comment.bot-handler.service';
import { IssuesService } from '@app/event-emitter/issues/issues.service';
import { JSONPath } from 'jsonpath-plus';
import { RedmineTypes } from '@app/event-emitter/models/redmine-types';
import { createAppError } from '@app/event-emitter/utils/result';
const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
@ -18,6 +22,60 @@ export function cutMessage(msg: string): string {
return msg;
}
export type OptionsParams = {
parse_mode?: TelegramBot.ParseMode | undefined;
};
export type RecipientByName = {
firstname: string;
lastname: string;
};
export type RecipientByIssueField = {
issueId: number;
field: string;
};
export type SendMessageParams = {
recipients: (RecipientByName | RecipientByIssueField)[];
msg: string;
options?: OptionsParams;
};
export function validateSendMessageParams(params: SendMessageParams): string[] {
const res: string[] = [];
if (typeof params.msg !== 'string') {
res.push('Wrong msg field value, must be string');
}
if (
typeof params.recipients !== 'object' ||
typeof params.recipients.length !== 'number'
) {
res.push('Wrong recipients field value, must be array');
}
for (let i = 0; i < params.recipients?.length; i++) {
const r: any = params.recipients[i];
const checkRecipient = Boolean(
(typeof r.firstname === 'string' && typeof r.lastname === 'string') ||
(typeof r.issueId === 'number' && typeof r.field === 'string'),
);
if (!checkRecipient) {
res.push(
'Wrong recipient item value, must be object with firstname+lastname or issueId+field',
);
}
}
if (
params?.options?.parse_mode &&
['Markdown', 'MarkdownV2', 'HTML'].indexOf(params.options.parse_mode) < 0
) {
res.push(
`Wrong options.parse_mode value, must be one of 'Markdown', 'MarkdownV2', 'HTML'`,
);
}
return res;
}
@Injectable()
export class TelegramBotService {
private logger = new Logger(TelegramBotService.name);
@ -35,6 +93,7 @@ export class TelegramBotService {
private configService: ConfigService,
private currentIssuesBotHandlerService: CurrentIssuesEccmBotHandlerService,
private setDailyEccmUserCommentBotHandlerService: SetDailyEccmUserCommentBotHandlerService,
private issuesService: IssuesService,
) {
this.telegramBotToken = this.configService.get<string>('telegramBotToken');
this.redminePublicUrlPrefix =
@ -142,6 +201,62 @@ export class TelegramBotService {
return await this.sendMessageByRedmineId(user.id, msg, options);
}
async sendMessageByParams(params: SendMessageParams): Promise<void> {
const paramsErrors = validateSendMessageParams(params);
if (paramsErrors && paramsErrors.length > 0) {
throw createAppError(`Params errors - ${JSON.stringify(paramsErrors)}`);
}
const issuesStore = await this.getIssuesStore(params);
for (let i = 0; i < params.recipients.length; i++) {
const recipient: any = params.recipients[i];
if (recipient.firstname && recipient.lastname) {
await this.sendMessageByName(
recipient.firstname,
recipient.lastname,
params.msg,
params.options,
);
} else if (recipient.issueId && recipient.field) {
const issue = issuesStore[recipient.issueId];
if (!issue) continue;
let fieldValue: any;
try {
fieldValue = JSONPath({ json: issue, path: recipient.field });
} catch (ex) {
const warnMsg =
`Error at get value from issueId = ${recipient.issueId}; ` +
`field = ${JSON.stringify(recipient.field)} ` +
`- ${ex.message}`;
this.logger.warn(warnMsg);
continue;
}
if (fieldValue && fieldValue.length > 0) {
fieldValue = fieldValue[0];
} else {
continue;
}
if (
typeof fieldValue === 'object' &&
fieldValue.firstname &&
fieldValue.lastname
) {
await this.sendMessageByName(
fieldValue.firstname,
fieldValue.lastname,
params.msg,
params.options,
);
} else if (typeof fieldValue === 'number') {
await this.sendMessageByRedmineId(
fieldValue,
params.msg,
params.options,
);
}
}
}
}
private async register(
msg: TelegramBot.Message,
): Promise<{ result: boolean; message: string }> {
@ -202,4 +317,24 @@ export class TelegramBotService {
await this.userMetaInfoService.delete(userMetaInfo);
}
}
private async getIssuesStore(
params: SendMessageParams,
): Promise<Record<number, RedmineTypes.Issue>> {
const issueIds: number[] = [];
for (let i = 0; i < params.recipients.length; i++) {
const recipient: any = params.recipients[i];
if (typeof recipient.issueId === 'number' && recipient.issueId > 0) {
issueIds.push(recipient.issueId);
}
}
if (issueIds.length <= 0) {
return [];
}
const issues = await this.issuesService.getIssues(issueIds);
return issues.reduce((acc, issue) => {
acc[issue.id] = issue;
return acc;
}, {});
}
}