Browse Source

Improve readme, examples, workflows

pull/10109/head
dolfies 3 years ago
parent
commit
188f21235e
  1. 2
      .github/workflows/build.yml
  2. 2
      .github/workflows/lint.yml
  3. 2
      .github/workflows/python-publish.yml
  4. 2
      .github/workflows/test.yml
  5. 42
      README.rst
  6. 8
      examples/basic_bot.py
  7. 8
      examples/basic_voice.py
  8. 9
      examples/converters.py
  9. 8
      examples/custom_context.py
  10. 7
      examples/deleted.py
  11. 7
      examples/edits.py
  12. 11
      examples/guessing_game.py
  13. 7
      examples/new_member.py
  14. 7
      examples/reaction_roles.py
  15. 7
      examples/reply.py
  16. 7
      examples/secret.py
  17. 62
      examples/views/confirm.py
  18. 48
      examples/views/counter.py
  19. 68
      examples/views/dropdown.py
  20. 52
      examples/views/ephemeral.py
  21. 44
      examples/views/link.py
  22. 68
      examples/views/persistent.py
  23. 144
      examples/views/tic_tac_toe.py
  24. 1
      pyproject.toml
  25. 4
      setup.py

2
.github/workflows/build.yml

@ -13,7 +13,7 @@ jobs:
matrix:
python-version: [ '3.8' ]
name: dists & docs ${{ matrix.python-version }}
name: dists & docs
steps:
- uses: actions/checkout@v2
with:

2
.github/workflows/lint.yml

@ -13,7 +13,7 @@ jobs:
matrix:
python-version: [ '3.8' ]
name: check ${{ matrix.python-version }}
name: lint
steps:
- uses: actions/checkout@v2
with:

2
.github/workflows/python-publish.yml

@ -11,8 +11,8 @@ on:
jobs:
deploy:
name: push to PyPi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python

2
.github/workflows/test.yml

@ -13,7 +13,7 @@ jobs:
matrix:
python-version: [ '3.8' ]
name: pytest ${{ matrix.python-version }}
name: pytest
steps:
- uses: actions/checkout@v2
with:

42
README.rst

@ -1,14 +1,14 @@
discord.py
==========
.. image:: https://discord.com/api/guilds/336642139381301249/embed.png
:target: https://discord.gg/r3sSKJJ
:alt: Discord server invite
.. image:: https://img.shields.io/pypi/v/discord.py.svg
:target: https://pypi.python.org/pypi/discord.py
discord.py-self
================
.. image:: https://img.shields.io/endpoint?url=https%3A%2F%2Frunkit.io%2Fdamiankrawczyk%2Ftelegram-badge%2Fbranches%2Fmaster%3Furl%3Dhttps%3A%2F%2Ft.me%2Fdpy_self
:target: https://t.me/dpy_self
:alt: Telegram chat
.. image:: https://img.shields.io/pypi/v/discord.py-self.svg
:target: https://pypi.python.org/pypi/discord.py-self
:alt: PyPI version info
.. image:: https://img.shields.io/pypi/pyversions/discord.py.svg
:target: https://pypi.python.org/pypi/discord.py
:target: https://pypi.python.org/pypi/discord.py-self
:alt: PyPI supported Python versions
A modern, easy to use, feature-rich, and async ready API wrapper for Discord written in Python.
@ -30,28 +30,28 @@ To install the library without full voice support, you can just run the followin
.. code:: sh
# Linux/macOS
python3 -m pip install -U discord.py
python3 -m pip install -U discord.py-self
# Windows
py -3 -m pip install -U discord.py
py -3 -m pip install -U discord.py-self
Otherwise to get voice support you should run the following command:
.. code:: sh
# Linux/macOS
python3 -m pip install -U "discord.py[voice]"
python3 -m pip install -U "discord.py-self[voice]"
# Windows
py -3 -m pip install -U discord.py[voice]
py -3 -m pip install -U discord.py-self[voice]
To install the development version, do the following:
.. code:: sh
$ git clone https://github.com/Rapptz/discord.py
$ cd discord.py
$ git clone https://github.com/dolfies/discord.py-self
$ cd discord.py-self
$ python3 -m pip install -U .[voice]
@ -77,8 +77,8 @@ Quick Example
print('Logged on as', self.user)
async def on_message(self, message):
# don't respond to ourselves
if message.author == self.user:
# only respond to ourselves
if message.author != self.user:
return
if message.content == 'ping':
@ -95,7 +95,7 @@ Bot Example
import discord
from discord.ext import commands
bot = commands.Bot(command_prefix='>')
bot = commands.Bot(command_prefix='>', self_bot=True)
@bot.command()
async def ping(ctx):
@ -108,6 +108,6 @@ You can find more examples in the examples directory.
Links
------
- `Documentation <https://discordpy.readthedocs.io/en/latest/index.html>`_
- `Official Discord Server <https://discord.gg/r3sSKJJ>`_
- `Discord API <https://discord.gg/discord-api>`_
- `Documentation (WIP) <https://discordpy-self.readthedocs.io/en/latest/index.html>`_
- `Project updates <https://t.me/dpy_self>`_
- `Discussion & support <https://t.me/dpy_self_discussions>`_

8
examples/basic_bot.py

@ -1,5 +1,3 @@
# This example requires the 'members' and 'message_content' privileged intents
import discord
from discord.ext import commands
import random
@ -9,11 +7,7 @@ module.
There are a number of utility commands being showcased here.'''
intents = discord.Intents.default()
intents.members = True
intents.message_content = True
bot = commands.Bot(command_prefix='?', description=description, intents=intents)
bot = commands.Bot(command_prefix='?', description=description, self_bot=True)
@bot.event
async def on_ready():

8
examples/basic_voice.py

@ -1,5 +1,3 @@
# This example requires the 'message_content' privileged intent to function.
import asyncio
import discord
@ -125,12 +123,8 @@ class Music(commands.Cog):
elif ctx.voice_client.is_playing():
ctx.voice_client.stop()
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"),
description='Relatively simple music bot example',
intents=intents)
description='Relatively simple music bot example')
@bot.event
async def on_ready():

9
examples/converters.py

@ -1,16 +1,9 @@
# This example requires the 'members' privileged intent to use the Member converter.
# This example also requires the 'message_content' privileged intent to function.
import typing
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.members = True
intents.message_content = True
bot = commands.Bot('!', intents=intents)
bot = commands.Bot('!', self_bot=True)
@bot.command()

8
examples/custom_context.py

@ -1,6 +1,3 @@
# This example requires the 'message_content' privileged intent to function.
import random
import discord
@ -31,10 +28,7 @@ class MyBot(commands.Bot):
# use the new MyContext class
return await super().get_context(message, cls=cls)
intents = discord.Intents.default()
intents.message_content = True
bot = MyBot(command_prefix='!', intents=intents)
bot = MyBot(command_prefix='!', self_bot=True)
@bot.command()
async def guess(ctx, number: int):

7
examples/deleted.py

@ -1,5 +1,3 @@
# This example requires the 'message_content' privileged intent to function.
import discord
class MyClient(discord.Client):
@ -19,8 +17,5 @@ class MyClient(discord.Client):
msg = f'{message.author} has deleted the message: {message.content}'
await message.channel.send(msg)
intents = discord.Intents.default()
intents.message_content = True
client = MyClient(intents=intents)
client = MyClient()
client.run('token')

7
examples/edits.py

@ -1,5 +1,3 @@
# This example requires the 'message_content' privileged intent to function.
import discord
import asyncio
@ -18,8 +16,5 @@ class MyClient(discord.Client):
msg = f'**{before.author}** edited their message:\n{before.content} -> {after.content}'
await before.channel.send(msg)
intents = discord.Intents.default()
intents.message_content = True
client = MyClient(intents=intents)
client = MyClient()
client.run('token')

11
examples/guessing_game.py

@ -1,5 +1,3 @@
# This example requires the 'message_content' privileged intent to function.
import discord
import random
import asyncio
@ -10,8 +8,8 @@ class MyClient(discord.Client):
print('------')
async def on_message(self, message):
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
# we do not want the bot to reply to other people
if message.author.id != self.user.id:
return
if message.content.startswith('$guess'):
@ -32,8 +30,5 @@ class MyClient(discord.Client):
else:
await message.channel.send(f'Oops. It is actually {answer}.')
intents = discord.Intents.default()
intents.message_content = True
client = MyClient(intents=intents)
client = MyClient()
client.run('token')

7
examples/new_member.py

@ -1,5 +1,3 @@
# This example requires the 'members' privileged intents
import discord
class MyClient(discord.Client):
@ -14,8 +12,5 @@ class MyClient(discord.Client):
await guild.system_channel.send(to_send)
intents = discord.Intents.default()
intents.members = True
client = MyClient(intents=intents)
client = MyClient()
client.run('token')

7
examples/reaction_roles.py

@ -1,5 +1,3 @@
# This example requires the 'members' privileged intents
import discord
class MyClient(discord.Client):
@ -78,8 +76,5 @@ class MyClient(discord.Client):
# If we want to do something in case of errors we'd do it here.
pass
intents = discord.Intents.default()
intents.members = True
client = MyClient(intents=intents)
client = MyClient()
client.run('token')

7
examples/reply.py

@ -1,5 +1,3 @@
# This example requires the 'message_content' privileged intent to function.
import discord
class MyClient(discord.Client):
@ -15,8 +13,5 @@ class MyClient(discord.Client):
if message.content.startswith('!hello'):
await message.reply('Hello!', mention_author=True)
intents = discord.Intents.default()
intents.message_content = True
client = MyClient(intents=intents)
client = MyClient()
client.run('token')

7
examples/secret.py

@ -1,14 +1,9 @@
# This example requires the 'message_content' privileged intent to function.
import typing
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!", intents=intents)
bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!", self_bot=True)
# the `hidden` keyword argument hides it from the help command.
@bot.group(hidden=True)

62
examples/views/confirm.py

@ -1,62 +0,0 @@
# This example requires the 'message_content' privileged intent to function.
from discord.ext import commands
import discord
class Bot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('$'), intents=intents)
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')

48
examples/views/counter.py

@ -1,48 +0,0 @@
# This example requires the 'message_content' privileged intent to function.
from discord.ext import commands
import discord
class CounterBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('$'), intents=intents)
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')

68
examples/views/dropdown.py

@ -1,68 +0,0 @@
# This example requires the 'message_content' privileged intent to function.
import typing
import discord
from discord.ext import commands
# Defines a custom Select containing colour options
# that the user can choose. The callback function
# of this class is called when the user changes their choice
class Dropdown(discord.ui.Select):
def __init__(self):
# Set the options that will be presented inside the dropdown
options = [
discord.SelectOption(label='Red', description='Your favourite colour is red', emoji='🟥'),
discord.SelectOption(label='Green', description='Your favourite colour is green', emoji='🟩'),
discord.SelectOption(label='Blue', description='Your favourite colour is blue', emoji='🟦')
]
# The placeholder is what will be shown when no option is chosen
# The min and max values indicate we can only pick one of the three options
# The options parameter defines the dropdown options. We defined this above
super().__init__(placeholder='Choose your favourite colour...', min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
# Use the interaction object to send a response message containing
# the user's favourite colour or choice. The self object refers to the
# Select object, and the values attribute gets a list of the user's
# selected options. We only want the first one.
await interaction.response.send_message(f'Your favourite colour is {self.values[0]}')
class DropdownView(discord.ui.View):
def __init__(self):
super().__init__()
# Adds the dropdown to our view object.
self.add_item(Dropdown())
class Bot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('$'), intents=intents)
async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')
bot = Bot()
@bot.command()
async def colour(ctx):
"""Sends a message with our dropdown containing colours"""
# Create the view containing our dropdown
view = DropdownView()
# Sending a message containing our view
await ctx.send('Pick your favourite colour:', view=view)
bot.run('token')

52
examples/views/ephemeral.py

@ -1,52 +0,0 @@
# This example requires the 'message_content' privileged intent to function.
from discord.ext import commands
import discord
class EphemeralCounterBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('$'), intents=intents)
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')

44
examples/views/link.py

@ -1,44 +0,0 @@
# This example requires the 'message_content' privileged intent to function.
from discord.ext import commands
import discord
from urllib.parse import quote_plus
class GoogleBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('$'), intents=intents)
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 google link button.
# We take in `query` as the query that the command author requests for
class Google(discord.ui.View):
def __init__(self, query: str):
super().__init__()
# we need to quote the query string to make a valid url. Discord will raise an error if it isn't valid.
query = quote_plus(query)
url = f'https://www.google.com/search?q={query}'
# Link buttons cannot be made with the decorator
# Therefore we have to manually create one.
# We add the quoted url to the button, and add the button to the view.
self.add_item(discord.ui.Button(label='Click Here', url=url))
bot = GoogleBot()
@bot.command()
async def google(ctx: commands.Context, *, query: str):
"""Returns a google link for a query"""
await ctx.send(f'Google Result for: `{query}`', view=Google(query))
bot.run('token')

68
examples/views/persistent.py

@ -1,68 +0,0 @@
# This example requires the 'message_content' privileged intent to function.
from discord.ext import commands
import discord
# 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)
class PersistentViewBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('$'), intents=intents)
self.persistent_views_added = False
async def on_ready(self):
if not self.persistent_views_added:
# Register the persistent view for listening here.
# 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.
self.add_view(PersistentView())
self.persistent_views_added = True
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')
bot = PersistentViewBot()
@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')

144
examples/views/tic_tac_toe.py

@ -1,144 +0,0 @@
# This example requires the 'message_content' privileged intent to function.
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):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix=commands.when_mentioned_or('$'), intents=intents)
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')

1
pyproject.toml

@ -29,7 +29,6 @@ line_length = 125
include = [
"discord",
"discord/types",
"discord/ui",
"discord/ext",
"discord/ext/commands",
"discord/ext/tasks",

4
setup.py

@ -58,8 +58,10 @@ setup(name='discord.py-self',
author='Dolfies',
url='https://github.com/dolfies/discord.py-self',
project_urls={
"Documentation": "https://dolf.ml/discord.py-self",
"Documentation": "https://discordpy-self.readthedocs.io/en/latest/",
"Issue tracker": "https://github.com/dolfies/discord.py-self/issues",
"Project updates": "https://t.me/dpy_self",
"Discussion & support": "https://t.me/dpy_self_discussions",
},
version=version,
packages=find_packages() + ['discord.ext.commands', 'discord.ext.tasks'],

Loading…
Cancel
Save