Skip to content
Snippets Groups Projects
telegram.service.ts 9.66 KiB
Newer Older
Pascal Kosak's avatar
Pascal Kosak committed
import { Injectable } from '@nestjs/common';
import { Message, Metadata } from 'node-telegram-bot-api';
import * as TelegramBot from 'node-telegram-bot-api';
import { PrismaService } from '../prisma/prisma.service';
import * as fs from 'fs';
import * as path from 'path';
import { v4 as uuid } from 'uuid';
import { Entry } from '@prisma/client';
import { pubSub } from '../pubSub.instance';
Pascal Kosak's avatar
Pascal Kosak committed

@Injectable()
export class TelegramService {
Pascal Kosak's avatar
Pascal Kosak committed
    private telegram: TelegramBot;

    private messageCache = new Map<number, Partial<Entry>>();

Pascal Kosak's avatar
Pascal Kosak committed
    constructor(private prismaService: PrismaService) {
        this.telegram = new TelegramBot(process.env.BOT_TOKEN, {
            polling: true,
        });

        this.telegram.onText(/^\/register ([\w|-]+) (.+)/, this.register.bind(this));
Pascal Kosak's avatar
Pascal Kosak committed
        this.telegram.onText(/^\/unregister/, this.unregister.bind(this));
Pascal Kosak's avatar
Pascal Kosak committed
        this.telegram.onText(/^\/profile/, this.profile.bind(this));
Pascal Kosak's avatar
Pascal Kosak committed
        this.telegram.onText(/^\/start/, this.start.bind(this));
Pascal Kosak's avatar
Pascal Kosak committed
        this.telegram.onText(/^\/entry (\w+)/, this.entry.bind(this));
Pascal Kosak's avatar
Pascal Kosak committed
        this.telegram.on('message', this.supplyValue.bind(this));
    }

Pascal Kosak's avatar
Pascal Kosak committed
    async start(msg: Message) {
        await this.telegram.sendMessage(
            msg.chat.id,
            '/register [Name]\n - Registers you as a new Agent\n' +
                '/unregister\n - Deletes all your Entries and Profile\n\n' +
                '/entry [public / private]\n - Create new Public/Private Entry from your previous message cache\n' +
                '/entry [clear]\n - Clears your current message cache\n' +
                '/profile\n - Shows your current profile',
        );
    }

Pascal Kosak's avatar
Pascal Kosak committed
    private lower = parseInt('aaaaaaa', 36);
    private upper = parseInt('zzzzzzzz', 36);

    private generateSlug() {
        return Math.floor(
            Math.random() * (this.upper - this.lower) + this.lower,
        ).toString(36);
    }

Pascal Kosak's avatar
Pascal Kosak committed
    async entry(msg: Message, match: RegExpMatchArray) {
        if (!match[1])
            return this.sendError(msg, '/entry [private/public/clear]');
Pascal Kosak's avatar
Pascal Kosak committed
        switch (match[1].toLowerCase()) {
            case 'private':
                return await this.commitMessage(msg.from.id, true, msg);
            case 'public':
                return await this.commitMessage(msg.from.id, false, msg);
            case 'clear':
                this.messageCache.delete(msg.from.id);
Pascal Kosak's avatar
Pascal Kosak committed
                await this.telegram.sendMessage(
                    msg.chat.id,
                    '♻ Message Queue cleared',
                );
        }
Pascal Kosak's avatar
Pascal Kosak committed
    }

    async commitMessage(id: number, isPrivate: boolean, msg: Message) {
        if (!(await this.isRegistered(msg.from.id)))
Pascal Kosak's avatar
Pascal Kosak committed
            return await this.sendError(msg, 'Not registered');
Pascal Kosak's avatar
Pascal Kosak committed

        if (!this.messageCache.has(id))
Pascal Kosak's avatar
Pascal Kosak committed
            return void (await this.telegram.sendMessage(
                msg.chat.id,
Pascal Kosak's avatar
Pascal Kosak committed
            ));

        let entry: Partial<Entry> = this.messageCache.get(msg.from.id);

        entry.private = isPrivate;

        this.messageCache.delete(msg.from.id);
Pascal Kosak's avatar
Pascal Kosak committed

        entry = await this.prismaService.entry.create({
Pascal Kosak's avatar
Pascal Kosak committed
        });

        await this.telegram.sendMessage(
            msg.chat.id,
Pascal Kosak's avatar
Pascal Kosak committed
            `✅ Created new ${isPrivate ? 'private' : 'public'} Entry!`,
        // Send a notification to subscriptions
Pascal Kosak's avatar
Pascal Kosak committed
        if (!entry.private) {
            await pubSub.publish('newEntry', {
                newEntry: {
                    ...entry,
                    locked: isPrivate,
                },
            });
Pascal Kosak's avatar
Pascal Kosak committed
        } else {
            await pubSub.publish('newEntry', {
                newEntry: {
                    id: entry.id,
                    agentId: entry.agentId,
                    private: true,
                    createdAt: entry.createdAt,
                    locked: true,
                },
            });
        }
Pascal Kosak's avatar
Pascal Kosak committed
    }

    async register(msg: Message, match: RegExpMatchArray) {
        if (!match[1]&&!match[2]) return;
Pascal Kosak's avatar
Pascal Kosak committed

        if (await this.isRegistered(msg.from.id))
            return void (await this.telegram.sendMessage(
                msg.chat.id,
                'Already registered',
            ));

        if((await this.prismaService.agent.count({
                where: { id: match[1] },
            })) == 0)
            return void (await this.telegram.sendMessage(
                msg.chat.id,
                'AgentId not found!',
            ));

        const agent = await this.prismaService.agent.update({
            where:{
                id: match[1]
            },
Pascal Kosak's avatar
Pascal Kosak committed
            data: {
                uid: String(msg.from.id),
                name: match[2],
Pascal Kosak's avatar
Pascal Kosak committed
                slug: this.generateSlug(),
                catchable: true,
                tokenCode: {
                    create: { value: 1 },
                },
Pascal Kosak's avatar
Pascal Kosak committed
            },
        });

        await this.telegram.sendMessage(
            msg.chat.id,
            `Id: ${agent.id}\nName: ${agent.name}\nCode: ${agent.slug}`,
        );
    }

Pascal Kosak's avatar
Pascal Kosak committed
    async unregister(msg: Message) {
Pascal Kosak's avatar
Pascal Kosak committed
        if (!(await this.isRegistered(msg.from.id)))
Pascal Kosak's avatar
Pascal Kosak committed
            return await this.sendError(msg, 'Not registered');
Pascal Kosak's avatar
Pascal Kosak committed

        const agent = await this.prismaService.agent.findFirst({
            where: { uid: String(msg.from.id) },
        });

        await this.prismaService.entry.deleteMany({
            where: { agentId: agent.id },
        });

        await this.prismaService.entry.deleteMany({
Pascal Kosak's avatar
Pascal Kosak committed
            where: { agentId: agent.id },
        });

        await this.prismaService.tokenCode.delete({
            where: { id: agent.tokenCodeId },
        });

Pascal Kosak's avatar
Pascal Kosak committed
        await this.prismaService.agent.delete({
            where: { id: agent.id },
        });

        await this.telegram.sendMessage(
            msg.chat.id,
            'Successfully deleted all Entries',
        );
    }

Pascal Kosak's avatar
Pascal Kosak committed
    async profile(msg: Message) {
Pascal Kosak's avatar
Pascal Kosak committed
        if (!(await this.isRegistered(msg.from.id)))
Pascal Kosak's avatar
Pascal Kosak committed
            return await this.sendError(msg, 'Not registered');
Pascal Kosak's avatar
Pascal Kosak committed

        const agent = await this.prismaService.agent.findFirst({
            where: { uid: String(msg.from.id) },
        });

        const publicEntries = await this.prismaService.entry.count({
            where: { agentId: agent.id, private: false },
Pascal Kosak's avatar
Pascal Kosak committed
        });

        const privateEntries = await this.prismaService.entry.count({
            where: { agentId: agent.id, private: true },
Pascal Kosak's avatar
Pascal Kosak committed
        });

        await this.telegram.sendMessage(
            msg.chat.id,
Pascal Kosak's avatar
Pascal Kosak committed
            `📂 Agent Information\n` +
                `Id: ${agent.id}\n` +
                `Name: ${agent.name}\n` +
                `Code: ${agent.slug}\n` +
                `Public Entries: ${publicEntries}\n` +
                `Private Entries: ${privateEntries}`,
    async receiveImage(msg: Message): Promise<Partial<Entry>> {
Pascal Kosak's avatar
Pascal Kosak committed
        let file: string;
        let size = -1;

        for (const p of msg.photo) {
            if (p.width > size) {
                size = p.width;
                file = p.file_id;
            }
        }

        const id = uuid();

        const dest = fs.createWriteStream(
            path.join(process.cwd(), 'photos', `${id}.jpg`),
        );
        const pipe = this.telegram.getFileStream(file).pipe(dest);

        await new Promise((resolve) => pipe.on('finish', resolve));

        return {
            image: `${id}.jpg`,
        };
    }

Pascal Kosak's avatar
Pascal Kosak committed
    async supplyValue(msg: Message, metadata: Metadata) {
        if (metadata.type === 'text' && msg.text.startsWith('/')) return;

        if (!(await this.isRegistered(msg.from.id))) return;

        const agent = await this.prismaService.agent.findFirst({
            where: { uid: String(msg.from.id) },
        });

        let entry: Partial<Entry> = {};
Pascal Kosak's avatar
Pascal Kosak committed
        if (this.messageCache.has(msg.from.id)) {
            entry = this.messageCache.get(msg.from.id);
Pascal Kosak's avatar
Pascal Kosak committed
        } else {
            await this.telegram.sendMessage(
                msg.chat.id,
                '📌 Message queue created\n' +
                    '<pre>/entry private</pre> to submit as private element\n' +
                    '<pre>/entry public</pre> to submit as public element\n' +
                    '<pre>/entry clear</pre> to reset the queue',
                { parse_mode: 'HTML' },
            );
        }
Pascal Kosak's avatar
Pascal Kosak committed

Pascal Kosak's avatar
Pascal Kosak committed

        if (metadata.type === 'photo') {
            const imageEntry = await this.receiveImage(msg);
            if (entry.image)
                await this.telegram.sendMessage(
                    msg.chat.id,
Pascal Kosak's avatar
Pascal Kosak committed
                    '✂ Overwriting previous image',
Pascal Kosak's avatar
Pascal Kosak committed

            entry.image = imageEntry.image;
Pascal Kosak's avatar
Pascal Kosak committed

Pascal Kosak's avatar
Pascal Kosak committed
            await this.telegram.sendMessage(msg.chat.id, '➡ Queued Image');
Pascal Kosak's avatar
Pascal Kosak committed
        } else if (metadata.type === 'text') {
Pascal Kosak's avatar
Pascal Kosak committed
            if (entry.content)
                await this.telegram.sendMessage(
                    msg.chat.id,
                    '✂ Overwriting previous text',
                );

Pascal Kosak's avatar
Pascal Kosak committed

Pascal Kosak's avatar
Pascal Kosak committed
            await this.telegram.sendMessage(msg.chat.id, '➡ Queued Text');
Pascal Kosak's avatar
Pascal Kosak committed
        } else if (metadata.type === 'location') {
            entry.lat = msg.location.latitude.toString();
            entry.lon = msg.location.longitude.toString();
Pascal Kosak's avatar
Pascal Kosak committed

Pascal Kosak's avatar
Pascal Kosak committed
            await this.telegram.sendMessage(msg.chat.id, '➡ Queued Location');
Pascal Kosak's avatar
Pascal Kosak committed
        } else {
Pascal Kosak's avatar
Pascal Kosak committed
            return await this.sendError(msg, 'Unsupported DataType');
Pascal Kosak's avatar
Pascal Kosak committed
        }

        this.messageCache.set(msg.from.id, entry);
Pascal Kosak's avatar
Pascal Kosak committed
    }
Pascal Kosak's avatar
Pascal Kosak committed

    private async isRegistered(uid: string | number): Promise<boolean> {
        return (
            (await this.prismaService.agent.count({
                where: { uid: String(uid) },
            })) > 0
        );
    }
Pascal Kosak's avatar
Pascal Kosak committed

    private async sendError(msg: Message, message: string) {
        await this.telegram.sendMessage(msg.chat.id, `$❌ ${message}`);
    }
Pascal Kosak's avatar
Pascal Kosak committed
}