Giter Site home page Giter Site logo

rapptz / discord.py Goto Github PK

View Code? Open in Web Editor NEW
14.3K 14.3K 3.7K 20.14 MB

An API wrapper for Discord written in Python.

Home Page: http://discordpy.rtfd.org/en/latest

License: MIT License

Python 100.00%
bot discord discord-api discord-bot discord-py python python-3

discord.py's People

Contributors

abstractumbra avatar apple502j avatar bijij avatar bryanforbes avatar diceroll123 avatar github-actions[bot] avatar gobot1234 avatar gorialis avatar harmon758 avatar hornwitser avatar imayhaveborkedit avatar ioistired avatar izxxr avatar jackenmen avatar khazhyk avatar lostluma avatar mikeshardmind avatar ncplayz avatar owocado avatar puncher1 avatar rapptz avatar sebbylaw avatar sgtlaggy avatar soheab avatar stockermc avatar vexs avatar willy-c avatar xuathegrate avatar z03h avatar zomatree avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

discord.py's Issues

mention highlights are broken

when discord.py sends a mention string (<@id>) it should highlight the user. I've noticed that recently it doesn't do that any longer, and mobile users see @invalid-user instead of the mention, screenshot: https://i.imgur.com/qCFwKJE.png

I've checked with discord support and other bot devs who use other libraries, and ultimately it sounds like a problem with discord.py

can you reproduce this at all? the following command doesn't highlight me, and appears broken on mobile:

return discord.send_message(message.channel, '<@%s> pong' % message.author.id)

Better support for roles

At the moment there is no support for:

  • Member specific permissions
  • Parsing member channel permission overwrites
  • Editing channel permission overwrites
  • Deleting channel permission overwrites
  • Give a role to a member in a server.
  • Revoke a role to a member.
  • Creating a Role.
  • Less stateful role editing.

And probably some more.

Mentions do not ping the person

elephantLocator/UltraBot/issues/4

When mentioning a person, as we have in our mention file, it won't have orange highlighting etc. & ping the person. Probably a bug?

Bot Crashes when user makes new account

Traceback (most recent call last):
File "bot.py", line 321, in
client.run()
File "/usr/local/lib/python2.7/dist-packages/discord/client.py", line 550, in run
self.ws.run()
File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 427, in run
if not self.once():
File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 305, in once
if not self.process(b):
File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 377, in process
self.received_message(s.message)
File "/usr/local/lib/python2.7/dist-packages/discord/client.py", line 129, in received_message
self.dispatch('socket_update', event, data)
File "/usr/local/lib/python2.7/dist-packages/discord/client.py", line 527, in dispatch
getattr(self, handle_method, utils._null_event)(_args, *_kwargs)
File "/usr/local/lib/python2.7/dist-packages/discord/client.py", line 535, in handle_socket_update
getattr(self.connection, method)(data)
File "/usr/local/lib/python2.7/dist-packages/discord/client.py", line 262, in handle_guild_member_add
member = Member(server=server, deaf=False, mute=False, **data)
TypeError: type object got multiple values for keyword argument 'mute'

Some websocket events are not being handled.

  • VOICE_STATE_UPDATE
  • GUILD_ROLE_DELETE
  • GUILD_ROLE_CREATE
  • GUILD_ROLE_UPDATE

Examples:

{
    "s": 265, 
    "op": 0, 
    "d": {
        "channel_id": "81826890993639424", 
        "token": "3a7c9908604b42d6", 
        "self_mute": false, 
        "user_id": "79285195756273664", 
        "mute": false, 
        "self_deaf": false, 
        "suppress": false, 
        "guild_id": "81826890972667904", 
        "deaf": false, 
        "session_id": "a9ae84990118100fc45b1579b0ae02fd"
    }, 
    "t": "VOICE_STATE_UPDATE"
}

GUILD_ROLE_CREATE data.

{
    "guild_id": "80056038593662976", 
    "role": {
        "name": "new role", 
        "color": 0, 
        "hoist": false, 
        "position": 14, 
        "id": "103972673217892352", 
        "permissions": 36953089
    }
}

GUILD_ROLE_DELETE data

{
    "guild_id": "80056038593662976", 
    "role_id": "103972673217892352"
}

GUILD_ROLE_UPDATE data

{
    "guild_id": "80056038593662976", 
    "role": {
        "name": "test role", 
        "color": 0, 
        "hoist": false, 
        "position": 4, 
        "id": "87687492047761408", 
        "permissions": 36953089
    }
}

Error on users joining

I got an error when new users join a channel:

Traceback (most recent call last):
  File "gm_bot.py", line 85, in <module>
    client.run()
  File "C:\Python27\lib\site-packages\discord\client.py", line 550, in run
    self.ws.run()
  File "C:\Python27\lib\site-packages\ws4py\websocket.py", line 427, in run
    if not self.once():
  File "C:\Python27\lib\site-packages\ws4py\websocket.py", line 305, in once
    if not self.process(b):
  File "C:\Python27\lib\site-packages\ws4py\websocket.py", line 377, in process
    self.received_message(s.message)
  File "C:\Python27\lib\site-packages\discord\client.py", line 129, in received_message
    self.dispatch('socket_update', event, data)
  File "C:\Python27\lib\site-packages\discord\client.py", line 527, in dispatch
    getattr(self, handle_method, utils._null_event)(*args, **kwargs)
  File "C:\Python27\lib\site-packages\discord\client.py", line 535, in handle_socket_update
    getattr(self.connection, method)(data)
  File "C:\Python27\lib\site-packages\discord\client.py", line 262, in handle_guild_member_add
    member = Member(server=server, deaf=False, mute=False, **data)
TypeError: type object got multiple values for keyword argument 'mute'

Nothing special in my own code (just some setting roles with commands, thats it). This happens every time when a new member joins a channel, tested on multiple channels.

Removing a role from a Member does not remove it internally, causing an immediately subsequent call to add_role to add the role back

I have a function that my bot provides that is designed to replace a role on a user with a different one. If I call remove_roles to remove the old role, then immediately call add_roles to add the new one, the end result is that the user now has both roles applied. I would expect that the user would only have the new role after this. My current workaround is to edit member.roles myself to remove the old role from that before calling add_roles.

Add a command extension API

Something to help people make basic or even complex bots in a nice self-contained manner.

Roadmap in no particular order:

  • Allow composable creation of subcommands and commands.
  • Commands should use regular functions to figure out how they should be called.
  • A global error handler to capture errors in a more general and consistent way.
    • Provide a way to have local error handlers as well to a specific command if needed.
  • Configurable command prefix.
    • Dynamic command prefixes could be set by making it a callable.
    • Command prefix could be a list to denote multiple prefixes.
  • Allow registration of "extensions" that allow you to load things from a different file and handles the unloading automatically.
  • Allow commands and such to be disabled or enabled dynamically.
  • Allow class-based categories that let you have some form of state per command.
  • Automatic generation of a help command that could be customised.
    • Let people provide their own Formatter to change the output of help.
  • Allow multiple listeners for an event.
    • This should be able to be used in the class based variant as well.
  • Allow people to set the strings in the help command for i18n.
  • A more deterministic cog unload function.
  • A catch-all on_cog_command_error that triggers whenever a cog's command encounters an error.
  • Write documentation for all of this.

Add option to get a user/channel from name or ID

Right now you always have to wait for a channel or user to be active in some way, which results in problems. For example when you want the bot to type regularly in a specific channel you always need to grab it from someone typing in it first. So you cant make the bot post a text every five minutes in the channel unless somebody types in that channel before. Equal for users, but the problem is rather minor there.

Add a standard list of colours.

The discord client only supports a limited set of colours (or rather, it will soon) so those limited colours should be the ones exposed via the library rather than a free-form rgb value.

When an user only has the @everyone role, their role array is empty

on the on_message event handler i was getting the author of the message, and printing their role array, but it would give me an empty array every time, so i couldn't even check if a role was the everyone role.

here's some sample code:

import discord
import json
import os

client = discord.Client()

if os.path.isfile("auth.json"):
        print("auth details loaded from auth.json")
        with open("auth.json") as data_file:
            authdetails = json.load(data_file)
            client.login(authdetails["email"], authdetails["pass"])

@client.event
def on_message(message):
    print(message.content)
    print(message.author.roles)

@client.event
def on_ready():
    print("logged in")

client.run()

and here's what i get if i run that, and send a message, with my account role set to everyone:
sample image

is this intended behavior? am i being stupid and missing something?

Support gateway redirects

Something to add to my todo list for a later date.

Stan says:

Just so you guys know probably within a week there will be a major real-time system change to Discord.
the /gateway call will need the auth token because gateways will be assigned around a consistent hash ring
If you conncet to the wrong gateway it will send you a redirect opcode with the URL to the correct one
But the easiest way to be ready for this is to just send the auth token to /api/gateway and then you dont need to worry about it
But the redirect opcode can be also fired if the servers rebalance
but doubt that will be happening for a bit after the upgrade
its special cause gateways support resuming a session id
instead of identify you send a resume
with the previous session id
and it keeps going on the new node
This way we can seamless move people between gateways during DDoS or just scale up ๐Ÿ˜ƒ
also works for timeouts (aka cloudflare issue)
opcode is 7
and the data is just
{"url": "new websocket url here"}
If you want to support resuming a session you need to keep the session_id in your websocket instance.. and also keep track of the 's' variable aka seq on events (keep last one)
and when you try reconnect after a DC or a redirect
send opcode 6 with {'session_id': session_id, 'seq': seq}
instead of identify
every message will have a seq
's'
keep the last one

GUILD_BAN_ADD and GUILD_BAN_REMOVE events are missing

GUILD_BAN_ADD denotes that a member was banned. This is then followed by GUILD_MEMBER_REMOVE.

Websocket Context

GUILD_BAN_REMOVE is straightforward and doesn't have much context to it.

{
    "t": "GUILD_BAN_REMOVE",
    "s": 362,
    "op": 0,
    "d": {
        "user": {
            "username": "\u300c Zeta \u300d",
            "id": "94129005791281152",
            "discriminator": "1920",
            "avatar": "a8503d9d0f3a8867ee7adb4b434e2c7a"
        },
        "guild_id": "104158907944939520"
    }
}

All errors/exceptions are swallowed?

I don't seem to be getting any error messages when running code using this library. Is this an artifact of the WebSocketClient library? Is it a bug? Is there some way of enabling errors that I'm missing?

Implement non-blocking run process

Right now the client has be started with the client.run() command. This blocks the script so I can't manually execute commands like client.send_message()whenever I like, I am constrained to the events that the client detects. Can this be fixed?

WebSocket ratelimiting cause the client to exit

If the client sends too many messages in a short amount of time on the WebSocet connection (for example by sending status updates too often), it will be terminated with the data "rate limited". The current rate limit on the WebSocet is 60/60 according to the discord devs.

The debug logs shows the following appearing:

DEBUG:websockets.protocol:client << Frame(fin=True, opcode=8, data=b'\x0f\xa8rate limited')
DEBUG:websockets.protocol:client >> Frame(fin=True, opcode=8, data=b'\x0f\xa8rate limited')

The Client.connect() method then exits with no errors logged or exceptions raised, making it hard to track down what happened.

Client crash: Fatal read error on SSL transport

The client crashes if the internet connection is dropped (ethernet cable unplugged in my case):

Fatal read error on SSL transport
protocol: <websockets.client.WebSocketClientProtocol object at 0x7fc1ede993c8>
transport: <_SelectorSslTransport fd=7 read=polling write=<idle, bufsize=0>>
Traceback (most recent call last):
  File "/usr/lib/python3.4/asyncio/selector_events.py", line 896, in _read_ready
    data = self._sock.recv(self.max_size)
  File "/usr/lib/python3.4/ssl.py", line 733, in recv
    return self.read(buflen)
  File "/usr/lib/python3.4/ssl.py", line 622, in read
    v = self._sslobj.read(len or 1024)
OSError: [Errno 113] No route to host
Task exception was never retrieved
future: <Task finished coro=<run() done, defined at /usr/local/lib/python3.4/dist-packages/websockets/protocol.py:296> exception=OSError(113, 'No route to host')>
Traceback (most recent call last):
  File "/usr/lib/python3.4/asyncio/tasks.py", line 237, in _step
    result = coro.throw(exc)
  File "/usr/local/lib/python3.4/dist-packages/websockets/protocol.py", line 302, in run
    msg = yield from self.read_message()
  File "/usr/local/lib/python3.4/dist-packages/websockets/protocol.py", line 324, in read_message
    frame = yield from self.read_data_frame(max_size=self.max_size)
  File "/usr/local/lib/python3.4/dist-packages/websockets/protocol.py", line 379, in read_data_frame
    frame = yield from self.read_frame(max_size)
  File "/usr/local/lib/python3.4/dist-packages/websockets/protocol.py", line 411, in read_frame
    self.reader.readexactly, is_masked, max_size=max_size)
  File "/usr/local/lib/python3.4/dist-packages/websockets/framing.py", line 77, in read_frame
    data = yield from reader(2)
  File "/usr/lib/python3.4/asyncio/streams.py", line 500, in readexactly
    block = yield from self.read(n)
  File "/usr/lib/python3.4/asyncio/streams.py", line 473, in read
    yield from self._wait_for_data('read')
  File "/usr/lib/python3.4/asyncio/streams.py", line 414, in _wait_for_data
    yield from self._waiter
  File "/usr/lib/python3.4/asyncio/futures.py", line 385, in __iter__
    yield self  # This tells Task to wait for completion.
  File "/usr/lib/python3.4/asyncio/tasks.py", line 288, in _wakeup
    value = future.result()
  File "/usr/lib/python3.4/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/lib/python3.4/asyncio/selector_events.py", line 896, in _read_ready
    data = self._sock.recv(self.max_size)
  File "/usr/lib/python3.4/ssl.py", line 733, in recv
    return self.read(buflen)
  File "/usr/lib/python3.4/ssl.py", line 622, in read
    v = self._sslobj.read(len or 1024)
OSError: [Errno 113] No route to host

ws4py setblocking error

My script tries to reboot when it crashes. I frequently, on reboot, see the exception:

Traceback (most recent call last):
  File "/home/luke/Desktop/discord/discord_main.py", line 48, in run
    client.run()
  File "/usr/local/lib/python2.7/dist-packages/discord/client.py", line 542, in run
    self.ws.run()
  File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 420, in run
    self.sock.setblocking(True)
AttributeError: 'NoneType' object has no attribute 'setblocking'

Is this a bug or something on my end?

Handle unavailable servers in READY event

When a guild is unavailable, it will contain

{
    "unavailable": true,
    "id": "41771983423143937"
}

in the guilds array in the READY event. This is not handled properly, and a KeyError is raised.

Traceback (most recent call last):
  File "bot.py", line 345, in <module>
    bot.run()
  File "/home/julian/prog/record/logger/discord/client.py", line 530, in run
    self.ws.run()
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 427, in run
    if not self.once():
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 305, in once
    if not self.process(b):
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 377, in process
    self.received_message(s.message)
  File "/home/julian/prog/record/logger/discord/client.py", line 130, in received_message
    self.dispatch('socket_update', event, data)
  File "/home/julian/prog/record/logger/discord/client.py", line 507, in dispatch
    getattr(self, handle_method, _null_event)(*args, **kwargs)
  File "/home/julian/prog/record/logger/discord/client.py", line 515, in handle_socket_update
    getattr(self.connection, method)(data)
  File "/home/julian/prog/record/logger/discord/client.py", line 202, in handle_ready
    self._add_server(guild)
  File "/home/julian/prog/record/logger/discord/client.py", line 161, in _add_server
    guild['roles'] = [Role(**role) for role in guild['roles']]
KeyError: 'roles'

Relevant WebSocket message (Warning: big).

requests 2.4.2 should be required

Spent a couple of minutes banging head against wall today; the json parameter for requests.post() was only added in requests 2.4.2 (see http://docs.python-requests.org/en/latest/user/quickstart/#more-complicated-post-requests), and yet when I installed discord.py requests 2.4.2+ was not installed (it took sudo pip3 install --upgrade requests). Therefore, interesting crash:

Traceback (most recent call last):
  File "./dissonance.py", line 26, in <module>
    client.login(email, password)
  File "/usr/local/lib/python3.4/dist-packages/discord/client.py", line 654, in login
    r = requests.post(endpoints.LOGIN, json=payload)
  File "/usr/lib/python3/dist-packages/requests/api.py", line 88, in post
    return request('post', url, data=data, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
TypeError: request() got an unexpected keyword argument 'json'

To fix: depend on requests โ‰ฅ 2.4.2 (assuming that's possible with pip/pip3).

ValueError on GUILD_MEMBER_REMOVE

It's possible to receive a GUILD_MEMBER_REMOVE for a member that has not been present in the guilds.members array in the READY event and for which no GUILD_MEMBER_ADD has been received for. Resulting in ValueError being raised with the following traceback.

Traceback (most recent call last):
  File "bot.py", line 345, in <module>
    bot.run()
  File "/home/julian/prog/record/logger/discord/client.py", line 560, in run
    self.ws.run()
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 427, in run
    if not self.once():
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 305, in once
    if not self.process(b):
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 377, in process
    self.received_message(s.message)
  File "/home/julian/prog/record/logger/discord/client.py", line 133, in received_message
    self.dispatch('socket_update', event, data)
  File "/home/julian/prog/record/logger/discord/client.py", line 537, in dispatch
    getattr(self, handle_method, _null_event)(*args, **kwargs)
  File "/home/julian/prog/record/logger/discord/client.py", line 545, in handle_socket_update
    getattr(self.connection, method)(data)
  File "/home/julian/prog/record/logger/discord/client.py", line 311, in handle_guild_member_remove
    server.members.remove(member)
ValueError: list.remove(x): x not in list

This was encountered on commit 5efddaf of the develop branch.

WebSocket context

voice_members not updating

I use this code to list all voice members in each channel on my server

server = discord.utils.find(lambda m: m.id == self.server, self.client.servers)
                if server is not None:
                    for channel in server.channels:
                        print(channel.voice_members)

However if i move channels after my bot has connected to the server, voice_members doesn't reflect the changes. it still shows me in the old channel.

Hopefully i'm just missing something small but i've been at this for a while so i believe its a bug.

Hitting issues when trying to create a bot

I'm trying to write a bot using this API which will let people play a game but I'm coming across some weird behaviours and I can't tell whether I'm doing something wrong or whether there's actually a problem.

The below code, upon talk_to_discord() being called, attempts to start a client and then listens for a message to tell it when to start the game (I'm going with !start). When it hears that message, it starts a Game object, passing both the Discord client (self.client) and the user who started the game (self.owner). The idea is that the Game object then messages the owner to ask who's playing (generating a list of usernames from the input string) and also what roles are available to players (generating a list of roles from the second input string).

The module is called werewolf. If I open Python and then type import werewolf and werewolf.talk_to_discord(), the client starts and I can message the relevant user on Discord.

The chat log in Discord between me and the user (also called 'werewolf') looks like this:

me: !start
werewolf: Let's start a game!
werewolf: Who will be playing?
me: Player 1, Player 2, Player 3, Player 4, Player 5
pause
me: !start
werewolf: Let's start a game!
werewolf: Who will be playing?
me: Player 1, Player 2, Player 3, Player 4, Player 5
werewolf: What roles will be in effect?
me: Tanner, Werewolf, Werewolf, Hunter, Robber, Seer, Insomniac, Villager

The console output before pause is:

Successfully logged in as werewolf with ID 100233992065814528.
A game is starting!
users input
[]

The output after pause is:

A game is starting!
users input
[u'!start', u'Player 1, Player 2, Player 3, Player 4, Player 5']
roles input
[u'!start', u'Player 1, Player 2, Player 3, Player 4, Player 5']
test

The code I've written is:

import discord

def talk_to_discord():

    client = discord.Client()
    client.login('email', 'password')

    @client.event
    def on_message(message):
        if message.content.startswith("!start"):
            client.send_message(message.channel, "Let's start a game!")
            Game(client, message.author)

    @client.event
    def on_ready():
        print('Successfully logged in as {0} with ID {1}.'.format(client.user.name, client.user.id))

    client.run()

# --------------------------------------------------------------------------------------------------

class Game(object):
    """Object for the entire game."""

    def __init__(self, client, owner):
        import random
        print('A game is starting!')

        self.client = client
        self.owner = owner

        # Work out who's playing.
        self.client.send_message(self.owner, 'Who will be playing?')
        time.sleep(30)
        print('users input')
        received = [m.content for m in self.client.messages if m.author == self.owner]
        print(received)
        self.users = received[-1].split(', ')

        self.client.send_message(self.owner, 'What roles will be in effect?')
        time.sleep(30)
        print('roles input')
        received = [m.content for m in self.client.messages if m.author == self.owner]
        print(received)
        roles = received[-1].split(', ')
        self.roles = random.sample([n.lower() for n in roles], len(roles))
        print('test')

        if len(self.users) + 3 != len(self.roles):
            raise ValueError('The number of roles must be the number of players + 3.')

        print('test2')

Here are my questions:

  1. Removing the if statement towards the end of the code causes test2 to print to the console. Why does the if statement as written cause Python to just stop, rather than either raising a ValueError or moving onto the next line?
  2. Why is the bot unable to successfully fetch the most recent message from the client? It seems to be fetching the most recent message prior to hearing the word '!start' and I don't really understand why that's the case.
  3. Why, when I hit Ctrl-C to interrupt the client and then run talk_to_discord() again, does every message start appearing in duplicate (or triplicate, or more depending on how many times I've hit Ctrl-C and run the function again)?
  4. At the moment I'm waiting a set time and then trying to poll for the user's most recent input during that time. Is there a better way to do this? I feel like I should be able to do something like while new_message_from_author(author) == False with a much shorter time delay in it, and then have the rest of the code run once a new message from the owner has been received, rather than having a preset amount of time in which to provide the relevant inputs.

I would be very grateful for any pointers on whether this is a problem with the API, or whether it's a problem with my trying to use the API.

Remove name from logs.

The logging module already supports this in the formatting so repeating it twice is unnecessary.

RFC: Should discord.py be asyncio compatible?

One of the biggest problems people usually talk about with the library is how the library is inherently blocking, either through the websocket itself or the events themselves. These issues are not inherently related to the library being async or not but a consequence of not having a threading model set up for these things.

That aside, an often mentioned thing people want from the library is for it to be more async. I won't divulge on the fact whether those people actually know the pros and cons but I will list them below. I hope to use this issue as a RFC to see if it's truly worth considering.

These lists are non-exhaustive.

Cons

  • The API will be somewhat harder to understand for newcomers and veterans alike.
    • A lot of the methods will become coroutines and a result you need to do something like yield from (or await in Python 3.5+) to actually invoke it.
      • This is quite a pitfall if you forget it and a common source of asyncio errors.
    • Another pitfall would be forgetting to create a function to invoke the library listeners itself in the asyncio event loop.
  • The library will have to be essentially rewritten to an extent.
    • Only discord.Client is heavily affected since the data classes just hold and parse data.
    • discord.Client is a large chunk of the library and rewriting is a very annoying task.
    • Essentially, time spent rewriting the library means time spent less on other features.
  • Support for Python versions lower than 3.4 will be dropped.
    • Python version 3.3 is supported via the backport of asyncio module found in pip.
  • This will be the biggest breaking change that the library has ever had.
    • All previous code will definitely not work.

Pros

  • The library will obviously become asynchronous (should you take advantage of it).
  • Rewriting the discord.Client will allow me to clean-up the code significantly.
  • Dropping support for Python 2.7 will alleviate personal issues with handling compatibility with fundamental differences.
    • bytes vs str vs unicode is one of the biggest compatibility hurdles that I put up with and removing it is a good thing.

Why asyncio? Why not Twister or gevent or my favourite library here?

asyncio comes built-in and is the future of asynchronous programming in Python.
The language itself now comes with full support for the stuff in asyncio module such as coroutines.
You can write async def blocks, async for loops, and async with statements. The support for
asynchronous programming in Python is essentially all based on the core concepts made in asyncio.

Another pro is that I can use libraries that are developed with asyncio in mind such as aiohttp
and have little external dependencies on the user.

Performance of these libraries is irrelevant. Python as a language is already "slow" and seeking
a lot of bleeding edge speed for the async library will probably not outweigh the benefits of
having to use a standardised library.

I am willing to reconsider other options but for now, I am strictly approaching this design by only
considering asyncio and nothing else.

Conclusion

The purpose of this RFC is to gauge library user interests and asking if these changes are worth it
going forward. If you have anything to add please comment below. I'll be reading all the comments
here and taking them seriously.

You can also use this thread to vote on the issue.

Of course, here's a link to the library if you're curious of how it is and the Discord API server where discussion will also take place.

Client occasionally gets stuck/crashes on websocket reconnect

Thread with discord client stops working with this in logs:

[23/Nov/2015 02:20:19] DEBUG [discord.client:77] (Thread-5  ) Keeping websocket alive with timestamp 1448238019
[23/Nov/2015 02:20:19] DEBUG [discord.client:509] (Thread-5  ) Dispatching event socket_raw_send
[23/Nov/2015 02:20:33] DEBUG [ws4py:360] (Bot       ) Closing message received (1000) ''
[23/Nov/2015 02:20:33] INFO [discord.client:95] (Bot       ) Closed with 1006 ("Going away") at 1448238033
[23/Nov/2015 02:20:33] DEBUG [discord.client:509] (Bot       ) Dispatching event socket_closed
[23/Nov/2015 02:20:33] DEBUG [discord.client:509] (Bot       ) Dispatching event socket_closed
[23/Nov/2015 02:20:33] INFO [requests.packages.urllib3.connectionpool:756] (Bot       ) Starting new HTTPS connection (1): discordapp.com
[23/Nov/2015 02:20:33] DEBUG [requests.packages.urllib3.connectionpool:387] (Bot       ) "GET /api/gateway HTTP/1.1" 200 43
[23/Nov/2015 02:20:33] INFO [discord.client:428] (Bot       ) websocket gateway found

Might be an error around self.ws.connect() but there are no handlers at this point or in my thread code.

It is extremely rare, I have updated my code to log exceptions from client thread so next time hopefully I'll see why it happens.

Make Message.author be a Member rather than a User if possible

Message.author at the moment is a User instance because it is possible for the message to be a private message in which Member does not make sense. However, it would be useful to receive a Member if it is possible and then delegate to User if it isn't possible as it would allow you to receive more information about the message author without having to loop through the member list yourself.

Role.is_everyone() is broken

Running the expression

'\n'.join(['**Server** {}\n{}'.format(s.name, '\n'.join(
    ['"{}"'.format(r.name) for r in s.roles if r.is_everyone()]
)) for s in self.servers])

to list up all @everyone roles in all servers yielded

Server Technic

Server Logger Test
"@everyone"
Server /r/Dota2
"@everyone"
"cant post links"
Server Discord Developers

Server Discord API
"@everyone"

Which is obviously not correct. Replacing r.is_everyone() with r.id == s.id correctly listed up all @everyone roles. I suggest changing the logic behind Role.is_everyone() to check if the role id is equal to the server id to fix this.

Client.send_message() sends the same message forever

Running echo.py from the examples directory. When I type a message into a channel, the bot repeats the message as expected, but doesn't stop repeating it! Adding debug statements suggests that on_message only gets called once, so it seems like send_message is repeatedly sending without ever returning, but I can't see anything in the code that would try to retransmit like this. (I checked both here at HEAD and the code actually installed on my machine)

Any idea what's going on?

I'm using discord.py 0.6.3 (from 'pip install discord.py')

Sending messages when ready?

Hi,
I have the "Quick Example" code from the README, and I want to add a message when the bot is ready, in the on_ready() function.
how do I specify the message be made on the #general channel?

Crash on duplicate CHANNEL_DELETE

Discord can apparently send duplicate CHANNEL_DELETEs.

This causes us to crash. : )

WebSocket Event: {'op': 0, 's': 1930069, 'd': {'last_message_id': '118018179669819392', 'position': 12, 'name': 'memeverse', 'permission_overwrites': [{'deny': 131072, 'id': '107053649628258304', 'allow': 0, 'type': 'role'}], 'id': '115560288178143238', 'topic': 'All the memes', 'is_private': False, 'guild_id': '107053649628258304', 'type': 'text'}, 't': 'CHANNEL_DELETE'}
WebSocket Event: {'op': 0, 's': 1930070, 'd': {'last_message_id': '118018179669819392', 'position': 12, 'name': 'memeverse', 'permission_overwrites': [{'deny': 131072, 'id': '107053649628258304', 'allow': 0, 'type': 'role'}], 'id': '115560288178143238', 'topic': 'All the memes', 'is_private': False, 'guild_id': '107053649628258304', 'type': 'text'}, 't': 'CHANNEL_DELETE'}
    self.dispatch('socket_update', event, data)
  File "/var/lib/spoo/venv/lib/python3.4/site-packages/discord/client.py", line 550, in dispatch
    getattr(self, handle_method, _null_event)(*args, **kwargs)
  File "/var/lib/spoo/venv/lib/python3.4/site-packages/discord/client.py", line 558, in handle_socket_update
    getattr(self.connection, method)(data)
  File "/var/lib/spoo/venv/lib/python3.4/site-packages/discord/client.py", line 276, in handle_channel_delete
    server.channels.remove(channel)
ValueError: list.remove(x): x not in list

AttributeError on GUILD_MEMBER_REMOVE

When you leave a server, a GUILD_DELETE message is sent on the WebSocket deleting the server. But afterwards a GUILD_MEMBER_REMOVE may appear referencing yourself, and the just deleted guild. Causing the following traceback:

Traceback (most recent call last):
  File "bot.py", line 66, in <module>
    bot.run()
  File "/home/julian/prog/record/discord/client.py", line 472, in run
    self.ws.run()
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 427, in run
    if not self.once():
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 305, in once
    if not self.process(b):
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 377, in process
    self.received_message(s.message)
  File "/home/julian/prog/record/discord/client.py", line 122, in received_message
    self.dispatch('socket_update', event, data)
  File "/home/julian/prog/record/discord/client.py", line 459, in dispatch
    getattr(self, handle_method, _null_event)(*args, **kwargs)
  File "/home/julian/prog/record/discord/client.py", line 467, in handle_socket_update
    getattr(self.connection, method)(data)
  File "/home/julian/prog/record/discord/client.py", line 285, in handle_guild_member_remove
    member = utils.find(lambda m: m.id == user_id, server.members)
AttributeError: 'NoneType' object has no attribute 'members'

See also the WebSocket context.

KeyError: 'game_id' -SOLVED

I'm trying to run this script with the example

Login goes fine, client.accept_invite goes fine.
But once the bot is added to a server(manual or with script, serverowner/admin rights or not) I get this error/quit when I run bot.py

Traceback (most recent call last):
  File "C:\Users\someone\Desktop\discordbot\bot.py", line 31, in <module>
    client.run()
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\client.py", line 542, in run
  File "C:\Python34\lib\site-packages\ws4py-0.3.4-py3.4.egg\ws4py\websocket.py", line 427, in run
  File "C:\Python34\lib\site-packages\ws4py-0.3.4-py3.4.egg\ws4py\websocket.py", line 305, in once
  File "C:\Python34\lib\site-packages\ws4py-0.3.4-py3.4.egg\ws4py\websocket.py", line 377, in process
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\client.py", line 128, in received_message
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\client.py", line 519, in dispatch
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\client.py", line 527, in handle_socket_update
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\client.py", line 160, in handle_ready
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\client.py", line 150, in _add_server
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\server.py", line 77, in __init__
  File "C:\Python34\lib\site-packages\discord.py-0.9.0-py3.4.egg\discord\server.py", line 122, in _from_data
KeyError: 'game_id'

.roles containing a string

Ignoring exception in on_message
Traceback (most recent call last):
  File "C:\Program Files (x86)\Python35-32\lib\site-packages\discord\client.py", line 251, in _run_event
    yield from getattr(self, event)(*args, **kwargs)
  File "red.py", line 145, in on_message
    if settings["FILTER"] and not isMemberAdmin(message):
  File "red.py", line 1475, in isMemberAdmin
    if discord.utils.get(message.author.roles, name=settings["ADMINROLE"]) != None:
  File "C:\Program Files (x86)\Python35-32\lib\site-packages\discord\utils.py", line 137, in get
    return find(predicate, iterable)
  File "C:\Program Files (x86)\Python35-32\lib\site-packages\discord\utils.py", line 78, in find
    if predicate(element):
  File "C:\Program Files (x86)\Python35-32\lib\site-packages\discord\utils.py", line 131, in predicate
    obj = getattr(obj, attribute)
AttributeError: 'str' object has no attribute 'name'
discord.utils.get(message.server.members, name="ThatGuysName").roles
['132984642558099456', <discord.role.Role object at 0x050942F0>]

This isn't first time it happens. It seem to only ever happen to me on that particular server, which is the only twitch related one I have a bot on.
That guy was recently synced (been assigned to the "[stream_name] Subscriber" role after he linked his Twitch account) and I'm quite sure it's the reason it happens. That's also the only role he has.
I'm using this commit, 5a1d7a2, my apologies if it turns out to be already fixed.

Retrieve images

From a quick code and documentation search, there's no functionality for handling images sent, or sending images. Would this be hard to implement? There doesn't even seem to be functionality for downloading raw image data as a string. At least this functionality would be helpful, because:

import discord
from PIL import Image
from StringIO import StringIO

client = discord.Client()
client.login('user','password')

def on_message(message):
    buff = StringIO()
    buff.write(message.content)
    try:
        im = Image.open(buff)
        isImage=True
    except:
        text = message.content
        isImage=False
    if isImage:
        im.save("img.jpg")
    else:
        print text

Setting it up like this would be an easy way to download images. Uploading images might work similarly.

member_join and member_remove lack server parameter

Events on new user joining and user leaving the server lack information on server parameter. As a result, it's hard to have meaningful use of those events.
See: handle_guild_member_add and handle_guild_member_remove

Error after running for approximately 2 hours 36 minutes

After running smoothly for a very long time, I suddenly got the error:

Traceback (most recent call last):
  File "/home/luke/Desktop/discord/discord_main.py", line 16, in <module>
    client.run()
  File "/usr/local/lib/python2.7/dist-packages/discord/client.py", line 542, in run
    self.ws.run()
  File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 427, in run
    if not self.once():
  File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 305, in once
    if not self.process(b):
  File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 362, in process
    self.close(s.closing.code, s.closing.reason)
  File "/usr/local/lib/python2.7/dist-packages/ws4py/client/__init__.py", line 198, in close
    self._write(self.stream.close(code=code, reason=reason).single(mask=True))
  File "/usr/local/lib/python2.7/dist-packages/ws4py/websocket.py", line 243, in _write
    self.sock.sendall(b)
  File "/usr/lib/python2.7/ssl.py", line 721, in sendall
    v = self.send(data[count:])
  File "/usr/lib/python2.7/ssl.py", line 687, in send
    v = self._sslobj.write(data)
error: [Errno 14] Bad address

Normally i'm pretty good at interpreting python errors, but as I've never used any of the libraries on which discord depends, I'm having trouble. Can someone explain to me why this happens and whether this is a bug?

AssertionError on unordered CHANNEL_CREATE

It's possible that messages are reordered such that a MESSAGE_CREATE event comes before the corresponding CHANNEL_CREATE for the private chat. This causes the code to crash with an AssertionError.

File "bot.py", line 316, in <module>
    charset='utf8mb4',
  File "/home/julian/prog/record/logger/discord/client.py", line 518, in run
    self.ws.run()
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 427, in run
    if not self.once():
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 305, in once
    if not self.process(b):
  File "/usr/lib/python3.5/site-packages/ws4py/websocket.py", line 377, in process
    self.received_message(s.message)
  File "/home/julian/prog/record/logger/discord/client.py", line 128, in received_message
    self.dispatch('socket_update', event, data)
  File "/home/julian/prog/record/logger/discord/client.py", line 505, in dispatch
    getattr(self, handle_method, _null_event)(*args, **kwargs)
  File "/home/julian/prog/record/logger/discord/client.py", line 513, in handle_socket_update
    getattr(self.connection, method)(data)
  File "/home/julian/prog/record/logger/discord/client.py", line 211, in handle_message_create
    message = Message(channel=channel, **data)
  File "/home/julian/prog/record/logger/discord/message.py", line 89, in __init__
    self._upgrade_to_member()
  File "/home/julian/prog/record/logger/discord/message.py", line 92, in _upgrade_to_member
    assert self.channel is not None
AssertionError

WebSocket Context (not much to see, it's just a message with an unknown channel id)

Allow changing the status on the bot.

Changing bot status to away or playing a certain game would be cool.

The payload should be something to send to the websocket with op of 3 and d of { idle_since: unix-timestamp, game_id: some-id }.

Keeps receiving Closed with 1006 ("Going away")

Version: 0.9.1
Branch: main

Hello, from week before, my chat bot keeps receives errors after some time of work (random time):
In python IDLE I see this errors: http://pastebin.com/MhDgE3CK
And at same time logging gives: 'Closed with 1006 ("Going away")'

What can I do with it? May be I can somehow make bot restart after this error? please help

Throw errors instead of returning a value.

Most functions either return a True/False or a value/None.

Exceptions are the proper way to go for Python and thus they should be used to denote errors instead. Return codes/values can be ignored while exceptions can't.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.