diff --git a/code/bot.py b/code/bot.py index bc006c3..1cb4065 100644 --- a/code/bot.py +++ b/code/bot.py @@ -1,61 +1,60 @@ -import asyncio, logging, time, random, scripts, vk_scripts +import asyncio +import logging +import random -from init import * -from config import * +import scripts +import vk_scripts +from init import bot, dp, du, db +from config import serviceToken from handlers import router +async def check_group(group_id, activ_user, sub_user): + tokens = [x[0] for x in activ_user if group_id in x[1]] + users = [x[0] for x in sub_user if group_id in x[1]] + + # токен случайного подписчика даёт доступ к закрытым страницам, иначе — сервисный ключ + if tokens: + vk = vk_scripts.VkParser(db.get_token(random.choice(tokens))) + else: vk = vk_scripts.VkParser(serviceToken) + + # vk_api синхронный — в отдельном потоке, чтобы не замирал весь бот + output = await asyncio.to_thread(vk.last_post, owner_id=du.get_vk_id(group_id)) + last_post = db.get_postGroup(group_id) + + if output[3] > last_post: + text, audio, media = scripts.pars_post(output) + db.update_postGroup(group_id, output[3]) + + for sub in users: + try: + await scripts.send_post(bot, du.get_tg_id(sub), text, audio, media) + except Exception: + # подписчик мог заблокировать бота — остальным пост всё равно уходит + logging.warning("Не удалось отправить пост подписчику %s", sub, exc_info=True) + + async def timer(wait_for) -> None: while True: await asyncio.sleep(wait_for) - notif_group = list(map(lambda x: x[0], db.all_notifGroup())) - activ_user = list(map(lambda x: [x[0], list(map(int, x[1].split(';')))], db.all_activUser())) - sub_user = list(map(lambda x: [x[0], list(map(int, x[1].split(';')))], db.all_subUser())) + notif_group = [x[0] for x in db.all_notifGroup()] + activ_user = [[x[0], list(map(int, x[1].split(';')))] for x in db.all_activUser() if x[1]] + sub_user = [[x[0], list(map(int, x[1].split(';')))] for x in db.all_subUser() if x[1]] for group_id in notif_group: - time.sleep(1) - - tokens = [x[0] for x in activ_user if group_id in x[1]] - users = [x[0] for x in sub_user if group_id in x[1]] - - if tokens: - user_id = tokens[random.randrange(len(tokens))] - vk = vk_scripts.VkParser(db.get_token(user_id)) - else: vk = vk_scripts.VkParser(serviceToken) + await asyncio.sleep(1) # пауза между страницами — лимиты VK API try: - output = vk.last_post(owner_id=du.get_vk_id(group_id)) - last_post = db.get_postGroup(group_id) - - if output[3] > last_post: - text, audio, media = scripts.pars_post(types, output) - db.update_postGroup(group_id, output[3]) - - for sub in users: - tg_id = du.get_tg_id(sub) - - if audio and media: - post_message = await bot.send_media_group(chat_id=tg_id, media=media) - await bot.send_media_group(chat_id=tg_id, media=audio, - reply_to_message_id=post_message[0].message_id) - elif audio and media == []: - post_message = await bot.send_message(chat_id=tg_id, text=text) - await bot.send_media_group(chat_id=tg_id, media=audio, - reply_to_message_id=post_message[0].message_id) - elif audio == [] and media: - await bot.send_media_group(chat_id=tg_id, media=media) - else: - await bot.send_message(chat_id=tg_id, text=text, disable_web_page_preview=True) - - except Exception as err: + await check_group(group_id, activ_user, sub_user) + except Exception: logging.error("Ошибка обновления поста:", exc_info=True) async def main() -> None: dp.include_router(router) await bot.delete_webhook(drop_pending_updates=True) - await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types(), skipUpdates=True) + await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()) async def process() -> None: @@ -63,7 +62,5 @@ async def process() -> None: if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - try: asyncio.run(process()) except KeyboardInterrupt: pass diff --git a/code/config.py b/code/config.py index 95f35ab..da5ff56 100644 --- a/code/config.py +++ b/code/config.py @@ -1,9 +1,13 @@ +import os + # TOKENS -botToken = '**********' -serviceToken = "**********" +botToken = os.getenv("BOT_TOKEN", "XXXXXXXXXXXXXXXXXXXXXXXX") # @vkportalbot +serviceToken = os.getenv("VK_SERVICE_TOKEN", "") # сервисный ключ приложения VK # URL -loginUrl = "************" +loginUrl = os.getenv("VK_LOGIN_URL", "https://oauth.vk.com/authorize?client_id=6121396&scope=215985366" + "&redirect_uri=https://oauth.vk.com/blank.html&display=page" + "&response_type=token&revoke=1") checkUrl = "https://oauth.vk.com/blank.html#access_token=" # VARIABILITY @@ -12,3 +16,4 @@ anonymous = {True: 'анонимный опрос', False: 'не анонимн # DATA data = {0: 'Б', 1: 'КБ', 2: 'МБ', 3: 'ГБ', 4: 'ТБ'} +dura = {0: 'с.', 1: 'мин.'} diff --git a/code/handlers.py b/code/handlers.py index 1dd8b3e..5ee8d18 100644 --- a/code/handlers.py +++ b/code/handlers.py @@ -1,13 +1,16 @@ +import asyncio +import logging + from aiogram import types, F, Router from aiogram.types import Message, CallbackQuery, ContentType from aiogram.filters import Command from emoji import emojize -from init import * -from config import * +from init import bot, du, db +from config import checkUrl, loginUrl, serviceToken from vk_scripts import VkParser -from scripts import login, pars_post +from scripts import login, pars_post, send_post router = Router() @@ -18,7 +21,7 @@ async def start(msg: Message) -> None: buttons = [[types.InlineKeyboardButton(text="КОМАНДЫ", callback_data="com"), types.InlineKeyboardButton(text="АВТОР", callback_data="auth")]] - keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons, row_width=2) + keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons) await msg.answer(text=f'Portal in VK - это бот для перепоста постов со страниц в социальной сети ВКонтакте. ' f'Для начала работы вам нужно всего лишь вызвать команду /add и добавить к ней ' @@ -39,7 +42,9 @@ async def function(call: CallbackQuery) -> None: '/list - список страниц от, которых вы получаете уведомления\n' '/notif - отписка или подписка от всех уведомлений\n' '/last_post - получение последнего поста по короткому имени страницы\n' - '/add и /del - добавление и удаление странички из списка подписок\n') + '/add и /del - добавление и удаление странички из списка подписок\n' + '/update - ответом на пост: прислать его заново с актуальными данными\n' + '/logout - удалить сохранённый токен VK\n') @router.callback_query(F.data == "auth") @@ -51,7 +56,7 @@ async def author(call: CallbackQuery) -> None: 'возможность ставить через Telegram лайки на посты в самой социальной сети!' '\n\nЯ же пишу подобные небольшие проекты, о которых вы можете узнать ' - 'больше на моём GitHub.') + 'больше на моём GitHub.') # КОМАНДЫ @@ -60,8 +65,10 @@ async def notification(msg: Message) -> None: user_id = login(msg.chat.id, du, db) status = db.get_status(user_id) - groups = list(map(int, db.get_user_groups(user_id).split(";"))) - for group in groups: db.update_countGroup(group, -1 if status else 1) + # у пользователя может ещё не быть ни одной подписки + groups = db.get_user_groups(user_id) + for group in map(int, groups.split(";") if groups else []): + db.update_countGroup(group, -1 if status else 1) if status: await msg.answer("Получение постов из ВК выключено!") else: await msg.answer("Получение постов из ВК включено!") @@ -81,7 +88,7 @@ async def add_del(msg: Message) -> None: if token: vk = VkParser(token) else: vk = VkParser(serviceToken) - group_id, typ, last_post = vk.login(domain) + group_id, typ, last_post = await asyncio.to_thread(vk.login, domain) if not du.group_exists(group_id): du.add_group(group_id) @@ -118,7 +125,7 @@ async def add_del(msg: Message) -> None: if db.get_status(user_id): db.update_countGroup(group_id, -1) await msg.answer("Группа успешно удалена из списка уведомлений!") - except Exception as err: + except Exception: logging.info("Нет доступа к страницы:", exc_info=True) await msg.answer("Произошла ошибка! Проверьте правильность написания короткого адреса " "страницы, имеете ли вы доступ к этой странице и есть ли на ней хотя бы " @@ -142,7 +149,7 @@ async def lst(msg: Message) -> None: group_ids = [du.get_vk_id(id) for id in user_groups] vk = VkParser(serviceToken) - peoples, groups = vk.info(group_ids) + peoples, groups = await asyncio.to_thread(vk.info, group_ids) text = "* Список страниц, от которых вы получаете уведомления!\n" @@ -186,10 +193,10 @@ async def like(msg: Message) -> None: [group_id, last_post] = list(map(int, url.split('wall')[-1].split('_'))) vk = VkParser(token) - vk.like(group_id, last_post) + await asyncio.to_thread(vk.like, group_id, last_post) await msg.answer('Вы поставили лайк 😉') - except Exception as err: + except Exception: logging.error("Не удалось поставить лайк:", exc_info=True) await msg.answer('По неизвестной причине не удалось поставить лайк!') else: @@ -226,27 +233,16 @@ async def update(msg: Message) -> None: if token: vk = VkParser(token) else: vk = VkParser(serviceToken) - output = vk.last_post(post=f"{group_id}_{last_post}") - text, audio, media = pars_post(types, output) + output = await asyncio.to_thread(vk.last_post, post=f"{group_id}_{last_post}") + text, audio, media = pars_post(output) - delete_r = await bot.delete_message(chat_id=user_id, message_id=msg.reply_to_message.message_id) - delete = await bot.delete_message(chat_id=user_id, message_id=msg.message_id) + await bot.delete_message(chat_id=user_id, message_id=msg.reply_to_message.message_id) + await bot.delete_message(chat_id=user_id, message_id=msg.message_id) - if audio and media: - post_message = await bot.send_media_group(chat_id=user_id, media=media) - await bot.send_media_group(chat_id=user_id, media=audio, - reply_to_message_id=post_message[0].message_id) - elif audio and media == []: - post_message = await bot.send_message(chat_id=user_id, text=text) - await bot.send_media_group(chat_id=user_id, media=audio, - reply_to_message_id=post_message[0].message_id) - elif audio == [] and media: - await bot.send_media_group(chat_id=user_id, media=media) - else: - await bot.send_message(chat_id=user_id, text=text, disable_web_page_preview=True) + await send_post(bot, user_id, text, audio, media) - except Exception as err: - logging.danger("Ошибка обновления поста:", exc_info=True) + except Exception: + logging.error("Ошибка обновления поста:", exc_info=True) await msg.answer(text='По неизвестной причине не удалось обновить пост!') else: await msg.answer(text='Вы ответили не на тот пост!') else: await msg.answer(text='Вы не отметили пост который хотите обновить!') @@ -265,26 +261,29 @@ async def last_post(msg: Message) -> None: if token: vk = VkParser(token) else: vk = VkParser(serviceToken) - output = vk.last_post(domain=domain); vk.login(domain) - text, audio, media = pars_post(types, output) + output = await asyncio.to_thread(vk.last_post, domain=domain) + text, audio, media = pars_post(output) - if audio and media: - post_message = await bot.send_media_group(chat_id=user_id, media=media) - await bot.send_media_group(chat_id=user_id, media=audio, reply_to_message_id=post_message[0].message_id) - elif audio and media == []: - post_message = await bot.send_message(chat_id=user_id, text=text) - await bot.send_media_group(chat_id=user_id, media=audio, reply_to_message_id=post_message[0].message_id) - elif audio == [] and media: - await bot.send_media_group(chat_id=user_id, media=media) - else: - await bot.send_message(chat_id=user_id, text=text, disable_web_page_preview=True) + await send_post(bot, user_id, text, audio, media) - except Exception as err: + except Exception: logging.info("Некорректное/недоступное короткое имя:", exc_info=True) await msg.answer("Короткое имя страницы неверное или у вас нет доступа к этой странице!") else: await msg.answer("Команда введена некорректно! Корректный ввод: /last_post example") +@router.message(Command("logout")) +async def logout(msg: Message) -> None: + user_id = login(msg.chat.id, du, db) + + if db.get_token(user_id): + db.update_token(user_id, None) + await msg.answer("Токен VK удалён, бот снова работает на сервисном ключе. Полностью отозвать доступ " + "можно в настройках VK: Приложения и сайты.") + else: + await msg.answer("Сохранённого токена нет.") + + # ПОЛУЧЕНИЕ ТОКЕНА ПОЛЬЗОВАТЕЛЯ @router.message(F.content_type == ContentType.TEXT) async def url(msg: Message) -> None: @@ -293,11 +292,14 @@ async def url(msg: Message) -> None: if checkUrl in msg.text: try: token = msg.text.split("&")[0].split("=")[-1] - info = VkParser(token).check() + await asyncio.to_thread(VkParser(token).check) db.update_token(user_id, token) - await msg.answer("Токен успешно сохранён!") - except Exception as err: + # ссылка с токеном не должна оставаться в переписке + try: await msg.delete() + except Exception: pass + await msg.answer("Токен успешно сохранён! Удалить его можно командой /logout") + except Exception: logging.info("Некорректная ссылка:", exc_info=True) await msg.answer("Ваша ссылка не корректна, попробуйте повторить копирование!") else: await msg.answer("Ваше сообщение не корректно, необходима ссылка, что отобразилась в строке " diff --git a/code/init.py b/code/init.py index 7f5bbd0..856283f 100644 --- a/code/init.py +++ b/code/init.py @@ -1,22 +1,21 @@ +import logging +import os + from aiogram import Bot, Dispatcher from aiogram.enums.parse_mode import ParseMode from aiogram.fsm.storage.memory import MemoryStorage from aiogram.client.bot import DefaultBotProperties - from config import botToken +from sql import Users, Base bot = Bot(token=botToken, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) dp = Dispatcher(storage=MemoryStorage()) - -from sql import Users, Base - +os.makedirs("../db", exist_ok=True) du = Users('../db/users.db') db = Base('../db/base.db') - -import logging, functools - -logging.basicConfig(level=logging.INFO, filename='../info.log',filemode="w", +# лог в файл, если задан LOG_FILE (так бот работал на сервере), иначе — в консоль +logging.basicConfig(level=logging.INFO, filename=os.getenv("LOG_FILE") or None, format="%(asctime)s %(levelname)s %(message)s") diff --git a/code/scripts.py b/code/scripts.py index 00fa9a1..ddcabf5 100644 --- a/code/scripts.py +++ b/code/scripts.py @@ -1,5 +1,12 @@ +from html import escape + +from aiogram import types + import config +CAPTION_LIMIT = 1024 # подпись к фото в Telegram +TEXT_LIMIT = 4096 # обычное сообщение + def login(user_id, du, db): user_id = int(user_id) @@ -11,7 +18,9 @@ def login(user_id, du, db): return user_id -def pars_post(types, output): +def pars_post(output): + """Пост из vk_scripts.get_output → текст в HTML, аудио и медиа для Telegram. + Всё, что пришло из ВК, экранируется: «<» или «&» в посте иначе ломают отправку""" media, video, url, audio, poll, link = [], [], [], [], [], [] for group in output[0]: @@ -20,55 +29,98 @@ def pars_post(types, output): elif group[0] == "video": video.append(group[1]) elif group[0] == "doc" and group[1][2] != 4: url.append([group[1][0], group[1][1], group[1][3]]) elif group[0] == "audio": audio.append(types.InputMediaAudio( - media=group[1][1], caption=f"Название: {group[1][0]} / Автор: {group[1][2]}")) + media=group[1][1], caption=f"Название: {escape(group[1][0])} / Автор: {escape(group[1][2])}")) if output[1] != "": - post = [elem_2 for elem_1 in output[1].split("[") for elem_2 in elem_1.split("]")] + # упоминания ВК вида [id1|Имя] превращаются в ссылки + post = [elem_2 for elem_1 in escape(output[1], quote=False).split("[") for elem_2 in elem_1.split("]")] for i, elem in enumerate(post): if "|" in elem and i != 0: - elem = elem.split("|") - post[i] = f"{elem[1]}" + elem = elem.split("|", 1) + post[i] = f"{elem[1]}" text = f"{output[2]}\n\n{''.join(post)}\n" else: text = f"{output[2]}\n" if poll: - text += f"\n> Проводиться {config.anonymous[poll[3]]} {config.unvote[poll[2]]}! Вопрос: {poll[0]} [{poll[1]}]\n" - text += ''.join([f'{i + 1}. Ответ: {ans[0]} [{ans[1]}]\n' for i, ans in enumerate(poll[4])]) + "\n" + text += (f"\n> Проводится {config.anonymous[poll[3]]} {config.unvote[poll[2]]}! " + f"Вопрос: {escape(poll[0])} [{poll[1]}]\n") + text += ''.join([f'{i + 1}. Ответ: {escape(ans[0])} [{ans[1]}]\n' + for i, ans in enumerate(poll[4])]) + "\n" if video: for frame in video: - text += f"\nВидео « {frame[0]} »\n" + text += f"\nВидео « {escape(frame[0])} »\n" k, dura = 0, frame[1] - while k < 1: - if dura > 60: dura = dura // 60; k += 1 - else: break + if dura > 60: dura, k = dura // 60, 1 - text += f"Длительность: {dura} {config.dura[k]}; Лайков: {frame[2]};\n" + text += f"Длительность: {dura} {config.dura[k]}; Просмотров: {frame[2]};\n" if url: for data in url: k, dt = 0, data[2] - while True: - if dt > 1024: dt = dt // 1024; k += 1 - else: break - text += f"\nДокумент - [ {data[0]} ({dt} {config.data[k]}) ]" + while dt > 1024 and k < len(config.data) - 1: + dt, k = dt // 1024, k + 1 + text += (f"\nДокумент - [ {escape(data[0])} " + f"({dt} {config.data[k]}) ]") text += "\n" - if link: text += ''.join([f"\nСсылка - [ [{s[0]}]({s[1]}) ]" for s in link]) + "\n" + if link: + text += ''.join([f"\nСсылка - [ {escape(s[0])} ]" + for s in link]) + "\n" if audio: text += '\n« Также присутствуют аудио файлы »' - for i, group in enumerate(output[0]): + for group in output[0]: if group[0] == "photo": - if i: media.append(types.InputMediaPhoto(media=group[1])) - else: media.append(types.InputMediaPhoto(media=group[1], caption=text)) - elif group[0] == "video": - if i: media.append(types.InputMediaPhoto(media=group[1][3])) - else: media.append(types.InputMediaPhoto(media=group[1][3], caption=text)) - elif group[0] == "doc": - if group[1][2] == 4: - if i: media.append(types.InputMediaPhoto(media=group[1][1])) - else: media.append(types.InputMediaPhoto(media=group[1][1], caption=text)) + media.append(types.InputMediaPhoto(media=group[1])) + elif group[0] == "video" and group[1][3]: # у видео может не быть обложки + media.append(types.InputMediaPhoto(media=group[1][3])) + elif group[0] == "doc" and group[1][2] == 4: + media.append(types.InputMediaPhoto(media=group[1][1])) - return text, audio, media + return text, audio[:10], media[:10] # в одном альбоме Telegram не больше 10 элементов + + +def split_text(text, limit=TEXT_LIMIT): + """Делит длинный текст по строкам, чтобы не разрывать HTML-теги посередине""" + parts, current = [], "" + for line in text.split("\n"): + while len(line) > limit: # одна строка длиннее лимита — режем как есть + if current: parts.append(current); current = "" + parts.append(line[:limit]); line = line[limit:] + if len(current) + len(line) + 1 > limit: + parts.append(current); current = "" + current += line + "\n" + if current.strip(): parts.append(current) + return parts + + +async def send_post(bot, chat_id, text, audio, media): + """Отправляет пост с учётом ограничений Telegram: альбом — от 2 до 10 элементов, подпись — + до 1024 символов, сообщение — до 4096""" + first = None + + if media: + caption = text if len(text) <= CAPTION_LIMIT else None + if len(media) == 1: + first = await bot.send_photo(chat_id=chat_id, photo=media[0].media, caption=caption) + else: + media[0].caption = caption + first = (await bot.send_media_group(chat_id=chat_id, media=media))[0] + if caption is None: + for part in split_text(text): + await bot.send_message(chat_id=chat_id, text=part, disable_web_page_preview=True, + reply_to_message_id=first.message_id) + else: + for part in split_text(text): + sent = await bot.send_message(chat_id=chat_id, text=part, disable_web_page_preview=True) + first = first or sent + + if audio: + reply_to = first.message_id if first else None + if len(audio) == 1: + await bot.send_audio(chat_id=chat_id, audio=audio[0].media, caption=audio[0].caption, + reply_to_message_id=reply_to) + else: + await bot.send_media_group(chat_id=chat_id, media=audio, reply_to_message_id=reply_to) diff --git a/code/sql.py b/code/sql.py index a1b249b..a5287eb 100644 --- a/code/sql.py +++ b/code/sql.py @@ -7,6 +7,11 @@ class Users: self.connection = sqlite3.connect(database) self.cursor = self.connection.cursor() + # на новой установке (например, в Docker с пустым томом) таблиц ещё нет + with self.connection: + self.cursor.execute("CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL)") + self.cursor.execute("CREATE TABLE IF NOT EXISTS \"group\" (id INTEGER PRIMARY KEY, group_id INTEGER NOT NULL)") + # КОМАНДЫ USER def user_exists(self, user_id): """Проверяем, есть ли уже пользователь в базе""" @@ -63,6 +68,12 @@ class Base: self.connection = sqlite3.connect(database) self.cursor = self.connection.cursor() + with self.connection: + self.cursor.execute("CREATE TABLE IF NOT EXISTS user (user_id INTEGER NOT NULL, status BOOLEAN NOT NULL " + "DEFAULT (True), count INTEGER NOT NULL DEFAULT (0), groups TEXT, token TEXT)") + self.cursor.execute("CREATE TABLE IF NOT EXISTS \"group\" (group_id INTEGER NOT NULL, type BOOLEAN NOT NULL, " + "count INTEGER NOT NULL DEFAULT (0), last_post INTEGER NOT NULL)") + # КОМАНДЫ USER def infoUser_exists(self, user_id): """Проверяем, есть ли данные уже в базе""" @@ -166,4 +177,3 @@ class Base: def close(self): """Закрываем соединение с БД""" self.connection.close() - diff --git a/code/vk_scripts.py b/code/vk_scripts.py index 0d5b56c..bf05160 100644 --- a/code/vk_scripts.py +++ b/code/vk_scripts.py @@ -1,3 +1,5 @@ +from html import escape + import vk_api @@ -70,7 +72,7 @@ def get_output(post, name, domain, repost=None): [post_id, post] = post owner_id, output = post['owner_id'], [] - attachments = post['attachments'] + attachments = post.get('attachments', []) types = [[typ['type'], typ] for typ in attachments] for typ in types: @@ -78,12 +80,12 @@ def get_output(post, name, domain, repost=None): output.append([typ[0], typ[1]['photo']['sizes'][-1]['url']]) elif typ[0] == 'video': video = typ[1]['video'] - if 'photo_1280' in video: - frame = video['photo_1280'] - else: - frame = video['photo_800'] + # обложка: самая крупная из доступных + frame = video.get('photo_1280') or video.get('photo_800') or video.get('photo_320') + if frame is None and video.get('image'): + frame = video['image'][-1]['url'] - output.append([typ[0], [video['title'], video['duration'], video['views'], frame]]) + output.append([typ[0], [video['title'], video.get('duration', 0), video.get('views', 0), frame]]) elif typ[0] == "doc": output.append([typ[0], [typ[1]['doc']['title'], typ[1]['doc']['url'], typ[1]['doc']['type'], typ[1]['doc']['size']]]) @@ -99,11 +101,11 @@ def get_output(post, name, domain, repost=None): if repost: [author_id, author_owner_id, author_name, author_domain, author_comment] = repost - comment = f"Автор репоста - {author_name}" - if author_comment != "": comment += f"\n
{author_comment}
\n" - comment += f"\nАвтор поста - {name}" + comment = f"Автор репоста - {escape(author_name)}" + if author_comment != "": comment += f"\n
{escape(author_comment)}
\n" + comment += f"\nАвтор поста - {escape(name)}" return [output, f"{post['text']}", comment, author_id] else: return [output, f"{post['text']}", - f"Автор поста - {name}", post_id] \ No newline at end of file + f"Автор поста - {escape(name)}", post_id] \ No newline at end of file diff --git a/db/.gitkeep b/db/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/db/base.db b/db/base.db deleted file mode 100644 index 69d4c44..0000000 Binary files a/db/base.db and /dev/null differ diff --git a/db/users.db b/db/users.db deleted file mode 100644 index 71ae8f0..0000000 Binary files a/db/users.db and /dev/null differ