Files
croccybot/src/modules/member_admin/settings.py

428 lines
16 KiB
Python

from typing import Any, Optional
import discord
from babel.translator import ctx_translator
from core.data import CoreData
from core.setting_types import MessageSetting
from meta import LionBot
from settings import ListData, ModelData
from settings.groups import SettingGroup
from settings.setting_types import BoolSetting, ChannelSetting, RoleListSetting
from utils.lib import recurse_map, replace_multiple, tabulate
from wards import low_management_iward, high_management_iward
from . import babel
from .data import MemberAdminData
_p = babel._p
_greeting_subkey_desc = {
'{mention}': _p('guildset:greeting_message|formatkey:mention',
"Mention the new member."),
'{user_name}': _p('guildset:greeting_message|formatkey:user_name',
"Display name of the new member."),
'{user_avatar}': _p('guildset:greeting_message|formatkey:user_avatar',
"Avatar url of the new member."),
'{guild_name}': _p('guildset:greeting_message|formatkey:guild_name',
"Name of this server."),
'{guild_icon}': _p('guildset:greeting_message|formatkey:guild_icon',
"Server icon url."),
'{studying_count}': _p('guildset:greeting_message|formatkey:studying_count',
"Number of current voice channel members."),
'{member_count}': _p('guildset:greeting_message|formatkey:member_count',
"Number of members in the server."),
}
class MemberAdminSettings(SettingGroup):
class GreetingChannel(ModelData, ChannelSetting):
setting_id = 'greeting_channel'
_write_ward = low_management_iward
_display_name = _p('guildset:greeting_channel', "welcome_channel")
_desc = _p(
'guildset:greeting_channel|desc',
"Channel in which to welcome new members to the server."
)
_long_desc = _p(
'guildset:greeting_channel|long_desc',
"New members will be sent the configured `welcome_message` in this channel, "
"and returning members will be sent the configured `returning_message`. "
"Unset to send these message via direct message."
)
_accepts = _p(
'guildset:greeting_channel|accepts',
"Name or id of the greeting channel, or 0 for DM."
)
_model = CoreData.Guild
_column = CoreData.Guild.greeting_channel.name
_allow_object = False
@property
def update_message(self) -> str:
t = ctx_translator.get().t
value = self.value
if value is None:
# Greetings will be sent via DM
resp = t(_p(
'guildset:greeting_channel|set_response:unset',
"Welcome messages will now be sent via direct message."
))
else:
resp = t(_p(
'guildset:greeting_channel|set_response:set',
"Welcome messages will now be sent to {channel}"
)).format(channel=value.mention)
return resp
@classmethod
def _format_data(cls, parent_id, data, **kwargs):
t = ctx_translator.get().t
if data is not None:
return f"<#{data}>"
else:
return t(_p(
'guildset:greeting_channel|formmatted:unset',
"Direct Message"
))
class GreetingMessage(ModelData, MessageSetting):
setting_id = 'greeting_message'
_write_ward = low_management_iward
_display_name = _p(
'guildset:greeting_message', "welcome_message"
)
_desc = _p(
'guildset:greeting_message|desc',
"Custom message used to greet new members when they join the server."
)
_long_desc = _p(
'guildset:greeting_message|long_desc',
"When set, this message will be sent to the `welcome_channel` when a *new* member joins the server. "
"If not set, no message will be sent."
)
_accepts = _p(
'guildset:greeting_message|accepts',
"JSON formatted greeting message data"
)
_soft_default = _p(
'guildset:greeting_message|default',
r"""
{
"embed": {
"title": "Welcome {user_name}!",
"thumbnail": {"url": "{user_avatar}"},
"description": "Welcome to **{guild_name}**!",
"footer": {
"text": "You are the {member_count}th member!"
},
"color": 15695665
}
}
"""
)
_model = CoreData.Guild
_column = CoreData.Guild.greeting_message.name
_subkey_desc = _greeting_subkey_desc
@property
def update_message(self) -> str:
t = ctx_translator.get().t
value = self.value
if value is None:
# No greetings
resp = t(_p(
'guildset:greeting_message|set_response:unset',
"Welcome message unset! New members will not be greeted."
))
else:
resp = t(_p(
'guildset:greeting_message|set_response:set',
"The welcome message has been updated."
))
return resp
@classmethod
def _format_data(cls, parent_id, data, **kwargs):
t = ctx_translator.get().t
if data is not None:
return super()._format_data(parent_id, data, **kwargs)
else:
return t(_p(
'guildset:greeting_message|formmatted:unset',
"Not set, members will not be welcomed."
))
@classmethod
async def generate_formatter(cls, bot: LionBot, member: discord.Member, **kwargs):
"""
Generate a formatter function for this message from the given context.
The formatter function both accepts and returns a message data dict.
"""
async def formatter(data_dict: Optional[dict[str, Any]]):
if not data_dict:
return None
guild = member.guild
active = sum(1 for ch in guild.voice_channels for member in ch.members)
mapping = {
'{mention}': member.mention,
'{user_name}': member.display_name,
'{user_avatar}': member.avatar.url if member.avatar else member.default_avatar.url,
'{guild_name}': guild.name,
'{guild_icon}': guild.icon.url if guild.icon else member.default_avatar.url,
'{studying_count}': str(active),
'{member_count}': guild.member_count,
}
recurse_map(
lambda loc, value: replace_multiple(value, mapping) if isinstance(value, str) else value,
data_dict,
)
return data_dict
return formatter
async def editor_callback(self, editor_data):
self.value = editor_data
await self.write()
def _desc_table(self, show_value: Optional[str] = None) -> list[tuple[str, str]]:
lines = super()._desc_table(show_value=show_value)
t = ctx_translator.get().t
keydescs = [
(key, t(value)) for key, value in self._subkey_desc.items()
]
keytable = tabulate(*keydescs, colon='')
expline = t(_p(
'guildset:greeting_message|embed_field|formatkeys|explanation',
"The following placeholders will be substituted with their values."
))
keyfield = (
t(_p('guildset:greeting_message|embed_field|formatkeys|name', "Placeholders")),
expline + '\n' + '\n'.join(f"> {line}" for line in keytable)
)
lines.append(keyfield)
return lines
class ReturningMessage(ModelData, MessageSetting):
setting_id = 'returning_message'
_write_ward = low_management_iward
_display_name = _p(
'guildset:returning_message', "returning_message"
)
_desc = _p(
'guildset:returning_message|desc',
"Custom message used to greet returning members when they rejoin the server."
)
_long_desc = _p(
'guildset:returning_message|long_desc',
"When set, this message will be sent to the `welcome_channel` when a member *returns* to the server. "
"If not set, no message will be sent."
)
_accepts = _p(
'guildset:returning_message|accepts',
"JSON formatted returning message data"
)
_soft_default = _p(
'guildset:returning_message|default',
r"""
{
"embed": {
"title": "Welcome Back {user_name}!",
"thumbnail": {"url": "{User_avatar}"},
"description": "Welcome back to **{guild_name}**!\nYou were last seen <t:{last_time}:R>.",
"color": 15695665
}
}
"""
)
_model = CoreData.Guild
_column = CoreData.Guild.returning_message.name
_subkey_desc_returning = {
'{last_time}': _p('guildset:returning_message|formatkey:last_time',
"Unix timestamp of the last time the member was seen in the server.")
}
_subkey_desc = _greeting_subkey_desc | _subkey_desc_returning
@property
def update_message(self) -> str:
t = ctx_translator.get().t
value = self.value
if value is None:
resp = t(_p(
'guildset:returning_message|set_response:unset',
"Returning member greeting unset! Will use `welcome_message` if set."
))
else:
resp = t(_p(
'guildset:greeting_message|set_response:set',
"The returning member greeting has been updated."
))
return resp
@classmethod
def _format_data(cls, parent_id, data, **kwargs):
t = ctx_translator.get().t
if data is not None:
return super()._format_data(parent_id, data, **kwargs)
else:
return t(_p(
'guildset:greeting_message|formmatted:unset',
"Not set, will use the `welcome_message` if set."
))
@classmethod
async def generate_formatter(cls, bot: LionBot,
member: discord.Member, last_seen: Optional[int],
**kwargs):
"""
Generate a formatter function for this message from the given context.
The formatter function both accepts and returns a message data dict.
"""
async def formatter(data_dict: Optional[dict[str, Any]]):
if not data_dict:
return None
guild = member.guild
active = sum(1 for ch in guild.voice_channels for member in ch.members)
mapping = {
'{mention}': member.mention,
'{user_name}': member.display_name,
'{user_avatar}': member.avatar.url if member.avatar else member.default_avatar.url,
'{guild_name}': guild.name,
'{guild_icon}': guild.icon.url if guild.icon else member.default_avatar.url,
'{studying_count}': str(active),
'{member_count}': str(guild.member_count),
'{last_time}': str(last_seen or member.joined_at.timestamp()),
}
recurse_map(
lambda loc, value: replace_multiple(value, mapping) if isinstance(value, str) else value,
data_dict,
)
return data_dict
return formatter
async def editor_callback(self, editor_data):
self.value = editor_data
await self.write()
def _desc_table(self, show_value: Optional[str] = None) -> list[tuple[str, str]]:
lines = super()._desc_table(show_value=show_value)
t = ctx_translator.get().t
keydescs = [
(key, t(value)) for key, value in self._subkey_desc_returning.items()
]
keytable = tabulate(*keydescs, colon='')
expline = t(_p(
'guildset:returning_message|embed_field|formatkeys|explanation',
"In *addition* to the placeholders supported by `welcome_message`"
))
keyfield = (
t(_p('guildset:returning_message|embed_field|formatkeys|', "Placeholders")),
expline + '\n' + '\n'.join(f"> {line}" for line in keytable)
)
lines.append(keyfield)
return lines
class Autoroles(ListData, RoleListSetting):
setting_id = 'autoroles'
_write_ward = high_management_iward
_display_name = _p(
'guildset:autoroles', "autoroles"
)
_desc = _p(
'guildset:autoroles|desc',
"Roles given to new members when they join the server."
)
_long_desc = _p(
'guildset:autoroles|long_desc',
"These roles will be given when a member joins the server. "
"If `role_persistence` is enabled, these roles will *not* be given to a returning member."
)
_table_interface = MemberAdminData.autoroles
_id_column = 'guildid'
_data_column = 'roleid'
_order_column = 'roleid'
class BotAutoroles(ListData, RoleListSetting):
setting_id = 'bot_autoroles'
_write_ward = high_management_iward
_display_name = _p(
'guildset:bot_autoroles', "bot_autoroles"
)
_desc = _p(
'guildset:bot_autoroles|desc',
"Roles given to new bots when they join the server."
)
_long_desc = _p(
'guildset:bot_autoroles|long_desc',
"These roles will be given when a bot joins the server."
)
_table_interface = MemberAdminData.bot_autoroles
_id_column = 'guildid'
_data_column = 'roleid'
_order_column = 'roleid'
class RolePersistence(ModelData, BoolSetting):
setting_id = 'role_persistence'
_event = 'guildset_role_persistence'
_write_ward = low_management_iward
_display_name = _p('guildset:role_persistence', "role_persistence")
_desc = _p(
'guildset:role_persistence|desc',
"Whether member roles should be restored on rejoin."
)
_long_desc = _p(
'guildset:role_persistence|long_desc',
"If enabled, member roles will be stored when they leave the server, "
"and then restored when they rejoin (instead of giving `autoroles`). "
"Note that this may conflict with other bots who manage join roles."
)
_default = True
_model = CoreData.Guild
_column = CoreData.Guild.persist_roles.name
@property
def update_message(self) -> str:
t = ctx_translator.get().t
value = self.value
if not value:
resp = t(_p(
'guildset:role_persistence|set_response:off',
"Roles will not be restored when members rejoin."
))
else:
resp = t(_p(
'guildset:greeting_message|set_response:on',
"Roles will now be restored when members rejoin."
))
return resp
guild_model_settings = (
GreetingChannel,
GreetingMessage,
ReturningMessage,
RolePersistence,
)