aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Leon Sandøy <[email protected]>2018-03-02 23:32:10 +0100
committerGravatar GitHub <[email protected]>2018-03-02 23:32:10 +0100
commit909b1edf8d6b7d6c72731280b9db1e781d31fad3 (patch)
treefc6f0c1ba784770d17e8adbfe4456e6dda4a2e6e
parentadds vagrant file (#14) (diff)
Tag feature (#21)
* [WIP] get_tag() * [WIP] get_tag() * [WIP] get_tag() * [WIP] - tags * [WIP] renamed to docs, tested and verified get and set commands, still need to add cooldowns and delete. * [WIP] get_tag() * [WIP] get_tag() * [WIP] get_tag() * [WIP] - tags * [WIP] renamed to docs, tested and verified get and set commands, still need to add cooldowns and delete. * removed the tag.py file * Refactoring docs to tags * [WIP] polish for the tags bot feature * Added pagination, lots of output polish, input sanitization * seperated out the the API interaction so it can be accessed from other cogs. Added a ton of polish, better documentation, and a help function if you just type bot.tags(). The core functionality should be done now, just missing the cooldown decorator. * Cooldown system per command per channel. Some annotations. Ready for PR! * oops, had left local url in. * removing some out of scope stuff * Using API key from constants * Replacing Union[str, None] with Optional[str] * adding some namespaceless commands * inverting some conditionals with returns in them. * removing some mysterious commas, and adding better error messages if the database operation fails * adhering to the standard set in clickup cog to prevent shadowing. * alphabetized cog load block, moved headers into a class attribute, and fixed punctuation consistency issues.
Diffstat (limited to '')
-rw-r--r--bot/__main__.py1
-rw-r--r--bot/cogs/tags.py263
-rw-r--r--bot/constants.py5
3 files changed, 268 insertions, 1 deletions
diff --git a/bot/__main__.py b/bot/__main__.py
index 895603689..88991ac53 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -34,6 +34,7 @@ bot.load_extension("bot.cogs.clickup")
bot.load_extension("bot.cogs.deployment")
bot.load_extension("bot.cogs.eval")
bot.load_extension("bot.cogs.fun")
+bot.load_extension("bot.cogs.tags")
bot.load_extension("bot.cogs.verification")
bot.run(os.environ.get("BOT_TOKEN"))
diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py
new file mode 100644
index 000000000..2fe5f72ef
--- /dev/null
+++ b/bot/cogs/tags.py
@@ -0,0 +1,263 @@
+import time
+from typing import Optional
+
+from aiohttp import ClientSession
+
+from discord import Colour, Embed
+from discord.ext.commands import AutoShardedBot, Context, command
+
+from bot.constants import ADMIN_ROLE, MODERATOR_ROLE, OWNER_ROLE
+from bot.constants import SITE_API_KEY, SITE_API_TAGS_URL, TAG_COOLDOWN
+from bot.decorators import with_role
+from bot.utils import paginate
+
+
+class Tags:
+ """
+ Save new tags and fetch existing tags.
+ """
+
+ def __init__(self, bot: AutoShardedBot):
+ self.bot = bot
+ self.tag_cooldowns = {}
+ self.headers = {"X-API-KEY": SITE_API_KEY}
+
+ async def get_tag_data(self, tag_name: Optional[str] = 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
+
+ async with ClientSession() as session:
+ response = await session.get(SITE_API_TAGS_URL, 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) -> 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.
+ :return: json response from the API in the following format:
+ {
+ 'success': bool
+ }
+ """
+
+ params = {
+ 'tag_name': tag_name,
+ 'tag_content': tag_content
+ }
+
+ async with ClientSession() as session:
+ response = await session.post(SITE_API_TAGS_URL, 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
+
+ async with ClientSession() as session:
+ response = await session.delete(SITE_API_TAGS_URL, headers=self.headers, json=params)
+ tag_data = await response.json()
+
+ return tag_data
+
+ @command(name="tags()", aliases=["tags"], hidden=True)
+ async def info_command(self, ctx: Context):
+ """
+ Show available methods for this class.
+
+ :param ctx: Discord message context
+ """
+
+ return await ctx.invoke(self.bot.get_command("help"), "Tags")
+
+ @command(name="tags.get()", aliases=["tags.get", "tags.show()", "tags.show", "get_tag"])
+ async def get_command(self, ctx: Context, tag_name: Optional[str] = None):
+ """
+ Get a list of all tags or a specified tag.
+
+ :param ctx: Discord message context
+ :param tag_name:
+ If provided, this function shows data for that specific tag.
+ If not provided, this function shows the caller a list of all tags.
+ """
+
+ def _command_on_cooldown(tag_name: str) -> bool:
+ """
+ Check if the command is currently on cooldown.
+ The cooldown duration is set in constants.py.
+
+ This works on a per-tag, per-channel basis.
+ :param tag_name: The name of the command to check.
+ :return: True if the command is cooling down. Otherwise False.
+ """
+
+ now = time.time()
+
+ cooldown_conditions = (
+ tag_name
+ and tag_name in self.tag_cooldowns
+ and (now - self.tag_cooldowns[tag_name]["time"]) < TAG_COOLDOWN
+ and self.tag_cooldowns[tag_name]["channel"] == ctx.channel.id
+ )
+
+ if cooldown_conditions:
+ return True
+ return False
+
+ if _command_on_cooldown(tag_name):
+ time_left = TAG_COOLDOWN - (time.time() - self.tag_cooldowns[tag_name]["time"])
+ print(f"That command is currently on cooldown. Try again in {time_left:.1f} seconds.")
+ return
+
+ embed = Embed()
+ tags = []
+
+ 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:
+ embed.title = tag_name
+ self.tag_cooldowns[tag_name] = {
+ "time": time.time(),
+ "channel": ctx.channel.id
+ }
+
+ else:
+ embed.title = "**Current tags**"
+
+ if isinstance(tag_data, list):
+ tags = [f"**»** {tag['tag_name']}" for tag in tag_data]
+ tags = sorted(tags)
+
+ else:
+ embed.description = tag_data['tag_content']
+
+ # If not, prepare an error message.
+ else:
+ embed.colour = Colour.red()
+ embed.description = "**There are no tags in the database!**"
+
+ if isinstance(tag_data, dict):
+ embed.description = f"Unknown tag: **{tag_name}**"
+
+ if tag_name:
+ embed.set_footer(text="To show a list of all tags, use bot.tags.get().")
+ embed.title = "Tag not found"
+
+ # Paginate if this is a list of all tags
+ if tags:
+ return await paginate(
+ (lines for lines in tags),
+ ctx, embed,
+ footer_text="To show a tag, type bot.tags.get <tagname>.",
+ empty=False,
+ max_size=200
+ )
+
+ return await ctx.send(embed=embed)
+
+ @with_role(ADMIN_ROLE, OWNER_ROLE, MODERATOR_ROLE)
+ @command(name="tags.set()", aliases=["tags.set", "tags.add", "tags.add()", "tags.edit", "tags.edit()", "add_tag"])
+ async def set_command(self, ctx: Context, tag_name: str, tag_content: str):
+ """
+ Create a new tag or edit 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.
+ """
+
+ embed = Embed()
+ embed.colour = Colour.red()
+
+ if "\n" in tag_name:
+ embed.title = "Please don't do that"
+ embed.description = "Don't be ridiculous. Newlines are obviously not allowed in the tag name."
+
+ else:
+ if not (tag_name and tag_content):
+ embed.title = "Missing parameters"
+ embed.description = "The tag needs both a name and some content."
+ return await ctx.send(embed=embed)
+
+ tag_name = tag_name.lower()
+ tag_data = await self.post_tag_data(tag_name, tag_content)
+
+ if tag_data.get("success"):
+ embed.colour = Colour.blurple()
+ embed.title = "Tag successfully added"
+ embed.description = f"**{tag_name}** added to tag database."
+ else:
+ embed.title = "Database error",
+ embed.description = ("There was a problem adding the data to the tags database. "
+ "Please try again. If the problem persists, check the API logs.")
+
+ return await ctx.send(embed=embed)
+
+ @with_role(ADMIN_ROLE, OWNER_ROLE)
+ @command(name="tags.delete()", aliases=["tags.delete", "tags.remove", "tags.remove()", "remove_tag"])
+ async def delete_command(self, ctx: Context, tag_name: str):
+ """
+ Remove a tag from the database.
+
+ :param ctx: discord message context
+ :param tag_name: The name of the tag to delete.
+ """
+
+ embed = Embed()
+ embed.colour = Colour.red()
+
+ if not tag_name:
+ embed.title = "Missing parameters"
+ embed.description = "This method requires a `tag_name` parameter."
+ return await ctx.send(embed=embed)
+
+ tag_data = await self.delete_tag_data(tag_name)
+
+ if tag_data.get("success"):
+ embed.colour = Colour.blurple()
+ embed.title = tag_name
+ embed.description = f"Tag successfully removed: {tag_name}."
+
+ else:
+ embed.title = "Database error",
+ embed.description = ("There was a problem deleting the data from the tags database. "
+ "Please try again. If the problem persists, check the API logs.")
+
+ return await ctx.send(embed=embed)
+
+
+def setup(bot):
+ bot.add_cog(Tags(bot))
+ print("Cog loaded: Tags")
diff --git a/bot/constants.py b/bot/constants.py
index cf42996c1..bf6f1ce2e 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -22,6 +22,8 @@ CLICKUP_KEY = os.environ.get("CLICKUP_KEY")
CLICKUP_SPACE = 757069
CLICKUP_TEAM = 754996
+TAG_COOLDOWN = 60 # Per channel, per tag
+
GITHUB_URL_BOT = "https://github.com/discord-python/bot"
BOT_AVATAR_URL = "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle.png"
@@ -32,7 +34,8 @@ DEPLOY_BOT_KEY = os.environ.get("DEPLOY_BOT_KEY")
DEPLOY_SITE_KEY = os.environ.get("DEPLOY_SITE_KEY")
SITE_API_KEY = os.environ.get("BOT_API_KEY")
-SITE_API_USER_URL = "https://api.pythondiscord.com/user"
+SITE_API_USER_URL = "http://api.pythondiscord.com/user"
+SITE_API_TAGS_URL = "http://api.pythondiscord.com/tags"
HELP_PREFIX = "bot."