feat: Implement Sponsors.
This commit is contained in:
7
data/migration/v13-v14/migration.sql
Normal file
7
data/migration/v13-v14/migration.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE bot_config ADD COLUMN sponsor_prompt TEXT;
|
||||
ALTER TABLE bot_config ADD COLUMN sponsor_message TEXT;
|
||||
|
||||
INSERT INTO VersionHistory (version, author) VALUES (14, 'v13-v14 migration');
|
||||
COMMIT;
|
||||
@@ -4,7 +4,7 @@ CREATE TABLE VersionHistory(
|
||||
time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
author TEXT
|
||||
);
|
||||
INSERT INTO VersionHistory (version, author) VALUES (13, 'Initial Creation');
|
||||
INSERT INTO VersionHistory (version, author) VALUES (14, 'Initial Creation');
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp_column()
|
||||
@@ -17,17 +17,6 @@ $$ language 'plpgsql';
|
||||
-- }}}
|
||||
|
||||
-- App metadata {{{
|
||||
CREATE TABLE AppData(
|
||||
appid TEXT PRIMARY KEY,
|
||||
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,
|
||||
@@ -50,6 +39,8 @@ CREATE TABLE app_config(
|
||||
|
||||
CREATE TABLE bot_config(
|
||||
appname TEXT PRIMARY KEY REFERENCES app_config(appname) ON DELETE CASCADE,
|
||||
sponsor_prompt TEXT,
|
||||
sponsor_message TEXT,
|
||||
default_skin TEXT
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
CONFIG_FILE = "config/bot.conf"
|
||||
DATA_VERSION = 13
|
||||
DATA_VERSION = 14
|
||||
|
||||
MAX_COINS = 2147483647 - 1
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ class CoreData(Registry, name="core"):
|
||||
------
|
||||
CREATE TABLE bot_config(
|
||||
appname TEXT PRIMARY KEY REFERENCES app_config(appname) ON DELETE CASCADE,
|
||||
sponsor_prompt TEXT,
|
||||
sponsor_message TEXT,
|
||||
default_skin TEXT
|
||||
);
|
||||
"""
|
||||
@@ -54,6 +56,8 @@ class CoreData(Registry, name="core"):
|
||||
|
||||
appname = String(primary=True)
|
||||
default_skin = String()
|
||||
sponsor_prompt = String()
|
||||
sponsor_message = String()
|
||||
|
||||
class Shard(RowModel):
|
||||
"""
|
||||
|
||||
@@ -18,6 +18,7 @@ active = [
|
||||
'.moderation',
|
||||
'.video_channels',
|
||||
'.meta',
|
||||
'.sponsors',
|
||||
'.test',
|
||||
]
|
||||
|
||||
|
||||
10
src/modules/sponsors/__init__.py
Normal file
10
src/modules/sponsors/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import logging
|
||||
from babel.translator import LocalBabel
|
||||
|
||||
babel = LocalBabel('sponsors')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
from .cog import SponsorCog
|
||||
await bot.add_cog(SponsorCog(bot))
|
||||
126
src/modules/sponsors/cog.py
Normal file
126
src/modules/sponsors/cog.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from typing import Optional
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ext import commands as cmds
|
||||
import discord.app_commands as appcmds
|
||||
|
||||
from meta import LionCog, LionBot, LionContext
|
||||
from wards import sys_admin_ward
|
||||
|
||||
from . import logger, babel
|
||||
from .data import SponsorData
|
||||
from .settings import SponsorSettings
|
||||
from .settingui import SponsorUI
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class SponsorCog(LionCog):
|
||||
def __init__(self, bot: LionBot):
|
||||
self.bot = bot
|
||||
self.data: SponsorData = bot.db.load_registry(SponsorData())
|
||||
self.settings = SponsorSettings
|
||||
|
||||
self.whitelisted = self.settings.Whitelist._cache
|
||||
|
||||
async def cog_load(self):
|
||||
await self.data.init()
|
||||
if (leo_setting_cog := self.bot.get_cog('LeoSettings')) is not None:
|
||||
leo_setting_cog.bot_setting_groups.append(self.settings)
|
||||
self.crossload_group(self.leo_group, leo_setting_cog.leo_group)
|
||||
|
||||
async def do_sponsor_prompt(self, interaction: discord.Interaction):
|
||||
"""
|
||||
Send the sponsor prompt as a followup to this interaction, if applicable.
|
||||
"""
|
||||
if not interaction.is_expired():
|
||||
# TODO: caching
|
||||
whitelist = (await self.settings.Whitelist.get(self.bot.appname)).value
|
||||
if interaction.guild and interaction.guild.id in whitelist:
|
||||
return
|
||||
setting = await self.settings.SponsorPrompt.get(self.bot.appname)
|
||||
value = setting.value
|
||||
if value:
|
||||
args = setting.value_to_args(self.bot.appname, value)
|
||||
followup = interaction.followup
|
||||
await followup.send(**args.send_args, ephemeral=True)
|
||||
|
||||
@cmds.hybrid_command(
|
||||
name=_p('cmd:sponsors', "sponsors"),
|
||||
description=_p(
|
||||
'cmd:sponsors|desc',
|
||||
"Check out our wonderful partners!"
|
||||
)
|
||||
)
|
||||
async def sponsor_cmd(self, ctx: LionContext):
|
||||
"""
|
||||
Display the sponsors message, if set.
|
||||
"""
|
||||
if ctx.interaction:
|
||||
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
|
||||
|
||||
sponsor = await self.settings.SponsorMessage.get(self.bot.appname)
|
||||
value = sponsor.value
|
||||
if value:
|
||||
args = sponsor.value_to_args(self.bot.appname, value)
|
||||
await ctx.reply(**args.send_args)
|
||||
else:
|
||||
await ctx.reply(
|
||||
"Coming Soon!"
|
||||
)
|
||||
|
||||
@LionCog.placeholder_group
|
||||
@cmds.hybrid_group("leo", with_app_command=False)
|
||||
async def leo_group(self, ctx: LionContext):
|
||||
...
|
||||
|
||||
@leo_group.command(
|
||||
name=_p(
|
||||
'cmd:leo_sponsors', "sponsors"
|
||||
),
|
||||
description=_p(
|
||||
'cmd:leo_sponsors|desc',
|
||||
"Configure the sponsor text and whitelist."
|
||||
)
|
||||
)
|
||||
@appcmds.rename(
|
||||
sponsor_prompt=SponsorSettings.SponsorPrompt._display_name,
|
||||
sponsor_message=SponsorSettings.SponsorMessage._display_name,
|
||||
)
|
||||
@appcmds.describe(
|
||||
sponsor_prompt=SponsorSettings.SponsorPrompt._desc,
|
||||
sponsor_message=SponsorSettings.SponsorMessage._desc,
|
||||
)
|
||||
@sys_admin_ward
|
||||
async def leo_sponsors_cmd(self, ctx: LionContext,
|
||||
sponsor_prompt: Optional[discord.Attachment] = None,
|
||||
sponsor_message: Optional[discord.Attachment] = None,
|
||||
):
|
||||
"""
|
||||
Open the configuration UI for sponsors, and optionally set the prompt and message.
|
||||
"""
|
||||
if not ctx.interaction:
|
||||
return
|
||||
|
||||
await ctx.interaction.response.defer(thinking=True)
|
||||
modified = []
|
||||
|
||||
if sponsor_prompt is not None:
|
||||
setting = self.settings.SponsorPrompt
|
||||
content = await setting.download_attachment(sponsor_prompt)
|
||||
instance = await setting.from_string(self.bot.appname, content)
|
||||
modified.append(instance)
|
||||
|
||||
if sponsor_message is not None:
|
||||
setting = self.settings.SponsorMessage
|
||||
content = await setting.download_attachment(sponsor_message)
|
||||
instance = await setting.from_string(self.bot.appname, content)
|
||||
modified.append(instance)
|
||||
|
||||
for instance in modified:
|
||||
await instance.write()
|
||||
|
||||
ui = SponsorUI(self.bot, self.bot.appname, ctx.channel.id)
|
||||
await ui.run(ctx.interaction)
|
||||
await ui.wait()
|
||||
5
src/modules/sponsors/data.py
Normal file
5
src/modules/sponsors/data.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from data import Registry, Table
|
||||
|
||||
|
||||
class SponsorData(Registry):
|
||||
sponsor_whitelist = Table('sponsor_guild_whitelist')
|
||||
87
src/modules/sponsors/settings.py
Normal file
87
src/modules/sponsors/settings.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from settings.data import ListData, ModelData
|
||||
from settings.groups import SettingGroup
|
||||
from settings.setting_types import GuildIDListSetting
|
||||
|
||||
from core.setting_types import MessageSetting
|
||||
from core.data import CoreData
|
||||
from wards import sys_admin_iward
|
||||
from . import babel
|
||||
from .data import SponsorData
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class SponsorSettings(SettingGroup):
|
||||
class Whitelist(ListData, GuildIDListSetting):
|
||||
setting_id = 'sponsor_whitelist'
|
||||
_write_ward = sys_admin_iward
|
||||
|
||||
_display_name = _p(
|
||||
'botset:sponsor_whitelist', "sponsor_whitelist"
|
||||
)
|
||||
_desc = _p(
|
||||
'botset:sponsor_whitelist|desc',
|
||||
"List of guildids where the sponsor prompt is not shown."
|
||||
)
|
||||
_long_desc = _p(
|
||||
'botset:sponsor_whitelist|long_desc',
|
||||
"The sponsor prompt will not appear in the set guilds."
|
||||
)
|
||||
_accepts = _p(
|
||||
'botset:sponsor_whitelist|accetps',
|
||||
"Comma separated list of guildids."
|
||||
)
|
||||
|
||||
_table_interface = SponsorData.sponsor_whitelist
|
||||
_id_column = 'appid'
|
||||
_data_column = 'guildid'
|
||||
_order_column = 'guildid'
|
||||
|
||||
class SponsorPrompt(ModelData, MessageSetting):
|
||||
setting_id = 'sponsor_prompt'
|
||||
_set_cmd = 'leo sponsors'
|
||||
_write_ward = sys_admin_iward
|
||||
|
||||
_display_name = _p(
|
||||
'botset:sponsor_prompt', "sponsor_prompt"
|
||||
)
|
||||
_desc = _p(
|
||||
'botset:sponsor_prompt|desc',
|
||||
"Message to add underneath core commands."
|
||||
)
|
||||
_long_desc = _p(
|
||||
'botset:sponsor_prompt|long_desc',
|
||||
"Content of the message to send after core commands such as stats,"
|
||||
" reminding users to check the sponsors command."
|
||||
)
|
||||
|
||||
_model = CoreData.BotConfig
|
||||
_column = CoreData.BotConfig.sponsor_prompt.name
|
||||
|
||||
async def editor_callback(self, editor_data):
|
||||
self.value = editor_data
|
||||
await self.write()
|
||||
|
||||
class SponsorMessage(ModelData, MessageSetting):
|
||||
setting_id = 'sponsor_message'
|
||||
_set_cmd = 'leo sponsors'
|
||||
_write_ward = sys_admin_iward
|
||||
|
||||
_display_name = _p(
|
||||
'botset:sponsor_message', "sponsor_message"
|
||||
)
|
||||
_desc = _p(
|
||||
'botset:sponsor_message|desc',
|
||||
"Message to send in response to /sponsors command."
|
||||
)
|
||||
_long_desc = _p(
|
||||
'botset:sponsor_message|long_desc',
|
||||
"Content of the message to send when a user runs the `/sponsors` command."
|
||||
)
|
||||
|
||||
_model = CoreData.BotConfig
|
||||
_column = CoreData.BotConfig.sponsor_message.name
|
||||
|
||||
async def editor_callback(self, editor_data):
|
||||
self.value = editor_data
|
||||
await self.write()
|
||||
122
src/modules/sponsors/settingui.py
Normal file
122
src/modules/sponsors/settingui.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ui.button import button, Button, ButtonStyle
|
||||
|
||||
from meta import LionBot
|
||||
|
||||
from utils.ui import ConfigUI
|
||||
from utils.lib import MessageArgs
|
||||
from utils.ui.msgeditor import MsgEditor
|
||||
|
||||
from .settings import SponsorSettings as Settings
|
||||
from . import babel, logger
|
||||
|
||||
_p = babel._p
|
||||
|
||||
|
||||
class SponsorUI(ConfigUI):
|
||||
setting_classes = (
|
||||
Settings.SponsorPrompt,
|
||||
Settings.SponsorMessage,
|
||||
Settings.Whitelist,
|
||||
)
|
||||
|
||||
def __init__(self, bot: LionBot, appname: str, channelid: int, **kwargs):
|
||||
self.settings = bot.get_cog('SponsorCog').settings
|
||||
super().__init__(bot, appname, channelid, **kwargs)
|
||||
|
||||
# ----- UI Components -----
|
||||
@button(
|
||||
label="SPONSOR_PROMPT_BUTTON_PLACEHOLDER",
|
||||
style=ButtonStyle.blurple
|
||||
)
|
||||
async def sponsor_prompt_button(self, press: discord.Interaction, pressed: Button):
|
||||
await press.response.defer(thinking=True, ephemeral=True)
|
||||
setting = self.get_instance(Settings.SponsorPrompt)
|
||||
|
||||
value = setting.value
|
||||
if value is None:
|
||||
value = {'content': "Empty"}
|
||||
|
||||
editor = MsgEditor(
|
||||
self.bot,
|
||||
value,
|
||||
callback=setting.editor_callback,
|
||||
callerid=press.user.id,
|
||||
)
|
||||
self._slaves.append(editor)
|
||||
await editor.run(press)
|
||||
|
||||
async def sponsor_prompt_button_refresh(self):
|
||||
button = self.sponsor_prompt_button
|
||||
t = self.bot.translator.t
|
||||
button.label = t(_p(
|
||||
'ui:sponsors|button:sponsor_prompt|label',
|
||||
"Sponsor Prompt"
|
||||
))
|
||||
|
||||
@button(
|
||||
label="SPONSOR_MESSAGE_BUTTON_PLACEHOLDER",
|
||||
style=ButtonStyle.blurple
|
||||
)
|
||||
async def sponsor_message_button(self, press: discord.Interaction, pressed: Button):
|
||||
await press.response.defer(thinking=True, ephemeral=True)
|
||||
setting = self.get_instance(Settings.SponsorMessage)
|
||||
|
||||
value = setting.value
|
||||
if value is None:
|
||||
value = {'content': "Empty"}
|
||||
|
||||
editor = MsgEditor(
|
||||
self.bot,
|
||||
value,
|
||||
callback=setting.editor_callback,
|
||||
callerid=press.user.id,
|
||||
)
|
||||
self._slaves.append(editor)
|
||||
await editor.run(press)
|
||||
|
||||
async def sponsor_message_button_refresh(self):
|
||||
button = self.sponsor_message_button
|
||||
t = self.bot.translator.t
|
||||
button.label = t(_p(
|
||||
'ui:sponsors|button:sponsor_message|label',
|
||||
"Sponsor Message"
|
||||
))
|
||||
# ----- UI Flow -----
|
||||
async def make_message(self) -> MessageArgs:
|
||||
t = self.bot.translator.t
|
||||
title = t(_p(
|
||||
'ui:sponsors|embed|title',
|
||||
"Leo Sponsor Panel"
|
||||
))
|
||||
embed = discord.Embed(
|
||||
title=title,
|
||||
colour=discord.Colour.orange()
|
||||
)
|
||||
for setting in self.instances:
|
||||
embed.add_field(**setting.embed_field, inline=False)
|
||||
|
||||
return MessageArgs(embed=embed)
|
||||
|
||||
async def reload(self):
|
||||
self.instances = [
|
||||
await setting.get(self.bot.appname)
|
||||
for setting in self.setting_classes
|
||||
]
|
||||
|
||||
async def refresh_components(self):
|
||||
to_refresh = (
|
||||
self.edit_button_refresh(),
|
||||
self.close_button_refresh(),
|
||||
self.reset_button_refresh(),
|
||||
self.sponsor_message_button_refresh(),
|
||||
self.sponsor_prompt_button_refresh(),
|
||||
)
|
||||
await asyncio.gather(*to_refresh)
|
||||
|
||||
self.set_layout(
|
||||
(self.sponsor_prompt_button, self.sponsor_message_button,
|
||||
self.edit_button, self.reset_button, self.close_button)
|
||||
)
|
||||
@@ -55,6 +55,8 @@ class StatsCog(LionCog):
|
||||
await ctx.interaction.response.defer(thinking=True)
|
||||
ui = ProfileUI(self.bot, ctx.author, ctx.guild)
|
||||
await ui.run(ctx.interaction)
|
||||
if sponsors := self.bot.get_cog('SponsorCog'):
|
||||
await sponsors.do_sponsor_prompt(ctx.interaction)
|
||||
await ui.wait()
|
||||
|
||||
@cmds.hybrid_command(
|
||||
@@ -101,6 +103,9 @@ class StatsCog(LionCog):
|
||||
file = discord.File(profile_data, 'profile.png')
|
||||
await ctx.reply(file=file)
|
||||
|
||||
if sponsors := self.bot.get_cog('SponsorCog'):
|
||||
await sponsors.do_sponsor_prompt(ctx.interaction)
|
||||
|
||||
@cmds.hybrid_command(
|
||||
name=_p('cmd:stats', "stats"),
|
||||
description=_p(
|
||||
@@ -116,6 +121,10 @@ class StatsCog(LionCog):
|
||||
await ctx.interaction.response.defer(thinking=True)
|
||||
ui = WeeklyMonthlyUI(self.bot, ctx.author, ctx.guild)
|
||||
await ui.run(ctx.interaction)
|
||||
|
||||
if sponsors := self.bot.get_cog('SponsorCog'):
|
||||
await sponsors.do_sponsor_prompt(ctx.interaction)
|
||||
|
||||
await ui.wait()
|
||||
|
||||
@cmds.hybrid_command(
|
||||
@@ -151,6 +160,10 @@ class StatsCog(LionCog):
|
||||
await ctx.interaction.response.defer(thinking=True)
|
||||
ui = LeaderboardUI(self.bot, ctx.author, ctx.guild)
|
||||
await ui.run(ctx.interaction)
|
||||
|
||||
if sponsors := self.bot.get_cog('SponsorCog'):
|
||||
await sponsors.do_sponsor_prompt(ctx.interaction)
|
||||
|
||||
await ui.wait()
|
||||
|
||||
@cmds.hybrid_command(
|
||||
|
||||
@@ -1381,7 +1381,7 @@ class StringListSetting(InteractiveSetting, ListSetting):
|
||||
_setting = StringSetting
|
||||
|
||||
|
||||
class GuildIDListSetting(InteractiveSetting, ListSetting):
|
||||
class GuildIDListSetting(ListSetting, InteractiveSetting):
|
||||
"""
|
||||
List of guildids.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user