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 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 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")
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 _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 _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 get_current_rest(self) -> dict: """ Get current actual info about package from GitHub REST page """ rest_status = await self.request.get(url=VERSION_REST, content_type="text/plain") if rest_status is None: logger.info("Unable to check version! Skipping it") return {"version": __version__} return rest_status
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] + "...", ), ))
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 get_updates(self): # noqa logger.info("Receiving updates from conversations") updates = [] close, offset = False, 0 while not close: conversations = await self.api.messages.get_conversations( offset, 200, filter="unanswered" ) if offset == 0: logger.info(f"Conversation count - {conversations.count}") if conversations.count == 0: return offset += 200 updates.extend([item.conversation.out_read for item in conversations.items]) if len(conversations.items) < 200: close = True logger.warning("Answering...") chunk = list(chunks(updates, 100)) for mid in chunk: try: messages = await self.api.messages.get_by_id(mid) await self.emulate( { "updates": [ { "type": "message_new", "object": {"message": m.dict(), "client_info": {}}, } for m in messages.items ] } ) except VKError: continue
async def enter(self): logger.info("Branch {} entered at", self.key)
async def message_processor(self, obj: dict, client_info: dict): processor = dict(object=obj, client_info=client_info) middleware_args = [] message = Message( **{ **obj, "text": init_bot_mention(self.group_id, obj["text"]) }, client_info=client_info, ) branch_checkup_key = message.dict()[self.branch.checkup_key.value] async for mr in self.middleware.run_middleware( message, flag=MiddlewareFlags.PRE): if self.status.middleware_expressions: if mr is False: return elif mr is not None: middleware_args.append(mr) if branch_checkup_key in await self.branch.queue: await self.branch_processor(obj, client_info, middleware_args) return logger.debug( '-> MESSAGE FROM {} TEXT "{}"', message.from_id, message.text.replace("\n", " "), ) for rules in self.handler.message_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) await self.handler_return(task, data=processor) logger.info( 'New message "{}" compiled with decorator <{}> (from: {}/{})' .format( message.text.replace("\n", " "), rules[0].call.__name__, message.peer_id, message.from_id, )) break async for mr in self.middleware.run_middleware(message, MiddlewareFlags.POST, *middleware_args): logger.debug(f"POST Middleware handler returned: {mr}")
def __init__( self, tokens: Token = None, *, group_id: int = None, debug: typing.Union[str, bool] = True, loop: asyncio.AbstractEventLoop = None, throw_errors: bool = True, log_to_path: typing.Union[str, bool] = None, patcher: Patcher = None, mobile: bool = False, secret: str = None, extension: AbstractExtension = None, logs_folder: typing.Optional[str] = None, only_asyncio_loop: bool = False, **context, ): """ Init bot :param tokens: bot tokens :param group_id: :param debug: should bot debug messages for emulating :param log_to_path: make logs :param secret: secret vk code for callback :param extension: """ # Base bot classifiers self.__tokens: typing.List[str] = [tokens] if isinstance( tokens, str ) else tokens self.__debug: bool = debug self.__wait = None self.__secret = secret self._status: BotStatus = BotStatus() self.context: dict = context if uvloop is not None: if not only_asyncio_loop: asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) if isinstance(debug, bool): debug = "INFO" if debug else "ERROR" self.logger = LoggerLevel(debug) if not Patcher.get_current(): Patcher.set_current( patcher if patcher is not None else Patcher(pattern="^{}$", validators=DefaultValidators) ) logger.remove() logger.add( sys.stderr, colorize=True, format="<level>[<blue>VKBottle</blue>] {message}</level> <white>[TIME {time:HH:MM:ss}]</white>", filter=self.logger, level=0, enqueue=mobile is False, ) logger.level("INFO", color="<white>") logger.level("ERROR", color="<red>") if log_to_path: logger.add( (logs_folder or "") + "log_{time}.log" if log_to_path is True else log_to_path, rotation="20 MB", ) self.group_id = group_id or self.get_id_by_token(self.__tokens[0]) self.__loop = loop or asyncio.get_event_loop() # Sign assets self.api: Api = Api(self.__tokens, throw_errors=throw_errors) self.extension: AbstractExtension = extension if extension is not None else StandardExtension() self._throw_errors: bool = throw_errors Api.set_current(self.api) AbstractExtension.set_current(self.extension) # Main workers self.branch: typing.Union[AbstractBranchGenerator, DictBranch] = DictBranch() self.middleware: MiddlewareExecutor = MiddlewareExecutor() self.on: Handler = Handler(self.group_id) self.error_handler: VKErrorHandler = DefaultErrorHandler() self._stop: bool = False logger.info("Using JSON_MODULE - {}".format(USAGE)) logger.info( "Using asyncio loop - {}".format( asyncio.get_event_loop_policy().__class__.__module__ ) )
def __init__( self, tokens: Token = None, *, login: str = None, password: str = None, user_id: int = None, debug: typing.Union[str, bool] = True, loop: asyncio.AbstractEventLoop = None, expand_models: bool = True, mobile: bool = False, log_to_path: typing.Union[str, bool] = None, vbml_patcher: vbml.Patcher = None, mode: int = 234, only_asyncio_loop: bool = False, **context, ): self.__tokens = [tokens] if isinstance(tokens, str) else tokens self.context: dict = context if uvloop is not None: if not only_asyncio_loop: asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) if login and password: self.__tokens = self.get_tokens(login, password) self.loop = loop or asyncio.get_event_loop() self.__debug: bool = debug self._api: UserApi = UserApi(self.__tokens) self.mode = mode self._expand_models: bool = expand_models self._patcher: vbml.Patcher = vbml_patcher or vbml.Patcher( pattern="^{}$") self.user_id: typing.Optional[int] = user_id or self.get_id_by_token( self.__tokens[0]) self._api.user_id = user_id self.error_handler: VKErrorHandler = DefaultErrorHandler() UserApi.set_current(self._api) VKErrorHandler.set_current(self.error_handler) self.on: UserHandler = UserHandler() self.branch: ABCBranchGenerator = DictBranch() self.middleware: MiddlewareExecutor = MiddlewareExecutor() self.error_handler: VKErrorHandler = DefaultErrorHandler() self.deconstructed_handle: UserProcessor = UserProcessor( self.user_id, expand_models=expand_models) self._stop: bool = False self.status = UserStatus() if isinstance(debug, bool): debug = "INFO" if debug else "ERROR" self.logger = LoggerLevel(debug) logger.remove() logger.add( sys.stderr, colorize=True, format="<level>[<blue>VKBottle</blue>] {message}</level>", filter=self.logger, level=0, enqueue=mobile is False, ) if log_to_path: logger.add( "log_user_{time}.log" if log_to_path is True else log_to_path, rotation="20 MB", ) logger.info("Using JSON_MODULE - {}".format(USAGE)) logger.info("Using asyncio loop - {}".format( asyncio.get_event_loop_policy().__class__.__module__))
async def exit(self): logger.info("Branch {} exit at", self.key or self.data["call"].__name__)
async def enter(self): logger.info("Branch {} entered at", self.key or self.data["call"].__name__)
async def exit(self): logger.info("Branch {} exit at", self.key)