def get_ssl_context(cls, ignore_error: bool = False) -> Optional[ssl.SSLContext]: if not cls.SSL_ENABLE: return None for name in ['SSL_CAFILE', 'SSL_CERT', 'SSL_KEY']: cfg_file = getattr(cls, name, None) if not cfg_file: msg = f'SSL_ENABLE is set but no config found for "{cfg_file}"' if not ignore_error: raise MinderError(msg) logger.warning(f'{msg}. Ignoring based on arguments and using HTTP') if not os.path.exists(cfg_file) or not os.path.stat(cfg_file): msg = f'SSL_ENABLE is set but cannot find "{name}" file in "{cfg_file}": Path does not exist or is unreadable' if not ignore_error: raise MinderError(msg) logger.warning(f'{msg}. Ignoring based on arguments and using HTTP') ssl_ctx = ssl.create_default_context(cafile=Config.SSL_CAFILE) ssl_ctx.load_cert_chain(certfile=cls.SSL_CERT, keyfile=cls.SSL_KEY) return ssl_ctx
def get_settings(self, handler_name: str, setting_name: str = None, throw_error: bool = True) -> Optional[Any]: if handler_name not in self.handlers: err_message = f'Unable to find settings handler for "{handler_name}"' if throw_error: raise MinderError(err_message) logger.error(err_message) return None hdr = self.handlers[handler_name] if setting_name: if not hdr.can_handle_setting(setting_name): err_message = f'No such setting "{setting_name}" found in handler "{handler_name}"' if throw_error: raise MinderError(err_message) logger.error(err_message) return None settings = [setting_name] else: settings = hdr.handled_settings # TODO: Fix this return settings
def get_user(self, username: str = None, user_id: int = None, throw_error: bool = False): if username: usr = self._user_by_name(username) if usr: return usr target = f'"username": {username}' elif user_id: user_id = user_id if user_id in self.users: return self.users[user_id] target = f'"user_id": {user_id}' else: raise MinderError( 'Unable to determine what user to fetch. Neither "username" nor "user_id" were provided' ) err_message = f'Unable to find configured user by {target}: Not found' if throw_error: raise MinderError(err_message) logger.warning(err_message) return None
def get_guild(self, name: str = None, guild_id: int = None, throw_error: bool = False): if not name and not guild_id: raise MinderError( 'Unable to determine what guild to fetch. Neither "name" nor "guild_id" were provided' ) if name: gld = self._guild_by_name(name) if gld: return gld target = f'"name": {name}' else: if guild_id in self.guilds: return self.guilds[guild_id] target = f'"guild_id": {guild_id}' err_message = f'Unable to find configured guild by {target}: Not found' if throw_error: raise MinderError(err_message) logger.warning(err_message) return None
def get_timezone(cls, timezone_name: str) -> pytz.BaseTzInfo: try: return pytz.timezone(timezone_name) except Exception as ex: err_type = cls.get_tz_error_type(ex) err_message = f'{err_type} error parsing "{timezone_name}"' raise MinderError(err_message, base_exception=ex) from ex
def __post_init__(self) -> None: self.redis_name = str(f'{self.action}:{self.timestamp.timestamp()}') if self.action not in StatusEntryActions: if self.action.upper() not in StatusEntryActions: raise MinderError( f'Invalid status entry action: "{self.action}". Must be one of "{", ".join(StatusEntryActions)}"' ) self.action = self.action.upper()
def from_yaml(cls, yaml_file: str) -> UserSettings: yaml_file = os.path.abspath(os.path.expanduser(yaml_file)) if not os.path.exists(yaml_file): raise MinderError( f'Unable to find user settings file "{yaml_file}"') try: with open(yaml_file, 'rt') as f: yaml_cfg = yaml.safe_load(f) except Exception as ex: raise MinderError( f'Failure parsing user settings from "{yaml_file}": {ex}' ) from ex member_id = yaml_cfg['member_id'] guild_id = yaml_cfg.get('guild_id', None) return UserSettings(guild_id=guild_id, member_id=member_id, settings=yaml_cfg['settings'])
def get_by(cls, attr_name: str, value: Any) -> Optional[SAModel]: if not cls.has_column(attr_name): raise MinderError(f'Cannot fetch "{cls.model_name()}" by "{attr_name}": No such column') qry = cls.query.filter_by(attr_name=value) cnt = qry.count() if cnt > 1: logger.warning(f'Request to fetch "{cls.model_name()}" by "{attr_name}" found #{cnt} results. Returning first match') return qry.first()
def save(self, yaml_file: str): yaml_cfg = { 'defaults': { 'admins': self.admins, 'extended_errors': self.extended_errors, 'ignore_other_guilds': self.ignore_other_guilds }, 'users': self.users, 'guilds': self.guilds } try: with open(yaml_file, 'wt') as f: yaml.safe_dump(yaml_cfg, f, indent=2, default_flow_style=False) except yaml.error.YAMLError as ex: raise MinderError( f'Error saving bot config YAML to "{yaml_file}": {ex}') from ex except Exception as ex: raise MinderError( f'General error while saving bot config to "{yaml_file}": {ex}' ) from ex
def get_user_setting(self, user_id: int, name: str, throw_error: bool = False, **kwargs) -> Optional[Any]: if 'default' in kwargs: has_default = True default = kwargs['default'] else: has_default = False default = None if user_id not in self.users: err_message = f'Requested setting for "{name}" of non-existent user with ID "{user_id}"' if throw_error: raise MinderError(err_message) logger.warning(err_message) return None usr = self.get_user(user_id=user_id, throw_error=False) if name in usr: return usr[name] if has_default: return default err_message = f'Requested missing setting for "{name}" from user "{usr["name"]}" (ID: {user_id})' if throw_error: raise MinderError(err_message) logger.warning(err_message) return None
def get_value(self, name: str, throw_error: bool = True, **kwargs) -> Optional[Any]: if self.has_setting(name): return self.settings[name] if 'default' in kwargs: return kwargs['default'] if throw_error: raise MinderError( f'Invalid user setting value requested: "{name}"') return None
def __init__(self, redis_helper: RedisentHelper) -> None: self.redis_helper = redis_helper self.handlers = {} self.loaded_settings = {} for cls in SettingsHandler.__subclasses__(): cur_cls = cls(self) for name in cur_cls.handled_settings: if name in self.handlers: found_handler = self.handlers[name].handler_name cur_handler = cur_cls.handler_name raise MinderError( f'Found existing handler for "{name}" in handlers. Existing handler was "{found_handler}", this is "{cur_handler}"' ) self.handlers[name] = cur_cls
def build(cls, action: str, message: str, context: Mapping[str, Any] = None, use_timestamp: datetime = None) -> StatusEntry: action = action.upper() if action not in StatusEntryActions: raise MinderError( f'Invalid action value provided while building status entry: {action}. Must be one of "{", ".join(StatusEntryActions)}"' ) ent_kwargs: MutableMapping[str, Any] = { 'action': action, 'message': message } if context: ent_kwargs['context'] = context if use_timestamp: ent_kwargs['timestamp'] = use_timestamp return StatusEntry(**ent_kwargs)
def build(cls, provided_when: str, created_time: DateTimeType = None, use_timezone: TimezoneType = None) -> FuzzyTime: if use_timezone: if isinstance(use_timezone, pytz.BaseTzInfo): timezone = Timezone(timezone_name=use_timezone.zone, timezone=use_timezone) elif isinstance(use_timezone, str): if not Timezone.is_valid_timezone(use_timezone): raise MinderError( f'Invalid timezone provided: "{use_timezone}"') timezone = Timezone.build(use_timezone) else: timezone = use_timezone else: timezone = Timezone.build() tz = timezone.timezone kwargs: MutableMapping[str, Any] = { 'provided_when': provided_when, 'use_timezone': use_timezone } if created_time: if isinstance(created_time, ( int, float, )): c_time = datetime.fromtimestamp(created_time, tz) else: c_time = created_time.astimezone(tz) kwargs['created_time'] = c_time return FuzzyTime(**kwargs)
def build(cls, trigger_time: Union[FuzzyTime, str], member: AnyMemberType, content: str, channel: AnyChannelType = None, created_at: datetime = None, use_timezone: Union[str, Timezone] = None) -> Reminder: member_id, member_name = None, None channel_id, channel_name = None, None from_dm = False target_tz: Optional[Timezone] = None if use_timezone: if isinstance(use_timezone, str): if not Timezone.is_valid_timezone(use_timezone): raise MinderError( f'Invalid timezone provided: "{use_timezone}"') target_tz = Timezone.build(timezone_name=use_timezone) else: target_tz = use_timezone if isinstance(member, Mapping): member_id = int(member['id']) member_name = member['name'] else: member_id = member.id member_name = member.name if channel: if isinstance(channel, discord.abc.Messageable): channel_id = channel.id if isinstance(channel, discord.DMChannel): channel_name = f'DM {member_name}' from_dm = True else: channel_name = channel.name else: channel_id = channel['id'] channel_name = channel['name'] if not isinstance(trigger_time, FuzzyTime): trigger_time = FuzzyTime.build(provided_when=trigger_time, created_time=created_at, use_timezone=target_tz) created_ts = trigger_time.created_timestamp trigger_ts = trigger_time.resolved_timestamp provided_when = trigger_time.provided_when tz_name = target_tz.timezone_name if target_tz else 'UTC' return Reminder(created_ts=created_ts, trigger_ts=trigger_ts, member_id=member_id, member_name=member_name, channel_id=channel_id, channel_name=channel_name, provided_when=provided_when, content=content, from_dm=from_dm, timezone_name=tz_name)
def load(cls, yaml_file: str) -> BotConfig: """ Parses Bot config settings from provided YAML file Example YAML Config Reference: .. code-block:: :language yaml: defaults: admins: [1234567890] # Bot-wide admin member IDs extended_errors: False # Include traceback in errors ignore_other_guilds: True # Indicates that guilds not specified here are otherwise ignored users: 1234567890: # Owner user name: 'minder admin' is_admin: True # Indicates if the user is a Web UI admin 1111111111: # "My Guild" admin name: 'guild admin' is_admin: False 9876543210: # "regular user" name: 'user' is_admin: False guilds: 5678912345: name: 'My Guild' admins: [111111111] # Guild-only admins bot_channel: 333333333 # Bot admin channel extended_errors: True # Enable extended error output 8765432134: name: 'Other server' admins: [] # No guild admins bot_channel: 444444444 # "#bot-admin" """ if not os.path.exists(yaml_file): raise MinderError(f'No such bot config YAML file: "{yaml_file}"') try: with open(yaml_file, 'rt') as f: yaml_cfg = yaml.safe_load(f) except yaml.error.YAMLError as ex: raise MinderError( f'Error loading bot config YAML from "{yaml_file}": {ex}' ) from ex except Exception as ex: raise MinderError( f'General error while loading bot config from "{yaml_file}": {ex}' ) from ex cls_kwargs = {} if 'defaults' in yaml_cfg: def_cfg = yaml_cfg['defaults'] if 'admins' in def_cfg: cls_kwargs['admins'] = def_cfg['admins'] if 'extended_errors' in def_cfg: cls_kwargs['extended_errors'] = def_cfg['extended_errors'] if 'ignore_other_guilds' in def_cfg: cls_kwargs['ignore_other_guilds'] = def_cfg[ 'ignore_other_guilds'] if 'admins' in yaml_cfg: cls_kwargs['admins'] = yaml_cfg['admins'] if 'users' in yaml_cfg: cls_kwargs['users'] = yaml_cfg['users'] if 'guilds' in yaml_cfg: cls_kwargs['guilds'] = yaml_cfg['guilds'] return cls(**cls_kwargs)
def process_setting(self, name: str, value): if name not in self.handled_settings: raise MinderError( f'Unable to process setting for "{name}" (not supported)') return self.redis_helper.get('settings', redis_name=name)
def process_setting(self, name: str, value: str) -> Timezone: if name not in self.handled_settings: raise MinderError( f'Unable to process setting for "{name}" (not supported)') return Timezone.build(value)