Example #1
0
    def execute_webhook(self, *, payload, wait=False, file=None, files=None):
        cleanup = None
        if file is not None:
            multipart = {
                'file': (file.filename, file.fp, 'application/octet-stream'),
                'payload_json': utils.to_json(payload)
            }
            data = None
            cleanup = file.close
            files_to_pass = [file]
        elif files is not None:
            multipart = {
                'payload_json': utils.to_json(payload)
            }
            for i, file in enumerate(files):
                multipart['file%i' % i] = (file.filename, file.fp, 'application/octet-stream')
            data = None

            def _anon():
                for f in files:
                    f.close()

            cleanup = _anon
            files_to_pass = files
        else:
            data = payload
            multipart = None
            files_to_pass = None

        url = '%s?wait=%d' % (self._request_url, wait)
        maybe_coro = None
        try:
            maybe_coro = self.request('POST', url, multipart=multipart, payload=data, files=files_to_pass)
        finally:
            if maybe_coro is not None and cleanup is not None:
                if not asyncio.iscoroutine(maybe_coro):
                    cleanup()
                else:
                    maybe_coro = self._wrap_coroutine_and_cleanup(maybe_coro, cleanup)

        # if request raises up there then this should never be `None`
        return self.handle_execution_response(maybe_coro, wait=wait)
Example #2
0
def send_files(self,
               channel_id,
               *,
               files,
               content=None,
               tts=False,
               embed=None,
               nonce=None,
               allowed_mentions=None,
               message_reference=None,
               components=None):
    r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
    form = []

    payload = {'tts': tts}
    if content:
        payload['content'] = content
    if embed:
        payload['embed'] = embed
    if nonce:
        payload['nonce'] = nonce
    if allowed_mentions:
        payload['allowed_mentions'] = allowed_mentions
    if message_reference:
        payload['message_reference'] = message_reference
    if components:
        payload['components'] = components

    form.append({'name': 'payload_json', 'value': utils.to_json(payload)})
    if len(files) == 1:
        file = files[0]
        form.append({
            'name': 'file',
            'value': file.fp,
            'filename': file.filename,
            'content_type': 'application/octet-stream'
        })
    else:
        for index, file in enumerate(files):
            form.append({
                'name': 'file%s' % index,
                'value': file.fp,
                'filename': file.filename,
                'content_type': 'application/octet-stream'
            })

    return self.request(r, form=form, files=files)
Example #3
0
    async def get_voice_client(self, channel):
        if isinstance(channel, Object):
            channel = self.get_channel(channel.id)

        if getattr(channel, 'type', ChannelType.text) != ChannelType.voice:
            raise AttributeError('Channel passed must be a voice channel')

        with await self.voice_client_connect_lock:
            server = channel.server
            if server.id in self.voice_clients:
                return self.voice_clients[server.id]

            payload = {
                'op': 4,
                'd': {
                    'guild_id': channel.server.id,
                    'channel_id': channel.id,
                    'self_mute': False,
                    'self_deaf': False
                }
            }

            await self.ws.send(utils.to_json(payload))
            await asyncio.wait_for(self._session_id_found.wait(), timeout=5.0, loop=self.loop)
            await asyncio.wait_for(self._voice_data_found.wait(), timeout=5.0, loop=self.loop)

            session_id = self.session_id
            voice_data = self._voice_data_found.data

            self._session_id_found.clear()
            self._voice_data_found.clear()

            kwargs = {
                'user': self.user,
                'channel': channel,
                'data': voice_data,
                'loop': self.loop,
                'session_id': session_id,
                'main_ws': self.ws
            }

            voice_client = VoiceClient(**kwargs)
            self.voice_clients[server.id] = voice_client

            await voice_client.connect()
            return voice_client
Example #4
0
    async def _update_voice_state(self, channel, *, mute=False, deaf=False):
        if isinstance(channel, Object):
            channel = self.get_channel(channel.id)

        if getattr(channel, 'type', ChannelType.text) != ChannelType.voice:
            raise AttributeError('Channel passed must be a voice channel')

        # I'm not sure if this lock is actually needed
        with await self.voice_client_move_lock:
            server = channel.server

            payload = {
                'op': 4,
                'd': {
                    'guild_id': server.id,
                    'channel_id': channel.id,
                    'self_mute': mute,
                    'self_deaf': deaf
                }
            }

            await self.ws.send(utils.to_json(payload))
            self.the_voice_clients[server.id].channel = channel
Example #5
0
 async def send_message(self, msg):
     data = to_json(msg)
     self.ws._dispatch("socket_raw_send", data)
     await self._amqp_channel.default_exchange.publish(
         aio_pika.Message(body=data), routing_key="gateway.send")
Example #6
0
    def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None):
        headers = {}
        data = None
        files = files or []
        if payload:
            headers['Content-Type'] = 'application/json'
            data = utils.to_json(payload)

        if reason:
            headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ')

        if multipart is not None:
            data = {'payload_json': multipart.pop('payload_json')}

        base_url = url.replace(self._request_url, '/') or '/'
        _id = self._webhook_id
        for tries in range(5):
            for file in files:
                file.reset(seek=tries)

            r = self.session.request(verb, url, headers=headers, data=data, files=multipart)
            r.encoding = 'utf-8'
            # Coerce empty responses to return None for hygiene purposes
            response = r.text or None

            # compatibility with aiohttp
            r.status = r.status_code

            log.debug('Webhook ID %s with %s %s has returned status code %s', _id, verb, base_url, r.status)
            if r.headers['Content-Type'] == 'application/json':
                response = json.loads(response)

            # check if we have rate limit header information
            remaining = r.headers.get('X-Ratelimit-Remaining')
            if remaining == '0' and r.status != 429 and self.sleep:
                delta = utils._parse_ratelimit_header(r)
                log.debug('Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', _id, delta)
                time.sleep(delta)

            if 300 > r.status >= 200:
                return response

            # we are being rate limited
            if r.status == 429:
                if self.sleep:
                    if not r.headers.get('Via'):
                        # Banned by Cloudflare more than likely.
                        raise HTTPException(r, data)

                    retry_after = response['retry_after'] / 1000.0
                    log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', _id, retry_after)
                    time.sleep(retry_after)
                    continue
                else:
                    raise HTTPException(r, response)

            if self.sleep and r.status in (500, 502):
                time.sleep(1 + tries * 2)
                continue

            if r.status == 403:
                raise Forbidden(r, response)
            elif r.status == 404:
                raise NotFound(r, response)
            else:
                raise HTTPException(r, response)

        # no more retries
        if r.status >= 500:
            raise DiscordServerError(r, response)
        raise HTTPException(r, response)
Example #7
0
    async def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None):
        headers = {}
        data = None
        files = files or []
        if payload:
            headers['Content-Type'] = 'application/json'
            data = utils.to_json(payload)

        if reason:
            headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ')


        base_url = url.replace(self._request_url, '/') or '/'
        _id = self._webhook_id
        for tries in range(5):
            for file in files:
                file.reset(seek=tries)

            if multipart:
                data = aiohttp.FormData()
                for key, value in multipart.items():
                    if key.startswith('file'):
                        data.add_field(key, value[1], filename=value[0], content_type=value[2])
                    else:
                        data.add_field(key, value)

            async with self.session.request(verb, url, headers=headers, data=data) as r:
                log.debug('Webhook ID %s with %s %s has returned status code %s', _id, verb, base_url, r.status)
                # Coerce empty strings to return None for hygiene purposes
                response = (await r.text(encoding='utf-8')) or None
                if r.headers['Content-Type'] == 'application/json':
                    response = json.loads(response)

                # check if we have rate limit header information
                remaining = r.headers.get('X-Ratelimit-Remaining')
                if remaining == '0' and r.status != 429:
                    delta = utils._parse_ratelimit_header(r)
                    log.debug('Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', _id, delta)
                    await asyncio.sleep(delta)

                if 300 > r.status >= 200:
                    return response

                # we are being rate limited
                if r.status == 429:
                    if not r.headers.get('Via'):
                        # Banned by Cloudflare more than likely.
                        raise HTTPException(r, data)

                    retry_after = response['retry_after'] / 1000.0
                    log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', _id, retry_after)
                    await asyncio.sleep(retry_after)
                    continue

                if r.status in (500, 502):
                    await asyncio.sleep(1 + tries * 2)
                    continue

                if r.status == 403:
                    raise Forbidden(r, response)
                elif r.status == 404:
                    raise NotFound(r, response)
                else:
                    raise HTTPException(r, response)

        # no more retries
        if r.status >= 500:
            raise DiscordServerError(r, response)
        raise HTTPException(r, response)
Example #8
0
def request(self: discord.http.HTTPClient,
            route: discord.http.Route,
            rate_limit_info: RateLimitInfo,
            *,
            header_bypass_delay=None,
            **kwargs):
    global global_over

    bucket = route.bucket
    method = route.method
    url = route.url

    lock = self._locks.get(bucket)
    if lock is None:
        lock = asyncio.Lock(loop=self.loop)
        if bucket is not None:
            self._locks[bucket] = lock

    # header creation
    headers = {
        'User-Agent': self.user_agent,
    }

    if self.token is not None:
        headers[
            'Authorization'] = 'Bot ' + self.token if self.bot_token else self.token

    # some checking if it's a JSON request
    if 'json' in kwargs:
        headers['Content-Type'] = 'application/json'
        kwargs['data'] = utils.to_json(kwargs.pop('json'))

    kwargs['headers'] = headers

    if not global_over.is_set():
        # wait until the global lock is complete
        yield from global_over.wait()

    yield from lock
    with discord.http.MaybeUnlock(lock) as maybe_lock:
        for tries in range(5):
            r = yield from self.session.request(method, url, **kwargs)
            log.debug(
                self.REQUEST_LOG.format(method=method,
                                        url=url,
                                        status=r.status,
                                        json=kwargs.get('data')))
            try:
                # even errors have text involved in them so this is safe to call
                data = yield from discord.http.json_or_text(r)

                # check if we have rate limit header information
                rate_limit_info.remaining = r.headers.get(
                    'X-Ratelimit-Remaining')
                if rate_limit_info.remaining is not None:
                    rate_limit_info.limit = r.headers['X-Ratelimit-Limit']
                    rate_limit_info.now = discord.http.parsedate_to_datetime(
                        r.headers['Date'])
                    rate_limit_info.reset = datetime.datetime.fromtimestamp(
                        int(r.headers['X-Ratelimit-Reset']),
                        datetime.timezone.utc)

                if rate_limit_info.remaining == '0' and r.status != 429:
                    # we've depleted our current bucket
                    if header_bypass_delay is None:
                        now = discord.http.parsedate_to_datetime(
                            r.headers['Date'])
                        reset = datetime.datetime.fromtimestamp(
                            int(r.headers['X-Ratelimit-Reset']),
                            datetime.timezone.utc)
                        delta = (reset - now).total_seconds()
                    else:
                        delta = header_bypass_delay

                    fmt = 'A rate limit bucket has been exhausted (bucket: {bucket}, retry: {delta}).'
                    log.info(fmt.format(bucket=bucket, delta=delta))
                    maybe_lock.defer()
                    self.loop.call_later(delta, lock.release)

                # the request was successful so just return the text/json
                if 300 > r.status >= 200:
                    log.debug(
                        self.SUCCESS_LOG.format(method=method,
                                                url=url,
                                                text=data))
                    return data

                # we are being rate limited
                if r.status == 429:
                    fmt = 'We are being rate limited. Retrying in {:.2} seconds. Handled under the bucket "{}"'

                    # sleep a bit
                    retry_after = data['retry_after'] / 1000.0
                    log.info(fmt.format(retry_after, bucket))

                    # check if it's a global rate limit
                    is_global = data.get('global', False)
                    if is_global:
                        log.info(
                            'Global rate limit has been hit. Retrying in {:.2} seconds.'
                            .format(retry_after))
                        global_over.clear()

                    yield from asyncio.sleep(retry_after, loop=self.loop)
                    log.debug('Done sleeping for the rate limit. Retrying...')

                    # release the global lock now that the
                    # global rate limit has passed
                    if is_global:
                        global_over.set()
                        log.debug('Global rate limit is now over.')

                    continue

                # we've received a 502, unconditional retry
                if r.status == 502 and tries <= 5:
                    yield from asyncio.sleep(1 + tries * 2, loop=self.loop)
                    continue

                # the usual error cases
                if r.status == 403:
                    raise Forbidden(r, data)
                elif r.status == 404:
                    raise NotFound(r, data)
                else:
                    raise HTTPException(r, data)
            finally:
                # clean-up just in case
                yield from r.release()
async def _request(self,
                   verb,
                   url,
                   payload=None,
                   multipart=None,
                   *,
                   files=None,
                   token=None):
    headers = {}
    if token:
        headers['Authorization'] = token

    data = None
    files = files or []
    if payload:
        headers['Content-Type'] = 'application/json'
        data = utils.to_json(payload)

    if multipart:
        data = aiohttp.FormData()
        for key, value in multipart.items():
            if key.startswith('file'):
                data.add_field(key,
                               value[1],
                               filename=value[0],
                               content_type=value[2])
            else:
                data.add_field(key, value)

    for tries in range(5):
        for file in files:
            file.reset(seek=tries)

        async with self.session.request(verb, url, headers=headers,
                                        data=data) as r:
            # Coerce empty strings to return None for hygiene purposes
            response = (await r.text(encoding='utf-8')) or None
            if r.headers['Content-Type'] == 'application/json':
                response = json.loads(response)

            # check if we have rate limit header information
            remaining = r.headers.get('X-Ratelimit-Remaining')
            if remaining == '0' and r.status != 429:
                delta = utils._parse_ratelimit_header(r)
                await asyncio.sleep(delta)

            if 300 > r.status >= 200:
                return response

            # we are being rate limited
            if r.status == 429:
                retry_after = response['retry_after'] / 1000.0
                await asyncio.sleep(retry_after)
                continue

            if r.status in (500, 502):
                await asyncio.sleep(1 + tries * 2)
                continue

            if r.status == 403:
                raise errors.Forbidden(r, response)
            elif r.status == 404:
                raise errors.NotFound(r, response)
            else:
                raise errors.HTTPException(r, response)
    # no more retries
    raise errors.HTTPException(r, response)
    def send(
        self,
        content=None,
        *,
        type=InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
        tts=False,
        embed=None,
        embeds=[],
        allowed_mentions=None,
        ephemeral=False,
        file=None,
        files=[],
    ):
        state = self._state
        if embed and embeds:
            raise RuntimeError("Both 'embed' and 'embeds' are specified.")

        if file and files:
            raise RuntimeError("Both 'file' and 'files' are specified.")

        if embed is not None:
            embeds = [embed]

        embeds = [embed.to_dict() for embed in embeds]

        if allowed_mentions is not None:
            if state.allowed_mentions is not None:
                allowed_mentions = state.allowed_mentions.merge(
                    allowed_mentions).to_dict()
            else:
                allowed_mentions = allowed_mentions.to_dict()
        else:
            allowed_mentions = (state.allowed_mentions
                                and state.allowed_mentions.to_dict())

        r = Route(
            "POST",
            "/interactions/{interaction.id}/{interaction.token}/callback"
            if type is not None else
            "/webhooks/{application_id}/{interaction.token}",
            interaction=self.interaction,
            application_id=self.bot.user.id,
        )

        if file:
            files = [file]

        json = dict(
            content=content,
            embeds=embeds,
            allowed_mentions=allowed_mentions,
            tts=tts,
        )
        kwargs = {}
        if files:
            form = aiohttp.FormData()
            form.add_field("payload_json", utils.to_json(json))
            multiple_files = len(files) > 1
            for idx, file in enumerate(files):
                name = f"file{idx if multiple_files else ''}"
                form.add_field(
                    name,
                    file.fp,
                    filename=file.filename,
                    content_type="application/octet-stream",
                )
            kwargs["data"] = form
            kwargs["files"] = files
        else:
            json = dict(
                type=int(type),
                data=json,
            )
            if ephemeral:
                json["data"]["flags"] = 64

            kwargs["json"] = json

        try:
            return state.http.request(r, **kwargs)
        finally:
            if files:
                for file in files:
                    file.close()