From f61a8fcebab0057d0fcfb3794c1e80345fc38a35 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Jun 2021 17:30:35 -0600 Subject: [PATCH] v0.4.0, implements hybrid slash commands (mostly...), simplifies dev cog, closes #2 --- jarvis/__init__.py | 10 +- jarvis/cogs/admin.py | 86 ++++++++++------- jarvis/cogs/dbrand.py | 40 +++++--- jarvis/cogs/dev.py | 209 ++++++++++++++++++++++++++---------------- jarvis/cogs/error.py | 11 +++ jarvis/cogs/image.py | 5 +- jarvis/cogs/jokes.py | 12 ++- jarvis/cogs/util.py | 39 ++++++-- 8 files changed, 275 insertions(+), 137 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index c1758e6..144a116 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -2,13 +2,12 @@ from pathlib import Path from discord import Intents from discord.ext import commands from discord.utils import find +from discord_slash import SlashCommand from psutil import Process import asyncio - from jarvis.config import get_config from jarvis import utils, logo - if asyncio.get_event_loop().is_closed(): asyncio.set_event_loop(asyncio.new_event_loop()) @@ -18,8 +17,9 @@ restart_ctx = None jarvis = commands.Bot(command_prefix=utils.get_prefix, intents=intents) +slash = SlashCommand(jarvis, sync_commands=True, sync_on_cog_reload=True) jarvis_self = Process() -__version__ = "0.3.0" +__version__ = "0.4.0" @jarvis.event @@ -70,7 +70,9 @@ def run(ctx=None): config = get_config() print( " https://discord.com/api/oauth2/authorize?client_id=" - + "{}&permissions=8&scope=bot".format(config.client_id) + + "{}&permissions=8&scope=bot%20applications.commands".format( + config.client_id + ) ) jarvis.run(config.token, bot=True, reconnect=True) for cog in jarvis.cogs: diff --git a/jarvis/cogs/admin.py b/jarvis/cogs/admin.py index 6215a24..82c1267 100644 --- a/jarvis/cogs/admin.py +++ b/jarvis/cogs/admin.py @@ -1,6 +1,7 @@ import jarvis from discord import User from discord.ext import commands +from discord_slash import cog_ext, SlashContext from jarvis.utils.db import DBManager @@ -16,43 +17,66 @@ class AdminCog(commands.Cog): config = jarvis.config.get_config() self.db = DBManager(config.mongo) - @commands.command(name="ban") - @commands.has_permissions(ban_members=True) - async def _ban(self, ctx, user: User = None, reason=None): + async def _ban(self, ctx: SlashContext, user: User = None, reason=None): if not user or user == ctx.message.author: await ctx.send("You cannot ban yourself.") - if not reason: - reason = ( - "Mr. Stark is displeased with your presence. Please leave." - ) - guild_name = ctx.message.guild.name - await user.send( - f"You have been banned from {guild_name}. Reason:\n{reason}" - ) - await ctx.guild.ban(user, reason=reason) - await ctx.send( - f"{user.name} has been banned from {guild_name}." - + f"Reason:\n{reason}" - ) + if not reason: + reason = ( + "Mr. Stark is displeased with your presence. Please leave." + ) + guild_name = ctx.message.guild.name + await user.send( + f"You have been banned from {guild_name}. Reason:\n{reason}" + ) + await ctx.guild.ban(user, reason=reason) + await ctx.send( + f"{user.name} has been banned from {guild_name}." + + f"Reason:\n{reason}" + ) + + @cog_ext.cog_slash( + name="ban", description="Ban a user", guild_ids=[578757004059738142] + ) + @commands.has_permissions(ban_members=True) + async def _ban_slash(self, ctx, user: User = None, reason=None): + await self._ban(ctx, user, reason) + + @commands.command(name="ban") + @commands.has_permissions(ban_members=True) + async def _ban_pref(self, ctx, user: User = None, reason=None): + await self._ban(ctx, user, reason) - @commands.command(name="kick") - @commands.has_permissions(kick_members=True) async def _kick(self, ctx, user: User = None, reason=None): if not user or user == ctx.message.author: await ctx.send("You cannot kick yourself.") - if not reason: - reason = ( - "Mr. Stark is displeased with your presence. Please leave." - ) - guild_name = ctx.message.guild.name - await user.send( - f"You have been kicked from {guild_name}. Reason:\n{reason}" - ) - await ctx.guild.kick(user, reason=reason) - await ctx.send( - f"{user.name} has been kicked from {guild_name}." - + f"Reason:\n{reason}" - ) + if not reason: + reason = ( + "Mr. Stark is displeased with your presence. Please leave." + ) + guild_name = ctx.message.guild.name + try: + await user.send( + f"You have been kicked from {guild_name}. Reason:\n{reason}" + ) + except Exception: + await ctx.send("Unable to message user.") + await ctx.guild.kick(user, reason=reason) + await ctx.send( + f"{user.name} has been kicked from {guild_name}." + + f"Reason:\n{reason}" + ) + + @commands.command(name="kick") + @commands.has_permissions(kick_members=True) + async def _kick_pref(self, ctx, user: User = None, reason=None): + await self._kick(ctx, user, reason) + + @cog_ext.cog_slash( + name="kick", description="Kick a user", guild_ids=[578757004059738142] + ) + @commands.has_permissions(kick_members=True) + async def _kick_slash(self, ctx, user: User = None, reason=None): + await self._kick(ctx, user, reason) def setup(bot): diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 5a332c0..6232480 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -7,6 +7,7 @@ from jarvis.utils import build_embed from jarvis.utils.field import Field from jarvis.data.dbrand import shipping_lookup from discord.ext import commands +from discord_slash import cog_ext, SlashContext class DbrandCog(commands.Cog): @@ -24,32 +25,47 @@ class DbrandCog(commands.Cog): self.api_url = get_config().urls["dbrand_shipping"] self.cache = {} - @commands.group(name="db", aliases=["dbrand"], pass_context=True) - async def _db(self, ctx): - if ctx.invoked_subcommand is None: - await ctx.send( - "Available subcommands: `grip`, `support`, " - + "`skin`, `prism`, `shipping`, `camo`" - ) + # @cog_ext.cog_slash(name="db", aliases=["dbrand"], pass_context=True) + # async def _db(self, ctx): + # if ctx.invoked_subcommand is None: + # await ctx.send( + # "Available subcommands: `grip`, `support`, " + # + "`skin`, `prism`, `shipping`, `camo`" + # ) - @_db.command(name="skin", aliases=["skins", "tape"]) + @cog_ext.cog_subcommand( + base="db", name="skin", guild_ids=[578757004059738142] + ) async def _skin(self, ctx): await ctx.send(self.base_url + "shop/skins") - @_db.command(name="robotcamo", aliases=["rc", "robot", "camo"]) + # @_db.command(name="robotcamo", aliases=["rc", "robot", "camo"]) + @cog_ext.cog_subcommand( + base="db", name="robotcamo", guild_ids=[578757004059738142] + ) async def _camo(self, ctx): await ctx.send(self.base_url + "shop/special-edition/robot-camo") - @_db.command(name="grip", aliases=["g", "case"]) + # @_db.command(name="grip", aliases=["g", "case"]) + @cog_ext.cog_subcommand( + base="db", name="grip", guild_ids=[578757004059738142] + ) async def _grip(self, ctx): await ctx.send(self.base_url + "shop/grip/#grip-devices") - @_db.command(name="support", aliases=["help", "contact"]) + # @_db.command(name="support", aliases=["help", "contact"]) + @cog_ext.cog_subcommand( + base="db", name="contact", guild_ids=[578757004059738142] + ) async def _support(self, ctx): await ctx.send(self.base_url + "contact") - @_db.command(name="shipping", aliases=["ship", "s"]) + # @_db.command(name="shipping", aliases=["ship", "s"]) + @cog_ext.cog_subcommand( + base="db", name="ship", guild_ids=[578757004059738142] + ) async def _shipping(self, ctx, *, search: str): + await ctx.defer() if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE): if re.match( r"^[\U0001f1e6-\U0001f1ff]{2}$", diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 7a4c93b..f715afd 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -12,6 +12,7 @@ import traceback from time import time from inspect import getsource from discord.ext import commands +from discord_slash import cog_ext from jarvis.utils import build_embed, convert_bytesize from jarvis.utils.field import Field from bson import ObjectId @@ -70,7 +71,6 @@ class DevCog(commands.Cog): def __init__(self, bot): self.bot = bot - @commands.command(name="hash") async def _hash(self, ctx, method: str, *, data: str = None): if method not in supported_hashes: algo_txt = ", ".join(f"`{x}`" for x in supported_hashes) @@ -104,7 +104,18 @@ class DevCog(commands.Cog): ) await ctx.send(embed=embed) - @commands.command(name="uuid") + @commands.command(name="hash") + async def _hash_pref(self, ctx, method: str, *, data: str = None): + await self._hash(ctx, method, data=data) + + @cog_ext.cog_slash( + name="hash", + description="Hash some data", + guild_ids=[578757004059738142], + ) + async def _hash_slash(self, ctx, method: str, *, data: str = None): + await self._hash(ctx, method, data=data) + async def _uuid(self, ctx, version: str = None, data: str = None): if not version: await ctx.send("Supported UUID versions: 3, 4, 5") @@ -130,17 +141,50 @@ class DevCog(commands.Cog): to_send = UUID_GET[version](uuid.NAMESPACE_DNS, data) await ctx.send(f"UUID{version}: `{to_send}`") - @commands.command(name="objectid") - async def _objectid(self, ctx: commands.Context): + @commands.command(name="uuid") + async def _uuid_pref(self, ctx, version: str = None, data: str = None): + await self._uuid(ctx, version, data) + + @cog_ext.cog_slash( + name="uuid", + description="Generate a UUID", + guild_ids=[578757004059738142], + ) + async def _uuid_slash(self, ctx, version: str = None, data: str = None): + await self._uuid(ctx, version, data) + + async def _objectid(self, ctx): """Generates new bson.ObjectId""" await ctx.send(f"ObjectId: `{str(ObjectId())}`") - @commands.command(name="ulid") - async def _ulid(self, ctx: commands.Context): + @commands.command(name="objectid") + async def _objectid_pref(self, ctx): + await self._objectid(ctx) + + @cog_ext.cog_slash( + name="objectid", + description="Generate an ObjectID", + guild_ids=[578757004059738142], + ) + async def _objectid_slash(self, ctx): + await self._objectid(ctx) + + async def _ulid(self, ctx): """Generates a new ULID""" await ctx.send(f"ULID: `{ulid.new().str}`") - @commands.command(name="uuid2ulid") + @commands.command(name="ulid") + async def _ulid_pref(self, ctx): + await self._ulid(ctx) + + @cog_ext.cog_slash( + name="ulid", + description="Generate a ULID", + guild_ids=[578757004059738142], + ) + async def _ulid_slash(self, ctx): + await self._ulid(ctx) + async def _uuid2ulid(self, ctx: commands.Context, u): """Converts a UUID to a ULID""" if UUID_VERIFY.match(u): @@ -149,7 +193,18 @@ class DevCog(commands.Cog): else: await ctx.send("Invalid UUID") - @commands.command(name="ulid2uuid") + @commands.command(name="uuid2ulid") + async def _uuid2ulid_pref(self, ctx, u: str): + await self._uuid2ulid(ctx, u) + + @cog_ext.cog_slash( + name="uuid2ulid", + description="Convert a UUID to a ULID", + guild_ids=[578757004059738142], + ) + async def _uuid2ulid_slash(self, ctx, u: str): + await self._uuid2ulid(ctx, u) + async def _ulid2uuid(self, ctx: commands.Context, u): """Converts a ULID to a UUID""" if ULID_VERIFY.match(u): @@ -158,91 +213,83 @@ class DevCog(commands.Cog): else: await ctx.send("Invalid ULID.") - @commands.group(name="encode") - async def _encode(self, ctx): + @commands.command(name="ulid2uuid") + async def _ulid2uuid_pref(self, ctx, u): + await self._ulid2uuid(ctx, u) + + @cog_ext.cog_slash( + name="ulid2uuid", + description="Convert a ULID to a UUID", + guild_ids=[578757004059738142], + ) + async def _ulid2uuid_slash(self, ctx, u): + await self._ulid2uuid(ctx, u) + + base64_methods = ["b64", "b16", "b32", "a85", "b85"] + + async def _encode(self, ctx, method: str, data: str): """Encodes text with specified encoding method""" - if ctx.invoked_subcommand is None: - await ctx.send("Usage: encode ") + if method not in self.base64_methods: + methods = ", ".join(f"`{x}`" for x in self.base64_methods) + await ctx.send( + "Usage: encode \nSupported methods:\n" + methods + ) + return + method = getattr(base64, method + "encode") + await ctx.send(f"`{method(data.encode('UTF-8')).decode('UTF-8')}`") - @_encode.command(name="b64") - async def _b64e(self, ctx, *, data: str): - """Base64 encoding""" - await ctx.send(base64.b64encode(data.encode("UTF-8")).decode("UTF-8")) + @commands.command(name="encode") + async def _encode_pref(self, ctx, method: str, *, data: str): + await self._encode(ctx, method, data) - @_encode.command(name="b16") - async def _b16e(self, ctx, *, data: str): - """Base16 encoding""" - await ctx.send( - "`" + base64.b16encode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) + @cog_ext.cog_slash( + name="encode", + description="Encode using the base64 module", + guild_ids=[578757004059738142], + ) + async def _encode_slash(self, ctx, method: str, *, data: str): + await self._encode(ctx, method, data) - @_encode.command(name="b32") - async def _b32e(self, ctx, *, data: str): - """Base32 encoding""" - await ctx.send( - "`" + base64.b32encode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) - - @_encode.command(name="a85") - async def _a85e(self, ctx, *, data: str): - """ASCII85 encoding""" - await ctx.send( - "`" + base64.a85encode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) - - @_encode.command(name="b85") - async def _b85e(self, ctx, *, data: str): - """Base85 encoding""" - await ctx.send( - "`" + base64.b85encode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) - - @commands.group(name="decode") - async def _decode(self, ctx): + async def _decode(self, ctx, method: str, data: str): """Decodes text with specified encoding method""" - if ctx.invoked_subcommand is None: - await ctx.send("Usage: decode ") + if method not in self.base64_methods: + methods = ", ".join(f"`{x}`" for x in self.base64_methods) + await ctx.send( + "Usage: decode \nSupported methods:\n" + methods + ) + return + method = getattr(base64, method + "decode") + await ctx.send(f"`{method(data.encode('UTF-8')).decode('UTF-8')}`") - @_decode.command(name="b64") - async def _b64d(self, ctx, *, data: str): - """Base64 decoding""" - await ctx.send( - "`" + base64.b64decode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) + @commands.command(name="decode") + async def _decode_pref(self, ctx, method: str, *, data: str): + await self._decode(ctx, method, data) - @_decode.command(name="b16") - async def _b16d(self, ctx, *, data: str): - """Base16 decoding""" - await ctx.send( - "`" + base64.b16decode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) + @cog_ext.cog_slash( + name="decode", + description="Decode using the base64 module", + guild_ids=[578757004059738142], + ) + async def _decode_slash(self, ctx, method: str, *, data: str): + await self._decode(ctx, method, data) - @_decode.command(name="b32") - async def _b32d(self, ctx, *, data: str): - """Base32 decoding""" - await ctx.send( - "`" + base64.b32decode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) - - @_decode.command(name="a85") - async def _a85d(self, ctx, *, data: str): - """ASCII85 decoding""" - await ctx.send( - "`" + base64.a85decode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) - - @_decode.command(name="b85") - async def _b85d(self, ctx, *, data: str): - """Base85 decoding""" - await ctx.send( - "`" + base64.b85decode(data.encode("UTF-8")).decode("UTF-8") + "`" - ) - - @commands.command(name="cloc", help="Get J.A.R.V.I.S. lines of code") async def _cloc(self, ctx): output = subprocess.check_output(["cloc", "."]).decode("UTF-8") await ctx.send(f"```\n{output}\n```") + @commands.command(name="cloc", help="Get J.A.R.V.I.S. lines of code") + async def _cloc_pref(self, ctx): + await self._cloc(ctx) + + @cog_ext.cog_slash( + name="cloc", + description="Get J.A.R.V.I.S. lines of code", + guild_ids=[578757004059738142], + ) + async def _cloc_slash(self, ctx): + await ctx.defer() + await self._cloc(ctx) + def resolve_variable(self, variable): if hasattr(variable, "__iter__"): var_length = len(list(variable)) diff --git a/jarvis/cogs/error.py b/jarvis/cogs/error.py index 2a9c26c..f3369ac 100644 --- a/jarvis/cogs/error.py +++ b/jarvis/cogs/error.py @@ -17,6 +17,17 @@ class ErrorHandlerCog(commands.Cog): else: await ctx.send(f"Error processing command:\n```{error}```") + @commands.Cog.listener() + async def on_slash_command_error(self, ctx, error): + if isinstance(error, commands.errors.MissingPermissions): + await ctx.send("I'm afraid I can't let you do that.") + elif isinstance(error, commands.errors.CommandNotFound): + await ctx.send( + "Command does not exist. Run `>help` to get a list of commands" + ) + else: + await ctx.send(f"Error processing command:\n```{error}```") + def setup(bot): bot.add_cog(ErrorHandlerCog(bot)) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 15cd0df..83d9980 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -24,7 +24,6 @@ class ImageCog(commands.Cog): r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE ) - @commands.command(name="resize", help="Resize an image") async def _resize(self, ctx, target: str, url: str = None): if not target: await ctx.send("Missing target size, i.e. 200KB.") @@ -103,6 +102,10 @@ class ImageCog(commands.Cog): file=File(bufio, filename="resized.png"), ) + @commands.command(name="resize", help="Resize an image") + async def _resize_pref(self, ctx, target: str, url: str = None): + await self._resize(ctx, target, url) + def setup(bot): bot.add_cog(ImageCog(bot)) diff --git a/jarvis/cogs/jokes.py b/jarvis/cogs/jokes.py index 887fb11..7a55795 100644 --- a/jarvis/cogs/jokes.py +++ b/jarvis/cogs/jokes.py @@ -8,6 +8,7 @@ from jarvis.utils import build_embed from jarvis.utils.db import DBManager from jarvis.utils.field import Field from discord.ext import commands +from discord_slash import cog_ext from datetime import datetime @@ -24,7 +25,6 @@ class JokeCog(commands.Cog): self.db = DBManager(config.mongo) # TODO: Make this a command group with subcommands - @commands.command(name="joke", help="Hear a joke") async def _joke(self, ctx, id: str = None): try: if randint(1, 100_000) == 5779 and id is None: @@ -108,6 +108,16 @@ class JokeCog(commands.Cog): ) # await ctx.send(f"**{result['title']}**\n\n{result['body']}") + @commands.command(name="joke", help="Hear a joke") + async def _joke_pref(self, ctx, id: str = None): + await self._joke(ctx, id) + + @cog_ext.cog_slash( + name="joke", description="Hear a joke", guild_ids=[578757004059738142] + ) + async def _joke_slash(self, ctx, id: str = None): + await self._joke(ctx, id) + def setup(bot): bot.add_cog(JokeCog(bot)) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 39173c6..389efc6 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -3,6 +3,7 @@ from jarvis import jarvis_self, config, logo from jarvis.utils import convert_bytesize, build_embed, get_repo_hash from jarvis.utils.field import Field from discord.ext import commands +from discord_slash import cog_ext, SlashContext class UtilCog(commands.Cog): @@ -16,7 +17,6 @@ class UtilCog(commands.Cog): self.bot = bot self.config = config.get_config() - @commands.command(name="status", help="Retrieve J.A.R.V.I.S. status") async def _status(self, ctx): title = "J.A.R.V.I.S. Status" desc = "All systems online" @@ -32,17 +32,42 @@ class UtilCog(commands.Cog): ) fields.append(Field("PID", jarvis_self.pid)) fields.append(Field("Version", jarvis.__version__, False)) - fields.append(Field("Git Hash", get_repo_hash(), False)) - embed = build_embed( - title=title, description=desc, fields=fields, color=color - ) - await ctx.send(embed=embed) + fields.append(Field("Git Hash", get_repo_hash(), False)) + embed = build_embed( + title=title, description=desc, fields=fields, color=color + ) + await ctx.send(embed=embed) + + @cog_ext.cog_slash( + name="status", + description="Retrieve J.A.R.V.I.S. status", + guild_ids=[578757004059738142], + ) + async def _status_slash(self, ctx): + await self._status(ctx) + + @commands.command( + name="status", description="Retrieve J.A.R.V.I.S. status" + ) + async def _status_pre(self, ctx): + await self._status(ctx) - @commands.command(name="logo", help="Get the current logo") async def _logo(self, ctx): lo = logo.get_logo(self.config.logo) await ctx.send(f"```\n{lo}\n```") + @commands.command(name="logo", help="Get the current logo") + async def _logo_pre(self, ctx): + await self._logo(ctx) + + @cog_ext.cog_slash( + name="logo", + description="Retrieve J.A.R.V.I.S. status", + guild_ids=[578757004059738142], + ) + async def _logo_slash(self, ctx): + await self._logo(ctx) + def setup(bot): bot.add_cog(UtilCog(bot))