diff --git a/.biblio.yaml b/.biblio.yaml deleted file mode 100644 index 28d14060..00000000 --- a/.biblio.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: Disco - -rules: - disco/*.py: - parser: numpy - -outputs: - - {type: markdown, path: docs/api/, title: 'Disco Documentation'} diff --git a/.gitignore b/.gitignore index 23d634fc..8d71a8e0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ storage.json .cache/ .benchmarks/ __pycache__ -.venv +.venv/ # Documentation stuff docs/api/ diff --git a/docs/README.md b/bot_tutorial/README.md similarity index 100% rename from docs/README.md rename to bot_tutorial/README.md diff --git a/docs/SUMMARY.md b/bot_tutorial/SUMMARY.md similarity index 100% rename from docs/SUMMARY.md rename to bot_tutorial/SUMMARY.md diff --git a/docs/bot_tutorial/advanced.md b/bot_tutorial/advanced.md similarity index 100% rename from docs/bot_tutorial/advanced.md rename to bot_tutorial/advanced.md diff --git a/docs/bot_tutorial/building_block_commands.md b/bot_tutorial/building_block_commands.md similarity index 97% rename from docs/bot_tutorial/building_block_commands.md rename to bot_tutorial/building_block_commands.md index 785f6720..6022d011 100644 --- a/docs/bot_tutorial/building_block_commands.md +++ b/bot_tutorial/building_block_commands.md @@ -1,113 +1,113 @@ -# 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: -`!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. - -## 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: -First, create a Plugin class: -```py -class myPlugin(Plugin): -``` -Now, we can add a command to it. The command will be named ping, and it will simply reply with `pong!` -```py -@Plugin.command('ping') -def on_ping_command(self, event): - event.msg.reply('Pong!') -``` -And there we go! Our very first command! - -## 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. -```py -@Plugin.command('echo', '') -def on_echo_command(self, event, 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. -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. - -## 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. -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: -```py -@Plugin.command('add', ' ', group='math') -def on_add_command(self, event, 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. - -## 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` - -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. -```py -tags = {} - -@Plugin.command('tag', ' [value:str...]') -def on_tag_command(self, event, name, value=None): - - if value: - tags[name] = value - event.msg.reply(':ok_hand: created tag `{}`'.format(name)) - else: - if name in tags.keys(): - return event.msg.reply(tags[name]) - else: - return event.msg.reply('Unknown tag: `{}`'.format(name)) -``` - -## 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. -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 -@Plugin.command('add', parser=True, group='math') -@Plugin.parser.add_argument('a', type=int) -@Plugin.parser.add_argument('b', type=int) -def on_add_command(self, event, args): - event.msg.reply('{}'.format(args.a + args.b) -``` - -These are all the commands we created in this tutorial: -```py -class myPlugin(Plugin): - @Plugin.command('ping') - def on_ping_command(self, event): - event.msg.reply('Pong!') - - @Plugin.command('echo', '') - def on_echo_command(self, event, content): - event.msg.reply(content) - - @Plugin.command('add', ' ', group='math') - def on_add_command(self, event, a, b): - event.msg.reply('{}'.format(a+b)) - - tags = {} - @Plugin.command('tag', ' [value:str...]') - def on_tag_command(self, event, name, value=None): - - if value: - tags[name] = value - event.msg.reply(':ok_hand: created tag `{}`'.format(name)) - else: - if name in tags.keys(): - return event.msg.reply(tags[name]) - else: - return event.msg.reply('Unknown tag: `{}`'.format(name)) - - @Plugin.command('add', parser=True, group='math') - @Plugin.parser.add_argument('a', type=int) - @Plugin.parser.add_argument('b', type=int) - def on_add_command(self, event, args): - event.msg.reply('{}'.format(args.a + args.b) -``` +# 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: +`!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. + +## 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: +First, create a Plugin class: +```py +class myPlugin(Plugin): +``` +Now, we can add a command to it. The command will be named ping, and it will simply reply with `pong!` +```py +@Plugin.command('ping') +def on_ping_command(self, event): + event.msg.reply('Pong!') +``` +And there we go! Our very first command! + +## 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. +```py +@Plugin.command('echo', '') +def on_echo_command(self, event, 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. +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. + +## 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. +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: +```py +@Plugin.command('add', ' ', group='math') +def on_add_command(self, event, 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. + +## 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` + +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. +```py +tags = {} + +@Plugin.command('tag', ' [value:str...]') +def on_tag_command(self, event, name, value=None): + + if value: + tags[name] = value + event.msg.reply(':ok_hand: created tag `{}`'.format(name)) + else: + if name in tags.keys(): + return event.msg.reply(tags[name]) + else: + return event.msg.reply('Unknown tag: `{}`'.format(name)) +``` + +## 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. +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 +@Plugin.command('add', parser=True, group='math') +@Plugin.parser.add_argument('a', type=int) +@Plugin.parser.add_argument('b', type=int) +def on_add_command(self, event, args): + event.msg.reply('{}'.format(args.a + args.b) +``` + +These are all the commands we created in this tutorial: +```py +class myPlugin(Plugin): + @Plugin.command('ping') + def on_ping_command(self, event): + event.msg.reply('Pong!') + + @Plugin.command('echo', '') + def on_echo_command(self, event, content): + event.msg.reply(content) + + @Plugin.command('add', ' ', group='math') + def on_add_command(self, event, a, b): + event.msg.reply('{}'.format(a+b)) + + tags = {} + @Plugin.command('tag', ' [value:str...]') + def on_tag_command(self, event, name, value=None): + + if value: + tags[name] = value + event.msg.reply(':ok_hand: created tag `{}`'.format(name)) + else: + if name in tags.keys(): + return event.msg.reply(tags[name]) + else: + return event.msg.reply('Unknown tag: `{}`'.format(name)) + + @Plugin.command('add', parser=True, group='math') + @Plugin.parser.add_argument('a', type=int) + @Plugin.parser.add_argument('b', type=int) + def on_add_command(self, event, args): + event.msg.reply('{}'.format(args.a + args.b) +``` diff --git a/docs/bot_tutorial/building_block_listeners.md b/bot_tutorial/building_block_listeners.md similarity index 100% rename from docs/bot_tutorial/building_block_listeners.md rename to bot_tutorial/building_block_listeners.md diff --git a/docs/bot_tutorial/building_block_plugins.md b/bot_tutorial/building_block_plugins.md similarity index 100% rename from docs/bot_tutorial/building_block_plugins.md rename to bot_tutorial/building_block_plugins.md diff --git a/docs/bot_tutorial/first_steps.md b/bot_tutorial/first_steps.md similarity index 100% rename from docs/bot_tutorial/first_steps.md rename to bot_tutorial/first_steps.md diff --git a/docs/installation.md b/bot_tutorial/installation.md similarity index 100% rename from docs/installation.md rename to bot_tutorial/installation.md diff --git a/docs/bot_tutorial/message_embeds.md b/bot_tutorial/message_embeds.md similarity index 97% rename from docs/bot_tutorial/message_embeds.md rename to bot_tutorial/message_embeds.md index c8497e40..f941598a 100644 --- a/docs/bot_tutorial/message_embeds.md +++ b/bot_tutorial/message_embeds.md @@ -1,61 +1,61 @@ -# 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. - -An example of a message embed: - -![A discord embed](https://i.stack.imgur.com/HRWHk.png "A discord embed") - -An embed can contain the following components: -* Author, including link and avatar -* Title -* Description -* Field(s) -* Thumbnail image -* Image -* Footer, including text and icon -* Timestamp -* Color (sets the color of the left sidebar of the embed) - -## Creating an embed -Creating an embed is simple, and can be done like this: -```py -from disco.types.message import MessageEmbed #We need this to create the embed -from datetime import datetime #We need this to set the timestamp - -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: -```py -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.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: -```py -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 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.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: -```py -embed.timestamp = datetime.utcnow().isoformat() -embed.set_footer(text='Disco Message Embeds tutorial') -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. -With `channel.send_message()`: -```py -self.state.channels.get().send_message('[optional text]', embed=embed) -``` -with the `event.msg.reply()` function: -```py -event.msg.reply('[optional text]', embed=embed) -``` - -The final embed we created in this tutorial would look like this: - -![alt text](http://i.imgur.com/G1sUcTm.png "The final embed") +# 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. + +An example of a message embed: + +![A discord embed](https://i.stack.imgur.com/HRWHk.png "A discord embed") + +An embed can contain the following components: +* Author, including link and avatar +* Title +* Description +* Field(s) +* Thumbnail image +* Image +* Footer, including text and icon +* Timestamp +* Color (sets the color of the left sidebar of the embed) + +## Creating an embed +Creating an embed is simple, and can be done like this: +```py +from disco.types.message import MessageEmbed #We need this to create the embed +from datetime import datetime #We need this to set the timestamp + +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: +```py +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.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: +```py +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 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.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: +```py +embed.timestamp = datetime.utcnow().isoformat() +embed.set_footer(text='Disco Message Embeds tutorial') +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. +With `channel.send_message()`: +```py +self.state.channels.get().send_message('[optional text]', embed=embed) +``` +with the `event.msg.reply()` function: +```py +event.msg.reply('[optional text]', embed=embed) +``` + +The final embed we created in this tutorial would look like this: + +![alt text](http://i.imgur.com/G1sUcTm.png "The final embed") diff --git a/disco/types/channel.py b/disco/types/channel.py index 6dc2c836..4e79b0dc 100644 --- a/disco/types/channel.py +++ b/disco/types/channel.py @@ -44,12 +44,14 @@ class PermissionOverwrite(ChannelSubType): ---------- id : snowflake The overwrite ID. - type : :const:`disco.types.channel.PermissionsOverwriteType` + type : :const:`~disco.types.channel.PermissionsOverwriteType` The overwrite type. - allow : :class:`disco.types.permissions.PermissionValue` + allow : :class:`~disco.types.permissions.PermissionValue` All allowed permissions. - deny : :class:`disco.types.permissions.PermissionValue` + deny : :class:`~disco.types.permissions.PermissionValue` All denied permissions. + compiled : :class:`~disco.types.permissions.PermissionValue` + All permissions, both allowed and denied """ id = Field(snowflake) type = Field(enum(PermissionOverwriteType)) @@ -60,6 +62,27 @@ class PermissionOverwrite(ChannelSubType): @classmethod 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 ptype = PermissionOverwriteType.ROLE if isinstance(entity, Role) else PermissionOverwriteType.MEMBER @@ -80,6 +103,22 @@ def compiled(self): return value 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.id, self.allow.value or 0, @@ -89,6 +128,16 @@ def save(self, **kwargs): return self 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) @@ -100,8 +149,8 @@ class Channel(SlottedModel, Permissible): ---------- id : snowflake The channel ID. - guild_id : Optional[snowflake] - The guild id this channel is part of. + guild_id : snowflake, optional + The guild id the channel is part of. name : str The channel's name. topic : str @@ -112,12 +161,16 @@ class Channel(SlottedModel, Permissible): The channel's bitrate. user_limit : int The channel's user limit. - recipients : list(:class:`disco.types.user.User`) - Members of this channel (if this is a DM channel). - type : :const:`ChannelType` - The type of this channel. - overwrites : dict(snowflake, :class:`disco.types.channel.PermissionOverwrite`) + recipients : list of :class:`~disco.types.user.User` + Members of the channel (if the is a DM channel). + type : :const:`~disco.types.channel.ChannelType` + The type of the channel. + overwrites : dict of snowflake to :class:`~disco.types.channel.PermissionOverwrite` 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) guild_id = Field(snowflake) @@ -152,9 +205,18 @@ def get_permissions(self, user): """ 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 ------- - :class:`disco.types.permissions.PermissionValue` + :class:`~disco.types.permissions.PermissionValue` Computed permission value for the user. """ if not self.guild_id: @@ -189,7 +251,7 @@ def mention(self): @property def is_guild(self): """ - Whether this channel belongs to a guild. + Whether the channel belongs to a guild. """ return self.type in ( ChannelType.GUILD_TEXT, @@ -201,7 +263,7 @@ def is_guild(self): @property 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). """ return self.type == ChannelType.GUILD_NEWS @@ -209,51 +271,55 @@ def is_news(self): @property 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) @property 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))) @property def is_voice(self): """ - Whether this channel supports voice. + Whether the channel supports voice. """ return self.type in (ChannelType.GUILD_VOICE, ChannelType.GROUP_DM) @property 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, - use `Channel.messages_iter`. + use :func:`~disco.types.channel.Channel.messages_iter`. """ return self.messages_iter() @cached_property def guild(self): - """ - Guild this channel belongs to (or None if not applicable). - """ return self.client.state.guilds.get(self.guild_id) @cached_property 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) 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. + + Parameters + ---------- + kwargs + Extra arguments to be passed into :class:`~disco.types.channel.MessageIterator` """ return MessageIterator(self.client, self, **kwargs) @@ -262,30 +328,49 @@ def get_message(self, message): Attempts to fetch and return a `Message` from the message object or id. + Arguments + --------- + message : :class:`~disco.types.message.Message` or snowflake + Returns ------- - `Message` + :class:`~disco.types.message.Message` The fetched message. """ return self.client.api.channels_messages_get(self.id, to_snowflake(message)) 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 ------- - list(`Invite`) - Returns a list of all invites for this channel. + list of :class:`~disco.types.invite.Invite` + Returns a list of all invites for the channel. """ return self.client.api.channels_invites_list(self.id) def create_invite(self, *args, **kwargs): """ + Create an invite for the channel + 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 ------- - `Invite` + :class:`~disco.types.invite.Invite` + The generated invite for the channel """ from disco.types.invite import Invite @@ -293,10 +378,14 @@ def create_invite(self, *args, **kwargs): def get_pins(self): """ + Get pinned messages + + Messages that have been pinned to the channel if there are any + Returns ------- - list(`Message`) - Returns a list of all pinned messages for this channel. + list of :class:`~disco.types.message.Message` + Returns a list of all pinned messages for the channel. """ return self.client.api.channels_pins_list(self.id) @@ -304,9 +393,10 @@ def create_pin(self, message): """ Pins the given message to the channel. + Parameters ---------- - message : `Message`|snowflake + message : :class:`~disco.types.message.Message` or snowflake The message or message ID to pin. """ self.client.api.channels_pins_create(self.id, to_snowflake(message)) @@ -317,25 +407,36 @@ def delete_pin(self, message): Parameters ---------- - message : `Message`|snowflake + message : :class:`~disco.types.message.Message` or snowflake The message or message ID to pin. """ self.client.api.channels_pins_delete(self.id, to_snowflake(message)) def get_webhooks(self): """ + Fetchs all webhooks operating on the channel + Returns ------- - list(`Webhook`) - Returns a list of all webhooks for this channel. + list of :class:`~disco.types.webhook.Webhook` + Returns a list of all webhooks for the channel. """ return self.client.api.channels_webhooks_list(self.id) 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. + 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 ------- `Webhook` @@ -345,37 +446,58 @@ def create_webhook(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. + 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 ------- - `disco.types.message.Message` - The created message. + :class:`~disco.types.message.Message` + The sent message. """ return self.client.api.channels_messages_create(self.id, *args, **kwargs) def send_typing(self): """ - Sends a typing event to this channel. See `APIClient.channels_typing` - for more information. + Signal typing status + + 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) def create_overwrite(self, *args, **kwargs): """ - Creates a `PermissionOverwrite` for this channel. See - `PermissionOverwrite.create_for_channel` for more information. + Create permission overwrite + + 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) def delete_message(self, message): """ - Deletes a single message from this channel. + Deletes a single message from the channel. Parameters ---------- - message : snowflake|`Message` + message : snowflake or :class:`~disco.types.message.Message` The message to delete. """ self.client.api.channels_messages_delete(self.id, to_snowflake(message)) @@ -383,14 +505,16 @@ def delete_message(self, message): @one_or_many def delete_messages(self, messages): """ + Deletes many messages + Deletes a set of messages using the correct API route based on the number of messages passed. 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 - from this channel. + from the channel. """ message_ids = list(map(to_snowflake, messages)) @@ -405,13 +529,27 @@ def delete_messages(self, messages): self.delete_message(msg) 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' self.client.api.channels_delete(self.id, **kwargs) def close(self): """ - Closes a DM channel. This is intended as a safer version of `delete`, - enforcing that the channel is actually a DM. + Delete guild channel + + 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' self.delete() @@ -419,24 +557,79 @@ def close(self): def set_topic(self, topic, reason=None): """ 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) def set_name(self, name, reason=None): """ 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) def set_position(self, position, reason=None): """ 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) def set_nsfw(self, value, reason=None): """ 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) return self.client.api.channels_modify(self.id, nsfw=value, reason=reason) @@ -444,6 +637,23 @@ def set_nsfw(self, value, reason=None): def set_bitrate(self, bitrate, reason=None): """ 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) return self.client.api.channels_modify(self.id, bitrate=bitrate, reason=reason) @@ -451,6 +661,25 @@ def set_bitrate(self, bitrate, reason=None): def set_user_limit(self, user_limit, reason=None): """ 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) return self.client.api.channels_modify(self.id, user_limit=user_limit, reason=reason) @@ -458,6 +687,25 @@ def set_user_limit(self, user_limit, reason=None): def set_parent(self, parent, reason=None): """ 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) return self.client.api.channels_modify( @@ -467,7 +715,26 @@ def set_parent(self, parent, 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) return self.client.api.channels_modify( @@ -477,8 +744,27 @@ def set_slowmode(self, interval, reason=None): def create_text_channel(self, *args, **kwargs): """ - Creates a sub-text-channel in this category. See `Guild.create_text_channel` - for arguments and more information. + Create text channel under this category + + 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: raise ValueError('Cannot create a sub-channel on a non-category channel') @@ -491,8 +777,27 @@ def create_text_channel(self, *args, **kwargs): def create_voice_channel(self, *args, **kwargs): """ - Creates a sub-voice-channel in this category. See `Guild.create_voice_channel` - for arguments and more information. + Create voice channel under this category + + 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: raise ValueError('Cannot create a sub-channel on a non-category channel') @@ -506,18 +811,24 @@ def create_voice_channel(self, *args, **kwargs): 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. - channel : `Channel` + channel : :class:`~disco.types.channel.Channel` The channel to iterate within. - direction : :attr:`MessageIterator.Direction` - The direction in which this iterator will move. + direction : :attr:`~disco.types.channel.MessageIterator.Direction` + The direction in which the iterator will move. 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. before : snowflake The message to begin scanning at. @@ -527,6 +838,16 @@ class MessageIterator(object): The number of messages to request per API call. """ 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 DOWN = 2 @@ -547,9 +868,14 @@ def __init__(self, client, channel, direction=Direction.UP, bulk=False, before=N 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.channel.id, @@ -573,6 +899,19 @@ def fill(self): return True 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__() def __iter__(self): diff --git a/disco/types/guild.py b/disco/types/guild.py index c220cbab..f4236947 100644 --- a/disco/types/guild.py +++ b/disco/types/guild.py @@ -38,20 +38,28 @@ class GuildEmoji(Emoji): """ An emoji object. + If the id is none, then this is a normal Unicode emoji, otherwise it's a custom discord emoji. + Attributes ---------- - id : snowflake + id : snowflake or None The ID of this emoji. name : str The name of this emoji. + guild_id : snowflake + The snowflake of the guild this emoji belongs too (Not available for unicode emojis) require_colons : bool - Whether this emoji requires colons to use. + Whether this emoji requires colons to use. (Not available for unicode emojis) managed : bool - Whether this emoji is managed by an integration. + Whether this emoji is managed by an integration. (Not available for unicode emojis) roles : list(snowflake) - Roles this emoji is attached to. + Roles this emoji is attached to. (Not available for unicode emojis) animated : bool - Whether this emoji is animated. + Whether this emoji is animated. (Not available for unicode emojis) + url : str + A url to the image of this emoji. (Not available for unicode emojis) + guild : :class:`~disco.types.guild.Guild` + The guild this emoji belongs too (Not available for unicode emojis) """ id = Field(snowflake) guild_id = Field(snowflake) @@ -60,14 +68,35 @@ class GuildEmoji(Emoji): managed = Field(bool) roles = ListField(snowflake) animated = Field(bool) + available = Field(bool) def __str__(self): return u'<{}:{}:{}>'.format('a' if self.animated else '', self.name, self.id) def update(self, **kwargs): + """ + Update emoji settings + + Update the settings for this non-unicode emoji + + Parameters + ---------- + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_emojis_modify` + """ return self.client.api.guilds_emojis_modify(self.guild_id, self.id, **kwargs) def delete(self, **kwargs): + """ + Delete emoji + + Remove this non-unicode emoji from the guild + + Parameters + ---------- + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_emojis_delete` + """ return self.client.api.guilds_emojis_delete(self.guild_id, self.id, **kwargs) @property @@ -80,6 +109,14 @@ def guild(self): class PruneCount(SlottedModel): + """ + Amount of people getting pruned + + Attributes + ---------- + pruned : int or None + The amount of people getting pruned if applicable + """ pruned = Field(int, default=None) @@ -87,6 +124,9 @@ class Role(SlottedModel): """ A role object. + Discord guild role. Roles can be used to seperate users on the member list, + color people's names and give people permissions. + Attributes ---------- id : snowflake @@ -103,6 +143,12 @@ class Role(SlottedModel): The permissions this role grants. position : int The position of this role in the hierarchy. + mentionable : bool + Whether this role can be mentioned by anyone + mention : str + A string mentioning the role + guild : :class:`~disco.types.guild.Guild` + The guild this emoji belongs too """ id = Field(snowflake) guild_id = Field(snowflake) @@ -118,9 +164,27 @@ def __str__(self): return self.name def delete(self, **kwargs): + """ + Delete role + + Parameters + ---------- + kwargs + Keyword arguments to be passed into :func:`~disco.types.guild.Guild.delete_role` + """ self.guild.delete_role(self, **kwargs) def update(self, *args, **kwargs): + """ + Update role + + Parameters + ---------- + args + Arguments to be passed into :func:`~disco.types.guild.Guild.update_role` + kwargs + Keyword arguments to be passed into :func:`~disco.types.guild.Guild.update_role` + """ self.guild.update_role(self, *args, **kwargs) @property @@ -133,37 +197,72 @@ def guild(self): class GuildBan(SlottedModel): + """ + Guild ban + + Attributes + ---------- + user : :class:`~disco.types.user.User` + The user that was banned + reason : str + The reason they were banned + """ user = Field(User) reason = Field(text) class GuildEmbed(SlottedModel): + """ + Guild embed + + Attributes + ---------- + enabled : bool + If the guild has it's embed enabled + channel_id : snowflake + The channel that's displayed on the guild embed + """ enabled = Field(bool) channel_id = Field(snowflake) class GuildMember(SlottedModel): """ - A GuildMember object. + A guild member + + Guild members are essentially wrappers on user that depend on the guild. + this includes information like nick names, roles, etc. Attributes ---------- - user : :class:`disco.types.user.User` + id : snowflake + The id of the user + user : :class:`~disco.types.user.User` The user object of this member. guild_id : snowflake The guild this member is part of. nick : str The nickname of the member. + name : str + The name of the member (the nickname if they have one, elsewise they're username) mute : bool Whether this member is server voice-muted. deaf : bool Whether this member is server voice-deafened. joined_at : datetime When this user joined the guild. - roles : list(snowflake) + roles : list of snowflake Roles this member is part of. premium_since : datetime When this user set their Nitro boost to this server. + owner : bool + If this member is the owner of the guild + mention : str + A string that mentions the member (different than user mention if they have nick) + guild : :class:`~disco.types.guild.Guild` + The guild this member belongs too + permissions : :class:`disco.types.permissions.PermissionValue` + The permissions the user has on this guild, ignoring channel overwrites """ user = Field(User) guild_id = Field(snowflake) @@ -179,16 +278,15 @@ def __str__(self): @property def name(self): - """ - The nickname of this user if set, otherwise their username - """ return self.nick or self.user.username def get_voice_state(self): """ + Get current voice state + Returns ------- - Optional[:class:`disco.types.voice.VoiceState`] + :class:`~disco.types.voice.VoiceState` or None Returns the voice state for the member if they are currently connected to the guild's voice server. """ @@ -197,6 +295,12 @@ def get_voice_state(self): def kick(self, **kwargs): """ Kicks the member from the guild. + + Parameters + ---------- + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_members_kick` + """ self.client.api.guilds_members_kick(self.guild.id, self.user.id, **kwargs) @@ -208,23 +312,35 @@ def ban(self, delete_message_days=0, **kwargs): ---------- delete_message_days : int The number of days to retroactively delete messages for. + kwargs + Keyword arguments to be passed into :func:`~disco.types.guild.Guild.create_ban` """ self.guild.create_ban(self, delete_message_days, **kwargs) def unban(self, **kwargs): """ - Unbans the member from the guild. + Unbans a member from the guild. + + Parameters + ---------- + kwargs + Keyword arguments to be passed into :func:`~disco.types.guild.Guild.delete_ban` """ self.guild.delete_ban(self, **kwargs) def set_nickname(self, nickname=None, **kwargs): """ - Sets the member's nickname (or clears it if None). + Sets the member's nickname + + Set's a guild member's username. If the nicname provided is None, their name will be reset. + This same method can be used if the guild member is the bot. Parameters ---------- - nickname : Optional[str] + nickname : str or None The nickname (or none to reset) to set. + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_members_modify`. """ if self.client.state.me.id == self.user.id: self.client.api.guilds_members_me_nick(self.guild.id, nick=nickname or '', **kwargs) @@ -233,17 +349,47 @@ def set_nickname(self, nickname=None, **kwargs): def disconnect(self): """ - Disconnects the member from voice (if they are connected). + Disconnects the member from voice. + + Removes this member from their choice channel, does nothing if they're not in a voice channel """ self.modify(channel_id=None) def modify(self, **kwargs): + """ + Modify this guild member + + Parameters + ---------- + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_members_modify`. + """ self.client.api.guilds_members_modify(self.guild.id, self.user.id, **kwargs) def add_role(self, role, **kwargs): + """ + Add role to this guild member + + Parameters + ---------- + role : :class:`~disco.types.guild.Role` or snowflake + The role to add to this member + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_members_roles_add`. + """ self.client.api.guilds_members_roles_add(self.guild.id, self.user.id, to_snowflake(role), **kwargs) def remove_role(self, role, **kwargs): + """ + Remove role to this guild member + + Parameters + ---------- + role : :class:`~disco.types.guild.Role` or snowflake + The role to remove from this member + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_members_roles_remove`. + """ self.client.api.guilds_members_roles_remove(self.guild.id, self.user.id, to_snowflake(role), **kwargs) @cached_property @@ -258,9 +404,6 @@ def mention(self): @property def id(self): - """ - Alias to the guild members user id. - """ return self.user.id @cached_property @@ -276,18 +419,24 @@ class Guild(SlottedModel, Permissible): """ A guild object. + Discord guilds are the parent to almost all discord objects, with the exception of DMs. + Attributes ---------- id : snowflake The id of this guild. owner_id : snowflake The id of the owner. + owner : :class:`~disco.types.guild.GuildMember` + The owner as a member afk_channel_id : snowflake The id of the afk channel. embed_channel_id : snowflake The id of the embed channel. system_channel_id : snowflake The id of the system channel. + system_channel : :class:`~disco.types.channel.Channel` + The system channel (system notifications like member joins are sent). name : str Guild's name. icon : str @@ -306,22 +455,32 @@ class Guild(SlottedModel, Permissible): The verification level used by the guild. mfa_level : int The MFA level used by the guild. - features : list(str) + features : list of str Extra features enabled for this guild. - members : dict(snowflake, :class:`GuildMember`) + members : dict of snowflake to :class:`~disco.types.guild.GuildMember` All of the guild's members. - channels : dict(snowflake, :class:`disco.types.channel.Channel`) + channels : dict of snowflake to :class:`~disco.types.channel.Channel` All of the guild's channels. - roles : dict(snowflake, :class:`Role`) + roles : dict of snowflake to :class:`~disco.types.guild.Role` All of the guild's roles. - emojis : dict(snowflake, :class:`GuildEmoji`) + emojis : dict of snowflake to :class:`~disco.types.guild.GuildEmoji` All of the guild's emojis. - voice_states : dict(str, :class:`disco.types.voice.VoiceState`) + voice_states : dict of str to :class:`~disco.types.voice.VoiceState` All of the guild's voice states. premium_tier : int Guild's premium tier. premium_subscription_count : int The amount of users using their Nitro boost on this guild. + icon_url : str + Shorthand for :func:`~disco.types.guild.Guild.get_icon_url` + vanity_url : str + Shorthand for :func:`~disco.types.guild.Guild.get_vanity_url` + splash_url : str + Shorthand for :func:`~disco.types.guild.Guild.get_splash_url` + banner_url : str + Shorthand for :func:`~disco.types.guild.Guild.get_banner_url` + audit_log : :class:`~disco.util.paginator.Pagniator` of :class:`~disco.types.guild.AuditLogEntry` + Shorthand for :func:`~disco.types.guild.Guild.audit_log_iter` """ id = Field(snowflake) owner_id = Field(snowflake) @@ -370,9 +529,14 @@ def get_permissions(self, member): """ Get the permissions a user has in this guild. + Parameters + ---------- + member : :class:`~disco.types.guild.GuildMember` or snowflake + Member to get the permissions for + Returns ------- - :class:`disco.types.permissions.PermissionValue` + :class:`~disco.types.permissions.PermissionValue` Computed permission value for the user. """ if not isinstance(member, GuildMember): @@ -393,12 +557,20 @@ def get_permissions(self, member): def get_voice_state(self, user): """ + Get voice state + Attempt to get a voice state for a given user (who should be a member of this guild). + Parameters + ---------- + user : :class:`~disco.types.guild.GuildMember` or snowflake + The guild member to get the voice state of + + Returns ------- - :class:`disco.types.voice.VoiceState` + :class:`~disco.types.voice.VoiceState` The voice state for the user in this guild. """ user = to_snowflake(user) @@ -411,9 +583,14 @@ def get_member(self, user): """ Attempt to get a member from a given user. + Parameters + ---------- + user : :class:`~disco.types.user.User` or snowflake + The user to get member status of + Returns ------- - :class:`GuildMember` + :class:`~disco.types.guild.GuildMember` The guild member object for the given user. """ user = to_snowflake(user) @@ -427,18 +604,55 @@ def get_member(self, user): return self.members.get(user) def get_prune_count(self, days=None): + """ + Get prune count + + Before pruning a discord, you should use this method to tell you how many people will be removed. + + Parameters + ---------- + days : int + The amount of days ago people to have sent a message to not be removed + + Returns + ------- + :class:`~disco.types.guild.PruneCount` + An object containing information on people getting pruned + """ return self.client.api.guilds_prune_count_get(self.id, days=days) def prune(self, days=None, compute_prune_count=None): + """ + Prunes the guild + + Removes inactive people from the guild + + Parameters + ---------- + days : int + The amount of days ago people to have sent a message to not be removed + compute_prune_count : bool + If true, will return how many people were removed + + Returns + ------- + :class:`~disco.types.guild.PruneCount` + An object with the pruned members, if compute_prune_count is True. + """ return self.client.api.guilds_prune_create(self.id, days=days, compute_prune_count=compute_prune_count) def create_role(self, **kwargs): """ Create a new role. + Parameters + ---------- + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_roles_create` + Returns ------- - :class:`Role` + :class:`~disco.types.guild.Role` The newly created role. """ return self.client.api.guilds_roles_create(self.id, **kwargs) @@ -446,22 +660,80 @@ def create_role(self, **kwargs): def delete_role(self, role, **kwargs): """ Delete a role. + + Parameters + ---------- + role : :class:`~disco.types.guild.Role` or snowflake + The role to delete + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_roles_delete` """ self.client.api.guilds_roles_delete(self.id, to_snowflake(role), **kwargs) def update_role(self, role, **kwargs): + """ + Update a role + + Update a role's settings. + You can provide a :class:`~disco.types.permissions.PermissionValue` when updating a role's permissions. + + Parameters + ---------- + role : :class:`~disco.types.guild.Role` or snowflake + The role that is being updated + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_roles_modify` + + Returns + ------- + :class:`~disco.types.guild.Role` + An updated version of the role + """ if 'permissions' in kwargs and isinstance(kwargs['permissions'], PermissionValue): kwargs['permissions'] = kwargs['permissions'].value return self.client.api.guilds_roles_modify(self.id, to_snowflake(role), **kwargs) def request_guild_members(self, query=None, limit=0, presences=False): + """ + Request guild members + + Fetch all guild members from a guild, and update the current guild's members with the returned members. + + Parameters + ---------- + query : str + Return members who's usernames start with this query + limit : int + Return members up until this limit + presences : bool + Return the member presences with the members + """ self.client.gw.request_guild_members(self.id, query, limit, presences) def request_guild_members_by_id(self, user_id_or_ids, limit=0, presences=False): + """ + Request guild members + + Fetch specified guild members from a guild, and update the current guild's members with the returned members. + + Parameters + ---------- + user_id_or_ids : snowflake or list of snowflake + The user or users to be appended to the guild's members + limit : int + Limit the amount of responses, not recommended + presences : bool + If the member presences should be fetched as well + """ self.client.gw.request_guild_members_by_id(self.id, user_id_or_ids, limit, presences) def sync(self): + """ + Update guild members + + Update this guild object with all the members in the guild + """ warnings.warn( 'Guild.sync has been deprecated in place of Guild.request_guild_members', DeprecationWarning) @@ -469,18 +741,75 @@ def sync(self): self.request_guild_members() def get_bans(self): + """ + Get all guild bans + + Returns + ------- + :class:`~disco.util.hashmap.HashMap` of snowflake to :class:`~disco.types.guild.GuildBan` + """ return self.client.api.guilds_bans_list(self.id) def get_ban(self, user): + """ + Get a user's ban + + Parameters + ---------- + user : snowflake or :class:`~disco.types.user.User` + The user that is banned + + Returns + ------- + :class:`~disco.types.guild.GuildBan` + The user's ban + """ return self.client.api.guilds_bans_get(self.id, user) def delete_ban(self, user, **kwargs): + """ + Remove a user's ban + + Parameters + ---------- + user : snowflake or :class:`~disco.types.user.User` + The user that was banned + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_bans_delete` + """ self.client.api.guilds_bans_delete(self.id, to_snowflake(user), **kwargs) def create_ban(self, user, *args, **kwargs): + """ + Ban a user for the guild + + Parameters + ---------- + user : snowflake or :class:`~disco.types.user.User` + The user to be banned + args + Arguments to be passed into :func:`~disco.api.client.APIClient.guilds_bans_create` + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_bans_create` + """ self.client.api.guilds_bans_create(self.id, to_snowflake(user), *args, **kwargs) def create_channel(self, *args, **kwargs): + """ + Create a channel (deprecated) + + Parameters + ---------- + args + Arguments to be passed into :func:`~disco.api.client.APIClient.guilds_channels_create` + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_channels_create` + + Returns + ------- + channel : :class:`~disco.types.channel.Channel` + The freshly made channel + """ warnings.warn( 'Guild.create_channel will be deprecated soon, please use:' ' Guild.create_text_channel or Guild.create_category or Guild.create_voice_channel', @@ -491,6 +820,24 @@ def create_channel(self, *args, **kwargs): def create_category(self, name, permission_overwrites=[], position=None, reason=None): """ Creates a category within the guild. + + Categories are usd to organize channels, and can be used to create default permissions for new channels. + + Parameters + ---------- + name : str + the name of the category + permission_overwrites : list of :class:`~disco.types.channel.PermissionOverwrite` + Permission overwrites that will be applied to the channel's permissions + position : int + If the channel should be above or below other channels, by default appended to end. + reason : str + the reason for creating the channel + + Returns + ------- + :class:`~disco.types.channel.Channel` + A freshly made category """ return self.client.api.guilds_channels_create( self.id, ChannelType.GUILD_CATEGORY, name=name, permission_overwrites=permission_overwrites, @@ -507,6 +854,26 @@ def create_text_channel( reason=None): """ Creates a text channel within the guild. + + Parameters + ---------- + name : str + The name of the text channel + permission_overwrites : list of :class:`~disco.types.channel.PermissionOverwrite` + Permission overwrites to apply to the channel + parent_id : snowflake + the ID of the parent channel + nsfw : bool + Whether the new channel will ne nsfw or not + position : int + The position in channel order the new channel will be + reason : str + The reason for creating the new channel + + Returns + ------- + :class:`~disco.types.channel.Channel` + The freshly made channel """ return self.client.api.guilds_channels_create( self.id, ChannelType.GUILD_TEXT, name=name, permission_overwrites=permission_overwrites, @@ -524,27 +891,108 @@ def create_voice_channel( reason=None): """ Creates a voice channel within the guild. + + Parameters + ---------- + name : str + The name of the text channel + permission_overwrites : list of :class:`~disco.types.channel.PermissionOverwrite` + Permission overwrites to apply to the channel + parent_id : snowflake + The ID of the parent channel + bitrate : int + The channel bitrate + user_limit : int + the max amount of people that can be in the voice channel + position : int + The position in channel order the new channel will be + reason : str + The reason for creating the new channel + + Returns + ------- + :class:`~disco.types.channel.Channel` + The freshly made voice channel """ return self.client.api.guilds_channels_create( self.id, ChannelType.GUILD_VOICE, name=name, permission_overwrites=permission_overwrites, - parent_id=parent_id, bitrate=bitrate, user_limit=user_limit, position=position, reason=None) + parent_id=parent_id, bitrate=bitrate, user_limit=user_limit, position=position, reason=reason) def leave(self): + """ + Leave this guild + + If you rejoin, the bot's permissions will have to be reset. + """ return self.client.api.users_me_guilds_delete(self.id) def get_invites(self): + """ + Get all invites that link to the guild + + Returns + ------- + list of :class:`~disco.types.invite.Invite` + Invites to this guild + """ return self.client.api.guilds_invites_list(self.id) def get_emojis(self): + """ + Get all emojis that were added to the guild + + Returns + ------- + list of :class:`~disco.types.message.Emoji` + Emojis added to this guild + """ return self.client.api.guilds_emojis_list(self.id) def get_emoji(self, emoji): + """ + Fetch the an emoji from the guild + + Parameters + ---------- + emoji : snowflake + The emoji ID of the emoji to get + + Returns + ------- + :class:`~disco.types.message.Emoji` + The fetched emoji + """ return self.client.api.guilds_emojis_get(self.id, emoji) def get_voice_regions(self): + """ + Get all available voice regions for the guild's voice channels + + Returns + ------- + :class:`~disco.types.base.Hashmap` + hashmap of snowflake to :class:`~disco.types.voice.VoiceRegion` + """ return self.client.api.guilds_voice_regions_list(self.id) def get_icon_url(self, still_format='webp', animated_format='gif', size=1024): + """ + Get the guilds icon's url. (if applicable) + + Parameters + ---------- + still_format : str + The image type to return if the guild icon is a still image + animated_format : str + the image type to return if the guild icon is a animated image + size : int + The size of width and height of the image + + Returns + ------- + str + The icon url, or an empty string if no guild icon was uploaded + """ if not self.icon: return '' @@ -558,18 +1006,56 @@ def get_icon_url(self, still_format='webp', animated_format='gif', size=1024): ) def get_vanity_url(self): + """ + get the guild's vanity url. (If applicable) + + Returns + ------- + str + The vanity url, or an empty string if there is none + """ if not self.vanity_url_code: return '' return 'https://discord.gg/' + self.vanity_url_code def get_splash_url(self, fmt='webp', size=1024): + """ + Get the guild's splash url + + Parameters + ---------- + fmt : str + The format of the splash image + size : int + The width and height of the image + + Returns + ------- + str + The splash image url, or an empty string if a splash image was not uploaded + """ if not self.splash: return '' return 'https://cdn.discordapp.com/splashes/{}/{}.{}?size={}'.format(self.id, self.splash, fmt, size) def get_banner_url(self, fmt='webp', size=1024): + """ + Get the guild's banner image + + Parameters + ---------- + fmt : str + The format of the splash image + size : int + The width and height of the image + + Returns + ------- + str + The banner image url, or an empty string is a banner image was not uploaded + """ if not self.banner: return '' @@ -600,6 +1086,18 @@ def audit_log(self): return self.audit_log_iter() def audit_log_iter(self, **kwargs): + """ + Iterate through audit logs + + Parameters + ---------- + kwargs + Keyword arguments to be passed into :class:`~disco.util.paginator.Paginator` + + Returns + ------- + :class:`~disco.util.paginator.Paginator` of :class:`~disco.types.guild.AuditLogEntry` + """ return Paginator( self.client.api.guilds_auditlogs_list, 'before', @@ -608,15 +1106,63 @@ def audit_log_iter(self, **kwargs): ) def get_audit_log_entries(self, *args, **kwargs): + """ + Get all AuditLog entries + + Parameters + ---------- + args + Arguments to be passed into :func:`~disco.api.client.APIClient.guilds_auditlogs_list` + kwargs + Keyword arguments to be passed into :func:`~disco.api.client.APIClient.guilds_auditlogs_list` + """ return self.client.api.guilds_auditlogs_list(self.id, *args, **kwargs) class IntegrationAccount(SlottedModel): + """ + The account associated with an integration + + Attributes + ---------- + id : str + The ID of the account (not a snowflake) + name : str + The name of the account + """ id = Field(text) name = Field(text) class Integration(SlottedModel): + """ + Guild integration object + + Attributes + ---------- + id : snowflake + The ID of the integration + name : str + The name of the integration + type : str + integration type (twitch, youtube, etc) + enabled : bool + If the integration is enabled + syncing : bool + If the integration is syncing + role_id : snowflake + The ID the integration uses for subscribers + expire_behavior : int + The behavior when the integration expires (0, Remove role. 1, Kick) + expire_grace_period : int + The grace period (in days) before expiring subscribers + user : :class:`~disco.types.user.User` + The user this integration is for + account : :class:`~disco.types.guild.IntegrationAccount` + The integration's account + synced_at : datetime + The last time the integration was synced + """ id = Field(snowflake) name = Field(text) type = Field(text) @@ -711,12 +1257,52 @@ class AuditLogActionTypes(object): class AuditLogObjectChange(SlottedModel): + """ + Audit log change object + + Attributes + ---------- + key : str + name of audit log change key + new_value : str + New value of the key + old_value : str + Old value of the key + """ key = Field(text) new_value = Field(text) old_value = Field(text) class AuditLogEntry(SlottedModel): + """ + An audit log entry + + Attributes + ---------- + id : snowflake + The snowflake of the audit log entry + guild_id : snowflake + The snowflake of the guild this audit log entry belongs too + user_id : snowflake + The user who made the changes + target_id : snowflake + Snowflake of the affected entity (webhook, user, role, etc.) + action_type : :class:`~disco.types.guild.AuditLogActionTypes` + The type of action that occurred + changes : list of :class:`~disco.types.guild.AuditLogObjectChange` + Changes made to the target_id + options : dict of str to str + Additional info for certain action types + reason : str + The reason for the action + guild : :class:`~disco.types.guild.Guild` + The guild this audit log entry belongs too + user : :class:`~disco.types.user.User` + the user this entry involves (if applicable) + target : Any type + The target of this audit log entry + """ id = Field(snowflake) guild_id = Field(snowflake) user_id = Field(snowflake) @@ -730,6 +1316,30 @@ class AuditLogEntry(SlottedModel): @classmethod def create(cls, client, users, webhooks, data, **kwargs): + """ + Initializes a new audit log entry based on the input + + There's little reason to use this in practice, as you should be fetching audit logs from the guild. + Making an audit doesn't execute the logged action. + + Parameters + ---------- + client : :class:`~disco.client.Client` + The client object used so models can interact with the API + users : list of :class:`~disco.types.user.User` + A list of all users on the respective guild + webhooks : list of :class:`~disco.types.webhook.Webhook` + A list of all webhooks on the respective guild + data : dict of str to Any + Audit log entry data + kwargs + Keyword arguments that will be merged with data + + Returns + ------- + :class:`~disco.types.guild.AuditLogEntry` + A freshly made audit log entry + """ self = super(SlottedModel, cls).create(client, data, **kwargs) if self.action_type in MEMBER_ACTIONS: diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/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) diff --git a/docs/book.json b/docs/book.json deleted file mode 100644 index 3ad18537..00000000 --- a/docs/book.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "title": "Disco", - "plugins": ["prism", "-highlight", "hints", "anchorjs"], - "pluginsConfig": { - "anchorjs": { - "placement": "left", - "visible": "always" - } - } -} diff --git a/docs/build.sh b/docs/build.sh deleted file mode 100755 index 2e5fa451..00000000 --- a/docs/build.sh +++ /dev/null @@ -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 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..a6a940e4 --- /dev/null +++ b/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'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..aea2aeb2 --- /dev/null +++ b/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` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..922152e9 --- /dev/null +++ b/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 diff --git a/docs/types/channel.rst b/docs/types/channel.rst new file mode 100644 index 00000000..e1b972d0 --- /dev/null +++ b/docs/types/channel.rst @@ -0,0 +1,5 @@ +Disco Channel API +================= + +.. automodule:: disco.types.channel + :members: \ No newline at end of file diff --git a/docs/types/guild.rst b/docs/types/guild.rst new file mode 100644 index 00000000..5815a375 --- /dev/null +++ b/docs/types/guild.rst @@ -0,0 +1,5 @@ +Disco Guild API +================= + +.. automodule:: disco.types.guild + :members: \ No newline at end of file diff --git a/docs/types/index.rst b/docs/types/index.rst new file mode 100644 index 00000000..1c7649bb --- /dev/null +++ b/docs/types/index.rst @@ -0,0 +1,9 @@ +Disco Type API +============== + +.. toctree:: + :maxdepth: 1 + :caption: Disco Type APIs + + channel + guild \ No newline at end of file diff --git a/setup.py b/setup.py index f91314c3..b7ce13cf 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,10 @@ 'wsaccel==0.6.2', ], 'sharding': ['gipc==0.6.0'], - 'docs': ['biblio==0.0.4'], + 'docs': [ + 'sphinx==2.4.4', + 'groundwork-sphinx-theme==1.1.1' + ], } setup(