Browse Source

Merge 7605cc859d into b5d26190a6

pull/177/merge
Nate the great 5 years ago
committed by GitHub
parent
commit
15642d8e5d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .biblio.yaml
  2. 2
      .gitignore
  3. 0
      bot_tutorial/README.md
  4. 0
      bot_tutorial/SUMMARY.md
  5. 0
      bot_tutorial/advanced.md
  6. 226
      bot_tutorial/building_block_commands.md
  7. 0
      bot_tutorial/building_block_listeners.md
  8. 0
      bot_tutorial/building_block_plugins.md
  9. 0
      bot_tutorial/first_steps.md
  10. 0
      bot_tutorial/installation.md
  11. 122
      bot_tutorial/message_embeds.md
  12. 463
      disco/types/channel.py
  13. 670
      disco/types/guild.py
  14. 20
      docs/Makefile
  15. 10
      docs/book.json
  16. 20
      docs/build.sh
  17. 60
      docs/conf.py
  18. 17
      docs/index.rst
  19. 35
      docs/make.bat
  20. 5
      docs/types/channel.rst
  21. 5
      docs/types/guild.rst
  22. 9
      docs/types/index.rst
  23. 5
      setup.py

8
.biblio.yaml

@ -1,8 +0,0 @@
name: Disco
rules:
disco/*.py:
parser: numpy
outputs:
- {type: markdown, path: docs/api/, title: 'Disco Documentation'}

2
.gitignore

@ -9,7 +9,7 @@ storage.json
.cache/ .cache/
.benchmarks/ .benchmarks/
__pycache__ __pycache__
.venv .venv/
# Documentation stuff # Documentation stuff
docs/api/ docs/api/

0
docs/README.md → bot_tutorial/README.md

0
docs/SUMMARY.md → bot_tutorial/SUMMARY.md

0
docs/bot_tutorial/advanced.md → bot_tutorial/advanced.md

226
docs/bot_tutorial/building_block_commands.md → bot_tutorial/building_block_commands.md

@ -1,113 +1,113 @@
# Commands # Commands
Commands are a big part of the Discord bot usage. A command can be defined as an order you give to a bot. Basic examples of commands are: Commands are a big part of the Discord bot usage. A command can be defined as an order you give to a bot. Basic examples of commands are:
`!help` or `!info`, most bots have either of the two. `!help` or `!info`, most bots have either of the two.
In the case of these examples, when you send `!help` or `!info` the bot will reply with a help or info message. In the case of these examples, when you send `!help` or `!info` the bot will reply with a help or info message.
## Basic commands ## Basic commands
Creating commands in Disco is really easy because of the [Plugins](https://b1naryth1ef.github.io/disco/bot_tutorial/building_block_plugins.html) that are a core fundamental of Disco. For more info on them, read back in the [Plugins](https://b1naryth1ef.github.io/disco/bot_tutorial/building_block_plugins.html) section of this tutorial. Creating a basic command is done as follows: Creating commands in Disco is really easy because of the [Plugins](https://b1naryth1ef.github.io/disco/bot_tutorial/building_block_plugins.html) that are a core fundamental of Disco. For more info on them, read back in the [Plugins](https://b1naryth1ef.github.io/disco/bot_tutorial/building_block_plugins.html) section of this tutorial. Creating a basic command is done as follows:
First, create a Plugin class: First, create a Plugin class:
```py ```py
class myPlugin(Plugin): class myPlugin(Plugin):
``` ```
Now, we can add a command to it. The command will be named ping, and it will simply reply with `pong!` Now, we can add a command to it. The command will be named ping, and it will simply reply with `pong!`
```py ```py
@Plugin.command('ping') @Plugin.command('ping')
def on_ping_command(self, event): def on_ping_command(self, event):
event.msg.reply('Pong!') event.msg.reply('Pong!')
``` ```
And there we go! Our very first command! And there we go! Our very first command!
## Command arguments ## Command arguments
Next, lets go on to some more advanced commands. Wye'll create an echo command that will respond with whatever we put in to it. Next, lets go on to some more advanced commands. Wye'll create an echo command that will respond with whatever we put in to it.
```py ```py
@Plugin.command('echo', '<content:str...>') @Plugin.command('echo', '<content:str...>')
def on_echo_command(self, event, content): def on_echo_command(self, event, content):
event.msg.reply(content) event.msg.reply(content)
``` ```
What we did here, was add an argument to our command. The argument we created here, content, is required. This means the command won't work if you don't pass in data for the `content` argument. What we did here, was add an argument to our command. The argument we created here, content, is required. This means the command won't work if you don't pass in data for the `content` argument.
You can also add optional arguments to a command. Instead of surrounding the name and type in angle brackets, you'd surround them in square brackets like this: `[content:str...]` You can also add optional arguments to a command. Instead of surrounding the name and type in angle brackets, you'd surround them in square brackets like this: `[content:str...]`
Keep in mind that arguments that are optional might not be there. You'll have to create some checks so that your program doesn't crash on unexpected null values. Keep in mind that arguments that are optional might not be there. You'll have to create some checks so that your program doesn't crash on unexpected null values.
## Command groups ## Command groups
Now that we have 2 basic commands and we know to create basic commands and add some arguments to it. Let's create a more advanced command utilizing what we just learned. Now that we have 2 basic commands and we know to create basic commands and add some arguments to it. Let's create a more advanced command utilizing what we just learned.
The command will take 2 numbers (integers) and simply adds them together. It will work like this: `!math add 1 4` and it would return 5. Instead of passing `'math add'` as the command name, we'll be using command groups here. The command will take 2 numbers (integers) and simply adds them together. It will work like this: `!math add 1 4` and it would return 5. Instead of passing `'math add'` as the command name, we'll be using command groups here.
Using command groups you can easily group commands together and create sub commands. Now, here comes our math command: Using command groups you can easily group commands together and create sub commands. Now, here comes our math command:
```py ```py
@Plugin.command('add', '<a:int> <b:int>', group='math') @Plugin.command('add', '<a:int> <b:int>', group='math')
def on_add_command(self, event, a, b): def on_add_command(self, event, a, b):
event.msg.reply('{}'.format(a+b)) event.msg.reply('{}'.format(a+b))
``` ```
Here, we added multiple arguments to our command. Namely, number a and number b, that we add together and return back. Of course, you can do loads more fun things with the Disco command handler. Here, we added multiple arguments to our command. Namely, number a and number b, that we add together and return back. Of course, you can do loads more fun things with the Disco command handler.
## Optional arguments ## Optional arguments
Lets create a tag system, that can either store a tag if you'd use it like this: `!tag name value` or retrieve a tag if you'd use it like this: `!tag name` Lets create a tag system, that can either store a tag if you'd use it like this: `!tag name value` or retrieve a tag if you'd use it like this: `!tag name`
We'll need 2 arguments. A name argument that's required, and an optional value argument. Inside the command we'll check if a `value` is provided. If there is, we'll store the tag. Otherwise, we'll try to retrieve the previously set value for that tag and return it. We'll need 2 arguments. A name argument that's required, and an optional value argument. Inside the command we'll check if a `value` is provided. If there is, we'll store the tag. Otherwise, we'll try to retrieve the previously set value for that tag and return it.
For the sake of this example, we'll assume that the `tags` dict gets stored somewhere so it doesn't get removed after a restart. For the sake of this example, we'll assume that the `tags` dict gets stored somewhere so it doesn't get removed after a restart.
```py ```py
tags = {} tags = {}
@Plugin.command('tag', '<name:str> [value:str...]') @Plugin.command('tag', '<name:str> [value:str...]')
def on_tag_command(self, event, name, value=None): def on_tag_command(self, event, name, value=None):
if value: if value:
tags[name] = value tags[name] = value
event.msg.reply(':ok_hand: created tag `{}`'.format(name)) event.msg.reply(':ok_hand: created tag `{}`'.format(name))
else: else:
if name in tags.keys(): if name in tags.keys():
return event.msg.reply(tags[name]) return event.msg.reply(tags[name])
else: else:
return event.msg.reply('Unknown tag: `{}`'.format(name)) return event.msg.reply('Unknown tag: `{}`'.format(name))
``` ```
## ArgumentParser ## ArgumentParser
A different way of adding arguments to a command is by using `argparse.ArgumentParser`. With `argparser` it's easier to create more complicated commands with many options or flags. A different way of adding arguments to a command is by using `argparse.ArgumentParser`. With `argparser` it's easier to create more complicated commands with many options or flags.
Let's put this into practice by recreating our math add command, but using `argparser`. More info on `argparser` and the `add_argument()` method can be found [here](https://docs.python.org/2/library/argparse.html#the-add-argument-method) Let's put this into practice by recreating our math add command, but using `argparser`. More info on `argparser` and the `add_argument()` method can be found [here](https://docs.python.org/2/library/argparse.html#the-add-argument-method)
```py ```py
@Plugin.command('add', parser=True, group='math') @Plugin.command('add', parser=True, group='math')
@Plugin.parser.add_argument('a', type=int) @Plugin.parser.add_argument('a', type=int)
@Plugin.parser.add_argument('b', type=int) @Plugin.parser.add_argument('b', type=int)
def on_add_command(self, event, args): def on_add_command(self, event, args):
event.msg.reply('{}'.format(args.a + args.b) event.msg.reply('{}'.format(args.a + args.b)
``` ```
These are all the commands we created in this tutorial: These are all the commands we created in this tutorial:
```py ```py
class myPlugin(Plugin): class myPlugin(Plugin):
@Plugin.command('ping') @Plugin.command('ping')
def on_ping_command(self, event): def on_ping_command(self, event):
event.msg.reply('Pong!') event.msg.reply('Pong!')
@Plugin.command('echo', '<content:str...>') @Plugin.command('echo', '<content:str...>')
def on_echo_command(self, event, content): def on_echo_command(self, event, content):
event.msg.reply(content) event.msg.reply(content)
@Plugin.command('add', '<a:int> <b:int>', group='math') @Plugin.command('add', '<a:int> <b:int>', group='math')
def on_add_command(self, event, a, b): def on_add_command(self, event, a, b):
event.msg.reply('{}'.format(a+b)) event.msg.reply('{}'.format(a+b))
tags = {} tags = {}
@Plugin.command('tag', '<name:str> [value:str...]') @Plugin.command('tag', '<name:str> [value:str...]')
def on_tag_command(self, event, name, value=None): def on_tag_command(self, event, name, value=None):
if value: if value:
tags[name] = value tags[name] = value
event.msg.reply(':ok_hand: created tag `{}`'.format(name)) event.msg.reply(':ok_hand: created tag `{}`'.format(name))
else: else:
if name in tags.keys(): if name in tags.keys():
return event.msg.reply(tags[name]) return event.msg.reply(tags[name])
else: else:
return event.msg.reply('Unknown tag: `{}`'.format(name)) return event.msg.reply('Unknown tag: `{}`'.format(name))
@Plugin.command('add', parser=True, group='math') @Plugin.command('add', parser=True, group='math')
@Plugin.parser.add_argument('a', type=int) @Plugin.parser.add_argument('a', type=int)
@Plugin.parser.add_argument('b', type=int) @Plugin.parser.add_argument('b', type=int)
def on_add_command(self, event, args): def on_add_command(self, event, args):
event.msg.reply('{}'.format(args.a + args.b) event.msg.reply('{}'.format(args.a + args.b)
``` ```

0
docs/bot_tutorial/building_block_listeners.md → bot_tutorial/building_block_listeners.md

0
docs/bot_tutorial/building_block_plugins.md → bot_tutorial/building_block_plugins.md

0
docs/bot_tutorial/first_steps.md → bot_tutorial/first_steps.md

0
docs/installation.md → bot_tutorial/installation.md

122
docs/bot_tutorial/message_embeds.md → bot_tutorial/message_embeds.md

@ -1,61 +1,61 @@
# Message Embeds # Message Embeds
A [Message Embed](https://b1naryth1ef.github.io/disco/api/disco_types_message.html#messageembed) represents a Discord Embed object. An Embed object is another component of Discord messages that can be used to present data with special formatting and structure. A [Message Embed](https://b1naryth1ef.github.io/disco/api/disco_types_message.html#messageembed) represents a Discord Embed object. An Embed object is another component of Discord messages that can be used to present data with special formatting and structure.
An example of a message embed: An example of a message embed:
![A discord embed](https://i.stack.imgur.com/HRWHk.png "A discord embed") ![A discord embed](https://i.stack.imgur.com/HRWHk.png "A discord embed")
An embed can contain the following components: An embed can contain the following components:
* Author, including link and avatar * Author, including link and avatar
* Title * Title
* Description * Description
* Field(s) * Field(s)
* Thumbnail image * Thumbnail image
* Image * Image
* Footer, including text and icon * Footer, including text and icon
* Timestamp * Timestamp
* Color (sets the color of the left sidebar of the embed) * Color (sets the color of the left sidebar of the embed)
## Creating an embed ## Creating an embed
Creating an embed is simple, and can be done like this: Creating an embed is simple, and can be done like this:
```py ```py
from disco.types.message import MessageEmbed #We need this to create the embed from disco.types.message import MessageEmbed #We need this to create the embed
from datetime import datetime #We need this to set the timestamp from datetime import datetime #We need this to set the timestamp
embed = MessageEmbed() embed = MessageEmbed()
``` ```
This will create a default, empty, Discord Embed object. Now that we have that, let's assign some values to it. First, lets set the author and the title, with a link that leads to this page. This can be done as follows: This will create a default, empty, Discord Embed object. Now that we have that, let's assign some values to it. First, lets set the author and the title, with a link that leads to this page. This can be done as follows:
```py ```py
embed.set_author(name='b1nzy#1337', url='https://b1naryth1ef.github.com/disco', icon_url='http://i.imgur.com/1tjdUId.jpg') embed.set_author(name='b1nzy#1337', url='https://b1naryth1ef.github.com/disco', icon_url='http://i.imgur.com/1tjdUId.jpg')
embed.title = 'How to create an embed' embed.title = 'How to create an embed'
embed.url = 'https://b1naryth1ef.github.io/disco/bot_tutorial/message_embeds.html' #This URL will be hooked up to the title of the embed embed.url = 'https://b1naryth1ef.github.io/disco/bot_tutorial/message_embeds.html' #This URL will be hooked up to the title of the embed
``` ```
Now, we can add a description and a few fields: Now, we can add a description and a few fields:
```py ```py
embed.add_field(name='Inline field 1', value='Some value for this field', inline=True) embed.add_field(name='Inline field 1', value='Some value for this field', inline=True)
embed.add_field(name='Inline field 2', value='Another value for another field', inline=True) embed.add_field(name='Inline field 2', value='Another value for another field', inline=True)
embed.add_field(name='Inline field 3', value='Third value for the third field', inline=True) embed.add_field(name='Inline field 3', value='Third value for the third field', inline=True)
embed.add_field(name='A non-inline field', value='You can only have a max of 3 inline field on 1 line', inline=False) embed.add_field(name='A non-inline field', value='You can only have a max of 3 inline field on 1 line', inline=False)
embed.description = 'This is the general description of the embed, you can use the Discord supported MD in here too, to make it look extra fancy. For example, creating some **bold** or ~~strikethrough~~ text.' embed.description = 'This is the general description of the embed, you can use the Discord supported MD in here too, to make it look extra fancy. For example, creating some **bold** or ~~strikethrough~~ text.'
``` ```
Last up, let's set a footer, color and add a timestamp: Last up, let's set a footer, color and add a timestamp:
```py ```py
embed.timestamp = datetime.utcnow().isoformat() embed.timestamp = datetime.utcnow().isoformat()
embed.set_footer(text='Disco Message Embeds tutorial') embed.set_footer(text='Disco Message Embeds tutorial')
embed.color = '10038562' #This can be any color, but I chose a nice dark red tint embed.color = '10038562' #This can be any color, but I chose a nice dark red tint
``` ```
Once your embed is finished, you can send it using the `channel.send_message()` message or the `event.msg.reply()` function. Once your embed is finished, you can send it using the `channel.send_message()` message or the `event.msg.reply()` function.
With `channel.send_message()`: With `channel.send_message()`:
```py ```py
self.state.channels.get(<ChannelID>).send_message('[optional text]', embed=embed) self.state.channels.get(<ChannelID>).send_message('[optional text]', embed=embed)
``` ```
with the `event.msg.reply()` function: with the `event.msg.reply()` function:
```py ```py
event.msg.reply('[optional text]', embed=embed) event.msg.reply('[optional text]', embed=embed)
``` ```
The final embed we created in this tutorial would look like this: The final embed we created in this tutorial would look like this:
![alt text](http://i.imgur.com/G1sUcTm.png "The final embed") ![alt text](http://i.imgur.com/G1sUcTm.png "The final embed")

463
disco/types/channel.py

@ -44,12 +44,14 @@ class PermissionOverwrite(ChannelSubType):
---------- ----------
id : snowflake id : snowflake
The overwrite ID. The overwrite ID.
type : :const:`disco.types.channel.PermissionsOverwriteType` type : :const:`~disco.types.channel.PermissionsOverwriteType`
The overwrite type. The overwrite type.
allow : :class:`disco.types.permissions.PermissionValue` allow : :class:`~disco.types.permissions.PermissionValue`
All allowed permissions. All allowed permissions.
deny : :class:`disco.types.permissions.PermissionValue` deny : :class:`~disco.types.permissions.PermissionValue`
All denied permissions. All denied permissions.
compiled : :class:`~disco.types.permissions.PermissionValue`
All permissions, both allowed and denied
""" """
id = Field(snowflake) id = Field(snowflake)
type = Field(enum(PermissionOverwriteType)) type = Field(enum(PermissionOverwriteType))
@ -60,6 +62,27 @@ class PermissionOverwrite(ChannelSubType):
@classmethod @classmethod
def create_for_channel(cls, channel, entity, allow=0, deny=0): def create_for_channel(cls, channel, entity, allow=0, deny=0):
""""
Creates a permission overwrite
Generates a permission overwrite for a channel given the entity and the permission bitsets provided.
Parameters
---------
channel : :class:`~disco.types.channel.Channel`
Channel to apply permission overwrite too
entity : :class:`~disco.types.guild.Role` or :class:`~disco.types.guild.GuildMember`
The role or member to provide or deny permissions too
allow : :class:`~disco.types.permissions.Permissions`, optional
Permissions to allow the role or user for the channel
deny : :class:`~disco.types.permissions.Permissions` optional
Permissions to deny the role or user for the channel
Returns
-------
:class:`~disco.types.channel.PermissionOverwrite`
An instance of the overwrite that was created
"""
from disco.types.guild import Role from disco.types.guild import Role
ptype = PermissionOverwriteType.ROLE if isinstance(entity, Role) else PermissionOverwriteType.MEMBER ptype = PermissionOverwriteType.ROLE if isinstance(entity, Role) else PermissionOverwriteType.MEMBER
@ -80,6 +103,22 @@ class PermissionOverwrite(ChannelSubType):
return value return value
def save(self, **kwargs): def save(self, **kwargs):
"""
Send discord the permission overwrite
This method is used if you created a permission overwrite without uploading it.
For most use cases, use the create_for_channel classmethod instead.
Parameters
----------
kwargs
Extra arguments to provide channels_permissions_modify
Returns
-------
:class:`~disco.types.channel.PermissionOverwrite`
Returns itself, no changes made
"""
self.client.api.channels_permissions_modify(self.channel_id, self.client.api.channels_permissions_modify(self.channel_id,
self.id, self.id,
self.allow.value or 0, self.allow.value or 0,
@ -89,6 +128,16 @@ class PermissionOverwrite(ChannelSubType):
return self return self
def delete(self, **kwargs): def delete(self, **kwargs):
"""
Delete permission overwrite
Removes the permission overwrite instance from it's channel. You can reverse the change with the save method.
Parameters
----------
kwargs
Extra arguments to provide channels_permissions_delete
"""
self.client.api.channels_permissions_delete(self.channel_id, self.id, **kwargs) self.client.api.channels_permissions_delete(self.channel_id, self.id, **kwargs)
@ -100,8 +149,8 @@ class Channel(SlottedModel, Permissible):
---------- ----------
id : snowflake id : snowflake
The channel ID. The channel ID.
guild_id : Optional[snowflake] guild_id : snowflake, optional
The guild id this channel is part of. The guild id the channel is part of.
name : str name : str
The channel's name. The channel's name.
topic : str topic : str
@ -112,12 +161,16 @@ class Channel(SlottedModel, Permissible):
The channel's bitrate. The channel's bitrate.
user_limit : int user_limit : int
The channel's user limit. The channel's user limit.
recipients : list(:class:`disco.types.user.User`) recipients : list of :class:`~disco.types.user.User`
Members of this channel (if this is a DM channel). Members of the channel (if the is a DM channel).
type : :const:`ChannelType` type : :const:`~disco.types.channel.ChannelType`
The type of this channel. The type of the channel.
overwrites : dict(snowflake, :class:`disco.types.channel.PermissionOverwrite`) overwrites : dict of snowflake to :class:`~disco.types.channel.PermissionOverwrite`
Channel permissions overwrites. Channel permissions overwrites.
mention : str
The channel's mention
guild : :class:`~disco.types.guild.Guild`, optional
Guild the channel belongs to (or None if not applicable).
""" """
id = Field(snowflake) id = Field(snowflake)
guild_id = Field(snowflake) guild_id = Field(snowflake)
@ -152,9 +205,18 @@ class Channel(SlottedModel, Permissible):
""" """
Get the permissions a user has in the channel. Get the permissions a user has in the channel.
This method will first apply the user's permissions based on their roles and / or if they're the owner.
It will then overwrite those permissions with the channel's permission overwrites.
If the channel is a DM, the user is considered an administrator.
Parameters
----------
user : :class:`~disco.types.user.User` or :class:`~disco.types.guild.GuildMember`
A user-like instance of the ID of a user to get the permissions for
Returns Returns
------- -------
:class:`disco.types.permissions.PermissionValue` :class:`~disco.types.permissions.PermissionValue`
Computed permission value for the user. Computed permission value for the user.
""" """
if not self.guild_id: if not self.guild_id:
@ -189,7 +251,7 @@ class Channel(SlottedModel, Permissible):
@property @property
def is_guild(self): def is_guild(self):
""" """
Whether this channel belongs to a guild. Whether the channel belongs to a guild.
""" """
return self.type in ( return self.type in (
ChannelType.GUILD_TEXT, ChannelType.GUILD_TEXT,
@ -201,7 +263,7 @@ class Channel(SlottedModel, Permissible):
@property @property
def is_news(self): def is_news(self):
""" """
Whether this channel contains news for the guild (used for verified guilds Whether the channel contains news for the guild (used for verified guilds
to produce activity feed news). to produce activity feed news).
""" """
return self.type == ChannelType.GUILD_NEWS return self.type == ChannelType.GUILD_NEWS
@ -209,51 +271,55 @@ class Channel(SlottedModel, Permissible):
@property @property
def is_dm(self): def is_dm(self):
""" """
Whether this channel is a DM (does not belong to a guild). Whether the channel is a DM (does not belong to a guild).
""" """
return self.type in (ChannelType.DM, ChannelType.GROUP_DM) return self.type in (ChannelType.DM, ChannelType.GROUP_DM)
@property @property
def is_nsfw(self): def is_nsfw(self):
""" """
Whether this channel is an NSFW channel. Whether the channel is an NSFW channel.
""" """
return bool(self.type == ChannelType.GUILD_TEXT and (self.nsfw or NSFW_RE.match(self.name))) return bool(self.type == ChannelType.GUILD_TEXT and (self.nsfw or NSFW_RE.match(self.name)))
@property @property
def is_voice(self): def is_voice(self):
""" """
Whether this channel supports voice. Whether the channel supports voice.
""" """
return self.type in (ChannelType.GUILD_VOICE, ChannelType.GROUP_DM) return self.type in (ChannelType.GUILD_VOICE, ChannelType.GROUP_DM)
@property @property
def messages(self): def messages(self):
""" """
A default `MessageIterator` for the channel, can be used to quickly and A default :class:`~disco.types.channel.MessageIterator` for the channel, can be used to quickly and
easily iterate over the channels entire message history. For more control, easily iterate over the channels entire message history. For more control,
use `Channel.messages_iter`. use :func:`~disco.types.channel.Channel.messages_iter`.
""" """
return self.messages_iter() return self.messages_iter()
@cached_property @cached_property
def guild(self): def guild(self):
"""
Guild this channel belongs to (or None if not applicable).
"""
return self.client.state.guilds.get(self.guild_id) return self.client.state.guilds.get(self.guild_id)
@cached_property @cached_property
def parent(self): def parent(self):
""" """
Parent this channel belongs to (or None if not applicable). Parent the channel belongs to (or None if not applicable).
""" """
return self.guild.channels.get(self.parent_id) return self.guild.channels.get(self.parent_id)
def messages_iter(self, **kwargs): def messages_iter(self, **kwargs):
""" """
Creates a new `MessageIterator` for the channel with the given keyword Creates message iterator
Creates a new :class:`~disco.types.channel.MessageIterator` for the channel with the given keyword
arguments. arguments.
Parameters
----------
kwargs
Extra arguments to be passed into :class:`~disco.types.channel.MessageIterator`
""" """
return MessageIterator(self.client, self, **kwargs) return MessageIterator(self.client, self, **kwargs)
@ -262,30 +328,49 @@ class Channel(SlottedModel, Permissible):
Attempts to fetch and return a `Message` from the message object Attempts to fetch and return a `Message` from the message object
or id. or id.
Arguments
---------
message : :class:`~disco.types.message.Message` or snowflake
Returns Returns
------- -------
`Message` :class:`~disco.types.message.Message`
The fetched message. The fetched message.
""" """
return self.client.api.channels_messages_get(self.id, to_snowflake(message)) return self.client.api.channels_messages_get(self.id, to_snowflake(message))
def get_invites(self): def get_invites(self):
""" """
Finds invites for the channel
Invites are not global for a server like they used to be, and now must be created for specific channels.
This method finds all the invites that use the channel as the landing page.
Returns Returns
------- -------
list(`Invite`) list of :class:`~disco.types.invite.Invite`
Returns a list of all invites for this channel. Returns a list of all invites for the channel.
""" """
return self.client.api.channels_invites_list(self.id) return self.client.api.channels_invites_list(self.id)
def create_invite(self, *args, **kwargs): def create_invite(self, *args, **kwargs):
""" """
Create an invite for the channel
Attempts to create a new invite with the given arguments. For more Attempts to create a new invite with the given arguments. For more
information see `Invite.create_for_channel`. information see :func:`~disco.types.invite.Invite.create_for_channel`.
Parameters
----------
args
Arguments to be passed into :func:`~disco.types.invite.Invite.create_for_channel`
kwargs
Keyword arguments to be passed into :func:`~disco.types.invite.Invite.create_for_channel`
Returns Returns
------- -------
`Invite` :class:`~disco.types.invite.Invite`
The generated invite for the channel
""" """
from disco.types.invite import Invite from disco.types.invite import Invite
@ -293,10 +378,14 @@ class Channel(SlottedModel, Permissible):
def get_pins(self): def get_pins(self):
""" """
Get pinned messages
Messages that have been pinned to the channel if there are any
Returns Returns
------- -------
list(`Message`) list of :class:`~disco.types.message.Message`
Returns a list of all pinned messages for this channel. Returns a list of all pinned messages for the channel.
""" """
return self.client.api.channels_pins_list(self.id) return self.client.api.channels_pins_list(self.id)
@ -304,9 +393,10 @@ class Channel(SlottedModel, Permissible):
""" """
Pins the given message to the channel. Pins the given message to the channel.
Parameters Parameters
---------- ----------
message : `Message`|snowflake message : :class:`~disco.types.message.Message` or snowflake
The message or message ID to pin. The message or message ID to pin.
""" """
self.client.api.channels_pins_create(self.id, to_snowflake(message)) self.client.api.channels_pins_create(self.id, to_snowflake(message))
@ -317,25 +407,36 @@ class Channel(SlottedModel, Permissible):
Parameters Parameters
---------- ----------
message : `Message`|snowflake message : :class:`~disco.types.message.Message` or snowflake
The message or message ID to pin. The message or message ID to pin.
""" """
self.client.api.channels_pins_delete(self.id, to_snowflake(message)) self.client.api.channels_pins_delete(self.id, to_snowflake(message))
def get_webhooks(self): def get_webhooks(self):
""" """
Fetchs all webhooks operating on the channel
Returns Returns
------- -------
list(`Webhook`) list of :class:`~disco.types.webhook.Webhook`
Returns a list of all webhooks for this channel. Returns a list of all webhooks for the channel.
""" """
return self.client.api.channels_webhooks_list(self.id) return self.client.api.channels_webhooks_list(self.id)
def create_webhook(self, *args, **kwargs): def create_webhook(self, *args, **kwargs):
""" """
Creates a webhook for this channel. See `APIClient.channels_webhooks_create` Creates a webhook
Creates a webhook for the channel. See :func:`~disco.api.client.APIClient.channels_webhooks_create`
for more information. for more information.
Parameters
----------
args
Arguments to be passed into :func:`~disco.api.client.APIClient.channels_webhooks_create`
kwargs
Keyword arguments to be passed into :func:`~disco.api.client.APIClient.channels_webhooks_create`
Returns Returns
------- -------
`Webhook` `Webhook`
@ -345,37 +446,58 @@ class Channel(SlottedModel, Permissible):
def send_message(self, *args, **kwargs): def send_message(self, *args, **kwargs):
""" """
Send a message to this channel. See `APIClient.channels_messages_create` Send a message
Send a message to the channel. See :func:`~disco.api.client.APIClient.channels_messages_create`
for more information. for more information.
Parameters
----------
args
Arguments to be passed into :func:`~disco.api.client.APIClient.channels_messages_create`
kwargs
Keyword arguments to be passed into :func:`~disco.api.client.APIClient.channels_messages_create`
Returns Returns
------- -------
`disco.types.message.Message` :class:`~disco.types.message.Message`
The created message. The sent message.
""" """
return self.client.api.channels_messages_create(self.id, *args, **kwargs) return self.client.api.channels_messages_create(self.id, *args, **kwargs)
def send_typing(self): def send_typing(self):
""" """
Sends a typing event to this channel. See `APIClient.channels_typing` Signal typing status
for more information.
Sends a typing event to the channel. this will make it seem as though the bot is sending a message.
This status is removed if a message is not sent before another typing event is sent, or a message is sent.
See :func:`~disco.api.client.APIClient.channels_typing` for more information.
""" """
self.client.api.channels_typing(self.id) self.client.api.channels_typing(self.id)
def create_overwrite(self, *args, **kwargs): def create_overwrite(self, *args, **kwargs):
""" """
Creates a `PermissionOverwrite` for this channel. See Create permission overwrite
`PermissionOverwrite.create_for_channel` for more information.
Creates a `PermissionOverwrite` for the channel.
See `PermissionOverwrite.create_for_channel` for more information.
Parameters
----------
args
Arguments to be passed into :func:`~disco.types.channel.PermissionOverwrite.create_for_channel`
kwargs
Keyword arguments to be passed into :func:`~disco.types.channel.PermissionOverwrite.create_for_channel`
""" """
return PermissionOverwrite.create_for_channel(self, *args, **kwargs) return PermissionOverwrite.create_for_channel(self, *args, **kwargs)
def delete_message(self, message): def delete_message(self, message):
""" """
Deletes a single message from this channel. Deletes a single message from the channel.
Parameters Parameters
---------- ----------
message : snowflake|`Message` message : snowflake or :class:`~disco.types.message.Message`
The message to delete. The message to delete.
""" """
self.client.api.channels_messages_delete(self.id, to_snowflake(message)) self.client.api.channels_messages_delete(self.id, to_snowflake(message))
@ -383,14 +505,16 @@ class Channel(SlottedModel, Permissible):
@one_or_many @one_or_many
def delete_messages(self, messages): def delete_messages(self, messages):
""" """
Deletes many messages
Deletes a set of messages using the correct API route based on the number Deletes a set of messages using the correct API route based on the number
of messages passed. of messages passed.
Parameters Parameters
---------- ----------
messages : list(snowflake|`Message`) messages : list of snowflake or list of :class:`~disco.types.message.Message`
List of messages (or message ids) to delete. All messages must originate List of messages (or message ids) to delete. All messages must originate
from this channel. from the channel.
""" """
message_ids = list(map(to_snowflake, messages)) message_ids = list(map(to_snowflake, messages))
@ -405,13 +529,27 @@ class Channel(SlottedModel, Permissible):
self.delete_message(msg) self.delete_message(msg)
def delete(self, **kwargs): def delete(self, **kwargs):
"""
Delete guild channel
Parameters
----------
kwargs
Keyword arguments to be passed into :func:`~disco.api.client.APIClient.channels_delete`
Raises
------
AssertionError
Raised is the channel is a DM, or if the bot doesn't have MANAGE_CHANNELS permissions for this guild.
"""
assert (self.is_dm or self.guild.can(self.client.state.me, Permissions.MANAGE_CHANNELS)), 'Invalid Permissions' assert (self.is_dm or self.guild.can(self.client.state.me, Permissions.MANAGE_CHANNELS)), 'Invalid Permissions'
self.client.api.channels_delete(self.id, **kwargs) self.client.api.channels_delete(self.id, **kwargs)
def close(self): def close(self):
""" """
Closes a DM channel. This is intended as a safer version of `delete`, Delete guild channel
enforcing that the channel is actually a DM.
Copy of :func:`~disco.types.channel.Channel.delete`, but doesn't check if the bot has correct permissions
""" """
assert self.is_dm, 'Cannot close non-DM channel' assert self.is_dm, 'Cannot close non-DM channel'
self.delete() self.delete()
@ -419,24 +557,79 @@ class Channel(SlottedModel, Permissible):
def set_topic(self, topic, reason=None): def set_topic(self, topic, reason=None):
""" """
Sets the channels topic. Sets the channels topic.
Parameters
----------
topic : str
The channel's topic or description
reason : str, optional
The reason for setting the topic
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
""" """
return self.client.api.channels_modify(self.id, topic=topic, reason=reason) return self.client.api.channels_modify(self.id, topic=topic, reason=reason)
def set_name(self, name, reason=None): def set_name(self, name, reason=None):
""" """
Sets the channels name. Sets the channels name.
Parameters
----------
name : str
The new channel name
reason : str
Reason for channel name update
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
""" """
return self.client.api.channels_modify(self.id, name=name, reason=reason) return self.client.api.channels_modify(self.id, name=name, reason=reason)
def set_position(self, position, reason=None): def set_position(self, position, reason=None):
""" """
Sets the channels position. Sets the channels position.
Change the order which channels are listed.
Parameters
----------
position : int
The new channel position (Check the guild to see how many channels it has)
reason : str
Reason for channel position update
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
""" """
return self.client.api.channels_modify(self.id, position=position, reason=reason) return self.client.api.channels_modify(self.id, position=position, reason=reason)
def set_nsfw(self, value, reason=None): def set_nsfw(self, value, reason=None):
""" """
Sets whether the channel is NSFW. Sets whether the channel is NSFW.
Parameters
----------
value : bool
Whether the channel should be NSFW or not
reason : str
Reason for channel nsfw update
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
Raises
------
AssertionError
Raised if the channel type isn't a guild text channel
""" """
assert (self.type == ChannelType.GUILD_TEXT) assert (self.type == ChannelType.GUILD_TEXT)
return self.client.api.channels_modify(self.id, nsfw=value, reason=reason) return self.client.api.channels_modify(self.id, nsfw=value, reason=reason)
@ -444,6 +637,23 @@ class Channel(SlottedModel, Permissible):
def set_bitrate(self, bitrate, reason=None): def set_bitrate(self, bitrate, reason=None):
""" """
Sets the channels bitrate. Sets the channels bitrate.
Parameters
----------
bitrate : int
The voice channel's new bitrate
reason : str
Reason for channel bitrate update
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
Raises
------
AssertionError
Raised if the channel isn't a voice channel
""" """
assert (self.is_voice) assert (self.is_voice)
return self.client.api.channels_modify(self.id, bitrate=bitrate, reason=reason) return self.client.api.channels_modify(self.id, bitrate=bitrate, reason=reason)
@ -451,6 +661,25 @@ class Channel(SlottedModel, Permissible):
def set_user_limit(self, user_limit, reason=None): def set_user_limit(self, user_limit, reason=None):
""" """
Sets the channels user limit. Sets the channels user limit.
Voice channels can be capped at how many people can be in it, this method sets that limit.
Parameters
----------
user_limit : int
The max amount of people in a voice channel
reason : str
Reason for channel user limit update
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
Raises
------
AssertionError
Raised if channel isn't a voice channel
""" """
assert (self.is_voice) assert (self.is_voice)
return self.client.api.channels_modify(self.id, user_limit=user_limit, reason=reason) return self.client.api.channels_modify(self.id, user_limit=user_limit, reason=reason)
@ -458,6 +687,25 @@ class Channel(SlottedModel, Permissible):
def set_parent(self, parent, reason=None): def set_parent(self, parent, reason=None):
""" """
Sets the channels parent. Sets the channels parent.
Channels can be organized under categories, this method moves the channel under a category
Parameters
----------
parent : :class:`~disco.types.channel.Channel` or snowflake
The category to move the channel under
reason : str
Reason for channel parent update
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
Raises
------
AssertionError
Raised if the channel doesn't belong to a guild
""" """
assert (self.is_guild) assert (self.is_guild)
return self.client.api.channels_modify( return self.client.api.channels_modify(
@ -467,7 +715,26 @@ class Channel(SlottedModel, Permissible):
def set_slowmode(self, interval, reason=None): def set_slowmode(self, interval, reason=None):
""" """
Sets the channels slowmode (rate_limit_per_user). Sets the channels slowmode
Slowmode is used to restrict how many messages a user can send at once
Parameters
----------
interval : int
The amount of seconds users have to wait after sending a message (between 0-21600 inclusive)
reason : str
Reason for channel slowmode update
Returns
-------
:class:`~disco.types.channel.Channel`
Updated version of the channel
Raises
------
AssertionError
Raised if the channel is not a guild text channel
""" """
assert (self.type == ChannelType.GUILD_TEXT) assert (self.type == ChannelType.GUILD_TEXT)
return self.client.api.channels_modify( return self.client.api.channels_modify(
@ -477,8 +744,27 @@ class Channel(SlottedModel, Permissible):
def create_text_channel(self, *args, **kwargs): def create_text_channel(self, *args, **kwargs):
""" """
Creates a sub-text-channel in this category. See `Guild.create_text_channel` Create text channel under this category
for arguments and more information.
Creates a text channel under this channel to keep channels organized.
This can only be used if the channel is a category.
Parameters
----------
args
Arguments to be passed into :func:`~disco.types.guild.Guild.create_text_channel`
kwargs
Keyword arguments to be passed into :func:`~disco.types.Guild.create_text_channel`
Returns
-------
:class:`~disco.types.channel.Channel`
Created text channel
Raises
------
ValueError
Raised if the channel is not a category channel
""" """
if self.type != ChannelType.GUILD_CATEGORY: if self.type != ChannelType.GUILD_CATEGORY:
raise ValueError('Cannot create a sub-channel on a non-category channel') raise ValueError('Cannot create a sub-channel on a non-category channel')
@ -491,8 +777,27 @@ class Channel(SlottedModel, Permissible):
def create_voice_channel(self, *args, **kwargs): def create_voice_channel(self, *args, **kwargs):
""" """
Creates a sub-voice-channel in this category. See `Guild.create_voice_channel` Create voice channel under this category
for arguments and more information.
Creates a voice channel under this channel to keep channels organized.
This can only be used if the channel is a category.
Parameters
----------
args
Arguments to be passed into :func:`~disco.types.guild.Guild.create_voice_channel`
kwargs
Keyword arguments to be passed into :func:`~disco.types.Guild.create_voice_channel`
Returns
-------
:class:`~disco.types.channel.Channel`
Created text channel
Raises
------
ValueError
Raised if the channel is not a category channel
""" """
if self.type != ChannelType.GUILD_CATEGORY: if self.type != ChannelType.GUILD_CATEGORY:
raise ValueError('Cannot create a sub-channel on a non-category channel') raise ValueError('Cannot create a sub-channel on a non-category channel')
@ -506,18 +811,24 @@ class Channel(SlottedModel, Permissible):
class MessageIterator(object): class MessageIterator(object):
""" """
An iterator which supports scanning through the messages for a channel. Message iterator
The discord API allows you to fetch 100 messages at once.
After that 100 you need to create a new request based on the last messages's snowflake.
This class makes interacting with the api much easier, and provides a constant stream of messages.
This is used internally for :func:`~disco.types.channel.Channel.messages_iter`,
and the :attr:`~disco.types.channel.Channel.messages` attribute.
Parameters Attributes
---------- ----------
client : :class:`disco.client.Client` client : :class:`~disco.client.Client`
The disco client instance to use when making requests. The disco client instance to use when making requests.
channel : `Channel` channel : :class:`~disco.types.channel.Channel`
The channel to iterate within. The channel to iterate within.
direction : :attr:`MessageIterator.Direction` direction : :attr:`~disco.types.channel.MessageIterator.Direction`
The direction in which this iterator will move. The direction in which the iterator will move.
bulk : bool bulk : bool
If true, this iterator will yield messages in list batches, otherwise each If true, the iterator will yield messages in list batches, otherwise each
message will be yield individually. message will be yield individually.
before : snowflake before : snowflake
The message to begin scanning at. The message to begin scanning at.
@ -527,6 +838,16 @@ class MessageIterator(object):
The number of messages to request per API call. The number of messages to request per API call.
""" """
class Direction(object): class Direction(object):
"""
What direction to go when traversing a channel
Attributes
----------
UP : int
Search through messages earliest to oldest
DOWN : int
Search through messages oldest to earliest
"""
UP = 1 UP = 1
DOWN = 2 DOWN = 2
@ -547,9 +868,14 @@ class MessageIterator(object):
def fill(self): def fill(self):
""" """
Fills the internal buffer up with :class:`disco.types.message.Message` objects from the API. Fetch messages
Returns a boolean indicating whether items were added to the buffer. Fills the internal buffer up with :class:`~disco.types.message.Message` objects from the API.
Returns
-------
bool
If True, the buffer was filled with more messages
""" """
self._buffer = self.client.api.channels_messages_list( self._buffer = self.client.api.channels_messages_list(
self.channel.id, self.channel.id,
@ -573,6 +899,19 @@ class MessageIterator(object):
return True return True
def next(self): def next(self):
"""
Get the next message
Returns
-------
:class:`~disco.types.message.Message`
The next message in the channel
Raises
------
StopIteration
Raised when there are no more messages left
"""
return self.__next__() return self.__next__()
def __iter__(self): def __iter__(self):

670
disco/types/guild.py

File diff suppressed because it is too large

20
docs/Makefile

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

10
docs/book.json

@ -1,10 +0,0 @@
{
"title": "Disco",
"plugins": ["prism", "-highlight", "hints", "anchorjs"],
"pluginsConfig": {
"anchorjs": {
"placement": "left",
"visible": "always"
}
}
}

20
docs/build.sh

@ -1,20 +0,0 @@
#!/bin/bash
echo "Building Autogenerated API Docs"
pushd ..
python -m biblio.cli ../disco/.biblio.yaml
popd
echo "Running Gitbook Build"
gitbook build
if [ ! -z "${GH_TOKEN:-}" ]; then
echo "Deploying to Github Pages"
pushd _book/
git init
git config user.name "AutoDoc"
git config user.email "<>"
git add .
git commit -m "Generated Documentation"
git push --force --quiet "https://${GH_TOKEN}@github.com/b1naryth1ef/disco" master:gh-pages
popd
fi

60
docs/conf.py

@ -0,0 +1,60 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
# -- Project information -----------------------------------------------------
project = 'disco'
copyright = '2020, Andrei Zbikowski'
author = 'Andrei Zbikowski'
# The full version, including alpha/beta/rc tags
release = '0.0.12'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.napoleon',
'sphinx.ext.autodoc'
]
napoleon_use_ivar = True
napoleon_use_rtype = False
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'groundwork'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

17
docs/index.rst

@ -0,0 +1,17 @@
Welcome to disco's documentation!
=================================
.. toctree::
:maxdepth: 1
:caption: Contents:
types/index
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

35
docs/make.bat

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

5
docs/types/channel.rst

@ -0,0 +1,5 @@
Disco Channel API
=================
.. automodule:: disco.types.channel
:members:

5
docs/types/guild.rst

@ -0,0 +1,5 @@
Disco Guild API
=================
.. automodule:: disco.types.guild
:members:

9
docs/types/index.rst

@ -0,0 +1,9 @@
Disco Type API
==============
.. toctree::
:maxdepth: 1
:caption: Disco Type APIs
channel
guild

5
setup.py

@ -20,7 +20,10 @@ extras_require = {
'wsaccel==0.6.2', 'wsaccel==0.6.2',
], ],
'sharding': ['gipc==0.6.0'], 'sharding': ['gipc==0.6.0'],
'docs': ['biblio==0.0.4'], 'docs': [
'sphinx==2.4.4',
'groundwork-sphinx-theme==1.1.1'
],
} }
setup( setup(

Loading…
Cancel
Save