Add allowed lang settings

This commit is contained in:
2022-01-02 18:13:56 +03:00
parent efc23049a8
commit 14f8c406d5
9 changed files with 189 additions and 49 deletions

View File

@@ -13,6 +13,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"docker-ip-get": "^1.1.5",
"envalid": "^7.2.2", "envalid": "^7.2.2",
"express": "^4.17.1", "express": "^4.17.1",
"got": "^11.8.3", "got": "^11.8.3",

View File

@@ -10,3 +10,7 @@ export const SEQUENCE_BOOKS_PREFIX = 'bs_';
export const RANDOM_BOOK = 'random_book'; export const RANDOM_BOOK = 'random_book';
export const RANDOM_AUTHOR = 'random_author'; export const RANDOM_AUTHOR = 'random_author';
export const RANDOM_SEQUENCE = 'random_sequence'; 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_';

View File

@@ -12,10 +12,10 @@ import * as BookLibrary from "./services/book_library";
import { CachedMessage, getBookCache } from './services/book_cache'; import { CachedMessage, getBookCache } from './services/book_cache';
import { getBookCacheBuffer } from './services/book_cache_buffer'; import { getBookCacheBuffer } from './services/book_cache_buffer';
import { download } from './services/downloader'; 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 { formatBook, formatAuthor, formatSequence } from './format';
import { getPaginatedMessage, registerPaginationCommand, registerRandomItemCallback } from './utils'; import { getPaginatedMessage, registerLanguageSettingsCallback, registerPaginationCommand, registerRandomItemCallback } from './utils';
import { getRandomKeyboard } from './keyboard'; import { getRandomKeyboard, getUserAllowedLangsKeyboard } from './keyboard';
export async function createApprovedBot(token: string, state: BotState): Promise<Telegraf> { export async function createApprovedBot(token: string, state: BotState): Promise<Telegraf> {
@@ -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_AUTHOR, BookLibrary.getRandomAuthor, formatAuthor);
registerRandomItemCallback(bot, CallbackData.RANDOM_SEQUENCE, BookLibrary.getRandomSequence, formatSequence); 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) => { bot.hears(/^\/d_[a-zA-Z0-9]+_[\d]+$/gm, async (ctx: Context) => {
if (!ctx.message || !('text' in ctx.message)) { if (!ctx.message || !('text' in ctx.message)) {
return; return;
@@ -134,7 +157,10 @@ export async function createApprovedBot(token: string, state: BotState): Promise
const authorId = ctx.message.text.split('_')[1]; 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, { await ctx.reply(pMessage.message, {
reply_markup: pMessage.keyboard.reply_markup 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 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, { await ctx.reply(pMessage.message, {
reply_markup: pMessage.keyboard.reply_markup reply_markup: pMessage.keyboard.reply_markup

View File

@@ -1,7 +1,8 @@
import { Markup } from 'telegraf'; import { Markup } from 'telegraf';
import { InlineKeyboardMarkup } from 'typegram'; 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<InlineKeyboardMarkup> { export function getPaginationKeyboard(prefix: string, query: string, page: number, totalPages: number): Markup.Markup<InlineKeyboardMarkup> {
@@ -41,3 +42,33 @@ export function getRandomKeyboard(): Markup.Markup<InlineKeyboardMarkup> {
[Markup.button.callback('Серию', RANDOM_SEQUENCE)], [Markup.button.callback('Серию', RANDOM_SEQUENCE)],
]); ]);
} }
const DEFAULT_ALLOWED_LANGS_CODES = ['ru', 'be', 'uk'];
export async function getUserAllowedLangsKeyboard(userId: number): Promise<Markup.Markup<InlineKeyboardMarkup>> {
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}`)];
})
]);
}

View File

@@ -1,6 +1,7 @@
import got from 'got'; import got from 'got';
import env from '@/config'; import env from '@/config';
import { getAllowedLangsSearchParams } from '../utils';
const PAGE_SIZE = 7; const PAGE_SIZE = 7;
@@ -94,27 +95,30 @@ export async function getBookById(book_id: number): Promise<DetailBook> {
} }
export async function searchByBookName(query: string, page: number): Promise<Page<Book>> { export async function searchByBookName(query: string, page: number, allowedLangs: string[]): Promise<Page<Book>> {
return _makeRequest<Page<Book>>(`/api/v1/books/search/${query}`, { const searchParams = getAllowedLangsSearchParams(allowedLangs);
page: page, searchParams.append('page', page.toString());
size: PAGE_SIZE, searchParams.append('size', PAGE_SIZE.toString());
})
return _makeRequest<Page<Book>>(`/api/v1/books/search/${query}`, searchParams);
} }
export async function searchAuthors(query: string, page: number): Promise<Page<Author>> { export async function searchAuthors(query: string, page: number, allowedLangs: string[]): Promise<Page<Author>> {
return _makeRequest<Page<Author>>(`/api/v1/authors/search/${query}`, { const searchParams = getAllowedLangsSearchParams(allowedLangs);
page: page, searchParams.append('page', page.toString());
size: PAGE_SIZE, searchParams.append('size', PAGE_SIZE.toString());
});
return _makeRequest<Page<Author>>(`/api/v1/authors/search/${query}`, searchParams);
} }
export async function searchSequences(query: string, page: number): Promise<Page<Sequence>> { export async function searchSequences(query: string, page: number, allowedLangs: string[]): Promise<Page<Sequence>> {
return _makeRequest<Page<Sequence>>(`/api/v1/sequences/search/${query}`, { const searchParams = getAllowedLangsSearchParams(allowedLangs);
page: page, searchParams.append('page', page.toString());
size: PAGE_SIZE, searchParams.append('size', PAGE_SIZE.toString());
});
return _makeRequest<Page<Sequence>>(`/api/v1/sequences/search/${query}`, searchParams);
} }
@@ -123,29 +127,31 @@ export async function getBookAnnotation(bookId: number): Promise<BookAnnotation>
} }
export async function getAuthorBooks(authorId: number, page: number): Promise<Page<AuthorBook>> { export async function getAuthorBooks(authorId: number, page: number, allowedLangs: string[]): Promise<Page<AuthorBook>> {
return _makeRequest<Page<AuthorBook>>(`/api/v1/authors/${authorId}/books`, { const searchParams = getAllowedLangsSearchParams(allowedLangs);
page: page, searchParams.append('page', page.toString());
size: PAGE_SIZE, searchParams.append('size', PAGE_SIZE.toString());
});
return _makeRequest<Page<AuthorBook>>(`/api/v1/authors/${authorId}/books`, searchParams);
} }
export async function getSequenceBooks(sequenceId: number, page: number): Promise<Page<Book>> { export async function getSequenceBooks(sequenceId: number, page: number, allowedLangs: string[]): Promise<Page<Book>> {
return _makeRequest<Page<Book>>(`/api/v1/sequences/${sequenceId}/books`, { const searchParams = getAllowedLangsSearchParams(allowedLangs);
page: page, searchParams.append('page', page.toString());
size: PAGE_SIZE, searchParams.append('size', PAGE_SIZE.toString());
});
return _makeRequest<Page<Book>>(`/api/v1/sequences/${sequenceId}/books`, searchParams);
} }
export async function getRandomBook(): Promise<Book> { export async function getRandomBook(allowedLangs: string[]): Promise<Book> {
return _makeRequest<Book>('/api/v1/books/random'); return _makeRequest<Book>('/api/v1/books/random', getAllowedLangsSearchParams(allowedLangs));
} }
export async function getRandomAuthor(): Promise<Author> { export async function getRandomAuthor(allowedLangs: string[]): Promise<Author> {
return _makeRequest<Author>('/api/v1/authors/random'); return _makeRequest<Author>('/api/v1/authors/random', getAllowedLangsSearchParams(allowedLangs));
} }
export async function getRandomSequence(): Promise<Sequence> { export async function getRandomSequence(allowedLangs: string[]): Promise<Sequence> {
return _makeRequest<Sequence>('/api/v1/sequences/random'); return _makeRequest<Sequence>('/api/v1/sequences/random', getAllowedLangsSearchParams(allowedLangs));
} }

View File

@@ -25,6 +25,7 @@ export interface UserSettingsUpdateData {
first_name: string; first_name: string;
username: string; username: string;
source: string; source: string;
allowed_langs?: string[];
} }
@@ -46,8 +47,8 @@ export async function getLanguages(): Promise<Language[]> {
} }
export async function getUserSettings(user_id: number): Promise<UserSettings | null> { export async function getUserSettings(userId: number): Promise<UserSettings> {
return _makeGetRequest<UserSettings>(`/users/${user_id}`); return _makeGetRequest<UserSettings>(`/users/${userId}`);
} }
export async function createOrUpdateUserSettings(data: UserSettingsUpdateData): Promise<UserSettings> { export async function createOrUpdateUserSettings(data: UserSettingsUpdateData): Promise<UserSettings> {

View File

@@ -1,8 +1,10 @@
import { Context, Markup, Telegraf, TelegramError } from 'telegraf'; import { Context, Markup, Telegraf, TelegramError } from 'telegraf';
import { InlineKeyboardMarkup } from 'typegram'; 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 * as BookLibrary from "./services/book_library";
import { createOrUpdateUserSettings, getUserSettings } from './services/user_settings';
interface PreparedMessage { interface PreparedMessage {
@@ -15,10 +17,11 @@ export async function getPaginatedMessage<T>(
prefix: string, prefix: string,
data: any, data: any,
page: number, page: number,
itemsGetter: (data: any, page: number) => Promise<BookLibrary.Page<T>>, allowedLangs: string[],
itemsGetter: (data: any, page: number, allowedLangs: string[]) => Promise<BookLibrary.Page<T>>,
itemFormater: (item: T) => string, itemFormater: (item: T) => string,
): Promise<PreparedMessage> { ): Promise<PreparedMessage> {
const itemsPage = await itemsGetter(data, page); const itemsPage = await itemsGetter(data, page, allowedLangs);
const formatedItems = itemsPage.items.map(itemFormater).join('\n\n\n'); const formatedItems = itemsPage.items.map(itemFormater).join('\n\n\n');
const message = formatedItems + `\n\nСтраница ${page}/${itemsPage.total_pages}`; const message = formatedItems + `\n\nСтраница ${page}/${itemsPage.total_pages}`;
@@ -34,7 +37,7 @@ export async function getPaginatedMessage<T>(
export function registerPaginationCommand<T>( export function registerPaginationCommand<T>(
bot: Telegraf, bot: Telegraf,
prefix: string, prefix: string,
itemsGetter: (data: any, page: number) => Promise<BookLibrary.Page<T>>, itemsGetter: (data: any, page: number, allowedLangs: string[]) => Promise<BookLibrary.Page<T>>,
itemFormater: (item: T) => string, itemFormater: (item: T) => string,
) { ) {
bot.action(new RegExp(prefix), async (ctx: Context) => { bot.action(new RegExp(prefix), async (ctx: Context) => {
@@ -42,7 +45,10 @@ export function registerPaginationCommand<T>(
const [_, query, sPage] = ctx.callbackQuery.data.split('_'); 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 { try {
await ctx.editMessageText(pMessage.message, { await ctx.editMessageText(pMessage.message, {
@@ -57,11 +63,17 @@ export function registerPaginationCommand<T>(
export function registerRandomItemCallback<T>( export function registerRandomItemCallback<T>(
bot: Telegraf, bot: Telegraf,
callback_data: string, callback_data: string,
itemGetter: () => Promise<T>, itemGetter: (allowedLangs: string[]) => Promise<T>,
itemFormatter: (item: T) => string, itemFormatter: (item: T) => string,
) { ) {
bot.action(callback_data, async (ctx: Context) => { 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([ const keyboard = Markup.inlineKeyboard([
[Markup.button.callback("Повторить?", callback_data)] [Markup.button.callback("Повторить?", callback_data)]
@@ -74,3 +86,53 @@ export function registerRandomItemCallback<T>(
}); });
}); });
} }
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;
}

View File

@@ -1,4 +1,7 @@
import express, { Response, Request, NextFunction } from 'express'; import express, { Response, Request, NextFunction } from 'express';
import { Server } from 'http';
import * as dockerIpTools from "docker-ip-get";
import got from 'got'; import got from 'got';
@@ -6,7 +9,6 @@ import { Telegraf } from 'telegraf';
import env from '@/config'; import env from '@/config';
import getBot, { BotStatuses } from './factory/index'; import getBot, { BotStatuses } from './factory/index';
import { Server } from 'http';
export enum Cache { export enum Cache {
ORIGINAL = "original", ORIGINAL = "original",
@@ -84,8 +86,12 @@ export default class BotsManager {
console.log(e); console.log(e);
} }
const dockerIp = await dockerIpTools.getContainerIp();
await bot.telegram.setWebhook( 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,
}
); );
} }

View File

@@ -1,3 +1,3 @@
import BotsManager from './bots/manager'; import BotsManager from './bots/manager';
setTimeout(() => BotsManager.start(), 5000); setTimeout(() => BotsManager.start(), 5_000);