Skip to content
This repository was archived by the owner on Nov 9, 2021. It is now read-only.

Commit 68a76a9

Browse files
committed
Initial commit
0 parents  commit 68a76a9

12 files changed

+436
-0
lines changed

.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.gitignore

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
MANIFEST
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.nox/
42+
.coverage
43+
.coverage.*
44+
.cache
45+
nosetests.xml
46+
coverage.xml
47+
*.cover
48+
.hypothesis/
49+
.pytest_cache/
50+
51+
# Translations
52+
*.mo
53+
*.pot
54+
55+
# Django stuff:
56+
*.log
57+
local_settings.py
58+
db.sqlite3
59+
60+
# Flask stuff:
61+
instance/
62+
.webassets-cache
63+
64+
# Scrapy stuff:
65+
.scrapy
66+
67+
# Sphinx documentation
68+
docs/_build/
69+
70+
# PyBuilder
71+
target/
72+
73+
# Jupyter Notebook
74+
.ipynb_checkpoints
75+
76+
# IPython
77+
profile_default/
78+
ipython_config.py
79+
80+
# pyenv
81+
.python-version
82+
83+
# celery beat schedule file
84+
celerybeat-schedule
85+
86+
# SageMath parsed files
87+
*.sage.py
88+
89+
# Environments
90+
.env
91+
.venv
92+
env/
93+
venv/
94+
ENV/
95+
env.bak/
96+
venv.bak/
97+
98+
# Spyder project settings
99+
.spyderproject
100+
.spyproject
101+
102+
# Rope project settings
103+
.ropeproject
104+
105+
# mkdocs documentation
106+
/site
107+
108+
# mypy
109+
.mypy_cache/
110+
.dmypy.json
111+
dmypy.json
112+
113+
# Pyre type checker
114+
.pyre/
115+
116+
.idea/

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Andrew Zhu
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# discord-buttons-menu
2+
3+
This project is a scratchpad for creating a menu using Discord interactions.

menu/__init__.py

Whitespace-only changes.

menu/config.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import os
2+
3+
DISCORD_BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN")

menu/main.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import discord
2+
from discord.ext import commands
3+
4+
import config
5+
import ui
6+
7+
8+
class MenuTestBot(commands.Bot):
9+
def __init__(self):
10+
super().__init__(command_prefix=commands.when_mentioned_or(']'))
11+
12+
async def on_ready(self):
13+
print(f'Logged in as {self.user} (ID: {self.user.id})')
14+
print('------')
15+
16+
17+
bot = MenuTestBot()
18+
19+
20+
@bot.command()
21+
async def counter(ctx: commands.Context):
22+
"""Starts a counter for pressing."""
23+
await ctx.send('Press!', view=ui.counter.EphemeralCounter())
24+
25+
26+
@bot.command()
27+
async def servsettings(ctx: commands.Context):
28+
"""Change your server settings."""
29+
view = ui.servsettings.ServerSettingsUI.new(
30+
owner=ctx.author,
31+
settings=ui.servsettings.ServerSettings(),
32+
guild_name=ctx.guild.name
33+
)
34+
await view.send_to(ctx)
35+
36+
37+
@bot.command()
38+
async def csettings(ctx: commands.Context):
39+
"""Change your character's settings (NYI)."""
40+
await ctx.send('NYI')
41+
42+
43+
if __name__ == '__main__':
44+
bot.run(config.DISCORD_BOT_TOKEN)

menu/ui/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import counter, servsettings

menu/ui/counter.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Counter example given at https://github.com/Rapptz/discord.py/blob/master/examples/views/ephemeral.py"""
2+
import discord
3+
4+
5+
# Define a simple View that gives us a counter button
6+
class Counter(discord.ui.View):
7+
8+
# Define the actual button
9+
# When pressed, this increments the number displayed until it hits 5.
10+
# When it hits 5, the counter button is disabled and it turns green.
11+
# note: The name of the function does not matter to the library
12+
@discord.ui.button(label='0', style=discord.ButtonStyle.red)
13+
async def count(self, button: discord.ui.Button, interaction: discord.Interaction):
14+
number = int(button.label) if button.label else 0
15+
if number + 1 >= 5:
16+
button.style = discord.ButtonStyle.green
17+
button.disabled = True
18+
button.label = str(number + 1)
19+
20+
# Make sure to update the message with our updated selves
21+
await interaction.response.edit_message(view=self)
22+
23+
24+
# Define a View that will give us our own personal counter button
25+
class EphemeralCounter(discord.ui.View):
26+
# When this button is pressed, it will respond with a Counter view_type that will
27+
# give the button presser their own personal button they can press 5 times.
28+
@discord.ui.button(label='Click', style=discord.ButtonStyle.blurple)
29+
async def receive(self, button: discord.ui.Button, interaction: discord.Interaction):
30+
# ephemeral=True makes the message hidden from everyone except the button presser
31+
await interaction.response.send_message('Enjoy!', view=Counter(), ephemeral=True)

menu/ui/menu.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from typing import Mapping, Optional, Type
2+
3+
import discord
4+
5+
6+
class MenuBase(discord.ui.View):
7+
__menu_copy_attrs__ = ()
8+
9+
def __init__(self, owner: discord.User, *args, **kwargs):
10+
super().__init__(*args, **kwargs)
11+
self.owner = owner
12+
self.message = None # type: Optional[discord.Message]
13+
14+
@classmethod
15+
def from_menu(cls, other: 'MenuBase', cancel_other=True):
16+
inst = cls(owner=other.owner)
17+
inst.message = other.message
18+
if cancel_other:
19+
other.stop()
20+
for attr in cls.__menu_copy_attrs__:
21+
# copy the instance attr to the new instance if available, or fall back to the class default
22+
sentinel = object()
23+
value = getattr(other, attr, sentinel)
24+
if value is sentinel:
25+
value = getattr(cls, attr, None)
26+
setattr(inst, attr, value)
27+
return inst
28+
29+
# ==== d.py overrides ====
30+
async def interaction_check(self, interaction: discord.Interaction) -> bool:
31+
if interaction.user.id == self.owner.id:
32+
return True
33+
await interaction.response.send_message("You are not the owner of this menu.", ephemeral=True)
34+
return False
35+
36+
async def on_timeout(self):
37+
if self.message is None:
38+
return
39+
await self.message.edit(view=None)
40+
41+
# ==== content ====
42+
def get_content(self) -> Mapping:
43+
"""Return a mapping of kwargs to send when sending the view."""
44+
return {}
45+
46+
# ==== helpers ====
47+
async def send_to(self, destination: discord.abc.Messageable, *args, **kwargs):
48+
"""Sends this menu to a given destination."""
49+
message = await destination.send(*args, view=self, **self.get_content(), **kwargs)
50+
self.message = message
51+
return message
52+
53+
async def defer_to(self, view_type: Type['MenuBase'], interaction: discord.Interaction):
54+
"""Defers control to another menu item."""
55+
view = view_type.from_menu(self, cancel_other=True)
56+
await interaction.response.edit_message(view=view, **view.get_content())
57+
58+
async def refresh_content(self, interaction: discord.Interaction):
59+
"""Refresh the interaction's message with the current state of the menu."""
60+
await interaction.response.edit_message(view=self, **self.get_content())

0 commit comments

Comments
 (0)