Migrate a lot of things to slash commands
This commit is contained in:
920
bot.py
920
bot.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user