async def _event_processor(self, obj: dict, event_type: str): """ LongPoll Events Processor :param event_type: VK Server Event Type :param obj: VK Server Event Object """ logger.debug( '-> EVENT FROM {} TYPE "{}"', get_attr(obj, ["user_id", "from_id"]), event_type.upper(), ) for rule in self.on.event.rules: if await rule.check(event_type): event = rule.data["data"](**obj) await rule.call(event, *rule.context.args, **rule.context.kwargs) logger.info( 'New event "{}" compiled with decorator <{}> (from: {})'. format( event_type.upper(), rule.call.__name__, get_attr(obj, ["user_id", "from_id"]), )) return True
async def event_processor(self, obj: dict, event_type: str): """ Bot polling event processor #TODO Reduce difference :param event_type: VK Server Event Type :param obj: VK Server Event Object """ logger.debug( '-> EVENT FROM {} TYPE "{}"', get_attr(obj, ["user_id", "from_id"]), event_type.upper(), ) for rule in self.handler.event.rules: if await rule.check(event_type): event = rule.data["data"](**obj) await rule.call(event, *rule.context.args, **rule.context.kwargs) logger.info( 'New event "{}" compiled with decorator <{}> (from: {})'. format( event_type.upper(), rule.call.__name__, get_attr(obj, ["user_id", "from_id"]), )) break
async def _branched_processor(self, message: Message, middleware_args: list): logger.debug( '-> BRANCHED MESSAGE FROM {} TEXT "{}"', message.peer_id, message.text.replace("\n", " "), ) disposal, branch = await self.branch.load(message.peer_id) for n, member in disposal.items(): rules = member[1] for rule in rules: if not await rule(message): break else: task = types.MethodType(member[0], branch) args = [a for rule in rules for a in rule.context.args] kwargs = { k: v for rule in rules for k, v in rule.context.kwargs.items() } await task(message, *middleware_args, *args, **kwargs), break logger.info( 'New BRANCHED "{0}" compiled with branch <{2}> (from: {1})'.format( message.text.replace("\n", " "), message.from_id, '"{}" with {} kwargs'.format( branch.key, branch.context if len(branch.context) < 100 else branch.context[1:99] + "...", ), ))
async def emulate(self, event: dict): """ Emulate event """ logger.debug("Response: {}", event) for update in event.get("updates", []): update_code, update_fields = update[0], update[1:] await self.handle.parent_processor(update, update_code, update_fields)
async def run(self, skip_updates: bool, wait: int = DEFAULT_WAIT): """ Run bot polling forever Can be manually stopped with: bot.stop() """ self.__wait = wait logger.debug("Polling will be started. Is it OK?") if self.__secret is not None: logger.warning("You set up secret and started polling. Removing secret") self.__secret = None if not self.status.dispatched: self.middleware.add_middleware(self.on.pre) await self.on.dispatch(self.get_current_rest) self.status.dispatched = True if not skip_updates: await self.get_updates() await self.get_server() logger.info("Polling successfully started. Press Ctrl+C to stop it") while not self._stop: event = await self.make_long_request(self.long_poll_server) if isinstance(event, dict) and event.get("ts"): self.loop.create_task(self.emulate(event)) self.long_poll_server["ts"] = event["ts"] else: await self.get_server() logger.error("Polling was stopped")
async def dispatch(self, get_current_rest: typing.Callable = None) -> None: """ Dispatch handlers from only-handlers and both-handlers :param get_current_rest: REST from vkbottle-rest :return: """ self.message_handler.rules += self.message.rules + self.chat_message.rules self.message_handler.payload.rules += ( self.message.payload.rules + self.chat_message.payload.rules ) self.rules = self.message_handler.payload.rules + self.message_handler.rules if get_current_rest: # Check updates from timoniq/vkbottle-rest current_rest = await get_current_rest() if current_rest["version"] != __version__: logger.info( "You are using old version of VKBottle. Update is found: {} | {}", current_rest["version"], current_rest["description"], ) logger.debug("Bot successfully dispatched")
async def run(self, wait: int = DEFAULT_WAIT): """ Run user polling forever Can be manually stopped with: >> user.stop() """ self.__wait = wait logger.info("Polling will be started. Is it OK?") await self.get_server() self.on.dispatch() logger.debug("User Polling successfully started") while not self._stop: try: event = await self.make_long_request(self.long_poll_server) if isinstance(event, dict) and event.get("ts"): self.__loop.create_task(self.emulate(event)) self.long_poll_server["ts"] = event["ts"] else: await self.get_server() except ( aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError, TimeoutError, ): # No internet connection logger.warning("Server Timeout Error!") except: logger.error( "While user lp was running error occurred \n\n{}".format( traceback.format_exc())) logger.error("Polling was stopped")
def set_blueprints(self, *blueprints: Blueprint): """ Add blueprints """ for blueprint in blueprints: blueprint.create(familiar=(self.branch, self.extension, self.api)) self.loop.create_task(self.dispatch(blueprint)) logger.debug("Blueprints have been successfully loaded")
def set_blueprints(self, *blueprints: Blueprint): """ Add blueprints """ for blueprint in blueprints: blueprint.create(api_instance=self.api, error_handler=self.error_handler) self.dispatch(blueprint) logger.debug("Blueprints have been successfully loaded")
def set_blueprints(self, *blueprints: Blueprint) -> None: """ Add blueprints """ for blueprint in blueprints: blueprint.create(familiar=(self.branch, self.api)) self.loop.run_until_complete(self.dispatch(blueprint)) logger.debug("Blueprints have been successfully loaded")
async def run_middleware(self, event: BaseModel, flag: MiddlewareFlags, *middleware_args): for middleware in self.middleware: logger.debug( f"Executing middleware {middleware.__class__.__name__} ({str(flag)})" ) yield await middleware.emulate_middleware(event, flag, *middleware_args)
async def _processor(self, obj: dict, client_info: dict): processor = dict(obj=obj, client_info=client_info) middleware_args = [] message = Message(**{ **obj, "text": init_bot_mention(self.group_id, obj["text"]) }, client_info=client_info) async for mr in self.middleware.run_middleware(message): if self.status.middleware_expressions: if mr is False: return elif mr is not None: middleware_args.append(mr) if message.peer_id in await self.branch.queue: await self._branched_processor(obj, client_info, middleware_args) return logger.debug( '-> MESSAGE FROM {} TEXT "{}"', message.from_id, message.text.replace("\n", " "), ) task = None for rules in self.on.rules: for rule in rules: if not await rule(message): break else: args = [a for rule in rules for a in rule.context.args] kwargs = { k: v for rule in rules for k, v in rule.context.kwargs.items() } args = [message, *middleware_args, *args] task = await rules[0].call(*args, **kwargs) logger.info( 'New message "{}" compiled with decorator <{}> (from: {}/{})' .format( message.text.replace("\n", " "), rules[0].call.__name__, message.peer_id, message.from_id, )) break await self._handler_return(task, **processor)
async def branch_processor(self, obj: dict, client_info: dict, middleware_args: list): """ Process messages for users in branch storage #TODO Make a simple branch extendable processor #TODO Move middleware_args """ obj["text"] = sub(r"\[club" + str(self.group_id) + r"\|.*?\] ", "", obj["text"]) message = Message(**obj, client_info=client_info) branch_checkup_key = message.dict()[self.branch.checkup_key.value] logger.debug( '-> BRANCHED MESSAGE FROM {} TEXT "{}"', message.peer_id, message.text.replace("\n", " "), ) disposal, branch = await self.branch.load(branch_checkup_key) edited = None for n, member in disposal.items(): rules = member[1] for rule in rules: if not await rule(message): break else: task = types.MethodType(member[0], branch) args = [a for rule in rules for a in rule.context.args] kwargs = { k: v for rule in rules for k, v in rule.context.kwargs.items() } edited = await self.handler_return( await task(message, *middleware_args, *args, **kwargs), data={ "object": obj, "client_info": client_info }, ) break # FIXME if (edited is False and self.branch.__class__.generator is GeneratorType.DATABASE): if branch_checkup_key in await self.branch.queue: logger.debug("Updating branch context") await self.branch.add(branch_checkup_key, branch.key, **branch.context) logger.info( 'New BRANCHED "{0}" compiled with branch <{2}> (from: {1})'.format( message.text.replace("\n", " "), message.from_id, repr(branch.key), ))
async def request( method: str, params: dict, token: str, session: HTTPRequest = None, error_handler: "VKErrorHandler" = None, request_instance=None, ): url = "{}{method}/?access_token={token}&v={version}".format( API_URL, method=method, token=token, version=API_VERSION, ) session = session or HTTPRequest() response = await session.post(url, data=params or {}) if not isinstance(response, dict): delay = 1 while not isinstance(response, dict): logger.error( "\n---" f"{time.strftime('%m-%d %H:%M:%S', time.localtime())} - DELAY {delay * 5} sec\n" f"Check your internet connection. Maybe VK died, request returned: {response}" f"Error appeared after request: {method}") await asyncio.sleep(delay * 5) response = await session.post(url, data=params or {}) logger.success( f"--- {time.strftime('%m-%d %H:%M:%S', time.localtime())}\n" f"- METHOD SUCCESS after {5 * sum(range(1, delay))} sec\n" f"RESPONSE: {response}\n") if "error" in response: logger.debug("Error after request {method}, response: {r}", method=method, r=response) exception = VKError( response["error"]["error_code"], response["error"]["error_msg"], from_attr( Categories, [method.split(".")[0], to_snake_case(method.split(".")[1])] if "." in method else method, (request_instance, None), ), params, raw_error=response["error"], ) if not error_handler: raise exception return await error_handler.handle_error(exception) return response
async def message_processor( self, update: dict, update_code: int, update_fields: typing.List[int], ) -> None: """ Process message events. Use base fields to make a dataclass Params described in parent_processor """ # Expanding data fields = ("message_id", "flags", *ADDITIONAL_FIELDS) data = dict(zip(fields, update_fields)) middleware_args = [] if self.expand_models: data.update(await self.expand_data(update_code, data)) message = Message(**data) # Executing middleware async for mr in self.middleware.run_middleware(message, MiddlewareFlags.PRE): if self.status.middleware_expressions: if mr is False: return elif mr is not None: middleware_args.append(mr) # Executing branch queue if message.dict()[ self.branch.checkup_key.value] in await self.branch.queue: return await self.branch_processor(message, middleware_args) # Rule checkup for rules in self.handler.message_rules: rule = rules[0] # API Complexity #FIXME check = await rule.check(update) if check is not None: args, kwargs = [], {} if isinstance(check, tuple): check = await self.filter(message, check) if not check: continue args, kwargs = check task = await rule.call(message, *args, **kwargs) await self.handler_return(task, data) break async for mr in self.middleware.run_middleware(message, MiddlewareFlags.POST, *middleware_args): logger.debug(f"POST Middleware handler returned: {mr}")
def dispatch(self, user: AnyUser) -> None: """ Concatenate handlers to current user object :param user: :return: """ self.on.concatenate(user.on) self.error_handler.handled_errors.update(user.error_handler.handled_errors) self.middleware.middleware += user.middleware.middleware self.branch.add_branches(user.branch.branches) logger.debug("Bot has been successfully dispatched")
def get_id_by_token(token: str, loop: asyncio.AbstractEventLoop) -> int: """ Get group id from token :param token: :return: """ logger.debug("Making API request users.get to get user_id") response = loop.run_until_complete(request("users.get", {}, token)) if "error" in response: raise VKError(0, "Token is invalid") return response["response"][0]["id"]
async def _branched_processor(self, obj: dict, client_info: dict, middleware_args: list): """ Branched messages processor manager :param obj: VK Server Event Object """ obj["text"] = sub(r"\[club" + str(self.group_id) + r"\|.*?\] ", "", obj["text"]) answer = Message(**obj, client_info=client_info) logger.debug( '-> BRANCHED MESSAGE FROM {} TEXT "{}"', answer.peer_id, answer.text.replace("\n", " "), ) disposal, branch = await self.branch.load(answer.peer_id) edited = None for n, member in disposal.items(): rules = member[1] for rule in rules: if not await rule(answer): break else: task = types.MethodType(member[0], branch) args = [a for rule in rules for a in rule.context.args] kwargs = { k: v for rule in rules for k, v in rule.context.kwargs.items() } edited = await self._handler_return( await task(answer, *middleware_args, *args, **kwargs), obj, client_info, ) break if edited is False and self.branch.generator is GeneratorType.DATABASE: if answer.peer_id in await self.branch.queue: await self.branch.add(answer.peer_id, branch.key, **branch.context) logger.info( 'New BRANCHED "{0}" compiled with branch <{2}> (from: {1})'.format( answer.text.replace("\n", " "), answer.from_id, '"{}" with {} kwargs'.format( branch.key, branch.context if len(branch.context) < 100 else branch.context[1:99] + "...", ), ))
def dispatch(self, bot: AnyBot): """ Concatenate handlers to current bot object :param bot: :return: """ self.on.concatenate(bot.on) self.error_handler.handled_errors.update(bot.error_handler.handled_errors) self.middleware.middleware += bot.middleware.middleware self.branch.add_branches(bot.branch.branches) logger.debug("Bot has been successfully dispatched")
def _check_secret(self, event: dict, secret: typing.Optional[str] = None): """ Match secret code with current secret :param event: :return: """ if self.__secret or secret: logger.debug("Checking secret for event ({secret})", secret=event.get("secret")) return event.get("secret") == (self.__secret or secret) return True
async def dispatch(self, bot: AnyBot): """ Concatenate handlers to current bot object :param bot: :return: """ self.on.concatenate(bot.on) self.error_handler.handled_errors.update(bot.error_handler.handled_errors) self.middleware.middleware += bot.middleware.middleware for branch_name, disposal in (await bot.branch.branches).items(): self.branch.add_branch(disposal[0], name=branch_name) logger.debug("Bot has been successfully dispatched")
async def wrapper(*args, **kwargs): try: return await func(*args, **kwargs) except exception as e: if ignore: return e if exception_handler is not None: await exception_handler(e, *args, **kwargs) else: logger.error(traceback.format_exc()) finally: logger.debug( f"{func.__name__} successfully handled with swear")
def get_id_by_token(token: str, throw_exc: bool = True) -> typing.Union[int, bool]: """ Get group id from token :param token: :param throw_exc: :return: """ logger.debug("Making API request groups.getById to get group_id") response = asyncio.get_event_loop().run_until_complete( request("groups.getById", {}, token, throw_errors=False) ) if "error" in response: if throw_exc: raise VKError(0, "Token is invalid") return False return response["response"][0]["id"]
def concatenate(self, handler: "Handler"): """ Concatenate handlers from current handler and another handler :param handler: another handler :return: """ self.message.concatenate(handler.message) self.chat_message.concatenate(handler.chat_message) self.message_handler.concatenate(handler.message_handler) self.event.rules += handler.event.rules if not self._pre_p: self._pre_p = handler.pre logger.debug( "Bot Handler was concatenated with {handler}", handler=handler.__class__.__name__, )
async def branch_processor(self, message: Message, middleware_args: list): """ Process messages for users in branch storage #TODO Make a simple branch extendable processor #TODO Move middleware_args """ branch_checkup_key = message.dict()[self.branch.checkup_key.value] logger.debug( '-> BRANCHED MESSAGE FROM {} TEXT "{}"', branch_checkup_key, message.text.replace("\n", " "), ) disposal, branch = await self.branch.load(branch_checkup_key) edited = None for n, member in disposal.items(): rules = member[1] for rule in rules: if not await rule(message): break else: task = types.MethodType(member[0], branch) args = [a for rule in rules for a in rule.context.args] kwargs = { k: v for rule in rules for k, v in rule.context.kwargs.items() } handler_return = await task(message, *middleware_args, *args, **kwargs) edited = await self.handler_return(handler_return, message.dict()) break # FIXME if edited is False and self.branch.generator is GeneratorType.DATABASE: if branch_checkup_key in await self.branch.queue: logger.debug("Updating branch context") await self.branch.add(branch_checkup_key, branch.key, **branch.context) logger.info( 'New BRANCHED "{0}" compiled with branch <{2}> (from: {1})'.format( message.text.replace("\n", " "), branch_checkup_key, repr(branch.key), ))
async def wrapper(*args, **kwargs): try: return await func(*args, **kwargs) except exception as e: if ignore: return e if exception_handler is not None: await exception_handler(e, *args, **kwargs) elif just_log: logger.error( "While {func} was handling error occurred \n\n{traceback}", func=func.__name__, traceback=traceback.format_exc(), ) finally: logger.debug( f"{func.__name__} successfully handled with swear")
async def _processor(self, update: dict, update_code: int, update_fields: list): try: data = update, update_code, update_fields if update_code not in list(map(int, UserEvents)): logger.warning("Undefined event {}", update_code) return event = UserEvents(update_code) logger.debug("New event: {} {}", event, update) if event is UserEvents.new_message: return await self._message_processor(*data) return await self._event_processor(*data) except VKError as e: await self.error_handler.handle_error(e) except: logger.error( "While user polling worked error occurred \n\n{traceback}", traceback=traceback.format_exc(), )
async def emulate( self, event: dict, confirmation_token: str = None, secret: str = None, ) -> typing.Union[str, None]: """ Process all types of events :param event: VK Event (LP or CB) :param confirmation_token: code which confirm VK callback :param secret: secret key for callback :return: "ok" """ if not self.status.dispatched: self.middleware.add_middleware(self.on.pre_p) await self.on.dispatch(self.get_current_rest) self.status.dispatched = True logger.debug("Event: {event}", event=event) if event is None: return if event.get("type") == "confirmation": if event.get("group_id") == self.group_id: return confirmation_token or "dissatisfied" updates = event.get("updates", [event]) if not self._check_secret(event, secret=secret): logger.debug("Aborted. Secret is invalid") return "access denied" for update in updates: if not update.get("object"): continue try: self.loop.create_task( self.handle.parent_processor(update, obj=update["object"])) except VKError as e: self.loop.create_task(self.error_handler.handle_error(e)) return "ok"
async def parent_processor( self, update: dict, update_code: int, update_fields: typing.List[int], ) -> bool: """ Classify and distribute user polling events as message and not message #TODO Reduce difference :param update: full event :param update_code: first element from update :param update_fields: fields stack """ data = update, update_code, update_fields if update_code not in list(map(int, UserEvents)): logger.warning("Undefined event {}", update_code) return event = UserEvents(update_code) logger.debug("New event: {} {}", event, update) if event is UserEvents.new_message: return await self.message_processor(*data) return await self.event_processor(*data)
async def __call__( self, method: str, params: dict, throw_errors: bool = None, response_model=None, raw_response: bool = False, ): response = await request( method, params, await self.token_generator.get_token(method=method, params=params), request_instance=self, error_handler=self.error_handler, ) logger.debug("Response: {}", response) if not response_model or raw_response: return response["response"] return response_model(**response).response