mirror of
https://github.com/flibusta-apps/book_bot.git
synced 2025-12-06 15:35:35 +01:00
Init
This commit is contained in:
8
src/bots/factory/bots/approved/callback_data.ts
Normal file
8
src/bots/factory/bots/approved/callback_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const SETTINGS_LANGUAGES = 'settings.languages';
|
||||
|
||||
export const SEARCH_BOOK_PREFIX = 'sb_';
|
||||
export const SEARCH_AUTHORS_PREFIX = 'sa_';
|
||||
export const SEARCH_SERIES_PREFIX = 'ss_';
|
||||
|
||||
export const AUTHOR_BOOKS_PREFIX = 'ba_';
|
||||
export const SEQUENCE_BOOKS_PREFIX = 'bs_';
|
||||
55
src/bots/factory/bots/approved/format.ts
Normal file
55
src/bots/factory/bots/approved/format.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { AuthorBook, Book, Author, Sequence } from './services/book_library';
|
||||
|
||||
|
||||
function isBook(item: AuthorBook | Book): item is Book {
|
||||
return 'authors' in item;
|
||||
}
|
||||
|
||||
|
||||
export function formatBook(book: AuthorBook | Book): string {
|
||||
let response: string[] = [];
|
||||
|
||||
response.push(`📖 ${book.title} | ${book.lang}`);
|
||||
|
||||
if (book.annotation_exists) {
|
||||
response.push(`📝 Аннотация: /b_info_${book.id}`)
|
||||
}
|
||||
|
||||
if (isBook(book) && book.authors.length > 0) {
|
||||
response.push('Авторы:')
|
||||
book.authors.forEach(author => response.push(`͏👤 ${author.last_name} ${author.first_name} ${author.middle_name}`));
|
||||
}
|
||||
|
||||
if (book.translators.length > 0) {
|
||||
response.push('Переводчики:');
|
||||
book.translators.forEach(author => response.push(`͏👤 ${author.last_name} ${author.first_name} ${author.middle_name}`));
|
||||
}
|
||||
|
||||
book.available_types.forEach(a_type => response.push(`📥 ${a_type}: /d_${a_type}_${book.id}`));
|
||||
|
||||
return response.join('\n');
|
||||
}
|
||||
|
||||
|
||||
export function formatAuthor(author: Author): string {
|
||||
let response = [];
|
||||
|
||||
response.push(`👤 ${author.last_name} ${author.first_name} ${author.middle_name}`);
|
||||
response.push(`/a_${author.id}`);
|
||||
|
||||
if (author.annotation_exists) {
|
||||
response.push(`📝 Аннотация: /a_info_${author.id}`);
|
||||
}
|
||||
|
||||
return response.join('\n');
|
||||
}
|
||||
|
||||
|
||||
export function formatSequence(sequence: Sequence): string {
|
||||
let response = [];
|
||||
|
||||
response.push(`📚 ${sequence.name}`);
|
||||
response.push(`/s_${sequence.id}`);
|
||||
|
||||
return response.join('\n');
|
||||
}
|
||||
142
src/bots/factory/bots/approved/index.ts
Normal file
142
src/bots/factory/bots/approved/index.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Context, Telegraf, Markup } from 'telegraf';
|
||||
|
||||
import { BotState } from '@/bots/manager';
|
||||
|
||||
import env from '@/config';
|
||||
|
||||
import * as Messages from "./messages";
|
||||
|
||||
import * as CallbackData from "./callback_data";
|
||||
|
||||
import * as BookLibrary from "./services/book_library";
|
||||
import { CachedMessage, getBookCache } from './services/book_cache';
|
||||
import { getBookCacheBuffer } from './services/book_cache_buffer';
|
||||
import { formatBook, formatAuthor, formatSequence } from './format';
|
||||
import { getPaginatedMessage, registerPaginationCommand } from './utils';
|
||||
|
||||
|
||||
export async function createApprovedBot(token: string, state: BotState): Promise<Telegraf> {
|
||||
const bot = new Telegraf(token, {
|
||||
telegram: {
|
||||
apiRoot: env.TELEGRAM_BOT_API_ROOT,
|
||||
}
|
||||
});
|
||||
|
||||
await bot.telegram.setMyCommands([
|
||||
{command: "random_book", description: "Случайная книга"},
|
||||
{command: "update_log", description: "Информация об обновлении каталога"},
|
||||
{command: "settings", description: "Настройки"},
|
||||
{command: "help", description: "Помощь"},
|
||||
]);
|
||||
|
||||
bot.help((ctx: Context) => ctx.reply(Messages.HELP_MESSAGE));
|
||||
|
||||
bot.start((ctx: Context) => {
|
||||
if (!ctx.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = ctx.message.from.first_name || ctx.message.from.username || 'пользователь';
|
||||
ctx.telegram.sendMessage(ctx.message.chat.id,
|
||||
Messages.START_MESSAGE.replace('{name}', name), {
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
registerPaginationCommand(bot, CallbackData.SEARCH_BOOK_PREFIX, BookLibrary.searchByBookName, formatBook);
|
||||
registerPaginationCommand(bot, CallbackData.SEARCH_AUTHORS_PREFIX, BookLibrary.searchAuthors, formatAuthor);
|
||||
registerPaginationCommand(bot, CallbackData.SEARCH_SERIES_PREFIX, BookLibrary.searchSequences, formatSequence);
|
||||
registerPaginationCommand(bot, CallbackData.AUTHOR_BOOKS_PREFIX, BookLibrary.getAuthorBooks, formatBook);
|
||||
registerPaginationCommand(bot, CallbackData.SEQUENCE_BOOKS_PREFIX, BookLibrary.getSequenceBooks, formatBook);
|
||||
|
||||
bot.hears(/^\/d_[a-zA-Z0-9]+_[\d]+$/gm, async (ctx: Context) => {
|
||||
if (!ctx.message || !('text' in ctx.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [_, format, id] = ctx.message.text.split('_');
|
||||
|
||||
let cache: CachedMessage;
|
||||
|
||||
if (state.privileged) {
|
||||
cache = await getBookCache(parseInt(id), format);
|
||||
} else {
|
||||
cache = await getBookCacheBuffer(parseInt(id), format);
|
||||
}
|
||||
|
||||
ctx.telegram.copyMessage(ctx.message.chat.id, cache.chat_id, cache.message_id, {
|
||||
allow_sending_without_reply: true,
|
||||
})
|
||||
});
|
||||
|
||||
bot.hears(/^\/b_info_[\d]+$/gm, async (ctx: Context) => {
|
||||
if (!ctx.message || !('text' in ctx.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bookId = ctx.message.text.split('_')[2];
|
||||
|
||||
const annotation = await BookLibrary.getBookAnnotation(parseInt(bookId));
|
||||
|
||||
ctx.reply(annotation.text);
|
||||
});
|
||||
|
||||
bot.hears(/^\/a_[\d]+$/gm, async (ctx: Context) => {
|
||||
if (!ctx.message || !('text' in ctx.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const authorId = ctx.message.text.split('_')[1];
|
||||
|
||||
const pMessage = await getPaginatedMessage(CallbackData.AUTHOR_BOOKS_PREFIX, authorId, 1, BookLibrary.getAuthorBooks, formatBook);
|
||||
|
||||
await ctx.reply(pMessage.message, {
|
||||
reply_markup: pMessage.keyboard.reply_markup
|
||||
});
|
||||
});
|
||||
|
||||
bot.hears(/^\/s_[\d]+$/gm, async (ctx: Context) => {
|
||||
if (!ctx.message || !('text' in ctx.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sequenceId = ctx.message.text.split('_')[1];
|
||||
|
||||
const pMessage = await getPaginatedMessage(CallbackData.SEQUENCE_BOOKS_PREFIX, sequenceId, 1, BookLibrary.getSequenceBooks, formatBook);
|
||||
|
||||
await ctx.reply(pMessage.message, {
|
||||
reply_markup: pMessage.keyboard.reply_markup
|
||||
});
|
||||
});
|
||||
|
||||
bot.on("message", async (ctx: Context) => {
|
||||
if (!ctx.message || !('text' in ctx.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = ctx.message.text.substring(0, 64 - 7);
|
||||
|
||||
let keyboard = Markup.inlineKeyboard([
|
||||
[
|
||||
Markup.button.callback('Книгу', `${CallbackData.SEARCH_BOOK_PREFIX}${query}_1`)
|
||||
],
|
||||
[
|
||||
Markup.button.callback('Автора', `${CallbackData.SEARCH_AUTHORS_PREFIX}${query}_1`),
|
||||
],
|
||||
[
|
||||
Markup.button.callback('Серию', `${CallbackData.SEARCH_SERIES_PREFIX}${query}_1`)
|
||||
],
|
||||
[
|
||||
Markup.button.callback('Переводчика', '# ToDO'),
|
||||
]
|
||||
]);
|
||||
|
||||
await ctx.telegram.sendMessage(ctx.message.chat.id, Messages.SEARCH_MESSAGE, {
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
reply_markup: keyboard.reply_markup,
|
||||
});
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
32
src/bots/factory/bots/approved/keyboard.ts
Normal file
32
src/bots/factory/bots/approved/keyboard.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Markup } from 'telegraf';
|
||||
import { InlineKeyboardMarkup } from 'typegram';
|
||||
|
||||
|
||||
export function getPaginationKeyboard(prefix: string, query: string, page: number, totalPages: number): Markup.Markup<InlineKeyboardMarkup> {
|
||||
function getRow(delta: number) {
|
||||
const row = [];
|
||||
|
||||
if (page - delta > 0) {
|
||||
row.push(Markup.button.callback(`-${delta}`, `${prefix}${query}_${page - delta}`));
|
||||
}
|
||||
if (page + delta <= totalPages) {
|
||||
row.push(Markup.button.callback(`+${delta}`, `${prefix}${query}_${page + delta}`));
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
const rows = [];
|
||||
|
||||
const row1 = getRow(1);
|
||||
if (row1) {
|
||||
rows.push(row1);
|
||||
}
|
||||
|
||||
const row5 = getRow(5);
|
||||
if (row5) {
|
||||
rows.push(row5);
|
||||
}
|
||||
|
||||
return Markup.inlineKeyboard(rows);
|
||||
}
|
||||
11
src/bots/factory/bots/approved/messages.ts
Normal file
11
src/bots/factory/bots/approved/messages.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const START_MESSAGE = 'Привет, {name}! \n' +
|
||||
'Этот бот поможет тебе загружать книги.\n' +
|
||||
'Узнать, как со мной работать /help.\n' +
|
||||
'Настройки языков для поиска /settings.\n';
|
||||
|
||||
export const HELP_MESSAGE = 'Лучше один раз увидеть, чем сто раз услышать.\n' +
|
||||
'https://youtu.be/HV6Wm87D6_A';
|
||||
|
||||
export const SETTINGS_MESSAGE = 'Настройки:';
|
||||
|
||||
export const SEARCH_MESSAGE = 'Что ищем?';
|
||||
37
src/bots/factory/bots/approved/services/book_cache.ts
Normal file
37
src/bots/factory/bots/approved/services/book_cache.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import got from 'got';
|
||||
|
||||
import env from '@/config';
|
||||
|
||||
|
||||
export interface CachedMessage {
|
||||
message_id: number,
|
||||
chat_id: string | number,
|
||||
}
|
||||
|
||||
|
||||
interface BookCache {
|
||||
id: number;
|
||||
object_id: number;
|
||||
object_type: string;
|
||||
data: CachedMessage & {
|
||||
file_token: string | null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function _makeRequest<T>(url: string, searchParams?: string | Record<string, string | number | boolean | null | undefined> | URLSearchParams | undefined): Promise<T> {
|
||||
const response = await got<T>(`${env.CACHE_SERVER_URL}${url}`, {
|
||||
searchParams,
|
||||
headers: {
|
||||
'Authorization': env.CACHE_SERVER_API_KEY,
|
||||
},
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
|
||||
export async function getBookCache(bookId: number, fileType: string): Promise<CachedMessage> {
|
||||
return (await _makeRequest<BookCache>(`/api/v1/${bookId}/${fileType}`)).data;
|
||||
}
|
||||
22
src/bots/factory/bots/approved/services/book_cache_buffer.ts
Normal file
22
src/bots/factory/bots/approved/services/book_cache_buffer.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import got from 'got';
|
||||
|
||||
import env from '@/config';
|
||||
import { CachedMessage } from './book_cache';
|
||||
|
||||
|
||||
async function _makeRequest<T>(url: string, searchParams?: string | Record<string, string | number | boolean | null | undefined> | URLSearchParams | undefined): Promise<T> {
|
||||
const response = await got<T>(`${env.BUFFER_SERVER_URL}${url}`, {
|
||||
searchParams,
|
||||
headers: {
|
||||
'Authorization': env.BUFFER_SERVER_API_KEY,
|
||||
},
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
|
||||
export async function getBookCacheBuffer(bookId: number, fileType: string): Promise<CachedMessage> {
|
||||
return _makeRequest<CachedMessage>(`/api/v1/${bookId}/${fileType}`);
|
||||
}
|
||||
121
src/bots/factory/bots/approved/services/book_library.ts
Normal file
121
src/bots/factory/bots/approved/services/book_library.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import got from 'got';
|
||||
|
||||
import env from '@/config';
|
||||
|
||||
|
||||
const PAGE_SIZE = 7;
|
||||
|
||||
|
||||
export interface Page<T> {
|
||||
items: T[];
|
||||
page: number;
|
||||
size: number;
|
||||
total: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
|
||||
interface BookAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
middle_name: string;
|
||||
}
|
||||
|
||||
|
||||
export interface AuthorBook {
|
||||
id: number;
|
||||
title: string;
|
||||
lang: string;
|
||||
file_type: string;
|
||||
available_types: string[];
|
||||
uploaded: string;
|
||||
annotation_exists: boolean;
|
||||
translators: BookAuthor[];
|
||||
}
|
||||
|
||||
|
||||
export interface Book extends AuthorBook {
|
||||
authors: BookAuthor[];
|
||||
}
|
||||
|
||||
|
||||
export interface Author {
|
||||
id: number;
|
||||
last_name: string;
|
||||
first_name: string;
|
||||
middle_name: string;
|
||||
annotation_exists: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface Sequence {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
|
||||
export interface BookAnnotation {
|
||||
id: number;
|
||||
title: string;
|
||||
text: string;
|
||||
file: string | null;
|
||||
}
|
||||
|
||||
|
||||
async function _makeRequest<T>(url: string, searchParams?: string | Record<string, string | number | boolean | null | undefined> | URLSearchParams | undefined): Promise<T> {
|
||||
const response = await got<T>(`${env.BOOK_SERVER_URL}${url}`, {
|
||||
searchParams,
|
||||
headers: {
|
||||
'Authorization': env.BOOK_SERVER_API_KEY,
|
||||
},
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
|
||||
export async function searchByBookName(query: string, page: number): Promise<Page<Book>> {
|
||||
return _makeRequest<Page<Book>>(`/api/v1/books/search/${query}`, {
|
||||
page: page,
|
||||
size: PAGE_SIZE,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export async function searchAuthors(query: string, page: number): Promise<Page<Author>> {
|
||||
return _makeRequest<Page<Author>>(`/api/v1/authors/search/${query}`, {
|
||||
page: page,
|
||||
size: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function searchSequences(query: string, page: number): Promise<Page<Sequence>> {
|
||||
return _makeRequest<Page<Sequence>>(`/api/v1/sequences/search/${query}`, {
|
||||
page: page,
|
||||
size: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function getBookAnnotation(bookId: number): Promise<BookAnnotation> {
|
||||
return _makeRequest<BookAnnotation>(`/api/v1/books/${bookId}/annotation`);
|
||||
}
|
||||
|
||||
|
||||
export async function getAuthorBooks(authorId: number, page: number): Promise<Page<AuthorBook>> {
|
||||
return _makeRequest<Page<AuthorBook>>(`/api/v1/authors/${authorId}/books`, {
|
||||
page: page,
|
||||
size: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function getSequenceBooks(sequenceId: number, page: number): Promise<Page<Book>> {
|
||||
return _makeRequest<Page<Book>>(`/api/v1/sequences/${sequenceId}/books`, {
|
||||
page: page,
|
||||
size: PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
55
src/bots/factory/bots/approved/utils.ts
Normal file
55
src/bots/factory/bots/approved/utils.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Context, Markup, Telegraf, TelegramError } from 'telegraf';
|
||||
import { InlineKeyboardMarkup } from 'typegram';
|
||||
|
||||
import { getPaginationKeyboard } from './keyboard';
|
||||
import * as BookLibrary from "./services/book_library";
|
||||
|
||||
|
||||
interface PreparedMessage {
|
||||
message: string;
|
||||
keyboard: Markup.Markup<InlineKeyboardMarkup>;
|
||||
}
|
||||
|
||||
|
||||
export async function getPaginatedMessage<T>(
|
||||
prefix: string,
|
||||
data: any,
|
||||
page: number,
|
||||
itemsGetter: (data: any, page: number) => Promise<BookLibrary.Page<T>>,
|
||||
itemFormater: (item: T) => string,
|
||||
): Promise<PreparedMessage> {
|
||||
const itemsPage = await itemsGetter(data, page);
|
||||
|
||||
const formatedItems = itemsPage.items.map(itemFormater).join('\n\n\n');
|
||||
const message = formatedItems + `\n\nСтраница ${page}/${itemsPage.total_pages}`;
|
||||
|
||||
const keyboard = getPaginationKeyboard(prefix, data, page, itemsPage.total_pages);
|
||||
|
||||
return {
|
||||
message,
|
||||
keyboard
|
||||
};
|
||||
}
|
||||
|
||||
export function registerPaginationCommand<T>(
|
||||
bot: Telegraf,
|
||||
prefix: string,
|
||||
itemsGetter: (data: any, page: number) => Promise<BookLibrary.Page<T>>,
|
||||
itemFormater: (item: T) => string,
|
||||
) {
|
||||
bot.action(new RegExp(prefix), async (ctx: Context) => {
|
||||
if (!ctx.callbackQuery || !('data' in ctx.callbackQuery)) return;
|
||||
|
||||
const [_, query, sPage] = ctx.callbackQuery.data.split('_');
|
||||
|
||||
const pMessage = await getPaginatedMessage(prefix, query, parseInt(sPage), itemsGetter, itemFormater);
|
||||
|
||||
try {
|
||||
await ctx.editMessageText(pMessage.message, {
|
||||
reply_markup: pMessage.keyboard.reply_markup
|
||||
});
|
||||
} catch (err) {
|
||||
// console.log(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
16
src/bots/factory/bots/blocked.ts
Normal file
16
src/bots/factory/bots/blocked.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Telegraf, Context } from 'telegraf';
|
||||
|
||||
import { BotState } from '@/bots/manager';
|
||||
|
||||
|
||||
export async function createBlockedBot(token: string, state: BotState): Promise<Telegraf> {
|
||||
const bot = new Telegraf(token);
|
||||
|
||||
await bot.telegram.deleteMyCommands();
|
||||
|
||||
bot.on("message", async (ctx: Context) => {
|
||||
await ctx.reply('Бот заблокирован!');
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
16
src/bots/factory/bots/pending.ts
Normal file
16
src/bots/factory/bots/pending.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Telegraf, Context } from 'telegraf';
|
||||
|
||||
import { BotState } from '@/bots/manager';
|
||||
|
||||
|
||||
export async function createPendingBot(token: string, state: BotState): Promise<Telegraf> {
|
||||
const bot = new Telegraf(token);
|
||||
|
||||
await bot.telegram.deleteMyCommands();
|
||||
|
||||
bot.on("message", async (ctx: Context) => {
|
||||
await ctx.reply('Бот зарегистрирован, но не подтвержден администратором!');
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
25
src/bots/factory/index.ts
Normal file
25
src/bots/factory/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Telegraf } from "telegraf";
|
||||
|
||||
import { BotState } from '@/bots/manager';
|
||||
|
||||
import { createPendingBot } from './bots/pending';
|
||||
import { createBlockedBot } from './bots/blocked';
|
||||
import { createApprovedBot } from './bots/approved/index';
|
||||
|
||||
|
||||
export enum BotStatuses {
|
||||
PENDING = 'pending',
|
||||
APPROVED = 'approved',
|
||||
BLOCKED = 'blocked',
|
||||
}
|
||||
|
||||
|
||||
export default async function getBot(token: string, state: BotState): Promise<Telegraf> {
|
||||
const handlers = {
|
||||
[BotStatuses.PENDING]: createPendingBot,
|
||||
[BotStatuses.BLOCKED]: createBlockedBot,
|
||||
[BotStatuses.APPROVED]: createApprovedBot,
|
||||
};
|
||||
|
||||
return handlers[state.status](token, state);
|
||||
}
|
||||
114
src/bots/manager.ts
Normal file
114
src/bots/manager.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import express, { Response, Request, NextFunction } from 'express';
|
||||
|
||||
import got from 'got';
|
||||
|
||||
import { Telegraf } from 'telegraf';
|
||||
|
||||
import env from '@/config';
|
||||
import getBot, { BotStatuses } from './factory/index';
|
||||
import { Server } from 'http';
|
||||
|
||||
|
||||
export interface BotState {
|
||||
id: number;
|
||||
token: string;
|
||||
status: BotStatuses;
|
||||
privileged: boolean;
|
||||
created_time: string;
|
||||
}
|
||||
|
||||
|
||||
async function _makeSyncRequest(): Promise<BotState[] | null> {
|
||||
try {
|
||||
const response = await got<BotState[]>(env.MANAGER_URL, {
|
||||
headers: {
|
||||
'Authorization': env.MANAGER_API_KEY
|
||||
},
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return response.body;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class BotsManager {
|
||||
static bots: {[key: number]: Telegraf} = {};
|
||||
static botsStates: {[key: number]: BotStatuses} = {};
|
||||
static syncInterval: NodeJS.Timer | null = null;
|
||||
static server: Server | null = null;
|
||||
|
||||
static async start() {
|
||||
await this.sync();
|
||||
|
||||
this.launch();
|
||||
|
||||
await this.sync();
|
||||
if (this.syncInterval === null) {
|
||||
this.syncInterval = setInterval(() => this.sync(), 30_000);
|
||||
}
|
||||
}
|
||||
|
||||
static async sync() {
|
||||
const botsData = await _makeSyncRequest();
|
||||
|
||||
if (botsData !== null) {
|
||||
await Promise.all(botsData.map((state) => this.updateBotState(state)));
|
||||
}
|
||||
}
|
||||
|
||||
static async updateBotState(state: BotState) {
|
||||
const isExists = this.bots[state.id] !== undefined;
|
||||
|
||||
if (isExists && this.botsStates[state.id] === state.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bot = await getBot(state.token, state);
|
||||
|
||||
this.bots[state.id] = bot;
|
||||
this.botsStates[state.id] = state.status;
|
||||
|
||||
try {
|
||||
const oldBot = new Telegraf(bot.telegram.token);
|
||||
await oldBot.telegram.deleteWebhook();
|
||||
await oldBot.telegram.logOut();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
await bot.telegram.setWebhook(
|
||||
`${env.WEBHOOK_BASE_URL}:${env.WEBHOOK_PORT}/${state.id}/${bot.telegram.token}`
|
||||
);
|
||||
}
|
||||
|
||||
static async handleUpdate(req: Request, res: Response, next: NextFunction) {
|
||||
const botIdStr = req.url.split("/")[1];
|
||||
const bot = this.bots[parseInt(botIdStr)];
|
||||
await bot.webhookCallback(`/${botIdStr}/${bot.telegram.token}`)(req, res);
|
||||
}
|
||||
|
||||
static async launch() {
|
||||
const application = express();
|
||||
application.use((req: Request, res: Response, next: NextFunction) => this.handleUpdate(req, res, next));
|
||||
this.server = application.listen(env.WEBHOOK_PORT);
|
||||
console.log("Server started!");
|
||||
|
||||
process.once('SIGINT', () => this.stop());
|
||||
process.once('SIGTERM', () => this.stop());
|
||||
}
|
||||
|
||||
static stop() {
|
||||
Object.keys(this.bots).forEach(key => this.bots[parseInt(key)].telegram.deleteWebhook());
|
||||
|
||||
if (this.syncInterval) {
|
||||
clearInterval(this.syncInterval);
|
||||
this.syncInterval = null;
|
||||
}
|
||||
|
||||
this.server?.close();
|
||||
this.server = null;
|
||||
}
|
||||
}
|
||||
16
src/config.ts
Normal file
16
src/config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { cleanEnv, str, num } from 'envalid';
|
||||
|
||||
|
||||
export default cleanEnv(process.env, {
|
||||
WEBHOOK_BASE_URL: str(),
|
||||
WEBHOOK_PORT: num(),
|
||||
TELEGRAM_BOT_API_ROOT: str({ default: "https://api.telegram.org" }),
|
||||
MANAGER_URL: str(),
|
||||
MANAGER_API_KEY: str(),
|
||||
BOOK_SERVER_URL: str(),
|
||||
BOOK_SERVER_API_KEY: str(),
|
||||
CACHE_SERVER_URL: str(),
|
||||
CACHE_SERVER_API_KEY: str(),
|
||||
BUFFER_SERVER_URL: str(),
|
||||
BUFFER_SERVER_API_KEY: str()
|
||||
});
|
||||
4
src/main.ts
Normal file
4
src/main.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import BotsManager from './bots/manager';
|
||||
|
||||
|
||||
BotsManager.start();
|
||||
Reference in New Issue
Block a user