diff --git a/bot/core/data.py b/bot/core/data.py index 58c1331b..71e45b52 100644 --- a/bot/core/data.py +++ b/bot/core/data.py @@ -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', diff --git a/bot/main.py b/bot/main.py index 066bf86e..1f401733 100644 --- a/bot/main.py +++ b/bot/main.py @@ -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() diff --git a/bot/modules/sponsors/commands.py b/bot/modules/sponsors/commands.py index e937739c..424f56a2 100644 --- a/bot/modules/sponsors/commands.py +++ b/bot/modules/sponsors/commands.py @@ -1,7 +1,6 @@ from cmdClient.checks import is_owner from .module import module -from .config import settings @module.cmd( @@ -18,10 +17,10 @@ async def cmd_sponsors(ctx, flags): if await is_owner.run(ctx) and any(flags.values()): if flags['edit']: # Run edit setting command - await settings.sponsor_message.command(ctx, 0) + await ctx.client.settings.sponsor_message.command(ctx, ctx.client.conf.bot['data_appid']) elif flags['prompt']: # Run prompt setting command - await settings.sponsor_prompt.command(ctx, 0) + await ctx.client.settings.sponsor_prompt.command(ctx, ctx.client.conf.bot['data_appid']) else: # Display message - await ctx.reply(**settings.sponsor_message.args(ctx)) + await ctx.reply(**ctx.client.settings.sponsor_message.args(ctx)) diff --git a/bot/modules/sponsors/config.py b/bot/modules/sponsors/config.py index 0cda5bc6..f8bcde25 100644 --- a/bot/modules/sponsors/config.py +++ b/bot/modules/sponsors/config.py @@ -1,23 +1,16 @@ from cmdClient.checks import is_owner -from settings.base import Setting, ColumnData, ObjectSettings +from settings import AppSettings, Setting, KeyValueData, ListData from settings.setting_types import Message, String from meta import client -from utils.lib import DotDict - -from .data import sponsor_text +from core.data import app_config -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 @@ -30,11 +23,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): @@ -44,8 +37,8 @@ class sponsor_prompt(String, ColumnData, Setting): return None -@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!"}' @@ -58,13 +51,10 @@ class sponsor_message(Message, ColumnData, Setting): "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" - - -settings = SponsorSettings(0) diff --git a/bot/modules/sponsors/module.py b/bot/modules/sponsors/module.py index 9f08c0c1..489af5ce 100644 --- a/bot/modules/sponsors/module.py +++ b/bot/modules/sponsors/module.py @@ -5,8 +5,6 @@ from LionContext import LionContext from meta import client -from .config import settings - module = LionModule("Sponsor") @@ -18,7 +16,7 @@ sponsored_commands = {'profile', 'stats', 'weekly', 'monthly'} async def sponsor_reply_wrapper(func, ctx: LionContext, *args, **kwargs): if ctx.cmd and ctx.cmd.name in sponsored_commands: sponsor_hint = discord.Embed( - description=settings.sponsor_prompt.value, + description=ctx.client.settings.sponsor_prompt.value, colour=discord.Colour.dark_theme() ) if 'embed' not in kwargs: diff --git a/bot/settings/app_settings.py b/bot/settings/app_settings.py new file mode 100644 index 00000000..637c4b51 --- /dev/null +++ b/bot/settings/app_settings.py @@ -0,0 +1,5 @@ +import settings +from utils.lib import DotDict + +class AppSettings(settings.ObjectSettings): + settings = DotDict() diff --git a/bot/settings/base.py b/bot/settings/base.py index 44678a97..a2750796 100644 --- a/bot/settings/base.py +++ b/bot/settings/base.py @@ -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 diff --git a/data/migration/v10-v11/migration.sql b/data/migration/v10-v11/migration.sql index 6c3b1f69..0e8740b0 100644 --- a/data/migration/v10-v11/migration.sql +++ b/data/migration/v10-v11/migration.sql @@ -1,8 +1,22 @@ +-- 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( + guildid INTEGER PRIMARY KEY +); +-- }}} + +-- Topgg Data {{{ +CREATE TABLE topgg_guild_whitelist( + guildid INTEGER PRIMARY KEY ); -- }}} diff --git a/data/schema.sql b/data/schema.sql index de7ee92f..97f468fd 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -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,16 @@ create TABLE topgg( boostedTimestamp TIMESTAMPTZ NOT NULL ); CREATE INDEX topgg_userid_timestamp ON topgg (userid, boostedTimestamp); + +CREATE TABLE topgg_guild_whitelist( + guildid INTEGER PRIMARY KEY +); +-- }}} + +-- Sponsor Data {{{ +CREATE TABLE sponsor_guild_whitelist( + guildid INTEGER PRIMARY KEY +); -- }}} -- vim: set fdm=marker: