From 651c9372f2dc724b3ec1913ef8567e3271ad25b9 Mon Sep 17 00:00:00 2001 From: Valerie Date: Fri, 23 May 2025 02:30:00 -0400 Subject: [PATCH] Add multiple new cogs for banking, economy tracking, and referrals, including setup, commands, and localization files. --- bankbackup/README.md | 16 + bankbackup/__init__.py | 15 + bankbackup/base.py | 86 ++ bankbackup/info.json | 28 + bankdecay/README.md | 62 ++ bankdecay/__init__.py | 11 + bankdecay/abc.py | 27 + bankdecay/commands/admin.py | 369 ++++++++ bankdecay/commands/locales/de-DE.po | 307 ++++++ bankdecay/commands/locales/es-ES.po | 350 +++++++ bankdecay/commands/locales/fr-FR.po | 307 ++++++ bankdecay/commands/locales/hr-HR.po | 307 ++++++ bankdecay/commands/locales/ko-KR.po | 307 ++++++ bankdecay/commands/locales/messages.pot | 322 +++++++ bankdecay/commands/locales/pt-PT.po | 307 ++++++ bankdecay/commands/locales/ru-RU.po | 307 ++++++ bankdecay/commands/locales/tr-TR.po | 307 ++++++ bankdecay/common/__init__.py | 28 + bankdecay/common/confirm_view.py | 42 + bankdecay/common/listeners.py | 84 ++ bankdecay/common/models.py | 47 + bankdecay/common/scheduler.py | 10 + bankdecay/info.json | 16 + bankdecay/locales/de-DE.po | 47 + bankdecay/locales/es-ES.po | 53 ++ bankdecay/locales/fr-FR.po | 47 + bankdecay/locales/hr-HR.po | 47 + bankdecay/locales/ko-KR.po | 47 + bankdecay/locales/messages.pot | 43 + bankdecay/locales/pt-PT.po | 47 + bankdecay/locales/ru-RU.po | 47 + bankdecay/locales/tr-TR.po | 47 + bankdecay/main.py | 214 +++++ bankevents/README.md | 6 + bankevents/__init__.py | 10 + bankevents/abc.py | 14 + bankevents/examples.txt | 67 ++ bankevents/info.json | 16 + bankevents/main.py | 140 +++ bankevents/overrides/__init__.py | 0 bankevents/overrides/bank.py | 247 +++++ bankevents/overrides/economy.py | 162 ++++ economytrack/README.md | 75 ++ economytrack/__init__.py | 15 + economytrack/abc.py | 24 + economytrack/commands.py | 354 +++++++ economytrack/economytrack.py | 275 ++++++ economytrack/graph.py | 31 + economytrack/info.json | 32 + economytrickle/__init__.py | 12 + economytrickle/economytrickle.py | 443 +++++++++ economytrickle/info.json | 20 + economytrickle/locales/ar-SA.po | 120 +++ economytrickle/locales/bg-BG.po | 120 +++ economytrickle/locales/cs-CZ.po | 120 +++ economytrickle/locales/da-DK.po | 120 +++ economytrickle/locales/de-DE.po | 120 +++ economytrickle/locales/es-ES.po | 120 +++ economytrickle/locales/fi-FI.po | 120 +++ economytrickle/locales/fr-FR.po | 120 +++ economytrickle/locales/hi-IN.po | 120 +++ economytrickle/locales/hr-HR.po | 120 +++ economytrickle/locales/hu-HU.po | 120 +++ economytrickle/locales/id-ID.po | 120 +++ economytrickle/locales/it-IT.po | 120 +++ economytrickle/locales/ja-JP.po | 120 +++ economytrickle/locales/ko-KR.po | 120 +++ economytrickle/locales/messages.pot | 127 +++ economytrickle/locales/nb-NO.po | 120 +++ economytrickle/locales/nl-NL.po | 120 +++ economytrickle/locales/pl-PL.po | 120 +++ economytrickle/locales/pt-BR.po | 120 +++ economytrickle/locales/pt-PT.po | 120 +++ economytrickle/locales/ru-RU.po | 120 +++ economytrickle/locales/sk-SK.po | 120 +++ economytrickle/locales/sl-SI.po | 120 +++ economytrickle/locales/sv-SE.po | 120 +++ economytrickle/locales/tr-TR.po | 120 +++ economytrickle/locales/uk-UA.po | 120 +++ economytrickle/locales/vi-VN.po | 120 +++ economytrickle/locales/zh-CN.po | 120 +++ economytrickle/locales/zh-TW.po | 120 +++ evolution/__init__.py | 21 + evolution/bank.py | 774 ++++++++++++++++ evolution/evolution.py | 746 +++++++++++++++ evolution/info.json | 16 + evolution/tasks.py | 117 +++ evolution/utils.py | 211 +++++ extendedeconomy/README.md | 94 ++ extendedeconomy/__init__.py | 11 + extendedeconomy/abc.py | 46 + extendedeconomy/commands/__init__.py | 7 + extendedeconomy/commands/admin.py | 783 ++++++++++++++++ extendedeconomy/commands/user.py | 71 ++ extendedeconomy/common/__init__.py | 28 + extendedeconomy/common/checks.py | 151 +++ extendedeconomy/common/generator.py | 27 + extendedeconomy/common/listeners.py | 247 +++++ extendedeconomy/common/models.py | 145 +++ extendedeconomy/common/parser.py | 30 + extendedeconomy/common/tasks.py | 130 +++ extendedeconomy/common/utils.py | 189 ++++ extendedeconomy/info.json | 27 + extendedeconomy/main.py | 121 +++ extendedeconomy/overrides/payday.py | 148 +++ extendedeconomy/views/__init__.py | 0 extendedeconomy/views/confirm.py | 37 + extendedeconomy/views/cost_menu.py | 413 +++++++++ lottery/__init__.py | 11 + lottery/checks.py | 8 + lottery/info.json | 14 + lottery/lottery.py | 264 ++++++ payday/__init__.py | 12 + payday/info.json | 20 + payday/locales/ar-SA.po | 298 ++++++ payday/locales/bg-BG.po | 298 ++++++ payday/locales/cs-CZ.po | 298 ++++++ payday/locales/da-DK.po | 298 ++++++ payday/locales/de-DE.po | 298 ++++++ payday/locales/es-ES.po | 298 ++++++ payday/locales/fi-FI.po | 298 ++++++ payday/locales/fr-FR.po | 298 ++++++ payday/locales/hi-IN.po | 298 ++++++ payday/locales/hr-HR.po | 298 ++++++ payday/locales/hu-HU.po | 298 ++++++ payday/locales/id-ID.po | 298 ++++++ payday/locales/it-IT.po | 298 ++++++ payday/locales/ja-JP.po | 298 ++++++ payday/locales/ko-KR.po | 298 ++++++ payday/locales/messages.pot | 330 +++++++ payday/locales/nb-NO.po | 298 ++++++ payday/locales/nl-NL.po | 298 ++++++ payday/locales/pl-PL.po | 298 ++++++ payday/locales/pt-BR.po | 298 ++++++ payday/locales/pt-PT.po | 298 ++++++ payday/locales/ru-RU.po | 298 ++++++ payday/locales/sk-SK.po | 298 ++++++ payday/locales/sl-SI.po | 298 ++++++ payday/locales/sv-SE.po | 298 ++++++ payday/locales/tr-TR.po | 298 ++++++ payday/locales/uk-UA.po | 298 ++++++ payday/locales/vi-VN.po | 298 ++++++ payday/locales/zh-CN.po | 298 ++++++ payday/locales/zh-TW.po | 298 ++++++ payday/payday.py | 874 ++++++++++++++++++ referrals/__init__.py | 16 + referrals/abc.py | 20 + referrals/build.py | 22 + referrals/commands/__init__.py | 7 + referrals/commands/admin.py | 277 ++++++ referrals/commands/user.py | 335 +++++++ referrals/common/__init__.py | 0 referrals/common/checks.py | 24 + referrals/common/utils.py | 62 ++ referrals/db/__init__.py | 15 + .../referrals_2025_01_03t22_29_51_871945.py | 338 +++++++ referrals/db/piccolo_app.py | 11 + referrals/db/piccolo_conf.py | 8 + referrals/db/tables.py | 41 + referrals/db/utils.py | 12 + referrals/engine/__init__.py | 11 + referrals/engine/common.py | 93 ++ referrals/engine/engine.py | 169 ++++ referrals/engine/errors.py | 6 + referrals/info.json | 16 + referrals/main.py | 55 ++ referrals/views/__init__.py | 0 referrals/views/dynamic_menu.py | 287 ++++++ rps/__init__.py | 25 + rps/duelview.py | 65 ++ rps/info.json | 23 + rps/locales/ar-SA.po | 153 +++ rps/locales/bg-BG.po | 153 +++ rps/locales/cs-CZ.po | 153 +++ rps/locales/da-DK.po | 153 +++ rps/locales/de-DE.po | 153 +++ rps/locales/es-ES.po | 179 ++++ rps/locales/fi-FI.po | 153 +++ rps/locales/fr-FR.po | 153 +++ rps/locales/hi-IN.po | 153 +++ rps/locales/hr-HR.po | 153 +++ rps/locales/hu-HU.po | 153 +++ rps/locales/id-ID.po | 153 +++ rps/locales/it-IT.po | 153 +++ rps/locales/ja-JP.po | 153 +++ rps/locales/ko-KR.po | 153 +++ rps/locales/messages.pot | 172 ++++ rps/locales/nb-NO.po | 153 +++ rps/locales/nl-NL.po | 153 +++ rps/locales/pl-PL.po | 153 +++ rps/locales/pt-BR.po | 153 +++ rps/locales/pt-PT.po | 153 +++ rps/locales/ru-RU.po | 153 +++ rps/locales/sk-SK.po | 153 +++ rps/locales/sl-SI.po | 153 +++ rps/locales/sv-SE.po | 153 +++ rps/locales/tr-TR.po | 153 +++ rps/locales/uk-UA.po | 153 +++ rps/locales/vi-VN.po | 153 +++ rps/locales/zh-CN.po | 153 +++ rps/locales/zh-TW.po | 153 +++ rps/playerview.py | 136 +++ rps/rps.py | 294 ++++++ rps/rpslsview.py | 179 ++++ rps/rpsview.py | 119 +++ rps/vars.py | 104 +++ slots/__init__.py | 16 + slots/data/fruits.yaml | 50 + slots/data/sports.yaml | 45 + slots/errors.py | 62 ++ slots/info.json | 20 + slots/slots.py | 413 +++++++++ 212 files changed, 32627 insertions(+) create mode 100644 bankbackup/README.md create mode 100644 bankbackup/__init__.py create mode 100644 bankbackup/base.py create mode 100644 bankbackup/info.json create mode 100644 bankdecay/README.md create mode 100644 bankdecay/__init__.py create mode 100644 bankdecay/abc.py create mode 100644 bankdecay/commands/admin.py create mode 100644 bankdecay/commands/locales/de-DE.po create mode 100644 bankdecay/commands/locales/es-ES.po create mode 100644 bankdecay/commands/locales/fr-FR.po create mode 100644 bankdecay/commands/locales/hr-HR.po create mode 100644 bankdecay/commands/locales/ko-KR.po create mode 100644 bankdecay/commands/locales/messages.pot create mode 100644 bankdecay/commands/locales/pt-PT.po create mode 100644 bankdecay/commands/locales/ru-RU.po create mode 100644 bankdecay/commands/locales/tr-TR.po create mode 100644 bankdecay/common/__init__.py create mode 100644 bankdecay/common/confirm_view.py create mode 100644 bankdecay/common/listeners.py create mode 100644 bankdecay/common/models.py create mode 100644 bankdecay/common/scheduler.py create mode 100644 bankdecay/info.json create mode 100644 bankdecay/locales/de-DE.po create mode 100644 bankdecay/locales/es-ES.po create mode 100644 bankdecay/locales/fr-FR.po create mode 100644 bankdecay/locales/hr-HR.po create mode 100644 bankdecay/locales/ko-KR.po create mode 100644 bankdecay/locales/messages.pot create mode 100644 bankdecay/locales/pt-PT.po create mode 100644 bankdecay/locales/ru-RU.po create mode 100644 bankdecay/locales/tr-TR.po create mode 100644 bankdecay/main.py create mode 100644 bankevents/README.md create mode 100644 bankevents/__init__.py create mode 100644 bankevents/abc.py create mode 100644 bankevents/examples.txt create mode 100644 bankevents/info.json create mode 100644 bankevents/main.py create mode 100644 bankevents/overrides/__init__.py create mode 100644 bankevents/overrides/bank.py create mode 100644 bankevents/overrides/economy.py create mode 100644 economytrack/README.md create mode 100644 economytrack/__init__.py create mode 100644 economytrack/abc.py create mode 100644 economytrack/commands.py create mode 100644 economytrack/economytrack.py create mode 100644 economytrack/graph.py create mode 100644 economytrack/info.json create mode 100644 economytrickle/__init__.py create mode 100644 economytrickle/economytrickle.py create mode 100644 economytrickle/info.json create mode 100644 economytrickle/locales/ar-SA.po create mode 100644 economytrickle/locales/bg-BG.po create mode 100644 economytrickle/locales/cs-CZ.po create mode 100644 economytrickle/locales/da-DK.po create mode 100644 economytrickle/locales/de-DE.po create mode 100644 economytrickle/locales/es-ES.po create mode 100644 economytrickle/locales/fi-FI.po create mode 100644 economytrickle/locales/fr-FR.po create mode 100644 economytrickle/locales/hi-IN.po create mode 100644 economytrickle/locales/hr-HR.po create mode 100644 economytrickle/locales/hu-HU.po create mode 100644 economytrickle/locales/id-ID.po create mode 100644 economytrickle/locales/it-IT.po create mode 100644 economytrickle/locales/ja-JP.po create mode 100644 economytrickle/locales/ko-KR.po create mode 100644 economytrickle/locales/messages.pot create mode 100644 economytrickle/locales/nb-NO.po create mode 100644 economytrickle/locales/nl-NL.po create mode 100644 economytrickle/locales/pl-PL.po create mode 100644 economytrickle/locales/pt-BR.po create mode 100644 economytrickle/locales/pt-PT.po create mode 100644 economytrickle/locales/ru-RU.po create mode 100644 economytrickle/locales/sk-SK.po create mode 100644 economytrickle/locales/sl-SI.po create mode 100644 economytrickle/locales/sv-SE.po create mode 100644 economytrickle/locales/tr-TR.po create mode 100644 economytrickle/locales/uk-UA.po create mode 100644 economytrickle/locales/vi-VN.po create mode 100644 economytrickle/locales/zh-CN.po create mode 100644 economytrickle/locales/zh-TW.po create mode 100644 evolution/__init__.py create mode 100644 evolution/bank.py create mode 100644 evolution/evolution.py create mode 100644 evolution/info.json create mode 100644 evolution/tasks.py create mode 100644 evolution/utils.py create mode 100644 extendedeconomy/README.md create mode 100644 extendedeconomy/__init__.py create mode 100644 extendedeconomy/abc.py create mode 100644 extendedeconomy/commands/__init__.py create mode 100644 extendedeconomy/commands/admin.py create mode 100644 extendedeconomy/commands/user.py create mode 100644 extendedeconomy/common/__init__.py create mode 100644 extendedeconomy/common/checks.py create mode 100644 extendedeconomy/common/generator.py create mode 100644 extendedeconomy/common/listeners.py create mode 100644 extendedeconomy/common/models.py create mode 100644 extendedeconomy/common/parser.py create mode 100644 extendedeconomy/common/tasks.py create mode 100644 extendedeconomy/common/utils.py create mode 100644 extendedeconomy/info.json create mode 100644 extendedeconomy/main.py create mode 100644 extendedeconomy/overrides/payday.py create mode 100644 extendedeconomy/views/__init__.py create mode 100644 extendedeconomy/views/confirm.py create mode 100644 extendedeconomy/views/cost_menu.py create mode 100644 lottery/__init__.py create mode 100644 lottery/checks.py create mode 100644 lottery/info.json create mode 100644 lottery/lottery.py create mode 100644 payday/__init__.py create mode 100644 payday/info.json create mode 100644 payday/locales/ar-SA.po create mode 100644 payday/locales/bg-BG.po create mode 100644 payday/locales/cs-CZ.po create mode 100644 payday/locales/da-DK.po create mode 100644 payday/locales/de-DE.po create mode 100644 payday/locales/es-ES.po create mode 100644 payday/locales/fi-FI.po create mode 100644 payday/locales/fr-FR.po create mode 100644 payday/locales/hi-IN.po create mode 100644 payday/locales/hr-HR.po create mode 100644 payday/locales/hu-HU.po create mode 100644 payday/locales/id-ID.po create mode 100644 payday/locales/it-IT.po create mode 100644 payday/locales/ja-JP.po create mode 100644 payday/locales/ko-KR.po create mode 100644 payday/locales/messages.pot create mode 100644 payday/locales/nb-NO.po create mode 100644 payday/locales/nl-NL.po create mode 100644 payday/locales/pl-PL.po create mode 100644 payday/locales/pt-BR.po create mode 100644 payday/locales/pt-PT.po create mode 100644 payday/locales/ru-RU.po create mode 100644 payday/locales/sk-SK.po create mode 100644 payday/locales/sl-SI.po create mode 100644 payday/locales/sv-SE.po create mode 100644 payday/locales/tr-TR.po create mode 100644 payday/locales/uk-UA.po create mode 100644 payday/locales/vi-VN.po create mode 100644 payday/locales/zh-CN.po create mode 100644 payday/locales/zh-TW.po create mode 100644 payday/payday.py create mode 100644 referrals/__init__.py create mode 100644 referrals/abc.py create mode 100644 referrals/build.py create mode 100644 referrals/commands/__init__.py create mode 100644 referrals/commands/admin.py create mode 100644 referrals/commands/user.py create mode 100644 referrals/common/__init__.py create mode 100644 referrals/common/checks.py create mode 100644 referrals/common/utils.py create mode 100644 referrals/db/__init__.py create mode 100644 referrals/db/migrations/referrals_2025_01_03t22_29_51_871945.py create mode 100644 referrals/db/piccolo_app.py create mode 100644 referrals/db/piccolo_conf.py create mode 100644 referrals/db/tables.py create mode 100644 referrals/db/utils.py create mode 100644 referrals/engine/__init__.py create mode 100644 referrals/engine/common.py create mode 100644 referrals/engine/engine.py create mode 100644 referrals/engine/errors.py create mode 100644 referrals/info.json create mode 100644 referrals/main.py create mode 100644 referrals/views/__init__.py create mode 100644 referrals/views/dynamic_menu.py create mode 100644 rps/__init__.py create mode 100644 rps/duelview.py create mode 100644 rps/info.json create mode 100644 rps/locales/ar-SA.po create mode 100644 rps/locales/bg-BG.po create mode 100644 rps/locales/cs-CZ.po create mode 100644 rps/locales/da-DK.po create mode 100644 rps/locales/de-DE.po create mode 100644 rps/locales/es-ES.po create mode 100644 rps/locales/fi-FI.po create mode 100644 rps/locales/fr-FR.po create mode 100644 rps/locales/hi-IN.po create mode 100644 rps/locales/hr-HR.po create mode 100644 rps/locales/hu-HU.po create mode 100644 rps/locales/id-ID.po create mode 100644 rps/locales/it-IT.po create mode 100644 rps/locales/ja-JP.po create mode 100644 rps/locales/ko-KR.po create mode 100644 rps/locales/messages.pot create mode 100644 rps/locales/nb-NO.po create mode 100644 rps/locales/nl-NL.po create mode 100644 rps/locales/pl-PL.po create mode 100644 rps/locales/pt-BR.po create mode 100644 rps/locales/pt-PT.po create mode 100644 rps/locales/ru-RU.po create mode 100644 rps/locales/sk-SK.po create mode 100644 rps/locales/sl-SI.po create mode 100644 rps/locales/sv-SE.po create mode 100644 rps/locales/tr-TR.po create mode 100644 rps/locales/uk-UA.po create mode 100644 rps/locales/vi-VN.po create mode 100644 rps/locales/zh-CN.po create mode 100644 rps/locales/zh-TW.po create mode 100644 rps/playerview.py create mode 100644 rps/rps.py create mode 100644 rps/rpslsview.py create mode 100644 rps/rpsview.py create mode 100644 rps/vars.py create mode 100644 slots/__init__.py create mode 100644 slots/data/fruits.yaml create mode 100644 slots/data/sports.yaml create mode 100644 slots/errors.py create mode 100644 slots/info.json create mode 100644 slots/slots.py diff --git a/bankbackup/README.md b/bankbackup/README.md new file mode 100644 index 0000000..104977f --- /dev/null +++ b/bankbackup/README.md @@ -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 ` + - Restricted to: `GUILD_OWNER` + +Restore your server's bank balances.
Attach your backup file with this command.

**Arguments**
- ``: Whether you want to `add` or `set` balances from the backup. + diff --git a/bankbackup/__init__.py b/bankbackup/__init__.py new file mode 100644 index 0000000..4c8daa4 --- /dev/null +++ b/bankbackup/__init__.py @@ -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) diff --git a/bankbackup/base.py b/bankbackup/base.py new file mode 100644 index 0000000..6f19fd9 --- /dev/null +++ b/bankbackup/base.py @@ -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** + - ``: 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!") diff --git a/bankbackup/info.json b/bankbackup/info.json new file mode 100644 index 0000000..e19291b --- /dev/null +++ b/bankbackup/info.json @@ -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" +} diff --git a/bankdecay/README.md b/bankdecay/README.md new file mode 100644 index 0000000..aac1d0c --- /dev/null +++ b/bankdecay/README.md @@ -0,0 +1,62 @@ +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. + +# [p]bankdecay +Setup economy credit decay for your server
+ - Usage: `[p]bankdecay` + - Restricted to: `ADMIN` + - Aliases: `bdecay` + - Checks: `server_only` +## [p]bankdecay toggle +Toggle the bank decay feature on or off.
+ - Usage: `[p]bankdecay toggle` +## [p]bankdecay setdays +Set the number of inactive days before decay starts.
+ - Usage: `[p]bankdecay setdays ` +## [p]bankdecay setpercent +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.
+ - Usage: `[p]bankdecay setpercent ` +## [p]bankdecay resettotal +Reset the total amount decayed to zero.
+ - Usage: `[p]bankdecay resettotal` +## [p]bankdecay initialize +Initialize the server and add every member to the config.
+ +**Arguments**
+- as_expired: (t/f) if True, initialize users as already expired
+ - Usage: `[p]bankdecay initialize ` +## [p]bankdecay getexpired +Get a list of users who are currently expired and how much they will lose if decayed
+ - Usage: `[p]bankdecay getexpired` +## [p]bankdecay ignorerole +Add/Remove a role from the ignore list
+ +Users with an ignored role will not have their balance decay
+ - Usage: `[p]bankdecay ignorerole ` +## [p]bankdecay logchannel +Set the log channel, each time the decay cycle runs this will be updated
+ - Usage: `[p]bankdecay logchannel ` +## [p]bankdecay cleanup +Remove users from the config that are no longer in the server or have no balance
+ - Usage: `[p]bankdecay cleanup ` +## [p]bankdecay seen +Check when a user was last active (if at all)
+ - Usage: `[p]bankdecay seen ` +## [p]bankdecay bulkaddpercent +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.
+ - Usage: `[p]bankdecay bulkaddpercent ` +## [p]bankdecay bulkrempercent +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.
+ - Usage: `[p]bankdecay bulkrempercent ` +## [p]bankdecay decaynow +Run a decay cycle on this server right now
+ - Usage: `[p]bankdecay decaynow [force=False]` +## [p]bankdecay view +View Bank Decay Settings
+ - Usage: `[p]bankdecay view` diff --git a/bankdecay/__init__.py b/bankdecay/__init__.py new file mode 100644 index 0000000..e5e6c63 --- /dev/null +++ b/bankdecay/__init__.py @@ -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) diff --git a/bankdecay/abc.py b/bankdecay/abc.py new file mode 100644 index 0000000..08fe877 --- /dev/null +++ b/bankdecay/abc.py @@ -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 diff --git a/bankdecay/commands/admin.py b/bankdecay/commands/admin.py new file mode 100644 index 0000000..fceecc0 --- /dev/null +++ b/bankdecay/commands/admin.py @@ -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"" + 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))) diff --git a/bankdecay/commands/locales/de-DE.po b/bankdecay/commands/locales/de-DE.po new file mode 100644 index 0000000..c010521 --- /dev/null +++ b/bankdecay/commands/locales/de-DE.po @@ -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 "" + diff --git a/bankdecay/commands/locales/es-ES.po b/bankdecay/commands/locales/es-ES.po new file mode 100644 index 0000000..48eb5b0 --- /dev/null +++ b/bankdecay/commands/locales/es-ES.po @@ -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: {}" + diff --git a/bankdecay/commands/locales/fr-FR.po b/bankdecay/commands/locales/fr-FR.po new file mode 100644 index 0000000..4a4b79b --- /dev/null +++ b/bankdecay/commands/locales/fr-FR.po @@ -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 "" + diff --git a/bankdecay/commands/locales/hr-HR.po b/bankdecay/commands/locales/hr-HR.po new file mode 100644 index 0000000..1998853 --- /dev/null +++ b/bankdecay/commands/locales/hr-HR.po @@ -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 "" + diff --git a/bankdecay/commands/locales/ko-KR.po b/bankdecay/commands/locales/ko-KR.po new file mode 100644 index 0000000..10e784b --- /dev/null +++ b/bankdecay/commands/locales/ko-KR.po @@ -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 "" + diff --git a/bankdecay/commands/locales/messages.pot b/bankdecay/commands/locales/messages.pot new file mode 100644 index 0000000..b7111cc --- /dev/null +++ b/bankdecay/commands/locales/messages.pot @@ -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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/bankdecay/commands/locales/pt-PT.po b/bankdecay/commands/locales/pt-PT.po new file mode 100644 index 0000000..fe31a05 --- /dev/null +++ b/bankdecay/commands/locales/pt-PT.po @@ -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 "" + diff --git a/bankdecay/commands/locales/ru-RU.po b/bankdecay/commands/locales/ru-RU.po new file mode 100644 index 0000000..e974056 --- /dev/null +++ b/bankdecay/commands/locales/ru-RU.po @@ -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 "" + diff --git a/bankdecay/commands/locales/tr-TR.po b/bankdecay/commands/locales/tr-TR.po new file mode 100644 index 0000000..b491a84 --- /dev/null +++ b/bankdecay/commands/locales/tr-TR.po @@ -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 "" + diff --git a/bankdecay/common/__init__.py b/bankdecay/common/__init__.py new file mode 100644 index 0000000..f039d63 --- /dev/null +++ b/bankdecay/common/__init__.py @@ -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) diff --git a/bankdecay/common/confirm_view.py b/bankdecay/common/confirm_view.py new file mode 100644 index 0000000..bac60ac --- /dev/null +++ b/bankdecay/common/confirm_view.py @@ -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() diff --git a/bankdecay/common/listeners.py b/bankdecay/common/listeners.py new file mode 100644 index 0000000..e0ebf0d --- /dev/null +++ b/bankdecay/common/listeners.py @@ -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) diff --git a/bankdecay/common/models.py b/bankdecay/common/models.py new file mode 100644 index 0000000..eac06e0 --- /dev/null +++ b/bankdecay/common/models.py @@ -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"" + + @property + def seen_f(self) -> str: + return 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() diff --git a/bankdecay/common/scheduler.py b/bankdecay/common/scheduler.py new file mode 100644 index 0000000..215b25a --- /dev/null +++ b/bankdecay/common/scheduler.py @@ -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")) diff --git a/bankdecay/info.json b/bankdecay/info.json new file mode 100644 index 0000000..117c4da --- /dev/null +++ b/bankdecay/info.json @@ -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" +} diff --git a/bankdecay/locales/de-DE.po b/bankdecay/locales/de-DE.po new file mode 100644 index 0000000..cddee9d --- /dev/null +++ b/bankdecay/locales/de-DE.po @@ -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 "" + diff --git a/bankdecay/locales/es-ES.po b/bankdecay/locales/es-ES.po new file mode 100644 index 0000000..c9f010f --- /dev/null +++ b/bankdecay/locales/es-ES.po @@ -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" + diff --git a/bankdecay/locales/fr-FR.po b/bankdecay/locales/fr-FR.po new file mode 100644 index 0000000..39e5489 --- /dev/null +++ b/bankdecay/locales/fr-FR.po @@ -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 "" + diff --git a/bankdecay/locales/hr-HR.po b/bankdecay/locales/hr-HR.po new file mode 100644 index 0000000..335b612 --- /dev/null +++ b/bankdecay/locales/hr-HR.po @@ -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 "" + diff --git a/bankdecay/locales/ko-KR.po b/bankdecay/locales/ko-KR.po new file mode 100644 index 0000000..aa10b70 --- /dev/null +++ b/bankdecay/locales/ko-KR.po @@ -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 "" + diff --git a/bankdecay/locales/messages.pot b/bankdecay/locales/messages.pot new file mode 100644 index 0000000..25b77b3 --- /dev/null +++ b/bankdecay/locales/messages.pot @@ -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 \n" +"Language-Team: LANGUAGE \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 "" diff --git a/bankdecay/locales/pt-PT.po b/bankdecay/locales/pt-PT.po new file mode 100644 index 0000000..04488a3 --- /dev/null +++ b/bankdecay/locales/pt-PT.po @@ -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 "" + diff --git a/bankdecay/locales/ru-RU.po b/bankdecay/locales/ru-RU.po new file mode 100644 index 0000000..5a4652c --- /dev/null +++ b/bankdecay/locales/ru-RU.po @@ -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 "" + diff --git a/bankdecay/locales/tr-TR.po b/bankdecay/locales/tr-TR.po new file mode 100644 index 0000000..71cb7c2 --- /dev/null +++ b/bankdecay/locales/tr-TR.po @@ -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 "" + diff --git a/bankdecay/main.py b/bankdecay/main.py new file mode 100644 index 0000000..dc93a28 --- /dev/null +++ b/bankdecay/main.py @@ -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""" diff --git a/bankevents/README.md b/bankevents/README.md new file mode 100644 index 0000000..60da79a --- /dev/null +++ b/bankevents/README.md @@ -0,0 +1,6 @@ +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 + +# [p]bankevents +Get help using the BankEvents cog
+ - Usage: `[p]bankevents` + - Restricted to: `BOT_OWNER` diff --git a/bankevents/__init__.py b/bankevents/__init__.py new file mode 100644 index 0000000..b9312b2 --- /dev/null +++ b/bankevents/__init__.py @@ -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)) diff --git a/bankevents/abc.py b/bankevents/abc.py new file mode 100644 index 0000000..1e53d75 --- /dev/null +++ b/bankevents/abc.py @@ -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 diff --git a/bankevents/examples.txt b/bankevents/examples.txt new file mode 100644 index 0000000..f3c325c --- /dev/null +++ b/bankevents/examples.txt @@ -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 + """ diff --git a/bankevents/info.json b/bankevents/info.json new file mode 100644 index 0000000..9b66240 --- /dev/null +++ b/bankevents/info.json @@ -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" +} diff --git a/bankevents/main.py b/bankevents/main.py new file mode 100644 index 0000000..79541df --- /dev/null +++ b/bankevents/main.py @@ -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) diff --git a/bankevents/overrides/__init__.py b/bankevents/overrides/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bankevents/overrides/bank.py b/bankevents/overrides/bank.py new file mode 100644 index 0000000..af21fff --- /dev/null +++ b/bankevents/overrides/bank.py @@ -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_ diff --git a/bankevents/overrides/economy.py b/bankevents/overrides/economy.py new file mode 100644 index 0000000..663e484 --- /dev/null +++ b/bankevents/overrides/economy.py @@ -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 + ) + ) diff --git a/economytrack/README.md b/economytrack/README.md new file mode 100644 index 0000000..15d0276 --- /dev/null +++ b/economytrack/README.md @@ -0,0 +1,75 @@ +Track your economy's total balance over time

Also track you server's member count! + +# [p]economytrack +Configure EconomyTrack
+ - Usage: `[p]economytrack` + - Aliases: `ecotrack` + - Checks: `server_only` +## [p]economytrack togglebanktrack +Enable/Disable economy tracking for this server
+ - Usage: `[p]economytrack togglebanktrack` + - Restricted to: `GUILD_OWNER` + - Checks: `server_only` +## [p]economytrack view +View EconomyTrack Settings
+ - Usage: `[p]economytrack view` +## [p]economytrack maxpoints +Set the max amount of data points the bot will store
+ +**Arguments**
+`` 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)
+ - Usage: `[p]economytrack maxpoints ` + - Restricted to: `BOT_OWNER` +## [p]economytrack timezone +Set your desired timezone for the graph
+ +**Arguments**
+`` 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.
+ - Usage: `[p]economytrack timezone ` +## [p]economytrack togglemembertrack +Enable/Disable member tracking for this server
+ - Usage: `[p]economytrack togglemembertrack` + - Restricted to: `GUILD_OWNER` + - Checks: `server_only` +# [p]remoutliers +Cleanup data above a certain total economy balance
+ +**Arguments**
+datatype: either `bank` or `member`
+ - Usage: `[p]remoutliers [datatype=bank]` + - Restricted to: `GUILD_OWNER` + - Checks: `server_only` +# [p]bankgraph +View bank status over a period of time.
+**Arguments**
+`` 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`
+ - 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.
+**Arguments**
+`` 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`
+ - Usage: `[p]membergraph [timespan=1d]` + - Aliases: `memgraph` + - Cooldown: `5 per 60.0 seconds` + - Checks: `server_only` diff --git a/economytrack/__init__.py b/economytrack/__init__.py new file mode 100644 index 0000000..4c9accf --- /dev/null +++ b/economytrack/__init__.py @@ -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) diff --git a/economytrack/abc.py b/economytrack/abc.py new file mode 100644 index 0000000..0fafebf --- /dev/null +++ b/economytrack/abc.py @@ -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 diff --git a/economytrack/commands.py b/economytrack/commands.py new file mode 100644 index 0000000..2aececb --- /dev/null +++ b/economytrack/commands.py @@ -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** + `` 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** + `` 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** + `` 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 \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** + `` 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 \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) diff --git a/economytrack/economytrack.py b/economytrack/economytrack.py new file mode 100644 index 0000000..db08513 --- /dev/null +++ b/economytrack/economytrack.py @@ -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 \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 \n{box(field, 'diff')}" + return reply diff --git a/economytrack/graph.py b/economytrack/graph.py new file mode 100644 index 0000000..80980b3 --- /dev/null +++ b/economytrack/graph.py @@ -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 diff --git a/economytrack/info.json b/economytrack/info.json new file mode 100644 index 0000000..566f5c5 --- /dev/null +++ b/economytrack/info.json @@ -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" +} diff --git a/economytrickle/__init__.py b/economytrickle/__init__.py new file mode 100644 index 0000000..66cb827 --- /dev/null +++ b/economytrickle/__init__.py @@ -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) diff --git a/economytrickle/economytrickle.py b/economytrickle/economytrickle.py new file mode 100644 index 0000000..dbdb1a4 --- /dev/null +++ b/economytrickle/economytrickle.py @@ -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: + """ + + 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 diff --git a/economytrickle/info.json b/economytrickle/info.json new file mode 100644 index 0000000..da14a3c --- /dev/null +++ b/economytrickle/info.json @@ -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" +} diff --git a/economytrickle/locales/ar-SA.po b/economytrickle/locales/ar-SA.po new file mode 100644 index 0000000..8247bf5 --- /dev/null +++ b/economytrickle/locales/ar-SA.po @@ -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: \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 "" + diff --git a/economytrickle/locales/bg-BG.po b/economytrickle/locales/bg-BG.po new file mode 100644 index 0000000..f88950b --- /dev/null +++ b/economytrickle/locales/bg-BG.po @@ -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: \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 "" + diff --git a/economytrickle/locales/cs-CZ.po b/economytrickle/locales/cs-CZ.po new file mode 100644 index 0000000..b9238cf --- /dev/null +++ b/economytrickle/locales/cs-CZ.po @@ -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: \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 "" + diff --git a/economytrickle/locales/da-DK.po b/economytrickle/locales/da-DK.po new file mode 100644 index 0000000..2f73285 --- /dev/null +++ b/economytrickle/locales/da-DK.po @@ -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: \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 "" + diff --git a/economytrickle/locales/de-DE.po b/economytrickle/locales/de-DE.po new file mode 100644 index 0000000..6efe823 --- /dev/null +++ b/economytrickle/locales/de-DE.po @@ -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: \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 "" + diff --git a/economytrickle/locales/es-ES.po b/economytrickle/locales/es-ES.po new file mode 100644 index 0000000..5e1c44c --- /dev/null +++ b/economytrickle/locales/es-ES.po @@ -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: \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 "" + diff --git a/economytrickle/locales/fi-FI.po b/economytrickle/locales/fi-FI.po new file mode 100644 index 0000000..e15a19a --- /dev/null +++ b/economytrickle/locales/fi-FI.po @@ -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: \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 "" + diff --git a/economytrickle/locales/fr-FR.po b/economytrickle/locales/fr-FR.po new file mode 100644 index 0000000..929224a --- /dev/null +++ b/economytrickle/locales/fr-FR.po @@ -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: \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 "" + diff --git a/economytrickle/locales/hi-IN.po b/economytrickle/locales/hi-IN.po new file mode 100644 index 0000000..6c5e121 --- /dev/null +++ b/economytrickle/locales/hi-IN.po @@ -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: \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 "" + diff --git a/economytrickle/locales/hr-HR.po b/economytrickle/locales/hr-HR.po new file mode 100644 index 0000000..70255e5 --- /dev/null +++ b/economytrickle/locales/hr-HR.po @@ -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: \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 "" + diff --git a/economytrickle/locales/hu-HU.po b/economytrickle/locales/hu-HU.po new file mode 100644 index 0000000..7ec8320 --- /dev/null +++ b/economytrickle/locales/hu-HU.po @@ -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: \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 "" + diff --git a/economytrickle/locales/id-ID.po b/economytrickle/locales/id-ID.po new file mode 100644 index 0000000..1401ec6 --- /dev/null +++ b/economytrickle/locales/id-ID.po @@ -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: \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 "" + diff --git a/economytrickle/locales/it-IT.po b/economytrickle/locales/it-IT.po new file mode 100644 index 0000000..bac49f8 --- /dev/null +++ b/economytrickle/locales/it-IT.po @@ -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: \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 "" + diff --git a/economytrickle/locales/ja-JP.po b/economytrickle/locales/ja-JP.po new file mode 100644 index 0000000..c4ea321 --- /dev/null +++ b/economytrickle/locales/ja-JP.po @@ -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: \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 "" + diff --git a/economytrickle/locales/ko-KR.po b/economytrickle/locales/ko-KR.po new file mode 100644 index 0000000..8322e29 --- /dev/null +++ b/economytrickle/locales/ko-KR.po @@ -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: \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 "" + diff --git a/economytrickle/locales/messages.pot b/economytrickle/locales/messages.pot new file mode 100644 index 0000000..c1b3a86 --- /dev/null +++ b/economytrickle/locales/messages.pot @@ -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 \n" +"Language-Team: LANGUAGE \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: \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 "" diff --git a/economytrickle/locales/nb-NO.po b/economytrickle/locales/nb-NO.po new file mode 100644 index 0000000..48d012d --- /dev/null +++ b/economytrickle/locales/nb-NO.po @@ -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: \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 "" + diff --git a/economytrickle/locales/nl-NL.po b/economytrickle/locales/nl-NL.po new file mode 100644 index 0000000..ba2cee2 --- /dev/null +++ b/economytrickle/locales/nl-NL.po @@ -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: \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 "" + diff --git a/economytrickle/locales/pl-PL.po b/economytrickle/locales/pl-PL.po new file mode 100644 index 0000000..b86dfc2 --- /dev/null +++ b/economytrickle/locales/pl-PL.po @@ -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: \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 "" + diff --git a/economytrickle/locales/pt-BR.po b/economytrickle/locales/pt-BR.po new file mode 100644 index 0000000..2b7a52a --- /dev/null +++ b/economytrickle/locales/pt-BR.po @@ -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: \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 "" + diff --git a/economytrickle/locales/pt-PT.po b/economytrickle/locales/pt-PT.po new file mode 100644 index 0000000..edaf624 --- /dev/null +++ b/economytrickle/locales/pt-PT.po @@ -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: \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 "" + diff --git a/economytrickle/locales/ru-RU.po b/economytrickle/locales/ru-RU.po new file mode 100644 index 0000000..75ebfb5 --- /dev/null +++ b/economytrickle/locales/ru-RU.po @@ -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: \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 "" + diff --git a/economytrickle/locales/sk-SK.po b/economytrickle/locales/sk-SK.po new file mode 100644 index 0000000..3a91381 --- /dev/null +++ b/economytrickle/locales/sk-SK.po @@ -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: \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 "" + diff --git a/economytrickle/locales/sl-SI.po b/economytrickle/locales/sl-SI.po new file mode 100644 index 0000000..8054bd4 --- /dev/null +++ b/economytrickle/locales/sl-SI.po @@ -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: \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 "" + diff --git a/economytrickle/locales/sv-SE.po b/economytrickle/locales/sv-SE.po new file mode 100644 index 0000000..54454a4 --- /dev/null +++ b/economytrickle/locales/sv-SE.po @@ -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: \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 "" + diff --git a/economytrickle/locales/tr-TR.po b/economytrickle/locales/tr-TR.po new file mode 100644 index 0000000..352c6c2 --- /dev/null +++ b/economytrickle/locales/tr-TR.po @@ -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: \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 "" + diff --git a/economytrickle/locales/uk-UA.po b/economytrickle/locales/uk-UA.po new file mode 100644 index 0000000..cde9dd2 --- /dev/null +++ b/economytrickle/locales/uk-UA.po @@ -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: \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 "" + diff --git a/economytrickle/locales/vi-VN.po b/economytrickle/locales/vi-VN.po new file mode 100644 index 0000000..65ba998 --- /dev/null +++ b/economytrickle/locales/vi-VN.po @@ -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: \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 "" + diff --git a/economytrickle/locales/zh-CN.po b/economytrickle/locales/zh-CN.po new file mode 100644 index 0000000..d212ba5 --- /dev/null +++ b/economytrickle/locales/zh-CN.po @@ -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: \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 "" + diff --git a/economytrickle/locales/zh-TW.po b/economytrickle/locales/zh-TW.po new file mode 100644 index 0000000..5fbbda5 --- /dev/null +++ b/economytrickle/locales/zh-TW.po @@ -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: \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 "" + diff --git a/evolution/__init__.py b/evolution/__init__.py new file mode 100644 index 0000000..a5d4b78 --- /dev/null +++ b/evolution/__init__.py @@ -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() diff --git a/evolution/bank.py b/evolution/bank.py new file mode 100644 index 0000000..89e217c --- /dev/null +++ b/evolution/bank.py @@ -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 diff --git a/evolution/evolution.py b/evolution/evolution.py new file mode 100644 index 0000000..452f136 --- /dev/null +++ b/evolution/evolution.py @@ -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) diff --git a/evolution/info.json b/evolution/info.json new file mode 100644 index 0000000..ed45980 --- /dev/null +++ b/evolution/info.json @@ -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 +} diff --git a/evolution/tasks.py b/evolution/tasks.py new file mode 100644 index 0000000..e982116 --- /dev/null +++ b/evolution/tasks.py @@ -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() diff --git a/evolution/utils.py b/evolution/utils.py new file mode 100644 index 0000000..9970768 --- /dev/null +++ b/evolution/utils.py @@ -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 diff --git a/extendedeconomy/README.md b/extendedeconomy/README.md new file mode 100644 index 0000000..e6a02e9 --- /dev/null +++ b/extendedeconomy/README.md @@ -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.
+ - Usage: `[p]bankpie [amount=10]` +# [p]extendedeconomy +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.
+ - Usage: `[p]extendedeconomy` + - Restricted to: `ADMIN` + - Aliases: `ecoset and exteco` + - Checks: `server_only` +## [p]extendedeconomy resetcooldown +Reset the payday cooldown for a user
+ - Usage: `[p]extendedeconomy resetcooldown ` +## [p]extendedeconomy stackpaydays +Toggle whether payday roles stack or not
+ - Usage: `[p]extendedeconomy stackpaydays` + - Aliases: `stackpayday` +## [p]extendedeconomy autopaydayrole +Add/Remove auto payday roles
+ - Usage: `[p]extendedeconomy autopaydayrole ` +## [p]extendedeconomy view +View the current settings
+ - Usage: `[p]extendedeconomy view` +## [p]extendedeconomy transfertax +Set the transfer tax percentage as a decimal
+ +*Example: `0.05` is for 5% tax*
+ +- Set to 0 to disable
+- Default is 0
+ - Usage: `[p]extendedeconomy transfertax ` +## [p]extendedeconomy mainlog +Set the main log channel
+ - Usage: `[p]extendedeconomy mainlog [channel=None]` +## [p]extendedeconomy autopayday +Toggle whether paydays are claimed automatically (Global bank)
+ - Usage: `[p]extendedeconomy autopayday` + - Restricted to: `BOT_OWNER` +## [p]extendedeconomy eventlog +Set an event log channel
+ +**Events:**
+- set_balance
+- transfer_credits
+- bank_wipe
+- prune
+- set_global
+- payday_claim
+ - Usage: `[p]extendedeconomy eventlog [channel=None]` +## [p]extendedeconomy rolebonus +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.
+ - Usage: `[p]extendedeconomy rolebonus ` +## [p]extendedeconomy autoclaimchannel +Set the auto claim channel
+ - Usage: `[p]extendedeconomy autoclaimchannel [channel]` +## [p]extendedeconomy deleteafter +Set the delete after time for cost check messages
+ +- Set to 0 to disable (Recommended for public bots)
+- Default is 0 (disabled)
+ - Usage: `[p]extendedeconomy deleteafter ` + - Restricted to: `BOT_OWNER` +# [p]addcost +Add a cost to a command
+ - 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
+ +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**
+ +- `` The role to set the currency of for each user that has it.
+- `` The amount of currency to set their balance to.
+ - Usage: `[p]banksetrole ` + - Restricted to: `ADMIN` + - Checks: `is_owner_if_bank_global` diff --git a/extendedeconomy/__init__.py b/extendedeconomy/__init__.py new file mode 100644 index 0000000..6125941 --- /dev/null +++ b/extendedeconomy/__init__.py @@ -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) diff --git a/extendedeconomy/abc.py b/extendedeconomy/abc.py new file mode 100644 index 0000000..45e639d --- /dev/null +++ b/extendedeconomy/abc.py @@ -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() diff --git a/extendedeconomy/commands/__init__.py b/extendedeconomy/commands/__init__.py new file mode 100644 index 0000000..e407a0a --- /dev/null +++ b/extendedeconomy/commands/__init__.py @@ -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""" diff --git a/extendedeconomy/commands/admin.py b/extendedeconomy/commands/admin.py new file mode 100644 index 0000000..2932612 --- /dev/null +++ b/extendedeconomy/commands/admin.py @@ -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** + + - `` The role to set the currency of for each user that has it. + - `` 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** + - `` 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.")) diff --git a/extendedeconomy/commands/user.py b/extendedeconomy/commands/user.py new file mode 100644 index 0000000..80f081b --- /dev/null +++ b/extendedeconomy/commands/user.py @@ -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) diff --git a/extendedeconomy/common/__init__.py b/extendedeconomy/common/__init__.py new file mode 100644 index 0000000..f039d63 --- /dev/null +++ b/extendedeconomy/common/__init__.py @@ -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) diff --git a/extendedeconomy/common/checks.py b/extendedeconomy/common/checks.py new file mode 100644 index 0000000..f06e5a2 --- /dev/null +++ b/extendedeconomy/common/checks.py @@ -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 diff --git a/extendedeconomy/common/generator.py b/extendedeconomy/common/generator.py new file mode 100644 index 0000000..d03bbe3 --- /dev/null +++ b/extendedeconomy/common/generator.py @@ -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") diff --git a/extendedeconomy/common/listeners.py b/extendedeconomy/common/listeners.py new file mode 100644 index 0000000..df34216 --- /dev/null +++ b/extendedeconomy/common/listeners.py @@ -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) diff --git a/extendedeconomy/common/models.py b/extendedeconomy/common/models.py new file mode 100644 index 0000000..76c4cf9 --- /dev/null +++ b/extendedeconomy/common/models.py @@ -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()) diff --git a/extendedeconomy/common/parser.py b/extendedeconomy/common/parser.py new file mode 100644 index 0000000..8194a6d --- /dev/null +++ b/extendedeconomy/common/parser.py @@ -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" diff --git a/extendedeconomy/common/tasks.py b/extendedeconomy/common/tasks.py new file mode 100644 index 0000000..2c7cd0e --- /dev/null +++ b/extendedeconomy/common/tasks.py @@ -0,0 +1,130 @@ +import calendar +import logging +import typing as t +from contextlib import suppress +from datetime import datetime, timezone + +import discord +from discord.ext import tasks +from redbot.core import Config, bank +from redbot.core.i18n import Translator +from redbot.core.utils.chat_formatting import humanize_number, text_to_file + +from ..abc import MixinMeta + +log = logging.getLogger("red.vrt.extendedeconomy.tasks") +_ = Translator("ExtendedEconomy", __file__) + + +class Tasks(MixinMeta): + @tasks.loop(seconds=60) + async def auto_paydays(self): + if not self.db.auto_payday_claim: + return + cog = self.bot.get_cog("Economy") + if cog is None: + return + eco_conf: Config = cog.config + is_global = await bank.is_global() + cur_time = calendar.timegm(datetime.now(tz=timezone.utc).utctimetuple()) + 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() + + updated = [] + for user in self.bot.users: + uid = str(user.id) + if uid not in accounts or uid not in ecousers: + # Reduce unnecessary writes for users that havent used economy + continue + next_payday = ecousers[uid].get("next_payday", 0) + payday_time + if cur_time < next_payday: + # Not ready yet + continue + accounts[uid]["balance"] = min(max_bal, accounts[uid]["balance"] + payday_credits) + ecousers[uid]["next_payday"] = cur_time + updated.append((f"{user.name} ({user.id}): {humanize_number(payday_credits)}\n", payday_credits)) + + if updated: + await bankgroup.set(accounts) + await ecogroup.set(ecousers) + if self.db.logs.auto_claim: + log.info(f"Claimed {len(updated)} global paydays") + ordered = sorted(updated, key=lambda x: x[1], reverse=True) + claimed = "\n".join([x[0] for x in ordered]) + channel = self.bot.get_channel(self.db.logs.auto_claim) + if channel is not None: + with suppress(discord.HTTPException): + await channel.send( + f"Claimed {len(updated)} global paydays", + file=text_to_file(claimed, "paydays.txt"), + ) + else: + keys = list(self.db.configs.keys()) + for guild_id in keys: + guild = self.bot.get_guild(guild_id) + if guild is None: + continue + conf = self.db.configs[guild_id] + if not conf.auto_claim_roles: + continue + + bankgroup = bank._config._get_base_group(bank._config.MEMBER, str(guild_id)) + ecogroup = eco_conf._get_base_group(eco_conf.MEMBER, str(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(guild) + payday_time = await eco_conf.guild(guild).PAYDAY_TIME() + payday_credits = await eco_conf.guild(guild).PAYDAY_CREDITS() + payday_roles: t.Dict[int, dict] = await eco_conf.all_roles() + + updated = [] + for member in guild.members: + uid = str(member.id) + if uid not in accounts or uid not in ecousers: + # Reduce unnecessary writes for members that havent used economy + continue + next_payday = ecousers[uid].get("next_payday", 0) + payday_time + if cur_time < next_payday: + # Not ready yet + continue + + to_give = payday_credits + can_autoclaim = False + for role in member.roles: + if role.id in payday_roles: + role_credits = payday_roles[role.id]["PAYDAY_CREDITS"] + if conf.stack_paydays: + to_give += role_credits + elif role_credits > to_give: + to_give = role_credits + + if role.id in conf.auto_claim_roles: + can_autoclaim = True + + if not can_autoclaim: + continue + + accounts[uid]["balance"] = min(max_bal, accounts[uid]["balance"] + to_give) + ecousers[uid]["next_payday"] = cur_time + updated.append((f"{member.name} ({member.id}): {humanize_number(to_give)}\n", to_give)) + + if updated: + await bankgroup.set(accounts) + await ecogroup.set(ecousers) + if conf.logs.auto_claim: + log.debug(f"Claimed {len(updated)} paydays in {guild.name}") + ordered = sorted(updated, key=lambda x: x[1], reverse=True) + claimed = "\n".join([x[0] for x in ordered]) + channel = guild.get_channel(conf.logs.auto_claim) + if channel is not None: + with suppress(discord.HTTPException): + await channel.send( + f"Claimed {len(updated)} paydays", + file=text_to_file(claimed, "paydays.txt"), + ) diff --git a/extendedeconomy/common/utils.py b/extendedeconomy/common/utils.py new file mode 100644 index 0000000..a7c872c --- /dev/null +++ b/extendedeconomy/common/utils.py @@ -0,0 +1,189 @@ +import asyncio +import json +import typing as t +from io import StringIO + +import discord +from aiocache import cached +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 humanize_number, humanize_timedelta +from redbot.core.utils.menus import start_adding_reactions +from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate + +from ..common.models import DB, CommandCost, GuildSettings + +_ = Translator("ExtendedEconomy", __file__) +CTYPES = commands.Context | discord.app_commands.ContextMenu | discord.app_commands.Command + + +async def edit_delete_delay(message: discord.Message, new_content: str, delay: int = None): + await message.edit(content=new_content, view=None) + if delay: + await message.delete(delay=delay) + + +def ctx_to_id(ctx: commands.Context): + """Generate a unique ID for a context command""" + parts = [ + str(ctx.author.id), + str(ctx.guild.id if ctx.guild else 0), + str(ctx.channel.id if ctx.guild else 0), + str(int(ctx.message.created_at.timestamp())), + ctx.command.qualified_name, + ] + return "-".join(parts) + + +def ctx_to_dict(c: t.Union[commands.Context, discord.Interaction]): + if isinstance(c, discord.Interaction): + info = { + "type": "Interaction", + "id": c.id, + "user": c.user.id, + "guild": c.guild.id if c.guild else None, + "channel": c.channel.id, + } + else: + info = { + "type": "Context", + "id": c.message.id, + "user": c.author.id, + "guild": c.guild.id if c.guild else None, + "channel": c.channel.id, + } + return json.dumps(info, indent=2) + + +@cached(ttl=600) +async def get_cached_credits_name(guild: discord.Guild) -> str: + return await bank.get_currency_name(guild) + + +def has_cost_check(command: CTYPES): + """Check if a command already has the cost check attached to it""" + for check in command.checks: + if check.__qualname__ == "Checks.cost_check": + return True + return False + + +async def confirm_msg(ctx: t.Union[commands.Context, discord.Interaction]) -> t.Union[bool, None]: + """Wait for user to respond yes or no""" + if isinstance(ctx, discord.Interaction): + pred = MessagePredicate.yes_or_no(channel=ctx.channel, user=ctx.user) + bot = ctx.client + else: + pred = MessagePredicate.yes_or_no(ctx) + bot = ctx.bot + try: + await bot.wait_for("message", check=pred, timeout=30) + except asyncio.TimeoutError: + return None + else: + return pred.result + + +async def confirm_msg_reaction(message: discord.Message, author: t.Union[discord.Member, discord.User], bot: Red): + """Wait for user to react with a checkmark or x""" + start_adding_reactions(message, ReactionPredicate.YES_OR_NO_EMOJIS) + pred = ReactionPredicate.yes_or_no(message, author) + try: + await bot.wait_for("reaction_add", check=pred, timeout=30) + except asyncio.TimeoutError: + return None + else: + return pred.result + + +def format_settings( + db: DB, + conf: GuildSettings, + is_global: bool, + owner: bool, + delay: t.Union[int, None], +) -> discord.Embed: + not_set = _("Not Set") + txt = StringIO() + txt.write(_("# Extended Economy Settings\n")) + tax = f"{round(conf.transfer_tax * 100, 2)}%" if conf.transfer_tax else _("None") + txt.write(_("`Transfer Tax: `{}\n").format(tax)) + if not is_global: + tax_whitelist = [f"<@&{r}>" for r in conf.transfer_tax_whitelist] + txt.write(_("`Tax Whitelist: `{}\n").format(", ".join(tax_whitelist) if tax_whitelist else _("None"))) + costs = len(db.command_costs) if is_global else len(conf.command_costs) + txt.write(_("`Command Costs: `{}\n").format(costs or _("None"))) + txt.write(_("`Global Bank: `{}\n").format(is_global)) + # If owner + txt.write( + _("`Delete After: `{}\n").format(humanize_timedelta(seconds=delay) if delay else _("Disabled")) + if owner + else "" + ) + # If global + txt.write( + _("`Set Global: `{}\n").format(f"<#{db.logs.set_global}>" if db.logs.set_global else not_set) + if is_global + else "" + ) + # If not global + txt.write(_("`Stack Roles: `{}\n").format(conf.stack_paydays) if not is_global else "") + if not is_global: + bonuses = ", ".join([f"<@&{r}>" for r in conf.role_bonuses]) if conf.role_bonuses else _("None") + txt.write(_("`Role Bonuses: `{}\n").format(bonuses) if not is_global else "") + txt.write(_("`Payday Autoclaim: `{}\n").format(db.auto_payday_claim)) + if not is_global: + autoclaim_channel = f"<#{conf.logs.auto_claim}>" if conf.logs.auto_claim else not_set + txt.write(_("`Autoclaim Channel: `{}\n").format(autoclaim_channel)) + roles = ", ".join([f"<@&{r}>" for r in conf.auto_claim_roles]) if conf.auto_claim_roles else _("None") + txt.write(_("`Autoclaim Roles: `{}\n").format(roles)) + + logs = _( + "## Event Log Channels\n" + "`Default Log Channel: `{}\n" + "`Set Balance: `{}\n" + "`Transfer Credits: `{}\n" + "`Bank Wipe: `{}\n" + "`Prune Accounts: `{}\n" + ).format( + f"<#{conf.logs.default_log_channel}>" if conf.logs.default_log_channel else not_set, + f"<#{conf.logs.set_balance}>" if conf.logs.set_balance else not_set, + f"<#{conf.logs.transfer_credits}>" if conf.logs.transfer_credits else not_set, + f"<#{conf.logs.bank_wipe}>" if conf.logs.bank_wipe else not_set, + f"<#{conf.logs.prune}>" if conf.logs.prune else not_set, + ) + txt.write(logs) + if not is_global and db.auto_payday_claim: + txt.write( + _("`Payday AutoClaim: `{}\n").format(f"<#{db.logs.auto_claim}>" if db.logs.auto_claim else not_set) + ) + + if isinstance(conf, DB): + footer = _("Showing settings for global bank") + else: + footer = _("Showing settings for this server") + embed = discord.Embed(description=txt.getvalue(), color=discord.Color.green()) + embed.set_footer(text=footer) + return embed + + +def format_command_txt(com: CommandCost): + txt = _( + "`Cost: `{}\n" + "`Duration: `{}\n" + "`Level: `{}\n" + "`Prompt: `{}\n" + "`Modifier: `{}\n" + "`Value: `{}\n" + "`Cached Uses: `{}\n" + ).format( + humanize_number(com.cost), + humanize_timedelta(seconds=com.duration), + com.level, + com.prompt, + com.modifier, + com.value, + humanize_number(len(com.uses)), + ) + return txt diff --git a/extendedeconomy/info.json b/extendedeconomy/info.json new file mode 100644 index 0000000..0518ed8 --- /dev/null +++ b/extendedeconomy/info.json @@ -0,0 +1,27 @@ +{ + "author": ["Vertyco"], + "description": "Add customizable/interactive costs to commands dynamically, log bank events and more!", + "disabled": false, + "end_user_data_statement": "This cog stores User ID-timestamp pairs to keep track of command usage.", + "hidden": false, + "install_msg": "Thank you for installing!\n\n**Disclaimer**\nThis cog is in BETA. When the final version is released, some settings may need to be reconfigured.", + "min_bot_version": "3.5.3", + "min_python_version": [3, 10, 0], + "permissions": [], + "required_cogs": {"bankevents": "https://github.com/vertyco/vrt-cogs.git"}, + "requirements": ["pydantic", "aiocache", "plotly", "pandas"], + "short": "Extended bot currency economy features.", + "tags": [ + "economy", + "currency", + "credits", + "bank", + "banking", + "costs", + "interactive", + "events", + "plotly", + "log" + ], + "type": "COG" +} diff --git a/extendedeconomy/main.py b/extendedeconomy/main.py new file mode 100644 index 0000000..b6b7569 --- /dev/null +++ b/extendedeconomy/main.py @@ -0,0 +1,121 @@ +import asyncio +import logging +import typing as t + +import discord +from redbot.core import Config, commands +from redbot.core.bot import Red + +from .abc import CompositeMetaClass +from .commands import Commands +from .common.checks import Checks +from .common.listeners import Listeners +from .common.models import DB +from .common.tasks import Tasks +from .common.utils import has_cost_check +from .overrides.payday import PaydayOverride + +log = logging.getLogger("red.vrt.extendedeconomy") +RequestType = t.Literal["discord_deleted_user", "owner", "user", "user_strict"] + + +class ExtendedEconomy( + Commands, + Checks, + Listeners, + Tasks, + PaydayOverride, + commands.Cog, + metaclass=CompositeMetaClass, +): + """ + Set prices for commands, customize how prices are applied, log bank events and more! + """ + + __author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)" + __version__ = "0.7.0" + + def __init__(self, bot: Red): + super().__init__() + self.bot: Red = bot + self.config = Config.get_conf(self, 117, force_registration=True) + self.config.register_global(db={}) + + # Cache + self.db: DB = DB() + self.saving = False + self.checks = set() + self.charged: t.Dict[str, int] = {} # Commands that were successfully charged credits + + # Overrides + self.payday_callback = None + + 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): + """Nothing to delete""" + + async def red_get_data_for_user(self, *, user_id: int): + """Nothing to get""" + + async def cog_load(self) -> None: + self.bot.before_invoke(self.cost_check) + self.bot.before_invoke(self.transfer_tax_check) + asyncio.create_task(self.initialize()) + + async def cog_unload(self) -> None: + self.bot.remove_before_invoke_hook(self.cost_check) + self.bot.remove_before_invoke_hook(self.transfer_tax_check) + + self.send_payloads.cancel() + self.auto_paydays.cancel() + + for cmd in self.bot.tree.walk_commands(): + if isinstance(cmd, discord.app_commands.Group): + continue + cmd.remove_check(self.slash_cost_check) + + payday: commands.Command = self.bot.get_command("payday") + if payday and self.payday_callback: + payday.callback = self.payday_callback + + 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") + + for cogname, cog in self.bot.cogs.items(): + if cogname in self.checks: + continue + 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.slash_cost_check) + self.checks.add(cogname) + + payday: commands.Command = self.bot.get_command("payday") + if payday: + self.payday_callback = payday.callback + payday.callback = self._extendedeconomy_payday_override.callback + + self.send_payloads.start() + self.auto_paydays.start() + log.info("Initialized") + + async def save(self) -> None: + if self.saving: + return + try: + self.saving = True + dump = await asyncio.to_thread(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 diff --git a/extendedeconomy/overrides/payday.py b/extendedeconomy/overrides/payday.py new file mode 100644 index 0000000..7c49557 --- /dev/null +++ b/extendedeconomy/overrides/payday.py @@ -0,0 +1,148 @@ +import calendar +import logging +from datetime import datetime, timedelta, timezone + +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 +from ..common.models import GuildSettings, PaydayClaimInformation + +log = logging.getLogger("red.vrt.extendedeconomy.overrides.payday") +_ = Translator("ExtendedEconomy", __file__) + + +class PaydayOverride(MixinMeta): + @commands.command(hidden=True) + @commands.guild_only() + async def _extendedeconomy_payday_override(self, ctx: commands.Context): + cog = self.bot.get_cog("Economy") + if cog is None: + raise commands.ExtensionError("Economy cog is not loaded.", name="Economy") + + 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() + conf: GuildSettings = self.bot.get_cog("ExtendedEconomy").db.get_conf(guild) + for role in author.roles: + role_credits = await cog.config.role(role).PAYDAY_CREDITS() + if conf.stack_paydays: + credit_amount += role_credits + elif role_credits > credit_amount: + credit_amount = role_credits + + if conf.role_bonuses and any(role.id in conf.role_bonuses for role in author.roles): + # Get the highest bonus multiplier that the user has + highest_bonus = max(conf.role_bonuses.get(role.id, 0) for role in author.roles) + credit_amount += round(credit_amount * highest_bonus) + + 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 + ) + ) diff --git a/extendedeconomy/views/__init__.py b/extendedeconomy/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extendedeconomy/views/confirm.py b/extendedeconomy/views/confirm.py new file mode 100644 index 0000000..8a6d61e --- /dev/null +++ b/extendedeconomy/views/confirm.py @@ -0,0 +1,37 @@ + +import discord +from redbot.core.i18n import Translator + +_ = Translator("ExtendedEconomy", __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.danger) + async def yes(self, interaction: discord.Interaction, button: discord.ui.Button): + self.value = True + await interaction.response.defer() + self.stop() + + @discord.ui.button(style=discord.ButtonStyle.secondary) + async def no(self, interaction: discord.Interaction, button: discord.ui.Button): + self.value = False + await interaction.response.defer() + self.stop() diff --git a/extendedeconomy/views/cost_menu.py b/extendedeconomy/views/cost_menu.py new file mode 100644 index 0000000..469de09 --- /dev/null +++ b/extendedeconomy/views/cost_menu.py @@ -0,0 +1,413 @@ +import contextlib +import logging +import math +import typing as t +from contextlib import suppress + +import discord +from redbot.core import commands +from redbot.core.bot import Red +from redbot.core.i18n import Translator + +from ..abc import MixinMeta +from ..common.models import CommandCost +from ..common.utils import format_command_txt, format_settings + +log = logging.getLogger("red.vrt.extendedeconomy.admin") +_ = Translator("ExtendedEconomy", __file__) +PER_PAGE = 2 +LEFT = "\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}" +LEFT10 = "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}" +RIGHT = "\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}" +RIGHT10 = "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}" +UP = "\N{UPWARDS BLACK ARROW}" +DOWN = "\N{DOWNWARDS BLACK ARROW}" +CLOSE = "\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}" +ADD = "\N{HEAVY PLUS SIGN}" +REMOVE = "\N{HEAVY MINUS SIGN}" +EDIT = "\N{PENCIL}" + + +class CostModal(discord.ui.Modal): + def __init__(self, title: str, data: t.Optional[dict] = None, add: t.Optional[bool] = False): + super().__init__(title=title, timeout=120) + if data is None: + data = {} + self.add = add + if add: + self.command = discord.ui.TextInput( + label=_("Command Name"), + placeholder="ping", + default=data.get("command"), + ) + self.add_item(self.command) + self.cost = discord.ui.TextInput( + label=_("Cost"), + placeholder="100", + default=data.get("cost"), + ) + self.add_item(self.cost) + self.duration = discord.ui.TextInput( + label=_("Duration (Seconds)"), + placeholder="3600", + default=data.get("duration", "3600"), + ) + self.add_item(self.duration) + self.details = discord.ui.TextInput( + label=_("level, prompt, modifier (Comma separated)"), + placeholder="all, notify, static", + default=data.get("details", "all, notify, static"), + ) + self.add_item(self.details) + self.value = discord.ui.TextInput( + label=_("Value (Decimal)[Optional]"), + placeholder="0.0", + default=data.get("value", "0.0"), + required=False, + ) + self.add_item(self.value) + self.data = {} + + async def try_respond(self, interaction: discord.Interaction, content: str): + try: + await interaction.response.send_message(content, ephemeral=True) + except discord.HTTPException: + with suppress(discord.NotFound): + await interaction.followup.send(content, ephemeral=True) + + async def on_submit(self, interaction: discord.Interaction): + try: + self.data["cost"] = int(self.cost.value) + except ValueError: + return await self.try_respond(interaction, _("Cost must be an integer!")) + try: + self.data["duration"] = int(self.duration.value) + except ValueError: + return await self.try_respond(interaction, _("Duration must be an integer!")) + try: + self.data["value"] = float(self.value.value) + except ValueError: + return await self.try_respond(interaction, _("Value must be a decimal!")) + txt = self.details.value + if "," in txt: + i = [x.strip() for x in txt.split(",")] + else: + i = [x.strip() for x in txt.split()] + if len(i) != 3: + txt = _("Invalid details! Must be 3 values separated by commas.") + txt += _("\nExample: `user, notify, static`") + return await self.try_respond(interaction, txt) + level, prompt, modifier = i + if level not in ["admin", "mod", "all", "user", "global"]: + return await self.try_respond( + interaction, _("Invalid level! You must use one of: admin, mod, all, user, global") + ) + if prompt not in ["text", "reaction", "button", "silent", "notify"]: + return await self.try_respond( + interaction, _("Invalid prompt! You must use one of: text, reaction, button, silent, notify") + ) + if modifier not in ["static", "percent", "exponential", "linear"]: + return await self.try_respond( + interaction, _("Invalid modifier! You must use one of: static, percent, exponential, linear") + ) + self.data["level"] = level + self.data["prompt"] = prompt + self.data["modifier"] = modifier + if self.add: + self.data["command"] = self.command.value + with suppress(discord.NotFound): + await interaction.response.defer() + self.stop() + + async def on_timeout(self) -> None: + self.stop() + return await super().on_timeout() + + async def on_error(self, interaction: discord.Interaction, error: Exception, /) -> None: + txt = f"Modal failed for {interaction.user.name}!\n" f"Guild: {interaction.guild}\n" f"Title: {self.title}\n" + log.error(txt, exc_info=error) + + +class MenuButton(discord.ui.Button): + def __init__( + self, + func: t.Callable, + emoji: t.Optional[t.Union[str, discord.Emoji, discord.PartialEmoji]] = None, + style: t.Optional[discord.ButtonStyle] = discord.ButtonStyle.primary, + label: t.Optional[str] = None, + disabled: t.Optional[bool] = False, + row: t.Optional[int] = None, + ): + super().__init__(style=style, label=label, disabled=disabled, emoji=emoji, row=row) + self.func = func + + async def callback(self, interaction: discord.Interaction): + await self.func(interaction, self) + + +class CostMenu(discord.ui.View): + def __init__(self, ctx: commands.Context, cog: MixinMeta, global_bank: bool, check: t.Callable): + super().__init__(timeout=240) + self.ctx = ctx + self.author = ctx.author + self.cog = cog + self.bot: Red = cog.bot + self.db = cog.db + self.global_bank = global_bank + self.check = check + + self.message: discord.Message = None + self.page: int = 0 + self.selected: int = 0 # Which command cost field is currently selected + self.pages: t.List[discord.Embed] = self.get_pages() + + self.b = { + "add": MenuButton(self.add, ADD, style=discord.ButtonStyle.success, row=1), + "up": MenuButton(self.up, UP, row=1), + "remove": MenuButton(self.remove, REMOVE, style=discord.ButtonStyle.danger, row=1), + "left": MenuButton(self.left, LEFT, row=2), + "edit": MenuButton(self.edit, EDIT, style=discord.ButtonStyle.secondary, row=2), + "right": MenuButton(self.right, RIGHT, row=2), + "left10": MenuButton(self.left10, LEFT10, row=3), + "down": MenuButton(self.down, DOWN, row=3), + "right10": MenuButton(self.right10, RIGHT10, row=3), + "close": MenuButton(self.close, CLOSE, style=discord.ButtonStyle.danger, row=4), + } + + async def interaction_check(self, interaction: discord.Interaction): + 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: + if self.message: + with contextlib.suppress(Exception): + await self.message.edit(view=None) + await self.ctx.tick() + + def get_costs(self) -> t.Dict[str, CommandCost]: + if self.global_bank: + return self.db.command_costs + return self.db.get_conf(self.ctx.guild).command_costs + + def get_command_name(self) -> str: + page = self.pages[self.page] + field = page.fields[self.selected] + return field.name.replace("➣ ", "") + + def get_cost_obj(self) -> t.Union[CommandCost, None]: + costs = self.get_costs() + command_name = self.get_command_name() + cost_obj = costs.get(command_name) + return cost_obj + + async def refresh(self): + self.pages = self.get_pages() + + self.clear_items() + if self.get_costs(): + for b in self.b.values(): + b.disabled = False + self.add_item(b) + else: + for k, b in self.b.items(): + if k in ["add", "close"]: + self.add_item(b) + else: + b.disabled = True + self.add_item(b) + + page: discord.Embed = self.pages[self.page] + if self.selected >= len(page.fields) and page.fields: + # Place the arrow on the last field if selected is out of bounds + self.selected = len(page.fields) - 1 + page.set_field_at( + self.selected, + name=f"➣ {page.fields[self.selected].name}", + value=page.fields[self.selected].value, + inline=False, + ) + + if self.message: + await self.message.edit(embed=page, view=self) + else: + self.message = await self.ctx.send(embed=page, view=self) + + def get_pages(self): + guildconf = self.db.get_conf(self.ctx.guild) + conf = self.db if self.global_bank else guildconf + itemized: t.List[t.Tuple[str, CommandCost]] = list(conf.command_costs.items()) + itemized.sort(key=lambda x: x[0]) + start, stop = 0, PER_PAGE + pages = [] + page_count = math.ceil(len(conf.command_costs) / PER_PAGE) + base_embed = format_settings( + self.db, + guildconf, + self.global_bank, + self.author.id in self.bot.owner_ids, + self.db.delete_after, + ) + for p in range(page_count): + embed = base_embed.copy() + embed.set_footer(text=_("Page {}/{}").format(p + 1, page_count)) + stop = min(stop, len(conf.command_costs)) + for i in range(start, stop): + command_name, cost_obj = itemized[i] + txt = format_command_txt(cost_obj) + is_selected = i % PER_PAGE == self.selected + name = f"➣ {command_name}" if is_selected else command_name + embed.add_field(name=name, value=txt, inline=False) + pages.append(embed) + start += PER_PAGE + stop += PER_PAGE + if not pages: + pages.append(base_embed) + return pages + + # ROW 1 + async def add(self, interaction: discord.Interaction, button: discord.ui.Button): + modal = CostModal(_("Add Command Cost"), add=True) + await interaction.response.send_modal(modal) + await modal.wait() + if not modal.data: + return + command_name = modal.data["command"] + if command_name in self.get_costs(): + return await interaction.followup.send(_("Command already has a cost!"), ephemeral=True) + command_obj = self.bot.get_command(command_name) + if not command_obj: + command_obj = self.bot.tree.get_command(command_name) + if not command_obj: + return await interaction.followup.send(_("Command not found!"), ephemeral=True) + if isinstance(command_obj, commands.commands._AlwaysAvailableCommand): + txt = _("You can't add a cost to a command that is always available!") + return await interaction.followup.send(txt, ephemeral=True) + if isinstance(command_obj, (commands.Command, commands.HybridCommand)): + if (command_obj.requires.privilege_level or 0) > await commands.requires.PrivilegeLevel.from_ctx(self.ctx): + txt = _("You can't add costs to commands you don't have permission to run!") + return await interaction.followup.send(txt, ephemeral=True) + + cost_obj = CommandCost( + cost=modal.data["cost"], + duration=modal.data["duration"], + level=modal.data["level"], + prompt=modal.data["prompt"], + modifier=modal.data["modifier"], + value=modal.data["value"], + ) + if self.global_bank: + self.cog.db.command_costs[command_name] = cost_obj + else: + conf = self.cog.db.get_conf(self.ctx.guild) + conf.command_costs[command_name] = cost_obj + msg = await interaction.followup.send(_("Command cost added!"), ephemeral=True) + if msg: + await msg.delete(delay=10) + await self.refresh() + await self.cog.save() + + async def up(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.NotFound): + await interaction.response.defer() + page = self.pages[self.page] + self.selected -= 1 + self.selected %= len(page.fields) + await self.refresh() + + async def remove(self, interaction: discord.Interaction, button: discord.ui.Button): + command_name = self.get_command_name() + costs = self.get_costs() + if command_name not in costs: + return await interaction.response.send_message(_("Command not found!"), ephemeral=True, delete_after=10) + if self.global_bank: + del self.db.command_costs[command_name] + else: + conf = self.db.get_conf(self.ctx.guild) + del conf.command_costs[command_name] + await interaction.response.send_message(_("Command cost removed!"), ephemeral=True) + await self.refresh() + await self.cog.save() + + # ROW 2 + async def left(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.NotFound): + await interaction.response.defer() + self.page -= 1 + self.page %= len(self.pages) + await self.refresh() + + async def edit(self, interaction: discord.Interaction, button: discord.ui.Button): + cost_obj = self.get_cost_obj() + if not cost_obj: + return await interaction.response.send_message(_("Command not found!"), ephemeral=True) + data = { + "cost": str(cost_obj.cost), + "duration": str(cost_obj.duration), + "details": f"{cost_obj.level}, {cost_obj.prompt}, {cost_obj.modifier}", + "value": str(cost_obj.value), + } + title = _("Edit Cost: {}").format(self.get_command_name()) + modal = CostModal(title, data) + await interaction.response.send_modal(modal) + await modal.wait() + if not modal.data: + return + cost_obj.cost = int(modal.data["cost"]) + cost_obj.duration = int(modal.data["duration"]) + cost_obj.level = modal.data["level"] + cost_obj.prompt = modal.data["prompt"] + cost_obj.modifier = modal.data["modifier"] + cost_obj.value = float(modal.data["value"]) + if self.global_bank: + self.db.command_costs[self.get_command_name()] = cost_obj + else: + conf = self.db.get_conf(self.ctx.guild) + conf.command_costs[self.get_command_name()] = cost_obj + + msg = await interaction.followup.send(_("Command cost updated!"), ephemeral=True) + if msg: + await msg.delete(delay=10) + await self.refresh() + await self.cog.save() + + async def right(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.NotFound): + await interaction.response.defer() + self.page += 1 + self.page %= len(self.pages) + await self.refresh() + + # ROW 3 + async def left10(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.NotFound): + await interaction.response.defer() + self.page -= 10 + self.page %= len(self.pages) + await self.refresh() + + async def down(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.NotFound): + await interaction.response.defer() + page = self.pages[self.page] + self.selected += 1 + self.selected %= len(page.fields) + await self.refresh() + + async def right10(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.NotFound): + await interaction.response.defer() + self.page += 10 + self.page %= len(self.pages) + await self.refresh() + + # ROW 4 + async def close(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.NotFound): + await interaction.response.defer() + if msg := self.message: + with suppress(discord.NotFound): + await msg.delete() + self.stop() + await self.ctx.tick() diff --git a/lottery/__init__.py b/lottery/__init__.py new file mode 100644 index 0000000..16979c2 --- /dev/null +++ b/lottery/__init__.py @@ -0,0 +1,11 @@ +import json +from pathlib import Path + +from .lottery import Lottery + +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): + await bot.add_cog(Lottery(bot)) diff --git a/lottery/checks.py b/lottery/checks.py new file mode 100644 index 0000000..03f2494 --- /dev/null +++ b/lottery/checks.py @@ -0,0 +1,8 @@ +from redbot.core import commands + + +def lucky3_enabled(): + async def pred(ctx: commands.Context): + return await ctx.bot.get_cog("Lottery").config.guild(ctx.guild).lucky3.enable() + + return commands.check(pred) diff --git a/lottery/info.json b/lottery/info.json new file mode 100644 index 0000000..401b726 --- /dev/null +++ b/lottery/info.json @@ -0,0 +1,14 @@ +{ + "author": ["YamiKaitou#8975"], + "name": "Lottery", + "short": "Play some Lottery games", + "description": "Play a Lucky3 'scratcher', a Daily Drawing, or a Weekly Drawing", + "tags": [ + "economy", "fun" + ], + "hidden": true, + "disabled": false, + "min_bot_version": "3.5.0", + "install_msg": "Thanks for installing the cog, please note that this cog is currently in Beta status while the 3 games are being worked on.\nI'm in the Cog Support server if you need help with anything. Make sure you take a look under `[p]lottoset` before getting started, the games are disabled by default", + "end_user_data_statement": "This cog does not persistently store data or metadata about users." +} \ No newline at end of file diff --git a/lottery/lottery.py b/lottery/lottery.py new file mode 100644 index 0000000..65e2622 --- /dev/null +++ b/lottery/lottery.py @@ -0,0 +1,264 @@ +import logging +import random + +import discord +from redbot.core import Config, bank, commands +from redbot.core.utils.chat_formatting import humanize_number +from redbot.core.utils.menus import DEFAULT_CONTROLS, menu + +from . import checks as lc + +log = logging.getLogger("red.yamicogs.lottery") + + +class Lottery(commands.Cog): + """ + Lottery Games + + More detailed docs: + """ + + lucky3 = ["🃏", "🔬", "💰", "🚀", "🏆", "🍰", "🌹", "🦀", "👑"] + + def __init__(self, bot): + self.bot = bot + self.config = Config.get_conf(self, identifier=582650109, force_registration=True) + self.config.register_guild( + **{ + "match1": {"enable": False, "cost": 100, "max": 30, "prize": 0}, + "match5": {"enable": False, "cost": 100, "max": 60, "prize": 0}, + "lucky3": { + "enable": False, + "cost": 100, + "icons": 3, + "prize": 1000, + }, + } + ) + + @commands.group() + async def lottery(self, ctx): + """Lottery Game""" + + @lottery.command(name="games") + async def l_games(self, ctx): + """View game explanations""" + + settings = await self.config.guild(ctx.guild).all() + currency = await bank.get_currency_name(ctx.guild) + + pages = [] + + if settings["match1"]["enable"]: + match1 = discord.Embed(title="Lottery Games - Match1", color=await ctx.embed_color()) + match1.description = ( + "Play a daily drawing where if you match the Winning Number, you win!" + ) + match1.add_field( + name="Rules", + inline=False, + value="Get a number between 1 and {}.\n" + "If your number matches the Winning Number, you win.\n" + "Number is drawn daily. Winnings are split between all winners.\n" + "If there is no winner, the prize pool rolls to the next drawing.".format( + settings["match1"]["max"] + ), + ) + match1.add_field( + name="Settings", + value="Cost per Entry: {1} {0}\nNumber Range: 1 - {2}\nCurrent Prize Pool: {3} {0}".format( + currency, + humanize_number(settings["match1"]["cost"]), + settings["match1"]["max"], + humanize_number(settings["match1"]["prize"]), + ), + ) + pages.append(match1) + + if settings["match5"]["enable"]: + match5 = discord.Embed(title="Lottery Games - Match5", color=await ctx.embed_color()) + match5.description = ( + "Play a weekly drawing where if you match the Winning Numbers, you win!" + ) + match5.add_field(name="Rules", value="To be detailed later", inline=False) + match5.add_field( + name="Settings", + value="Cost per Entry: {1} {0}\nNumber Range: 1 - {2}\nCurrent Prize Pool: {3} {0}".format( + currency, + humanize_number(settings["match5"]["cost"]), + settings["match5"]["max"], + humanize_number(settings["match5"]["prize"]), + ), + ) + pages.append(match5) + + if settings["lucky3"]["enable"]: + lucky3 = discord.Embed(title="Lottery Games - Lucky3", color=await ctx.embed_color()) + lucky3.description = "Draw 3 Symbols and with a prize if they all match!\nTo play, use `{}lottery lucky3`".format( + ctx.clean_prefix + ) + lucky3.add_field( + name="Rules", + inline=False, + value="You will get {} random emojis. If you match 3 of them, you win!".format( + settings["lucky3"]["icons"] + ), + ) + lucky3.add_field( + name="Settings", + value="Cost per Entry: {1} {0}\nPrize: {2} {0}\nEmojis: {3}".format( + currency, + humanize_number(settings["match1"]["cost"]), + humanize_number(settings["lucky3"]["prize"]), + settings["lucky3"]["icons"], + ), + ) + pages.append(lucky3) + + if pages != []: + await menu(ctx, pages, DEFAULT_CONTROLS) + else: + await ctx.send("No games are enabled") + + @lc.lucky3_enabled() + @lottery.command(name="lucky3") + async def l_lucky3(self, ctx): + """Play a game of Lucky 3""" + if not await self.config.guild(ctx.guild).lucky3.enable(): + return + + cost = await self.config.guild(ctx.guild).lucky3.cost() + currency = await bank.get_currency_name(ctx.guild) + + if not await bank.can_spend(ctx.author, cost): + await ctx.send( + "You do not have enough {} to play, you need at least {}".format(currency, cost) + ) + return + + icons = await self.config.guild(ctx.guild).lucky3.icons() - 1 + prize = await self.config.guild(ctx.guild).lucky3.prize() + + num1 = random.randrange(0, icons) + num2 = random.randrange(0, icons) + num3 = random.randrange(0, icons) + + await bank.withdraw_credits(ctx.author, cost) + await ctx.send(f"{self.lucky3[num1]}{self.lucky3[num2]}{self.lucky3[num3]}") + + if num1 == num2 and num1 == num3: + await ctx.send("🥳 WINNER!!! 🎉 +{} {}".format(humanize_number(prize), currency)) + await bank.deposit_credits(ctx.author, prize) + else: + await ctx.send("Not a winner 😢") + + @commands.group() + async def lottoset(self, ctx): + """Lottery Settings""" + + @lottoset.command(name="info") + async def ls_info(self, ctx): + """View configured settings""" + + settings = await self.config.guild(ctx.guild).all() + + embed = discord.Embed(title="Lottery Settings", color=await ctx.embed_color()) + embed.add_field( + name="Match 1 (Not yet implemented)", + inline=True, + value="Enabled?: {0}\nCost: {1}\nMax #: {2}".format( + settings["match1"]["enable"], + humanize_number(settings["match1"]["cost"]), + settings["match1"]["max"], + ), + ) + embed.add_field( + name="Match 5 (Not yet implemented)", + inline=True, + value="Enabled?: {0}\nCost: {1}\nMax #: {2}".format( + settings["match5"]["enable"], + humanize_number(settings["match5"]["cost"]), + settings["match5"]["max"], + ), + ) + embed.add_field( + name="Lucky 3", + inline=True, + value="Enabled?: {0}\nCost: {1}\nIcons: {2}\nPrize: {3}".format( + settings["lucky3"]["enable"], + humanize_number(settings["lucky3"]["cost"]), + settings["lucky3"]["icons"], + humanize_number(settings["lucky3"]["prize"]), + ), + ) + + await ctx.send(embed=embed) + + @lottoset.group(name="lucky3") + async def ls_lucky3(self, ctx): + """Configure settings for Lucky 3""" + + @ls_lucky3.command(name="enable") + async def ls3_enable(self, ctx, state: str): + """ + Enable or Disable the Lucky3 game. + + should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false` + """ + + if state in ["on", "yes", "1", "true"]: + await self.config.guild(ctx.guild).lucky3.enable.set(True) + await ctx.tick() + elif state in ["off", "no", "0", "false"]: + await self.config.guild(ctx.guild).lucky3.enable.set(False) + await ctx.tick() + else: + await ctx.send_help() + + @ls_lucky3.command(name="icons") + async def ls3_icons(self, ctx, icons: int): + """ + Sets the number of Emojis to choose from + + Valid options are 2-9 + Approximate Win percentrages are + ``` + Icons: 2 | 25.0% + Icons: 3 | 11.1% + Icons: 4 | 6.3% + Icons: 5 | 4.0% + Icons: 6 | 2.8% + Icons: 7 | 2.1% + Icons: 8 | 1.6% + Icons: 9 | 1.2%``` + """ + + if 2 <= icons <= 9: + await self.config.guild(ctx.guild).lucky3.icons.set(icons) + await ctx.tick() + else: + await ctx.send( + "Sorry, but **2** is the lowest and **9** is the highest settings supported" + ) + + @ls_lucky3.command(name="cost") + async def ls3_cost(self, ctx, cost: int): + """Set the cost per game""" + + await self.config.guild(ctx.guild).lucky3.cost.set(cost) + await ctx.tick() + + @ls_lucky3.command(name="prize") + async def ls3_prize(self, ctx, prize: int): + """Set the Prize amount""" + + await self.config.guild(ctx.guild).lucky3.prize.set(prize) + await ctx.tick() + + 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 diff --git a/payday/__init__.py b/payday/__init__.py new file mode 100644 index 0000000..95076bb --- /dev/null +++ b/payday/__init__.py @@ -0,0 +1,12 @@ +import json +from pathlib import Path + +from .payday import PayDay + +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 = PayDay(bot) + await bot.add_cog(cog) diff --git a/payday/info.json b/payday/info.json new file mode 100644 index 0000000..37ba8ee --- /dev/null +++ b/payday/info.json @@ -0,0 +1,20 @@ +{ + "author": ["YamiKaitou#8975"], + "name": "PayDay", + "short": "More PayDay options", + "description": "Give bonus credits for daily and other times", + "tags": [ + "utility", + "economy", + "daily", + "payday", + "freecredits" + ], + "requirements": [ + "tabulate" + ], + "min_bot_version": "3.5.0", + "end_user_data_statement": "This cog does not persistently store end user data. This cog does store Discord UserIDs as needed for operation.", + "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]pdconfig` before getting started, all commands are turned off by default", + "hidden": false +} diff --git a/payday/locales/ar-SA.po b/payday/locales/ar-SA.po new file mode 100644 index 0000000..f3d8315 --- /dev/null +++ b/payday/locales/ar-SA.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:33\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: ar_SA\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/bg-BG.po b/payday/locales/bg-BG.po new file mode 100644 index 0000000..156c967 --- /dev/null +++ b/payday/locales/bg-BG.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:33\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: bg_BG\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/cs-CZ.po b/payday/locales/cs-CZ.po new file mode 100644 index 0000000..f0cc2ed --- /dev/null +++ b/payday/locales/cs-CZ.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: cs_CZ\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/da-DK.po b/payday/locales/da-DK.po new file mode 100644 index 0000000..5d673cd --- /dev/null +++ b/payday/locales/da-DK.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: da_DK\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/de-DE.po b/payday/locales/de-DE.po new file mode 100644 index 0000000..2c71d52 --- /dev/null +++ b/payday/locales/de-DE.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: de_DE\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/es-ES.po b/payday/locales/es-ES.po new file mode 100644 index 0000000..17016eb --- /dev/null +++ b/payday/locales/es-ES.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:33\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: es_ES\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/fi-FI.po b/payday/locales/fi-FI.po new file mode 100644 index 0000000..c56ae36 --- /dev/null +++ b/payday/locales/fi-FI.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: fi_FI\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/fr-FR.po b/payday/locales/fr-FR.po new file mode 100644 index 0000000..52f6bbf --- /dev/null +++ b/payday/locales/fr-FR.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:33\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: fr_FR\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/hi-IN.po b/payday/locales/hi-IN.po new file mode 100644 index 0000000..8706e1b --- /dev/null +++ b/payday/locales/hi-IN.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: hi_IN\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/hr-HR.po b/payday/locales/hr-HR.po new file mode 100644 index 0000000..212fd9b --- /dev/null +++ b/payday/locales/hr-HR.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: hr_HR\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/hu-HU.po b/payday/locales/hu-HU.po new file mode 100644 index 0000000..386a18b --- /dev/null +++ b/payday/locales/hu-HU.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: hu_HU\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/id-ID.po b/payday/locales/id-ID.po new file mode 100644 index 0000000..64d782a --- /dev/null +++ b/payday/locales/id-ID.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: id_ID\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/it-IT.po b/payday/locales/it-IT.po new file mode 100644 index 0000000..f76784f --- /dev/null +++ b/payday/locales/it-IT.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: it_IT\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/ja-JP.po b/payday/locales/ja-JP.po new file mode 100644 index 0000000..7a0c02e --- /dev/null +++ b/payday/locales/ja-JP.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: ja_JP\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/ko-KR.po b/payday/locales/ko-KR.po new file mode 100644 index 0000000..686002e --- /dev/null +++ b/payday/locales/ko-KR.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: ko_KR\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/messages.pot b/payday/locales/messages.pot new file mode 100644 index 0000000..b302d42 --- /dev/null +++ b/payday/locales/messages.pot @@ -0,0 +1,330 @@ +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \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" + +#: payday/payday.py:82 +#, docstring +msgid "" +"\n" +" Customizable PayDay system\n" +"\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "" +"You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "" +"You have claimed all available {bankname} from the `freecredits` program! " +"+{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "" +"You have been given {free_credits} {bankname} plus {streak_bonus} for " +"maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "" +"\n" +" Configure the `freecredits` options\n" +"\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "" +"\n" +" Configure the `hourly` options\n" +"\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "" +"\n" +" Configure the `daily` options\n" +"\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "" +"\n" +" Configure the `weekly` options\n" +"\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "" +"\n" +" Configure the `monthly` options\n" +"\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "" +"\n" +" Configure the `quarterly` options\n" +"\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "" +"\n" +" Configure the `yearly` options\n" +"\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "" +"\n" +" Configure the `hourly` streaks value\n" +"\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "" +"\n" +" Configure the `daily` streaks value\n" +"\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "" +"\n" +" Configure the `weekly` streaks value\n" +"\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "" +"\n" +" Configure the `monthly` streaks value\n" +"\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "" +"\n" +" Configure the `quarterly` streaks value\n" +"\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "" +"\n" +" Configure the `yearly` streaks value\n" +"\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "" +"\n" +" Configure streaks to be a percentage or flat amount\n" +"\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "" +"Pull some config data for a specific user/member, useful for Support " +"questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "" +"\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n" +"\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" diff --git a/payday/locales/nb-NO.po b/payday/locales/nb-NO.po new file mode 100644 index 0000000..223e688 --- /dev/null +++ b/payday/locales/nb-NO.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: nb_NO\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/nl-NL.po b/payday/locales/nl-NL.po new file mode 100644 index 0000000..f7010f3 --- /dev/null +++ b/payday/locales/nl-NL.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: nl_NL\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "Instelling opgeslagen" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/pl-PL.po b/payday/locales/pl-PL.po new file mode 100644 index 0000000..bfcd42e --- /dev/null +++ b/payday/locales/pl-PL.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: pl_PL\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/pt-BR.po b/payday/locales/pt-BR.po new file mode 100644 index 0000000..96b9ffa --- /dev/null +++ b/payday/locales/pt-BR.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: pt_BR\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/pt-PT.po b/payday/locales/pt-PT.po new file mode 100644 index 0000000..fe91e83 --- /dev/null +++ b/payday/locales/pt-PT.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: pt_PT\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/ru-RU.po b/payday/locales/ru-RU.po new file mode 100644 index 0000000..acff5f8 --- /dev/null +++ b/payday/locales/ru-RU.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: ru_RU\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/sk-SK.po b/payday/locales/sk-SK.po new file mode 100644 index 0000000..061354b --- /dev/null +++ b/payday/locales/sk-SK.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: sk_SK\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/sl-SI.po b/payday/locales/sl-SI.po new file mode 100644 index 0000000..80df904 --- /dev/null +++ b/payday/locales/sl-SI.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: sl_SI\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/sv-SE.po b/payday/locales/sv-SE.po new file mode 100644 index 0000000..f3c3c68 --- /dev/null +++ b/payday/locales/sv-SE.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: sv_SE\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/tr-TR.po b/payday/locales/tr-TR.po new file mode 100644 index 0000000..61ec056 --- /dev/null +++ b/payday/locales/tr-TR.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: tr_TR\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/uk-UA.po b/payday/locales/uk-UA.po new file mode 100644 index 0000000..1412a81 --- /dev/null +++ b/payday/locales/uk-UA.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: uk_UA\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/vi-VN.po b/payday/locales/vi-VN.po new file mode 100644 index 0000000..e1a7261 --- /dev/null +++ b/payday/locales/vi-VN.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: vi_VN\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/zh-CN.po b/payday/locales/zh-CN.po new file mode 100644 index 0000000..b0f4234 --- /dev/null +++ b/payday/locales/zh-CN.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: zh_CN\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/locales/zh-TW.po b/payday/locales/zh-TW.po new file mode 100644 index 0000000..8e0334c --- /dev/null +++ b/payday/locales/zh-TW.po @@ -0,0 +1,298 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-04-22 21:53+0000\n" +"PO-Revision-Date: 2024-07-01 17:34\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/payday/locales/messages.pot\n" +"X-Crowdin-File-ID: 166\n" +"Language: zh_TW\n" + +#: payday/payday.py:82 +#, docstring +msgid "\n" +" Customizable PayDay system\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:144 +#, docstring +msgid "More options to get free credits" +msgstr "" + +#: payday/payday.py:149 +#, docstring +msgid "Display remaining time for all options" +msgstr "" + +#: payday/payday.py:169 payday/payday.py:183 +msgid "Available Now!" +msgstr "" + +#: payday/payday.py:188 +msgid "No freecredit options have been configured yet" +msgstr "" + +#: payday/payday.py:195 +#, docstring +msgid "Claim all available freecredits" +msgstr "" + +#: payday/payday.py:214 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\n" +"Plus an additional {total_streak} for maintaining your streaks" +msgstr "" + +#: payday/payday.py:221 +msgid "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" +msgstr "" + +#: payday/payday.py:227 +msgid "You have no available {bankname} for claiming." +msgstr "" + +#: payday/payday.py:233 +#, docstring +msgid "Get some free currency every hour" +msgstr "" + +#: payday/payday.py:239 +msgid "Sorry, you still have {remain} until your next hourly bonus" +msgstr "" + +#: payday/payday.py:245 payday/payday.py:277 payday/payday.py:309 +#: payday/payday.py:341 payday/payday.py:373 payday/payday.py:405 +msgid "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" +msgstr "" + +#: payday/payday.py:255 payday/payday.py:287 payday/payday.py:319 +#: payday/payday.py:351 payday/payday.py:383 payday/payday.py:415 +msgid "You have been given {free_credits} {bankname}" +msgstr "" + +#: payday/payday.py:265 +#, docstring +msgid "Get some free currency every day" +msgstr "" + +#: payday/payday.py:271 +msgid "Sorry, you still have {remain} until your next dailya bonus" +msgstr "" + +#: payday/payday.py:297 +#, docstring +msgid "Get some free currency every week (7 days)" +msgstr "" + +#: payday/payday.py:303 +msgid "Sorry, you still have {remain} until your next weekly bonus" +msgstr "" + +#: payday/payday.py:329 +#, docstring +msgid "Get some free currency every month (30 days)" +msgstr "" + +#: payday/payday.py:335 +msgid "Sorry, you still have {remain} until your next monthly bonus" +msgstr "" + +#: payday/payday.py:361 +#, docstring +msgid "Get some free currency every quarter (122 days)" +msgstr "" + +#: payday/payday.py:367 +msgid "Sorry, you still have {remain} until your next quarterly bonus" +msgstr "" + +#: payday/payday.py:393 +#, docstring +msgid "Get some free currency every year (365 days)" +msgstr "" + +#: payday/payday.py:399 +msgid "Sorry, you still have {remain} until your next yearly bonus" +msgstr "" + +#: payday/payday.py:469 +#, docstring +msgid "\n" +" Configure the `freecredits` options\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: payday/payday.py:479 +#, docstring +msgid "Print the `freecredits` options" +msgstr "" + +#: payday/payday.py:492 +#, docstring +msgid "\n" +" Configure the `hourly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:499 payday/payday.py:521 payday/payday.py:544 +#: payday/payday.py:567 payday/payday.py:590 payday/payday.py:613 +#: payday/payday.py:642 payday/payday.py:665 payday/payday.py:688 +#: payday/payday.py:711 payday/payday.py:734 payday/payday.py:757 +msgid "You must provide a non-negative value or 0" +msgstr "" + +#: payday/payday.py:509 payday/payday.py:531 payday/payday.py:554 +#: payday/payday.py:577 payday/payday.py:600 payday/payday.py:623 +#: payday/payday.py:652 payday/payday.py:675 payday/payday.py:698 +#: payday/payday.py:721 payday/payday.py:744 payday/payday.py:767 +#: payday/payday.py:788 +msgid "Setting saved" +msgstr "" + +#: payday/payday.py:515 +#, docstring +msgid "\n" +" Configure the `daily` options\n\n" +" Setting this to 0 will disable the command" +msgstr "" + +#: payday/payday.py:537 +#, docstring +msgid "\n" +" Configure the `weekly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:560 +#, docstring +msgid "\n" +" Configure the `monthly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:583 +#, docstring +msgid "\n" +" Configure the `quarterly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:606 +#, docstring +msgid "\n" +" Configure the `yearly` options\n\n" +" Setting this to 0 will disable the command\n" +" " +msgstr "" + +#: payday/payday.py:629 +#, docstring +msgid "Configure the `streaks` options" +msgstr "" + +#: payday/payday.py:635 +#, docstring +msgid "\n" +" Configure the `hourly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:658 +#, docstring +msgid "\n" +" Configure the `daily` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:681 +#, docstring +msgid "\n" +" Configure the `weekly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:704 +#, docstring +msgid "\n" +" Configure the `monthly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:727 +#, docstring +msgid "\n" +" Configure the `quarterly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:750 +#, docstring +msgid "\n" +" Configure the `yearly` streaks value\n\n" +" Setting this to 0 will disable the streak bonus\n" +" " +msgstr "" + +#: payday/payday.py:773 +#, docstring +msgid "\n" +" Configure streaks to be a percentage or flat amount\n\n" +" should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false`\n" +" " +msgstr "" + +#: payday/payday.py:794 +#, docstring +msgid "Pull some config data for a specific user/member, useful for Support questions" +msgstr "" + +#: payday/payday.py:802 payday/payday.py:814 +msgid "Global Settings" +msgstr "" + +#: payday/payday.py:804 +msgid "User Settings for {person}" +msgstr "" + +#: payday/payday.py:816 +msgid "Member Settings for {person}" +msgstr "" + +#: payday/payday.py:827 +#, docstring +msgid "\n" +" Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away\n\n" +" For , you can provide any combination of the following (seperate by a space to include multiple)\n" +" hour\n" +" day\n" +" week\n" +" month\n" +" quarter\n" +" year\n" +" " +msgstr "" + +#: payday/payday.py:852 +msgid "The provided times for {person} have been reset" +msgstr "" + diff --git a/payday/payday.py b/payday/payday.py new file mode 100644 index 0000000..00eda21 --- /dev/null +++ b/payday/payday.py @@ -0,0 +1,874 @@ +import logging +import math +from datetime import datetime, timedelta +from typing import Literal, Union + +import discord +from redbot.core import Config, bank, checks, commands +from redbot.core.bot import Red +from redbot.core.i18n import Translator, cog_i18n +from redbot.core.utils import AsyncIter +from redbot.core.utils.chat_formatting import box, humanize_timedelta +from tabulate import tabulate + +log = logging.getLogger("red.yamicogs.payday") + +_ = Translator("Talk", __file__) + + +def cmd_check(option): + async def pred(ctx: commands.Context): + if await bank.is_global(): + if await ctx.bot.get_cog("PayDay").config.get_raw(option) > 0: + return True + elif not await bank.is_global() and ctx.guild is not None: + if await ctx.bot.get_cog("PayDay").config.guild(ctx.guild).get_raw(option) > 0: + return True + else: + return False + + return commands.check(pred) + + +def cmd_all(): + async def pred(ctx: commands.Context): + if await bank.is_global(): + return True + elif not await bank.is_global() and ctx.guild is not None: + return True + else: + return False + + return commands.check(pred) + + +# taken from Red-Discordbot economy.py +def guild_only_check(): + async def pred(ctx: commands.Context): + if await bank.is_global(): + return True + elif not await bank.is_global() and ctx.guild is not None: + return True + else: + return False + + return commands.check(pred) + + +# 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 PayDay(commands.Cog): + """ + Customizable PayDay system + + More detailed docs: + """ + + settings = {"day": 1, "week": 7, "month": 30, "quarter": 91, "year": 365} + friendly = { + "hour": "Hourly", + "day": "Daily", + "week": "Weekly", + "month": "Monthly", + "quarter": "Quarterly", + "year": "Yearly", + } + times = { + "hour": 1, + "day": 24, + "week": 168, + "month": 720, + "quarter": 2184, + "year": 8760, + } + + def __init__(self, bot: Red): + self.bot = bot + self.config = Config.get_conf(self, identifier=582650109, force_registration=True) + + default_config = { + "hour": 0, + "day": 0, + "week": 0, + "month": 0, + "quarter": 0, + "year": 0, + "streaks": { + "hour": 0, + "day": 0, + "week": 0, + "month": 0, + "quarter": 0, + "year": 0, + "percent": False, + }, + } + default_user = { + "hour": "2016-01-02T04:25:00-04:00", + "day": "2016-01-02T04:25:00-04:00", + "week": "2016-01-02T04:25:00-04:00", + "month": "2016-01-02T04:25:00-04:00", + "quarter": "2016-01-02T04:25:00-04:00", + "year": "2016-01-02T04:25:00-04:00", + } + + self.config.register_global(**default_config) + self.config.register_guild(**default_config) + self.config.register_member(**default_user) + self.config.register_user(**default_user) + + @guild_only_check() + @commands.group() + async def freecredits(self, ctx): + """More options to get free credits""" + + @cmd_all() + @freecredits.command(name="times") + async def freecredits_times(self, ctx): + """Display remaining time for all options""" + + if await bank.is_global(): + amounts = await self.config.all() + times = await self.config.user(ctx.author).all() + else: + amounts = await self.config.guild(ctx.guild).all() + times = await self.config.member(ctx.author).all() + + now = datetime.now().astimezone().replace(microsecond=0) + strings = "" + + if amounts["hour"]: + td = now - datetime.fromisoformat(times["hour"]) + strings += ( + self.friendly["hour"] + + ": " + + ( + humanize_timedelta(timedelta=(timedelta(hours=1) - td)) + if td.seconds < 3600 + else _("Available Now!") + ) + + "\n" + ) + + for k, v in self.settings.items(): + if amounts[k]: + td = now - (datetime.fromisoformat(times[k])) + strings += ( + self.friendly[k] + + ": " + + ( + humanize_timedelta(timedelta=(timedelta(days=v) - td)) + if td.days < v + else _("Available Now!") + ) + + "\n" + ) + if strings == "": + await ctx.send(_("No freecredit options have been configured yet")) + else: + await ctx.send(strings) + + @cmd_all() + @freecredits.command(name="all") + async def freecredits_all(self, ctx): + """Claim all available freecredits""" + + total_amount = 0 + total_streak = 0 + + for k in self.friendly: + free, streak, remain = await self.grant_award(ctx, k, False) + if remain != -1: + continue + elif streak != -1: + total_amount += free + total_streak += streak + elif free != -1: + total_amount += free + + bankname = await bank.get_currency_name(ctx.guild) + if total_streak > 0: + await bank.deposit_credits(ctx.author, total_amount + total_streak) + await ctx.send( + _( + "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}\nPlus an additional {total_streak} for maintaining your streaks" + ).format(bankname=bankname, total_amount=total_amount, total_streak=total_streak) + ) + elif total_amount > 0: + await bank.deposit_credits(ctx.author, total_amount) + await ctx.send( + _( + "You have claimed all available {bankname} from the `freecredits` program! +{total_amount} {bankname}" + ).format(bankname=bankname, total_amount=total_amount) + ) + else: + await ctx.send( + _("You have no available {bankname} for claiming.").format(bankname=bankname) + ) + + @cmd_check("hour") + @freecredits.command(name="hourly") + async def freecredits_hourly(self, ctx): + """Get some free currency every hour""" + + free, streak, remain = await self.grant_award(ctx, "hour") + + if remain != -1: + await ctx.send( + _("Sorry, you still have {remain} until your next hourly bonus").format( + remain=remain + ) + ) + elif streak != -1: + await ctx.send( + _( + "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" + ).format( + free_credits=free, + bankname=(await bank.get_currency_name(ctx.guild)), + streak_bonus=streak, + ) + ) + elif free != -1: + await ctx.send( + _("You have been given {free_credits} {bankname}").format( + free_credits=free, bankname=(await bank.get_currency_name(ctx.guild)) + ) + ) + else: + log.debug("grant_award returned no results") + + @cmd_check("day") + @freecredits.command(name="daily") + async def freecredits_daily(self, ctx): + """Get some free currency every day""" + + free, streak, remain = await self.grant_award(ctx, "day") + + if remain != -1: + await ctx.send( + _("Sorry, you still have {remain} until your next dailya bonus").format( + remain=remain + ) + ) + elif streak != -1: + await ctx.send( + _( + "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" + ).format( + free_credits=free, + bankname=(await bank.get_currency_name(ctx.guild)), + streak_bonus=streak, + ) + ) + elif free != -1: + await ctx.send( + _("You have been given {free_credits} {bankname}").format( + free_credits=free, bankname=(await bank.get_currency_name(ctx.guild)) + ) + ) + else: + log.debug("grant_award returned no results") + + @cmd_check("week") + @freecredits.command(name="weekly") + async def freecredits_weekly(self, ctx): + """Get some free currency every week (7 days)""" + + free, streak, remain = await self.grant_award(ctx, "week") + + if remain != -1: + await ctx.send( + _("Sorry, you still have {remain} until your next weekly bonus").format( + remain=remain + ) + ) + elif streak != -1: + await ctx.send( + _( + "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" + ).format( + free_credits=free, + bankname=(await bank.get_currency_name(ctx.guild)), + streak_bonus=streak, + ) + ) + elif free != -1: + await ctx.send( + _("You have been given {free_credits} {bankname}").format( + free_credits=free, bankname=(await bank.get_currency_name(ctx.guild)) + ) + ) + else: + log.debug("grant_award returned no results") + + @cmd_check("month") + @freecredits.command(name="monthly") + async def freecredits_monthly(self, ctx): + """Get some free currency every month (30 days)""" + + free, streak, remain = await self.grant_award(ctx, "month") + + if remain != -1: + await ctx.send( + _("Sorry, you still have {remain} until your next monthly bonus").format( + remain=remain + ) + ) + elif streak != -1: + await ctx.send( + _( + "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" + ).format( + free_credits=free, + bankname=(await bank.get_currency_name(ctx.guild)), + streak_bonus=streak, + ) + ) + elif free != -1: + await ctx.send( + _("You have been given {free_credits} {bankname}").format( + free_credits=free, bankname=(await bank.get_currency_name(ctx.guild)) + ) + ) + else: + log.debug("grant_award returned no results") + + @cmd_check("quarter") + @freecredits.command(name="quarterly") + async def freecredits_quarterly(self, ctx): + """Get some free currency every quarter (122 days)""" + + free, streak, remain = await self.grant_award(ctx, "quarter") + + if remain != -1: + await ctx.send( + _("Sorry, you still have {remain} until your next quarterly bonus").format( + remain=remain + ) + ) + elif streak != -1: + await ctx.send( + _( + "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" + ).format( + free_credits=free, + bankname=(await bank.get_currency_name(ctx.guild)), + streak_bonus=streak, + ) + ) + elif free != -1: + await ctx.send( + _("You have been given {free_credits} {bankname}").format( + free_credits=free, bankname=(await bank.get_currency_name(ctx.guild)) + ) + ) + else: + log.debug("grant_award returned no results") + + @cmd_check("year") + @freecredits.command(name="yearly") + async def freecredits_yearly(self, ctx): + """Get some free currency every year (365 days)""" + + free, streak, remain = await self.grant_award(ctx, "year") + + if remain != -1: + await ctx.send( + _("Sorry, you still have {remain} until your next yearly bonus").format( + remain=remain + ) + ) + elif streak != -1: + await ctx.send( + _( + "You have been given {free_credits} {bankname} plus {streak_bonus} for maintaining a streak" + ).format( + free_credits=free, + bankname=(await bank.get_currency_name(ctx.guild)), + streak_bonus=streak, + ) + ) + elif free != -1: + await ctx.send( + _("You have been given {free_credits} {bankname}").format( + free_credits=free, bankname=(await bank.get_currency_name(ctx.guild)) + ) + ) + else: + log.debug("grant_award returned no results") + + async def grant_award(self, ctx, option, deposit=True): + if await bank.is_global(): + free = await self.config.get_raw(option) + streak = await self.config.streaks.get_raw(option) + perc = await self.config.streaks.percent() + config = self.config.user(ctx.author) + else: + free = await self.config.guild(ctx.guild).get_raw(option) + streak = await self.config.guild(ctx.guild).streaks.get_raw(option) + perc = await self.config.guild(ctx.guild).streaks.percent() + config = self.config.member(ctx.author) + + if free > 0: + last = datetime.fromisoformat(await config.get_raw(option)) + now = datetime.now().astimezone().replace(microsecond=0) + + if streak > 0 and ( + timedelta(hours=(self.times[option] * 2)) + > (now - last) + >= timedelta(hours=self.times[option]) + ): + if perc: + streak = free * math.floor(streak / 100) + if deposit: + await bank.deposit_credits(ctx.author, free + streak) + await config.set_raw(option, value=now.isoformat()) + return (free, streak, -1) + elif (now - last) >= timedelta(hours=self.times[option]): + if deposit: + await bank.deposit_credits(ctx.author, free) + await config.set_raw(option, value=now.isoformat()) + return (free, -1, -1) + else: + return ( + 0, + 0, + humanize_timedelta( + timedelta=(timedelta(hours=self.times[option]) - (now - last)) + ), + ) + + return (-1, -1, -1) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @commands.group() + async def pdconfig(self, ctx): + """ + Configure the `freecredits` options + + More detailed docs: + """ + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="settings") + async def pdconfig_info(self, ctx): + """Print the `freecredits` options""" + + if await bank.is_global(): + conf = await self.config.all() + await ctx.send(box(tabulate(conf.items()))) + else: + conf = await self.config.guild(ctx.guild).all() + await ctx.send(box(tabulate(conf.items()))) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="hourly", aliases=["hour"]) + async def pdconfig_hourly(self, ctx, value: int): + """ + Configure the `hourly` options + + Setting this to 0 will disable the command + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.hour.set(value) + else: + await self.config.guild(ctx.guild).hour.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="daily", aliases=["day"]) + async def pdconfig_daily(self, ctx, value: int): + """ + Configure the `daily` options + + Setting this to 0 will disable the command""" + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.day.set(value) + else: + await self.config.guild(ctx.guild).day.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="weekly", aliases=["week"]) + async def pdconfig_weekly(self, ctx, value: int): + """ + Configure the `weekly` options + + Setting this to 0 will disable the command + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.week.set(value) + else: + await self.config.guild(ctx.guild).week.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="monthly", aliases=["month"]) + async def pdconfig_monthly(self, ctx, value: int): + """ + Configure the `monthly` options + + Setting this to 0 will disable the command + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.month.set(value) + else: + await self.config.guild(ctx.guild).month.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="quarterly", aliases=["quarter"]) + async def pdconfig_quarterly(self, ctx, value: int): + """ + Configure the `quarterly` options + + Setting this to 0 will disable the command + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.quarter.set(value) + else: + await self.config.guild(ctx.guild).quarter.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="yearly", aliases=["year"]) + async def pdconfig_yearly(self, ctx, value: int): + """ + Configure the `yearly` options + + Setting this to 0 will disable the command + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.year.set(value) + else: + await self.config.guild(ctx.guild).year.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.group(name="streaks") + async def pdconfig_streaks(self, ctx): + """Configure the `streaks` options""" + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig_streaks.command(name="hourly", aliases=["hour"]) + async def pdconfig_streaks_hourly(self, ctx, value: int): + """ + Configure the `hourly` streaks value + + Setting this to 0 will disable the streak bonus + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.streaks.hour.set(value) + else: + await self.config.guild(ctx.guild).streaks.hour.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig_streaks.command(name="daily", aliases=["day"]) + async def pdconfig_streaks_daily(self, ctx, value: int): + """ + Configure the `daily` streaks value + + Setting this to 0 will disable the streak bonus + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.streaks.day.set(value) + else: + await self.config.guild(ctx.guild).streaks.day.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig_streaks.command(name="weekly", aliases=["week"]) + async def pdconfig_streaks_weekly(self, ctx, value: int): + """ + Configure the `weekly` streaks value + + Setting this to 0 will disable the streak bonus + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.streaks.week.set(value) + else: + await self.config.guild(ctx.guild).streaks.week.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig_streaks.command(name="monthly", aliases=["month"]) + async def pdconfig_streaks_monthly(self, ctx, value: int): + """ + Configure the `monthly` streaks value + + Setting this to 0 will disable the streak bonus + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.streaks.month.set(value) + else: + await self.config.guild(ctx.guild).streaks.month.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig_streaks.command(name="quarterly", aliases=["quarter"]) + async def pdconfig_streaks_quarterly(self, ctx, value: int): + """ + Configure the `quarterly` streaks value + + Setting this to 0 will disable the streak bonus + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.streaks.quarter.set(value) + else: + await self.config.guild(ctx.guild).streaks.quarter.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig_streaks.command(name="yearly", aliases=["year"]) + async def pdconfig_streaks_yearly(self, ctx, value: int): + """ + Configure the `yearly` streaks value + + Setting this to 0 will disable the streak bonus + """ + + if value < 0: + return await ctx.send(_("You must provide a non-negative value or 0")) + try: + if await bank.is_global(): + await self.config.streaks.year.set(value) + else: + await self.config.guild(ctx.guild).streaks.year.set(value) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig_streaks.command(name="percent", aliases=["percentage"]) + async def pdconfig_streaks_percentage(self, ctx, state: bool): + """ + Configure streaks to be a percentage or flat amount + + should be any of these combinations, `on/off`, `yes/no`, `1/0`, `true/false` + """ + + try: + if await bank.is_global(): + await self.config.streaks.percent.set(state) + else: + await self.config.guild(ctx.guild).streaks.percent.set(state) + except Exception as e: + raise e + else: + if not await ctx.tick(): + await ctx.send(_("Setting saved")) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="debug", hidden=True) + async def pdconfig_debug(self, ctx, person: Union[discord.Member, discord.User]): + """Pull some config data for a specific user/member, useful for Support questions""" + + if await bank.is_global(): + amounts = await self.config.all() + times = await self.config.user(person).all() + + await ctx.send( + "```{global_label}\n{global_items}\n\n{user_label}\n{user_items}```".format( + global_label=_("Global Settings"), + global_items=tabulate(amounts.items()), + user_label=_("User Settings for {person}").format(person=person.id), + user_items=tabulate(times.items()), + ) + ) + else: + amounts = await self.config.guild(ctx.guild).all() + times = await self.config.member(person).all() + + await ctx.send( + "```{global_label}\n{global_items}\n\n{user_label}\n{user_items}```".format( + global_label=_("Global Settings"), + global_items=tabulate(amounts.items()), + user_label=_("Member Settings for {person}").format(person=person.id), + user_items=tabulate(times.items()), + ) + ) + + @is_owner_if_bank_global() + @checks.guildowner_or_permissions(administrator=True) + @pdconfig.command(name="reset", hidden=True) + async def pdconfig_reset( + self, ctx, person: Union[discord.Member, discord.User], *, options: str + ): + """ + Forcibly reset the time for a user. WARNING, this will allow the user to claim the credits right away + + For , you can provide any combination of the following (seperate by a space to include multiple) + hour + day + week + month + quarter + year + """ + + try: + if await bank.is_global(): + for opt in options.split(): + await self.config.user(person).set_raw(opt, value="2016-01-02T04:25:00-04:00") + else: + for opt in options.split(): + await self.config.member(person).set_raw( + opt, value="2016-01-02T04:25:00-04:00" + ) + except Exception as e: + raise e + else: + await ctx.send( + _("The provided times for {person} have been reset").format( + person=person.display_name + ) + ) + + async def red_delete_data_for_user( + self, + *, + requester: Literal["discord_deleted_user", "owner", "user", "user_strict"], + user_id: int, + ): + if requester == "discord_deleted_user": + # user is deleted, just comply + + data = await self.config.all_members() + async for guild_id, members in AsyncIter(data.items(), steps=100): + if user_id in members: + await self.config.member_from_ids(guild_id, user_id).clear() + + data = await self.config.all_users() + async for users in AsyncIter(data.items(), steps=100): + if user_id in users: + await self.config.user_from_id(user_id).clear() diff --git a/referrals/__init__.py b/referrals/__init__.py new file mode 100644 index 0000000..ada9bab --- /dev/null +++ b/referrals/__init__.py @@ -0,0 +1,16 @@ +from redbot.core.bot import Red +from redbot.core.utils import get_end_user_data_statement + +from .commands.user import referredby_context +from .main import Referrals + +__red_end_user_data_statement__ = get_end_user_data_statement(__file__) + + +async def setup(bot: Red): + await bot.add_cog(Referrals(bot)) + bot.tree.add_command(referredby_context) + + +async def teardown(bot: Red): + bot.tree.remove_command(referredby_context) diff --git a/referrals/abc.py b/referrals/abc.py new file mode 100644 index 0000000..055e3a5 --- /dev/null +++ b/referrals/abc.py @@ -0,0 +1,20 @@ +from abc import ABC, ABCMeta + +from discord.ext.commands.cog import CogMeta +from piccolo.engine.sqlite import SQLiteEngine +from redbot.core.bot import Red + +from .db.utils import DBUtils + + +class CompositeMetaClass(CogMeta, ABCMeta): + """Type detection""" + + +class MixinMeta(ABC): + """Type hinting""" + + def __init__(self, *_args): + self.bot: Red + self.db: SQLiteEngine | None + self.db_utils: DBUtils diff --git a/referrals/build.py b/referrals/build.py new file mode 100644 index 0000000..4b4d668 --- /dev/null +++ b/referrals/build.py @@ -0,0 +1,22 @@ +import asyncio +from pathlib import Path + +from engine import engine + +root = Path(__file__).parent + + +async def main(): + try: + desc = input("Enter a description for the migration: ") + res = await engine.create_migrations(root, True, desc) + if "The command failed." in res: + raise Exception(res) + print(res) + except Exception as e: + print(f"Error: {e}") + print(await engine.diagnose_issues(root)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/referrals/commands/__init__.py b/referrals/commands/__init__.py new file mode 100644 index 0000000..e407a0a --- /dev/null +++ b/referrals/commands/__init__.py @@ -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""" diff --git a/referrals/commands/admin.py b/referrals/commands/admin.py new file mode 100644 index 0000000..c6f72a5 --- /dev/null +++ b/referrals/commands/admin.py @@ -0,0 +1,277 @@ +import logging +import typing as t +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_timedelta + +from ..abc import MixinMeta +from ..common import utils +from ..common.checks import ensure_db_connection +from ..db.tables import GuildSettings, Referral +from ..views.dynamic_menu import DynamicMenu + +log = logging.getLogger("red.vrt.referrals.commands.admin") + +_ = Translator("Referrals", __file__) + + +@cog_i18n(_) +class Admin(MixinMeta): + @commands.group(aliases=["refset", "referralset"]) + @commands.guild_only() + @commands.admin_or_permissions(manage_guild=True) + async def referset(self, ctx: commands.Context): + """Settings for the referral system""" + pass + + @referset.command() + @ensure_db_connection() + async def view(self, ctx: commands.Context): + """View the current settings for the guild""" + settings = await self.db_utils.get_create_guild(ctx.guild) + currency_name = await bank.get_currency_name(ctx.guild) + title = _("Referral System Settings") + embed = discord.Embed(title=title, color=await ctx.embed_color()) + embed.add_field( + name=_("Status"), + value=_("`Enabled`") if settings.enabled else _("`Disabled`"), + inline=False, + ) + embed.add_field( + name=_("Referral Reward"), + value=_("Amount of {} given to the person who referred someone: {}").format( + currency_name, f"`{settings.referral_reward}`" + ), + inline=False, + ) + embed.add_field( + name=_("Referred Reward"), + value=_("Amount of {} given to the person who was referred: {}").format( + currency_name, f"`{settings.referred_reward}`" + ), + inline=False, + ) + embed.add_field( + name=_("Referral Log"), + value=_("Channel where referral events are sent: {}").format( + f"<#{settings.referral_channel}>" if settings.referral_channel else _("`Not set`") + ), + inline=False, + ) + min_age = humanize_timedelta(seconds=settings.min_account_age_minutes * 60) + embed.add_field( + name=_("Minimum Account Age"), + value=_("Account of referred user must be at least {} old to make the claim.").format(f"`{min_age}`") + if settings.min_account_age_minutes + else _("There is no minimum account age requirement for users to claim their referral"), + inline=False, + ) + claim_timeout = humanize_timedelta(seconds=settings.claim_timeout_minutes * 60) + embed.add_field( + name=_("Claim Timeout"), + value=_("Referral claims must be done within {} after joining.").format(f"`{claim_timeout}`") + if settings.claim_timeout_minutes + else _("Users have no time limit to claim their referral"), + inline=False, + ) + embed.add_field( + name=_("Initialized Users"), + value=_("Users who have been initialized: {}").format(f"`{len(settings.initialized_users)}`"), + inline=False, + ) + embed.add_field( + name=_("Referrals Claimed"), + value=_("Total referrals claimed: {}").format( + f"`{await Referral.count().where(Referral.guild == ctx.guild.id)}`" + ), + ) + await ctx.send(embed=embed) + + @referset.command() + @ensure_db_connection() + async def listreferrals(self, ctx: commands.Context): + """List all referrals made in the server""" + referrals = await Referral.objects().where(Referral.guild == ctx.guild.id) + if not referrals: + return await ctx.send(_("No referrals found in this server")) + color = await self.bot.get_embed_color(ctx) + embeds = [] + chunk_size = 5 + for idx, chunk in enumerate(utils.chunk(referrals, chunk_size)): + txt = StringIO() + for idxx, referral in enumerate(chunk, start=idx): + referrer = await self.bot.get_or_fetch_user(referral.referrer_id) + referred = await self.bot.get_or_fetch_user(referral.referred_id) + txt.write(f"{idxx + 1}. {referred} referred by {referrer}\n") + embed = discord.Embed(title=_("Referrals"), description=txt.getvalue(), color=color) + embed.set_footer(text=_("Page {}/{}").format(idx + 1, len(referrals))) + embeds.append(embed) + await DynamicMenu(ctx, embeds).refresh() + + @referset.command() + @ensure_db_connection() + async def toggle(self, ctx: commands.Context): + """Toggle the referral system on or off""" + settings = await self.db_utils.get_create_guild(ctx.guild) + settings.enabled = not settings.enabled + await settings.save() + await ctx.send(_("Referral system is now {}").format(_("enabled") if settings.enabled else _("disabled"))) + + @referset.command() + @ensure_db_connection() + async def reward(self, ctx: commands.Context, reward_type: t.Literal["referral", "referred"], amount: int): + """Set the reward for referring or being referred + + **Arguments:** + - `reward_type` - Either `referral` or `referred` + - `referral` - The reward for referring someone + - `referred` - The reward for being referred + - `amount` - The amount of currency to reward + + *Set to 0 to disable the reward* + """ + settings = await self.db_utils.get_create_guild(ctx.guild) + currency_name = await bank.get_currency_name(ctx.guild) + if reward_type == "referral": + settings.referral_reward = amount + txt = _("The person who referred someone will receive {}").format(f"`{amount} {currency_name}`") + elif reward_type == "referred": + settings.referred_reward = amount + txt = _("The person who was referred will receive {}").format(f"`{amount} {currency_name}`") + else: + return await ctx.send_help() + await settings.save([GuildSettings.referral_reward, GuildSettings.referred_reward]) + await ctx.send(txt) + + @referset.command() + @ensure_db_connection() + async def channel(self, ctx: commands.Context, channel: discord.TextChannel = None): + """Set the channel where referral events will be sent""" + settings = await self.db_utils.get_create_guild(ctx.guild) + if not channel and not settings.referral_channel: + return await ctx.send(_("Referral events are not being sent to any channel")) + settings.referral_channel = channel.id if channel else 0 + await settings.save([GuildSettings.referral_channel]) + if channel: + if not channel.permissions_for(ctx.me).send_messages: + return await ctx.send(_("I don't have permission to send messages in that channel")) + if not channel.permissions_for(ctx.me).embed_links: + return await ctx.send(_("I don't have permission to send embeds in that channel")) + await ctx.send(_("Referral events will be sent to {}").format(channel.mention)) + else: + await ctx.send(_("Referral events will no longer be sent to a channel")) + + @referset.command() + @ensure_db_connection() + async def age(self, ctx: commands.Context, age: str): + """Set the minimum account age for referred users to be eligible for rewards + + - `age` - The minimum account age in the format of `30m`, `1h`, `2d12h`, etc. + """ + settings = await self.db_utils.get_create_guild(ctx.guild) + if "none" in age.lower(): + delta = None + else: + try: + delta = commands.parse_timedelta(age) + except ValueError: + delta = None + + if delta is None: + settings.min_account_age_minutes = 0 + txt = _("Account age restriction removed") + else: + settings.min_account_age_minutes = int(delta.total_seconds() / 60) + txt = _("The minimum account age required to claim a referral is {}").format( + f"`{humanize_timedelta(timedelta=delta)}`" + ) + await settings.save([GuildSettings.min_account_age_minutes]) + await ctx.send(txt) + + @referset.command() + @ensure_db_connection() + async def timeout(self, ctx: commands.Context, timeout: str): + """Set the time frame for users to claim their reward after joining + + - `timeout` - The time frame in the format of `30m`, `1h`, `2d12h`, etc. + """ + settings = await self.db_utils.get_create_guild(ctx.guild) + if "none" in timeout.lower(): + delta = None + else: + try: + delta = commands.parse_timedelta(timeout) + except ValueError: + delta = None + + if delta is None: + settings.claim_timeout_minutes = 0 + txt = _("Claim timeout removed") + else: + settings.claim_timeout_minutes = int(delta.total_seconds() / 60) + txt = _("Users will have {} to claim their referral after joining.").format( + f"`{humanize_timedelta(timedelta=delta)}" + ) + await settings.save([GuildSettings.claim_timeout_minutes]) + await ctx.send(txt) + + @referset.command(aliases=["initialize"]) + @ensure_db_connection() + async def init(self, ctx: commands.Context): + """Initialize all unreferred users in the server so they cannot retroactively claim rewards""" + settings = await self.db_utils.get_create_guild(ctx.guild) + referrals: list[int] = ( + await Referral.select(Referral.referred_id).where(Referral.guild == ctx.guild.id).output(as_list=True) + ) + guild_members: list[int] = [member.id for member in ctx.guild.members] + unreferred = list(set(guild_members) - set(referrals)) + if not unreferred: + return await ctx.send(_("All users have been initialized")) + new_refs = list(set(unreferred + settings.initialized_users)) + settings.initialized_users = new_refs + await GuildSettings.raw( + "UPDATE guild_settings SET initialized_users = {} WHERE id = {}", new_refs, ctx.guild.id + ) + await ctx.send(_("Initialized {} users").format(len(unreferred))) + + @referset.command() + @ensure_db_connection() + async def resetall(self, ctx: commands.Context, confirm: bool): + """Reset all referral data for the guild""" + if not confirm: + return await ctx.send(_("Rerun this with `True` to comfirm")) + await Referral.delete().where(Referral.guild == ctx.guild.id) + await GuildSettings.delete().where(GuildSettings.id == ctx.guild.id) + await ctx.send(_("All referral data has been reset for this server")) + + @referset.command() + @ensure_db_connection() + async def resetreferrals(self, ctx: commands.Context, confirm: bool): + """Reset all referral data for the guild""" + if not confirm: + return await ctx.send(_("Rerun this with `True` to confirm")) + await Referral.delete().where(Referral.guild == ctx.guild.id) + await ctx.send(_("All referral data has been reset for this server")) + + @referset.command() + @ensure_db_connection() + async def resetsettings(self, ctx: commands.Context, confirm: bool): + """Reset all referral data for the guild""" + if not confirm: + return await ctx.send(_("Rerun this with `True` to confirm")) + await GuildSettings.delete().where(GuildSettings.id == ctx.guild.id) + await ctx.send(_("All referral settings have been reset for this server")) + + @referset.command() + @ensure_db_connection() + async def resetinitialized(self, ctx: commands.Context, confirm: bool): + """Reset all referral data for the guild""" + if not confirm: + return await ctx.send(_("Rerun this with `True` to confirm")) + settings = await self.db_utils.get_create_guild(ctx.guild) + settings.initialized_users = [] + await settings.save([GuildSettings.initialized_users]) + await ctx.send(_("All initialized users have been reset for this server")) diff --git a/referrals/commands/user.py b/referrals/commands/user.py new file mode 100644 index 0000000..4e4d7a7 --- /dev/null +++ b/referrals/commands/user.py @@ -0,0 +1,335 @@ +import logging +from datetime import datetime +from io import StringIO + +import discord +from discord import app_commands +from redbot.core import bank, commands +from redbot.core.bot import Red +from redbot.core.errors import BalanceTooHigh +from redbot.core.i18n import Translator, cog_i18n +from redbot.core.utils.chat_formatting import humanize_timedelta + +from ..abc import MixinMeta +from ..common import utils +from ..common.checks import ensure_db_connection +from ..db.tables import Referral +from ..views.dynamic_menu import DynamicMenu + +log = logging.getLogger("red.vrt.referrals.commands.admin") + +_ = Translator("Referrals", __file__) + + +@app_commands.context_menu(name="Referred By") +@app_commands.guild_only() +async def referredby_context(interaction: discord.Interaction, member: discord.Member): + if not interaction.guild: + return await interaction.response.send_message(_("This command can only be used in a server."), ephemeral=True) + if member.bot: + return await interaction.response.send_message(_("Bots can't refer users."), ephemeral=True) + if member.id == interaction.user.id: + return await interaction.response.send_message(_("You can't refer yourself."), ephemeral=True) + if not member.guild: + return await interaction.response.send_message( + _("The user you are trying to refer is not in this server."), ephemeral=True + ) + bot: Red = interaction.client + cog: MixinMeta = bot.get_cog("Referrals") + if not cog.db: + return await interaction.response.send_message( + _("Database connection is not active, try again later."), ephemeral=True + ) + + await interaction.response.defer(thinking=True) + settings = await cog.db_utils.get_create_guild(interaction.guild) + if not settings.enabled: + return await interaction.followup.send(_("Referral rewards are not enabled for this server.")) + if not settings.referral_reward and not settings.referred_reward: + return await interaction.followup.send(_("Referral rewards are not configured for this server.")) + if interaction.user.id in settings.initialized_users: + return await interaction.followup.send( + _("You were already a part of this server when the referral system was initialized.") + ) + if interaction.user.joined_at: + if interaction.user.joined_at < member.joined_at: + return await interaction.followup.send( + _("This user joined the server after you, they couldn't have referred you.") + ) + if log_channel := bot.get_channel(settings.referral_channel): + if not all( + [ + log_channel.permissions_for(interaction.guild.me).send_messages, + log_channel.permissions_for(interaction.guild.me).embed_links, + ] + ): + log_channel = None + account_age_minutes = int((datetime.now().astimezone() - interaction.user.created_at).total_seconds() / 60) + if settings.min_account_age_minutes and account_age_minutes < settings.min_account_age_minutes: + required_time = humanize_timedelta(seconds=settings.min_account_age_minutes * 60) + account_age_humanized = humanize_timedelta(seconds=account_age_minutes * 60) + if log_channel: + txt = _("Account age less than {} old. ({} old)").format(f"`{required_time}`", f"`{account_age_humanized}`") + embed = utils.referral_error( + referrer=member, + referred=interaction.user, + error=txt, + channel=interaction.channel.mention, + ) + await log_channel.send(embed=embed) + return await interaction.followup.send( + _("You must have an account age of at least {} to be eligible for referral rewards.").format( + f"**{required_time}**" + ) + ) + join_minutes = int((datetime.now().astimezone() - interaction.user.joined_at).total_seconds() / 60) + if settings.claim_timeout_minutes and join_minutes > settings.claim_timeout_minutes: + required_time = humanize_timedelta(seconds=settings.claim_timeout_minutes * 60) + in_server_time = humanize_timedelta(seconds=join_minutes * 60) + if log_channel: + txt = _("Missed claim timeout of {} after joining. ({} ago)").format( + f"`{required_time}`", f"`{in_server_time}`" + ) + embed = utils.referral_error( + referrer=member, + referred=interaction.user, + error=txt, + channel=interaction.channel.mention, + ) + await log_channel.send(embed=embed) + + return await interaction.followup.send( + _("You joined the server too long ago to claim a referral reward. You must claim it within {}.").format( + f"**{required_time}**" + ) + ) + existing_referral = await Referral.objects().get( + (Referral.guild == interaction.guild.id) & (Referral.referred_id == interaction.user.id) + ) + if existing_referral: + referrer = await bot.fetch_user(existing_referral.referrer_id) + return await interaction.followup.send(_("You have already been referred by {}.").format(f"**{referrer}**")) + + currency_name = await bank.get_currency_name(interaction.guild) + response = StringIO() + response.write(_("You have been referred by {}!\n").format(f"**{member}**")) + if settings.referred_reward: + try: + await bank.deposit_credits(interaction.user, settings.referred_reward) + except BalanceTooHigh as e: + await bank.set_balance(interaction.user, e.max_balance) + txt = _("-# You have received {} as a reward for being referred!\n").format( + f"`{settings.referred_reward} {currency_name}`" + ) + response.write(txt) + + if settings.referral_reward: + try: + await bank.deposit_credits(member, settings.referral_reward) + except BalanceTooHigh as e: + await bank.set_balance(member, e.max_balance) + txt = _("-# {} has received {} as a reward for referring you!").format( + f"`{member}`", f"`{settings.referral_reward} {currency_name}`" + ) + response.write(txt) + + referral = Referral( + guild=interaction.guild.id, + referred_id=interaction.user.id, + referrer_id=member.id, + ) + await referral.save() + await interaction.followup.send(response.getvalue()) + + if log_channel: + embed = utils.referral_embed( + referrer=member, + referred=interaction.user, + referrer_reward=settings.referral_reward, + referred_reward=settings.referred_reward, + currency=currency_name, + channel=interaction.channel.mention, + ) + await log_channel.send(embed=embed) + + +@cog_i18n(_) +class User(MixinMeta): + @app_commands.command(name="referredby", description="Claim a referral") + @app_commands.describe(referred_by="The user who referred you") + @app_commands.guild_only() + async def referredby_slash(self, interaction: discord.Interaction, referred_by: discord.Member): + """Claim a referral + + Claim a referral from a user who referred you + + If referral rewards are enabled, you will receive the reward for being referred. + If referrer rewards are enabled, the person who referred you will also receive a reward. + """ + await interaction.response.defer() + ctx = await commands.Context.from_interaction(interaction) + async with ctx.channel.typing(): + await self.handle_referral_claim(ctx, referred_by) + + @commands.command() + @commands.guild_only() + @ensure_db_connection() + async def referredby(self, ctx: commands.Context, referred_by: discord.Member): + """Claim a referral + + Claim a referral from a user who referred you + + If referral rewards are enabled, you will receive the reward for being referred. + If referrer rewards are enabled, the person who referred you will also receive a reward. + """ + async with ctx.typing(): + await self.handle_referral_claim(ctx, referred_by) + + async def handle_referral_claim(self, ctx: commands.Context, referred_by: discord.Member): + if referred_by.id == ctx.author.id: + return await ctx.send(_("You can't refer yourself.")) + if referred_by.bot: + return await ctx.send(_("Bots can't refer users.")) + settings = await self.db_utils.get_create_guild(ctx.guild) + if not settings.enabled: + return await ctx.send(_("Referral rewards are not enabled for this server.")) + if ctx.author.joined_at: + if ctx.author.joined_at < referred_by.joined_at: + return await ctx.send(_("This user joined the server after you, they couldn't have referred you.")) + log_channel = self.bot.get_channel(settings.referral_channel) + if log_channel: + if not all( + [ + log_channel.permissions_for(ctx.guild.me).send_messages, + log_channel.permissions_for(ctx.guild.me).embed_links, + ] + ): + log_channel = None + + if not settings.referral_reward and not settings.referred_reward: + return await ctx.send(_("Referral rewards are not configured for this server.")) + if ctx.author.id in settings.initialized_users: + return await ctx.send(_("You were already a part of this server when the referral system was initialized.")) + account_age_minutes = int((datetime.now().astimezone() - ctx.author.created_at).total_seconds() / 60) + if settings.min_account_age_minutes and account_age_minutes < settings.min_account_age_minutes: + required_time = humanize_timedelta(seconds=settings.min_account_age_minutes * 60) + account_age_humanized = humanize_timedelta(seconds=account_age_minutes * 60) + if log_channel: + txt = _("Account age less than {} old. ({} old)").format( + f"`{required_time}`", f"`{account_age_humanized}`" + ) + embed = utils.referral_error( + referrer=referred_by, + referred=ctx.author, + error=txt, + channel=ctx.channel.mention, + ) + await log_channel.send(embed=embed) + return await ctx.send( + _("You must have an account age of at least {} to be eligible for referral rewards.").format( + f"**{required_time}**" + ) + ) + join_minutes = int((datetime.now().astimezone() - ctx.author.joined_at).total_seconds() / 60) + if settings.claim_timeout_minutes and join_minutes > settings.claim_timeout_minutes: + required_time = humanize_timedelta(seconds=settings.claim_timeout_minutes * 60) + in_server_time = humanize_timedelta(seconds=join_minutes * 60) + if log_channel: + txt = _("Missed claim timeout of {} after joining. ({} ago)").format( + f"`{required_time}`", f"`{in_server_time}`" + ) + embed = utils.referral_error( + referrer=referred_by, + referred=ctx.author, + error=txt, + channel=ctx.channel.mention, + ) + await log_channel.send(embed=embed) + return await ctx.send( + _("You joined the server too long ago to claim a referral reward. You must claim it within {}.").format( + f"**{required_time}**" + ) + ) + existing_referral = await Referral.objects().get( + (Referral.guild == ctx.guild.id) & (Referral.referred_id == ctx.author.id) + ) + if existing_referral: + referrer = await self.bot.fetch_user(existing_referral.referrer_id) + return await ctx.send(_("You have already been referred by {}.").format(f"**{referrer}**")) + + currency_name = await bank.get_currency_name(ctx.guild) + response = StringIO() + response.write(_("You have been referred by {}!\n").format(f"**{referred_by}**")) + if settings.referred_reward: + try: + await bank.deposit_credits(ctx.author, settings.referred_reward) + except BalanceTooHigh as e: + await bank.set_balance(ctx.author, e.max_balance) + txt = _("-# You have received {} as a reward for being referred!\n").format( + f"`{settings.referred_reward} {currency_name}`" + ) + response.write(txt) + + if settings.referral_reward: + try: + await bank.deposit_credits(referred_by, settings.referral_reward) + except BalanceTooHigh as e: + await bank.set_balance(referred_by, e.max_balance) + txt = _("-# {} has received {} as a reward for referring you!").format( + f"`{referred_by}`", f"`{settings.referral_reward} {currency_name}`" + ) + response.write(txt) + + referral = Referral( + guild=ctx.guild.id, + referred_id=ctx.author.id, + referrer_id=referred_by.id, + ) + await referral.save() + await ctx.send(response.getvalue()) + + if log_channel: + embed = utils.referral_embed( + referrer=referred_by, + referred=ctx.author, + referrer_reward=settings.referral_reward, + referred_reward=settings.referred_reward, + currency=currency_name, + channel=ctx.channel.mention, + ) + await log_channel.send(embed=embed) + + @commands.hybrid_command() + @commands.guild_only() + @ensure_db_connection() + async def myreferrals(self, ctx: commands.Context): + """Check your referrals""" + referrals = await Referral.objects().where( + (Referral.guild == ctx.guild.id) & (Referral.referrer_id == ctx.author.id) + ) + if not referrals: + return await ctx.send(_("You have not referred anyone yet.")) + color = await self.bot.get_embed_color(ctx) + embeds = [] + chunk_size = 5 + for idx, chunk in enumerate(utils.chunk(referrals, chunk_size)): + txt = StringIO() + for idxx, referral in enumerate(chunk, start=idx): + referred_user = await self.bot.get_or_fetch_user(referral.referred_id) + txt.write(f"{idxx + 1}. {referred_user}\n") + embed = discord.Embed(title=_("Your Referrals"), description=txt.getvalue(), color=color) + embed.set_footer(text=_("Page {}/{}").format(idx + 1, len(referrals))) + embeds.append(embed) + await DynamicMenu(ctx, embeds).refresh() + + @commands.hybrid_command() + @commands.guild_only() + @ensure_db_connection() + async def whoreferred(self, ctx: commands.Context, *, user: discord.Member): + """Check if a specific user has been referred""" + referral = await Referral.objects().get((Referral.guild == ctx.guild.id) & (Referral.referred_id == user.id)) + if not referral: + return await ctx.send(_("{} has not been referred by anyone.").format(user)) + + referrer = await self.bot.get_or_fetch_user(referral.referrer_id) + await ctx.send(_("{} was referred by {}.").format(user, referrer)) diff --git a/referrals/common/__init__.py b/referrals/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/referrals/common/checks.py b/referrals/common/checks.py new file mode 100644 index 0000000..8907521 --- /dev/null +++ b/referrals/common/checks.py @@ -0,0 +1,24 @@ + +from discord.ext.commands.core import check +from redbot.core import commands + + +def ensure_db_connection(): + """Decorator to ensure a database connection is active. + + Example: + ```python + @ensure_db_connection() + @commands.command() + async def mycommand(self, ctx): + await ctx.send("Database connection is active") + ``` + """ + + async def predicate(ctx: commands.Context) -> bool: + if not ctx.cog.db: + txt = "Database connection is not active, try again later" + raise commands.UserFeedbackCheckFailure(txt) + return True + + return check(predicate) diff --git a/referrals/common/utils.py b/referrals/common/utils.py new file mode 100644 index 0000000..8ad11b4 --- /dev/null +++ b/referrals/common/utils.py @@ -0,0 +1,62 @@ +from datetime import datetime + +import discord +from redbot.core.i18n import Translator + +_ = Translator("Referrals", __file__) + + +def chunk(obj_list: list, chunk_size: int): + for i in range(0, len(obj_list), chunk_size): + yield obj_list[i : i + chunk_size] + + +def referral_embed( + referrer: discord.Member, # The member who referred the user + referred: discord.Member, # The member who was referred + referrer_reward: int, # Reward for person who referred the user + referred_reward: int, # Reward for the person who was referred + currency: str, + channel: str, +): + referrer_name = f"{referrer.name} ({referrer.id})" + referred_name = f"{referred.name} ({referred.id})" + embed = discord.Embed(color=discord.Color.green(), timestamp=datetime.now()) + embed.add_field(name=_("Referred User"), value=referred_name, inline=False) + embed.add_field(name=_("Channel"), value=channel, inline=False) + if referred_reward: + embed.add_field( + name=_("Referred Reward"), + value=f"`{referred_reward}` {currency} -> {referred_name}", + inline=False, + ) + if referrer_reward: + embed.add_field( + name=_("Referrer Reward"), + value=f"`{referrer_reward}` {currency} -> {referrer_name}", + inline=False, + ) + embed.set_author(name=_("Referral Claimed"), icon_url=referred.display_avatar) + embed.set_footer(text=_("Referred by {}").format(referrer_name), icon_url=referrer.display_avatar) + return embed + + +def referral_error( + referrer: discord.Member, # The member who referred the user + referred: discord.Member, # The member who was referred + error: str, + channel: str, +): + referrer_name = f"{referrer.name} ({referrer.id})" + referred_name = f"{referred.name} ({referred.id})" + embed = discord.Embed(color=discord.Color.red(), timestamp=datetime.now()) + embed.add_field(name=_("Referred User"), value=referred_name, inline=False) + embed.add_field(name=_("Channel"), value=channel, inline=False) + embed.add_field( + name=_("Error"), + value=error, + inline=False, + ) + embed.set_author(name=_("Referral Error"), icon_url=referred.display_avatar) + embed.set_footer(text=_("Referred by {}").format(referrer_name), icon_url=referrer.display_avatar) + return embed diff --git a/referrals/db/__init__.py b/referrals/db/__init__.py new file mode 100644 index 0000000..0a9731d --- /dev/null +++ b/referrals/db/__init__.py @@ -0,0 +1,15 @@ +import sqlite3 + +from piccolo.engine import sqlite + + +### MONKEYPATCHING ### +# This is a workaround for a bug in Piccolo's SQLite engine where it doesn't handle integers correctly. +@sqlite.decode_to_string +def convert_int_out(value: str) -> int: + return int(value) + + +sqlite.CONVERTERS["INTEGER"] = convert_int_out +sqlite3.register_converter("INTEGER", convert_int_out) +### END MONKEYPATCHING ### diff --git a/referrals/db/migrations/referrals_2025_01_03t22_29_51_871945.py b/referrals/db/migrations/referrals_2025_01_03t22_29_51_871945.py new file mode 100644 index 0000000..908660e --- /dev/null +++ b/referrals/db/migrations/referrals_2025_01_03t22_29_51_871945.py @@ -0,0 +1,338 @@ +from piccolo.apps.migrations.auto.migration_manager import MigrationManager +from piccolo.columns.base import OnDelete, OnUpdate +from piccolo.columns.column_types import ( + Array, + BigInt, + Boolean, + ForeignKey, + Integer, + Timestamptz, +) +from piccolo.columns.defaults.timestamptz import TimestamptzNow +from piccolo.columns.indexes import IndexMethod +from piccolo.table import Table + + +class GuildSettings(Table, tablename="guild_settings", schema=None): + id = BigInt( + default=0, + null=False, + primary_key=True, + unique=False, + index=True, + index_method=IndexMethod.btree, + choices=None, + db_column_name=None, + secret=False, + ) + + +ID = "2025-01-03T22:29:51:871945" +VERSION = "1.13.0" +DESCRIPTION = "initial migration" + + +async def forwards(): + manager = MigrationManager( + migration_id=ID, app_name="referrals", description=DESCRIPTION + ) + + manager.add_table( + class_name="Referral", tablename="referral", schema=None, columns=None + ) + + manager.add_table( + class_name="GuildSettings", + tablename="guild_settings", + schema=None, + columns=None, + ) + + manager.add_column( + table_class_name="Referral", + tablename="referral", + column_name="guild", + db_column_name="guild", + column_class_name="ForeignKey", + column_class=ForeignKey, + params={ + "references": GuildSettings, + "on_delete": OnDelete.cascade, + "on_update": OnUpdate.cascade, + "target_column": None, + "null": True, + "primary_key": False, + "unique": False, + "index": True, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="Referral", + tablename="referral", + column_name="created_at", + db_column_name="created_at", + column_class_name="Timestamptz", + column_class=Timestamptz, + params={ + "default": TimestamptzNow(), + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="Referral", + tablename="referral", + column_name="referred_id", + db_column_name="referred_id", + column_class_name="BigInt", + column_class=BigInt, + params={ + "default": 0, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="Referral", + tablename="referral", + column_name="referrer_id", + db_column_name="referrer_id", + column_class_name="BigInt", + column_class=BigInt, + params={ + "default": 0, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="id", + db_column_name="id", + column_class_name="BigInt", + column_class=BigInt, + params={ + "default": 0, + "null": False, + "primary_key": True, + "unique": False, + "index": True, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="created_at", + db_column_name="created_at", + column_class_name="Timestamptz", + column_class=Timestamptz, + params={ + "default": TimestamptzNow(), + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="enabled", + db_column_name="enabled", + column_class_name="Boolean", + column_class=Boolean, + params={ + "default": False, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="referral_reward", + db_column_name="referral_reward", + column_class_name="Integer", + column_class=Integer, + params={ + "default": 0, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="referred_reward", + db_column_name="referred_reward", + column_class_name="Integer", + column_class=Integer, + params={ + "default": 0, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="referral_channel", + db_column_name="referral_channel", + column_class_name="Integer", + column_class=Integer, + params={ + "default": 0, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="min_account_age_minutes", + db_column_name="min_account_age_minutes", + column_class_name="Integer", + column_class=Integer, + params={ + "default": 0, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="claim_timeout_minutes", + db_column_name="claim_timeout_minutes", + column_class_name="Integer", + column_class=Integer, + params={ + "default": 0, + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + manager.add_column( + table_class_name="GuildSettings", + tablename="guild_settings", + column_name="initialized_users", + db_column_name="initialized_users", + column_class_name="Array", + column_class=Array, + params={ + "base_column": BigInt( + default=0, + null=False, + primary_key=False, + unique=False, + index=False, + index_method=IndexMethod.btree, + choices=None, + db_column_name=None, + secret=False, + ), + "default": [], + "null": False, + "primary_key": False, + "unique": False, + "index": False, + "index_method": IndexMethod.btree, + "choices": None, + "db_column_name": None, + "secret": False, + }, + schema=None, + ) + + return manager diff --git a/referrals/db/piccolo_app.py b/referrals/db/piccolo_app.py new file mode 100644 index 0000000..7be27e1 --- /dev/null +++ b/referrals/db/piccolo_app.py @@ -0,0 +1,11 @@ +import os + +from piccolo.conf.apps import AppConfig, table_finder + +CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) + +APP_CONFIG = AppConfig( + app_name=os.getenv("APP_NAME"), + table_classes=table_finder(["db.tables"]), + migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "migrations"), +) diff --git a/referrals/db/piccolo_conf.py b/referrals/db/piccolo_conf.py new file mode 100644 index 0000000..a6915bc --- /dev/null +++ b/referrals/db/piccolo_conf.py @@ -0,0 +1,8 @@ +import os + +from piccolo.conf.apps import AppRegistry +from piccolo.engine.sqlite import SQLiteEngine + +DB = SQLiteEngine(path=os.getenv("DB_PATH")) + +APP_REGISTRY = AppRegistry(apps=["db.piccolo_app"]) diff --git a/referrals/db/tables.py b/referrals/db/tables.py new file mode 100644 index 0000000..1227a2c --- /dev/null +++ b/referrals/db/tables.py @@ -0,0 +1,41 @@ + +from piccolo.columns import ( + Array, + BigInt, + Boolean, + ForeignKey, + Integer, + Serial, + Timestamptz, +) +from piccolo.table import Table, sort_table_classes +from redbot.core.i18n import Translator + +_ = Translator("Referrals", __file__) + + +class GuildSettings(Table): + id = BigInt(primary_key=True, index=True, help_text="Guild ID") + created_at = Timestamptz() + enabled = Boolean(default=False) + referral_reward = Integer(help_text="Reward sent to the person who referred someone") + referred_reward = Integer(help_text="Reward sent to the person who was referred") + referral_channel = Integer(help_text="Channel where referral messages are sent") + min_account_age_minutes = Integer(help_text="Minimum account age to be eligible for rewards") + claim_timeout_minutes = Integer(help_text="User must claim their reward within this time frame after joining") + initialized_users = Array(BigInt(), default=[]) + + +class Referral(Table): + id: Serial + guild = ForeignKey(references=GuildSettings, index=True) + created_at = Timestamptz() + + # The person who was referred, there should only be one of these per server + referred_id = BigInt(help_text="ID of the person who was referred") + + # There can be multiple entries with the same referrer_id + referrer_id = BigInt(help_text="ID of the person who referred someone") + + +TABLES: list[Table] = sort_table_classes([GuildSettings, Referral]) diff --git a/referrals/db/utils.py b/referrals/db/utils.py new file mode 100644 index 0000000..96c9a50 --- /dev/null +++ b/referrals/db/utils.py @@ -0,0 +1,12 @@ +import discord + +from ..db.tables import GuildSettings + + +class DBUtils: + async def get_create_guild(self, guild: discord.Guild | int) -> GuildSettings: + guild_id = guild.id if isinstance(guild, discord.Guild) else guild + settings = await GuildSettings.objects().get_or_create( + where=(GuildSettings.id == guild_id), defaults={GuildSettings.id: guild_id} + ) + return settings diff --git a/referrals/engine/__init__.py b/referrals/engine/__init__.py new file mode 100644 index 0000000..b5cefbc --- /dev/null +++ b/referrals/engine/__init__.py @@ -0,0 +1,11 @@ +from .engine import diagnose_issues, register_cog, reverse_migration, run_migrations +from .errors import DirectoryError, UNCPathError + +__all__ = [ + "DirectoryError", + "UNCPathError", + "diagnose_issues", + "register_cog", + "reverse_migration", + "run_migrations", +] diff --git a/referrals/engine/common.py b/referrals/engine/common.py new file mode 100644 index 0000000..9e2c1e7 --- /dev/null +++ b/referrals/engine/common.py @@ -0,0 +1,93 @@ +import asyncio +import inspect +import os +import subprocess +import sys +from pathlib import Path + +from discord.ext import commands +from redbot.core.data_manager import cog_data_path + + +def get_root(cog_instance: commands.Cog | Path) -> Path: + """Get the root path of the cog""" + if isinstance(cog_instance, Path): + return cog_instance + return Path(inspect.getfile(cog_instance.__class__)).parent + + +def is_unc_path(path: Path) -> bool: + """Check if path is a UNC path""" + return path.is_absolute() and str(path).startswith(r"\\\\") + + +def is_windows() -> bool: + """Check if the OS is Windows""" + return os.name == "nt" + + +def find_piccolo_executable() -> Path: + """Find the piccolo executable in the system's PATH.""" + for path in os.environ["PATH"].split(os.pathsep): + for executable_name in ["piccolo", "piccolo.exe"]: + executable = Path(path) / executable_name + if executable.exists(): + return executable + + # Fetch the lib path from downloader + lib_path = cog_data_path(raw_name="Downloader") / "lib" + if lib_path.exists(): + for folder in lib_path.iterdir(): + for executable_name in ["piccolo", "piccolo.exe"]: + executable = folder / executable_name + if executable.exists(): + return executable + + default_path = Path(sys.executable).parent / "piccolo" + if default_path.exists(): + return default_path + + raise FileNotFoundError("Piccolo package not found!") + + +def get_env(cog_instance: commands.Cog | Path, postgres_config: dict = None) -> dict: + """Create mock environment for subprocess""" + env = os.environ.copy() + if "PICCOLO_CONF" not in env: + # Dont want to overwrite the user's config + env["PICCOLO_CONF"] = "db.piccolo_conf" + env["APP_NAME"] = get_root(cog_instance).stem + if isinstance(cog_instance, Path): + env["DB_PATH"] = str(cog_instance / "db.sqlite") + else: + env["DB_PATH"] = str(cog_data_path(cog_instance) / "db.sqlite") + if is_windows(): + env["PYTHONIOENCODING"] = "utf-8" + if postgres_config is not None: + env["POSTGRES_USER"] = postgres_config.get("user", "postgres") + env["POSTGRES_PASSWORD"] = postgres_config.get("password", "postgres") + env["POSTGRES_DATABASE"] = postgres_config.get("database", "postgres") + env["POSTGRES_HOST"] = postgres_config.get("host", "localhost") + env["POSTGRES_PORT"] = postgres_config.get("port", "5432") + return env + + +async def run_shell( + cog_instance: commands.Cog | Path, + commands: list[str], + is_shell: bool, +) -> str: + """Run a shell command in a separate thread""" + + def _exe() -> str: + res = subprocess.run( + commands, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=is_shell, + cwd=str(get_root(cog_instance)), + env=get_env(cog_instance), + ) + return res.stdout.decode(encoding="utf-8", errors="ignore").replace("👍", "!") + + return await asyncio.to_thread(_exe) diff --git a/referrals/engine/engine.py b/referrals/engine/engine.py new file mode 100644 index 0000000..40f5c2e --- /dev/null +++ b/referrals/engine/engine.py @@ -0,0 +1,169 @@ +import logging +from pathlib import Path + +from discord.ext import commands +from piccolo.engine.sqlite import SQLiteEngine +from piccolo.table import Table +from redbot.core.data_manager import cog_data_path + +from .common import find_piccolo_executable, get_root, is_unc_path, run_shell +from .errors import DirectoryError, UNCPathError + +log = logging.getLogger("red.vrt.referrals.engine") + + +async def register_cog( + cog_instance: commands.Cog | Path, + tables: list[type[Table]], + *, + trace: bool = False, + skip_migrations: bool = False, +) -> SQLiteEngine: + """Registers a Discord cog with a database connection and runs migrations. + + Args: + cog_instance (commands.Cog | Path): The instance/path of the cog to register. + tables (list[type[Table]]): List of Piccolo Table classes to associate with the database engine. + trace (bool, optional): Whether to enable tracing for migrations. Defaults to False. + skip_migrations (bool, optional): Whether to skip running migrations. Defaults to False. + + Raises: + TypeError: If the cog instance is not a subclass of discord.ext.commands.Cog or a valid directory path. + UNCPathError: If the cog path is a UNC path, which is not supported. + DirectoryError: If the cog files are not in a valid directory. + + Returns: + SQLiteEngine: The database engine associated with the registered cog. + """ + if isinstance(cog_instance, commands.Cog): + save_path = cog_data_path(cog_instance) + elif cog_instance.is_dir(): + save_path = cog_instance + else: + # Must be a cog instance or directory + raise TypeError(f"Invalid cog instance: {cog_instance}, must be a cog or directory path") + if is_unc_path(save_path): + raise UNCPathError(f"UNC paths are not supported, please move the cog's location: {save_path}") + if not save_path.is_dir(): + raise DirectoryError(f"Cog files are not in a valid directory: {save_path}") + + if not skip_migrations: + log.info("Running migrations, if any") + result = await run_migrations(cog_instance, trace) + if "No migrations need to be run" in result: + log.info("No migrations needed!") + else: + log.info(f"Migration result...\n{result}") + if "Traceback" in result: + diagnoses = await diagnose_issues(cog_instance) + log.error(diagnoses + "\nOne or more migrations failed to run!") + + log.debug("Fetching database engine") + db = SQLiteEngine(path=str(save_path / "db.sqlite")) + for table_class in tables: + table_class._meta.db = db + return db + + +async def run_migrations( + cog_instance: commands.Cog | Path, + trace: bool = False, +) -> str: + """Runs database migrations for the cog + + Args: + cog_instance (commands.Cog | Path): The instance of the cog for which to run migrations. + trace (bool, optional): Whether to enable tracing for migrations. Defaults to False. + + Returns: + str: The result of the migration process, including any output messages. + """ + commands = [ + str(find_piccolo_executable()), + "migrations", + "forwards", + get_root(cog_instance).stem, + ] + if trace: + commands.append("--trace") + return await run_shell(cog_instance, commands, False) + + +async def reverse_migration( + cog_instance: commands.Cog | Path, + timestamp: str, + trace: bool = False, +) -> str: + """Reverses database migrations for the cog + + Args: + cog_instance (commands.Cog | Path): The instance of the cog for which to reverse the migration. + timestamp (str): The timestamp of the migration to reverse to. + trace (bool, optional): Whether to enable tracing for migrations. Defaults to False. + + Returns: + str: The result of the migration process, including any output messages. + """ + commands = [ + str(find_piccolo_executable()), + "migrations", + "backwards", + get_root(cog_instance).stem, + timestamp, + ] + if trace: + commands.append("--trace") + return await run_shell(cog_instance, commands, False) + + +async def create_migrations( + cog_instance: commands.Cog | Path, + trace: bool = False, + description: str = None, +) -> str: + """Creates new database migrations for the cog + + THIS SHOULD BE RUN MANUALLY! + + Args: + cog_instance (commands.Cog | Path): The instance of the cog to create migrations for. + name (str): The name of the migration to create. + + Returns: + str: The result of the migration process, including any output messages. + """ + commands = [ + str(find_piccolo_executable()), + "migrations", + "new", + get_root(cog_instance).stem, + "--auto", + ] + if trace: + commands.append("--trace") + if description is not None: + commands.append(f"--desc={description}") + return await run_shell(cog_instance, commands, True) + + +async def diagnose_issues(cog_instance: commands.Cog | Path) -> str: + """Diagnose issues with the cog's database connection + + Args: + cog_instance (commands.Cog | Path): The instance of the cog to diagnose. + + Returns: + str: The result of the diagnosis process, including any output messages. + """ + piccolo_path = find_piccolo_executable() + diagnoses = await run_shell( + cog_instance, + [str(piccolo_path), "--diagnose"], + False, + ) + check = await run_shell( + cog_instance, + [str(piccolo_path), "migrations", "check"], + False, + ) + return f"{diagnoses}\n{check}" diff --git a/referrals/engine/errors.py b/referrals/engine/errors.py new file mode 100644 index 0000000..94db7af --- /dev/null +++ b/referrals/engine/errors.py @@ -0,0 +1,6 @@ +class UNCPathError(Exception): + message: str + + +class DirectoryError(Exception): + message: str diff --git a/referrals/info.json b/referrals/info.json new file mode 100644 index 0000000..fcf03de --- /dev/null +++ b/referrals/info.json @@ -0,0 +1,16 @@ +{ + "author": ["Vertyco"], + "description": "Incentivise users to invite others to your server with a referral system! Configure rewards for both the inviter and the invitee, and track how many people each user has invited.", + "disabled": false, + "end_user_data_statement": "This cog stores Discord IDs to track referrals. It does not store any other personal data.", + "hidden": false, + "install_msg": "Thank you for installing Referrals! Type `[p]help Referrals` to get started.", + "min_bot_version": "3.5.3", + "min_python_version": [3, 10, 0], + "permissions": [], + "required_cogs": {}, + "requirements": ["piccolo", "piccolo[sqlite]", "aiosqlite"], + "short": "Simple referral system with economy", + "tags": ["referrals", "referral", "refer", "invites", "economy", "currency", "credits"], + "type": "COG" +} diff --git a/referrals/main.py b/referrals/main.py new file mode 100644 index 0000000..793778e --- /dev/null +++ b/referrals/main.py @@ -0,0 +1,55 @@ +import asyncio +import logging +import typing as t + +from piccolo.engine.sqlite import SQLiteEngine +from redbot.core import commands +from redbot.core.bot import Red +from redbot.core.i18n import Translator + +from .abc import CompositeMetaClass +from .commands import Commands +from .db.tables import TABLES +from .db.utils import DBUtils +from .engine import engine + +log = logging.getLogger("red.vrt.referrals") +RequestType = t.Literal["discord_deleted_user", "owner", "user", "user_strict"] +_ = Translator("Referrals", __file__) + + +class Referrals(Commands, commands.Cog, metaclass=CompositeMetaClass): + """Simple referral system for Discord servers.""" + + __author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)" + __version__ = "0.0.8b" + + def __init__(self, bot: Red): + super().__init__() + self.bot: Red = bot + self.db: SQLiteEngine = None + self.db_utils: DBUtils = DBUtils() + + def format_help_for_context(self, ctx: commands.Context): + helpcmd = super().format_help_for_context(ctx) + txt = "Version: {}\n\nAuthor: {}".format(self.__version__, self.__author__) + return f"{helpcmd}\n\n{txt}" + + async def red_get_data_for_user(self, *, user_id: int): + pass + + async def red_delete_data_for_user(self, *, requester: RequestType, user_id: int): + # Nothing to delete, saved referrals are required for the appeal system to function + pass + + async def cog_load(self) -> None: + asyncio.create_task(self.initialize()) + + async def cog_unload(self) -> None: + pass + + async def initialize(self) -> None: + await self.bot.wait_until_red_ready() + logging.getLogger("aiosqlite").setLevel(logging.INFO) + self.db = await engine.register_cog(self, TABLES, trace=True) + log.info("Cog initialized") diff --git a/referrals/views/__init__.py b/referrals/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/referrals/views/dynamic_menu.py b/referrals/views/dynamic_menu.py new file mode 100644 index 0000000..e2ad463 --- /dev/null +++ b/referrals/views/dynamic_menu.py @@ -0,0 +1,287 @@ +import asyncio +import logging +import typing as t +from contextlib import suppress +from io import BytesIO + +import discord +from rapidfuzz import fuzz +from redbot.core import commands + +log = logging.getLogger("red.vrt.referrals.dynamic_menu") + + +class SearchModal(discord.ui.Modal): + def __init__(self, current: t.Optional[str] = None): + super().__init__(title="Search", timeout=240) + self.query = current + self.input = discord.ui.TextInput(label="Enter Search Query or Page", default=current) + self.add_item(self.input) + + async def on_submit(self, interaction: discord.Interaction): + self.query = self.input.value + await interaction.response.defer() + self.stop() + + +class DynamicMenu(discord.ui.View): + def __init__( + self, + ctx: commands.Context, + pages: t.Union[t.List[discord.Embed], t.List[str]], + message: t.Optional[t.Union[discord.Message, discord.InteractionMessage, None]] = None, + page: int = 0, + timeout: t.Union[int, float, None] = 300, + image_bytes: t.Optional[bytes] = None, + ): + super().__init__(timeout=timeout) + self.check_pages(pages) # Modifies pages in place + + self.ctx = ctx + self.author = ctx.author + self.channel = ctx.channel + self.guild = ctx.guild + self.pages = pages + self.message = message + self.page = page + self.image_bytes = image_bytes + self.page_count = len(pages) + + def check_pages(self, pages: t.List[t.Union[discord.Embed, str]]): + # Ensure pages are either all embeds or all strings + if isinstance(pages[0], discord.Embed): + if not all(isinstance(page, discord.Embed) for page in pages): + raise TypeError("All pages must be Embeds or strings.") + # If the first page has no footer, add one to all pages for page number + if pages[0].footer: + return + page_count = len(pages) + for idx in range(len(pages)): + pages[idx].set_footer(text=f"Page {idx + 1}/{page_count}") + else: + if not all(isinstance(page, str) for page in pages): + raise TypeError("All pages must be Embeds or strings.") + + async def interaction_check(self, interaction: discord.Interaction): + 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: + if self.message: + with suppress(discord.NotFound, discord.Forbidden, discord.HTTPException): + await self.message.edit(view=None) + await self.ctx.tick() + + async def refresh(self, interaction: discord.Interaction = None): + """Call this to start and refresh the menu.""" + try: + await self.__refresh(interaction) + except Exception as e: + current_page = self.pages[self.page] + if isinstance(current_page, discord.Embed): + content = current_page.description or current_page.title + if not content: + content = "" + for field in current_page.fields: + content += f"{field.name}\n{field.value}\n" + else: + content = current_page + log.error(f"Error refreshing menu, current page: {content}", exc_info=e) + + async def __refresh(self, interaction: discord.Interaction = None): + self.clear_items() + single = [self.close] + small = [self.left] + single + [self.right] + large = small + [self.left10, self.search, self.right10] + + buttons = large if self.page_count > 10 else small if self.page_count > 1 else single + for button in buttons: + self.add_item(button) + + if len(buttons) == 1 and isinstance(self.pages[self.page], discord.Embed): + for embed in self.pages: + embed.set_footer(text=None) + + attachments = [] + file = None + if self.image_bytes: + file = discord.File(BytesIO(self.image_bytes), filename="image.webp") + attachments.append(file) + + kwargs = {"view": self} + if isinstance(self.pages[self.page], discord.Embed): + kwargs["embed"] = self.pages[self.page] + kwargs["content"] = None + else: + kwargs["content"] = self.pages[self.page] + + if (self.message or interaction) and attachments: + kwargs["attachments"] = attachments + elif (not self.message and not interaction) and file: + kwargs["file"] = file # Need to send new message + + if interaction and self.message is not None: + # We are refreshing due to a button press + if not interaction.response.is_done(): + try: + await interaction.response.edit_message(**kwargs) + return self + except discord.HTTPException: + try: + await self.message.edit(**kwargs) + except discord.HTTPException: + kwargs.pop("attachments", None) + kwargs["file"] = file + self.message = await self.ctx.send(**kwargs) + else: + try: + await interaction.edit_original_response(**kwargs) + return self + except discord.HTTPException: + try: + await self.message.edit(**kwargs) + except discord.HTTPException: + kwargs.pop("attachments", None) + kwargs["file"] = file + self.message = await self.ctx.send(**kwargs) + return self + + if self.message: + try: + await self.message.edit(**kwargs) + except discord.HTTPException: + kwargs.pop("attachments", None) + kwargs["file"] = file + self.message = await self.ctx.send(**kwargs) + else: + self.message = await self.ctx.send(**kwargs) + return self + + @discord.ui.button( + emoji="\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}", + style=discord.ButtonStyle.primary, + row=1, + ) + async def left10(self, interaction: discord.Interaction, button: discord.ui.Button): + self.page -= 10 + self.page %= self.page_count + await self.refresh(interaction) + + @discord.ui.button( + emoji="\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}", + style=discord.ButtonStyle.primary, + ) + async def left(self, interaction: discord.Interaction, button: discord.ui.Button): + self.page -= 1 + self.page %= self.page_count + await self.refresh(interaction) + + @discord.ui.button( + emoji="\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}", + style=discord.ButtonStyle.danger, + ) + async def close(self, interaction: discord.Interaction, button: discord.ui.Button): + with suppress(discord.HTTPException): + await interaction.response.defer() + try: + await interaction.delete_original_response() + except discord.HTTPException: + if self.message: + with suppress(discord.HTTPException): + await self.message.delete() + self.stop() + + @discord.ui.button( + emoji="\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}", + style=discord.ButtonStyle.primary, + ) + async def right(self, interaction: discord.Interaction, button: discord.ui.Button): + self.page += 1 + self.page %= self.page_count + await self.refresh(interaction) + + @discord.ui.button( + emoji="\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}", + style=discord.ButtonStyle.primary, + row=1, + ) + async def right10(self, interaction: discord.Interaction, button: discord.ui.Button): + self.page += 10 + self.page %= self.page_count + await self.refresh(interaction) + + @discord.ui.button( + emoji="\N{LEFT-POINTING MAGNIFYING GLASS}", + style=discord.ButtonStyle.secondary, + row=1, + ) + async def search(self, interaction: discord.Interaction, button: discord.ui.Button): + modal = SearchModal(str(self.page + 1)) + await interaction.response.send_modal(modal) + await modal.wait() + + if modal.query is None: + return + + if modal.query.isnumeric(): + self.page = int(modal.query) - 1 + self.page %= self.page_count + return await self.refresh(interaction) + + if isinstance(self.pages[self.page], str): + for i, page in enumerate(self.pages): + if modal.query.casefold() in page.casefold(): + self.page = i + return await self.refresh(interaction) + with suppress(discord.HTTPException): + await interaction.followup.send("No page found matching that query.", ephemeral=True) + return + + # Pages are embeds + for i, embed in enumerate(self.pages): + if embed.title and modal.query.casefold() in embed.title.casefold(): + self.page = i + return await self.refresh(interaction) + if modal.query.casefold() in embed.description.casefold(): + self.page = i + return await self.refresh(interaction) + if embed.footer and modal.query.casefold() in embed.footer.text.casefold(): + self.page = i + return await self.refresh(interaction) + for field in embed.fields: + if modal.query.casefold() in field.name.casefold(): + self.page = i + return await self.refresh(interaction) + if modal.query.casefold() in field.value.casefold(): + self.page = i + return await self.refresh(interaction) + + # No results found, resort to fuzzy matching + def _fuzzymatch() -> list[tuple[int, int]]: + # [(match, index)] + matches: list[tuple[int, int]] = [] + for i, embed in enumerate(self.pages): + matches.append((fuzz.ratio(modal.query.lower(), embed.title.lower()), i)) + matches.append((fuzz.ratio(modal.query.lower(), embed.description.lower()), i)) + if embed.footer: + matches.append((fuzz.ratio(modal.query.lower(), embed.footer.text.lower()), i)) + for field in embed.fields: + matches.append((fuzz.ratio(modal.query.lower(), field.name.lower()), i)) + matches.append((fuzz.ratio(modal.query.lower(), field.value.lower()), i)) + if matches: + matches.sort(key=lambda x: x[0], reverse=True) + return matches + + matches = await asyncio.to_thread(_fuzzymatch) + + # Sort by best match + best_score, best_index = matches[0] + if best_score < 50: + with suppress(discord.HTTPException): + await interaction.followup.send("No page found matching that query.", ephemeral=True) + return + self.page = best_index + await self.refresh(interaction) + await interaction.followup.send("Found closest match of {}%".format(int(best_score)), ephemeral=True) diff --git a/rps/__init__.py b/rps/__init__.py new file mode 100644 index 0000000..518edc6 --- /dev/null +++ b/rps/__init__.py @@ -0,0 +1,25 @@ +import json +from asyncio import create_task +from pathlib import Path + +from .rps import RPS + +with open(Path(__file__).parent / "info.json") as fp: + __red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"] + +old_rps = None + +async def setup(bot): + global old_rps + old_rps = bot.get_command("rps") + if old_rps: + bot.remove_command(old_rps.name) + + cog = RPS(bot) + await bot.add_cog(cog) + +async def teardown(bot): + global old_rps + if old_rps: + bot.remove_command("rps") + bot.add_command(old_rps) diff --git a/rps/duelview.py b/rps/duelview.py new file mode 100644 index 0000000..a114f85 --- /dev/null +++ b/rps/duelview.py @@ -0,0 +1,65 @@ +import discord +from redbot.core.i18n import Translator, set_contextual_locales_from_guild + +from .vars import Result, RPSLSChoice, RPSLSIcon + +from .playerview import PlayerView + +_ = Translator("RPS", __file__) + + +class DuelView(discord.ui.View): + def __init__(self, cog, _id: int): + super().__init__(timeout=600.0) + self.cog = cog + self._id = _id + self.value = None + + async def interaction_check(self, interaction: discord.Interaction, /) -> bool: + if ( + self.cog.games[self._id]["p1"].id == interaction.user.id + or self.cog.games[self._id]["p2"].id == interaction.user.id + ): + return True + else: + await set_contextual_locales_from_guild(self.bot, self.games[id]["original"].guild) + await interaction.response.send_message( + content=_("Sorry, but you are not in this duel"), ephemeral=True + ) + return False + + @discord.ui.button( + label="Accept", + style=discord.ButtonStyle.green, + custom_id="duelaccept", + emoji="\N{HEAVY CHECK MARK}\N{VARIATION SELECTOR-16}", + row=0, + ) + async def duelaccept(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user.id == self.cog.games[self._id]["p1"].id: + await set_contextual_locales_from_guild(self.bot, self.games[id]["original"].guild) + await interaction.response.send_message( + _("You have already accepted it by starting the duel."), ephemeral=True + ) + + view = PlayerView(self.cog, self._id, interaction.user) + if self.cog[self._id]["gametype"] == "RPS": + view = view.remove_item(view.children[3]).remove_item(view.children[3]) + + await interaction.response.send_message(view=view, ephemeral=True) + + @discord.ui.button( + label="Decline", + style=discord.ButtonStyle.red, + custom_id="dueldecline", + emoji="\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}", + row=0, + ) + async def dueldecline(self, interaction: discord.Interaction, button: discord.ui.Button): + + await interaction.response.defer() + await set_contextual_locales_from_guild(self.bot, self.games[id]["original"].guild) + await self.cog.games[self._id]["original"].edit_original_response( + content=_("Duel has been rejected"), embed=None, view=None + ) + del self.cog.games[self._id] diff --git a/rps/info.json b/rps/info.json new file mode 100644 index 0000000..6b676ff --- /dev/null +++ b/rps/info.json @@ -0,0 +1,23 @@ +{ + "name": "RPS", + "short": "Rock, Paper, Scissors (Lizard, Spock)", + "description": "Play a game of Rock, Paper, Scissors (Lizard, Spock); but with Buttons!", + "end_user_data_statement": "This cog does not persistently store any data or metadata about users.", + "install_msg": "Thanks for installing the cog. I'm in the Cog Support server if you need help with anything. This cog will gracefully handle removing the `rps` command from Red's General cog if it is loaded. Loading General after this cog will cause an error (bot startup is gracefully handled as well)", + "author": [ + "YamiKaitou#8975" + ], + "required_cogs": {}, + "tags": [ + "game", + "fun", + "rps", + "rpsls", + "fun", + "buttons" + ], + "min_bot_version": "3.5.0", + "hidden": false, + "disabled": false, + "type": "COG" +} diff --git a/rps/locales/ar-SA.po b/rps/locales/ar-SA.po new file mode 100644 index 0000000..9a2bad3 --- /dev/null +++ b/rps/locales/ar-SA.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: ar_SA\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/bg-BG.po b/rps/locales/bg-BG.po new file mode 100644 index 0000000..4fa6171 --- /dev/null +++ b/rps/locales/bg-BG.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: bg_BG\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/cs-CZ.po b/rps/locales/cs-CZ.po new file mode 100644 index 0000000..025dce5 --- /dev/null +++ b/rps/locales/cs-CZ.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: cs_CZ\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/da-DK.po b/rps/locales/da-DK.po new file mode 100644 index 0000000..58690f9 --- /dev/null +++ b/rps/locales/da-DK.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: da_DK\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/de-DE.po b/rps/locales/de-DE.po new file mode 100644 index 0000000..61a85bf --- /dev/null +++ b/rps/locales/de-DE.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: de_DE\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/es-ES.po b/rps/locales/es-ES.po new file mode 100644 index 0000000..244e075 --- /dev/null +++ b/rps/locales/es-ES.po @@ -0,0 +1,179 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: es_ES\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "Muy bien, quizás más tarde" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "Piedra, Papel, Tijeras" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "Un juego de habilidad (azar).\n" +"Simplemente selecciona tu opción y mira si puedes vencer al bot\n\n\n" +"Piedra {ROCK} gana a Tijeras {SCISSORS}\n" +"Tijeras {SCISSORS} gana a Papel {PAPER}\n" +"Papel {PAPER} gana a Piedra {ROCK}\n" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "Piedra, Papel, Tijeras, Lagarto, Spock" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "Un juego de habilidad (azar).\n" +"Simplemente selecciona tu opción y mira si puedes vencer al bot\n\n\n" +"Piedra {ROCK} gana a Tijeras {SCISSORS} y Lagarto {LIZARD}\n" +"Papel {PAPER} gana a Piedra {ROCK} y Spock {SPOCK}\n" +"Tijeras {SCISSORS} gana a Papel {PAPER} y Lagarto {LIZARD}\n" +"Lagarto {LIZARD} gana a Papel {PAPER} y Spock {SPOCK}\n" +"Spock {SPOCK} gana a Piedra {ROCK} y Tijeras {SCISSORS}\n" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "\n" +" Piedra, Papel, Tijeras (Lagarto, Spock)\n\n" +" Documentación más detallada: \n" +" " + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "Reglas de Piedra, Papel, Tijeras (Lagarto, Spock)" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "Un juego de habilidad (azar).\n" +"Simplemente selecciona tu opción y mira si puedes vencer al bot\n\n" +"Se incluyen 2 versiones, las reglas están abajo\n" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "Piedra {ROCK} gana a Tijeras {SCISSORS}\n" +"Tijeras {SCISSORS} gana a Papel {PAPER}\n" +"Papel {PAPER} gana a Piedra {ROCK}\n\n" +"Juega con `{PREFIX}rps`\n" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "Piedra {ROCK} gana a Tijeras {SCISSORS} y Lagarto {LIZARD}\n" +"Papel {PAPER} gana a Piedra {ROCK} y Spock {SPOCK}\n" +"Tijeras {SCISSORS} gana a Papel {PAPER} y Lagarto {LIZARD}\n" +"Lagarto {LIZARD} gana a Papel {PAPER} y Spock {SPOCK}\n" +"Spock {SPOCK} gana a Piedra {ROCK} y Tijeras {SCISSORS}\n\n" +"Juega con `{PREFIX}rpsls`\n" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "Juega una partida de Piedra, Papel, Tijeras" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "Juega una partida de Piedra, Papel, Tijeras, Lagarto, Spock" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "Bueno, esto es embarazoso. No tengo ni idea de qué pasa ahora" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "¡Felicidades, tú ganas!\n\n" +"Tú {player_icon} - {computer_icon} Yo" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "¡Mira eso, yo gano!\n\n" +"Tú {player_icon} - {computer_icon} Yo" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "¡Bueno, debemos de leernos la mente!\n\n" +"Tú {player_icon} - {computer_icon} Yo" + diff --git a/rps/locales/fi-FI.po b/rps/locales/fi-FI.po new file mode 100644 index 0000000..f0a7ca1 --- /dev/null +++ b/rps/locales/fi-FI.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: fi_FI\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/fr-FR.po b/rps/locales/fr-FR.po new file mode 100644 index 0000000..0df7428 --- /dev/null +++ b/rps/locales/fr-FR.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: fr_FR\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/hi-IN.po b/rps/locales/hi-IN.po new file mode 100644 index 0000000..d300334 --- /dev/null +++ b/rps/locales/hi-IN.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: hi_IN\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/hr-HR.po b/rps/locales/hr-HR.po new file mode 100644 index 0000000..e881571 --- /dev/null +++ b/rps/locales/hr-HR.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: hr_HR\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/hu-HU.po b/rps/locales/hu-HU.po new file mode 100644 index 0000000..f2e3980 --- /dev/null +++ b/rps/locales/hu-HU.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: hu_HU\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/id-ID.po b/rps/locales/id-ID.po new file mode 100644 index 0000000..4c127f0 --- /dev/null +++ b/rps/locales/id-ID.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: id_ID\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/it-IT.po b/rps/locales/it-IT.po new file mode 100644 index 0000000..0caa897 --- /dev/null +++ b/rps/locales/it-IT.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: it_IT\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/ja-JP.po b/rps/locales/ja-JP.po new file mode 100644 index 0000000..d2207f9 --- /dev/null +++ b/rps/locales/ja-JP.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: ja_JP\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/ko-KR.po b/rps/locales/ko-KR.po new file mode 100644 index 0000000..570cc46 --- /dev/null +++ b/rps/locales/ko-KR.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: ko_KR\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/messages.pot b/rps/locales/messages.pot new file mode 100644 index 0000000..5448ab0 --- /dev/null +++ b/rps/locales/messages.pot @@ -0,0 +1,172 @@ +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \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" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "" +"A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n" +"\n" +"\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "" +"A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n" +"\n" +"\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "" +"\n" +" Rock, Paper, Scissors (Lizard, Spock)\n" +"\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "" +"A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n" +"\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +"\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +"\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "" +"{player1_mention} wins!!\n" +"\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "" +"{player2_mention} wins!!\n" +"\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "" +"Well, we must be mind-readers!\n" +"\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "" +"Congrats, you win!\n" +"\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "" +"Look at that, I win!\n" +"\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "" +"Well, we must be mind-readers!\n" +"\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" diff --git a/rps/locales/nb-NO.po b/rps/locales/nb-NO.po new file mode 100644 index 0000000..af6bd64 --- /dev/null +++ b/rps/locales/nb-NO.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: nb_NO\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/nl-NL.po b/rps/locales/nl-NL.po new file mode 100644 index 0000000..dcacd5f --- /dev/null +++ b/rps/locales/nl-NL.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: nl_NL\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/pl-PL.po b/rps/locales/pl-PL.po new file mode 100644 index 0000000..2f068c4 --- /dev/null +++ b/rps/locales/pl-PL.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: pl_PL\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/pt-BR.po b/rps/locales/pt-BR.po new file mode 100644 index 0000000..15ececb --- /dev/null +++ b/rps/locales/pt-BR.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: pt_BR\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/pt-PT.po b/rps/locales/pt-PT.po new file mode 100644 index 0000000..fcc7777 --- /dev/null +++ b/rps/locales/pt-PT.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: pt_PT\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/ru-RU.po b/rps/locales/ru-RU.po new file mode 100644 index 0000000..0a7fb7c --- /dev/null +++ b/rps/locales/ru-RU.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: ru_RU\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/sk-SK.po b/rps/locales/sk-SK.po new file mode 100644 index 0000000..e8f6ee0 --- /dev/null +++ b/rps/locales/sk-SK.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: sk_SK\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/sl-SI.po b/rps/locales/sl-SI.po new file mode 100644 index 0000000..cddb59c --- /dev/null +++ b/rps/locales/sl-SI.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: sl_SI\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/sv-SE.po b/rps/locales/sv-SE.po new file mode 100644 index 0000000..bb30511 --- /dev/null +++ b/rps/locales/sv-SE.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: sv_SE\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/tr-TR.po b/rps/locales/tr-TR.po new file mode 100644 index 0000000..120ce73 --- /dev/null +++ b/rps/locales/tr-TR.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: tr_TR\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/uk-UA.po b/rps/locales/uk-UA.po new file mode 100644 index 0000000..a64f0a3 --- /dev/null +++ b/rps/locales/uk-UA.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: uk_UA\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/vi-VN.po b/rps/locales/vi-VN.po new file mode 100644 index 0000000..784c6a3 --- /dev/null +++ b/rps/locales/vi-VN.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: vi_VN\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/zh-CN.po b/rps/locales/zh-CN.po new file mode 100644 index 0000000..6e8c4e5 --- /dev/null +++ b/rps/locales/zh-CN.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: zh_CN\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/locales/zh-TW.po b/rps/locales/zh-TW.po new file mode 100644 index 0000000..8621cc3 --- /dev/null +++ b/rps/locales/zh-TW.po @@ -0,0 +1,153 @@ +msgid "" +msgstr "" +"Project-Id-Version: yamicogs\n" +"POT-Creation-Date: 2024-07-01 17:23+0000\n" +"PO-Revision-Date: 2024-07-01 17:32\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/rps/locales/messages.pot\n" +"X-Crowdin-File-ID: 170\n" +"Language: zh_TW\n" + +#: rps/duelview.py:27 +msgid "Sorry, but you are not in this duel" +msgstr "" + +#: rps/duelview.py:42 +msgid "You have already accepted it by starting the duel." +msgstr "" + +#: rps/duelview.py:63 +msgid "Duel has been rejected" +msgstr "" + +#: rps/playerview.py:92 rps/rps.py:152 rps/rps.py:163 rps/rpslsview.py:116 +#: rps/rpsview.py:74 +msgid "Very well, maybe later" +msgstr "" + +#: rps/playerview.py:108 rps/rps.py:45 rps/rpsview.py:88 +msgid "Rock, Paper, Scissors" +msgstr "" + +#: rps/playerview.py:109 rps/rpsview.py:90 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n" +msgstr "" + +#: rps/playerview.py:117 rps/rps.py:62 rps/rpslsview.py:130 +msgid "Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/playerview.py:118 rps/rpslsview.py:132 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n\n" +"Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" +msgstr "" + +#: rps/rps.py:22 +#, docstring +msgid "\n" +" Rock, Paper, Scissors (Lizard, Spock)\n\n" +" More detailed docs: \n" +" " +msgstr "" + +#: rps/rps.py:34 +#, docstring +msgid "Rules of Rock, Paper, Scissors (Lizard, Spock)" +msgstr "" + +#: rps/rps.py:39 +msgid "A game of skill (chance).\n" +"Simply select your choice and see if you can defeat the computer\n\n" +"2 versions are included, the rules are below\n" +msgstr "" + +#: rps/rps.py:48 +msgid "Rock {ROCK} beats Scissors {SCISSORS}\n" +"Scissors {SCISSORS} beats Paper {PAPER}\n" +"Paper {PAPER} beats Rock {ROCK}\n\n" +"Play with `{PREFIX}rps`\n" +msgstr "" + +#: rps/rps.py:65 +msgid "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" +"Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" +"Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" +"Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" +"Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" +"Play with `{PREFIX}rpsls`\n" +msgstr "" + +#: rps/rps.py:94 rps/rps.py:145 +#, docstring +msgid "Play a game of Rock, Paper, Scissors" +msgstr "" + +#: rps/rps.py:122 +msgid "An {choice} Duel is starting" +msgstr "" + +#: rps/rps.py:126 +msgid "{player2}, you have been challanged to an {choice} Duel" +msgstr "" + +#: rps/rps.py:156 +#, docstring +msgid "Play a game of Rock, Paper, Scissors, Lizard, Spock" +msgstr "" + +#: rps/rps.py:169 +msgid "Duel has been rejected (timed out)" +msgstr "" + +#: rps/rps.py:188 +msgid "{player1_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:202 +msgid "{player2_mention} wins!!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:216 +msgid "Well, we must be mind-readers!\n\n" +"{player1_name} {player1_icon} - {player2_icon} {player2_name}" +msgstr "" + +#: rps/rps.py:229 rps/rps.py:291 +msgid "Well, this is embarrassing. No idea what happens now" +msgstr "" + +#: rps/rps.py:267 +msgid "Congrats, you win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:275 +msgid "Look at that, I win!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + +#: rps/rps.py:283 +msgid "Well, we must be mind-readers!\n\n" +"You {player_icon} - {computer_icon} Me" +msgstr "" + diff --git a/rps/playerview.py b/rps/playerview.py new file mode 100644 index 0000000..fc52517 --- /dev/null +++ b/rps/playerview.py @@ -0,0 +1,136 @@ +import discord +from redbot.core.i18n import Translator, set_contextual_locales_from_guild + +from .vars import Choice, Icon, Result, RPSLSChoice, RPSLSIcon + +_ = Translator("RPS", __file__) + + +class PlayerView(discord.ui.View): + def __init__(self, cog, _id: int, player): + super().__init__(timeout=300.0) + self.cog = cog + self._id = _id + self.player = player + self.value = None + + @discord.ui.button( + label="Rock", + style=discord.ButtonStyle.blurple, + custom_id="rock", + emoji=Icon.ROCK, + row=0, + ) + async def rock(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._end_game(self._id, interaction.user, Choice.ROCK, Icon.ROCK) + await interaction.delete_original_response() + self.value = True + + @discord.ui.button( + label="Paper", + style=discord.ButtonStyle.blurple, + custom_id="paper", + emoji=Icon.PAPER, + row=0, + ) + async def paper(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._end_game(self._id, interaction.user, Choice.PAPER, Icon.PAPER) + await interaction.delete_original_response() + self.value = True + + @discord.ui.button( + label="Scissors", + style=discord.ButtonStyle.blurple, + custom_id="scissors", + emoji=Icon.SCISSORS, + row=0, + ) + async def scissors(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._end_game(self._id, interaction.user, Choice.SCISSORS, Icon.SCISSORS) + await interaction.delete_original_response() + self.value = True + + @discord.ui.button( + label="Lizard", + style=discord.ButtonStyle.blurple, + custom_id="lizard", + emoji=Icon.LIZARD, + row=0, + ) + async def lizard(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._end_game(self._id, interaction.user, Choice.LIZARD, Icon.LIZARD) + await interaction.delete_original_response() + self.value = True + + @discord.ui.button( + label="Spock", + style=discord.ButtonStyle.blurple, + custom_id="spock", + emoji=Icon.SPOCK, + row=0, + ) + async def spock(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._end_game(self._id, interaction.user, Choice.SPOCK, Icon.SPOCK) + await interaction.delete_original_response() + self.value = True + + @discord.ui.button( + label="Cancel", + style=discord.ButtonStyle.red, + custom_id="cancel", + emoji="\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}", + row=1, + ) + async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button): + await set_contextual_locales_from_guild(self.bot, interaction.guild) + await interaction.response.defer() + await interaction.message.edit(content=_("Very well, maybe later"), embed=None, view=None) + await self.cog._end_game(self._id, interaction.user, None, None) + self.value = False + + @discord.ui.button( + label="Rules", + style=discord.ButtonStyle.gray, + custom_id="rules", + emoji="\N{MEMO}", + row=1, + ) + async def rules(self, interaction: discord.Interaction, button: discord.ui.Button): + await set_contextual_locales_from_guild(self.bot, interaction.guild) + embed = discord.Embed() + embed.color = self.cog.bot.get_embed_color(interaction.channel) + if self.cog.games[self._id]["gametype"] == "RPS": + embed.title = _("Rock, Paper, Scissors") + embed.description = _( + "A game of skill (chance).\n" + "Simply select your choice and see if you can defeat the computer\n\n\n" + "Rock {ROCK} beats Scissors {SCISSORS}\n" + "Scissors {SCISSORS} beats Paper {PAPER}\n" + "Paper {PAPER} beats Rock {ROCK}\n" + ).format(ROCK=Icon.ROCK, PAPER=Icon.PAPER, SCISSORS=Icon.SCISSORS) + elif self.cog.games[self._id]["gametype"] == "RPSLS": + embed.title = _("Rock, Paper, Scissors, Lizard, Spock") + embed.description = _( + "A game of skill (chance).\n" + "Simply select your choice and see if you can defeat the computer\n\n\n" + "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" + "Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" + "Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" + "Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" + "Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" + ).format( + ROCK=Icon.ROCK, + PAPER=Icon.PAPER, + SCISSORS=Icon.SCISSORS, + LIZARD=Icon.LIZARD, + SPOCK=Icon.SPOCK, + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + + async def interaction_check(self, interaction: discord.Interaction, /) -> bool: + return self.player.id == interaction.user.id diff --git a/rps/rps.py b/rps/rps.py new file mode 100644 index 0000000..ac6e6af --- /dev/null +++ b/rps/rps.py @@ -0,0 +1,294 @@ +import logging +import random +from typing import Optional + +import discord +from redbot.core import app_commands, commands +from redbot.core.bot import Red +from redbot.core.i18n import Translator, cog_i18n, set_contextual_locales_from_guild + +from .duelview import DuelView +from .playerview import PlayerView +from .rpslsview import RPSLSView +from .rpsview import RPSView +from .vars import Choice, GameType, Icon, Result + +log = logging.getLogger("red.yamicogs.rps") +_ = Translator("RPS", __file__) + + +@cog_i18n(_) +class RPS(commands.Cog): + """ + Rock, Paper, Scissors (Lizard, Spock) + + More detailed docs: + """ + + def __init__(self, bot: Red) -> None: + self.bot = bot + self.games = {} + + @commands.command(name="rpsrules", aliases=["rpslsrules"]) + async def _rps_rules(self, ctx): + """Rules of Rock, Paper, Scissors (Lizard, Spock)""" + + embed = discord.Embed() + embed.title = "Rock, Paper, Scissors (Lizard, Spock)" + embed.color = await ctx.embed_color() + embed.description = _( + "A game of skill (chance).\n" + "Simply select your choice and see if you can defeat the computer\n\n" + "2 versions are included, the rules are below\n" + ) + embed.add_field( + name=_("Rock, Paper, Scissors"), + inline=False, + value=( + _( + "Rock {ROCK} beats Scissors {SCISSORS}\n" + "Scissors {SCISSORS} beats Paper {PAPER}\n" + "Paper {PAPER} beats Rock {ROCK}\n\n" + "Play with `{PREFIX}rps`\n" + ).format( + ROCK=Icon.ROCK, + PAPER=Icon.PAPER, + SCISSORS=Icon.SCISSORS, + PREFIX=ctx.clean_prefix, + ) + ), + ) + embed.add_field( + name=_("Rock, Paper, Scissors, Lizard, Spock"), + inline=False, + value=( + _( + "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" + "Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" + "Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" + "Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" + "Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n\n" + "Play with `{PREFIX}rpsls`\n" + ).format( + ROCK=Icon.ROCK, + PAPER=Icon.PAPER, + SCISSORS=Icon.SCISSORS, + LIZARD=Icon.LIZARD, + SPOCK=Icon.SPOCK, + PREFIX=ctx.clean_prefix, + ) + ), + ) + + await ctx.send(embed=embed) + + @app_commands.command(name="rps") + @app_commands.describe( + user="The user you would like to duel. Leave blank to duel the bot.", + gametype="The version of RPS to play", + ) + @app_commands.choices(gametype=GameType) + async def slash_rps( + self, interaction: discord.Interaction, user: Optional[discord.User], gametype: str + ): + """Play a game of Rock, Paper, Scissors""" + if user is None or user.bot: + user = self.bot.user + bot = True + else: + bot = False + + self.games[interaction.id] = { + "p1": interaction.user, + "p2": user, + "gametype": gametype, + "original": interaction, + interaction.user.id: None, + user.id: None, + } + await set_contextual_locales_from_guild(self.bot, self.games[id]["original"].guild) + + if bot: + if gametype == "RPS": + comp = random.choice([Choice.ROCK, Choice.PAPER, Choice.SCISSORS]) + else: + comp = random.choice( + [Choice.ROCK, Choice.PAPER, Choice.SCISSORS, Choice.LIZARD, Choice.SPOCK] + ) + + await self._end_game(interaction.id, user, comp, Icon[comp.name]) + + await interaction.response.send_message( + content=_("An {choice} Duel is starting").format(choice=gametype), + ) + else: + await interaction.response.send_message( + content=_("{player2}, you have been challanged to an {choice} Duel").format( + player2=user.mention, choice=gametype + ), + view=DuelView(self, interaction.id), + ) + self.games[interaction.id]["msg"] = await interaction.original_response() + + view = PlayerView(self, interaction.id, interaction.user) + if gametype == "RPS": + view = view.remove_item(view.children[3]).remove_item(view.children[3]) + + await interaction.followup.send( + content="Please make your selection", + view=view, + ephemeral=True, + ) + + @commands.command(name="rps") + async def _rps(self, ctx): + """Play a game of Rock, Paper, Scissors""" + + view = RPSView(self, ctx.author.id) + msg = await ctx.send(view=view) + + await view.wait() + if view.value is None: + await msg.edit(content=_("Very well, maybe later"), embed=None, view=None) + + @commands.command(name="rpsls") + async def _rpsls(self, ctx): + """Play a game of Rock, Paper, Scissors, Lizard, Spock""" + + view = RPSLSView(self, ctx.author.id) + msg = await ctx.send(view=view) + + await view.wait() + if view.value is None: + await msg.edit(content=_("Very well, maybe later"), embed=None, view=None) + + async def _end_game(self, id: int, player: discord.User, pick: Choice, icon: Icon): + await set_contextual_locales_from_guild(self.bot, self.games[id]["original"].guild) + if pick is None: + await self.games[id]["msg"].edit( + content=_("Duel has been rejected (timed out)"), embed=None, view=None + ) + del self.games[id] + return + + self.games[id][player.id] = [pick, icon] + + if ( + self.games[id][self.games[id]["p1"].id] is not None + and self.games[id][self.games[id]["p2"].id] is not None + ): + p1 = self.games[id][self.games[id]["p1"].id] + p2 = self.games[id][self.games[id]["p2"].id] + print(p1) + print(p2) + result = self._decide(p1[0], p2[0]) + + if result == Result.WIN: + await self.games[id]["msg"].edit( + content=_( + "{player1_mention} wins!!\n\n{player1_name} {player1_icon} - {player2_icon} {player2_name}" + ).format( + player1_mention=self.games[id]["p1"].mention, + player1_name=self.games[id]["p1"].display_name, + player1_icon=p1[1], + player2_name=self.games[id]["p2"].display_name, + player2_icon=p2[1], + ), + embed=None, + view=None, + ) + elif result == Result.LOSE: + await self.games[id]["msg"].edit( + content=_( + "{player2_mention} wins!!\n\n{player1_name} {player1_icon} - {player2_icon} {player2_name}" + ).format( + player2_mention=self.games[id]["p2"].mention, + player1_name=self.games[id]["p1"].display_name, + player1_icon=p1[1], + player2_name=self.games[id]["p2"].display_name, + player2_icon=p2[1], + ), + embed=None, + view=None, + ) + elif result == Result.TIE: + await self.games[id]["msg"].edit( + content=_( + "Well, we must be mind-readers!\n\n{player1_name} {player1_icon} - {player2_icon} {player2_name}" + ).format( + player1_name=self.games[id]["p1"].display_name, + player1_icon=p1[1], + player2_name=self.games[id]["p2"].display_name, + player2_icon=p2[1], + ), + embed=None, + view=None, + ) + else: + await self.games[id]["msg"].edit( + content=_("Well, this is embarrassing. No idea what happens now"), + embed=None, + view=None, + ) + + def _decide(self, p1, p2): + if p1 == p2: + return Result.TIE + elif p1 == Choice.ROCK: + if p2 in [Choice.SCISSORS, Choice.LIZARD]: + return Result.WIN + else: + return Result.LOSE + elif p1 == Choice.PAPER: + if p2 in [Choice.ROCK, Choice.SPOCK]: + return Result.WIN + else: + return Result.LOSE + elif p1 == Choice.SCISSORS: + if p2 in [Choice.PAPER, Choice.LIZARD]: + return Result.WIN + else: + return Result.LOSE + elif p1 == Choice.LIZARD: + if p2 in [Choice.PAPER, Choice.SPOCK]: + return Result.WIN + else: + return Result.LOSE + elif p1 == Choice.SPOCK: + if p2 in [Choice.SCISSORS, Choice.ROCK]: + return Result.WIN + else: + return Result.LOSE + + async def _outcome(self, interaction, outcome, player, computer): + await set_contextual_locales_from_guild(self.bot, interaction.guild) + if outcome == "win": + await interaction.message.edit( + content=_("Congrats, you win!\n\nYou {player_icon} - {computer_icon} Me").format( + player_icon=player, computer_icon=computer + ), + embed=None, + view=None, + ) + elif outcome == "lose": + await interaction.message.edit( + content=_("Look at that, I win!\n\nYou {player_icon} - {computer_icon} Me").format( + player_icon=player, computer_icon=computer + ), + embed=None, + view=None, + ) + elif outcome == "tie": + await interaction.message.edit( + content=_( + "Well, we must be mind-readers!\n\nYou {player_icon} - {computer_icon} Me" + ).format(player_icon=player, computer_icon=computer), + embed=None, + view=None, + ) + else: + await interaction.message.edit( + content=_("Well, this is embarrassing. No idea what happens now"), + embed=None, + view=None, + ) diff --git a/rps/rpslsview.py b/rps/rpslsview.py new file mode 100644 index 0000000..daabf84 --- /dev/null +++ b/rps/rpslsview.py @@ -0,0 +1,179 @@ +import random + +import discord +from redbot.core.i18n import Translator, set_contextual_locales_from_guild + +from .vars import Choice, Icon, Result + +_ = Translator("RPS", __file__) + + +class RPSLSView(discord.ui.View): + def __init__(self, cog, user): + super().__init__(timeout=600.0) + self.cog = cog + self.user = user + self.value = None + self.computer = random.choice( + [Choice.ROCK, Choice.PAPER, Choice.SCISSORS, Choice.LIZARD, Choice.SPOCK] + ) + + @discord.ui.button( + label="Rock", + style=discord.ButtonStyle.blurple, + custom_id="rpslsrock", + emoji=Icon.ROCK, + row=0, + ) + async def rpslsrock(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, + self._check(Choice.ROCK), + Icon.ROCK, + Icon[self.computer.name], + ) + self.value = True + + @discord.ui.button( + label="Paper", + style=discord.ButtonStyle.blurple, + custom_id="rpslspaper", + emoji=Icon.PAPER, + row=0, + ) + async def rpslspaper(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, + self._check(Choice.PAPER), + Icon.PAPER, + Icon[self.computer.name], + ) + self.value = True + + @discord.ui.button( + label="Scissors", + style=discord.ButtonStyle.blurple, + custom_id="rpslsscissors", + emoji=Icon.SCISSORS, + row=0, + ) + async def rpslsscissors(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, + self._check(Choice.SCISSORS), + Icon.SCISSORS, + Icon[self.computer.name], + ) + self.value = True + + @discord.ui.button( + label="Lizard", + style=discord.ButtonStyle.blurple, + custom_id="rpslslizard", + emoji=Icon.LIZARD, + row=0, + ) + async def rpslslizard(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, + self._check(Choice.LIZARD), + Icon.LIZARD, + Icon[self.computer.name], + ) + self.value = True + + @discord.ui.button( + label="Spock", + style=discord.ButtonStyle.blurple, + custom_id="rpslsspock", + emoji=Icon.SPOCK, + row=0, + ) + async def rpslsspock(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, + self._check(Choice.SPOCK), + Icon.SPOCK, + Icon[self.computer.name], + ) + self.value = True + + @discord.ui.button( + label="Cancel", + style=discord.ButtonStyle.red, + custom_id="rpslscancel", + emoji="\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}", + row=1, + ) + async def rpslscancel(self, interaction: discord.Interaction, button: discord.ui.Button): + await set_contextual_locales_from_guild(self.bot, interaction.guild) + await interaction.response.defer() + await interaction.message.edit(content=_("Very well, maybe later"), embed=None, view=None) + self.value = False + + @discord.ui.button( + label="Rules", + style=discord.ButtonStyle.gray, + custom_id="rpslsrules", + emoji="\N{MEMO}", + row=1, + ) + async def rpslsrules(self, interaction: discord.Interaction, button: discord.ui.Button): + await set_contextual_locales_from_guild(self.bot, interaction.guild) + await interaction.response.defer() + embed = discord.Embed() + embed.title = _("Rock, Paper, Scissors, Lizard, Spock") + embed.color = interaction.user.color + embed.description = _( + "A game of skill (chance).\n" + "Simply select your choice and see if you can defeat the computer\n\n\n" + "Rock {ROCK} beats Scissors {SCISSORS} and Lizard {LIZARD}\n" + "Paper {PAPER} beats Rock {ROCK} and Spock {SPOCK}\n" + "Scissors {SCISSORS} beats Paper {PAPER} and Lizard {LIZARD}\n" + "Lizard {LIZARD} beats Paper {PAPER} and Spock {SPOCK}\n" + "Spock {SPOCK} beats Rock {ROCK} and Scissors {SCISSORS}\n" + ).format( + ROCK=Icon.ROCK, + PAPER=Icon.PAPER, + SCISSORS=Icon.SCISSORS, + LIZARD=Icon.LIZARD, + SPOCK=Icon.SPOCK, + ) + await interaction.followup.send(embed=embed, ephemeral=True) + + async def interaction_check(self, interaction: discord.Interaction, /) -> bool: + return self.user == interaction.user.id + + def _check(self, choice) -> str: + if self.computer == choice: + return Result.TIE + elif self.computer == Choice.ROCK: + if choice in [Choice.SCISSORS, Choice.LIZARD]: + return Result.LOSE + else: + return Result.WIN + elif self.computer == Choice.PAPER: + if choice in [Choice.ROCK, Choice.SPOCK]: + return Result.LOSE + else: + return Result.WIN + elif self.computer == Choice.SCISSORS: + if choice in [Choice.PAPER, Choice.LIZARD]: + return Result.LOSE + else: + return Result.WIN + elif self.computer == Choice.LIZARD: + if choice in [Choice.PAPER, Choice.SPOCK]: + return Result.LOSE + else: + return Result.WIN + elif self.computer == Choice.SPOCK: + if choice in [Choice.SCISSORS, Choice.ROCK]: + return Result.LOSE + else: + return Result.WIN diff --git a/rps/rpsview.py b/rps/rpsview.py new file mode 100644 index 0000000..cb283f8 --- /dev/null +++ b/rps/rpsview.py @@ -0,0 +1,119 @@ +import random + +import discord +from redbot.core.i18n import Translator, set_contextual_locales_from_guild + +from .vars import Choice, Icon, Result + +_ = Translator("RPS", __file__) + + +class RPSView(discord.ui.View): + def __init__(self, cog, user): + super().__init__(timeout=600.0) + self.cog = cog + self.user = user + self.value = None + self.computer = random.choice([Choice.ROCK, Choice.PAPER, Choice.SCISSORS]) + + @discord.ui.button( + label="Rock", + style=discord.ButtonStyle.blurple, + custom_id="rpsrock", + emoji=Icon.ROCK, + row=0, + ) + async def rpsrock(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, self._check(Choice.ROCK), Icon.ROCK, Icon[self.computer.name] + ) + self.value = True + + @discord.ui.button( + label="Paper", + style=discord.ButtonStyle.blurple, + custom_id="rpspaper", + emoji=Icon.PAPER, + row=0, + ) + async def rpspaper(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, self._check(Choice.PAPER), Icon.PAPER, Icon[self.computer.name] + ) + self.value = True + + @discord.ui.button( + label="Scissors", + style=discord.ButtonStyle.blurple, + custom_id="rpsscissors", + emoji=Icon.SCISSORS, + row=0, + ) + async def rpsscissors(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + await self.cog._outcome( + interaction, + self._check(Choice.SCISSORS), + Icon.SCISSORS, + Icon[self.computer.name], + ) + self.value = True + + @discord.ui.button( + label="Cancel", + style=discord.ButtonStyle.red, + custom_id="rpscancel", + emoji="\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}", + row=1, + ) + async def rpscancel(self, interaction: discord.Interaction, button: discord.ui.Button): + await set_contextual_locales_from_guild(self.cog.bot, interaction.guild) + await interaction.response.defer() + await interaction.message.edit(content=_("Very well, maybe later"), embed=None, view=None) + self.value = False + + @discord.ui.button( + label="Rules", + style=discord.ButtonStyle.gray, + custom_id="rpsrules", + emoji="\N{MEMO}", + row=1, + ) + async def rpsrules(self, interaction: discord.Interaction, button: discord.ui.Button): + await set_contextual_locales_from_guild(self.cog.bot, interaction.guild) + await interaction.response.defer() + embed = discord.Embed() + embed.title = _("Rock, Paper, Scissors") + embed.color = interaction.user.color + embed.description = _( + "A game of skill (chance).\n" + "Simply select your choice and see if you can defeat the computer\n\n\n" + "Rock {ROCK} beats Scissors {SCISSORS}\n" + "Scissors {SCISSORS} beats Paper {PAPER}\n" + "Paper {PAPER} beats Rock {ROCK}\n" + ).format(ROCK=Icon.ROCK, PAPER=Icon.PAPER, SCISSORS=Icon.SCISSORS) + await interaction.followup.send(embed=embed, ephemeral=True) + + async def interaction_check(self, interaction: discord.Interaction, /) -> bool: + return self.user == interaction.user.id + + def _check(self, choice) -> str: + if self.computer == choice: + return Result.TIE + elif self.computer == Choice.ROCK: + if choice == Choice.SCISSORS: + return Result.LOSE + else: + return Result.WIN + elif self.computer == Choice.PAPER: + if choice == Choice.ROCK: + return Result.LOSE + else: + return Result.WIN + elif self.computer == Choice.SCISSORS: + if choice == Choice.PAPER: + return Result.LOSE + else: + return Result.WIN diff --git a/rps/vars.py b/rps/vars.py new file mode 100644 index 0000000..bce59b2 --- /dev/null +++ b/rps/vars.py @@ -0,0 +1,104 @@ +from enum import Enum + +from redbot.core import app_commands +from redbot.core.i18n import Translator + +_ = Translator("RPS", __file__) + + +class Result(str, Enum): + LOSE = "lose" + WIN = "win" + TIE = "tie" + + def __str__(self) -> str: + return self.value + + def __int__(self) -> int: + return self.value + + +class RPSChoice(Enum): + ROCK = 1 + PAPER = 2 + SCISSORS = 3 + + def __str__(self) -> str: + return self.value + + def __int__(self) -> int: + return self.value + + +class RPSLSChoice(Enum): + ROCK = 1 + PAPER = 2 + SCISSORS = 3 + LIZARD = 4 + SPOCK = 5 + + def __str__(self) -> str: + return self.value + + def __int__(self) -> int: + return self.value + + +class RPSIcon(str, Enum): + ROCK = "\U0001faa8" + PAPER = "\N{NEWSPAPER}" + SCISSORS = "\N{BLACK SCISSORS}\N{VARIATION SELECTOR-16}" + + def __str__(self) -> str: + return self.value + + def __int__(self) -> int: + return self.value + + +class RPSLSIcon(str, Enum): + ROCK = "\U0001faa8" + PAPER = "\N{NEWSPAPER}" + SCISSORS = "\N{BLACK SCISSORS}\N{VARIATION SELECTOR-16}" + LIZARD = "\N{LIZARD}" + SPOCK = "\N{RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS}" + + def __str__(self) -> str: + return self.value + + def __int__(self) -> int: + return self.value + + +class Icon(str, Enum): + ROCK = "\U0001faa8" + PAPER = "\N{NEWSPAPER}" + SCISSORS = "\N{BLACK SCISSORS}\N{VARIATION SELECTOR-16}" + LIZARD = "\N{LIZARD}" + SPOCK = "\N{RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS}" + + def __str__(self) -> str: + return self.value + + def __int__(self) -> int: + return self.value + + +class Choice(Enum): + ROCK = 1 + PAPER = 2 + SCISSORS = 3 + LIZARD = 4 + SPOCK = 5 + + def __str__(self) -> str: + return self.value + + def __int__(self) -> int: + return self.value + + +GameType = [ + app_commands.Choice(name="Rock, Paper, Scissors", value="RPS"), + app_commands.Choice(name="Rock, Paper, Scissors, Lizard, Spock", value="RPSLS"), +] diff --git a/slots/__init__.py b/slots/__init__.py new file mode 100644 index 0000000..cdb4109 --- /dev/null +++ b/slots/__init__.py @@ -0,0 +1,16 @@ +import json +from pathlib import Path + +from dislash.slash_commands import SlashClient +from redbot.core.bot import Red + +from .slots import Slots + +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: Red) -> None: + bot.add_cog(Slots(bot)) + if not hasattr(bot, "slash"): + bot.slash = SlashClient(bot) diff --git a/slots/data/fruits.yaml b/slots/data/fruits.yaml new file mode 100644 index 0000000..a374409 --- /dev/null +++ b/slots/data/fruits.yaml @@ -0,0 +1,50 @@ +name: Fruits +description: Spin some fruits and lose all your credits 😈 +cost: 10 +randomize: true +icons: + 1: + name: Cherry + emoji: 🍒 + 2: + name: Coconut + emoji: 🥥 + 3: + name: Eggplant + emoji: 🍆 + 4: + name: Peach + emoji: 🍑 + 5: + name: Lemon + emoji: 🍋 + 6: + name: Orange + emoji: 🍊 + 7: + name: Pear + emoji: 🍐 + 8: + name: Mango + emoji: 🥭 + 9: + name: Kiwi + emoji: 🥝 + 10: + name: Watermelon + emoji: 🍉 +prizes: + 1: + name: Match 2 + prize: 2 + 2: + name: Match 3 + prize: 3 + 3: + name: Eggplant + pattern: Eggplant Eggplant Eggplant + prize: 1 + 4: + name: Peach + pattern: Peach Peach Peach + prize: 5 diff --git a/slots/data/sports.yaml b/slots/data/sports.yaml new file mode 100644 index 0000000..0ea6c33 --- /dev/null +++ b/slots/data/sports.yaml @@ -0,0 +1,45 @@ +name: Sports +description: Play some Sport slots +cost: 120 +xp-lose: 10 +slots: + 1: + name: American Football + emoji: 🏈 + prize: 1 + xp: 1 + 2: + name: Football (Soccer) + emoji: ⚽ + prize: 2 + xp: 2 + 3: + name: Baseball + emoji: ⚾ + prize: 3 + xp: 3 + 4: + name: Basketball + emoji: 🏀 + prize: 4 + xp: 4 + 5: + name: Volleyball + emoji: 🏐 + prize: 5 + xp: 5 + 6: + name: Pool + emoji: 🎱 + prize: 6 + xp: 6 + 7: + name: Rugby + emoji: 🏉 + prize: 7 + xp: 7 + 8: + name: Tennis + emoji: 🎾 + prize: 8 + xp: 8 diff --git a/slots/errors.py b/slots/errors.py new file mode 100644 index 0000000..34c5729 --- /dev/null +++ b/slots/errors.py @@ -0,0 +1,62 @@ +class SlotsError(Exception): + """Base error class for Slots-related errors.""" + + +class MachineMissingCost(SlotsError): + """Machine doesn't have a play cost""" + + +class MachineMissingName(SlotsError): + """Machine doesn't have a name""" + + +class MachineMissingDescription(SlotsError): + """Machine doesn't have a description""" + + +class MachineMissingReels(SlotsError): + """Machine doesn't have any reel slots""" + + +class MachineMissingPrizes(SlotsError): + """Machine doesn't have any prizes""" + + +class ReelSlotMissingEmoji(SlotsError): + """Reel slot doesn't have an emoji""" + + +class ReelSlotMissingName(SlotsError): + """Reel slot doesn't have a name""" + + +class ReelSlotEmojiUnusable(SlotsError): + """Reel slot's Emoji cannot be used""" + + +class PrizeMissingName(SlotsError): + """Prize doesn't have a name""" + + +class PrizeMissingPattern(SlotsError): + """Prize doesn't have a pattern""" + + +class PrizeMissingAmount(SlotsError): + """Prize doesn't have an amount""" + + +class ValidateTypeCost(SlotsError): + """Cost is not an Integer""" + + +class ValidateTypeRandomize(SlotsError): + """Randomize is not an Boolean""" + + +class ValidateTypePrizeKey(SlotsError): + """Prize Key is not an Integer""" + + +class ValidateTypePrizeAmount(SlotsError): + """Prize amount is not an Integer""" diff --git a/slots/info.json b/slots/info.json new file mode 100644 index 0000000..375afda --- /dev/null +++ b/slots/info.json @@ -0,0 +1,20 @@ +{ + "name": "Slots", + "short": "Various Slot Machine games", + "description": "Various Slot Machine games", + "end_user_data_statement": "This cog does not persistently store any data or metadata about users.", + "install_msg": "Thanks for installing the cog. I'm in the Cog Support server if you need help with anything.", + "author": [ + "YamiKaitou#8975" + ], + "required_cogs": {}, + "requirements": [ + "dislash.py==1.0.16" + ], + "tags": [], + "min_bot_version": "3.4.0", + "max_bot_version": "3.4.26", + "hidden": true, + "disabled": true, + "type": "COG" +} diff --git a/slots/slots.py b/slots/slots.py new file mode 100644 index 0000000..9e02368 --- /dev/null +++ b/slots/slots.py @@ -0,0 +1,413 @@ +import asyncio +import logging +import random +import re +import unicodedata +from collections import deque + +import discord +import yaml +from dislash import * # pylint:disable=unused-wildcard-import +from redbot.core import commands, data_manager +from redbot.core.bot import Red +from redbot.core.config import Config + +from . import errors + +log = logging.getLogger("red.yamicogs.slots") +DISCORD_EMOJI_RE = re.compile(r"(?)") + + +class Slots(commands.Cog): + """ + Various Slot Machine games + """ + + def __init__(self, bot: Red) -> None: + self.bot = bot + self.config = Config.get_conf( + self, + identifier=582650109, + force_registration=True, + ) + + self.config.register_global( + **{"machines": ["local/fruits.yaml"]} + ) # , "local/sports.yaml"]}) + self.config.register_user(**{"playing": False}) + + self.slot_machines = {} + self.bot.loop.create_task(self._load_machines()) + + async def _load_machines(self): + await self.bot.wait_until_red_ready() + + machines = await self.config.machines() + for machine_str in machines: + location, filename = machine_str.split("/") + if location == "local": + machine = yaml.safe_load(open(data_manager.bundled_data_path(self) / filename)) + errors = await self._validate_machine(machine) + if errors == []: + self.slot_machines[machine["name"].lower()] = machine + else: + log.info(f"Failed to parse slot machine {filename}") + log.debug(errors) + else: + machine = yaml.safe_load(open(data_manager.bundled_data_path(self) / filename)) + errors = await self._validate_machine(machine) + if errors == []: + self.slot_machines[machine["name"].lower()] = machine + else: + log.info(f"Failed to parse slot machine {filename}") + log.debug(errors) + + async def _validate_machine(self, machine): + error = [] + if "cost" not in machine: + error.append(errors.MachineMissingCost("Required Option is missing (cost)")) + if not isinstance(machine["cost"], int): + error.append(errors.ValidateTypeCost("cost must be a number")) + + if "description" not in machine: + error.append( + errors.MachineMissingDescription("Required Option is missing (description)") + ) + + if "name" not in machine: + error.append(errors.MachineMissingName("Required Option is missing (name)")) + + if "randomize" in machine: + if not isinstance(machine["randomize"], bool): + error.append( + errors.ValidateTypeRandomize("randomize must be either true or false") + ) + + if "prizes" not in machine: + error.append(errors.MachineMissingPrizes("Required Option is missing (prizes)")) + else: + for k, v in machine["prizes"].items(): + if not isinstance(k, int): + error.append( + errors.ValidateTypePrizeKey(f"Prize Keys should be numbers ({k})") + ) + + if "name" not in v: + error.append(errors.PrizeMissingName(f"Prize is missing a name ({k})")) + + if "prize" not in v: + error.append(errors.PrizeMissingAmount(f"Prize is missing an amount ({k})")) + if not isinstance(v["prize"], int): + error.append( + errors.ValidateTypePrizeAmount(f"Prize amount must be a number ({k})") + ) + + if not (v["name"] != "Match 2" or v["name"] != "Match 3") and "pattern" not in v: + error.append(errors.PrizeMissingPattern(f"Prize is missing a pattern ({k})")) + + if "icons" not in machine: + error.append(errors.MachineMissingReels("Required Option is missing (icons)")) + else: + for k, v in machine["icons"].items(): + if "name" not in v: + error.append(errors.ReelSlotMissingName(f"Icon is missing a name ({k})")) + + if "emoji" not in v: + error.append(errors.ReelSlotMissingEmoji(f"Icon is missing an emoji ({k})")) + for match in DISCORD_EMOJI_RE.finditer(v["emoji"]): + if discord.utils.get(self.bot.emojis, name=match.group(3)) is None: + error.append( + errors.ReelSlotEmojiUnusable(f"Icon Emoji is not usable ({k})") + ) + + return error + + async def _load_machine(self, source, filename): + if source == "local": + machine = yaml.safe_load(open(data_manager.bundled_data_path(self) / filename)) + else: + machine = yaml.safe_load(open(data_manager.cog_data_path(self) / filename)) + errors = await self._validate_machine(machine) + if errors == []: + self.slot_machines[machine["name"].lower()] = machine + else: + log.info(f"Failed to parse slot machine {filename}") + log.debug(errors) + + return errors + + async def _play_game(self, game): + reel = deque() + for slot in game["icons"].values(): + reel.append([slot["name"], slot["emoji"]]) + + reels = [] + for k in range(3): # pylint:disable=unused-variable + reel.rotate(random.randint(-999, 999)) + reels.append(deque(reel, maxlen=3)) + + return reels + + @commands.bot_has_permissions(embed_links=True) + @commands.command(name="slots") + async def slots(self, ctx, bid: int = 0): + """ + Play some slot games + + Not providing a bid will cause you to bid at the machines default amount + """ + embed = discord.Embed() + embed.title = "Slot Machine Alley" + embed.description = ( + "Welcome to Slot Machine Alley.\n\nEnjoy your stay and watch your credits." + ) + + buttons = [] + for machine in self.slot_machines.values(): + if bid != 0: + machine["cost"] = bid + embed.add_field( + name=machine["name"], + value=f"{machine['description']}\n{machine['cost']} credits per spin\n", + inline=False, + ) + buttons.append( + Button( + style=ButtonStyle.blurple, + label=machine["name"], + custom_id=machine["name"].lower(), + ) + ) + msg = await ctx.send(embed=embed, components=auto_rows(*buttons, max_in_row=5)) + + def check(inter): + return inter.author == ctx.author + + inter = await msg.wait_for_button_click(check=check) + + try: + machine = self.slot_machines[inter.clicked_button.custom_id] + if bid != 0: + machine["cost"] = bid + except KeyError: + await inter.reply( + f"That machine somehow doesn't exist", type=ResponseType.UpdateMessage + ) + await msg.edit(components=None, embed=None) + else: + await inter.reply(type=ResponseType.DeferredUpdateMessage) + await self._slots_play(ctx, machine, msg) + + async def _slots_play(self, ctx, machine, msg): + embed = discord.Embed() + embed.title = "Slot Machine - " + machine["name"] + embed.description = "Lets spin those reels" + embed.color = discord.Color.blurple() + winnings = 0 + + def button_check(inter): + if inter.author != ctx.author: + self.bot.loop.create_task( + inter.reply( + f"Sorry, this is not your game to play, try launching your own with `{ctx.prefix}slots`", + ephemeral=True, + ) + ) + return False + + if inter.clicked_button.custom_id == "dead": + self.bot.loop.create_task( + inter.reply( + f"Sorry, but clicking on the slot icons doesn't do anything", + ephemeral=True, + ) + ) + return False + + if inter.clicked_button.custom_id == "coins": + self.bot.loop.create_task( + inter.reply( + f"This is the amount of credits you have gained/lost during this session", + ephemeral=True, + ) + ) + return False + + return True + + while True: + slots = await self._play_game(machine) + + if len(embed.fields) == 0: + outcome_field = embed.insert_field_at + else: + outcome_field = embed.set_field_at + + outcome = await self._check_outcome(machine, slots) + if outcome is False: + winnings -= machine["cost"] + outcome_field(0, name="Outcome", value="No matches, try again!") + else: + winnings += outcome[1] + outcome_field( + 0, name="Outcome", value=f"Winner!! {outcome[0]}\n+ {outcome[1]} credits" + ) + + buttons = [ + ActionRow( + Button( + style=ButtonStyle.gray, + disabled=False, + emoji="\N{BLACK SQUARE FOR STOP}\N{VARIATION SELECTOR-16}", + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[0][0][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[1][0][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[2][0][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.green, + label="Spin", + custom_id="spin", + ), + ), + ActionRow( + Button( + style=ButtonStyle.gray, + disabled=False, + emoji="\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}", + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[0][1][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[1][1][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[2][1][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.red, + label="Exit", + custom_id="cancel", + ), + ), + ActionRow( + Button( + style=ButtonStyle.gray, + disabled=False, + emoji="\N{BLACK SQUARE FOR STOP}\N{VARIATION SELECTOR-16}", + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[0][2][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[1][2][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.gray, + disabled=False, + emoji=slots[2][2][1], + custom_id="dead", + ), + Button( + style=ButtonStyle.blurple, + disabled=False, + emoji="\U0001fa99", + label=winnings, + custom_id="coins", + ), + ), + ] + + await msg.edit(embed=embed, components=buttons) + + try: + inter = await msg.wait_for_button_click(check=button_check, timeout=60) + except asyncio.TimeoutError: + await msg.edit( + content="Okay then, see ya later!\nYou have {} {} credits this session".format( + ("lost" if winnings < 0 else "won"), abs(winnings) + ), + components=None, + embed=None, + ) + return + + if inter.clicked_button.custom_id == "cancel": + await inter.reply( + "Okay then, see ya later!\nYou have {} {} credits this session".format( + ("lost" if winnings < 0 else "won"), abs(winnings) + ), + type=ResponseType.UpdateMessage, + ) + await msg.edit(components=None, embed=None) + return + + await inter.reply(type=ResponseType.DeferredUpdateMessage) + + async def _check_outcome(self, machine, reels): + table = machine["prizes"] + + for k in sorted(table.keys(), reverse=True): + try: + pattern = table[k]["pattern"].split() + + if ( + reels[0][1][0] == pattern[0] + and reels[1][1][0] == pattern[1] + and reels[2][1][0] == pattern[2] + ): + return (table[k]["name"], machine["cost"] * table[k]["prize"]) + except KeyError: + if table[k]["name"] == "Match 3": + if reels[0][1][0] == reels[1][1][0] == reels[2][1][0]: + return (table[k]["name"], machine["cost"] * table[k]["prize"]) + if table[k]["name"] == "Match 2": + if ( + reels[0][1][0] == reels[1][1][0] + or reels[0][1][0] == reels[2][1][0] + or reels[1][1][0] == reels[2][1][0] + ): + return (table[k]["name"], machine["cost"] * table[k]["prize"]) + + return False + + async def red_get_data_for_user(self, *, user_id: int): + # this cog does not store any user data + return {} + + async def red_delete_data_for_user(self, *, requester, user_id: int) -> None: + # this cog does not store any user data + pass