feat: Implement Sponsors.

This commit is contained in:
2023-10-20 19:52:22 +03:00
parent 8855d2efb8
commit ad455e5ac3
12 changed files with 380 additions and 14 deletions

View 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;

View File

@@ -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
);

View File

@@ -1,5 +1,5 @@
CONFIG_FILE = "config/bot.conf"
DATA_VERSION = 13
DATA_VERSION = 14
MAX_COINS = 2147483647 - 1

View File

@@ -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):
"""

View File

@@ -18,6 +18,7 @@ active = [
'.moderation',
'.video_channels',
'.meta',
'.sponsors',
'.test',
]

View 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
View 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()

View File

@@ -0,0 +1,5 @@
from data import Registry, Table
class SponsorData(Registry):
sponsor_whitelist = Table('sponsor_guild_whitelist')

View 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()

View 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)
)

View File

@@ -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(

View File

@@ -1381,7 +1381,7 @@ class StringListSetting(InteractiveSetting, ListSetting):
_setting = StringSetting
class GuildIDListSetting(InteractiveSetting, ListSetting):
class GuildIDListSetting(ListSetting, InteractiveSetting):
"""
List of guildids.
"""