Переделан сервис интеграции с telegram через простую либу с командами

This commit is contained in:
Pavel Gnedov 2022-09-14 19:44:07 +07:00
parent 908b927bca
commit 3a869023e5
5 changed files with 1798 additions and 250 deletions

1851
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -33,13 +33,12 @@
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"imap-simple": "^5.1.0", "imap-simple": "^5.1.0",
"nano": "^10.0.0", "nano": "^10.0.0",
"nestjs-telegraf": "^2.6.0", "node-telegram-bot-api": "^0.59.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rss-parser": "^3.12.0", "rss-parser": "^3.12.0",
"rxjs": "^7.2.0", "rxjs": "^7.2.0",
"socket.io": "^4.4.1", "socket.io": "^4.4.1"
"telegraf": "^4.8.6"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^8.0.0", "@nestjs/cli": "^8.0.0",
@ -49,6 +48,7 @@
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jest": "27.4.0", "@types/jest": "27.4.0",
"@types/node": "^16.0.0", "@types/node": "^16.0.0",
"@types/node-telegram-bot-api": "^0.57.1",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",

View file

@ -19,7 +19,6 @@ import { ChangesCacheWriterService } from './changes-cache-writer/changes-cache-
import { Issues } from '@app/event-emitter/couchdb-datasources/issues'; import { Issues } from '@app/event-emitter/couchdb-datasources/issues';
import { Users } from '@app/event-emitter/couchdb-datasources/users'; import { Users } from '@app/event-emitter/couchdb-datasources/users';
import { TelegramBotService } from './telegram-bot/telegram-bot.service'; import { TelegramBotService } from './telegram-bot/telegram-bot.service';
import { TelegrafModule } from 'nestjs-telegraf';
import { UserMetaInfoService } from './user-meta-info/user-meta-info.service'; import { UserMetaInfoService } from './user-meta-info/user-meta-info.service';
import { UserMetaInfo } from './couchdb-datasources/user-meta-info'; import { UserMetaInfo } from './couchdb-datasources/user-meta-info';
@ -29,9 +28,6 @@ import { UserMetaInfo } from './couchdb-datasources/user-meta-info';
config: configuration().redmineIssueEventEmitterConfig, config: configuration().redmineIssueEventEmitterConfig,
}), }),
ConfigModule.forRoot({ load: [configuration] }), ConfigModule.forRoot({ load: [configuration] }),
TelegrafModule.forRoot({
token: configuration().telegramBotToken,
}),
CacheModule.register({ CacheModule.register({
isGlobal: true, isGlobal: true,
}), }),
@ -63,6 +59,7 @@ export class AppModule implements OnModuleInit {
private currentUserEnhancer: CurrentUserEnhancer, private currentUserEnhancer: CurrentUserEnhancer,
private statusChangeNotificationsService: StatusChangeNotificationsService, private statusChangeNotificationsService: StatusChangeNotificationsService,
private changesCacheWriterService: ChangesCacheWriterService, private changesCacheWriterService: ChangesCacheWriterService,
private telegramBotService: TelegramBotService,
) {} ) {}
onModuleInit() { onModuleInit() {

View file

@ -1,44 +1,153 @@
import { UsersService } from '@app/event-emitter/users/users.service'; import { UsersService } from '@app/event-emitter/users/users.service';
import { Injectable } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { Update, Ctx, Start, Help, On, Hears } from 'nestjs-telegraf'; import { ConfigService } from '@nestjs/config';
import TelegramBot from 'node-telegram-bot-api';
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service'; import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
import { Context } from 'telegraf'; import axios from 'axios';
import { UserMetaInfoModel } from 'src/models/user-meta-info.model';
@Update()
@Injectable() @Injectable()
export class TelegramBotService { export class TelegramBotService {
private logger = new Logger(TelegramBotService.name);
private bot: TelegramBot;
private telegramBotToken: string;
private redmineApiUrlPrefix: string;
private redminePublicUrlPrefix: string;
private registerRe = /\/register (\d+) (.+)/;
constructor( constructor(
private userMetaInfoService: UserMetaInfoService, private userMetaInfoService: UserMetaInfoService,
private usersService: UsersService, private usersService: UsersService,
) {} private configService: ConfigService,
) {
this.telegramBotToken = this.configService.get<string>('telegramBotToken');
this.redmineApiUrlPrefix =
this.configService.get<string>('redmineUrlPrefix');
this.redminePublicUrlPrefix =
this.configService.get<string>('redmineUrlPublic');
this.initTelegramBot();
}
@Start() private async initTelegramBot(): Promise<void> {
async start(@Ctx() ctx: Context): Promise<void> { const Telegram = await require('node-telegram-bot-api');
const chat = await ctx.getChat(); this.bot = new Telegram(this.telegramBotToken, { polling: true });
const chatId = chat.id; this.bot.onText(/\/start/, async (msg) => {
await this.showHelpMessage(msg);
});
this.bot.onText(/\/help/, async (msg) => {
await this.showHelpMessage(msg);
});
this.bot.onText(this.registerRe, async (msg) => {
await this.register(msg);
});
this.bot.onText(/\/leave/, async (msg) => {
await this.leave(msg);
});
}
private async showHelpMessage(msg: TelegramBot.Message): Promise<void> {
const userMetaInfo = await this.userMetaInfoService.findByTelegramId( const userMetaInfo = await this.userMetaInfoService.findByTelegramId(
chatId, msg.chat.id,
); );
if (!userMetaInfo) { let helpMessage: string;
await ctx.reply('Привет, незнакомец!'); if (userMetaInfo) {
// eslint-disable-next-line prettier/prettier
helpMessage = [
`/current_issues - мои текущие задачи`,
`/help`
].join('\n');
} else { } else {
const user = await this.usersService.getUser(userMetaInfo.user_id); // eslint-disable-next-line prettier/prettier
await ctx.reply(`Привет, ${user.name}`); helpMessage = [
`/register <redmine_id> <redmine_token>`,
`/help`
].join('\n');
}
this.bot.sendMessage(msg.chat.id, helpMessage);
}
async sendMessageByRedmineId(
redmineId: number,
msg: string,
): Promise<boolean> {
const userMetaInfo = await this.userMetaInfoService.findByRedmineId(
redmineId,
);
if (!userMetaInfo) return false;
const chatId = userMetaInfo.telegram_chat_id;
await this.bot.sendMessage(chatId, msg);
return true;
}
async sendMessageByName(
firstname: string,
lastname: string,
msg: string,
): Promise<boolean> {
const user = await this.usersService.findUserByName(firstname, lastname);
if (!user) return false;
return await this.sendMessageByRedmineId(user.id, msg);
}
private async register(
msg: TelegramBot.Message,
): Promise<{ result: boolean; message: string }> {
const items = (msg.text || '').match(this.registerRe);
const result = (result: boolean, message: string, logData?: any) => {
const logMsg =
`Telegram registration ${result ? 'successed' : 'failed'} ` +
`with message ${message}, ` +
`log data = ${JSON.stringify(logData || null)}`;
this.logger.log(logMsg);
this.bot.sendMessage(msg.chat.id, message);
return { result: result, message: message };
};
if (!items || !items[1] || !items[2]) {
return result(false, 'Не указаны необходимые данные');
}
let redmineId: number, redmineToken: string;
try {
redmineId = Number(items[1]);
redmineToken = items[2];
} catch (ex) {
return result(false, 'Введены неверные значения', {});
}
const url = `${this.redminePublicUrlPrefix}/users/current.json`;
const resp = await axios.get(url, {
headers: { 'X-Redmine-API-Key': redmineToken },
});
if (resp && resp.data && resp.statusText == 'OK') {
const data = resp.data;
if (data.user.id == redmineId) {
const userData = await this.usersService.getUser(redmineId);
const userMetaInfo: UserMetaInfoModel = {
telegram_chat_id: msg.chat.id,
user_id: userData.id,
};
await this.userMetaInfoService.save(userMetaInfo);
return result(true, `Данные для ${userData.name} подтверждены`, {
redmineId: redmineId,
});
}
}
return result(false, 'Не удалось проверить подлинность указанных данных');
}
private async leave(msg: TelegramBot.Message): Promise<void> {
const telegramChatId = msg.chat.id;
const userMetaInfo = await this.userMetaInfoService.findByTelegramId(
telegramChatId,
);
if (userMetaInfo) {
await this.userMetaInfoService.delete(userMetaInfo);
} }
} }
@Help()
async help(@Ctx() ctx: Context): Promise<void> {
await ctx.reply('Send me a sticker');
}
@On('sticker')
async on(@Ctx() ctx: Context): Promise<void> {
await ctx.reply('👍');
}
@Hears('мои задачи')
async hears(@Ctx() ctx: Context): Promise<void> {
await ctx.reply('В разработке');
}
} }

View file

@ -8,7 +8,7 @@ export class UserMetaInfoService {
constructor(private userMetaInfo: UserMetaInfo) {} constructor(private userMetaInfo: UserMetaInfo) {}
@CacheTTL(60) @CacheTTL(60)
async findById(id: number): Promise<UserMetaInfoModel | null> { async findByRedmineId(id: number): Promise<UserMetaInfoModel | null> {
const db = await this.userMetaInfo.getDatasource(); const db = await this.userMetaInfo.getDatasource();
try { try {
return await db.get(String(id)); return await db.get(String(id));
@ -35,7 +35,10 @@ export class UserMetaInfoService {
const id = String(data.user_id); const id = String(data.user_id);
const db = await this.userMetaInfo.getDatasource(); const db = await this.userMetaInfo.getDatasource();
let item: (nano.MaybeDocument & UserMetaInfoModel) | null = null; let item: (nano.MaybeDocument & UserMetaInfoModel) | null = null;
const newItem: nano.MaybeDocument & UserMetaInfoModel = { ...data }; const newItem: nano.MaybeDocument & UserMetaInfoModel = {
_id: id,
...data,
};
try { try {
item = await db.get(id); item = await db.get(id);
} catch (ex) {} } catch (ex) {}
@ -45,4 +48,16 @@ export class UserMetaInfoService {
} }
await db.insert(newItem); await db.insert(newItem);
} }
async delete(data: UserMetaInfoModel): Promise<void> {
const id = String(data.user_id);
const db = await this.userMetaInfo.getDatasource();
let item: (nano.MaybeDocument & UserMetaInfoModel) | null = null;
try {
item = await db.get(id);
} catch (ex) {}
if (item) {
await db.destroy(item._id, item._rev);
}
}
} }