Add multiple new cogs for banking, economy tracking, and referrals, including setup, commands, and localization files.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-23 02:30:00 -04:00
parent 7da3f0ab66
commit 651c9372f2
212 changed files with 32627 additions and 0 deletions

16
bankbackup/README.md Normal file
View file

@ -0,0 +1,16 @@
# BankBackup Help
Backup bank balances for all members of a guild
# bankbackup
- Usage: `[p]bankbackup`
- Restricted to: `GUILD_OWNER`
Backup your server's bank balances
# bankrestore
- Usage: `[p]bankrestore <set_or_add>`
- Restricted to: `GUILD_OWNER`
Restore your server's bank balances.<br/>Attach your backup file with this command.<br/><br/>**Arguments**<br/>- `<set_or_add>`: Whether you want to `add` or `set` balances from the backup.

15
bankbackup/__init__.py Normal file
View file

@ -0,0 +1,15 @@
import discord
from redbot.core.bot import Red
from redbot.core.utils import get_end_user_data_statement
from .base import BankBackup
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
async def setup(bot: Red):
cog = BankBackup(bot)
if discord.__version__ > "1.7.3":
await bot.add_cog(cog)
else:
bot.add_cog(cog)

86
bankbackup/base.py Normal file
View file

@ -0,0 +1,86 @@
import json
import typing as t
import aiohttp
from redbot.core import bank, commands
from redbot.core.bot import Red
from redbot.core.errors import BalanceTooHigh
from redbot.core.utils.chat_formatting import box, text_to_file
class BankBackup(commands.Cog):
"""
Backup bank balances for all members of a guild
"""
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
__version__ = "0.0.2"
def format_help_for_context(self, ctx):
helpcmd = super().format_help_for_context(ctx)
return f"{helpcmd}\nCog Version: {self.__version__}\nAuthor: {self.__author__}"
async def red_delete_data_for_user(self, *, requester, user_id: int):
"""No data to delete"""
def __init__(self, bot: Red):
self.bot: Red = bot
@commands.command(name="bankbackup")
@commands.guildowner()
async def backup(self, ctx: commands.Context):
"""Backup your guild's bank balances"""
if await bank.is_global():
return await ctx.send("Cannot make backup. Bank is set to global.")
_bank_members = await bank._config.all_members(ctx.guild)
bank_members: t.Dict[str, int] = {str(k): v["balance"] for k, v in _bank_members.items()}
raw = json.dumps(bank_members, indent=2)
file = text_to_file(raw, filename=f"bank_backup_{ctx.guild.id}.json")
await ctx.send("Here's your bank backup file!", file=file)
@commands.command(name="bankrestore")
@commands.guildowner()
async def restore(self, ctx: commands.Context, set_or_add: str):
"""
Restore your guild's bank balances.
Attach your backup file with this command.
**Arguments**
- `<set_or_add>`: Whether you want to `add` or `set` balances from the backup.
"""
if await bank.is_global():
return await ctx.send("Cannot restore backup because bank is set to global.")
if not ctx.message.attachments:
return await ctx.send("Attach your backup file to the message when using this command.")
if "a" not in set_or_add.lower() and "s" not in set_or_add.lower():
return await ctx.send(
"Specify whether you want to `add` or `set` balances from the backup.\n"
"Add: adds the backed up balance to the user's current balance\n"
"Set: sets the backup balance as the user's new balance.\n"
"You just type in 'set' or 'add' for this argument."
)
attachment_url = ctx.message.attachments[0].url
try:
async with aiohttp.ClientSession() as session:
async with session.get(attachment_url) as resp:
bank_data = await resp.json()
except Exception as e:
return await ctx.send(f"Error:{box(str(e), lang='python')}")
for uid, balance in bank_data.items():
member = ctx.guild.get_member(int(uid))
if not member:
continue
if "a" in set_or_add.lower():
try:
await bank.deposit_credits(member, balance)
except BalanceTooHigh as e:
await bank.set_balance(member, e.max_balance)
else:
await bank.set_balance(member, balance)
if "a" in set_or_add.lower():
await ctx.send("Saved balances have been added to user's current balance!")
else:
await ctx.send("Balances have been restored from the backup!")

28
bankbackup/info.json Normal file
View file

@ -0,0 +1,28 @@
{
"author": [
"Vertyco"
],
"description": "Backup/Restore for server bank balances",
"disabled": false,
"end_user_data_statement": "This cog does not store data about users.",
"hidden": false,
"install_msg": "Thank you for installing! This cog is for transferring bank balances on a per-server basis for bots that have local banks enabled.",
"min_bot_version": "3.4.0",
"min_python_version": [
3,
8,
1
],
"permissions": [
"administrator"
],
"required_cogs": {},
"requirements": [],
"short": "Backup and restore bank balances for all members in a server",
"tags": [
"utility",
"economy",
"bank"
],
"type": "COG"
}

62
bankdecay/README.md Normal file
View file

@ -0,0 +1,62 @@
Economy decay!<br/><br/>Periodically reduces users' red currency based on inactivity, encouraging engagement.<br/>Server admins can configure decay parameters, view settings, and manually trigger decay cycles.<br/>User activity is tracked via messages and reactions.
# [p]bankdecay
Setup economy credit decay for your server<br/>
- Usage: `[p]bankdecay`
- Restricted to: `ADMIN`
- Aliases: `bdecay`
- Checks: `server_only`
## [p]bankdecay toggle
Toggle the bank decay feature on or off.<br/>
- Usage: `[p]bankdecay toggle`
## [p]bankdecay setdays
Set the number of inactive days before decay starts.<br/>
- Usage: `[p]bankdecay setdays <days>`
## [p]bankdecay setpercent
Set the percentage of decay that occurs after the inactive period.<br/>
**Example**<br/>
If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.<br/>
- Usage: `[p]bankdecay setpercent <percent>`
## [p]bankdecay resettotal
Reset the total amount decayed to zero.<br/>
- Usage: `[p]bankdecay resettotal`
## [p]bankdecay initialize
Initialize the server and add every member to the config.<br/>
**Arguments**<br/>
- as_expired: (t/f) if True, initialize users as already expired<br/>
- Usage: `[p]bankdecay initialize <as_expired>`
## [p]bankdecay getexpired
Get a list of users who are currently expired and how much they will lose if decayed<br/>
- Usage: `[p]bankdecay getexpired`
## [p]bankdecay ignorerole
Add/Remove a role from the ignore list<br/>
Users with an ignored role will not have their balance decay<br/>
- Usage: `[p]bankdecay ignorerole <role>`
## [p]bankdecay logchannel
Set the log channel, each time the decay cycle runs this will be updated<br/>
- Usage: `[p]bankdecay logchannel <channel>`
## [p]bankdecay cleanup
Remove users from the config that are no longer in the server or have no balance<br/>
- Usage: `[p]bankdecay cleanup <confirm>`
## [p]bankdecay seen
Check when a user was last active (if at all)<br/>
- Usage: `[p]bankdecay seen <user>`
## [p]bankdecay bulkaddpercent
Add a percentage to all member balances.<br/>
Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.<br/>
- Usage: `[p]bankdecay bulkaddpercent <percent> <confirm>`
## [p]bankdecay bulkrempercent
Remove a percentage from all member balances.<br/>
Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.<br/>
- Usage: `[p]bankdecay bulkrempercent <percent> <confirm>`
## [p]bankdecay decaynow
Run a decay cycle on this server right now<br/>
- Usage: `[p]bankdecay decaynow [force=False]`
## [p]bankdecay view
View Bank Decay Settings<br/>
- Usage: `[p]bankdecay view`

11
bankdecay/__init__.py Normal file
View file

@ -0,0 +1,11 @@
from redbot.core.bot import Red
from redbot.core.utils import get_end_user_data_statement
from .main import BankDecay
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
async def setup(bot: Red):
cog = BankDecay(bot)
await bot.add_cog(cog)

27
bankdecay/abc.py Normal file
View file

@ -0,0 +1,27 @@
import typing as t
from abc import ABCMeta, abstractmethod
import discord
from discord.ext.commands.cog import CogMeta
from redbot.core.bot import Red
from .common.models import DB
class CompositeMetaClass(CogMeta, ABCMeta):
"""Type detection"""
class MixinMeta(metaclass=ABCMeta):
"""Type hinting"""
bot: Red
db: DB
@abstractmethod
async def save(self) -> None:
raise NotImplementedError
@abstractmethod
async def decay_guild(self, guild: discord.Guild, check_only: bool = False) -> t.Dict[str, int]:
raise NotImplementedError

369
bankdecay/commands/admin.py Normal file
View file

@ -0,0 +1,369 @@
import math
from datetime import datetime, timedelta
from io import StringIO
import discord
from redbot.core import bank, commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import humanize_number, text_to_file
from ..abc import MixinMeta
from ..common.confirm_view import ConfirmView
from ..common.models import User
_ = Translator("BankDecay", __file__)
@cog_i18n(_)
class Admin(MixinMeta):
@commands.group(aliases=["bdecay"])
@commands.admin_or_permissions(manage_guild=True)
@commands.guild_only()
async def bankdecay(self, ctx: commands.Context):
"""
Setup economy credit decay for your server
"""
pass
@bankdecay.command(name="view")
@commands.bot_has_permissions(embed_links=True)
async def view_settings(self, ctx: commands.Context):
"""View Bank Decay Settings"""
conf = self.db.get_conf(ctx.guild)
expired = 0
active = 0
left_server = 0
for uid, user in conf.users.items():
member = ctx.guild.get_member(uid)
if not member:
left_server += 1
elif user.last_active + timedelta(days=conf.inactive_days) < datetime.now():
expired += 1
else:
active += 1
ignored_roles = [f"<@&{i}>" for i in conf.ignored_roles]
log_channel = (
ctx.guild.get_channel(conf.log_channel) if ctx.guild.get_channel(conf.log_channel) else _("Not Set")
)
now = datetime.now()
next_midnight = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
next_run = f"<t:{round(next_midnight.timestamp())}:R>"
txt = _(
"`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
).format(
conf.enabled,
conf.inactive_days,
round(conf.percent_decay * 100),
humanize_number(len(conf.users)),
humanize_number(active),
humanize_number(expired),
humanize_number(left_server),
humanize_number(conf.total_decayed),
log_channel,
)
if conf.enabled:
txt += _("`Next Runtime: `{}\n").format(next_run)
if ignored_roles:
joined = ", ".join(ignored_roles)
txt += _("**Ignored Roles**\n") + joined
embed = discord.Embed(
title=_("BankDecay Settings"),
description=txt,
color=ctx.author.color,
)
await ctx.send(embed=embed)
@bankdecay.command(name="toggle")
async def toggle_decay(self, ctx: commands.Context):
"""
Toggle the bank decay feature on or off.
"""
if await bank.is_global():
await ctx.send(_("This command is not available when using global bank."))
return
conf = self.db.get_conf(ctx.guild)
conf.enabled = not conf.enabled
await ctx.send(_("Bank decay has been {}.").format(_("enabled") if conf.enabled else _("disabled")))
await self.save()
@bankdecay.command(name="setdays")
async def set_inactive_days(self, ctx: commands.Context, days: commands.positive_int):
"""
Set the number of inactive days before decay starts.
"""
conf = self.db.get_conf(ctx.guild)
conf.inactive_days = days
await ctx.send(_("Inactive days set to {}.").format(days))
await self.save()
@bankdecay.command(name="setpercent")
async def set_percent_decay(self, ctx: commands.Context, percent: float):
"""
Set the percentage of decay that occurs after the inactive period.
**Example**
If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.
"""
if not 0 <= percent <= 1:
await ctx.send(_("Percent decay must be between 0 and 1."))
return
conf = self.db.get_conf(ctx.guild)
conf.percent_decay = percent
await ctx.send(_("Percent decay set to {}%.").format(round(percent * 100)))
await self.save()
@bankdecay.command(name="resettotal")
async def reset_total_decayed(self, ctx: commands.Context):
"""
Reset the total amount decayed to zero.
"""
conf = self.db.get_conf(ctx.guild)
conf.total_decayed = 0
await ctx.send(_("Total decayed amount has been reset to 0."))
await self.save()
@bankdecay.command(name="decaynow")
async def decay_now(self, ctx: commands.Context, force: bool = False):
"""
Run a decay cycle on this server right now
"""
if await bank.is_global():
await ctx.send(_("This command is not available when using global bank."))
return
conf = self.db.get_conf(ctx.guild)
if not conf.enabled:
txt = _("The decay system is currently disabled!")
return await ctx.send(txt)
async with ctx.typing():
currency = await bank.get_currency_name(ctx.guild)
if not force:
decayed = await self.decay_guild(ctx.guild, check_only=True)
if not decayed:
txt = _("There were no users affected by the decay cycle")
return await ctx.send(txt)
grammar = _("account") if len(decayed) == 1 else _("accounts")
txt = _("Are you sure you want to decay {} for a total of {}?").format(
f"**{humanize_number(len(decayed))}** {grammar}",
f"**{humanize_number(sum(decayed.values()))}** {currency}",
)
view = ConfirmView(ctx.author)
msg = await ctx.send(txt, view=view)
await view.wait()
if not view.value:
txt = _("Decay cycle cancelled")
return await msg.edit(content=txt, view=None)
txt = _("Decaying user accounts, one moment...")
await msg.edit(content=txt, view=None)
else:
txt = _("Decaying user accounts, one moment...")
msg = await ctx.send(txt)
decayed = await self.decay_guild(ctx.guild)
txt = _("User accounts have been decayed!\n- Users Affected: {}\n- Total {} Decayed: {}").format(
humanize_number(len(decayed)), currency, humanize_number(sum(decayed.values()))
)
await msg.edit(content=txt)
await self.save()
@bankdecay.command(name="getexpired")
async def get_expired_users(self, ctx: commands.Context):
"""Get a list of users who are currently expired and how much they will lose if decayed"""
if await bank.is_global():
await ctx.send(_("This command is not available when using global bank."))
return
async with ctx.typing():
decayed = await self.decay_guild(ctx.guild, check_only=True)
if not decayed:
txt = _("There were no users that would be affected by the decay cycle")
return await ctx.send(txt)
grammar = _("account") if len(decayed) == 1 else _("accounts")
txt = _("This would decay {} for a total of {}").format(
f"**{humanize_number(len(decayed))}** {grammar}",
f"**{humanize_number(sum(decayed.values()))}** credits",
)
# Create a text file with the list of users and how much they will lose
buffer = StringIO()
for user, amount in sorted(decayed.items(), key=lambda x: x[1], reverse=True):
buffer.write(f"{user}: {amount}\n")
buffer.seek(0)
file = text_to_file(buffer.getvalue(), filename="expired_users.txt")
await ctx.send(txt, file=file)
@bankdecay.command(name="cleanup")
async def cleanup(self, ctx: commands.Context, confirm: bool):
"""
Remove users from the config that are no longer in the server or have no balance
"""
if await bank.is_global():
await ctx.send(_("This command is not available when using global bank."))
return
if not confirm:
txt = _("Not removing users from the config")
return await ctx.send(txt)
conf = self.db.get_conf(ctx.guild)
global_bank = await bank.is_global()
cleaned = 0
for uid in conf.users.copy():
member = ctx.guild.get_member(uid)
if not member:
del conf.users[uid]
cleaned += 1
elif not global_bank and await bank.get_balance(member) == 0:
del conf.users[uid]
cleaned += 1
if not cleaned:
txt = _("No users were removed from the config.")
return await ctx.send(txt)
grammar = _("user") if cleaned == 1 else _("users")
txt = _("Removed {} from the config.").format(f"{cleaned} {grammar}")
await ctx.send(txt)
await self.save()
@bankdecay.command(name="initialize")
async def initialize_guild(self, ctx: commands.Context, as_expired: bool):
"""
Initialize the server and add every member to the config.
**Arguments**
- as_expired: (t/f) if True, initialize users as already expired
"""
if await bank.is_global():
await ctx.send(_("This command is not available when using global bank."))
return
async with ctx.typing():
initialized = 0
conf = self.db.get_conf(ctx.guild)
for member in ctx.guild.members:
if member.bot: # Skip bots
continue
if member.id in conf.users:
continue
user = conf.get_user(member) # This will add the member to the config if not already present
initialized += 1
if as_expired:
user.last_active = user.last_active - timedelta(days=conf.inactive_days + 1)
grammar = _("member") if initialized == 1 else _("members")
await ctx.send(_("Server initialized! {} added to the config.").format(f"{initialized} {grammar}"))
await self.save()
@bankdecay.command(name="seen")
async def last_seen(self, ctx: commands.Context, *, user: discord.Member | int):
"""
Check when a user was last active (if at all)
"""
conf = self.db.get_conf(ctx.guild)
uid = user if isinstance(user, int) else user.id
if uid not in conf.users:
txt = _("This user is not in the config yet!")
return await ctx.send(txt)
user: User = conf.get_user(uid)
txt = _("User was last seen {}").format(f"{user.seen_f} ({user.seen_r})")
await ctx.send(txt)
@bankdecay.command(name="ignorerole")
async def ignore_role(self, ctx: commands.Context, *, role: discord.Role):
"""
Add/Remove a role from the ignore list
Users with an ignored role will not have their balance decay
"""
conf = self.db.get_conf(ctx.guild)
if role.id in conf.ignored_roles:
conf.ignored_roles.remove(role.id)
txt = _("Role removed from the ignore list.")
else:
conf.ignored_roles.append(role.id)
txt = _("Role added to the ignore list.")
await ctx.send(txt)
await self.save()
@bankdecay.command(name="logchannel")
async def set_log_channel(self, ctx: commands.Context, *, channel: discord.TextChannel):
"""
Set the log channel, each time the decay cycle runs this will be updated
"""
conf = self.db.get_conf(ctx.guild)
conf.log_channel = channel.id
await ctx.send(_("Log channel has been set!"))
await self.save()
@bankdecay.command(name="bulkaddpercent")
async def bulk_add_percent(self, ctx: commands.Context, percent: int, confirm: bool):
"""
Add a percentage to all member balances.
Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.
"""
if await bank.is_global():
await ctx.send(_("This command is not available when using global bank."))
return
if not confirm:
txt = _("Not adding credits to users")
return await ctx.send(txt)
if percent < 1:
txt = _("Percent must be greater than 1!")
return await ctx.send(txt)
async with ctx.typing():
refunded = 0
ratio = percent / 100
conf = self.db.get_conf(ctx.guild)
users = [ctx.guild.get_member(int(i)) for i in conf.users if ctx.guild.get_member(int(i))]
for user in users:
bal = await bank.get_balance(user)
to_give = math.ceil(bal * ratio)
await bank.set_balance(user, bal + to_give)
refunded += to_give
await ctx.send(_("Credits added: {}").format(humanize_number(refunded)))
@bankdecay.command(name="bulkrempercent")
async def bulk_rem_percent(self, ctx: commands.Context, percent: int, confirm: bool):
"""
Remove a percentage from all member balances.
Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.
"""
if await bank.is_global():
await ctx.send(_("This command is not available when using global bank."))
return
if not confirm:
txt = _("Not removing credits from users")
return await ctx.send(txt)
if percent < 1:
txt = _("Percent must be greater than 1!")
return await ctx.send(txt)
async with ctx.typing():
taken = 0
ratio = percent / 100
conf = self.db.get_conf(ctx.guild)
users = [ctx.guild.get_member(int(i)) for i in conf.users if ctx.guild.get_member(int(i))]
for user in users:
bal = await bank.get_balance(user)
to_take = math.ceil(bal * ratio)
await bank.withdraw_credits(user, to_take)
taken += to_take
await ctx.send(_("Credits removed: {}").format(humanize_number(taken)))

View file

@ -0,0 +1,307 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: de_DE\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,350 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: es_ES\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr "\n"
" Configura la caída de créditos de la economía para tu servidor\n"
" "
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr "Ver Configuraciones de Caída del Banco"
#: commands\admin.py:48
msgid "Not Set"
msgstr "No fijado"
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr "`Decadencia Activada: `{}\n"
"`Días Inactivos: `{}\n"
"`Porcentaje de Decadencia: `{}\n"
"`Usuarios Guardados: `{}\n"
"`Usuarios Activos: `{}\n"
"`Usuarios Expirados: `{}\n"
"`Usuarios Antiguos: `{}\n"
"`Total Decadente: `{}\n"
"`Canal de Registro: `{}\n"
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr "`Próxima Ejecución: `{}\n"
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr "**Funciones ignoradas**\n"
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr "Configuraciones de Decadencia del Banco"
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr "\n"
" Activa o desactiva la función de caída del banco.\n"
" "
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr "Este comando no está disponible cuando se usa el banco global."
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr "La caída del banco ha sido {}."
#: commands\admin.py:96
msgid "enabled"
msgstr "activada"
#: commands\admin.py:96
msgid "disabled"
msgstr "desactivada"
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr "\n"
" Establece el número de días inactivos antes de que comience la decadencia.\n"
" "
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr "Los días inactivos no pueden ser negativos."
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr "Días inactivos establecidos en {}."
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr "\n"
" Establece el porcentaje de decadencia que ocurre después del período de inactividad.\n\n"
" **Ejemplo**\n"
" Si la decadencia es del 5%, después de los días establecidos de inactividad perderán el 5% de su saldo cada día.\n"
" "
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr "El porcentaje de decadencia debe estar entre 0 y 1."
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr "Porcentaje de decadencia establecido en {}%."
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr "\n"
" Restablece la cantidad total decadente a cero.\n"
" "
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr "La cantidad total decadente ha sido restablecida a 0."
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr "\n"
" Ejecuta un ciclo de decadencia en este servidor ahora mismo\n"
" "
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr "¡El sistema de decadencia está actualmente desactivado!"
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr "No hubo usuarios afectados por el ciclo de decadencia"
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr "cuenta"
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr "cuentas"
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr "¿Estás seguro de que quieres decaer {} por un total de {}?"
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr "Ciclo de decadencia cancelado"
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr "Decayendo cuentas de usuarios, un momento..."
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr "¡Las cuentas de usuario han decaído!\n"
"- Usuarios Afectados: {}\n"
"- Total {} Decadido: {}"
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr "Obtener una lista de usuarios que están actualmente expirados y cuánto perderán si se descomponen"
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr "No hubo usuarios que se verían afectados por el ciclo de descomposición"
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr "Esto descompondría {} por un total de {}"
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr "\n"
" Eliminar usuarios de la configuración que ya no están en el servidor o no tienen saldo\n"
" "
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr "No se están eliminando usuarios de la configuración"
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr "No se eliminaron usuarios de la configuración."
#: commands\admin.py:235
msgid "user"
msgstr "usuario"
#: commands\admin.py:235
msgid "users"
msgstr "usuarios"
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr "Eliminado {} de la configuración."
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr "\n"
" Inicializar el servidor y agregar a cada miembro a la configuración.\n\n"
" **Argumentos**\n"
" - as_expired: (t/f) si es True, inicializa a los usuarios como ya expirados\n"
" "
#: commands\admin.py:265
msgid "member"
msgstr "miembro"
#: commands\admin.py:265
msgid "members"
msgstr "miembros"
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr "¡Servidor inicializado! {} añadido a la configuración."
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr "\n"
" Verifica cuándo fue la última vez que un usuario estuvo activo (si es que alguna vez)\n"
" "
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr "¡Este usuario aún no está en la configuración!"
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr "El usuario fue visto por última vez {}"
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr "\n"
" Añadir/Eliminar un rol de la lista de ignorados\n\n"
" Los usuarios con un rol ignorado no verán decaer su saldo\n"
" "
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr "Rol eliminado de la lista de ignorados."
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr "Rol añadido a la lista de ignorados."
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr "\n"
" Configurar el canal de registro, cada vez que se ejecute el ciclo de descomposición esto se actualizará\n"
" "
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr "¡Se ha establecido el canal de registro!"
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr "\n"
" Agregar un porcentaje a los saldos de todos los miembros.\n\n"
" ¿Dismuiste accidentalmente demasiados créditos? Añadir en masa a cada saldo de usuario en el servidor basado en un porcentaje de su saldo actual.\n"
" "
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr "No agregando créditos a los usuarios"
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr "¡El porcentaje debe ser mayor que 1!"
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr "Créditos agregados: {}"
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr "\n"
" Eliminar un porcentaje de los saldos de todos los miembros.\n\n"
" ¿Reembolsaste accidentalmente demasiados créditos con bulkaddpercent? Eliminar en masa de cada saldo de usuario en el servidor basado en un porcentaje de su saldo actual.\n"
" "
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr "No eliminando créditos a los usuarios"
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr "Créditos eliminados: {}"

View file

@ -0,0 +1,307 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:56\n"
"Last-Translator: \n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: fr_FR\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,307 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: hr\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: hr_HR\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,307 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: ko\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: ko_KR\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,322 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
#: commands\admin.py:23
#, docstring
msgid ""
"\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid ""
"`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid ""
"\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid ""
"\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid ""
"\n"
" Set the percentage of decay that occurs after the inactive period.\n"
"\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid ""
"\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid ""
"\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid ""
"User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid ""
"Get a list of users who are currently expired and how much they will lose if"
" decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid ""
"\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid ""
"\n"
" Initialize the server and add every member to the config.\n"
"\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid ""
"\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid ""
"\n"
" Add/Remove a role from the ignore list\n"
"\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid ""
"\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid ""
"\n"
" Add a percentage to all member balances.\n"
"\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid ""
"\n"
" Remove a percentage from all member balances.\n"
"\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,307 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: pt_PT\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,307 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: ru\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: ru_RU\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,307 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-01-04 12:10-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/commands/locales/messages.pot\n"
"X-Crowdin-File-ID: 108\n"
"Language: tr_TR\n"
#: commands\admin.py:23
#, docstring
msgid "\n"
" Setup economy credit decay for your server\n"
" "
msgstr ""
#: commands\admin.py:31
#, docstring
msgid "View Bank Decay Settings"
msgstr ""
#: commands\admin.py:48
msgid "Not Set"
msgstr ""
#: commands\admin.py:53
msgid "`Decay Enabled: `{}\n"
"`Inactive Days: `{}\n"
"`Percent Decay: `{}\n"
"`Saved Users: `{}\n"
"`Active Users: `{}\n"
"`Expired Users: `{}\n"
"`Stale Users: `{}\n"
"`Total Decayed: `{}\n"
"`Log Channel: `{}\n"
msgstr ""
#: commands\admin.py:75
msgid "`Next Runtime: `{}\n"
msgstr ""
#: commands\admin.py:78
msgid "**Ignored Roles**\n"
msgstr ""
#: commands\admin.py:80
msgid "BankDecay Settings"
msgstr ""
#: commands\admin.py:88
#, docstring
msgid "\n"
" Toggle the bank decay feature on or off.\n"
" "
msgstr ""
#: commands\admin.py:92 commands\admin.py:144 commands\admin.py:186
#: commands\admin.py:213 commands\admin.py:249 commands\admin.py:318
#: commands\admin.py:350
msgid "This command is not available when using global bank."
msgstr ""
#: commands\admin.py:96
msgid "Bank decay has been {}."
msgstr ""
#: commands\admin.py:96
msgid "enabled"
msgstr ""
#: commands\admin.py:96
msgid "disabled"
msgstr ""
#: commands\admin.py:101
#, docstring
msgid "\n"
" Set the number of inactive days before decay starts.\n"
" "
msgstr ""
#: commands\admin.py:105
msgid "Inactive days cannot be negative."
msgstr ""
#: commands\admin.py:109
msgid "Inactive days set to {}."
msgstr ""
#: commands\admin.py:114
#, docstring
msgid "\n"
" Set the percentage of decay that occurs after the inactive period.\n\n"
" **Example**\n"
" If decay is 5%, then after the set days of inactivity they will lose 5% of their balance every day.\n"
" "
msgstr ""
#: commands\admin.py:121
msgid "Percent decay must be between 0 and 1."
msgstr ""
#: commands\admin.py:125
msgid "Percent decay set to {}%."
msgstr ""
#: commands\admin.py:130
#, docstring
msgid "\n"
" Reset the total amount decayed to zero.\n"
" "
msgstr ""
#: commands\admin.py:135
msgid "Total decayed amount has been reset to 0."
msgstr ""
#: commands\admin.py:140
#, docstring
msgid "\n"
" Run a decay cycle on this server right now\n"
" "
msgstr ""
#: commands\admin.py:148
msgid "The decay system is currently disabled!"
msgstr ""
#: commands\admin.py:155
msgid "There were no users affected by the decay cycle"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "account"
msgstr ""
#: commands\admin.py:157 commands\admin.py:194
msgid "accounts"
msgstr ""
#: commands\admin.py:158
msgid "Are you sure you want to decay {} for a total of {}?"
msgstr ""
#: commands\admin.py:166
msgid "Decay cycle cancelled"
msgstr ""
#: commands\admin.py:168 commands\admin.py:171
msgid "Decaying user accounts, one moment..."
msgstr ""
#: commands\admin.py:176
msgid "User accounts have been decayed!\n"
"- Users Affected: {}\n"
"- Total {} Decayed: {}"
msgstr ""
#: commands\admin.py:184
#, docstring
msgid "Get a list of users who are currently expired and how much they will lose if decayed"
msgstr ""
#: commands\admin.py:191
msgid "There were no users that would be affected by the decay cycle"
msgstr ""
#: commands\admin.py:195
msgid "This would decay {} for a total of {}"
msgstr ""
#: commands\admin.py:209
#, docstring
msgid "\n"
" Remove users from the config that are no longer in the server or have no balance\n"
" "
msgstr ""
#: commands\admin.py:217
msgid "Not removing users from the config"
msgstr ""
#: commands\admin.py:232
msgid "No users were removed from the config."
msgstr ""
#: commands\admin.py:235
msgid "user"
msgstr ""
#: commands\admin.py:235
msgid "users"
msgstr ""
#: commands\admin.py:236
msgid "Removed {} from the config."
msgstr ""
#: commands\admin.py:242
#, docstring
msgid "\n"
" Initialize the server and add every member to the config.\n\n"
" **Arguments**\n"
" - as_expired: (t/f) if True, initialize users as already expired\n"
" "
msgstr ""
#: commands\admin.py:265
msgid "member"
msgstr ""
#: commands\admin.py:265
msgid "members"
msgstr ""
#: commands\admin.py:266
msgid "Server initialized! {} added to the config."
msgstr ""
#: commands\admin.py:271
#, docstring
msgid "\n"
" Check when a user was last active (if at all)\n"
" "
msgstr ""
#: commands\admin.py:277
msgid "This user is not in the config yet!"
msgstr ""
#: commands\admin.py:280
msgid "User was last seen {}"
msgstr ""
#: commands\admin.py:285
#, docstring
msgid "\n"
" Add/Remove a role from the ignore list\n\n"
" Users with an ignored role will not have their balance decay\n"
" "
msgstr ""
#: commands\admin.py:293
msgid "Role removed from the ignore list."
msgstr ""
#: commands\admin.py:296
msgid "Role added to the ignore list."
msgstr ""
#: commands\admin.py:302
#, docstring
msgid "\n"
" Set the log channel, each time the decay cycle runs this will be updated\n"
" "
msgstr ""
#: commands\admin.py:307
msgid "Log channel has been set!"
msgstr ""
#: commands\admin.py:312
#, docstring
msgid "\n"
" Add a percentage to all member balances.\n\n"
" Accidentally decayed too many credits? Bulk add to every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:322
msgid "Not adding credits to users"
msgstr ""
#: commands\admin.py:326 commands\admin.py:358
msgid "Percent must be greater than 1!"
msgstr ""
#: commands\admin.py:340
msgid "Credits added: {}"
msgstr ""
#: commands\admin.py:344
#, docstring
msgid "\n"
" Remove a percentage from all member balances.\n\n"
" Accidentally refunded too many credits with bulkaddpercent? Bulk remove from every user's balance in the server based on a percentage of their current balance.\n"
" "
msgstr ""
#: commands\admin.py:354
msgid "Not removing credits from users"
msgstr ""
#: commands\admin.py:372
msgid "Credits removed: {}"
msgstr ""

View file

@ -0,0 +1,28 @@
import orjson
from pydantic import VERSION, BaseModel
class Base(BaseModel):
def model_dump_json(self, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_dump_json(*args, **kwargs)
return super().json(*args, **kwargs)
def model_dump(self, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_dump(*args, **kwargs)
if kwargs.pop("mode", "") == "json":
return orjson.loads(super().json(*args, **kwargs))
return super().dict(*args, **kwargs)
@classmethod
def model_validate_json(cls, obj, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_validate_json(obj, *args, **kwargs)
return super().parse_raw(obj, *args, **kwargs)
@classmethod
def model_validate(cls, obj, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_validate(obj, *args, **kwargs)
return super().parse_obj(obj, *args, **kwargs)

View file

@ -0,0 +1,42 @@
from contextlib import suppress
import discord
from redbot.core.i18n import Translator
_ = Translator("BankDecay", __file__)
class ConfirmView(discord.ui.View):
def __init__(self, author: discord.Member):
super().__init__(timeout=60)
self.author = author
self.yes.label = _("Yes")
self.no.label = _("No")
self.value = None
async def interaction_check(self, interaction: discord.Interaction) -> bool:
if interaction.user.id != self.author.id:
await interaction.response.send_message(_("This isn't your menu!"), ephemeral=True)
return False
return True
async def on_timeout(self) -> None:
self.stop()
@discord.ui.button(style=discord.ButtonStyle.primary)
async def yes(self, interaction: discord.Interaction, button: discord.ui.Button):
with suppress(discord.NotFound):
await interaction.response.defer()
self.value = True
self.stop()
@discord.ui.button(style=discord.ButtonStyle.primary)
async def no(self, interaction: discord.Interaction, button: discord.ui.Button):
with suppress(discord.NotFound):
await interaction.response.defer()
self.value = False
self.stop()

View file

@ -0,0 +1,84 @@
import discord
from redbot.core import commands
from ..abc import MixinMeta
class Listeners(MixinMeta):
@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
if not message.guild:
return
if not message.author:
return
if message.author.bot:
return
self.db.refresh_user(message.author)
@commands.Cog.listener()
async def on_message_edit(self, before: discord.Message, after: discord.Message) -> None:
guild = before.guild or after.guild
if not guild:
return
author = before.author or after.author
if not author:
return
if author.bot:
return
self.db.refresh_user(author)
@commands.Cog.listener()
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None:
if not payload.guild_id:
return
guild = self.bot.get_guild(payload.guild_id)
if not guild:
return
if not payload.member:
return
if payload.member.bot:
return
self.db.refresh_user(payload.member)
@commands.Cog.listener()
async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent) -> None:
if not payload.guild_id:
return
guild = self.bot.get_guild(payload.guild_id)
if not guild:
return
if not payload.member:
return
if payload.member.bot:
return
self.db.refresh_user(payload.member)
@commands.Cog.listener()
async def on_presence_update(self, before: discord.Member, after: discord.Member) -> None:
guild = before.guild or after.guild
if not guild:
return
author = before or after
if author.bot:
return
self.db.refresh_user(author)
@commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member) -> None:
guild = before.guild or after.guild
if not guild:
return
author = before or after
if author.bot:
return
self.db.refresh_user(author)
@commands.Cog.listener()
async def on_voice_state_update(
self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState
) -> None:
if not member.guild:
return
if member.bot:
return
self.db.refresh_user(member)

View file

@ -0,0 +1,47 @@
from datetime import datetime
import discord
from pydantic import Field
from . import Base
class User(Base):
last_active: datetime = Field(default_factory=lambda: datetime.now())
@property
def seen_r(self) -> str:
return f"<t:{int(self.last_active.timestamp())}:R>"
@property
def seen_f(self) -> str:
return f"<t:{int(self.last_active.timestamp())}:F>"
class GuildSettings(Base):
enabled: bool = False
inactive_days: int = 30
percent_decay: float = 0.05 # 5%
users: dict[int, User] = {}
total_decayed: int = 0
ignored_roles: list[int] = []
log_channel: int = 0
def get_user(self, user: discord.Member | discord.User | int) -> User:
uid = user if isinstance(user, int) else user.id
return self.users.setdefault(uid, User())
class DB(Base):
configs: dict[int, GuildSettings] = {}
last_run: datetime = None
def get_conf(self, guild: discord.Guild | int) -> GuildSettings:
gid = guild if isinstance(guild, int) else guild.id
return self.configs.setdefault(gid, GuildSettings())
def refresh_user(self, user: discord.Member | discord.User) -> None:
if isinstance(user, discord.User):
return
conf = self.get_conf(user.guild)
conf.get_user(user).last_active = datetime.now()

View file

@ -0,0 +1,10 @@
import os
import pytz
from apscheduler.jobstores.memory import MemoryJobStore
from apscheduler.schedulers.asyncio import AsyncIOScheduler
if "TZ" not in os.environ:
os.environ["TZ"] = "UTC"
scheduler = AsyncIOScheduler(jobstores={"default": MemoryJobStore()})
scheduler.configure(timezone=pytz.timezone("UTC"))

16
bankdecay/info.json Normal file
View file

@ -0,0 +1,16 @@
{
"author": ["Vertyco"],
"description": "Inactivity-based economy credit decay with customizable settings",
"disabled": false,
"end_user_data_statement": "This cog does not store any private data about users.",
"hidden": false,
"install_msg": "Thank you for installing! Type `[p]bankdecay` to view the commands.\n**NOTE**: THIS DOES NOT WORK WITH GLOBAL BANKS",
"min_bot_version": "3.5.3",
"min_python_version": [3, 10, 0],
"permissions": [],
"required_cogs": {},
"requirements": ["pydantic", "pytz", "apscheduler"],
"short": "Inactivity-based economy credit decay",
"tags": [],
"type": "COG"
}

View file

@ -0,0 +1,47 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: de_DE\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

View file

@ -0,0 +1,53 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: es_ES\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr "\n"
" ¡Degradación de la economía!\n\n"
" Reduce periódicamente la moneda roja de los usuarios basada en la inactividad, fomentando la participación.\n"
" Los administradores del servidor pueden configurar parámetros de degradación, ver configuraciones y activar manualmente ciclos de degradación.\n"
" La actividad de los usuarios se rastrea a través de mensajes y reacciones.\n"
" "
#: main.py:157
msgid "Bank Decay Cycle"
msgstr "Ciclo de Degradación Bancaria"
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr "- Saldos de Usuarios Degradados: {}\n"
"- Cantidad Total Degradada: {}"
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr "No se degradaron saldos de usuarios durante este ciclo."
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr "No hay datos que borrar"

View file

@ -0,0 +1,47 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: fr_FR\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

View file

@ -0,0 +1,47 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: hr\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: hr_HR\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

View file

@ -0,0 +1,47 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: ko\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: ko_KR\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

View file

@ -0,0 +1,43 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
#: main.py:29
#, docstring
msgid ""
"\n"
" Economy decay!\n"
"\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid ""
"- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

View file

@ -0,0 +1,47 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: pt_PT\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

View file

@ -0,0 +1,47 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: ru\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: ru_RU\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

View file

@ -0,0 +1,47 @@
msgid ""
msgstr ""
"Project-Id-Version: vrt-cogs\n"
"POT-Creation-Date: 2024-02-08 18:30-0500\n"
"PO-Revision-Date: 2024-12-03 14:57\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: vrt-cogs\n"
"X-Crowdin-Project-ID: 550681\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: /[vertyco.vrt-cogs] main/bankdecay/locales/messages.pot\n"
"X-Crowdin-File-ID: 110\n"
"Language: tr_TR\n"
#: main.py:29
#, docstring
msgid "\n"
" Economy decay!\n\n"
" Periodically reduces users' red currency based on inactivity, encouraging engagement.\n"
" Server admins can configure decay parameters, view settings, and manually trigger decay cycles.\n"
" User activity is tracked via messages and reactions.\n"
" "
msgstr ""
#: main.py:157
msgid "Bank Decay Cycle"
msgstr ""
#: main.py:159
msgid "- User Balances Decayed: {}\n"
"- Total Amount Decayed: {}"
msgstr ""
#: main.py:164
msgid "No user balances were decayed during this cycle."
msgstr ""
#: main.py:214
#, docstring
msgid "No data to delete"
msgstr ""

214
bankdecay/main.py Normal file
View file

@ -0,0 +1,214 @@
import asyncio
import logging
import math
import typing as t
from datetime import datetime, timedelta
from io import StringIO
import discord
from redbot.core import Config, bank, commands
from redbot.core.bot import Red
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import humanize_number, text_to_file
from .abc import CompositeMetaClass
from .commands.admin import Admin
from .common.listeners import Listeners
from .common.models import DB
from .common.scheduler import scheduler
log = logging.getLogger("red.vrt.bankdecay")
RequestType = t.Literal["discord_deleted_user", "owner", "user", "user_strict"]
_ = Translator("BankDecay", __file__)
# redgettext -D main.py commands/admin.py --command-docstring
@cog_i18n(_)
class BankDecay(Admin, Listeners, commands.Cog, metaclass=CompositeMetaClass):
"""
Economy decay!
Periodically reduces users' red currency based on inactivity, encouraging engagement.
Server admins can configure decay parameters, view settings, and manually trigger decay cycles.
User activity is tracked via messages and reactions.
"""
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
__version__ = "0.3.12"
def __init__(self, bot: Red):
super().__init__()
self.bot = bot
self.config = Config.get_conf(self, 117, force_registration=True)
self.config.register_global(db={})
self.db: DB = DB()
self.saving = False
async def cog_load(self) -> None:
scheduler.start()
scheduler.remove_all_jobs()
asyncio.create_task(self.initialize())
async def cog_unload(self) -> None:
scheduler.remove_all_jobs()
scheduler.shutdown(wait=False)
async def initialize(self) -> None:
await self.bot.wait_until_red_ready()
data = await self.config.db()
self.db = await asyncio.to_thread(DB.model_validate, data)
log.info("Config loaded")
await self.start_jobs()
async def start_jobs(self):
kwargs = {
"func": self.autodecay_guilds,
"trigger": "cron",
"minute": 0,
"hour": 0,
"id": "BankDecay.autodecay_guilds",
"replace_existing": True,
"misfire_grace_time": 3600, # 1 hour grace time for missed job
}
# If it has been more than 24 hours since the last run, schedule it to run now
if self.db.last_run is not None and (datetime.now() - self.db.last_run) > timedelta(hours=24):
kwargs["next_run_time"] = datetime.now() + timedelta(seconds=5)
# Schedule decay job
scheduler.add_job(**kwargs)
async def autodecay_guilds(self):
if await bank.is_global():
log.error("This cog cannot be used with a global bank!")
return
log.info("Running decay_guilds!")
total_affected = 0
total_decayed = 0
for guild_id in self.db.configs.copy():
guild = self.bot.get_guild(guild_id)
if not guild:
# Remove guids that the bot is no longer a part of
del self.db.configs[guild_id]
continue
decayed = await self.decay_guild(guild)
total_affected += len(decayed)
total_decayed += sum(decayed.values())
if total_affected or total_decayed:
log.info(f"Decayed {total_affected} users balances for a total of {total_decayed} credits!")
self.db.last_run = datetime.now()
await self.save()
async def decay_guild(self, guild: discord.Guild, check_only: bool = False) -> t.Dict[str, int]:
now = datetime.now()
conf = self.db.get_conf(guild)
if not conf.enabled and not check_only:
return {}
_bank_members = await bank._config.all_members(guild)
bank_members: t.Dict[int, int] = {int(k): v["balance"] for k, v in _bank_members.items()}
# Decayed users: dict[username, amount]
decayed: t.Dict[str, int] = {}
uids = [i for i in conf.users]
for user_id in uids:
user = guild.get_member(user_id)
if not user:
# User no longer in guild
continue
if any(r.id in conf.ignored_roles for r in user.roles):
# Don't decay user balances with roles in the ignore list
continue
last_active = conf.get_user(user).last_active
delta = now - last_active
if delta.days <= conf.inactive_days:
continue
bal = bank_members.get(user_id)
# bal = await bank.get_balance(user)
if not bal:
continue
credits_to_remove = math.ceil(bal * conf.percent_decay)
new_bal = bal - credits_to_remove
if not check_only:
await bank.set_balance(user, new_bal)
decayed[user.name] = credits_to_remove
if check_only:
return decayed
conf.total_decayed += sum(decayed.values())
log.info(f"Decayed guild {guild.name}.\nUsers decayed: {len(decayed)}\nTotal: {sum(decayed.values())}")
log_channel = guild.get_channel(conf.log_channel)
if not log_channel:
return decayed
if not log_channel.permissions_for(guild.me).embed_links:
return decayed
title = _("Bank Decay Cycle")
if decayed:
txt = _("- User Balances Decayed: {}\n- Total Amount Decayed: {}").format(
f"`{humanize_number(len(decayed))}`", f"`{humanize_number(sum(decayed.values()))}`"
)
color = discord.Color.yellow()
else:
txt = _("No user balances were decayed during this cycle.")
color = discord.Color.blue()
embed = discord.Embed(
title=title,
description=txt,
color=color,
timestamp=datetime.now(),
)
# Create a text file with the list of users and how much they will lose
buffer = StringIO()
for user, amount in sorted(decayed.items(), key=lambda x: x[1], reverse=True):
buffer.write(f"{user}: {amount}\n")
file = text_to_file(buffer.getvalue(), filename="decay.txt")
perms = [
log_channel.permissions_for(guild.me).attach_files,
log_channel.permissions_for(guild.me).embed_links,
]
if not any(perms):
return decayed
try:
if perms[0] and perms[1]:
await log_channel.send(embed=embed, file=file)
elif perms[1]:
await log_channel.send(embed=embed)
except Exception as e:
log.error(f"Failed to send decay log to {log_channel.name} in {guild.name}", exc_info=e)
return decayed
async def save(self) -> None:
if self.saving:
return
try:
self.saving = True
dump = self.db.model_dump(mode="json")
await self.config.db.set(dump)
except Exception as e:
log.exception("Failed to save config", exc_info=e)
finally:
self.saving = False
def format_help_for_context(self, ctx: commands.Context):
helpcmd = super().format_help_for_context(ctx)
txt = "Version: {}\nAuthor: {}".format(self.__version__, self.__author__)
return f"{helpcmd}\n\n{txt}"
async def red_delete_data_for_user(self, *, requester: RequestType, user_id: int):
"""No data to delete"""

6
bankevents/README.md Normal file
View file

@ -0,0 +1,6 @@
Dispatches listener events for Red bank transactions and payday claims.<br/>- red_bank_set_balance<br/>- red_bank_transfer_credits<br/>- red_bank_wipe<br/>- red_bank_prune<br/>- red_bank_set_global<br/>- red_economy_payday_claim<br/><br/>Shoutout to YamiKaitou for starting the work on this 2+ years ago with a PR.<br/>Maybe one day it will be merged into core.<br/>https://github.com/Cog-Creators/Red-DiscordBot/pull/5325
# [p]bankevents
Get help using the BankEvents cog<br/>
- Usage: `[p]bankevents`
- Restricted to: `BOT_OWNER`

10
bankevents/__init__.py Normal file
View file

@ -0,0 +1,10 @@
from redbot.core.bot import Red
from redbot.core.utils import get_end_user_data_statement
from .main import BankEvents
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
async def setup(bot: Red):
await bot.add_cog(BankEvents(bot))

14
bankevents/abc.py Normal file
View file

@ -0,0 +1,14 @@
from abc import ABCMeta
from discord.ext.commands.cog import CogMeta
from redbot.core.bot import Red
class CompositeMetaClass(CogMeta, ABCMeta):
"""Type detection"""
class MixinMeta(metaclass=ABCMeta):
"""Type hinting"""
bot: Red

67
bankevents/examples.txt Normal file
View file

@ -0,0 +1,67 @@
@commands.Cog.listener()
async def on_red_bank_set_balance(self, payload: BankSetBalanceInformation):
"""Payload attributes:
- recipient: Union[discord.Member, discord.User]
- guild: Union[discord.Guild, None]
- recipient_old_balance: int
- recipient_new_balance: int
"""
@commands.Cog.listener()
async def on_red_bank_transfer_credits(self, payload: BankTransferInformation):
"""Payload attributes:
- sender: Union[discord.Member, discord.User]
- recipient: Union[discord.Member, discord.User]
- guild: Union[discord.Guild, None]
- transfer_amount: int
- sender_new_balance: int
- recipient_new_balance: int
"""
@commands.Cog.listener()
async def on_red_bank_wipe(self, scope: Union[int, None]):
"""scope: int (-1 for global, None for all members, guild_id for server bank)"""
@commands.Cog.listener()
async def on_red_bank_prune(self, payload: BankPruneInformation):
"""Payload attributes:
- guild: Union[discord.Guild, None]
- user_id: Union[int, None]
- scope: int (1 for global, 2 for server, 3 for user)
- pruned_users: list[int(user_id)] or dict[int(guild_id), list[int(user_id)]]
"""
@commands.Cog.listener()
async def on_red_bank_set_global(self, is_global: bool):
"""is_global: True if global bank, False if server bank"""
@commands.Cog.listener()
async def on_red_bank_withdraw_credits(self, payload: BankWithdrawDepositInformation):
"""Payload attributes:
- member: discord.Member
- guild: Union[discord.Guild, None]
- amount: int
- old_balance: int
- new_balance: int
"""
@commands.Cog.listener()
async def on_red_bank_deposit_credits(self, payload: BankWithdrawDepositInformation):
"""Payload attributes:
- member_id: int
- guild_id: int (0 if global bank)
- amount: int
- old_balance: int
- new_balance: int
"""
@commands.Cog.listener()
async def on_red_economy_payday_claim(self, payload: PaydayClaimInformation):
"""Payload attributes:
- member: discord.Member
- channel: Union[discord.TextChannel, discord.Thread, discord.ForumChannel]
- message: discord.Message
- amount: int
- old_balance: int
- new_balance: int
"""

16
bankevents/info.json Normal file
View file

@ -0,0 +1,16 @@
{
"author": ["Vertyco"],
"description": "Dispatches events when different bank transactions occur, such as when a user deposits credits, withdraws credits, transfers credits, or runs payday.",
"disabled": false,
"end_user_data_statement": "This cog does not store end user data.",
"hidden": false,
"install_msg": "Thank you for installing!\n**WARNING:** This cog modifies Red's bank methods by wrapping them in a method that dispatches the event. If you are not okay with that, please uninstall this cog.",
"min_bot_version": "3.5.0",
"min_python_version": [3, 9, 0],
"permissions": [],
"required_cogs": {},
"requirements": [],
"short": "Bank transaction listener events for 3rd party cogs",
"tags": ["bank", "events", "listeners", "economy", "developers", "economy"],
"type": "COG"
}

140
bankevents/main.py Normal file
View file

@ -0,0 +1,140 @@
import asyncio
import logging
from pathlib import Path
import discord
from redbot.core import bank, commands
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import box
from .abc import CompositeMetaClass
from .overrides import bank as custombank
from .overrides.bank import init
from .overrides.economy import PaydayOverride
log = logging.getLogger("red.vrt.bankevents")
_ = Translator("BankEvents", __file__)
class BankEvents(PaydayOverride, commands.Cog, metaclass=CompositeMetaClass):
"""
Dispatches listener events for Red bank transactions and payday claims.
- red_bank_set_balance
- red_bank_transfer_credits
- red_bank_wipe
- red_bank_prune
- red_bank_set_global
- red_economy_payday_claim
Shoutout to YamiKaitou for starting the work on this 2+ years ago with a PR.
Maybe one day it will be merged into core.
https://github.com/Cog-Creators/Red-DiscordBot/pull/5325
"""
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
__version__ = "2.2.2"
def __init__(self, bot: Red):
super().__init__()
self.bot: Red = bot
init(self.bot)
# Original methods
self.set_balance_coro = None
self.transfer_credits_coro = None
self.wipe_bank_coro = None
self.bank_prune_coro = None
self.set_global_coro = None
self.is_global_coro = None
# Original commands
self.payday_callback = None
def format_help_for_context(self, ctx: commands.Context) -> str:
helpcmd = super().format_help_for_context(ctx)
txt = "Version: {}\nAuthor: {}\nContributors: YamiKaitou".format(self.__version__, self.__author__)
return f"{helpcmd}\n\n{txt}"
async def red_delete_data_for_user(self, *args, **kwargs):
return
async def red_get_data_for_user(self, *args, **kwargs):
return
async def cog_load(self) -> None:
asyncio.create_task(self.initialize())
async def initialize(self) -> None:
await self.bot.wait_until_red_ready()
# Save original methods
self.set_balance_coro = bank.set_balance
self.transfer_credits_coro = bank.transfer_credits
self.wipe_bank_coro = bank.wipe_bank
self.bank_prune_coro = bank.bank_prune
self.set_global_coro = bank.set_global
self.is_global_coro = bank.is_global
# Wrap methods
setattr(bank, "set_balance", custombank.set_balance)
setattr(bank, "transfer_credits", custombank.transfer_credits)
setattr(bank, "wipe_bank", custombank.wipe_bank)
setattr(bank, "bank_prune", custombank.bank_prune)
setattr(bank, "set_global", custombank.set_global)
setattr(bank, "is_global", custombank.is_global)
payday: commands.Command = self.bot.get_command("payday")
if payday:
self.payday_callback = payday.callback
payday.callback = self.payday_override.callback
log.info("Methods wrapped")
async def cog_unload(self) -> None:
if self.set_balance_coro is not None:
setattr(bank, "set_balance", self.set_balance_coro)
if self.transfer_credits_coro is not None:
setattr(bank, "transfer_credits", self.transfer_credits_coro)
if self.wipe_bank_coro is not None:
setattr(bank, "wipe_bank", self.wipe_bank_coro)
if self.bank_prune_coro is not None:
setattr(bank, "bank_prune", self.bank_prune_coro)
if self.set_global_coro is not None:
setattr(bank, "set_global", self.set_global_coro)
if self.is_global_coro is not None:
setattr(bank, "is_global", self.is_global_coro)
payday: commands.Command = self.bot.get_command("payday")
if payday and self.payday_callback:
payday.callback = self.payday_callback
log.info("Methods restored")
@commands.Cog.listener()
async def on_cog_add(self, cog: commands.Cog):
if cog.qualified_name != "Economy":
return
command: commands.Command = self.bot.get_command("payday")
if command:
self.payday_callback = command.callback
command.callback = self.payday_override.callback
@commands.command()
@commands.is_owner()
@commands.bot_has_permissions(embed_links=True)
async def bankevents(self, ctx: commands.Context):
"""Get help using the BankEvents cog"""
txt = _(
"This cog allows you to add listeners for Red bank transactions in your own cogs "
"by dispatching the following events:\n"
"- red_bank_set_balance\n"
"- red_bank_transfer_credits\n"
"- red_bank_wipe\n"
"- red_bank_prune\n"
"- red_bank_set_global\n"
"- red_economy_payday_claim\n"
"Here are the implementations you can use in your cogs that will work when this cog is loaded:\n"
)
examples = Path(__file__).parent / "examples.txt"
await ctx.send(txt)
embed = discord.Embed(description=box(examples.read_text(), "python"))
await ctx.send(embed=embed)

View file

View file

@ -0,0 +1,247 @@
import json
from typing import Dict, NamedTuple, Optional, Union
import discord
from redbot.core import bank
from redbot.core.bot import Red
from redbot.core.errors import BalanceTooHigh, BankPruneError
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number
_bot_ref: Optional[Red] = None
_cache_is_global = None
def init(bot: Red):
global _bot_ref
_bot_ref = bot
# Thanks to YamiKaitou for starting the work on this 2+ years ago
# Maybe one day it will be merged
# https://github.com/Cog-Creators/Red-DiscordBot/pull/5325
class BankSetBalanceInformation(NamedTuple):
recipient: Union[discord.Member, discord.User]
guild: Union[discord.Guild, None]
recipient_old_balance: int
recipient_new_balance: int
def to_dict(self) -> dict:
return {
"recipient": self.recipient.id,
"guild": getattr(self.guild, "id", None),
"recipient_old_balance": self.recipient_old_balance,
"recipient_new_balance": self.recipient_new_balance,
}
def to_json(self) -> str:
return json.dumps(self.to_dict())
class BankTransferInformation(NamedTuple):
sender: Union[discord.Member, discord.User]
recipient: Union[discord.Member, discord.User]
guild: Union[discord.Guild, None]
transfer_amount: int
sender_new_balance: int
recipient_new_balance: int
def to_dict(self) -> dict:
return {
"sender": self.sender.id,
"recipient": self.recipient.id,
"guild": getattr(self.guild, "id", None),
"transfer_amount": self.transfer_amount,
"sender_new_balance": self.sender_new_balance,
"recipient_new_balance": self.recipient_new_balance,
}
def to_json(self) -> str:
return json.dumps(self.to_dict())
class BankWithdrawDepositInformation(NamedTuple):
member: discord.Member
guild: Union[discord.Guild, None]
amount: int
old_balance: int
new_balance: int
def to_dict(self) -> dict:
return {
"member": self.member.id,
"guild": getattr(self.guild, "id", None),
"amount": self.amount,
"old_balance": self.old_balance,
"new_balance": self.new_balance,
}
def to_json(self) -> str:
return json.dumps(self.to_dict())
class BankPruneInformation(NamedTuple):
guild: Union[discord.Guild, None]
user_id: Union[int, None]
# {user_id: {name: str, balance: int, created_at: int}}
pruned_users: Dict[str, Dict[str, Union[int, str]]]
@property
def scope(self) -> str:
if self.guild is None and self.user_id is None:
return "global"
elif self.guild is not None and self.user_id is None:
return "guild"
return "user"
def to_dict(self) -> dict:
return {
"guild": getattr(self.guild, "id", None),
"user_id": self.user_id,
"scope": self.scope,
"pruned_users": self.pruned_users,
}
def to_json(self) -> str:
return json.dumps(self.to_dict())
async def set_balance(member: Union[discord.Member, discord.User], amount: int) -> int:
if not isinstance(amount, int):
raise TypeError("Amount must be of type int, not {}.".format(type(amount)))
if amount < 0:
raise ValueError("Not allowed to have negative balance.")
guild = getattr(member, "guild", None)
max_bal = await bank.get_max_balance(guild)
if amount > max_bal:
currency = await bank.get_currency_name(guild)
raise BalanceTooHigh(user=member, max_balance=max_bal, currency_name=currency)
if await is_global():
group = bank._config.user(member)
else:
group = bank._config.member(member)
old_balance = await group.balance()
await group.balance.set(amount)
if await group.created_at() == 0:
time = bank._encoded_current_time()
await group.created_at.set(time)
if await group.name() == "":
await group.name.set(member.display_name)
payload = BankSetBalanceInformation(member, guild, old_balance, amount)
_bot_ref.dispatch("red_bank_set_balance", payload)
return amount
async def transfer_credits(
from_: Union[discord.Member, discord.User],
to: Union[discord.Member, discord.User],
amount: int,
) -> int:
if not isinstance(amount, int):
raise TypeError("Transfer amount must be of type int, not {}.".format(type(amount)))
if bank._invalid_amount(amount):
raise ValueError("Invalid transfer amount {} <= 0".format(humanize_number(amount, override_locale="en_US")))
guild = getattr(to, "guild", None)
max_bal = await bank.get_max_balance(guild)
if await bank.get_balance(to) + amount > max_bal:
currency = await bank.get_currency_name(guild)
raise bank.errors.BalanceTooHigh(user=to.display_name, max_balance=max_bal, currency_name=currency)
sender_new = await bank.withdraw_credits(from_, amount)
recipient_new = await bank.deposit_credits(to, amount)
payload = BankTransferInformation(from_, to, guild, amount, sender_new, recipient_new)
_bot_ref.dispatch("red_bank_transfer_credits", payload)
return recipient_new
async def wipe_bank(guild: Optional[discord.Guild] = None) -> None:
if await is_global():
await bank._config.clear_all_users()
_bot_ref.dispatch("red_bank_wipe", -1)
else:
await bank._config.clear_all_members(guild)
_bot_ref.dispatch("red_bank_wipe", getattr(guild, "id", None))
async def bank_prune(bot: Red, guild: discord.Guild = None, user_id: int = None) -> None:
global_bank = await is_global()
if not global_bank and guild is None:
raise BankPruneError("'guild' can't be None when pruning a local bank")
_guilds = set()
_uguilds = set()
if global_bank:
group = bank._config._get_base_group(bank._config.USER)
if user_id is None:
async for g in AsyncIter(bot.guilds, steps=100):
if g.unavailable:
_uguilds.add(g)
elif not g.chunked:
_guilds.add(g)
else:
group = bank._config._get_base_group(bank._config.MEMBER, str(guild.id))
if user_id is None:
if guild.unavailable:
_uguilds.add(guild)
else:
_guilds.add(guild)
if user_id is None:
for _guild in _guilds:
await _guild.chunk()
members = bot.get_all_members() if global_bank else guild.members
valid_users = {str(m.id) for m in members if m.guild not in _uguilds}
accounts = await group.all()
valid_accounts = {k: v for k, v in accounts.items() if k in valid_users}
await group.set(valid_accounts)
pruned = {k: v for k, v in accounts.items() if k not in valid_users}
else:
pruned = {}
user_id = str(user_id)
accounts = await group.all()
if user_id in accounts:
pruned = {user_id: accounts[user_id]}
await group.clear_raw(user_id)
payload = BankPruneInformation(guild, user_id, pruned)
_bot_ref.dispatch("red_bank_prune", payload)
async def is_global() -> bool:
"""Determine if the bank is currently global.
Returns
-------
bool
:code:`True` if the bank is global, otherwise :code:`False`.
"""
global _cache_is_global
if _cache_is_global is None:
_cache_is_global = await bank._config.is_global()
return _cache_is_global
async def set_global(global_: bool) -> bool:
if (await is_global()) is global_:
return global_
global _cache_is_global
if await is_global():
await bank._config.clear_all_users()
_bot_ref.dispatch("red_bank_wipe", -1)
else:
await bank._config.clear_all_members()
_bot_ref.dispatch("red_bank_wipe", None)
await bank._config.is_global.set(global_)
_cache_is_global = global_
_bot_ref.dispatch("red_bank_set_global", global_)
return global_

View file

@ -0,0 +1,162 @@
import calendar
import json
import logging
from datetime import datetime, timedelta, timezone
from typing import NamedTuple, Union
import discord
from redbot.core import bank, commands, errors
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_number
from ..abc import MixinMeta
log = logging.getLogger("red.vrt.bankevents")
_ = Translator("BankEvents", __file__)
class PaydayClaimInformation(NamedTuple):
member: discord.Member
channel: Union[discord.TextChannel, discord.Thread, discord.ForumChannel]
message: discord.Message
amount: int
old_balance: int
new_balance: int
def to_dict(self) -> dict:
return {
"member": self.member.id,
"channel": self.channel.id,
"message": self.message.id,
"amount": self.amount,
"old_balance": self.old_balance,
"new_balance": self.new_balance,
}
def to_json(self) -> str:
return json.dumps(self.to_dict())
class PaydayOverride(MixinMeta):
@commands.command(hidden=True)
@commands.guild_only()
async def payday_override(self, ctx: commands.Context):
cog = self.bot.get_cog("Economy")
if cog is None:
raise commands.ExtensionError("Economy cog is not loaded.")
author = ctx.author
guild = ctx.guild
cur_time = calendar.timegm(ctx.message.created_at.utctimetuple())
credits_name = await bank.get_currency_name(guild)
old_balance = await bank.get_balance(author)
if await bank.is_global():
next_payday = await cog.config.user(author).next_payday() + await cog.config.PAYDAY_TIME()
if cur_time >= next_payday:
credit_amount = await cog.config.PAYDAY_CREDITS()
try:
new_balance = await bank.deposit_credits(author, credit_amount)
except errors.BalanceTooHigh as exc:
new_balance = await bank.set_balance(author, exc.max_balance)
await ctx.send(
_(
"You've reached the maximum amount of {currency}! "
"Please spend some more \N{GRIMACING FACE}\n\n"
"You currently have {new_balance} {currency}."
).format(currency=credits_name, new_balance=humanize_number(exc.max_balance))
)
payload = PaydayClaimInformation(
author, ctx.channel, ctx.message, exc.max_balance - credit_amount, old_balance, new_balance
)
self.bot.dispatch("red_economy_payday_claim", payload)
return
await cog.config.user(author).next_payday.set(cur_time)
pos = await bank.get_leaderboard_position(author)
await ctx.send(
_(
"{author.mention} Here, take some {currency}. "
"Enjoy! (+{amount} {currency}!)\n\n"
"You currently have {new_balance} {currency}.\n\n"
"You are currently #{pos} on the global leaderboard!"
).format(
author=author,
currency=credits_name,
amount=humanize_number(credit_amount),
new_balance=humanize_number(await bank.get_balance(author)),
pos=humanize_number(pos) if pos else pos,
)
)
payload = PaydayClaimInformation(
author, ctx.channel, ctx.message, credit_amount, new_balance, old_balance
)
self.bot.dispatch("red_economy_payday_claim", payload)
else:
relative_time = discord.utils.format_dt(
datetime.now(timezone.utc) + timedelta(seconds=next_payday - cur_time), "R"
)
await ctx.send(
_("{author.mention} Too soon. Your next payday is {relative_time}.").format(
author=author, relative_time=relative_time
)
)
else:
# Gets the users latest successfully payday and adds the guilds payday time
next_payday = await cog.config.member(author).next_payday() + await cog.config.guild(guild).PAYDAY_TIME()
if cur_time >= next_payday:
credit_amount = await cog.config.guild(guild).PAYDAY_CREDITS()
for role in author.roles:
role_credits = await cog.config.role(role).PAYDAY_CREDITS() # Nice variable name
if role_credits > credit_amount:
credit_amount = role_credits
try:
new_balance = await bank.deposit_credits(author, credit_amount)
except errors.BalanceTooHigh as exc:
new_balance = await bank.set_balance(author, exc.max_balance)
await ctx.send(
_(
"You've reached the maximum amount of {currency}! "
"Please spend some more \N{GRIMACING FACE}\n\n"
"You currently have {new_balance} {currency}."
).format(currency=credits_name, new_balance=humanize_number(exc.max_balance))
)
payload = PaydayClaimInformation(
author, ctx.channel, ctx.message, exc.max_balance - credit_amount, new_balance, old_balance
)
self.bot.dispatch("red_economy_payday_claim", payload)
return
# Sets the latest payday time to the current time
next_payday = cur_time
await cog.config.member(author).next_payday.set(next_payday)
pos = await bank.get_leaderboard_position(author)
await ctx.send(
_(
"{author.mention} Here, take some {currency}. "
"Enjoy! (+{amount} {currency}!)\n\n"
"You currently have {new_balance} {currency}.\n\n"
"You are currently #{pos} on the global leaderboard!"
).format(
author=author,
currency=credits_name,
amount=humanize_number(credit_amount),
new_balance=humanize_number(await bank.get_balance(author)),
pos=humanize_number(pos) if pos else pos,
)
)
payload = PaydayClaimInformation(
author, ctx.channel, ctx.message, credit_amount, new_balance, old_balance
)
self.bot.dispatch("red_economy_payday_claim", payload)
else:
relative_time = discord.utils.format_dt(
datetime.now(timezone.utc) + timedelta(seconds=next_payday - cur_time), "R"
)
await ctx.send(
_("{author.mention} Too soon. Your next payday is {relative_time}.").format(
author=author, relative_time=relative_time
)
)

75
economytrack/README.md Normal file
View file

@ -0,0 +1,75 @@
Track your economy's total balance over time<br/><br/>Also track you server's member count!
# [p]economytrack
Configure EconomyTrack<br/>
- Usage: `[p]economytrack`
- Aliases: `ecotrack`
- Checks: `server_only`
## [p]economytrack togglebanktrack
Enable/Disable economy tracking for this server<br/>
- Usage: `[p]economytrack togglebanktrack`
- Restricted to: `GUILD_OWNER`
- Checks: `server_only`
## [p]economytrack view
View EconomyTrack Settings<br/>
- Usage: `[p]economytrack view`
## [p]economytrack maxpoints
Set the max amount of data points the bot will store<br/>
**Arguments**<br/>
`<max_points>` Maximum amount of data points to store<br/>
The loop runs every 2 minutes, so 720 points equals 1 day<br/>
The default is 21600 (30 days)<br/>
Set to 0 to store data indefinitely (Not Recommended)<br/>
- Usage: `[p]economytrack maxpoints <max_points>`
- Restricted to: `BOT_OWNER`
## [p]economytrack timezone
Set your desired timezone for the graph<br/>
**Arguments**<br/>
`<timezone>` A string representing a valid timezone<br/>
**Example:** `[p]ecotrack timezone US/Eastern`<br/>
Use this command without the argument to get a huge list of valid timezones.<br/>
- Usage: `[p]economytrack timezone <timezone>`
## [p]economytrack togglemembertrack
Enable/Disable member tracking for this server<br/>
- Usage: `[p]economytrack togglemembertrack`
- Restricted to: `GUILD_OWNER`
- Checks: `server_only`
# [p]remoutliers
Cleanup data above a certain total economy balance<br/>
**Arguments**<br/>
datatype: either `bank` or `member`<br/>
- Usage: `[p]remoutliers <max_value> [datatype=bank]`
- Restricted to: `GUILD_OWNER`
- Checks: `server_only`
# [p]bankgraph
View bank status over a period of time.<br/>
**Arguments**<br/>
`<timespan>` How long to look for, or `all` for all-time data. Defaults to 1 day.<br/>
Must be at least 1 hour.<br/>
**Examples:**<br/>
- `[p]bankgraph 3w2d`<br/>
- `[p]bankgraph 5d`<br/>
- `[p]bankgraph all`<br/>
- Usage: `[p]bankgraph [timespan=1d]`
- Aliases: `bgraph`
- Cooldown: `5 per 60.0 seconds`
- Checks: `server_only`
# [p]membergraph
View member count over a period of time.<br/>
**Arguments**<br/>
`<timespan>` How long to look for, or `all` for all-time data. Defaults to 1 day.<br/>
Must be at least 1 hour.<br/>
**Examples:**<br/>
- `[p]membergraph 3w2d`<br/>
- `[p]membergraph 5d`<br/>
- `[p]membergraph all`<br/>
- Usage: `[p]membergraph [timespan=1d]`
- Aliases: `memgraph`
- Cooldown: `5 per 60.0 seconds`
- Checks: `server_only`

15
economytrack/__init__.py Normal file
View file

@ -0,0 +1,15 @@
from redbot.core import VersionInfo, version_info
from redbot.core.bot import Red
from redbot.core.utils import get_end_user_data_statement
from .economytrack import EconomyTrack
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
async def setup(bot: Red):
cog = EconomyTrack(bot)
if version_info >= VersionInfo.from_str("3.5.0"):
await bot.add_cog(cog)
else:
bot.add_cog(cog)

24
economytrack/abc.py Normal file
View file

@ -0,0 +1,24 @@
from abc import ABC, ABCMeta, abstractmethod
from concurrent.futures import ThreadPoolExecutor
import discord
import pandas as pd
from discord.ext.commands.cog import CogMeta
from redbot.core.bot import Red
from redbot.core.config import Config
class CompositeMetaClass(CogMeta, ABCMeta):
"""Type detection"""
class MixinMeta(ABC):
"""Type hinting"""
bot: Red
config: Config
executor: ThreadPoolExecutor
@abstractmethod
async def get_plot(self, df: pd.DataFrame, y_label: str) -> discord.File:
raise NotImplementedError

354
economytrack/commands.py Normal file
View file

@ -0,0 +1,354 @@
import datetime
import discord
import pandas as pd
import pytz
from discord.ext.commands.cooldowns import BucketType
from rapidfuzz import fuzz
from redbot.core import bank, commands
from redbot.core.commands import parse_timedelta
from redbot.core.utils.chat_formatting import box, humanize_number, humanize_timedelta
from economytrack.abc import MixinMeta
class EconomyTrackCommands(MixinMeta):
@commands.group(aliases=["ecotrack"])
@commands.has_permissions(manage_messages=True)
@commands.guild_only()
async def economytrack(self, ctx: commands.Context):
"""Configure EconomyTrack"""
@economytrack.command()
@commands.guildowner()
@commands.guild_only()
async def togglebanktrack(self, ctx: commands.Context):
"""Enable/Disable economy tracking for this server"""
async with self.config.guild(ctx.guild).all() as conf:
if conf["enabled"]:
conf["enabled"] = False
await ctx.send("Economy tracking has been **Disabled**")
else:
conf["enabled"] = True
await ctx.send("Economy tracking has been **Enabled**")
@economytrack.command()
@commands.guildowner()
@commands.guild_only()
async def togglemembertrack(self, ctx: commands.Context):
"""Enable/Disable member tracking for this server"""
async with self.config.guild(ctx.guild).all() as conf:
if conf["member_tracking"]:
conf["member_tracking"] = False
await ctx.send("Member tracking has been **Disabled**")
else:
conf["member_tracking"] = True
await ctx.send("Member tracking has been **Enabled**")
@economytrack.command()
@commands.is_owner()
async def maxpoints(self, ctx: commands.Context, max_points: int):
"""
Set the max amount of data points the bot will store
**Arguments**
`<max_points>` Maximum amount of data points to store
The loop runs every 2 minutes, so 720 points equals 1 day
The default is 21600 (30 days)
Set to 0 to store data indefinitely (Not Recommended)
"""
await self.config.max_points.set(max_points)
await ctx.tick()
@economytrack.command()
async def timezone(self, ctx: commands.Context, timezone: str):
"""
Set your desired timezone for the graph
**Arguments**
`<timezone>` A string representing a valid timezone
**Example:** `[p]ecotrack timezone US/Eastern`
Use this command without the argument to get a huge list of valid timezones.
"""
timezone = timezone.lower()
try:
tz = pytz.timezone(timezone)
except pytz.UnknownTimeZoneError:
likely_match = sorted(pytz.common_timezones, key=lambda x: fuzz.ratio(timezone, x.lower()), reverse=True)[0]
return await ctx.send(f"Invalid Timezone, did you mean `{likely_match}`?")
time = datetime.datetime.now(tz).strftime("%I:%M %p") # Convert to 12-hour format
await ctx.send(f"Timezone set to **{timezone}** (`{time}`)")
await self.config.guild(ctx.guild).timezone.set(timezone)
@economytrack.command()
@commands.bot_has_permissions(embed_links=True)
async def view(self, ctx: commands.Context):
"""View EconomyTrack Settings"""
max_points = await self.config.max_points()
is_global = await bank.is_global()
conf = await self.config.guild(ctx.guild).all()
timezone = conf["timezone"]
enabled = conf["enabled"]
if is_global:
data = await self.config.data()
points = len(data)
else:
data = await self.config.guild(ctx.guild).data()
points = len(data)
avg_iter = self.looptime if self.looptime else "(N/A)"
ptime = humanize_timedelta(seconds=int(points * 60))
mptime = humanize_timedelta(seconds=int(max_points * 60))
desc = (
f"`Enabled: `{enabled}\n"
f"`Timezone: `{timezone}\n"
f"`Max Points: `{humanize_number(max_points)} ({mptime})\n"
f"`Collected: `{humanize_number(points)} ({ptime if ptime else 'None'})\n"
f"`LoopTime: `{avg_iter}ms"
)
embed = discord.Embed(title="EconomyTrack Settings", description=desc, color=ctx.author.color)
memtime = humanize_timedelta(seconds=len(conf["member_data"]) * 60)
embed.add_field(
name="Member Tracking",
value=(
f"`Enabled: `{conf['member_tracking']}\n"
f"`Collected: `{humanize_number(len(conf['member_data']))} ({memtime if memtime else 'None'})"
),
inline=False,
)
await ctx.send(embed=embed)
@commands.command()
@commands.guildowner()
@commands.guild_only()
@commands.bot_has_permissions(embed_links=True)
async def remoutliers(self, ctx: commands.Context, max_value: int, datatype: str = "bank"):
"""
Cleanup data above a certain total economy balance
**Arguments**
datatype: either `bank` or `member`
"""
if datatype.lower() in ["b", "bank", "bnk"]:
banktype = True
else:
banktype = False
is_global = await bank.is_global()
if banktype:
if is_global:
data = await self.config.data()
else:
data = await self.config.guild(ctx.guild).data()
else:
data = await self.config.guild(ctx.guild).member_data()
if len(data) < 10:
embed = discord.Embed(
description="There is not enough data collected. Try again later.",
color=discord.Color.red(),
)
return await ctx.send(embed=embed)
newrows = [i for i in data if i[1] and i[1] <= max_value]
deleted = len(data) - len(newrows)
if not deleted:
return await ctx.send("No data to delete")
async with ctx.typing():
if banktype:
if is_global:
await self.config.data.set(newrows)
else:
await self.config.guild(ctx.guild).data.set(newrows)
else:
await self.config.guild(ctx.guild).member_data.set(newrows)
await ctx.send("Deleted all data points above " + str(max_value))
@commands.command(aliases=["bgraph"])
@commands.cooldown(5, 60.0, BucketType.user)
@commands.guild_only()
@commands.bot_has_permissions(embed_links=True, attach_files=True)
async def bankgraph(self, ctx: commands.Context, timespan: str = "1d"):
"""
View bank status over a period of time.
**Arguments**
`<timespan>` How long to look for, or `all` for all-time data. Defaults to 1 day.
Must be at least 1 hour.
**Examples:**
- `[p]bankgraph 3w2d`
- `[p]bankgraph 5d`
- `[p]bankgraph all`
"""
if timespan.lower() == "all":
delta = datetime.timedelta(days=36500)
else:
delta = parse_timedelta(timespan, minimum=datetime.timedelta(hours=1))
if delta is None:
delta = datetime.timedelta(hours=1)
is_global = await bank.is_global()
currency_name = await bank.get_currency_name(ctx.guild)
bank_name = await bank.get_bank_name(ctx.guild)
if is_global:
data = await self.config.data()
else:
data = await self.config.guild(ctx.guild).data()
if len(data) < 10:
embed = discord.Embed(
description="There is not enough data collected to generate a graph right now. Try again later.",
color=discord.Color.red(),
)
return await ctx.send(embed=embed)
timezone = await self.config.guild(ctx.guild).timezone()
now = datetime.datetime.now().astimezone(tz=pytz.timezone(timezone))
start = now - delta
columns = ["ts", "total"]
rows = [i for i in data]
for i in rows:
i[0] = datetime.datetime.fromtimestamp(i[0]).astimezone(tz=pytz.timezone(timezone))
df = pd.DataFrame(rows, columns=columns)
df = df.set_index(["ts"])
df = df[~df.index.duplicated(keep="first")] # Remove duplicate indexes
mask = (df.index > start) & (df.index <= now)
df = df.loc[mask]
df = pd.DataFrame(df)
if df.empty or len(df.values) < 10: # In case there is data but it is old
embed = discord.Embed(
description="There is not enough data collected to generate a graph right now. Try again later.",
color=discord.Color.red(),
)
return await ctx.send(embed=embed)
delta: datetime.timedelta = df.index[-1] - df.index[0]
if timespan.lower() == "all":
title = f"Total economy balance for all time ({humanize_timedelta(timedelta=delta)})"
else:
title = f"Total economy balance over the last {humanize_timedelta(timedelta=delta)}"
lowest = df.min().total
highest = df.max().total
avg = df.mean().total
current = df.values[-1][0]
desc = (
f"`DataPoints: `{humanize_number(len(df.values))}\n"
f"`BankName: `{bank_name}\n"
f"`Currency: `{currency_name}"
)
field = (
f"`Current: `{humanize_number(current)}\n"
f"`Average: `{humanize_number(round(avg))}\n"
f"`Highest: `{humanize_number(highest)}\n"
f"`Lowest: `{humanize_number(lowest)}\n"
f"`Diff: `{humanize_number(highest - lowest)}"
)
first = df.values[0][0]
diff = "+" if current > first else "-"
field2 = f"{diff} {humanize_number(abs(current - first))}"
embed = discord.Embed(title=title, description=desc, color=ctx.author.color)
embed.add_field(name="Statistics", value=field)
embed.add_field(
name="Change",
value=f"Since <t:{int(df.index[0].timestamp())}:D>\n{box(field2, 'diff')}",
)
embed.set_image(url="attachment://plot.png")
embed.set_footer(text=f"Timezone: {timezone}")
async with ctx.typing():
file = await self.get_plot(df, "Total Economy Credits")
await ctx.send(embed=embed, file=file)
@commands.command(aliases=["memgraph"])
@commands.cooldown(5, 60.0, BucketType.user)
@commands.guild_only()
@commands.bot_has_permissions(embed_links=True, attach_files=True)
async def membergraph(self, ctx: commands.Context, timespan: str = "1d"):
"""
View member count over a period of time.
**Arguments**
`<timespan>` How long to look for, or `all` for all-time data. Defaults to 1 day.
Must be at least 1 hour.
**Examples:**
- `[p]membergraph 3w2d`
- `[p]membergraph 5d`
- `[p]membergraph all`
"""
if timespan.lower() == "all":
delta = datetime.timedelta(days=36500)
else:
delta = parse_timedelta(timespan, minimum=datetime.timedelta(hours=1))
if delta is None:
delta = datetime.timedelta(hours=1)
data = await self.config.guild(ctx.guild).member_data()
if len(data) < 10:
embed = discord.Embed(
description="There is not enough data collected to generate a graph right now. Try again later.",
color=discord.Color.red(),
)
return await ctx.send(embed=embed)
timezone = await self.config.guild(ctx.guild).timezone()
now = datetime.datetime.now().astimezone(tz=pytz.timezone(timezone))
start = now - delta
columns = ["ts", "total"]
rows = [i for i in data]
for i in rows:
i[0] = datetime.datetime.fromtimestamp(i[0]).astimezone(tz=pytz.timezone(timezone))
df = pd.DataFrame(rows, columns=columns)
df = df.set_index(["ts"])
df = df[~df.index.duplicated(keep="first")] # Remove duplicate indexes
mask = (df.index > start) & (df.index <= now)
df = df.loc[mask]
df = pd.DataFrame(df)
if df.empty or len(df.values) < 10: # In case there is data but it is old
embed = discord.Embed(
description="There is not enough data collected to generate a graph right now. Try again later.",
color=discord.Color.red(),
)
return await ctx.send(embed=embed)
delta: datetime.timedelta = df.index[-1] - df.index[0]
if timespan.lower() == "all":
title = f"Total member count for all time ({humanize_timedelta(timedelta=delta)})"
else:
title = f"Total member count over the last {humanize_timedelta(timedelta=delta)}"
lowest = df.min().total
highest = df.max().total
avg = df.mean().total
current = df.values[-1][0]
desc = f"`DataPoints: `{humanize_number(len(df.values))}"
field = (
f"`Current: `{humanize_number(current)}\n"
f"`Average: `{humanize_number(round(avg))}\n"
f"`Highest: `{humanize_number(highest)}\n"
f"`Lowest: `{humanize_number(lowest)}\n"
f"`Diff: `{humanize_number(highest - lowest)}"
)
first = df.values[0][0]
diff = "+" if current > first else "-"
field2 = f"{diff} {humanize_number(abs(current - first))}"
embed = discord.Embed(title=title, description=desc, color=ctx.author.color)
embed.add_field(name="Statistics", value=field)
embed.add_field(
name="Change",
value=f"Since <t:{int(df.index[0].timestamp())}:D>\n{box(field2, 'diff')}",
)
embed.set_image(url="attachment://plot.png")
embed.set_footer(text=f"Timezone: {timezone}")
async with ctx.typing():
file = await self.get_plot(df, "Member Count")
await ctx.send(embed=embed, file=file)

View file

@ -0,0 +1,275 @@
import asyncio
import logging
from datetime import datetime, timedelta
from time import monotonic
import discord
import pandas as pd
import pytz
from discord.ext import tasks
from redbot.core import Config, bank, commands
from redbot.core.bot import Red
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box, humanize_number, humanize_timedelta
from economytrack.abc import CompositeMetaClass
from economytrack.commands import EconomyTrackCommands
from economytrack.graph import PlotGraph
log = logging.getLogger("red.vrt.economytrack")
# Credits to Vexed01 for having a great reference cog for some of the logic that went into this!
# Vex-Cogs - https://github.com/Vexed01/Vex-Cogs - (StatTrack)
class EconomyTrack(commands.Cog, EconomyTrackCommands, PlotGraph, metaclass=CompositeMetaClass):
"""
Track your economy's total balance over time
Also track you server's member count!
"""
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
__version__ = "0.5.6"
def format_help_for_context(self, ctx):
helpcmd = super().format_help_for_context(ctx)
info = f"{helpcmd}\n" f"Cog Version: {self.__version__}\n" f"Author: {self.__author__}\n"
return info
async def red_delete_data_for_user(self, *, requester, user_id: int):
"""No data to delete"""
def __init__(self, bot: Red, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bot = bot
self.config = Config.get_conf(self, identifier=117, force_registration=True)
default_global = {"max_points": 21600, "data": []}
default_guild = {
"timezone": "UTC",
"data": [],
"enabled": False,
"member_data": [],
"member_tracking": False,
}
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
self.looptime = None
self.bank_loop.start()
def cog_unload(self):
self.bank_loop.cancel()
@tasks.loop(minutes=2)
async def bank_loop(self):
start = monotonic()
is_global = await bank.is_global()
max_points = await self.config.max_points()
if max_points == 0: # 0 is no limit
max_points = 26280000 # 100 years is plenty
now = datetime.now().replace(microsecond=0, second=0).timestamp()
if is_global:
total = await self.get_total_bal()
async with self.config.data() as data:
data.append((now, total))
if len(data) > max_points:
del data[0 : len(data) - max_points]
else:
async for guild in AsyncIter(self.bot.guilds):
if not await self.config.guild(guild).enabled():
continue
total = await self.get_total_bal(guild)
async with self.config.guild(guild).data() as data:
data.append((now, total))
if len(data) > max_points:
del data[0 : len(data) - max_points]
async for guild in AsyncIter(self.bot.guilds):
if not await self.config.guild(guild).member_tracking():
continue
members = guild.member_count
async with self.config.guild(guild).member_data() as data:
data.append((now, members))
if len(data) > max_points:
del data[0 : len(data) - max_points]
iter_time = round((monotonic() - start) * 1000)
avg_iter = self.looptime
if avg_iter is None:
self.looptime = iter_time
else:
self.looptime = round((avg_iter + iter_time) / 2)
@staticmethod
async def get_total_bal(guild: discord.guild = None) -> int:
is_global = await bank.is_global()
if is_global:
members = await bank._config.all_users()
else:
members = await bank._config.all_members(guild)
total = sum(value["balance"] for value in members.values())
return int(total)
@bank_loop.before_loop
async def before_bank_loop(self):
await self.bot.wait_until_red_ready()
await asyncio.sleep(120)
log.info("EconomyTrack Ready")
@commands.Cog.listener()
async def on_assistant_cog_add(self, cog: commands.Cog):
schema1 = {
"name": "get_member_count_info",
"description": "Get member stats of the current server over a period of time",
"parameters": {
"type": "object",
"properties": {
"timespan": {
"type": "string",
"description": "span of time to pull data for, defaults to one day, 'all' can be specified to pull history for all time. Examples: 3w2d, 5d, 20h",
},
},
},
}
schema2 = {
"name": "get_economy_info",
"description": "Get total amount of currency for the current guild, along with bank info and economy stats",
"parameters": {
"type": "object",
"properties": {
"timespan": {
"type": "string",
"description": "span of time to pull data for, defaults to one day, 'all' can be specified to pull history for all time. Examples: 3w2d, 5d, 20h",
},
},
},
}
await cog.register_functions("EconomyTrack", [schema1, schema2])
async def get_member_count_info(self, guild: discord.Guild, timespan: str = "1d", *args, **kwargs) -> str:
if timespan.lower() == "all":
delta = timedelta(days=36500)
else:
delta = commands.parse_timedelta(timespan, minimum=timedelta(hours=1))
if delta is None:
delta = timedelta(hours=1)
data = await self.config.guild(guild).member_data()
if len(data) < 2:
return "There is not enough data collected. Try again later."
timezone = await self.config.guild(guild).timezone()
now = datetime.now().astimezone(tz=pytz.timezone(timezone))
start = now - delta
columns = ["ts", "total"]
rows = [i for i in data]
for i in rows:
i[0] = datetime.fromtimestamp(i[0]).astimezone(tz=pytz.timezone(timezone))
df = pd.DataFrame(rows, columns=columns)
df = df.set_index(["ts"])
df = df[~df.index.duplicated(keep="first")] # Remove duplicate indexes
mask = (df.index > start) & (df.index <= now)
df = df.loc[mask]
df = pd.DataFrame(df)
if df.empty or len(df.values) < 2: # In case there is data but it is old
return "There is not enough data collected. Try again later."
if timespan.lower() == "all":
alltime = humanize_timedelta(seconds=len(data) * 60)
reply = f"Total member count for all time ({alltime})\n"
else:
delta: timedelta = df.index[-1] - df.index[0]
reply = f"Total member count over the last {humanize_timedelta(timedelta=delta)}\n"
lowest = df.min().total
highest = df.max().total
avg = df.mean().total
current = df.values[-1][0]
reply += f"`DataPoints: `{humanize_number(len(df.values))}\n"
reply += (
"Statistics\n"
f"`Current: `{humanize_number(current)}\n"
f"`Average: `{humanize_number(round(avg))}\n"
f"`Highest: `{humanize_number(highest)}\n"
f"`Lowest: `{humanize_number(lowest)}\n"
f"`Diff: `{humanize_number(highest - lowest)}\n"
)
first = df.values[0][0]
diff = "+" if current > first else "-"
field = f"{diff} {humanize_number(abs(current - first))}"
reply += f"Since <t:{int(df.index[0].timestamp())}:D>\n{box(field, 'diff')}"
return reply
async def get_economy_info(self, guild: discord.Guild, timespan: str = "1d", *args, **kwargs) -> str:
if timespan.lower() == "all":
delta = timedelta(days=36500)
else:
delta = commands.parse_timedelta(timespan, minimum=timedelta(hours=1))
if delta is None:
delta = timedelta(hours=1)
is_global = await bank.is_global()
currency_name = await bank.get_currency_name(guild)
bank_name = await bank.get_bank_name(guild)
if is_global:
data = await self.config.data()
else:
data = await self.config.guild(guild).data()
if len(data) < 2:
return "There is not enough data collected. Try again later."
timezone = await self.config.guild(guild).timezone()
now = datetime.now().astimezone(tz=pytz.timezone(timezone))
start = now - delta
columns = ["ts", "total"]
rows = [i for i in data]
for i in rows:
i[0] = datetime.fromtimestamp(i[0]).astimezone(tz=pytz.timezone(timezone))
df = pd.DataFrame(rows, columns=columns)
df = df.set_index(["ts"])
df = df[~df.index.duplicated(keep="first")] # Remove duplicate indexes
mask = (df.index > start) & (df.index <= now)
df = df.loc[mask]
df = pd.DataFrame(df)
if df.empty or len(df.values) < 2: # In case there is data but it is old
return "There is not enough data collectedTry again later."
if timespan.lower() == "all":
alltime = humanize_timedelta(seconds=len(data) * 60)
reply = f"Total economy balance for all time ({alltime})"
else:
delta: timedelta = df.index[-1] - df.index[0]
reply = f"Total economy balance over the last {humanize_timedelta(timedelta=delta)}"
lowest = df.min().total
highest = df.max().total
avg = df.mean().total
current = df.values[-1][0]
reply += (
f"`DataPoints: `{humanize_number(len(df.values))}\n"
f"`BankName: `{bank_name}\n"
f"`Currency: `{currency_name}"
)
reply += (
"Statistics\n"
f"`Current: `{humanize_number(current)}\n"
f"`Average: `{humanize_number(round(avg))}\n"
f"`Highest: `{humanize_number(highest)}\n"
f"`Lowest: `{humanize_number(lowest)}\n"
f"`Diff: `{humanize_number(highest - lowest)}\n"
)
first = df.values[0][0]
diff = "+" if current > first else "-"
field = f"{diff} {humanize_number(abs(current - first))}"
reply += f"Since <t:{int(df.index[0].timestamp())}:D>\n{box(field, 'diff')}"
return reply

31
economytrack/graph.py Normal file
View file

@ -0,0 +1,31 @@
import asyncio
from io import BytesIO
import discord
import pandas as pd
from plotly import express as px
from economytrack.abc import MixinMeta
class PlotGraph(MixinMeta):
async def get_plot(self, df: pd.DataFrame, y_label: str) -> discord.File:
return await asyncio.to_thread(self.make_plot, df, y_label)
@staticmethod
def make_plot(df: pd.DataFrame, y_label: str) -> discord.File:
fig = px.line(
df,
template="plotly_dark",
labels={"ts": "Date", "value": y_label},
)
fig.update_xaxes(tickformat="%I:%M %p\n%b %d %Y")
fig.update_yaxes(tickformat="si")
fig.update_layout(
showlegend=False,
)
bytefile = fig.to_image(format="png", width=800, height=500, scale=1)
buffer = BytesIO(bytefile)
buffer.seek(0)
file = discord.File(buffer, filename="plot.png")
return file

32
economytrack/info.json Normal file
View file

@ -0,0 +1,32 @@
{
"author": [
"Vertyco"
],
"description": "Track your economy's total balance over time",
"disabled": false,
"end_user_data_statement": "This cog does not store data about users.",
"hidden": false,
"install_msg": "Thank you for installing EconomyTrack!\n\nDOCUMENTATION: https://github.com/vertyco/vrt-cogs/blob/main/economytrack/README.md",
"min_bot_version": "3.4.0",
"min_python_version": [
3,
9,
1
],
"permissions": [],
"required_cogs": {},
"requirements": [
"pandas",
"plotly",
"kaleido"
],
"short": "Economy tracker",
"tags": [
"utility",
"economy",
"bank",
"tracking",
"stats"
],
"type": "COG"
}

View file

@ -0,0 +1,12 @@
import json
from pathlib import Path
from .economytrickle import EconomyTrickle
with open(Path(__file__).parent / "info.json") as fp:
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
async def setup(bot):
cog = EconomyTrickle(bot)
await bot.add_cog(cog)

View file

@ -0,0 +1,443 @@
import logging
import math
from typing import Union
import discord
from discord.ext import tasks
from redbot.core import Config, bank, commands
from redbot.core.bot import Red
from redbot.core.i18n import Translator, cog_i18n
from tabulate import tabulate # pylint:disable=import-error
log = logging.getLogger("red.yamicogs.economytrickle")
_ = Translator("EconomyTrickle", __file__)
# taken from Red-Discordbot bank.py
def is_owner_if_bank_global():
"""
Command decorator. If the bank is global, it checks if the author is
bot owner, otherwise it only checks
if command was used in guild - it DOES NOT check any permissions.
When used on the command, this should be combined
with permissions check like `guildowner_or_permissions()`.
"""
async def pred(ctx: commands.Context):
author = ctx.author
if not await bank.is_global():
if not ctx.guild:
return False
return True
else:
return await ctx.bot.is_owner(author)
return commands.check(pred)
@cog_i18n(_)
class EconomyTrickle(commands.Cog):
"""
Trickle credits into your Economy
More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>
"""
def __init__(self, bot: Red):
self.bot = bot
self.config = Config.get_conf(self, identifier=582650109, force_registration=True)
default_config = {"credits": 0, "messages": 0, "voice": 0, "blocklist": []}
self.config.register_global(**default_config)
self.config.register_guild(**default_config)
self.message = {}
self.voice = {}
async def cog_load(self):
self.bank = await bank.is_global()
self.blocklist = await self.config.blocklist()
self.trickle.start() # pylint:disable=no-member
async def cog_unload(self):
self.trickle.cancel() # pylint:disable=no-member
@commands.Cog.listener()
async def on_message_without_command(self, message):
if message.author.bot:
return
if message.guild == None:
return
if await self.bot.cog_disabled_in_guild(self, message.guild):
return
if message.channel.id in self.blocklist:
log.debug(
f"Found message from {message.author.id} in a blocked channel {message.guild.id}-{message.channel.id}"
)
return
if await bank.is_global():
try:
log.debug(f"Found message from {message.author.id}")
self.message[message.author.id].append(message.id)
except KeyError:
self.message[message.author.id] = [message.id]
else:
try:
log.debug(f"Found message from {message.author.id} in {message.guild.id}")
self.message[message.guild.id]
try:
self.message[message.guild.id][message.author.id].append(message.id)
except KeyError:
self.message[message.guild.id][message.author.id] = [message.id]
except KeyError:
self.message[message.guild.id] = {message.author.id: [message.id]}
@commands.Cog.listener()
async def on_voice_state_update(self, member, before, after):
if member.bot:
return
if await self.bot.cog_disabled_in_guild(self, member.guild):
return
if before.channel is None and after.channel is not None:
if after.channel.id in self.blocklist:
log.debug(
f"Found voice state join from {member.id} in a blocked channel {member.guild.id}-{after.channel.id}"
)
return
if await bank.is_global():
log.debug(f"Found voice join from {member.id}")
self.voice[member.id] = 1
else:
try:
log.debug(f"Found voice join from {member.id} in {member.guild.id}")
self.voice[member.guild.id][member.id] = 1
except KeyError:
self.voice[member.guild.id] = {member.id: 1}
elif after.channel is None and before.channel is not None:
if before.channel.id in self.blocklist:
log.debug(
f"Found voice state leave from {member.id} in a blocked channel {member.guild.id}-{after.channel.id}"
)
return
if await bank.is_global():
log.debug(f"Found voice leave from {member.id}")
self.voice[member.id] = 0
else:
try:
log.debug(f"Found voice leave from {member.id} in {member.guild.id}")
self.voice[member.guild.id][member.id] = 0
except KeyError:
self.voice[member.guild.id] = {member.id: 0}
elif before.channel is not after.channel:
if after.channel.id in self.blocklist:
log.debug(
f"Found voice state change from {member.id} in a blocked channel {member.guild.id}-{after.channel.id}"
)
if await bank.is_global():
self.voice[member.id] = 0
else:
try:
self.voice[member.guild.id][member.id] = 0
except KeyError:
self.voice[member.guild.id] = {member.id: 0}
return
elif before.channel.id in self.blocklist:
if await bank.is_global():
log.debug(f"Found voice change from {member.id}")
self.voice[member.id] = 1
else:
try:
log.debug(f"Found voice change from {member.id} in {member.guild.id}")
self.voice[member.guild.id][member.id] = 1
except KeyError:
self.voice[member.guild.id] = {member.id: 1}
@tasks.loop(minutes=1)
async def trickle(self):
if self.bank is not await bank.is_global():
if await bank.is_global():
self.cache = await self.config.all()
else:
self.cache = await self.config.all_guilds()
self.bank = await bank.is_global()
if await bank.is_global():
msgs = self.message
cache = await self.config.all()
if cache["messages"] != 0 and cache["credits"] != 0:
for user, msg in msgs.items():
if len(msg) >= cache["messages"]:
num = math.floor(len(msg) / cache["messages"])
log.debug(f"Processing {num} messages for {user}")
del (self.message[user])[0 : (num * cache["messages"])]
await bank.deposit_credits(
(await self.bot.get_or_fetch_user(user)),
num * cache["credits"],
)
voice = self.voice
if cache["voice"] != 0:
for user, yes in voice.items():
if yes:
log.debug(f"Processing voice for {user}")
await bank.deposit_credits(
(await self.bot.get_or_fetch_user(user)),
cache["voice"],
)
else:
msgs = self.message
for guild, users in msgs.items():
if not await self.bot.cog_disabled_in_guild(self, self.bot.get_guild(guild)):
cache = await self.config.guild_from_id(guild).all()
if cache["messages"] != 0 and cache["credits"] != 0:
for user, msg in users.items():
if len(msg) >= cache["messages"]:
num = math.floor(len(msg) / cache["messages"])
log.debug(f"Processing {num} messages for {user} in {guild}")
del (self.message[guild][user])[0 : (num * cache["messages"])]
await bank.deposit_credits(
(
await self.bot.get_or_fetch_member(
self.bot.get_guild(guild), user
)
),
num * cache["credits"],
)
voice = self.voice
for guild, users in voice.items():
if not await self.bot.cog_disabled_in_guild(self, self.bot.get_guild(guild)):
cache = await self.config.guild_from_id(guild).all()
if cache["voice"] != 0:
for user, yes in users.items():
if yes:
log.debug(f"Processing voice for {user} in {guild}")
await bank.deposit_credits(
(
await self.bot.get_or_fetch_member(
self.bot.get_guild(guild), user
)
),
cache["voice"],
)
@trickle.before_loop
async def before_trickle(self):
await self.bot.wait_until_red_ready()
@is_owner_if_bank_global()
@commands.admin_or_permissions(manage_guild=True)
@commands.group(aliases=["trickleset"])
async def economytrickle(self, ctx):
"""Configure various settings"""
@is_owner_if_bank_global()
@commands.admin_or_permissions(manage_guild=True)
@economytrickle.command(name="settings", aliases=["info", "showsettings"])
async def ts_settings(self, ctx):
"""Show the current settings"""
if await bank.is_global():
cache = await self.config.all()
await ctx.send(
_(
"Message Credits: {credits}\nMessage Count: {messages}\nVoice Credits: {voice}"
).format(
credits=cache["credits"], messages=cache["messages"], voice=cache["voice"]
)
)
else:
if ctx.guild is not None:
cache = await self.config.guild(ctx.guild).all()
await ctx.send(
_(
"Message Credits: {credits}\nMessage Count: {messages}\nVoice Credits: {voice}"
).format(
credits=cache["credits"], messages=cache["messages"], voice=cache["voice"]
)
)
else:
await ctx.send(
_(
"Your bank is set to per-server. Please try this command in a server instead"
)
)
@is_owner_if_bank_global()
@commands.admin_or_permissions(manage_guild=True)
@economytrickle.command(name="credits")
async def ts_credits(self, ctx, number: int):
"""
Set the number of credits to grant
Set the number to 0 to disable
Max value is 1000
"""
if await bank.is_global():
if 0 <= number <= 1000:
await self.config.credits.set(number)
if not await ctx.tick():
await ctx.send(_("Setting saved"))
else:
await ctx.send(
_("You must specify a value that is not less than 0 and not more than 1000")
)
else:
if ctx.guild is not None:
if 0 <= number <= 1000:
await self.config.guild(ctx.guild).credits.set(number)
if not await ctx.tick():
await ctx.send(_("Setting saved"))
else:
await ctx.send(
_(
"You must specify a value that is not less than 0 and not more than 1000"
)
)
else:
await ctx.send(
_(
"Your bank is set to per-server. Please try this command in a server instead"
)
)
@is_owner_if_bank_global()
@commands.admin_or_permissions(manage_guild=True)
@economytrickle.command(name="messages")
async def ts_messages(self, ctx, number: int):
"""
Set the number of messages required to gain credits
Set the number to 0 to disable
Max value is 100
"""
if await bank.is_global():
if 0 <= number <= 100:
await self.config.messages.set(number)
if not await ctx.tick():
await ctx.send(_("Setting saved"))
else:
await ctx.send(
_("You must specify a value that is not less than 0 and not more than 100")
)
else:
if ctx.guild is not None:
if 0 <= number <= 100:
await self.config.guild(ctx.guild).messages.set(number)
if not await ctx.tick():
await ctx.send(_("Setting saved"))
else:
await ctx.send(
_("You must specify a value that is not less than 0 and not more than 100")
)
else:
await ctx.send(
_(
"Your bank is set to per-server. Please try this command in a server instead"
)
)
@is_owner_if_bank_global()
@commands.admin_or_permissions(manage_guild=True)
@economytrickle.command(name="voice")
async def ts_voice(self, ctx, number: int):
"""
Set the number of credits to grant every minute
Set the number to 0 to disable
Max value is 1000
"""
if await bank.is_global():
if 0 <= number <= 1000:
await self.config.voice.set(number)
if not await ctx.tick():
await ctx.send(_("Setting saved"))
else:
await ctx.send(
_("You must specify a value that is not less than 0 and not more than 1000")
)
else:
if ctx.guild is not None:
if 0 <= number <= 1000:
await self.config.guild(ctx.guild).voice.set(number)
if not await ctx.tick():
await ctx.send(_("Setting saved"))
else:
await ctx.send(
_(
"You must specify a value that is not less than 0 and not more than 1000"
)
)
else:
await ctx.send(
_(
"Your bank is set to per-server. Please try this command in a server instead"
)
)
@commands.guild_only()
@commands.admin_or_permissions(manage_guild=True)
@economytrickle.command(name="blocklist", aliases=["blacklist"])
async def ts_blocklist(
self, ctx, channel: Union[discord.TextChannel, discord.VoiceChannel] = None
):
"""
Add/Remove the current channel (or a specific channel) to the blocklist
Not passing a channel will add/remove the channel you ran the command in to the blocklist
"""
if channel is None:
channel = ctx.channel
try:
self.blocklist.remove(channel.id)
await ctx.send(_("Channel removed from the blocklist"))
except ValueError:
self.blocklist.append(channel.id)
await ctx.send(_("Channel added to the blocklist"))
finally:
await self.config.blocklist.set(self.blocklist)
@commands.guild_only()
@commands.admin_or_permissions(manage_guild=True)
@economytrickle.command(name="showblocks", aliases=["showblock"])
async def ts_showblocks(self, ctx):
"""Provide a list of channels that are on the blocklist for this server"""
blocks = ""
for block in self.blocklist:
chan = self.bot.get_channel(block)
if chan.guild is ctx.guild:
blocks += f"{chan.name}\n"
if blocks == "":
blocks = _("No channels blocked")
await ctx.send(
_("The following channels are blocked from EconomyTrickle\n{blocked_channels}").format(
blocked_channels=blocks
)
)
async def red_get_data_for_user(self, *, user_id: int):
# this cog does not store any data
return {}
async def red_delete_data_for_user(self, *, requester, user_id: int) -> None:
# this cog does not store any data
pass

20
economytrickle/info.json Normal file
View file

@ -0,0 +1,20 @@
{
"author": [
"YamiKaitou#8975"
],
"name": "EconomyTrickle",
"short": "Trickle credits into your economy",
"description": "Automatically trickle some credits into your bot's economy",
"tags": [
"economy",
"trickle"
],
"requirements": [
"tabulate"
],
"min_bot_version": "3.5.0",
"end_user_data_statement": "This cog does not persistently store end user data.",
"hidden": false,
"disabled": false,
"install_msg": "Thanks for installing the cog. I'm in the Cog Support server if you need help with anything. Make sure you take a look under `[p]economytrickle` before getting started, the settings are all set to 0 by default"
}

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: ar\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: ar_SA\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: bg\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: bg_BG\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: cs\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: cs_CZ\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: da\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: da_DK\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: de_DE\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: es_ES\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: fi\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: fi_FI\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: fr_FR\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: hi\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: hi_IN\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: hr\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: hr_HR\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: hu\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: hu_HU\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Indonesian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: id\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: id_ID\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: it\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: it_IT\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: ja_JP\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: ko\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: ko_KR\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,127 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid ""
"\n"
" Trickle credits into your Economy\n"
"\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid ""
"Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid ""
"Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid ""
"\n"
" Set the number of credits to grant\n"
"\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid ""
"You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid ""
"\n"
" Set the number of messages required to gain credits\n"
"\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid ""
"\n"
" Set the number of credits to grant every minute\n"
"\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid ""
"\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n"
"\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid ""
"The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Norwegian Bokmal\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: nb\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: nb_NO\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-07-17 18:56\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: nl\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: nl_NL\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr "Configureer verschillende instellingen"
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr "Toon de huidige instellingen"
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr "Instelling opgeslagen"
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr "Kanaal verwijderd uit de blokkeerlijst"
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr "Geen kanalen geblokkeerd"
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: pl\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: pl_PL\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: pt-BR\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: pt_BR\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: pt_PT\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: ru\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: ru_RU\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Slovak\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: sk\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: sk_SK\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-07-01 17:33\n"
"Last-Translator: \n"
"Language-Team: Slovenian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: sl\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: sl_SI\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: sv-SE\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: sv_SE\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: tr_TR\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: uk\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: uk_UA\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Vietnamese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: vi\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: vi_VN\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: zh_CN\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

View file

@ -0,0 +1,120 @@
msgid ""
msgstr ""
"Project-Id-Version: yamicogs\n"
"POT-Creation-Date: 2024-04-14 18:44+0000\n"
"PO-Revision-Date: 2024-04-14 18:49\n"
"Last-Translator: \n"
"Language-Team: Chinese Traditional\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: yamicogs\n"
"X-Crowdin-Project-ID: 436902\n"
"X-Crowdin-Language: zh-TW\n"
"X-Crowdin-File: /master/economytrickle/locales/messages.pot\n"
"X-Crowdin-File-ID: 160\n"
"Language: zh_TW\n"
#: economytrickle/economytrickle.py:40
#, docstring
msgid "\n"
" Trickle credits into your Economy\n\n"
" More detailed docs: <https://cogs.yamikaitou.dev/economytrickle.html>\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:243
#, docstring
msgid "Configure various settings"
msgstr ""
#: economytrickle/economytrickle.py:249
#, docstring
msgid "Show the current settings"
msgstr ""
#: economytrickle/economytrickle.py:254 economytrickle/economytrickle.py:264
msgid "Message Credits: {credits}\n"
"Message Count: {messages}\n"
"Voice Credits: {voice}"
msgstr ""
#: economytrickle/economytrickle.py:272 economytrickle/economytrickle.py:311
#: economytrickle/economytrickle.py:348 economytrickle/economytrickle.py:387
msgid "Your bank is set to per-server. Please try this command in a server instead"
msgstr ""
#: economytrickle/economytrickle.py:281
#, docstring
msgid "\n"
" Set the number of credits to grant\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:292 economytrickle/economytrickle.py:302
#: economytrickle/economytrickle.py:331 economytrickle/economytrickle.py:341
#: economytrickle/economytrickle.py:368 economytrickle/economytrickle.py:378
msgid "Setting saved"
msgstr ""
#: economytrickle/economytrickle.py:295 economytrickle/economytrickle.py:305
#: economytrickle/economytrickle.py:371 economytrickle/economytrickle.py:381
msgid "You must specify a value that is not less than 0 and not more than 1000"
msgstr ""
#: economytrickle/economytrickle.py:320
#, docstring
msgid "\n"
" Set the number of messages required to gain credits\n\n"
" Set the number to 0 to disable\n"
" Max value is 100\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:334 economytrickle/economytrickle.py:344
msgid "You must specify a value that is not less than 0 and not more than 100"
msgstr ""
#: economytrickle/economytrickle.py:357
#, docstring
msgid "\n"
" Set the number of credits to grant every minute\n\n"
" Set the number to 0 to disable\n"
" Max value is 1000\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:398
#, docstring
msgid "\n"
" Add/Remove the current channel (or a specific channel) to the blocklist\n\n"
" Not passing a channel will add/remove the channel you ran the command in to the blocklist\n"
" "
msgstr ""
#: economytrickle/economytrickle.py:409
msgid "Channel removed from the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:412
msgid "Channel added to the blocklist"
msgstr ""
#: economytrickle/economytrickle.py:420
#, docstring
msgid "Provide a list of channels that are on the blocklist for this server"
msgstr ""
#: economytrickle/economytrickle.py:429
msgid "No channels blocked"
msgstr ""
#: economytrickle/economytrickle.py:432
msgid "The following channels are blocked from EconomyTrickle\n"
"{blocked_channels}"
msgstr ""

21
evolution/__init__.py Normal file
View file

@ -0,0 +1,21 @@
from . import bank
from .evolution import Evolution
__red_end_user_data_statement__ = (
"This cog stores user's Discord IDs for the sake of storing game data. "
"Users may delete their own data at the cost of losing game data through "
"a data request, if the bot is configured to lose data at the cost of "
"functionality. Alternatively, there is a in-cog command to delete user "
"data as well."
)
async def setup(bot):
bank._init(bot)
is_global = await bank.is_global()
if not is_global:
raise RuntimeError("Bank must be global for this cog to work.")
cog = Evolution(bot)
await bot.add_cog(cog)
await cog.utils.initialize()

774
evolution/bank.py Normal file
View file

@ -0,0 +1,774 @@
from __future__ import annotations
import asyncio
import datetime
from functools import wraps
from typing import TYPE_CHECKING, List, Optional, Union
import discord
from redbot.core import Config, bank, commands, errors
from redbot.core.bank import Account
from redbot.core.bank import BankPruneError as BankPruneError
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number
if TYPE_CHECKING:
from redbot.core.bot import Red
# Credits: https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/core/bank.py
# This is a modified version of Red's Bank API that listen for the existance of the Adventure Cog.
# If Cog is not loaded, then it will default to Red's Bank API
_ = Translator("Adventure Bank API", __file__)
__all__ = [
"Account",
"get_balance",
"set_balance",
"withdraw_credits",
"deposit_credits",
"can_spend",
"transfer_credits",
"wipe_bank",
"get_account",
"is_global",
"set_global",
"get_bank_name",
"set_bank_name",
"get_currency_name",
"set_currency_name",
"get_default_balance",
"set_default_balance",
"get_max_balance",
"set_max_balance",
"cost",
"AbortPurchase",
"bank_prune",
"get_next_payday",
"set_next_payday",
"BankPruneError",
]
_MAX_BALANCE = 2**63 - 1
_DEFAULT_MEMBER = {"balance": 0, "next_payday": 0}
_config: Config = None
_bot: Red = None
def _init(bot: Red):
global _config, _bot
if _config is None:
_config = Config.get_conf(
None, 384734293238749, cog_name="AdventureBank", force_registration=True
)
_config.register_user(**_DEFAULT_MEMBER)
_bot = bot
class AdventureAccount:
"""A single account.
This class should ONLY be instantiated by the bank itself."""
def __init__(self, balance: int, next_payday: int):
self.balance = balance
self.next_payday = next_payday
def _encoded_current_time() -> int:
"""Get the current UTC time as a timestamp.
Returns
-------
int
The current UTC timestamp.
"""
now = datetime.datetime.utcnow()
return _encode_time(now)
def _encode_time(time: datetime.datetime) -> int:
"""Convert a datetime object to a serializable int.
Parameters
----------
time : datetime.datetime
The datetime to convert.
Returns
-------
int
The timestamp of the datetime object.
"""
ret = int(time.timestamp())
return ret
def _decode_time(time: int) -> datetime.datetime:
"""Convert a timestamp to a datetime object.
Parameters
----------
time : int
The timestamp to decode.
Returns
-------
datetime.datetime
The datetime object from the timestamp.
"""
return datetime.datetime.utcfromtimestamp(time)
async def get_balance(member: discord.Member, _forced: bool = False) -> int:
"""Get the current balance of a member.
Parameters
----------
member : discord.Member
The member whose balance to check.
Returns
-------
int
The member's balance
"""
acc = await get_account(member, _forced=_forced)
return int(acc.balance)
async def get_next_payday(member: discord.Member) -> int:
"""Get the current balance of a member.
Parameters
----------
member : discord.Member
The member whose balance to check.
Returns
-------
int
The member's balance
"""
if (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return 0
acc = await get_account(member)
return int(acc.next_payday)
async def set_next_payday(member: Union[discord.Member, discord.User], amount: int) -> int:
"""Set an account next payday.
Parameters
----------
member : Union[discord.Member, discord.User]
The member whose next payday to set.
amount : int
The amount to set the next payday to.
Returns
-------
int
New account next payday.
"""
if (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return 0
amount = int(amount)
group = _config.user(member)
await group.next_payday.set(amount)
return amount
async def can_spend(member: discord.Member, amount: int, _forced: bool = False) -> bool:
"""Determine if a member can spend the given amount.
Parameters
----------
member : discord.Member
The member wanting to spend.
amount : int
The amount the member wants to spend.
Returns
-------
bool
:code:`True` if the member has a sufficient balance to spend the
amount, else :code:`False`.
"""
return await get_balance(member, _forced=_forced) >= amount
async def set_balance(
member: Union[discord.Member, discord.User], amount: int, _forced: bool = False
) -> int:
"""Set an account balance.
Parameters
----------
member : Union[discord.Member, discord.User]
The member whose balance to set.
amount : int
The amount to set the balance to.
Returns
-------
int
New account balance.
Raises
------
ValueError
If attempting to set the balance to a negative number.
RuntimeError
If the bank is guild-specific and a discord.User object is provided.
BalanceTooHigh
If attempting to set the balance to a value greater than
``bank._MAX_BALANCE``.
"""
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.set_balance(member=member, amount=amount)
guild = getattr(member, "guild", None)
max_bal = await get_max_balance(guild)
if amount > max_bal:
currency = await get_currency_name(guild)
raise errors.BalanceTooHigh(
user=member.display_name, max_balance=max_bal, currency_name=currency
)
amount = int(amount)
group = _config.user(member)
await group.balance.set(amount)
return amount
async def withdraw_credits(member: discord.Member, amount: int, _forced: bool = False) -> int:
"""Remove a certain amount of credits from an account.
Parameters
----------
member : discord.Member
The member to withdraw credits from.
amount : int
The amount to withdraw.
Returns
-------
int
New account balance.
Raises
------
ValueError
If the withdrawal amount is invalid or if the account has insufficient
funds.
TypeError
If the withdrawal amount is not an `int`.
"""
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.withdraw_credits(member=member, amount=amount)
if not isinstance(amount, (int, float)):
raise TypeError("Withdrawal amount must be of type int, not {}.".format(type(amount)))
amount = int(amount)
bal = await get_balance(member)
if amount > bal:
raise ValueError(
"Insufficient funds {} > {}".format(
humanize_number(amount, override_locale="en_US"),
humanize_number(bal, override_locale="en_US"),
)
)
return await set_balance(member, bal - amount)
async def deposit_credits(member: discord.Member, amount: int, _forced: bool = False) -> int:
"""Add a given amount of credits to an account.
Parameters
----------
member : discord.Member
The member to deposit credits to.
amount : int
The amount to deposit.
Returns
-------
int
The new balance.
Raises
------
ValueError
If the deposit amount is invalid.
TypeError
If the deposit amount is not an `int`.
"""
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.deposit_credits(member=member, amount=amount)
if not isinstance(amount, (int, float)):
raise TypeError("Deposit amount must be of type int, not {}.".format(type(amount)))
amount = int(amount)
bal = int(await get_balance(member))
return await set_balance(member, amount + bal)
async def transfer_credits(
from_: Union[discord.Member, discord.User],
to: Union[discord.Member, discord.User],
amount: int,
tax: float = 0.0,
):
"""Transfer a given amount of credits from one account to another with a 50% tax.
Parameters
----------
from_: Union[discord.Member, discord.User]
The member to transfer from.
to : Union[discord.Member, discord.User]
The member to transfer to.
amount : int
The amount to transfer.
Returns
-------
int
The new balance of the member gaining credits.
Raises
------
ValueError
If the amount is invalid or if ``from_`` has insufficient funds.
TypeError
If the amount is not an `int`.
RuntimeError
If the bank is guild-specific and a discord.User object is provided.
BalanceTooHigh
If the balance after the transfer would be greater than
``bank._MAX_BALANCE``.
"""
if (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.transfer_credits(from_=from_, to=to, amount=amount)
if not isinstance(amount, (int, float)):
raise TypeError("Transfer amount must be of type int, not {}.".format(type(amount)))
guild = getattr(to, "guild", None)
max_bal = await get_max_balance(guild)
new_amount = int(amount - (amount * tax))
if await get_balance(to) + new_amount > max_bal:
currency = await get_currency_name(guild)
raise errors.BalanceTooHigh(
user=to.display_name, max_balance=max_bal, currency_name=currency
)
await withdraw_credits(from_, int(amount))
await deposit_credits(to, int(new_amount))
return int(new_amount)
async def wipe_bank(guild: Optional[discord.Guild] = None) -> None:
"""Delete all accounts from the bank.
Parameters
----------
guild : discord.Guild
The guild to clear accounts for. If unsupplied and the bank is
per-server, all accounts in every guild will be wiped.
"""
if (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.wipe_bank(guild=guild)
await _config.clear_all_users()
async def bank_prune(bot: Red, guild: discord.Guild = None, user_id: int = None) -> None:
"""Prune bank accounts from the bank.
Parameters
----------
bot : Red
The bot.
guild : discord.Guild
The guild to prune. This is required if the bank is set to local.
user_id : int
The id of the user whose account will be pruned.
If supplied this will prune only this user's bank account
otherwise it will prune all invalid users from the bank.
Raises
------
BankPruneError
If guild is :code:`None` and the bank is Local.
"""
if (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.bank_prune(bot=bot, guild=guild, user_id=user_id)
_guilds = set()
_uguilds = set()
if user_id is None:
async for g in AsyncIter(bot.guilds, steps=100):
if not g.unavailable and g.large and not g.chunked:
_guilds.add(g)
elif g.unavailable:
_uguilds.add(g)
group = _config._get_base_group(_config.USER)
if user_id is None:
await bot.request_offline_members(*_guilds)
accounts = await group.all()
tmp = accounts.copy()
members = bot.get_all_members()
user_list = {str(m.id) for m in members if m.guild not in _uguilds}
async with group.all() as bank_data: # FIXME: use-config-bulk-update
if user_id is None:
for acc in tmp:
if acc not in user_list:
del bank_data[acc]
else:
user_id = str(user_id)
if user_id in bank_data:
del bank_data[user_id]
async def get_leaderboard(
positions: int = None, guild: discord.Guild = None, _forced: bool = False
) -> List[tuple]:
"""
Gets the bank's leaderboard
Parameters
----------
positions : `int`
The number of positions to get
guild : discord.Guild
The guild to get the leaderboard of. If the bank is global and this
is provided, get only guild members on the leaderboard
Returns
-------
`list` of `tuple`
The sorted leaderboard in the form of :code:`(user_id, raw_account)`
Raises
------
TypeError
If the bank is guild-specific and no guild was specified
"""
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.get_leaderboard(positions=positions, guild=guild)
raw_accounts = await _config.all_users()
if guild is not None:
tmp = raw_accounts.copy()
for acc in tmp:
if not guild.get_member(acc):
del raw_accounts[acc]
sorted_acc = sorted(raw_accounts.items(), key=lambda x: x[1]["balance"], reverse=True)
if positions is None:
return sorted_acc
else:
return sorted_acc[:positions]
async def get_leaderboard_position(
member: Union[discord.User, discord.Member], _forced: bool = False
) -> Union[int, None]:
"""
Get the leaderboard position for the specified user
Parameters
----------
member : `discord.User` or `discord.Member`
The user to get the leaderboard position of
Returns
-------
`int`
The position of the user on the leaderboard
Raises
------
TypeError
If the bank is currently guild-specific and a `discord.User` object was passed in
"""
if await is_global():
guild = None
else:
guild = member.guild if hasattr(member, "guild") else None
try:
leaderboard = await get_leaderboard(None, guild, _forced=_forced)
except TypeError:
raise
else:
pos = discord.utils.find(lambda x: x[1][0] == member.id, enumerate(leaderboard, 1))
if pos is None:
return None
else:
return pos[0]
async def get_account(
member: Union[discord.Member, discord.User], _forced: bool = False
) -> Union[Account, AdventureAccount]:
"""Get the appropriate account for the given user or member.
A member is required if the bank is currently guild specific.
Parameters
----------
member : `discord.User` or `discord.Member`
The user whose account to get.
Returns
-------
Account
The user's account.
"""
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.get_account(member)
all_accounts = await _config.all_users()
if member.id not in all_accounts:
acc_data = {"balance": 250, "next_payday": 0}
else:
acc_data = all_accounts[member.id]
return AdventureAccount(**acc_data)
async def is_global(_forced: bool = False) -> bool:
"""Determine if the bank is currently global.
Returns
-------
bool
:code:`True` if the bank is global, otherwise :code:`False`.
"""
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.is_global()
return True
async def set_global(global_: bool) -> bool:
"""Set global status of the bank.
.. important::
All accounts are reset when you switch!
Parameters
----------
global_ : bool
:code:`True` will set bank to global mode.
Returns
-------
bool
New bank mode, :code:`True` is global.
Raises
------
RuntimeError
If bank is becoming global and a `discord.Member` was not provided.
"""
return await bank.set_global(global_)
async def get_bank_name(guild: discord.Guild = None) -> str:
"""Get the current bank name.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the bank name for (required if bank is
guild-specific).
Returns
-------
str
The bank's name.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
return await bank.get_bank_name(guild)
async def set_bank_name(name: str, guild: discord.Guild = None) -> str:
"""Set the bank name.
Parameters
----------
name : str
The new name for the bank.
guild : `discord.Guild`, optional
The guild to set the bank name for (required if bank is
guild-specific).
Returns
-------
str
The new name for the bank.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
return await bank.set_bank_name(name=name, guild=guild)
async def get_currency_name(guild: discord.Guild = None, _forced: bool = False) -> str:
"""Get the currency name of the bank.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the currency name for (required if bank is
guild-specific).
Returns
-------
str
The currency name.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.get_currency_name(guild=guild)
return _("gold coins")
async def set_currency_name(name: str, guild: discord.Guild = None) -> str:
"""Set the currency name for the bank.
Parameters
----------
name : str
The new name for the currency.
guild : `discord.Guild`, optional
The guild to set the currency name for (required if bank is
guild-specific).
Returns
-------
str
The new name for the currency.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
return await bank.set_currency_name(name=name, guild=guild)
async def get_max_balance(guild: discord.Guild = None) -> int:
"""Get the max balance for the bank.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the max balance for (required if bank is
guild-specific).
Returns
-------
int
The maximum allowed balance.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return await bank.get_max_balance(guild=guild)
return _MAX_BALANCE
async def set_max_balance(amount: int, guild: discord.Guild = None) -> int:
"""Set the maximum balance for the bank.
Parameters
----------
amount : int
The new maximum balance.
guild : `discord.Guild`, optional
The guild to set the max balance for (required if bank is
guild-specific).
Returns
-------
int
The new maximum balance.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
ValueError
If the amount is less than 0 or higher than 2 ** 63 - 1.
"""
return await bank.set_max_balance(amount=amount, guild=guild)
async def get_default_balance(guild: discord.Guild = None) -> int:
"""Get the current default balance amount.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the default balance for (required if bank is
guild-specific).
Returns
-------
int
The bank's default balance.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
return await bank.get_default_balance(guild=guild)
async def set_default_balance(amount: int, guild: discord.Guild = None) -> int:
"""Set the default balance amount.
Parameters
----------
amount : int
The new default balance.
guild : `discord.Guild`, optional
The guild to set the default balance for (required if bank is
guild-specific).
Returns
-------
int
The new default balance.
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
ValueError
If the amount is less than 0 or higher than the max allowed balance.
"""
return await bank.set_default_balance(amount=amount, guild=guild)
class AbortPurchase(Exception):
pass
def cost(amount: int):
"""
Decorates a coroutine-function or command to have a cost.
If the command raises an exception, the cost will be refunded.
You can intentionally refund by raising `AbortPurchase`
(this error will be consumed and not show to users)
Other exceptions will propagate and will be handled by Red's (and/or
any other configured) error handling.
"""
if not isinstance(amount, int) or amount < 0:
raise ValueError("This decorator requires an integer cost greater than or equal to zero")
def deco(coro_or_command):
is_command = isinstance(coro_or_command, commands.Command)
if not is_command and not asyncio.iscoroutinefunction(coro_or_command):
raise TypeError("@bank.cost() can only be used on commands or `async def` functions")
coro = coro_or_command.callback if is_command else coro_or_command
@wraps(coro)
async def wrapped(*args, **kwargs):
context: commands.Context = None
for arg in args:
if isinstance(arg, commands.Context):
context = arg
break
if not context.guild and not await is_global():
raise commands.UserFeedbackCheckFailure(
_("Can't pay for this command in DM without a global bank.")
)
try:
await withdraw_credits(context.author, amount)
except Exception:
credits_name = await get_currency_name(context.guild)
raise commands.UserFeedbackCheckFailure(
_("You need at least {cost} {currency} to use this command.").format(
cost=humanize_number(amount), currency=credits_name
)
)
else:
try:
return await coro(*args, **kwargs)
except AbortPurchase:
await deposit_credits(context.author, amount)
except Exception:
await deposit_credits(context.author, amount)
raise
if not is_command:
return wrapped
else:
wrapped.__module__ = coro_or_command.callback.__module__
coro_or_command.callback = wrapped
return coro_or_command
def _get_config(_forced: bool = False):
if _forced or (cog := _bot.get_cog("Adventure")) is None or not cog._separate_economy:
return bank._config
return _config

746
evolution/evolution.py Normal file
View file

@ -0,0 +1,746 @@
"""
MIT License
Copyright (c) 2018-Present NeuroAssassin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import asyncio
import copy
import math
import random
import traceback
from collections import defaultdict
from datetime import timedelta
from typing import Literal, Optional
import discord
from redbot.core import Config, commands, errors
from redbot.core.bot import Red
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box, humanize_number, humanize_timedelta, inline
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu, start_adding_reactions
from redbot.core.utils.predicates import ReactionPredicate
from tabulate import tabulate
from .tasks import EvolutionTaskManager
from .utils import EvolutionUtils
from . import bank
ANIMALS = ["chicken", "dog", "cat", "shark", "tiger", "penguin", "pupper", "dragon"]
IMAGES = {
"shark": "https://www.bostonmagazine.com/wp-content/uploads/sites/2/2019/05/Great-white-shark.jpg",
"chicken": "https://i1.wp.com/thechickhatchery.com/wp-content/uploads/2018/01/RI-White.jpg?fit=371%2C363&ssl=1",
"penguin": "https://cdn.britannica.com/77/81277-050-2A6A35B2/Adelie-penguin.jpg",
"dragon": "https://images-na.ssl-images-amazon.com/images/I/61NTUxEnn0L._SL1032_.jpg",
"tiger": "https://c402277.ssl.cf1.rackcdn.com/photos/18134/images/hero_small/Medium_WW226365.jpg?1574452099",
"cat": "https://icatcare.org/app/uploads/2018/07/Thinking-of-getting-a-cat.png",
"dog": "https://d17fnq9dkz9hgj.cloudfront.net/breed-uploads/2018/09/dog-landing-hero-lg.jpg?bust=1536935129&width=1080",
"pupper": "https://i.ytimg.com/vi/MPV2METPeJU/maxresdefault.jpg",
}
import inspect
class Evolution(commands.Cog):
"""EVOLVE THOSE ANIMALS!!!!!!!!!!!"""
def __init__(self, bot):
self.bot: Red = bot
self.lock = asyncio.Lock()
self.conf: Config = Config.get_conf(self, identifier=473541068378341376)
self.cache = defaultdict(self.cache_defaults) # Thanks to Theelx#4980
self.utils: EvolutionUtils = EvolutionUtils(self)
self.task_manager: EvolutionTaskManager = EvolutionTaskManager(self)
self.utils.init_config()
self.task_manager.init_tasks()
self.inmarket = []
def cache_defaults(self):
return {"animal": "", "animals": {}, "multiplier": 1.0, "bought": {}}
def cog_unload(self):
self.__unload()
def __unload(self):
self.cache.clear()
self.task_manager.shutdown()
async def red_delete_data_for_user(
self,
*,
requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
user_id: int,
):
"""This cog stores game data by user ID. It will delete the user's game data,
reset their progress and wipe traces of their ID."""
await self.conf.user_from_id(user_id).clear()
try:
del self.cache[user_id]
except KeyError:
pass
return
@commands.group(aliases=["e", "evo"])
async def evolution(self, ctx):
"""EVOLVE THE GREATEST ANIMALS OF ALL TIME!!!!"""
pass
@evolution.command(usage=" ")
async def deletemydata(self, ctx, check: bool = False):
"""Delete your game data.
WARNING! Your data *will not be able to be recovered*!"""
if not check:
return await ctx.send(
f"Warning! This will completely delete your game data and restart you from scratch! If you are sure you want to do this, re-run this command as `{ctx.prefix}evolution deletemydata True`."
)
await self.red_delete_data_for_user(requester="user", user_id=ctx.author.id)
await ctx.send("Data deleted. Your game data has been reset.")
@commands.is_owner()
@evolution.group()
async def tasks(self, ctx):
"""View the status of the cog tasks.
These are for debugging purposes"""
pass
@tasks.command(aliases=["checkdelivery", "cd"])
async def income(self, ctx):
"""Check the delivery status of your money.
In reality terms, check to see if the income background task has run into an issue"""
statuses = self.task_manager.get_statuses()
message = self.utils.format_task(statuses["income"])
await ctx.send(message)
@commands.is_owner()
@evolution.command(hidden=True)
async def removeuser(self, ctx, user: discord.User):
"""Removes a user from the market place if they are stuck for some reason.
Only use this if you have to, otherwise things could break"""
try:
self.inmarket.remove(user.id)
except ValueError:
return await ctx.send("The user is not in the marketplace")
await ctx.tick()
@evolution.command()
async def start(self, ctx):
"""Start your adventure..."""
# No locks are needed here because they are all being used with values that don't change,
# or shouldn't be changing at the moment
animal = await self.conf.user(ctx.author).animal()
if animal != "":
return await ctx.send("You have already started your evolution.")
if animal == "P":
return await ctx.send("You are starting your evolution.")
async with self.lock:
await self.conf.user(ctx.author).animal.set("P")
await ctx.send(
f"Hello there. Welcome to Evolution, where you can buy animals to earn credits for economy. What would you like your animals to be named (singular please)? Warning: this cannot be changed. Here is a list of the current available ones: `{'`, `'.join(ANIMALS)}`"
)
def check(m):
return (
(m.author.id == ctx.author.id)
and (m.channel.id == ctx.channel.id)
and (m.content.lower() in ANIMALS)
)
try:
message = await self.bot.wait_for("message", check=check, timeout=30.0)
except asyncio.TimeoutError:
async with self.lock:
await self.conf.user(ctx.author).animal.set("")
return await ctx.send("Command timed out.")
async with self.lock:
async with self.conf.user(ctx.author).all() as data:
data["animal"] = message.content.lower()
data["animals"] = {1: 1}
self.cache[ctx.author.id] = data
await ctx.send(
f"Your animal has been set to {message.content}. You have been granted one to start."
)
@evolution.group()
async def market(self, ctx):
"""Buy or sell animals from different sellers"""
pass
@market.command(aliases=["shop"])
async def store(
self,
ctx,
level: Optional[int] = None,
amount: Optional[int] = 1,
skip_confirmation: Optional[bool] = False,
):
"""Buy animals from the always in-stock store.
While the store will always have animals for sale, you cannot buy above a certain level,
and they will be for a higher price."""
if level is None:
if ctx.channel.permissions_for(ctx.guild.me).embed_links:
return await self.shop(ctx)
else:
return await ctx.send(
'I require the "Embed Links" permission to display the shop.'
)
if ctx.author.id in self.inmarket:
return await ctx.send("Complete your current transaction or evolution first.")
self.inmarket.append(ctx.author.id)
async with self.lock:
data = await self.conf.user(ctx.author).all()
animals = data["animals"]
bought = data["bought"]
animal = data["animal"]
multiplier = data["multiplier"]
if animal in ["", "P"]:
self.inmarket.remove(ctx.author.id)
return await ctx.send("Finish starting your evolution first")
highest = max(list(map(int, animals.keys())))
prev = int(animals.get(str(level), 0))
balance = await bank.get_balance(ctx.author)
current_bought = int(bought.get(str(level), 0))
price = self.utils.get_total_price(level, current_bought, amount)
e = math.ceil((multiplier - 1) * 5)
if balance < price:
self.inmarket.remove(ctx.author.id)
return await ctx.send(f"You need {humanize_number(price)} credits for all of that!")
if prev >= 6 + e:
self.inmarket.remove(ctx.author.id)
return await ctx.send("You have too many of those! Evolve some of them already.")
if prev + amount > 6 + e:
self.inmarket.remove(ctx.author.id)
return await ctx.send("You'd have too many of those! Evolve some of them already.")
if level < 1:
self.inmarket.remove(ctx.author.id)
return await ctx.send("Ya cant buy a negative level!")
if amount < 1:
self.inmarket.remove(ctx.author.id)
return await ctx.send("Ya cant buy a negative amount!")
if (level > int(highest) - 3) and (level > 1):
self.inmarket.remove(ctx.author.id)
return await ctx.send("Please get higher animals to buy higher levels of them.")
if level > 22:
self.inmarket.remove(ctx.author.id)
return await ctx.send("The highest level you can buy is level 22.")
if not skip_confirmation:
m = await ctx.send(
f"Are you sure you want to buy {amount} Level {str(level)} {animal}{'s' if amount != 1 else ''}? This will cost you {humanize_number(price)}."
)
await m.add_reaction("\N{WHITE HEAVY CHECK MARK}")
await m.add_reaction("\N{CROSS MARK}")
def check(reaction, user):
return (
(user.id == ctx.author.id)
and (str(reaction.emoji) in ["\N{WHITE HEAVY CHECK MARK}", "\N{CROSS MARK}"])
and (reaction.message.id == m.id)
)
try:
reaction, user = await self.bot.wait_for("reaction_add", check=check, timeout=60.0)
except asyncio.TimeoutError:
self.inmarket.remove(ctx.author.id)
return await ctx.send(f"You left the {animal} shop without buying anything.")
if str(reaction.emoji) == "\N{CROSS MARK}":
self.inmarket.remove(ctx.author.id)
return await ctx.send(f"You left the {animal} shop without buying anything.")
animals[str(level)] = prev + amount
bought[level] = current_bought + 1
async with self.lock:
async with self.conf.user(ctx.author).all() as data:
data["animals"] = animals
data["bought"] = bought
self.cache[ctx.author.id] = data
await bank.withdraw_credits(ctx.author, price)
await ctx.send(
box(
f"[Transaction Complete]\nYou spent {humanize_number(price)} credits to buy {amount} Level {str(level)} {animal}{'s' if amount != 1 else ''}.",
"css",
)
)
self.inmarket.remove(ctx.author.id)
async def shop(self, ctx, start_level: int = None):
"""Friendlier menu for displaying the animals available at the store."""
async with self.lock:
data = await self.conf.user(ctx.author).all()
animals = data["animals"]
bought = data["bought"]
animal = data["animal"]
if animal in ["", "P"]:
return await ctx.send("Finish starting your evolution first")
embed_list = []
for x in range(1, max(list(map(int, animals.keys()))) + 1):
embed = discord.Embed(
title=f"{animal.title()} Shop", description=f"Level {str(x)}", color=0xD2B48C
)
embed.add_field(name="You currently own", value=animals.get(str(x), 0))
current = int(bought.get(str(x), 0))
embed.add_field(name="You have bought", value=current)
embed.add_field(
name="Price", value=humanize_number(self.utils.get_total_price(int(x), current, 1))
)
last = 0
chances = []
try:
for chance, value in self.utils.levels[int(x)].items():
chances.append(f"{str(chance-last)}% chance to gain {str(value)}")
last = chance
except KeyError:
chances = ["100% chance to gain 1000"]
embed.add_field(name="Income", value="\n".join(chances))
embed.add_field(
name="Credit delay",
value=humanize_timedelta(timedelta=timedelta(seconds=self.utils.delays[int(x)])),
)
embed_list.append(embed)
highest_level = max([int(a) for a in animals.keys() if int(animals[a]) > 0])
highest_level -= 3
if start_level and not (animals.get(str(start_level), False) is False):
highest_level = start_level
highest_level -= 1
if highest_level < 0:
highest_level = 0
controls = dict(DEFAULT_CONTROLS)
controls["\N{MONEY BAG}"] = self.utils.shop_control_callback
await menu(ctx, embed_list, controls, page=highest_level)
@market.command()
async def daily(self, ctx):
"""View the daily deals.
These will come at a lower price than the store, but can only be bought once per day.
Status guide:
A: Available to be bought and put in backyard
B: Already purchased
S: Available to be bought, but will be put in stash because you either do not have the space for the, or above your level threshold
"""
async with self.lock:
data = await self.conf.user(ctx.author).all()
animals = data["animals"]
animal = data["animal"]
if animal in ["", "P"]:
return await ctx.send("Finish starting your evolution first")
multiplier = data["multiplier"]
highest = max(list(map(int, animals.keys())))
e = 6 + math.ceil((multiplier - 1) * 5)
display = []
deals = await self.conf.daily()
for did, deal in deals.items():
status = ""
amount = deal["details"]["amount"]
level = deal["details"]["level"]
if ctx.author.id in deal["bought"]:
status = "[B]"
elif (level > int(highest) - 3 and level != 1) or (
amount + animals.get(str(level), 0) > e
):
status = "#S "
else:
status = " A "
price = self.utils.get_total_price(level, 0, amount, False) * 0.75
display.append(
[
did,
status,
humanize_number(price),
f"{amount} Level {level} {animal}{'s' if amount != 1 else ''}",
]
)
message = await ctx.send(
f"{box(tabulate(display, tablefmt='psql'), lang='css')}Would you like to buy any of these fine animals? Click the corresponding reaction below."
)
emojis = ReactionPredicate.NUMBER_EMOJIS[1:7]
start_adding_reactions(message, emojis)
pred = ReactionPredicate.with_emojis(emojis, message, ctx.author)
try:
await self.bot.wait_for("reaction_add", check=pred, timeout=60.0)
except asyncio.TimeoutError:
return await ctx.send(
"The vendor grew uncomfortable with you there, and told you to leave and come back later."
)
if ctx.author.id in self.inmarket:
return await ctx.send("Complete your current transaction or evolution first.")
self.inmarket.append(ctx.author.id)
buying = pred.result + 1
deal = deals[str(buying)]
if ctx.author.id in deal["bought"]: # ;no
self.inmarket.remove(ctx.author.id)
return await ctx.send(
"You already bought this deal. You cannot buy daily deals multiple times."
)
level = deal["details"]["level"]
amount = deal["details"]["amount"]
price = self.utils.get_total_price(level, 0, amount, False) * 0.75
balance = await bank.get_balance(ctx.author)
if balance < price:
self.inmarket.remove(ctx.author.id)
return await ctx.send(
f"You need {humanize_number(price - balance)} more credits to buy that deal."
)
stashing = 0
delivering = amount
if level > int(highest) - 3 and level != 1:
stashing = amount
delivering = 0
elif amount + animals.get(str(level), 0) > e:
delivering = e - animals[str(level)]
stashing = amount - delivering
async with self.lock:
async with self.conf.user(ctx.author).all() as data:
data["animals"][str(level)] = animals.get(str(level), 0) + delivering
if stashing:
current_stash = data["stash"]["animals"].get(str(level), 0)
data["stash"]["animals"][str(level)] = current_stash + stashing
self.cache[ctx.author.id] = data
async with self.conf.daily() as data: # In case someone buys at the same time, we need to re-read the data
data[str(buying)]["bought"].append(ctx.author.id)
await bank.withdraw_credits(ctx.author, int(price))
await ctx.send(
box(
(
f"[Transaction Complete]\nYou spent {humanize_number(price)} credits to buy {amount} Level {str(level)} {animal}{'s' if amount != 1 else ''}."
f"\n\n{delivering} have been added to your backyard, {stashing} have been sent to your stash."
),
"css",
)
)
self.inmarket.remove(ctx.author.id)
@evolution.group()
async def stash(self, ctx):
"""Where your special animals are put if you cannot hold them in your backyard"""
if not ctx.invoked_subcommand:
await ctx.invoke(self.view)
@stash.command()
async def view(self, ctx):
"""View the animals and perks you have in your stash"""
async with self.lock:
data = await self.conf.user(ctx.author).all()
animal = data["animal"]
if animal in ["", "P"]:
return await ctx.send("Finish starting your evolution first")
if await ctx.embed_requested():
embed = discord.Embed(
title=f"{ctx.author.display_name}'s stash",
description=(
"Animals/perks in your stash have no impact on you. "
"They are here because you could not hold them at the time you picked up the items, or required approval."
),
color=0xD2B48C,
)
asv = ""
if not data["stash"]["animals"]:
asv = inline("You do not have any animals in your stash.")
else:
for level, amount in data["stash"]["animals"].items():
asv += f"{humanize_number(amount)} Level {level} animal{'s' if amount != 1 else ''}\n"
embed.add_field(name="Animal Stash", value=asv)
psv = ""
if not data["stash"]["perks"]:
psv = inline("You do not have any perks in your stash.")
else:
pass
# for level, amount in data["stash"]["perks"].items():
# asv += f"{humanize_number(amount)} Level {level} animal{'s' if amount != 1 else ''}\n"
embed.add_field(name="Perk Stash", value=psv)
await ctx.send(embed=embed)
@stash.group()
async def claim(self, ctx):
"""Claim animals or perks from your stash."""
@claim.command()
async def animal(self, ctx, level: int):
"""Claim animals from your stash"""
async with self.lock:
data = await self.conf.user(ctx.author).all()
animal = data["animal"]
if animal in ["", "P"]:
return await ctx.send("Finish starting your evolution first")
animals = data["animals"]
stash = data["stash"]
multiplier = data["multiplier"]
highest = max(list(map(int, animals.keys())))
e = 6 + math.ceil((multiplier - 1) * 5)
try:
level = int(level)
except ValueError:
return await ctx.send("Invalid level; please supply a number.")
if level > 25 or level < 1:
return await ctx.send("Invalid level; level cannot be above 25 or below 1.")
try:
amount = stash["animals"][str(level)]
assert amount != 0
except (KeyError, AssertionError):
return await ctx.send("You don't have any animals at that level in your stash.")
if level > int(highest) - 3 and level != 1:
return await ctx.send(
"You are not of a required level to claim those animals from stash. Cancelled."
)
if animals.get(str(level), 0) == e:
return await ctx.send(
f"You already have the max amount of Level {level} animals in your backyard. Cancelled."
)
async with self.lock:
async with self.conf.user(ctx.author).all() as new_data:
current = new_data["animals"].get(str(level), 0)
amount = new_data["stash"]["animals"][str(level)]
claiming = min([e - current, amount])
full = True
if claiming != amount:
full = False
new_data["animals"][str(level)] = current + claiming
if amount - claiming == 0:
del new_data["stash"]["animals"][str(level)]
else:
new_data["stash"]["animals"][str(level)] = amount - claiming
self.cache[ctx.author.id] = new_data
extra = ""
if not full:
extra = f"There are still {amount - claiming} {animal}{'s' if claiming != 1 else ''} left in your Level {level} stash."
await ctx.send(
f"Successfully moved {claiming} {animal}{'s' if claiming != 1 else ''} from your stash to your backyard. {extra}"
)
@claim.command(hidden=True)
async def perk(self, ctx, *, name: str):
"""Claim a perk from your stash"""
return await ctx.send("This command is not available. Check back soon!")
@commands.bot_has_permissions(embed_links=True)
@evolution.command(aliases=["by"])
async def backyard(self, ctx, use_menu: bool = False):
"""Where ya animals live! Pass 1 or true to put it in a menu."""
async with self.lock:
data = await self.conf.user(ctx.author).all()
animal = data["animal"]
animals = data["animals"]
multiplier = data["multiplier"]
e = 6 + math.ceil((multiplier - 1) * 5)
if animal in ["", "P"]:
return await ctx.send("Finish starting your evolution first")
if use_menu:
embed_list = []
for level, amount in animals.items():
if amount == 0:
continue
embed = discord.Embed(
title=f"Level {str(level)} {animal}",
description=f"You have {str(amount)} Level {level} {animal}{'s' if amount != 1 else ''}",
color=0xD2B48C,
)
embed.set_thumbnail(url=IMAGES[animal])
embed_list.append(embed)
await menu(ctx, embed_list, DEFAULT_CONTROLS)
else:
embed = discord.Embed(
title=f"The amount of {animal}s you have in your backyard.",
color=0xD2B48C,
description=f"Multiplier: {inline(str(multiplier))}\nMax amount of animals: {inline(str(e))}",
)
embed.set_thumbnail(url=IMAGES[animal])
animals = {k: v for k, v in sorted(animals.items(), key=lambda x: int(x[0]))}
for level, amount in animals.items():
if amount == 0:
continue
embed.add_field(
name=f"Level {str(level)} {animal}",
value=f"You have {str(amount)} Level {level} {animal}{'s' if amount != 1 else ''} \N{ZERO WIDTH SPACE} \N{ZERO WIDTH SPACE}",
)
await ctx.send(embed=embed)
@evolution.command()
async def evolve(self, ctx, level: int, amount: int = 1):
"""Evolve them animals to get more of da economy credits"""
if ctx.author.id in self.inmarket:
return await ctx.send("Complete your current transaction or evolution first.")
self.inmarket.append(ctx.author.id)
if level < 1 or amount < 1:
self.inmarket.remove(ctx.author.id)
return await ctx.send("Too low!")
async with self.lock:
data = await self.conf.user(ctx.author).all()
animal = data["animal"]
animals = data["animals"]
multiplier = data["multiplier"]
e = math.ceil((multiplier - 1) * 5)
if amount > (6 + e) // 2:
self.inmarket.remove(ctx.author.id)
return await ctx.send("Too high!")
if animal in ["", "P"]:
self.inmarket.remove(ctx.author.id)
return await ctx.send("Finish starting your evolution first")
current = animals.get(str(level), 0)
highest = max(list(map(int, animals.keys())))
nextlevel = animals.get(str(level + 1), 0)
if current < (amount * 2):
self.inmarket.remove(ctx.author.id)
return await ctx.send("You don't have enough animals at that level.")
if nextlevel + amount > 6 + e:
self.inmarket.remove(ctx.author.id)
return await ctx.send(
f"You'd have too many Level {str(level + 1)}s! Evolve some of them instead!"
)
found_new = False
recreate = False
currentlevelstr = str(level)
nextlevelstr = str(level + 1)
if level < 11:
number = random.randint(1, 100)
elif level < 21:
number = random.randint(1, 1000)
else:
number = random.randint(1, 10000)
if number == 1:
# Evolution is going to fail
number = random.randint(1, 10)
extra = f"Your {animal}s were successfully recovered however."
if number != 1:
animals[currentlevelstr] -= 2 * amount
extra = f"Your {animal}s were unable to be recovered."
await ctx.send(
box(
(
f"Evolution [Failed]\n\nFailed to convert {str(amount * 2)} Level {currentlevelstr} {animal}s "
f"into {str(amount)} Level {nextlevelstr} {animal}{'s'if amount != 1 else ''}. {extra}"
),
lang="css",
)
)
else:
animals[currentlevelstr] -= 2 * amount
if highest == level:
found_new = True
animals[nextlevelstr] = animals.get(nextlevelstr, 0) + amount
if level + 1 == 26:
recreate = True
if found_new:
sending = "CONGRATULATIONS! You have found a new animal!"
else:
sending = ""
await ctx.send(
box(
(
f"Evolution #Successful\n\nSuccessfully converted {str(amount * 2)} Level {currentlevelstr} {animal}s "
f"into {str(amount)} Level {nextlevelstr} {animal}{'s' if amount != 1 else ''}.\n\n{sending}"
),
lang="css",
)
)
if recreate:
async with self.lock:
async with self.conf.user(ctx.author).all() as data:
multiplier = data["multiplier"]
data["animals"] = {1: 1}
data["multiplier"] = multiplier + 0.2
new = (
"**Report:**\n"
f"**To:** {ctx.author.display_name}\n"
f"**Concerning:** Animal experiment #{str(math.ceil(((multiplier - 1) * 5) + 1))}\n"
f"**Subject:** Animal experiment concluded.\n\n"
f"Congratulations, {ctx.author.display_name}! You have successfully combined enough animals to reach a Level 26 Animal! This means that it is time to recreate universe! This will reset your bank account, remove all of your animals, but allow one more animal of every level, and give you an extra 20% income rate for the next universe from all income. Congratulations!\n\n"
f"From, The Head {animal.title()}"
)
await ctx.send(new)
await bank.set_balance(ctx.author, 0)
else:
async with self.lock:
await self.conf.user(ctx.author).animals.set(animals)
self.inmarket.remove(ctx.author.id)

16
evolution/info.json Normal file
View file

@ -0,0 +1,16 @@
{
"author": [
"Neuro Assassin"
],
"install_msg": "Thank you for downloading this cog. This cog requires for the bank to be global in order to be used.",
"name": "evolution",
"short": "Buy and get animals to get more economy credits!",
"description": "Buy animals using economy credits or get them every 10 minutes, and gain a certain amount of credits every minute!",
"tags": [
"fun"
],
"requirements": [
"tabulate"
],
"hidden": false
}

117
evolution/tasks.py Normal file
View file

@ -0,0 +1,117 @@
from __future__ import annotations
import asyncio
import contextlib
import random
import time
from typing import TYPE_CHECKING, Dict
from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.utils import AsyncIter
if TYPE_CHECKING:
from .evolution import Evolution
from . import bank
class EvolutionTaskManager:
def __init__(self, cog):
self.bot: Red = cog.bot
self.conf: Config = cog.conf
self.cog: Evolution = cog
self.tasks: Dict[str, asyncio.Task] = {}
async def process_credits(self, data, ct, timedata):
all_gaining = 0
async for key, value in AsyncIter(data.items()):
last_processed = timedata[str(key)]
if ct > last_processed + self.cog.utils.delays[int(key)]:
for x in range(0, value):
chance = random.randint(1, 100)
chances = list(self.cog.utils.levels[int(key)].keys())
chosen = min([c for c in chances if chance <= c])
gaining = self.cog.utils.levels[int(key)][chosen]
all_gaining += gaining
return all_gaining
async def process_times(self, ct, timedata):
async for key in AsyncIter(range(1, 26)):
last_processed = timedata[str(key)]
if ct > last_processed + self.cog.utils.delays[int(key)]:
timedata[str(key)] = ct
return timedata
async def income_task(self):
await self.bot.wait_until_ready()
while True:
# First, process the credits being added
bulk_edit = {}
ct = time.time()
lastcredited = await self.cog.conf.lastcredited()
async for userid, data in AsyncIter(self.cog.cache.copy().items()):
animal = data["animal"]
if animal == "":
continue
multiplier = data["multiplier"]
animals = data["animals"]
gaining = await self.process_credits(animals, ct, lastcredited) * multiplier
bulk_edit[str(userid)] = gaining
# Credit to aikaterna's seen cog for this bulk write
config = bank._get_config()
users = config._get_base_group(config.USER)
max_credits = await bank.get_max_balance()
async with users.all() as new_data:
for user_id, userdata in bulk_edit.items():
if str(user_id) not in new_data:
new_data[str(user_id)] = {"balance": userdata}
continue
if new_data[str(user_id)]["balance"] + userdata > max_credits:
new_data[str(user_id)]["balance"] = int(max_credits)
else:
new_data[str(user_id)]["balance"] = int(
new_data[str(user_id)]["balance"] + userdata
)
await self.cog.conf.lastcredited.set(await self.process_times(ct, lastcredited))
await asyncio.sleep(60)
async def daily_task(self):
await self.bot.wait_until_ready()
while True:
lastdailyupdate = await self.cog.conf.lastdailyupdate()
if lastdailyupdate + 86400 <= time.time():
deals = {}
levels = random.sample(
self.cog.utils.randlvl_chances, len(self.cog.utils.randlvl_chances)
)
amounts = random.sample(
self.cog.utils.randamt_chances, len(self.cog.utils.randamt_chances)
)
for x in range(1, 7):
level = random.choice(levels)
amount = random.choice(amounts)
deals[str(x)] = {"details": {"level": level, "amount": amount}, "bought": []}
await self.cog.conf.daily.set(deals)
await self.cog.conf.lastdailyupdate.set(time.time())
await asyncio.sleep(300)
def get_statuses(self):
returning = {}
for task, obj in self.tasks.items():
exc = None
with contextlib.suppress(asyncio.exceptions.InvalidStateError):
exc = obj.exception()
returning[task] = {"state": obj._state, "exc": exc}
return returning
def init_tasks(self):
self.tasks["income"] = self.bot.loop.create_task(self.income_task())
self.tasks["daily"] = self.bot.loop.create_task(self.daily_task())
def shutdown(self):
for task in self.tasks.values():
task.cancel()

211
evolution/utils.py Normal file
View file

@ -0,0 +1,211 @@
from __future__ import annotations
import traceback
from typing import TYPE_CHECKING
from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.utils.menus import menu
if TYPE_CHECKING:
from .evolution import Evolution
class EvolutionUtils:
def __init__(self, cog):
self.bot: Red = cog.bot
self.conf: Config = cog.conf
self.cog: Evolution = cog
@staticmethod
def get_total_price(level, bought, amount, bt=True):
total = 0
for x in range(amount):
normal = level * 800
level_tax = ((2**level) * 10) - 200
if bt:
tax = bought * 300
extra = x * 300
else:
tax = 0
extra = 0
total += normal + level_tax + tax + extra
return total
@property
def levels(self):
return {
1: {100: 10},
2: {90: 10, 100: 100},
3: {80: 10, 100: 100},
4: {70: 10, 100: 100},
5: {60: 10, 100: 100},
6: {50: 10, 90: 100, 100: 1000},
7: {40: 10, 80: 100, 100: 1000},
8: {30: 10, 70: 100, 100: 1000},
9: {20: 10, 60: 100, 100: 1000},
10: {10: 10, 50: 100, 100: 1000},
11: {40: 100, 90: 1000, 100: 1500},
12: {30: 100, 80: 1000, 100: 1500},
13: {20: 100, 70: 1000, 100: 1500},
14: {10: 100, 60: 1000, 100: 1500},
15: {50: 1000, 100: 1500},
16: {40: 1000, 100: 1500},
17: {30: 1000, 100: 1500},
18: {20: 1000, 100: 1500},
19: {10: 1000, 100: 1500},
20: {90: 1500, 100: 2000},
21: {80: 1500, 100: 2000},
22: {70: 1500, 100: 2000},
23: {60: 1500, 100: 2000},
24: {50: 1500, 100: 2000},
25: {100: 2000},
}
@property
def delays(self):
return {
1: 86400, # 24 hours
2: 64800, # 18 hours
3: 43200, # 12 hours
4: 39600, # 11 hours
5: 36000, # 10 hours
6: 32400, # 9 hours
7: 28800, # 8 hours
8: 25200, # 7 hours
9: 21600, # 6 hours
10: 18000, # 5 hours
11: 14400, # 4 hours
12: 10800, # 3 hours
13: 7200, # 2 hours
14: 3600, # 1 hour
15: 3000, # 50 minutes
16: 2400, # 40 minutes
17: 1800, # 30 minutes
18: 1200, # 20 minutes
19: 600, # 10 minutes
20: 420, # 7 minutes
21: 300, # 5 minutes
22: 240, # 4 minutes
23: 180, # 3 minutes
24: 120, # 2 minutes
25: 60, # 1 minute
26: 60, # 1 minute (Just in case)
}
@property
def randlvl_chances(self):
return [
1,
2,
3,
4,
4,
5,
5,
5,
6,
6,
6,
6,
7,
7,
7,
7,
8,
8,
8,
8,
9,
9,
9,
9,
10,
10,
10,
10,
10,
10,
11,
11,
11,
11,
11,
12,
12,
12,
12,
12,
12,
13,
13,
13,
13,
13,
14,
14,
14,
14,
14,
15,
15,
15,
15,
16,
16,
16,
17,
17,
18,
19,
20,
]
@property
def randamt_chances(self):
return [1, 1, 2, 2, 2, 3, 3, 3, 4, 5]
async def shop_control_callback(self, ctx, pages, controls, message, page, timeout, emoji):
description = message.embeds[0].description
level = int(description.split(" ")[1])
self.bot.loop.create_task(ctx.invoke(self.cog.store, level=level))
return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout)
def format_task(self, task):
state = task["state"].lower()
if task["exc"]:
e = task["exc"]
exc = traceback.format_exception(type(e), e, e.__traceback__)
exc_output = (
f"Please report the following error to Neuro Assassin: ```py\n{''.join(exc)}```"
)
else:
exc_output = "No error has been encountered."
return f"Task is currently {state}. {exc_output}"
def init_config(self):
default_user = {
"animal": "",
"animals": {},
"multiplier": 1.0,
"bought": {},
"stash": {"animals": {}, "perks": {}},
}
default_guild = {"cartchannel": 0, "last": 0}
default_global = {
"travelercooldown": "2h",
"lastcredited": {},
"lastdailyupdate": 0,
"daily": {},
}
for x in range(1, 27):
default_global["lastcredited"][str(x)] = 0
self.conf.register_user(**default_user)
self.conf.register_guild(**default_guild)
self.conf.register_global(**default_global)
async def initialize(self):
config = await self.cog.conf.all_users()
for k, v in config.items():
self.cog.cache[k] = v

94
extendedeconomy/README.md Normal file
View file

@ -0,0 +1,94 @@
Set prices for commands, customize how prices are applied, log bank events and more!
# [p]bankpie
View a pie chart of the top X bank balances.<br/>
- Usage: `[p]bankpie [amount=10]`
# [p]extendedeconomy
Extended Economy settings<br/>
**NOTE**<br/>
Although setting prices for pure slash commands works, there is no refund mechanism in place for them.<br/>
Should a hybrid or text command fail due to an unhandled exception, the user will be refunded.<br/>
- Usage: `[p]extendedeconomy`
- Restricted to: `ADMIN`
- Aliases: `ecoset and exteco`
- Checks: `server_only`
## [p]extendedeconomy resetcooldown
Reset the payday cooldown for a user<br/>
- Usage: `[p]extendedeconomy resetcooldown <member>`
## [p]extendedeconomy stackpaydays
Toggle whether payday roles stack or not<br/>
- Usage: `[p]extendedeconomy stackpaydays`
- Aliases: `stackpayday`
## [p]extendedeconomy autopaydayrole
Add/Remove auto payday roles<br/>
- Usage: `[p]extendedeconomy autopaydayrole <role>`
## [p]extendedeconomy view
View the current settings<br/>
- Usage: `[p]extendedeconomy view`
## [p]extendedeconomy transfertax
Set the transfer tax percentage as a decimal<br/>
*Example: `0.05` is for 5% tax*<br/>
- Set to 0 to disable<br/>
- Default is 0<br/>
- Usage: `[p]extendedeconomy transfertax <tax>`
## [p]extendedeconomy mainlog
Set the main log channel<br/>
- Usage: `[p]extendedeconomy mainlog [channel=None]`
## [p]extendedeconomy autopayday
Toggle whether paydays are claimed automatically (Global bank)<br/>
- Usage: `[p]extendedeconomy autopayday`
- Restricted to: `BOT_OWNER`
## [p]extendedeconomy eventlog
Set an event log channel<br/>
**Events:**<br/>
- set_balance<br/>
- transfer_credits<br/>
- bank_wipe<br/>
- prune<br/>
- set_global<br/>
- payday_claim<br/>
- Usage: `[p]extendedeconomy eventlog <event> [channel=None]`
## [p]extendedeconomy rolebonus
Add/Remove Payday role bonuses<br/>
Example: `[p]ecoset rolebonus @role 0.1` - Adds a 10% bonus to the user's payday if they have the role.<br/>
To remove a bonus, set the bonus to 0.<br/>
- Usage: `[p]extendedeconomy rolebonus <role> <bonus>`
## [p]extendedeconomy autoclaimchannel
Set the auto claim channel<br/>
- Usage: `[p]extendedeconomy autoclaimchannel [channel]`
## [p]extendedeconomy deleteafter
Set the delete after time for cost check messages<br/>
- Set to 0 to disable (Recommended for public bots)<br/>
- Default is 0 (disabled)<br/>
- Usage: `[p]extendedeconomy deleteafter <seconds>`
- Restricted to: `BOT_OWNER`
# [p]addcost
Add a cost to a command<br/>
- Usage: `[p]addcost [command=] [cost=0] [duration=3600] [level=all] [prompt=notify] [modifier=static] [value=0.0]`
- Restricted to: `ADMIN`
- Checks: `server_only`
# [p]banksetrole
Set the balance of all user accounts that have a specific role<br/>
Putting + or - signs before the amount will add/remove currency on the user's bank account instead.<br/>
Examples:<br/>
- `[p]banksetrole @everyone 420` - Sets everyones balance to 420<br/>
- `[p]banksetrole @role +69` - Increases balance by 69 for everyone with the role<br/>
- `[p]banksetrole @role -42` - Decreases balance by 42 for everyone with the role<br/>
**Arguments**<br/>
- `<role>` The role to set the currency of for each user that has it.<br/>
- `<creds>` The amount of currency to set their balance to.<br/>
- Usage: `[p]banksetrole <role> <creds>`
- Restricted to: `ADMIN`
- Checks: `is_owner_if_bank_global`

View file

@ -0,0 +1,11 @@
from redbot.core.bot import Red
from redbot.core.utils import get_end_user_data_statement
from .main import ExtendedEconomy
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
async def setup(bot: Red):
cog = ExtendedEconomy(bot)
await bot.add_cog(cog)

46
extendedeconomy/abc.py Normal file
View file

@ -0,0 +1,46 @@
import typing as t
from abc import ABC, ABCMeta, abstractmethod
import discord
from discord.ext.commands.cog import CogMeta
from redbot.core import commands
from redbot.core.bot import Red
from .common.models import DB
class CompositeMetaClass(CogMeta, ABCMeta):
"""Type detection"""
class MixinMeta(ABC):
"""Type hinting"""
def __init__(self, *_args):
self.bot: Red
self.db: DB
self.checks: set
self.charged: t.Dict[str, int]
self.payday_callback: t.Optional[t.Callable]
@abstractmethod
async def save(self) -> None:
raise NotImplementedError()
@abstractmethod
async def cost_check(self, ctx: commands.Context):
raise NotImplementedError()
@abstractmethod
async def slash_cost_check(self, interaction: discord.Interaction):
raise NotImplementedError()
@abstractmethod
async def transfer_tax_check(self, ctx: commands.Context):
raise NotImplementedError()
@abstractmethod
async def send_payloads(self):
raise NotImplementedError()

View file

@ -0,0 +1,7 @@
from ..abc import CompositeMetaClass
from .admin import Admin
from .user import User
class Commands(Admin, User, metaclass=CompositeMetaClass):
"""Subclass all command classes"""

View file

@ -0,0 +1,783 @@
import calendar
import io
import logging
import typing as t
from datetime import datetime, timezone
import discord
from discord import app_commands
from redbot.core import Config, bank, commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import humanize_number, humanize_timedelta
from ..abc import MixinMeta
from ..common.models import CommandCost
from ..common.parser import SetParser
from ..views.confirm import ConfirmView
from ..views.cost_menu import CostMenu
log = logging.getLogger("red.vrt.extendedeconomy.admin")
_ = Translator("ExtendedEconomy", __file__)
@cog_i18n(_)
class Admin(MixinMeta):
@commands.group(aliases=["ecoset", "exteco"])
@commands.admin_or_permissions(manage_guild=True)
@commands.guild_only()
async def extendedeconomy(self, ctx: commands.Context):
"""
Extended Economy settings
**NOTE**
Although setting prices for pure slash commands works, there is no refund mechanism in place for them.
Should a hybrid or text command fail due to an unhandled exception, the user will be refunded.
"""
pass
@extendedeconomy.command(name="diagnose", hidden=True)
@commands.guildowner()
async def diagnose_issues(self, ctx: commands.Context, *, member: discord.Member):
"""
Diagnose issues with the cog for a user
"""
eco = self.bot.get_cog("Economy")
is_global = await bank.is_global()
txt = _("**Global Bank:** `{}`\n").format(is_global)
txt += _("**Economy Cog:** `{}`\n").format(_("Loaded") if eco else _("Not Loaded"))
txt += _("**Auto Paydays:** `{}`\n").format(self.db.auto_payday_claim)
conf = self.db.get_conf(ctx.guild)
if not is_global:
txt += _("**Auto Payday Roles:** {}\n").format(
", ".join([f"<@&{x}>" for x in conf.auto_claim_roles]) if conf.auto_claim_roles else _("None")
)
cur_time = calendar.timegm(datetime.now(tz=timezone.utc).utctimetuple())
eco_conf: Config = eco.config
if is_global:
bankgroup = bank._config._get_base_group(bank._config.USER)
ecogroup = eco_conf._get_base_group(eco_conf.USER)
accounts: t.Dict[str, dict] = await bankgroup.all()
ecousers: t.Dict[str, dict] = await ecogroup.all()
# max_bal = await bank.get_max_balance()
payday_time = await eco_conf.PAYDAY_TIME()
# payday_credits = await eco_conf.PAYDAY_CREDITS()
else:
bankgroup = bank._config._get_base_group(bank._config.MEMBER, str(ctx.guild.id))
ecogroup = eco_conf._get_base_group(eco_conf.MEMBER, str(ctx.guild.id))
accounts: t.Dict[str, dict] = await bankgroup.all()
ecousers: t.Dict[str, dict] = await ecogroup.all()
# max_bal = await bank.get_max_balance(ctx.guild)
payday_time = await eco_conf.guild(ctx.guild).PAYDAY_TIME()
# payday_credits = await eco_conf.guild(ctx.guild).PAYDAY_CREDITS()
# payday_roles: t.Dict[int, dict] = await eco_conf.all_roles()
uid = str(member.id)
if uid not in accounts:
txt += _("- {} has not used the bank yet.\n").format(member.display_name)
if uid not in ecousers:
txt += _("- {} has not used the economy commands yet.\n").format(member.display_name)
else:
next_payday = ecousers[uid].get("next_payday", 0) + payday_time
if cur_time < next_payday:
time_left = next_payday - cur_time
txt += _("- {} has {} seconds left until their next payday.\n").format(member.display_name, time_left)
else:
txt += _("- {} is ready for their next payday.\n").format(member.display_name)
await ctx.send(txt)
@extendedeconomy.command(name="view")
@commands.bot_has_permissions(embed_links=True)
async def view_settings(self, ctx: commands.Context):
"""
View the current settings
"""
is_global = await bank.is_global()
if is_global and ctx.author.id not in self.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to view these settings when global bank is enabled."))
view = CostMenu(ctx, self, is_global, self.cost_check)
await view.refresh()
@extendedeconomy.command(name="resetcooldown")
async def reset_payday_cooldown(self, ctx: commands.Context, *, member: discord.Member):
"""Reset the payday cooldown for a user"""
cog = self.bot.get_cog("Economy")
if not cog:
return await ctx.send(_("Economy cog is not loaded."))
if await bank.is_global() and ctx.author.id not in self.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to reset cooldowns when global bank is enabled!"))
cur_time = calendar.timegm(ctx.message.created_at.utctimetuple())
if await bank.is_global():
payday_time = await cog.config.PAYDAY_TIME()
new_time = int(cur_time - payday_time)
await cog.config.user(member).next_payday.set(new_time)
else:
payday_time = await cog.config.guild(ctx.guild).PAYDAY_TIME()
new_time = int(cur_time - payday_time)
await cog.config.member(member).next_payday.set(new_time)
await ctx.send(_("Payday cooldown reset for **{}**.").format(member.display_name))
@extendedeconomy.command(name="stackpaydays", aliases=["stackpayday"])
async def stack_paydays(self, ctx: commands.Context):
"""Toggle whether payday roles stack or not"""
is_global = await bank.is_global()
if is_global:
return await ctx.send(_("This setting is not available when global bank is enabled."))
conf = self.db.get_conf(ctx.guild)
conf.stack_paydays = not conf.stack_paydays
if conf.stack_paydays:
txt = _("Payday role amounts will now stack.")
else:
txt = _("Payday role amounts will no longer stack.")
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="autopaydayrole")
async def autopayday_roles(self, ctx: commands.Context, *, role: discord.Role):
"""Add/Remove auto payday roles"""
is_global = await bank.is_global()
if is_global:
txt = _("This setting is not available when global bank is enabled.")
if ctx.author.id in self.bot.owner_ids:
txt += _("\nUse {} to allow auto-claiming for for all users.").format(
f"`{ctx.clean_prefix}ecoset autopayday`"
)
return await ctx.send(txt)
conf = self.db.get_conf(ctx.guild)
if role.id in conf.auto_claim_roles:
conf.auto_claim_roles.remove(role.id)
txt = _("This role will no longer recieve paydays automatically.")
else:
conf.auto_claim_roles.append(role.id)
txt = _("This role will now receive paydays automatically.")
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="rolebonus")
async def role_bonus(self, ctx: commands.Context, role: discord.Role, bonus: float):
"""
Add/Remove Payday role bonuses
Example: `[p]ecoset rolebonus @role 0.1` - Adds a 10% bonus to the user's payday if they have the role.
To remove a bonus, set the bonus to 0.
"""
is_global = await bank.is_global()
if is_global:
return await ctx.send(_("This setting is not available when global bank is enabled."))
conf = self.db.get_conf(ctx.guild)
if bonus <= 0:
if role.id in conf.role_bonuses:
del conf.role_bonuses[role.id]
txt = _("Role bonus removed.")
await self.save()
else:
txt = _("That role does not have a bonus.")
return await ctx.send(txt)
if role.id in conf.role_bonuses:
current = conf.role_bonuses[role.id]
if current == bonus:
return await ctx.send(_("That role already has that bonus."))
txt = _("Role bonus updated.")
else:
txt = _("Role bonus added.")
conf.role_bonuses[role.id] = bonus
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="autopayday")
@commands.is_owner()
async def autopayday(self, ctx: commands.Context):
"""Toggle whether paydays are claimed automatically (Global bank)"""
is_global = await bank.is_global()
self.db.auto_payday_claim = not self.db.auto_payday_claim
if self.db.auto_payday_claim:
if is_global:
txt = _("Paydays will now be claimed automatically for all users.")
else:
txt = _("Paydays will now be claimed automatically for set roles.")
else:
txt = _("Paydays will no longer be claimed automatically.")
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="autoclaimchannel")
async def auto_claim_channel(self, ctx: commands.Context, *, channel: t.Optional[discord.TextChannel] = None):
"""Set the auto claim channel"""
is_global = await bank.is_global()
if is_global:
return await ctx.send(_("There is no auto claim channel when global bank is enabled!"))
txt = _("Auto claim channel set to {}").format(channel.mention) if channel else _("Auto claim channel removed.")
if is_global:
self.db.logs.auto_claim = channel.id if channel else 0
else:
conf = self.db.get_conf(ctx.guild)
conf.logs.auto_claim = channel.id if channel else 0
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="transfertax")
async def set_transfertax(self, ctx: commands.Context, tax: float):
"""
Set the transfer tax percentage as a decimal
*Example: `0.05` is for 5% tax*
- Set to 0 to disable
- Default is 0
"""
is_global = await bank.is_global()
if is_global and ctx.author.id not in self.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to set the transfer tax when global bank is enabled."))
if tax < 0 or tax >= 1:
return await ctx.send(_("Invalid tax percentage. Must be between 0 and 1."))
if is_global:
self.db.transfer_tax = tax
else:
conf = self.db.get_conf(ctx.guild)
conf.transfer_tax = tax
await ctx.send(_("Transfer tax set to {}%").format(round(tax * 100, 2)))
await self.save()
@extendedeconomy.command(name="taxwhitelist")
async def set_taxwhitelist(self, ctx: commands.Context, *, role: discord.Role):
"""
Add/Remove roles from the transfer tax whitelist
"""
is_global = await bank.is_global()
if is_global:
return await ctx.send(_("This setting is not available when global bank is enabled."))
conf = self.db.get_conf(ctx.guild)
if role.id in conf.transfer_tax_whitelist:
conf.transfer_tax_whitelist.remove(role.id)
txt = _("Role removed from the transfer tax whitelist.")
else:
conf.transfer_tax_whitelist.append(role.id)
txt = _("Role added to the transfer tax whitelist.")
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="mainlog")
async def set_mainlog(self, ctx: commands.Context, channel: t.Optional[discord.TextChannel] = None):
"""
Set the main log channel
"""
is_global = await bank.is_global()
if is_global and ctx.author.id not in self.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to set the main log channel when global bank is enabled."))
if is_global:
current = self.db.logs.default_log_channel
else:
conf = self.db.get_conf(ctx.guild)
current = conf.logs.default_log_channel
if not channel and current:
txt = _("Removing the main log channel.")
elif not channel and not current:
return await ctx.send_help()
elif channel and current:
if channel.id == current:
return await ctx.send(_("That is already the main log channel."))
txt = _("Main log channel changed to {}").format(channel.mention)
else:
txt = _("Main log channel set to {}").format(channel.mention)
if is_global:
self.db.logs.default_log_channel = channel.id if channel else 0
else:
conf = self.db.get_conf(ctx.guild)
conf.logs.default_log_channel = channel.id if channel else 0
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="eventlog")
async def set_eventlog(
self,
ctx: commands.Context,
event: str,
channel: t.Optional[discord.TextChannel] = None,
):
"""
Set an event log channel
**Events:**
- set_balance
- transfer_credits
- bank_wipe
- prune
- set_global
- payday_claim
"""
is_global = await bank.is_global()
if is_global and ctx.author.id not in self.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to set an event log channel when global bank is enabled."))
if is_global:
logs = self.db.logs
else:
conf = self.db.get_conf(ctx.guild)
logs = conf.logs
valid_events = [
"set_balance",
"transfer_credits",
"bank_wipe",
"prune",
"set_global",
"payday_claim",
]
if event not in valid_events:
return await ctx.send(_("Invalid event. Must be one of: {}").format(", ".join(valid_events)))
current = getattr(logs, event)
if not current and not channel:
return await ctx.send(_("No channel set for this event."))
if current and not channel:
txt = _("Event log channel for {} removed.").format(event)
elif current and channel:
if channel.id == current:
return await ctx.send(_("That is already the event log channel for {}.").format(event))
txt = _("Event log channel for {} changed to {}").format(event, channel.mention)
else:
txt = _("Event log channel for {} set to {}").format(event, channel.mention)
if is_global:
setattr(self.db.logs, event, channel.id if channel else 0)
else:
conf = self.db.get_conf(ctx.guild)
setattr(conf.logs, event, channel.id if channel else 0)
await ctx.send(txt)
await self.save()
@extendedeconomy.command(name="deleteafter")
@commands.is_owner()
async def set_delete_after(self, ctx: commands.Context, seconds: int):
"""
Set the delete after time for cost check messages
- Set to 0 to disable (Recommended for public bots)
- Default is 0 (disabled)
"""
if not seconds:
self.db.delete_after = None
await ctx.send(_("Delete after time disabled."))
else:
self.db.delete_after = seconds
await ctx.send(_("Delete after time set to {} seconds.").format(seconds))
await self.save()
# @extendedeconomy.command(name="perguildoverride")
# @commands.is_owner()
# async def per_guild_override(self, ctx: commands.Context):
# """Toggle per guild prices when global bank is enabled"""
# self.db.per_guild_override = not self.db.per_guild_override
# if self.db.per_guild_override:
# txt = _("Per guild prices are now enabled.")
# else:
# txt = _("Per guild prices are now disabled.")
# await ctx.send(txt)
# await self.save()
@commands.command(name="addcost")
@commands.admin_or_permissions(manage_guild=True)
@commands.guild_only()
async def add_cost(
self,
ctx: commands.Context,
command: str = "",
cost: int = 0,
duration: int = 3600,
level: t.Literal["admin", "mod", "all", "user", "global"] = "all",
prompt: t.Literal["text", "reaction", "button", "silent", "notify"] = "notify",
modifier: t.Literal["static", "percent", "exponential", "linear"] = "static",
value: float = 0.0,
):
"""
Add a cost to a command
"""
if not command:
help_txt = _(
"- **cost**: The amount of currency to charge\n"
"- **duration**(`default: 3600`): The time in seconds before the cost resets\n"
"- **level**(`default: all`): The minimum permission level to apply the cost\n"
" - admin: Admins and above can use the command for free\n"
" - mod: Mods and above can use the command for free\n"
" - all: Everyone must pay the cost to use the command\n"
" - user: All users must pay the cost to use the command unless they are mod or admin\n"
" - global: The cost is applied to all users globally\n"
"- **prompt**(`default: notify`): How the user will be prompted to confirm the cost\n"
" - text: The bot will send a text message asking the user to confirm the cost with yes or no\n"
" - reaction: The bot will send a message with emoji reactions to confirm the cost\n"
" - button: The bot will send a message with buttons to confirm the cost\n"
" - silent: The bot will not prompt the user to confirm the cost\n"
" - notify: The bot will simply notify the user of the cost without asking for confirmation\n"
"- **modifier**(`default: static`): The type of cost modifier\n"
" - static: The cost is a fixed amount\n"
" - percent: The cost is a percentage of the user's balance on top of the base cost\n"
" - exponential: The cost increases exponentially based on how frequently the command is used\n"
" - Ex: `Cost = cost + (value * uses over the duration^2)`\n"
" - linear: The cost increases linearly based on how frequently the command is used\n"
" - Ex: `Cost = cost + (value * uses over the duration)`\n"
"- **value**(`default: 0.0`): The value of the cost modifier depends on the modifier type\n"
" - static: This will be 0 and does nothing\n"
" - percent: Value will be the percentage of the user's balance to add to the base cost\n"
" - exponential: Value will be the base cost multiplier\n"
" - linear: Value will be multiplied by the number of uses in the last hour to get the cost increase\n"
)
await ctx.send_help()
return await ctx.send(help_txt)
if command == "addcost":
return await ctx.send(_("You can't add a cost to the addcost command."))
is_global = await bank.is_global()
if is_global:
if ctx.author.id not in ctx.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to use this command while global bank is active."))
if level != "global":
return await ctx.send(_("Global bank is active, you must use the global level."))
else:
if level == "global":
if ctx.author.id not in ctx.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to use the global level."))
return await ctx.send(_("You must enable global bank to use the global level."))
command_obj: commands.Command = self.bot.get_command(command)
if not command_obj:
command_obj: app_commands.Command = self.bot.tree.get_command(command)
if not command_obj:
return await ctx.send(_("Command not found."))
if not isinstance(command_obj, app_commands.Command):
return await ctx.send(_("That is not a valid app command"))
if isinstance(command_obj, commands.commands._AlwaysAvailableCommand):
return await ctx.send(_("You can't add costs to commands that are always available!"))
if isinstance(command_obj, (commands.Command, commands.HybridCommand)):
if (command_obj.requires.privilege_level or 0) > await commands.requires.PrivilegeLevel.from_ctx(ctx):
return await ctx.send(_("You can't add costs to commands you don't have permission to run!"))
cost_obj = CommandCost(
cost=cost,
duration=duration,
level=level,
prompt=prompt,
modifier=modifier,
value=value,
)
overwrite_warning = _("This will overwrite the existing cost for this command. Continue?")
costs = self.db.command_costs if is_global else self.db.get_conf(ctx.guild).command_costs
if command in costs:
view = ConfirmView(ctx.author)
msg = await ctx.send(overwrite_warning, view=view)
await view.wait()
if not view.value:
return await msg.edit(content=_("Not adding cost."), view=None)
await msg.edit(content=_("{} cost updated.").format(command), view=None)
else:
await ctx.send(_("{} cost added.").format(command))
if is_global:
self.db.command_costs[command] = cost_obj
else:
conf = self.db.get_conf(ctx.guild)
conf.command_costs[command] = cost_obj
await self.save()
@commands.command(name="banksetrole")
@bank.is_owner_if_bank_global()
@commands.admin_or_permissions(manage_guild=True)
async def bank_set_role(self, ctx: commands.Context, role: discord.Role, creds: SetParser):
"""Set the balance of all user accounts that have a specific role
Putting + or - signs before the amount will add/remove currency on the user's bank account instead.
Examples:
- `[p]banksetrole @everyone 420` - Sets everyones balance to 420
- `[p]banksetrole @role +69` - Increases balance by 69 for everyone with the role
- `[p]banksetrole @role -42` - Decreases balance by 42 for everyone with the role
**Arguments**
- `<role>` The role to set the currency of for each user that has it.
- `<creds>` The amount of currency to set their balance to.
"""
async with ctx.typing():
if await bank.is_global():
group = bank._config._get_base_group(bank._config.USER)
else:
group = bank._config._get_base_group(bank._config.MEMBER, str(ctx.guild.id))
currency = await bank.get_currency_name(ctx.guild)
max_bal = await bank.get_max_balance(ctx.guild)
try:
default_balance = await bank.get_default_balance(ctx.guild)
except AttributeError:
default_balance = await bank.get_default_balance()
members: t.List[discord.Member] = [user for user in ctx.guild.members if role in user.roles]
if not members:
return await ctx.send(_("No users found with that role."))
users_affected = 0
total = 0
async with group.all() as accounts:
for mem in members:
uid = str(mem.id)
if uid in accounts:
wallet = accounts[uid]
else:
wallet = {"name": mem.display_name, "balance": default_balance, "created_at": 0}
accounts[uid] = wallet
match creds.operation:
case "deposit":
amount = min(max_bal - wallet["balance"], creds.sum)
case "withdraw":
amount = -min(wallet["balance"], creds.sum)
case _: # set
amount = creds.sum - wallet["balance"]
accounts[uid]["balance"] += amount
total += amount
users_affected += 1
if not users_affected:
return await ctx.send(_("No users were affected."))
if not total:
return await ctx.send(_("No balances were changed."))
grammar = _("user was") if users_affected == 1 else _("users were")
msg = _("Balances for {} updated, total change was {}.").format(
f"{users_affected} {grammar}", f"{total} {currency}"
)
await ctx.send(msg)
@commands.command(name="backpay")
@bank.is_owner_if_bank_global()
@commands.admin_or_permissions(manage_guild=True)
@commands.guild_only()
async def backpay_cmd(self, ctx: commands.Context, duration: commands.TimedeltaConverter, confirm: bool = False):
"""Calculate and award missed paydays for all members within a time period.
This will calculate how many paydays each member could have claimed within the
specified time period and award them accordingly.
By default, this command will only show a preview. Use `confirm=True` to apply changes.
Examples:
- `[p]backpay 48h` - Preview paydays missed in the last 48 hours
- `[p]backpay 7d True` - Calculate and give paydays missed in the last 7 days
**Arguments**
- `<duration>` How far back to check for missed paydays. Use time abbreviations like 1d, 12h, etc.
- `[confirm]` Set to True to actually apply the changes. Default: False (preview only)
"""
if await bank.is_global() and ctx.author.id not in self.bot.owner_ids:
return await ctx.send(_("You must be a bot owner to use this command while global bank is active."))
if duration.total_seconds() <= 0:
return await ctx.send(_("Duration must be positive!"))
async with ctx.typing():
eco_cog = self.bot.get_cog("Economy")
if not eco_cog:
return await ctx.send(_("Economy cog is not loaded."))
eco_conf: Config = eco_cog.config
is_global = await bank.is_global()
currency = await bank.get_currency_name(ctx.guild)
max_bal = await bank.get_max_balance(ctx.guild)
# Get current time and the time to look back to
current_time = calendar.timegm(datetime.now(tz=timezone.utc).utctimetuple())
# Get the payday cooldown period
if is_global:
payday_time = await eco_conf.PAYDAY_TIME()
payday_credits = await eco_conf.PAYDAY_CREDITS()
else:
payday_time = await eco_conf.guild(ctx.guild).PAYDAY_TIME()
payday_credits = await eco_conf.guild(ctx.guild).PAYDAY_CREDITS()
# Calculate total possible paydays in the lookback period
# This ensures everyone gets the same number of paydays for the same duration
total_possible_paydays = int(duration.total_seconds() // payday_time)
if total_possible_paydays <= 0:
return await ctx.send(_("The specified duration is too short for any paydays."))
# Create a list to store the report data
report_data = []
users_updated = 0
total_credits = 0
if is_global:
bankgroup = bank._config._get_base_group(bank._config.USER)
ecogroup = eco_conf._get_base_group(eco_conf.USER)
accounts: t.Dict[str, dict] = await bankgroup.all()
ecousers: t.Dict[str, dict] = await ecogroup.all()
for member in ctx.guild.members:
uid = str(member.id)
# Skip users with no bank accounts or economy data
if uid not in accounts or uid not in ecousers:
continue
# All eligible users get the same number of paydays
potential_paydays = total_possible_paydays
# Calculate amount to give (base amount * number of paydays)
amount_to_give = payday_credits * potential_paydays
# Don't exceed max balance
current_balance = accounts[uid]["balance"]
new_balance = min(current_balance + amount_to_give, max_bal)
added_amount = new_balance - current_balance
# Add to report even if no credits are added due to max balance
report_data.append(
{
"name": member.display_name,
"id": member.id,
"paydays": potential_paydays,
"amount": added_amount,
"current_balance": current_balance,
"new_balance": new_balance,
"max_hit": added_amount < amount_to_give,
}
)
# Track stats
total_credits += added_amount
users_updated += 1
# Only update the account if confirm is True
if confirm:
accounts[uid]["balance"] = new_balance
ecousers[uid]["next_payday"] = current_time
# Save changes if any and confirmation is True
if users_updated > 0 and confirm:
await bankgroup.set(accounts)
await ecogroup.set(ecousers)
else:
# Per-guild logic
conf = self.db.get_conf(ctx.guild)
bankgroup = bank._config._get_base_group(bank._config.MEMBER, str(ctx.guild.id))
ecogroup = eco_conf._get_base_group(eco_conf.MEMBER, str(ctx.guild.id))
accounts: t.Dict[str, dict] = await bankgroup.all()
ecousers: t.Dict[str, dict] = await ecogroup.all()
payday_roles: t.Dict[int, dict] = await eco_conf.all_roles()
for member in ctx.guild.members:
uid = str(member.id)
# Skip users with no bank accounts or economy data
if uid not in accounts or uid not in ecousers:
continue
# All eligible users get the same number of paydays
potential_paydays = total_possible_paydays
# Calculate per-payday amount with role bonuses
base_amount = payday_credits
for role in member.roles:
if role.id in payday_roles:
role_credits = payday_roles[role.id]["PAYDAY_CREDITS"]
if conf.stack_paydays:
base_amount += role_credits
elif role_credits > base_amount:
base_amount = role_credits
# Apply role bonus multipliers if configured
if conf.role_bonuses and any(role.id in conf.role_bonuses for role in member.roles):
highest_bonus = max(conf.role_bonuses.get(role.id, 0) for role in member.roles)
base_amount += round(base_amount * highest_bonus)
# Calculate total amount to give
amount_to_give = base_amount * potential_paydays
# Don't exceed max balance
current_balance = accounts[uid]["balance"]
new_balance = min(current_balance + amount_to_give, max_bal)
added_amount = new_balance - current_balance
# Add to report even if no credits are added due to max balance
report_data.append(
{
"name": member.display_name,
"id": member.id,
"paydays": potential_paydays,
"amount": added_amount,
"current_balance": current_balance,
"new_balance": new_balance,
"max_hit": added_amount < amount_to_give,
"payday_value": base_amount,
}
)
# Track stats
total_credits += added_amount
users_updated += 1
# Only update the account if confirm is True
if confirm:
accounts[uid]["balance"] = new_balance
ecousers[uid]["next_payday"] = current_time
# Save changes if any and confirmation is True
if users_updated > 0 and confirm:
await bankgroup.set(accounts)
await ecogroup.set(ecousers)
# Generate report
if users_updated > 0:
# Sort by amount in descending order
report_data.sort(key=lambda x: x["amount"], reverse=True)
report_lines = [
f"# Backpay Report for {humanize_timedelta(seconds=int(duration.total_seconds()))}\n",
f"Total Users: {users_updated}",
f"Total Credits: {humanize_number(total_credits)} {currency}\n",
"Details:",
]
for entry in report_data:
max_note = " (Max balance hit)" if entry.get("max_hit") else ""
per_payday = (
f" ({humanize_number(entry.get('payday_value', payday_credits))}/payday)"
if "payday_value" in entry
else ""
)
report_lines.append(
f"{entry['name']} (ID: {entry['id']}): "
f"{humanize_number(entry['amount'])} {currency} for {entry['paydays']} paydays{per_payday}{max_note}"
)
report_text = "\n".join(report_lines)
report_file = discord.File(io.StringIO(report_text), filename=f"backpay_report_{ctx.guild.id}.txt")
if confirm:
msg = _(
"Backpay complete for {duration}!\n{users} users received a total of {credits} {currency}."
).format(
duration=humanize_timedelta(seconds=int(duration.total_seconds())),
users=humanize_number(users_updated),
credits=humanize_number(total_credits),
currency=currency,
)
await ctx.send(msg, file=report_file)
else:
msg = _(
"Backpay preview for {duration}:\n{users} users would receive a total of {credits} {currency}.\n"
"Run with `confirm=True` to apply these changes."
).format(
duration=humanize_timedelta(seconds=int(duration.total_seconds())),
users=humanize_number(users_updated),
credits=humanize_number(total_credits),
currency=currency,
)
await ctx.send(msg, file=report_file)
else:
await ctx.send(_("No users were eligible for backpay in that time period."))

View file

@ -0,0 +1,71 @@
import asyncio
import logging
from redbot.core import bank, commands
from redbot.core.i18n import Translator, cog_i18n
from ..abc import MixinMeta
from ..common.generator import generate_pie_chart
log = logging.getLogger("red.vrt.extendedeconomy.admin")
_ = Translator("ExtendedEconomy", __file__)
@cog_i18n(_)
class User(MixinMeta):
@commands.command(name="idbalance")
async def id_balance(self, ctx: commands.Context, user_id: int):
"""Get the balance of a user by ID.
Helpful for checking a user's balance if they are not in the server.
"""
if await bank.is_global():
all_accounts = await bank._config.all_users()
else:
all_accounts = await bank._config.all_members(ctx.guild)
if user_id in all_accounts:
balance = all_accounts[user_id]["balance"]
await ctx.send(f"User ID: {user_id} has a balance of `{balance}`")
else:
await ctx.send(_("User ID not found."))
@commands.command(name="bankpie")
@commands.bot_has_permissions(attach_files=True)
async def bank_pie(self, ctx: commands.Context, amount: int = 10):
"""View a pie chart of the top X bank balances."""
is_global = await bank.is_global()
if is_global:
members = await bank._config.all_users()
else:
members = await bank._config.all_members(ctx.guild)
user_balances = []
for user_id, wallet in members.items():
user = ctx.guild.get_member(user_id)
if user:
user_balances.append((user.display_name, wallet["balance"]))
if not user_balances:
return await ctx.send(_("No users found."))
# Sort users by balance in descending order and take the top X amount
user_balances.sort(key=lambda x: x[1], reverse=True)
top_users = user_balances[:amount]
other_balance = sum(balance for _, balance in user_balances[amount:])
labels = [user for user, _ in top_users]
sizes = [balance for _, balance in top_users]
if other_balance > 0:
labels.append("Other")
sizes.append(other_balance)
file = await asyncio.to_thread(
generate_pie_chart,
labels,
sizes,
_("Bank Balances for {}").format(ctx.guild.name),
)
await ctx.send(file=file)

View file

@ -0,0 +1,28 @@
import orjson
from pydantic import VERSION, BaseModel
class Base(BaseModel):
def model_dump_json(self, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_dump_json(*args, **kwargs)
return super().json(*args, **kwargs)
def model_dump(self, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_dump(*args, **kwargs)
if kwargs.pop("mode", "") == "json":
return orjson.loads(super().json(*args, **kwargs))
return super().dict(*args, **kwargs)
@classmethod
def model_validate_json(cls, obj, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_validate_json(obj, *args, **kwargs)
return super().parse_raw(obj, *args, **kwargs)
@classmethod
def model_validate(cls, obj, *args, **kwargs):
if VERSION >= "2.0.1":
return super().model_validate(obj, *args, **kwargs)
return super().parse_obj(obj, *args, **kwargs)

View file

@ -0,0 +1,151 @@
import asyncio
import logging
import math
import typing as t
import discord
from redbot.core import bank, commands
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_number
from ..abc import MixinMeta
from ..views.confirm import ConfirmView
from .utils import (
confirm_msg,
confirm_msg_reaction,
ctx_to_dict,
ctx_to_id,
edit_delete_delay,
get_cached_credits_name,
)
log = logging.getLogger("red.vrt.extendedeconomy.checks")
_ = Translator("ExtendedEconomy", __file__)
class Checks(MixinMeta):
async def cost_check(self, ctx: t.Union[commands.Context, discord.Interaction]):
return await self._cost_check(ctx, ctx.author if isinstance(ctx, commands.Context) else ctx.user)
async def slash_cost_check(self, interaction: discord.Interaction):
return await self._cost_check(interaction, interaction.user)
async def _cost_check(
self,
ctx: t.Union[commands.Context, discord.Interaction],
user: t.Union[discord.Member, discord.User],
):
if isinstance(ctx, discord.Interaction):
user = ctx.guild.get_member(ctx.user.id) if ctx.guild else ctx.user
else:
user = ctx.author
command_name = ctx.command.qualified_name
is_global = await bank.is_global()
if not is_global and ctx.guild is None:
# Command run in DMs and bank is not global, cant apply cost so just return
return True
if is_global:
cost_obj = self.db.command_costs.get(command_name)
elif ctx.guild is not None:
conf = self.db.get_conf(ctx.guild)
cost_obj = conf.command_costs.get(command_name)
else:
log.error(f"Unknown condition in '{command_name}' cost check for {user.name}: {ctx_to_dict(ctx)}")
return True
if not cost_obj:
return True
# At this point we know that the command has a cost associated with it
log.debug(f"Priced command '{ctx.command.qualified_name}' invoked by {user.name} - ({type(ctx)})")
cost = await cost_obj.get_cost(self.bot, user)
if cost == 0:
cost_obj.update_usage(user.id)
return True
currency = await get_cached_credits_name(ctx.guild)
is_broke = _("You do not have enough {} to run that command! (Need {})").format(currency, humanize_number(cost))
notify = _("{}, you spent {} to run this command").format(
user.display_name, f"{humanize_number(cost)} {currency}"
)
del_delay = self.db.delete_after if self.db.delete_after else None
interaction = ctx if isinstance(ctx, discord.Interaction) else ctx.interaction
if interaction is None and cost_obj.prompt != "silent":
# For text commands only
if not await bank.can_spend(user, cost):
raise commands.UserFeedbackCheckFailure(is_broke)
message: discord.Message = None
if cost_obj.prompt != "notify":
txt = _("Do you want to spend {} {} to run this command?").format(humanize_number(cost), currency)
if cost_obj.prompt == "text":
message = await ctx.send(txt)
yes = await confirm_msg(ctx)
elif cost_obj.prompt == "reaction":
message = await ctx.send(txt)
yes = await confirm_msg_reaction(message, user, self.bot)
elif cost_obj.prompt == "button":
view = ConfirmView(user)
message = await ctx.send(txt, view=view)
await view.wait()
yes = view.value
else:
raise ValueError(f"Invalid prompt type: {cost_obj.prompt}")
if not yes:
txt = _("Not running `{}`.").format(command_name)
asyncio.create_task(edit_delete_delay(message, txt, del_delay))
raise commands.UserFeedbackCheckFailure()
if message:
asyncio.create_task(edit_delete_delay(message, notify, del_delay))
else:
asyncio.create_task(ctx.send(notify, delete_after=del_delay))
try:
await bank.withdraw_credits(user, cost)
cost_obj.update_usage(user.id)
if isinstance(ctx, commands.Context):
self.charged[ctx_to_id(ctx)] = cost
elif cost_obj.prompt != "silent":
asyncio.create_task(ctx.channel.send(notify, delete_after=del_delay))
return True
except ValueError:
log.debug(f"Failed to charge {user.name} for '{command_name}' - cost: {cost} {currency}")
if isinstance(ctx, commands.Context):
self.charged.pop(ctx_to_id(ctx), None)
if isinstance(ctx, discord.Interaction):
await interaction.response.send_message(is_broke, ephemeral=True)
return False
# asyncio.create_task(ctx.send(is_broke, delete_after=del_delay, ephemeral=True))
raise commands.UserFeedbackCheckFailure()
async def transfer_tax_check(self, ctx: commands.Context):
if ctx.command.qualified_name != "bank transfer":
return True
is_global = await bank.is_global()
conf = self.db if is_global else self.db.get_conf(ctx.guild)
tax = conf.transfer_tax
if tax == 0:
log.debug("No transfer tax set")
return True
if not is_global and getattr(conf, "transfer_tax_whitelist", None):
whitelist = conf.transfer_tax_whitelist
if any(r.id in whitelist for r in ctx.author.roles):
# log.debug(f"{ctx.author} is in the transfer tax whitelist")
return True
# Args: EconomyCog, ctx, to, amount
amount: int = ctx.args[-1]
deduction = math.ceil(amount * tax)
asyncio.create_task(bank.withdraw_credits(ctx.author, deduction))
# Modify the amount to be transferred
ctx.args[-1] = amount - deduction
currency = await get_cached_credits_name(ctx.guild)
txt = _("{}% transfer tax applied, {} deducted from transfer").format(
f"{round(tax * 100)}", f"{humanize_number(deduction)} {currency}"
)
await ctx.send(txt)
return True

View file

@ -0,0 +1,27 @@
from io import BytesIO
import discord
import plotly.express as px
def generate_pie_chart(labels: list, sizes: list, title: str) -> discord.File:
fig = px.pie(
names=labels,
values=sizes,
title=title,
hole=0.3,
)
marker = dict(line=dict(color="#ffffff", width=2))
fig.update_traces(textposition="inside", textinfo="percent+label", marker=marker)
fig.update_layout(
font_color="rgb(255,255,255)",
font_size=20,
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
)
buffer = BytesIO()
fig.write_image(buffer, format="webp", scale=2)
buffer.seek(0)
return discord.File(buffer, filename="pie.webp")

View file

@ -0,0 +1,247 @@
import logging
import typing as t
from contextlib import suppress
from datetime import datetime
import discord
from discord.ext import tasks
from redbot.core import bank, commands, errors
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_number
from ..abc import MixinMeta
from ..common.utils import ctx_to_id, has_cost_check
log = logging.getLogger("red.vrt.extendedeconomy.listeners")
_ = Translator("ExtendedEconomy", __file__)
class Listeners(MixinMeta):
def __init__(self):
super().__init__()
self.payloads: t.Dict[int, t.List[discord.Embed]] = {}
@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error: Exception, *args, **kwargs):
if not ctx.command:
return
# log.debug(f"Command error in '{ctx.command.qualified_name}' for {ctx.author.name}:({type(error)}) {error}")
runtime_id = ctx_to_id(ctx)
if runtime_id not in self.charged:
# User wasn't charged for this command
return
# We need to refund the user if the command failed
amount = self.charged.pop(runtime_id)
try:
await bank.deposit_credits(ctx.author, amount)
except errors.BalanceTooHigh as e:
await bank.set_balance(ctx.author, e.max_balance)
cmd = ctx.command.qualified_name
log.info(f"{ctx.author.name} has been refunded {amount} since the '{cmd}' command failed.")
txt = _("You have been refunded since this command failed.")
await ctx.send(txt)
@commands.Cog.listener()
async def on_command_completion(self, ctx: commands.Context):
if not ctx.command:
return
self.charged.pop(ctx_to_id(ctx), None)
@commands.Cog.listener()
async def on_cog_add(self, cog: commands.Cog):
if cog.qualified_name == "BankEvents":
log.debug("BankEvents cog loaded 'after' ExtendedEconomy, overriding payday command.")
payday: commands.Command = self.bot.get_command("payday")
if payday:
self.payday_callback = payday.callback
payday.callback = self._extendedeconomy_payday_override.callback
if cog.qualified_name in self.checks:
return
for cmd in cog.walk_app_commands():
if isinstance(cmd, discord.app_commands.Group):
continue
if has_cost_check(cmd):
continue
cmd.add_check(self.cost_check)
self.checks.add(cog.qualified_name)
@commands.Cog.listener()
async def on_cog_remove(self, cog: commands.Cog):
self.checks.discard(cog.qualified_name)
async def log_event(self, event: str, payload: t.NamedTuple):
is_global = await bank.is_global()
guild = (
payload.member.guild
if event == "payday_claim" and isinstance(payload.member, discord.Member)
else getattr(payload, "guild", None)
)
if not is_global and not guild:
log.error(f"Guild is None for non-global bank event: {event}\n{payload}")
return
logs = self.db.logs if is_global else self.db.get_conf(guild).logs
channel_id = getattr(logs, event, 0) or logs.default_log_channel
if not channel_id:
return
channel: discord.TextChannel = self.bot.get_channel(channel_id)
if not channel:
return
event_map = {
"set_balance": _("Set Balance"),
"transfer_credits": _("Transfer Credits"),
"bank_wipe": _("Bank Wipe"),
"prune": _("Prune Accounts"),
"set_global": _("Set Global"),
"payday_claim": _("Payday Claim"),
}
currency = await bank.get_currency_name(guild)
title = _("Bank Event: {}").format(event_map[event])
color = await self.bot.get_embed_color(channel)
embed = discord.Embed(title=title, color=color, timestamp=datetime.now())
if event == "set_balance":
embed.add_field(name=_("Recipient"), value=f"{payload.recipient.mention}\n`{payload.recipient.id}`")
embed.add_field(name=_("Old Balance"), value=humanize_number(payload.recipient_old_balance))
embed.add_field(name=_("New Balance"), value=humanize_number(payload.recipient_new_balance))
if guild and is_global:
embed.add_field(name=_("Guild"), value=guild.name)
elif event == "transfer_credits":
embed.add_field(name=_("Sender"), value=f"{payload.sender.mention}\n`{payload.sender.id}`")
embed.add_field(name=_("Recipient"), value=f"{payload.recipient.mention}\n`{payload.recipient.id}`")
embed.add_field(name=_("Transfer Amount"), value=f"{humanize_number(payload.transfer_amount)} {currency}")
if guild and is_global:
embed.add_field(name=_("Guild"), value=guild.name)
elif event == "prune":
if payload.user_id:
embed.add_field(name=_("User ID"), value=payload.user_id)
else:
embed.add_field(name=_("Pruned Users"), value=humanize_number(len(payload.pruned_users)))
if guild and is_global:
embed.add_field(name=_("Guild"), value=guild.name)
elif event == "payday_claim":
embed.add_field(name=_("Recipient"), value=f"{payload.member.mention}\n`{payload.member.id}`")
embed.add_field(name=_("Amount"), value=f"{humanize_number(payload.amount)} {currency}")
embed.add_field(name=_("Old Balance"), value=humanize_number(payload.old_balance))
embed.add_field(name=_("New Balance"), value=humanize_number(payload.new_balance))
if is_global:
embed.add_field(name=_("Guild"), value=guild.name if guild else _("Unknown"))
else:
embed.add_field(name=_("Channel"), value=payload.channel.mention)
if message := getattr(payload, "message", None):
embed.add_field(name=_("Message"), value=f"[Jump]({message.jump_url})")
else:
log.error(f"Unknown event type: {event}")
return
if channel.id in self.payloads:
self.payloads[channel.id].append(embed)
else:
self.payloads[channel.id] = [embed]
@commands.Cog.listener()
async def on_red_bank_set_balance(self, payload: t.NamedTuple):
"""Payload attributes:
- recipient: Union[discord.Member, discord.User]
- guild: Union[discord.Guild, None]
- recipient_old_balance: int
- recipient_new_balance: int
"""
await self.log_event("set_balance", payload)
@commands.Cog.listener()
async def on_red_bank_transfer_credits(self, payload: t.NamedTuple):
"""Payload attributes:
- sender: Union[discord.Member, discord.User]
- recipient: Union[discord.Member, discord.User]
- guild: Union[discord.Guild, None]
- transfer_amount: int
- sender_new_balance: int
- recipient_new_balance: int
"""
await self.log_event("transfer_credits", payload)
@commands.Cog.listener()
async def on_red_bank_wipe(self, scope: t.Optional[int] = None):
"""scope: int (-1 for global, None for all members, guild_id for server bank)"""
if scope == -1 or scope is None:
log_channel_id = self.db.logs.bank_wipe or self.db.logs.default_log_channel
txt = _("Global bank has been wiped!")
elif scope is None:
log_channel_id = self.db.logs.bank_wipe or self.db.logs.default_log_channel
txt = _("All bank accounts for all guilds have been wiped!")
else:
# Scope is a guild ID
guild: discord.Guild = self.bot.get_guild(scope)
if not guild:
return
conf = self.db.get_conf(guild)
log_channel_id = conf.logs.bank_wipe or conf.logs.default_log_channel
txt = _("Bank accounts have been wiped!")
if not log_channel_id:
return
log_channel: discord.TextChannel = self.bot.get_channel(log_channel_id)
if not log_channel:
return
embed = discord.Embed(
title=_("Bank Wipe"),
description=txt,
color=await self.bot.get_embed_color(log_channel),
)
with suppress(discord.HTTPException, discord.Forbidden):
await log_channel.send(embed=embed)
@commands.Cog.listener()
async def on_red_bank_prune(self, payload: t.NamedTuple):
"""Payload attributes:
- guild: Union[discord.Guild, None]
- user_id: Union[int, None]
- scope: int (1 for global, 2 for server, 3 for user)
- pruned_users: list[int(user_id)] or dict[int(guild_id), list[int(user_id)]]
"""
await self.log_event("prune", payload)
@commands.Cog.listener()
async def on_red_bank_set_global(self, is_global: bool):
"""is_global: True if global bank, False if server bank"""
txt = _("Bank has been set to Global!") if is_global else _("Bank has been set to per-server!")
log_channel_id = self.db.logs.set_global or self.db.logs.default_log_channel
if not log_channel_id:
return
log_channel: discord.TextChannel = self.bot.get_channel(log_channel_id)
if not log_channel:
return
embed = discord.Embed(
title=_("Set Global Bank"),
description=txt,
color=await self.bot.get_embed_color(log_channel),
)
# with suppress(discord.HTTPException, discord.Forbidden):
await log_channel.send(embed=embed)
@commands.Cog.listener()
async def on_red_economy_payday_claim(self, payload: t.NamedTuple):
"""Payload attributes:
- member: discord.Member
- channel: Union[discord.TextChannel, discord.Thread, discord.ForumChannel]
- amount: int
- old_balance: int
- new_balance: int
"""
await self.log_event("payday_claim", payload)
@tasks.loop(seconds=4)
async def send_payloads(self):
"""Send embeds in chunks to avoid rate limits"""
if not self.payloads:
return
tmp = self.payloads.copy()
self.payloads.clear()
for channel_id, embeds in tmp.items():
channel = self.bot.get_channel(channel_id)
if not channel:
continue
# Group the embeds into 5 per message
for i in range(0, len(embeds), 5):
chunk = embeds[i : i + 5]
with suppress(discord.HTTPException, discord.Forbidden):
await channel.send(embeds=chunk)

View file

@ -0,0 +1,145 @@
import json
import math
import typing as t
from datetime import datetime
import discord
from redbot.core import bank
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from . import Base
_ = Translator("ExtendedEconomy", __file__)
class PaydayClaimInformation(t.NamedTuple):
member: discord.Member
channel: t.Union[discord.TextChannel, discord.Thread, discord.ForumChannel]
message: discord.Message
amount: int
old_balance: int
new_balance: int
def to_dict(self) -> dict:
return {
"member": self.member.id,
"channel": self.channel.id,
"message": self.message.id,
"amount": self.amount,
"old_balance": self.old_balance,
"new_balance": self.new_balance,
}
def to_json(self) -> str:
return json.dumps(self.to_dict())
class CommandCost(Base):
"""
- cost: the base cost of the command
- duration: the time period in seconds in which the command usage is tracked and used to calculate the cost
- level: the minimum permission level of the command in which the cost is applied
- admin: admins and above can use the command for free
- mod: mods and above can use the command for free
- all: everyone must pay the cost to use the command
- user: all users must pay the cost to use the command unless they are mod or admin
- global: the cost is applied to all users globally
- prompt: how will the user be prompted to confirm the cost (Slash commands will always use silent mode)
- text: the bot will send a text message asking the user to confirm the cost with yes or no
- reaction: the bot will send a message with emoji reactions to confirm the cost
- button: the bot will send a message with buttons to confirm the cost
- silent: the bot will not prompt the user to confirm the cost
- notify: the bot will simply notify the user of the cost without asking for confirmation
- modifier: the type of cost modifier
- static: the cost is a fixed amount
- percent: the cost is a percentage of the user's balance on top of the base cost
- exponential: the cost increases exponentially based on how frequenty the command is used
- Ex: cost gets doubled every time the command is used within a set time period
- linear: the cost increases linearly based on how frequently the command is used
- Ex: cost gets increased by a fixed amount every time the command is used within a set time period
- value: the value of the cost modifier depends on the modifier type
- static: this will be 0 and does nothing
- percent: value will be the percentage of the user's balance to add to the base cost
- exponential: value will be the base cost multiplier
- linear: value will multiplied by the number of uses in the last hour to get the cost increase
- uses: a list of lists containing the user ID and the timestamp of the command usage
"""
cost: int
duration: int
level: t.Literal["admin", "mod", "all", "user", "global"]
prompt: t.Literal["text", "reaction", "button", "silent", "notify"]
modifier: t.Literal["static", "percent", "exponential", "linear"]
value: float
uses: t.List[t.List[t.Union[int, float]]] = []
async def get_cost(self, bot: Red, user: t.Union[discord.Member, discord.User]) -> int:
if self.level == "global" and user.id in bot.owner_ids:
return 0
elif self.level != "global":
free = [
await bot.is_admin(user) and self.level in ["admin", "mod"],
await bot.is_mod(user) and self.level == "mod",
]
if any(free):
return 0
if self.modifier == "static":
return self.cost
if self.modifier == "percent":
bal = await bank.get_balance(user)
return math.ceil(self.cost + (bal * self.value))
min_time = datetime.now().timestamp() - self.duration
uses_in_duration = len([u for u in self.uses if u[0] == user.id and u[1] > min_time])
if self.modifier == "exponential":
return math.ceil(self.cost + self.value * (2**uses_in_duration))
if self.modifier == "linear":
return math.ceil(self.cost + (self.value * uses_in_duration))
raise ValueError(f"Invalid cost modifier: {self.modifier}")
def update_usage(self, user_id: int):
self.uses.append([user_id, datetime.now().timestamp()])
self.uses = [u for u in self.uses if u[1] > datetime.now().timestamp() - self.duration]
class LogChannels(Base):
default_log_channel: int = 0
# Event log channel overrides
set_balance: int = 0
transfer_credits: int = 0
bank_wipe: int = 0
prune: int = 0
set_global: int = 0
payday_claim: int = 0
auto_claim: int = 0
class GuildSettings(Base):
logs: LogChannels = LogChannels()
command_costs: t.Dict[str, CommandCost] = {}
transfer_tax: float = 0.0
transfer_tax_whitelist: t.List[int] = [] # Role IDs that are exempt from transfer tax
# Unique to GuildSettings (local bank only)
stack_paydays: bool = False
auto_claim_roles: t.List[int] = [] # Role IDs that auto claim paydays
role_bonuses: t.Dict[int, float] = {} # Role ID: bonus multiplier
class DB(Base):
configs: t.Dict[int, GuildSettings] = {}
delete_after: t.Union[int, None] = None
logs: LogChannels = LogChannels()
command_costs: t.Dict[str, CommandCost] = {}
transfer_tax: float = 0.0
auto_payday_claim: bool = False # If True, guilds that set auto_claim_roles will auto claim paydays
# Allow prices per guild when global bank is enabled
# per_guild_override: bool = False
def get_conf(self, guild: discord.Guild | int) -> GuildSettings:
gid = guild if isinstance(guild, int) else guild.id
return self.configs.setdefault(gid, GuildSettings())

View file

@ -0,0 +1,30 @@
from redbot.core import commands
from redbot.core.i18n import Translator
_ = Translator("ExtendedEconomy", __file__)
class SetParser:
def __init__(self, argument):
allowed = ("+", "-")
try:
self.sum = int(argument)
except ValueError:
raise commands.BadArgument(
_("Invalid value, the argument must be an integer, optionally preceded with a `+` or `-` sign.")
)
if argument and argument[0] in allowed:
if self.sum < 0:
self.operation = "withdraw"
elif self.sum > 0:
self.operation = "deposit"
else:
raise commands.BadArgument(
_(
"Invalid value, the amount of currency to increase or decrease"
" must be an integer different from zero."
)
)
self.sum = abs(self.sum)
else:
self.operation = "set"

Some files were not shown because too many files have changed in this diff Show more