diff --git a/package.json b/package.json index aa2d609..b42e5c6 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "", "license": "ISC", "dependencies": { + "docker-ip-get": "^1.1.5", "envalid": "^7.2.2", "express": "^4.17.1", "got": "^11.8.3", diff --git a/src/bots/factory/bots/approved/callback_data.ts b/src/bots/factory/bots/approved/callback_data.ts index 47a3931..e2f08bc 100644 --- a/src/bots/factory/bots/approved/callback_data.ts +++ b/src/bots/factory/bots/approved/callback_data.ts @@ -10,3 +10,7 @@ export const SEQUENCE_BOOKS_PREFIX = 'bs_'; export const RANDOM_BOOK = 'random_book'; export const RANDOM_AUTHOR = 'random_author'; export const RANDOM_SEQUENCE = 'random_sequence'; + +export const LANG_SETTINGS = 'lang_settings'; +export const ENABLE_LANG_PREFIX = 'lang_on_'; +export const DISABLE_LANG_PREFIX = 'lang_off_'; diff --git a/src/bots/factory/bots/approved/index.ts b/src/bots/factory/bots/approved/index.ts index 420e4bd..89947ff 100644 --- a/src/bots/factory/bots/approved/index.ts +++ b/src/bots/factory/bots/approved/index.ts @@ -12,10 +12,10 @@ import * as BookLibrary from "./services/book_library"; import { CachedMessage, getBookCache } from './services/book_cache'; import { getBookCacheBuffer } from './services/book_cache_buffer'; import { download } from './services/downloader'; -import { createOrUpdateUserSettings } from './services/user_settings'; +import { createOrUpdateUserSettings, getUserSettings } from './services/user_settings'; import { formatBook, formatAuthor, formatSequence } from './format'; -import { getPaginatedMessage, registerPaginationCommand, registerRandomItemCallback } from './utils'; -import { getRandomKeyboard } from './keyboard'; +import { getPaginatedMessage, registerLanguageSettingsCallback, registerPaginationCommand, registerRandomItemCallback } from './utils'; +import { getRandomKeyboard, getUserAllowedLangsKeyboard } from './keyboard'; export async function createApprovedBot(token: string, state: BotState): Promise { @@ -88,6 +88,29 @@ export async function createApprovedBot(token: string, state: BotState): Promise registerRandomItemCallback(bot, CallbackData.RANDOM_AUTHOR, BookLibrary.getRandomAuthor, formatAuthor); registerRandomItemCallback(bot, CallbackData.RANDOM_SEQUENCE, BookLibrary.getRandomSequence, formatSequence); + bot.command("settings", async (ctx: Context) => { + const keyboard = Markup.inlineKeyboard([ + [Markup.button.callback("Языки", CallbackData.LANG_SETTINGS)] + ]); + + ctx.reply("Настройки:", { + reply_markup: keyboard.reply_markup + }); + }); + + bot.action(CallbackData.LANG_SETTINGS, async (ctx: Context) => { + if (!ctx.callbackQuery || !('data' in ctx.callbackQuery)) return; + + const keyboard = await getUserAllowedLangsKeyboard(ctx.callbackQuery.from.id); + + ctx.editMessageText("Настройки языков:", { + reply_markup: keyboard.reply_markup, + }); + }); + + registerLanguageSettingsCallback(bot, 'on', CallbackData.ENABLE_LANG_PREFIX); + registerLanguageSettingsCallback(bot, 'off', CallbackData.DISABLE_LANG_PREFIX); + bot.hears(/^\/d_[a-zA-Z0-9]+_[\d]+$/gm, async (ctx: Context) => { if (!ctx.message || !('text' in ctx.message)) { return; @@ -134,7 +157,10 @@ export async function createApprovedBot(token: string, state: BotState): Promise const authorId = ctx.message.text.split('_')[1]; - const pMessage = await getPaginatedMessage(CallbackData.AUTHOR_BOOKS_PREFIX, authorId, 1, BookLibrary.getAuthorBooks, formatBook); + const userSettings = await getUserSettings(ctx.message.from.id); + const allowedLangs = userSettings.allowed_langs.map((lang) => lang.code); + + const pMessage = await getPaginatedMessage(CallbackData.AUTHOR_BOOKS_PREFIX, authorId, 1, allowedLangs, BookLibrary.getAuthorBooks, formatBook); await ctx.reply(pMessage.message, { reply_markup: pMessage.keyboard.reply_markup @@ -148,7 +174,10 @@ export async function createApprovedBot(token: string, state: BotState): Promise const sequenceId = ctx.message.text.split('_')[1]; - const pMessage = await getPaginatedMessage(CallbackData.SEQUENCE_BOOKS_PREFIX, sequenceId, 1, BookLibrary.getSequenceBooks, formatBook); + const userSettings = await getUserSettings(ctx.message.from.id); + const allowedLangs = userSettings.allowed_langs.map((lang) => lang.code); + + const pMessage = await getPaginatedMessage(CallbackData.SEQUENCE_BOOKS_PREFIX, sequenceId, 1, allowedLangs, BookLibrary.getSequenceBooks, formatBook); await ctx.reply(pMessage.message, { reply_markup: pMessage.keyboard.reply_markup diff --git a/src/bots/factory/bots/approved/keyboard.ts b/src/bots/factory/bots/approved/keyboard.ts index d1491ab..f865c0c 100644 --- a/src/bots/factory/bots/approved/keyboard.ts +++ b/src/bots/factory/bots/approved/keyboard.ts @@ -1,7 +1,8 @@ import { Markup } from 'telegraf'; import { InlineKeyboardMarkup } from 'typegram'; -import { RANDOM_BOOK, RANDOM_AUTHOR, RANDOM_SEQUENCE } from './callback_data'; +import { RANDOM_BOOK, RANDOM_AUTHOR, RANDOM_SEQUENCE, ENABLE_LANG_PREFIX, DISABLE_LANG_PREFIX } from './callback_data'; +import { getUserSettings, getLanguages } from './services/user_settings'; export function getPaginationKeyboard(prefix: string, query: string, page: number, totalPages: number): Markup.Markup { @@ -41,3 +42,33 @@ export function getRandomKeyboard(): Markup.Markup { [Markup.button.callback('Серию', RANDOM_SEQUENCE)], ]); } + +const DEFAULT_ALLOWED_LANGS_CODES = ['ru', 'be', 'uk']; + +export async function getUserAllowedLangsKeyboard(userId: number): Promise> { + const allLangs = await getLanguages(); + const userSettings = await getUserSettings(userId); + + const userAllowedLangsCodes = userSettings !== null + ? userSettings.allowed_langs.map((lang) => lang.code) + : DEFAULT_ALLOWED_LANGS_CODES; + + const onEmoji = '🟢'; + const offEmoji = '🔴'; + + return Markup.inlineKeyboard([ + ...allLangs.map((lang) => { + let titlePrefix: string; + let callbackDataPrefix: string; + if (userAllowedLangsCodes.includes(lang.code)) { + titlePrefix = onEmoji; + callbackDataPrefix = DISABLE_LANG_PREFIX; + } else { + titlePrefix = offEmoji; + callbackDataPrefix = ENABLE_LANG_PREFIX; + } + const title = `${titlePrefix} ${lang.label}`; + return [Markup.button.callback(title, `${callbackDataPrefix}${lang.code}`)]; + }) + ]); +} diff --git a/src/bots/factory/bots/approved/services/book_library.ts b/src/bots/factory/bots/approved/services/book_library.ts index 7a14f6a..e5bb53a 100644 --- a/src/bots/factory/bots/approved/services/book_library.ts +++ b/src/bots/factory/bots/approved/services/book_library.ts @@ -1,6 +1,7 @@ import got from 'got'; import env from '@/config'; +import { getAllowedLangsSearchParams } from '../utils'; const PAGE_SIZE = 7; @@ -94,27 +95,30 @@ export async function getBookById(book_id: number): Promise { } -export async function searchByBookName(query: string, page: number): Promise> { - return _makeRequest>(`/api/v1/books/search/${query}`, { - page: page, - size: PAGE_SIZE, - }) +export async function searchByBookName(query: string, page: number, allowedLangs: string[]): Promise> { + const searchParams = getAllowedLangsSearchParams(allowedLangs); + searchParams.append('page', page.toString()); + searchParams.append('size', PAGE_SIZE.toString()); + + return _makeRequest>(`/api/v1/books/search/${query}`, searchParams); } -export async function searchAuthors(query: string, page: number): Promise> { - return _makeRequest>(`/api/v1/authors/search/${query}`, { - page: page, - size: PAGE_SIZE, - }); +export async function searchAuthors(query: string, page: number, allowedLangs: string[]): Promise> { + const searchParams = getAllowedLangsSearchParams(allowedLangs); + searchParams.append('page', page.toString()); + searchParams.append('size', PAGE_SIZE.toString()); + + return _makeRequest>(`/api/v1/authors/search/${query}`, searchParams); } -export async function searchSequences(query: string, page: number): Promise> { - return _makeRequest>(`/api/v1/sequences/search/${query}`, { - page: page, - size: PAGE_SIZE, - }); +export async function searchSequences(query: string, page: number, allowedLangs: string[]): Promise> { + const searchParams = getAllowedLangsSearchParams(allowedLangs); + searchParams.append('page', page.toString()); + searchParams.append('size', PAGE_SIZE.toString()); + + return _makeRequest>(`/api/v1/sequences/search/${query}`, searchParams); } @@ -123,29 +127,31 @@ export async function getBookAnnotation(bookId: number): Promise } -export async function getAuthorBooks(authorId: number, page: number): Promise> { - return _makeRequest>(`/api/v1/authors/${authorId}/books`, { - page: page, - size: PAGE_SIZE, - }); +export async function getAuthorBooks(authorId: number, page: number, allowedLangs: string[]): Promise> { + const searchParams = getAllowedLangsSearchParams(allowedLangs); + searchParams.append('page', page.toString()); + searchParams.append('size', PAGE_SIZE.toString()); + + return _makeRequest>(`/api/v1/authors/${authorId}/books`, searchParams); } -export async function getSequenceBooks(sequenceId: number, page: number): Promise> { - return _makeRequest>(`/api/v1/sequences/${sequenceId}/books`, { - page: page, - size: PAGE_SIZE, - }); +export async function getSequenceBooks(sequenceId: number, page: number, allowedLangs: string[]): Promise> { + const searchParams = getAllowedLangsSearchParams(allowedLangs); + searchParams.append('page', page.toString()); + searchParams.append('size', PAGE_SIZE.toString()); + + return _makeRequest>(`/api/v1/sequences/${sequenceId}/books`, searchParams); } -export async function getRandomBook(): Promise { - return _makeRequest('/api/v1/books/random'); +export async function getRandomBook(allowedLangs: string[]): Promise { + return _makeRequest('/api/v1/books/random', getAllowedLangsSearchParams(allowedLangs)); } -export async function getRandomAuthor(): Promise { - return _makeRequest('/api/v1/authors/random'); +export async function getRandomAuthor(allowedLangs: string[]): Promise { + return _makeRequest('/api/v1/authors/random', getAllowedLangsSearchParams(allowedLangs)); } -export async function getRandomSequence(): Promise { - return _makeRequest('/api/v1/sequences/random'); +export async function getRandomSequence(allowedLangs: string[]): Promise { + return _makeRequest('/api/v1/sequences/random', getAllowedLangsSearchParams(allowedLangs)); } diff --git a/src/bots/factory/bots/approved/services/user_settings.ts b/src/bots/factory/bots/approved/services/user_settings.ts index b4446fa..06cbc69 100644 --- a/src/bots/factory/bots/approved/services/user_settings.ts +++ b/src/bots/factory/bots/approved/services/user_settings.ts @@ -25,6 +25,7 @@ export interface UserSettingsUpdateData { first_name: string; username: string; source: string; + allowed_langs?: string[]; } @@ -46,8 +47,8 @@ export async function getLanguages(): Promise { } -export async function getUserSettings(user_id: number): Promise { - return _makeGetRequest(`/users/${user_id}`); +export async function getUserSettings(userId: number): Promise { + return _makeGetRequest(`/users/${userId}`); } export async function createOrUpdateUserSettings(data: UserSettingsUpdateData): Promise { diff --git a/src/bots/factory/bots/approved/utils.ts b/src/bots/factory/bots/approved/utils.ts index 1d5855f..5c5c5bc 100644 --- a/src/bots/factory/bots/approved/utils.ts +++ b/src/bots/factory/bots/approved/utils.ts @@ -1,8 +1,10 @@ import { Context, Markup, Telegraf, TelegramError } from 'telegraf'; import { InlineKeyboardMarkup } from 'typegram'; +import { URLSearchParams } from 'url'; -import { getPaginationKeyboard } from './keyboard'; +import { getPaginationKeyboard, getUserAllowedLangsKeyboard } from './keyboard'; import * as BookLibrary from "./services/book_library"; +import { createOrUpdateUserSettings, getUserSettings } from './services/user_settings'; interface PreparedMessage { @@ -15,10 +17,11 @@ export async function getPaginatedMessage( prefix: string, data: any, page: number, - itemsGetter: (data: any, page: number) => Promise>, + allowedLangs: string[], + itemsGetter: (data: any, page: number, allowedLangs: string[]) => Promise>, itemFormater: (item: T) => string, ): Promise { - const itemsPage = await itemsGetter(data, page); + const itemsPage = await itemsGetter(data, page, allowedLangs); const formatedItems = itemsPage.items.map(itemFormater).join('\n\n\n'); const message = formatedItems + `\n\nСтраница ${page}/${itemsPage.total_pages}`; @@ -34,7 +37,7 @@ export async function getPaginatedMessage( export function registerPaginationCommand( bot: Telegraf, prefix: string, - itemsGetter: (data: any, page: number) => Promise>, + itemsGetter: (data: any, page: number, allowedLangs: string[]) => Promise>, itemFormater: (item: T) => string, ) { bot.action(new RegExp(prefix), async (ctx: Context) => { @@ -42,7 +45,10 @@ export function registerPaginationCommand( const [_, query, sPage] = ctx.callbackQuery.data.split('_'); - const pMessage = await getPaginatedMessage(prefix, query, parseInt(sPage), itemsGetter, itemFormater); + const userSettings = await getUserSettings(ctx.callbackQuery.from.id); + const allowedLangs = userSettings.allowed_langs.map((lang) => lang.code); + + const pMessage = await getPaginatedMessage(prefix, query, parseInt(sPage), allowedLangs, itemsGetter, itemFormater); try { await ctx.editMessageText(pMessage.message, { @@ -57,11 +63,17 @@ export function registerPaginationCommand( export function registerRandomItemCallback( bot: Telegraf, callback_data: string, - itemGetter: () => Promise, + itemGetter: (allowedLangs: string[]) => Promise, itemFormatter: (item: T) => string, ) { bot.action(callback_data, async (ctx: Context) => { - const item = await itemGetter(); + if (!ctx.callbackQuery || !('data' in ctx.callbackQuery)) return; + + const userSettings = await getUserSettings(ctx.callbackQuery.from.id); + + const item = await itemGetter( + userSettings.allowed_langs.map((lang) => lang.code) + ); const keyboard = Markup.inlineKeyboard([ [Markup.button.callback("Повторить?", callback_data)] @@ -74,3 +86,53 @@ export function registerRandomItemCallback( }); }); } + + +export function registerLanguageSettingsCallback( + bot: Telegraf, + action: 'on' | 'off', + prefix: string, +) { + bot.action(new RegExp(prefix), async (ctx: Context) => { + if (!ctx.callbackQuery || !('data' in ctx.callbackQuery)) return; + + const userSettings = await getUserSettings(ctx.callbackQuery.from.id); + + let allowedLangsCodes = userSettings.allowed_langs.map((item) => item.code); + + const tLang = ctx.callbackQuery.data.split("_")[2]; + + if (action === 'on') { + allowedLangsCodes.push(tLang); + } else { + allowedLangsCodes = allowedLangsCodes.filter((item) => item !== tLang); + } + + if (allowedLangsCodes.length === 0) { + ctx.answerCbQuery("Должен быть активен, хотя бы один язык!", { + show_alert: true, + }); + return; + } + + const user = ctx.callbackQuery.from; + await createOrUpdateUserSettings({ + user_id: user.id, + last_name: user.last_name || '', + first_name: user.first_name, + username: user.username || '', + source: ctx.botInfo.username, + allowed_langs: allowedLangsCodes, + }); + + const keyboard = await getUserAllowedLangsKeyboard(user.id); + + ctx.editMessageReplyMarkup(keyboard.reply_markup); + }); +} + +export function getAllowedLangsSearchParams(allowedLangs: string[]): URLSearchParams { + const sp = new URLSearchParams(); + allowedLangs.forEach((lang) => sp.append('allowed_langs', lang)); + return sp; +} diff --git a/src/bots/manager.ts b/src/bots/manager.ts index ffaeacd..f0d8182 100644 --- a/src/bots/manager.ts +++ b/src/bots/manager.ts @@ -1,4 +1,7 @@ import express, { Response, Request, NextFunction } from 'express'; +import { Server } from 'http'; + +import * as dockerIpTools from "docker-ip-get"; import got from 'got'; @@ -6,7 +9,6 @@ import { Telegraf } from 'telegraf'; import env from '@/config'; import getBot, { BotStatuses } from './factory/index'; -import { Server } from 'http'; export enum Cache { ORIGINAL = "original", @@ -84,8 +86,12 @@ export default class BotsManager { console.log(e); } + const dockerIp = await dockerIpTools.getContainerIp(); + await bot.telegram.setWebhook( - `${env.WEBHOOK_BASE_URL}:${env.WEBHOOK_PORT}/${state.id}/${bot.telegram.token}` + `${env.WEBHOOK_BASE_URL}:${env.WEBHOOK_PORT}/${state.id}/${bot.telegram.token}`, { + ip_address: dockerIp, + } ); } diff --git a/src/main.ts b/src/main.ts index 86916c7..fcefbc7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,3 @@ import BotsManager from './bots/manager'; -setTimeout(() => BotsManager.start(), 5000); +setTimeout(() => BotsManager.start(), 5_000);