-
Adrian Paschkowski authoredAdrian Paschkowski authored
telegram.service.ts 6.69 KiB
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Markup, Telegraf } from 'telegraf';
import { TelegrafContext } from 'telegraf/typings/context';
import { TelegramEntry } from './telegram.entry';
import { PrismaService } from '../prisma/prisma.service';
import { MinioService } from '../minio/minio.service';
import { v4 as uuid } from 'uuid';
@Injectable()
export class TelegramService implements OnModuleInit {
private readonly bot: Telegraf<TelegrafContext>;
private readonly logger = new Logger('TelegramService');
private readonly entries = new Map<number, TelegramEntry>();
constructor(
private readonly prisma: PrismaService,
private readonly minio: MinioService,
) {
this.bot = new Telegraf(process.env.TELEGRAM_TOKEN);
this.bot.start(this.start);
this.bot.command('unregister', (ctx) =>
this.unregister.call(this, ctx).catch(console.error),
);
this.bot.command('register', (ctx) =>
this.register.call(this, ctx).catch(console.error),
);
this.bot.command('profile', (ctx) =>
this.profile.call(this, ctx).catch(console.error),
);
this.bot.on('message', (ctx) =>
this.onMessage.call(this, ctx).catch(console.error),
);
this.bot.on('edited_message', (ctx) =>
this.onMessage.call(this, ctx).catch(console.error),
);
this.bot.action('delete', (ctx) =>
this.deleteButton.call(this, ctx).catch(console.error),
);
this.bot.action('public', (ctx) =>
this.publicButton.call(this, ctx).catch(console.error),
);
this.bot.action('private', (ctx) =>
this.privateButton.call(this, ctx).catch(console.error),
);
}
async onModuleInit() {
await this.bot
.launch()
.then(() => this.bot.telegram.getMe())
.then((user) => this.logger.log(`Logged in as ${user.username}`));
}
async onMessage(ctx: TelegrafContext) {
if (!(await this.isRegistered(ctx.from.id))) {
await ctx.reply('User not registered');
return;
}
const message = ctx.message ?? ctx.editedMessage;
const content = message.text;
const location = message.location;
const image = message?.photo?.reduce((prev, curr) => {
if (prev.width > curr.width) return prev;
else return curr;
});
const entry: TelegramEntry = { content };
if (image)
entry.photo = {
id: image.file_id,
width: image.width,
height: image.height,
};
if (location)
entry.location = {
lat: location.latitude,
lon: location.longitude,
};
if (!entry.content && !entry.photo && !entry.location) return;
this.entries.set(ctx.from.id, entry);
const msg = await ctx.reply('📌 Message queued', {
reply_markup: {
inline_keyboard: [
[
Markup.callbackButton('Save as public', 'public'),
Markup.callbackButton('Save as private', 'private'),
Markup.callbackButton('Delete', 'delete'),
],
],
one_time_keyboard: true,
resize_keyboard: true,
},
});
try {
for (let i = 1; i <= 2; i++)
await ctx.telegram.editMessageReplyMarkup(
ctx.from.id,
msg.message_id - i,
undefined,
'',
);
} catch (e) {}
}
async start(ctx: TelegrafContext) {
await ctx.reply(`
/register <password> <name>\n - Registers you as a new Agent. The password will be assigned to you beforehand, while the name is what will be displayed to other users.\n
/unregister\n - Deletes all your Entries and Profile.\n\n
/profile\n - Shows your current profile.
`);
}
async unregister(ctx: TelegrafContext) {
const agent = await this.prisma.agent.update({
where: { uid: String(ctx.from.id) },
data: { uid: null },
});
if (agent) {
await ctx.reply('Unregistered');
} else {
await ctx.reply('User not registered');
}
}
async register(ctx: TelegrafContext) {
if (!ctx.message.text) return;
if (await this.isRegistered(ctx.from.id)) {
await ctx.reply('User already registered');
return;
}
const args = ctx.message.text.split(' ');
if (args.length !== 3) return await this.start(ctx);
const exists = await this.prisma.agent.count({
where: { slug: args[1] },
});
if (exists === 0) return await ctx.reply('Slug not found');
const agent = await this.prisma.agent.update({
where: { slug: args[1] },
data: {
name: args[2],
uid: String(ctx.from.id),
},
});
if (agent) {
await ctx.reply('Registered');
} else {
await ctx.reply('Password not found');
}
}
async profile(ctx: TelegrafContext) {
const agent = await this.prisma.agent.findFirst({
where: { uid: String(ctx.from.id) },
});
if (!agent) {
await ctx.reply('User not registered');
return;
}
const [publicEntries, privateEntries] = await this.prisma.$transaction([
this.prisma.entry.count({
where: { agentId: agent.id, private: false },
}),
this.prisma.entry.count({
where: { agentId: agent.id, private: true },
}),
]);
await ctx.reply(
`📂 Agent Information\n` +
`- Id: ${agent.id}\n` +
`- Name: ${agent.name}\n` +
`- Code: ${agent.slug}\n` +
`- Public Entries: ${publicEntries}\n` +
`- Private Entries: ${privateEntries}`,
);
}
async deleteButton(ctx: TelegrafContext) {
await ctx.deleteMessage();
}
async privateButton(ctx: TelegrafContext) {
if (!(await this.isRegistered(ctx.from.id))) {
await ctx.reply('User not registered');
return;
}
await this.createEntry(ctx, true);
}
async publicButton(ctx: TelegrafContext) {
if (!(await this.isRegistered(ctx.from.id))) {
await ctx.reply('User not registered');
return;
}
await this.createEntry(ctx, false);
}
async createEntry(ctx: TelegrafContext, isPrivate: boolean) {
const data = this.entries.get(ctx.from.id);
if (!data) return;
if (ctx.update?.callback_query?.message?.message_id)
await ctx.telegram.editMessageReplyMarkup(
ctx.from.id,
ctx.update.callback_query.message.message_id,
undefined,
'',
);
this.entries.delete(ctx.from.id);
let image_id;
if (data.photo) {
image_id = uuid();
const link = await ctx.telegram.getFileLink(data.photo.id);
const res = await fetch(link)
.then((res) => res.arrayBuffer())
.then((buf) => Buffer.from(buf));
await this.minio.saveFile(
`${process.env.MINIO_ENTRY_IMAGE_PATH}/${image_id}.jpg`,
res,
);
}
await this.prisma.entry.create({
data: {
image_id,
image_width: data.photo?.width,
image_height: data.photo?.height,
content: data.content,
lat: data.location?.lat,
lon: data.location?.lon,
private: isPrivate,
agent: {
connect: { uid: String(ctx.from.id) },
},
},
});
await ctx.reply(`✅ ${isPrivate ? 'Private' : 'Public'} Entry created`);
}
isRegistered(uid: string | number): Promise<boolean> {
return this.prisma.agent
.count({
where: { uid: String(uid) },
})
.then((count) => count > 0);
}
}