Merge pull request #50 from StudyLions/rewrite

This commit is contained in:
Interitio
2023-09-24 12:26:32 +03:00
committed by GitHub
78 changed files with 1575 additions and 975 deletions

View File

@@ -11,6 +11,8 @@ ALSO_READ = config/emojis.conf, config/secrets.conf, config/gui.conf
asset_path = assets asset_path = assets
support_guild =
[ENDPOINTS] [ENDPOINTS]
guild_log = guild_log =
gem_transaction = gem_transaction =
@@ -19,8 +21,10 @@ gem_transaction =
log_file = bot.log log_file = bot.log
general_log = general_log =
error_log = error_log = %(general_log)
critical_log = critical_log = %(general_log)
warning_log = %(general_log)
warning_prefix =
error_prefix = error_prefix =
critical_prefix = critical_prefix =

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,41 +18,41 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: src/modules/pomodoro/timer.py:51 #: src/modules/pomodoro/timer.py:52
msgctxt "timer|stage:break|name" msgctxt "timer|stage:break|name"
msgid "BREAK" msgid "BREAK"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:52 #: src/modules/pomodoro/timer.py:53
msgctxt "timer|stage:focus|name" msgctxt "timer|stage:focus|name"
msgid "FOCUS" msgid "FOCUS"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:158 #: src/modules/pomodoro/timer.py:160
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "timer|webhook|name" msgctxt "timer|webhook|name"
msgid "{bot_name} Pomodoro" msgid "{bot_name} Pomodoro"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:162 #: src/modules/pomodoro/timer.py:164
msgctxt "timer|webhook|audit_reason" msgctxt "timer|webhook|audit_reason"
msgid "Pomodoro Notifications" msgid "Pomodoro Notifications"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:173 #: src/modules/pomodoro/timer.py:175
msgctxt "timer|webhook|error:insufficient_permissions" msgctxt "timer|webhook|error:insufficient_permissions"
msgid "" msgid ""
"I require the `MANAGE_WEBHOOKS` permission to send pomodoro notifications " "I require the `MANAGE_WEBHOOKS` permission to send pomodoro notifications "
"here!" "here!"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:232 #: src/modules/pomodoro/timer.py:234
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "timer|default_base_name" msgctxt "timer|default_base_name"
msgid "Timer {pattern}" msgid "Timer {pattern}"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:406 #: src/modules/pomodoro/timer.py:408
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "timer|kicked_message" msgctxt "timer|kicked_message"
msgid "" msgid ""
@@ -64,20 +64,20 @@ msgid_plural ""
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: src/modules/pomodoro/timer.py:499 #: src/modules/pomodoro/timer.py:501
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "timer|status|stage:focus|statusline" msgctxt "timer|status|stage:focus|statusline"
msgid "{channel} is now in **FOCUS**! Good luck, **BREAK** starts {timestamp}" msgid "{channel} is now in **FOCUS**! Good luck, **BREAK** starts {timestamp}"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:504 #: src/modules/pomodoro/timer.py:506
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "timer|status|stage:break|statusline" msgctxt "timer|status|stage:break|statusline"
msgid "" msgid ""
"{channel} is now on **BREAK**! Take a rest, **FOCUS** starts {timestamp}" "{channel} is now on **BREAK**! Take a rest, **FOCUS** starts {timestamp}"
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:536 #: src/modules/pomodoro/timer.py:538
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "timer|status|warningline" msgctxt "timer|status|warningline"
msgid "" msgid ""
@@ -85,13 +85,13 @@ msgid ""
"next stage." "next stage."
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:555 #: src/modules/pomodoro/timer.py:557
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "timer|status|stopped:auto" msgctxt "timer|status|stopped:auto"
msgid "Timer stopped! Join {channel} to start the timer." msgid "Timer stopped! Join {channel} to start the timer."
msgstr "" msgstr ""
#: src/modules/pomodoro/timer.py:560 #: src/modules/pomodoro/timer.py:562
msgctxt "timer|status|stopped:manual" msgctxt "timer|status|stopped:manual"
msgid "Timer stopped! Press `Start` to restart the timer." msgid "Timer stopped! Press `Start` to restart the timer."
msgstr "" msgstr ""
@@ -116,34 +116,34 @@ msgctxt "dash:stats|dropdown|placeholder"
msgid "Pomodoro Timer Panel" msgid "Pomodoro Timer Panel"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:82 #: src/modules/pomodoro/cog.py:96
msgctxt "cmd_check:ready|failed" msgctxt "cmd_check:ready|failed"
msgid "" msgid ""
"I am currently restarting! The Pomodoro timers will be unavailable until I " "I am currently restarting! The Pomodoro timers will be unavailable until I "
"have restarted. Thank you for your patience!" "have restarted. Thank you for your patience!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:322 #: src/modules/pomodoro/cog.py:336
msgctxt "cmd:timer" msgctxt "cmd:timer"
msgid "timer" msgid "timer"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:323 #: src/modules/pomodoro/cog.py:337
msgctxt "cmd:timer|desc" msgctxt "cmd:timer|desc"
msgid "Show your current (or selected) pomodoro timer." msgid "Show your current (or selected) pomodoro timer."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:326 #: src/modules/pomodoro/cog.py:340
msgctxt "cmd:timer|param:channel" msgctxt "cmd:timer|param:channel"
msgid "timer_channel" msgid "timer_channel"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:331 #: src/modules/pomodoro/cog.py:345
msgctxt "cmd:timer|param:channel|desc" msgctxt "cmd:timer|param:channel|desc"
msgid "Select a timer to display (by selecting the timer voice channel)" msgid "Select a timer to display (by selecting the timer voice channel)"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:353 src/modules/pomodoro/cog.py:423 #: src/modules/pomodoro/cog.py:367 src/modules/pomodoro/cog.py:438
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timer|error:no_timers|desc" msgctxt "cmd:timer|error:no_timers|desc"
msgid "" msgid ""
@@ -152,7 +152,7 @@ msgid ""
"rent a private room with {room_cmd} and create one yourself!" "rent a private room with {room_cmd} and create one yourself!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:367 #: src/modules/pomodoro/cog.py:381
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timer|error:no_channel|desc" msgctxt "cmd:timer|error:no_channel|desc"
msgid "" msgid ""
@@ -161,7 +161,7 @@ msgid ""
"list the available timers in this server." "list the available timers in this server."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:380 #: src/modules/pomodoro/cog.py:394
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timer|error:no_timer_in_channel" msgctxt "cmd:timer|error:no_timer_in_channel"
msgid "" msgid ""
@@ -169,17 +169,17 @@ msgid ""
"Use {timers_cmd} to list the available timers in this server." "Use {timers_cmd} to list the available timers in this server."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:396 #: src/modules/pomodoro/cog.py:411
msgctxt "cmd:timers" msgctxt "cmd:timers"
msgid "timers" msgid "timers"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:397 #: src/modules/pomodoro/cog.py:412
msgctxt "cmd:timers|desc" msgctxt "cmd:timers|desc"
msgid "List the available pomodoro timer rooms." msgid "List the available pomodoro timer rooms."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:436 #: src/modules/pomodoro/cog.py:451
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timer|error:no_visible_timers|desc" msgctxt "cmd:timer|error:no_visible_timers|desc"
msgid "" msgid ""
@@ -188,13 +188,13 @@ msgid ""
"with {room_cmd} and create one yourself!" "with {room_cmd} and create one yourself!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:449 #: src/modules/pomodoro/cog.py:464
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timers|embed:timer_list|title" msgctxt "cmd:timers|embed:timer_list|title"
msgid "Pomodoro Timer Rooms in **{guild}**" msgid "Pomodoro Timer Rooms in **{guild}**"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:458 #: src/modules/pomodoro/cog.py:473
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timers|status:stopped_auto" msgctxt "cmd:timers|status:stopped_auto"
msgid "" msgid ""
@@ -202,7 +202,7 @@ msgid ""
"Join {channel} to restart it." "Join {channel} to restart it."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:464 #: src/modules/pomodoro/cog.py:479
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timers|status:stopped_manual" msgctxt "cmd:timers|status:stopped_manual"
msgid "" msgid ""
@@ -210,7 +210,7 @@ msgid ""
"Join {channel} and press `Start` to start it!" "Join {channel} and press `Start` to start it!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:471 #: src/modules/pomodoro/cog.py:486
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timers|status:running_focus" msgctxt "cmd:timers|status:running_focus"
msgid "" msgid ""
@@ -218,7 +218,7 @@ msgid ""
"Currently **focusing**, with break starting {timestamp}" "Currently **focusing**, with break starting {timestamp}"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:477 #: src/modules/pomodoro/cog.py:492
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:timers|status:running_break" msgctxt "cmd:timers|status:running_break"
msgid "" msgid ""
@@ -226,78 +226,78 @@ msgid ""
"Currently **resting**, with focus starting {timestamp}" "Currently **resting**, with focus starting {timestamp}"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:491 #: src/modules/pomodoro/cog.py:506
msgctxt "cmd:pomodoro" msgctxt "cmd:pomodoro"
msgid "pomodoro" msgid "pomodoro"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:492 #: src/modules/pomodoro/cog.py:507
msgctxt "cmd:pomodoro|desc" msgctxt "cmd:pomodoro|desc"
msgid "Create and configure pomodoro timer rooms." msgid "Create and configure pomodoro timer rooms."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:499 #: src/modules/pomodoro/cog.py:514
msgctxt "cmd:pomodoro_create" msgctxt "cmd:pomodoro_create"
msgid "create" msgid "create"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:502 #: src/modules/pomodoro/cog.py:517
msgctxt "cmd:pomodoro_create|desc" msgctxt "cmd:pomodoro_create|desc"
msgid "Create a new Pomodoro timer. Requires manage channel permissions." msgid "Create a new Pomodoro timer. Requires manage channel permissions."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:506 #: src/modules/pomodoro/cog.py:521
msgctxt "cmd:pomodoro_create|param:channel" msgctxt "cmd:pomodoro_create|param:channel"
msgid "timer_channel" msgid "timer_channel"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:512 #: src/modules/pomodoro/cog.py:527
msgctxt "cmd:pomodoro_create|param:channel|desc" msgctxt "cmd:pomodoro_create|param:channel|desc"
msgid "" msgid ""
"Voice channel to create the timer in. (Defaults to your current channel, or " "Voice channel to create the timer in. (Defaults to your current channel, or "
"makes a new one.)" "makes a new one.)"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:557 #: src/modules/pomodoro/cog.py:572
msgctxt "cmd:pomodoro_create|new_channel|error:your_insufficient_perms|title" msgctxt "cmd:pomodoro_create|new_channel|error:your_insufficient_perms|title"
msgid "Could not create pomodoro voice channel!" msgid "Could not create pomodoro voice channel!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:561 #: src/modules/pomodoro/cog.py:576
msgctxt "cmd:pomodoro_create|new_channel|error:your_insufficient_perms" msgctxt "cmd:pomodoro_create|new_channel|error:your_insufficient_perms"
msgid "" msgid ""
"No `timer_channel` was provided, and you lack the 'Manage Channels` " "No `timer_channel` was provided, and you lack the 'Manage Channels` "
"permission required to create a new timer room!" "permission required to create a new timer room!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:572 #: src/modules/pomodoro/cog.py:587
msgctxt "cmd:pomodoro_create|new_channel|error:my_insufficient_perms|title" msgctxt "cmd:pomodoro_create|new_channel|error:my_insufficient_perms|title"
msgid "Could not create pomodoro voice channel!" msgid "Could not create pomodoro voice channel!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:576 #: src/modules/pomodoro/cog.py:591
msgctxt "cmd:pomodoro_create|new_channel|error:my_insufficient_perms|desc" msgctxt "cmd:pomodoro_create|new_channel|error:my_insufficient_perms|desc"
msgid "" msgid ""
"No `timer_channel` was provided, and I lack the 'Manage Channels' permission " "No `timer_channel` was provided, and I lack the 'Manage Channels' permission "
"required to create a new voice channel." "required to create a new voice channel."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:587 #: src/modules/pomodoro/cog.py:602
msgctxt "cmd:pomodoro_create|new_channel|default_name" msgctxt "cmd:pomodoro_create|new_channel|default_name"
msgid "Timer" msgid "Timer"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:591 #: src/modules/pomodoro/cog.py:606
msgctxt "cmd:pomodoro_create|new_channel|audit_reason" msgctxt "cmd:pomodoro_create|new_channel|audit_reason"
msgid "Creating Pomodoro Voice Channel" msgid "Creating Pomodoro Voice Channel"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:600 #: src/modules/pomodoro/cog.py:615
msgctxt "cmd:pomodoro_create|new_channel|error:channel_create_failed|title" msgctxt "cmd:pomodoro_create|new_channel|error:channel_create_failed|title"
msgid "Could not create pomodoro voice channel!" msgid "Could not create pomodoro voice channel!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:604 #: src/modules/pomodoro/cog.py:619
msgctxt "cmd:pomodoro_create|new_channel|error:channel_create_failed|desc" msgctxt "cmd:pomodoro_create|new_channel|error:channel_create_failed|desc"
msgid "" msgid ""
"Failed to create a new pomodoro voice channel due to an unknown Discord " "Failed to create a new pomodoro voice channel due to an unknown Discord "
@@ -305,13 +305,13 @@ msgid ""
"the `timer_channel` argument of this command." "the `timer_channel` argument of this command."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:621 #: src/modules/pomodoro/cog.py:636
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:pomodoro_create|add_timer|error:timer_exists" msgctxt "cmd:pomodoro_create|add_timer|error:timer_exists"
msgid "A timer already exists in {channel}! Reconfigure it with {edit_cmd}." msgid "A timer already exists in {channel}! Reconfigure it with {edit_cmd}."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:635 #: src/modules/pomodoro/cog.py:650
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:pomodoro_create|add_timer|error:your_insufficient_perms" msgctxt "cmd:pomodoro_create|add_timer|error:your_insufficient_perms"
msgid "" msgid ""
@@ -319,43 +319,43 @@ msgid ""
"timer there!" "timer there!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:684 #: src/modules/pomodoro/cog.py:699
msgctxt "cmd:pomodoro_create|response:success|content" msgctxt "cmd:pomodoro_create|response:success|content"
msgid "Timer created successfully! Use the panel below to reconfigure." msgid "Timer created successfully! Use the panel below to reconfigure."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:690 #: src/modules/pomodoro/cog.py:705
msgctxt "cmd:pomodoro_destroy" msgctxt "cmd:pomodoro_destroy"
msgid "destroy" msgid "destroy"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:693 #: src/modules/pomodoro/cog.py:708
msgctxt "cmd:pomodoro_destroy|desc" msgctxt "cmd:pomodoro_destroy|desc"
msgid "Remove a pomodoro timer from a voice channel." msgid "Remove a pomodoro timer from a voice channel."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:697 #: src/modules/pomodoro/cog.py:712
msgctxt "cmd:pomodoro_destroy|param:channel" msgctxt "cmd:pomodoro_destroy|param:channel"
msgid "timer_channel" msgid "timer_channel"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:700 #: src/modules/pomodoro/cog.py:715
msgctxt "cmd:pomodoro_destroy|param:channel" msgctxt "cmd:pomodoro_destroy|param:channel"
msgid "Select a timer voice channel to remove the timer from." msgid "Select a timer voice channel to remove the timer from."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:718 #: src/modules/pomodoro/cog.py:733
msgctxt "cmd:pomodoro_destroy|error:no_timer" msgctxt "cmd:pomodoro_destroy|error:no_timer"
msgid "This channel doesn't have an attached pomodoro timer!" msgid "This channel doesn't have an attached pomodoro timer!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:731 #: src/modules/pomodoro/cog.py:746
msgctxt "cmd:pomodoro_destroy|error:insufficient_perms|owned" msgctxt "cmd:pomodoro_destroy|error:insufficient_perms|owned"
msgid "" msgid ""
"You need to be an administrator or own this channel to remove this timer!" "You need to be an administrator or own this channel to remove this timer!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:740 #: src/modules/pomodoro/cog.py:755
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:pomodoro_destroy|error:insufficient_perms|notowned" msgctxt "cmd:pomodoro_destroy|error:insufficient_perms|notowned"
msgid "" msgid ""
@@ -363,38 +363,38 @@ msgid ""
"this timer!" "this timer!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:751 #: src/modules/pomodoro/cog.py:766
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:pomdoro_destroy|response:success|description" msgctxt "cmd:pomdoro_destroy|response:success|description"
msgid "Timer successfully removed from {channel}." msgid "Timer successfully removed from {channel}."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:757 #: src/modules/pomodoro/cog.py:772
msgctxt "cmd:pomodoro_edit" msgctxt "cmd:pomodoro_edit"
msgid "edit" msgid "edit"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:760 #: src/modules/pomodoro/cog.py:775
msgctxt "cmd:pomodoro_edit|desc" msgctxt "cmd:pomodoro_edit|desc"
msgid "Reconfigure a pomodoro timer." msgid "Reconfigure a pomodoro timer."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:764 #: src/modules/pomodoro/cog.py:779
msgctxt "cmd:pomodoro_edit|param:channel" msgctxt "cmd:pomodoro_edit|param:channel"
msgid "timer_channel" msgid "timer_channel"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:770 #: src/modules/pomodoro/cog.py:785
msgctxt "cmd:pomodoro_edit|param:channel|desc" msgctxt "cmd:pomodoro_edit|param:channel|desc"
msgid "Select a timer voice channel to reconfigure." msgid "Select a timer voice channel to reconfigure."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:811 #: src/modules/pomodoro/cog.py:826
msgctxt "cmd:pomodoro_edit|error:no_timer" msgctxt "cmd:pomodoro_edit|error:no_timer"
msgid "This channel doesn't have an attached pomodoro timer to edit!" msgid "This channel doesn't have an attached pomodoro timer to edit!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:824 #: src/modules/pomodoro/cog.py:839
msgctxt "cmd:pomodoro_edit|error:insufficient_perms|role:other" msgctxt "cmd:pomodoro_edit|error:insufficient_perms|role:other"
msgid "" msgid ""
"Insufficient permissions to modifiy this timer!\n" "Insufficient permissions to modifiy this timer!\n"
@@ -402,28 +402,28 @@ msgid ""
"manager role." "manager role."
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:845 #: src/modules/pomodoro/cog.py:860
msgctxt "cmd:pomodoro_edit|error:insufficient_permissions|role_needed:admin" msgctxt "cmd:pomodoro_edit|error:insufficient_permissions|role_needed:admin"
msgid "You need to be a guild admin to modify this option!" msgid "You need to be a guild admin to modify this option!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:850 #: src/modules/pomodoro/cog.py:865
msgctxt "cmd:pomodoro_edit|error:insufficient_permissions|role_needed:owner" msgctxt "cmd:pomodoro_edit|error:insufficient_permissions|role_needed:owner"
msgid "You need to be a channel owner or guild admin to modify this option!" msgid "You need to be a channel owner or guild admin to modify this option!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:855 #: src/modules/pomodoro/cog.py:870
msgctxt "cmd:pomodoro_edit|error:insufficient_permissions|role_needed:manager" msgctxt "cmd:pomodoro_edit|error:insufficient_permissions|role_needed:manager"
msgid "" msgid ""
"You need to be a guild admin or have the manager role to modify this option!" "You need to be a guild admin or have the manager role to modify this option!"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:891 #: src/modules/pomodoro/cog.py:906
msgctxt "cmd:configure_pomodoro" msgctxt "cmd:configure_pomodoro"
msgid "pomodoro" msgid "pomodoro"
msgstr "" msgstr ""
#: src/modules/pomodoro/cog.py:892 #: src/modules/pomodoro/cog.py:907
msgctxt "cmd:configure_pomodoro|desc" msgctxt "cmd:configure_pomodoro|desc"
msgid "Configure Pomodoro Timer System" msgid "Configure Pomodoro Timer System"
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -84,7 +84,7 @@ msgctxt "cmd:userconfig_language|button:reset|label"
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: src/babel/cog.py:251 #: src/babel/cog.py:252
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "acmpl:language|no_match" msgctxt "acmpl:language|no_match"
msgid "No supported languages matching {partial}" msgid "No supported languages matching {partial}"
@@ -219,162 +219,172 @@ msgctxt "guildset:locale|response"
msgid "You have set the guild language to {lang}." msgid "You have set the guild language to {lang}."
msgstr "" msgstr ""
#: src/babel/enums.py:42
msgctxt "localenames|locale:en-US"
msgid "American English"
msgstr ""
#: src/babel/enums.py:43 #: src/babel/enums.py:43
msgctxt "localenames|locale:en-GB" msgctxt "localenames|locale:id"
msgid "British English" msgid "Indonesian"
msgstr "" msgstr ""
#: src/babel/enums.py:44 #: src/babel/enums.py:44
msgctxt "localenames|locale:bg"
msgid "Bulgarian"
msgstr ""
#: src/babel/enums.py:45
msgctxt "localenames|locale:zh-CN"
msgid "Chinese"
msgstr ""
#: src/babel/enums.py:46
msgctxt "localenames|locale:zh-TW"
msgid "Taiwan Chinese"
msgstr ""
#: src/babel/enums.py:47
msgctxt "localenames|locale:hr"
msgid "Croatian"
msgstr ""
#: src/babel/enums.py:48
msgctxt "localenames|locale:cs"
msgid "Czech"
msgstr ""
#: src/babel/enums.py:49
msgctxt "localenames|locale:da" msgctxt "localenames|locale:da"
msgid "Danish" msgid "Danish"
msgstr "" msgstr ""
#: src/babel/enums.py:50 #: src/babel/enums.py:45
msgctxt "localenames|locale:nl"
msgid "Dutch"
msgstr ""
#: src/babel/enums.py:51
msgctxt "localenames|locale:fi"
msgid "Finnish"
msgstr ""
#: src/babel/enums.py:52
msgctxt "localenames|locale:fr"
msgid "French"
msgstr ""
#: src/babel/enums.py:53
msgctxt "localenames|locale:de" msgctxt "localenames|locale:de"
msgid "German" msgid "German"
msgstr "" msgstr ""
#: src/babel/enums.py:54 #: src/babel/enums.py:46
msgctxt "localenames|locale:el" msgctxt "localenames|locale:en-GB"
msgid "Greek" msgid "English, UK"
msgstr "" msgstr ""
#: src/babel/enums.py:55 #: src/babel/enums.py:47
msgctxt "localenames|locale:hi" msgctxt "localenames|locale:en-US"
msgid "Hindi" msgid "English, US"
msgstr "" msgstr ""
#: src/babel/enums.py:56 #: src/babel/enums.py:48
msgctxt "localenames|locale:hu" msgctxt "localenames|locale:es-ES"
msgid "Hungarian" msgid "Spanish"
msgstr "" msgstr ""
#: src/babel/enums.py:57 #: src/babel/enums.py:49
msgctxt "localenames|locale:fr"
msgid "French"
msgstr ""
#: src/babel/enums.py:50
msgctxt "localenames|locale:hr"
msgid "Croatian"
msgstr ""
#: src/babel/enums.py:51
msgctxt "localenames|locale:it" msgctxt "localenames|locale:it"
msgid "Italian" msgid "Italian"
msgstr "" msgstr ""
#: src/babel/enums.py:58 #: src/babel/enums.py:52
msgctxt "localenames|locale:ja"
msgid "Japanese"
msgstr ""
#: src/babel/enums.py:59
msgctxt "localenames|locale:ko"
msgid "Korean"
msgstr ""
#: src/babel/enums.py:60
msgctxt "localenames|locale:lt" msgctxt "localenames|locale:lt"
msgid "Lithuanian" msgid "Lithuanian"
msgstr "" msgstr ""
#: src/babel/enums.py:61 #: src/babel/enums.py:53
msgctxt "localenames|locale:hu"
msgid "Hungarian"
msgstr ""
#: src/babel/enums.py:54
msgctxt "localenames|locale:nl"
msgid "Dutch"
msgstr ""
#: src/babel/enums.py:55
msgctxt "localenames|locale:no" msgctxt "localenames|locale:no"
msgid "Norwegian" msgid "Norwegian"
msgstr "" msgstr ""
#: src/babel/enums.py:62 #: src/babel/enums.py:56
msgctxt "localenames|locale:pl" msgctxt "localenames|locale:pl"
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
#: src/babel/enums.py:63 #: src/babel/enums.py:57
msgctxt "localenames|locale:pt-BR" msgctxt "localenames|locale:pt-BR"
msgid "Brazil Portuguese" msgid "Portuguese, Brazilian"
msgstr "" msgstr ""
#: src/babel/enums.py:64 #: src/babel/enums.py:58
msgctxt "localenames|locale:ro" msgctxt "localenames|locale:ro"
msgid "Romanian" msgid "Romanian, Romania"
msgstr "" msgstr ""
#: src/babel/enums.py:65 #: src/babel/enums.py:59
msgctxt "localenames|locale:ru" msgctxt "localenames|locale:fi"
msgid "Russian" msgid "Finnish"
msgstr "" msgstr ""
#: src/babel/enums.py:66 #: src/babel/enums.py:60
msgctxt "localenames|locale:es-ES"
msgid "Spain Spanish"
msgstr ""
#: src/babel/enums.py:67
msgctxt "localenames|locale:sv-SE" msgctxt "localenames|locale:sv-SE"
msgid "Swedish" msgid "Swedish"
msgstr "" msgstr ""
#: src/babel/enums.py:68 #: src/babel/enums.py:61
msgctxt "localenames|locale:th"
msgid "Thai"
msgstr ""
#: src/babel/enums.py:69
msgctxt "localenames|locale:tr"
msgid "Turkish"
msgstr ""
#: src/babel/enums.py:70
msgctxt "localenames|locale:uk"
msgid "Ukrainian"
msgstr ""
#: src/babel/enums.py:71
msgctxt "localenames|locale:vi" msgctxt "localenames|locale:vi"
msgid "Vietnamese" msgid "Vietnamese"
msgstr "" msgstr ""
#: src/babel/enums.py:62
msgctxt "localenames|locale:tr"
msgid "Turkish"
msgstr ""
#: src/babel/enums.py:63
msgctxt "localenames|locale:cs"
msgid "Czech"
msgstr ""
#: src/babel/enums.py:64
msgctxt "localenames|locale:el"
msgid "Greek"
msgstr ""
#: src/babel/enums.py:65
msgctxt "localenames|locale:bg"
msgid "Bulgarian"
msgstr ""
#: src/babel/enums.py:66
msgctxt "localenames|locale:ru"
msgid "Russian"
msgstr ""
#: src/babel/enums.py:67
msgctxt "localenames|locale:uk"
msgid "Ukrainian"
msgstr ""
#: src/babel/enums.py:68
msgctxt "localenames|locale:hi"
msgid "Hindi"
msgstr ""
#: src/babel/enums.py:69
msgctxt "localenames|locale:th"
msgid "Thai"
msgstr ""
#: src/babel/enums.py:70
msgctxt "localenames|locale:zh-CN"
msgid "Chinese, China"
msgstr ""
#: src/babel/enums.py:71
msgctxt "localenames|locale:ja"
msgid "Japanese"
msgstr ""
#: src/babel/enums.py:72 #: src/babel/enums.py:72
msgctxt "localenames|locale:zh-TW"
msgid "Chinese, Taiwan"
msgstr ""
#: src/babel/enums.py:73
msgctxt "localenames|locale:ko"
msgid "Korean"
msgstr ""
#: src/babel/enums.py:78
msgctxt "localenames|locale:he" msgctxt "localenames|locale:he"
msgid "Hebrew" msgid "Hebrew"
msgstr "" msgstr ""
#: src/babel/enums.py:73 #: src/babel/enums.py:79
msgctxt "localenames|locale:he_IL" msgctxt "localenames|locale:he-IL"
msgid "Hebrew (Israel)" msgid "Hebrew"
msgstr ""
#: src/babel/enums.py:80
msgctxt "localenames|locale:test"
msgid "Test Language"
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,74 +17,74 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: src/modules/sysadmin/exec_cog.py:257 #: src/modules/sysadmin/exec_cog.py:269
msgctxt "ward:sys_admin|failed" msgctxt "ward:sys_admin|failed"
msgid "You must be a bot owner to do this!" msgid "You must be a bot owner to do this!"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:262 #: src/modules/sysadmin/exec_cog.py:274
msgid "async" msgid "async"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:263 #: src/modules/sysadmin/exec_cog.py:275
msgid "Execute arbitrary code with Exec" msgid "Execute arbitrary code with Exec"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:325 #: src/modules/sysadmin/exec_cog.py:337
msgctxt "command" msgctxt "command"
msgid "eval" msgid "eval"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:326 #: src/modules/sysadmin/exec_cog.py:338
msgctxt "command:eval" msgctxt "command:eval"
msgid "Execute arbitrary code with Eval" msgid "Execute arbitrary code with Eval"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:329 #: src/modules/sysadmin/exec_cog.py:341
msgctxt "command:eval|param:string" msgctxt "command:eval|param:string"
msgid "Code to evaluate." msgid "Code to evaluate."
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:336 #: src/modules/sysadmin/exec_cog.py:348
msgctxt "command" msgctxt "command"
msgid "asyncall" msgid "asyncall"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:337 #: src/modules/sysadmin/exec_cog.py:349
msgctxt "command:asyncall|desc" msgctxt "command:asyncall|desc"
msgid "Execute arbitrary code on all shards." msgid "Execute arbitrary code on all shards."
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:340 #: src/modules/sysadmin/exec_cog.py:352
msgctxt "command:asyncall|param:string" msgctxt "command:asyncall|param:string"
msgid "Cross-shard code to execute. Cannot reference ctx!" msgid "Cross-shard code to execute. Cannot reference ctx!"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:341 #: src/modules/sysadmin/exec_cog.py:353
msgctxt "command:asyncall|param:target" msgctxt "command:asyncall|param:target"
msgid "Target shard app name, see autocomplete for options." msgid "Target shard app name, see autocomplete for options."
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:384 #: src/modules/sysadmin/exec_cog.py:396
msgid "reload" msgid "reload"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:385 #: src/modules/sysadmin/exec_cog.py:397
msgid "Reload a given LionBot extension. Launches an ExecUI." msgid "Reload a given LionBot extension. Launches an ExecUI."
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:388 #: src/modules/sysadmin/exec_cog.py:400
msgid "Name of the extension to reload. See autocomplete for options." msgid "Name of the extension to reload. See autocomplete for options."
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:389 #: src/modules/sysadmin/exec_cog.py:401
msgid "Whether to force an extension reload even if it doesn't exist." msgid "Whether to force an extension reload even if it doesn't exist."
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:425 #: src/modules/sysadmin/exec_cog.py:437
msgid "shutdown" msgid "shutdown"
msgstr "" msgstr ""
#: src/modules/sysadmin/exec_cog.py:426 #: src/modules/sysadmin/exec_cog.py:438
msgid "Shutdown (or restart) the client." msgid "Shutdown (or restart) the client."
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -27,45 +27,47 @@ msgctxt "settype:coin|parse|error:notinteger"
msgid "The coin quantity must be a positive integer!" msgid "The coin quantity must be a positive integer!"
msgstr "" msgstr ""
#: src/core/setting_types.py:54 #: src/core/setting_types.py:55
#, possible-python-brace-format
msgctxt "settype:coin|parse|error:too_large" msgctxt "settype:coin|parse|error:too_large"
msgid "Provided number of coins was too high!" msgid "You cannot set this to more than {coin}**{max}**!"
msgstr "" msgstr ""
#: src/core/setting_types.py:60 #: src/core/setting_types.py:63
msgctxt "settype:coin|parse|error:too_large" #, possible-python-brace-format
msgid "Provided number of coins was too low!" msgctxt "settype:coin|parse|error:too_small"
msgid "You cannot set this to less than {coin}**{min}**!"
msgstr "" msgstr ""
#: src/core/setting_types.py:71 #: src/core/setting_types.py:75
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "settype:coin|formatted" msgctxt "settype:coin|formatted"
msgid "{coin}**{amount}**" msgid "{coin}**{amount}**"
msgstr "" msgstr ""
#: src/core/setting_types.py:87 #: src/core/setting_types.py:91
msgctxt "settype:message|accepts" msgctxt "settype:message|accepts"
msgid "JSON formatted raw message data" msgid "JSON formatted raw message data"
msgstr "" msgstr ""
#: src/core/setting_types.py:102 #: src/core/setting_types.py:106
msgctxt "settype:message|download|error:not_json" msgctxt "settype:message|download|error:not_json"
msgid "The attached message data is not a JSON file!" msgid "The attached message data is not a JSON file!"
msgstr "" msgstr ""
#: src/core/setting_types.py:107 #: src/core/setting_types.py:111
msgctxt "settype:message|download|error:size" msgctxt "settype:message|download|error:size"
msgid "The attached message data is too large!" msgid "The attached message data is too large!"
msgstr "" msgstr ""
#: src/core/setting_types.py:116 #: src/core/setting_types.py:120
msgctxt "settype:message|download|error:decoding" msgctxt "settype:message|download|error:decoding"
msgid "" msgid ""
"Could not decode the message data. Please ensure it is saved with the " "Could not decode the message data. Please ensure it is saved with the "
"`UTF-8` encoding." "`UTF-8` encoding."
msgstr "" msgstr ""
#: src/core/setting_types.py:173 #: src/core/setting_types.py:177
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "settype:message|error_suffix" msgctxt "settype:message|error_suffix"
msgid "" msgid ""
@@ -73,7 +75,7 @@ msgid ""
"({link})." "({link})."
msgstr "" msgstr ""
#: src/core/setting_types.py:185 #: src/core/setting_types.py:189
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "settype:message|error:invalid_json" msgctxt "settype:message|error:invalid_json"
msgid "" msgid ""
@@ -81,34 +83,34 @@ msgid ""
"`{error}`" "`{error}`"
msgstr "" msgstr ""
#: src/core/setting_types.py:193 #: src/core/setting_types.py:197
msgctxt "settype:message|error:json_missing_keys" msgctxt "settype:message|error:json_missing_keys"
msgid "" msgid ""
"Message data must be a JSON object with at least one of the following " "Message data must be a JSON object with at least one of the following "
"fields: `content`, `embed`, `embeds`" "fields: `content`, `embed`, `embeds`"
msgstr "" msgstr ""
#: src/core/setting_types.py:202 #: src/core/setting_types.py:206
msgctxt "settype:message|error:json_embed_type" msgctxt "settype:message|error:json_embed_type"
msgid "`embed` field must be a valid JSON object." msgid "`embed` field must be a valid JSON object."
msgstr "" msgstr ""
#: src/core/setting_types.py:210 #: src/core/setting_types.py:214
msgctxt "settype:message|error:json_embeds_type" msgctxt "settype:message|error:json_embeds_type"
msgid "`embeds` field must be a list." msgid "`embeds` field must be a list."
msgstr "" msgstr ""
#: src/core/setting_types.py:217 #: src/core/setting_types.py:221
msgctxt "settype:message|error:json_embed_embeds" msgctxt "settype:message|error:json_embed_embeds"
msgid "Message data cannot include both `embed` and `embeds`." msgid "Message data cannot include both `embed` and `embeds`."
msgstr "" msgstr ""
#: src/core/setting_types.py:225 #: src/core/setting_types.py:229
msgctxt "settype:message|error:json_content_type" msgctxt "settype:message|error:json_content_type"
msgid "`content` field must be a string." msgid "`content` field must be a string."
msgstr "" msgstr ""
#: src/core/setting_types.py:241 #: src/core/setting_types.py:245
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:settype:message|error:embed_conversion" msgctxt "ui:settype:message|error:embed_conversion"
msgid "" msgid ""
@@ -116,7 +118,7 @@ msgid ""
"**Error:** `{exception}`" "**Error:** `{exception}`"
msgstr "" msgstr ""
#: src/core/setting_types.py:269 #: src/core/setting_types.py:273
msgctxt "settype:message|format:too_long" msgctxt "settype:message|format:too_long"
msgid "Too long to display! See Preview." msgid "Too long to display! See Preview."
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -44,7 +44,7 @@ msgstr ""
#: src/modules/member_admin/settingui.py:213 #: src/modules/member_admin/settingui.py:213
msgctxt "ui:memberadmin|embed|title" msgctxt "ui:memberadmin|embed|title"
msgid "Member Admin Configuration Panel" msgid "Greetings and Initial Roles Panel"
msgstr "" msgstr ""
#: src/modules/member_admin/settingui.py:257 #: src/modules/member_admin/settingui.py:257
@@ -67,39 +67,39 @@ msgctxt "dash:member_admin|section:initial_roles|name"
msgid "Initial Roles ({commands[configure welcome]})" msgid "Initial Roles ({commands[configure welcome]})"
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:234 #: src/modules/member_admin/cog.py:239
msgctxt "cmd:resetmember" msgctxt "cmd:resetmember"
msgid "resetmember" msgid "resetmember"
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:237 #: src/modules/member_admin/cog.py:242
msgctxt "cmd:resetmember|desc" msgctxt "cmd:resetmember|desc"
msgid "Reset (server-associated) member data for the target member or user." msgid "Reset (server-associated) member data for the target member or user."
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:241 #: src/modules/member_admin/cog.py:246
msgctxt "cmd:resetmember|param:target" msgctxt "cmd:resetmember|param:target"
msgid "member_to_reset" msgid "member_to_reset"
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:242 #: src/modules/member_admin/cog.py:247
msgctxt "cmd:resetmember|param:saved_roles" msgctxt "cmd:resetmember|param:saved_roles"
msgid "saved_roles" msgid "saved_roles"
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:247 #: src/modules/member_admin/cog.py:252
msgctxt "cmd:resetmember|param:target|desc" msgctxt "cmd:resetmember|param:target|desc"
msgid "Choose the member (current or past) you want to reset." msgid "Choose the member (current or past) you want to reset."
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:251 #: src/modules/member_admin/cog.py:256
msgctxt "cmd:resetmember|param:saved_roles|desc" msgctxt "cmd:resetmember|param:saved_roles|desc"
msgid "" msgid ""
"Clear the saved roles for this member, so their past roles are not restored " "Clear the saved roles for this member, so their past roles are not restored "
"on rejoin." "on rejoin."
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:278 #: src/modules/member_admin/cog.py:283
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:resetmember|reset:saved_roles|success" msgctxt "cmd:resetmember|reset:saved_roles|success"
msgid "" msgid ""
@@ -107,17 +107,17 @@ msgid ""
"roles if they rejoin." "roles if they rejoin."
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:286 #: src/modules/member_admin/cog.py:291
msgctxt "cmd:resetmember|error:nothing_to_do" msgctxt "cmd:resetmember|error:nothing_to_do"
msgid "No reset operation selected, nothing to do." msgid "No reset operation selected, nothing to do."
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:302 #: src/modules/member_admin/cog.py:307
msgctxt "cmd:configure_welcome" msgctxt "cmd:configure_welcome"
msgid "welcome" msgid "welcome"
msgstr "" msgstr ""
#: src/modules/member_admin/cog.py:305 #: src/modules/member_admin/cog.py:310
msgctxt "cmd:configure_welcome|desc" msgctxt "cmd:configure_welcome|desc"
msgid "Configure new member greetings and roles." msgid "Configure new member greetings and roles."
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -32,17 +32,22 @@ msgctxt "cmd:configure_ranks|param:rank_type|choice:message"
msgid "Message" msgid "Message"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:406 #: src/modules/ranks/cog.py:495
msgctxt "event:rank_update|embed:notify" msgctxt "event:rank_update|embed:notify"
msgid "New Activity Rank Attained!" msgid "New Activity Rank Attained!"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:516 #: src/modules/ranks/cog.py:601
msgctxt "rank_refresh|error:cannot_chunk|desc"
msgid "Could not retrieve member list from Discord. Please try again later."
msgstr ""
#: src/modules/ranks/cog.py:614
msgctxt "rank_refresh|error:roles_dne|desc" msgctxt "rank_refresh|error:roles_dne|desc"
msgid "Some ranks have invalid or deleted roles! Please remove them first." msgid "Some ranks have invalid or deleted roles! Please remove them first."
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:526 #: src/modules/ranks/cog.py:624
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rank_refresh|error:unassignable_roles|desc" msgctxt "rank_refresh|error:unassignable_roles|desc"
msgid "" msgid ""
@@ -50,36 +55,36 @@ msgid ""
"{roles}" "{roles}"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:596 #: src/modules/ranks/cog.py:694
msgctxt "rank_refresh|remove_roles|audit" msgctxt "rank_refresh|remove_roles|audit"
msgid "Removing invalid rank role." msgid "Removing invalid rank role."
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:610 #: src/modules/ranks/cog.py:708
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rank_refresh|remove_roles|small_error" msgctxt "rank_refresh|remove_roles|small_error"
msgid "*Could not remove ranks from {member}*" msgid "*Could not remove ranks from {member}*"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:617 #: src/modules/ranks/cog.py:715
msgctxt "rank_refresh|remove_roles|error:too_many_issues" msgctxt "rank_refresh|remove_roles|error:too_many_issues"
msgid "" msgid ""
"Too many issues occurred while removing ranks! Please check my permissions " "Too many issues occurred while removing ranks! Please check my permissions "
"and try again in a few minutes." "and try again in a few minutes."
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:631 #: src/modules/ranks/cog.py:729
msgctxt "rank_refresh|add_roles|audit" msgctxt "rank_refresh|add_roles|audit"
msgid "Adding rank role from refresh" msgid "Adding rank role from refresh"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:645 #: src/modules/ranks/cog.py:743
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rank_refresh|add_roles|small_error" msgctxt "rank_refresh|add_roles|small_error"
msgid "*Could not add {role} to {member}*" msgid "*Could not add {role} to {member}*"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:652 #: src/modules/ranks/cog.py:750
msgctxt "rank_refresh|add_roles|error:too_many_issues" msgctxt "rank_refresh|add_roles|error:too_many_issues"
msgid "" msgid ""
"Too many issues occurred while adding ranks! Please check my permissions and " "Too many issues occurred while adding ranks! Please check my permissions and "
@@ -87,22 +92,22 @@ msgid ""
msgstr "" msgstr ""
#. ---------- Commands ---------- #. ---------- Commands ----------
#: src/modules/ranks/cog.py:677 #: src/modules/ranks/cog.py:775
msgctxt "cmd:ranks" msgctxt "cmd:ranks"
msgid "ranks" msgid "ranks"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:709 #: src/modules/ranks/cog.py:807
msgctxt "cmd:configure_ranks" msgctxt "cmd:configure_ranks"
msgid "ranks" msgid "ranks"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:710 #: src/modules/ranks/cog.py:808
msgctxt "cmd:configure_ranks|desc" msgctxt "cmd:configure_ranks|desc"
msgid "Configure Activity Ranks" msgid "Configure Activity Ranks"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:770 #: src/modules/ranks/cog.py:868
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "" msgctxt ""
"cmd:configure_ranks|response:updated|setting:notification|withdm_withchannel" "cmd:configure_ranks|response:updated|setting:notification|withdm_withchannel"
@@ -111,20 +116,20 @@ msgid ""
"otherwise to {channel}" "otherwise to {channel}"
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:776 #: src/modules/ranks/cog.py:874
msgctxt "" msgctxt ""
"cmd:configure_ranks|response:updated|setting:notification|withdm_nochannel" "cmd:configure_ranks|response:updated|setting:notification|withdm_nochannel"
msgid "Rank update notifications will be sent via **direct message**." msgid "Rank update notifications will be sent via **direct message**."
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:782 #: src/modules/ranks/cog.py:880
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "" msgctxt ""
"cmd:configure_ranks|response:updated|setting:notification|nodm_withchannel" "cmd:configure_ranks|response:updated|setting:notification|nodm_withchannel"
msgid "Rank update notifications will be sent to {channel}." msgid "Rank update notifications will be sent to {channel}."
msgstr "" msgstr ""
#: src/modules/ranks/cog.py:787 #: src/modules/ranks/cog.py:885
msgctxt "" msgctxt ""
"cmd:configure_ranks|response:updated|setting:notification|nodm_nochannel" "cmd:configure_ranks|response:updated|setting:notification|nodm_nochannel"
msgid "Members will not be notified when their activity rank updates." msgid "Members will not be notified when their activity rank updates."
@@ -346,31 +351,31 @@ msgctxt "guildset:dm_ranks|response:false"
msgid "I will never direct message members upon rank advancement." msgid "I will never direct message members upon rank advancement."
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:74 #: src/modules/ranks/ui/preview.py:75
msgctxt "ui:rank_preview|button:edit|error:role_deleted" msgctxt "ui:rank_preview|button:edit|error:role_deleted"
msgid "" msgid ""
"The role underlying this rank no longer exists! Please select a new role " "The role underlying this rank no longer exists! Please select a new role "
"from the role menu." "from the role menu."
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:81 #: src/modules/ranks/ui/preview.py:82
msgctxt "ui:rank_preview|button:edit|error:role_not_assignable" msgctxt "ui:rank_preview|button:edit|error:role_not_assignable"
msgid "" msgid ""
"I do not have permission to edit the underlying role! Please select a new " "I do not have permission to edit the underlying role! Please select a new "
"role from the role menu, or ensure my top role is above the selected role." "role from the role menu, or ensure my top role is above the selected role."
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:90 #: src/modules/ranks/ui/preview.py:91
msgctxt "ui:rank_preview|button:edit|error|title" msgctxt "ui:rank_preview|button:edit|error|title"
msgid "Failed to edit rank!" msgid "Failed to edit rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:108 #: src/modules/ranks/ui/preview.py:109
msgctxt "ui:rank_preview|button:edit|label" msgctxt "ui:rank_preview|button:edit|label"
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:139 #: src/modules/ranks/ui/preview.py:142
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:rank_preview|button:delete|response:success|description|with_role" msgctxt "ui:rank_preview|button:delete|response:success|description|with_role"
msgid "" msgid ""
@@ -378,24 +383,24 @@ msgid ""
"the role." "the role."
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:144 #: src/modules/ranks/ui/preview.py:147
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:rank_preview|button:delete|response:success|description|no_role" msgctxt "ui:rank_preview|button:delete|response:success|description|no_role"
msgid "You have deleted the rank {mention}." msgid "You have deleted the rank {mention}."
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:150 #: src/modules/ranks/ui/preview.py:153
msgctxt "ui:rank_preview|button:delete|response:success|title" msgctxt "ui:rank_preview|button:delete|response:success|title"
msgid "Rank Deleted" msgid "Rank Deleted"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:160 #: src/modules/ranks/ui/preview.py:163
msgctxt "" msgctxt ""
"ui:rank_preview|button:delete|response:success|button:delete_role|label" "ui:rank_preview|button:delete|response:success|button:delete_role|label"
msgid "Delete Role" msgid "Delete Role"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:176 #: src/modules/ranks/ui/preview.py:179
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "" msgctxt ""
"ui:rank_preview|button:delete|response:success|button:delete_role|response:" "ui:rank_preview|button:delete|response:success|button:delete_role|response:"
@@ -405,7 +410,7 @@ msgid ""
"unknown error." "unknown error."
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:182 #: src/modules/ranks/ui/preview.py:185
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "" msgctxt ""
"ui:rank_preview|button:delete|response:success|button:delete_role|response:" "ui:rank_preview|button:delete|response:success|button:delete_role|response:"
@@ -413,37 +418,24 @@ msgctxt ""
msgid "You have deleted the rank **{name}** along with the underlying role." msgid "You have deleted the rank **{name}** along with the underlying role."
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:199 #: src/modules/ranks/ui/preview.py:202
msgctxt "ui:rank_preview|button:delete|label" msgctxt "ui:rank_preview|button:delete|label"
msgid "Delete Rank" msgid "Delete Rank"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:219 #: src/modules/ranks/ui/preview.py:232
#, possible-python-brace-format
msgctxt "ui:rank_preview|menu:roles|error:above_caller"
msgid ""
"You have insufficient permissions to assign {mention} as a rank role! You "
"may only manage roles below your top role."
msgstr ""
#: src/modules/ranks/ui/preview.py:225
msgctxt "ui:rank_preview|menu:roles|error:above_caller|title"
msgid "Insufficient permissions!"
msgstr ""
#: src/modules/ranks/ui/preview.py:241
msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:is_default" msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:is_default"
msgid "The @everyone role cannot be removed, and cannot be a rank!" msgid "The @everyone role cannot be removed, and cannot be a rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:246 #: src/modules/ranks/ui/preview.py:237
msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:is_managed" msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:is_managed"
msgid "" msgid ""
"The role is managed by another application or integration, and cannot be a " "The role is managed by another application or integration, and cannot be a "
"rank!" "rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:251 #: src/modules/ranks/ui/preview.py:242
msgctxt "" msgctxt ""
"ui:rank_preview|menu:roles|error:not_assignable|suberror:no_permissions" "ui:rank_preview|menu:roles|error:not_assignable|suberror:no_permissions"
msgid "" msgid ""
@@ -451,121 +443,121 @@ msgid ""
"manage ranks!" "manage ranks!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:256 #: src/modules/ranks/ui/preview.py:247
msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:above_me" msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:above_me"
msgid "" msgid ""
"This role is above my top role in the role hierarchy, so I cannot add or " "This role is above my top role in the role hierarchy, so I cannot add or "
"remove it!" "remove it!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:262 #: src/modules/ranks/ui/preview.py:253
msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:other" msgctxt "ui:rank_preview|menu:roles|error:not_assignable|suberror:other"
msgid "I am not able to manage the selected role, so it cannot be a rank!" msgid "I am not able to manage the selected role, so it cannot be a rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:268 #: src/modules/ranks/ui/preview.py:259
msgctxt "ui:rank_preview|menu:roles|error:not_assignable|title" msgctxt "ui:rank_preview|menu:roles|error:not_assignable|title"
msgid "Could not update rank!" msgid "Could not update rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:278 #: src/modules/ranks/ui/preview.py:269
msgctxt "ui:rank_preview|menu:roles|placeholder" msgctxt "ui:rank_preview|menu:roles|placeholder"
msgid "Update Rank Role" msgid "Update Rank Role"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:290 #: src/modules/ranks/ui/preview.py:281
msgctxt "ui:rank_preview|embed|title" msgctxt "ui:rank_preview|embed|title"
msgid "Rank Information" msgid "Rank Information"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:297 #: src/modules/ranks/ui/preview.py:288
msgctxt "ui:rank_preview|embed|field:role|name" msgctxt "ui:rank_preview|embed|field:role|name"
msgid "Role" msgid "Role"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:304 #: src/modules/ranks/ui/preview.py:295
msgctxt "ui:rank_preview|embed|field:required|name" msgctxt "ui:rank_preview|embed|field:required|name"
msgid "Required" msgid "Required"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:311 #: src/modules/ranks/ui/preview.py:302
msgctxt "ui:rank_preview|embed|field:reward|name" msgctxt "ui:rank_preview|embed|field:reward|name"
msgid "Reward" msgid "Reward"
msgstr "" msgstr ""
#: src/modules/ranks/ui/preview.py:320 #: src/modules/ranks/ui/preview.py:311
msgctxt "ui:rank_preview|embed|field:message" msgctxt "ui:rank_preview|embed|field:message"
msgid "Congratulatory Message" msgid "Congratulatory Message"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:125 #: src/modules/ranks/ui/refresh.py:134
msgctxt "ui:refresh_ranks|embed|title:errored" msgctxt "ui:refresh_ranks|embed|title:errored"
msgid "Could not refresh the server ranks!" msgid "Could not refresh the server ranks!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:133 #: src/modules/ranks/ui/refresh.py:142
msgctxt "ui:refresh_ranks|embed|title:done" msgctxt "ui:refresh_ranks|embed|title:done"
msgid "Rank refresh complete!" msgid "Rank refresh complete!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:139 #: src/modules/ranks/ui/refresh.py:148
msgctxt "ui:refresh_ranks|embed|title:working" msgctxt "ui:refresh_ranks|embed|title:working"
msgid "Refreshing your server ranks, please wait." msgid "Refreshing your server ranks, please wait."
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:157 #: src/modules/ranks/ui/refresh.py:166
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|line:ranks" msgctxt "ui:refresh_ranks|embed|line:ranks"
msgid "**Loading server ranks:** {emoji}" msgid "**Loading server ranks:** {emoji}"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:167 #: src/modules/ranks/ui/refresh.py:176
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|line:members" msgctxt "ui:refresh_ranks|embed|line:members"
msgid "**Loading server members:** {emoji}" msgid "**Loading server members:** {emoji}"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:177 #: src/modules/ranks/ui/refresh.py:186
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|line:roles" msgctxt "ui:refresh_ranks|embed|line:roles"
msgid "**Loading rank roles:** {emoji}" msgid "**Loading rank roles:** {emoji}"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:187 #: src/modules/ranks/ui/refresh.py:196
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|line:compute" msgctxt "ui:refresh_ranks|embed|line:compute"
msgid "**Computing correct ranks:** {emoji}" msgid "**Computing correct ranks:** {emoji}"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:198 #: src/modules/ranks/ui/refresh.py:207
msgctxt "ui:refresh_ranks|embed|field:remove|name" msgctxt "ui:refresh_ranks|embed|field:remove|name"
msgid "Removing invalid rank roles from members" msgid "Removing invalid rank roles from members"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:202 #: src/modules/ranks/ui/refresh.py:211
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|field:remove|value" msgctxt "ui:refresh_ranks|embed|field:remove|value"
msgid "{progress} {done}/{total} removed" msgid "{progress} {done}/{total} removed"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:213 #: src/modules/ranks/ui/refresh.py:222
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|line:remove" msgctxt "ui:refresh_ranks|embed|line:remove"
msgid "**Removed invalid ranks:** {done}/{target}" msgid "**Removed invalid ranks:** {done}/{target}"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:221 #: src/modules/ranks/ui/refresh.py:230
msgctxt "ui:refresh_ranks|embed|field:add|name" msgctxt "ui:refresh_ranks|embed|field:add|name"
msgid "Giving members their rank roles" msgid "Giving members their rank roles"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:225 #: src/modules/ranks/ui/refresh.py:234
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|field:add|value" msgctxt "ui:refresh_ranks|embed|field:add|value"
msgid "{progress} {done}/{total} given" msgid "{progress} {done}/{total} given"
msgstr "" msgstr ""
#: src/modules/ranks/ui/refresh.py:236 #: src/modules/ranks/ui/refresh.py:245
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:refresh_ranks|embed|line:add" msgctxt "ui:refresh_ranks|embed|line:add"
msgid "**Updated member ranks:** {done}/{target}" msgid "**Updated member ranks:** {done}/{target}"
@@ -621,67 +613,54 @@ msgctxt "dash:rank|dropdown|placeholder"
msgid "Activity Rank Panel" msgid "Activity Rank Panel"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:94 #: src/modules/ranks/ui/overview.py:95
msgctxt "ui:rank_overview|button:auto|label" msgctxt "ui:rank_overview|button:auto|label"
msgid "Auto Create" msgid "Auto Create"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:109 #: src/modules/ranks/ui/overview.py:110
msgctxt "ui:rank_overview|button:refresh|label" msgctxt "ui:rank_overview|button:refresh|label"
msgid "Refresh Member Ranks" msgid "Refresh Member Ranks"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:121 #: src/modules/ranks/ui/overview.py:122
msgctxt "ui:rank_overview|button:clear|confirm" msgctxt "ui:rank_overview|button:clear|confirm"
msgid "Are you sure you want to **delete all activity ranks** in this server?" msgid "Are you sure you want to **delete all activity ranks** in this server?"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:126 #: src/modules/ranks/ui/overview.py:127
msgctxt "ui:rank_overview|button:clear|confirm|button:yes" msgctxt "ui:rank_overview|button:clear|confirm|button:yes"
msgid "Yes, clear ranks" msgid "Yes, clear ranks"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:132 #: src/modules/ranks/ui/overview.py:133
msgctxt "ui:rank_overview|button:clear|confirm|button:no" msgctxt "ui:rank_overview|button:clear|confirm|button:no"
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:148 #: src/modules/ranks/ui/overview.py:149
msgctxt "ui:rank_overview|button:clear|label" msgctxt "ui:rank_overview|button:clear|label"
msgid "Clear Ranks" msgid "Clear Ranks"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:178 #: src/modules/ranks/ui/overview.py:179
msgctxt "ui:rank_overview|button:create|label" msgctxt "ui:rank_overview|button:create|label"
msgid "Create Rank" msgid "Create Rank"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:194 #: src/modules/ranks/ui/overview.py:222
#, possible-python-brace-format
msgctxt "ui:rank_overview|menu:roles|error:above_caller"
msgid ""
"You have insufficient permissions to assign {mention} as a rank role! You "
"may only manage roles below your top role."
msgstr ""
#: src/modules/ranks/ui/overview.py:200
msgctxt "ui:rank_overview|menu:roles|error:above_caller|title"
msgid "Insufficient permissions!"
msgstr ""
#: src/modules/ranks/ui/overview.py:233
msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:is_default" msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:is_default"
msgid "The @everyone role cannot be removed, and cannot be a rank!" msgid "The @everyone role cannot be removed, and cannot be a rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:238 #: src/modules/ranks/ui/overview.py:227
msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:is_managed" msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:is_managed"
msgid "" msgid ""
"The role is managed by another application or integration, and cannot be a " "The role is managed by another application or integration, and cannot be a "
"rank!" "rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:243 #: src/modules/ranks/ui/overview.py:232
msgctxt "" msgctxt ""
"ui:rank_overview|menu:roles|error:not_assignable|suberror:no_permissions" "ui:rank_overview|menu:roles|error:not_assignable|suberror:no_permissions"
msgid "" msgid ""
@@ -689,45 +668,71 @@ msgid ""
"manage ranks!" "manage ranks!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:248 #: src/modules/ranks/ui/overview.py:237
msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:above_me" msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:above_me"
msgid "" msgid ""
"This role is above my top role in the role hierarchy, so I cannot add or " "This role is above my top role in the role hierarchy, so I cannot add or "
"remove it!" "remove it!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:254 #: src/modules/ranks/ui/overview.py:243
msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:other" msgctxt "ui:rank_overview|menu:roles|error:not_assignable|suberror:other"
msgid "I am not able to manage the selected role, so it cannot be a rank!" msgid "I am not able to manage the selected role, so it cannot be a rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:260 #: src/modules/ranks/ui/overview.py:249
msgctxt "ui:rank_overview|menu:roles|error:not_assignable|title" msgctxt "ui:rank_overview|menu:roles|error:not_assignable|title"
msgid "Could not create rank!" msgid "Could not create rank!"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:284 #: src/modules/ranks/ui/overview.py:273
msgctxt "ui:rank_overview|menu:roles|placeholder" msgctxt "ui:rank_overview|menu:roles|placeholder"
msgid "Create from role" msgid "Create from role"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:301 #: src/modules/ranks/ui/overview.py:290
msgctxt "ui:rank_overview|menu:ranks|placeholder" msgctxt "ui:rank_overview|menu:ranks|placeholder"
msgid "View or edit rank" msgid "View or edit rank"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:387 #: src/modules/ranks/ui/overview.py:376
msgctxt "ui:rank_overview|embed:noranks|desc"
msgid ""
"No activity ranks have been set up!\n"
"Press 'AUTO' to automatically create a standard heirachy of voice | text | "
"xp ranks, or select a role or press Create below!"
msgstr ""
#: src/modules/ranks/ui/overview.py:384
#, possible-python-brace-format
msgctxt "ui:rank_overview|embed|title|type:voice"
msgid "Voice Ranks in {guild_name}"
msgstr ""
#: src/modules/ranks/ui/overview.py:389
#, possible-python-brace-format
msgctxt "ui:rank_overview|embed|title|type:xp"
msgid "XP ranks in {guild_name}"
msgstr ""
#: src/modules/ranks/ui/overview.py:394
#, possible-python-brace-format
msgctxt "ui:rank_overview|embed|title|type:message"
msgid "Message ranks in {guild_name}"
msgstr ""
#: src/modules/ranks/ui/overview.py:406
msgctxt "ui:rank_overview|embed|field:note|name" msgctxt "ui:rank_overview|embed|field:note|name"
msgid "Note" msgid "Note"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:393 #: src/modules/ranks/ui/overview.py:412
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:rank_overview|embed|field:note|value:with_season" msgctxt "ui:rank_overview|embed|field:note|value:with_season"
msgid "Ranks are determined by activity since {timestamp}." msgid "Ranks are determined by activity since {timestamp}."
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:400 #: src/modules/ranks/ui/overview.py:419
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:rank_overview|embed|field:note|value:without_season" msgctxt "ui:rank_overview|embed|field:note|value:without_season"
msgid "" msgid ""
@@ -736,7 +741,7 @@ msgid ""
"ranks) set the `season_start` with {stats_cmd}" "ranks) set the `season_start` with {stats_cmd}"
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:407 #: src/modules/ranks/ui/overview.py:426
msgctxt "ui:rank_overview|embed|field:note|value|voice_addendum" msgctxt "ui:rank_overview|embed|field:note|value|voice_addendum"
msgid "" msgid ""
"Also note that ranks will only be updated when a member leaves a tracked " "Also note that ranks will only be updated when a member leaves a tracked "
@@ -744,32 +749,6 @@ msgid ""
"members manually." "members manually."
msgstr "" msgstr ""
#: src/modules/ranks/ui/overview.py:415
msgctxt "ui:rank_overview|embed:noranks|desc"
msgid ""
"No activity ranks have been set up!\n"
"Press 'AUTO' to automatically create a standard heirachy of voice | text | "
"xp ranks, or select a role or press Create below!"
msgstr ""
#: src/modules/ranks/ui/overview.py:423
#, possible-python-brace-format
msgctxt "ui:rank_overview|embed|title|type:voice"
msgid "Voice Ranks in {guild_name}"
msgstr ""
#: src/modules/ranks/ui/overview.py:428
#, possible-python-brace-format
msgctxt "ui:rank_overview|embed|title|type:xp"
msgid "XP ranks in {guild_name}"
msgstr ""
#: src/modules/ranks/ui/overview.py:433
#, possible-python-brace-format
msgctxt "ui:rank_overview|embed|title|type:message"
msgid "Message ranks in {guild_name}"
msgstr ""
#: src/modules/ranks/ui/editor.py:33 #: src/modules/ranks/ui/editor.py:33
msgctxt "ui:rank_editor|input:role_name|label" msgctxt "ui:rank_editor|input:role_name|label"
msgid "Role Name" msgid "Role Name"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -349,13 +349,18 @@ msgctxt "cmd:rolemenu_edit|parse:template|success:custom"
msgid "Now using a custom menu message." msgid "Now using a custom menu message."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:994 #: src/modules/rolemenus/cog.py:986
msgctxt "cmd:rolemenu_edit|parse:custom_message|success"
msgid "Custom menu message updated."
msgstr ""
#: src/modules/rolemenus/cog.py:1001
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_edit|repost|success" msgctxt "cmd:rolemenu_edit|repost|success"
msgid "The role menu is now available at {message}" msgid "The role menu is now available at {message}"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1005 #: src/modules/rolemenus/cog.py:1012
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_edit|repost|error:forbidden" msgctxt "cmd:rolemenu_edit|repost|error:forbidden"
msgid "" msgid ""
@@ -363,7 +368,7 @@ msgid ""
"permission in {channel}." "permission in {channel}."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1010 #: src/modules/rolemenus/cog.py:1017
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_edit|repost|error:unknown" msgctxt "cmd:rolemenu_edit|repost|error:unknown"
msgid "" msgid ""
@@ -371,40 +376,40 @@ msgid ""
"**Error:** `{exception}`" "**Error:** `{exception}`"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1044 #: src/modules/rolemenus/cog.py:1051
msgctxt "cmd:rolemenu_delete" msgctxt "cmd:rolemenu_delete"
msgid "delmenu" msgid "delmenu"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1047 #: src/modules/rolemenus/cog.py:1054
msgctxt "cmd:rolemenu_delete|desc" msgctxt "cmd:rolemenu_delete|desc"
msgid "Delete a role menu." msgid "Delete a role menu."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1051 #: src/modules/rolemenus/cog.py:1058
msgctxt "cmd:rolemenu_delete|param:name" msgctxt "cmd:rolemenu_delete|param:name"
msgid "menu" msgid "menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1056 #: src/modules/rolemenus/cog.py:1063
msgctxt "cmd:rolemenu_delete|param:name|desc" msgctxt "cmd:rolemenu_delete|param:name|desc"
msgid "Name of the rolemenu to delete." msgid "Name of the rolemenu to delete."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1071 #: src/modules/rolemenus/cog.py:1078
msgctxt "cmd:rolemenu_delete|error:author_perms" msgctxt "cmd:rolemenu_delete|error:author_perms"
msgid "" msgid ""
"You need the `MANAGE_ROLES` permission in order to manage the server role " "You need the `MANAGE_ROLES` permission in order to manage the server role "
"menus." "menus."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1094 #: src/modules/rolemenus/cog.py:1101
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_delete|error:menu_not_found" msgctxt "cmd:rolemenu_delete|error:menu_not_found"
msgid "This server does not have a role menu called `{name}`!" msgid "This server does not have a role menu called `{name}`!"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1102 #: src/modules/rolemenus/cog.py:1109
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_delete|confirm|title" msgctxt "cmd:rolemenu_delete|confirm|title"
msgid "" msgid ""
@@ -412,272 +417,272 @@ msgid ""
"reversible!" "reversible!"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1107 #: src/modules/rolemenus/cog.py:1114
msgctxt "cmd:rolemenu_delete|confirm|button:yes" msgctxt "cmd:rolemenu_delete|confirm|button:yes"
msgid "Yes, Delete Now" msgid "Yes, Delete Now"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1112 #: src/modules/rolemenus/cog.py:1119
msgctxt "cmd:rolemenu_delete|confirm|button:no" msgctxt "cmd:rolemenu_delete|confirm|button:no"
msgid "No, Cancel" msgid "No, Cancel"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1137 #: src/modules/rolemenus/cog.py:1144
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_delete|success|desc" msgctxt "cmd:rolemenu_delete|success|desc"
msgid "Successfully deleted the menu **{name}**" msgid "Successfully deleted the menu **{name}**"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1145 #: src/modules/rolemenus/cog.py:1152
msgctxt "cmd:rolemenu_addrole" msgctxt "cmd:rolemenu_addrole"
msgid "addrole" msgid "addrole"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1148 #: src/modules/rolemenus/cog.py:1155
msgctxt "cmd:rolemenu_addrole|desc" msgctxt "cmd:rolemenu_addrole|desc"
msgid "Add a new role to an existing role menu." msgid "Add a new role to an existing role menu."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1153 #: src/modules/rolemenus/cog.py:1160
msgctxt "cmd:rolemenu_addrole|param:menu" msgctxt "cmd:rolemenu_addrole|param:menu"
msgid "menu" msgid "menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1156 #: src/modules/rolemenus/cog.py:1163
msgctxt "cmd:rolemenu_addrole|param:role" msgctxt "cmd:rolemenu_addrole|param:role"
msgid "role" msgid "role"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1167 #: src/modules/rolemenus/cog.py:1174
msgctxt "cmd:rolemenu_addrole|param:menu|desc" msgctxt "cmd:rolemenu_addrole|param:menu|desc"
msgid "Name of the menu to add a role to" msgid "Name of the menu to add a role to"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1171 #: src/modules/rolemenus/cog.py:1178
msgctxt "cmd:rolemenu_addrole|param:role|desc" msgctxt "cmd:rolemenu_addrole|param:role|desc"
msgid "Role to add to the menu" msgid "Role to add to the menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1179 #: src/modules/rolemenus/cog.py:1186
msgctxt "cmd:rolemenu_addrole|param:duration|desc" msgctxt "cmd:rolemenu_addrole|param:duration|desc"
msgid "Lifetime of the role after selection in minutes." msgid "Lifetime of the role after selection in minutes."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1227 #: src/modules/rolemenus/cog.py:1234
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_addrole|error:menu_not_found" msgctxt "cmd:rolemenu_addrole|error:menu_not_found"
msgid "This server does not have a role menu called `{name}`!" msgid "This server does not have a role menu called `{name}`!"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1312 #: src/modules/rolemenus/cog.py:1319
msgctxt "cmd:rolemenu_addrole|success:create|title" msgctxt "cmd:rolemenu_addrole|success:create|title"
msgid "Added Menu Role" msgid "Added Menu Role"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1316 #: src/modules/rolemenus/cog.py:1323
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_addrole|success:create|desc" msgctxt "cmd:rolemenu_addrole|success:create|desc"
msgid "Add the role {role} to the menu **{menu}**." msgid "Add the role {role} to the menu **{menu}**."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1334 #: src/modules/rolemenus/cog.py:1341
msgctxt "cmd:rolemenu_addrole|success:edit|title" msgctxt "cmd:rolemenu_addrole|success:edit|title"
msgid "Menu Role updated" msgid "Menu Role updated"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1346 #: src/modules/rolemenus/cog.py:1353
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_addrole|error:role_exists" msgctxt "cmd:rolemenu_addrole|error:role_exists"
msgid "The role {role} is already selectable from the menu **{menu}**" msgid "The role {role} is already selectable from the menu **{menu}**"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1364 #: src/modules/rolemenus/cog.py:1371
msgctxt "cmd:rolemenu_addrole|success|error:reaction|name" msgctxt "cmd:rolemenu_addrole|success|error:reaction|name"
msgid "Note" msgid "Note"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1376 #: src/modules/rolemenus/cog.py:1383
msgctxt "cmd:rolemenu_addrole|success|button:editor|label" msgctxt "cmd:rolemenu_addrole|success|button:editor|label"
msgid "Edit Menu" msgid "Edit Menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1393 #: src/modules/rolemenus/cog.py:1400
msgctxt "cmd:rolemenu_editrole" msgctxt "cmd:rolemenu_editrole"
msgid "editrole" msgid "editrole"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1396 #: src/modules/rolemenus/cog.py:1403
msgctxt "cmd:rolemenu_editrole|desc" msgctxt "cmd:rolemenu_editrole|desc"
msgid "Edit role options in an existing role menu." msgid "Edit role options in an existing role menu."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1401 #: src/modules/rolemenus/cog.py:1408
msgctxt "cmd:rolemenu_editrole|param:menu" msgctxt "cmd:rolemenu_editrole|param:menu"
msgid "menu" msgid "menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1404 #: src/modules/rolemenus/cog.py:1411
msgctxt "cmd:rolemenu_editrole|param:menu_role" msgctxt "cmd:rolemenu_editrole|param:menu_role"
msgid "menu_role" msgid "menu_role"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1407 #: src/modules/rolemenus/cog.py:1414
msgctxt "cmd:rolemenu_editrole|param:role" msgctxt "cmd:rolemenu_editrole|param:role"
msgid "new_role" msgid "new_role"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1418 #: src/modules/rolemenus/cog.py:1425
msgctxt "cmd:rolemenu_editrole|param:menu|desc" msgctxt "cmd:rolemenu_editrole|param:menu|desc"
msgid "Name of the menu to edit the role for" msgid "Name of the menu to edit the role for"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1422 #: src/modules/rolemenus/cog.py:1429
msgctxt "cmd:rolemenu_editrole|param:menu_role|desc" msgctxt "cmd:rolemenu_editrole|param:menu_role|desc"
msgid "Label, name, or mention of the menu role to edit." msgid "Label, name, or mention of the menu role to edit."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1426 #: src/modules/rolemenus/cog.py:1433
msgctxt "cmd:rolemenu_editrole|param:role|desc" msgctxt "cmd:rolemenu_editrole|param:role|desc"
msgid "New server role this menu role should give." msgid "New server role this menu role should give."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1434 #: src/modules/rolemenus/cog.py:1441
msgctxt "cmd:rolemenu_editrole|param:duration|desc" msgctxt "cmd:rolemenu_editrole|param:duration|desc"
msgid "Lifetime of the role after selection in minutes." msgid "Lifetime of the role after selection in minutes."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1475 #: src/modules/rolemenus/cog.py:1482
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_editrole|error:menu_not_found" msgctxt "cmd:rolemenu_editrole|error:menu_not_found"
msgid "This server does not have a role menu called `{name}`!" msgid "This server does not have a role menu called `{name}`!"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1503 #: src/modules/rolemenus/cog.py:1510
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_editrole|error:role_not_found" msgctxt "cmd:rolemenu_editrole|error:role_not_found"
msgid "The menu **{menu}** does not have the role **{name}**" msgid "The menu **{menu}** does not have the role **{name}**"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1569 #: src/modules/rolemenus/cog.py:1576
msgctxt "cmd:rolemenu_editrole|success|title" msgctxt "cmd:rolemenu_editrole|success|title"
msgid "Role menu role updated" msgid "Role menu role updated"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1584 #: src/modules/rolemenus/cog.py:1591
msgctxt "cmd:rolemenu_editrole|success|error:reaction|name" msgctxt "cmd:rolemenu_editrole|success|error:reaction|name"
msgid "Warning!" msgid "Warning!"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1609 #: src/modules/rolemenus/cog.py:1616
msgctxt "cmd:rolemenu_delrole" msgctxt "cmd:rolemenu_delrole"
msgid "delrole" msgid "delrole"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1612 #: src/modules/rolemenus/cog.py:1619
msgctxt "cmd:rolemenu_delrole|desc" msgctxt "cmd:rolemenu_delrole|desc"
msgid "Remove a role from a role menu." msgid "Remove a role from a role menu."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1616 #: src/modules/rolemenus/cog.py:1623
msgctxt "cmd:rolemenu_delrole|param:menu" msgctxt "cmd:rolemenu_delrole|param:menu"
msgid "menu" msgid "menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1617 #: src/modules/rolemenus/cog.py:1624
msgctxt "cmd:rolemenu_delrole|param:menu_role" msgctxt "cmd:rolemenu_delrole|param:menu_role"
msgid "menu_role" msgid "menu_role"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1622 #: src/modules/rolemenus/cog.py:1629
msgctxt "cmd:rolemenu_delrole|param:menu|desc" msgctxt "cmd:rolemenu_delrole|param:menu|desc"
msgid "Name of the menu to delete the role from." msgid "Name of the menu to delete the role from."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1626 #: src/modules/rolemenus/cog.py:1633
msgctxt "cmd:rolemenu_delrole|param:menu_role|desc" msgctxt "cmd:rolemenu_delrole|param:menu_role|desc"
msgid "Name, label, or mention of the role to delete." msgid "Name, label, or mention of the role to delete."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1644 #: src/modules/rolemenus/cog.py:1651
msgctxt "cmd:rolemenu_delrole|error:author_perms" msgctxt "cmd:rolemenu_delrole|error:author_perms"
msgid "" msgid ""
"You need the `MANAGE_ROLES` permission in order to manage the server role " "You need the `MANAGE_ROLES` permission in order to manage the server role "
"menus." "menus."
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1668 #: src/modules/rolemenus/cog.py:1675
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_delrole|error:menu_not_found" msgctxt "cmd:rolemenu_delrole|error:menu_not_found"
msgid "This server does not have a role menu called `{name}`!" msgid "This server does not have a role menu called `{name}`!"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1696 #: src/modules/rolemenus/cog.py:1703
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_delrole|error:role_not_found" msgctxt "cmd:rolemenu_delrole|error:role_not_found"
msgid "The menu **{menu}** does not have the role **{name}**" msgid "The menu **{menu}** does not have the role **{name}**"
msgstr "" msgstr ""
#: src/modules/rolemenus/cog.py:1713 #: src/modules/rolemenus/cog.py:1720
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:rolemenu_delrole|success" msgctxt "cmd:rolemenu_delrole|success"
msgid "The role **{name}** was successfully removed from the menu **{menu}**." msgid "The role **{name}** was successfully removed from the menu **{menu}**."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:54 #: src/modules/rolemenus/roleoptions.py:57
msgctxt "roleset:role" msgctxt "roleset:role"
msgid "role" msgid "role"
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:57 #: src/modules/rolemenus/roleoptions.py:60
msgctxt "roleset:role|desc" msgctxt "roleset:role|desc"
msgid "The role associated to this menu item." msgid "The role associated to this menu item."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:61 #: src/modules/rolemenus/roleoptions.py:64
msgctxt "roleset:role|long_desc" msgctxt "roleset:role|long_desc"
msgid "The role given when this menu item is selected in the role menu." msgid "The role given when this menu item is selected in the role menu."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:74 #: src/modules/rolemenus/roleoptions.py:77
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "roleset:role|set_response:set" msgctxt "roleset:role|set_response:set"
msgid "This menu item will now give the role {role}." msgid "This menu item will now give the role {role}."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:82 #: src/modules/rolemenus/roleoptions.py:88
msgctxt "roleset:label" msgctxt "roleset:label"
msgid "label" msgid "label"
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:85 #: src/modules/rolemenus/roleoptions.py:91
msgctxt "roleset:label|desc" msgctxt "roleset:label|desc"
msgid "A short button label for this role." msgid "A short button label for this role."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:90 #: src/modules/rolemenus/roleoptions.py:96
msgctxt "roleset:label|long_desc" msgctxt "roleset:label|long_desc"
msgid "" msgid ""
"A short name for this role, to be displayed in button labels, dropdown " "A short name for this role, to be displayed in button labels, dropdown "
"titles, and some menu layouts. By default uses the Discord role name." "titles, and some menu layouts. By default uses the Discord role name."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:104 #: src/modules/rolemenus/roleoptions.py:110
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "roleset:role|set_response" msgctxt "roleset:role|set_response"
msgid "This menu role is now called `{value}`." msgid "This menu role is now called `{value}`."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:112 #: src/modules/rolemenus/roleoptions.py:118
msgctxt "roleset:emoji" msgctxt "roleset:emoji"
msgid "emoji" msgid "emoji"
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:115 #: src/modules/rolemenus/roleoptions.py:121
msgctxt "roleset:emoji|desc" msgctxt "roleset:emoji|desc"
msgid "The emoji associated with this role." msgid "The emoji associated with this role."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:119 #: src/modules/rolemenus/roleoptions.py:125
msgctxt "roleset:emoji|long_desc" msgctxt "roleset:emoji|long_desc"
msgid "" msgid ""
"The role emoji is used for the reaction (in reaction role menus), and " "The role emoji is used for the reaction (in reaction role menus), and "
@@ -685,110 +690,120 @@ msgid ""
"The emoji is also displayed next to the role in most menu templates." "The emoji is also displayed next to the role in most menu templates."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:157 #: src/modules/rolemenus/roleoptions.py:165
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "roleset:emoji|error:test_emoji" msgctxt "roleset:emoji|error:test_emoji"
msgid "The selected emoji `{emoji}` is invalid or has been deleted." msgid "The selected emoji `{emoji}` is invalid or has been deleted."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:168 #: src/modules/rolemenus/roleoptions.py:176
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "roleset:emoji|set_response:set" msgctxt "roleset:emoji|set_response:set"
msgid "The menu role emoji is now {emoji}." msgid "The menu role emoji is now {emoji}."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:173 #: src/modules/rolemenus/roleoptions.py:181
msgctxt "roleset:emoji|set_response:unset" msgctxt "roleset:emoji|set_response:unset"
msgid "The menu role emoji has been removed." msgid "The menu role emoji has been removed."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:181 #: src/modules/rolemenus/roleoptions.py:189
msgctxt "roleset:description" msgctxt "roleset:description"
msgid "description" msgid "description"
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:184 #: src/modules/rolemenus/roleoptions.py:192
msgctxt "roleset:description|desc" msgctxt "roleset:description|desc"
msgid "A longer description of this role." msgid "A longer description of this role."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:189 #: src/modules/rolemenus/roleoptions.py:197
msgctxt "roleset:description|long_desc" msgctxt "roleset:description|long_desc"
msgid "" msgid ""
"The description is displayed under the role label in dropdown style menus. " "The description is displayed under the role label in dropdown style menus. "
"It may also be used as a substitution key in custom role selection responses." "It may also be used as a substitution key in custom role selection responses."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:205 #: src/modules/rolemenus/roleoptions.py:213
msgctxt "roleset:description|set_response:set" msgctxt "roleset:description|set_response:set"
msgid "The role description has been set." msgid "The role description has been set."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:210 #: src/modules/rolemenus/roleoptions.py:218
msgctxt "roleset:description|set_response:unset" msgctxt "roleset:description|set_response:unset"
msgid "The role description has been removed." msgid "The role description has been removed."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:218 #: src/modules/rolemenus/roleoptions.py:226
msgctxt "roleset:price" msgctxt "roleset:price"
msgid "price" msgid "price"
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:221
msgctxt "roleset:price|desc"
msgid "Price of the role, in LionCoins."
msgstr ""
#: src/modules/rolemenus/roleoptions.py:225
msgctxt "roleset:price|long_desc"
msgid "How much the role costs when selected, in LionCoins."
msgstr ""
#: src/modules/rolemenus/roleoptions.py:229 #: src/modules/rolemenus/roleoptions.py:229
msgctxt "roleset:price|desc"
msgid "Price of the role, in LionCoins. May be negative."
msgstr ""
#: src/modules/rolemenus/roleoptions.py:233
msgctxt "roleset:price|long_desc"
msgid ""
"How many LionCoins should be deducted from a member's account when they "
"equip this role through this menu.\n"
"The price may be negative, in which case the member will instead be rewarded "
"coins when they equip the role."
msgstr ""
#: src/modules/rolemenus/roleoptions.py:240
msgctxt "roleset:price|accepts" msgctxt "roleset:price|accepts"
msgid "Amount of coins that the role costs." msgid "Amount of coins that the role costs when equipped."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:242 #: src/modules/rolemenus/roleoptions.py:254
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "roleset:price|set_response:set" msgctxt "roleset:price|set_response:positive"
msgid "This role will now cost {price} to equip." msgid "Equipping this role will now cost {coin}**{price}**."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:247 #: src/modules/rolemenus/roleoptions.py:259
msgctxt "roleset:price|set_response:unset" msgctxt "roleset:price|set_response:zero"
msgid "This role will now be free to equip from this role menu." msgid "Equipping this role is now free."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:255 #: src/modules/rolemenus/roleoptions.py:264
#, possible-python-brace-format
msgctxt "roleset:price|set_response:negative"
msgid "Equipping this role will now reward {coin}**{price}**."
msgstr ""
#: src/modules/rolemenus/roleoptions.py:272
msgctxt "roleset:duration" msgctxt "roleset:duration"
msgid "duration" msgid "duration"
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:258 #: src/modules/rolemenus/roleoptions.py:275
msgctxt "roleset:duration|desc" msgctxt "roleset:duration|desc"
msgid "Lifetime of the role after selection" msgid "Lifetime of the role after selection"
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:262 #: src/modules/rolemenus/roleoptions.py:279
msgctxt "roleset:duration|long_desc" msgctxt "roleset:duration|long_desc"
msgid "" msgid ""
"Allows creation of 'temporary roles' which expire a given time after being " "Allows creation of 'temporary roles' which expire a given time after being "
"equipped. Refunds will not be given upon expiry." "equipped. Refunds will not be given upon expiry."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:267 #: src/modules/rolemenus/roleoptions.py:284
msgctxt "roleset:duration|notset" msgctxt "roleset:duration|notset"
msgid "Forever." msgid "Forever."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:280 #: src/modules/rolemenus/roleoptions.py:297
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "roleset:duration|set_response:set" msgctxt "roleset:duration|set_response:set"
msgid "This role will now expire after {duration}." msgid "This role will now expire after {duration}."
msgstr "" msgstr ""
#: src/modules/rolemenus/roleoptions.py:285 #: src/modules/rolemenus/roleoptions.py:302
msgctxt "roleset:duration|set_response:unset" msgctxt "roleset:duration|set_response:unset"
msgid "This role will no longer expire after being selected." msgid "This role will no longer expire after being selected."
msgstr "" msgstr ""
@@ -893,25 +908,31 @@ msgctxt "rolemenu|deselect|success:refund|desc"
msgid "You have removed **{role}**, and been refunded {coin} **{amount}**." msgid "You have removed **{role}**, and been refunded {coin} **{amount}**."
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:540 #: src/modules/rolemenus/rolemenu.py:541
#, possible-python-brace-format
msgctxt "rolemenu|deselect|success:negrefund|desc"
msgid "You have removed **{role}**, and have lost {coin} **{amount}**."
msgstr ""
#: src/modules/rolemenus/rolemenu.py:546
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|deselect|success:norefund|desc" msgctxt "rolemenu|deselect|success:norefund|desc"
msgid "You have unequipped **{role}**." msgid "You have unequipped **{role}**."
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:554 #: src/modules/rolemenus/rolemenu.py:560
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|select|error:required_role" msgctxt "rolemenu|select|error:required_role"
msgid "You need to have the **{role}** role to use this!" msgid "You need to have the role **{role}** required to use this menu!"
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:568 #: src/modules/rolemenus/rolemenu.py:574
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|select|error:max_obtainable" msgctxt "rolemenu|select|error:max_obtainable"
msgid "You already have the maximum of {obtainable} roles from this menu!" msgid "You already have the maximum of {obtainable} roles from this menu!"
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:582 #: src/modules/rolemenus/rolemenu.py:588
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|select|error:insufficient_funds" msgctxt "rolemenu|select|error:insufficient_funds"
msgid "" msgid ""
@@ -919,41 +940,41 @@ msgid ""
"**{balance}**!" "**{balance}**!"
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:598 #: src/modules/rolemenus/rolemenu.py:604
msgctxt "rolemenu|select|error:perms" msgctxt "rolemenu|select|error:perms"
msgid "I don't have enough permissions to give you this role!" msgid "I don't have enough permissions to give you this role!"
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:605 #: src/modules/rolemenus/rolemenu.py:611
msgctxt "rolemenu|select|error:discord" msgctxt "rolemenu|select|error:discord"
msgid "" msgid ""
"An unknown error occurred while assigning your role! Please try again later." "An unknown error occurred while assigning your role! Please try again later."
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:647 #: src/modules/rolemenus/rolemenu.py:653
msgctxt "rolemenu|select|success|title" msgctxt "rolemenu|select|success|title"
msgid "Role equipped" msgid "Role equipped"
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:653 #: src/modules/rolemenus/rolemenu.py:659
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|select|success:purchase|desc" msgctxt "rolemenu|select|success:purchase|desc"
msgid "You have purchased the role **{role}** for {coin}**{amount}**" msgid "You have purchased the role **{role}** for {coin}**{amount}**"
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:658 #: src/modules/rolemenus/rolemenu.py:664
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|select|success:nopurchase|desc" msgctxt "rolemenu|select|success:nopurchase|desc"
msgid "You have equipped the role **{role}**" msgid "You have equipped the role **{role}**"
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:664 #: src/modules/rolemenus/rolemenu.py:670
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|select|expires_at" msgctxt "rolemenu|select|expires_at"
msgid "The role will expire at {timestamp}." msgid "The role will expire at {timestamp}."
msgstr "" msgstr ""
#: src/modules/rolemenus/rolemenu.py:717 #: src/modules/rolemenus/rolemenu.py:724
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "rolemenu|content:reactions" msgctxt "rolemenu|content:reactions"
msgid "[Click here]({jump_link}) to jump back." msgid "[Click here]({jump_link}) to jump back."
@@ -1200,251 +1221,251 @@ msgctxt "ui:menu_editor|button:bulk_edit|modal|title"
msgid "Menu Options" msgid "Menu Options"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:215 #: src/modules/rolemenus/ui/menueditor.py:218
msgctxt "ui:menu_editor|button:bulk_edit|label" msgctxt "ui:menu_editor|button:bulk_edit|label"
msgid "Bulk Edit" msgid "Bulk Edit"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:235 #: src/modules/rolemenus/ui/menueditor.py:238
msgctxt "ui:menu_editor|button:sticky|label" msgctxt "ui:menu_editor|button:sticky|label"
msgid "Toggle Sticky" msgid "Toggle Sticky"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:259 #: src/modules/rolemenus/ui/menueditor.py:262
msgctxt "ui:menu_editor|button:refunds|label" msgctxt "ui:menu_editor|button:refunds|label"
msgid "Toggle Refunds" msgid "Toggle Refunds"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:289 #: src/modules/rolemenus/ui/menueditor.py:292
msgctxt "ui:menu_editor|menu:reqroles|placeholder" msgctxt "ui:menu_editor|menu:reqroles|placeholder"
msgid "Select Required Role" msgid "Select Required Role"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:308 #: src/modules/rolemenus/ui/menueditor.py:311
msgctxt "ui:menu_editor|button:modify_roles|label" msgctxt "ui:menu_editor|button:modify_roles|label"
msgid "Modify Roles" msgid "Modify Roles"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:337 #: src/modules/rolemenus/ui/menueditor.py:340
msgctxt "ui:menu_editor|role_editor|modal|title" msgctxt "ui:menu_editor|role_editor|modal|title"
msgid "Edit Menu Role" msgid "Edit Menu Role"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:411 #: src/modules/rolemenus/ui/menueditor.py:416
msgctxt "ui:menu_editor|menu:add_roles|error:too_many_reactions" msgctxt "ui:menu_editor|menu:add_roles|error:too_many_reactions"
msgid "Too many roles! Reaction role menus cannot exceed `20` roles." msgid "Too many roles! Reaction role menus cannot exceed `20` roles."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:416 #: src/modules/rolemenus/ui/menueditor.py:421
msgctxt "ui:menu_editor|menu:add_roles|error:too_many_roles" msgctxt "ui:menu_editor|menu:add_roles|error:too_many_roles"
msgid "Too many roles! Role menus cannot have more than `25` roles." msgid "Too many roles! Role menus cannot have more than `25` roles."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:455 #: src/modules/rolemenus/ui/menueditor.py:460
msgctxt "ui:menu_editor|menu:add_roles|placeholder" msgctxt "ui:menu_editor|menu:add_roles|placeholder"
msgid "Add Roles" msgid "Add Roles"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:481 #: src/modules/rolemenus/ui/menueditor.py:486
msgctxt "ui:menu_editor|menu:edit_roles|placeholder" msgctxt "ui:menu_editor|menu:edit_roles|placeholder"
msgid "Edit Roles" msgid "Edit Roles"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:514 #: src/modules/rolemenus/ui/menueditor.py:524
msgctxt "ui:menu_editor|menu:del_role|placeholder" msgctxt "ui:menu_editor|menu:del_role|placeholder"
msgid "Remove Roles" msgid "Remove Roles"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:538 #: src/modules/rolemenus/ui/menueditor.py:548
msgctxt "ui:menu_editor|button:style|error:non-managed" msgctxt "ui:menu_editor|button:style|error:non-managed"
msgid "" msgid ""
"Cannot change the style of a menu attached to a message I did not send! " "Cannot change the style of a menu attached to a message I did not send! "
"Please repost first." "Please repost first."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:551 #: src/modules/rolemenus/ui/menueditor.py:561
msgctxt "ui:menu_editor|button:style|label" msgctxt "ui:menu_editor|button:style|label"
msgid "Menu Style" msgid "Menu Style"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:576 #: src/modules/rolemenus/ui/menueditor.py:586
msgctxt "ui:menu_editor|menu:style|error:too_many_reactions" msgctxt "ui:menu_editor|menu:style|error:too_many_reactions"
msgid "" msgid ""
"Too many roles! The Reaction style is limited to `20` roles (Discord " "Too many roles! The Reaction style is limited to `20` roles (Discord "
"limitation)." "limitation)."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:586 #: src/modules/rolemenus/ui/menueditor.py:596
msgctxt "ui:menu_editor|menu:style|error:incomplete_emojis" msgctxt "ui:menu_editor|menu:style|error:incomplete_emojis"
msgid "" msgid ""
"Cannot switch to the Reaction Role Style! Every role needs to have a " "Cannot switch to the Reaction Role Style! Every role needs to have a "
"distinct emoji first." "distinct emoji first."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:602 #: src/modules/rolemenus/ui/menueditor.py:614
msgctxt "ui:menu_editor|menu:style|placeholder" msgctxt "ui:menu_editor|menu:style|placeholder"
msgid "Select Menu Style" msgid "Select Menu Style"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:606 #: src/modules/rolemenus/ui/menueditor.py:618
msgctxt "ui:menu_editor|menu:style|option:reaction|label" msgctxt "ui:menu_editor|menu:style|option:reaction|label"
msgid "Reaction Roles" msgid "Reaction Roles"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:609 #: src/modules/rolemenus/ui/menueditor.py:621
msgctxt "ui:menu_editor|menu:style|option:reaction|desc" msgctxt "ui:menu_editor|menu:style|option:reaction|desc"
msgid "Roles are represented compactly as clickable reactions on a message." msgid "Roles are represented compactly as clickable reactions on a message."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:615 #: src/modules/rolemenus/ui/menueditor.py:627
msgctxt "ui:menu_editor|menu:style|option:button|label" msgctxt "ui:menu_editor|menu:style|option:button|label"
msgid "Button Menu" msgid "Button Menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:618 #: src/modules/rolemenus/ui/menueditor.py:630
msgctxt "ui:menu_editor|menu:style|option:button|desc" msgctxt "ui:menu_editor|menu:style|option:button|desc"
msgid "" msgid ""
"Roles are represented in 5 rows of 5 buttons, each with an emoji and label." "Roles are represented in 5 rows of 5 buttons, each with an emoji and label."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:624 #: src/modules/rolemenus/ui/menueditor.py:636
msgctxt "ui:menu_editor|menu:style|option:dropdown|label" msgctxt "ui:menu_editor|menu:style|option:dropdown|label"
msgid "Dropdown Menu" msgid "Dropdown Menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:627 #: src/modules/rolemenus/ui/menueditor.py:639
msgctxt "ui:menu_editor|menu:style|option:dropdown|desc" msgctxt "ui:menu_editor|menu:style|option:dropdown|desc"
msgid "Roles are selectable from a dropdown menu below the message." msgid "Roles are selectable from a dropdown menu below the message."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:697 #: src/modules/rolemenus/ui/menueditor.py:709
msgctxt "ui:menu_editor|menu:template|placeholder" msgctxt "ui:menu_editor|menu:template|placeholder"
msgid "Select Message Template" msgid "Select Message Template"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:707 #: src/modules/rolemenus/ui/menueditor.py:719
msgctxt "ui:menu_editor|menu:template|option:custom|label" msgctxt "ui:menu_editor|menu:template|option:custom|label"
msgid "Custom Message" msgid "Custom Message"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:712 #: src/modules/rolemenus/ui/menueditor.py:724
msgctxt "ui:menu_editor|menu:template|option:custom|description" msgctxt "ui:menu_editor|menu:template|option:custom|description"
msgid "Entirely custom menu message (opens an interactive editor)." msgid "Entirely custom menu message (opens an interactive editor)."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:729 #: src/modules/rolemenus/ui/menueditor.py:741
msgctxt "ui:menu_editor|button:delete|confirm|title" msgctxt "ui:menu_editor|button:delete|confirm|title"
msgid "Are you sure you want to delete this menu? This is not reversible!" msgid "Are you sure you want to delete this menu? This is not reversible!"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:734 #: src/modules/rolemenus/ui/menueditor.py:746
msgctxt "ui:menu_editor|button:delete|confirm|button:yes" msgctxt "ui:menu_editor|button:delete|confirm|button:yes"
msgid "Yes, Delete Now" msgid "Yes, Delete Now"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:739 #: src/modules/rolemenus/ui/menueditor.py:751
msgctxt "ui:menu_editor|button:delete|confirm|button:no" msgctxt "ui:menu_editor|button:delete|confirm|button:no"
msgid "No, Go Back" msgid "No, Go Back"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:756 #: src/modules/rolemenus/ui/menueditor.py:768
msgctxt "ui:menu_editor|button:delete|label" msgctxt "ui:menu_editor|button:delete|label"
msgid "Delete Menu" msgid "Delete Menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:813 #: src/modules/rolemenus/ui/menueditor.py:825
msgctxt "ui:menu_editor|button:edit_msg|label" msgctxt "ui:menu_editor|button:edit_msg|label"
msgid "Edit Message" msgid "Edit Message"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:844 #: src/modules/rolemenus/ui/menueditor.py:851
msgctxt "ui:menu_editor|button:preview|label" msgctxt "ui:menu_editor|button:preview|label"
msgid "Preview" msgid "Preview"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:860 #: src/modules/rolemenus/ui/menueditor.py:867
msgctxt "ui:menu_editor|button:repost|widget:repost|menu:channel|placeholder" msgctxt "ui:menu_editor|button:repost|widget:repost|menu:channel|placeholder"
msgid "Select New Channel" msgid "Select New Channel"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:878 #: src/modules/rolemenus/ui/menueditor.py:885
msgctxt "ui:menu_editor|button:repost|widget:repost|error:perms|title" msgctxt "ui:menu_editor|button:repost|widget:repost|error:perms|title"
msgid "Insufficient Permissions!" msgid "Insufficient Permissions!"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:882 #: src/modules/rolemenus/ui/menueditor.py:889
msgctxt "ui:menu_editor|button:repost|eidget:repost|error:perms|desc" msgctxt "ui:menu_editor|button:repost|eidget:repost|error:perms|desc"
msgid "I lack the `EMBED_LINKS` or `SEND_MESSAGES` permission in this channel." msgid "I lack the `EMBED_LINKS` or `SEND_MESSAGES` permission in this channel."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:895 #: src/modules/rolemenus/ui/menueditor.py:902
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:menu_editor|button:repost|widget:repost|error:post_failed" msgctxt "ui:menu_editor|button:repost|widget:repost|error:post_failed"
msgid "" msgid ""
"An error ocurred while posting to {channel}. Do I have sufficient " "An unknown error ocurred while posting to {channel}!\n"
"permissions?" "**Error:** `{exception}`"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:907 #: src/modules/rolemenus/ui/menueditor.py:915
msgctxt "ui:menu_editor|button:repost|widget:repost|success|title" msgctxt "ui:menu_editor|button:repost|widget:repost|success|title"
msgid "Role Menu Moved" msgid "Role Menu Moved"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:913 #: src/modules/rolemenus/ui/menueditor.py:921
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:menu_editor|button:repost|widget:repost|success|desc:general" msgctxt "ui:menu_editor|button:repost|widget:repost|success|desc:general"
msgid "The role menu `{name}` is now available at {message_link}." msgid "The role menu `{name}` is now available at {message_link}."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:927 #: src/modules/rolemenus/ui/menueditor.py:935
msgctxt "ui:menu_editor|button:repost|widget:repost|success|desc:reactions" msgctxt "ui:menu_editor|button:repost|widget:repost|success|desc:reactions"
msgid "Please check the message reactions are correct." msgid "Please check the message reactions are correct."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:940 #: src/modules/rolemenus/ui/menueditor.py:948
msgctxt "ui:menu_editor|button:repost|widget:repost|title" msgctxt "ui:menu_editor|button:repost|widget:repost|title"
msgid "Repost Role Menu" msgid "Repost Role Menu"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:944 #: src/modules/rolemenus/ui/menueditor.py:952
msgctxt "ui:menu_editor|button:repost|widget:repost|description" msgctxt "ui:menu_editor|button:repost|widget:repost|description"
msgid "Please select the channel to which you want to resend this menu." msgid "Please select the channel to which you want to resend this menu."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:959 #: src/modules/rolemenus/ui/menueditor.py:967
msgctxt "ui:menu_editor|button:repost|label:repost" msgctxt "ui:menu_editor|button:repost|label:repost"
msgid "Repost" msgid "Repost"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:964 #: src/modules/rolemenus/ui/menueditor.py:972
msgctxt "ui:menu_editor|button:repost|label:post" msgctxt "ui:menu_editor|button:repost|label:post"
msgid "Post" msgid "Post"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:974 #: src/modules/rolemenus/ui/menueditor.py:982
msgctxt "ui:menu_editor|embed|title" msgctxt "ui:menu_editor|embed|title"
msgid "Role Menu Editor" msgid "Role Menu Editor"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:983 #: src/modules/rolemenus/ui/menueditor.py:991
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:menu_editor|embed|description|jump_text:attached" msgctxt "ui:menu_editor|embed|description|jump_text:attached"
msgid "Members may use this menu from {jump_url}" msgid "Members may use this menu from {jump_url}"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:988 #: src/modules/rolemenus/ui/menueditor.py:996
msgctxt "ui:menu_editor|embed|description|jump_text:unattached" msgctxt "ui:menu_editor|embed|description|jump_text:unattached"
msgid "" msgid ""
"This menu is not currently active!\n" "This menu is not currently active!\n"
"Make it available by clicking `Post` below." "Make it available by clicking `Post` below."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:1002 #: src/modules/rolemenus/ui/menueditor.py:1010
msgctxt "ui:menu_editor|embed|field:tips|name" msgctxt "ui:menu_editor|embed|field:tips|name"
msgid "Command Tips" msgid "Command Tips"
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:1006 #: src/modules/rolemenus/ui/menueditor.py:1014
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:menu_editor|embed|field:tips|value" msgctxt "ui:menu_editor|embed|field:tips|value"
msgid "" msgid ""
@@ -1454,12 +1475,12 @@ msgid ""
"{editrole} to edit role options." "{editrole} to edit role options."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:1046 #: src/modules/rolemenus/ui/menueditor.py:1054
msgctxt "ui:menu_editor|error:invald_emoji|title" msgctxt "ui:menu_editor|error:invald_emoji|title"
msgid "Invalid emoji encountered." msgid "Invalid emoji encountered."
msgstr "" msgstr ""
#: src/modules/rolemenus/ui/menueditor.py:1050 #: src/modules/rolemenus/ui/menueditor.py:1058
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:menu_editor|error:invalid_emoji|desc" msgctxt "ui:menu_editor|error:invalid_emoji|desc"
msgid "" msgid ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,25 +18,25 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: src/modules/schedule/cog.py:429 #: src/modules/schedule/cog.py:478
msgctxt "create_booking|error:no_lobby" msgctxt "create_booking|error:no_lobby"
msgid "" msgid ""
"This server has not set a `session_lobby`, so the scheduled session system " "This server has not set a `session_lobby`, so the scheduled session system "
"is disabled!" "is disabled!"
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:441 #: src/modules/schedule/cog.py:490
msgctxt "create_booking|error:no_member" msgctxt "create_booking|error:no_member"
msgid "An unknown Discord error occurred. Please try again in a few minutes." msgid "An unknown Discord error occurred. Please try again in a few minutes."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:449 #: src/modules/schedule/cog.py:498
msgctxt "create_booking|error:blacklisted" msgctxt "create_booking|error:blacklisted"
msgid "" msgid ""
"You have been blacklisted from the scheduled session system in this server." "You have been blacklisted from the scheduled session system in this server."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:460 #: src/modules/schedule/cog.py:509
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "create_booking|error:insufficient_balance" msgctxt "create_booking|error:insufficient_balance"
msgid "" msgid ""
@@ -48,22 +48,22 @@ msgid_plural ""
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: src/modules/schedule/cog.py:474 #: src/modules/schedule/cog.py:523
msgctxt "create_booking|error:already_booked" msgctxt "create_booking|error:already_booked"
msgid "One or more requested timeslots are already booked!" msgid "One or more requested timeslots are already booked!"
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:677 #: src/modules/schedule/cog.py:726
msgctxt "cmd:schedule" msgctxt "cmd:schedule"
msgid "schedule" msgid "schedule"
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:680 #: src/modules/schedule/cog.py:729
msgctxt "cmd:schedule|desc" msgctxt "cmd:schedule|desc"
msgid "View and manage your scheduled session." msgid "View and manage your scheduled session."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:708 #: src/modules/schedule/cog.py:757
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|cancel_booking|error:parse_slot" msgctxt "cmd:schedule|cancel_booking|error:parse_slot"
msgid "" msgid ""
@@ -71,25 +71,25 @@ msgid ""
"from the autocomplete options." "from the autocomplete options."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:716 #: src/modules/schedule/cog.py:765
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|cancel_booking|error:not_booked" msgctxt "cmd:schedule|cancel_booking|error:not_booked"
msgid "Could not cancel {time} booking because it is not booked!" msgid "Could not cancel {time} booking because it is not booked!"
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:725 #: src/modules/schedule/cog.py:774
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|cancel_booking|error:too_soon" msgctxt "cmd:schedule|cancel_booking|error:too_soon"
msgid "Cannot cancel {time} booking because it is running or starting soon!" msgid "Cannot cancel {time} booking because it is running or starting soon!"
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:738 #: src/modules/schedule/cog.py:787
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|cancel_booking|success" msgctxt "cmd:schedule|cancel_booking|success"
msgid "Successfully cancelled your booking at {time}." msgid "Successfully cancelled your booking at {time}."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:751 #: src/modules/schedule/cog.py:800
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|create_booking|error:parse_slot" msgctxt "cmd:schedule|create_booking|error:parse_slot"
msgid "" msgid ""
@@ -97,30 +97,30 @@ msgid ""
"from the autocomplete options." "from the autocomplete options."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:759 #: src/modules/schedule/cog.py:808
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|create_booking|error:already_booked" msgctxt "cmd:schedule|create_booking|error:already_booked"
msgid "You have already booked a scheduled session for {time}." msgid "You have already booked a scheduled session for {time}."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:768 #: src/modules/schedule/cog.py:817
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|create_booking|error:too_soon" msgctxt "cmd:schedule|create_booking|error:too_soon"
msgid "Cannot book session at {time} because it is running or starting soon!" msgid "Cannot book session at {time} because it is running or starting soon!"
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:780 #: src/modules/schedule/cog.py:829
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:schedule|create_booking|success" msgctxt "cmd:schedule|create_booking|success"
msgid "You have successfully scheduled a session at {time}." msgid "You have successfully scheduled a session at {time}."
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:847 #: src/modules/schedule/cog.py:896
msgctxt "cmd:configure_schedule" msgctxt "cmd:configure_schedule"
msgid "schedule" msgid "schedule"
msgstr "" msgstr ""
#: src/modules/schedule/cog.py:850 #: src/modules/schedule/cog.py:899
msgctxt "cmd:configure_schedule|desc" msgctxt "cmd:configure_schedule|desc"
msgid "Configure Scheduled Session system" msgid "Configure Scheduled Session system"
msgstr "" msgstr ""
@@ -841,7 +841,7 @@ msgid ""
"`MANAGE_WEBHOOKS` permission." "`MANAGE_WEBHOOKS` permission."
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:274 #: src/modules/schedule/core/session.py:280
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "session|prepare|error:room_permissions" msgctxt "session|prepare|error:room_permissions"
msgid "" msgid ""
@@ -850,7 +850,7 @@ msgid ""
"`VIEW_CHANNEL` permissions." "`VIEW_CHANNEL` permissions."
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:317 #: src/modules/schedule/core/session.py:330
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "session|open|error:room_permissions" msgctxt "session|open|error:room_permissions"
msgid "" msgid ""
@@ -859,57 +859,57 @@ msgid ""
"`VIEW_CHANNEL` permissions." "`VIEW_CHANNEL` permissions."
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:358 #: src/modules/schedule/core/session.py:371
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "session|status|title" msgctxt "session|status|title"
msgid "Session {start} - {end}" msgid "Session {start} - {end}"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:369 #: src/modules/schedule/core/session.py:382
msgctxt "session|status|desc:cancelled" msgctxt "session|status|desc:cancelled"
msgid "" msgid ""
"I cancelled this scheduled session because I was unavailable. All members " "I cancelled this scheduled session because I was unavailable. All members "
"who booked the session have been refunded." "who booked the session have been refunded."
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:376 #: src/modules/schedule/core/session.py:389
msgctxt "session|status|desc:no_members" msgctxt "session|status|desc:no_members"
msgid "*No members scheduled this session.*" msgid "*No members scheduled this session.*"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:382 #: src/modules/schedule/core/session.py:395
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "session|status:preparing|desc:has_members" msgctxt "session|status:preparing|desc:has_members"
msgid "Starting {start}" msgid "Starting {start}"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:385 #: src/modules/schedule/core/session.py:398
msgctxt "session|status:preparing|field:members" msgctxt "session|status:preparing|field:members"
msgid "Members" msgid "Members"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:392 #: src/modules/schedule/core/session.py:405
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "session|status:running|desc:has_members" msgctxt "session|status:running|desc:has_members"
msgid "Finishing {start}" msgid "Finishing {start}"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:426 #: src/modules/schedule/core/session.py:439
msgctxt "session|status:running|field:waiting" msgctxt "session|status:running|field:waiting"
msgid "Waiting For" msgid "Waiting For"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:432 #: src/modules/schedule/core/session.py:445
msgctxt "session|status:running|field:attending" msgctxt "session|status:running|field:attending"
msgid "Attending" msgid "Attending"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:438 #: src/modules/schedule/core/session.py:451
msgctxt "session|status:running|field:attended" msgctxt "session|status:running|field:attended"
msgid "Attended" msgid "Attended"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:463 #: src/modules/schedule/core/session.py:476
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "session|status:finished|desc:everyone_att" msgctxt "session|status:finished|desc:everyone_att"
msgid "" msgid ""
@@ -917,7 +917,7 @@ msgid ""
"**{reward} + {bonus}**!" "**{reward} + {bonus}**!"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:474 #: src/modules/schedule/core/session.py:487
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "session|status:finished|desc:some_att" msgctxt "session|status:finished|desc:some_att"
msgid "" msgid ""
@@ -927,7 +927,7 @@ msgid ""
"without refund!*" "without refund!*"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:486 #: src/modules/schedule/core/session.py:499
msgctxt "session|status:finished|desc:some_att" msgctxt "session|status:finished|desc:some_att"
msgid "" msgid ""
"No-one attended this session! No-one received rewards.\n" "No-one attended this session! No-one received rewards.\n"
@@ -935,12 +935,12 @@ msgid ""
"without refund!*" "without refund!*"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:492 #: src/modules/schedule/core/session.py:505
msgctxt "session|status:finished|field:attended" msgctxt "session|status:finished|field:attended"
msgid "Attended" msgid "Attended"
msgstr "" msgstr ""
#: src/modules/schedule/core/session.py:497 #: src/modules/schedule/core/session.py:510
msgctxt "session|status:finished|field:missing" msgctxt "session|status:finished|field:missing"
msgid "Missing" msgid "Missing"
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -84,91 +84,91 @@ msgctxt "settype:role|accepts"
msgid "A role name or id" msgid "A role name or id"
msgstr "" msgstr ""
#: src/settings/setting_types.py:427 #: src/settings/setting_types.py:430
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "settype:role|parse|error:not_found" msgctxt "settype:role|parse|error:not_found"
msgid "Role `{string}` could not be found in this guild!" msgid "Role `{string}` could not be found in this guild!"
msgstr "" msgstr ""
#: src/settings/setting_types.py:492 #: src/settings/setting_types.py:495
msgctxt "settype:bool|accepts" msgctxt "settype:bool|accepts"
msgid "Enabled/Disabled" msgid "Enabled/Disabled"
msgstr "" msgstr ""
#: src/settings/setting_types.py:497 #: src/settings/setting_types.py:500
msgctxt "settype:bool|parse:truthy_values" msgctxt "settype:bool|parse:truthy_values"
msgid "enabled|yes|true|on|enable|1" msgid "enabled|yes|true|on|enable|1"
msgstr "" msgstr ""
#: src/settings/setting_types.py:501 #: src/settings/setting_types.py:504
msgctxt "settype:bool|parse:falsey_values" msgctxt "settype:bool|parse:falsey_values"
msgid "disabled|no|false|off|disable|0" msgid "disabled|no|false|off|disable|0"
msgstr "" msgstr ""
#: src/settings/setting_types.py:506 #: src/settings/setting_types.py:509
msgctxt "settype:bool|output:true" msgctxt "settype:bool|output:true"
msgid "On" msgid "On"
msgstr "" msgstr ""
#: src/settings/setting_types.py:507 #: src/settings/setting_types.py:510
msgctxt "settype:bool|output:false" msgctxt "settype:bool|output:false"
msgid "Off" msgid "Off"
msgstr "" msgstr ""
#: src/settings/setting_types.py:508 #: src/settings/setting_types.py:511
msgctxt "settype:bool|output:none" msgctxt "settype:bool|output:none"
msgid "Not Set" msgid "Not Set"
msgstr "" msgstr ""
#: src/settings/setting_types.py:619 #: src/settings/setting_types.py:622
msgctxt "settype:integer|accepts" msgctxt "settype:integer|accepts"
msgid "An integer" msgid "An integer"
msgstr "" msgstr ""
#: src/settings/setting_types.py:682 #: src/settings/setting_types.py:685
msgctxt "settype:emoji|desc" msgctxt "settype:emoji|desc"
msgid "Unicode or custom emoji" msgid "Unicode or custom emoji"
msgstr "" msgstr ""
#: src/settings/setting_types.py:754 #: src/settings/setting_types.py:757
msgctxt "settype:guildid|accepts" msgctxt "settype:guildid|accepts"
msgid "Any Snowflake ID" msgid "Any Snowflake ID"
msgstr "" msgstr ""
#: src/settings/setting_types.py:823 #: src/settings/setting_types.py:826
msgctxt "settype:timezone|accepts" msgctxt "settype:timezone|accepts"
msgid "A timezone name from the 'tz database' (e.g. 'Europe/London')" msgid "A timezone name from the 'tz database' (e.g. 'Europe/London')"
msgstr "" msgstr ""
#: src/settings/setting_types.py:893 #: src/settings/setting_types.py:896
msgctxt "settype:timezone|summary_table|field:supported|key" msgctxt "settype:timezone|summary_table|field:supported|key"
msgid "Supported" msgid "Supported"
msgstr "" msgstr ""
#: src/settings/setting_types.py:897 #: src/settings/setting_types.py:900
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "settype:timezone|summary_table|field:supported|value" msgctxt "settype:timezone|summary_table|field:supported|value"
msgid "Any timezone from the [tz database]({link})." msgid "Any timezone from the [tz database]({link})."
msgstr "" msgstr ""
#: src/settings/setting_types.py:914 #: src/settings/setting_types.py:917
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "set_type:timezone|acmpl|no_matching" msgctxt "set_type:timezone|acmpl|no_matching"
msgid "No timezones matching '{input}'!" msgid "No timezones matching '{input}'!"
msgstr "" msgstr ""
#: src/settings/setting_types.py:927 #: src/settings/setting_types.py:930
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "set_type:timezone|acmpl|choice" msgctxt "set_type:timezone|acmpl|choice"
msgid "{tz} (Currently {now})" msgid "{tz} (Currently {now})"
msgstr "" msgstr ""
#: src/settings/setting_types.py:957 #: src/settings/setting_types.py:960
msgctxt "settype:timestamp|accepts" msgctxt "settype:timestamp|accepts"
msgid "A timestamp in the form YYYY-MM-DD HH:MM" msgid "A timestamp in the form YYYY-MM-DD HH:MM"
msgstr "" msgstr ""
#: src/settings/setting_types.py:986 #: src/settings/setting_types.py:989
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "settype:timestamp|parse|error:invalid" msgctxt "settype:timestamp|parse|error:invalid"
msgid "" msgid ""
@@ -176,43 +176,43 @@ msgid ""
"format." "format."
msgstr "" msgstr ""
#: src/settings/setting_types.py:1017 #: src/settings/setting_types.py:1020
msgctxt "settype:raw|accepts" msgctxt "settype:raw|accepts"
msgid "Anything" msgid "Anything"
msgstr "" msgstr ""
#: src/settings/setting_types.py:1070 #: src/settings/setting_types.py:1073
msgctxt "settype:enum|accepts" msgctxt "settype:enum|accepts"
msgid "A valid option." msgid "A valid option."
msgstr "" msgstr ""
#: src/settings/setting_types.py:1120 #: src/settings/setting_types.py:1123
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "settype:enum|parse|error:not_found" msgctxt "settype:enum|parse|error:not_found"
msgid "`{provided}` is not a valid option!" msgid "`{provided}` is not a valid option!"
msgstr "" msgstr ""
#: src/settings/setting_types.py:1168 #: src/settings/setting_types.py:1171
msgctxt "settype:duration|accepts" msgctxt "settype:duration|accepts"
msgid "A number of days, hours, minutes, and seconds, e.g. `2d 4h 10s`." msgid "A number of days, hours, minutes, and seconds, e.g. `2d 4h 10s`."
msgstr "" msgstr ""
#: src/settings/setting_types.py:1349 #: src/settings/setting_types.py:1352
msgctxt "settype:channel_list|accepts" msgctxt "settype:channel_list|accepts"
msgid "Comma separated list of channel ids." msgid "Comma separated list of channel ids."
msgstr "" msgstr ""
#: src/settings/setting_types.py:1360 #: src/settings/setting_types.py:1363
msgctxt "settype:role_list|accepts" msgctxt "settype:role_list|accepts"
msgid "Comma separated list of role ids." msgid "Comma separated list of role ids."
msgstr "" msgstr ""
#: src/settings/setting_types.py:1376 #: src/settings/setting_types.py:1379
msgctxt "settype:stringlist|accepts" msgctxt "settype:stringlist|accepts"
msgid "Comma separated strings." msgid "Comma separated strings."
msgstr "" msgstr ""
#: src/settings/setting_types.py:1387 #: src/settings/setting_types.py:1390
msgctxt "settype:guildidlist|accepts" msgctxt "settype:guildidlist|accepts"
msgid "Comma separated list of guild ids." msgid "Comma separated list of guild ids."
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -32,27 +32,27 @@ msgctxt "cmd:shop|desc"
msgid "Purchase coloures, roles, and other goodies with LionCoins." msgid "Purchase coloures, roles, and other goodies with LionCoins."
msgstr "" msgstr ""
#: src/modules/shop/cog.py:124 #: src/modules/shop/cog.py:125
msgctxt "cmd:shop_open" msgctxt "cmd:shop_open"
msgid "open" msgid "open"
msgstr "" msgstr ""
#: src/modules/shop/cog.py:125 #: src/modules/shop/cog.py:126
msgctxt "cmd:shop_open|desc" msgctxt "cmd:shop_open|desc"
msgid "Open the server shop." msgid "Open the server shop."
msgstr "" msgstr ""
#: src/modules/shop/cog.py:151 #: src/modules/shop/cog.py:153
msgctxt "cmd:shop_open|error:no_shops" msgctxt "cmd:shop_open|error:no_shops"
msgid "There is nothing to buy!" msgid "There is nothing to buy!"
msgstr "" msgstr ""
#: src/modules/shop/cog.py:213 #: src/modules/shop/cog.py:215
msgctxt "ui:stores|button:close|label" msgctxt "ui:stores|button:close|label"
msgid "Close" msgid "Close"
msgstr "" msgstr ""
#: src/modules/shop/cog.py:220 #: src/modules/shop/cog.py:222
msgctxt "ui:stores|button:close|response|title" msgctxt "ui:stores|button:close|response|title"
msgid "Shop Closed" msgid "Shop Closed"
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,58 +17,58 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: src/modules/statistics/cog.py:42 #: src/modules/statistics/cog.py:43
msgctxt "cmd:me" msgctxt "cmd:me"
msgid "me" msgid "me"
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:45 #: src/modules/statistics/cog.py:46
msgctxt "cmd:me|desc" msgctxt "cmd:me|desc"
msgid "Display your personal profile and summary statistics." msgid "Display your personal profile and summary statistics."
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:55 #: src/modules/statistics/cog.py:56
msgctxt "cmd:stats" msgctxt "cmd:stats"
msgid "stats" msgid "stats"
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:58 #: src/modules/statistics/cog.py:59
msgctxt "cmd:stats|desc" msgctxt "cmd:stats|desc"
msgid "Weekly and monthly statistics for your recent activity." msgid "Weekly and monthly statistics for your recent activity."
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:71 #: src/modules/statistics/cog.py:72
msgctxt "cmd:leaderboard" msgctxt "cmd:leaderboard"
msgid "leaderboard" msgid "leaderboard"
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:74 #: src/modules/statistics/cog.py:75
msgctxt "cmd:leaderboard|desc" msgctxt "cmd:leaderboard|desc"
msgid "Server leaderboard." msgid "Server leaderboard."
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:89 #: src/modules/statistics/cog.py:90
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "cmd:leaderboard|chunking|desc" msgctxt "cmd:leaderboard|chunking|desc"
msgid "Requesting server member list from Discord, please wait {loading}" msgid "Requesting server member list from Discord, please wait {loading}"
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:108 #: src/modules/statistics/cog.py:113
msgctxt "cmd:configure_statistics" msgctxt "cmd:configure_statistics"
msgid "statistics" msgid "statistics"
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:109 #: src/modules/statistics/cog.py:114
msgctxt "cmd:configure_statistics|desc" msgctxt "cmd:configure_statistics|desc"
msgid "Statistics configuration panel" msgid "Statistics configuration panel"
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:112 #: src/modules/statistics/cog.py:117
msgctxt "cmd:configure_statistics|param:season_start" msgctxt "cmd:configure_statistics|param:season_start"
msgid "season_start" msgid "season_start"
msgstr "" msgstr ""
#: src/modules/statistics/cog.py:117 #: src/modules/statistics/cog.py:122
msgctxt "cmd:configure_statistics|param:season_start|desc" msgctxt "cmd:configure_statistics|param:season_start|desc"
msgid "" msgid ""
"Time from which to start counting activity for rank badges and season " "Time from which to start counting activity for rank badges and season "
@@ -613,101 +613,108 @@ msgid ""
"again to revert." "again to revert."
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:250 #: src/modules/statistics/ui/leaderboard.py:253
msgctxt "ui:leaderboard|menu:stats|placeholder" msgctxt "ui:leaderboard|menu:stats|placeholder"
msgid "Select Activity Type" msgid "Select Activity Type"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:259 #: src/modules/statistics/ui/leaderboard.py:262
msgctxt "ui:leaderboard|menu:stats|item:voice" msgctxt "ui:leaderboard|menu:stats|item:voice"
msgid "Voice Activity" msgid "Voice Activity"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:270 #: src/modules/statistics/ui/leaderboard.py:273
msgctxt "ui:leaderboard|menu:stats|item:study" msgctxt "ui:leaderboard|menu:stats|item:study"
msgid "Study Statistics" msgid "Study Statistics"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:281 #: src/modules/statistics/ui/leaderboard.py:284
msgctxt "ui:leaderboard|menu:stats|item:message" msgctxt "ui:leaderboard|menu:stats|item:message"
msgid "Message Activity" msgid "Message Activity"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:292 #: src/modules/statistics/ui/leaderboard.py:295
msgctxt "ui:leaderboard|menu;stats|item:anki" msgctxt "ui:leaderboard|menu;stats|item:anki"
msgid "Anki Cards Reviewed" msgid "Anki Cards Reviewed"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:346 #: src/modules/statistics/ui/leaderboard.py:349
msgctxt "ui:leaderboard|button:season|label" msgctxt "ui:leaderboard|button:season|label"
msgid "This Season" msgid "This Season"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:350 #: src/modules/statistics/ui/leaderboard.py:353
msgctxt "ui:leaderboard|button:day|label" msgctxt "ui:leaderboard|button:day|label"
msgid "Today" msgid "Today"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:354 #: src/modules/statistics/ui/leaderboard.py:357
msgctxt "ui:leaderboard|button:week|label" msgctxt "ui:leaderboard|button:week|label"
msgid "This Week" msgid "This Week"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:358 #: src/modules/statistics/ui/leaderboard.py:361
msgctxt "ui:leaderboard|button:month|label" msgctxt "ui:leaderboard|button:month|label"
msgid "This Month" msgid "This Month"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:362 #: src/modules/statistics/ui/leaderboard.py:365
msgctxt "ui:leaderboard|button:alltime|label" msgctxt "ui:leaderboard|button:alltime|label"
msgid "All Time" msgid "All Time"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:366 #: src/modules/statistics/ui/leaderboard.py:369
msgctxt "ui:leaderboard|button:jump|label" msgctxt "ui:leaderboard|button:jump|label"
msgid "Jump" msgid "Jump"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:381 #: src/modules/statistics/ui/leaderboard.py:384
msgctxt "ui:leaderboard|button:jump|input:title" msgctxt "ui:leaderboard|button:jump|input:title"
msgid "Jump to page" msgid "Jump to page"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:385 #: src/modules/statistics/ui/leaderboard.py:388
msgctxt "ui:leaderboard|button:jump|input:question" msgctxt "ui:leaderboard|button:jump|input:question"
msgid "Page number to jump to" msgid "Page number to jump to"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:396 #: src/modules/statistics/ui/leaderboard.py:399
msgctxt "ui:leaderboard|button:jump|error:invalid_page" msgctxt "ui:leaderboard|button:jump|error:invalid_page"
msgid "Invalid page number, please try again!" msgid "Invalid page number, please try again!"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:442 #: src/modules/statistics/ui/leaderboard.py:443
msgctxt "ui:leaderboard|chunk_warning"
msgid ""
"**Note:** Could not retrieve member list from Discord, so some members may "
"be missing. Try again in a minute!"
msgstr ""
#: src/modules/statistics/ui/leaderboard.py:450
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:leaderboard|since" msgctxt "ui:leaderboard|since"
msgid "Counting statistics since {timestamp}" msgid "Counting statistics since {timestamp}"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:453 #: src/modules/statistics/ui/leaderboard.py:463
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:leaderboard|mode:voice|message:empty|desc" msgctxt "ui:leaderboard|mode:voice|message:empty|desc"
msgid "There has been no voice activity since {timestamp}" msgid "There has been no voice activity since {timestamp}"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:458 #: src/modules/statistics/ui/leaderboard.py:468
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:leaderboard|mode:text|message:empty|desc" msgctxt "ui:leaderboard|mode:text|message:empty|desc"
msgid "There has been no message activity since {timestamp}" msgid "There has been no message activity since {timestamp}"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:463 #: src/modules/statistics/ui/leaderboard.py:473
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ui:leaderboard|mode:anki|message:empty|desc" msgctxt "ui:leaderboard|mode:anki|message:empty|desc"
msgid "There have been no Anki cards reviewed since {timestamp}" msgid "There have been no Anki cards reviewed since {timestamp}"
msgstr "" msgstr ""
#: src/modules/statistics/ui/leaderboard.py:472 #: src/modules/statistics/ui/leaderboard.py:482
msgctxt "ui:leaderboard|message:empty|title" msgctxt "ui:leaderboard|message:empty|title"
msgid "Leaderboard Empty!" msgid "Leaderboard Empty!"
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,68 +17,68 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: src/wards.py:79 #: src/wards.py:83
msgctxt "ward:sys_admin|failed" msgctxt "ward:sys_admin|failed"
msgid "You must be a bot owner to do this!" msgid "You must be a bot owner to do this!"
msgstr "" msgstr ""
#: src/wards.py:95 #: src/wards.py:99
msgctxt "ward:high_management|failed" msgctxt "ward:high_management|failed"
msgid "You must have the `ADMINISTRATOR` permission in this server to do this!" msgid "You must have the `ADMINISTRATOR` permission in this server to do this!"
msgstr "" msgstr ""
#: src/wards.py:111 #: src/wards.py:115
msgctxt "ward:low_management|failed" msgctxt "ward:low_management|failed"
msgid "You must have the `MANAGE_GUILD` permission in this server to do this!" msgid "You must have the `MANAGE_GUILD` permission in this server to do this!"
msgstr "" msgstr ""
#: src/wards.py:123 #: src/wards.py:127
msgctxt "ward:moderator|failed" msgctxt "ward:moderator|failed"
msgid "" msgid ""
"You must have the configured moderator role, or `MANAGE_GUILD` permissions " "You must have the configured moderator role, or `MANAGE_GUILD` permissions "
"to do this." "to do this."
msgstr "" msgstr ""
#: src/wards.py:149 #: src/wards.py:153
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ward:equippable_role|error:bot_managed" msgctxt "ward:equippable_role|error:bot_managed"
msgid "I cannot manage {role} because it is managed by another bot!" msgid "I cannot manage {role} because it is managed by another bot!"
msgstr "" msgstr ""
#: src/wards.py:156 #: src/wards.py:160
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ward:equippable_role|error:integration" msgctxt "ward:equippable_role|error:integration"
msgid "I cannot manage {role} because it is managed by a server integration." msgid "I cannot manage {role} because it is managed by a server integration."
msgstr "" msgstr ""
#: src/wards.py:163 #: src/wards.py:167
msgctxt "ward:equippable_role|error:default_role" msgctxt "ward:equippable_role|error:default_role"
msgid "I cannot manage the server's default role." msgid "I cannot manage the server's default role."
msgstr "" msgstr ""
#: src/wards.py:170 #: src/wards.py:174
msgctxt "ward:equippable_role|error:no_perms" msgctxt "ward:equippable_role|error:no_perms"
msgid "I need the `MANAGE_ROLES` permission before I can manage roles!" msgid "I need the `MANAGE_ROLES` permission before I can manage roles!"
msgstr "" msgstr ""
#: src/wards.py:177 #: src/wards.py:181
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ward:equippable_role|error:my_top_role" msgctxt "ward:equippable_role|error:my_top_role"
msgid "I cannot assign or remove {role} because it is above my top role!" msgid "I cannot assign or remove {role} because it is above my top role!"
msgstr "" msgstr ""
#: src/wards.py:184 #: src/wards.py:188
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ward:equippable_role|error:not_assignable" msgctxt "ward:equippable_role|error:not_assignable"
msgid "I don't have sufficient permissions to assign or remove {role}." msgid "I don't have sufficient permissions to assign or remove {role}."
msgstr "" msgstr ""
#: src/wards.py:192 #: src/wards.py:196
msgctxt "ward:equippable_role|error:actor_perms" msgctxt "ward:equippable_role|error:actor_perms"
msgid "You need the `MANAGE_ROLES` permission before you can configure roles!" msgid "You need the `MANAGE_ROLES` permission before you can configure roles!"
msgstr "" msgstr ""
#: src/wards.py:199 #: src/wards.py:203
#, possible-python-brace-format #, possible-python-brace-format
msgctxt "ward:equippable_role|error:actor_top_role" msgctxt "ward:equippable_role|error:actor_top_role"
msgid "You cannot configure {role} because it is above your top role!" msgid "You cannot configure {role} because it is above your top role!"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-13 08:47+0300\n" "POT-Creation-Date: 2023-09-24 12:21+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -3,7 +3,7 @@ cachetools==4.2.2
configparser==5.0.2 configparser==5.0.2
discord.py discord.py
iso8601==0.1.16 iso8601==0.1.16
psycopg psycopg[pool]
pytz==2021.1 pytz==2021.1
topggpy topggpy
psutil psutil

View File

@@ -22,7 +22,7 @@ async def shard_snapshot():
snap = ShardSnapshot( snap = ShardSnapshot(
guild_count=len(bot.guilds), guild_count=len(bot.guilds),
voice_count=sum(len(channel.members) for guild in bot.guilds for channel in guild.voice_channels), voice_count=sum(len(channel.members) for guild in bot.guilds for channel in guild.voice_channels),
member_count=sum(len(guild.members) for guild in bot.guilds), member_count=sum(guild.member_count for guild in bot.guilds),
user_count=len(set(m.id for guild in bot.guilds for m in guild.members)) user_count=len(set(m.id for guild in bot.guilds for m in guild.members))
) )
return snap return snap

View File

@@ -230,14 +230,15 @@ class BabelCog(LionCog):
supported = self.bot.translator.supported_locales supported = self.bot.translator.supported_locales
formatted = [] formatted = []
for locale in supported: for locale in supported:
name = locale_names.get(locale.replace('_', '-'), None) names = locale_names.get(locale.replace('_', '-'), None)
if name: if names:
localestr = f"{locale} ({t(name)})" local_name, native_name = names
localestr = f"{native_name} ({t(local_name)})"
else: else:
localestr = locale localestr = locale
formatted.append((locale, localestr)) formatted.append((locale, localestr))
matching = {item for item in formatted if partial in item[1]} matching = {item for item in formatted if partial in item[1] or partial in item[0]}
if matching: if matching:
choices = [ choices = [
appcmds.Choice(name=localestr, value=locale) appcmds.Choice(name=localestr, value=locale)

View File

@@ -38,37 +38,44 @@ class LocaleMap(Enum):
hebrew = 'he-IL' hebrew = 'he-IL'
# Original Discord names
locale_names = { locale_names = {
'en-US': _p('localenames|locale:en-US', "American English"), 'id': (_p('localenames|locale:id', "Indonesian"), "Bahasa Indonesia"),
'en-GB': _p('localenames|locale:en-GB', "British English"), 'da': (_p('localenames|locale:da', "Danish"), "Dansk"),
'bg': _p('localenames|locale:bg', "Bulgarian"), 'de': (_p('localenames|locale:de', "German"), "Deutsch"),
'zh-CN': _p('localenames|locale:zh-CN', "Chinese"), 'en-GB': (_p('localenames|locale:en-GB', "English, UK"), "English, UK"),
'zh-TW': _p('localenames|locale:zh-TW', "Taiwan Chinese"), 'en-US': (_p('localenames|locale:en-US', "English, US"), "English, US"),
'hr': _p('localenames|locale:hr', "Croatian"), 'es-ES': (_p('localenames|locale:es-ES', "Spanish"), "Español"),
'cs': _p('localenames|locale:cs', "Czech"), 'fr': (_p('localenames|locale:fr', "French"), "Français"),
'da': _p('localenames|locale:da', "Danish"), 'hr': (_p('localenames|locale:hr', "Croatian"), "Hrvatski"),
'nl': _p('localenames|locale:nl', "Dutch"), 'it': (_p('localenames|locale:it', "Italian"), "Italiano"),
'fi': _p('localenames|locale:fi', "Finnish"), 'lt': (_p('localenames|locale:lt', "Lithuanian"), "Lietuviškai"),
'fr': _p('localenames|locale:fr', "French"), 'hu': (_p('localenames|locale:hu', "Hungarian"), "Magyar"),
'de': _p('localenames|locale:de', "German"), 'nl': (_p('localenames|locale:nl', "Dutch"), "Nederlands"),
'el': _p('localenames|locale:el', "Greek"), 'no': (_p('localenames|locale:no', "Norwegian"), "Norsk"),
'hi': _p('localenames|locale:hi', "Hindi"), 'pl': (_p('localenames|locale:pl', "Polish"), "Polski"),
'hu': _p('localenames|locale:hu', "Hungarian"), 'pt-BR': (_p('localenames|locale:pt-BR', "Portuguese, Brazilian"), "Português do Brasil"),
'it': _p('localenames|locale:it', "Italian"), 'ro': (_p('localenames|locale:ro', "Romanian, Romania"), "Română"),
'ja': _p('localenames|locale:ja', "Japanese"), 'fi': (_p('localenames|locale:fi', "Finnish"), "Suomi"),
'ko': _p('localenames|locale:ko', "Korean"), 'sv-SE': (_p('localenames|locale:sv-SE', "Swedish"), "Svenska"),
'lt': _p('localenames|locale:lt', "Lithuanian"), 'vi': (_p('localenames|locale:vi', "Vietnamese"), "Tiếng Việt"),
'no': _p('localenames|locale:no', "Norwegian"), 'tr': (_p('localenames|locale:tr', "Turkish"), "Türkçe"),
'pl': _p('localenames|locale:pl', "Polish"), 'cs': (_p('localenames|locale:cs', "Czech"), "Čeština"),
'pt-BR': _p('localenames|locale:pt-BR', "Brazil Portuguese"), 'el': (_p('localenames|locale:el', "Greek"), "Ελληνικά"),
'ro': _p('localenames|locale:ro', "Romanian"), 'bg': (_p('localenames|locale:bg', "Bulgarian"), "български"),
'ru': _p('localenames|locale:ru', "Russian"), 'ru': (_p('localenames|locale:ru', "Russian"), "Pусский"),
'es-ES': _p('localenames|locale:es-ES', "Spain Spanish"), 'uk': (_p('localenames|locale:uk', "Ukrainian"), "Українська"),
'sv-SE': _p('localenames|locale:sv-SE', "Swedish"), 'hi': (_p('localenames|locale:hi', "Hindi"), "हिन्दी"),
'th': _p('localenames|locale:th', "Thai"), 'th': (_p('localenames|locale:th', "Thai"), "ไทย"),
'tr': _p('localenames|locale:tr', "Turkish"), 'zh-CN': (_p('localenames|locale:zh-CN', "Chinese, China"), "中文"),
'uk': _p('localenames|locale:uk', "Ukrainian"), 'ja': (_p('localenames|locale:ja', "Japanese"), "日本語"),
'vi': _p('localenames|locale:vi', "Vietnamese"), 'zh-TW': (_p('localenames|locale:zh-TW', "Chinese, Taiwan"), "繁體中文"),
'he': _p('localenames|locale:he', "Hebrew"), 'ko': (_p('localenames|locale:ko', "Korean"), "한국어"),
'he-IL': _p('localenames|locale:he_IL', "Hebrew (Israel)"), }
# More names for languages not supported by Discord
locale_names |= {
'he': (_p('localenames|locale:he', "Hebrew"), "Hebrew"),
'he-IL': (_p('localenames|locale:he-IL', "Hebrew"), "Hebrew"),
'ceaser': (_p('localenames|locale:test', "Test Language"), "dfbtfs"),
} }

View File

@@ -43,9 +43,9 @@ class LocaleSetting(StringSetting):
if data is None: if data is None:
formatted = t(_p('settype:locale|formatted:unset', "Unset")) formatted = t(_p('settype:locale|formatted:unset', "Unset"))
else: else:
name = locale_names.get(data, None) if data in locale_names:
if name: local_name, native_name = locale_names[data]
formatted = f"`{data} ({t(name)})`" formatted = f"`{native_name} ({t(local_name)})`"
else: else:
formatted = f"`{data}`" formatted = f"`{data}`"
return formatted return formatted

View File

@@ -47,33 +47,43 @@ class LeoBabel(Translator):
Initialise the gettext translators for the supported_locales. Initialise the gettext translators for the supported_locales.
""" """
self.read_supported() self.read_supported()
missing = []
loaded = []
for locale in self.supported_locales: for locale in self.supported_locales:
for domain in self.supported_domains: for domain in self.supported_domains:
if locale == SOURCE_LOCALE: if locale == SOURCE_LOCALE:
continue continue
try: try:
translator = gettext.translation(domain, "locales/", languages=[locale]) translator = gettext.translation(domain, "locales/", languages=[locale])
loaded.append(f"Loaded translator for <locale: {locale}> <domain: {domain}>")
except OSError: except OSError:
# Presume translation does not exist # Presume translation does not exist
logger.warning(f"Could not load translator for supported <locale: {locale}> <domain: {domain}>") missing.append(f"Could not load translator for supported <locale: {locale}> <domain: {domain}>")
pass translator = null
else:
logger.debug(f"Loaded translator for <locale: {locale}> <domain: {domain}>")
self.translators[locale][domain] = translator self.translators[locale][domain] = translator
if missing:
logger.warning('\n'.join(("Missing Translators:", *missing)))
if loaded:
logger.debug('\n'.join(("Loaded Translators:", *loaded)))
async def unload(self): async def unload(self):
self.translators.clear() self.translators.clear()
def get_translator(self, locale, domain): def get_translator(self, locale, domain):
if locale == SOURCE_LOCALE: if locale == SOURCE_LOCALE:
return null translator = null
elif locale in self.supported_locales and domain in self.supported_domains:
translator = self.translators[locale].get(domain, None) translator = self.translators[locale].get(domain, None)
if translator is None: if translator is None:
# This should never really happen because we already loaded the supported translators
logger.warning( logger.warning(
f"Translator missing for requested <locale: {locale}> and <domain: {domain}>. Setting NullTranslator." f"Translator missing for supported <locale: {locale}> "
"and <domain: {domain}>. Setting NullTranslator."
) )
self.translators[locale][domain] = null translator = self.translators[locale][domain] = null
else:
# Unsupported
translator = null translator = null
return translator return translator

View File

@@ -9,6 +9,7 @@ from meta import LionBot, conf, sharding, appname, shard_talk
from meta.app import shardname from meta.app import shardname
from meta.logger import log_context, log_action_stack, setup_main_logger from meta.logger import log_context, log_action_stack, setup_main_logger
from meta.context import ctx_bot from meta.context import ctx_bot
from meta.monitor import ComponentMonitor, StatusLevel, ComponentStatus
from data import Database from data import Database
@@ -21,7 +22,7 @@ for name in conf.config.options('LOGGING_LEVELS', no_defaults=True):
logging.getLogger(name).setLevel(conf.logging_levels[name]) logging.getLogger(name).setLevel(conf.logging_levels[name])
setup_main_logger() logging_queue = setup_main_logger()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -29,6 +30,25 @@ logger = logging.getLogger(__name__)
db = Database(conf.data['args']) db = Database(conf.data['args'])
async def _data_monitor() -> ComponentStatus:
"""
Component monitor callback for the database.
"""
data = {
'stats': str(db.pool.get_stats())
}
if not db.pool._opened:
level = StatusLevel.WAITING
info = "(WAITING) Database Pool is not opened."
elif db.pool._closed:
level = StatusLevel.ERRORED
info = "(ERROR) Database Pool is closed."
else:
level = StatusLevel.OKAY
info = "(OK) Database Pool statistics: {stats}"
return ComponentStatus(level, info, info, data)
async def main(): async def main():
log_action_stack.set(("Initialising",)) log_action_stack.set(("Initialising",))
logger.info("Initialising StudyLion") logger.info("Initialising StudyLion")
@@ -69,9 +89,13 @@ async def main():
shard_count=sharding.shard_count, shard_count=sharding.shard_count,
help_command=None, help_command=None,
proxy=conf.bot.get('proxy', None), proxy=conf.bot.get('proxy', None),
translator=translator translator=translator,
chunk_guilds_at_startup=False,
) as lionbot: ) as lionbot:
ctx_bot.set(lionbot) ctx_bot.set(lionbot)
lionbot.system_monitor.add_component(
ComponentMonitor('Database', _data_monitor)
)
try: try:
log_context.set(f"APP: {appname}") log_context.set(f"APP: {appname}")
logger.info("StudyLion initialised, starting!", extra={'action': 'Starting'}) logger.info("StudyLion initialised, starting!", extra={'action': 'Starting'})

View File

@@ -49,16 +49,20 @@ class CoinSetting(IntegerSetting):
if num > cls._max: if num > cls._max:
t = ctx_translator.get().t t = ctx_translator.get().t
raise UserInputError(t(_p( raise UserInputError(
t(_p(
'settype:coin|parse|error:too_large', 'settype:coin|parse|error:too_large',
"Provided number of coins was too high!" "You cannot set this to more than {coin}**{max}**!"
))) from None )).format(coin=conf.emojis.coin, max=cls._max)
) from None
elif num < cls._min: elif num < cls._min:
t = ctx_translator.get().t t = ctx_translator.get().t
raise UserInputError(t(_p( raise UserInputError(
'settype:coin|parse|error:too_large', t(_p(
"Provided number of coins was too low!" 'settype:coin|parse|error:too_small',
))) from None "You cannot set this to less than {coin}**{min}**!"
)).format(coin=conf.emojis.coin, min=cls._min)
) from None
return num return num

Submodule src/gui updated: b781f7f9f2...ba9ace6ced

View File

@@ -12,6 +12,8 @@ from aiohttp import ClientSession
from data import Database from data import Database
from utils.lib import tabulate from utils.lib import tabulate
from gui.errors import RenderingException
from babel.translator import ctx_locale
from .config import Conf from .config import Conf
from .logger import logging_context, log_context, log_action_stack, log_wrap, set_logging_context from .logger import logging_context, log_context, log_action_stack, log_wrap, set_logging_context
@@ -19,6 +21,7 @@ from .context import context
from .LionContext import LionContext from .LionContext import LionContext
from .LionTree import LionTree from .LionTree import LionTree
from .errors import HandledException, SafeCancellation from .errors import HandledException, SafeCancellation
from .monitor import SystemMonitor, ComponentMonitor, StatusLevel, ComponentStatus
if TYPE_CHECKING: if TYPE_CHECKING:
from core import CoreCog from core import CoreCog
@@ -46,9 +49,40 @@ class LionBot(Bot):
self.core: Optional['CoreCog'] = None self.core: Optional['CoreCog'] = None
self.translator = translator self.translator = translator
self.system_monitor = SystemMonitor()
self.monitor = ComponentMonitor('LionBot', self._monitor_status)
self.system_monitor.add_component(self.monitor)
self._locks = WeakValueDictionary() self._locks = WeakValueDictionary()
self._running_events = set() self._running_events = set()
async def _monitor_status(self):
if self.is_closed():
level = StatusLevel.ERRORED
info = "(ERROR) Websocket is closed"
data = {}
elif self.is_ws_ratelimited():
level = StatusLevel.WAITING
info = "(WAITING) Websocket is ratelimited"
data = {}
elif not self.is_ready():
level = StatusLevel.STARTING
info = "(STARTING) Not yet ready"
data = {}
else:
level = StatusLevel.OKAY
info = (
"(OK) "
"Logged in with {guild_count} guilds, "
", websocket latency {latency}, and {events} running events."
)
data = {
'guild_count': len(self.guilds),
'latency': self.latency,
'events': len(self._running_events),
}
return ComponentStatus(level, info, info, data)
async def setup_hook(self) -> None: async def setup_hook(self) -> None:
log_context.set(f"APP: {self.application_id}") log_context.set(f"APP: {self.application_id}")
await self.app_ipc.connect() await self.app_ipc.connect()
@@ -204,13 +238,23 @@ class LionBot(Bot):
pass pass
except asyncio.TimeoutError: except asyncio.TimeoutError:
pass pass
except RenderingException as e:
logger.info(f"Command failed due to RenderingException: {repr(e)}")
embed = self.tree.rendersplat(e)
try:
await ctx.error_reply(embed=embed)
except discord.HTTPException:
pass
except Exception as e: except Exception as e:
logger.exception( logger.exception(
f"Caught an unknown CommandInvokeError while executing: {cmd_str}", f"Caught an unknown CommandInvokeError while executing: {cmd_str}",
extra={'action': 'BotError', 'with_ctx': True} extra={'action': 'BotError', 'with_ctx': True}
) )
error_embed = discord.Embed(title="Something went wrong!") error_embed = discord.Embed(
title="Something went wrong!",
colour=discord.Colour.dark_red()
)
error_embed.description = ( error_embed.description = (
"An unexpected error occurred while processing your command!\n" "An unexpected error occurred while processing your command!\n"
"Our development team has been notified, and the issue will be addressed soon.\n" "Our development team has been notified, and the issue will be addressed soon.\n"
@@ -225,11 +269,12 @@ class LionBot(Bot):
details['cmd'] = f"`{ctx.command.qualified_name}`" details['cmd'] = f"`{ctx.command.qualified_name}`"
if ctx.author: if ctx.author:
details['author'] = f"`{ctx.author.id}` -- `{ctx.author}`" details['author'] = f"`{ctx.author.id}` -- `{ctx.author}`"
details['locale'] = f"`{ctx_locale.get()}`"
if ctx.guild: if ctx.guild:
details['guild'] = f"`{ctx.guild.id}` -- `{ctx.guild.name}`" details['guild'] = f"`{ctx.guild.id}` -- `{ctx.guild.name}`"
details['my_guild_perms'] = f"`{ctx.guild.me.guild_permissions.value}`" details['my_guild_perms'] = f"`{ctx.guild.me.guild_permissions.value}`"
if ctx.author: if ctx.author:
ownerstr = ' (owner)' if ctx.author == ctx.guild.owner else '' ownerstr = ' (owner)' if ctx.author.id == ctx.guild.owner_id else ''
details['author_guild_perms'] = f"`{ctx.author.guild_permissions.value}{ownerstr}`" details['author_guild_perms'] = f"`{ctx.author.guild_permissions.value}{ownerstr}`"
if ctx.channel.type is discord.enums.ChannelType.private: if ctx.channel.type is discord.enums.ChannelType.private:
details['channel'] = "`Direct Message`" details['channel'] = "`Direct Message`"
@@ -246,7 +291,7 @@ class LionBot(Bot):
try: try:
await ctx.error_reply(embed=error_embed) await ctx.error_reply(embed=error_embed)
except Exception: except discord.HTTPException:
pass pass
finally: finally:
exception.original = HandledException(exception.original) exception.original = HandledException(exception.original)
@@ -270,3 +315,29 @@ class LionBot(Bot):
def add_command(self, command): def add_command(self, command):
if not hasattr(command, '_placeholder_group_'): if not hasattr(command, '_placeholder_group_'):
super().add_command(command) super().add_command(command)
def request_chunking_for(self, guild):
if not guild.chunked:
return asyncio.create_task(
self._connection.chunk_guild(guild, wait=False, cache=True),
name=f"Background chunkreq for {guild.id}"
)
async def on_interaction(self, interaction: discord.Interaction):
"""
Adds the interaction author to guild cache if appropriate.
This gets run a little bit late, so it is possible the interaction gets handled
without the author being in case.
"""
guild = interaction.guild
user = interaction.user
if guild is not None and user is not None and isinstance(user, discord.Member):
if not guild.get_member(user.id):
guild._add_member(user)
if guild is not None and not guild.chunked:
# Getting an interaction in the guild is a good enough reason to request chunking
logger.info(
f"Unchunked guild <gid: {guild.id}> requesting chunking after interaction."
)
self.request_chunking_for(guild)

View File

@@ -6,6 +6,7 @@ from typing import Optional, TYPE_CHECKING
import discord import discord
from discord.enums import ChannelType from discord.enums import ChannelType
from discord.ext.commands import Context from discord.ext.commands import Context
from babel.translator import ctx_locale
if TYPE_CHECKING: if TYPE_CHECKING:
from .LionBot import LionBot from .LionBot import LionBot
@@ -35,6 +36,7 @@ FlatContext = namedtuple(
'interaction', 'interaction',
'guild', 'guild',
'author', 'author',
'channel',
'alias', 'alias',
'prefix', 'prefix',
'failed') 'failed')
@@ -78,6 +80,7 @@ class LionContext(Context['LionBot']):
parts['alias'] = f"\"{self.invoked_with}\"" parts['alias'] = f"\"{self.invoked_with}\""
if self.command_failed: if self.command_failed:
parts['failed'] = self.command_failed parts['failed'] = self.command_failed
parts['locale'] = f"\"{ctx_locale.get()}\""
return "<LionContext: {}>".format( return "<LionContext: {}>".format(
' '.join(f"{name}={value}" for name, value in parts.items()) ' '.join(f"{name}={value}" for name, value in parts.items())

View File

@@ -8,9 +8,12 @@ from discord.enums import InteractionType
from discord.app_commands.namespace import Namespace from discord.app_commands.namespace import Namespace
from utils.lib import tabulate from utils.lib import tabulate
from gui.errors import RenderingException
from babel.translator import ctx_locale
from .logger import logging_context, set_logging_context, log_wrap, log_action_stack from .logger import logging_context, set_logging_context, log_wrap, log_action_stack
from .errors import SafeCancellation from .errors import SafeCancellation
from .config import conf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -29,18 +32,37 @@ class LionTree(CommandTree):
except SafeCancellation: except SafeCancellation:
# Assume this has already been handled # Assume this has already been handled
pass pass
except RenderingException as e:
logger.info(f"Tree interaction failed due to rendering exception: {repr(e)}")
embed = self.rendersplat(e)
await self.error_reply(interaction, embed)
except Exception: except Exception:
logger.exception(f"Unhandled exception in interaction: {interaction}", extra={'action': 'TreeError'}) logger.exception(f"Unhandled exception in interaction: {interaction}", extra={'action': 'TreeError'})
embed = self.bugsplat(interaction, error)
await self.error_reply(interaction, embed)
async def error_reply(self, interaction, embed):
if not interaction.is_expired(): if not interaction.is_expired():
splat = self.bugsplat(interaction, error)
try: try:
if interaction.response.is_done(): if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True) await interaction.followup.send(embed=embed, ephemeral=True)
else: else:
await interaction.response.send_message(embed=splat, ephemeral=True) await interaction.response.send_message(embed=embed, ephemeral=True)
except discord.HTTPException: except discord.HTTPException:
pass pass
def rendersplat(self, e: RenderingException):
embed = discord.Embed(
title="Resource Currently Unavailable!",
description=(
"Sorry, the graphics service is currently unavailable!\n"
"Please try again in a few minutes.\n"
"If the error persists, please contact our [support team]({link})"
).format(link=conf.bot.support_guild),
colour=discord.Colour.dark_red()
)
return embed
def bugsplat(self, interaction, e): def bugsplat(self, interaction, e):
error_embed = discord.Embed(title="Something went wrong!", colour=discord.Colour.red()) error_embed = discord.Embed(title="Something went wrong!", colour=discord.Colour.red())
error_embed.description = ( error_embed.description = (
@@ -55,13 +77,14 @@ class LionTree(CommandTree):
details['interactiontype'] = f"`{interaction.type}`" details['interactiontype'] = f"`{interaction.type}`"
if interaction.command: if interaction.command:
details['cmd'] = f"`{interaction.command.qualified_name}`" details['cmd'] = f"`{interaction.command.qualified_name}`"
details['locale'] = f"`{ctx_locale.get()}`"
if interaction.user: if interaction.user:
details['user'] = f"`{interaction.user.id}` -- `{interaction.user}`" details['user'] = f"`{interaction.user.id}` -- `{interaction.user}`"
if interaction.guild: if interaction.guild:
details['guild'] = f"`{interaction.guild.id}` -- `{interaction.guild.name}`" details['guild'] = f"`{interaction.guild.id}` -- `{interaction.guild.name}`"
details['my_guild_perms'] = f"`{interaction.guild.me.guild_permissions.value}`" details['my_guild_perms'] = f"`{interaction.guild.me.guild_permissions.value}`"
if interaction.user: if interaction.user:
ownerstr = ' (owner)' if interaction.user == interaction.guild.owner else '' ownerstr = ' (owner)' if interaction.user.id == interaction.guild.owner_id else ''
details['user_guild_perms'] = f"`{interaction.user.guild_permissions.value}{ownerstr}`" details['user_guild_perms'] = f"`{interaction.user.guild_permissions.value}{ownerstr}`"
if interaction.channel.type is discord.enums.ChannelType.private: if interaction.channel.type is discord.enums.ChannelType.private:
details['channel'] = "`Direct Message`" details['channel'] = "`Direct Message`"

View File

@@ -3,7 +3,7 @@ import configparser as cfgp
from .args import args from .args import args
shard_number = args.shard or 0 shard_number = args.shard
class configEmoji(PartialEmoji): class configEmoji(PartialEmoji):
__slots__ = ('fallback',) __slots__ = ('fallback',)
@@ -87,7 +87,10 @@ class Conf:
"emoji": configEmoji.from_str, "emoji": configEmoji.from_str,
} }
) )
self.config.read(configfile)
with open(configfile) as conff:
# Opening with read_file mainly to ensure the file exists
self.config.read_file(conff)
self.section_name = section_name if section_name in self.config else 'DEFAULT' self.section_name = section_name if section_name in self.config else 'DEFAULT'

View File

@@ -10,6 +10,7 @@ from io import StringIO
from functools import wraps from functools import wraps
from contextvars import ContextVar from contextvars import ContextVar
import discord
from discord import Webhook, File from discord import Webhook, File
import aiohttp import aiohttp
@@ -188,6 +189,14 @@ class LessThanFilter(logging.Filter):
# non-zero return means we log this message # non-zero return means we log this message
return 1 if record.levelno < self.max_level else 0 return 1 if record.levelno < self.max_level else 0
class ExactLevelFilter(logging.Filter):
def __init__(self, target_level, name=""):
super().__init__(name)
self.target_level = target_level
def filter(self, record):
return (record.levelno == self.target_level)
class ThreadFilter(logging.Filter): class ThreadFilter(logging.Filter):
def __init__(self, thread_name): def __init__(self, thread_name):
@@ -234,7 +243,6 @@ class ContextInjection(logging.Filter):
logging_handler_out = logging.StreamHandler(sys.stdout) logging_handler_out = logging.StreamHandler(sys.stdout)
logging_handler_out.setLevel(logging.DEBUG) logging_handler_out.setLevel(logging.DEBUG)
logging_handler_out.setFormatter(log_fmt) logging_handler_out.setFormatter(log_fmt)
logging_handler_out.addFilter(LessThanFilter(logging.WARNING))
logging_handler_out.addFilter(ContextInjection()) logging_handler_out.addFilter(ContextInjection())
logger.addHandler(logging_handler_out) logger.addHandler(logging_handler_out)
log_logger.addHandler(logging_handler_out) log_logger.addHandler(logging_handler_out)
@@ -297,6 +305,10 @@ class WebHookHandler(logging.StreamHandler):
self.webhook = Webhook.from_url(self.webhook_url, session=self.session) self.webhook = Webhook.from_url(self.webhook_url, session=self.session)
async def post(self, record): async def post(self, record):
if record.context == 'Webhook Logger':
# Don't livelog livelog errors
# Otherwise we recurse and Cloudflare hates us
return
log_context.set("Webhook Logger") log_context.set("Webhook Logger")
log_action_stack.set(("Logging",)) log_action_stack.set(("Logging",))
log_app.set(record.app) log_app.set(record.app)
@@ -363,18 +375,20 @@ class WebHookHandler(logging.StreamHandler):
return return
except BucketFull: except BucketFull:
logger.warning( logger.warning(
f"Live logging webhook {self.webhook.id} going too fast! " "Can't keep up! "
"Ignoring records until rate slows down." f"Ignoring records on live-logger {self.webhook.id}."
) )
self.ignored += 1 self.ignored += 1
return return
else: else:
if self.ignored > 0: if self.ignored > 0:
logger.warning( logger.warning(
"Can't keep up! "
f"{self.ignored} live logging records on webhook {self.webhook.id} skipped, continuing." f"{self.ignored} live logging records on webhook {self.webhook.id} skipped, continuing."
) )
self.ignored = 0 self.ignored = 0
try:
if as_file or len(message) > 1900: if as_file or len(message) > 1900:
with StringIO(message) as fp: with StringIO(message) as fp:
fp.seek(0) fp.seek(0)
@@ -385,6 +399,11 @@ class WebHookHandler(logging.StreamHandler):
) )
else: else:
await self.webhook.send(self.prefix + '\n' + message, username=log_app.get()) await self.webhook.send(self.prefix + '\n' + message, username=log_app.get())
except discord.HTTPException:
logger.exception(
"Live logger errored. Slowing down live logger."
)
self.bucket.fill()
handlers = [] handlers = []
@@ -392,9 +411,15 @@ if webhook := conf.logging['general_log']:
handler = WebHookHandler(webhook, batch=True) handler = WebHookHandler(webhook, batch=True)
handlers.append(handler) handlers.append(handler)
if webhook := conf.logging['warning_log']:
handler = WebHookHandler(webhook, prefix=conf.logging['warning_prefix'], batch=True)
handler.addFilter(ExactLevelFilter(logging.WARNING))
handler.setLevel(logging.WARNING)
handlers.append(handler)
if webhook := conf.logging['error_log']: if webhook := conf.logging['error_log']:
handler = WebHookHandler(webhook, prefix=conf.logging['error_prefix'], batch=True) handler = WebHookHandler(webhook, prefix=conf.logging['error_prefix'], batch=True)
handler.setLevel(logging.WARNING) handler.setLevel(logging.ERROR)
handlers.append(handler) handlers.append(handler)
if webhook := conf.logging['critical_log']: if webhook := conf.logging['critical_log']:

139
src/meta/monitor.py Normal file
View File

@@ -0,0 +1,139 @@
import logging
import asyncio
from enum import IntEnum
from collections import deque, ChainMap
import datetime as dt
logger = logging.getLogger(__name__)
class StatusLevel(IntEnum):
ERRORED = -2
UNSURE = -1
WAITING = 0
STARTING = 1
OKAY = 2
@property
def symbol(self):
return symbols[self]
symbols = {
StatusLevel.ERRORED: '🟥',
StatusLevel.UNSURE: '🟧',
StatusLevel.WAITING: '',
StatusLevel.STARTING: '🟫',
StatusLevel.OKAY: '🟩',
}
class ComponentStatus:
def __init__(self, level: StatusLevel, short_formatstr: str, long_formatstr: str, data: dict = {}):
self.level = level
self.short_formatstr = short_formatstr
self.long_formatstr = long_formatstr
self.data = data
self.created_at = dt.datetime.now(tz=dt.timezone.utc)
def format_args(self):
extra = {
'created_at': self.created_at,
'level': self.level,
'symbol': self.level.symbol,
}
return ChainMap(extra, self.data)
@property
def short(self):
return self.short_formatstr.format(**self.format_args())
@property
def long(self):
return self.long_formatstr.format(**self.format_args())
class ComponentMonitor:
_name = None
def __init__(self, name=None, callback=None):
self._callback = callback
self.name = name or self._name
if not self.name:
raise ValueError("ComponentMonitor must have a name")
async def _make_status(self, *args, **kwargs):
if self._callback is not None:
return await self._callback(*args, **kwargs)
else:
raise NotImplementedError
async def status(self) -> ComponentStatus:
try:
status = await self._make_status()
except Exception as e:
logger.exception(
f"Status callback for component '{self.name}' failed. This should not happen."
)
status = ComponentStatus(
level=StatusLevel.UNSURE,
short_formatstr="Status callback for '{name}' failed with error '{error}'",
long_formatstr="Status callback for '{name}' failed with error '{error}'",
data={
'name': self.name,
'error': repr(e)
}
)
return status
class SystemMonitor:
def __init__(self):
self.components = {}
self.recent = deque(maxlen=10)
def add_component(self, component: ComponentMonitor):
self.components[component.name] = component
return component
async def request(self):
"""
Request status from each component.
"""
tasks = {
name: asyncio.create_task(comp.status())
for name, comp in self.components.items()
}
await asyncio.gather(*tasks.values())
status = {
name: await fut for name, fut in tasks.items()
}
self.recent.append(status)
return status
async def _format_summary(self, status_dict: dict[str, ComponentStatus]):
"""
Format a one line summary from a status dict.
"""
freq = {level: 0 for level in StatusLevel}
for status in status_dict.values():
freq[status.level] += 1
summary = '\t'.join(f"{level.symbol} {count}" for level, count in freq.items() if count)
return summary
async def _format_overview(self, status_dict: dict[str, ComponentStatus]):
"""
Format an overview (one line per component) from a status dict.
"""
lines = []
for name, status in status_dict.items():
lines.append(f"{status.level.symbol} {name}: {status.short}")
summary = await self._format_summary(status_dict)
return '\n'.join((summary, *lines))
async def get_summary(self):
return await self._format_summary(await self.request())
async def get_overview(self):
return await self._format_overview(await self.request())

View File

@@ -190,7 +190,7 @@ class Economy(LionCog):
# First fetch the members which currently exist # First fetch the members which currently exist
query = self.bot.core.data.Member.table.select_where(guildid=ctx.guild.id) query = self.bot.core.data.Member.table.select_where(guildid=ctx.guild.id)
query.select('userid').with_no_adapter() query.select('userid').with_no_adapter()
if 2 * len(targets) < len(ctx.guild.members): if 2 * len(targets) < ctx.guild.member_count:
# More efficient to fetch the targets explicitly # More efficient to fetch the targets explicitly
query.where(userid=list(targetids)) query.where(userid=list(targetids))
existent_rows = await query existent_rows = await query

View File

@@ -181,15 +181,17 @@ class MemberAdminCog(LionCog):
finally: finally:
self._adding_roles.discard((member.guild.id, member.id)) self._adding_roles.discard((member.guild.id, member.id))
@LionCog.listener('on_member_remove') @LionCog.listener('on_raw_member_remove')
@log_wrap(action="Farewell") @log_wrap(action="Farewell")
async def admin_member_farewell(self, member: discord.Member): async def admin_member_farewell(self, payload: discord.RawMemberRemoveEvent):
# Ignore members that just joined # Ignore members that just joined
if (member.guild.id, member.id) in self._adding_roles: guildid = payload.guild_id
userid = payload.user.id
if (guildid, userid) in self._adding_roles:
return return
# Set lion last_left, creating the lion_member if needed # Set lion last_left, creating the lion_member if needed
lion = await self.bot.core.lions.fetch_member(member.guild.id, member.id) lion = await self.bot.core.lions.fetch_member(guildid, userid)
await lion.data.update(last_left=utc_now()) await lion.data.update(last_left=utc_now())
# Save member roles # Save member roles
@@ -197,18 +199,21 @@ class MemberAdminCog(LionCog):
self.bot.db.conn = conn self.bot.db.conn = conn
async with conn.transaction(): async with conn.transaction():
await self.data.past_roles.delete_where( await self.data.past_roles.delete_where(
guildid=member.guild.id, guildid=guildid,
userid=member.id userid=userid
) )
# Insert current member roles # Insert current member roles
if member.roles: print(type(payload.user))
if isinstance(payload.user, discord.Member) and payload.user.roles:
member = payload.user
await self.data.past_roles.insert_many( await self.data.past_roles.insert_many(
('guildid', 'userid', 'roleid'), ('guildid', 'userid', 'roleid'),
*((member.guild.id, member.id, role.id) for role in member.roles) *((guildid, userid, role.id) for role in member.roles)
) )
logger.debug( logger.debug(
f"Stored persisting roles for member <uid:{member.id}> in <gid:{member.guild.id}>." f"Stored persisting roles for member <uid:{userid}> in <gid:{guildid}>."
) )
# TODO: Event log, and include info about unchunked members
@LionCog.listener('on_guild_join') @LionCog.listener('on_guild_join')
async def admin_init_guild(self, guild: discord.Guild): async def admin_init_guild(self, guild: discord.Guild):

View File

@@ -173,7 +173,7 @@ class MemberAdminSettings(SettingGroup):
'{guild_name}': guild.name, '{guild_name}': guild.name,
'{guild_icon}': guild.icon.url if guild.icon else member.default_avatar.url, '{guild_icon}': guild.icon.url if guild.icon else member.default_avatar.url,
'{studying_count}': str(active), '{studying_count}': str(active),
'{member_count}': len(guild.members), '{member_count}': guild.member_count,
} }
recurse_map( recurse_map(
@@ -297,7 +297,7 @@ class MemberAdminSettings(SettingGroup):
'{guild_name}': guild.name, '{guild_name}': guild.name,
'{guild_icon}': guild.icon.url if guild.icon else member.default_avatar.url, '{guild_icon}': guild.icon.url if guild.icon else member.default_avatar.url,
'{studying_count}': str(active), '{studying_count}': str(active),
'{member_count}': str(len(guild.members)), '{member_count}': str(guild.member_count),
'{last_time}': str(last_seen or member.joined_at.timestamp()), '{last_time}': str(last_seen or member.joined_at.timestamp()),
} }

View File

@@ -210,7 +210,7 @@ class MemberAdminUI(ConfigUI):
t = self.bot.translator.t t = self.bot.translator.t
title = t(_p( title = t(_p(
'ui:memberadmin|embed|title', 'ui:memberadmin|embed|title',
"Member Admin Configuration Panel" "Greetings and Initial Roles Panel"
)) ))
embed = discord.Embed( embed = discord.Embed(
title=title, title=title,

View File

@@ -32,6 +32,6 @@ class MetaCog(LionCog):
ctx.bot, ctx.bot,
ctx.author, ctx.author,
ctx.guild, ctx.guild,
show_admin=await low_management(ctx.bot, ctx.author), show_admin=await low_management(ctx.bot, ctx.author, ctx.guild),
) )
await ui.run(ctx.interaction) await ui.run(ctx.interaction)

View File

@@ -10,6 +10,7 @@ from discord import app_commands as appcmds
from meta import LionCog, LionBot, LionContext from meta import LionCog, LionBot, LionContext
from meta.logger import log_wrap from meta.logger import log_wrap
from meta.sharding import THIS_SHARD from meta.sharding import THIS_SHARD
from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel
from utils.lib import utc_now from utils.lib import utc_now
from wards import low_management_ward from wards import low_management_ward
@@ -42,12 +43,25 @@ class TimerCog(LionCog):
self.bot = bot self.bot = bot
self.data = bot.db.load_registry(TimerData()) self.data = bot.db.load_registry(TimerData())
self.settings = TimerSettings() self.settings = TimerSettings()
self.monitor = ComponentMonitor('TimerCog', self._monitor)
self.timer_options = TimerOptions() self.timer_options = TimerOptions()
self.ready = False self.ready = False
self.timers = defaultdict(dict) self.timers = defaultdict(dict)
async def _monitor(self):
if not self.ready:
level = StatusLevel.STARTING
info = "(STARTING) Not ready. {timers} timers loaded."
else:
level = StatusLevel.OKAY
info = "(OK) {timers} timers loaded."
data = dict(timers=len(self.timers))
return ComponentStatus(level, info, info, data)
async def cog_load(self): async def cog_load(self):
self.bot.system_monitor.add_component(self.monitor)
await self.data.init() await self.data.init()
self.bot.core.guild_config.register_model_setting(self.settings.PomodoroChannel) self.bot.core.guild_config.register_model_setting(self.settings.PomodoroChannel)
@@ -386,8 +400,9 @@ class TimerCog(LionCog):
) )
else: else:
# Display the timer status ephemerally # Display the timer status ephemerally
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
status = await timer.current_status(with_notify=False, with_warnings=False) status = await timer.current_status(with_notify=False, with_warnings=False)
await ctx.reply(**status.send_args, ephemeral=True) await ctx.interaction.edit_original_response(**status.edit_args)
if error is not None: if error is not None:
await ctx.reply(embed=error, ephemeral=True) await ctx.reply(embed=error, ephemeral=True)

View File

@@ -12,6 +12,7 @@ from utils.lib import MessageArgs, utc_now, replace_multiple
from core.lion_guild import LionGuild from core.lion_guild import LionGuild
from core.data import CoreData from core.data import CoreData
from babel.translator import ctx_locale from babel.translator import ctx_locale
from gui.errors import RenderingException
from . import babel, logger from . import babel, logger
from .data import TimerData from .data import TimerData
@@ -83,6 +84,7 @@ class Timer:
self.destroyed = False self.destroyed = False
def __repr__(self): def __repr__(self):
# TODO: Add lock status and current state and stage
return ( return (
"<Timer " "<Timer "
f"channelid={self.data.channelid} " f"channelid={self.data.channelid} "
@@ -560,19 +562,20 @@ class Timer:
"Timer stopped! Press `Start` to restart the timer." "Timer stopped! Press `Start` to restart the timer."
)).format(channel=f"<#{self.data.channelid}>") )).format(channel=f"<#{self.data.channelid}>")
card = await get_timer_card(self.bot, self, stage)
await card.render()
if (ui := self.status_view) is None: if (ui := self.status_view) is None:
ui = self.status_view = TimerStatusUI(self.bot, self, self.channel) ui = self.status_view = TimerStatusUI(self.bot, self, self.channel)
await ui.refresh() await ui.refresh()
return MessageArgs( card = await get_timer_card(self.bot, self, stage)
content=content, try:
file=card.as_file(f"pomodoro_{self.data.channelid}.png"), await card.render()
view=ui file = card.as_file(f"pomodoro_{self.data.channelid}.png")
) args = MessageArgs(content=content, file=file, view=ui)
except RenderingException:
args = MessageArgs(content=content, view=ui)
return args
@log_wrap(action='Send Timer Status') @log_wrap(action='Send Timer Status')
async def send_status(self, delete_last=True, **kwargs): async def send_status(self, delete_last=True, **kwargs):
@@ -785,8 +788,8 @@ class Timer:
to_next_stage = (current.end - utc_now()).total_seconds() to_next_stage = (current.end - utc_now()).total_seconds()
# TODO: Consider request rate and load # TODO: Consider request rate and load
if to_next_stage > 1 * 60 - drift: if to_next_stage > 5 * 60 - drift:
time_to_sleep = 1 * 60 time_to_sleep = 5 * 60
else: else:
time_to_sleep = to_next_stage time_to_sleep = to_next_stage

View File

@@ -286,25 +286,76 @@ class RankCog(LionCog):
await task await task
async def _role_check(self, session_rank: SeasonRank): async def _role_check(self, session_rank: SeasonRank):
guild = self.bot.get_guild(session_rank.guildid) """
member = guild.get_member(session_rank.userid) Update the member's rank roles, if required.
"""
guildid = session_rank.guildid
guild = self.bot.get_guild(guildid)
userid = session_rank.userid
member = guild.get_member(userid)
if guild is not None and member is not None and guild.me.guild_permissions.manage_roles:
ranks = await self.get_guild_ranks(guildid)
crank = session_rank.current_rank crank = session_rank.current_rank
roleid = crank.roleid if crank else None current_roleid = crank.roleid if crank else None
# First gather rank roleids, note that the last_roleid is an 'honourary' roleid
last_roleid = session_rank.rankrow.last_roleid last_roleid = session_rank.rankrow.last_roleid
if guild is not None and member is not None and roleid != last_roleid: rank_roleids = {rank.roleid for rank in ranks}
new_role = guild.get_role(roleid) if roleid else None rank_roleids.add(last_roleid)
last_role = guild.get_role(last_roleid) if last_roleid else None
# Gather member roleids
mem_roleids = {role.id: role for role in member.roles}
# Calculate diffs
to_add = guild.get_role(current_roleid) if (current_roleid not in mem_roleids) else None
to_rm = [
role for roleid, role in mem_roleids.items()
if roleid in rank_roleids and roleid != current_roleid
]
# Now update roles
new_last_roleid = last_roleid new_last_roleid = last_roleid
if guild.me.guild_permissions.manage_roles:
# TODO: Event log here, including errors
to_rm = [role for role in to_rm if role.is_assignable()]
if to_rm:
try: try:
if last_role and last_role.is_assignable(): await member.remove_roles(
await member.remove_roles(last_role) *to_rm,
reason="Removing Old Rank Roles",
atomic=True
)
roleids = ', '.join(str(role.id) for role in to_rm)
logger.info(
f"Removed old rank roles from <uid:{userid}> in <gid:{guildid}>: {roleids}"
)
new_last_roleid = None new_last_roleid = None
if new_role and new_role.is_assignable(): except discord.HTTPException:
await member.add_roles(new_role) logger.warning(
new_last_roleid = roleid f"Unexpected error removing old rank roles from <uid:{member.id}> in <gid:{guild.id}>: {to_rm}",
except discord.HTTPClient: exc_info=True
pass )
if to_add and to_add.is_assignable():
try:
await member.add_roles(
to_add,
reason="Rewarding Activity Rank",
atomic=True
)
logger.info(
f"Rewarded rank role <rid:{to_add.id}> to <uid:{userid}> in <gid:{guildid}>."
)
new_last_roleid = to_add.id
except discord.HTTPException:
logger.warning(
f"Unexpected error giving <uid:{userid}> in <gid:{guildid}> their rank role <rid:{to_add.id}>",
exc_info=True
)
if new_last_roleid != last_roleid: if new_last_roleid != last_roleid:
await session_rank.rankrow.update(last_roleid=new_last_roleid) await session_rank.rankrow.update(last_roleid=new_last_roleid)
@@ -336,23 +387,61 @@ class RankCog(LionCog):
if member is None: if member is None:
return return
new_role = guild.get_role(new_rank.roleid) last_roleid = session_rank.rankrow.last_roleid
if last_roleid := session_rank.rankrow.last_roleid:
last_role = guild.get_role(last_roleid)
else:
last_role = None
# Update ranks
if guild.me.guild_permissions.manage_roles: if guild.me.guild_permissions.manage_roles:
# First gather rank roleids, note that the last_roleid is an 'honourary' roleid
rank_roleids = {rank.roleid for rank in ranks}
rank_roleids.add(last_roleid)
# Gather member roleids
mem_roleids = {role.id: role for role in member.roles}
# Calculate diffs
to_add = guild.get_role(new_rank.roleid) if (new_rank.roleid not in mem_roleids) else None
to_rm = [
role for roleid, role in mem_roleids.items()
if roleid in rank_roleids and roleid != new_rank.roleid
]
# Now update roles
# TODO: Event log here, including errors
to_rm = [role for role in to_rm if role.is_assignable()]
if to_rm:
try: try:
if last_role and last_role.is_assignable(): await member.remove_roles(
await member.remove_roles(last_role) *to_rm,
reason="Removing Old Rank Roles",
atomic=True
)
roleids = ', '.join(str(role.id) for role in to_rm)
logger.info(
f"Removed old rank roles from <uid:{userid}> in <gid:{guildid}>: {roleids}"
)
last_roleid = None last_roleid = None
if new_role and new_role.is_assignable():
await member.add_roles(new_role)
last_roleid = new_role.id
except discord.HTTPException: except discord.HTTPException:
# TODO: Event log either way logger.warning(
pass f"Unexpected error removing old rank roles from <uid:{member.id}> in <gid:{guild.id}>: {to_rm}",
exc_info=True
)
if to_add and to_add.is_assignable():
try:
await member.add_roles(
to_add,
reason="Rewarding Activity Rank",
atomic=True
)
logger.info(
f"Rewarded rank role <rid:{to_add.id}> to <uid:{userid}> in <gid:{guildid}>."
)
last_roleid=to_add.id
except discord.HTTPException:
logger.warning(
f"Unexpected error giving <uid:{userid}> in <gid:{guildid}> their rank role <rid:{to_add.id}>",
exc_info=True
)
# Update MemberRank row # Update MemberRank row
column = { column = {
@@ -438,7 +527,7 @@ class RankCog(LionCog):
required = format_stat_range(rank_type, rank.required, short=False) required = format_stat_range(rank_type, rank.required, short=False)
key_map = { key_map = {
'{role_name}': role.name, '{role_name}': role.name if role else 'Unknown',
'{guild_name}': guild.name, '{guild_name}': guild.name,
'{user_name}': member.name, '{user_name}': member.name,
'{role_id}': role.id, '{role_id}': role.id,
@@ -495,6 +584,7 @@ class RankCog(LionCog):
await interaction.response.defer(thinking=False) await interaction.response.defer(thinking=False)
ui = RankRefreshUI(self.bot, guild, callerid=interaction.user.id, timeout=None) ui = RankRefreshUI(self.bot, guild, callerid=interaction.user.id, timeout=None)
await ui.send(interaction.channel) await ui.send(interaction.channel)
ui.start()
# Retrieve fresh rank roles # Retrieve fresh rank roles
ranks = await self.get_guild_ranks(guild.id, refresh=True) ranks = await self.get_guild_ranks(guild.id, refresh=True)
@@ -503,7 +593,15 @@ class RankCog(LionCog):
# Ensure guild is chunked # Ensure guild is chunked
if not guild.chunked: if not guild.chunked:
members = await guild.chunk() try:
members = await asyncio.wait_for(guild.chunk(), timeout=60)
except asyncio.TimeoutError:
error = t(_p(
'rank_refresh|error:cannot_chunk|desc',
"Could not retrieve member list from Discord. Please try again later."
))
await ui.set_error(error)
return
else: else:
members = guild.members members = guild.members
ui.stage_members = True ui.stage_members = True
@@ -524,7 +622,7 @@ class RankCog(LionCog):
error = t(_p( error = t(_p(
'rank_refresh|error:unassignable_roles|desc', 'rank_refresh|error:unassignable_roles|desc',
"I have insufficient permissions to assign the following role(s):\n{roles}" "I have insufficient permissions to assign the following role(s):\n{roles}"
)).format(roles='\n'.join(role.mention for role in failing)), )).format(roles='\n'.join(role.mention for role in failing))
await ui.set_error(error) await ui.set_error(error)
return return
@@ -609,8 +707,8 @@ class RankCog(LionCog):
'rank_refresh|remove_roles|small_error', 'rank_refresh|remove_roles|small_error',
"*Could not remove ranks from {member}*" "*Could not remove ranks from {member}*"
)).format(member=to_remove[index][0].mention) )).format(member=to_remove[index][0].mention)
self.ui.errors.append(error) ui.errors.append(error)
if len(self.ui.errors) > 10: if len(ui.errors) > 10:
await ui.set_error( await ui.set_error(
t(_p( t(_p(
'rank_refresh|remove_roles|error:too_many_issues', 'rank_refresh|remove_roles|error:too_many_issues',
@@ -644,8 +742,8 @@ class RankCog(LionCog):
'rank_refresh|add_roles|small_error', 'rank_refresh|add_roles|small_error',
"*Could not add {role} to {member}*" "*Could not add {role} to {member}*"
)).format(member=to_add[index][0].mention, role=to_add[index][1].mention) )).format(member=to_add[index][0].mention, role=to_add[index][1].mention)
self.ui.errors.append(error) ui.errors.append(error)
if len(self.ui.errors) > 10: if len(ui.errors) > 10:
await ui.set_error( await ui.set_error(
t(_p( t(_p(
'rank_refresh|add_roles|error:too_many_issues', 'rank_refresh|add_roles|error:too_many_issues',

View File

@@ -12,6 +12,7 @@ from data import ORDER
from utils.ui import MessageUI, Confirm from utils.ui import MessageUI, Confirm
from utils.lib import MessageArgs from utils.lib import MessageArgs
from wards import equippable_role
from babel.translator import ctx_translator from babel.translator import ctx_translator
from .. import babel, logger from .. import babel, logger
@@ -185,25 +186,11 @@ class RankOverviewUI(MessageUI):
or edit an existing rank, or edit an existing rank,
or throw an error if the role is @everyone or not manageable by the client. or throw an error if the role is @everyone or not manageable by the client.
""" """
role: discord.Role = selected.values[0] role: discord.Role = selected.values[0]
if role >= selection.user.top_role:
# Do not allow user to manage a role above their own top role if role.is_assignable():
t = self.bot.translator.t # Create or edit the selected role
error = t(_p(
'ui:rank_overview|menu:roles|error:above_caller',
"You have insufficient permissions to assign {mention} as a rank role! "
"You may only manage roles below your top role."
)).format(mention=role.mention)
embed = discord.Embed(
title=t(_p(
'ui:rank_overview|menu:roles|error:above_caller|title',
"Insufficient permissions!"
)),
description=error,
colour=discord.Colour.brand_red()
)
await selection.response.send_message(embed=embed, ephemeral=True)
elif role.is_assignable():
existing = next((rank for rank in self.ranks if rank.roleid == role.id), None) existing = next((rank for rank in self.ranks if rank.roleid == role.id), None)
if existing: if existing:
# Display and edit the given role # Display and edit the given role
@@ -216,6 +203,8 @@ class RankOverviewUI(MessageUI):
) )
else: else:
# Create new rank based on role # Create new rank based on role
# Need to check the calling author has authority to manage this role
await equippable_role(self.bot, role, selection.user)
await RankEditor.create_rank( await RankEditor.create_rank(
selection, selection,
self.rank_type, self.rank_type,
@@ -380,34 +369,6 @@ class RankOverviewUI(MessageUI):
] or [[]] ] or [[]]
lines = line_blocks[self.pagen] lines = line_blocks[self.pagen]
desc = '\n'.join(reversed(lines)) desc = '\n'.join(reversed(lines))
# Add note about season start
note_name = t(_p(
'ui:rank_overview|embed|field:note|name',
"Note"
))
season_start = self.lguild.data.season_start
if season_start:
season_str = t(_p(
'ui:rank_overview|embed|field:note|value:with_season',
"Ranks are determined by activity since {timestamp}."
)).format(
timestamp=discord.utils.format_dt(season_start)
)
else:
season_str = t(_p(
'ui:rank_overview|embed|field:note|value:without_season',
"Ranks are determined by *all-time* statistics.\n"
"To reward ranks from a later time (e.g. to have monthly/quarterly/yearly ranks) "
"set the `season_start` with {stats_cmd}"
)).format(stats_cmd=self.bot.core.mention_cmd('configure statistics'))
if self.rank_type is RankType.VOICE:
addendum = t(_p(
'ui:rank_overview|embed|field:note|value|voice_addendum',
"Also note that ranks will only be updated when a member leaves a tracked voice channel! "
"Use the **Refresh Member Ranks** button below to update all members manually."
))
season_str = '\n'.join((season_str, addendum))
else: else:
# No ranks, give hints about adding ranks # No ranks, give hints about adding ranks
desc = t(_p( desc = t(_p(
@@ -439,6 +400,33 @@ class RankOverviewUI(MessageUI):
description=desc description=desc
) )
if show_note: if show_note:
# Add note about season start
note_name = t(_p(
'ui:rank_overview|embed|field:note|name',
"Note"
))
season_start = self.lguild.data.season_start
if season_start:
season_str = t(_p(
'ui:rank_overview|embed|field:note|value:with_season',
"Ranks are determined by activity since {timestamp}."
)).format(
timestamp=discord.utils.format_dt(season_start)
)
else:
season_str = t(_p(
'ui:rank_overview|embed|field:note|value:without_season',
"Ranks are determined by *all-time* statistics.\n"
"To reward ranks from a later time (e.g. to have monthly/quarterly/yearly ranks) "
"set the `season_start` with {stats_cmd}"
)).format(stats_cmd=self.bot.core.mention_cmd('configure statistics'))
if self.rank_type is RankType.VOICE:
addendum = t(_p(
'ui:rank_overview|embed|field:note|value|voice_addendum',
"Also note that ranks will only be updated when a member leaves a tracked voice channel! "
"Use the **Refresh Member Ranks** button below to update all members manually."
))
season_str = '\n'.join((season_str, addendum))
embed.add_field( embed.add_field(
name=note_name, name=note_name,
value=season_str, value=season_str,

View File

@@ -7,6 +7,7 @@ from discord.ui.button import button, Button, ButtonStyle
from meta import conf, LionBot from meta import conf, LionBot
from core.data import RankType from core.data import RankType
from wards import equippable_role
from utils.ui import MessageUI, AButton, AsComponents from utils.ui import MessageUI, AButton, AsComponents
from utils.lib import MessageArgs, replace_multiple from utils.lib import MessageArgs, replace_multiple
@@ -112,6 +113,7 @@ class RankPreviewUI(MessageUI):
await submit.response.defer(thinking=False) await submit.response.defer(thinking=False)
if self.parent is not None: if self.parent is not None:
asyncio.create_task(self.parent.refresh()) asyncio.create_task(self.parent.refresh())
self.bot.get_cog('RankCog').flush_guild_ranks(self.guild.id)
await self.refresh() await self.refresh()
@button(label="DELETE_PLACEHOLDER", style=ButtonStyle.red) @button(label="DELETE_PLACEHOLDER", style=ButtonStyle.red)
@@ -130,6 +132,7 @@ class RankPreviewUI(MessageUI):
role = None role = None
await self.rank.delete() await self.rank.delete()
self.bot.get_cog('RankCog').flush_guild_ranks(self.guild.id)
mention = role.mention if role else str(self.rank.roleid) mention = role.mention if role else str(self.rank.roleid)
@@ -212,25 +215,13 @@ class RankPreviewUI(MessageUI):
role: discord.Role = selected.values[0] role: discord.Role = selected.values[0]
await selection.response.defer(thinking=True, ephemeral=True) await selection.response.defer(thinking=True, ephemeral=True)
if role >= selection.user.top_role: if role.is_assignable():
# Do not allow user to manage a role above their own top role
error = t(_p(
'ui:rank_preview|menu:roles|error:above_caller',
"You have insufficient permissions to assign {mention} as a rank role! "
"You may only manage roles below your top role."
))
embed = discord.Embed(
title=t(_p(
'ui:rank_preview|menu:roles|error:above_caller|title',
"Insufficient permissions!"
)),
description=error,
colour=discord.Colour.brand_red()
)
await selection.response.send_message(embed=embed, ephemeral=True)
elif role.is_assignable():
# Update the rank role # Update the rank role
# Generic permission check for the new role
await equippable_role(self.bot, role, selection.user)
await self.rank.update(roleid=role.id) await self.rank.update(roleid=role.id)
self.bot.get_cog('RankCog').flush_guild_ranks(self.guild.id)
if self.parent is not None and not self.parent.is_finished(): if self.parent is not None and not self.parent.is_finished():
asyncio.create_task(self.parent.refresh()) asyncio.create_task(self.parent.refresh())
await self.refresh(thinking=selection) await self.refresh(thinking=selection)

View File

@@ -24,6 +24,9 @@ _p = babel._p
class RankRefreshUI(MessageUI): class RankRefreshUI(MessageUI):
# Cache of live rank UIs, mainly for introspection
_running = set()
def __init__(self, bot: LionBot, guild: discord.Guild, **kwargs): def __init__(self, bot: LionBot, guild: discord.Guild, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.bot = bot self.bot = bot
@@ -64,13 +67,18 @@ class RankRefreshUI(MessageUI):
def poke(self): def poke(self):
self._wakeup.set() self._wakeup.set()
def start(self):
self._loop_task = asyncio.create_task(self._refresh_loop(), name='Rank RefreshUI Monitor')
self._running.add(self)
async def run(self, *args, **kwargs): async def run(self, *args, **kwargs):
await super().run(*args, **kwargs) await super().run(*args, **kwargs)
self._loop_task = asyncio.create_task(self._refresh_loop(), name='refresh ui loop') self.start()
async def cleanup(self): async def cleanup(self):
if self._loop_task and not self._loop_task.done(): if self._loop_task and not self._loop_task.done():
self._loop_task.cancel() self._loop_task.cancel()
self._running.discard(self)
await super().cleanup() await super().cleanup()
def progress_bar(self, value, minimum, maximum, width=10) -> str: def progress_bar(self, value, minimum, maximum, width=10) -> str:
@@ -104,12 +112,13 @@ class RankRefreshUI(MessageUI):
# Join all the sections together and return # Join all the sections together and return
return ''.join(bar) return ''.join(bar)
@log_wrap('refresh ui loop') @log_wrap(action='refresh ui loop')
async def _refresh_loop(self): async def _refresh_loop(self):
while True: while True:
try: try:
await asyncio.sleep(1) await asyncio.sleep(5)
await self._wakeup.wait() await self._wakeup.wait()
self._wakeup.clear()
await self.refresh() await self.refresh()
except asyncio.CancelledError: except asyncio.CancelledError:
break break

View File

@@ -971,14 +971,21 @@ class RoleMenuCog(LionCog):
) )
# TODO: Generate the custom message from the template if it doesn't exist # TODO: Generate the custom message from the template if it doesn't exist
# TODO: Pathway for setting menu style
if rawmessage is not None: if rawmessage is not None:
msg_config = target.config.rawmessage msg_config = target.config.rawmessage
content = await msg_config.download_attachment(rawmessage) content = await msg_config.download_attachment(rawmessage)
data = await msg_config._parse_string(content) data = await msg_config._parse_string(0, content)
update_args[msg_config._column] = data update_args[msg_config._column] = data
if template is None: if template is None:
update_args[self.data.RoleMenu.templateid.name] = None update_args[self.data.RoleMenu.templateid.name] = None
ack_lines.append(msg_config.update_message) ack_lines.append(
t(_p(
'cmd:rolemenu_edit|parse:custom_message|success',
"Custom menu message updated."
))
)
# Update the data, if applicable # Update the data, if applicable
if update_args: if update_args:
@@ -1185,7 +1192,7 @@ class RoleMenuCog(LionCog):
label: Optional[appcmds.Range[str, 1, 100]] = None, label: Optional[appcmds.Range[str, 1, 100]] = None,
emoji: Optional[appcmds.Range[str, 0, 100]] = None, emoji: Optional[appcmds.Range[str, 0, 100]] = None,
description: Optional[appcmds.Range[str, 0, 100]] = None, description: Optional[appcmds.Range[str, 0, 100]] = None,
price: Optional[appcmds.Range[int, 0, MAX_COINS]] = None, price: Optional[appcmds.Range[int, -MAX_COINS, MAX_COINS]] = None,
duration: Optional[Transform[int, DurationTransformer(60)]] = None, duration: Optional[Transform[int, DurationTransformer(60)]] = None,
): ):
# Type checking guards # Type checking guards
@@ -1356,7 +1363,7 @@ class RoleMenuCog(LionCog):
await target.update_message() await target.update_message()
if target_is_reaction: if target_is_reaction:
try: try:
await self.menu.update_reactons() await target.update_reactons()
except SafeCancellation as e: except SafeCancellation as e:
embed.add_field( embed.add_field(
name=t(_p( name=t(_p(
@@ -1441,7 +1448,7 @@ class RoleMenuCog(LionCog):
label: Optional[appcmds.Range[str, 1, 100]] = None, label: Optional[appcmds.Range[str, 1, 100]] = None,
emoji: Optional[appcmds.Range[str, 0, 100]] = None, emoji: Optional[appcmds.Range[str, 0, 100]] = None,
description: Optional[appcmds.Range[str, 0, 100]] = None, description: Optional[appcmds.Range[str, 0, 100]] = None,
price: Optional[appcmds.Range[int, 0, MAX_COINS]] = None, price: Optional[appcmds.Range[int, -MAX_COINS, MAX_COINS]] = None,
duration: Optional[Transform[int, DurationTransformer(60)]] = None, duration: Optional[Transform[int, DurationTransformer(60)]] = None,
): ):
# Type checking wards # Type checking wards

View File

@@ -165,7 +165,7 @@ class RoleMenu:
await menu.attach() await menu.attach()
return menu return menu
async def fetch_message(self, refresh=False): async def fetch_message(self, refresh=False) -> Optional[discord.Message]:
""" """
Fetch the message the menu is attached to. Fetch the message the menu is attached to.
""" """
@@ -529,11 +529,17 @@ class RoleMenu:
"Role removed" "Role removed"
)) ))
) )
if total_refund: if total_refund > 0:
embed.description = t(_p( embed.description = t(_p(
'rolemenu|deselect|success:refund|desc', 'rolemenu|deselect|success:refund|desc',
"You have removed **{role}**, and been refunded {coin} **{amount}**." "You have removed **{role}**, and been refunded {coin} **{amount}**."
)).format(role=role.name, coin=self.bot.config.emojis.coin, amount=total_refund) )).format(role=role.name, coin=self.bot.config.emojis.coin, amount=total_refund)
if total_refund < 0:
# TODO: Consider disallowing them from removing roles if their balance would go negative
embed.description = t(_p(
'rolemenu|deselect|success:negrefund|desc',
"You have removed **{role}**, and have lost {coin} **{amount}**."
)).format(role=role.name, coin=self.bot.config.emojis.coin, amount=-total_refund)
else: else:
embed.description = t(_p( embed.description = t(_p(
'rolemenu|deselect|success:norefund|desc', 'rolemenu|deselect|success:norefund|desc',
@@ -551,7 +557,7 @@ class RoleMenu:
raise UserInputError( raise UserInputError(
t(_p( t(_p(
'rolemenu|select|error:required_role', 'rolemenu|select|error:required_role',
"You need to have the **{role}** role to use this!" "You need to have the role **{role}** required to use this menu!"
)).format(role=name) )).format(role=name)
) )
@@ -647,7 +653,7 @@ class RoleMenu:
"Role equipped" "Role equipped"
)) ))
) )
if price > 0: if price:
embed.description = t(_p( embed.description = t(_p(
'rolemenu|select|success:purchase|desc', 'rolemenu|select|success:purchase|desc',
"You have purchased the role **{role}** for {coin}**{amount}**" "You have purchased the role **{role}** for {coin}**{amount}**"
@@ -665,6 +671,7 @@ class RoleMenu:
)).format( )).format(
timestamp=discord.utils.format_dt(expiry) timestamp=discord.utils.format_dt(expiry)
) )
# TODO Event logging
return embed return embed
async def interactive_selection(self, interaction: discord.Interaction, menuroleid: int): async def interactive_selection(self, interaction: discord.Interaction, menuroleid: int):

View File

@@ -1,14 +1,17 @@
from typing import Optional
import discord import discord
from settings import ModelData from settings import ModelData
from settings.groups import SettingGroup, ModelConfig, SettingDotDict from settings.groups import SettingGroup, ModelConfig, SettingDotDict
from settings.setting_types import ( from settings.setting_types import (
RoleSetting, BoolSetting, StringSetting, DurationSetting, EmojiSetting RoleSetting, StringSetting, DurationSetting, EmojiSetting
) )
from core.setting_types import CoinSetting from core.setting_types import CoinSetting
from utils.ui import AButton, AsComponents from utils.ui import AButton, AsComponents
from meta.errors import UserInputError from meta.errors import UserInputError
from meta import conf
from babel.translator import ctx_translator from babel.translator import ctx_translator
from constants import MAX_COINS
from .data import RoleMenuData from .data import RoleMenuData
from . import babel from . import babel
@@ -74,6 +77,9 @@ class RoleMenuRoleOptions(SettingGroup):
"This menu item will now give the role {role}." "This menu item will now give the role {role}."
)).format(role=self.formatted) )).format(role=self.formatted)
return resp return resp
else:
raise ValueError("Attempt to call update_message without a value.")
@RoleMenuRoleConfig.register_model_setting @RoleMenuRoleConfig.register_model_setting
class Label(ModelData, StringSetting): class Label(ModelData, StringSetting):
@@ -138,7 +144,9 @@ class RoleMenuRoleOptions(SettingGroup):
return button return button
@classmethod @classmethod
async def _parse_string(cls, parent_id, string: str, interaction: discord.Interaction = None, **kwargs): async def _parse_string(cls, parent_id, string: str,
interaction: Optional[discord.Interaction] = None,
**kwargs):
emojistr = await super()._parse_string(parent_id, string, interaction=interaction, **kwargs) emojistr = await super()._parse_string(parent_id, string, interaction=interaction, **kwargs)
if emojistr and interaction is not None: if emojistr and interaction is not None:
# Use the interaction to test # Use the interaction to test
@@ -151,7 +159,7 @@ class RoleMenuRoleOptions(SettingGroup):
view=view, view=view,
) )
except discord.HTTPException: except discord.HTTPException:
t = interaction.client.translator.t t = ctx_translator.get().t
raise UserInputError(t(_p( raise UserInputError(t(_p(
'roleset:emoji|error:test_emoji', 'roleset:emoji|error:test_emoji',
"The selected emoji `{emoji}` is invalid or has been deleted." "The selected emoji `{emoji}` is invalid or has been deleted."
@@ -218,34 +226,43 @@ class RoleMenuRoleOptions(SettingGroup):
_display_name = _p('roleset:price', "price") _display_name = _p('roleset:price', "price")
_desc = _p( _desc = _p(
'roleset:price|desc', 'roleset:price|desc',
"Price of the role, in LionCoins." "Price of the role, in LionCoins. May be negative."
) )
_long_desc = _p( _long_desc = _p(
'roleset:price|long_desc', 'roleset:price|long_desc',
"How much the role costs when selected, in LionCoins." "How many LionCoins should be deducted from a member's account "
"when they equip this role through this menu.\n"
"The price may be negative, in which case the member will instead be rewarded "
"coins when they equip the role."
) )
_accepts = _p( _accepts = _p(
'roleset:price|accepts', 'roleset:price|accepts',
"Amount of coins that the role costs." "Amount of coins that the role costs when equipped."
) )
_default = 0 _default = 0
_min = - MAX_COINS
_model = RoleMenuData.RoleMenuRole _model = RoleMenuData.RoleMenuRole
_column = RoleMenuData.RoleMenuRole.price.name _column = RoleMenuData.RoleMenuRole.price.name
@property @property
def update_message(self) -> str: def update_message(self) -> str:
t = ctx_translator.get().t t = ctx_translator.get().t
value = self.value value = self.value or 0
if value: if value > 0:
resp = t(_p( resp = t(_p(
'roleset:price|set_response:set', 'roleset:price|set_response:positive',
"This role will now cost {price} to equip." "Equipping this role will now cost {coin}**{price}**."
)).format(price=self.formatted) )).format(price=value, coin=conf.emojis.coin)
elif value == 0:
resp = t(_p(
'roleset:price|set_response:zero',
"Equipping this role is now free."
))
else: else:
resp = t(_p( resp = t(_p(
'roleset:price|set_response:unset', 'roleset:price|set_response:negative',
"This role will now be free to equip from this role menu." "Equipping this role will now reward {coin}**{price}**."
)) )).format(price=-value, coin=conf.emojis.coin)
return resp return resp
@RoleMenuRoleConfig.register_model_setting @RoleMenuRoleConfig.register_model_setting

View File

@@ -190,7 +190,10 @@ class MenuEditor(MessageUI):
if not userstr: if not userstr:
new_data = None new_data = None
else: else:
new_data = await instance._parse_string(instance.parent_id, userstr) new_data = await instance._parse_string(
instance.parent_id, userstr,
guildid=self.menu.data.guildid
)
instance.data = new_data instance.data = new_data
modified.append(instance) modified.append(instance)
if modified: if modified:
@@ -349,7 +352,9 @@ class MenuEditor(MessageUI):
if not userstr: if not userstr:
new_data = None new_data = None
else: else:
new_data = await instance._parse_string(instance.parent_id, userstr, interaction=interaction) new_data = await instance._parse_string(
instance.parent_id, userstr, interaction=interaction
)
instance.data = new_data instance.data = new_data
modified.append(instance) modified.append(instance)
if modified: if modified:
@@ -503,6 +508,11 @@ class MenuEditor(MessageUI):
await self.refresh(thinking=selection) await self.refresh(thinking=selection)
await self.update_preview() await self.update_preview()
await self.menu.update_message() await self.menu.update_message()
if self.menu.data.menutype is MenuType.REACTION:
try:
await self.menu.update_reactons()
except SafeCancellation:
pass
else: else:
await selection.response.defer(thinking=False) await selection.response.defer(thinking=False)
@@ -591,6 +601,8 @@ class MenuEditor(MessageUI):
await self.refresh(thinking=selection) await self.refresh(thinking=selection)
await self.update_preview() await self.update_preview()
await self.menu.update_message() await self.menu.update_message()
if menutype is MenuType.REACTION:
await self.menu.update_reactons()
else: else:
await selection.response.defer() await selection.response.defer()
@@ -644,7 +656,7 @@ class MenuEditor(MessageUI):
self.bot, json.loads(self.menu.data.rawmessage), callback=self._editor_callback, callerid=self._callerid self.bot, json.loads(self.menu.data.rawmessage), callback=self._editor_callback, callerid=self._callerid
) )
self._slaves.append(editor) self._slaves.append(editor)
await editor.run(interaction) await editor.run(interaction, ephemeral=True)
# Template/Custom Menu # Template/Custom Menu
@select(cls=Select, placeholder="TEMPLATE_MENU_PLACEHOLDER", min_values=1, max_values=1) @select(cls=Select, placeholder="TEMPLATE_MENU_PLACEHOLDER", min_values=1, max_values=1)
@@ -821,20 +833,15 @@ class MenuEditor(MessageUI):
""" """
Display or update the preview message. Display or update the preview message.
""" """
args = await self.menu.make_args()
view = await self.menu.make_view()
if self._preview is not None: if self._preview is not None:
try: try:
await self._preview.delete_original_response() await self._preview.delete_original_response()
except discord.HTTPException: except discord.HTTPException:
pass pass
self._preview = None self._preview = None
await press.response.send_message( await press.response.defer(thinking=True, ephemeral=True)
**args.send_args,
view=view or discord.utils.MISSING,
ephemeral=True
)
self._preview = press self._preview = press
await self.update_preview()
async def preview_button_refresh(self): async def preview_button_refresh(self):
t = self.bot.translator.t t = self.bot.translator.t
@@ -887,13 +894,14 @@ class MenuEditor(MessageUI):
description=desc description=desc
) )
await selection.edit_original_response(embed=embed) await selection.edit_original_response(embed=embed)
except discord.HTTPException: except discord.HTTPException as e:
error = discord.Embed( error = discord.Embed(
colour=discord.Colour.brand_red(), colour=discord.Colour.brand_red(),
description=t(_p( description=t(_p(
'ui:menu_editor|button:repost|widget:repost|error:post_failed', 'ui:menu_editor|button:repost|widget:repost|error:post_failed',
"An error ocurred while posting to {channel}. Do I have sufficient permissions?" "An unknown error ocurred while posting to {channel}!\n"
)).format(channel=channel.mention) "**Error:** `{exception}`"
)).format(channel=channel.mention, exception=e.text)
) )
await selection.edit_original_response(embed=error) await selection.edit_original_response(embed=error)
else: else:
@@ -948,7 +956,7 @@ class MenuEditor(MessageUI):
title=title, description=desc title=title, description=desc
) )
# Send as response with the repost widget attached # Send as response with the repost widget attached
await press.response.send_message(embed=embed, view=AsComponents(repost_widget)) await press.response.send_message(embed=embed, view=AsComponents(repost_widget), ephemeral=True)
async def repost_button_refresh(self): async def repost_button_refresh(self):
t = self.bot.translator.t t = self.bot.translator.t
@@ -1039,7 +1047,7 @@ class MenuEditor(MessageUI):
role_index = int(splits[i-1]) role_index = int(splits[i-1])
mrole = self.menu.roles[role_index] mrole = self.menu.roles[role_index]
error = discord.Embed( embed = discord.Embed(
colour=discord.Colour.brand_red(), colour=discord.Colour.brand_red(),
title=t(_p( title=t(_p(
'ui:menu_editor|error:invald_emoji|title', 'ui:menu_editor|error:invald_emoji|title',
@@ -1051,7 +1059,7 @@ class MenuEditor(MessageUI):
)).format(emoji=mrole.config.emoji.data, label=mrole.config.label.data) )).format(emoji=mrole.config.emoji.data, label=mrole.config.label.data)
) )
await mrole.data.update(emoji=None) await mrole.data.update(emoji=None)
await self.channel.send(embed=error) await self.channel.send(embed=embed)
async def _redraw(self, args): async def _redraw(self, args):
try: try:

View File

@@ -13,6 +13,7 @@ from meta import LionCog, LionBot, LionContext
from meta.logger import log_wrap from meta.logger import log_wrap
from meta.errors import UserInputError, ResponseTimedOut from meta.errors import UserInputError, ResponseTimedOut
from meta.sharding import THIS_SHARD from meta.sharding import THIS_SHARD
from meta.monitor import ComponentMonitor, ComponentStatus, StatusLevel
from utils.lib import utc_now, error_embed from utils.lib import utc_now, error_embed
from utils.ui import Confirm from utils.ui import Confirm
from utils.data import MULTIVALUE_IN, MEMBERS from utils.data import MULTIVALUE_IN, MEMBERS
@@ -38,6 +39,10 @@ class ScheduleCog(LionCog):
self.bot = bot self.bot = bot
self.data: ScheduleData = bot.db.load_registry(ScheduleData()) self.data: ScheduleData = bot.db.load_registry(ScheduleData())
self.settings = ScheduleSettings() self.settings = ScheduleSettings()
self.monitor = ComponentMonitor(
'ScheduleCog',
self._monitor
)
# Whether we are ready to take events # Whether we are ready to take events
self.initialised = asyncio.Event() self.initialised = asyncio.Event()
@@ -57,12 +62,56 @@ class ScheduleCog(LionCog):
self.session_channels = self.settings.SessionChannels._cache self.session_channels = self.settings.SessionChannels._cache
async def _monitor(self):
nowid = self.nowid
now = None
now_lock = self.slotlock(nowid)
if not self.initialised.is_set():
level = StatusLevel.STARTING
info = (
"(STARTING) "
"Not ready. "
"Spawn task is {spawn}. "
"Spawn lock is {spawn_lock}. "
"Active slots {active}."
)
elif nowid not in self.active_slots:
level = StatusLevel.UNSURE
info = (
"(UNSURE) "
"Setup, but current slotid {nowid} not active. "
"Spawn task is {spawn}. "
"Spawn lock is {spawn_lock}. "
"Now lock is {now_lock}. "
"Active slots {active}."
)
else:
now = self.active_slots[nowid]
level = StatusLevel.OKAY
info = (
"(OK) "
"Running current slot {now}. "
"Spawn lock is {spawn_lock}. "
"Now lock is {now_lock}. "
"Active slots {active}."
)
data = {
'spawn': self.spawn_task,
'spawn_lock': self.spawn_lock,
'active': self.active_slots,
'nowid': nowid,
'now_lock': now_lock,
'now': now,
}
return ComponentStatus(level, info, info, data)
@property @property
def nowid(self): def nowid(self):
now = utc_now() now = utc_now()
return time_to_slotid(now) return time_to_slotid(now)
async def cog_load(self): async def cog_load(self):
self.bot.system_monitor.add_component(self.monitor)
await self.data.init() await self.data.init()
# Update the session channel cache # Update the session channel cache

View File

@@ -253,6 +253,12 @@ class ScheduledSession:
overwrites = room.overwrites overwrites = room.overwrites
for member in members: for member in members:
mobj = guild.get_member(member.userid) mobj = guild.get_member(member.userid)
if not mobj and not guild.chunked:
self.bot.request_chunking_for(guild)
try:
mobj = await guild.fetch_member(member.userid)
except discord.HTTPException:
mobj = None
if mobj: if mobj:
overwrites[mobj] = discord.PermissionOverwrite(connect=True, view_channel=True) overwrites[mobj] = discord.PermissionOverwrite(connect=True, view_channel=True)
try: try:
@@ -297,6 +303,13 @@ class ScheduledSession:
} }
for member in members: for member in members:
mobj = guild.get_member(member.userid) mobj = guild.get_member(member.userid)
if not mobj and not guild.chunked:
self.bot.request_chunking_for(guild)
try:
mobj = await guild.fetch_member(member.userid)
except discord.HTTPException:
mobj = None
if mobj: if mobj:
overwrites[mobj] = discord.PermissionOverwrite(connect=True, view_channel=True) overwrites[mobj] = discord.PermissionOverwrite(connect=True, view_channel=True)
try: try:

View File

@@ -440,7 +440,7 @@ class TimeSlot:
) )
def launch(self) -> asyncio.Task: def launch(self) -> asyncio.Task:
self.run_task = asyncio.create_task(self.run()) self.run_task = asyncio.create_task(self.run(), name=f"TimeSlot {self.slotid}")
return self.run_task return self.run_task
@log_wrap(action="TimeSlot Run") @log_wrap(action="TimeSlot Run")

View File

@@ -117,6 +117,7 @@ class Shopping(LionCog):
name=_p('cmd:shop', 'shop'), name=_p('cmd:shop', 'shop'),
description=_p('cmd:shop|desc', "Purchase coloures, roles, and other goodies with LionCoins.") description=_p('cmd:shop|desc', "Purchase coloures, roles, and other goodies with LionCoins.")
) )
@appcmds.guild_only
async def shop_group(self, ctx: LionContext): async def shop_group(self, ctx: LionContext):
return return
@@ -124,6 +125,7 @@ class Shopping(LionCog):
name=_p('cmd:shop_open', 'open'), name=_p('cmd:shop_open', 'open'),
description=_p('cmd:shop_open|desc', "Open the server shop.") description=_p('cmd:shop_open|desc', "Open the server shop.")
) )
@appcmds.guild_only
async def shop_open_cmd(self, ctx: LionContext): async def shop_open_cmd(self, ctx: LionContext):
""" """
Opens the shop UI for the current guild. Opens the shop UI for the current guild.

View File

@@ -1,3 +1,4 @@
import asyncio
import logging import logging
from typing import Optional from typing import Optional
@@ -90,8 +91,12 @@ class StatsCog(LionCog):
)).format(loading=self.bot.config.emojis.loading), )).format(loading=self.bot.config.emojis.loading),
timestamp=utc_now(), timestamp=utc_now(),
) )
await ctx.interaction.response(embed=waiting_embed) await ctx.interaction.response.send_message(embed=waiting_embed)
await ctx.guild.chunk() try:
await asyncio.wait_for(ctx.guild.chunk(), timeout=10)
pass
except asyncio.TimeoutError:
pass
else: else:
await ctx.interaction.response.defer(thinking=True) await ctx.interaction.response.defer(thinking=True)
ui = LeaderboardUI(self.bot, ctx.author, ctx.guild) ui = LeaderboardUI(self.bot, ctx.author, ctx.guild)

View File

@@ -44,7 +44,7 @@ async def get_profile_card(bot: LionBot, userid: int, guildid: int):
if crank: if crank:
roleid = crank.roleid roleid = crank.roleid
role = guild.get_role(roleid) role = guild.get_role(roleid)
name = role.name if role else str(role.id) name = role.name if role else 'Unknown Rank'
minimum = crank.required minimum = crank.required
maximum = nrank.required if nrank else None maximum = nrank.required if nrank else None
rangestr = format_stat_range(rank_type, minimum, maximum) rangestr = format_stat_range(rank_type, minimum, maximum)
@@ -63,7 +63,7 @@ async def get_profile_card(bot: LionBot, userid: int, guildid: int):
if nrank: if nrank:
roleid = nrank.roleid roleid = nrank.roleid
role = guild.get_role(roleid) role = guild.get_role(roleid)
name = role.name if role else str(role.id) name = role.name if role else 'Unknown Rank'
minimum = nrank.required minimum = nrank.required
guild_ranks = await ranks.get_guild_ranks(guildid) guild_ranks = await ranks.get_guild_ranks(guildid)

View File

@@ -75,6 +75,8 @@ class LeaderboardUI(StatsUI):
# (type, period) -> (pagen -> Optional[Future[Card]]) # (type, period) -> (pagen -> Optional[Future[Card]])
self.cache = {} self.cache = {}
self.was_chunked: bool = guild.chunked
async def run(self, interaction: discord.Interaction): async def run(self, interaction: discord.Interaction):
self._original = interaction self._original = interaction
@@ -136,6 +138,7 @@ class LeaderboardUI(StatsUI):
# Filter out members which are not in the server and unranked roles and bots # Filter out members which are not in the server and unranked roles and bots
# Usually hits cache # Usually hits cache
self.was_chunked = self.guild.chunked
unranked_setting = await self.bot.get_cog('StatsCog').settings.UnrankedRoles.get(self.guild.id) unranked_setting = await self.bot.get_cog('StatsCog').settings.UnrankedRoles.get(self.guild.id)
unranked_roleids = set(unranked_setting.data) unranked_roleids = set(unranked_setting.data)
true_leaderboard = [] true_leaderboard = []
@@ -299,42 +302,42 @@ class LeaderboardUI(StatsUI):
@button(label="This Season", style=ButtonStyle.grey) @button(label="This Season", style=ButtonStyle.grey)
async def season_button(self, press: discord.Interaction, pressed: Button): async def season_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True) await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.SEASON self.current_period = LBPeriod.SEASON
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="Today", style=ButtonStyle.grey) @button(label="Today", style=ButtonStyle.grey)
async def day_button(self, press: discord.Interaction, pressed: Button): async def day_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True) await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.DAY self.current_period = LBPeriod.DAY
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="This Week", style=ButtonStyle.grey) @button(label="This Week", style=ButtonStyle.grey)
async def week_button(self, press: discord.Interaction, pressed: Button): async def week_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True) await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.WEEK self.current_period = LBPeriod.WEEK
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="This Month", style=ButtonStyle.grey) @button(label="This Month", style=ButtonStyle.grey)
async def month_button(self, press: discord.Interaction, pressed: Button): async def month_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True) await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.MONTH self.current_period = LBPeriod.MONTH
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(label="All Time", style=ButtonStyle.grey) @button(label="All Time", style=ButtonStyle.grey)
async def alltime_button(self, press: discord.Interaction, pressed: Button): async def alltime_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True) await press.response.defer(thinking=True, ephemeral=True)
self.current_period = LBPeriod.ALLTIME self.current_period = LBPeriod.ALLTIME
self.focused = True self.focused = True
await self.refresh(thinking=press) await self.refresh(thinking=press)
@button(emoji=conf.emojis.backward, style=ButtonStyle.grey) @button(emoji=conf.emojis.backward, style=ButtonStyle.grey)
async def prev_button(self, press: discord.Interaction, pressed: Button): async def prev_button(self, press: discord.Interaction, pressed: Button):
await press.response.defer(thinking=True) await press.response.defer(thinking=True, ephemeral=True)
self.pagen -= 1 self.pagen -= 1
self.focused = False self.focused = False
await self.refresh(thinking=press) await self.refresh(thinking=press)
@@ -435,12 +438,19 @@ class LeaderboardUI(StatsUI):
Generate UI message arguments from stored data Generate UI message arguments from stored data
""" """
t = self.bot.translator.t t = self.bot.translator.t
chunk_warning = t(_p(
'ui:leaderboard|chunk_warning',
"**Note:** Could not retrieve member list from Discord, so some members may be missing. "
"Try again in a minute!"
))
if self.card is not None: if self.card is not None:
period_start = self.period_starts[self.current_period] period_start = self.period_starts[self.current_period]
header = t(_p( header = t(_p(
'ui:leaderboard|since', 'ui:leaderboard|since',
"Counting statistics since {timestamp}" "Counting statistics since {timestamp}"
)).format(timestamp=discord.utils.format_dt(period_start)) )).format(timestamp=discord.utils.format_dt(period_start))
if not self.was_chunked:
header = '\n'.join((header, chunk_warning))
args = MessageArgs( args = MessageArgs(
embed=None, embed=None,
content=header, content=header,
@@ -473,7 +483,11 @@ class LeaderboardUI(StatsUI):
)), )),
description=empty_description description=empty_description
) )
args = MessageArgs(content=None, embed=embed, files=[]) args = MessageArgs(
content=chunk_warning if not self.was_chunked else None,
embed=embed,
files=[]
)
return args return args
async def refresh_components(self): async def refresh_components(self):

View File

@@ -186,6 +186,17 @@ def mk_print(fp: io.StringIO) -> Callable[..., None]:
return _print return _print
def mk_status_printer(bot, printer):
async def _status(details=False):
if details:
status = await bot.system_monitor.get_overview()
else:
status = await bot.system_monitor.get_summary()
printer(status)
return status
return _status
@log_wrap(action="Code Exec") @log_wrap(action="Code Exec")
async def _async(to_eval: str, style='exec'): async def _async(to_eval: str, style='exec'):
newline = '\n' * ('\n' in to_eval) newline = '\n' * ('\n' in to_eval)
@@ -202,6 +213,7 @@ async def _async(to_eval: str, style='exec'):
scope['ctx'] = ctx = context.get() scope['ctx'] = ctx = context.get()
scope['bot'] = ctx_bot.get() scope['bot'] = ctx_bot.get()
scope['print'] = _print # type: ignore scope['print'] = _print # type: ignore
scope['print_status'] = mk_status_printer(scope['bot'], _print)
try: try:
if ctx and ctx.message: if ctx and ctx.message:
@@ -297,7 +309,7 @@ class Exec(LionCog):
file = discord.File(fp, filename=f"output-{target}.md") file = discord.File(fp, filename=f"output-{target}.md")
await ctx.reply(file=file) await ctx.reply(file=file)
elif result: elif result:
await ctx.reply(f"```md{result}```") await ctx.reply(f"```md\n{result}```")
else: else:
await ctx.reply("Command completed, and had no output.") await ctx.reply("Command completed, and had no output.")
else: else:
@@ -351,7 +363,7 @@ class Exec(LionCog):
except asyncio.TimeoutError: except asyncio.TimeoutError:
return return
if ctx.interaction: if ctx.interaction:
await ctx.interaction.response.defer(thinking=True, ephemeral=True) await ctx.interaction.response.defer(thinking=True)
if target is not None: if target is not None:
if target not in shard_talk.peers: if target not in shard_talk.peers:
embed = discord.Embed(description=f"Unknown peer {target}", colour=discord.Colour.red()) embed = discord.Embed(description=f"Unknown peer {target}", colour=discord.Colour.red())
@@ -376,7 +388,7 @@ class Exec(LionCog):
await ctx.reply(file=file) await ctx.reply(file=file)
else: else:
# Send as message # Send as message
await ctx.reply(f"```md\n{output}```", ephemeral=True) await ctx.reply(f"```md\n{output}```")
asyncall_cmd.autocomplete('target')(_peer_acmpl) asyncall_cmd.autocomplete('target')(_peer_acmpl)

View File

@@ -1,3 +1,4 @@
import asyncio
import datetime import datetime
import discord import discord
@@ -22,8 +23,8 @@ class GuildLog(LionCog):
embed.set_author(name="Left guild!") embed.set_author(name="Left guild!")
# Add more specific information about the guild # Add more specific information about the guild
embed.add_field(name="Owner", value="{0.name} (ID: {0.id})".format(guild.owner), inline=False) embed.add_field(name="Owner", value="<@{}>".format(guild.owner_id), inline=False)
embed.add_field(name="Members (cached)", value="{}".format(len(guild.members)), inline=False) embed.add_field(name="Members", value="{}".format(guild.member_count), inline=False)
embed.add_field(name="Now studying in", value="{} guilds".format(len(self.bot.guilds)), inline=False) embed.add_field(name="Now studying in", value="{} guilds".format(len(self.bot.guilds)), inline=False)
# Retrieve the guild log channel and log the event # Retrieve the guild log channel and log the event
@@ -35,8 +36,14 @@ class GuildLog(LionCog):
@LionCog.listener('on_guild_join') @LionCog.listener('on_guild_join')
@log_wrap(action="Log Guild Join") @log_wrap(action="Log Guild Join")
async def log_join_guild(self, guild: discord.Guild): async def log_join_guild(self, guild: discord.Guild):
owner = guild.owner try:
await asyncio.wait_for(guild.chunk(), timeout=60)
except asyncio.TimeoutError:
pass
# TODO: Add info about when we last joined this guild etc once we have it.
if guild.chunked:
bots = 0 bots = 0
known = 0 known = 0
unknown = 0 unknown = 0
@@ -68,6 +75,12 @@ class GuildLog(LionCog):
mem3, mem3,
mem4 mem4
) )
else:
mem_str = (
"`{count}` total members.\n"
"(Could not chunk guild within `60` seconds.)"
).format(count=guild.member_count)
created = "<t:{}>".format(int(guild.created_at.timestamp())) created = "<t:{}>".format(int(guild.created_at.timestamp()))
embed = discord.Embed( embed = discord.Embed(
@@ -77,7 +90,7 @@ class GuildLog(LionCog):
) )
embed.set_author(name="Joined guild!") embed.set_author(name="Joined guild!")
embed.add_field(name="Owner", value="{0} (ID: {0.id})".format(owner), inline=False) embed.add_field(name="Owner", value="<@{}>".format(guild.owner_id), inline=False)
embed.add_field(name="Created at", value=created, inline=False) embed.add_field(name="Created at", value=created, inline=False)
embed.add_field(name="Members", value=mem_str, inline=False) embed.add_field(name="Members", value=mem_str, inline=False)
embed.add_field(name="Now studying in", value="{} guilds".format(len(self.bot.guilds)), inline=False) embed.add_field(name="Now studying in", value="{} guilds".format(len(self.bot.guilds)), inline=False)

View File

@@ -396,7 +396,7 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
if data is not None: if data is not None:
role = None role = None
guildid = cls._get_guildid(parent_id) guildid = cls._get_guildid(parent_id, **kwargs)
bot = ctx_bot.get() bot = ctx_bot.get()
guild = bot.get_guild(guildid) guild = bot.get_guild(guildid)
if guild is not None: if guild is not None:
@@ -409,11 +409,14 @@ class RoleSetting(InteractiveSetting[ParentID, int, Union[discord.Role, discord.
async def _parse_string(cls, parent_id, string: str, **kwargs): async def _parse_string(cls, parent_id, string: str, **kwargs):
if not string or string.lower() == 'none': if not string or string.lower() == 'none':
return None return None
guildid = cls._get_guildid(parent_id, **kwargs)
t = ctx_translator.get().t t = ctx_translator.get().t
bot = ctx_bot.get() bot = ctx_bot.get()
role = None role = None
guild = bot.get_guild(parent_id) guild = bot.get_guild(guildid)
if guild is None:
raise ValueError("Attempting to parse role string with no guild.")
if string.isdigit(): if string.isdigit():
maybe_id = int(string) maybe_id = int(string)

View File

@@ -80,6 +80,10 @@ class Bucket:
self._last_full = False self._last_full = False
self._level += 1 self._level += 1
def fill(self):
self._leak()
self._level = max(self._level, self.max_level + 1)
async def wait(self): async def wait(self):
""" """
Wait until the bucket has room. Wait until the bucket has room.

View File

@@ -10,6 +10,8 @@ from discord.ui import Modal, View, Item
from meta.logger import log_action_stack, logging_context from meta.logger import log_action_stack, logging_context
from meta.errors import SafeCancellation from meta.errors import SafeCancellation
from gui.errors import RenderingException
from . import logger from . import logger
from ..lib import MessageArgs, error_embed from ..lib import MessageArgs, error_embed
@@ -228,6 +230,12 @@ class LeoUI(View):
f"Caught a safe cancellation from LeoUI: {e.details}", f"Caught a safe cancellation from LeoUI: {e.details}",
extra={'action': 'Cancel'} extra={'action': 'Cancel'}
) )
except RenderingException as e:
logger.info(
f"UI interaction failed due to rendering exception: {repr(e)}"
)
embed = interaction.client.tree.rendersplat(e)
await interaction.client.tree.error_reply(interaction, embed)
except Exception: except Exception:
logger.exception( logger.exception(
f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r} from interaction: " f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r} from interaction: "
@@ -235,15 +243,8 @@ class LeoUI(View):
extra={'with_ctx': True, 'action': 'UIError'} extra={'with_ctx': True, 'action': 'UIError'}
) )
# Explicitly handle the bugsplat ourselves # Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error) splat = interaction.client.tree.bugsplat(interaction, error)
try: await interaction.client.tree.error_reply(interaction, splat)
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
class MessageUI(LeoUI): class MessageUI(LeoUI):
@@ -475,21 +476,20 @@ class LeoModal(Modal):
""" """
try: try:
raise error raise error
except RenderingException as e:
logger.info(
f"Modal submit failed due to rendering exception: {repr(e)}"
)
embed = interaction.client.tree.rendersplat(e)
await interaction.client.tree.error_reply(interaction, embed)
except Exception: except Exception:
logger.exception( logger.exception(
f"Unhandled interaction exception occurred in {self!r}. Interaction: {interaction.data}", f"Unhandled interaction exception occurred in {self!r}. Interaction: {interaction.data}",
extra={'with_ctx': True, 'action': 'ModalError'} extra={'with_ctx': True, 'action': 'ModalError'}
) )
# Explicitly handle the bugsplat ourselves # Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error) splat = interaction.client.tree.bugsplat(interaction, error)
try: await interaction.client.tree.error_reply(interaction, splat)
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
def error_handler_for(exc): def error_handler_for(exc):

View File

@@ -21,14 +21,18 @@ async def sys_admin(bot: LionBot, userid: int):
return userid in admins return userid in admins
async def high_management(bot: LionBot, member: discord.Member): async def high_management(bot: LionBot, member: discord.Member, guild: discord.Guild):
if not guild:
return True
if await sys_admin(bot, member.id): if await sys_admin(bot, member.id):
return True return True
return member.guild_permissions.administrator return member.guild_permissions.administrator
async def low_management(bot: LionBot, member: discord.Member): async def low_management(bot: LionBot, member: discord.Member, guild: discord.Guild):
if await high_management(bot, member): if not guild:
return True
if await high_management(bot, member, guild):
return True return True
return member.guild_permissions.manage_guild return member.guild_permissions.manage_guild
@@ -42,20 +46,20 @@ async def sys_admin_iward(interaction: discord.Interaction) -> bool:
async def high_management_iward(interaction: discord.Interaction) -> bool: async def high_management_iward(interaction: discord.Interaction) -> bool:
if not interaction.guild: if not interaction.guild:
return False return False
return await high_management(interaction.client, interaction.user) return await high_management(interaction.client, interaction.user, interaction.guild)
async def low_management_iward(interaction: discord.Interaction) -> bool: async def low_management_iward(interaction: discord.Interaction) -> bool:
if not interaction.guild: if not interaction.guild:
return False return False
return await low_management(interaction.client, interaction.user) return await low_management(interaction.client, interaction.user, interaction.guild)
# High level ctx wards # High level ctx wards
async def moderator_ctxward(ctx: LionContext) -> bool: async def moderator_ctxward(ctx: LionContext) -> bool:
if not ctx.guild: if not ctx.guild:
return False return False
passed = await low_management(ctx.bot, ctx.author) passed = await low_management(ctx.bot, ctx.author, ctx.guild)
if passed: if passed:
return True return True
modrole = ctx.lguild.data.mod_role modrole = ctx.lguild.data.mod_role
@@ -85,7 +89,7 @@ async def sys_admin_ward(ctx: LionContext) -> bool:
async def high_management_ward(ctx: LionContext) -> bool: async def high_management_ward(ctx: LionContext) -> bool:
if not ctx.guild: if not ctx.guild:
return False return False
passed = await high_management(ctx.bot, ctx.author) passed = await high_management(ctx.bot, ctx.author, ctx.guild)
if passed: if passed:
return True return True
else: else:
@@ -101,7 +105,7 @@ async def high_management_ward(ctx: LionContext) -> bool:
async def low_management_ward(ctx: LionContext) -> bool: async def low_management_ward(ctx: LionContext) -> bool:
if not ctx.guild: if not ctx.guild:
return False return False
passed = await low_management(ctx.bot, ctx.author) passed = await low_management(ctx.bot, ctx.author, ctx.guild)
if passed: if passed:
return True return True
else: else:
@@ -192,7 +196,7 @@ async def equippable_role(bot: LionBot, target_role: discord.Role, actor: discor
"You need the `MANAGE_ROLES` permission before you can configure roles!" "You need the `MANAGE_ROLES` permission before you can configure roles!"
)).format(role=target_role.mention) )).format(role=target_role.mention)
) )
elif actor.top_role <= target_role and not actor == guild.owner: elif actor.top_role <= target_role and not actor.id == guild.owner_id:
raise UserInputError( raise UserInputError(
t(_p( t(_p(
'ward:equippable_role|error:actor_top_role', 'ward:equippable_role|error:actor_top_role',