Переделан сервис интеграции с telegram через простую либу с командами
This commit is contained in:
parent
908b927bca
commit
3a869023e5
5 changed files with 1798 additions and 250 deletions
1851
package-lock.json
generated
1851
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -33,13 +33,12 @@
|
|||
"handlebars": "^4.7.7",
|
||||
"imap-simple": "^5.1.0",
|
||||
"nano": "^10.0.0",
|
||||
"nestjs-telegraf": "^2.6.0",
|
||||
"node-telegram-bot-api": "^0.59.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rss-parser": "^3.12.0",
|
||||
"rxjs": "^7.2.0",
|
||||
"socket.io": "^4.4.1",
|
||||
"telegraf": "^4.8.6"
|
||||
"socket.io": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^8.0.0",
|
||||
|
|
@ -49,6 +48,7 @@
|
|||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/node-telegram-bot-api": "^0.57.1",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import { ChangesCacheWriterService } from './changes-cache-writer/changes-cache-
|
|||
import { Issues } from '@app/event-emitter/couchdb-datasources/issues';
|
||||
import { Users } from '@app/event-emitter/couchdb-datasources/users';
|
||||
import { TelegramBotService } from './telegram-bot/telegram-bot.service';
|
||||
import { TelegrafModule } from 'nestjs-telegraf';
|
||||
import { UserMetaInfoService } from './user-meta-info/user-meta-info.service';
|
||||
import { UserMetaInfo } from './couchdb-datasources/user-meta-info';
|
||||
|
||||
|
|
@ -29,9 +28,6 @@ import { UserMetaInfo } from './couchdb-datasources/user-meta-info';
|
|||
config: configuration().redmineIssueEventEmitterConfig,
|
||||
}),
|
||||
ConfigModule.forRoot({ load: [configuration] }),
|
||||
TelegrafModule.forRoot({
|
||||
token: configuration().telegramBotToken,
|
||||
}),
|
||||
CacheModule.register({
|
||||
isGlobal: true,
|
||||
}),
|
||||
|
|
@ -63,6 +59,7 @@ export class AppModule implements OnModuleInit {
|
|||
private currentUserEnhancer: CurrentUserEnhancer,
|
||||
private statusChangeNotificationsService: StatusChangeNotificationsService,
|
||||
private changesCacheWriterService: ChangesCacheWriterService,
|
||||
private telegramBotService: TelegramBotService,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
|
|
|
|||
|
|
@ -1,44 +1,153 @@
|
|||
import { UsersService } from '@app/event-emitter/users/users.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Update, Ctx, Start, Help, On, Hears } from 'nestjs-telegraf';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import TelegramBot from 'node-telegram-bot-api';
|
||||
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()
|
||||
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(
|
||||
private userMetaInfoService: UserMetaInfoService,
|
||||
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()
|
||||
async start(@Ctx() ctx: Context): Promise<void> {
|
||||
const chat = await ctx.getChat();
|
||||
const chatId = chat.id;
|
||||
private async initTelegramBot(): Promise<void> {
|
||||
const Telegram = await require('node-telegram-bot-api');
|
||||
this.bot = new Telegram(this.telegramBotToken, { polling: true });
|
||||
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(
|
||||
chatId,
|
||||
msg.chat.id,
|
||||
);
|
||||
if (!userMetaInfo) {
|
||||
await ctx.reply('Привет, незнакомец!');
|
||||
let helpMessage: string;
|
||||
if (userMetaInfo) {
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
helpMessage = [
|
||||
`/current_issues - мои текущие задачи`,
|
||||
`/help`
|
||||
].join('\n');
|
||||
} else {
|
||||
const user = await this.usersService.getUser(userMetaInfo.user_id);
|
||||
await ctx.reply(`Привет, ${user.name}`);
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
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('В разработке');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export class UserMetaInfoService {
|
|||
constructor(private userMetaInfo: UserMetaInfo) {}
|
||||
|
||||
@CacheTTL(60)
|
||||
async findById(id: number): Promise<UserMetaInfoModel | null> {
|
||||
async findByRedmineId(id: number): Promise<UserMetaInfoModel | null> {
|
||||
const db = await this.userMetaInfo.getDatasource();
|
||||
try {
|
||||
return await db.get(String(id));
|
||||
|
|
@ -35,7 +35,10 @@ export class UserMetaInfoService {
|
|||
const id = String(data.user_id);
|
||||
const db = await this.userMetaInfo.getDatasource();
|
||||
let item: (nano.MaybeDocument & UserMetaInfoModel) | null = null;
|
||||
const newItem: nano.MaybeDocument & UserMetaInfoModel = { ...data };
|
||||
const newItem: nano.MaybeDocument & UserMetaInfoModel = {
|
||||
_id: id,
|
||||
...data,
|
||||
};
|
||||
try {
|
||||
item = await db.get(id);
|
||||
} catch (ex) {}
|
||||
|
|
@ -45,4 +48,16 @@ export class UserMetaInfoService {
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue