Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
 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
Ejemplo n.º 6
0
    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()
Ejemplo n.º 7
0
    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'])
Ejemplo n.º 8
0
    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()
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
    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)
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
    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)