Skip to content

Commit

Permalink
Add examples for how to use views
Browse files Browse the repository at this point in the history
  • Loading branch information
Rapptz committed Jun 29, 2021
1 parent 2beee8b commit 7386a97
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 0 deletions.
57 changes: 57 additions & 0 deletions examples/views/confirm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from discord.ext import commands

import discord


class Bot(commands.Bot):
def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$'))

async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')


# Define a simple View that gives us a confirmation menu
class Confirm(discord.ui.View):
def __init__(self):
super().__init__()
self.value = None

# When the confirm button is pressed, set the inner value to `True` and
# stop the View from listening to more input.
# We also send the user an ephemeral message that we're confirming their choice.
@discord.ui.button(label='Confirm', style=discord.ButtonStyle.green)
async def confirm(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message('Confirming', ephemeral=True)
self.value = True
self.stop()

# This one is similar to the confirmation button except sets the inner value to `False`
@discord.ui.button(label='Cancel', style=discord.ButtonStyle.grey)
async def cancel(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message('Cancelling', ephemeral=True)
self.value = False
self.stop()


bot = Bot()


@bot.command()
async def ask(ctx: commands.Context):
"""Asks the user a question to confirm something."""
# We create the view and assign it to a variable so we can wait for it later.
view = Confirm()
await ctx.send('Do you want to continue?', view=view)
# Wait for the View to stop listening for input...
await view.wait()
if view.value is None:
print('Timed out...')
elif view.value:
print('Confirmed...')
else:
print('Cancelled...')


bot.run('token')
43 changes: 43 additions & 0 deletions examples/views/counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from discord.ext import commands

import discord


class CounterBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$'))

async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')


# Define a simple View that gives us a counter button
class Counter(discord.ui.View):

# Define the actual button
# When pressed, this increments the number displayed until it hits 5.
# When it hits 5, the counter button is disabled and it turns green.
# note: The name of the function does not matter to the library
@discord.ui.button(label='0', style=discord.ButtonStyle.red)
async def count(self, button: discord.ui.Button, interaction: discord.Interaction):
number = int(button.label) if button.label else 0
if number + 1 >= 5:
button.style = discord.ButtonStyle.green
button.disabled = True
button.label = str(number + 1)

# Make sure to update the message with our updated selves
await interaction.response.edit_message(view=self)


bot = CounterBot()


@bot.command()
async def counter(ctx: commands.Context):
"""Starts a counter for pressing."""
await ctx.send('Press!', view=Counter())


bot.run('token')
47 changes: 47 additions & 0 deletions examples/views/ephemeral.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from discord.ext import commands

import discord

class EphemeralCounterBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$'))

async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')

# Define a simple View that gives us a counter button
class Counter(discord.ui.View):

# Define the actual button
# When pressed, this increments the number displayed until it hits 5.
# When it hits 5, the counter button is disabled and it turns green.
# note: The name of the function does not matter to the library
@discord.ui.button(label='0', style=discord.ButtonStyle.red)
async def count(self, button: discord.ui.Button, interaction: discord.Interaction):
number = int(button.label) if button.label else 0
if number + 1 >= 5:
button.style = discord.ButtonStyle.green
button.disabled = True
button.label = str(number + 1)

# Make sure to update the message with our updated selves
await interaction.response.edit_message(view=self)

# Define a View that will give us our own personal counter button
class EphemeralCounter(discord.ui.View):
# When this button is pressed, it will respond with a Counter view that will
# give the button presser their own personal button they can press 5 times.
@discord.ui.button(label='Click', style=discord.ButtonStyle.blurple)
async def receive(self, button: discord.ui.Button, interaction: discord.Interaction):
# ephemeral=True makes the message hidden from everyone except the button presser
await interaction.response.send_message('Enjoy!', view=Counter(), ephemeral=True)

bot = EphemeralCounterBot()

@bot.command()
async def counter(ctx: commands.Context):
"""Starts a counter for pressing."""
await ctx.send('Press!', view=EphemeralCounter())

bot.run('token')
60 changes: 60 additions & 0 deletions examples/views/persistent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from discord.ext import commands
import discord


class PersistentViewBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$'))

async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')


# Define a simple View that persists between bot restarts
# In order a view to persist between restarts it needs to meet the following conditions:
# 1) The timeout of the View has to be set to None
# 2) Every item in the View has to have a custom_id set
# It is recommended that the custom_id be sufficiently unique to
# prevent conflicts with other buttons the bot sends.
# For this example the custom_id is prefixed with the name of the bot.
# Note that custom_ids can only be up to 100 characters long.
class PersistentView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)

@discord.ui.button(label='Green', style=discord.ButtonStyle.green, custom_id='persistent_view:green')
async def green(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message('This is green.', ephemeral=True)

@discord.ui.button(label='Red', style=discord.ButtonStyle.red, custom_id='persistent_view:red')
async def red(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message('This is red.', ephemeral=True)

@discord.ui.button(label='Grey', style=discord.ButtonStyle.grey, custom_id='persistent_view:grey')
async def grey(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message('This is grey.', ephemeral=True)


bot = PersistentViewBot()

# Register the persistent view for listening
# Note that this does not send the view to any message.
# In order to do this you need to first send a message with the View, which is shown below.
# If you have the message_id you can also pass it as a keyword argument, but for this example
# we don't have one.
bot.add_view(PersistentView())


@bot.command()
@commands.is_owner()
async def prepare(ctx: commands.Context):
"""Starts a persistent view."""
# In order for a persistent view to be listened to, it needs to be sent to an actual message.
# Call this method once just to store it somewhere.
# In a more complicated program you might fetch the message_id from a database for use later.
# However this is outside of the scope of this simple example.
await ctx.send("What's your favourite colour?", view=PersistentView())


bot.run('token')
139 changes: 139 additions & 0 deletions examples/views/tic_tac_toe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from typing import List
from discord.ext import commands
import discord

# Defines a custom button that contains the logic of the game.
# The ['TicTacToe'] bit is for type hinting purposes to tell your IDE or linter
# what the type of `self.view` is. It is not required.
class TicTacToeButton(discord.ui.Button['TicTacToe']):
def __init__(self, x: int, y: int):
# A label is required, but we don't need one so a zero-width space is used
# The row parameter tells the View which row to place the button under.
# A View can only contain up to 5 rows -- each row can only have 5 buttons.
# Since a Tic Tac Toe grid is 3x3 that means we have 3 rows and 3 columns.
super().__init__(style=discord.ButtonStyle.secondary, label='\u200b', row=y)
self.x = x
self.y = y

# This function is called whenever this particular button is pressed
# This is part of the "meat" of the game logic
async def callback(self, interaction: discord.Interaction):
assert self.view is not None
view: TicTacToe = self.view
state = view.board[self.y][self.x]
if state in (view.X, view.O):
return

if view.current_player == view.X:
self.style = discord.ButtonStyle.danger
self.label = 'X'
self.disabled = True
view.board[self.y][self.x] = view.X
view.current_player = view.O
content = "It is now O's turn"
else:
self.style = discord.ButtonStyle.success
self.label = 'O'
self.disabled = True
view.board[self.y][self.x] = view.O
view.current_player = view.X
content = "It is now X's turn"

winner = view.check_board_winner()
if winner is not None:
if winner == view.X:
content = 'X won!'
elif winner == view.O:
content = 'O won!'
else:
content = "It's a tie!"

for child in view.children:
child.disabled = True

view.stop()

await interaction.response.edit_message(content=content, view=view)


# This is our actual board View
class TicTacToe(discord.ui.View):
# This tells the IDE or linter that all our children will be TicTacToeButtons
# This is not required
children: List[TicTacToeButton]
X = -1
O = 1
Tie = 2

def __init__(self):
super().__init__()
self.current_player = self.X
self.board = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
]

# Our board is made up of 3 by 3 TicTacToeButtons
# The TicTacToeButton maintains the callbacks and helps steer
# the actual game.
for x in range(3):
for y in range(3):
self.add_item(TicTacToeButton(x, y))

# This method checks for the board winner -- it is used by the TicTacToeButton
def check_board_winner(self):
for across in self.board:
value = sum(across)
if value == 3:
return self.O
elif value == -3:
return self.X

# Check vertical
for line in range(3):
value = self.board[0][line] + self.board[1][line] + self.board[2][line]
if value == 3:
return self.O
elif value == -3:
return self.X

# Check diagonals
diag = self.board[0][2] + self.board[1][1] + self.board[2][0]
if diag == 3:
return self.O
elif diag == -3:
return self.X

diag = self.board[0][0] + self.board[1][1] + self.board[2][2]
if diag == 3:
return self.O
elif diag == -3:
return self.X

# If we're here, we need to check if a tie was made
if all(i != 0 for row in self.board for i in row):
return self.Tie

return None


class TicTacToeBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$'))

async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')


bot = TicTacToeBot()


@bot.command()
async def tic(ctx: commands.Context):
"""Starts a tic-tac-toe game with yourself."""
await ctx.send('Tic Tac Toe: X goes first', view=TicTacToe())


bot.run('token')

0 comments on commit 7386a97

Please sign in to comment.