(guild admin): Add greeting messages.
New `SettingType` `Message` for general message settings. New setting `greeting_message`. New setting `greeting_channel`. New setting `starting_funds`. New setting `returning_message`. Add a greeting message hook. Add initial funds on lion creation. Data migration v3 -> v4.
This commit is contained in:
@@ -1,2 +1,2 @@
|
|||||||
CONFIG_FILE = "config/bot.conf"
|
CONFIG_FILE = "config/bot.conf"
|
||||||
DATA_VERSION = 3
|
DATA_VERSION = 4
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ lions = RowTable(
|
|||||||
'workout_count', 'last_workout_start',
|
'workout_count', 'last_workout_start',
|
||||||
'last_study_badgeid',
|
'last_study_badgeid',
|
||||||
'video_warned',
|
'video_warned',
|
||||||
|
'_timestamp'
|
||||||
),
|
),
|
||||||
('guildid', 'userid'),
|
('guildid', 'userid'),
|
||||||
cache=TTLCache(5000, ttl=60*5),
|
cache=TTLCache(5000, ttl=60*5),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import pytz
|
|||||||
|
|
||||||
from meta import client
|
from meta import client
|
||||||
from data import tables as tb
|
from data import tables as tb
|
||||||
from settings import UserSettings
|
from settings import UserSettings, GuildSettings
|
||||||
|
|
||||||
|
|
||||||
class Lion:
|
class Lion:
|
||||||
@@ -41,7 +41,13 @@ class Lion:
|
|||||||
if key in cls._lions:
|
if key in cls._lions:
|
||||||
return cls._lions[key]
|
return cls._lions[key]
|
||||||
else:
|
else:
|
||||||
tb.lions.fetch_or_create(key)
|
lion = tb.lions.fetch(key)
|
||||||
|
if not lion:
|
||||||
|
tb.lions.create_row(
|
||||||
|
guildid=guildid,
|
||||||
|
userid=userid,
|
||||||
|
coins=GuildSettings(guildid).starting_funds.value
|
||||||
|
)
|
||||||
return cls(guildid, userid)
|
return cls(guildid, userid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ from .module import module
|
|||||||
|
|
||||||
from . import guild_config
|
from . import guild_config
|
||||||
from . import statreset
|
from . import statreset
|
||||||
|
from . import utils
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from .module import module
|
|||||||
|
|
||||||
# Pages of configuration categories to display
|
# Pages of configuration categories to display
|
||||||
cat_pages = {
|
cat_pages = {
|
||||||
'Administration': ('Meta', 'Guild Roles'),
|
'Administration': ('Meta', 'Guild Roles', 'Greetings', 'Farewells'),
|
||||||
'Moderation': ('Moderation', 'Video Channels'),
|
'Moderation': ('Moderation', 'Video Channels'),
|
||||||
'Productivity': ('Study Tracking', 'TODO List', 'Workout'),
|
'Productivity': ('Study Tracking', 'TODO List', 'Workout'),
|
||||||
'Study Rooms': ('Rented Rooms', 'Accountability Rooms'),
|
'Study Rooms': ('Rented Rooms', 'Accountability Rooms'),
|
||||||
@@ -21,6 +21,7 @@ cat_pages = {
|
|||||||
descriptions = {
|
descriptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@module.cmd("config",
|
@module.cmd("config",
|
||||||
desc="View and modify the server settings.",
|
desc="View and modify the server settings.",
|
||||||
flags=('add', 'remove'),
|
flags=('add', 'remove'),
|
||||||
@@ -91,27 +92,27 @@ async def cmd_config(ctx, flags):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(parts) == 1:
|
if len(parts) == 1 and not ctx.msg.attachments:
|
||||||
# config <setting>
|
# config <setting>
|
||||||
# View config embed for provided setting
|
# View config embed for provided setting
|
||||||
await ctx.reply(embed=setting.get(ctx.guild.id).embed)
|
await setting.get(ctx.guild.id).widget(ctx, flags=flags)
|
||||||
else:
|
else:
|
||||||
# config <setting> <value>
|
# config <setting> <value>
|
||||||
# Check the write ward
|
# Check the write ward
|
||||||
if not await setting.write_ward.run(ctx):
|
if not await setting.write_ward.run(ctx):
|
||||||
await ctx.error_reply(setting.msg)
|
await ctx.error_reply(setting.write_ward.msg)
|
||||||
|
|
||||||
# Attempt to set config setting
|
# Attempt to set config setting
|
||||||
try:
|
try:
|
||||||
parsed = await setting.parse(ctx.guild.id, ctx, parts[1])
|
parsed = await setting.parse(ctx.guild.id, ctx, parts[1] if len(parts) > 1 else '')
|
||||||
parsed.write(add_only=flags['add'], remove_only=flags['remove'])
|
parsed.write(add_only=flags['add'], remove_only=flags['remove'])
|
||||||
except UserInputError as e:
|
except UserInputError as e:
|
||||||
await ctx.reply(embed=discord.Embed(
|
await ctx.reply(embed=discord.Embed(
|
||||||
description="{} {}".format('❌', e.msg),
|
description="{} {}".format('❌', e.msg),
|
||||||
Colour=discord.Colour.red()
|
colour=discord.Colour.red()
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
await ctx.reply(embed=discord.Embed(
|
await ctx.reply(embed=discord.Embed(
|
||||||
description="{} {}".format('✅', setting.get(ctx.guild.id).success_response),
|
description="{} {}".format('✅', setting.get(ctx.guild.id).success_response),
|
||||||
Colour=discord.Colour.green()
|
colour=discord.Colour.green()
|
||||||
))
|
))
|
||||||
|
|||||||
2
bot/modules/guild_admin/utils/__init__.py
Normal file
2
bot/modules/guild_admin/utils/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import settings
|
||||||
|
from . import greetings
|
||||||
29
bot/modules/guild_admin/utils/greetings.py
Normal file
29
bot/modules/guild_admin/utils/greetings.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import discord
|
||||||
|
from cmdClient.Context import Context
|
||||||
|
|
||||||
|
from meta import client
|
||||||
|
|
||||||
|
from .settings import greeting_message, greeting_channel, returning_message
|
||||||
|
|
||||||
|
|
||||||
|
@client.add_after_event('member_join')
|
||||||
|
async def send_greetings(client, member):
|
||||||
|
guild = member.guild
|
||||||
|
|
||||||
|
returning = bool(client.data.lions.fetch((guild.id, member.id)))
|
||||||
|
|
||||||
|
# Handle greeting message
|
||||||
|
channel = greeting_channel.get(guild.id).value
|
||||||
|
if channel is not None:
|
||||||
|
if channel == greeting_channel.DMCHANNEL:
|
||||||
|
channel = member
|
||||||
|
|
||||||
|
ctx = Context(client, guild=guild, author=member)
|
||||||
|
if returning:
|
||||||
|
args = returning_message.get(guild.id).args(ctx)
|
||||||
|
else:
|
||||||
|
args = greeting_message.get(guild.id).args(ctx)
|
||||||
|
try:
|
||||||
|
await channel.send(**args)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
206
bot/modules/guild_admin/utils/settings.py
Normal file
206
bot/modules/guild_admin/utils/settings.py
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import datetime
|
||||||
|
import discord
|
||||||
|
|
||||||
|
from settings import GuildSettings, GuildSetting
|
||||||
|
import settings.setting_types as stypes
|
||||||
|
|
||||||
|
|
||||||
|
@GuildSettings.attach_setting
|
||||||
|
class greeting_channel(stypes.Channel, GuildSetting):
|
||||||
|
"""
|
||||||
|
Setting describing the destination of the greeting message.
|
||||||
|
|
||||||
|
Extended to support the following special values, with input and output supported.
|
||||||
|
Data `None` corresponds to `Off`.
|
||||||
|
Data `1` corresponds to `DM`.
|
||||||
|
"""
|
||||||
|
DMCHANNEL = object()
|
||||||
|
|
||||||
|
category = "Greetings"
|
||||||
|
|
||||||
|
attr_name = 'greeting_channel'
|
||||||
|
_data_column = 'greeting_channel'
|
||||||
|
|
||||||
|
display_name = "greeting_channel"
|
||||||
|
desc = "Channel to send the greeting message in"
|
||||||
|
|
||||||
|
long_desc = (
|
||||||
|
"Channel to post the `greeting_message` in when a new user joins the server. "
|
||||||
|
"Accepts `DM` to indicate the greeting should be direct messaged to the new member."
|
||||||
|
)
|
||||||
|
_accepts = (
|
||||||
|
"Text Channel name/id/mention, or `DM`, or `None` to disable."
|
||||||
|
)
|
||||||
|
_chan_type = discord.ChannelType.text
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _data_to_value(cls, id, data, **kwargs):
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
elif data == 1:
|
||||||
|
return cls.DMCHANNEL
|
||||||
|
else:
|
||||||
|
return super()._data_to_value(id, data, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _data_from_value(cls, id, value, **kwargs):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
elif value == cls.DMCHANNEL:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return super()._data_from_value(id, value, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def _parse_userstr(cls, ctx, id, userstr, **kwargs):
|
||||||
|
lower = userstr.lower()
|
||||||
|
if lower in ('0', 'none', 'off'):
|
||||||
|
return None
|
||||||
|
elif lower == 'dm':
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return await super()._parse_userstr(ctx, id, userstr, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _format_data(cls, id, data, **kwargs):
|
||||||
|
if data is None:
|
||||||
|
return "Off"
|
||||||
|
elif data == 1:
|
||||||
|
return "DM"
|
||||||
|
else:
|
||||||
|
return "<#{}>".format(data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def success_response(self):
|
||||||
|
value = self.value
|
||||||
|
if not value:
|
||||||
|
return "Greeting messages are disabled."
|
||||||
|
elif value == self.DMCHANNEL:
|
||||||
|
return "Greeting messages will be sent via direct message."
|
||||||
|
else:
|
||||||
|
return "Greeting messages will be posted in {}".format(self.formatted)
|
||||||
|
|
||||||
|
|
||||||
|
@GuildSettings.attach_setting
|
||||||
|
class greeting_message(stypes.Message, GuildSetting):
|
||||||
|
category = "Greetings"
|
||||||
|
|
||||||
|
attr_name = 'greeting_message'
|
||||||
|
_data_column = 'greeting_message'
|
||||||
|
|
||||||
|
display_name = 'greeting_message'
|
||||||
|
desc = "Greeting message sent to welcome new members."
|
||||||
|
|
||||||
|
long_desc = (
|
||||||
|
"Message to send to the configured `greeting_channel` when a member joins the server for the first time."
|
||||||
|
)
|
||||||
|
|
||||||
|
_default = r"""
|
||||||
|
{
|
||||||
|
"embed": {
|
||||||
|
"title": "Welcome!",
|
||||||
|
"thumbnail": {"url": "{guild_icon}"},
|
||||||
|
"description": "Hi {mention}!\nWelcome to **{guild_name}**! You are the **{member_count}**th member.\nThere are currently **{studying_count}** people studying.\nGood luck and stay productive!",
|
||||||
|
"color": 15695665
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_substitution_desc = {
|
||||||
|
'{mention}': "Mention the new member.",
|
||||||
|
'{user_name}': "Username of the new member.",
|
||||||
|
'{user_avatar}': "Avatar of the new member.",
|
||||||
|
'{guild_name}': "Name of this server.",
|
||||||
|
'{guild_icon}': "Server icon url.",
|
||||||
|
'{member_count}': "Number of members in the server.",
|
||||||
|
'{studying_count}': "Number of current voice channel members.",
|
||||||
|
}
|
||||||
|
|
||||||
|
def substitution_keys(self, ctx, **kwargs):
|
||||||
|
return {
|
||||||
|
'{mention}': ctx.author.mention,
|
||||||
|
'{user_name}': ctx.author.name,
|
||||||
|
'{user_avatar}': str(ctx.author.avatar_url),
|
||||||
|
'{guild_name}': ctx.guild.name,
|
||||||
|
'{guild_icon}': str(ctx.guild.icon_url),
|
||||||
|
'{member_count}': str(len(ctx.guild.members)),
|
||||||
|
'{studying_count}': str(len([member for ch in ctx.guild.voice_channels for member in ch.members]))
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def success_response(self):
|
||||||
|
return "The greeting message has been set!"
|
||||||
|
|
||||||
|
|
||||||
|
@GuildSettings.attach_setting
|
||||||
|
class returning_message(stypes.Message, GuildSetting):
|
||||||
|
category = "Greetings"
|
||||||
|
|
||||||
|
attr_name = 'returning_message'
|
||||||
|
_data_column = 'returning_message'
|
||||||
|
|
||||||
|
display_name = 'returning_message'
|
||||||
|
desc = "Greeting message sent to returning members."
|
||||||
|
|
||||||
|
long_desc = (
|
||||||
|
"Message to send to the configured `greeting_channel` when a member returns to the server."
|
||||||
|
)
|
||||||
|
|
||||||
|
_default = r"""
|
||||||
|
{
|
||||||
|
"embed": {
|
||||||
|
"title": "Welcome Back {user_name}!",
|
||||||
|
"thumbnail": {"url": "{guild_icon}"},
|
||||||
|
"description": "Welcome back to **{guild_name}**!\nYou last studied with us <t:{last_time}:R>.\nThere are currently **{studying_count}** people studying.\nGood luck and stay productive!",
|
||||||
|
"color": 15695665
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_substitution_desc = {
|
||||||
|
'{mention}': "Mention the returning member.",
|
||||||
|
'{user_name}': "Username of the member.",
|
||||||
|
'{user_avatar}': "Avatar of the member.",
|
||||||
|
'{guild_name}': "Name of this server.",
|
||||||
|
'{guild_icon}': "Server icon url.",
|
||||||
|
'{member_count}': "Number of members in the server.",
|
||||||
|
'{studying_count}': "Number of current voice channel members.",
|
||||||
|
'{last_time}': "Unix timestamp of the last time the member studied.",
|
||||||
|
}
|
||||||
|
|
||||||
|
def substitution_keys(self, ctx, **kwargs):
|
||||||
|
return {
|
||||||
|
'{mention}': ctx.author.mention,
|
||||||
|
'{user_name}': ctx.author.name,
|
||||||
|
'{user_avatar}': str(ctx.author.avatar_url),
|
||||||
|
'{guild_name}': ctx.guild.name,
|
||||||
|
'{guild_icon}': str(ctx.guild.icon_url),
|
||||||
|
'{member_count}': str(len(ctx.guild.members)),
|
||||||
|
'{studying_count}': str(len([member for ch in ctx.guild.voice_channels for member in ch.members])),
|
||||||
|
'{last_time}': int(ctx.alion.data._timestamp.replace(tzinfo=datetime.timezone.utc).timestamp()),
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def success_response(self):
|
||||||
|
return "The returning message has been set!"
|
||||||
|
|
||||||
|
|
||||||
|
@GuildSettings.attach_setting
|
||||||
|
class starting_funds(stypes.Integer, GuildSetting):
|
||||||
|
category = "Greetings"
|
||||||
|
|
||||||
|
attr_name = 'starting_funds'
|
||||||
|
_data_column = 'starting_funds'
|
||||||
|
|
||||||
|
display_name = 'starting_funds'
|
||||||
|
desc = "Coins given when a user first joins."
|
||||||
|
|
||||||
|
long_desc = (
|
||||||
|
"Members will be given this number of coins the first time they join the server."
|
||||||
|
)
|
||||||
|
|
||||||
|
_default = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def success_response(self):
|
||||||
|
return "Members will be given `{}` coins when they first join the server.".format(self.formatted)
|
||||||
@@ -51,6 +51,14 @@ class Setting:
|
|||||||
embed.description = "{}\n{}".format(self.long_desc.format(self=self, client=self.client), table)
|
embed.description = "{}\n{}".format(self.long_desc.format(self=self, client=self.client), table)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
async def widget(self, ctx: Context, **kwargs):
|
||||||
|
"""
|
||||||
|
Show the setting widget for this setting.
|
||||||
|
By default this displays the setting embed.
|
||||||
|
Settings may override this if they need more complex widget context or logic.
|
||||||
|
"""
|
||||||
|
return await ctx.reply(embed=self.embed)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def summary(self):
|
def summary(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import json
|
||||||
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
|
from io import StringIO
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
@@ -9,11 +12,14 @@ from cmdClient.Context import Context
|
|||||||
from cmdClient.lib import SafeCancellation
|
from cmdClient.lib import SafeCancellation
|
||||||
|
|
||||||
from meta import client
|
from meta import client
|
||||||
from utils.lib import parse_dur, strfdur, strfdelta
|
from utils.lib import parse_dur, strfdur, strfdelta, prop_tabulate, multiple_replace
|
||||||
|
|
||||||
from .base import UserInputError
|
from .base import UserInputError
|
||||||
|
|
||||||
|
|
||||||
|
preview_emoji = '🔍'
|
||||||
|
|
||||||
|
|
||||||
class SettingType:
|
class SettingType:
|
||||||
"""
|
"""
|
||||||
Abstract class representing a setting type.
|
Abstract class representing a setting type.
|
||||||
@@ -672,6 +678,209 @@ class Duration(SettingType):
|
|||||||
return "`{}`".format(strfdelta(datetime.timedelta(seconds=data)))
|
return "`{}`".format(strfdelta(datetime.timedelta(seconds=data)))
|
||||||
|
|
||||||
|
|
||||||
|
class Message(SettingType):
|
||||||
|
"""
|
||||||
|
Message type storing json-encoded message arguments.
|
||||||
|
Messages without an embed are displayed differently from those with an embed.
|
||||||
|
|
||||||
|
Types:
|
||||||
|
data: str
|
||||||
|
A json dictionary with the fields `content` and `embed`.
|
||||||
|
value: dict
|
||||||
|
An argument dictionary suitable for `Message.send` or `Message.edit`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_substitution_desc = {
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _data_from_value(cls, id, value, **kwargs):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return json.dumps(value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _data_to_value(cls, id, data, **kwargs):
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return json.loads(data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def parse(cls, id: int, ctx: Context, userstr: str, **kwargs):
|
||||||
|
"""
|
||||||
|
Return a setting instance initialised from a parsed user string.
|
||||||
|
"""
|
||||||
|
if ctx.msg.attachments:
|
||||||
|
attachment = ctx.msg.attachments[0]
|
||||||
|
if 'text' in attachment.content_type or 'json' in attachment.content_type:
|
||||||
|
userstr = (await attachment.read()).decode()
|
||||||
|
data = await cls._parse_userstr(ctx, id, userstr, as_json=True, **kwargs)
|
||||||
|
else:
|
||||||
|
raise UserInputError("Can't read the attached file!")
|
||||||
|
else:
|
||||||
|
data = await cls._parse_userstr(ctx, id, userstr, **kwargs)
|
||||||
|
return cls(id, data, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def _parse_userstr(cls, ctx, id, userstr, as_json=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Parse the provided string as either a content-only string, or json-format arguments.
|
||||||
|
Provided string is not trusted, and is parsed in a safe manner.
|
||||||
|
"""
|
||||||
|
if userstr.lower() == 'none':
|
||||||
|
return None
|
||||||
|
|
||||||
|
if as_json:
|
||||||
|
try:
|
||||||
|
args = json.loads(userstr)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise UserInputError(
|
||||||
|
"Couldn't parse your message! "
|
||||||
|
"You can test and fix it on the embed builder "
|
||||||
|
"[here](https://glitchii.github.io/embedbuilder/?editor=json)."
|
||||||
|
)
|
||||||
|
if 'embed' in args and 'timestamp' in args['embed']:
|
||||||
|
args['embed'].pop('timestamp')
|
||||||
|
return json.dumps(args)
|
||||||
|
else:
|
||||||
|
return json.dumps({'content': userstr})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _format_data(cls, id, data, **kwargs):
|
||||||
|
if data is None:
|
||||||
|
return "Empty"
|
||||||
|
value = cls._data_to_value(id, data, **kwargs)
|
||||||
|
if 'embed' not in value and len(value['content']) < 100:
|
||||||
|
return "`{}`".format(value['content'])
|
||||||
|
else:
|
||||||
|
return "Too long to display here!"
|
||||||
|
|
||||||
|
def substitution_keys(self, ctx, **kwargs):
|
||||||
|
"""
|
||||||
|
Instances should override this to provide their own substitution implementation.
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def args(self, ctx, **kwargs):
|
||||||
|
"""
|
||||||
|
Applies the substitutions with the given context to generate the final message args.
|
||||||
|
"""
|
||||||
|
value = self.value
|
||||||
|
substitutions = self.substitution_keys(ctx, **kwargs)
|
||||||
|
args = {}
|
||||||
|
if 'content' in value:
|
||||||
|
args['content'] = multiple_replace(value['content'], substitutions)
|
||||||
|
if 'embed' in value:
|
||||||
|
args['embed'] = discord.Embed.from_dict(
|
||||||
|
json.loads(multiple_replace(json.dumps(value['embed']), substitutions))
|
||||||
|
)
|
||||||
|
return args
|
||||||
|
|
||||||
|
async def widget(self, ctx, **kwargs):
|
||||||
|
value = self.value
|
||||||
|
args = self.args(ctx, **kwargs)
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
return await ctx.reply(embed=self.embed)
|
||||||
|
|
||||||
|
current_str = None
|
||||||
|
preview = None
|
||||||
|
file_content = None
|
||||||
|
if 'embed' in value or len(value['content']) > 1024:
|
||||||
|
current_str = "See attached file."
|
||||||
|
file_content = json.dumps(value, indent=4)
|
||||||
|
elif "`" in value['content']:
|
||||||
|
current_str = "```{}```".format(value['content'])
|
||||||
|
if len(args['content']) < 1000:
|
||||||
|
preview = args['content']
|
||||||
|
else:
|
||||||
|
current_str = "`{}`".format(value['content'])
|
||||||
|
if len(args['content']) < 1000:
|
||||||
|
preview = args['content']
|
||||||
|
|
||||||
|
description = "{}\n\n**Current Value**: {}".format(
|
||||||
|
self.long_desc.format(self=self, client=self.client),
|
||||||
|
current_str
|
||||||
|
)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Configuration options for `{}`".format(self.display_name),
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
if preview:
|
||||||
|
embed.add_field(name="Message Preview", value=preview, inline=False)
|
||||||
|
embed.add_field(
|
||||||
|
name="Setting Guide",
|
||||||
|
value=(
|
||||||
|
"• For plain text without an embed, use `{prefix}config {setting} <text>`.\n"
|
||||||
|
"• To include an embed, build the message [here]({builder}) "
|
||||||
|
"and upload the json code as a file with the `{prefix}config {setting}` command.\n"
|
||||||
|
"• To reset the message to the default, use `{prefix}config {setting} None`."
|
||||||
|
).format(
|
||||||
|
prefix=ctx.best_prefix,
|
||||||
|
setting=self.display_name,
|
||||||
|
builder="https://glitchii.github.io/embedbuilder/?editor=gui"
|
||||||
|
),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
if self._substitution_desc:
|
||||||
|
embed.add_field(
|
||||||
|
name="Substitution Keys",
|
||||||
|
value=(
|
||||||
|
"*The following keys will be substituted for their current values.*\n{}"
|
||||||
|
).format(
|
||||||
|
prop_tabulate(*zip(*self._substitution_desc.items()), colon=False)
|
||||||
|
),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
embed.set_footer(
|
||||||
|
text="React with {} to preview the message.".format(preview_emoji)
|
||||||
|
)
|
||||||
|
if file_content:
|
||||||
|
with StringIO() as message_file:
|
||||||
|
message_file.write(file_content)
|
||||||
|
message_file.seek(0)
|
||||||
|
out_file = discord.File(message_file, filename="{}.json".format(self.display_name))
|
||||||
|
out_msg = await ctx.reply(embed=embed, file=out_file)
|
||||||
|
else:
|
||||||
|
out_msg = await ctx.reply(embed=embed)
|
||||||
|
|
||||||
|
# Add the preview reaction and send the preview when requested
|
||||||
|
try:
|
||||||
|
await out_msg.add_reaction(preview_emoji)
|
||||||
|
except discord.HTTPException:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ctx.client.wait_for(
|
||||||
|
'reaction_add',
|
||||||
|
check=lambda r, u: r.message.id == out_msg.id and r.emoji == preview_emoji and u == ctx.author,
|
||||||
|
timeout=180
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
try:
|
||||||
|
await out_msg.remove_reaction(preview_emoji, ctx.client.user)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
await ctx.offer_delete(
|
||||||
|
await ctx.reply(**args, allowed_mentions=discord.AllowedMentions.none())
|
||||||
|
)
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
await ctx.reply(
|
||||||
|
embed=discord.Embed(
|
||||||
|
colour=discord.Colour.red(),
|
||||||
|
title="Preview failed! Error below",
|
||||||
|
description="```{}```".format(
|
||||||
|
e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SettingList(SettingType):
|
class SettingList(SettingType):
|
||||||
"""
|
"""
|
||||||
List of a particular type of setting.
|
List of a particular type of setting.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ tick = '✅'
|
|||||||
cross = '❌'
|
cross = '❌'
|
||||||
|
|
||||||
|
|
||||||
def prop_tabulate(prop_list, value_list, indent=True):
|
def prop_tabulate(prop_list, value_list, indent=True, colon=True):
|
||||||
"""
|
"""
|
||||||
Turns a list of properties and corresponding list of values into
|
Turns a list of properties and corresponding list of values into
|
||||||
a pretty string with one `prop: value` pair each line,
|
a pretty string with one `prop: value` pair each line,
|
||||||
@@ -39,7 +39,7 @@ def prop_tabulate(prop_list, value_list, indent=True):
|
|||||||
max_len = max(len(prop) for prop in prop_list)
|
max_len = max(len(prop) for prop in prop_list)
|
||||||
return "".join(["`{}{}{}`\t{}{}".format(" " * (max_len - len(prop)) if indent else "",
|
return "".join(["`{}{}{}`\t{}{}".format(" " * (max_len - len(prop)) if indent else "",
|
||||||
prop,
|
prop,
|
||||||
":" if len(prop) else " " * 2,
|
(":" if len(prop) else " " * 2) if colon else '',
|
||||||
value_list[i],
|
value_list[i],
|
||||||
'' if str(value_list[i]).endswith("```") else '\n')
|
'' if str(value_list[i]).endswith("```") else '\n')
|
||||||
for i, prop in enumerate(prop_list)])
|
for i, prop in enumerate(prop_list)])
|
||||||
@@ -528,3 +528,14 @@ def utc_now():
|
|||||||
Return the current timezone-aware utc timestamp.
|
Return the current timezone-aware utc timestamp.
|
||||||
"""
|
"""
|
||||||
return datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
|
return datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def multiple_replace(string, rep_dict):
|
||||||
|
if rep_dict:
|
||||||
|
pattern = re.compile(
|
||||||
|
"|".join([re.escape(k) for k in sorted(rep_dict, key=len, reverse=True)]),
|
||||||
|
flags=re.DOTALL
|
||||||
|
)
|
||||||
|
return pattern.sub(lambda x: str(rep_dict[x.group(0)]), string)
|
||||||
|
else:
|
||||||
|
return string
|
||||||
|
|||||||
7
data/migration/v3-v4/migration.sql
Normal file
7
data/migration/v3-v4/migration.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE guild_config
|
||||||
|
ADD COLUMN greeting_channel BIGINT,
|
||||||
|
ADD COLUMN greeting_message TEXT,
|
||||||
|
ADD COLUMN returning_message TEXT,
|
||||||
|
ADD COLUMN starting_funds INTEGER;
|
||||||
|
|
||||||
|
INSERT INTO VersionHistory (version, author) VALUES (4, 'v3-v4 Migration');
|
||||||
@@ -72,7 +72,11 @@ CREATE TABLE guild_config(
|
|||||||
accountability_reward INTEGER,
|
accountability_reward INTEGER,
|
||||||
accountability_price INTEGER,
|
accountability_price INTEGER,
|
||||||
video_studyban BOOLEAN,
|
video_studyban BOOLEAN,
|
||||||
video_grace_period INTEGER
|
video_grace_period INTEGER,
|
||||||
|
greeting_channel BIGINT,
|
||||||
|
greeting_message TEXT,
|
||||||
|
returning_message TEXT,
|
||||||
|
starting_funds INTEGER
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE ignored_members(
|
CREATE TABLE ignored_members(
|
||||||
|
|||||||
Reference in New Issue
Block a user