From 106d4a6fcbda21d24c11f4e16cfa5f4676a9d2e0 Mon Sep 17 00:00:00 2001 From: Stephen <48072084+StephenDaDev@users.noreply.github.com> Date: Sun, 28 Jul 2019 22:06:53 -0400 Subject: [PATCH 01/13] Fix Expired Invite (#334) --- SPONSORS.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPONSORS.json b/SPONSORS.json index 989f1dfdfd..5f79ff80ea 100644 --- a/SPONSORS.json +++ b/SPONSORS.json @@ -21,7 +21,7 @@ }, { "name": "Discord Server!", - "value": "[**Click Here**](https://discord.gg/s8Ddphx)" + "value": "[**Click Here**](https://discord.gg/V8ErqHb)" } ] } From b4b9ec56d25c583de78196b2640d980d1d7d147d Mon Sep 17 00:00:00 2001 From: Kyber Date: Mon, 29 Jul 2019 13:39:08 +1000 Subject: [PATCH 02/13] Add dockerfile --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..d33f146c9e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM kennethreitz/pipenv + +COPY . /app + +CMD python3 bot.py From 52b5b2b4b2502ba102dcb7df43747ac410b1e95f Mon Sep 17 00:00:00 2001 From: KongGal Date: Mon, 29 Jul 2019 13:57:44 +0200 Subject: [PATCH 03/13] Updated Dockerfile to allow for automated building (#335) --- Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index d33f146c9e..773031d731 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ -FROM kennethreitz/pipenv +FROM library/python:latest +RUN apt update && apt install -y pipenv +RUN mkdir -p /bot && cd /bot && git clone https://github.com/kyb3r/modmail . +WORKDIR /bot +RUN pipenv install -COPY . /app - -CMD python3 bot.py +CMD ["pipenv", "run", "python3", "bot.py"] From af904218d129beb5167b3ed0f3ac0669c2cf5814 Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Mon, 29 Jul 2019 12:09:27 -0700 Subject: [PATCH 04/13] Fix stuff with viewing perm --- CHANGELOG.md | 4 ++-- cogs/modmail.py | 2 +- cogs/utility.py | 19 +++++++++---------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47934e28ff..8d57ffdc9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -225,7 +225,7 @@ Un-deprecated the `OWNERS` config variable to support discord developer team acc ### New Permissions System - A brand new permission system! Replacing the old guild-based permissions (ie. manage channels, manage messages), the new system enables you to customize your desired permission level specific to a command or a group of commands for a role or user. -- There are five permission groups/levels: +- There are five permission levels: - Owner [5] - Administrator [4] - Moderator [3] @@ -247,7 +247,7 @@ The same applies to individual commands permissions: To revoke permission, use `remove` instead of `add`. -To view all roles and users with permission for a permission group or command do: +To view all roles and users with permission for a permission level or command do: - `?permissions get command command-name` - `?permissions get level owner` diff --git a/cogs/modmail.py b/cogs/modmail.py index 7949688577..c29cfec308 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -87,7 +87,7 @@ async def setup(self, ctx): await self.bot.config.update() await ctx.send( "Successfully set up server.\n" - "Consider setting permission groups to give access " + "Consider setting permission levels to give access " "to roles or users the ability to use Modmail.\n" f"Type `{self.bot.prefix}permissions` for more info." ) diff --git a/cogs/utility.py b/cogs/utility.py index 1252341d89..4f7d17a502 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -220,9 +220,9 @@ async def send_error_message(self, error): choices = set() - for name, cmd in self.context.bot.all_commands.items(): + for cmd in self.context.bot.walk_commands(): if not cmd.hidden: - choices.add(name) + choices.add(cmd.name) closest = get_close_matches(command, choices) if closest: @@ -1221,7 +1221,7 @@ def _verify_user_or_role(user_or_role): @permissions.command(name="add", usage="[command/level] [name] [user_or_role]") @checks.has_permissions(PermissionLevel.OWNER) async def permissions_add( - self, ctx, type_: str.lower, name: str, *, user_or_role: Union[User, Role, str] + self, ctx, type_: str.lower, name: str, *, user_or_role: Union[Role, User, str] ): """ Add a permission to a command or a permission level. @@ -1279,7 +1279,7 @@ async def permissions_add( ) @checks.has_permissions(PermissionLevel.OWNER) async def permissions_remove( - self, ctx, type_: str.lower, name: str, *, user_or_role: Union[User, Role, str] + self, ctx, type_: str.lower, name: str, *, user_or_role: Union[Role, User, str] ): """ Remove permission to use a command or permission level. @@ -1300,8 +1300,7 @@ async def permissions_remove( level = None if type_ == "command": - command = self.bot.get_command(name.lower()) - name = command.qualified_name if command is not None else name + name = getattr(self.bot.get_command(name.lower()), "qualified_name", name) else: if name.upper() not in PermissionLevel.__members__: embed = Embed( @@ -1364,7 +1363,7 @@ def _get_perm(self, ctx, name, type_): @permissions.command(name="get", usage="[@user] or [command/level] [name]") @checks.has_permissions(PermissionLevel.OWNER) async def permissions_get( - self, ctx, user_or_role: Union[User, Role, str], *, name: str = None + self, ctx, user_or_role: Union[Role, User, str], *, name: str = None ): """ View the currently-set permissions. @@ -1388,7 +1387,7 @@ async def permissions_get( levels = [] done = set() - for _, command in self.bot.all_commands.items(): + for command in self.bot.walk_commands(): if command not in done: done.add(command) permissions = self.bot.config["command_permissions"].get( @@ -1421,7 +1420,7 @@ async def permissions_get( color=self.bot.main_color, ), Embed( - title=f"{mention} has permission with the following permission groups:", + title=f"{mention} has permission with the following permission levels:", description=desc_level, color=self.bot.main_color, ), @@ -1457,7 +1456,7 @@ async def permissions_get( else: if user_or_role == "command": done = set() - for _, command in self.bot.all_commands.items(): + for command in self.bot.walk_commands(): if command not in done: done.add(command) embeds.append( From c596f1c48b8c6f21e61c64b4446d9625fd54deb7 Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Mon, 29 Jul 2019 12:50:22 -0700 Subject: [PATCH 05/13] 1-5 as level names --- CHANGELOG.md | 1 + cogs/utility.py | 38 +++++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d57ffdc9d..ee45406fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ however, insignificant breaking changes does not guarantee a major version bump, - `?help` works for alias and snippets. - `?config help ` shows a help embed for the configuration. - Support setting permissions for sub commands. +- Support numbers (1-5) as substitutes for Permission Level REGULAR - OWNER in `?perms` sub commands. ### Changes diff --git a/cogs/utility.py b/cogs/utility.py index 4f7d17a502..62d02ebeba 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -1218,6 +1218,22 @@ def _verify_user_or_role(user_or_role): else: raise commands.BadArgument(f'User or Role "{user_or_role}" not found') + @staticmethod + def _parse_level(name): + name = name.upper() + try: + return PermissionLevel[name] + except KeyError: + pass + transform = { + "1": PermissionLevel.REGULAR, + "2": PermissionLevel.SUPPORTER, + "3": PermissionLevel.MODERATOR, + "4": PermissionLevel.ADMINISTRATOR, + "5": PermissionLevel.OWNER, + } + return transform.get(name) + @permissions.command(name="add", usage="[command/level] [name] [user_or_role]") @checks.has_permissions(PermissionLevel.OWNER) async def permissions_add( @@ -1246,8 +1262,8 @@ async def permissions_add( command = self.bot.get_command(name.lower()) check = command is not None else: - check = name.upper() in PermissionLevel.__members__ - level = PermissionLevel[name.upper()] if check else None + level = self._parse_level(name) + check = level is not None if not check: embed = Embed( @@ -1302,14 +1318,14 @@ async def permissions_remove( if type_ == "command": name = getattr(self.bot.get_command(name.lower()), "qualified_name", name) else: - if name.upper() not in PermissionLevel.__members__: + level = self._parse_level(name) + if level is None: embed = Embed( title="Error", color=Color.red(), description=f"The referenced {type_} does not exist: `{name}`.", ) return await ctx.send(embed=embed) - level = PermissionLevel[name.upper()] name = level.name value = self._verify_user_or_role(user_or_role) @@ -1370,9 +1386,15 @@ async def permissions_get( To find a list of permission levels, see `{prefix}help perms`. + To view all command and level permissions: + Examples: - `{prefix}perms get @user` - `{prefix}perms get 984301093849028` + + To view all users and roles of a command or level permission: + + Examples: - `{prefix}perms get command reply` - `{prefix}perms get command plugin remove` - `{prefix}perms get level SUPPORTER` @@ -1401,7 +1423,9 @@ async def permissions_get( if value in permissions: levels.append(level.name) - mention = getattr(user_or_role, "name", user_or_role) + mention = getattr( + user_or_role, "name", getattr(user_or_role, "id", user_or_role) + ) desc_cmd = ( ", ".join(map(lambda x: f"`{x}`", cmds)) if cmds @@ -1436,8 +1460,8 @@ async def permissions_get( command = self.bot.get_command(name.lower()) check = command is not None else: - check = name.upper() in PermissionLevel.__members__ - level = PermissionLevel[name.upper()] if check else None + level = self._parse_level(name) + check = level is not None if not check: embed = Embed( From 3a07329b79941832bb1370b21dc0233f478ffe7c Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Tue, 30 Jul 2019 17:25:19 -0700 Subject: [PATCH 06/13] Add doc for snippet raw --- cogs/modmail.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index c29cfec308..32d716ec89 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -117,7 +117,7 @@ async def snippet(self, ctx, *, name: str.lower = None): with `{prefix}snippet-name`, the message "A pre-defined text." will be sent to the recipient. - Currently, there is not a default anonymous snippet command; however, a workaround + Currently, there is not a built-in anonymous snippet command; however, a workaround is available using `{prefix}alias`. Here is how: - `{prefix}alias add snippet-name anonreply A pre-defined anonymous text.` @@ -165,6 +165,9 @@ async def snippet(self, ctx, *, name: str.lower = None): @snippet.command(name="raw") @checks.has_permissions(PermissionLevel.SUPPORTER) async def snippet_raw(self, ctx, *, name: str.lower): + """ + View the raw content of a snippet. + """ val = self.bot.snippets.get(name) if val is None: embed = create_not_found_embed(name, self.bot.snippets.keys(), "Snippet") From 92fad07af76c6d89c2b8a81ec0b6d078df04cb38 Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Tue, 30 Jul 2019 17:39:27 -0700 Subject: [PATCH 07/13] Fix log url prefix Signed-off-by: Taaku18 <45324516+Taaku18@users.noreply.github.com> --- cogs/modmail.py | 12 ++++-------- core/clients.py | 10 ++++++++-- core/thread.py | 10 ++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cogs/modmail.py b/cogs/modmail.py index 32d716ec89..dc4368160c 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -570,16 +570,12 @@ def format_log_embeds(self, logs, avatar_url): title = f"Total Results Found ({len(logs)})" for entry in logs: - - key = entry["key"] - created_at = parser.parse(entry["created_at"]) - prefix = self.bot.config["log_url_prefix"] - if prefix == "NONE": - prefix = "" - - log_url = self.bot.config["log_url"].strip("/") + f"{prefix}/{key}" + prefix = self.bot.config['log_url_prefix'].strip('/') + if prefix == 'NONE': + prefix = '' + log_url = f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{entry['key']}" username = entry["recipient"]["name"] + "#" username += entry["recipient"]["discriminator"] diff --git a/core/clients.py b/core/clients.py index a4ed847622..31a73be390 100644 --- a/core/clients.py +++ b/core/clients.py @@ -99,7 +99,10 @@ async def get_log(self, channel_id: Union[str, int]) -> dict: async def get_log_link(self, channel_id: Union[str, int]) -> str: doc = await self.get_log(channel_id) logger.debug("Retrieving log link for channel %s.", channel_id) - return f"{self.bot.config['log_url'].strip('/')}{self.bot.config['log_url_prefix']}/{doc['key']}" + prefix = self.bot.config['log_url_prefix'].strip('/') + if prefix == 'NONE': + prefix = '' + return f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{doc['key']}" async def create_log_entry( self, recipient: Member, channel: TextChannel, creator: Member @@ -135,7 +138,10 @@ async def create_log_entry( } ) logger.debug("Created a log entry, key %s.", key) - return f"{self.bot.config['log_url'].strip('/')}{self.bot.config['log_url_prefix']}/{key}" + prefix = self.bot.config['log_url_prefix'].strip('/') + if prefix == 'NONE': + prefix = '' + return f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{key}" async def get_config(self) -> dict: conf = await self.db.config.find_one({"bot_id": self.bot.user.id}) diff --git a/core/thread.py b/core/thread.py index 12bb71ab3c..7e8a2574db 100644 --- a/core/thread.py +++ b/core/thread.py @@ -338,12 +338,10 @@ async def _close( ) if isinstance(log_data, dict): - prefix = self.bot.config["log_url_prefix"] - if prefix == "NONE": - prefix = "" - log_url = ( - f"{self.bot.config['log_url'].strip('/')}{prefix}/{log_data['key']}" - ) + prefix = self.bot.config['log_url_prefix'].strip('/') + if prefix == 'NONE': + prefix = '' + log_url = f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{log_data['key']}" if log_data["messages"]: content = str(log_data["messages"][0]["content"]) From 5a26c28900616ca2958376c4f0e7bbf723957472 Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Tue, 30 Jul 2019 22:07:52 -0700 Subject: [PATCH 08/13] Config help on protected keys --- CHANGELOG.md | 1 + cogs/modmail.py | 6 ++-- cogs/utility.py | 18 ++++++----- core/config_help.json | 72 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee45406fb2..49d41d382f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ however, insignificant breaking changes does not guarantee a major version bump, - `disable_recipient_thread_close` is removed, a new configuration variable `recipient_thread_close` replaces it which defaults to False. - Truthy and falsy values for binary configuration variables are now interpreted respectfully. +- `LOG_URL_PREFIX` cannot be set to "NONE" to specify no additional path in the future, "/" is the new method. ### Added diff --git a/cogs/modmail.py b/cogs/modmail.py index dc4368160c..693adafb18 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -572,9 +572,9 @@ def format_log_embeds(self, logs, avatar_url): for entry in logs: created_at = parser.parse(entry["created_at"]) - prefix = self.bot.config['log_url_prefix'].strip('/') - if prefix == 'NONE': - prefix = '' + prefix = self.bot.config["log_url_prefix"].strip("/") + if prefix == "NONE": + prefix = "" log_url = f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{entry['key']}" username = entry["recipient"]["name"] + "#" diff --git a/cogs/utility.py b/cogs/utility.py index 62d02ebeba..672d8f3448 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -844,8 +844,9 @@ async def config_get(self, ctx, key: str.lower = None): color=Color.red(), description=f"`{key}` is an invalid key.", ) - valid_keys = [f"`{k}`" for k in keys] - embed.add_field(name="Valid keys", value=", ".join(valid_keys)) + embed.set_footer( + text=f'Type "{self.bot.prefix}config options" for a list of config variables.' + ) else: embed = Embed( @@ -870,7 +871,9 @@ async def config_help(self, ctx, key: str.lower): """ Show information on a specified configuration. """ - if key not in self.bot.config.public_keys: + if not ( + key in self.bot.config.public_keys or key in self.bot.config.protected_keys + ): embed = Embed( title="Error", color=Color.red(), @@ -904,10 +907,11 @@ def fmt(val): embed.add_field( name="Information:", value=fmt(info["description"]), inline=False ) - example_text = "" - for example in info["examples"]: - example_text += f"- {fmt(example)}\n" - embed.add_field(name="Example(s):", value=example_text, inline=False) + if info["examples"]: + example_text = "" + for example in info["examples"]: + example_text += f"- {fmt(example)}\n" + embed.add_field(name="Example(s):", value=example_text, inline=False) note_text = "" for note in info["notes"]: diff --git a/core/config_help.json b/core/config_help.json index 37106f286a..f4e8eae36f 100644 --- a/core/config_help.json +++ b/core/config_help.json @@ -373,5 +373,77 @@ "See also: `anon_avatar_url`, `anon_username`, `mod_tag`." ], "image": "https://i.imgur.com/SKOC42Z.png" + }, + "modmail_guild_id": { + "default": "Fallback on `GUILD_ID`", + "description": "The ID of the discord server where the threads channels should be created (receiving server).", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file or environment (config) variables." + ] + }, + "guild_id": { + "default": "None, required", + "description": "The ID of the discord server where recipient users reside (users server).", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file or environment (config) variables." + ] + }, + "log_url": { + "default": "https://example.com/", + "description": "The base log viewer URL link, leave this as-is to not configure a log viewer.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file or environment (config) variables." + ] + }, + "log_url_prefix": { + "default": "`/logs`", + "description": "The path to your log viewer extending from your `LOG_URL`, set this to `/` to specify no extra path to the log viewer.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file or environment (config) variables." + ] + }, + "mongo_uri": { + "default": "None, required", + "description": "A MongoDB SRV connection string.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file or environment (config) variables." + ] + }, + "owners": { + "default": "None, required", + "description": "A list of definite bot owners, use `{prefix}perms add level OWNER @user` to set flexible bot owners.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file, ~~`config.json` file~~ (removed), or environment (config) variables." + ] + }, + "token": { + "default": "None, required", + "description": "Your bot token as found in the Discord Developer Portal.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file, ~~`config.json` file~~ (removed), or environment (config) variables." + ] + }, + "log_level": { + "default": "INFO", + "description": "The logging level for logging to stdout.", + "examples": [ + ], + "notes": [ + "This configuration can only to be set through `.env` file, ~~`config.json` file~~ (removed), or environment (config) variables." + ] } } \ No newline at end of file From 30a6c7f6bc6547f25450ec09209df60e2251f53c Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Wed, 31 Jul 2019 01:51:03 -0700 Subject: [PATCH 09/13] Added raw alias --- .pylintrc | 2 +- CHANGELOG.md | 1 + app.json | 4 ---- cogs/modmail.py | 11 +++-------- cogs/plugins.py | 2 +- cogs/utility.py | 44 +++++++++++++++++++++++++------------------- core/checks.py | 4 ++-- core/config.py | 2 -- core/thread.py | 19 +++++++++---------- core/utils.py | 12 ++++++++++-- 10 files changed, 52 insertions(+), 49 deletions(-) diff --git a/.pylintrc b/.pylintrc index 1bed8185c7..a45837fa82 100644 --- a/.pylintrc +++ b/.pylintrc @@ -19,7 +19,7 @@ ignore-patterns= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. -jobs=1 +jobs=0 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d41d382f..f904043075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ however, insignificant breaking changes does not guarantee a major version bump, - `?plugin registry page-number` plugin registry can specify a page number for quick access. - A reworked interface for `?snippet` and `?alias`. - Add an `?snippet raw ` command for viewing the raw content of a snippet (escaped markdown). + - Add an `?alias raw ` command for viewing the raw content of a alias (escaped markdown). - The placeholder channel for the streaming status changed to https://www.twitch.tv/discordmodmail/. - Removed unclear `rm` alias for some `remove` commands. - Paginate `?config options`. diff --git a/app.json b/app.json index 8d16cd75c2..e4f5032e81 100644 --- a/app.json +++ b/app.json @@ -15,10 +15,6 @@ "description": "Comma separated user IDs of people that are allowed to use owner only commands. (eval and update).", "required": true }, - "GITHUB_ACCESS_TOKEN": { - "description": "Your personal access token for GitHub, adding this gives you the ability to use the 'update' command, which will sync your fork with the main repo.", - "required": false - }, "MONGO_URI": { "description": "Mongo DB connection URI for self-hosting your data.", "required": true diff --git a/cogs/modmail.py b/cogs/modmail.py index 693adafb18..081385d202 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -1,7 +1,7 @@ import asyncio import logging from datetime import datetime -from itertools import zip_longest, takewhile +from itertools import zip_longest from typing import Optional, Union from types import SimpleNamespace as param @@ -17,7 +17,7 @@ from core.models import PermissionLevel from core.paginator import PaginatorSession from core.time import UserFriendlyTime, human_timedelta -from core.utils import format_preview, User, create_not_found_embed +from core.utils import format_preview, User, create_not_found_embed, format_description logger = logging.getLogger("Modmail") @@ -149,12 +149,7 @@ async def snippet(self, ctx, *, name: str.lower = None): for i, names in enumerate( zip_longest(*(iter(sorted(self.bot.snippets)),) * 15) ): - description = "\n".join( - ": ".join((str(a + i * 15), b)) - for a, b in enumerate( - takewhile(lambda x: x is not None, names), start=1 - ) - ) + description = format_description(i, names) embed = discord.Embed(color=self.bot.main_color, description=description) embed.set_author(name="Snippets", icon_url=ctx.guild.icon_url) embeds.append(embed) diff --git a/cogs/plugins.py b/cogs/plugins.py index 6ed1dccf1b..ebb9a72241 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -288,7 +288,7 @@ async def plugin_remove(self, ctx, *, plugin_name: str): for i in self.bot.config["plugins"] ): # if there are no more of such repos, delete the folder - def onerror(func, path, exc_info): # pylint: disable=W0613 + def onerror(func, path, _): if not os.access(path, os.W_OK): # Is the error an access error? os.chmod(path, stat.S_IWUSR) diff --git a/cogs/utility.py b/cogs/utility.py index 672d8f3448..b1fe7aa7b3 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -33,6 +33,7 @@ get_perm_level, create_not_found_embed, parse_alias, + format_description, ) logger = logging.getLogger("Modmail") @@ -248,11 +249,10 @@ def __init__(self, bot): verify_checks=False, command_attrs={"help": "Shows this help message."} ) # Looks a bit ugly - # noinspection PyProtectedMember - self.bot.help_command._command_impl = checks.has_permissions( # pylint: disable=W0212 + self.bot.help_command._command_impl = checks.has_permissions( # pylint: disable=protected-access PermissionLevel.REGULAR )( - self.bot.help_command._command_impl # pylint: disable=W0212 + self.bot.help_command._command_impl # pylint: disable=protected-access ) self.bot.help_command.cog = self @@ -998,12 +998,7 @@ async def alias(self, ctx, *, name: str.lower = None): embeds = [] for i, names in enumerate(zip_longest(*(iter(sorted(self.bot.aliases)),) * 15)): - description = "\n".join( - ": ".join((str(a + i * 15), b)) - for a, b in enumerate( - takewhile(lambda x: x is not None, names), start=1 - ) - ) + description = format_description(i, names) embed = Embed(color=self.bot.main_color, description=description) embed.set_author(name="Command Aliases", icon_url=ctx.guild.icon_url) embeds.append(embed) @@ -1011,6 +1006,18 @@ async def alias(self, ctx, *, name: str.lower = None): session = PaginatorSession(ctx, *embeds) await session.run() + @alias.command(name="raw") + @checks.has_permissions(PermissionLevel.MODERATOR) + async def alias_raw(self, ctx, *, name: str.lower): + """ + View the raw content of an alias. + """ + val = self.bot.aliases.get(name) + if val is None: + embed = create_not_found_embed(name, self.bot.aliases.keys(), "Alias") + return await ctx.send(embed=embed) + return await ctx.send(escape_markdown(escape_mentions(val)).replace("<", "\\<")) + @alias.command(name="add") @checks.has_permissions(PermissionLevel.MODERATOR) async def alias_add(self, ctx, name: str.lower, *, value): @@ -1027,36 +1034,36 @@ async def alias_add(self, ctx, name: str.lower, *, value): - This will fail: `{prefix}alias add reply You'll need to type && to work` - Correct method: `{prefix}alias add reply "You'll need to type && to work"` """ + embed = None if self.bot.get_command(name): embed = Embed( title="Error", color=Color.red(), description=f"A command with the same name already exists: `{name}`.", ) - return await ctx.send(embed=embed) - if name in self.bot.aliases: + elif name in self.bot.aliases: embed = Embed( title="Error", color=Color.red(), description=f"Another alias with the same name already exists: `{name}`.", ) - return await ctx.send(embed=embed) - if name in self.bot.snippets: + elif name in self.bot.snippets: embed = Embed( title="Error", color=Color.red(), description=f"A snippet with the same name already exists: `{name}`.", ) - return await ctx.send(embed=embed) - if len(name) > 120: + elif len(name) > 120: embed = Embed( title="Error", color=Color.red(), description=f"Alias names cannot be longer than 120 characters.", ) + + if embed is not None: return await ctx.send(embed=embed) values = parse_alias(value) @@ -1106,7 +1113,7 @@ async def alias_add(self, ctx, name: str.lower, *, value): return await ctx.send(embed=embed) embed.description += f"\n{i}: {val}" - self.bot.aliases[name] = "&&".join(values) + self.bot.aliases[name] = " && ".join(values) await self.bot.config.update() return await ctx.send(embed=embed) @@ -1217,10 +1224,9 @@ async def permissions(self, ctx): def _verify_user_or_role(user_or_role): if hasattr(user_or_role, "id"): return user_or_role.id - elif user_or_role in {"everyone", "all"}: + if user_or_role in {"everyone", "all"}: return -1 - else: - raise commands.BadArgument(f'User or Role "{user_or_role}" not found') + raise commands.BadArgument(f'User or Role "{user_or_role}" not found') @staticmethod def _parse_level(name): diff --git a/core/checks.py b/core/checks.py index 3c7f8b701f..503877b3a7 100644 --- a/core/checks.py +++ b/core/checks.py @@ -33,7 +33,7 @@ async def predicate(ctx): if not has_perm and ctx.command.qualified_name != "help": logger.error( - "You does not have permission to use this command: `%s` (%s).", + "You do not have permission to use this command: `%s` (%s).", str(ctx.command.qualified_name), str(permission_level.name), ) @@ -43,7 +43,7 @@ async def predicate(ctx): return commands.check(predicate) -async def check_permissions( # pylint: disable=R0911 +async def check_permissions( # pylint: disable=too-many-return-statements ctx, command_name, permission_level ) -> bool: """Logic for checking permissions for a command for a user""" diff --git a/core/config.py b/core/config.py index ae0a1f0a3e..e7776cb39d 100644 --- a/core/config.py +++ b/core/config.py @@ -3,7 +3,6 @@ import logging import os import typing -from collections import namedtuple from copy import deepcopy from dotenv import load_dotenv @@ -155,7 +154,6 @@ def populate_cache(self) -> dict: os.path.dirname(os.path.abspath(__file__)), "config_help.json" ) with open(config_help_json, "r") as f: - Entry = namedtuple("Entry", ["index", "embed"]) self.config_help = dict(sorted(json.load(f).items())) return self._cache diff --git a/core/thread.py b/core/thread.py index 7e8a2574db..95eeadc3dd 100644 --- a/core/thread.py +++ b/core/thread.py @@ -338,9 +338,9 @@ async def _close( ) if isinstance(log_data, dict): - prefix = self.bot.config['log_url_prefix'].strip('/') - if prefix == 'NONE': - prefix = '' + prefix = self.bot.config["log_url_prefix"].strip("/") + if prefix == "NONE": + prefix = "" log_url = f"{self.bot.config['log_url'].strip('/')}{'/' + prefix if prefix else ''}/{log_data['key']}" if log_data["messages"]: @@ -410,7 +410,9 @@ async def _close( await asyncio.gather(*tasks) async def cancel_closure( - self, auto_close: bool = False, all: bool = False # pylint: disable=W0622 + self, + auto_close: bool = False, + all: bool = False, # pylint: disable=redefined-builtin ) -> None: if self.close_task is not None and (not auto_close or all): self.close_task.cancel() @@ -746,8 +748,7 @@ async def send( file_upload_count += 1 if from_mod: - # noinspection PyUnresolvedReferences,PyDunderSlots - embed.color = self.bot.mod_color # pylint: disable=E0237 + embed.colour = self.bot.mod_color # Anonymous reply sent in thread channel if anonymous and isinstance(destination, discord.TextChannel): embed.set_footer(text="Anonymous Reply") @@ -760,12 +761,10 @@ async def send( else: embed.set_footer(text=self.bot.config["anon_tag"]) elif note: - # noinspection PyUnresolvedReferences,PyDunderSlots - embed.color = discord.Color.blurple() # pylint: disable=E0237 + embed.colour = discord.Color.blurple() else: embed.set_footer(text=f"Recipient") - # noinspection PyUnresolvedReferences,PyDunderSlots - embed.color = self.bot.recipient_color # pylint: disable=E0237 + embed.colour = self.bot.recipient_color try: await destination.trigger_typing() diff --git a/core/utils.py b/core/utils.py index 024c6e0f03..89f9b55f81 100644 --- a/core/utils.py +++ b/core/utils.py @@ -2,7 +2,8 @@ import shlex import typing from difflib import get_close_matches -from distutils.util import strtobool as _stb # pylint: disable=E0401 +from distutils.util import strtobool as _stb +from itertools import takewhile from urllib import parse import discord @@ -37,7 +38,7 @@ async def convert(self, ctx, argument): return discord.Object(int(match.group(1))) -def truncate(text: str, max: int = 50) -> str: # pylint: disable=W0622 +def truncate(text: str, max: int = 50) -> str: # pylint: disable=redefined-builtin """ Reduces the string to `max` length, by trimming the message into "...". @@ -253,3 +254,10 @@ def parse_alias(alias): if not all(cmd): return [] return cmd + + +def format_description(i, names): + return "\n".join( + ": ".join((str(a + i * 15), b)) + for a, b in enumerate(takewhile(lambda x: x is not None, names), start=1) + ) From c89ce1495f2e7ce5bdae2167347722f11c7afb7d Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Wed, 31 Jul 2019 23:48:00 -0600 Subject: [PATCH 10/13] Emoji wasn't showing cuz im dumb --- core/paginator.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/paginator.py b/core/paginator.py index 2fb72da8ff..a19db604b7 100644 --- a/core/paginator.py +++ b/core/paginator.py @@ -194,6 +194,12 @@ async def close(self, delete: bool = True) -> typing.Optional[Message]: """ self.running = False + sent_emoji, _ = await self.ctx.bot.retrieve_emoji() + try: + await self.ctx.message.add_reaction(sent_emoji) + except (HTTPException, InvalidArgument): + pass + if delete: return await self.base.delete() @@ -202,12 +208,6 @@ async def close(self, delete: bool = True) -> typing.Optional[Message]: except HTTPException: pass - sent_emoji, _ = await self.ctx.bot.retrieve_emoji() - try: - await self.ctx.message.add_reaction(sent_emoji) - except (HTTPException, InvalidArgument): - pass - async def first_page(self) -> None: """ Go to the first page. @@ -391,7 +391,11 @@ async def close(self, delete: bool = True) -> typing.Optional[Message]: """ self.running = False - self.ctx.bot.loop.create_task(self.ctx.message.add_reaction("✅")) + sent_emoji, _ = await self.ctx.bot.retrieve_emoji() + try: + await self.ctx.message.add_reaction(sent_emoji) + except (HTTPException, InvalidArgument): + pass if delete: return await self.base.delete() From 2cb9fdcb296bb20a7c8a273b19fd94db40b59c89 Mon Sep 17 00:00:00 2001 From: Kyber Date: Fri, 2 Aug 2019 18:53:49 +1000 Subject: [PATCH 11/13] Remove sponsor --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 5776675b28..d887601646 100644 --- a/README.md +++ b/README.md @@ -99,12 +99,6 @@ Sepcial thanks to our sponsors for supporting the project. - - - - - - Become a [sponsor](https://patreon.com/kyber). From bef1437930140dd1944e46bd1fee65abde05829c Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Fri, 2 Aug 2019 21:41:34 -0600 Subject: [PATCH 12/13] Paginator change --- CHANGELOG.md | 3 +- cogs/modmail.py | 13 ++- cogs/plugins.py | 6 +- cogs/utility.py | 24 ++-- core/paginator.py | 274 ++++++++++------------------------------------ 5 files changed, 81 insertions(+), 239 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f904043075..da6784d12c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,7 +66,8 @@ however, insignificant breaking changes does not guarantee a major version bump, - Use discord tasks for metadata loop. - More debug based logging. - Reduce redundancies in `?perms` sub commands. - +- paginator been split into `EmbedPaginatorSession` and `MessagePaginatorSession`, both subclassing `PaginatorSession`. + # v3.0.3 ### Added diff --git a/cogs/modmail.py b/cogs/modmail.py index 081385d202..c3750d7900 100644 --- a/cogs/modmail.py +++ b/cogs/modmail.py @@ -15,7 +15,7 @@ from core import checks from core.decorators import trigger_typing from core.models import PermissionLevel -from core.paginator import PaginatorSession +from core.paginator import EmbedPaginatorSession from core.time import UserFriendlyTime, human_timedelta from core.utils import format_preview, User, create_not_found_embed, format_description @@ -154,7 +154,7 @@ async def snippet(self, ctx, *, name: str.lower = None): embed.set_author(name="Snippets", icon_url=ctx.guild.icon_url) embeds.append(embed) - session = PaginatorSession(ctx, *embeds) + session = EmbedPaginatorSession(ctx, *embeds) await session.run() @snippet.command(name="raw") @@ -631,7 +631,7 @@ async def logs(self, ctx, *, user: User = None): embeds = self.format_log_embeds(logs, avatar_url=icon_url) - session = PaginatorSession(ctx, *embeds) + session = EmbedPaginatorSession(ctx, *embeds) await session.run() @logs.command(name="closed-by", aliases=["closeby"]) @@ -664,7 +664,7 @@ async def logs_closed_by(self, ctx, *, user: User = None): ) return await ctx.send(embed=embed) - session = PaginatorSession(ctx, *embeds) + session = EmbedPaginatorSession(ctx, *embeds) await session.run() @logs.command(name="search", aliases=["find"]) @@ -697,7 +697,7 @@ async def logs_search(self, ctx, limit: Optional[int] = None, *, query): ) return await ctx.send(embed=embed) - session = PaginatorSession(ctx, *embeds) + session = EmbedPaginatorSession(ctx, *embeds) await session.run() @commands.command() @@ -893,7 +893,8 @@ async def blocked(self, ctx): else: embeds[-1].description = "Currently there are no blocked users." - await PaginatorSession(ctx, *embeds).run() + session = EmbedPaginatorSession(ctx, *embeds) + await session.run() @blocked.command(name="whitelist") @checks.has_permissions(PermissionLevel.MODERATOR) diff --git a/cogs/plugins.py b/cogs/plugins.py index ebb9a72241..f871209bb6 100644 --- a/cogs/plugins.py +++ b/cogs/plugins.py @@ -17,7 +17,7 @@ from core import checks from core.models import PermissionLevel -from core.paginator import PaginatorSession +from core.paginator import EmbedPaginatorSession logger = logging.getLogger("Modmail") @@ -465,7 +465,7 @@ async def plugin_registry(self, ctx, *, plugin_name: typing.Union[int, str] = No embeds.append(embed) - paginator = PaginatorSession(ctx, *embeds) + paginator = EmbedPaginatorSession(ctx, *embeds) paginator.current = index await paginator.run() @@ -499,7 +499,7 @@ async def plugin_registry_compact(self, ctx): embed.set_author(name="Plugin Registry", icon_url=self.bot.user.avatar_url) embeds.append(embed) - paginator = PaginatorSession(ctx, *embeds) + paginator = EmbedPaginatorSession(ctx, *embeds) await paginator.run() diff --git a/cogs/utility.py b/cogs/utility.py index b1fe7aa7b3..f661cecb40 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -26,7 +26,7 @@ from core.changelog import Changelog from core.decorators import trigger_typing from core.models import InvalidConfigError, PermissionLevel -from core.paginator import PaginatorSession, MessagePaginatorSession +from core.paginator import EmbedPaginatorSession, MessagePaginatorSession from core.utils import ( cleanup_code, User, @@ -112,17 +112,17 @@ async def send_bot_help(self, mapping): if no_cog_commands: embeds.extend(await self.format_cog_help(no_cog_commands, no_cog=True)) - p_session = PaginatorSession( + session = EmbedPaginatorSession( self.context, *embeds, destination=self.get_destination() ) - return await p_session.run() + return await session.run() async def send_cog_help(self, cog): embeds = await self.format_cog_help(cog) - p_session = PaginatorSession( + session = EmbedPaginatorSession( self.context, *embeds, destination=self.get_destination() ) - return await p_session.run() + return await session.run() async def send_command_help(self, command): if not await self.filter_commands([command]): @@ -285,7 +285,7 @@ async def changelog(self, ctx, version: str.lower = ""): ) try: - paginator = PaginatorSession(ctx, *changelog.embeds) + paginator = EmbedPaginatorSession(ctx, *changelog.embeds) paginator.current = index await paginator.run() except asyncio.CancelledError: @@ -358,7 +358,7 @@ async def sponsors(self, ctx): random.shuffle(embeds) - session = PaginatorSession(ctx, *embeds) + session = EmbedPaginatorSession(ctx, *embeds) await session.run() @commands.group(invoke_without_command=True) @@ -762,7 +762,7 @@ async def config_options(self, ctx): ) embeds.append(embed) - session = PaginatorSession(ctx, *embeds) + session = EmbedPaginatorSession(ctx, *embeds) await session.run() @config.command(name="set", aliases=["add"]) @@ -926,7 +926,7 @@ def fmt(val): embed.set_thumbnail(url=fmt(info["thumbnail"])) embeds += [embed] - paginator = PaginatorSession(ctx, *embeds) + paginator = EmbedPaginatorSession(ctx, *embeds) paginator.current = index await paginator.run() @@ -1003,7 +1003,7 @@ async def alias(self, ctx, *, name: str.lower = None): embed.set_author(name="Command Aliases", icon_url=ctx.guild.icon_url) embeds.append(embed) - session = PaginatorSession(ctx, *embeds) + session = EmbedPaginatorSession(ctx, *embeds) await session.run() @alias.command(name="raw") @@ -1500,8 +1500,8 @@ async def permissions_get( for perm_level in PermissionLevel: embeds.append(self._get_perm(ctx, perm_level.name, "level")) - p_session = PaginatorSession(ctx, *embeds) - return await p_session.run() + session = EmbedPaginatorSession(ctx, *embeds) + return await session.run() @commands.group(invoke_without_command=True) @checks.has_permissions(PermissionLevel.OWNER) diff --git a/core/paginator.py b/core/paginator.py index a19db604b7..0a8aa8b814 100644 --- a/core/paginator.py +++ b/core/paginator.py @@ -8,7 +8,7 @@ class PaginatorSession: """ - Class that interactively paginates a list of `Embed`. + Class that interactively paginates something. Parameters ---------- @@ -16,11 +16,8 @@ class PaginatorSession: The context of the command. timeout : float How long to wait for before the session closes. - embeds : List[Embed] + pages : List[Any] A list of entries to paginate. - edit_footer : bool, optional - Whether to set the footer. - Defaults to `True`. Attributes ---------- @@ -28,7 +25,7 @@ class PaginatorSession: The context of the command. timeout : float How long to wait for before the session closes. - embeds : List[Embed] + pages : List[Any] A list of entries to paginate. running : bool Whether the paginate session is running. @@ -36,18 +33,17 @@ class PaginatorSession: The `Message` of the `Embed`. current : int The current page number. - reaction_map : Dict[str, meth] + reaction_map : Dict[str, method] A mapping for reaction to method. - """ - def __init__(self, ctx: commands.Context, *embeds, **options): + def __init__(self, ctx: commands.Context, *pages, **options): self.ctx = ctx self.timeout: int = options.get("timeout", 210) - self.embeds: typing.List[Embed] = list(embeds) self.running = False self.base: Message = None self.current = 0 + self.pages = list(pages) self.destination = options.get("destination", ctx) self.reaction_map = { "⏮": self.first_page, @@ -57,48 +53,31 @@ def __init__(self, ctx: commands.Context, *embeds, **options): "🛑": self.close, } - if options.get("edit_footer", True) and len(self.embeds) > 1: - for i, embed in enumerate(self.embeds): - footer_text = f"Page {i + 1} of {len(self.embeds)}" - if embed.footer.text: - footer_text = footer_text + " • " + embed.footer.text - embed.set_footer(text=footer_text, icon_url=embed.footer.icon_url) - - def add_page(self, embed: Embed) -> None: + def add_page(self, item) -> None: """ - Add a `Embed` page. - - Parameters - ---------- - embed : Embed - The `Embed` to add. + Add a page. """ - if isinstance(embed, Embed): - self.embeds.append(embed) - else: - raise TypeError("Page must be an Embed object.") + raise NotImplementedError - async def create_base(self, embed: Embed) -> None: + async def create_base(self, item) -> None: """ Create a base `Message`. - - Parameters - ---------- - embed : Embed - The `Embed` to fill the base `Message`. """ - self.base = await self.destination.send(embed=embed) + await self._create_base(item) - if len(self.embeds) == 1: + if len(self.pages) == 1: self.running = False return self.running = True for reaction in self.reaction_map: - if len(self.embeds) == 2 and reaction in "⏮⏭": + if len(self.pages) == 2 and reaction in "⏮⏭": continue await self.base.add_reaction(reaction) + async def _create_base(self, item) -> None: + raise NotImplementedError + async def show_page(self, index: int) -> None: """ Show a page by page number. @@ -108,17 +87,20 @@ async def show_page(self, index: int) -> None: index : int The index of the page. """ - if not 0 <= index < len(self.embeds): + if not 0 <= index < len(self.pages): return self.current = index - page = self.embeds[index] + page = self.pages[index] if self.running: - await self.base.edit(embed=page) + return await self._show_page(page) else: await self.create_base(page) + async def _show_page(self, page): + raise NotImplementedError + def react_check(self, reaction: Reaction, user: User) -> bool: """ @@ -218,201 +200,59 @@ async def last_page(self) -> None: """ Go to the last page. """ - await self.show_page(len(self.embeds) - 1) + await self.show_page(len(self.pages) - 1) -class MessagePaginatorSession: +class EmbedPaginatorSession(PaginatorSession): + def __init__(self, ctx: commands.Context, *embeds, **options): + super().__init__(ctx, *embeds, **options) - # TODO: Subclass MessagePaginatorSession from PaginatorSession + if len(self.pages) > 1: + for i, embed in enumerate(self.pages): + footer_text = f"Page {i + 1} of {len(self.pages)}" + if embed.footer.text: + footer_text = footer_text + " • " + embed.footer.text + embed.set_footer(text=footer_text, icon_url=embed.footer.icon_url) + + def add_page(self, embed: Embed) -> None: + if isinstance(embed, Embed): + self.pages.append(embed) + else: + raise TypeError("Page must be an Embed object.") + + async def _create_base(self, embed: Embed) -> None: + self.base = await self.destination.send(embed=embed) + + async def _show_page(self, page): + await self.base.edit(embed=page) + + +class MessagePaginatorSession(PaginatorSession): def __init__( self, ctx: commands.Context, *messages, embed: Embed = None, **options ): - self.ctx = ctx - self.timeout: int = options.get("timeout", 180) - self.messages: typing.List[str] = list(messages) - - self.running = False - self.base: Message = None self.embed = embed - if embed is not None: - self.footer_text = self.embed.footer.text - else: - self.footer_text = None - - self.current = 0 - self.reaction_map = { - "⏮": self.first_page, - "◀": self.previous_page, - "▶": self.next_page, - "⏭": self.last_page, - "🛑": self.close, - } + self.footer_text = self.embed.footer.text if embed is not None else None + super().__init__(ctx, *messages, **options) def add_page(self, msg: str) -> None: - """ - Add a message page. - - Parameters - ---------- - msg : str - The message to add. - """ if isinstance(msg, str): - self.messages.append(msg) + self.pages.append(msg) else: raise TypeError("Page must be a str object.") - async def create_base(self, msg: str) -> None: - """ - Create a base `Message`. - - Parameters - ---------- - msg : str - The message content to fill the base `Message`. - """ + def _set_footer(self): if self.embed is not None: - footer_text = f"Page {self.current+1} of {len(self.messages)}" + footer_text = f"Page {self.current+1} of {len(self.pages)}" if self.footer_text: footer_text = footer_text + " • " + self.footer_text self.embed.set_footer(text=footer_text, icon_url=self.embed.footer.icon_url) + async def _create_base(self, msg: str) -> None: + self._set_footer() self.base = await self.ctx.send(content=msg, embed=self.embed) - if len(self.messages) == 1: - self.running = False - return - - self.running = True - for reaction in self.reaction_map: - if len(self.messages) == 2 and reaction in "⏮⏭": - continue - await self.base.add_reaction(reaction) - - async def show_page(self, index: int) -> None: - """ - Show a page by page number. - - Parameters - ---------- - index : int - The index of the page. - """ - if not 0 <= index < len(self.messages): - return - - self.current = index - page = self.messages[index] - - if self.embed is not None: - footer_text = f"Page {self.current + 1} of {len(self.messages)}" - if self.footer_text: - footer_text = footer_text + " • " + self.footer_text - self.embed.set_footer(text=footer_text, icon_url=self.embed.footer.icon_url) - - if self.running: - await self.base.edit(content=page, embed=self.embed) - else: - await self.create_base(page) - - def react_check(self, reaction: Reaction, user: User) -> bool: - """ - - Parameters - ---------- - reaction : Reaction - The `Reaction` object of the reaction. - user : User - The `User` or `Member` object of who sent the reaction. - - Returns - ------- - bool - """ - return ( - reaction.message.id == self.base.id - and user.id == self.ctx.author.id - and reaction.emoji in self.reaction_map.keys() - ) - - async def run(self) -> typing.Optional[Message]: - """ - Starts the pagination session. - - Returns - ------- - Optional[Message] - If it's closed before running ends. - """ - if not self.running: - await self.show_page(self.current) - while self.running: - try: - reaction, user = await self.ctx.bot.wait_for( - "reaction_add", check=self.react_check, timeout=self.timeout - ) - except asyncio.TimeoutError: - return await self.close(delete=False) - else: - action = self.reaction_map.get(reaction.emoji) - await action() - try: - await self.base.remove_reaction(reaction, user) - except (HTTPException, InvalidArgument): - pass - - async def previous_page(self) -> None: - """ - Go to the previous page. - """ - await self.show_page(self.current - 1) - - async def next_page(self) -> None: - """ - Go to the next page. - """ - await self.show_page(self.current + 1) - - async def close(self, delete: bool = True) -> typing.Optional[Message]: - """ - Closes the pagination session. - - Parameters - ---------- - delete : bool, optional - Whether or delete the message upon closure. - Defaults to `True`. - - Returns - ------- - Optional[Message] - If `delete` is `True`. - """ - self.running = False - - sent_emoji, _ = await self.ctx.bot.retrieve_emoji() - try: - await self.ctx.message.add_reaction(sent_emoji) - except (HTTPException, InvalidArgument): - pass - - if delete: - return await self.base.delete() - - try: - await self.base.clear_reactions() - except HTTPException: - pass - - async def first_page(self) -> None: - """ - Go to the first page. - """ - await self.show_page(0) - - async def last_page(self) -> None: - """ - Go to the last page. - """ - await self.show_page(len(self.messages) - 1) + async def _show_page(self, page) -> None: + self._set_footer() + await self.base.edit(content=page, embed=self.embed) From e0d6c07828b992e59a104e90f73f6470492dd437 Mon Sep 17 00:00:00 2001 From: Taaku18 <45324516+Taaku18@users.noreply.github.com> Date: Mon, 5 Aug 2019 09:25:55 -0600 Subject: [PATCH 13/13] Add pull.yml --- .github/pull.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/pull.yml diff --git a/.github/pull.yml b/.github/pull.yml new file mode 100644 index 0000000000..2fec8bbda0 --- /dev/null +++ b/.github/pull.yml @@ -0,0 +1,8 @@ +version: "1" +rules: + - base: master + upstream: kyb3r:master + mergeMethod: hardreset + - base: development + upstream: kyb3r:development + mergeMethod: hardreset \ No newline at end of file