Make users able to set a default name for their channels

This commit is contained in:
Elnath 2021-01-04 22:31:06 +01:00
parent 6cdd236d48
commit c54e7af00a
4 changed files with 155 additions and 4 deletions

View File

@ -2,19 +2,21 @@ import atexit
import json
import logging
from pathlib import Path
from typing import Union, Dict, List, Callable, Tuple
from typing import Union, Dict, List, Callable, Tuple, Any, Optional
import discord
import utils
from . import vNoneTov1_0
from . import v1_0Tov1_1
logger = logging.getLogger(__name__)
# Functions used to convert between configuration versions, in a dictionary of: old_version -> converter
# Each converter takes the old configuration and returns the converted configuration and the new version
config_version_converters: Dict[Union[None, str], Callable[[Dict], Tuple[Dict, str]]] = {
None: vNoneTov1_0.convert
None: vNoneTov1_0.convert,
"1.0": v1_0Tov1_1.convert,
}
@ -22,7 +24,7 @@ class ChannelsConfigFile:
"""
Wrapper for the channels configuration file, telling which channels to watch and which channels were created by the bot.
"""
version = "1.0"
version = "1.1"
@staticmethod
def empty_config() -> Dict:
@ -121,6 +123,7 @@ class ChannelsConfigFile:
"_name": guild.name,
"watched_channels": [],
"created_channels": [],
"user_config": {},
}
return self.config[str_guild_id]
@ -202,3 +205,65 @@ class ChannelsConfigFile:
:return: Whether the given channel is one of the channels that have been created by the bot
"""
return channel.id in self._get_created_channels(guild)
def _get_user_configs(self, guild: discord.Guild) -> Dict[str, Dict]:
"""
:return: The configuration for all users in a guild
"""
guild_configuration = self._get_guild_configuration(guild)
if "user_config" not in guild_configuration:
guild_configuration["user_config"] = {}
return guild_configuration["user_config"]
def _get_user_config(self, guild: discord.Guild, user: discord.Member) -> Dict[str, Any]:
"""
:return: The configuration for a user in a guild
"""
all_users_configs = self._get_user_configs(guild)
user_id_str = str(user.id)
if user_id_str not in all_users_configs:
all_users_configs[user_id_str] = {
"__username": f"{user.name}#{user.discriminator}",
"channel_name": None,
}
return all_users_configs[user_id_str]
def _clean_user_config_if_useless(self, guild: discord.Guild, user: discord.Member):
"""
If some of the information for a user in the user config of a guild is useless, remove it.
If the whole entry for a user in the user config of a guild is useless (i.e. it contains only its username), delete it.
"""
all_users_config = self._get_user_configs(guild)
user_id_str = str(user.id)
user_config = all_users_config[user_id_str] if user_id_str in all_users_config else {}
# If the default channel name for this user is set to None, it is the same as not being set
if "channel_name" in user_config and user_config["channel_name"] is None:
del user_config["channel_name"]
# If the only information remaining is the username (or nothing), delete the entry
if list(user_config.keys()) in ([], ["__username"]):
del all_users_config[user_id_str]
logger.debug(f"[{guild.name}({guild.id})] Deleted user {user.display_name}({user.id}) configuration because it was useless")
def set_user_channel_name(self, guild: discord.Guild, user: discord.Member, channel_name: str) -> None:
"""
Set the initial name for the channels created for a specific user
"""
user_config = self._get_user_config(guild, user)
user_config["channel_name"] = channel_name
def get_user_channel_name(self, guild: discord.Guild, user: discord.Member) -> Optional[str]:
"""
:return: The initial name for the channels created for a specific user, if the user set one
"""
user_config = self._get_user_config(guild, user)
channel_name = user_config["channel_name"] if "channel_name" in user_config else None
self._clean_user_config_if_useless(guild, user)
return channel_name
def clear_user_channel_name(self, guild: discord.Guild, user: discord.Member):
"""
Remove the user preferences about the names for their channels
"""
user_config = self._get_user_config(guild, user)
user_config["channel_name"] = None
self._clean_user_config_if_useless(guild, user)

View File

@ -0,0 +1,21 @@
"""
Converter for converting ChannelsConfigFile from version 1.0 to version 1.1
"""
from typing import Dict, Tuple
def convert(config: Dict) -> Tuple[Dict, str]:
assert config["__version__"] == "1.0"
for config_key, config_value in config.items():
# We skip the version information
if config_key == "__version__":
continue
# All the other entries are guild configuration
guild_config = config_value
if "user_config" not in guild_config:
guild_config["user_config"] = {}
config["__version__"] = "1.1"
return config, "1.1"

View File

@ -3,6 +3,7 @@ import argparse
import logging
import sys
from pathlib import Path
from typing import List
import discord
import discord.utils
@ -150,6 +151,44 @@ class VocalMaisBot(commands.Cog):
self.channels_config.clear_watched_channels(ctx.guild)
await ctx.send(":white_check_mark: I am no longer watching any channel")
@commands.command(
"set_name",
brief = "Set the default name for your voice channels",
help = "Set the default name for your voice channels. If you put {user} in the name, it will be replaced by your nickname",
usage = "<name>"
)
async def set_user_default_channel_name(self, ctx: commands.Context, *name_elements: str):
# Verify that the given name does not mention anyone by checking the message mentions
if len(set(ctx.message.mentions) - {self.bot.user}) > 0:
await ctx.send(":no_entry: User mentions are not allowed in channel names. Use {user} if you want to include your name.")
return
name = " ".join(name_elements)
if len(name) == 0:
await ctx.send(f":x: Missing channel name! {self.get_command_usage_message(ctx.command)}")
raise discord_utils.CheckFailDoNotNotify
self.channels_config.set_user_channel_name(ctx.guild, ctx.author, discord_utils.voice_channel_safe_name(name))
self.channels_config.save_to_file()
# We escape the markdown from the channel name when sending the message because for now discord does not support markdown in channel names,
# so we do not want the users to get their hopes up by seeing markdown working in the message
await ctx.send(f":white_check_mark: The default name for your channels has been set! If you create one now, it will appear as {discord_utils.voice_channel_safe_name(name, user_name_replacement = ctx.author.display_name, escape_markdown = True)}")
@commands.command("get_name", help = "Get the default name for your voice channels")
async def get_user_default_channel_name(self, ctx: commands.Context):
channel_name = self.channels_config.get_user_channel_name(ctx.guild, ctx.author)
if channel_name is None:
await ctx.send(":person_shrugging: You have not set a special name for your voice channels")
else:
channel_name = discord_utils.voice_channel_safe_name(channel_name, user_name_replacement = ctx.author.display_name, escape_markdown = True)
await ctx.send(f":memo: If you create a voice channel right now, it will be named like this: {channel_name}")
@commands.command("clear_name", help = "Do not use a special name for your voice channels")
async def clear_user_default_channel_name(self, ctx: commands.Context):
self.channels_config.clear_user_channel_name(ctx.guild, ctx.author)
self.channels_config.save_to_file()
await ctx.send(":white_check_mark: Your voice channels will use the default naming convention")
@commands.Cog.listener()
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
if before.channel is not None: # They left a channel
@ -173,7 +212,10 @@ class VocalMaisBot(commands.Cog):
channel_permissions[user].manage_channels = True # We allow the user for which we created the channel to change the channel's name
# Computing the channel name
channel_name = f"{user.display_name}'s channel"
user_default_channel_name = self.channels_config.get_user_channel_name(channel.guild, user)
if user_default_channel_name is None:
user_default_channel_name = "{user}'s channel"
channel_name = discord_utils.voice_channel_safe_name(user_default_channel_name, user_name_replacement = user.display_name)
# Creating the channel and moving the user into it
user_channel = await category.create_voice_channel(channel_name, overwrites = channel_permissions)

View File

@ -1,3 +1,4 @@
import discord
import discord.ext.commands as commands
@ -7,3 +8,25 @@ class CheckFailDoNotNotify(commands.CheckFailure):
For example it can be used if the check function already sent the user a customised error message.
"""
pass
def voice_channel_safe_name(template: str, max_chars: int = 100, escape_mentions: bool = True, escape_markdown: bool = False, user_name_replacement: str = None) -> str:
"""
Make a name that is suitable for naming voice channels from a template (e.g. found in the config file or given by a user)
:param template: The template used as basis for the name
:param max_chars: The name is ellipsed if it exceeds this number of characters. For a discord voice channel, it is currently 100 chars
:param escape_mentions: If true, escape role and user mentions
:param escape_markdown: If true, escape discord's markdown. For now, discord does not support markdown in channel names anyways
:param user_name_replacement: If not None, replace occurrences of '{user}' in the message with this value
"""
message = template
if user_name_replacement is not None:
message = message.replace("{user}", user_name_replacement)
if escape_mentions:
message = discord.utils.escape_mentions(message)
if escape_markdown:
message = discord.utils.escape_markdown(message)
if len(message) > max_chars:
message = message[:max_chars - 3] + "..."
return message