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)
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)
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
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
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")
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)
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)
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()