Example #1
0
class WebSocektRFC6455(object):
    def __init__(self, url):
        self.ws = WebSocketClient(url)
        self.queue = Queue.Queue()

        def onmessage(m):
            print "received: %s" % m
            self.queue.put(unicode(str(m), 'utf-8'))

        self.ws.received_message = onmessage

    def connect(self):
        self.ws.connect()
        return self

    def send(self, msg):
        self.ws.send(msg)
        return self

    def recv(self, timeout=1.0):
        try:
            return self.queue.get(timeout=timeout)
        except Queue.Empty:
            raise TimeoutException("websocket didn't receive any thing")

    def close(self):
        self.ws.close()
Example #2
0
class WebSocektRFC6455(object):

    def __init__(self, url):
        self.ws = WebSocketClient(url)
        self.queue = Queue.Queue()
        def onmessage(m):
            print "received: %s" % m
            self.queue.put(unicode(str(m), 'utf-8'))

        self.ws.received_message = onmessage

    def connect(self):
        self.ws.connect()
        return self

    def send(self, msg):
        self.ws.send(msg)
        return self

    def recv(self, timeout=1.0):
        try:
            return self.queue.get(timeout=timeout)
        except Queue.Empty:
            raise TimeoutException("websocket didn't receive any thing")

    def close(self):
        self.ws.close()
Example #3
0
    def GET(self):

        url = 'ws://{0}:{1}/yapt/ws?clientname={2}'.format(
            c.conf.YAPT.WebUiAddress, str(c.conf.YAPT.WebUiPort),
            c.conf.YAPT.WebUiPlugin)
        wsc = WebSocketClient(url=url)
        wsc.connect()
        fname = './logs/info.log'
        fsize = os.stat(fname).st_size
        iter = 0
        lines = 17

        with open(fname, 'r') as f:

            bufsize = fsize - 1
            data = []

            while True:

                iter += 1
                f.seek(fsize - bufsize * iter)
                data.extend(f.readlines())

                if len(data) >= lines or f.tell() == 0:
                    # print(''.join(data[-lines:]))
                    wsc.send(payload=json.dumps({
                        'action': c.UI_ACTION_INIT_LOG_VIEWER,
                        'data': ''.join(data[-lines:])
                    }))
                    break
        wsc.close()
        pass
Example #4
0
 def close(self, code=1000, reason=''):
     try:
         WebSocketClient.close(self, code=code, reason=reason)
     except:
         pass
     try:
         WebSocketClient.close_connection(self)
     except:
         pass
     self.connected = False
Example #5
0
 def close(self, code=1000, reason=''):
     try:
         WebSocketClient.close(self, code=code, reason=reason)
     except:
         pass
     try:
         WebSocketClient.close_connection(self)
     except:
         pass
     self.connected = False
def skim_test_4_1_2(n_topics=10):
    # test 'globals'
    subLock = threading.Lock()
    subscriptions = dict()
    def received_message(m):
        data = json.loads(m.data)
        if data['op'] == 'publish':
            r_topic = data['topic']
            r_msg = json.dumps(data['msg'])

            with subLock:
                for sub in subscriptions.itervalues():
                    if r_topic == sub['topic']:
                        sub['callback'](r_msg)

    ws = WebSocketClient(ROSBRIDGE_HOST)
    ws.received_message = received_message

    ws.connect()

    current_subID = 1
    for i_top in xrange(n_topics):
        # RUN: One Topic (per topic code)
        topicArrived = threading.Event()
        topicArrived.clear()
        def genf(i_topic=0):
            return lambda data: topicArrived.set()

        # Subscribe
        subID = current_subID
        current_subID += 1
        msg = {'op': 'subscribe', 'topic': SUBTOPIC,
               'type': MSGTYPE, 'id': subID, 'throttle_rate': 5}
        ws.send(json.dumps(msg))
        with subLock:
            subscriptions[subID] = {'callback': genf(i_top),
                                    'topic': SUBTOPIC}

        # Wait for Msg
        got_msg = topicArrived.wait(2.0)

        # Unsubscribe
        msg = {'op': 'unsubscribe', 'id': subID, 'topic': SUBTOPIC}
        with subLock:
            del subscriptions[subID]
        ws.send(json.dumps(msg))
        sleep(0.01)  # 0.1 doesn't seem to cause the problem (after 20hr)...

    ws.close()
Example #7
0
    def stream_audio(self):
        while not self.listening:
            time.sleep(0.1)

        reccmd = ["arecord", "-f", "S16_LE", "-r", "16000", "-t", "raw"]
        p = subprocess.Popen(reccmd, stdout=subprocess.PIPE)

        while self.listening:
            data = p.stdout.read(1024)

            try:
                self.send(bytearray(data), binary=True)
            except ssl.SSLError:
                pass
            except:
                print "RECONNECT"
                self.status_handler(0)
                self.__init__(self.username,
                              self.password,
                              model=self.model,
                              on_recv_msg=self.on_recv_msg,
                              interim=self.interim,
                              keywords=self.keywords,
                              keywords_threshold=self.keywords_threshold,
                              status_handler=self.status_handler)
                self.status_handler(1)

            # change lang ?
            if self.change_lang_flag == True:
                self.change_lang_flag = False
                print "CHANGE LANG = %s" % (self.model)
                self.status_handler(0)
                WebSocketClient.close(self)
                time.sleep(1)
                self.__init__(self.username,
                              self.password,
                              model=self.model,
                              on_recv_msg=self.on_recv_msg,
                              interim=self.interim,
                              keywords=self.keywords,
                              keywords_threshold=self.keywords_threshold,
                              status_handler=self.status_handler)
                self.status_handler(1)

        p.kill()
def test_4_1(n_topics=10):
    if 1:
        ws = WebSocketClient(ROSBRIDGE_HOST)
    else:
        ws = RosBridgeClient(ROSBRIDGE_HOST)
    ws.connect()

    topicArrived = threading.Event()
    for i_top in xrange(n_topics):
        topicArrived.clear()
        def genf(i_topic=0):
            return lambda data: topicArrived.set()

        subID = ws.subscribe(SUBTOPIC, MSGTYPE, genf(i_top))
        got_msg = topicArrived.wait(2.0)
        ws.unsubscribe(subID)

    ws.close()
Example #9
0
def crd_open_url(host, url):
    """ Opens URL (developer mode only)

        If your Chromecast device is whitelisted, make
        embedded browser navigate to url."""
    try:
        # ask Chrome debugger interface for api endpoint
        resp = requests.get(FORMAT_DEBUG_URL.format(host))
    except requests.exceptions.ConnectionError:
        return False

    payload = json.loads(resp.text)
    wsurl = payload[0]['webSocketDebuggerUrl']

    # format and send api navigate command to the endpoint
    debugcom = FORMAT_CMD.format(url)
    remws = WebSocketClient(wsurl)
    remws.connect()
    remws.send(debugcom)
    remws.close()

    return True
Example #10
0
class Client(object):
    def __init__(self, listener):
        self.listener = listener
        self._ws_url = 'wss://ws.bitso.com'
        self.ws_client = WebSocketClient(self._ws_url)
        self.ws_client.opened = self.listener.on_connect
        self.ws_client.received_message = self._received
        self.ws_client.closed = self._closed
        self.channels = []

    def connect(self, channels):
        self.channels = channels
        self.ws_client.opened = self._opened
        self.ws_client.connect()
        self.ws_client.run_forever()

    def close(self):
        self.ws_client.close()
        
    def _opened(self):
        for channel in self.channels:
            self.ws_client.send(json.dumps({ 'action': 'subscribe', 'book': 'btc_mxn', 'type': channel }))
        thread.start_new_thread(self._ping_server, ())
        self.listener.on_connect()

    def _received(self, m):
        #print m.data
        val = json.loads(m.data)
        obj = StreamUpdate(val)
        self.listener.on_update(obj)
        
    def _closed(self, code, reason):
        self.listener.on_close(code, reason)
        
    def _ping_server(self):
        while True:
            #keep-alive
            self.ws_client.send(PingControlMessage(u'ping'))
            time.sleep(20)
Example #11
0
    def close(self, code, reason):
        self.info("closed code={0} reason={1}".format(code, reason))

        # If there is an audio streaming thread, wait for it to complete
        if self.stream_audio_thread:
            # cause the connection to close
            self.info("Stopping audio streaming thread")
            self.listening = False

            # wait for audio thread to complete
            self.stream_audio_thread.join()
            self.stream_audio_thread = None

        self.info("Closing WebSocketClient")
        WebSocketClient.close(self, code, reason)

        if self.audioF:
            self.info(
                "Closing audio recording {0}.{1} file at position {2}".format(
                    self.audioFilename[0], self.audioFilename[1],
                    self.audioF.tell()))
            self.audioF.close()
            self.audioF = None
def hack_test_4_1_2(n_topics=10):
    # test 'globals'
    subLock = threading.Lock()
    subscriptions = dict()
    def received_message(m):
        data = json.loads(m.data)
        if data['op'] == 'publish':
            r_topic = data['topic']
            r_msg = json.dumps(data['msg'])
#             print data
#             print data.keys()

            with subLock:
                for sub in subscriptions.itervalues():
                    if r_topic == sub['topic']:
                        sub['callback'](r_msg)

    w_type = 2
    if w_type==1:
        ws = WebSocketClient(ROSBRIDGE_HOST)
    elif w_type==2:
        ws = WebSocketClient(ROSBRIDGE_HOST)
        ws.received_message = received_message
#     elif w_type==3:
#         ws = WebSocketClient(ROSBRIDGE_HOST)
#         def received_message(self, m):
#             data = json.loads(m.data)
#             if data['op'] == 'publish':
#                 r_topic = data['topic']
#                 r_msg = json.dumps(data['msg'])
# 
#                 self.subLock.acquire(True)
#                 for sub in self.subscriptions.itervalues():
#                     if r_topic == sub['topic']:
#                         sub['callback'](r_msg)
#                 self.subLock.release()
# #         ws.received_message = received_message
    else:
        ws = RosBridgeClient(ROSBRIDGE_HOST)


    ws.connect()

#     subLock = threading.Lock()
#     subscriptions = dict()
    current_subID = 1
    for i_top in xrange(n_topics):
        # RUN: One Topic (per topic code)
        topicArrived = threading.Event()
        topicArrived.clear()
        def genf(i_topic=0):
            return lambda data: topicArrived.set()

    #     subID = ws.subscribe(SUBTOPIC, MSGTYPE, genf())
        def subscribe(current_subID, topic, messageType, callback):
            subID = current_subID
            current_subID += 1
            msg = {'op': 'subscribe', 'topic': SUBTOPIC,
                   'type': MSGTYPE, 'id': subID, 'throttle_rate': 5}
            ws.send(json.dumps(msg))
            with subLock:
                subscriptions[subID] = {'callback': callback, 'topic': SUBTOPIC}
            return subID
        subID = subscribe(current_subID, SUBTOPIC, MSGTYPE, genf(i_top))

        got_msg = topicArrived.wait(2.0)

    #     ws.unsubscribe(subID)
        def unsubscribe(subID):
            msg = {'op': 'unsubscribe', 'id': subID, 'topic': SUBTOPIC}
            with subLock:
                del subscriptions[subID]
            ws.send(json.dumps(msg))
        unsubscribe(subID)

    ws.close()
Example #13
0
class Client(object):
    """Represents a client connection that connects to Discord.
    This class is used to interact with the Discord WebSocket and API.

    A number of options can be passed to the :class:`Client` via keyword arguments.

    :param int max_length: The maximum number of messages to store in :attr:`messages`. Defaults to 5000.

    Instance attributes:

     .. attribute:: user

         A :class:`User` that represents the connected client. None if not logged in.
     .. attribute:: servers

         A list of :class:`Server` that the connected client has available.
     .. attribute:: private_channels

         A list of :class:`PrivateChannel` that the connected client is participating on.
     .. attribute:: messages

        A deque_ of :class:`Message` that the client has received from all servers and private messages.
     .. attribute:: email

        The email used to login. This is only set if login is successful, otherwise it's None.

    .. _deque: https://docs.python.org/3.4/library/collections.html#collections.deque
    """

    def __init__(self, **kwargs):
        self._is_logged_in = False
        self.user = None
        self.email = None
        self.servers = []
        self.private_channels = []
        self.token = ''
        self.messages = deque([], maxlen=kwargs.get('max_length', 5000))
        self.events = {
            'on_ready': _null_event,
            'on_disconnect': _null_event,
            'on_error': _null_event,
            'on_response': _null_event,
            'on_message': _null_event,
            'on_message_delete': _null_event,
            'on_message_edit': _null_event,
            'on_status': _null_event,
            'on_channel_delete': _null_event,
            'on_channel_create': _null_event,
            'on_channel_update': _null_event,
            'on_member_join': _null_event,
            'on_member_remove': _null_event,
            'on_member_update': _null_event,
            'on_server_create': _null_event,
            'on_server_delete': _null_event,
        }

        # the actual headers for the request...
        # we only override 'authorization' since the rest could use the defaults.
        self.headers = {
            'authorization': self.token,
        }

    def _get_message(self, msg_id):
        return utils.find(lambda m: m.id == msg_id, self.messages)

    def _get_server(self, guild_id):
        return utils.find(lambda g: g.id == guild_id, self.servers)

    def _add_server(self, guild):
        guild['roles'] = [Role(**role) for role in guild['roles']]
        members = guild['members']
        owner = guild['owner_id']
        for i, member in enumerate(members):
            roles = member['roles']
            for j, roleid in enumerate(roles):
                role = utils.find(lambda r: r.id == roleid, guild['roles'])
                if role is not None:
                    roles[j] = role
            members[i] = Member(**member)

            # found the member that owns the server
            if members[i].id == owner:
                owner = members[i]

        for presence in guild['presences']:
            user_id = presence['user']['id']
            member = utils.find(lambda m: m.id == user_id, members)
            if member is not None:
                member.status = presence['status']
                member.game_id = presence['game_id']


        server = Server(owner=owner, **guild)

        # give all the members their proper server
        for member in server.members:
            member.server = server

        channels = [Channel(server=server, **channel) for channel in guild['channels']]
        server.channels = channels
        self.servers.append(server)

    def _create_websocket(self, url, reconnect=False):
        if url is None:
            raise GatewayNotFound()
        log.info('websocket gateway found')
        self.ws = WebSocketClient(url, protocols=['http-only', 'chat'])

        # this is kind of hacky, but it's to avoid deadlocks.
        # i.e. python does not allow me to have the current thread running if it's self
        # it throws a 'cannot join current thread' RuntimeError
        # So instead of doing a basic inheritance scheme, we're overriding the member functions.

        self.ws.opened = self._opened
        self.ws.closed = self._closed
        self.ws.received_message = self._received_message
        self.ws.connect()
        log.info('websocket has connected')

        if reconnect == False:
            second_payload = {
                'op': 2,
                'd': {
                    'token': self.token,
                    'properties': {
                        '$os': sys.platform,
                        '$browser': 'discord.py',
                        '$device': 'discord.py',
                        '$referrer': '',
                        '$referring_domain': ''
                    },
                    'v': 2
                }
            }

            self.ws.send(json.dumps(second_payload))

    def _resolve_mentions(self, content, mentions):
        if isinstance(mentions, list):
            return [user.id for user in mentions]
        elif mentions == True:
            return re.findall(r'<@(\d+)>', content)
        else:
            return []

    def _invoke_event(self, event_name, *args, **kwargs):
        try:
            log.info('attempting to invoke event {}'.format(event_name))
            self.events[event_name](*args, **kwargs)
        except Exception as e:
            log.error('an error ({}) occurred in event {} so on_error is invoked instead'.format(type(e).__name__, event_name))
            self.events['on_error'](event_name, *sys.exc_info())

    def _received_message(self, msg):
        response = json.loads(str(msg))
        log.debug('WebSocket Event: {}'.format(response))
        if response.get('op') != 0:
            return

        self._invoke_event('on_response', response)
        event = response.get('t')
        data = response.get('d')

        if event == 'READY':
            self.user = User(**data['user'])
            guilds = data.get('guilds')

            for guild in guilds:
                self._add_server(guild)

            for pm in data.get('private_channels'):
                self.private_channels.append(PrivateChannel(id=pm['id'], user=User(**pm['recipient'])))

            # set the keep alive interval..
            interval = data.get('heartbeat_interval') / 1000.0
            self.keep_alive = KeepAliveHandler(interval, self.ws)
            self.keep_alive.start()

            # we're all ready
            self._invoke_event('on_ready')
        elif event == 'MESSAGE_CREATE':
            channel = self.get_channel(data.get('channel_id'))
            message = Message(channel=channel, **data)
            self._invoke_event('on_message', message)
            self.messages.append(message)
        elif event == 'MESSAGE_DELETE':
            channel = self.get_channel(data.get('channel_id'))
            message_id = data.get('id')
            found = self._get_message(message_id)
            if found is not None:
                self._invoke_event('on_message_delete', found)
                self.messages.remove(found)
        elif event == 'MESSAGE_UPDATE':
            older_message = self._get_message(data.get('id'))
            if older_message is not None:
                # create a copy of the new message
                message = copy.deepcopy(older_message)
                # update the new update
                for attr in data:
                    if attr == 'channel_id' or attr == 'author':
                        continue
                    value = data[attr]
                    if 'time' in attr:
                        setattr(message, attr, utils.parse_time(value))
                    else:
                        setattr(message, attr, value)
                self._invoke_event('on_message_edit', older_message, message)
                # update the older message
                older_message = message

        elif event == 'PRESENCE_UPDATE':
            server = self._get_server(data.get('guild_id'))
            if server is not None:
                status = data.get('status')
                user = data['user']
                member_id = user['id']
                member = utils.find(lambda m: m.id == member_id, server.members)
                if member is not None:
                    member.status = data.get('status')
                    member.game_id = data.get('game_id')
                    member.name = user.get('username', member.name)
                    member.avatar = user.get('avatar', member.avatar)

                    # call the event now
                    self._invoke_event('on_status', member)
                    self._invoke_event('on_member_update', member)
        elif event == 'USER_UPDATE':
            self.user = User(**data)
        elif event == 'CHANNEL_DELETE':
            server =  self._get_server(data.get('guild_id'))
            if server is not None:
                channel_id = data.get('id')
                channel = utils.find(lambda c: c.id == channel_id, server.channels)
                server.channels.remove(channel)
                self._invoke_event('on_channel_delete', channel)
        elif event == 'CHANNEL_UPDATE':
            server = self._get_server(data.get('guild_id'))
            if server is not None:
                channel_id = data.get('id')
                channel = utils.find(lambda c: c.id == channel_id, server.channels)
                channel.update(server=server, **data)
                self._invoke_event('on_channel_update', channel)
        elif event == 'CHANNEL_CREATE':
            is_private = data.get('is_private', False)
            channel = None
            if is_private:
                recipient = User(**data.get('recipient'))
                pm_id = data.get('id')
                channel = PrivateChannel(id=pm_id, user=recipient)
                self.private_channels.append(channel)
            else:
                server = self._get_server(data.get('guild_id'))
                if server is not None:
                    channel = Channel(server=server, **data)
                    server.channels.append(channel)

            self._invoke_event('on_channel_create', channel)
        elif event == 'GUILD_MEMBER_ADD':
            server = self._get_server(data.get('guild_id'))
            member = Member(server=server, deaf=False, mute=False, **data)
            server.members.append(member)
            self._invoke_event('on_member_join', member)
        elif event == 'GUILD_MEMBER_REMOVE':
            server = self._get_server(data.get('guild_id'))
            user_id = data['user']['id']
            member = utils.find(lambda m: m.id == user_id, server.members)
            server.members.remove(member)
            self._invoke_event('on_member_remove', member)
        elif event == 'GUILD_MEMBER_UPDATE':
            server = self._get_server(data.get('guild_id'))
            user_id = data['user']['id']
            member = utils.find(lambda m: m.id == user_id, server.members)
            if member is not None:
                user = data['user']
                member.name = user['username']
                member.discriminator = user['discriminator']
                member.avatar = user['avatar']
                member.roles = []
                # update the roles
                for role in server.roles:
                    if role.id in data['roles']:
                        member.roles.append(role)

                self._invoke_event('on_member_update', member)
        elif event == 'GUILD_CREATE':
            self._add_server(data)
            self._invoke_event('on_server_create', self.servers[-1])
        elif event == 'GUILD_DELETE':
            server = self._get_server(data.get('id'))
            self.servers.remove(server)
            self._invoke_event('on_server_delete', server)

    def _opened(self):
        log.info('Opened at {}'.format(int(time.time())))

    def _closed(self, code, reason=None):
        log.info('Closed with {} ("{}") at {}'.format(code, reason, int(time.time())))
        self._invoke_event('on_disconnect')

    def run(self):
        """Runs the client and allows it to receive messages and events."""
        log.info('Client is being run')
        self.ws.run_forever()

    @property
    def is_logged_in(self):
        """Returns True if the client is successfully logged in. False otherwise."""
        return self._is_logged_in

    def get_channel(self, id):
        """Returns a :class:`Channel` or :class:`PrivateChannel` with the following ID. If not found, returns None."""
        if id is None:
            return None

        for server in self.servers:
            for channel in server.channels:
                if channel.id == id:
                    return channel

        for pm in self.private_channels:
            if pm.id == id:
                return pm

    def start_private_message(self, user):
        """Starts a private message with the user. This allows you to :meth:`send_message` to it.

        Note that this method should rarely be called as :meth:`send_message` does it automatically.

        :param user: A :class:`User` to start the private message with.
        """
        if not isinstance(user, User):
            raise TypeError('user argument must be a User')

        payload = {
            'recipient_id': user.id
        }

        r = requests.post('{}/{}/channels'.format(endpoints.USERS, self.user.id), json=payload, headers=self.headers)
        if is_response_successful(r):
            data = r.json()
            log.debug(request_success_log.format(name='start_private_message', response=r, json=payload, data=data))
            self.private_channels.append(PrivateChannel(id=data['id'], user=user))
        else:
            log.error(request_logging_format.format(name='start_private_message', response=r))

    def send_message(self, destination, content, mentions=True, tts=False):
        """Sends a message to the destination given with the content given.

        The destination could be a :class:`Channel` or a :class:`PrivateChannel`. For convenience
        it could also be a :class:`User`. If it's a :class:`User` or :class:`PrivateChannel` then it
        sends the message via private message, otherwise it sends the message to the channel.

        The content must be a type that can convert to a string through ``str(content)``.

        The mentions must be either an array of :class:`User` to mention or a boolean. If
        ``mentions`` is ``True`` then all the users mentioned in the content are mentioned, otherwise
        no one is mentioned. Note that to mention someone in the content, you should use :meth:`User.mention`.

        :param destination: The location to send the message.
        :param content: The content of the message to send.
        :param mentions: A list of :class:`User` to mention in the message or a boolean. Ignored for private messages.
        :param tts: If ``True``, sends tries to send the message using text-to-speech.
        :return: The :class:`Message` sent or None if error occurred.
        """

        channel_id = ''
        is_private_message = True
        if isinstance(destination, Channel) or isinstance(destination, PrivateChannel):
            channel_id = destination.id
            is_private_message = destination.is_private
        elif isinstance(destination, User):
            found = utils.find(lambda pm: pm.user == destination, self.private_channels)
            if found is None:
                # Couldn't find the user, so start a PM with them first.
                self.start_private_message(destination)
                channel_id = self.private_channels[-1].id
            else:
                channel_id = found.id
        else:
            raise InvalidDestination('Destination must be Channel, PrivateChannel, or User')

        content = str(content)
        mentions = self._resolve_mentions(content, mentions)

        url = '{base}/{id}/messages'.format(base=endpoints.CHANNELS, id=channel_id)
        payload = {
            'content': content,
        }

        if not is_private_message:
            payload['mentions'] = mentions

        if tts:
            payload['tts'] = True

        response = requests.post(url, json=payload, headers=self.headers)
        if is_response_successful(response):
            data = response.json()
            log.debug(request_success_log.format(name='send_message', response=response, json=payload, data=data))
            channel = self.get_channel(data.get('channel_id'))
            message = Message(channel=channel, **data)
            return message
        else:
            log.error(request_logging_format.format(name='send_message', response=response))

    def delete_message(self, message):
        """Deletes a :class:`Message`.

        Your own messages could be deleted without any proper permissions. However to
        delete other people's messages, you need the proper permissions to do so.

        :param message: The :class:`Message` to delete.
        :returns: True if the message was deleted successfully, False otherwise.
        """

        url = '{}/{}/messages/{}'.format(endpoints.CHANNELS, message.channel.id, message.id)
        response = requests.delete(url, headers=self.headers)
        log.debug(request_logging_format.format(name='delete_message', response=response))
        return is_response_successful(response)

    def edit_message(self, message, new_content, mentions=True):
        """Edits a :class:`Message` with the new message content.

        The new_content must be able to be transformed into a string via ``str(new_content)``.

        :param message: The :class:`Message` to edit.
        :param new_content: The new content to replace the message with.
        :param mentions: The mentions for the user. Same as :meth:`send_message`.
        :return: The new edited message or None if an error occurred.
        """

        channel = message.channel
        content = str(new_content)

        url = '{}/{}/messages/{}'.format(endpoints.CHANNELS, channel.id, message.id)
        payload = {
            'content': content
        }

        if not channel.is_private:
            payload['mentions'] = self._resolve_mentions(content, mentions)

        response = requests.patch(url, headers=self.headers, json=payload)
        if is_response_successful(response):
            data = response.json()
            log.debug(request_success_log.format(name='edit_message', response=response, json=payload, data=data))
            return Message(channel=channel, **data)
        else:
            log.error(request_logging_format.format(name='edit_message', response=response))

    def login(self, email, password):
        """Logs in the user with the following credentials and initialises
        the connection to Discord.

        After this function is called, :attr:`is_logged_in` returns True if no
        errors occur.

        :param str email: The email used to login.
        :param str password: The password used to login.
        """

        payload = {
            'email': email,
            'password': password
        }

        r = requests.post(endpoints.LOGIN, json=payload)

        if is_response_successful(r):
            log.info('logging in returned status code {}'.format(r.status_code))
            self.email = email

            body = r.json()
            self.token = body['token']
            self.headers['authorization'] = self.token

            gateway = requests.get(endpoints.GATEWAY, headers=self.headers)
            if not is_response_successful(gateway):
                raise GatewayNotFound()
            self._create_websocket(gateway.json().get('url'), reconnect=False)
            self._is_logged_in = True
        else:
            log.error(request_logging_format.format(name='login', response=r))

    def logout(self):
        """Logs out of Discord and closes all connections."""
        response = requests.post(endpoints.LOGOUT)
        self.ws.close()
        self._is_logged_in = False
        self.keep_alive.stop.set()
        log.debug(request_logging_format.format(name='logout', response=response))

    def logs_from(self, channel, limit=500):
        """A generator that obtains logs from a specified channel.

        Yielding from the generator returns a :class:`Message` object with the message data.

        Example: ::

            for message in client.logs_from(channel):
                if message.content.startswith('!hello'):
                    if message.author == client.user:
                        client.edit_message(message, 'goodbye')


        :param channel: The :class:`Channel` to obtain the logs from.
        :param limit: The number of messages to retrieve.
        """

        url = '{}/{}/messages'.format(endpoints.CHANNELS, channel.id)
        params = {
            'limit': limit
        }
        response = requests.get(url, params=params, headers=self.headers)
        if is_response_successful(response):
            messages = response.json()
            log.info('logs_from: {0.url} was successful'.format(response))
            for message in messages:
                yield Message(channel=channel, **message)
        else:
            log.error(request_logging_format.format(name='logs_from', response=response))

    def event(self, function):
        """A decorator that registers an event to listen to.

        You can find more info about the events on the :ref:`documentation below <discord-api-events>`.

        Example: ::

            @client.event
            def on_ready():
                print('Ready!')
        """

        if function.__name__ not in self.events:
            raise InvalidEventName('The function name {} is not a valid event name'.format(function.__name__))

        self.events[function.__name__] = function
        log.info('{0.__name__} has successfully been registered as an event'.format(function))
        return function

    def delete_channel(self, channel):
        """Deletes a channel.

        In order to delete the channel, the client must have the proper permissions
        in the server the channel belongs to.

        :param channel: The :class:`Channel` to delete.
        :returns: True if channel was deleted successfully, False otherwise.
        """

        url = '{}/{}'.format(endpoints.CHANNELS, channel.id)
        response = requests.delete(url, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='delete_channel'))
        return is_response_successful(response)

    def kick(self, server, user):
        """Kicks a :class:`User` from their respective :class:`Server`.

        You must have the proper permissions to kick a user in the server.

        :param server: The :class:`Server` to kick the member from.
        :param user: The :class:`User` to kick.
        :returns: True if kick was successful, False otherwise.
        """

        url = '{base}/{server}/members/{user}'.format(base=endpoints.SERVERS, server=server.id, user=user.id)
        response = requests.delete(url, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='kick'))
        return is_response_successful(response)

    def ban(self, server, user):
        """Bans a :class:`User` from their respective :class:`Server`.

        You must have the proper permissions to ban a user in the server.

        :param server: The :class:`Server` to ban the member from.
        :param user: The :class:`User` to ban.
        :returns: True if ban was successful, False otherwise.
        """

        url = '{base}/{server}/bans/{user}'.format(base=endpoints.SERVERS, server=server.id, user=user.id)
        response = requests.put(url, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='ban'))
        return is_response_successful(response)

    def unban(self, server, name):
        """Unbans a :class:`User` from their respective :class:`Server`.

        You must have the proper permissions to unban a user in the server.

        :param server: The :class:`Server` to unban the member from.
        :param user: The :class:`User` to unban.
        :returns: True if unban was successful, False otherwise.
        """

        url = '{base}/{server}/bans/{user}'.format(base=endpoints.SERVERS, server=server.id, user=user.id)
        response = requests.delete(url, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='unban'))
        return is_response_successful(response)

    def edit_profile(self, password, **fields):
        """Edits the current profile of the client.

        All fields except password are optional.

        :param password: The current password for the client's account.
        :param new_password: The new password you wish to change to.
        :param email: The new email you wish to change to.
        :param username: The new username you wish to change to.
        """

        payload = {
            'password': password,
            'new_password': fields.get('new_password'),
            'email': fields.get('email', self.email),
            'username': fields.get('username', self.user.name),
            'avatar': self.user.avatar
        }

        url = '{0}/@me'.format(endpoints.USERS)
        response = requests.patch(url, headers=self.headers, json=payload)

        if is_response_successful(response):
            data = response.json()
            log.debug(request_success_log.format(name='edit_profile', response=response, json=payload, data=data))
            self.token = data['token']
            self.email = data['email']
            self.headers['authorization'] = self.token
            self.user = User(**data)
        else:
            log.debug(request_logging_format.format(response=response, name='edit_profile'))

    def edit_channel(self, channel, **options):
        """Edits a :class:`Channel`.

        You must have the proper permissions to edit the channel.

        References pointed to the channel will be updated with the new information.

        :param channel: The :class:`Channel` to update.
        :param name: The new channel name.
        :param position: The new channel's position in the GUI.
        :param topic: The new channel's topic.
        :returns: True if editing was successful, False otherwise.
        """

        url = '{0}/{1.id}'.format(endpoints.CHANNELS, channel)
        payload = {
            'name': options.get('name', channel.name),
            'topic': options.get('topic', channel.topic),
            'position': options.get('position', channel.position)
        }

        response = requests.patch(url, headers=self.headers, json=payload)
        if is_response_successful(response):
            data = response.json()
            log.debug(request_success_log.format(name='edit_channel', response=response, json=payload, data=data))
            channel.update(server=channel.server, **data)
            return True
        else:
            log.debug(request_logging_format.format(response=response, name='edit_channel'))
            return False

    def create_channel(self, server, name, type='text'):
        """Creates a :class:`Channel` in the specified :class:`Server`.

        Note that you need the proper permissions to create the channel.

        :param server: The :class:`Server` to create the channel in.
        :param name: The channel's name.
        :param type: The type of channel to create. 'text' or 'voice'.
        :returns: The newly created :class:`Channel` if successful, else None.
        """

        payload = {
            'name': name,
            'type': type
        }

        url = '{0}/{1.id}/channels'.format(endpoints.SERVERS, server)
        response = requests.post(url, headers=self.headers, json=payload)
        if is_response_successful(response):
            data = response.json()
            log.debug(request_success_log.format(name='create_channel', response=response, data=data, json=payload))
            channel = Channel(server=server, **data)
            # We don't append it to server.channels because CHANNEL_CREATE handles it for us.
            return channel
        else:
            log.debug(request_logging_format.format(response=response, name='create_channel'))

    def leave_server(self, server):
        """Leaves a :class:`Server`.

        :param server: The :class:`Server` to leave.
        :returns: True if leaving was successful, False otherwise.
        """

        url = '{0}/{1.id}'.format(endpoints.SERVERS, server)
        response = requests.delete(url, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='leave_server'))
        return is_response_successful(response)

    def create_invite(self, destination, **options):
        """Creates an invite for the destination which could be either a :class:`Server` or :class:`Channel`.

        The available options are:

        :param destination: The :class:`Server` or :class:`Channel` to create the invite to.
        :param max_age: How long the invite should last. If it's 0 then the invite doesn't expire. Defaults to 0.
        :param max_uses: How many uses the invite could be used for. If it's 0 then there are unlimited uses. Defaults to 0.
        :param temporary: A boolean to denote that the invite grants temporary membership (i.e. they get kicked after they disconnect). Defaults to False.
        :param xkcd: A boolean to indicate if the invite URL is human readable. Defaults to False.
        :returns: The :class:`Invite` if creation is successful, None otherwise.
        """

        payload = {
            'max_age': options.get('max_age', 0),
            'max_uses': options.get('max_uses', 0),
            'temporary': options.get('temporary', False),
            'xkcdpass': options.get('xkcd', False)
        }

        url = '{0}/{1.id}/invites'.format(endpoints.CHANNELS, destination)
        response = requests.post(url, headers=self.headers, json=payload)
        if is_response_successful(response):
            data = response.json()
            log.debug(request_success_log.format(name='create_invite', json=payload, response=response, data=data))
            data['server'] = self._get_server(data['guild']['id'])
            channel_id = data['channel']['id']
            data['channel'] = utils.find(lambda ch: ch.id == channel_id, data['server'].channels)
            return Invite(**data)
        else:
            log.debug(request_logging_format.format(response=response, name='create_invite'))

    def accept_invite(self, invite):
        """Accepts an :class:`Invite` or a URL to an invite.

        The URL must be a discord.gg URL. e.g. "http://discord.gg/codehere"

        :param invite: The :class:`Invite` or URL to an invite to accept.
        :returns: True if the invite was successfully accepted, False otherwise.
        """

        destination = None
        if isinstance(invite, Invite):
            destination = invite.id
        else:
            rx = r'(?:https?\:\/\/)?discord\.gg\/(.+)'
            m = re.match(rx, invite)
            if m:
                destination = m.group(1)

        if destination is None:
            return False

        url = '{0}/invite/{1}'.format(endpoints.API_BASE, destination)
        response = requests.post(url, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='accept_invite'))
        return is_response_successful(response)

    def edit_role(self, server, role):
        """Edits the specified :class:`Role` for the entire :class:`Server`.

        To use this you have to edit the role yourself and then pass it
        to this member function. For example: ::

            server = message.channel.server
            role = find(lambda r: r.name == 'My Cool Role', server.roles)
            role.name = 'My Not So Cool Role'
            role.permissions.can_kick_members = False
            role.permissions.can_ban_members = False
            client.edit_role(server, role)

        Note that you cannot edit the name of the @everyone role as that role is special.

        :param server: The :class:`Server` the role belongs to.
        :param role: The :class:`Role` to edit.
        :return: ``True`` if editing was successful, ``False`` otherwise.
        """

        url = '{0}/{1.id}/roles/{2.id}'.format(endpoints.SERVERS, server, role)

        payload = {
            'name': role.name,
            'permissions': role.permissions.value
        }

        response = requests.patch(url, json=payload, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='edit_role'))
        return is_response_successful(response)

    def delete_role(self, server, role):
        """Deletes the specified :class:`Role` for the entire :class:`Server`.

        Works in a similar matter to :func:`edit_role`.

        :param server: The :class:`Server` the role belongs to.
        :param role: The :class:`Role` to delete.
        :return: ``True`` if deleting was successful, ``False`` otherwise.
        """

        url = '{0}/{1.id}/roles/{2.id}'.format(endpoints.SERVERS, server, role)
        response = requests.delete(url, headers=self.headers)
        log.debug(request_logging_format.format(response=response, name='delete_role'))
        return is_response_successful(response)
 def close(self):
     self.listening = False
     self.stream_audio_thread.join()
     WebSocketClient.close(self)
Example #15
0
 def close(self):
     self.listening = False
     print("Getting ready to exit thread...")
     self.stream_audio_thread.join()
     print("Exited thread successfuly")
     WebSocketClient.close(self)
Example #16
0
 def close(self, force=False):
     WebSocketClient.close(self)
     self.client_terminated = True
     if force: self.close_connection()
Example #17
0
 def close(self, code=1000, reason=''):
     WebSocketClient.close(self, code, reason)
Example #18
0
 def close(self, code=1001, reason=None):
     if not self._connected:
         return
     WebSocketClient.close(self, code, reason)
Example #19
0
 def close(self, code=1001, reason=None):
     if not self._connected:
         return
     WebSocketClient.close(self, code, reason)
Example #20
0
    "mid": "6aad0b12-2192-4b90-8f40-08a2bc0b5c2a",
    "version": "1.0",
    "request": {
        "apiVer": "1.0.0",
        "timestamp": 1234567890,
        "pki":"fndjsafhop3u8rheowfh"
    },
    "params": {
        "sn": "0000DB11138104887174101101740000",
        "category": "0xDB",
        "model": "123",
        "id": "23454365465643",
        "ip": "0.0.0.0",
        "mac": "88e9fe5d3829",
        "random": "545623"
    }
}
if __name__ == '__main__':
    try:
        ws = WebSocketClient('ws://linksit.aimidea.cn:10000/cloud/connect', protocols=['chat'])
        ws.connect()
        print(ws.received_message("连接成功"))  #
        # ws.run_forever()
        print("连接成功")
    except KeyboardInterrupt:
        ws.close()
        print("失败了")
    ws.send(json.dumps(data))
    # print(ws.recv())
    time.sleep(10)
    ws.close()
Example #21
0
	def close(self,force=False):
		WebSocketClient.close(self)
		self.client_terminated = True
		if force: self.close_connection()
Example #22
0
    def close(self, code=1000, reason=''):

        self.listening = False
        WebSocketClient.close(self)
Example #23
0
class Client(object):
    """Represents a client connection that connects to Discord.
    This class is used to interact with the Discord WebSocket and API.

    A number of options can be passed to the :class:`Client` via keyword arguments.

    :param int max_length: The maximum number of messages to store in :attr:`messages`. Defaults to 5000.

    Instance attributes:

     .. attribute:: user

         A :class:`User` that represents the connected client. None if not logged in.
     .. attribute:: servers

         A list of :class:`Server` that the connected client has available.
     .. attribute:: private_channels

         A list of :class:`PrivateChannel` that the connected client is participating on.
     .. attribute:: messages

        A deque_ of :class:`Message` that the client has received from all servers and private messages.

    .. _deque: https://docs.python.org/3.4/library/collections.html#collections.deque
    """

    def __init__(self, **kwargs):
        self._is_logged_in = False
        self.user = None
        self.servers = []
        self.private_channels = []
        self.token = ""
        self.messages = deque([], maxlen=kwargs.get("max_length", 5000))
        self.events = {
            "on_ready": _null_event,
            "on_disconnect": _null_event,
            "on_error": _null_event,
            "on_response": _null_event,
            "on_message": _null_event,
            "on_message_delete": _null_event,
            "on_message_edit": _null_event,
            "on_status": _null_event,
            "on_channel_delete": _null_event,
            "on_channel_create": _null_event,
            "on_member_join": _null_event,
            "on_member_remove": _null_event,
            "on_member_typing": _null_event,
        }

        gateway = requests.get(endpoints.GATEWAY)
        if gateway.status_code != 200:
            raise GatewayNotFound()
        gateway_js = gateway.json()
        url = gateway_js.get("url")
        if url is None:
            raise GatewayNotFound()

        self.ws = WebSocketClient(url, protocols=["http-only", "chat"])

        # this is kind of hacky, but it's to avoid deadlocks.
        # i.e. python does not allow me to have the current thread running if it's self
        # it throws a 'cannot join current thread' RuntimeError
        # So instead of doing a basic inheritance scheme, we're overriding the member functions.

        self.ws.opened = self._opened
        self.ws.closed = self._closed
        self.ws.received_message = self._received_message

        # the actual headers for the request...
        # we only override 'authorization' since the rest could use the defaults.
        self.headers = {"authorization": self.token}

    def _get_message(self, msg_id):
        return next((m for m in self.messages if m.id == msg_id), None)

    def _get_server(self, guild_id):
        return next((s for s in self.servers if s.id == guild_id), None)

    def _get_channel(self, channel_id):
        return next((c for c in server.channels if c.id == channel_id), None)

    def _get_member(self, user_id):
        return next((m for m in server.members if m.id == user_id), None)

    def _resolve_mentions(self, content, mentions):
        if isinstance(mentions, list):
            return [user.id for user in mentions]
        elif mentions == True:
            return re.findall(r"@<(\d+)>", content)
        else:
            return []

    def _invoke_event(self, event_name, *args, **kwargs):
        try:
            self.events[event_name](*args, **kwargs)
        except Exception as e:
            pass

    def _received_message(self, msg):
        response = json.loads(str(msg))
        if response.get("op") != 0:
            return

        self._invoke_event("on_response", response)
        event = response.get("t")
        data = response.get("d")

        if event == "READY":
            self.user = User(**data["user"])
            guilds = data.get("guilds")

            for guild in guilds:
                guild["roles"] = [Role(**role) for role in guild["roles"]]
                members = guild["members"]
                for i, member in enumerate(members):
                    roles = member["roles"]
                    for j, roleid in enumerate(roles):
                        role = next((r for r in guild["roles"] if r.id == roleid), None)
                        if role is not None:
                            roles[j] = role
                    members[i] = Member(**member)

                for presence in guild["presences"]:
                    user_id = presence["user"]["id"]
                    member = next((m for m in members if m.id == user_id), None)
                    if member is not None:
                        member.status = presence["status"]
                        member.game_id = presence["game_id"]

                server = Server(**guild)

                # give all the members their proper server
                for member in server.members:
                    member.server = server

                for channel in guild["channels"]:
                    changed_roles = []
                    permission_overwrites = channel["permission_overwrites"]

                    for overridden in permission_overwrites:
                        # this is pretty inefficient due to the deep nested loops unfortunately
                        role = next((role for role in guild["roles"] if role.id == overridden["id"]), None)
                        if role is None:
                            continue
                        denied = overridden.get("deny", 0)
                        allowed = overridden.get("allow", 0)
                        override = copy.deepcopy(role)

                        # Basically this is what's happening here.
                        # We have an original bit array, e.g. 1010
                        # Then we have another bit array that is 'denied', e.g. 1111
                        # And then we have the last one which is 'allowed', e.g. 0101
                        # We want original OP denied to end up resulting in whatever is in denied to be set to 0.
                        # So 1010 OP 1111 -> 0000
                        # Then we take this value and look at the allowed values. And whatever is allowed is set to 1.
                        # So 0000 OP2 0101 -> 0101
                        # The OP is (base ^ denied) & ~denied.
                        # The OP2 is base | allowed.
                        override.permissions.value = ((override.permissions.value ^ denied) & ~denied) | allowed
                        changed_roles.append(override)

                    channel["permission_overwrites"] = changed_roles
                channels = [Channel(server=server, **channel) for channel in guild["channels"]]
                server.channels = channels
                self.servers.append(server)

            for pm in data.get("private_channels"):
                self.private_channels.append(PrivateChannel(id=pm["id"], user=User(**pm["recipient"])))

            # set the keep alive interval..
            interval = data.get("heartbeat_interval") / 1000.0
            self.keep_alive = _keep_alive_handler(interval, self.ws)

            # we're all ready
            self._invoke_event("on_ready")
        elif event == "MESSAGE_CREATE":
            channel = self.get_channel(data.get("channel_id"))
            message = Message(channel=channel, **data)
            self.events["on_message"](message)
            self.messages.append(message)
        elif event == "MESSAGE_DELETE":
            channel = self.get_channel(data.get("channel_id"))
            message_id = data.get("id")
            found = self._get_message(message_id)
            if found is not None:
                self.events["on_message_delete"](found)
                self.messages.remove(found)
        elif event == "MESSAGE_UPDATE":
            older_message = self._get_message(data.get("id"))
            if older_message is not None:
                # create a copy of the new message
                message = copy.deepcopy(older_message)
                # update the new update
                for attr in data:
                    if attr == "channel_id":
                        continue
                    value = data[attr]
                    if "time" in attr:
                        setattr(message, attr, message._parse_time(value))
                    else:
                        setattr(message, attr, value)
                self._invoke_event("on_message_edit", older_message, message)
                # update the older message
                older_message = message

        elif event == "PRESENCE_UPDATE":
            server = self._get_server(data.get("guild_id"))
            if server is not None:
                status = data.get("status")
                member_id = data["user"]["id"]
                member = next((u for u in server.members if u.id == member_id), None)
                if member is not None:
                    member.status = data.get("status")
                    member.game_id = data.get("game_id")
                    # call the event now
                    self._invoke_event("on_status", member)
        elif event == "USER_UPDATE":
            self.user = User(**data)
        elif event == "CHANNEL_DELETE":
            server = self._get_server(data.get("guild_id"))
            if server is not None:
                channel_id = data.get("id")
                channel = next((c for c in server.channels if c.id == channel_id), None)
                server.channels.remove(channel)
                self._invoke_event("on_channel_delete", channel)
        elif event == "CHANNEL_CREATE":
            is_private = data.get("is_private", False)
            channel = None
            if is_private:
                recipient = User(**data.get("recipient"))
                pm_id = data.get("id")
                channel = PrivateChannel(id=pm_id, user=recipient)
                self.private_channels.append(channel)
            else:
                server = self._get_server(data.get("guild_id"))
                if server is not None:
                    channel = Channel(server=server, **data)
                    server.channels.append(channel)

            self._invoke_event("on_channel_create", channel)
        elif event == "GUILD_MEMBER_ADD":
            server = self._get_server(data.get("guild_id"))
            member = Member(deaf=False, mute=False, **data)
            server.members.append(member)
            self._invoke_event("on_member_join", member)
        elif event == "GUILD_MEMBER_REMOVE":
            server = self._get_server(data.get("guild_id"))
            user_id = data["user"]["id"]
            member = next((m for m in server.members if m.id == user_id), None)
            server.members.remove(member)
            self._invoke_event("on_member_remove", member)
        elif event == "TYPING_START":
            if self.events["on_member_typing"] == _null_event:
                return

            member = self._get_member(data["user_id"])
            channel = self._get_channel(data["channel_id"])
            timestamp = data["timestamp"]
            self._invoke_event("on_member_remove", member, channel, timestamp)
        # elif event == 'VOICE_STATE_UPDATE':
        #   pass
        # else:
        #    print("Unhandled event (%s)" % event)
        #    from pprint import pprint
        #    pprint(response)

    def _opened(self):
        print("Opened at {}".format(int(time.time())))

    def _closed(self, code, reason=None):
        print('Closed with {} ("{}") at {}'.format(code, reason, int(time.time())))
        self._invoke_event("on_disconnect")

    def run(self):
        """Runs the client and allows it to receive messages and events."""
        self.ws.run_forever()

    @property
    def is_logged_in(self):
        """Returns True if the client is successfully logged in. False otherwise."""
        return self._is_logged_in

    def get_channel(self, id):
        """Returns a :class:`Channel` or :class:`PrivateChannel` with the following ID. If not found, returns None."""
        if id is None:
            return None

        for server in self.servers:
            for channel in server.channels:
                if channel.id == id:
                    return channel

        for pm in self.private_channels:
            if pm.id == id:
                return pm

    def start_private_message(self, user):
        """Starts a private message with the user. This allows you to :meth:`send_message` to it.

        Note that this method should rarely be called as :meth:`send_message` does it automatically.

        :param user: A :class:`User` to start the private message with.
        """
        if not isinstance(user, User):
            raise TypeError("user argument must be a User")

        payload = {"recipient_id": user.id}

        r = requests.post("{}/{}/channels".format(endpoints.USERS, self.user.id), json=payload, headers=self.headers)
        if r.status_code == 200:
            data = r.json()
            self.private_channels.append(PrivateChannel(id=data["id"], user=user))

    def send_message(self, destination, content, mentions=True):
        """Sends a message to the destination given with the content given.

        The destination could be a :class:`Channel` or a :class:`PrivateChannel`. For convenience
        it could also be a :class:`User`. If it's a :class:`User` or :class:`PrivateChannel` then it
        sends the message via private message, otherwise it sends the message to the channel.

        The content must be a type that can convert to a string through ``str(content)``.

        The mentions must be either an array of :class:`User` to mention or a boolean. If
        ``mentions`` is ``True`` then all the users mentioned in the content are mentioned, otherwise
        no one is mentioned. Note that to mention someone in the content, you should use :meth:`User.mention`.

        :param destination: The location to send the message.
        :param content: The content of the message to send.
        :param mentions: A list of :class:`User` to mention in the message or a boolean. Ignored for private messages.
        :return: The :class:`Message` sent or None if error occurred.
        """

        channel_id = ""
        is_private_message = True
        if isinstance(destination, Channel) or isinstance(destination, PrivateChannel):
            channel_id = destination.id
            is_private_message = destination.is_private
        elif isinstance(destination, User):
            found = next((pm for pm in self.private_channels if pm.user == destination), None)
            if found is None:
                # Couldn't find the user, so start a PM with them first.
                self.start_private_message(destination)
                channel_id = self.private_channels[-1].id
            else:
                channel_id = found.id
        else:
            raise InvalidDestination("Destination must be Channel, PrivateChannel, or User")

        content = str(content)
        mentions = self._resolve_mentions(content, mentions)

        url = "{base}/{id}/messages".format(base=endpoints.CHANNELS, id=channel_id)
        payload = {"content": content}

        if not is_private_message:
            payload["mentions"] = mentions

        response = requests.post(url, json=payload, headers=self.headers)
        if response.status_code == 200:
            data = response.json()
            channel = self.get_channel(data.get("channel_id"))
            message = Message(channel=channel, **data)
            return message

    def delete_message(self, message):
        """Deletes a :class:`Message`

        A fairly straightforward function.

        :param message: The :class:`Message` to delete.
        """
        url = "{}/{}/messages/{}".format(endpoints.CHANNELS, message.channel.id, message.id)
        response = requests.delete(url, headers=self.headers)

    def edit_message(self, message, new_content, mentions=True):
        """Edits a :class:`Message` with the new message content.

        The new_content must be able to be transformed into a string via ``str(new_content)``.

        :param message: The :class:`Message` to edit.
        :param new_content: The new content to replace the message with.
        :param mentions: The mentions for the user. Same as :meth:`send_message`.
        :return: The new edited message or None if an error occurred."""
        channel = message.channel
        content = str(new_content)

        url = "{}/{}/messages/{}".format(endpoints.CHANNELS, channel.id, message.id)
        payload = {"content": content}

        if not channel.is_private:
            payload["mentions"] = self._resolve_mentions(content, mentions)

        response = requests.patch(url, headers=self.headers, json=payload)
        if response.status_code == 200:
            data = response.json()
            return Message(channel=channel, **data)

    def login(self, email, password):
        """Logs in the user with the following credentials and initialises
        the connection to Discord.

        After this function is called, :attr:`is_logged_in` returns True if no
        errors occur.

        :param str email: The email used to login.
        :param str password: The password used to login.
        """

        self.ws.connect()

        payload = {"email": email, "password": password}

        r = requests.post(endpoints.LOGIN, json=payload)

        if r.status_code == 200:
            body = r.json()
            self.token = body["token"]
            self.headers["authorization"] = self.token
            second_payload = {
                "op": 2,
                "d": {
                    "token": self.token,
                    "properties": {
                        "$os": sys_platform,
                        "$browser": "discord.py",
                        "$device": "discord.py",
                        "$referrer": "",
                        "$referring_domain": "",
                    },
                    "v": 2,
                },
            }

            self.ws.send(json.dumps(second_payload))
            self._is_logged_in = True

    def logout(self):
        """Logs out of Discord and closes all connections."""
        response = requests.post(endpoints.LOGOUT)
        self.ws.close()
        self._is_logged_in = False
        self.keep_alive.cancel()

    def logs_from(self, channel, limit=500):
        """A generator that obtains logs from a specified channel.

        Yielding from the generator returns a :class:`Message` object with the message data.

        Example: ::

            for message in client.logs_from(channel):
                if message.content.startswith('!hello'):
                    if message.author == client.user:
                        client.edit_message(message, 'goodbye')


        :param channel: The :class:`Channel` to obtain the logs from.
        :param limit: The number of messages to retrieve.
        """

        url = "{}/{}/messages".format(endpoints.CHANNELS, channel.id)
        params = {"limit": limit}
        response = requests.get(url, params=params, headers=self.headers)
        if response.status_code == 200:
            messages = response.json()
            for message in messages:
                yield Message(channel=channel, **message)

    def event(self, function):
        """A decorator that registers an event to listen to.

        You can find more info about the events on the :ref:`documentation below <discord-api-events>`.

        Example: ::

            @client.event
            def on_ready():
                print('Ready!')
        """

        if function.__name__ not in self.events:
            raise InvalidEventName("The function name {} is not a valid event name".format(function.__name__))

        self.events[function.__name__] = function
        return function

    def create_channel(self, server, name, type="text"):
        """Creates a channel in the specified server.

        In order to create the channel the client must have the proper permissions.

        :param server: The :class:`Server` to create the channel from.
        :param name: The name of the channel to create.
        :param type: The type of channel to create. Valid values are 'text' or 'voice'.
        :returns: The recently created :class:`Channel`.
        """
        url = "{}/{}/channels".format(endpoints.SERVERS, server.id)
        payload = {"name": name, "type": type}
        response = requests.post(url, json=payload, headers=self.headers)
        if response.status_code == 200:
            channel = Channel(server=server, **response.json())
            return channel

    def delete_channel(self, channel):
        """Deletes a channel.

        In order to delete the channel, the client must have the proper permissions
        in the server the channel belongs to.

        :param channel: The :class:`Channel` to delete.
        """
        url = "{}/{}".format(endpoints.CHANNELS, channel.id)
        response = requests.delete(url, headers=self.headers)