async def obama(self, ctx, *, text: str): """ Synthesize video clips of Obama """ text = ctx.message.clean_content[len(f"{ctx.prefix}{ctx.invoked_with}" ):] if len(text) > 280: msg = "A maximum character total of 280 is enforced. You sent: `{}` characters" return await ctx.send(msg.format(len(text))) async with ctx.typing(): async with self.session.post( url="http://talkobamato.me/synthesize.py", data={"input_text": text}) as resp: if resp.status >= 400: raise discord.HTTPException( resp, f"{resp.url} returned error code {resp.status}") url = resp.url key = url.query['speech_key'] link = f"http://talkobamato.me/synth/output/{key}/obama.mp4" await asyncio.sleep(len(text) // 5) async with self.session.get(link) as resp: if resp.status >= 400: raise discord.HTTPException( resp, f"{resp.url} returned error code {resp.status}") async with self.session.get(link) as r: data = BytesIO(await r.read()) data.name = "obama.mp4" data.seek(0) file = discord.File(data) await ctx.send(files=[file])
async def make_request(self, method: str, url: str, *, github=False, json=None): """Makes a request to either the GitLab API or Github API Parameters ----------- method: str The type of method to use. url: str The URL you are requesting. """ if github: headers = self.github_headers headers.update({'Accept': 'application/vnd.github.v3+json'}) else: headers = self.gitlab_headers headers.update({"User-Agent": "Lightning Bot Git Cog"}) async with self.bot.aiosession.request(method, url, headers=headers, json=json) as resp: ratelimit = resp.headers.get("RateLimit-Remaining") if ratelimit == 0: raise LightningError( "Currently ratelimited. Try again later(?)") elif 300 > resp.status >= 200: data = await resp.json() else: raise discord.HTTPException(response=resp, message=str(resp.reason)) return data
async def test_relay_message_handles_attachment_http_error( self, send_attachments, send_webhook): """The `relay_message` method should handle irretrievable attachments.""" message = helpers.MockMessage(clean_content="message", attachments=["attachment"]) self.cog.webhook = helpers.MockAsyncWebhook() log = logging.getLogger("bot.cogs.duck_pond") side_effect = discord.HTTPException(MagicMock(), "") send_attachments.side_effect = side_effect with self.subTest(side_effect=type(side_effect).__name__): with self.assertLogs(logger=log, level=logging.ERROR) as log_watcher: await self.cog.relay_message(message) send_webhook.assert_called_once_with( content=message.clean_content, username=message.author.display_name, avatar_url=message.author.avatar_url) self.assertEqual(len(log_watcher.records), 1) record = log_watcher.records[0] self.assertEqual(record.levelno, logging.ERROR)
async def create_and_add_channel_webhook( self, conn, channel: discord.Channel) -> (str, str): # This method is only called if there's no webhook found in the DB (and hopefully within a transaction) # No need to worry about error handling if there's a DB conflict (which will throw an exception because DB constraints) req_headers = {"Authorization": "Bot {}".format(self.token)} # First, check if there's already a webhook belonging to the bot async with self.session.get( "https://discordapp.com/api/v6/channels/{}/webhooks".format( channel.id), headers=req_headers) as resp: if resp.status == 200: webhooks = await resp.json() for webhook in webhooks: if webhook["user"]["id"] == self.client.user.id: # This webhook belongs to us, we can use that, return it and save it return await self.save_channel_webhook( conn, channel, webhook["id"], webhook["token"]) elif resp.status == 403: self.logger.warning( "Did not have permission to fetch webhook list (server={}, channel={})" .format(channel.server.id, channel.id)) raise WebhookPermissionError() else: raise discord.HTTPException(resp, await resp.text()) # Then, try submitting a new one req_data = {"name": "PluralKit Proxy Webhook"} async with self.session.post( "https://discordapp.com/api/v6/channels/{}/webhooks".format( channel.id), json=req_data, headers=req_headers) as resp: if resp.status == 200: webhook = await resp.json() return await self.save_channel_webhook(conn, channel, webhook["id"], webhook["token"]) elif resp.status == 403: self.logger.warning( "Did not have permission to create webhook (server={}, channel={})" .format(channel.server.id, channel.id)) raise WebhookPermissionError() else: raise discord.HTTPException(resp, await resp.text())
async def test_add_emoji_role_pair_emoji_http_exception_400(mocker): """Adds a emoji/role pair to the role message creation, but there is an HTTPException 400.""" cog, mock_bot, _ = init_mocks() mock_message = tosurnament_mock.DEFAULT_MESSAGE_MOCK mock_not_found_response = mocker.Mock() mock_not_found_response.status = 400 mock_message.add_reaction = mocker.AsyncMock(side_effect=discord.HTTPException(mock_not_found_response, "")) await cog.add_emoji_role_pair(cog, tosurnament_mock.CtxMock(mock_bot, message=mock_message), "🛎️", None) cog.send_reply.assert_called_once_with(mocker.ANY, "emoji_not_found", "🛎️")
async def test_send_prompt_returns_none_if_channel_fetch_fails(self): """None should be returned if there's an HTTPException when fetching the channel.""" self.bot.get_channel.return_value = None self.bot.fetch_channel.side_effect = discord.HTTPException( mock.MagicMock(), "test error!") ret_val = await self.syncer._send_prompt() self.assertIsNone(ret_val)
async def test_add_emoji_role_pair_emoji_http_exception_other(mocker): """Adds a emoji/role pair to the role message creation, but there is an HTTPException other than 400.""" cog, mock_bot, _ = init_mocks() mock_message = tosurnament_mock.DEFAULT_MESSAGE_MOCK mock_not_found_response = mocker.Mock() mock_not_found_response.status = 500 mock_message.add_reaction = mocker.AsyncMock(side_effect=discord.HTTPException(mock_not_found_response, "")) with pytest.raises(discord.HTTPException): await cog.add_emoji_role_pair(cog, tosurnament_mock.CtxMock(mock_bot, message=mock_message), "🛎️", None)
async def create_video(self, text): with async_timeout.timeout(10): async with self.bot.session.post( url="http://talkobamato.me/synthesize.py", data={"input_text": text}) as resp: if resp.status >= 400: raise discord.HTTPException( resp, f"{resp.url} returned error code {resp.status}") url = resp.url key = url.query['speech_key'] link = f"http://talkobamato.me/synth/output/{key}/obama.mp4" await asyncio.sleep(len(text) // 5) with async_timeout.timeout(10): async with self.bot.session.get(link) as resp: if resp.status >= 400: raise discord.HTTPException( resp, f"{resp.url} returned error code {resp.status}") return link
async def test_download_file_fail(self): """If `to_file` fails on a non-404 error, function logs the exception & returns None.""" arbitrary_error = discord.HTTPException( MagicMock(aiohttp.ClientResponse), "Arbitrary API error") attachment = MockAttachment(to_file=AsyncMock( side_effect=arbitrary_error)) with self.assertLogs(logger=incidents.log, level=logging.ERROR): acquired_file = await incidents.download_file(attachment) self.assertIsNone(acquired_file)
async def test_on_reaction_add_to_message_exception_when_adding_role(mocker): """Adds the selected role from a role message, but an exception occurs.""" cog, mock_bot, reaction_for_role_message = init_mocks() reaction_for_role_message.message_id = tosurnament_mock.DEFAULT_MESSAGE_MOCK.id reaction_for_role_message.emojis = "🛎️" reaction_for_role_message.roles = str(tosurnament_mock.VERIFIED_ROLE_MOCK.id) mock_author = tosurnament_mock.DEFAULT_USER_MOCK mock_not_found_response = mocker.Mock() mock_not_found_response.status = 400 mock_author.add_roles = mocker.AsyncMock(side_effect=discord.HTTPException(mock_not_found_response, "")) await cog.on_reaction_add_to_message.__wrapped__(cog, tosurnament_mock.CtxMock(mock_bot, mock_author), "🛎️") mock_author.add_roles.assert_called_once_with(tosurnament_mock.VERIFIED_ROLE_MOCK)
def test_send_webhook_logs_when_sending_message_fails(self): """The `send_webhook` method should catch a `discord.HTTPException` and log accordingly.""" self.cog.webhook = helpers.MockAsyncWebhook() self.cog.webhook.send.side_effect = discord.HTTPException(response=MagicMock(), message="Something failed.") log = logging.getLogger('bot.cogs.duck_pond') with self.assertLogs(logger=log, level=logging.ERROR) as log_watcher: asyncio.run(self.cog.send_webhook()) self.assertEqual(len(log_watcher.records), 1) record = log_watcher.records[0] self.assertEqual(record.levelno, logging.ERROR)
def test_fetch_webhook_logs_when_unable_to_fetch_webhook(self): """The `fetch_webhook` method should log an exception when it fails to fetch the webhook.""" self.bot.fetch_webhook.side_effect = discord.HTTPException(response=MagicMock(), message="Not found.") self.cog.webhook_id = 1 log = logging.getLogger('bot.cogs.duck_pond') with self.assertLogs(logger=log, level=logging.ERROR) as log_watcher: asyncio.run(self.cog.fetch_webhook()) self.bot.wait_until_guild_available.assert_called_once() self.bot.fetch_webhook.assert_called_once_with(1) self.assertEqual(len(log_watcher.records), 1) record = log_watcher.records[0] self.assertEqual(record.levelno, logging.ERROR)
async def test_resolve_message_fetch_fails(self): """ Non-404 errors are handled, logged & None is returned. In contrast with a 404, this should make an error-level log. We assert that at least one such log was made - we do not make any assertions about the log's message. """ self.cog_instance.bot._connection._get_message = MagicMock( return_value=None) # Cache returns None arbitrary_error = discord.HTTPException( response=MagicMock(aiohttp.ClientResponse), message="Arbitrary error", ) fetch_message = AsyncMock(side_effect=arbitrary_error) self.cog_instance.bot.get_channel = MagicMock( return_value=MockTextChannel(fetch_message=fetch_message)) with self.assertLogs(logger=incidents.log, level=logging.ERROR): self.assertIsNone(await self.cog_instance.resolve_message(123))
async def get_from_cdn(url) -> bytes: """ A method that downloads an image from a url. Parameters: url (str): The url of the image. Returns: (bytes): A bytes object of the downloaded image. """ async with ClientSession() as session: async with session.get(url) as resp: if resp.status == 200: return await resp.read() elif resp.status == 404: raise discord.NotFound(resp, 'Not Found') elif resp.status == 403: raise discord.Forbidden(resp, 'Forbidden') else: raise discord.HTTPException(resp, 'Failed')
def error(*args, **kwargs): raise dpy.HTTPException(MockDpyObject(status=400, reason="No"), "no")
async def do_proxy_message(self, conn, member: db.ProxyMember, original_message: discord.Message, text: str, attachment_url: str, has_already_retried=False): hook_id, hook_token = await self.get_webhook_for_channel( conn, original_message.channel) form_data = aiohttp.FormData() form_data.add_field( "username", "{} {}".format(member.name, member.tag or "").strip()) if text: form_data.add_field("content", text) if attachment_url: attachment_resp = await self.session.get(attachment_url) form_data.add_field("file", attachment_resp.content, content_type=attachment_resp.content_type, filename=attachment_resp.url.name) if member.avatar_url: form_data.add_field("avatar_url", member.avatar_url) time_before = time.perf_counter() async with self.session.post( "https://discordapp.com/api/v6/webhooks/{}/{}?wait=true". format(hook_id, hook_token), data=form_data) as resp: if resp.status == 200: message = await resp.json() # Report webhook stats to Influx await self.stats.report_webhook( time.perf_counter() - time_before, True) await db.add_message(conn, message["id"], message["channel_id"], member.id, original_message.author.id, text or "") try: await self.client.delete_message(original_message) except discord.Forbidden: self.logger.warning( "Did not have permission to delete original message (server={}, channel={})" .format(original_message.server.id, original_message.channel.id)) raise DeletionPermissionError() except discord.NotFound: self.logger.warning( "Tried to delete message when proxying, but message was already gone (server={}, channel={})" .format(original_message.server.id, original_message.channel.id)) message_image = None if message["attachments"]: first_attachment = message["attachments"][0] if "width" in first_attachment and "height" in first_attachment: # Only log attachments that are actually images message_image = first_attachment["url"] await self.channel_logger.log_message_proxied( conn, server_id=original_message.server.id, channel_name=original_message.channel.name, channel_id=original_message.channel.id, sender_name=original_message.author.name, sender_disc=original_message.author.discriminator, member_name=member.name, member_hid=member.hid, member_avatar_url=member.avatar_url, system_name=member.system_name, system_hid=member.system_hid, message_text=text, message_image=message_image, message_timestamp=ciso8601.parse_datetime( message["timestamp"]), message_id=message["id"]) elif resp.status == 404 and not has_already_retried: # Report webhook stats to Influx await self.stats.report_webhook( time.perf_counter() - time_before, False) # Webhook doesn't exist. Delete it from the DB, create, and add a new one self.logger.warning( "Webhook registered in DB doesn't exist, deleting hook from DB, re-adding, and trying again (channel={}, hook={})" .format(original_message.channel.id, hook_id)) await db.delete_webhook(conn, original_message.channel.id) await self.create_and_add_channel_webhook( conn, original_message.channel) # Then try again all over, making sure to not retry again and go in a loop should it continually fail return await self.do_proxy_message(conn, member, original_message, text, attachment_url, has_already_retried=True) else: # Report webhook stats to Influx await self.stats.report_webhook( time.perf_counter() - time_before, False) raise discord.HTTPException(resp, await resp.text())
def disc_http_exception(): response = simple_class_factory(status=401, reason="") return discord.HTTPException(message="", response=response)