Migrate a lot of things to slash commands

This commit is contained in:
2021-07-06 16:24:24 +02:00
parent f803693202
commit 57a7037ce9
3 changed files with 649 additions and 279 deletions

920
bot.py
View File

@@ -3,17 +3,19 @@ import os
import random
import re
import time
from types import coroutine
from typing import Optional, List, Dict
from typing import Optional, List, Dict, Iterable
import discord
import gtts
from discord.ext import commands
from discord.ext.commands import MemberConverter
from discord_slash import SlashCommand, SlashContext, SlashCommandOptionType
from discord_slash.utils.manage_commands import create_option, create_choice
from dotenv import load_dotenv
from lib.config import config, config_load, config_save, config_get, config_set, config_get_descriptions, \
config_set_raw
from lib.utils import async_filter, find_category, find_role_case_insensitive
config_set_raw, config_meta
from lib.utils import async_filter, find_category, find_role_case_insensitive, link_channel
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
@@ -51,6 +53,7 @@ intents = discord.Intents.default()
intents.members = True
intents.voice_states = True
bot = commands.Bot(command_prefix=config.get('prefix'), intents=intents)
slash = SlashCommand(bot, sync_commands=True)
if 'LIBOPUS' in os.environ and not len(os.environ['LIBOPUS']) == 0:
discord.opus.load_opus(os.environ['LIBOPUS'])
@@ -262,281 +265,6 @@ async def flip_pipelin_command(ctx: commands.Context):
await ctx.channel.send("Pipelin " + random.choice(['schnel', 'langsam']))
@bot.command(name='quote', brief='Get a random quote or add new ones')
async def quote_command(ctx: commands.Context, author: Optional[str], *, quote: Optional[str]):
quotes: Optional[Dict[str, List[str]]] = config_get("guild_quotes", guild=ctx.guild.id)
if quotes is None:
quotes = {}
config_set_raw("guild_quotes", quotes, ctx.guild.id)
if author is None:
authors = quotes.keys()
if authors:
author = random.choice([*authors])
author_quotes = quotes[author]
quote = '\n'.join(map(lambda line: '> ' + line, random.choice(author_quotes).splitlines()))
await ctx.channel.send(quote + '\n *~' + author + '*')
else:
await ctx.channel.send("No quotes present!")
elif author == 'list':
def create_list_embed(quote_author: str, _quotes: List[str]) -> discord.Embed:
embed: discord.Embed = discord.Embed(
title=quote_author,
description='\n'.join(_quotes)
)
return embed
if quote is None:
coroutines = []
for author, author_quotes in quotes.items():
coroutines.append(ctx.send(embed=create_list_embed(author, author_quotes)))
await asyncio.gather(*coroutines)
else:
author = quote.strip().title()
if author in quotes:
await ctx.send(embed=create_list_embed(author, quotes[author]))
else:
await ctx.send("No such author!")
else:
author = author.strip().title()
if quote:
quote = quote.strip()
quote = quote[0].upper() + quote[1::]
if author in quotes:
author_quotes: List[str] = quotes[author]
regex = re.compile(r'\W')
stripped_quote = regex.sub('', quote).lower()
for aq in author_quotes:
if regex.sub('', aq).lower() == stripped_quote:
await ctx.channel.send("Quote already exists!")
return
author_quotes.append(quote)
config_save()
await ctx.channel.send("Quote added")
else:
quotes[author] = [quote]
config_save()
await ctx.channel.send("Quote added")
else:
if author in quotes:
quote = random.choice(quotes[author])
quote = '\n'.join(map(lambda line: '> ' + line, quote.splitlines()))
await ctx.channel.send(quote + '\n *~ ' + author + '*')
else:
await ctx.channel.send("Unknown author!")
@bot.command(name='quote_remove', brief='Remove a quote')
async def quote_remove_command(ctx: commands.Context, author: str, quote: str):
if not ctx.author.guild_permissions.administrator:
await ctx.channel.send("Only admins are allowed to remove quotes!")
quotes: Optional[Dict[str, List[str]]] = config_get("guild_quotes", guild=ctx.guild.id)
if quotes is None:
await ctx.channel.send("No quotes available!")
else:
quote = quote.strip().lower()
author = author.strip().title()
if author in quotes:
for q in quotes[author]:
if q.lower() == quote:
quotes[author].remove(q)
if not quotes[author]:
quotes.pop(author)
await ctx.channel.send("Successfully removed quote!")
config_save()
return
# No matching quote found
try:
quote = int(quote)
quotes[author].pop(quote)
if not quotes[author]:
quotes.pop(author)
await ctx.channel.send("Successfully removed quote!")
config_save()
return
except ValueError:
pass
await ctx.channel.send("No such quote found!")
@bot.command(name='groups', brief="Manage groups")
async def group_command(ctx: commands.Context, subcommand: Optional[str], arg: Optional[str],
members: commands.Greedy[discord.Member]):
if not ctx.author.guild_permissions.manage_roles:
await ctx.send("Access denied!")
return
if subcommand is None:
await ctx.send("Available commands: `list`, `create <group> <members>`, "
"`archive <group>`, `unarchive <group>`, `delete <group>`, `clear_vcs`")
return
guild: discord.Guild = ctx.guild
role_prefix = config_get("groups-role-prefix", guild.id)
def collect_group_channels(cat: discord.CategoryChannel) -> Dict[str, discord.abc.GuildChannel]:
return {channel.name: channel for channel in cat.text_channels}
async def collect_group_roles() -> List[discord.Role]:
return list(filter(lambda role: role.name.startswith(role_prefix), await guild.fetch_roles()))
async def fail_category(type: str, expected: str):
await ctx.send("Unable to find channel category \"" + expected + "\" for " + type + ". Change in configs.")
def link_channel(channel: discord.abc.GuildChannel, italic: bool = False) -> str:
if italic:
return '[*' + channel.name + '*](https://discord.com/channels/' + str(guild.id) + '/' + str(
channel.id) + ')'
return '[' + channel.name + '](https://discord.com/channels/' + str(guild.id) + '/' + str(channel.id) + ')'
groups_cat = find_category(guild, config_get("groups-category", guild.id))
if groups_cat is None:
await fail_category("groups", config_get("groups-category", guild.id))
return
if subcommand == 'list':
archive_cat = find_category(guild, config_get("groups-archive-category", guild.id))
if archive_cat is None:
await fail_category("archive", config_get("groups-archive-category", guild.id))
return
active_groups = collect_group_channels(groups_cat)
archived_groups = collect_group_channels(archive_cat)
msg = ""
for role in await collect_group_roles():
name = role.name[len(role_prefix):].lower()
if name in active_groups:
msg += link_channel(active_groups[name]) + "\n"
elif name in archived_groups:
msg += link_channel(archived_groups[name], True) + "\n"
else:
msg += "*" + name + "*\n"
embed = discord.Embed(title="Groups", description=msg)
embed.set_footer(text="Italic groups are archived or unavailable.")
await ctx.send(embed=embed)
elif subcommand == 'archive':
if arg is None:
await ctx.send("Group name required!")
return
channel_name = arg.lower()
groups = collect_group_channels(groups_cat)
if channel_name in groups:
archive_cat = find_category(guild, config_get("groups-archive-category", guild.id))
if archive_cat is None:
await fail_category("archive", config_get("groups-archive-category", guild.id))
return
await groups[channel_name].edit(reason="Archive group " + arg, category=archive_cat)
if channel_name + "_vc" in groups:
await groups[channel_name + "_vc"].delete(reason="Archive group" + arg)
await ctx.send("Group " + arg + " archived.")
else:
await ctx.send("Can't find that group!")
elif subcommand == 'unarchive':
if arg is None:
await ctx.send("Group name required!")
return
channel_name = arg.lower()
archive_cat = find_category(guild, config_get("groups-archive-category", guild.id))
if archive_cat is None:
await fail_category("archive", config_get("groups-archive-category", guild.id))
return
archived_groups = collect_group_channels(archive_cat)
if channel_name in archived_groups:
await archived_groups[channel_name].edit(reason="Archive group " + arg, category=groups_cat)
await ctx.send("Group " + arg + " unarchived.")
else:
await ctx.send("Can't find that group!")
elif subcommand == 'delete':
if arg is None:
await ctx.send("Group name required!")
return
archive_cat = find_category(guild, config_get("groups-archive-category", guild.id))
if archive_cat is None:
await fail_category("archive", config_get("groups-archive-category", guild.id))
return
role = await find_role_case_insensitive(guild, arg, role_prefix)
if role is not None:
await role.delete(reason="Delete group " + arg)
groups = collect_group_channels(groups_cat)
groups.update(collect_group_channels(archive_cat))
channel_name = arg.lower()
if channel_name in groups:
await groups[channel_name].delete(reason="Delete group " + arg)
if channel_name + "_vc" in groups:
await groups[channel_name + "_vc"].delete(reason="Delete group " + arg)
await ctx.send("Group " + arg + " deleted.")
elif subcommand == 'create':
if arg is None:
await ctx.send("Group name required!")
return
arg.strip()
cor = groups_cat.create_text_channel(arg.lower(), reason="Create group " + arg)
cor_role = guild.create_role(name=config_get("groups-role-prefix", guild.id) + arg,
mentionable=True, reason="Create group " + arg)
channel: discord.TextChannel = await cor
cor = channel.edit(sync_permissions=True)
role: discord.Role = await cor_role
await cor
await channel.set_permissions(role, reason="Create group " + arg, read_messages=True)
if members:
for member in members:
await member.add_roles(role, reason="Create group " + arg)
await asyncio.sleep(5)
await channel.send("Hi, " + role.mention)
await ctx.send("Group " + arg + " created.")
elif subcommand == 'clear_vcs':
cors: List[coroutine] = []
for vc in groups_cat.voice_channels:
cors.append(vc.delete(reason="Clear temporary vcs, as requested by " + ctx.author.mention))
for cor in cors:
await cor
@bot.command(name='groupvc', brief='Creates a temporary vc for the current group text channel')
async def groupvc_command(ctx: commands.Context):
if ctx.channel.category is not None:
guild: discord.Guild = ctx.guild
channel: discord.TextChannel = ctx.channel
if channel.category.name.lower() == config_get("groups-category", guild.id).lower():
role = await find_role_case_insensitive(guild, channel.name, config_get("groups-role-prefix", guild.id))
if role is None:
await ctx.send("Couldn't resolve group role!")
return
category: discord.CategoryChannel = channel.category
for vc in category.voice_channels:
if vc.name.lower() == channel.name.lower() + "_vc":
await ctx.send("Temporary group channel already exists.")
return
reason = "Create temporary vc for group " + channel.name
vc = await category.create_voice_channel(channel.name + "_vc", reason=reason)
await vc.edit(sync_permissions=True, reason=reason)
await vc.set_permissions(role, view_channel=True, connect=True, reason=reason)
await ctx.send("Created temporary vc for this group")
return
await ctx.send("Not an active group channel!")
def _is_message_valid_for_selection(message: discord.Message, reaction_filter: Optional[str] = None) -> bool:
if message.clean_content.strip() == '':
return False
@@ -599,4 +327,638 @@ async def config_prefix_command(ctx: commands.Context, cmd: str = '', key: str =
await ctx.send('Unknown command!')
# ------------------- The slashy way of life ------------------- #
slash_guild_ids = None
config_choices = [create_choice(name=entry[1][1], value=entry[0]) for entry in config_meta.items() if entry[1][0]]
config_option = create_option("config_key", "A key identifying the config entry to target", str, True, config_choices)
async def check_slash_context(ctx: SlashContext, require_op: bool = False, require_manage_roles: bool = False) -> bool:
if ctx.guild is None:
await ctx.send('You can\'t run config commands in private messages!')
return False
if require_op and not ctx.author.guild_permissions.administrator:
await ctx.send('You\'re not allowed to run this command on this server!', hidden=True)
return False
if require_manage_roles and not ctx.author.guild_permissions.manage_roles:
await ctx.send('You\'re not allowed to run this command on this server!', hidden=True)
return False
return True
async def check_text_channel(ctx: SlashContext, channel) -> bool:
if not isinstance(channel, discord.TextChannel):
await ctx.send('The given channel is not a text channel!', hidden=True)
return False
return True
# ____ ____ ___ _ _ ____ ____
# / ___| _ \ / _ \| | | | _ \/ ___|
# | | _| |_) | | | | | | | |_) \___ \
# | |_| | _ <| |_| | |_| | __/ ___) |
# \____|_| \_\\___/ \___/|_| |____/
def get_groups_role_prefix(guild_id: int) -> str:
return config_get("groups-role-prefix", guild_id)
async def get_groups_category(ctx: SlashContext) -> Optional[discord.CategoryChannel]:
category = find_category(ctx.guild, config_get("groups-category", ctx.guild_id))
if category is None:
await ctx.send("Failed to locate group category!")
return None
return category
async def get_groups_archive_category(ctx: SlashContext) -> Optional[discord.CategoryChannel]:
category = find_category(ctx.guild, config_get("groups-archive-category", ctx.guild_id))
if category is None:
await ctx.send("Failed to locate group category!")
return None
return category
async def get_groups_categories(ctx: SlashContext) -> (discord.CategoryChannel, discord.CategoryChannel):
groups_cat = await get_groups_category(ctx)
if groups_cat is None:
return None
archive_cat = await get_groups_archive_category(ctx)
if archive_cat is None:
return None
return groups_cat, archive_cat
def collect_group_channels(cat: discord.CategoryChannel) -> Dict[str, discord.abc.GuildChannel]:
return {channel.name: channel for channel in cat.text_channels}
async def collect_group_roles(guild: discord.Guild) -> List[discord.Role]:
role_prefix = get_groups_role_prefix(guild.id)
return list(filter(lambda role: role.name.startswith(role_prefix), await guild.fetch_roles()))
@slash.subcommand(
base="groups",
name="list",
description="Lists all groups on this server",
guild_ids=slash_guild_ids
)
async def groups_list_slash(ctx: SlashContext):
if not await check_slash_context(ctx, require_manage_roles=True):
return
categories = await get_groups_categories(ctx)
if categories is None:
return
groups_cat, archive_cat = categories
active_groups = collect_group_channels(groups_cat)
archived_groups = collect_group_channels(archive_cat)
role_prefix = get_groups_role_prefix(ctx.guild_id)
msg = ""
for role in await collect_group_roles(ctx.guild):
name = role.name[len(role_prefix):].lower()
if name in active_groups:
msg += link_channel(active_groups[name]) + "\n"
elif name in archived_groups:
msg += link_channel(archived_groups[name], True) + "\n"
else:
msg += "*" + name + "*\n"
embed = discord.Embed(title="Groups", description=msg)
embed.set_footer(text="Italic groups are archived or unavailable.")
await ctx.send(embed=embed)
@slash.subcommand(
base="groups",
name="archive",
description="Move a group to the archive",
options=[
create_option(
name="group_channel",
description="The group identified by it's channel",
option_type=discord.abc.GuildChannel,
required=True
)
],
guild_ids=slash_guild_ids
)
async def groups_archive_slash(ctx: SlashContext, group_channel: discord.abc.GuildChannel):
if not await check_slash_context(ctx, require_manage_roles=True):
return
if not await check_text_channel(ctx, group_channel):
return
categories = await get_groups_categories(ctx)
if categories is None:
return
groups_cat, archive_cat = categories
group_name = group_channel.name
groups = collect_group_channels(groups_cat)
if group_channel.category == groups_cat:
await group_channel.edit(reason="Archive group " + group_name, category=archive_cat)
if group_name + "_vc" in groups:
await groups[group_name + "_vc"].delete(reason="Archive group " + group_name)
await ctx.send("Group " + group_name + " archived.")
elif group_channel.category == archive_cat:
await ctx.send("Group " + group_name + " is already archived.", hidden=True)
else:
await ctx.send(group_name + " is not a group")
@slash.subcommand(
base="groups",
name="unarchive",
description="Move a group from the archive to the main category",
options=[
create_option(
name="group_channel",
description="The group identified by it's channel",
option_type=discord.abc.GuildChannel,
required=True
)
],
guild_ids=slash_guild_ids
)
async def groups_archive_slash(ctx: SlashContext, group_channel: discord.abc.GuildChannel):
if not await check_slash_context(ctx, require_manage_roles=True):
return
if not await check_text_channel(ctx, group_channel):
return
categories = await get_groups_categories(ctx)
if categories is None:
return
groups_cat, archive_cat = categories
group_name = group_channel.name
if group_channel.category == archive_cat:
await ctx.defer()
await group_channel.edit(reason="Archive group " + group_name, category=archive_cat)
await ctx.send("Group " + group_name + " archived.")
elif group_channel.category == groups_cat:
await ctx.send("Group " + group_name + " is not archived.", hidden=True)
else:
await ctx.send(group_name + " is not a group")
@slash.subcommand(
base="groups",
name="delete",
description="Move a group from the archive to the main category",
options=[
create_option(
name="group_channel",
description="The group identified by it's channel",
option_type=discord.abc.GuildChannel,
required=True
)
],
guild_ids=slash_guild_ids
)
async def groups_delete_slash(ctx: SlashContext, group_channel: discord.abc.GuildChannel):
if not await check_slash_context(ctx, require_manage_roles=True):
return
if not await check_text_channel(ctx, group_channel):
return
group_channel: discord.TextChannel = group_channel
group_name = group_channel.name
await ctx.defer()
role = await find_role_case_insensitive(ctx.guild, group_channel.name, get_groups_role_prefix(ctx.guild_id))
if role is not None:
await role.delete(reason="Delete group " + group_name)
if group_channel.category:
vcs: List[discord.VoiceChannel] = group_channel.category.voice_channels
for vc in vcs:
if vc.name == group_name + "_vc":
await vc.delete(reason="Delete group " + group_name)
break
await group_channel.delete(reason="Delete group " + group_name)
await ctx.send("Group " + group_name + " deleted.")
@slash.subcommand(
base="groups",
name="create",
description="Creates a new group channel and role",
options=[
create_option(
name="name",
description="The name of the group",
option_type=str,
required=True
),
create_option(
name="members",
description="A comma-separated list of members",
option_type=str,
required=False
)
],
guild_ids=slash_guild_ids
)
async def groups_create_slash(ctx: SlashContext, name: str, members: Optional[str] = None):
if not await check_slash_context(ctx, require_manage_roles=True):
return
await ctx.defer()
name = name.strip()
groups_cat = await get_groups_category(ctx)
if not groups_cat:
return
cor = groups_cat.create_text_channel(name.lower(), reason="Create grooup " + name)
cor_role = ctx.guild.create_role(name=config_get("groups-role-prefix", ctx.guild_id) + name.lower(),
mentionable=True, reason="Create group " + name)
channel: discord.TextChannel = await cor
cor = channel.edit(sync_permissions=True)
role: discord.Role = await cor_role
await cor
await channel.set_permissions(role, reason="Create group " + name, read_messages=True)
extra = ""
if members is not None:
members = members.split(',')
converter = MemberConverter()
for member_str in members:
member_str = member_str.strip()
try:
member = await converter.convert(ctx, member_str)
await member.add_roles(role, reason="Create group " + name)
except discord.ext.commands.MemberNotFound:
extra += '\n*' + member_str + '*'
if extra:
extra = '\nCouldn\'t resolve users:' + extra
await asyncio.sleep(3)
await channel.send("Hi, " + role.mention)
await ctx.send("Group " + name + " created." + extra)
@slash.subcommand(
base="groups",
name="remove-vc",
description="Removes a temporary voice channel associated with the current group",
guild_ids=slash_guild_ids
)
async def groups_remove_vc_slash(ctx: SlashContext):
if not await check_slash_context(ctx):
return
groups_cat = await get_groups_category(ctx)
if not groups_cat:
return
if ctx.channel.category == groups_cat:
vc: Optional[discord.VoiceChannel] = discord.utils.find(
lambda _vc: _vc.name == ctx.channel.name + '_vc',
ctx.channel.category.voice_channels
)
if vc is None:
await ctx.send("Voice chat is not open!", hidden=True)
else:
await ctx.defer(hidden=True)
await vc.delete(reason="Delete temporary voice chat")
await ctx.send("Removed temporary voice chat.", hidden=True)
else:
await ctx.send("This channel is not an active group!", hidden=True)
@slash.subcommand(
base="groups",
name="vc",
description="Create a temporary voice chat. Available to anyone.",
guild_ids=slash_guild_ids
)
async def groups_vc_slash(ctx: SlashContext):
if ctx.channel.category is not None:
guild: discord.Guild = ctx.guild
channel: discord.TextChannel = ctx.channel
if channel.category.name.lower() == config_get("groups-category", guild.id).lower():
role = await find_role_case_insensitive(guild, channel.name, config_get("groups-role-prefix", guild.id))
if role is None:
await ctx.send("Couldn't resolve group role!", hidden=True)
return
category: discord.CategoryChannel = channel.category
for vc in category.voice_channels:
if vc.name.lower() == channel.name.lower() + "_vc":
await ctx.send("Temporary group channel already exists.", hidden=True)
return
await ctx.defer()
reason = "Create temporary vc for group " + channel.name
vc = await category.create_voice_channel(channel.name + "_vc", reason=reason)
await vc.edit(sync_permissions=True, reason=reason)
await vc.set_permissions(role, view_channel=True, connect=True, reason=reason)
await ctx.send("Created temporary vc for this group")
return
await ctx.send("Not an active group channel!", hidden=True)
# ___ _ _ ___ _____ _____ ____
# / _ \| | | |/ _ \_ _| ____/ ___|
# | | | | | | | | | || | | _| \___ \
# | |_| | |_| | |_| || | | |___ ___) |
# \__\_\\___/ \___/ |_| |_____|____/
def get_quotes(guild_id: int) -> Dict[str, List[str]]:
quotes: Optional[Dict[str, List[str]]] = config_get("guild_quotes", guild=guild_id)
if quotes is None:
quotes = {}
config_set_raw("guild_quotes", quotes, guild_id)
return quotes
@slash.subcommand(
base="quotes",
name="random",
description="Gets a random quote, optionally for the given author",
options=[
create_option(
name="author",
description="The author to filter by",
option_type=str,
required=False
)
],
guild_ids=slash_guild_ids
)
async def quote_random_slash(ctx: SlashContext, author: Optional[str] = None):
if not await check_slash_context(ctx, False):
return
quotes = get_quotes(ctx.guild_id)
if author is None:
authors = quotes.keys()
if authors:
author = random.choice([*authors])
else:
await ctx.send('No quotes available')
return
if author not in quotes:
await ctx.send('No such author!', hidden=True)
return
author_quotes = quotes[author]
quote = '\n'.join(map(lambda line: '> ' + line, random.choice(author_quotes).splitlines()))
await ctx.send(quote + '\n *~' + author + '*')
@slash.subcommand(
base="quotes",
name="list",
description="Lists all authors or all quotes by a given author",
options=[
create_option(
name="author",
description="The author to filter by, use * for all authors",
option_type=str,
required=False
)
],
guild_ids=slash_guild_ids
)
async def quote_list_slash(ctx: SlashContext, author: Optional[str] = None):
if not await check_slash_context(ctx, False):
return
def create_list_embed(title: str, items: Iterable[str]) -> discord.Embed:
embed: discord.Embed = discord.Embed(
title=title,
description='\n'.join(items)
)
return embed
quotes = get_quotes(ctx.guild_id)
if author is None:
await ctx.send(embed=create_list_embed("Available authors", quotes.keys()))
elif author == '*':
coroutines = []
await ctx.defer()
for author, author_quotes in quotes.items():
coroutines.append(ctx.send(embed=create_list_embed(author, author_quotes)))
await asyncio.gather(*coroutines)
else:
author = author.strip().title()
if author in quotes:
await ctx.send(embed=create_list_embed(author, quotes[author]))
else:
await ctx.send("No such author: \"" + author + "\"", hidden=True)
@slash.subcommand(
base="quotes",
name="add",
description="Add a new quote",
options=[
create_option(
name="author",
description="The author of the new quote",
option_type=str,
required=True
),
create_option(
name="quote",
description="The quote to add",
option_type=str,
required=True
)
],
guild_ids=slash_guild_ids
)
async def quote_add_slash(ctx: SlashContext, author: str, quote: str):
if not await check_slash_context(ctx, False):
return
ignored_character_regex = re.compile(r'\W')
def prepare_quote_for_compare(_quote: str) -> str:
return ignored_character_regex.sub('', _quote).lower()
quotes = get_quotes(ctx.guild_id)
author = author.strip().title()
quote = quote.strip()
quote = quote[0].upper() + quote[1::]
if author in quotes:
author_quotes: List[str] = quotes[author]
stripped_quote = prepare_quote_for_compare(quote)
for aq in author_quotes:
if prepare_quote_for_compare(aq) == stripped_quote:
await ctx.send("Quote already exists!", hidden=True)
return
author_quotes.append(quote)
config_save()
await ctx.send("Quote added.")
else:
quotes[author] = [quote]
config_save()
await ctx.send("Author and quote added")
@slash.subcommand(
base="quotes",
name="remove",
description="Removes a quote. Requires administrator permissions.",
options=[
create_option(
name="author",
description="The author to delete from",
option_type=str,
required=True
),
create_option(
name="quote",
description="The exact quote to remove",
option_type=str,
required=False
),
create_option(
name="quote_position",
description="The quote position to delete",
option_type=SlashCommandOptionType.INTEGER,
required=False
)
],
guild_ids=slash_guild_ids
)
async def quote_remove_slash(ctx: SlashContext, author: str, quote: Optional[str] = None, quote_position: Optional[int] = None):
if not await check_slash_context(ctx, True):
return
quotes = get_quotes(ctx.guild_id)
if quotes is None:
await ctx.send("No quotes available!", hidden=True)
else:
author = author.strip().title()
if author not in quotes:
await ctx.send("No quotes from author \"" + author + "\"!", hidden=True)
return
author_quotes = quotes[author]
if quote is not None:
quote = quote.strip().lower()
for aq in author_quotes:
if aq.lower() == quote:
author_quotes.remove(aq)
if not author_quotes:
quotes.pop(author)
await ctx.send("Removed quote from author \"" + author + "\"")
config_save()
return
await ctx.send("No such quote found!", hidden=True)
elif quote_position is not None:
if 0 <= quote_position < len(author_quotes):
author_quotes.pop(quote_position)
if not author_quotes:
quotes.pop(author)
await ctx.send("Removed quote from author \"" + author + "\"")
config_save()
return
await ctx.send("Invalid quote position - must be within the range of 0 and " + str(len(author_quotes)) + "!", hidden=True)
else:
await ctx.send("Either quote or quote_position must be used!", hidden=True)
# ____ ___ _ _ _____ ___ ____
# / ___/ _ \| \ | | ___|_ _/ ___|
# | | | | | | \| | |_ | | | _
# | |__| |_| | |\ | _| | | |_| |
# \____\___/|_| \_|_| |___\____|
@slash.slash(
name="config",
description="Change or query the configuration of this bot",
guild_ids=slash_guild_ids
)
async def config_slash(ctx: SlashContext):
await ctx.send("missingno", hidden=True)
@slash.subcommand(
base="config",
name="list",
description="List available config options",
guild_ids=slash_guild_ids
)
async def config_list_slash(ctx: SlashContext):
msg = 'Available config options:'
for (key, value) in config_get_descriptions():
msg += '\n - `' + key + '`: ' + value[1]
await ctx.send(msg, hidden=True)
@slash.subcommand(
base="config",
name="get",
description="Queries the configuration for a value",
options=[
config_option
],
guild_ids=slash_guild_ids
)
async def config_get_slash(ctx: SlashContext, config_key: str):
if not await check_slash_context(ctx, False):
return
if config_key in config_meta and config_meta[config_key][0]:
await ctx.send('`' + config_key + '` is set to `' + str(config_get(config_key, ctx.guild.id)) + '`',
hidden=True)
else:
await ctx.send('Unknown config option `' + config_key + '`', hidden=True)
@slash.subcommand(
base="config",
name="set",
description="Changes a configuration entry",
options=[
config_option,
create_option(
name="value",
description="The value to set",
option_type=str,
required=True
)
],
guild_ids=slash_guild_ids
)
async def config_set_slash(ctx: SlashContext, config_key: str, value: str):
if not await check_slash_context(ctx, True):
return
if ctx.author.guild_permissions.administrator:
await ctx.send(config_set(config_key, value, ctx.guild.id), hidden=True)
else:
await ctx.send('You\'re not allowed to change the configuration on this server!', hidden=True)
bot.run(TOKEN)

View File

@@ -22,3 +22,10 @@ async def find_role_case_insensitive(guild: discord.Guild, name: str, prefix: st
if name.strip().lower() == role.name[len(prefix):].lower():
return role
return None
def link_channel(channel: discord.abc.GuildChannel, italic: bool = False) -> str:
if italic:
return '[*' + channel.name + '*](https://discord.com/channels/' + str(channel.guild.id) + '/' + str(
channel.id) + ')'
return '[' + channel.name + '](https://discord.com/channels/' + str(channel.guild.id) + '/' + str(channel.id) + ')'

View File

@@ -1,4 +1,5 @@
discord
discord-py-slash-command>=2
python-dotenv
PyNaCl
ffmpeg