Merge branch 'feature-sponsors' into staging
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from . import data # noqa
|
||||
|
||||
from . import patches
|
||||
|
||||
from .module import module
|
||||
from .lion import Lion
|
||||
from . import blacklists
|
||||
|
||||
@@ -11,6 +11,9 @@ meta = RowTable(
|
||||
attach_as='meta',
|
||||
)
|
||||
|
||||
# TODO: Consider converting to RowTable for per-shard config caching
|
||||
app_config = Table('AppConfig')
|
||||
|
||||
|
||||
user_config = RowTable(
|
||||
'user_config',
|
||||
|
||||
111
bot/core/patches.py
Normal file
111
bot/core/patches.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
Temporary patches for the discord.py library to support new features of the discord API.
|
||||
"""
|
||||
from discord.http import Route, HTTPClient
|
||||
from discord.abc import Messageable
|
||||
from discord.utils import InvalidArgument
|
||||
from discord import File, AllowedMentions
|
||||
|
||||
|
||||
def send_message(self, channel_id, content, *, tts=False, embeds=None,
|
||||
nonce=None, allowed_mentions=None, message_reference=None):
|
||||
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
||||
payload = {}
|
||||
|
||||
if content:
|
||||
payload['content'] = content
|
||||
|
||||
if tts:
|
||||
payload['tts'] = True
|
||||
|
||||
if embeds:
|
||||
payload['embeds'] = embeds
|
||||
|
||||
if nonce:
|
||||
payload['nonce'] = nonce
|
||||
|
||||
if allowed_mentions:
|
||||
payload['allowed_mentions'] = allowed_mentions
|
||||
|
||||
if message_reference:
|
||||
payload['message_reference'] = message_reference
|
||||
|
||||
return self.request(r, json=payload)
|
||||
|
||||
|
||||
HTTPClient.send_message = send_message
|
||||
|
||||
|
||||
async def send(self, content=None, *, tts=False, embed=None, embeds=None, file=None,
|
||||
files=None, delete_after=None, nonce=None,
|
||||
allowed_mentions=None, reference=None,
|
||||
mention_author=None):
|
||||
|
||||
channel = await self._get_channel()
|
||||
state = self._state
|
||||
content = str(content) if content is not None else None
|
||||
if embed is not None:
|
||||
if embeds is not None:
|
||||
embeds.append(embed)
|
||||
else:
|
||||
embeds = [embed]
|
||||
embed = embed.to_dict()
|
||||
if embeds is not None:
|
||||
embeds = [embed.to_dict() for embed in embeds]
|
||||
|
||||
if allowed_mentions is not None:
|
||||
if state.allowed_mentions is not None:
|
||||
allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict()
|
||||
else:
|
||||
allowed_mentions = allowed_mentions.to_dict()
|
||||
else:
|
||||
allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()
|
||||
|
||||
if mention_author is not None:
|
||||
allowed_mentions = allowed_mentions or AllowedMentions().to_dict()
|
||||
allowed_mentions['replied_user'] = bool(mention_author)
|
||||
|
||||
if reference is not None:
|
||||
try:
|
||||
reference = reference.to_message_reference_dict()
|
||||
except AttributeError:
|
||||
raise InvalidArgument('reference parameter must be Message or MessageReference') from None
|
||||
|
||||
if file is not None and files is not None:
|
||||
raise InvalidArgument('cannot pass both file and files parameter to send()')
|
||||
|
||||
if file is not None:
|
||||
if not isinstance(file, File):
|
||||
raise InvalidArgument('file parameter must be File')
|
||||
|
||||
try:
|
||||
data = await state.http.send_files(channel.id, files=[file], allowed_mentions=allowed_mentions,
|
||||
content=content, tts=tts, embed=embed, nonce=nonce,
|
||||
message_reference=reference)
|
||||
finally:
|
||||
file.close()
|
||||
|
||||
elif files is not None:
|
||||
if len(files) > 10:
|
||||
raise InvalidArgument('files parameter must be a list of up to 10 elements')
|
||||
elif not all(isinstance(file, File) for file in files):
|
||||
raise InvalidArgument('files parameter must be a list of File')
|
||||
|
||||
try:
|
||||
data = await state.http.send_files(channel.id, files=files, content=content, tts=tts,
|
||||
embed=embed, nonce=nonce, allowed_mentions=allowed_mentions,
|
||||
message_reference=reference)
|
||||
finally:
|
||||
for f in files:
|
||||
f.close()
|
||||
else:
|
||||
data = await state.http.send_message(channel.id, content, tts=tts, embeds=embeds,
|
||||
nonce=nonce, allowed_mentions=allowed_mentions,
|
||||
message_reference=reference)
|
||||
|
||||
ret = state.create_message(channel=channel, data=data)
|
||||
if delete_after is not None:
|
||||
await ret.delete(delay=delete_after)
|
||||
return ret
|
||||
|
||||
Messageable.send = send
|
||||
@@ -4,6 +4,9 @@ from data import tables
|
||||
|
||||
import core # noqa
|
||||
|
||||
# Note: This MUST be imported after core, due to table definition orders
|
||||
from settings import AppSettings
|
||||
|
||||
import modules # noqa
|
||||
|
||||
# Load and attach app specific data
|
||||
@@ -15,6 +18,8 @@ client.appdata = core.data.meta.fetch_or_create(appname)
|
||||
|
||||
client.data = tables
|
||||
|
||||
client.settings = AppSettings(conf.bot['data_appid'])
|
||||
|
||||
# Initialise all modules
|
||||
client.initialise_modules()
|
||||
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
from cmdClient.checks import is_owner
|
||||
|
||||
from .module import module
|
||||
from .config import settings
|
||||
|
||||
|
||||
@module.cmd(
|
||||
name="sponsors",
|
||||
group="Meta",
|
||||
desc="Check out our wonderful partners!",
|
||||
flags=('edit', 'prompt')
|
||||
)
|
||||
async def cmd_sponsors(ctx, flags):
|
||||
async def cmd_sponsors(ctx):
|
||||
"""
|
||||
Usage``:
|
||||
{prefix}sponsors
|
||||
"""
|
||||
if await is_owner.run(ctx) and any(flags.values()):
|
||||
if flags['edit']:
|
||||
# Run edit setting command
|
||||
await settings.sponsor_message.command(ctx, 0)
|
||||
elif flags['prompt']:
|
||||
# Run prompt setting command
|
||||
await settings.sponsor_prompt.command(ctx, 0)
|
||||
else:
|
||||
# Display message
|
||||
await ctx.reply(**settings.sponsor_message.args(ctx))
|
||||
await ctx.reply(**ctx.client.settings.sponsor_message.args(ctx))
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
from cmdClient.checks import is_owner
|
||||
|
||||
from settings.base import Setting, ColumnData, ObjectSettings
|
||||
from settings.setting_types import Message, String
|
||||
from settings import AppSettings, Setting, KeyValueData, ListData
|
||||
from settings.setting_types import Message, String, GuildIDList
|
||||
|
||||
from meta import client
|
||||
from utils.lib import DotDict
|
||||
from core.data import app_config
|
||||
|
||||
from .data import sponsor_text
|
||||
from .data import guild_whitelist
|
||||
|
||||
|
||||
class SponsorSettings(ObjectSettings):
|
||||
settings = DotDict()
|
||||
pass
|
||||
|
||||
|
||||
@SponsorSettings.attach_setting
|
||||
class sponsor_prompt(String, ColumnData, Setting):
|
||||
@AppSettings.attach_setting
|
||||
class sponsor_prompt(String, KeyValueData, Setting):
|
||||
attr_name = 'sponsor_prompt'
|
||||
_default = "Type {prefix}sponsors to check our wonderful partners!"
|
||||
_default = None
|
||||
|
||||
write_ward = is_owner
|
||||
|
||||
display_name = 'sponsor_prompt'
|
||||
category = 'Sponsors'
|
||||
desc = "Text to send after core commands to encourage checking `sponsors`."
|
||||
long_desc = (
|
||||
"Text posted after several commands to encourage users to check the `sponsors` command. "
|
||||
@@ -30,11 +25,11 @@ class sponsor_prompt(String, ColumnData, Setting):
|
||||
|
||||
_quote = False
|
||||
|
||||
_data_column = 'prompt_text'
|
||||
_table_interface = sponsor_text
|
||||
_id_column = 'ID'
|
||||
_upsert = True
|
||||
_create_row = True
|
||||
_table_interface = app_config
|
||||
_id_column = 'appid'
|
||||
_key_column = 'key'
|
||||
_value_column = 'value'
|
||||
_key = 'sponsor_prompt'
|
||||
|
||||
@classmethod
|
||||
def _data_to_value(cls, id, data, **kwargs):
|
||||
@@ -43,28 +38,55 @@ class sponsor_prompt(String, ColumnData, Setting):
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def success_response(self):
|
||||
if self.value:
|
||||
return "The sponsor prompt has been update."
|
||||
else:
|
||||
return "The sponsor prompt has been cleared."
|
||||
|
||||
@SponsorSettings.attach_setting
|
||||
class sponsor_message(Message, ColumnData, Setting):
|
||||
|
||||
@AppSettings.attach_setting
|
||||
class sponsor_message(Message, KeyValueData, Setting):
|
||||
attr_name = 'sponsor_message'
|
||||
_default = '{"content": "Coming Soon!"}'
|
||||
|
||||
write_ward = is_owner
|
||||
|
||||
display_name = 'sponsor_message'
|
||||
category = 'Sponsors'
|
||||
desc = "`sponsors` command response."
|
||||
|
||||
long_desc = (
|
||||
"Message to reply with when a user runs the `sponsors` command."
|
||||
)
|
||||
|
||||
_data_column = 'command_response'
|
||||
_table_interface = sponsor_text
|
||||
_id_column = 'ID'
|
||||
_upsert = True
|
||||
_create_row = True
|
||||
_table_interface = app_config
|
||||
_id_column = 'appid'
|
||||
_key_column = 'key'
|
||||
_value_column = 'value'
|
||||
_key = 'sponsor_message'
|
||||
|
||||
_cmd_str = "{prefix}sponsors --edit"
|
||||
|
||||
@property
|
||||
def success_response(self):
|
||||
return "The `sponsors` command message has been updated."
|
||||
|
||||
settings = SponsorSettings(0)
|
||||
|
||||
@AppSettings.attach_setting
|
||||
class sponsor_guild_whitelist(GuildIDList, ListData, Setting):
|
||||
attr_name = 'sponsor_guild_whitelist'
|
||||
write_ward = is_owner
|
||||
|
||||
category = 'Sponsors'
|
||||
display_name = 'sponsor_hidden_in'
|
||||
desc = "Guilds where the sponsor prompt is not displayed."
|
||||
long_desc = (
|
||||
"A list of guilds where the sponsor prompt hint will be hidden (see the `sponsor_prompt` setting)."
|
||||
)
|
||||
|
||||
_table_interface = guild_whitelist
|
||||
_id_column = 'appid'
|
||||
_data_column = 'guildid'
|
||||
_force_unique = True
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from data import Table
|
||||
|
||||
|
||||
sponsor_text = Table("sponsor_text")
|
||||
guild_whitelist = Table("sponsor_guild_whitelist")
|
||||
|
||||
@@ -5,8 +5,6 @@ from LionContext import LionContext
|
||||
|
||||
from meta import client
|
||||
|
||||
from .config import settings
|
||||
|
||||
|
||||
module = LionModule("Sponsor")
|
||||
|
||||
@@ -17,8 +15,10 @@ sponsored_commands = {'profile', 'stats', 'weekly', 'monthly'}
|
||||
@LionContext.reply.add_wrapper
|
||||
async def sponsor_reply_wrapper(func, ctx: LionContext, *args, **kwargs):
|
||||
if ctx.cmd and ctx.cmd.name in sponsored_commands:
|
||||
if (prompt := ctx.client.settings.sponsor_prompt.value):
|
||||
if not ctx.guild or ctx.guild.id not in ctx.client.settings.sponsor_guild_whitelist.value:
|
||||
sponsor_hint = discord.Embed(
|
||||
description=settings.sponsor_prompt.value,
|
||||
description=prompt,
|
||||
colour=discord.Colour.dark_theme()
|
||||
)
|
||||
if 'embed' not in kwargs:
|
||||
|
||||
@@ -4,3 +4,4 @@ from . import exec_cmds
|
||||
from . import guild_log
|
||||
from . import status
|
||||
from . import blacklist
|
||||
from . import botconfig
|
||||
|
||||
96
bot/modules/sysadmin/botconfig.py
Normal file
96
bot/modules/sysadmin/botconfig.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import difflib
|
||||
import discord
|
||||
from cmdClient.checks import is_owner
|
||||
|
||||
from settings import UserInputError
|
||||
|
||||
from utils.lib import prop_tabulate
|
||||
|
||||
from .module import module
|
||||
|
||||
|
||||
@module.cmd("botconfig",
|
||||
desc="Update global bot configuration.",
|
||||
flags=('add', 'remove'),
|
||||
group="Bot Admin")
|
||||
@is_owner()
|
||||
async def cmd_botconfig(ctx, flags):
|
||||
"""
|
||||
Usage``
|
||||
{prefix}botconfig
|
||||
{prefix}botconfig info
|
||||
{prefix}botconfig <setting>
|
||||
{prefix}botconfig <setting> <value>
|
||||
Description:
|
||||
Usage directly follows the `config` command for guild configuration.
|
||||
"""
|
||||
# Cache and map some info for faster access
|
||||
setting_displaynames = {setting.display_name.lower(): setting for setting in ctx.client.settings.settings.values()}
|
||||
appid = ctx.client.conf['data_appid']
|
||||
|
||||
if not ctx.args or ctx.args.lower() in ('info', 'help'):
|
||||
# Fill the setting cats
|
||||
cats = {}
|
||||
for setting in ctx.client.settings.settings.values():
|
||||
cat = cats.get(setting.category, [])
|
||||
cat.append(setting)
|
||||
cats[setting.category] = cat
|
||||
|
||||
# Format the cats
|
||||
sections = {}
|
||||
for catname, cat in cats.items():
|
||||
catprops = {
|
||||
setting.display_name: setting.get(appid).summary if not ctx.args else setting.desc
|
||||
for setting in cat
|
||||
}
|
||||
# TODO: Add cat description here
|
||||
sections[catname] = prop_tabulate(*zip(*catprops.items()))
|
||||
|
||||
# Build the cat page
|
||||
embed = discord.Embed(
|
||||
colour=discord.Colour.orange(),
|
||||
title="App Configuration"
|
||||
)
|
||||
for name, section in sections.items():
|
||||
embed.add_field(name=name, value=section, inline=False)
|
||||
|
||||
await ctx.reply(embed=embed)
|
||||
else:
|
||||
# Some args were given
|
||||
parts = ctx.args.split(maxsplit=1)
|
||||
|
||||
name = parts[0]
|
||||
setting = setting_displaynames.get(name.lower(), None)
|
||||
if setting is None:
|
||||
matches = difflib.get_close_matches(name, setting_displaynames.keys(), n=2)
|
||||
match = "`{}`".format('` or `'.join(matches)) if matches else None
|
||||
return await ctx.error_reply(
|
||||
"Couldn't find a setting called `{}`!\n"
|
||||
"{}"
|
||||
"Use `{}botconfig info` to see all the available settings.".format(
|
||||
name,
|
||||
"Maybe you meant {}?\n".format(match) if match else "",
|
||||
ctx.best_prefix
|
||||
)
|
||||
)
|
||||
|
||||
if len(parts) == 1 and not ctx.msg.attachments:
|
||||
# config <setting>
|
||||
# View config embed for provided setting
|
||||
await setting.get(appid).widget(ctx, flags=flags)
|
||||
else:
|
||||
# config <setting> <value>
|
||||
# Attempt to set config setting
|
||||
try:
|
||||
parsed = await setting.parse(appid, ctx, parts[1] if len(parts) > 1 else '')
|
||||
parsed.write(add_only=flags['add'], remove_only=flags['remove'])
|
||||
except UserInputError as e:
|
||||
await ctx.reply(embed=discord.Embed(
|
||||
description="{} {}".format('❌', e.msg),
|
||||
colour=discord.Colour.red()
|
||||
))
|
||||
else:
|
||||
await ctx.reply(embed=discord.Embed(
|
||||
description="{} {}".format('✅', setting.get(appid).success_response),
|
||||
colour=discord.Colour.green()
|
||||
))
|
||||
@@ -1,4 +1,4 @@
|
||||
from data.interfaces import RowTable
|
||||
from data.interfaces import RowTable, Table
|
||||
|
||||
topggvotes = RowTable(
|
||||
'topgg',
|
||||
@@ -6,3 +6,4 @@ topggvotes = RowTable(
|
||||
'voteid'
|
||||
)
|
||||
|
||||
guild_whitelist = Table('topgg_guild_whitelist')
|
||||
|
||||
@@ -45,6 +45,8 @@ async def topgg_reply_wrapper(func, ctx: LionContext, *args, suggest_vote=True,
|
||||
pass
|
||||
elif ctx.cmd.name in boostfree_commands or ctx.cmd.group in boostfree_groups:
|
||||
pass
|
||||
elif ctx.guild and ctx.guild.id in ctx.client.settings.topgg_guild_whitelist.value:
|
||||
pass
|
||||
elif not get_last_voted_timestamp(ctx.author.id):
|
||||
upvote_info_formatted = upvote_info.format(lion_yayemote, ctx.best_prefix, lion_loveemote)
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
from settings.user_settings import UserSettings, UserSetting
|
||||
from settings.setting_types import Boolean
|
||||
from cmdClient.checks import is_owner
|
||||
|
||||
from settings import UserSettings, UserSetting, AppSettings
|
||||
from settings.base import ListData, Setting
|
||||
from settings.setting_types import Boolean, GuildIDList
|
||||
|
||||
from modules.reminders.reminder import Reminder
|
||||
from modules.reminders.data import reminders
|
||||
|
||||
from .utils import create_remainder, remainder_content, topgg_upvote_link
|
||||
from .data import guild_whitelist
|
||||
|
||||
|
||||
@UserSettings.attach_setting
|
||||
@@ -48,3 +52,21 @@ class topgg_vote_remainder(Boolean, UserSetting):
|
||||
return (
|
||||
"I will no longer send you voting reminders."
|
||||
)
|
||||
|
||||
|
||||
@AppSettings.attach_setting
|
||||
class topgg_guild_whitelist(GuildIDList, ListData, Setting):
|
||||
attr_name = 'topgg_guild_whitelist'
|
||||
write_ward = is_owner
|
||||
|
||||
category = 'Topgg Voting'
|
||||
display_name = 'topgg_hidden_in'
|
||||
desc = "Guilds where the topgg vote prompt is not displayed."
|
||||
long_desc = (
|
||||
"A list of guilds where the topgg vote prompt will be hidden."
|
||||
)
|
||||
|
||||
_table_interface = guild_whitelist
|
||||
_id_column = 'appid'
|
||||
_data_column = 'guildid'
|
||||
_force_unique = True
|
||||
|
||||
@@ -3,3 +3,4 @@ from .setting_types import * # noqa
|
||||
|
||||
from .user_settings import UserSettings, UserSetting # noqa
|
||||
from .guild_settings import GuildSettings, GuildSetting # noqa
|
||||
from .app_settings import AppSettings
|
||||
|
||||
5
bot/settings/app_settings.py
Normal file
5
bot/settings/app_settings.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import settings
|
||||
from utils.lib import DotDict
|
||||
|
||||
class AppSettings(settings.ObjectSettings):
|
||||
settings = DotDict()
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import discord
|
||||
from cmdClient.cmdClient import cmdClient
|
||||
from cmdClient.lib import SafeCancellation
|
||||
@@ -459,5 +460,55 @@ class ListData:
|
||||
cls._cache[id] = data
|
||||
|
||||
|
||||
class KeyValueData:
|
||||
"""
|
||||
Mixin for settings implemented in a Key-Value table.
|
||||
The underlying table should have a Unique constraint on the `(_id_column, _key_column)` pair.
|
||||
"""
|
||||
_table_interface: Table = None
|
||||
|
||||
_id_column: str = None
|
||||
|
||||
_key_column: str = None
|
||||
|
||||
_value_column: str = None
|
||||
|
||||
_key: str = None
|
||||
|
||||
@classmethod
|
||||
def _reader(cls, id: ..., **kwargs):
|
||||
params = {
|
||||
"select_columns": (cls._value_column, ),
|
||||
cls._id_column: id,
|
||||
cls._key_column: cls._key
|
||||
}
|
||||
|
||||
row = cls._table_interface.select_one_where(**params)
|
||||
data = row[cls._value_column] if row else None
|
||||
|
||||
if data is not None:
|
||||
data = json.loads(data)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def _writer(cls, id: ..., data: ..., **kwargs):
|
||||
params = {
|
||||
cls._id_column: id,
|
||||
cls._key_column: cls._key
|
||||
}
|
||||
if data is not None:
|
||||
values = {
|
||||
cls._value_column: json.dumps(data)
|
||||
}
|
||||
cls._table_interface.upsert(
|
||||
constraint=f"{cls._id_column}, {cls._key_column}",
|
||||
**params,
|
||||
**values
|
||||
)
|
||||
else:
|
||||
cls._table_interface.delete_where(**params)
|
||||
|
||||
|
||||
class UserInputError(SafeCancellation):
|
||||
pass
|
||||
|
||||
@@ -473,6 +473,64 @@ class Emoji(SettingType):
|
||||
return str(data)
|
||||
|
||||
|
||||
class GuildID(SettingType):
|
||||
"""
|
||||
Integer type for storing Guild IDs. Stores any snowflake.
|
||||
|
||||
Types:
|
||||
data: Optional[int]
|
||||
The stored integer value.
|
||||
value: Optional[int]
|
||||
The stored integer value.
|
||||
"""
|
||||
accepts = "Any snowflake id."
|
||||
|
||||
@classmethod
|
||||
def _data_from_value(cls, id: int, value: Optional[bool], **kwargs):
|
||||
"""
|
||||
Both data and value are of type Optional[int].
|
||||
Directly return the provided value as data.
|
||||
"""
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _data_to_value(cls, id: int, data: Optional[bool], **kwargs):
|
||||
"""
|
||||
Both data and value are of type Optional[int].
|
||||
Directly return the internal data as the value.
|
||||
"""
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
async def _parse_userstr(cls, ctx: Context, id: int, userstr: str, **kwargs):
|
||||
"""
|
||||
Relies on integer casting to convert the user string
|
||||
"""
|
||||
if not userstr or userstr.lower() == "none":
|
||||
return None
|
||||
|
||||
try:
|
||||
num = int(userstr)
|
||||
except Exception:
|
||||
raise UserInputError("Couldn't parse provided guild id.") from None
|
||||
|
||||
return num
|
||||
|
||||
@classmethod
|
||||
def _format_data(cls, id: int, data: Optional[int], **kwargs):
|
||||
"""
|
||||
Return the string version of the data.
|
||||
"""
|
||||
if data is None:
|
||||
return None
|
||||
elif (guild := client.get_guild(data)):
|
||||
return f"`{data}` ({guild.name})"
|
||||
elif (row := client.data.guild_config.fetch(data)):
|
||||
return f"`{data}` ({row.name})"
|
||||
else:
|
||||
return f"`{data}`"
|
||||
|
||||
|
||||
class Timezone(SettingType):
|
||||
"""
|
||||
Timezone type, storing a valid timezone string.
|
||||
@@ -757,12 +815,17 @@ class Message(SettingType):
|
||||
if as_json:
|
||||
try:
|
||||
args = json.loads(userstr)
|
||||
if not isinstance(args, dict) or (not args.get('content', None) and not args.get('embed', None)):
|
||||
raise ValueError("At least one of the 'content' or 'embed' data fields are required.")
|
||||
if not isinstance(args, dict) or (not {'content', 'embed', 'embeds'}.intersection(args.keys())):
|
||||
raise ValueError("At least one of the 'content', 'embed', or 'embeds' fields are required.")
|
||||
if 'embed' in args:
|
||||
discord.Embed.from_dict(
|
||||
args['embed']
|
||||
)
|
||||
if 'embeds' in args:
|
||||
for embed in args['embeds']:
|
||||
discord.Embed.from_dict(
|
||||
embed
|
||||
)
|
||||
except Exception as e:
|
||||
only_error = "".join(traceback.TracebackException.from_exception(e).format_exception_only())
|
||||
raise UserInputError(
|
||||
@@ -773,6 +836,8 @@ class Message(SettingType):
|
||||
)
|
||||
if 'embed' in args and 'timestamp' in args['embed']:
|
||||
args['embed'].pop('timestamp')
|
||||
if 'embeds' in args:
|
||||
[embed.pop('timestamp', None) for embed in args['embeds']]
|
||||
return json.dumps(args)
|
||||
else:
|
||||
return json.dumps({'content': userstr})
|
||||
@@ -782,9 +847,9 @@ class Message(SettingType):
|
||||
if data is None:
|
||||
return "Empty"
|
||||
value = cls._data_to_value(id, data, **kwargs)
|
||||
if 'embed' not in value and 'content' not in value:
|
||||
if not {'embed', 'content', 'embeds'}.intersection(value.keys()):
|
||||
return "Invalid"
|
||||
if 'embed' not in value and len(value['content']) < 100:
|
||||
if 'content' in value and 'embed' not in value and 'embeds' not in value and len(value['content']) < 100:
|
||||
return "`{}`".format(value['content'])
|
||||
else:
|
||||
return "Too long to display here!"
|
||||
@@ -808,6 +873,13 @@ class Message(SettingType):
|
||||
args['embed'] = discord.Embed.from_dict(
|
||||
json.loads(multiple_replace(json.dumps(value['embed']), substitutions))
|
||||
)
|
||||
if value.get('embeds', None):
|
||||
args['embeds'] = [
|
||||
discord.Embed.from_dict(
|
||||
json.loads(multiple_replace(json.dumps(embed), substitutions))
|
||||
)
|
||||
for embed in value['embeds']
|
||||
]
|
||||
return args
|
||||
|
||||
async def widget(self, ctx, **kwargs):
|
||||
@@ -820,7 +892,7 @@ class Message(SettingType):
|
||||
current_str = None
|
||||
preview = None
|
||||
file_content = None
|
||||
if 'embed' in value or len(value['content']) > 1024:
|
||||
if 'embed' in value or 'embeds' in value or len(value['content']) > 1024:
|
||||
current_str = "See attached file."
|
||||
file_content = json.dumps(value, indent=4)
|
||||
elif "`" in value['content']:
|
||||
@@ -1032,3 +1104,16 @@ class StringList(SettingList):
|
||||
"Write `--add` or `--remove` to add or remove strings."
|
||||
)
|
||||
_setting = String
|
||||
|
||||
|
||||
class GuildIDList(SettingList):
|
||||
"""
|
||||
List of guildids.
|
||||
"""
|
||||
accepts = (
|
||||
"Comma separated list of guild ids. Use `None` to unset. "
|
||||
"Write `--add` or `--remove` to add or remove ids. "
|
||||
"The provided ids are not verified in any way."
|
||||
)
|
||||
|
||||
_setting = GuildID
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
-- App Config Data {{{
|
||||
CREATE TABLE AppConfig(
|
||||
appid TEXT,
|
||||
key TEXT,
|
||||
value TEXT,
|
||||
PRIMARY KEY(appid, key)
|
||||
);
|
||||
-- }}}
|
||||
|
||||
|
||||
-- Sponsor Data {{{
|
||||
CREATE TABLE sponsor_text(
|
||||
ID INTEGER PRIMARY KEY DEFAULT 0,
|
||||
prompt_text TEXT,
|
||||
command_response TEXT
|
||||
CREATE TABLE sponsor_guild_whitelist(
|
||||
appid TEXT,
|
||||
guildid BIGINT,
|
||||
PRIMARY KEY(appid, guildid)
|
||||
);
|
||||
-- }}}
|
||||
|
||||
-- Topgg Data {{{
|
||||
CREATE TABLE topgg_guild_whitelist(
|
||||
appid TEXT,
|
||||
guildid BIGINT,
|
||||
PRIMARY KEY(appid, guildid)
|
||||
);
|
||||
-- }}}
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ CREATE TABLE AppData(
|
||||
last_study_badge_scan TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE AppConfig(
|
||||
appid TEXT,
|
||||
key TEXT,
|
||||
value TEXT,
|
||||
PRIMARY KEY(appid, key)
|
||||
);
|
||||
|
||||
CREATE TABLE global_user_blacklist(
|
||||
userid BIGINT PRIMARY KEY,
|
||||
ownerid BIGINT NOT NULL,
|
||||
@@ -37,16 +44,6 @@ CREATE TABLE global_guild_blacklist(
|
||||
);
|
||||
-- }}}
|
||||
|
||||
|
||||
-- Sponsor Data {{{
|
||||
CREATE TABLE sponsor_text(
|
||||
ID INTEGER PRIMARY KEY DEFAULT 0,
|
||||
prompt_text TEXT,
|
||||
command_response TEXT
|
||||
);
|
||||
-- }}}
|
||||
|
||||
|
||||
-- User configuration data {{{
|
||||
CREATE TABLE user_config(
|
||||
userid BIGINT PRIMARY KEY,
|
||||
@@ -808,6 +805,20 @@ create TABLE topgg(
|
||||
boostedTimestamp TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
CREATE INDEX topgg_userid_timestamp ON topgg (userid, boostedTimestamp);
|
||||
|
||||
CREATE TABLE topgg_guild_whitelist(
|
||||
appid TEXT,
|
||||
guildid BIGINT,
|
||||
PRIMARY KEY(appid, guildid)
|
||||
);
|
||||
-- }}}
|
||||
|
||||
-- Sponsor Data {{{
|
||||
CREATE TABLE sponsor_guild_whitelist(
|
||||
appid TEXT,
|
||||
guildid BIGINT,
|
||||
PRIMARY KEY(appid, guildid)
|
||||
);
|
||||
-- }}}
|
||||
|
||||
-- vim: set fdm=marker:
|
||||
|
||||
Reference in New Issue
Block a user