def plugin_init(self): hues.info("Загрузка плагинов...") # Подгружаем плагины self.plugin_system = PluginSystem(self.vk, folder=abspath('plugins')) self.plugin_system.register_commands() # Чтобы плагины могли получить список всех плагинов (костыль) self.vk.get_plugins = self.plugin_system.get_plugins # Для парсинга команд с пробелом используется # обратная сортировка, для того, чтобы самые # длинные команды были первыми в списке command_names = list(self.plugin_system.commands.keys()) command_names.sort(key=len, reverse=True) from command import CommandSystem self.cmd_system = CommandSystem(command_names, self.plugin_system) self.scheduled_funcs = self.plugin_system.scheduled_events hues.success("Загрузка плагинов завершена")
class Bot(object): """Главный класс бота""" __slots__ = [ "PREFIXES", "LOG_MESSAGES", "LOG_COMMANDS", "WHITELISTED", "FLOOD_INTERVAL", "USERS", "PROXIES", "SCOPE", "APP_ID", "DATABASE_CHARSET", "ONLY_CHAT", "USE_CHATTER", "DO_CHAT", "IGNORE_PREFIX", "BLACKLIST_MESSAGE", "WHITELIST_MESSAGE", "messages_date", "plugin_system", "cmd_system", "last_ts", "scheduled_funcs", "longpoll_server", "longpoll_key", "chatter", "longpoll_values", "event_loop", "last_message_id", "vk" ] def __init__(self): self.init_settings() self.vk_init() self.plugin_init() if self.DO_CHAT: if self.USE_CHATTER: self.chatter = ChatterBot() else: from chat.chat import chatter self.chatter = chatter def init_settings(self): """Функция инициализации файла настроек и его создания""" # Если у нас есть только settings.py.sample if isfile('settings.py.sample') and not isfile('settings.py'): try: shutil.copy('settings.py.sample', 'settings.py') except Exception: fatal('Я не могу копировать файлы в текущей папке, ' 'проверьте ваши права на неё!') hues.info('Был создан файл settings.py, ' 'не забудьте добавить данные для авторизации!') exit() # Если у нас уже есть settings.py elif isfile('settings.py'): import settings try: self.WHITELISTED = False self.WHITELIST_MESSAGE = settings.WHITELIST_MESSAGE self.BLACKLIST_MESSAGE = settings.BLACKLIST_MESSAGE self.PREFIXES = settings.PREFIXES self.LOG_MESSAGES = settings.LOG_MESSAGES self.LOG_COMMANDS = settings.LOG_COMMANDS self.APP_ID = settings.APP_ID self.SCOPE = settings.SCOPE self.FLOOD_INTERVAL = settings.FLOOD_INTERVAL self.USERS = settings.USERS self.PROXIES = settings.PROXIES self.DO_CHAT = settings.DO_CHAT self.ONLY_CHAT = settings.ONLY_CHAT self.USE_CHATTER = settings.USE_CHATTER self.IGNORE_PREFIX = settings.IGNORE_PREFIX settings.ADMINS settings.BLACKLIST settings.WHITELIST if not self.USERS: fatal( "Проверьте, что у есть LOGIN и PASSWORD, или же TOKEN в файле settings.py!" "Без них бот работать НЕ СМОЖЕТ.") except (ValueError, AttributeError, NameError): fatal( 'Проверьте содержимое файла settings.py, возможно вы удалили что-то нужное!' ) # Если не нашли ни settings.py, ни settings.py.sample else: fatal( "settings.py и settings.py.sample не найдены, возможно вы их удалили?" ) def vk_init(self): hues.warn("Авторизация в ВКонтакте...") # Словарь вида ID -> время self.messages_date = {} self.vk = VkPlus(users_data=self.USERS, proxies=self.PROXIES, bot=self, scope=self.SCOPE, app_id=self.APP_ID) if self.vk: hues.success("Успешная авторизация") def plugin_init(self): hues.info("Загрузка плагинов...") # Подгружаем плагины self.plugin_system = PluginSystem(self.vk, folder=abspath('plugins')) self.plugin_system.register_commands() # Чтобы плагины могли получить список всех плагинов (костыль) self.vk.get_plugins = self.plugin_system.get_plugins # Для парсинга команд с пробелом используется # обратная сортировка, для того, чтобы самые # длинные команды были первыми в списке command_names = list(self.plugin_system.commands.keys()) command_names.sort(key=len, reverse=True) from command import CommandSystem self.cmd_system = CommandSystem(command_names, self.plugin_system) self.scheduled_funcs = self.plugin_system.scheduled_events hues.success("Загрузка плагинов завершена") async def init_long_polling(self, update=0): """Функция для инициализации Long Polling""" retries = 5 for x in range(retries): result = await self.vk.method('messages.getLongPollServer', {'use_ssl': 1}) if result: break if not result: fatal("Не удалось получить значения Long Poll сервера!") try: self.last_ts = self.longpoll_values['ts'] self.longpoll_key = self.longpoll_values['key'] except AttributeError: pass except ValueError: pass if update == 0: # Если нам нужно инициализировать с нуля, меняем сервер self.longpoll_server = "https://" + result['server'] if update in (0, 3): # Если нам нужно инициализировать с нуля, или ошибка 3 self.longpoll_key = result['key'] self.last_ts = result['ts'] # Последний timestamp elif update == 2: # Если ошибка 2 - то нужен новый ключ self.longpoll_key = result['key'] self.longpoll_values = { 'act': 'a_check', 'key': self.longpoll_key, 'ts': self.last_ts, 'wait': 25, # Тайм-аут запроса 'mode': 10, 'version': 1 } async def check_event(self, new_event): if not new_event: return # На всякий случай event_id = new_event.pop(0) if event_id != 4: return # Если событие - не новое сообщение msg_id, flags, peer_id, ts, subject, text, attaches = new_event # Получаем параметры сообщения # https://vk.com/dev/using_longpoll_2 flags = parse_msg_flags(flags) # Если сообщение - исходящее if flags['outbox']: return # Если ID находится в чёрном списке if await get_or_none(Role, user_id=peer_id, role="blacklisted"): if self.BLACKLIST_MESSAGE: await self.vk.method("messages.send", { "user_id": peer_id, "message": self.BLACKLIST_MESSAGE }) return if self.WHITELISTED and not await get_or_none( Role, user_id=peer_id, role="whitelisted"): if self.WHITELIST_MESSAGE: await self.vk.method("messages.send", { "user_id": peer_id, "message": self.WHITELIST_MESSAGE }) return # Тип сообщения - конференция или ЛС? try: # Пробуем получить ID пользователя, который отправил # сообщение в беседе user_id = attaches['from'] peer_id -= 2000000000 conf = True except KeyError: # Если ключа from нет - это ЛС user_id = peer_id conf = False user_id = int(user_id) cleaned_body = text.replace('<br>', '\n') data = MessageEventData(conf, peer_id, user_id, cleaned_body, ts, msg_id, attaches) user = await get_or_none(User, uid=user_id) if user: if ts - user.message_date <= self.FLOOD_INTERVAL: user.message_date = ts await db.update(user) return user.message_date = ts await db.update(user) else: user = await db.create(User, uid=user_id) await self.check_if_command(data, user) async def do_chat(self, msg, user): if not self.DO_CHAT: return if user.chat_data: chat_data = json.loads(user.chat_data) chat_data.append(normalize(msg.text)) chat_data = chat_data[::-1] else: chat_data = [normalize(msg.text)] answer = self.chatter.parse_message(chat_data) if answer is not None: chat_data = chat_data[::-1] chat_data.append(normalize(answer)) user.chat_data = json.dumps(chat_data) await db.update(user) await msg.answer(answer) async def check_if_command(self, data: MessageEventData, user) -> None: if self.LOG_MESSAGES: who = f"{'конференции' if data.conf else 'ЛС'} {data.peer_id}" hues.info(f"Сообщение из {who} > {data.body}") msg_obj = Message(self.vk, data, user) cmd = Command(msg_obj) if not cmd.has_prefix: if self.DO_CHAT and self.IGNORE_PREFIX: await self.do_chat(msg_obj, user) return if self.ONLY_CHAT and self.DO_CHAT: await self.do_chat(msg_obj, user) else: result = await self.cmd_system.process_command(msg_obj, cmd) if result is False: await self.do_chat(msg_obj, user) async def run(self, event_loop): """Главная функция бота - тут происходит ожидание новых событий (сообщений)""" self.event_loop = event_loop # Нужен для шедулинга функций await self.init_long_polling() session = aiohttp.ClientSession(loop=event_loop) while True: try: resp = await session.get(self.longpoll_server, params=self.longpoll_values) except (aiohttp.ClientOSError, asyncio.TimeoutError): # У меня были такие ошибки на Manjaro 16.10.3 Fringilla # ВК почему-то присылал сервер, к которому нельзя подключиться hues.warn( 'Сервер Long Polling не отвечает, подключаюсь к другому...' ) await self.init_long_polling() continue """ Тут не используется resp.json() по простой причине: aiohttp будет писать warning'и из-за плохого mimetype Неизвестно, почему он у ВК такой - text/javascript; charset=utf-8 """ events_text = await resp.text() try: events = json.loads(events_text) except ValueError: # В JSON ошибка, или это вовсе не JSON continue # Проверяем на код ошибки failed = events.get('failed') if failed: err_num = int(failed) # Код 1 - Нам нужно обновить timestamp if err_num == 1: self.longpoll_values['ts'] = events['ts'] # Коды 2 и 3 - нужно запросить данные нового # Long Polling сервера elif err_num in (2, 3): await self.init_long_polling(err_num) continue # Обновляем время, чтобы не приходили старые события self.longpoll_values['ts'] = events['ts'] for event in events['updates']: schedule_coroutine(self.check_event(event))
class Bot(object): """Главный класс бота""" __slots__ = [ "BLACKLIST", "PREFIXES", "LOG_MESSAGES", "LOG_COMMANDS", "NEED_CONVERT", "APP_ID", "SCOPE", "FLOOD_INTERVAL", "IS_TOKEN", "TOKEN", "VK_LOGIN", "VK_PASSWORD", "messages_date", "plugin_system", "cmd_system", "scheduled_funcs", "longpoll_server", "longpoll_key", "event_loop", "last_message_id", "vk", "longpoll_values", "last_ts" ] def __init__(self): self.last_message_id = 0 self.init_settings() self.vk_init() self.plugin_init() def init_settings(self): """Функция инициализации файла настроек и его создания""" # Если у нас есть только settings.py.sample if isfile('settings.py.sample') and not isfile('settings.py'): try: shutil.copy('settings.py.sample', 'settings.py') except Exception: fatal('Я не могу копировать файлы в текущей папке, ' 'проверьте ваши права на неё!') fatal('Был создан файл settings.py, ' 'не забудьте добавить данные для авторизации!') # Если у нас уже есть settings.py elif isfile('settings.py'): import settings try: self.BLACKLIST = settings.BLACKLIST self.PREFIXES = settings.PREFIXES self.LOG_MESSAGES = settings.LOG_MESSAGES self.LOG_COMMANDS = settings.LOG_COMMANDS self.NEED_CONVERT = settings.NEED_CONVERT self.APP_ID = settings.APP_ID self.SCOPE = settings.SCOPE self.FLOOD_INTERVAL = settings.FLOOD_INTERVAL # По умолчанию - False self.IS_TOKEN = False # Если в настройках есть токен if settings.TOKEN: self.IS_TOKEN = True self.TOKEN = settings.TOKEN # Или логин и пароль if settings.LOGIN and settings.PASSWORD: self.VK_LOGIN = settings.LOGIN self.VK_PASSWORD = settings.PASSWORD if not self.TOKEN and not (self.VK_LOGIN and self.VK_PASSWORD): fatal( "Проверьте, что у есть LOGIN и PASSWORD, или же TOKEN в файле settings.py!" "Без них бот работать НЕ СМОЖЕТ.") except (ValueError, AttributeError, NameError): fatal( 'Проверьте содержимое файла settings.py, возможно вы удалили что-то нужное!' ) # Если не нашли ни settings.py, ни settings.py.sample else: fatal( "settings.py и settings.py.sample не найдены, возможно вы их удалили?" ) def vk_init(self): hues.warn("Авторизация в ВКонтакте...") # Словарь вида ID -> время self.messages_date = {} self.vk = VkPlus(token=self.TOKEN, login=self.VK_LOGIN, password=self.VK_PASSWORD, scope=self.SCOPE, app_id=self.APP_ID) hues.success("Успешная авторизация") def plugin_init(self): hues.warn("Загрузка плагинов...") # Подгружаем плагины self.plugin_system = PluginSystem(folder=abspath('plugins')) self.plugin_system.register_commands() # Чтобы плагины могли получить список всех плагинов (костыль) self.vk.get_plugins = self.plugin_system.get_plugins # Для парсинга команд с пробелом используется # обратная сортировка, для того, чтобы самые # длинные команды были первыми в списке command_names = list(self.plugin_system.commands.keys()) command_names.sort(key=len, reverse=True) from command import CommandSystem self.cmd_system = CommandSystem(command_names, self.plugin_system, self.NEED_CONVERT) self.scheduled_funcs = self.plugin_system.scheduled_events hues.success("Загрузка плагинов завершена") async def init_long_polling(self, update=0): """Функция для инициализации Long Polling""" retries = 5 for x in range(retries): result = await self.vk.method('messages.getLongPollServer', {'use_ssl': 1}) if result: break if not result: fatal("Не удалось получить значения Long Poll сервера!") try: self.last_ts = self.longpoll_values['ts'] self.longpoll_key = self.longpoll_values['key'] except AttributeError: pass except ValueError: pass if update == 0: # Если нам нужно инициализировать с нуля, меняем сервер self.longpoll_server = "https://" + result['server'] if update in (0, 3): # Если нам нужно инициализировать с нуля, или ошибка 3 self.longpoll_key = result['key'] self.last_ts = result['ts'] # Последний timestamp elif update == 2: # Если ошибка 2 - то нужен новый ключ self.longpoll_key = result['key'] self.longpoll_values = { 'act': 'a_check', 'key': self.longpoll_key, 'ts': self.last_ts, 'wait': 25, # Тайм-аут запроса 'mode': 2, 'version': 1 } async def check_event(self, new_event): if not new_event: return # На всякий случай event_id = new_event.pop(0) if event_id != 4: return # Если событие - не новое сообщение msg_id, flags, peer_id, ts, subject, text, attaches = new_event # Если ID находится в чёрном списке if peer_id in self.BLACKLIST: return # Получаем параметры сообщения # https://vk.com/dev/using_longpoll_2 flags = parse_msg_flags(flags) # Если сообщение - исходящее if flags['outbox']: return # Тип сообщения - конференция или ЛС? try: # Пробуем получить ID пользователя, который отправил # сообщение в беседе user_id = attaches['from'] peer_id -= 2000000000 conf = True except KeyError: # Если ключа from нет - это ЛС user_id = peer_id conf = False user_id = int(user_id) cleaned_body = text.replace('<br>', '\n') data = MessageEventData(conf, peer_id, user_id, cleaned_body, ts, msg_id, attaches) try: # Проверяем на интервал между командами для этого ID пользователя if ts - self.messages_date[user_id] <= self.FLOOD_INTERVAL: self.messages_date[user_id] = ts return else: self.messages_date[user_id] = ts except KeyError: self.messages_date[user_id] = ts await self.check_if_command(data, msg_id) def schedule_coroutine(self, target): """Schedules target coroutine in the given event loop If not given, *loop* defaults to the current thread's event loop Returns the scheduled task. """ if asyncio.iscoroutine(target): return asyncio.ensure_future(target, loop=self.event_loop) else: raise TypeError("target must be a coroutine, " "not {!r}".format(type(target))) async def run(self, event_loop): """Главная функция бота - тут происходит ожидание новых событий (сообщений)""" self.event_loop = event_loop # Нужен для шедулинга функций await self.init_long_polling() session = aiohttp.ClientSession(loop=event_loop) while True: try: resp = await session.get(self.longpoll_server, params=self.longpoll_values) except aiohttp.errors.ClientOSError: # У меня были такие ошибки на Manjaro 16.10.3 Fringilla # ВК почему-то присылал сервер, к которому нельзя подключиться hues.warn( 'Сервер Long Polling не отвечает, подключаюсь к другому...' ) await self.init_long_polling() continue """ Тут не используется resp.json() по простой причине: aiohttp будет писать warning'и из-за плохого mimetype Неизвестно, почему он у ВК такой - text/javascript; charset=utf-8 """ events_text = await resp.text() try: events = json.loads(events_text) except ValueError: # В JSON ошибка, или это вовсе не JSON continue # Проверяем на код ошибки failed = events.get('failed') if failed: err_num = int(failed) # Код 1 - Нам нужно обновить timestamp if err_num == 1: self.longpoll_values['ts'] = events['ts'] # Коды 2 и 3 - нужно запросить данные нового # Long Polling сервера elif err_num in (2, 3): await self.init_long_polling(err_num) continue # Обновляем время, чтобы не приходили старые события self.longpoll_values['ts'] = events['ts'] for event in events['updates']: self.schedule_coroutine(self.check_event(event)) async def check_if_command(self, data: MessageEventData, msg_id: int) -> None: msg_obj = Message(self.vk, data) result = await self.cmd_system.process_command(msg_obj) if self.LOG_MESSAGES: who = f"{'конференции' if data.conf else 'ЛС'} {data.peer_id}" hues.info(f"Сообщение из {who} > {data.body}")
class Bot(object): """Bot object""" __slots__ = ["WHITELISTED", "messages_date", "plugin_system", "cmd_system", "last_ts", "scheduled_funcs", "longpoll_server", "longpoll_key", "chatter", "longpoll_values", "event_loop", "last_message_id", "vk"] def __init__(self): self.WHITELISTED = False self.longpoll_values = {} self.longpoll_server = "" self.longpoll_key = "" self.last_ts = 0 self.vk_init() if sys.argv and "-nu" in sys.argv: self.plugin_download(f"https://vkbots.github.io/vbot-plugins") self.plugin_clear() if sys.argv and "-ou" in sys.argv: hues.success("Плагины обновлены") exit() self.plugin_init() if settings.CHAT_ENABLE: if settings.CHAT_CHATTER: self.chatter = ChatterBot() else: from chat.chat import chatter self.chatter = chatter def vk_init(self): hues.warn("Авторизация в ВКонтакте...") self.messages_date = {} # Словарь вида ID -> время self.vk = VkPlus(users_data=settings.USERS, proxies=settings.PROXIES, bot=self, scope=settings.SCOPE, app_id=settings.APP_ID) if self.vk: hues.success("Успешная авторизация") @staticmethod def plugin_clear(): hues.info("Удаление лишних плагинов...") for file in os.listdir("plugins"): if (file not in settings.ENABLED_PLUGINS and "all" not in settings.ENABLED_PLUGINS) or \ file in settings.DISABLED_PLUGINS: shutil.rmtree(f"plugins/{file}") @staticmethod def plugin_download(repository_url): if not os.path.exists("plugins"): os.mkdir("plugins") hues.info("Обновление и загрузка плагинов...") base = repository_url for i in requests.get(f"{base}/dir").text.splitlines(): file_type, file_name = i.split(";") plugin_name = file_name if file_type == "dir": if ("all" in settings.ENABLED_PLUGINS or file_name in settings.ENABLED_PLUGINS) and \ file_name not in settings.DISABLED_PLUGINS: plugin_folder = f"{base}/{file_name}" info = requests.get(f"{plugin_folder}/info.txt").text.strip().splitlines() version, bot_version, name, description, authors = info bot_version = bot_version.split(";") if VERSION not in bot_version: old_version_n = 0 while True: p_plugin_folder = f"{plugin_folder}/old{old_version_n}" response = requests.get(f"{p_plugin_folder}/info.txt") if response.status_code == 404: plugin_folder = "" hues.warn(f"Для плагина {plugin_name} необходим бот " f"версии (одной из): {', '.join(bot_version)}") break version, bot_version, *other = response.text.splitlines() if VERSION not in bot_version: old_version_n += 1 continue plugin_folder = p_plugin_folder break if plugin_folder: if os.path.exists(f"plugins/{plugin_name}/info.txt"): with open(f"plugins/{plugin_name}/info.txt") as f: v, bv, n, d, a = f.read().strip().splitlines() if v != version: hues.warn(f"Обновление плагина {plugin_name} с версии {v} до {version}") shutil.rmtree(f"plugins/{plugin_name}/") else: continue else: hues.warn(f"Загрузка плагина {plugin_name} версии {version}") for j in requests.get(f"{plugin_folder}/dir").text.splitlines(): file_type, file_name = j.split(";") if file_type == "dir" and file_name.startswith("old"): continue if not os.path.exists(f"plugins/{plugin_name}"): os.mkdir(f"plugins/{plugin_name}") with open(f"plugins/{plugin_name}/{file_name}", "wb") as f: f.write(requests.get(f"{plugin_folder}/{file_name}").content) def plugin_init(self): hues.info("Активация плагинов...") # Загружаем плагины self.plugin_system = PluginSystem(self.vk, folder=abspath('plugins')) self.plugin_system.register_commands() self.vk.get_plugins = self.plugin_system.get_plugins # Для парсинга команд с пробелом используется # обратная сортировка, для того, чтобы самые # длинные команды были первыми в списке command_names = list(self.plugin_system.commands.keys()) command_names.sort(key=len, reverse=True) from command import CommandSystem self.cmd_system = CommandSystem(command_names, self.plugin_system) self.scheduled_funcs = self.plugin_system.scheduled_events hues.success("Загрузка плагинов завершена") @staticmethod async def get_long_poll_server(retries: int, vk: VkPlus) -> dict: result = "" for x in range(retries): result = await vk.method('messages.getLongPollServer', {'use_ssl': 1}) if result: break time.sleep(1) return result async def init_long_polling(self, update: int=0): """Функция для инициализации Long Polling""" result = await self.get_long_poll_server(10, self.vk) if not result: time.sleep(30) result = await self.get_long_poll_server(10, self.vk) if not result: fatal("Не удалось получить значения Long Poll сервера!") try: self.last_ts = self.longpoll_values['ts'] self.longpoll_key = self.longpoll_values['key'] except (AttributeError, ValueError, KeyError): pass if update == 0: # Если нам нужно инициализировать с нуля, меняем сервер self.longpoll_server = "https://" + result['server'] if update in (0, 3): # Если нам нужно инициализировать с нуля, или ошибка 3 self.longpoll_key = result['key'] self.last_ts = result['ts'] # Последний timestamp elif update == 2: # Если ошибка 2 - то нужен новый ключ self.longpoll_key = result['key'] self.longpoll_values = { 'act': 'a_check', 'key': self.longpoll_key, 'ts': self.last_ts, 'wait': 25, # Тайм-аут запроса 'mode': 10, 'version': 1 } async def check_event(self, new_event: List): if not new_event: return event_id = new_event.pop(0) if event_id != 4: # Если событие - не новое сообщение return msg_id, flags, peer_id, ts, subject, text, attaches = new_event # Получаем параметры сообщения # https://vk.com/dev/using_longpoll_2 flags = parse_msg_flags(flags) # Если сообщение - исходящее if flags['outbox'] and not settings.READ_OUT: return # Тип сообщения - конференция или ЛС try: # Пробуем получить ID пользователя, который отправил сообщение в беседе user_id = attaches['from'] peer_id -= 2000000000 conf = True except KeyError: # Если ключа from нет - это ЛС user_id = peer_id conf = False user_id = int(user_id) # Если ID находится в чёрном списке if await get_or_none(Role, user_id=user_id, role="blacklisted") or \ await get_or_none(Role, user_id=peer_id, role="blacklisted"): if settings.BLACKLIST_MESSAGE: await self.vk.method("messages.send", {"user_id": peer_id, "message": settings.BLACKLIST_MESSAGE}) return # Если ID моет писать боту или белый список отключён if self.WHITELISTED and not await get_or_none(Role, user_id=peer_id, role="whitelisted"): if settings.WHITELIST_MESSAGE: await self.vk.method("messages.send", {"user_id": peer_id, "message": settings.WHITELIST_MESSAGE}) return cleaned_body = text.replace('<br>', '\n') forwarded = [] if "fwd" in attaches: forwarded = self.parse_forwarded_messages(attaches["fwd"]) del attaches['fwd'] data = MessageEventData(conf, peer_id, user_id, cleaned_body, ts, msg_id, flags['outbox'], attaches, forwarded) user = await get_or_none(User, user_id=user_id) if user: if not flags['outbox']: if ts - user.message_date <= settings.FLOOD_INTERVAL: user.message_date = ts await db.update(user) return user.message_date = ts await db.update(user) else: user = await db.create(User, user_id=user_id) # Обработка команды await self.check_if_command(data, user) async def do_chat(self, msg: Message, user: User): if not settings.CHAT_ENABLE: return if user.chat_data: chat_data = json.loads(user.chat_data) chat_data.append(normalize(msg.text)) chat_data = chat_data[::-1] else: chat_data = [normalize(msg.text)] answer = await self.chatter.parse_message(user, chat_data) if answer is not None: chat_data = chat_data[::-1] chat_data.append(normalize(answer)) user.chat_data = json.dumps(chat_data) await db.update(user) await msg.answer(answer) def parse_forwarded_messages(self, forwarded: str) -> List: result = [] msgs = forwarded.split(",") for i in range(len(msgs)): sep_pos = msgs[i].find(":") if sep_pos == -1: result.append((msgs[i].replace(")", ""), [])) else: result.append((msgs[i][:sep_pos], self.parse_forwarded_messages(msgs[i][sep_pos + 2:-1]))) return result async def check_if_command(self, data: MessageEventData, user: User) -> None: if settings.LOG_MESSAGES: who = f"{'конференции' if data.conf else 'ЛС'} {data.peer_id}" hues.info(f"Сообщение из {who} > {data.body}") msg_obj = await create_message(self.vk, data, user) if not settings.CHAT_ONLY or not settings.CHAT_ENABLE: messages_to_check = [msg_obj] done = False if settings.CHECK_FORWARDED_MESSAGES: for msg in traverse(await msg_obj.full_forwarded): if msg is None: continue messages_to_check.append(msg) for msg in messages_to_check: cmd = Command(msg) result = await self.cmd_system.process_command(msg, cmd) done = True if done else result if result and not settings.CHECK_FORWARDED_MESSAGES_ALL: break if done: return if settings.CHAT_ENABLE and (settings.CHAT_IGNORE_PREFIX or msg_obj.prefix): await self.do_chat(msg_obj, user) async def run(self, event_loop: asyncio.AbstractEventLoop) -> None: """Главная функция бота - тут происходит ожидание новых событий (сообщений)""" self.event_loop = event_loop # Нужен для шедулинга функций await self.init_long_polling() with aiohttp.ClientSession(loop=event_loop) as session: while True: try: resp = await session.get(self.longpoll_server, params=self.longpoll_values) except (aiohttp.ClientOSError, asyncio.TimeoutError): hues.warn('Сервер Long Polling не отвечает, подключаюсь к другому...') await self.init_long_polling() continue events_text = await resp.text() # text(), а не json() из-за плохого mimetype у вк try: events = json.loads(events_text) except ValueError: continue failed = events.get('failed') if failed: err_num = int(failed) if err_num == 1: # Код 1 - Нам нужно обновить timestamp self.longpoll_values['ts'] = events['ts'] elif err_num in (2, 3): # Коды 2 и 3 - нужно запросить данные нового Long Polling сервера await self.init_long_polling(err_num) continue # Обновление времени, чтобы не приходили старые события self.longpoll_values['ts'] = events['ts'] # Обработка сообщений for event in events['updates']: schedule_coroutine(self.check_event(event))