示例#1
0
def test_save() -> None:
    """Unit Test"""
    random.seed()
    key = f"unitTest{random.randint(1000,10000)}"  # nosec

    config = ConfigFile("./tests/fixtures/mock_config.json")
    config.load()

    assert config.config

    assert key not in config.config.keys()
    assert config.create(key, "Test Value")
    assert config.save()

    assert key in config.config.keys()
    assert config.delete(key)
    assert config.save()

    assert key not in config.config.keys()
    assert config.config
示例#2
0
class ChatKudos:
    """Kudos points brought to Discord"""

    logger = logging.getLogger(__name__)

    def __init__(self,
                 client: Client,
                 config_file: str = DEFAULT_CONFIG) -> None:
        """Create instance and load configuration file"""
        self.logger.info("Initializing ChatKudos module")
        self.config = ConfigFile()
        self.config.load(config_file)
        if not self.config.config:
            self.config.create("module", MODULE_NAME)
            self.config.create("version", MODULE_VERSION)

    def get_guild(self, guild_id: str) -> KudosConfig:
        """Load a guild from the config, return defaults if empty"""
        self.logger.debug("Get guild '%s'", guild_id)
        guild_conf = self.config.read(guild_id)
        if not guild_conf:
            return KudosConfig()
        return KudosConfig.from_dict(guild_conf)

    def save_guild(self, guild_id: str, **kwargs: Any) -> None:
        """
        Save a guild entry. Any keyword excluded will save existing value.

        Keyword Args:
            roles: List[str], roles that can use when locked
            users: List[str], users that can use when locked
            max: int, max points granted in one line
            lock: bool, restict to `roles`/`users` or open to all
            gain_message: str, message displayed on gain of points
            loss_message: str, message displayed on loss of points
            scores: Dict[str, int], Discord user id paired with total Kudos
        """
        self.logger.debug("Save: %s, (%s)", guild_id, kwargs)
        guild_conf = self.get_guild(guild_id)
        new_conf = KudosConfig(
            roles=kwargs.get("roles", guild_conf.roles),
            users=kwargs.get("users", guild_conf.users),
            max=kwargs.get("max", guild_conf.max),
            lock=kwargs.get("lock", guild_conf.lock),
            gain_message=kwargs.get("gain_message", guild_conf.gain_message),
            loss_message=kwargs.get("loss_message", guild_conf.loss_message),
            scores=kwargs.get("scores", guild_conf.scores),
        )
        if not self.config.read(guild_id):
            self.config.create(guild_id, new_conf.as_dict())
        else:
            self.config.update(guild_id, new_conf.as_dict())

    def set_max(self, message: Message) -> str:
        """Set max number of points to be gained in one line"""
        self.logger.debug("Set %s max: %s", message.guild.name,
                          message.content)
        try:
            max_ = int(message.content.replace("kudos!max", ""))
        except ValueError:
            return "Usage: `kudo!max [N]` where N is a number."
        self.save_guild(str(message.guild.id), max=max_)
        return f"Max points now: {max_}" if max_ > 0 else "Max points now: unlimited"

    def set_gain(self, message: Message) -> str:
        """Update the gain message of a guild"""
        content = message.content.replace("kudos!gain", "").strip()
        return self._set_message(str(message.guild.id), "gain_message",
                                 content)

    def set_loss(self, message: Message) -> str:
        """Update the loss message of a guild"""
        content = message.content.replace("kudos!loss", "").strip()
        return self._set_message(str(message.guild.id), "loss_message",
                                 content)

    def _set_message(self, guild_id: str, key: str, content: Dict[str,
                                                                  str]) -> str:
        """Sets and saves gain/loss messages"""
        if content:
            self.save_guild(guild_id, **{key: content})
            return "Message has been set."
        return ""

    def set_lists(self, message: Message) -> str:
        """Update user and role lists based on message mentions"""
        changes: List[str] = self._set_users_list(message)
        changes.extend(self._set_roles_list(message))

        return "Allow list changes: " + ", ".join(changes) if changes else ""

    def _set_users_list(self, message: Message) -> List[str]:
        """Process and user mentions in message, return changes"""
        changes: List[str] = []
        users = set(self.get_guild(str(message.guild.id)).users)

        for mention in message.mentions:
            if str(mention.id) in users:
                users.remove(str(mention.id))
                changes.append(f"**-**{mention.display_name}")
            else:
                users.add(str(mention.id))
                changes.append(f"**+**{mention.display_name}")
        if changes:
            self.logger.error(changes)
            self.save_guild(str(message.guild.id), users=list(users))
        return changes

    def _set_roles_list(self, message: Message) -> List[str]:
        """Process all role mentions in message, return changes"""
        changes: List[str] = []
        roles = set(self.get_guild(str(message.guild.id)).roles)

        for role_mention in message.role_mentions:
            if str(role_mention.id) in roles:
                roles.remove(str(role_mention.id))
                changes.append(f"**-**{role_mention.name}")
            else:
                roles.add(str(role_mention.id))
                changes.append(f"**+**{role_mention.name}")
        if changes:
            self.save_guild(str(message.guild.id), roles=list(roles))
        return changes

    def set_lock(self, message: Message) -> str:
        """Toggle lock for guild"""
        new_lock = not self.get_guild(str(message.guild.id)).lock
        self.save_guild(str(message.guild.id), lock=new_lock)
        if new_lock:
            return "ChatKudos is now locked. Only allowed users/roles can use it!"
        return "ChatKudos is now unlocked. **Everyone** can use it!"

    def show_help(self, message: Message) -> str:
        """Help and self-plug, yay!"""
        self.logger.debug("Help: %s", message.author.name)
        return (
            "Detailed use instructions here: "
            "https://github.com/Preocts/eggbot/blob/main/docs/chatkudos.md")

    def generate_board(self, message: Message) -> str:
        """Create scoreboard"""
        self.logger.debug("Scoreboard: %s", message.content)
        try:
            count = int(message.content.replace("kudos!board", ""))
        except ValueError:
            count = 10
        guild_conf = self.get_guild(str(message.guild.id))
        # Make a list of keys (user IDs) sorted by their value (score) low to high
        id_list = sorted(guild_conf.scores,
                         key=lambda key: guild_conf.scores[key])
        score_list: List[str] = [f"Top {count} ChatKudos holders:", "```"]
        while count > 0 and id_list:
            user_id = id_list.pop()
            user = message.guild.get_member(int(user_id))
            display_name = user.display_name if user else user_id
            score_list.append("{:>5} | {:<38}".format(
                guild_conf.scores[user_id], display_name))
            count -= 1
        score_list.append("```")
        return "\n".join(score_list)

    def find_kudos(self, message: Message) -> List[Kudos]:
        """Process a chat-line for Kudos"""
        kudos_list: List[Kudos] = []

        for mention in message.mentions:
            kudos = self._calc_kudos(message, str(mention.id))
            if kudos is None:
                continue
            current = self.get_guild(str(message.guild.id)).scores.get(
                str(mention.id), 0)
            kudos_list.append(
                Kudos(str(mention.id), mention.display_name, kudos, current))
            self.logger.debug("Find Kudos: %s", kudos_list[-1])
        return kudos_list

    def _calc_kudos(self, message: Message, mention_id: str) -> Optional[int]:
        """Calculate the number of kudos given to a mention"""
        max_ = self.get_guild(str(message.guild.id)).max

        for idx, word in enumerate(message.content.split()):
            if not re.search(f"{mention_id}", word):
                continue
            try:
                next_word = message.content.split()[idx + 1]
            except IndexError:
                continue
            if "+" not in next_word and "-" not in next_word:
                continue

            point_change = next_word.count("+") - next_word.count("-")
            if max_ > 0 < point_change > max_:
                point_change = max_
            elif max_ > 0 > point_change < (max_ * -1):
                point_change = max_ * -1

            return point_change
        return None

    def apply_kudos(self, guild_id: str, kudos_list: List[Kudos]) -> None:
        """Update scores in config"""
        scores = self.get_guild(guild_id).scores
        for kudos in kudos_list:
            scores[kudos.user_id] = scores.get(kudos.user_id, 0) + kudos.amount

        self.save_guild(guild_id, scores=scores)

    def parse_command(self, message: Message) -> str:
        """Process all commands prefixed with 'kudos!'"""
        self.logger.debug("Parsing command: %s", message.content)
        command = message.content.split()[0]
        try:
            result = getattr(self, COMMAND_CONFIG[command])(message)
        except (AttributeError, KeyError):
            self.logger.error("'%s' attribute not found!", command)
            return ""
        return result

    def is_command_allowed(self, message: Message) -> bool:
        """Determine if author of message can run commands"""
        if str(message.author.id) == str(message.guild.owner.id):
            return True

        if str(message.author.id) in self.get_guild(str(
                message.guild.id)).users:
            return True

        return False

    def is_kudos_allowed(self, message: Message) -> bool:
        """Determine if author can grant kudos"""
        allowed_roles = self.get_guild(str(message.guild.id)).roles

        if not self.get_guild(str(message.guild.id)).lock:
            return True

        for role in message.author.roles:
            if str(role.id) in allowed_roles:
                return True

        return self.is_command_allowed(message)

    async def on_message(self, message: Message) -> None:
        """On Message event hook for bot"""
        if not message.content or str(message.channel.type) != "text":
            return

        tic = time.perf_counter()
        self.logger.debug("[START] onmessage - ChatKudos")

        if message.content.startswith("kudos!") and self.is_command_allowed(
                message):
            response = self.parse_command(message)
            if response:
                await message.channel.send(response)
                self.config.save()
            return

        if not (message.mentions and self.is_kudos_allowed(message)):
            return

        kudos_list = self.find_kudos(message)
        self.apply_kudos(str(message.guild.id), kudos_list)
        await self._announce_kudos(message, kudos_list)
        self.config.save()

        toc = time.perf_counter()
        self.logger.debug("[FINISH] onmessage: %f ms", round(toc - tic, 2))

    async def _announce_kudos(self, message: Message,
                              kudos_list: List[Kudos]) -> None:
        """Send any Kudos to the chat"""
        for kudos in kudos_list:
            if kudos.amount < 0:
                msg = self.get_guild(str(message.guild.id)).loss_message
            else:
                msg = self.get_guild(str(message.guild.id)).gain_message

            await message.channel.send(self._format_message(msg, kudos))

    @staticmethod
    def _format_message(content: str, kudos: Kudos) -> str:
        """Apply metadata replacements"""
        new_total = kudos.current + kudos.amount

        formatted_msg = content.replace("[POINTS]", str(kudos.amount))
        formatted_msg = formatted_msg.replace("[NAME]", kudos.display_name)
        formatted_msg = formatted_msg.replace("[TOTAL]", str(new_total))

        return formatted_msg
示例#3
0
class ShoulderBirdConfig:
    """Shoulder Bird Config class, CRUD config operations"""

    logger = logging.getLogger(__name__)

    def __init__(self, config_file: str = DEFAULT_CONFIG) -> None:
        """Init and load config"""
        self.logger.info("Initializing Shoulder Bird Parser")
        self.__configclient = ConfigFile()
        self.__configclient.load(config_file)
        if not self.__configclient.config:
            self.__configclient.create("module", MODULE_NAME)
            self.__configclient.create("version", MODULE_VERSION)

    def __load_guild(self, guild_id: str) -> Dict[str, Any]:
        """Load a specific guild from config. Will create guild if not found"""
        self.logger.debug("load_guild: '%s'", guild_id)
        if guild_id not in self.__configclient.config:
            self.__configclient.create(guild_id, {})
        return self.__configclient.read(guild_id)

    def __save_member_to_guild(self, guild_id: str,
                               member: BirdMember) -> None:
        """Save a specific member to a guild. Creates new or overwrites existing"""
        guild_config = self.__load_guild(guild_id)
        guild_config[member.member_id] = member.to_dict()
        self.__configclient.update(guild_id, guild_config)

    def reload_config(self) -> bool:
        """Reloads current config file without saving"""
        return self.__configclient.load()

    def save_config(self) -> bool:
        """Saves current config to file"""
        return self.__configclient.save()

    def member_list_all(self, member_id: str) -> List[BirdMember]:
        """Returns all configs for member across guilds, can return empty list"""
        self.logger.debug("member_list_all: '%s'", member_id)
        config_list = []
        for guild in self.__configclient.config.values():
            if member_id in guild:
                config_list.append(BirdMember(**guild[member_id]))
        return config_list

    def guild_list_all(self, guild_id: str) -> List[BirdMember]:
        """Returns all configs within a single guild, can return empty list"""
        self.logger.debug("guild_list_all: '%s'", guild_id)
        config_list = []
        for member in self.__load_guild(guild_id).values():
            config_list.append(BirdMember(**member))
        return config_list

    def load_member(self, guild_id: str, member_id: str) -> BirdMember:
        """Load a member from a guild. Will return empty member if not found"""
        self.logger.debug("load_member: '%s', '%s'", guild_id, member_id)
        member = self.__load_guild(guild_id).get(member_id)
        return BirdMember(
            **member) if member else BirdMember(guild_id, member_id)

    def save_member(self, guild_id: str, member_id: str,
                    **kwargs: Any) -> BirdMember:
        """Save (creating or updating) a member to a guild

        Keyword Args:
            regex [str] : Regular expression
            toggle [bool] : True if config is active, False if inactive
            ignore Set[str] : Set of member IDs to ignore. Can be empty
        """
        self.logger.debug("save_member: '%s', '%s', '%s'", guild_id, member_id,
                          kwargs)
        member_config = self.load_member(guild_id, member_id)
        member_config.regex = kwargs.get("regex", member_config.regex)
        member_config.toggle = kwargs.get("toggle", member_config.toggle)
        member_config.ignore = kwargs.get("ignore", member_config.ignore)
        self.__save_member_to_guild(guild_id, member_config)
        return member_config

    def delete_member(self, guild_id: str, member_id: str) -> bool:
        """Deletes member from specific guild, returns false if not found"""
        self.logger.debug("delete_member: '%s', '%s'", guild_id, member_id)
        guild_config = self.__load_guild(guild_id)
        deleted_value = guild_config.pop(member_id, None)
        if deleted_value:
            self.__configclient.update(guild_id, guild_config)
        return bool(deleted_value)