async def call(self, command: rc.Command, data: rc.CommandData, parameters: List[str]): log.info(f"Calling command: {command.name}") try: # Run the command await command.run(rc.CommandArgs(parameters), data) except rc.InvalidInputError as e: await data.reply( f"⚠️ {e.message}\n" f"Syntax: [c]{self.prefix}{command.name} {command.syntax}[/c]") except rc.UserError as e: await data.reply(f"⚠️ {e.message}") except rc.UnsupportedError as e: await data.reply(f"⚠️ {e.message}") except rc.ExternalError as e: await data.reply(f"⚠️ {e.message}") except rc.ConfigurationError as e: await data.reply(f"⚠️ {e.message}") except rc.ProgramError as e: await data.reply(f"⛔️ {e.message}") except rc.CommandError as e: await data.reply(f"⚠️ {e.message}") except Exception as e: ru.sentry_exc(e) await data.reply(f"⛔️ [b]{e.__class__.__name__}[/b]\n" + '\n'.join(map(lambda a: repr(a), e.args)))
def register_events(self, events: List[Type[Event]], pack_cfg: Dict[str, Any]): for SelectedEvent in events: # Create a new interface interface = self.Interface(config=pack_cfg) # Initialize the event try: event = SelectedEvent(interface) except Exception as e: log.error( f"Skipping: " f"{SelectedEvent.__qualname__} - {e.__class__.__qualname__} in the initialization." ) ru.sentry_exc(e) continue # Register the event if SelectedEvent.name in self.events: log.warning( f"Overriding (already defined): {SelectedEvent.__qualname__} -> {SelectedEvent.name}" ) else: log.debug( f"Registering: {SelectedEvent.__qualname__} -> {SelectedEvent.name}" ) self.events[SelectedEvent.name] = event
async def network_handler( self, message: Union[rh.Request, rh.Broadcast]) -> rh.Response: try: event: Event = self.events[message.handler] except KeyError: log.warning(f"No event for '{message.handler}'") return rh.ResponseFailure( "no_event", f"This serf does not have any event for {message.handler}.") log.debug(f"Event called: {event.name}") if isinstance(message, rh.Request): try: response_data = await event.run(**message.data) return rh.ResponseSuccess(data=response_data) except CommandError as e: return rh.ResponseFailure( "error_in_event", f"The event '{message.handler}' raised a {e.__class__.__qualname__}.", extra_info={ "type": e.__class__.__qualname__, "message": str(e) }) except Exception as e: ru.sentry_exc(e) return rh.ResponseFailure( "unhandled_exception_in_event", f"The event '{message.handler}' raised an unhandled" f" {e.__class__.__qualname__}.", extra_info={ "type": e.__class__.__qualname__, "message": str(e) }) elif isinstance(message, rh.Broadcast): await event.run(**message.data)
async def call(self, command: Command, data: CommandData, parameters: List[str]): log.info(f"Calling command: {command.name}") try: # Run the command await command.run(CommandArgs(parameters), data) except InvalidInputError as e: await data.reply( f"⚠️ {e.message}\n" f"Syntax: [c]{command.interface.prefix}{command.name} {command.syntax}[/c]" ) except UserError as e: await data.reply(f"⚠️ {e.message}") except UnsupportedError as e: await data.reply(f"⚠️ {e.message}") except ExternalError as e: await data.reply(f"⚠️ {e.message}") except ConfigurationError as e: await data.reply(f"⚠️ {e.message}") except ProgramError as e: await data.reply(f"⛔️ {e.message}") except CommandError as e: await data.reply(f"⚠️ {e.message}") except Exception as e: ru.sentry_exc(e) await data.reply(f"⛔️ [b]{e.__class__.__name__}[/b]\n" + '\n'.join(e.args)) finally: await data.session_close()
async def run_updater(self): log.info(f"Starting updater: {self.name}") while True: log.debug(f"Updater cycle: {self.name}") session = self.alchemy.Session() objects = await self.get_updatables(session) for obj in objects: log.debug(f"Updating: {obj} ({self.name})") async def change(attribute: str, value: Any): """A shortcut for self.__change.""" await self._change(session=session, obj=obj, attribute=attribute, new=value) try: await self.update(session=session, obj=obj, change=change) except Exception as e: ru.sentry_exc(e) delay = self.delay() log.debug(f"Waiting for: {delay} seconds (delay)") await aio.sleep(delay) log.debug(f"Committing updates: {self.name}") await ru.asyncify(session.commit) session.close() period = self.period() log.debug(f"Waiting for: {period} seconds (period)") await aio.sleep(period)
async def api_call(f: Callable, *args, **kwargs) -> Optional: """Call a :class:`telegram.Bot` method safely, without getting a mess of errors raised. The method may return None if it was decided that the call should be skipped.""" while True: try: return await ru.asyncify(f, *args, **kwargs) except telegram.error.TimedOut as error: log.debug(f"Timed out during {f.__qualname__} (retrying immediatly): {error}") continue except telegram.error.NetworkError as error: log.debug(f"Network error during {f.__qualname__} (skipping): {error}") break except telegram.error.Unauthorized as error: log.info(f"Unauthorized to run {f.__qualname__} (skipping): {error}") break except telegram.error.RetryAfter as error: log.warning(f"Rate limited during {f.__qualname__} (retrying in 15s): {error}") await aio.sleep(15) continue except urllib3.exceptions.HTTPError as error: log.warning(f"urllib3 HTTPError during {f.__qualname__} (retrying in 15s): {error}") await aio.sleep(15) continue except Exception as error: log.error(f"{error.__class__.__qualname__} during {f} (skipping): {error}") ru.sentry_exc(error) break return None
def _test_all(self): for spell in self._dnddata: try: log.debug(f"Testing: {spell['name']}") result = self._parse_spell(spell) except Exception as e: log.error(f"Failed: {spell['name']}") sentry_exc(e) log.info(f"All spell tests complete!")
def _test_all(self): for item in self._dnddata: try: log.debug(f"Testing: {item['name']}") result = self._parse_item(item) except Exception as e: log.error(f"Failed: {item['name']}") sentry_exc(e) breakpoint() log.info(f"All item tests complete!")
def register_exc_stars(self, exc_stars: List[Type[ExceptionStar]], pack_cfg: Dict[str, Any]): for SelectedPageStar in exc_stars: log.debug(f"Registering: {SelectedPageStar.error} -> {SelectedPageStar.__qualname__}") try: page_star_instance = SelectedPageStar(constellation=self, config=pack_cfg) except Exception as e: log.error(f"Skipping: " f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.") ru.sentry_exc(e) continue self.starlette.add_exception_handler(page_star_instance.error, page_star_instance.page)
def register_page_stars(self, page_stars: List[Type[PageStar]], pack_cfg: Dict[str, Any]): for SelectedPageStar in page_stars: log.debug(f"Registering: {SelectedPageStar.path} -> {SelectedPageStar.__qualname__}") try: page_star_instance = SelectedPageStar(interface=self.Interface(pack_cfg)) except Exception as e: log.error(f"Skipping: " f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.") ru.sentry_exc(e) continue self.stars.append(page_star_instance) self.starlette.add_route(*self._page_star_wrapper(page_star_instance))
def register_commands(self, commands: List[Type[Command]], pack_cfg: Dict[str, Any]) -> None: """Initialize and register all commands passed as argument.""" # Instantiate the Commands for SelectedCommand in commands: # Create a new interface interface = self.Interface(config=pack_cfg) # Try to instantiate the command try: command = SelectedCommand(interface) except Exception as e: log.error( f"Skipping: " f"{SelectedCommand.__qualname__} - {e.__class__.__qualname__} in the initialization." ) ru.sentry_exc(e) continue # Link the interface to the command interface.command = command # Warn if the command would be overriding something if f"{self.Interface.prefix}{SelectedCommand.name}" in self.commands: log.info( f"Overriding (already defined): " f"{SelectedCommand.__qualname__} -> {self.Interface.prefix}{SelectedCommand.name}" ) else: log.debug( f"Registering: " f"{SelectedCommand.__qualname__} -> {self.Interface.prefix}{SelectedCommand.name}" ) # Register the command in the commands dict self.commands[ f"{interface.prefix}{SelectedCommand.name}"] = command # Register aliases, but don't override anything for alias in SelectedCommand.aliases: if f"{interface.prefix}{alias}" not in self.commands: log.debug( f"Aliasing: {SelectedCommand.__qualname__} -> {interface.prefix}{alias}" ) self.commands[f"{interface.prefix}{alias}"] = \ self.commands[f"{interface.prefix}{SelectedCommand.name}"] else: log.warning( f"Ignoring (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{alias}" )
async def page(self, request: Request) -> JSONResponse: if request.query_params: data = request.query_params else: try: data = await request.json() except JSONDecodeError: data = {} apidata = ApiData(data=data, star=self) method = request.method.lower() try: if method == "get": response = await self.get(apidata) elif method == "post": response = await self.post(apidata) elif method == "put": response = await self.put(apidata) elif method == "delete": response = await self.delete(apidata) elif method == "options": return api_success("Preflight allowed.", methods=self.methods()) else: raise MethodNotImplementedError("Unknown method") except UnauthorizedError as e: return api_error(e, code=401, methods=self.methods()) except NotFoundError as e: return api_error(e, code=404, methods=self.methods()) except ForbiddenError as e: return api_error(e, code=403, methods=self.methods()) except MethodNotImplementedError as e: return api_error(e, code=405, methods=self.methods()) except BadRequestError as e: return api_error(e, code=400, methods=self.methods()) except Exception as e: ru.sentry_exc(e) return api_error(e, code=500, methods=self.methods()) else: return api_success(response, methods=self.methods()) finally: await apidata.session_close()
def run_process(cls, **kwargs): """Blockingly create and run the Serf. This should be used as the target of a :class:`multiprocessing.Process`.""" ru.init_logging(kwargs["logging_cfg"]) if kwargs["sentry_cfg"] is None or not kwargs["sentry_cfg"]["enabled"]: log.info("Sentry: disabled") else: try: ru.init_sentry(kwargs["sentry_cfg"]) except ImportError: log.info("Sentry: not installed") loop = aio.get_event_loop() serf = cls(loop=loop, **kwargs) try: serf.loop.run_until_complete(serf.run()) except Exception as e: ru.sentry_exc(e, level="fatal")
async def press(key: rc.KeyboardKey, data: rc.CommandData): log.info(f"Calling key_callback: {repr(key)}") try: await key.press(data) except rc.InvalidInputError as e: await data.reply(f"⚠️ {e.message}") except rc.UserError as e: await data.reply(f"⚠️ {e.message}") except rc.UnsupportedError as e: await data.reply(f"⚠️ {e.message}") except rc.ExternalError as e: await data.reply(f"⚠️ {e.message}") except rc.ConfigurationError as e: await data.reply(f"⚠️ {e.message}") except rc.ProgramError as e: await data.reply(f"⛔️ {e.message}") except rc.CommandError as e: await data.reply(f"⚠️ {e.message}") except Exception as e: ru.sentry_exc(e) await data.reply(f"⛔️ [b]{e.__class__.__name__}[/b]\n" + '\n'.join(map(lambda a: repr(a), e.args)))
def register_commands(self, commands: List[Type[rc.Command]], pack_cfg: rc.ConfigDict) -> None: """Initialize and register all commands passed as argument.""" # Instantiate the Commands for SelectedCommand in commands: # Try to instantiate the command try: command = SelectedCommand(serf=self, config=pack_cfg) except Exception as e: log.error( f"Skipping: " f"{SelectedCommand.__qualname__} - {e.__class__.__qualname__} in the initialization." ) ru.sentry_exc(e) continue # Warn if the command would be overriding something if SelectedCommand.name in self.commands: log.info( f"Overriding (already defined): " f"{SelectedCommand.__qualname__} -> {SelectedCommand.name}" ) else: log.debug( f"Registering: " f"{SelectedCommand.__qualname__} -> {SelectedCommand.name}" ) # Register the command in the commands dict self.commands[SelectedCommand.name] = command # Register aliases, but don't override anything for alias in SelectedCommand.aliases: if alias not in self.commands: log.debug( f"Aliasing: {SelectedCommand.__qualname__} -> {alias}") self.commands[alias] = self.commands[SelectedCommand.name] else: log.warning( f"Ignoring (already defined): {SelectedCommand.__qualname__} -> {alias}" )
def register_events(self, events: List[Type[rc.HeraldEvent]], pack_cfg: rc.ConfigDict): for SelectedEvent in events: # Initialize the event try: event = SelectedEvent(parent=self, config=pack_cfg) except Exception as e: log.error( f"Skipping: " f"{SelectedEvent.__qualname__} - {e.__class__.__qualname__} in the initialization." ) ru.sentry_exc(e) continue # Register the event if SelectedEvent.name in self.events: log.warning( f"Overriding (already defined): {SelectedEvent.__qualname__} -> {SelectedEvent.name}" ) else: log.debug( f"Registering: {SelectedEvent.__qualname__} -> {SelectedEvent.name}" ) self.events[SelectedEvent.name] = event
async def press(self, key: KeyboardKey, data: CommandData): log.info(f"Calling key_callback: {repr(key)}") try: await key.press(data) except InvalidInputError as e: await data.reply(f"⚠️ {e.message}") except UserError as e: await data.reply(f"⚠️ {e.message}") except UnsupportedError as e: await data.reply(f"⚠️ {e.message}") except ExternalError as e: await data.reply(f"⚠️ {e.message}") except ConfigurationError as e: await data.reply(f"⚠️ {e.message}") except ProgramError as e: await data.reply(f"⛔️ {e.message}") except CommandError as e: await data.reply(f"⚠️ {e.message}") except Exception as e: ru.sentry_exc(e) await data.reply(f"⛔️ [b]{e.__class__.__name__}[/b]\n" + '\n'.join(e.args)) finally: await data.session_close()
async def on_error(self, event_method, *args, **kwargs): exc_type, exc_obj, exc_tb = sys.exc_info() sentry_exc(exc_obj)
async def get(self, data: rca.ApiData) -> ru.JSON: """Login to Royalnet with your osu! account.""" OsuT = self.alchemy.get(Osu) TokenT = self.alchemy.get(rbt.Token) code = data.str("code") state = data.str("state", optional=True) if state is not None: serializer = itsdangerous.URLSafeSerializer( self.config["secret_key"], salt="osu") uid = serializer.loads(state) user = await rbt.User.find(self.alchemy, data.session, uid) else: user = None try: t = await oauth_auth(url="https://osu.ppy.sh/oauth/token", client_id=self.client_id, client_secret=self.client_secret, redirect_uri=f"{self.base_url}{self.path}", auth_code=code) except aiohttp.client_exceptions.ClientResponseError as e: ru.sentry_exc(e) raise rca.ForbiddenError( "osu! API returned an error in the OAuth token exchange") async with aiohttp.ClientSession( headers={"Authorization": f"Bearer {t['access_token']}" }) as session: async with session.get( "https://osu.ppy.sh/api/v2/me/") as response: m = await response.json() if user is not None: osu = OsuT(user=user, access_token=t["access_token"], refresh_token=t["refresh_token"], expiration_date=datetime.datetime.now() + datetime.timedelta(seconds=t["expires_in"]), osu_id=m["id"], username=m["username"]) data.session.add(osu) else: osu = await ru.asyncify( data.session.query(OsuT).filter_by(osu_id=m["id"]).all) if osu is None: raise rcae.ForbiddenError("Unknown osu! account") user = osu.user if self.config["osu"]["login"]["enabled"]: token: rbt.Token = TokenT.generate( alchemy=self.alchemy, user=user, expiration_delta=datetime.timedelta(days=7)) data.session.add(token) await data.session_commit() return token.json() else: await data.session_commit() raise rcae.ForbiddenError( "Account linked successfully; cannot use this account to generate a Royalnet" " login token, as osu! login is currently disabled on this Royalnet instance." )