Files
croccybot/src/modules/economy/cog.py

909 lines
39 KiB
Python

from typing import Optional, Union
import asyncio
import discord
from discord.ext import commands as cmds
from discord import app_commands as appcmds
from meta import LionCog, LionBot, LionContext, conf
from meta.errors import ResponseTimedOut
from babel import LocalBabel
from data import ORDER
from utils.ui import Confirm, Pager
from utils.lib import error_embed, MessageArgs, utc_now
from wards import low_management_ward, moderator_ward
from constants import MAX_COINS
from . import babel, logger
from .data import EconomyData, TransactionType, AdminActionType
from .settings import EconomySettings
from .settingui import EconomyConfigUI
_, _p, _np = babel._, babel._p, babel._np
class Economy(LionCog):
"""
Commands
--------
/economy balances [target:<mentionable>] [add:<int>] [set:<int>].
With no arguments, show a summary of current balances in the server.
With a target user or role, show their balance, and possibly their most recent transactions.
With a target user or role, and add or set, modify their balance. Confirm if more than 1 user is affected.
With no target user or role, apply to everyone in the guild. Confirm if more than 1 user affected.
/economy reset [target:<mentionable>]
Reset the economy system for the given target, or everyone in the guild.
Acts as an alias to `/economy balances target:target set:0
/economy history [target:<mentionable>]
Display a paged audit trail with the history of the selected member,
all the users in the selected role, or all users.
/sendcoins <user:<user>> [note:<str>]
Send coins to the specified user, with an optional note.
"""
def __init__(self, bot: LionBot):
self.bot = bot
self.data = bot.db.load_registry(EconomyData())
self.settings = EconomySettings()
self.bonuses = {}
async def cog_load(self):
await self.data.init()
self.bot.core.guild_config.register_model_setting(self.settings.AllowTransfers)
self.bot.core.guild_config.register_model_setting(self.settings.CoinsPerXP)
self.bot.core.guild_config.register_model_setting(self.settings.StartingFunds)
configcog = self.bot.get_cog('ConfigCog')
if configcog is None:
logger.critical(
"Attempting to load the EconomyCog before ConfigCog! Failed to crossload configuration group."
)
else:
self.crossload_group(self.configure_group, configcog.configure_group)
# ----- Economy Bonus registration -----
def register_economy_bonus(self, bonus_coro, name=None):
name = name or bonus_coro.__name__
self.bonuses[name] = bonus_coro
def deregister_economy_bonus(self, name):
bonus_coro = self.bonuses.pop(name, None)
if bonus_coro is None:
raise ValueError(f"Bonus function '{name}' is not registered!")
return
async def fetch_economy_bonus(self, guildid: int, userid: int, **kwargs):
multiplier = 1
for coro in self.bonuses.values():
multiplier *= await coro(guildid, userid, **kwargs)
return multiplier
# ----- Economy group commands -----
@cmds.hybrid_group(name=_p('cmd:economy', "economy"))
@cmds.guild_only()
async def economy_group(self, ctx: LionContext):
pass
@economy_group.command(
name=_p('cmd:economy_balance', "balance"),
description=_p(
'cmd:economy_balance|desc',
"Display or modify LionCoin balance for members and roles."
)
)
@appcmds.rename(
target=_p('cmd:economy_balance|param:target', "target"),
add=_p('cmd:economy_balance|param:add', "add"),
set_to=_p('cmd:economy_balance|param:set', "set")
)
@appcmds.describe(
target=_p(
'cmd:economy_balance|param:target|desc',
"Target user or role to view or update. Use @everyone to update the entire guild."
),
add=_p(
'cmd:economy_balance|param:add|desc',
"Number of LionCoins to add to the target member's balance. May be negative to remove."
),
set_to=_p(
'cmd:economy_balance|param:set|set',
"New balance to set the target's balance to."
)
)
@moderator_ward
async def economy_balance_cmd(
self,
ctx: LionContext,
target: discord.User | discord.Member | discord.Role,
set_to: Optional[appcmds.Range[int, 0, MAX_COINS]] = None,
add: Optional[int] = None
):
t = self.bot.translator.t
cemoji = self.bot.config.emojis.getemoji('coin')
targets: list[Union[discord.User, discord.Member]]
if not ctx.guild:
# Added for the typechecker
# This is impossible from the guild_only ward
return
if not self.bot.core:
return
if not ctx.interaction:
return
if isinstance(target, discord.Role):
targets = [mem for mem in target.members if not mem.bot]
role = target
else:
targets = [target]
role = None
if role and not targets:
# Guard against provided target role having no members
# Possible chunking failed for this guild, want to explicitly inform.
await ctx.reply(
embed=error_embed(
t(_p(
'cmd:economy_balance|error:no_target',
"There are no valid members in {role.mention}! It has a total of `0` LC."
)).format(role=target)
),
ephemeral=True
)
elif not role and target.bot:
# Guard against reading or modifying a bot account
await ctx.reply(
embed=error_embed(
t(_p(
'cmd:economy_balance|error:target_is_bot',
"Bots cannot have coin balances!"
))
),
ephemeral=True
)
elif set_to is not None and add is not None:
# Requested operation doesn't make sense
await ctx.reply(
embed=error_embed(
t(_p(
'cmd:economy_balance|error:args',
"You cannot simultaneously `set` and `add` member balances!"
))
),
ephemeral=True
)
elif set_to is not None or add is not None:
# Setting route
# First ensure all the targets we will be updating already have rows
# As this is one of the few operations that acts on members not already registered,
# We may need to do a mass row create operation.
targetids = set(target.id for target in targets)
if len(targets) > 1:
async def wrapper():
async with self.bot.db.connection() as conn:
self.bot.db.conn = conn
async with conn.transaction():
# First fetch the members which currently exist
query = self.bot.core.data.Member.table.select_where(guildid=ctx.guild.id)
query.select('userid').with_no_adapter()
if 2 * len(targets) < ctx.guild.member_count:
# More efficient to fetch the targets explicitly
query.where(userid=list(targetids))
existent_rows = await query
existentids = set(r['userid'] for r in existent_rows)
# Then check if any new userids need adding, and if so create them
new_ids = targetids.difference(existentids)
if new_ids:
# We use ON CONFLICT IGNORE here in case the users already exist.
await self.bot.core.data.User.table.insert_many(
('userid',),
*((id,) for id in new_ids)
).on_conflict(ignore=True)
# TODO: Replace 0 here with the starting_coin value
await self.bot.core.data.Member.table.insert_many(
('guildid', 'userid', 'coins'),
*((ctx.guild.id, id, 0) for id in new_ids)
).on_conflict(ignore=True)
task = asyncio.create_task(wrapper(), name="wrapped-create-members")
await task
else:
# With only one target, we can take a simpler path, and make better use of local caches.
await self.bot.core.lions.fetch_member(ctx.guild.id, target.id)
# Now we are certain these members have a database row
# Perform the appropriate action
if role:
affected = t(_np(
'cmd:economy_balance|embed:success|affected',
"One user was affected.",
"**{count}** users were affected.",
len(targets)
)).format(count=len(targets))
conf_affected = t(_np(
'cmd:economy_balance|confirm|affected',
"One user will be affected.",
"**{count}** users will be affected.",
len(targets)
)).format(count=len(targets))
confirm = Confirm(conf_affected)
confirm.confirm_button = t(_p(
'cmd:economy_balance|confirm|button:confirm',
"Yes, adjust balances"
))
confirm.confirm_button = t(_p(
'cmd:economy_balance|confirm|button:cancel',
"No, cancel"
))
if set_to is not None:
if role:
if role.is_default():
description = t(_p(
'cmd:economy_balance|embed:success_set|desc',
"All members of **{guild_name}** have had their "
"balance set to {coin_emoji}**{amount}**."
)).format(
guild_name=ctx.guild.name,
coin_emoji=cemoji,
amount=set_to
) + '\n' + affected
conf_description = t(_p(
'cmd:economy_balance|confirm_set|desc',
"Are you sure you want to set everyone's balance to {coin_emoji}**{amount}**?"
)).format(
coin_emoji=cemoji,
amount=set_to
) + '\n' + conf_affected
else:
description = t(_p(
'cmd:economy_balance|embed:success_set|desc',
"All members of {role_mention} have had their "
"balance set to {coin_emoji}**{amount}**."
)).format(
role_mention=role.mention,
coin_emoji=cemoji,
amount=set_to
) + '\n' + affected
conf_description = t(_p(
'cmd:economy_balance|confirm_set|desc',
"Are you sure you want to set the balance of everyone with {role_mention} "
"to {coin_emoji}**{amount}**?"
)).format(
role_mention=role.mention,
coin_emoji=cemoji,
amount=set_to
) + '\n' + conf_affected
confirm.embed.description = conf_description
try:
result = await confirm.ask(ctx.interaction, ephemeral=True)
except ResponseTimedOut:
return
if not result:
return
else:
description = t(_p(
'cmd:economy_balance|embed:success_set|desc',
"{user_mention} now has a balance of {coin_emoji}**{amount}**."
)).format(
user_mention=target.mention,
coin_emoji=cemoji,
amount=set_to
)
await self.bot.core.data.Member.table.update_where(
guildid=ctx.guild.id, userid=list(targetids)
).set(
coins=set_to
)
else:
if role:
if role.is_default():
description = t(_p(
'cmd:economy_balance|embed:success_add|desc',
"All members of **{guild_name}** have been given "
"{coin_emoji}**{amount}**."
)).format(
guild_name=ctx.guild.name,
coin_emoji=cemoji,
amount=add
) + '\n' + affected
conf_description = t(_p(
'cmd:economy_balance|confirm_add|desc',
"Are you sure you want to add **{amount}** to everyone's balance?"
)).format(
coin_emoji=cemoji,
amount=add
) + '\n' + conf_affected
else:
description = t(_p(
'cmd:economy_balance|embed:success_add|desc',
"All members of {role_mention} have been given "
"{coin_emoji}**{amount}**."
)).format(
role_mention=role.mention,
coin_emoji=cemoji,
amount=add
) + '\n' + affected
conf_description = t(_p(
'cmd:economy_balance|confirm_add|desc',
"Are you sure you want to add {coin_emoji}**{amount}** to everyone in {role_mention}?"
)).format(
coin_emoji=cemoji,
amount=add,
role_mention=role.mention
) + '\n' + conf_affected
confirm.embed.description = conf_description
try:
result = await confirm.ask(ctx.interaction, ephemeral=True)
except ResponseTimedOut:
return
if not result:
return
results = await self.bot.core.data.Member.table.update_where(
guildid=ctx.guild.id, userid=list(targetids)
).set(
coins=(self.bot.core.data.Member.coins + add)
)
# Single member case occurs afterwards so we can pick up the results
if not role:
description = t(_p(
'cmd:economy_balance|embed:success_add|desc',
"{user_mention} was given {coin_emoji}**{amount}**, and "
"now has a balance of {coin_emoji}**{new_amount}**."
)).format(
user_mention=target.mention,
coin_emoji=cemoji,
amount=add,
new_amount=results[0]['coins']
)
title = t(_np(
'cmd:economy_balance|embed:success|title',
"Account successfully updated.",
"Accounts successfully updated.",
len(targets)
))
await ctx.reply(
embed=discord.Embed(
colour=discord.Colour.brand_green(),
description=description,
title=title,
)
)
else:
# TODO: Restrict view to the top 1000 so we don't murder the main thread
await ctx.interaction.response.defer()
# Viewing route
MemModel = self.bot.core.data.Member
if role:
query = MemModel.table.select_where(
(MemModel.guildid == role.guild.id) & (MemModel.coins != 0)
).with_no_adapter()
if not role.is_default():
# Everyone role is handled differently for data efficiency
ids = [target.id for target in targets]
query = query.where(userid=ids)
# First get a summary
summary = await query.select(
_count='COUNT(*)',
_coin_total='SUM(coins)',
)
record = summary[0]
count = record['_count']
total = record['_coin_total']
if count > 0:
# Then get the top 1000 members
query._columns = ()
query.order_by('coins', ORDER.DESC)
query.limit(1000)
rows = await query.select('userid', 'coins')
else:
rows = []
name = t(_p(
'cmd:economy_balance|embed:role_lb|author',
"Balance sheet for {name}"
)).format(name=role.name if not role.is_default() else role.guild.name)
if rows:
if role.is_default():
header = t(_p(
'cmd:economy_balance|embed:role_lb|header',
"This server has a total balance of {coin_emoji}**{total}**."
)).format(
coin_emoji=cemoji,
total=total
)
else:
header = t(_p(
'cmd:economy_balance|embed:role_lb|header',
"{role_mention} has `{count}` members with non-zero balance, "
"with a total balance of {coin_emoji}**{total}**."
)).format(
count=count,
role_mention=role.mention,
total=total,
coin_emoji=cemoji
)
# Build the leaderboard:
lb_format = t(_p(
'cmd:economy_balance|embed:role_lb|row_format',
"`[{pos:>{numwidth}}]` | `{coins:>{coinwidth}} LC` | {mention}"
))
blocklen = 20
blocks = [rows[i:i+blocklen] for i in range(0, len(rows), blocklen)]
paged = len(blocks) > 1
pages = []
for i, block in enumerate(blocks):
lines = []
numwidth = len(str(i + len(block)))
coinwidth = len(str(max(row['coins'] for row in rows)))
for j, row in enumerate(block, start=i*blocklen):
lines.append(
lb_format.format(
pos=j, numwidth=numwidth,
coins=row['coins'], coinwidth=coinwidth,
mention=f"<@{row['userid']}>"
)
)
lb_block = '\n'.join(lines)
embed = discord.Embed(
description=f"{header}\n{lb_block}"
)
embed.set_author(name=name)
if paged:
embed.set_footer(
text=t(_p(
'cmd:economy_balance|embed:role_lb|footer',
"Page {page}/{total}"
)).format(page=i+1, total=len(blocks))
)
pages.append(MessageArgs(embed=embed))
pager = Pager(pages, show_cancel=True)
await pager.run(ctx.interaction)
else:
if role.is_default():
header = t(_p(
'cmd:economy_balance|embed:role_lb|header',
"This server has a total balance of {coin_emoji}**0**."
)).format(
coin_emoji=cemoji,
)
else:
header = t(_p(
'cmd:economy_balance|embed:role_lb|header',
"The role {role_mention} has a total balance of {coin_emoji}**0**."
)).format(
role_mention=role.mention,
coin_emoji=cemoji
)
embed = discord.Embed(
colour=discord.Colour.orange(),
description=header
)
embed.set_author(name=name)
await ctx.reply(embed=embed)
else:
# If we have a single target, show their current balance, with a short transaction history.
user = targets[0]
row = await self.bot.core.data.Member.fetch(ctx.guild.id, user.id, cached=False)
embed = discord.Embed(
colour=discord.Colour.orange(),
description=t(_p(
'cmd:economy_balance|embed:single|desc',
"{mention} currently owns {coin_emoji} {coins}."
)).format(
mention=user.mention,
coin_emoji=self.bot.config.emojis.getemoji('coin'),
coins=row.coins
)
).set_author(
icon_url=user.avatar,
name=t(_p(
'cmd:economy_balance|embed:single|author',
"Balance statement for {user}"
)).format(user=str(user))
)
await ctx.reply(
embed=embed
)
# TODO: Add small transaction history block when we have transaction formatter
@economy_group.command(
name=_p('cmd:economy_reset', "reset"),
description=_p(
'cmd:economy_reset|desc',
"Reset the coin balance for a target user or role. (See also \"economy balance\".)"
)
)
@appcmds.rename(
target=_p('cmd:economy_reset|param:target', "target"),
)
@appcmds.describe(
target=_p(
'cmd:economy_reset|param:target|desc',
"Target user or role to view or update. Use @everyone to reset the entire guild."
),
)
@moderator_ward
async def economy_reset_cmd(
self,
ctx: LionContext,
target: discord.User | discord.Member | discord.Role,
):
# TODO: Permission check
t = self.bot.translator.t
starting_balance = 0
coin_emoji = self.bot.config.emojis.getemoji('coin')
# Typechecker guards
if not ctx.guild:
return
if not ctx.bot.core:
return
if not ctx.interaction:
return
if isinstance(target, discord.Role):
if target.is_default():
# Confirm: Reset Guild
confirm_msg = t(_p(
'cmd:economy_reset|confirm:reset_guild|desc',
"Are you sure you want to reset the coin balance for everyone in **{guild_name}**?\n"
"*This is not reversible!*"
)).format(
guild_name=ctx.guild.name
)
confirm = Confirm(confirm_msg)
confirm.confirm_button.label = t(_p(
'cmd:economy_reset|confirm:reset_guild|button:confirm',
"Yes, reset the economy"
))
confirm.cancel_button.label = t(_p(
'cmd:economy_reset|confirm:reset_guild|button:cancel',
"Cancel reset"
))
try:
result = await confirm.ask(ctx.interaction, ephemeral=True)
except ResponseTimedOut:
return
if result:
# Complete reset
await ctx.bot.core.data.Member.table.update_where(
guildid=ctx.guild.id,
).set(coins=starting_balance)
await ctx.reply(
embed=discord.Embed(
description=t(_p(
'cmd:economy_reset|embed:success_guild|desc',
"Everyone in **{guild_name}** has had their balance reset to {coin_emoji}**{amount}**."
)).format(
guild_name=ctx.guild.name,
coin_emoji=coin_emoji,
amount=starting_balance
)
)
)
else:
# Provided a role to reset
targets = [member for member in target.members if not member.bot]
if not targets:
# Error: No targets
await ctx.reply(
embed=error_embed(
t(_p(
'cmd:economy_reset|error:no_target|desc',
"The role {mention} has no members to reset!"
)).format(mention=target.mention)
),
ephemeral=True
)
else:
# Confirm: Reset Role
# Include number of people affected
confirm_msg = t(_p(
'cmd:economy_reset|confirm:reset_role|desc',
"Are you sure you want to reset the balance for everyone in {mention}?\n"
"**{count}** members will be affected."
)).format(
mention=target.mention,
count=len(targets)
)
confirm = Confirm(confirm_msg)
confirm.confirm_button.label = t(_p(
'cmd:economy_reset|confirm:reset_role|button:confirm',
"Yes, complete economy reset"
))
confirm.cancel_button.label = t(_p(
'cmd:economy_reset|confirm:reset_role|button:cancel',
"Cancel"
))
try:
result = await confirm.ask(ctx.interaction, ephemeral=True)
except ResponseTimedOut:
return
if result:
# Complete reset
await ctx.bot.core.data.Member.table.update_where(
guildid=ctx.guild.id,
userid=[t.id for t in targets],
).set(coins=starting_balance)
await ctx.reply(
embed=discord.Embed(
description=t(_p(
'cmd:economy_reset|embed:success_role|desc',
"Everyone in {role_mention} has had their "
"coin balance reset to {coin_emoji}**{amount}**."
)).format(
mention=target.mention,
coin_emoji=coin_emoji,
amount=starting_balance
)
)
)
else:
# Provided an individual user.
# Reset their balance
# Do not create the member row if it does not already exist.
# TODO: Audit logging trail
await ctx.bot.core.data.Member.table.update_where(
guildid=ctx.guild.id,
userid=target.id,
).set(coins=starting_balance)
await ctx.reply(
embed=discord.Embed(
description=t(_p(
'cmd:economy_reset|embed:success_user|desc',
"{mention}'s balance has been reset to {coin_emoji}**{amount}**."
)).format(
mention=target.mention,
coin_emoji=coin_emoji,
amount=starting_balance
)
)
)
@cmds.hybrid_command(
name=_p('cmd:send', "send"),
description=_p(
'cmd:send|desc',
"Gift the target user a certain number of LionCoins."
)
)
@appcmds.rename(
target=_p('cmd:send|param:target', "target"),
amount=_p('cmd:send|param:amount', "amount"),
note=_p('cmd:send|param:note', "note")
)
@appcmds.describe(
target=_p('cmd:send|param:target|desc', "User to send the gift to"),
amount=_p('cmd:send|param:amount|desc', "Number of coins to send"),
note=_p('cmd:send|param:note|desc', "Optional note to add to the gift.")
)
@appcmds.guild_only()
async def send_cmd(self, ctx: LionContext,
target: discord.Member,
amount: appcmds.Range[int, 1, MAX_COINS],
note: Optional[str] = None):
"""
Send `amount` lioncoins to the provided `target`, with the optional `note` attached.
"""
if not ctx.interaction:
return
if not ctx.guild:
return
if not self.bot.core:
return
t = self.bot.translator.t
error = None
if not ctx.lguild.config.get('allow_transfers').value:
error = error_embed(
t(_p(
'cmd:send|error:not_allowed',
"Sorry, this server has disabled LionCoin transfers!"
))
)
elif target == ctx.author:
# Funny response
error = discord.Embed(
colour=discord.Colour.brand_red(),
description=t(_p( # TRANSLATOR NOTE: Easter egg/Funny error, translate as you wish.
'cmd:send|error:sending-to-self',
"What is this, tax evasion?\n"
"(You can not send coins to yourself.)"
))
)
elif target == ctx.guild.me:
# Funny response
error = discord.Embed(
colour=discord.Colour.orange(),
description=t(_p( # TRANSLATOR NOTE: Easter egg/Funny error, translate as you wish.
'cmd:send|error:sending-to-leo',
"I appreciate it, but you need it more than I do!\n"
"(You cannot send coins to bots.)"
))
)
elif target.bot:
# Funny response
error = discord.Embed(
colour=discord.Colour.brand_red(),
description=t(_p( # TRANSLATOR NOTE: Easter egg/Funny error, translate as you wish.
'cmd:send|error:sending-to-bot',
"{target} appreciates the gesture, but said they don't have any use for {coin}.\n"
"(You cannot send coins to bots.)"
)).format(target=target.mention, coin=self.bot.config.emojis.coin)
)
if error is not None:
await ctx.interaction.response.send_message(embed=error, ephemeral=True)
return
# Ensure the target member exists
Member = self.bot.core.data.Member
target_lion = await self.bot.core.lions.fetch_member(ctx.guild.id, target.id)
# TODO: Add a "Send thanks" button to the DM?
# Alternative flow could be waiting until the target user presses accept
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
async def wrapped():
async with self.bot.db.connection() as conn:
self.bot.db.conn = conn
async with conn.transaction():
# We do this in a transaction so that if something goes wrong,
# the coins deduction is rolled back atomicly
balance = ctx.alion.data.coins
if amount > balance:
await ctx.interaction.edit_original_response(
embed=error_embed(
t(_p(
'cmd:send|error:insufficient',
"You do not have enough lioncoins to do this!\n"
"`Current Balance:` {coin_emoji}{balance}"
)).format(
coin_emoji=self.bot.config.emojis.getemoji('coin'),
balance=balance
)
),
)
return
# Transfer the coins
await ctx.alion.data.update(coins=(Member.coins - amount))
await target_lion.data.update(coins=(Member.coins + amount))
# TODO: Audit trail
await asyncio.create_task(wrapped(), name="wrapped-send")
# Message target
embed = discord.Embed(
title=t(_p(
'cmd:send|embed:gift|title',
"{user} sent you a gift!"
)).format(user=ctx.author.name),
description=t(_p(
'cmd:send|embed:gift|desc',
"{mention} sent you {coin_emoji}**{amount}**."
)).format(
coin_emoji=self.bot.config.emojis.getemoji('coin'),
amount=amount,
mention=ctx.author.mention
),
timestamp=utc_now()
)
if note:
embed.add_field(
name="Note Attached",
value=note
)
try:
await target.send(embed=embed)
failed = False
except discord.HTTPException:
failed = True
pass
# Ack transfer
embed = discord.Embed(
colour=discord.Colour.brand_green(),
description=t(_p(
'cmd:send|embed:ack|desc',
"**{coin_emoji}{amount}** has been deducted from your balance and sent to {mention}!"
)).format(
coin_emoji=self.bot.config.emojis.getemoji('coin'),
amount=amount,
mention=target.mention
)
)
if failed:
embed.description += '\n' + t(_p(
'cmd:send|embed:ack|desc|error:unreachable',
"Unfortunately, I was not able to message the recipient. Perhaps they have me blocked?"
))
await ctx.interaction.edit_original_response(embed=embed)
# -------- Configuration Commands --------
@LionCog.placeholder_group
@cmds.hybrid_group('configure', with_app_command=False)
async def configure_group(self, ctx: LionContext):
# Placeholder group method, not used
pass
@configure_group.command(
name=_p('cmd:configure_economy', "economy"),
description=_p(
'cmd:configure_economy|desc',
"Configure LionCoin Economy"
)
)
@appcmds.rename(
allow_transfers=EconomySettings.AllowTransfers._display_name,
coins_per_xp=EconomySettings.CoinsPerXP._display_name,
starting_funds=EconomySettings.StartingFunds._display_name,
)
@appcmds.describe(
allow_transfers=EconomySettings.AllowTransfers._desc,
coins_per_xp=EconomySettings.CoinsPerXP._desc,
starting_funds=EconomySettings.StartingFunds._desc,
)
@appcmds.choices(
allow_transfers=[
appcmds.Choice(name=EconomySettings.AllowTransfers._outputs[True], value=1),
appcmds.Choice(name=EconomySettings.AllowTransfers._outputs[False], value=0),
]
)
@appcmds.default_permissions(manage_guild=True)
@moderator_ward
async def configure_economy(self, ctx: LionContext,
allow_transfers: Optional[appcmds.Choice[int]] = None,
coins_per_xp: Optional[appcmds.Range[int, 0, MAX_COINS]] = None,
starting_funds: Optional[appcmds.Range[int, 0, MAX_COINS]] = None,
):
t = self.bot.translator.t
if not ctx.interaction:
return
if not ctx.guild:
return
setting_allow_transfers = ctx.lguild.config.get('allow_transfers')
setting_coins_per_xp = ctx.lguild.config.get('coins_per_xp')
setting_starting_funds = ctx.lguild.config.get('starting_funds')
modified = []
if allow_transfers is not None:
setting_allow_transfers.data = bool(allow_transfers.value)
await setting_allow_transfers.write()
modified.append(setting_allow_transfers)
if coins_per_xp is not None:
setting_coins_per_xp.data = coins_per_xp
await setting_coins_per_xp.write()
modified.append(setting_coins_per_xp)
if starting_funds is not None:
setting_starting_funds.data = starting_funds
await setting_starting_funds.write()
modified.append(setting_starting_funds)
if modified:
desc = '\n'.join(f"{conf.emojis.tick} {setting.update_message}" for setting in modified)
await ctx.reply(
embed=discord.Embed(
colour=discord.Colour.brand_green(),
description=desc
)
)
if ctx.channel.id not in EconomyConfigUI._listening or not modified:
configui = EconomyConfigUI(self.bot, ctx.guild.id, ctx.channel.id)
await configui.run(ctx.interaction)
await configui.wait()