Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0c14717
dependencies: add dislash for discord interactions
onerandomusername Aug 16, 2021
c29dd05
create a slash client
onerandomusername Aug 16, 2021
2835227
chore: export dependencies
onerandomusername Aug 16, 2021
73729e3
feat: add button pagination
onerandomusername Aug 16, 2021
6448efb
chore: update changelog with interaction paginator
onerandomusername Aug 16, 2021
9b43d4f
feat: add the pagination logic
onerandomusername Aug 16, 2021
b4b995f
Merge branch 'main' into feat/paginator
onerandomusername Aug 17, 2021
27c2c51
dependencies: get rid of dislash
onerandomusername Aug 17, 2021
787e52b
pagination: rewrite entire system with native dpy
onerandomusername Aug 17, 2021
4be4628
paginate: refactor and review pagination methods
onerandomusername Aug 17, 2021
5f6b895
chore: update icons for pagination
onerandomusername Aug 18, 2021
2c583ea
rename paginator to ButtonPaginator
onerandomusername Aug 20, 2021
8e68384
Merge branch 'main' into feat/paginator
onerandomusername Aug 20, 2021
0b69522
major: mixin discordpy paginator to the paginator
onerandomusername Aug 20, 2021
eed2a83
chore: don't paginate if one page
onerandomusername Aug 20, 2021
b11fa3e
chore: better docstring for Paginator class
onerandomusername Aug 21, 2021
6d0cfaa
credit: remove most paginator credit as running a diff shows it was r…
onerandomusername Aug 21, 2021
ce3a9cd
chore: refactor imports, move custom errors to designated file
onerandomusername Aug 21, 2021
93e9b18
minor: prefix all pagination interaction ids with pag_
onerandomusername Aug 21, 2021
4ef8f43
interactions: add paginator cleaner
onerandomusername Aug 21, 2021
3642329
nit: reverse if statement for readability
onerandomusername Aug 21, 2021
00d9d1d
feat: monkey-patch embeds to give more init param options
onerandomusername Aug 21, 2021
33d61bf
paginate: add embed support
onerandomusername Aug 21, 2021
42903c8
minor: make a string an f-string again
onerandomusername Aug 21, 2021
9c248c3
minor: document_modify_states
onerandomusername Aug 21, 2021
da5946c
minor: remove if statements that should always be false
onerandomusername Aug 21, 2021
74947e5
nit: remove an errant logging statement
onerandomusername Aug 21, 2021
eaeab25
minor: rename footer to footer_text, modify to only use () around pag…
onerandomusername Aug 21, 2021
bcecc55
fix: reimplement pagination on extension management
onerandomusername Aug 21, 2021
06cb3b7
minor: modify extensions list to not output modmail
onerandomusername Aug 21, 2021
3cb0fea
use '_pages' interally instead of 'pages'
onerandomusername Aug 22, 2021
b2720bc
chore: keep buttons blue
onerandomusername Aug 22, 2021
f13e9c8
nit: change jump labels to look like arrows
onerandomusername Aug 22, 2021
8529324
minor: update comments for paginator labels
onerandomusername Aug 22, 2021
1062b43
pagination: implement role and user restriction
onerandomusername Aug 22, 2021
fd84bde
fix: make role whitelist whitelist roles
onerandomusername Aug 22, 2021
d4e2cca
Merge branch 'main' into feat/paginator
onerandomusername Aug 27, 2021
7d972c5
tests: add small paginator stub test
onerandomusername Aug 30, 2021
117ca07
Merge branch 'main' into feat/paginator
onerandomusername Aug 30, 2021
01040b5
minor: use feature annotations, underscore unused variables
onerandomusername Aug 30, 2021
71e5c1e
nit: document correct character in docstring
onerandomusername Aug 30, 2021
a5ac924
nit: comment why the _stop method is named as such
onerandomusername Aug 31, 2021
f782ba2
minor: import annotations feature and stringify them
onerandomusername Aug 31, 2021
fa3ca63
chore: rename paginator cleaner to paginator manager
onerandomusername Aug 31, 2021
44f8dac
nit: make docstrings more accurate
onerandomusername Aug 31, 2021
5811482
minor: less liberal on the for loops
onerandomusername Aug 31, 2021
8781354
minor: unprivatize embed and index variables
onerandomusername Aug 31, 2021
a32cd6c
chore: invert disabled components
onerandomusername Aug 31, 2021
43a2a49
chore: move embed footer to new method
onerandomusername Aug 31, 2021
47836fe
nit: fix paginatot docstring typo
onerandomusername Aug 31, 2021
23f4772
minor: rename to update_states and fold footer update
onerandomusername Aug 31, 2021
b7a0356
minor: support strings for contents list
onerandomusername Aug 31, 2021
e68f5aa
tests: add footer test
onerandomusername Aug 31, 2021
cd7b5ec
fix: make paginator count pages properly again
onerandomusername Aug 31, 2021
02d0421
chore: switch extension paginator to a string
onerandomusername Aug 31, 2021
ac392ab
nit: make it possible to run individual tests
onerandomusername Aug 31, 2021
ba1046b
tests: add a few more tests
onerandomusername Aug 31, 2021
435da8f
fix: remove logic bug
onerandomusername Aug 31, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added

- Interaction Paginator that uses discord buttons (#50)
- docker-compose.yml (#13)
- Running the bot after configuring the env vars is now as simple as `docker-compose up`
- Automatic docker image creation: `ghcr.io/discord-modmail/modmail` (#19)
Expand Down
13 changes: 10 additions & 3 deletions modmail/extensions/extension_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from modmail.log import ModmailLogger
from modmail.utils.cogs import BotModes, ExtMetadata, ModmailCog
from modmail.utils.extensions import EXTENSIONS, NO_UNLOAD, unqualify, walk_extensions
from modmail.utils.pagination import ButtonPaginator

log: ModmailLogger = logging.getLogger(__name__)

Expand Down Expand Up @@ -84,6 +85,7 @@ class ExtensionManager(ModmailCog, name="Extension Manager"):
"""

type = "extension"
module_name = "extensions" # modmail/extensions

def __init__(self, bot: ModmailBot):
self.bot = bot
Expand Down Expand Up @@ -179,14 +181,16 @@ async def list_extensions(self, ctx: Context) -> None:
for category, extensions in sorted(categories.items()):
# Treat each category as a single line by concatenating everything.
# This ensures the paginator will not cut off a page in the middle of a category.
log.trace(f"Extensions in category {category}: {extensions}")
category = category.replace("_", " ").title()
extensions = "\n".join(sorted(extensions))
lines.append(f"**{category}**\n{extensions}\n")

log.debug(f"{ctx.author} requested a list of all {self.type}s. " "Returning a paginated list.")

# TODO: since we currently don't have a paginator.
await ctx.send("".join(lines) or f"There are no {self.type}s installed.")
await ButtonPaginator.paginate(
lines or f"There are no {self.type}s installed.", ctx.message, embed=embed
)

@extensions_group.command(name="refresh", aliases=("rewalk", "rescan"))
async def resync_extensions(self, ctx: Context) -> None:
Expand Down Expand Up @@ -222,7 +226,10 @@ def group_extension_statuses(self) -> t.Mapping[str, str]:
status = ":red_circle:"

root, name = ext.rsplit(".", 1)
category = " - ".join(root.split("."))
if root.split(".", 1)[1] == self.module_name:
category = f"General {self.type}s"
else:
category = " - ".join(root.split(".")[2:])
categories[category].append(f"{status} {name}")

return dict(categories)
Expand Down
1 change: 1 addition & 0 deletions modmail/extensions/plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class PluginManager(ExtensionManager, name="Plugin Manager"):
"""Plugin management commands."""

type = "plugin"
module_name = "plugins" # modmail/plugins

def __init__(self, bot: ModmailBot) -> None:
super().__init__(bot)
Expand Down
Empty file.
58 changes: 58 additions & 0 deletions modmail/extensions/utils/paginator_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

import asyncio
import logging
from typing import TYPE_CHECKING

from discord import InteractionType

from modmail.utils.cogs import ModmailCog

if TYPE_CHECKING:
from discord import Interaction

from modmail.bot import ModmailBot
from modmail.log import ModmailLogger

logger: ModmailLogger = logging.getLogger(__name__)


class PaginatorManager(ModmailCog):
"""Handles paginators that were still active when the bot shut down."""

def __init__(self, bot: ModmailBot):
self.bot = bot

@ModmailCog.listener()
async def on_interaction(self, interaction: Interaction) -> None:
"""
Remove components from paginator messages if they fail.

The paginator handles all interactions while it is active, but if the bot is restarted,
those interactions stop being dealt with.

This handles all paginator interactions that fail, which should only happen if
the paginator was unable to delete its message.
"""
# paginator only has component interactions
if not interaction.type == InteractionType.component:
return
logger.debug(f"Interaction sent by {interaction.user}.")
logger.trace(f"Interaction data: {interaction.data}")
if (
interaction.data["custom_id"].startswith("pag_")
and interaction.message.author.id == self.bot.user.id
):
# sleep for two seconds to give the paginator time to respond.
# this is due to discord requiring a response within 3 seconds,
# and we don't want to let the paginator fail.
await asyncio.sleep(2)
if not interaction.response.is_done():
await interaction.response.send_message(content="This paginator has expired.", ephemeral=True)
await asyncio.sleep(0.1) # sleep for just a moment so we don't jar the user
await interaction.message.edit(view=None)


def setup(bot: ModmailBot) -> None:
"""Add the paginator cleaner to the bot."""
bot.add_cog(PaginatorManager(bot))
Empty file added modmail/utils/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions modmail/utils/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class MissingAttributeError(Exception):
"""Missing attribute."""

pass


class InvalidArgumentError(Exception):
"""Improper argument."""

pass
Loading