diff options
| -rw-r--r-- | bot/__main__.py | 8 | ||||
| -rw-r--r-- | bot/api.py | 40 | ||||
| -rw-r--r-- | bot/cogs/doc.py | 153 | ||||
| -rw-r--r-- | bot/cogs/events.py | 16 | ||||
| -rw-r--r-- | bot/cogs/off_topic_names.py | 72 | ||||
| -rw-r--r-- | bot/cogs/sync.py | 86 | ||||
| -rw-r--r-- | bot/cogs/tags.py | 249 | 
7 files changed, 242 insertions, 382 deletions
diff --git a/bot/__main__.py b/bot/__main__.py index 3c40a3243..c598fd921 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,3 +1,4 @@ +import asyncio  import logging  import socket @@ -5,6 +6,7 @@ from aiohttp import AsyncResolver, ClientSession, TCPConnector  from discord import Game  from discord.ext.commands import Bot, when_mentioned_or +from bot.api import APIClient  from bot.constants import Bot as BotConfig, DEBUG_MODE  from bot.utils.service_discovery import wait_for_rmq @@ -27,6 +29,7 @@ bot.http_session = ClientSession(          family=socket.AF_INET,      )  ) +bot.api_client = APIClient(loop=asyncio.get_event_loop())  log.info("Waiting for RabbitMQ...")  has_rmq = wait_for_rmq() @@ -57,9 +60,13 @@ if not DEBUG_MODE:      bot.load_extension("bot.cogs.verification")  # Feature cogs +<<<<<<< HEAD +=======  bot.load_extension("bot.cogs.alias")  bot.load_extension("bot.cogs.deployment") +>>>>>>> master  bot.load_extension("bot.cogs.defcon") +bot.load_extension("bot.cogs.deployment")  bot.load_extension("bot.cogs.eval")  bot.load_extension("bot.cogs.fun")  bot.load_extension("bot.cogs.superstarify") @@ -71,6 +78,7 @@ bot.load_extension("bot.cogs.reminders")  bot.load_extension("bot.cogs.site")  bot.load_extension("bot.cogs.snakes")  bot.load_extension("bot.cogs.snekbox") +bot.load_extension("bot.cogs.sync")  bot.load_extension("bot.cogs.tags")  bot.load_extension("bot.cogs.token_remover")  bot.load_extension("bot.cogs.utils") diff --git a/bot/api.py b/bot/api.py new file mode 100644 index 000000000..f8757d3d0 --- /dev/null +++ b/bot/api.py @@ -0,0 +1,40 @@ +from urllib.parse import quote as quote_url + +import aiohttp + +from .constants import Keys, URLs + + +class APIClient: +    def __init__(self, **kwargs): +        auth_headers = { +            'Authorization': f"Token {Keys.site_api}" +        } + +        if 'headers' in kwargs: +            kwargs['headers'].update(auth_headers) +        else: +            kwargs['headers'] = auth_headers + +        self.session = aiohttp.ClientSession( +            **kwargs, +            raise_for_status=True +        ) + +    @staticmethod +    def _url_for(endpoint: str): +        return f"{URLs.site_schema}{URLs.site_api}/{quote_url(endpoint)}" + +    async def get(self, endpoint: str, *args, **kwargs): +        async with self.session.get(self._url_for(endpoint), *args, **kwargs) as resp: +            return await resp.json() + +    async def post(self, endpoint: str, *args, **kwargs): +        async with self.session.post(self._url_for(endpoint), *args, **kwargs) as resp: +            return await resp.json() + +    async def delete(self, endpoint: str, *args, **kwargs): +        async with self.session.delete(self._url_for(endpoint), *args, **kwargs) as resp: +            if resp.status == 204: +                return None +            return await resp.json() diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index 2b310f11c..860ec7f62 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -1,11 +1,10 @@  import asyncio  import functools  import logging -import random  import re  import textwrap  from collections import OrderedDict -from typing import Dict, List, Optional, Tuple +from typing import Optional, Tuple  import discord  from bs4 import BeautifulSoup @@ -14,7 +13,7 @@ from markdownify import MarkdownConverter  from requests import ConnectionError  from sphinx.ext import intersphinx -from bot.constants import ERROR_REPLIES, Keys, Roles, URLs +from bot.constants import Keys, Roles  from bot.converters import ValidPythonIdentifier, ValidURL  from bot.decorators import with_role  from bot.pagination import LinePaginator @@ -126,7 +125,6 @@ class Doc:          self.base_urls = {}          self.bot = bot          self.inventories = {} -        self.headers = {"X-API-KEY": Keys.site_api}      async def on_ready(self):          await self.refresh_inventory() @@ -179,7 +177,7 @@ class Doc:          coros = [              self.update_single(                  package["package"], package["base_url"], package["inventory_url"], config -            ) for package in await self.get_all_packages() +            ) for package in await self.bot.api_client.get('bot/documentation-links')          ]          await asyncio.gather(*coros) @@ -267,95 +265,6 @@ class Doc:              description=f"```py\n{signature}```{description}"          ) -    async def get_all_packages(self) -> List[Dict[str, str]]: -        """ -        Performs HTTP GET to get all packages from the website. - -        :return: -        A list of packages, in the following format: -        [ -            { -                "package": "example-package", -                "base_url": "https://example.readthedocs.io", -                "inventory_url": "https://example.readthedocs.io/objects.inv" -            }, -            ... -        ] -        `package` specifies the package name, for example 'aiohttp'. -        `base_url` specifies the documentation root URL, used to build absolute links. -        `inventory_url` specifies the location of the Intersphinx inventory. -        """ - -        async with self.bot.http_session.get(URLs.site_docs_api, headers=self.headers) as resp: -            return await resp.json() - -    async def get_package(self, package_name: str) -> Optional[Dict[str, str]]: -        """ -        Performs HTTP GET to get the specified package from the documentation database. - -        :param package_name: The package name for which information should be returned. -        :return: -        Either a dictionary with information in the following format: -        { -            "package": "example-package", -            "base_url": "https://example.readthedocs.io", -            "inventory_url": "https://example.readthedocs.io/objects.inv" -        } -        or `None` if the site didn't returned no results for the given name. -        """ - -        params = {"package": package_name} - -        async with self.bot.http_session.get(URLs.site_docs_api, -                                             headers=self.headers, -                                             params=params) as resp: -            package_data = await resp.json() -            if not package_data: -                return None -            return package_data[0] - -    async def set_package(self, name: str, base_url: str, inventory_url: str) -> Dict[str, bool]: -        """ -        Performs HTTP POST to add a new package to the website's documentation database. - -        :param name: The name of the package, for example `aiohttp`. -        :param base_url: The documentation root URL, used to build absolute links. -        :param inventory_url: The absolute URl to the intersphinx inventory of the package. - -        :return: The JSON response of the server, which is always: -        { -            "success": True -        } -        """ - -        package_json = { -            'package': name, -            'base_url': base_url, -            'inventory_url': inventory_url -        } - -        async with self.bot.http_session.post(URLs.site_docs_api, -                                              headers=self.headers, -                                              json=package_json) as resp: -            return await resp.json() - -    async def delete_package(self, name: str) -> bool: -        """ -        Performs HTTP DELETE to delete the specified package from the documentation database. - -        :param name: The package to delete. - -        :return: `True` if successful, `False` if the package is unknown. -        """ - -        package_json = {'package': name} - -        async with self.bot.http_session.delete(URLs.site_docs_api, -                                                headers=self.headers, -                                                json=package_json) as resp: -            changes = await resp.json() -            return changes["deleted"] == 1  # Did the package delete successfully? -      @commands.group(name='docs', aliases=('doc', 'd'), invoke_without_command=True)      async def docs_group(self, ctx, symbol: commands.clean_content = None):          """Lookup documentation for Python symbols.""" @@ -386,7 +295,12 @@ class Doc:              )              lines = sorted(f"• [`{name}`]({url})" for name, url in self.base_urls.items()) -            await LinePaginator.paginate(lines, ctx, inventory_embed, max_size=400, empty=False) +            if self.base_urls: +                await LinePaginator.paginate(lines, ctx, inventory_embed, max_size=400, empty=False) + +            else: +                inventory_embed.description = "Hmmm, seems like there's nothing here yet." +                await ctx.send(embed=inventory_embed)          else:              # Fetching documentation for a symbol (at least for the first time, since @@ -427,7 +341,13 @@ class Doc:                      https://discordpy.readthedocs.io/en/rewrite/objects.inv          """ -        await self.set_package(package_name, base_url, inventory_url) +        body = { +            'package': package_name, +            'base_url': base_url, +            'inventory_url': inventory_url +        } +        await self.bot.api_client.post('bot/documentation-links', json=body) +          log.info(              f"User @{ctx.author.name}#{ctx.author.discriminator} ({ctx.author.id}) "              "added a new documentation package:\n" @@ -455,42 +375,13 @@ class Doc:              !docs delete aiohttp          """ -        success = await self.delete_package(package_name) -        if success: +        await self.bot.api_client.delete(f'bot/documentation-links/{package_name}') -            async with ctx.typing(): -                # Rebuild the inventory to ensure that everything -                # that was from this package is properly deleted. -                await self.refresh_inventory() -            await ctx.send(f"Successfully deleted `{package_name}` and refreshed inventory.") - -        else: -            await ctx.send( -                f"Can't find any package named `{package_name}` in the database. " -                "View all known packages by using `docs.get()`." -            ) - -    @get_command.error -    @delete_command.error -    @set_command.error -    async def general_command_error(self, ctx, error: commands.CommandError): -        """ -        Handle the `BadArgument` error caused by -        the commands when argument validation fails. - -        :param ctx: Discord message context of the message creating the error -        :param error: The error raised, usually `BadArgument` -        """ - -        if isinstance(error, commands.BadArgument): -            embed = discord.Embed( -                title=random.choice(ERROR_REPLIES), -                description=f"Error: {error}", -                colour=discord.Colour.red() -            ) -            await ctx.send(embed=embed) -        else: -            log.exception(f"Unhandled error: {error}") +        async with ctx.typing(): +            # Rebuild the inventory to ensure that everything +            # that was from this package is properly deleted. +            await self.refresh_inventory() +        await ctx.send(f"Successfully deleted `{package_name}` and refreshed inventory.")  def setup(bot): diff --git a/bot/cogs/events.py b/bot/cogs/events.py index edfc6e579..160791fb0 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -1,5 +1,6 @@  import logging +from aiohttp import ClientResponseError  from discord import Colour, Embed, Member, Object  from discord.ext.commands import (      BadArgument, Bot, BotMissingPermissions, @@ -140,10 +141,17 @@ class Events:                  f"Here's what I'm missing: **{e.missing_perms}**"              )          elif isinstance(e, CommandInvokeError): -            await ctx.send( -                f"Sorry, an unexpected error occurred. Please let us know!\n\n```{e}```" -            ) -            raise e.original +            if isinstance(e.original, ClientResponseError): +                if e.original.code == 404: +                    await ctx.send("There does not seem to be anything matching your query.") +                else: +                    await ctx.send("BEEP BEEP UNKNOWN API ERROR!=?!??!?!?!?") + +            else: +                await ctx.send( +                    f"Sorry, an unexpected error occurred. Please let us know!\n\n```{e}```" +                ) +                raise e.original          raise e      async def on_ready(self): diff --git a/bot/cogs/off_topic_names.py b/bot/cogs/off_topic_names.py index 25b8a48b8..b22926664 100644 --- a/bot/cogs/off_topic_names.py +++ b/bot/cogs/off_topic_names.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta  from discord import Colour, Embed  from discord.ext.commands import BadArgument, Bot, Context, Converter, group -from bot.constants import Channels, Keys, Roles, URLs +from bot.constants import Channels, Keys, Roles  from bot.decorators import with_role  from bot.pagination import LinePaginator @@ -44,7 +44,7 @@ async def update_names(bot: Bot, headers: dict):      Args:          bot (Bot):              The running bot instance, used for fetching data from the -            website via the bot's `http_session`. +            website via the bot's `api_client`.      """      while True: @@ -53,11 +53,9 @@ async def update_names(bot: Bot, headers: dict):          seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds          await asyncio.sleep(seconds_to_sleep) -        response = await bot.http_session.get( -            f'{URLs.site_off_topic_names_api}?random_items=3', -            headers=headers +        channel_0_name, channel_1_name, channel_2_name = await bot.api_client.get( +            'bot/off-topic-channel-names', params={'random_items': 3}          ) -        channel_0_name, channel_1_name, channel_2_name = await response.json()          channel_0, channel_1, channel_2 = (bot.get_channel(channel_id) for channel_id in CHANNELS)          await channel_0.edit(name=f'ot0-{channel_0_name}') @@ -98,49 +96,24 @@ class OffTopicNames:      async def add_command(self, ctx, name: OffTopicName):          """Adds a new off-topic name to the rotation.""" -        result = await self.bot.http_session.post( -            URLs.site_off_topic_names_api, -            headers=self.headers, -            params={'name': name} +        await self.bot.api_client.post(f'bot/off-topic-channel-names', params={'name': name}) +        log.info( +            f"{ctx.author.name}#{ctx.author.discriminator}" +            f" added the off-topic channel name '{name}"          ) - -        response = await result.json() - -        if result.status == 200: -            log.info( -                f"{ctx.author.name}#{ctx.author.discriminator}" -                f" added the off-topic channel name '{name}" -            ) -            await ctx.send(":ok_hand:") -        else: -            error_reason = response.get('message', "No reason provided.") -            await ctx.send(f":warning: got non-200 from the API: {error_reason}") +        await ctx.send(":ok_hand:")      @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd'))      @with_role(Roles.owner, Roles.admin, Roles.moderator)      async def delete_command(self, ctx, name: OffTopicName):          """Removes a off-topic name from the rotation.""" -        result = await self.bot.http_session.delete( -            URLs.site_off_topic_names_api, -            headers=self.headers, -            params={'name': name} +        await self.bot.api_client.delete(f'bot/off-topic-channel-names/{name}') +        log.info( +            f"{ctx.author.name}#{ctx.author.discriminator}" +            f" deleted the off-topic channel name '{name}"          ) - -        response = await result.json() - -        if result.status == 200: -            if response['deleted'] == 0: -                await ctx.send(f":warning: No name matching `{name}` was found in the database.") -            else: -                log.info( -                    f"{ctx.author.name}#{ctx.author.discriminator}" -                    f" deleted the off-topic channel name '{name}" -                ) -                await ctx.send(":ok_hand:") -        else: -            error_reason = response.get('message', "No reason provided.") -            await ctx.send(f":warning: got non-200 from the API: {error_reason}") +        await ctx.send(":ok_hand:")      @otname_group.command(name='list', aliases=('l',))      @with_role(Roles.owner, Roles.admin, Roles.moderator) @@ -150,18 +123,17 @@ class OffTopicNames:          Restricted to Moderator and above to not spoil the surprise.          """ -        result = await self.bot.http_session.get( -            URLs.site_off_topic_names_api, -            headers=self.headers -        ) -        response = await result.json() -        lines = sorted(f"• {name}" for name in response) - +        result = await self.bot.api_client.get('bot/off-topic-channel-names') +        lines = sorted(f"• {name}" for name in result)          embed = Embed( -            title=f"Known off-topic names (`{len(response)}` total)", +            title=f"Known off-topic names (`{len(result)}` total)",              colour=Colour.blue()          ) -        await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False) +        if result: +            await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False) +        else: +            embed.description = "Hmmm, seems like there's nothing here yet." +            await ctx.send(embed=embed)  def setup(bot: Bot): diff --git a/bot/cogs/sync.py b/bot/cogs/sync.py new file mode 100644 index 000000000..cccaa7d28 --- /dev/null +++ b/bot/cogs/sync.py @@ -0,0 +1,86 @@ +import logging +from collections import namedtuple +from typing import Callable, Iterable + +from discord import Guild, Role +from discord.ext.commands import Bot + + +log = logging.getLogger(__name__) +Role = namedtuple('Role', ('id', 'name', 'colour', 'permissions')) + + +async def sync_roles(bot: Bot, guild: Guild): +    """ +    Synchronize roles found on the given `guild` with the ones on the API. +    """ + +    def convert_role(role: Role): +        return { +            'id': role.id, +            'name': role.name, +            'colour': role.colour, +            'permissions': role.permissions +        } + +    roles = await bot.api_client.get('bot/roles') +    site_roles = { +        Role(**role_dict) +        for role_dict in roles +    } +    server_roles = { +        Role( +            id=role.id, name=role.name, +            colour=role.colour.value, permissions=role.permissions.value +        ) +        for role in guild.roles +    } +    roles_to_update = server_roles - site_roles + +    for role in roles_to_update: +        log.info(f"Updating role `{role.name}` on the site.") +        await bot.api_client.post( +            'bot/roles', +            json={ +                'id': role.id, +                'name': role.name, +                'colour': role.colour, +                'permissions': role.permissions +            } +        ) + + +async def sync_members(bot: Bot, guild: Guild): +    """ +    Synchronize members found on the given `guild` with the ones on the API. +    """ + +    current_members = await bot.api_client.get('bot/members') + + +class Sync: +    """Captures relevant events and sends them to the site.""" + +    # The server to synchronize events on. +    # Note that setting this wrongly will result in things getting deleted +    # that possibly shouldn't be. +    SYNC_SERVER_ID = 267624335836053506 + +    # An iterable of callables that are called when the bot is ready. +    ON_READY_SYNCERS: Iterable[Callable[[Bot, Guild], None]] = ( +        sync_roles, +    ) + +    def __init__(self, bot): +        self.bot = bot + +    async def on_ready(self): +        guild = self.bot.get_guild(self.SYNC_SERVER_ID) +        if guild is not None: +            for syncer in self.ON_READY_SYNCERS: +                await syncer(self.bot, guild) + + +def setup(bot): +    bot.add_cog(Sync(bot)) +    log.info("Cog loaded: Sync") diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index b128b6de1..5a0198db8 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -1,7 +1,5 @@  import logging -import random  import time -from typing import Optional  from discord import Colour, Embed  from discord.ext.commands import ( @@ -9,9 +7,7 @@ from discord.ext.commands import (      Context, group  ) -from bot.constants import ( -    Channels, Cooldowns, ERROR_REPLIES, Keys, Roles, URLs -) +from bot.constants import Channels, Cooldowns, Keys, Roles  from bot.converters import TagContentConverter, TagNameConverter, ValidURL  from bot.decorators import with_role  from bot.pagination import LinePaginator @@ -34,72 +30,7 @@ class Tags:      def __init__(self, bot: Bot):          self.bot = bot          self.tag_cooldowns = {} -        self.headers = {"X-API-KEY": Keys.site_api} - -    async def get_tag_data(self, tag_name=None) -> dict: -        """ -        Retrieve the tag_data from our API - -        :param tag_name: the tag to retrieve -        :return: -        if tag_name was provided, returns a dict with tag data. -        if not, returns a list of dicts with all tag data. - -        """ -        params = {} - -        if tag_name: -            params["tag_name"] = tag_name - -        response = await self.bot.http_session.get(URLs.site_tags_api, headers=self.headers, params=params) -        tag_data = await response.json() - -        return tag_data - -    async def post_tag_data(self, tag_name: str, tag_content: str, image_url: Optional[str]) -> dict: -        """ -        Send some tag_data to our API to have it saved in the database. - -        :param tag_name: The name of the tag to create or edit. -        :param tag_content: The content of the tag. -        :param image_url: The image URL of the tag, can be `None`. -        :return: json response from the API in the following format: -        { -            'success': bool -        } -        """ - -        params = { -            'tag_name': tag_name, -            'tag_content': tag_content, -            'image_url': image_url -        } - -        response = await self.bot.http_session.post(URLs.site_tags_api, headers=self.headers, json=params) -        tag_data = await response.json() - -        return tag_data - -    async def delete_tag_data(self, tag_name: str) -> dict: -        """ -        Delete a tag using our API. - -        :param tag_name: The name of the tag to delete. -        :return: json response from the API in the following format: -        { -            'success': bool -        } -        """ - -        params = {} - -        if tag_name: -            params['tag_name'] = tag_name - -        response = await self.bot.http_session.delete(URLs.site_tags_api, headers=self.headers, json=params) -        tag_data = await response.json() - -        return tag_data +        self.headers = {"Authorization": f"Token {Keys.site_api}"}      @group(name='tags', aliases=('tag', 't'), hidden=True, invoke_without_command=True)      async def tags_group(self, ctx: Context, *, tag_name: TagNameConverter = None): @@ -147,69 +78,32 @@ class Tags:                          f"Cooldown ends in {time_left:.1f} seconds.")              return -        tags = [] - -        embed = Embed() -        embed.colour = Colour.red() -        tag_data = await self.get_tag_data(tag_name) - -        # If we found something, prepare that data -        if tag_data: -            embed.colour = Colour.blurple() - -            if tag_name: -                log.debug(f"{ctx.author} requested the tag '{tag_name}'") -                embed.title = tag_name - -                if ctx.channel.id not in TEST_CHANNELS: -                    self.tag_cooldowns[tag_name] = { -                        "time": time.time(), -                        "channel": ctx.channel.id -                    } - -            else: -                embed.title = "**Current tags**" - -            if isinstance(tag_data, list): -                log.debug(f"{ctx.author} requested a list of all tags") -                tags = [f"**»**   {tag['tag_name']}" for tag in tag_data] -                tags = sorted(tags) - -            else: -                embed.description = tag_data['tag_content'] -                if tag_data['image_url'] is not None: -                    embed.set_image(url=tag_data['image_url']) +        if tag_name is not None: +            tag = await self.bot.api_client.get(f'bot/tags/{tag_name}') +            if ctx.channel.id not in TEST_CHANNELS: +                self.tag_cooldowns[tag_name] = { +                    "time": time.time(), +                    "channel": ctx.channel.id +                } +            await ctx.send(embed=Embed.from_data(tag['embed'])) -        # If its invoked from error handler just ignore it. -        elif hasattr(ctx, "invoked_from_error_handler"): -            return -        # If not, prepare an error message.          else: -            embed.colour = Colour.red() - -            if isinstance(tag_data, dict): -                log.warning(f"{ctx.author} requested the tag '{tag_name}', but it could not be found.") -                embed.description = f"**{tag_name}** is an unknown tag name. Please check the spelling and try again." +            tags = await self.bot.api_client.get('bot/tags') +            if not tags: +                await ctx.send(embed=Embed( +                    description="**There are no tags in the database!**", +                    colour=Colour.red() +                ))              else: -                log.warning(f"{ctx.author} requested a list of all tags, but the tags database was empty!") -                embed.description = "**There are no tags in the database!**" - -            if tag_name: -                embed.set_footer(text="To show a list of all tags, use !tags.") -                embed.title = "Tag not found." - -        # Paginate if this is a list of all tags -        if tags: -            log.debug(f"Returning a paginated list of all tags.") -            return await LinePaginator.paginate( -                (lines for lines in tags), -                ctx, embed, -                footer_text="To show a tag, type !tags <tagname>.", -                empty=False, -                max_lines=15 -            ) - -        return await ctx.send(embed=embed) +                embed = Embed(title="**Current tags**") +                await LinePaginator.paginate( +                    sorted(f"**»**   {tag['title']}" for tag in tags), +                    ctx, +                    embed, +                    footer_text="To show a tag, type !tags <tagname>.", +                    empty=False, +                    max_lines=15 +                )      @tags_group.command(name='set', aliases=('add', 'edit', 's'))      @with_role(Roles.admin, Roles.owner, Roles.moderator) @@ -217,44 +111,36 @@ class Tags:          self,          ctx: Context,          tag_name: TagNameConverter, +        *,          tag_content: TagContentConverter, -        image_url: ValidURL = None      ):          """ -        Create a new tag or edit an existing one. +        Create a new tag or update an existing one.          :param ctx: discord message context          :param tag_name: The name of the tag to create or edit.          :param tag_content: The content of the tag. -        :param image_url: An optional image for the tag.          """ -        tag_name = tag_name.lower().strip() -        tag_content = tag_content.strip() - -        embed = Embed() -        embed.colour = Colour.red() -        tag_data = await self.post_tag_data(tag_name, tag_content, image_url) - -        if tag_data.get("success"): -            log.debug(f"{ctx.author} successfully added the following tag to our database: \n" -                      f"tag_name: {tag_name}\n" -                      f"tag_content: '{tag_content}'\n" -                      f"image_url: '{image_url}'") -            embed.colour = Colour.blurple() -            embed.title = "Tag successfully added" -            embed.description = f"**{tag_name}** added to tag database." -        else: -            log.error("There was an unexpected database error when trying to add the following tag: \n" -                      f"tag_name: {tag_name}\n" -                      f"tag_content: '{tag_content}'\n" -                      f"image_url: '{image_url}'\n" -                      f"response: {tag_data}") -            embed.title = "Database error" -            embed.description = ("There was a problem adding the data to the tags database. " -                                 "Please try again. If the problem persists, see the error logs.") +        body = { +            'title': tag_name.lower().strip(), +            'embed': { +                'title': tag_name, +                'description': tag_content +            } +        } -        return await ctx.send(embed=embed) +        await self.bot.api_client.post('bot/tags', json=body) + +        log.debug(f"{ctx.author} successfully added the following tag to our database: \n" +                  f"tag_name: {tag_name}\n" +                  f"tag_content: '{tag_content}'\n") + +        await ctx.send(embed=Embed( +            title="Tag successfully added", +            description=f"**{tag_name}** added to tag database.", +            colour=Colour.blurple() +        ))      @tags_group.command(name='delete', aliases=('remove', 'rm', 'd'))      @with_role(Roles.admin, Roles.owner) @@ -266,45 +152,14 @@ class Tags:          :param tag_name: The name of the tag to delete.          """ -        tag_name = tag_name.lower().strip() -        embed = Embed() -        embed.colour = Colour.red() -        tag_data = await self.delete_tag_data(tag_name) - -        if tag_data.get("success") is True: -            log.debug(f"{ctx.author} successfully deleted the tag called '{tag_name}'") -            embed.colour = Colour.blurple() -            embed.title = tag_name -            embed.description = f"Tag successfully removed: {tag_name}." +        await self.bot.api_client.delete(f'bot/tags/{tag_name}') -        elif tag_data.get("success") is False: -            log.debug(f"{ctx.author} tried to delete a tag called '{tag_name}', but the tag does not exist.") -            embed.colour = Colour.red() -            embed.title = tag_name -            embed.description = "Tag doesn't appear to exist." - -        else: -            log.error("There was an unexpected database error when trying to delete the following tag: \n" -                      f"tag_name: {tag_name}\n" -                      f"response: {tag_data}") -            embed.title = "Database error" -            embed.description = ("There was an unexpected error with deleting the data from the tags database. " -                                 "Please try again. If the problem persists, see the error logs.") - -        return await ctx.send(embed=embed) - -    @get_command.error -    @set_command.error -    @delete_command.error -    async def command_error(self, ctx, error): -        if isinstance(error, BadArgument): -            embed = Embed() -            embed.colour = Colour.red() -            embed.description = str(error) -            embed.title = random.choice(ERROR_REPLIES) -            await ctx.send(embed=embed) -        else: -            log.error(f"Unhandled tag command error: {error} ({error.original})") +        log.debug(f"{ctx.author} successfully deleted the tag called '{tag_name}'") +        await ctx.send(embed=Embed( +            title=tag_name, +            description=f"Tag successfully removed: {tag_name}.", +            colour=Colour.blurple() +        ))  def setup(bot):  |