Compare commits

...

190 Commits

Author SHA1 Message Date
Jakub Kuczys 7e2a74b276 Add redbot-update command for updating Red (#6734) 2026-05-13 14:14:43 -08:00
Jakub Kuczys 899f24ceca Add Environment abstraction to internal Downloader API (#6710) 2026-05-14 00:09:45 +02:00
Jakub Kuczys 13f45f69ac Use Simple Repository API for fetching latest version (#6704) 2026-05-13 14:03:20 -08:00
Jakub Kuczys a234fc1e02 Add redbot-setup restore cli command (#6709) 2026-05-13 21:18:17 +02:00
Jakub Kuczys edce32364f Fix Red.send_interactive() permission check again (#6697) 2026-05-10 16:30:03 -04:00
Jakub Kuczys 7305f44f68 Make changelog easy to parse (#6711) 2026-05-10 16:11:36 -04:00
Jakub Kuczys cbd4643bd3 Fix deprecated use of shutdown_timeout kwarg in TCPSite (#6743) 2026-05-10 16:00:10 -04:00
Jakub Kuczys b02fa38423 Stop referencing V2 everywhere and move migration guide (#6737) 2026-05-10 15:51:36 -04:00
Jakub Kuczys 99babf9ad3 Update uvloop to 0.22.1 (#6705) 2026-05-10 15:46:57 -04:00
Jakub Kuczys 169d0eed49 Reuse name input logic and allow retry in data path input (#6696) 2026-04-19 20:04:05 +00:00
Jakub Kuczys 70faa8cd52 Make logging setup consistent between redbot and redbot-setup (#6695)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2026-04-19 19:41:50 +00:00
Jakub Kuczys 2ea4c766ad Add basic backcompat for people using Downloader internals (#6713) 2026-04-07 18:17:11 -04:00
Jakub Kuczys 6ceb45b35c Fix remaining issues with internal Downloader API (#6721) 2026-04-07 17:57:49 -04:00
Jakub Kuczys 4032648dcc Simplify bot class (Red) __init__ arguments, remove unused (#6714) 2026-04-05 15:52:43 -04:00
Jakub Kuczys f70c48ec30 Warn when venv/install doesn't match previously used one (#6715) 2026-04-05 15:44:17 -04:00
Jakub Kuczys fcb8bc0265 Prefer using repr() on the Dev cog results (excl. str instances) (#6726) 2026-04-03 02:05:54 +02:00
Jakub Kuczys ee1db01a2f Rip out Downloader's non-UI functionality into private core API (#6706) 2026-03-29 22:25:04 +02:00
EternalllZM e2acec0862 [Docs] Cleanup "About Virtual Environment" docs (#6701) 2026-03-29 13:54:58 -04:00
EternalllZM b83b882921 Update README to Explain Discord (#6650)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2026-03-29 13:51:08 -04:00
Chris 99d7b0e3b7 Update Twitch API token setup link (#6703) 2026-03-23 21:13:59 -08:00
Jakub Kuczys 9270373c56 Update Spotify API instructions + use SetApiView (#6699) 2026-03-15 12:57:14 +01:00
Jakub Kuczys e8f0ea0510 Switch PAT use to app token (#6698) 2026-03-10 14:30:20 +01:00
github-actions[bot] b42bab4de9 Version bump to 3.5.25.dev1 (#6691)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-06 02:43:22 +01:00
github-actions[bot] e868872214 Version bump to 3.5.24 (#6689)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-06 02:21:33 +01:00
github-actions[bot] bee0ddbffc Automated Crowdin downstream (#6690)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-06 02:21:14 +01:00
Jakub Kuczys 2de3d03cc9 Red 3.5.24 - Changelog (#6686) 2026-03-06 02:15:52 +01:00
Jakub Kuczys 056f2de557 Bump Lavalink version to 3.7.13+red.5 (#6688) 2026-03-06 02:14:27 +01:00
Jakub Kuczys 34cbd15ba9 Stop waiting for update check on bot startup (#6687) 2026-03-06 01:51:22 +01:00
EternalllZM 9a458fdd83 [Docs] Misc fixes (#6685) 2026-03-05 23:36:42 +01:00
Jakub Kuczys 0e78051c5d Bump Lavalink version to 3.7.13+red.3 (#6683) 2026-03-05 20:52:23 +01:00
Jakub Kuczys 53766173d0 Update supported Java versions (#6681) 2026-03-05 20:52:15 +01:00
Jakub Kuczys 36a5f752a2 Add --no-debug flag for resetting the verbosity level (#6680) 2026-03-05 01:04:56 +01:00
github-actions[bot] b2007a718d Version bump to 3.5.24.dev1 (#6679)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-05 00:37:09 +01:00
Jakub Kuczys 015f5a00fd Replace "that" with "who" in the contributors thanks in the changelog (#6678) 2026-03-05 00:23:23 +01:00
github-actions[bot] 6e417419aa Version bump to 3.5.23 (#6676)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-05 00:14:28 +01:00
github-actions[bot] 316f237397 Automated Crowdin downstream (#6677)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-05 00:14:06 +01:00
Jakub Kuczys 75e37ca896 Red 3.5.23 - Changelog (#6675) 2026-03-05 00:09:25 +01:00
Evanroby c701c79ed0 [serverinfo]: Paginate server features. (#6652)
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
2026-03-04 22:49:34 +01:00
Karlo Prikratki 60323c99d1 Fix fuzzy command search not ignoring blacklisted users (#6594) 2026-03-04 22:40:59 +01:00
Jakub Kuczys b7c11c016e Add Sphinx extension for extracting prompt contents (#6671) 2026-03-04 22:12:17 +01:00
Jakub Kuczys bdc66c3f56 Update supported systems (#6669) 2026-03-04 22:11:37 +01:00
Jakub Kuczys e6c6b9874b Fix invalid rST argument syntax in one of prompts in update doc (#6670) 2026-03-04 22:06:48 +01:00
Jakub Kuczys 40b01c7985 Bump dependencies (#6666) 2026-03-04 22:05:14 +01:00
Jakub Kuczys 666e249413 Revert "strip unicode bidi control characters in cleanup_code (#6649)" (#6672) 2026-03-04 22:04:12 +01:00
Jakub Kuczys 1bfe2a78fa Make few adjustments to [p]mywarnings and its docs (#6673) 2026-03-04 21:57:52 +01:00
Kowlin afe4e636b7 Handle UIB interactions for ignoring channels. (#6503)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
Co-authored-by: Jakub Kuczys <me@jacken.men>
2026-03-04 20:38:30 +00:00
Jakub Kuczys 45c55418a4 Bump discord.py to 2.7.1 (#6664) 2026-03-04 02:50:26 +01:00
Jakub Kuczys 29758fd104 Update to support DAVE protocol (#6665) 2026-03-04 02:46:09 +01:00
EternalllZM 8626aef36f [Docs] Red backup and restore instructions. (#6654) 2026-03-04 00:30:29 +01:00
Jakub Kuczys 18154465c3 Rework shutdown logic (#6659)
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
2026-03-03 22:45:23 +01:00
Jakub Kuczys 07e6f1b264 Fix send_interactive not working on User/Member objects (#6656)
Co-authored-by: Kowlin <git@wyvern.blue>
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
2026-03-03 01:34:34 +01:00
Myra effc390ddf [p]mywarnings improvements - Respect show_mod and allow sending self requested warnings to DMs (#6641)
Co-authored-by: Kowlin <git@wyvern.blue>
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
2026-03-03 00:42:07 +01:00
Jakub Kuczys 9afbe363e5 Allow to enable d.py debug logging through env var (#6657) 2026-02-19 02:25:39 +01:00
Predä 5023f7f1b5 [Streams] Check required permissions in alerts (#4967)
Co-authored-by: palmtree5 <3577255+palmtree5@users.noreply.github.com>
2026-02-15 14:38:02 -09:00
cswimr 01a3c17f8e strip unicode bidi control characters in cleanup_code (#6649) 2026-02-02 12:48:57 -05:00
EternalllZM fe574189d6 Update "Creating a bot account" instructions and assets (#6647) 2026-01-31 22:13:28 -05:00
cswimr fec1818e5a Add --cog-path CLI argument (#6510) 2026-01-05 17:31:10 -05:00
Ryan dd3b9a01d3 Fix set api modal not submitting (#6626) 2025-10-12 09:09:35 +02:00
Kowlin a809c3604a Explicitly comment some speed up dependencies not directly used by Red (#6621) 2025-09-26 19:45:08 +02:00
github-actions[bot] 982f082cb0 Version bump to 3.5.23.dev1 (#6617)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-05 01:50:31 +02:00
github-actions[bot] 6a4d777723 Automated Crowdin downstream (#6616)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-05 01:35:29 +02:00
github-actions[bot] 4dc18342d9 Version bump to 3.5.22 (#6615)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-05 01:34:31 +02:00
Jakub Kuczys 71311c446a Red 3.5.22 - Changelog (#6613)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-09-05 01:34:17 +02:00
Michael Oliveira d4a1ee38f0 Fix audio command docstrings not translating (#6609) 2025-09-05 01:26:49 +02:00
Jakub Kuczys 8507b0165f Update deprecated use of TextInput.label (#6612) 2025-09-04 21:12:26 +02:00
Jakub Kuczys c7651da2f9 Bump dependencies (includes discord.py 2.6.3 bump) (#6614) 2025-09-04 21:11:35 +02:00
AAA3A 4d24365d17 Update the invite Regex to match invites with backslashes / ignore promos (#6586)
Co-authored-by: Twentysix <6267772+Twentysix26@users.noreply.github.com>
2025-09-04 16:49:48 +02:00
aikaterna d2f0f8d5ed [Audio] Update application.yml source options (#6611) 2025-09-02 00:22:44 +02:00
github-actions[bot] cc803579a0 Version bump to 3.5.22.dev1 (#6606)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-26 03:33:47 +02:00
github-actions[bot] 0271dee092 Version bump to 3.5.21 (#6604)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-26 02:36:47 +02:00
github-actions[bot] 1e5620935d Automated Crowdin downstream (#6605)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-26 00:36:39 +00:00
Jakub Kuczys 031e57891c Red 3.5.21 - Changelog (#6603)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-08-26 02:13:18 +02:00
Jakub Kuczys 72f8952baf Bump d.py version to 2.6.2 (#6597) 2025-08-25 19:52:36 -04:00
Jakub Kuczys 0e25534677 Fix LL YT plugin client options not being applied (#6601) 2025-08-25 19:34:26 -04:00
Jakub Kuczys cfbf2fa401 Bump LL YT plugin version to 1.13.5 (#6602) 2025-08-25 19:33:31 -04:00
Jakub Kuczys a65509c67c Bump dependencies (#6599)
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
2025-08-25 22:41:45 +00:00
Jakub Kuczys 936e17338c Fix unhandled exception in stringification of DevOutput's result (#6592) 2025-08-25 17:43:41 -04:00
Jakub Kuczys 6c3c2e8fa7 Drop Ubuntu 20.04 and update supported RHEL versions (#6598) 2025-08-25 17:29:14 -04:00
Michael Oliveira 8c3cdf20a6 Inject guild locale information in app commands (#6579) 2025-08-25 22:52:06 +02:00
Michael Oliveira f68580fab9 Improve i18n documentation for cog creators (#6595) 2025-08-17 19:06:39 +02:00
Michael Oliveira ec66666036 Rewrite intents and public bots doc page (#6578) 2025-08-17 19:06:25 +02:00
Myra 3fd23d4163 Mod cog: Option to show an extra field with custom content on the ban embed (#6593)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-08-10 17:54:51 -04:00
Jakub Kuczys 2dbbb51208 Disable Rich tracebacks by default (#6576) 2025-08-09 17:45:03 -04:00
TrustyJAID b177c80b4e Fix an issue with alias quote detection (#6582)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-08-09 16:55:37 -04:00
Ben Cos 029029e9a5 Add missing dependency in label pattern exhaustiveness check (#6589) 2025-08-01 04:59:48 +02:00
TrustyJAID c6ff2191f3 Fix Reports in dm's not checking the selected guild for a configured channel (#6573) 2025-06-11 19:16:22 -04:00
Kreusada 6603cd1a86 Check attach_files permission inside send_interactive (#6552)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-05-25 17:04:35 -04:00
Kreusada 1daf56f3d8 Add Group.all method explanation to Config framework (#6550)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-05-25 14:48:47 -04:00
Kreusada b3f0349ba2 [Docs] Update publishing cogs guide with [botname] substitution tip (#6539)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-05-25 14:39:02 -04:00
A Frozen Lake bfc3561928 Fix formatting of [p]names. (#6538)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-05-25 14:33:28 -04:00
github-actions[bot] 8d8918b3c6 Version bump to 3.5.21.dev1 (#6571)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-03 16:13:01 +00:00
github-actions[bot] 550cf49bc8 Version bump to 3.5.20 (#6570)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-03 18:06:21 +02:00
Jakub Kuczys 313eeffc84 Red 3.5.20 - Changelog (#6569) 2025-05-03 15:42:37 +00:00
Jakub Kuczys 88e1f72467 Bump dependencies (#6568) 2025-05-03 16:35:33 +01:00
aikaterna 3c6146d6ca Bump YT plugin to 1.13.1 (#6566) 2025-05-03 17:30:26 +02:00
aikaterna bfab9cc5f8 [Audio] Update application.yml generation for new sources (#6567)
Co-authored-by: Jakub Kuczys <me@jacken.men>
2025-05-03 17:26:05 +02:00
github-actions[bot] 07ee31a88f Version bump to 3.5.20.dev1 (#6562)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-27 03:19:35 +02:00
github-actions[bot] bd61c105e0 Version bump to 3.5.19 (#6560)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-27 03:01:18 +02:00
github-actions[bot] db0e831a2e Automated Crowdin downstream (#6561)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-27 03:01:03 +02:00
Jakub Kuczys d7a56f1518 Red 3.5.19 - Changelog (#6559) 2025-04-27 03:00:39 +02:00
Jakub Kuczys 5f2c6d19d1 Bump dependencies + drop F40, add F42 to support matrix (#6558) 2025-04-27 02:56:38 +02:00
Jakub Kuczys 7b3600ecb1 Bump YT plugin version to 1.13.0 (#6557) 2025-04-27 00:08:36 +00:00
github-actions[bot] 6f5f34c80a Version bump to 3.5.19.dev1 (#6546)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-26 20:15:13 +01:00
github-actions[bot] 2e902b067e Version bump to 3.5.18 (#6545)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-26 20:04:52 +01:00
Jakub Kuczys 61695daded Red 3.5.18 - Changelog (#6544) 2025-03-26 20:00:45 +01:00
Jakub Kuczys 10889642ce Bump dependencies (#6543) 2025-03-26 18:53:40 +00:00
Kreusada a3b254fe8e [Audio] Bump YT source plugin version (#6542) 2025-03-26 19:52:00 +01:00
github-actions[bot] 71554c981d Version bump to 3.5.18.dev1 (#6535)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-08 20:45:09 +00:00
github-actions[bot] e1225029b0 Version bump to 3.5.17 (#6533)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-08 21:37:30 +01:00
github-actions[bot] 85923d4c0f Automated Crowdin downstream (#6534)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-08 21:36:48 +01:00
Jakub Kuczys 19b34c63b2 Red 3.5.17 - Changelog (#6532)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2025-03-08 21:24:16 +01:00
Kowlin 60e819159f Bump d.py and Jinja2 (#6531) 2025-03-08 20:44:22 +01:00
Jakub Kuczys 0f4c7b0fe6 Bump deps (including d.py 2.5 bump) (#6529)
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
2025-03-04 23:38:37 +01:00
aikaterna 3bf7c64d01 [Audio] Bump YT plugin version (#6530) 2025-03-04 22:57:52 +01:00
Karlo Prikratki 4558b72082 Fix YouTube upcoming stream's embed not being timezone-agnostic (#6527) 2025-02-15 23:33:15 +01:00
github-actions[bot] 73958d87f1 Version bump to 3.5.17.dev1 (#6522)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-06 02:44:41 +01:00
github-actions[bot] d6054412f4 Version bump to 3.5.16 (#6520)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-06 02:36:23 +01:00
github-actions[bot] 5cf69bdc51 Automated Crowdin downstream (#6521)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-06 02:35:50 +01:00
Jakub Kuczys 2fd6ea88d9 Red 3.5.16 - Changelog (#6519) 2025-02-06 02:31:40 +01:00
Jakub Kuczys 50ad59a6c8 Catch TypeError when setting global locale (#6518) 2025-02-06 02:31:03 +01:00
Jakub Kuczys dfc1e742f8 Handle invalid locale configuration graciously during startup (#6517) 2025-02-05 19:42:42 -05:00
github-actions[bot] 769c319ffd Version bump to 3.5.16.dev1 (#6516)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-03 02:13:08 +00:00
github-actions[bot] 3c1f2cddfd Version bump to 3.5.15 (#6514)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-03 02:43:09 +01:00
github-actions[bot] 1299db0f3a Automated Crowdin downstream (#6515)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-03 01:41:27 +00:00
Jakub Kuczys c58d208ca2 Red 3.5.15 - Changelog (#6513) 2025-02-03 02:38:31 +01:00
Jakub Kuczys c308ea0184 Bump dependencies and drop openSUSE Leap 15.5 (#6512) 2025-02-03 02:35:02 +01:00
Kowlin 8e0948d560 Truncate service names in [p]set api modal (#6502) 2025-02-01 18:36:37 -05:00
aikaterna 952a372652 [Audio] Bump YT plugin version (#6511) 2025-02-01 23:56:01 +01:00
Jakub Kuczys a0c1713e78 Split public and private i18n APIs (#6022) 2025-01-26 19:33:06 -05:00
Jakub Kuczys 8b1daf1ad0 Move private things in modlog and redbot.core.errors (#6020) 2025-01-26 18:49:03 -05:00
Jakub Kuczys dcdef9d798 Use build.jobs.install to avoid installing unnecessary deps (#6508) 2025-01-27 00:28:07 +01:00
Jakub Kuczys f962aeb7b8 Split public and private config/driver APIs (#6024) 2025-01-26 18:25:53 -05:00
Jakub Kuczys b13b1f8f16 Add explicit Sphinx config to .readthedocs.yml (#6507) 2025-01-27 00:10:10 +01:00
Jakub Kuczys 6bf2a88995 Single-source supported Java versions in Audio code (#6500) 2025-01-26 14:03:20 -08:00
Kowlin 22888f8014 Bump actions to V4 (#6504) 2025-01-07 20:41:17 +00:00
Jakub Kuczys ba44370020 Add subnet masks in CIDR notation as answers to computers trivia (#6495) 2025-01-07 21:31:27 +01:00
Jakub Kuczys cd0e8750c1 Allow usage of [p]audioset logs in DMs (#6499) 2024-12-28 16:48:24 -08:00
github-actions[bot] 679289fd1c Version bump to 3.5.15.dev1 (#6498)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-25 04:34:06 +00:00
github-actions[bot] ce6489325e Version bump to 3.5.14 (#6496)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-25 05:25:15 +01:00
github-actions[bot] d64cbdf83e Automated Crowdin downstream (#6497)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-25 04:24:55 +00:00
Jakub Kuczys 66d1c87b5a Red 3.5.14 - Changelog (#6493) 2024-12-25 05:20:47 +01:00
Jakub Kuczys 3888f09cfa Fix safety of `[p]repo list` (#6494) 2024-12-24 23:09:00 -05:00
Jakub Kuczys 8ad9c55d50 Bump dependencies and update OS matrix (#6492) 2024-12-24 19:54:56 +01:00
Glas 3aac07a4d7 Explain how to remove URL restrictions in the play error message (#6348)
Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-12-24 15:22:02 +01:00
Kowlin bd26e7d5af Fix permissions fetching for User Installable Bots (#6457)
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-12-24 14:16:03 +00:00
Karlo Prikratki 9392077434 Allow passing multiple cogs to slash enablecog and slash disablecog (#6001)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2024-12-23 22:49:19 -05:00
Chovin 1f48919005 Add a separate timeout for Lavalink download (#6461)
Co-authored-by: chovin <chovin@users.noreply.github.com>
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-12-24 04:08:49 +01:00
TrustyJAID fdaa869130 [Help command] Utilize copy to clipboard (#6244)
Co-authored-by: Kreusada Ignad Amredes <67752638+Kreusada@users.noreply.github.com>
2024-12-24 03:23:26 +01:00
Michael Oliveira 18614b1604 Respect [p]bypasscooldowns in [p]slash sync (#6465) 2024-12-24 03:04:11 +01:00
Michael Oliveira 016684bcce Add methods for getting app command IDs/mentions from cache (#6278)
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-12-24 03:02:16 +01:00
Kowlin 5cfb8edab8 ANSI formatting support (#5538)
Co-authored-by: Kowlin <Kowlin@users.noreply.github.com>
Co-authored-by: Vexed <vex@vexcodes.com>
Co-authored-by: Jack Tealey <67752638+Kreusada@users.noreply.github.com>
2024-12-24 01:20:42 +00:00
cdaman3141 150692538f Fixed [p]ban raising an unhandled error if an ID too large is provided (#6486)
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-12-24 01:12:33 +00:00
TrustyJAID f4ffc6bc80 Add support for SimpleMenu to use custom select options (#6480)
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-12-24 02:00:41 +01:00
Kevin Wang 9419f2642a Allow enforcing reason to be filled in Mod cog commands (#6477)
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-12-24 01:58:57 +01:00
Jakub Kuczys f0a29e9815 Support [botname] substitutions in cog install messages too (#6491) 2024-12-23 19:18:36 -05:00
aikaterna d29ae723c1 Bump YT plugin version (#6490) 2024-12-22 23:15:43 +01:00
Jakub Kuczys 9920628948 Bump YT plugin version and update LL server config (#6488) 2024-12-17 21:28:05 -08:00
Ascensionn 48b2fe77c0 [p]warn ask to ban when user not in server (#6481)
Co-authored-by: Michael Oliveira <34169552+Flame442@users.noreply.github.com>
2024-12-08 13:42:22 -05:00
palmtree5 33e0eac741 Remove deprecated options from .pylintrc (#6462)
Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com>
2024-11-17 17:01:31 +00:00
Kyla 2871992772 Define view once inside menus.menu for button menus (#6472) 2024-11-17 16:24:30 +00:00
Seaswimmer 30058c0f73 Add links to the output of [p]repo list (#6284)
Co-authored-by: Seaswimmer <102361830+SeaswimmerTheFsh@users.noreply.github.com>
Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com>
2024-10-29 18:10:02 +00:00
Jakub Kuczys 4134881fae Optimize Trivia list loading (#6336)
Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com>
2024-10-29 17:58:28 +00:00
Lemon Rose 4396323205 [Trivia] Add non-punctuated alias (#5889)
Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com>
2024-10-29 17:46:58 +00:00
sravan 4e27059209 [Core] Add error handling when [p]load tries to load "locales" (#6466) 2024-10-20 16:20:29 -04:00
Jakub Kuczys d3887b595f Fix release helper subcommands (#6451) 2024-09-15 02:49:04 +02:00
Jakub Kuczys 005b8af10a Update docs vars to work with RTD changes (#6410) 2024-09-11 05:32:36 +02:00
Kreusada Ignar Yadrak d304da7a16 [Downloader] Support [botname] substitutions in cog install messages (#6443) 2024-09-11 05:32:18 +02:00
Kreusada f3c89ad8bd Fix header utility docstring (#6444) 2024-09-02 00:51:14 +02:00
Kreusada 05cf9b7f39 Add header, hyperlink and subtext utilities (#6102)
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
2024-09-01 21:40:12 +00:00
Jakub Kuczys 907a3f7561 Split out non-Python assets in Publish Release workflow (#6440) 2024-08-28 15:47:35 +02:00
Mellow 2595c9de10 [Trivia] Correct typos in World Cup trivia list for Golden Glove questions (#6441) 2024-08-27 18:02:53 +01:00
Jakub Kuczys 8be7b0850c Version bump to 3.5.14.dev1 2024-08-27 02:12:58 +02:00
Jakub Kuczys 61ec913789 Change Red 3.5.13 release date 2024-08-27 02:00:28 +02:00
Jakub Kuczys 88b11f2b9c Add missing perm to Publish Release workflow 2024-08-27 02:00:13 +02:00
Jakub Kuczys 9ca0ced2d8 Fix publish release workflow 2024-08-27 01:54:47 +02:00
github-actions[bot] a5a178bfaf Version bump to 3.5.13 (#6438)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-27 01:22:52 +02:00
github-actions[bot] eeb90aaa45 Automated Crowdin downstream (#6439)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-26 23:21:43 +00:00
Jakub Kuczys b7a59b5e4c Red 3.5.13 - Changelog (#6437) 2024-08-27 01:12:13 +02:00
Jakub Kuczys 2769ea025f Bump dependencies (#6436) 2024-08-26 18:08:50 -04:00
aikaterna 818420a641 [Audio] Fix trying to send notify message with no channel object (#6429) 2024-08-26 19:53:30 +02:00
aikaterna 3c49a77e34 [Audio] Update Lavalink.jar and yt source build numbers (#6435)
Co-authored-by: Jakub Kuczys <me@jacken.men>
2024-08-26 19:51:59 +02:00
Jakub Kuczys 90691ba2b9 Generate default LL server config and attach it to GH release (#6430) 2024-08-26 19:34:37 +02:00
Guyonsteroids 68f2806204 [Trivia] Correct Stephen to Steven (#6434)
Co-authored-by: Artemis6969 <82315185+Artemis6969@users.noreply.github.com>
2024-08-24 23:33:20 +02:00
github-actions[bot] 903992f48a Version bump to 3.5.13.dev1 (#6428)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-08 04:25:02 +02:00
github-actions[bot] e4b75f5333 Version bump to 3.5.12 (#6426)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-08 04:11:33 +02:00
github-actions[bot] 254d5a91d6 Automated Crowdin downstream (#6427)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-08 02:11:20 +00:00
Jakub Kuczys b979a7c4d1 Red 3.5.12 - Changelog (#6425) 2024-08-07 19:08:08 -07:00
aikaterna 54a29174ea [Audio] Update yt source version (#6424) 2024-08-07 22:38:46 +02:00
aikaterna 5bbced5b0d [Audio] Fix llset secured (#6423) 2024-08-07 22:36:27 +02:00
github-actions[bot] 5b21c89505 Version bump to 3.5.12.dev1 (#6421)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-04 22:07:27 +00:00
734 changed files with 89557 additions and 56477 deletions
+11 -5
View File
@@ -50,10 +50,6 @@
- redbot/cogs/downloader/*
# Docs
- docs/cog_guides/downloader.rst
# Tests
- redbot/pytest/downloader.py
- redbot/pytest/downloader_testrepo.*
- tests/cogs/downloader/**/*
"Category: Cogs - Economy":
# Source
- redbot/cogs/economy/*
@@ -162,6 +158,7 @@
- any:
- redbot/core/_drivers/**/*
- "!redbot/core/_drivers/**/locales/*"
- redbot/core/_config.py
- redbot/core/config.py
# Docs
- docs/framework_config.rst
@@ -207,14 +204,23 @@
- docs/cog_guides/core.rst
"Category: Core - Command-line Interfaces":
- redbot/__main__.py
- redbot/_update/**/*
- redbot/logging.py
- redbot/core/_cli.py
- redbot/core/_debuginfo.py
- redbot/setup.py
"Category: Core - Downloader":
# Source
- redbot/core/_downloader/**/*
# Tests
- redbot/pytest/downloader.py
- redbot/pytest/downloader_testrepo.*
- tests/core/_downloader/**/*
"Category: Core - Help":
- redbot/core/commands/help.py
"Category: Core - i18n":
# Source
- redbot/core/_i18n.py
- redbot/core/i18n.py
# Locale files
- redbot/**/locales/*
@@ -261,7 +267,6 @@
- docs/framework_events.rst
- docs/guide_cog_creation.rst
- docs/guide_cog_creators.rst
- docs/guide_migration.rst
- docs/guide_publish_cogs.rst
- docs/guide_slash_and_interactions.rst
"Category: Docs - Install Guides":
@@ -271,6 +276,7 @@
- docs/bot_application_guide.rst
- docs/install_guides/**/*
- docs/update_red.rst
- docs/backup_red.rst
"Category: Docs - Other":
- docs/host-list.rst
- docs/index.rst
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
@@ -17,7 +17,7 @@ jobs:
- name: Install script's pre-requirements
run: |
python -m pip install -U pip
python -m pip install -U pathspec pyyaml rich
python -m pip install -U pathspec pyyaml rich typing_extensions
- name: Check label pattern exhaustiveness
run: |
python .github/workflows/scripts/check_label_pattern_exhaustiveness.py
+3 -4
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
@@ -34,14 +34,13 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: 'python'
# Override the default behavior so that the action doesn't attempt
# to auto-install Python dependencies
# Learn more...
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#analyzing-python-dependencies
setup-python-dependencies: false
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -55,4 +54,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
if: github.repository == 'Cog-Creators/Red-DiscordBot'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: Lint Python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ env.ref }}
- uses: actions/setup-python@v4
+37 -38
View File
@@ -7,18 +7,24 @@ on:
required: false
default: 'auto'
permissions:
contents: write
pull-requests: write
jobs:
crowdin_download_translations:
environment: Prepare Release
needs: pr_stable_bump
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.RED_RELEASER_CLIENT_ID }}
private-key: ${{ secrets.RED_RELEASER_PRIVATE_KEY }}
# Checkout repository and install Python
- uses: actions/checkout@v6
with:
token: ${{ steps.app-token.outputs.token }}
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.8'
- name: Install dependencies
@@ -43,7 +49,7 @@ jobs:
id: cpr_crowdin
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ steps.app-token.outputs.token }}
commit-message: Automated Crowdin downstream
title: "Automated Crowdin downstream"
body: |
@@ -51,31 +57,32 @@ jobs:
Please ensure that there are no errors or invalid files are in the PR.
labels: "Automated PR, Changelog Entry: Skipped"
branch: "automated/i18n"
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
committer: >-
${{ steps.app-token.outputs.app-slug }}[bot]
<263745220+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>
author: >-
${{ steps.app-token.outputs.app-slug }}[bot]
<263745220+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>
milestone: ${{ needs.pr_stable_bump.outputs.milestone_number }}
- name: Close and reopen the PR with different token to trigger CI
uses: actions/github-script@v6
env:
PR_NUMBER: ${{ steps.cpr_crowdin.outputs.pull-request-number }}
PR_OPERATION: ${{ steps.cpr_crowdin.outputs.pull-request-operation }}
with:
github-token: ${{ secrets.cogcreators_bot_repo_scoped }}
script: |
const script = require(
`${process.env.GITHUB_WORKSPACE}/.github/workflows/scripts/close_and_reopen_pr.js`
);
console.log(script({github, context}));
pr_stable_bump:
environment: Prepare Release
runs-on: ubuntu-latest
outputs:
milestone_number: ${{ steps.get_milestone_number.outputs.result }}
steps:
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.RED_RELEASER_CLIENT_ID }}
private-key: ${{ secrets.RED_RELEASER_PRIVATE_KEY }}
# Checkout repository and install Python
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
token: ${{ steps.app-token.outputs.token }}
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.8'
@@ -105,7 +112,7 @@ jobs:
id: cpr_bump_stable
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ steps.app-token.outputs.token }}
commit-message: Version bump to ${{ steps.bump_version_stable.outputs.new_version }}
title: Version bump to ${{ steps.bump_version_stable.outputs.new_version }}
body: |
@@ -113,18 +120,10 @@ jobs:
Please ensure that there are no errors or invalid files are in the PR.
labels: "Automated PR, Changelog Entry: Skipped"
branch: "automated/pr_bumps/${{ steps.bump_version_stable.outputs.new_version }}"
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
committer: >-
${{ steps.app-token.outputs.app-slug }}[bot]
<263745220+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>
author: >-
${{ steps.app-token.outputs.app-slug }}[bot]
<263745220+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>
milestone: ${{ steps.get_milestone_number.outputs.result }}
- name: Close and reopen the PR with different token to trigger CI
uses: actions/github-script@v6
env:
PR_NUMBER: ${{ steps.cpr_bump_stable.outputs.pull-request-number }}
PR_OPERATION: ${{ steps.cpr_bump_stable.outputs.pull-request-operation }}
with:
github-token: ${{ secrets.cogcreators_bot_repo_scoped }}
script: |
const script = require(
`${process.env.GITHUB_WORKSPACE}/.github/workflows/scripts/close_and_reopen_pr.js`
);
console.log(await script({github, context}));
+62 -24
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Checkout repository and install Python
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
@@ -57,7 +57,7 @@ jobs:
name: Build package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
@@ -75,27 +75,68 @@ jobs:
run: python -m twine check dist/*
- name: Upload packaged distributions
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: build-output
path: ./dist
generate_default_ll_server_config:
name: Generate default application.yml
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Install script's dependencies
run: python -m pip install PyYAML
- name: Generate default application.yml
env:
APP_YML_FILE: "Red-DiscordBot-${{ github.ref_name }}-default-lavalink-application.yml"
run: |
mkdir -p release_assets
python .github/workflows/scripts/get_default_ll_server_config.py "release_assets/$APP_YML_FILE"
- name: Upload default application.yml
uses: actions/upload-artifact@v4
with:
name: ll-default-server-config
path: ./release_assets
release_to_pypi:
needs:
- release_information
- build
- generate_default_ll_server_config
environment: Release
name: Release to PyPI
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Download packaged distributions
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Download default application.yml
uses: actions/download-artifact@v4
with:
name: ll-default-server-config
path: release_assets/
- name: Upload dists to GitHub Release
env:
GITHUB_TOKEN: "${{ github.token }}"
run: |
gh release upload "$GITHUB_REF_NAME" dist/* release_assets/* --repo "$GITHUB_REPOSITORY"
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
@@ -106,9 +147,7 @@ jobs:
print-hash: true
pr_dev_bump:
permissions:
contents: write
pull-requests: write
environment: Prepare Release
needs: release_to_pypi
name: Update Red version number to dev
runs-on: ubuntu-latest
@@ -119,11 +158,18 @@ jobs:
run: |
echo "BASE_BRANCH=${TAG_BASE_BRANCH#'refs/heads/'}" >> $GITHUB_ENV
- uses: actions/checkout@v3
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.RED_RELEASER_CLIENT_ID }}
private-key: ${{ secrets.RED_RELEASER_PRIVATE_KEY }}
- uses: actions/checkout@v6
with:
ref: ${{ env.BASE_BRANCH }}
token: ${{ steps.app-token.outputs.token }}
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.8'
@@ -153,7 +199,7 @@ jobs:
id: cpr_bump_dev
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ steps.app-token.outputs.token }}
commit-message: Version bump to ${{ steps.bump_version_dev.outputs.new_version }}
title: Version bump to ${{ steps.bump_version_dev.outputs.new_version }}
body: |
@@ -161,19 +207,11 @@ jobs:
Please ensure that there are no errors or invalid files are in the PR.
labels: "Automated PR, Changelog Entry: Skipped"
branch: "automated/pr_bumps/${{ steps.bump_version_dev.outputs.new_version }}"
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
committer: >-
${{ steps.app-token.outputs.app-slug }}[bot]
<263745220+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>
author: >-
${{ steps.app-token.outputs.app-slug }}[bot]
<263745220+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>
milestone: ${{ steps.get_milestone_number.outputs.result }}
base: ${{ env.BASE_BRANCH }}
- name: Close and reopen the PR with different token to trigger CI
uses: actions/github-script@v6
env:
PR_NUMBER: ${{ steps.cpr_bump_dev.outputs.pull-request-number }}
PR_OPERATION: ${{ steps.cpr_bump_dev.outputs.pull-request-operation }}
with:
github-token: ${{ secrets.cogcreators_bot_repo_scoped }}
script: |
const script = require(
`${process.env.GITHUB_WORKSPACE}/.github/workflows/scripts/close_and_reopen_pr.js`
);
console.log(await script({github, context}));
+7 -7
View File
@@ -15,7 +15,7 @@ jobs:
- macos-latest
steps:
- name: Checkout the repository.
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Python 3.8.
uses: actions/setup-python@v4
@@ -48,7 +48,7 @@ jobs:
python .github/workflows/scripts/compile_requirements.py
- name: Upload requirements files.
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.compile_requirements.outputs.sys_platform }}
path: requirements/${{ steps.compile_requirements.outputs.sys_platform }}-*.txt
@@ -59,7 +59,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository.
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Python 3.8.
uses: actions/setup-python@v4
@@ -71,17 +71,17 @@ jobs:
python -m pip install -U "packaging>=22.0"
- name: Download Windows requirements.
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: win32
path: requirements
- name: Download Linux requirements.
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: linux
path: requirements
- name: Download macOS requirements.
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: darwin
path: requirements
@@ -91,7 +91,7 @@ jobs:
python .github/workflows/scripts/merge_requirements.py
- name: Upload merged requirements files.
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: merged
path: |
@@ -17,6 +17,12 @@ def pip_compile(version: str, name: str) -> None:
if EXCLUDE_STEM_RE.fullmatch(stem):
return
constraint_flags = [
arg
for file in REQUIREMENTS_FOLDER.glob(f"{sys.platform}-3.8-*.txt")
for arg in ("-c", file.name)
]
executable = ("py", f"-{version}") if sys.platform == "win32" else (f"python{version}",)
subprocess.check_call(
(
@@ -30,6 +36,7 @@ def pip_compile(version: str, name: str) -> None:
f"{name}.in",
"--output-file",
f"{stem}.txt",
*constraint_flags,
)
)
@@ -0,0 +1,31 @@
import sys
from pathlib import Path
import yaml
ROOT_FOLDER = Path(__file__).parents[3].absolute()
AUDIO_FOLDER = ROOT_FOLDER / "redbot/cogs/audio"
# We want to import `redbot.cogs.audio.managed_node` package as if it were top-level package
# so we have to the `redbot/cogs/audio` directory to Python's path.
sys.path.insert(0, str(AUDIO_FOLDER))
def main() -> int:
try:
output_file = sys.argv[1]
except IndexError:
print("Usage:", sys.argv[0], "<output_file>", file=sys.stderr)
return 2
import managed_node
server_config = managed_node.get_default_server_config()
with open(output_file, "w", encoding="utf-8") as fp:
yaml.safe_dump(server_config, fp)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -49,6 +49,8 @@ def get_requirements(fp: TextIO) -> List[RequirementData]:
via_prefix = "via "
if source.startswith(via_prefix):
source = source[len(via_prefix) :]
if source.startswith("-c ") and source != "-c base.txt":
continue
current.comments.add(source)
elif line and not line.startswith(("#", " ")):
current = RequirementData(line)
@@ -135,25 +137,27 @@ for name in names:
python_version_marker = (
# Requirement present on less Python versions than not.
" or ".join(
f"python_version == '{python_version}'" for python_version in python_versions
f"python_version == '{python_version}'"
for python_version in sorted(python_versions)
)
if len(python_versions) < len(all_python_versions - python_versions)
# Requirement present on more Python versions than not
# This may generate an empty string when Python version is irrelevant.
else " and ".join(
f"python_version != '{python_version}'"
for python_version in all_python_versions - python_versions
for python_version in sorted(all_python_versions - python_versions)
)
)
platform_marker = (
# Requirement present on less platforms than not.
" or ".join(f"sys_platform == '{platform}'" for platform in platforms)
" or ".join(f"sys_platform == '{platform}'" for platform in sorted(platforms))
if len(platforms) < len(all_platforms - platforms)
# Requirement present on more platforms than not
# This may generate an empty string when platform is irrelevant.
else " and ".join(
f"sys_platform != '{platform}'" for platform in all_platforms - platforms
f"sys_platform != '{platform}'"
for platform in sorted(all_platforms - platforms)
)
)
@@ -167,12 +171,12 @@ for name in names:
# Requirement present on less envs than not.
" or ".join(
f"(sys_platform == '{platform}' and python_version == '{python_version}')"
for platform, python_version in iter_envs(envs)
for platform, python_version in iter_envs(sorted(envs))
)
if len(envs) < len(all_envs - envs.keys())
else " and ".join(
f"(sys_platform != '{platform}' and python_version != '{python_version}')"
for platform, python_version in iter_envs(all_envs - envs.keys())
for platform, python_version in iter_envs(sorted(all_envs - envs.keys()))
)
)
+2 -2
View File
@@ -39,7 +39,7 @@ jobs:
fail-fast: false
name: Tox - ${{ matrix.friendly_name }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ env.ref }}
- name: Set up Python
@@ -76,7 +76,7 @@ jobs:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ env.ref }}
- name: Set up Python
-9
View File
@@ -26,14 +26,6 @@ unsafe-load-any-extension=no
# run arbitrary code
extension-pkg-whitelist=
# Allow optimization of some AST trees. This will activate a peephole AST
# optimizer, which will apply various small optimizations. For instance, it can
# be used to obtain the result of joining multiple strings with the addition
# operator. Joining a lot of strings can lead to a maximum recursion error in
# Pylint and this flag can prevent that. It has one side effect, the resulting
# AST will be different than the one from reality.
optimize-ast=no
[MESSAGES CONTROL]
@@ -66,7 +58,6 @@ disable=C, # black is enforcing this for us already, incompatibly
[REPORTS]
output-format=parseable
files-output=no
reports=no
+10
View File
@@ -4,6 +4,16 @@ build:
os: "ubuntu-22.04"
tools:
python: "3.8"
jobs:
install:
- pip install .[doc]
post_build:
- mkdir -p docs/_build/doctrees docs/_build/markdown "$READTHEDOCS_OUTPUT/html/_markdown"
- python -m sphinx -T -b markdown -d docs/_build/doctrees -D "language=$READTHEDOCS_LANGUAGE" docs docs/_build/markdown
- cp docs/_build/markdown/changelog.md "$READTHEDOCS_OUTPUT/html/_markdown/changelog.md"
sphinx:
configuration: docs/conf.py
python:
install:
+1477 -106
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -64,6 +64,12 @@ liking, making it completely customizable. This is a *self-hosted bot* meani
to host and maintain your own instance. You can turn Red into an admin bot, music bot, trivia bot,
new best friend or all of these together!
Red is built for [Discord](https://discord.com/), a popular VOIP and instant messaging platform.
It's best suited for use in guilds (also known as servers), where it utilizes Discord's
well-documented API to communicate and deliver its many features. Discord offers its API to
encourage developers to explore their creativity by building programs, tools, and services that
enhance the Discord experience.
[Installation](#installation) is easy, and you do **NOT** need to know anything about coding! Aside
from installing and updating, every part of the bot can be controlled from within Discord.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 50 KiB

+43
View File
@@ -0,0 +1,43 @@
from typing import Any, Dict, List
from docutils import nodes
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
class ChangelogContributors(SphinxDirective):
has_content = True
def run(self) -> List[nodes.Node]:
contributors = [contributor for line in self.content for contributor in line.split()]
comment_value = " ".join(contributors)
line_nodes = []
for contributor in contributors:
if line_nodes:
line_nodes.append(nodes.Text(", "))
line_nodes.append(
nodes.reference(
contributor,
f"@{contributor}",
internal=False,
refuri=f"https://github.com/sponsors/{contributor}",
)
)
node = nodes.line_block(
"",
nodes.comment("", f"RED-CHANGELOG-CONTRIBUTORS: {comment_value}"),
nodes.line("", "Thanks to all these amazing people who contributed to this release:"),
nodes.line("", "", *line_nodes),
)
return [node]
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_directive("changelog-contributors", ChangelogContributors)
return {
"version": "1.0",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
+154
View File
@@ -0,0 +1,154 @@
from __future__ import annotations
import json
import os
from typing import Any, Dict, List, Set
from docutils import nodes
from docutils.io import StringOutput
from docutils.nodes import Element
from sphinx.application import Sphinx
from sphinx.builders.text import TextBuilder
from sphinx.writers.text import TextWriter
from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
logger = logging.getLogger(__name__)
class PromptTranslator(SphinxTranslator):
builder: PromptBuilder
def __init__(self, document: nodes.document, builder: PromptBuilder) -> None:
super().__init__(document, builder)
self.body = ""
self.prompts: List[Dict[str, str]] = []
def visit_document(self, node: Element) -> None:
pass
def depart_document(self, node: Element) -> None:
if not self.prompts:
self.body = ""
return
if self.builder.out_suffix.endswith(".json"):
self.body = json.dumps(self.prompts, indent=4)
else:
self.body = "\n".join(prompt["content"] for prompt in self.prompts)
def unknown_visit(self, node: Element) -> None:
pass
def unknown_departure(self, node: Element) -> None:
pass
def visit_prompt(self, node: Element) -> None:
self.prompts.append(
{
"language": node.attributes["language"],
"prompts": node.attributes["prompts"],
"modifiers": node.attributes["modifiers"],
"rawsource": node.rawsource,
"content": node.children[0],
}
)
class PromptWriter(TextWriter):
def translate(self) -> None:
visitor = self.builder.create_translator(self.document, self.builder)
self.document.walkabout(visitor)
self.output = visitor.body
class prompt(nodes.literal_block):
pass
class PromptBuilder(TextBuilder):
"""Extract prompts from documents."""
format = "json"
epilog = "The files with prompts are in %(outdir)s."
out_suffix = ".json"
default_translator_class = PromptTranslator
writer: PromptWriter
def init(self) -> None:
sphinx_prompt = __import__("sphinx-prompt")
def run(self) -> List[prompt]:
self.assert_has_content()
rawsource = "\n".join(self.content)
language = self.options.get("language") or "text"
prompts = [
p
for p in (
self.options.get("prompts") or sphinx_prompt.PROMPTS.get(language, "")
).split(",")
if p
]
modifiers = [
modifier for modifier in self.options.get("modifiers", "").split(",") if modifier
]
content = rawsource
if "auto" in modifiers:
parts = []
for line in self.content:
for p in prompts:
if line.startswith(p):
line = line[len(p) + 1 :].rstrip()
parts.append(line)
content = "\n".join(parts)
node = prompt(
rawsource,
content,
directive_content=self.content,
language=language,
prompts=self.options.get("prompts") or sphinx_prompt.PROMPTS.get(language, ""),
modifiers=modifiers,
)
return [node]
sphinx_prompt.PromptDirective.run = run
def prepare_writing(self, docnames: Set[str]) -> None:
del docnames
self.writer = PromptWriter(self)
def write_doc(self, docname: str, doctree: nodes.document) -> None:
self.writer.write(doctree, StringOutput(encoding="utf-8"))
if not self.writer.output:
# don't write empty files
return
filename = os.path.join(self.outdir, docname.replace("/", os.path.sep) + self.out_suffix)
os.makedirs(os.path.dirname(filename), exist_ok=True)
try:
with open(filename, "w", encoding="utf-8") as f:
f.write(self.writer.output)
except OSError as err:
logger.warning("error writing file %s: %s", filename, err)
class JsonPromptBuilder(PromptBuilder):
name = "jsonprompt"
out_suffix = ".json"
class TextPromptBuilder(PromptBuilder):
name = "textprompt"
out_suffix = ".txt"
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(JsonPromptBuilder)
app.add_builder(TextPromptBuilder)
return {
"version": "1.0",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
+1 -1
View File
@@ -5,7 +5,7 @@
<p class="first admonition-title">Warning</p>
<p class="last">
This document is for Red's development version, which can be significantly different from previous releases.
If you're a regular user, you should read the <a href="{{ dict(versions)['stable'] }}">Red documentation for the current stable release</a>.
If you're a regular user, you should read the <a href="/{{ rtd_language }}/stable/">Red documentation for the current stable release</a>.
</p>
</div>
{% endif %}
+17 -17
View File
@@ -3,16 +3,14 @@
==========================
About Virtual Environments
==========================
Creating a virtual environment is really easy and usually prevents many common installation
problems.
Creating a virtual environment is simple and helps prevent installation problems.
**What Are Virtual Environments For?**
Virtual environments allow you to isolate Red's library dependencies, cog dependencies and python
binaries from the rest of your system. There is no performance overhead to using virtual environment
and it saves you from a lot of troubles during setup. It also makes sure Red and its dependencies
are installed to a predictable location which makes uninstalling Red as simple as removing a single folder,
without worrying about losing your data or other things on your system becoming broken.
Virtual environments allow you to isolate Red's library dependencies, cog dependencies, and Python
binaries from the rest of your system with no performance overhead, ensuring those dependencies
and Red are installed to a predictable location. This makes uninstalling Red as simple as removing
a single folder, preventing any data loss or breaking other things on your system.
--------------------------------------------
@@ -21,19 +19,21 @@ Virtual Environments with Multiple Instances
If you are running multiple instances of Red on the same machine, you have the option of either
using the same virtual environment for all of them, or creating separate ones.
.. note::
Using a *single* virtual environment for all of your instances means you:
This only applies for multiple instances of V3. If you are running a V2 instance as well,
you **must** use separate virtual environments.
- Only need to update Red once for all instances.
- Must shut down all instances prior to updating.
- Will save space on your hard drive.
- Want all instances to share the same version/dependencies.
The advantages of using a *single* virtual environment for all of your V3 instances are:
Using *multiple* virtual environments for each individual or select groups of instances means you:
- When updating Red, you will only need to update it once for all instances (however you will still need to restart all instances for the changes to take effect)
- It will save space on your hard drive
On the other hand, you may wish to update each of your instances individually.
- Need to update Red within each virtual environment separately.
- Can update Red without needing to update all instances.
- Only need to shut down the instance(s) being updated.
- Want different Red/dependency versions on different instances.
.. important::
Windows users with multiple instances should create *separate* virtual environments, as
updating multiple running instances at once is likely to cause errors.
Regardless of which option you choose, do not update while any instances within that virtual
environment are running. This is especially true for Windows, as files are locked by the system while in use.
+143
View File
@@ -0,0 +1,143 @@
.. _backup-red:
============================
Backing Up and Restoring Red
============================
Red can be backed up and restored to any system as long as it is a supported per our `end-user-guarantees`.
The system it's restored to can be different from the system that was backed up.
.. note::
Some 3rd-party cogs may not support all systems that Core Red supports and such cogs may therefore not work,
if restored to an unsupported system. This does not affect cogs that do not impose additional restrictions.
.. contents::
:local:
:depth: 2
Creating backups
****************
Windows
-------
To make a backup, perform the following steps:
#. Stop the bot, ideally with ``[p]shutdown``.
#. Activate your venv.
.. prompt:: batch
"%userprofile%\redenv\Scripts\activate.bat"
#. Backup your Red instance with the following command:
.. prompt:: batch
:prompts: (redenv) C:\\>
redbot-setup backup <your instance name>
.. attention::
Replace ``<your instance name>`` with the name of the instance you want to backup.
#. The command will create a backup file for you and show you the path to it.
.. tip::
If you want to backup your instance to a custom folder,
you can run the ``redbot-setup backup`` command as shown below,
replacing ``C:\path\to\backup\folder`` with the path to the folder that
you want to backup your instance to:
.. prompt:: batch
:prompts: (redenv) C:\\>
redbot-setup backup <your instance name> C:\path\to\backup\folder
Linux & Mac
-----------
To make a backup, perform the following steps:
#. Stop the bot, ideally with ``[p]shutdown``.
#. Activate your venv.
.. prompt:: bash
source ~/redenv/bin/activate
#. Backup your Red instance with the following command:
.. prompt:: bash
:prompts: (redenv) $
redbot-setup backup <your instance name>
.. attention::
Replace ``<your instance name>`` with the name of the instance you want to backup.
#. The command will create a backup file for you and show you the path to it.
.. tip::
If you want to backup your instance to a custom folder,
you can run the ``redbot-setup backup`` command as shown below,
replacing ``/path/to/backup/folder`` with the path to the folder that
you want to backup your instance to:
.. prompt:: bash
:prompts: (redenv) $
redbot-setup backup <your instance name> /path/to/backup/folder
Restoring backups
*****************
Windows
-------
To restore a backup, perform the following steps:
#. `Install Red <windows-install-guide>` on the new machine/location, skipping the ``redbot-setup`` step.
#. Activate your venv.
.. prompt:: batch
"%userprofile%\redenv\Scripts\activate.bat"
#. Restore your Red instance with the following command:
.. prompt:: batch
:prompts: (redenv) C:\\>
redbot-setup restore C:\path\to\backup\file.tar.gz
.. attention::
Replace ``C:\path\to\backup\file.tar.gz`` with the path to the backup file
that you want to restore from.
#. The command will guide you through the restore process.
Linux & Mac
-----------
To restore a backup, perform the following steps:
#. `Install Red <install-guides>` on the new machine/location, skipping the ``redbot-setup`` step.
#. Activate your venv.
.. prompt:: bash
source ~/redenv/bin/activate
#. Restore your Red instance with the following command:
.. prompt:: bash
:prompts: (redenv) $
redbot-setup restore /path/to/backup/file.tar.gz
.. attention::
Replace ``/path/to/backup/file.tar.gz`` with the path to the backup file
that you want to restore from.
#. The command will guide you through the restore process.
+16 -7
View File
@@ -21,25 +21,34 @@ Creating a Bot account is a pretty straightforward process.
.. image:: /.resources/bot-guide/discord_create_app_button.png
:alt: The new application button.
4. Give the application a name and click "Create".
4. Give the application a name, check the box to accept the Terms of Service, and click "Create".
.. image:: /.resources/bot-guide/discord_create_app_form.png
:alt: The new application form filled in.
5. Create a Bot User by navigating to the "Bot" tab and clicking "Add Bot".
5. Navigate to the "Install" tab on the left side of the screen.
- Click "Yes, do it!" to continue.
.. image:: /.resources/bot-guide/discord_installation_tab.png
:alt: The installation tab in the application page.
.. image:: /.resources/bot-guide/discord_create_bot_user.png
:alt: The Add Bot button.
6. If you want others to be able to invite your bot tick the **Public Bot**. Keeping it unticked will prevent others from inviting your bot to their servers and only you will be able to add the bot to servers (provided that you have needed permissions in the server you want to add the bot to).
6. Uncheck "User Install" and set "Install Link" to "None"
.. image:: /.resources/bot-guide/discord_installation_options.png
:alt: How the Installation options should look like for most people.
7. Navigate to the "Bot" tab on the left side of the screen.
.. image:: /.resources/bot-guide/discord_bot_tab.png
:alt: The bot tab in the application page.
8. If you want others to be able to invite your bot tick the **Public Bot**. Keeping it unticked will prevent others from inviting your bot to their servers and only you will be able to add the bot to servers (provided that you have needed permissions in the server you want to add the bot to).
- Make sure **Require OAuth2 Code Grant** is unchecked.
.. image:: /.resources/bot-guide/discord_bot_user_options.png
:alt: How the Bot User options should look like for most people.
7. Copy the token using the "Copy" button.
9. Acquire the token using the "Reset Token" button, then "Copy" after it is revealed.
- **This is not the Client Secret at the General Information page**
+9 -9
View File
@@ -116,18 +116,18 @@ How can I use this playlist link with playlist commands in audio?**
:ref:`setting up Audio for multiple bots<multibots>`. Otherwise, another process is using the
port, so you need to figure out what is using port 2333 and terminate/disconnect it yourself.
**Q: My terminal is saying that I "must install Java 17 or 11 for Lavalink to run". How can I fix this?**
**Q: My terminal is saying that I "must install Java 21 or 17 for Lavalink to run". How can I fix this?**
You are getting this error because you have a different version of Java installed, or you don't have
Java installed at all. As the error states, Java 17 or 11 is required, and can be installed from
`here <https://adoptium.net/temurin/releases/?version=17>`__.
Java installed at all. As the error states, Java 21 or 17 is required, and can be installed from
`here <https://adoptium.net/temurin/releases/?version=21>`__.
If you have Java 17 or 11 installed, and are still getting this error, you will have to manually tell Audio where your Java install is located.
Use ``[p]llset java <path_to_java_17_or_11_executable>``, to make Audio launch Lavalink with a
If you have Java 21 or 17 installed, and are still getting this error, you will have to manually tell Audio where your Java install is located.
Use ``[p]llset java <path_to_java_21_or_17_executable>``, to make Audio launch Lavalink with a
specific Java binary. To do this, you will need to locate your ``java.exe``/``java`` file
in your **Java 17 or 11 install**.
in your **Java 21 or 17 install**.
Alternatively, update your PATH settings so that Java 17 or 11 is the one used by ``java``. However,
Alternatively, update your PATH settings so that Java 21 or 17 is the one used by ``java``. However,
you should confirm that nothing other than Red is running on the machine that requires Java.
.. _queue_commands:
@@ -550,7 +550,7 @@ uses OpenJDK 17 in the managed Lavalink configuration. It can be installed by ru
sudo apt install openjdk-17-jre-headless -y
Otherwise, Lavalink works well with most versions of Java 11, 13, 15, 16, 17, and 18. Azul
Otherwise, Lavalink works well with most versions of Java 17 and higher. Azul
Zulu builds are suggested, see `here <https://github.com/lavalink-devs/Lavalink/#requirements>`__ for more information.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -3651,7 +3651,7 @@ This command shouldn't need to be used most of the time,
and is only useful if the host machine has conflicting Java versions.
If changing this make sure that the Java executable you set is supported by Audio.
The current supported versions are Java 17 and 11.
The current supported versions are Java 21 or 17.
**Arguments**
+12 -6
View File
@@ -1829,7 +1829,10 @@ Commands to add servers or channels to the ignore list.
The ignore list will prevent the bot from responding to commands in the configured locations.
.. Note:: Owners and Admins override the ignore list.
.. Note::
- Category ignores are ignored by user-installed commands
- Owners and Admins override the ignore list.
.. _core-command-ignore-channel:
@@ -1850,7 +1853,10 @@ Ignore commands in the channel, thread, or category.
Defaults to the current thread or channel.
.. Note:: Owners, Admins, and those with Manage Channel permissions override ignored channels.
.. Note::
- Category ignores are ignored by user-installed commands
- Owners and Admins override the ignore list.
**Examples:**
@@ -4185,7 +4191,7 @@ slash disablecog
.. code-block:: none
[p]slash disablecog <cog_name>
[p]slash disablecog <cog_names...>
**Description**
@@ -4195,7 +4201,7 @@ This command does NOT sync the enabled commands with Discord, that must be done
with ``[p]slash sync`` for commands to appear in users' clients.
**Arguments:**
- ``<cog_name>`` - The cog to disable commands from. This argument is case sensitive.
- ``<cog_names>`` - The cogs to disable commands from. This argument is case sensitive.
.. _core-command-slash-enable:
@@ -4230,7 +4236,7 @@ slash enablecog
.. code-block:: none
[p]slash enablecog <cog_name>
[p]slash enablecog <cog_names...>
**Description**
@@ -4240,7 +4246,7 @@ This command does NOT sync the enabled commands with Discord, that must be done
with ``[p]slash sync`` for commands to appear in users' clients.
**Arguments:**
- ``<cog_name>`` - The cog to enable commands from. This argument is case sensitive.
- ``<cog_names>`` - The cogs to enable commands from. This argument is case sensitive.
.. _core-command-slash-list:
+105 -1
View File
@@ -253,7 +253,23 @@ modset dm
.. code-block:: none
[p]modset dm [enabled]
[p]modset dm
**Description**
Settings for messaging the user when being kicked or banned.
.. _mod-command-modset-dm-sendmessage:
"""""""""""""""""""""
modset dm sendmessage
"""""""""""""""""""""
**Syntax**
.. code-block:: none
[p]modset dm sendmessage [enabled]
**Description**
@@ -266,6 +282,94 @@ and reason as to why they were kicked/banned.
* ``[enabled]``: Whether a message should be sent to a user when they are kicked/banned. |bool-input|
.. _mod-command-modset-banshowextrafield:
"""""""""""""""""""""""""""
modset dm banshowextrafield
"""""""""""""""""""""""""""
**Syntax**
.. code-block:: none
[p]modset dm banshowextrafield [enabled]
**Description**
Toggle whether to show an extra customizable field when banning.
This can be used to add additional information for the banned user, such as a ban appeal link.
**Arguments**
* ``[enabled]``: If an extra customizable embed field should appear when banning. |bool-input|
.. _mod-command-modset-banextrafieldtitle:
""""""""""""""""""""""""""""
modset dm banextrafieldtitle
""""""""""""""""""""""""""""
**Syntax**
.. code-block:: none
[p]modset dm banextrafieldtitle [title]
**Description**
Set the title for the optional extra embed on ban.
Cannot be over 252 characters long.
**Arguments**
* ``[title]``: The title of the embed field. Can by any string of text under 252 charcters long.
.. _mod-command-modset-banextrafieldcontents:
"""""""""""""""""""""""""""""""
modset dm banextrafieldcontents
"""""""""""""""""""""""""""""""
**Syntax**
.. code-block:: none
[p]modset dm banextrafieldcontents [contents]
**Description**
Set the contents for the optional extra embed on ban
Cannot be over 1024 characters long.
**Arguments**
* ``[contents]``: The contents of the embed field. Can by any string of text under 1024 charcters long.
.. _mod-command-modset-requirereason:
""""""""""""""""""""
modset requirereason
""""""""""""""""""""
**Syntax**
.. code-block:: none
[p]modset requirereason [enabled]
**Description**
Toggle whether a reason is required for mod actions.
If this is enabled, the bot will require a reason to be provided for all mod actions.
**Arguments**
* ``[enabled]``: Whether a reason should be required when performing mod actions. |bool-input|
.. _mod-command-modset-hierarchy:
""""""""""""""""
+1 -1
View File
@@ -324,7 +324,7 @@ Explains how to set the Twitch token.
To set the Twitch API tokens, follow these steps:
1. Go to this page: https://dev.twitch.tv/dashboard/apps.
1. Go to this page: https://dev.twitch.tv/console/apps.
2. Click Register Your Application.
+21 -1
View File
@@ -291,7 +291,7 @@ warningset showmoderator
**Description**
Decide whether the name of the moderator warning a user should be included in the DM to that user.
Decide whether the name of the moderator warning a user should be included in the DM to that user when being warned or self requesting their warnings.
**Arguments**
@@ -337,6 +337,26 @@ Set the channel where warnings should be sent to.
* ``[channel]``: |channel-input| Leave empty to use the channel ``[p]warn`` command was called in.
.. _warnings-command-warningset-mywarnings-sendtodms:
"""""""""""""""""""""""""""""""
warningset mywarnings sendtodms
"""""""""""""""""""""""""""""""
**Syntax**
.. code-block:: none
[p]warningset mywarnings sendtodms <true_or_false>
**Description**
Whether a member self requesting their warnings with ``[p]mywarnings`` should get them sent to DMs or in the current channel.
**Arguments**
* ``<true_or_false>``: |bool-input|
.. _warnings-command-warnreason:
^^^^^^^^^^
+14
View File
@@ -44,8 +44,11 @@ extensions = [
"sphinx.ext.napoleon",
"sphinx.ext.doctest",
"sphinxcontrib_trio",
"sphinx_markdown_builder",
"sphinx-prompt",
"changelog_contributors",
"deprecated_removed",
"prompt_builder",
]
# Add any paths that contain templates here, relative to this directory.
@@ -139,6 +142,9 @@ html_context = {
"github_user": "Cog-Creators",
"github_repo": "Red-DiscordBot",
"github_version": "V3/develop",
"version_slug": os.environ.get("READTHEDOCS_VERSION", ""),
"rtd_language": os.environ.get("READTHEDOCS_LANGUAGE", ""),
"READTHEDOCS": os.environ.get("READTHEDOCS", "") == "True",
}
# Add any paths that contain custom static files (such as style sheets) here,
@@ -226,6 +232,14 @@ linkcheck_ignore = [r"https://java.com*", r"https://chocolatey.org*"]
linkcheck_retries = 3
# -- Options for markdown builder ----------------------------------------
markdown_http_base = os.environ.get(
"READTHEDOCS_CANONICAL_URL", "https://docs.discord.red/en/stable"
)
markdown_uri_doc_suffix = ".html"
# -- Options for extensions -----------------------------------------------
if dpy_version_info.releaselevel == "final":
-3
View File
@@ -7,9 +7,6 @@
Bank
====
Bank has now been separated from Economy for V3. New to bank is support for
having a global bank.
***********
Basic Usage
***********
+1 -1
View File
@@ -9,7 +9,7 @@ Bot
Red
^^^
.. autoclass:: Red
.. autoclass:: Red()
:members:
:exclude-members: get_context, get_embed_color
+18 -45
View File
@@ -155,6 +155,22 @@ Here is an example of the :code:`async with` syntax:
blah.append(new_blah)
await ctx.send("The new blah value has been added!")
There is also a :py:meth:`Group.all` method. This will return all the stored data associated
with a specific config group as a :py:class:`dict`. By negating the need to excessively call config,
this method can be particularly useful when multiple values are to be retrieved from the same group.
Here is an example of :py:meth:`Group.all` usage:
.. code-block:: python
@commands.command()
async def getall(self, ctx):
all_global_data = await self.config.all()
await ctx.send("Foobar is {foobar}, foo baz is {foo_baz}".format(
foobar=str(all_global_data["foobar"]),
foo_baz=str(all_global_data["foo"]["baz"])
))
.. important::
@@ -398,7 +414,7 @@ We're responsible pet owners here, so we've also got to have a way to feed our p
# We could accomplish the same thing a slightly different way
await self.config.user(ctx.author).pets.get_attr(pet_name).hunger.set(new_hunger)
await ctx.send("Your pet is now at {}/100 hunger!".format(new_hunger)
await ctx.send("Your pet is now at {}/100 hunger!".format(new_hunger))
Of course, if we're less than responsible pet owners, there are consequences::
@@ -430,49 +446,6 @@ Of course, if we're less than responsible pet owners, there are consequences::
"how poorly it was taken care of."
)
*************
V2 Data Usage
*************
There has been much conversation on how to bring V2 data into V3 and, officially, we recommend that cog developers
make use of the public interface in Config (using the categories as described in these docs) rather than simply
copying and pasting your V2 data into V3. Using Config as recommended will result in a much better experience for
you in the long run and will simplify cog creation and maintenance.
However.
We realize that many of our cog creators have expressed disinterest in writing converters for V2 to V3 style data.
As a result we have opened up config to take standard V2 data and allow cog developers to manipulate it in V3 in
much the same way they would in V2. The following examples will demonstrate how to accomplish this.
.. warning::
By following this method to use V2 data in V3 you may be at risk of data corruption if your cog is used on a bot
with multiple shards. USE AT YOUR OWN RISK.
.. code-block:: python
from redbot.core import Config, commands
class ExampleCog(commands.Cog):
def __init__(self):
self.config = Config.get_conf(self, 1234567890)
self.config.init_custom("V2", 1)
self.data = {}
async def load_data(self):
self.data = await self.config.custom("V2", "V2").all()
async def save_data(self):
await self.config.custom("V2", "V2").set(self.data)
async def setup(bot):
cog = ExampleCog()
await cog.load_data()
await bot.add_cog(cog)
************************************
Best practices and performance notes
************************************
@@ -481,7 +454,7 @@ Config prioritizes being a safe data store without developers needing to
know how end users have configured their bot.
This does come with some performance costs, so keep the following in mind when choosing to
develop using config
develop using config.
* Config use in events should be kept minimal and should only occur
after confirming the event needs to interact with config
+74 -14
View File
@@ -14,36 +14,96 @@ Basic Usage
.. code-block:: python
from redbot.core import commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.i18n import Translator, cog_i18n, set_contextual_locales_from_guild
# The translator should be defined in the module scope, with __file__ as the second parameter
_ = Translator("ExampleCog", __file__)
# This decorator must be used for cog and command docstrings to be translated!
@cog_i18n(_)
class ExampleCog:
"""description"""
class ExampleCog(commands.Cog):
"""Cog description"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def mycom(self, ctx):
"""command description"""
await ctx.send(_("This is a test command"))
"""Command description"""
# Correct way to translate strings:
await ctx.send(_("This is a test command run by {author}!").format(author=ctx.author.display_name))
# !!! Do not do this - String interpolation should happen after translation
await ctx.send(_("This is a test command run by {author}!".format(author=ctx.author.display_name)))
# !!! Do not use f-strings - String interpolation should happen after translation
await ctx.send(_(f"This is a test command run by {ctx.author.display_name}!"))
@commands.Cog.listener()
async def on_message(self, message):
# In non-command locations, you must manually call this method for guild locale settings to apply
await set_contextual_locales_from_guild(self.bot, message.guild)
if message.author.bot:
return
await message.channel.send(_("This is a non command with translation support!"))
--------
Tutorial
--------
After making your cog, generate a :code:`messages.pot` file
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Preparing your cog for translations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We recommend using redgettext - a modified version of pygettext for Red.
You can install redgettext by running :code:`pip install redgettext` in a command prompt.
The first step to adding translations to your cog is to add Red's internationalization framework
to the strings in your cog. The first step is to instantiate an instance of
`redbot.core.i18n.Translator` just after the imports in each file. This object is traditionally
stored in the variable ``_`` to reduce its character count and visual impact on the code. Next,
add the `redbot.core.i18n.cog_i18n` decorator to your cog class. This will allow docstrings of
the class and its commands to be translated. Every user-facing string that is not a docstring
should then be wrapped by the Translator object. If variables are included in a string,
``.format()`` must be used, and should be called after the translation function call. This is
because ``.format()`` within the translation function call and f-strings cause the interpolation
to happen **before** the translation is applied. The translation logic needs to match the template
string to translate it, and will be unable to successfully match after interpolation occurs.
Finally, any non-command portions of your code, including listeners, tasks, and views, should call
`redbot.core.i18n.set_contextual_locales_from_guild` prior to translating any strings, as only
commands are able to implicitly determine which guild's configured locale to use. See the example
above for the exact recommended syntax.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Generating a messages.pot file
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A ``messages.pot`` file is a template for translating all of the strings in your cog. It should
be generated using ``redgettext`` - a modified version of ``pygettext`` for use with Red cogs.
You can install ``redgettext`` by running :code:`pip install redgettext` in your development
environment.
Once you have ``redgettext`` installed, you will now need to run
:code:`python -m redgettext -c [path_to_cog_folder]`
This will generate a ``messages.pot`` file in ``path_to_cog_folder/locales``. This file will
contain all strings to be translated, including docstrings.
To generate the :code:`messages.pot` file, you will now need to run
:code:`python -m redgettext -c [path_to_cog]`
This file will contain all strings to be translated, including
docstrings.
(For advanced usage check :code:`python -m redgettext -h`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Creating language specific translations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can now use a tool like `poedit
<https://poedit.net/>`_ to translate the strings in your messages.pot file.
<https://poedit.net/>`_ to translate the strings in your ``messages.pot`` file.
Alternatively, you can use any text editor to manually create translations. To do this, first
create a copy of the ``messages.pot`` file in the same folder, and name the copy
``LANGUAGE-CODE.po``, where ``LANGUAGE-CODE`` is a five character language code supported by
``[p]set locale``. Open the copy in your text editor of choice. This file contains the strings
in your cog prefixed by ``msgid`` and an empty string for you to apply translations prefixed by
``msgstr``. The original string should be translated to the target language by modifying the
associated ``msgstr``. Any variables within curly braces should **not** be translated to avoid
breaking the code when translations are applied. If keyword arguments were used in ``.format()``
calls, it may be safe to re-order variables if the grammer of the language requires doing so.
-------------
API Reference
@@ -51,4 +111,4 @@ API Reference
.. automodule:: redbot.core.i18n
:members:
:special-members: __call__
:special-members: __call__, __init__
-2
View File
@@ -8,8 +8,6 @@
Mod log
=======
Mod log has now been separated from Mod for V3.
***********
Basic Usage
***********
+1 -1
View File
@@ -9,7 +9,7 @@ RPC
RPC support is included in Red on a `provisional <developer-guarantees-exclusions>` basis.
Backwards incompatible changes (up to and including removal of the RPC) may occur if deemed necessary.
V3 comes default with an internal RPC server that may be used to remotely control the bot in various ways.
Red comes default with an internal RPC server that may be used to remotely control the bot in various ways.
Cogs must register functions to be exposed to RPC clients.
Each of those functions must only take JSON serializable parameters and must return JSON serializable objects.
+7 -9
View File
@@ -3,11 +3,11 @@
.. role:: python(code)
:language: python
========================
Creating cogs for Red V3
========================
=====================
Creating cogs for Red
=====================
This guide serves as a tutorial on creating cogs for Red V3.
This guide serves as a tutorial on creating cogs for Red.
It will cover the basics of setting up a package for your
cog and the basics of setting up the file structure. We will
also point you towards some further resources that may assist
@@ -111,8 +111,8 @@ Make sure that both files are saved.
Testing your cog
----------------
To test your cog, you will need a running instance of V3.
Assuming you installed V3 as outlined above, run :code:`redbot-setup`
To test your cog, you will need a running instance of Red.
Assuming you installed Red as outlined above, run :code:`redbot-setup`
and provide the requested information. Once that's done, run Red
by doing :code:`redbot <instance name> --dev` to start Red.
Complete the initial setup by providing a valid token and setting a
@@ -169,6 +169,4 @@ Becoming an Approved Cog Creator
Additional resources
--------------------
Be sure to check out the :doc:`/guide_migration` for some resources
on developing cogs for V3. This will also cover differences between V2 and V3 for
those who developed cogs for V2.
If you've developed cogs for V2, you might find `incompatible_changes/v2_migration` document helpful.
+3 -3
View File
@@ -1,7 +1,7 @@
.. Publishing cogs for V3
Publishing cogs for Red V3
==========================
Publishing cogs for Red
=======================
Users of Red install 3rd-party cogs using Downloader cog. To make your cog available
to install for others, you will have to create a git repository
@@ -47,7 +47,7 @@ Keys common to both repo and cog info.json (case sensitive)
is installed or a repo is added
.. tip:: You can use the ``[p]`` key in your string to use the prefix
used for installing.
used for installing, and ``[botname]`` to show the bot's username.
- ``short`` (string) - A short description of the cog or repo. For cogs, this info
is displayed when a user executes ``[p]cog list``
+4 -5
View File
@@ -32,12 +32,11 @@ First, we would like to make something clear:
Hosting on a VPS or Dedicated Server
------------------------------------
| You can host Red in a VPS running Linux or Windows. Using a Linux VPS is the
| You can host Red on a VPS running Linux or Windows. Using a Linux VPS is the
recommended option. Dedicated servers also work but are overpowered and cost
ineffective unless one plans to run a very large bot or use their server for
more than just hosting Red. If you have already created an instance, Red can be moved to a different
server for hosting with a backup/restore process. More information and guidance
about this process is available in the `Red Support Server <https://discord.com/invite/red>`_.
server for hosting using the :doc:`backup/restore process </backup_red>`.
.. warning::
Please be aware that a Linux server is controlled through a command line.
@@ -87,7 +86,7 @@ Average Providers
| `OVH <https://us.ovhcloud.com/vps/>`_ is a company focused on providing hosting
and cloud services with locations in Europe, North America and Asia Pacific.
| `Time4VPS <https://www.time4vps.eu/>`_ is a Lithuanian VPS provider mainly focused
| `Time4VPS <https://www.time4vps.com/>`_ is a Lithuanian VPS provider mainly focused
on lower cost.
| `GalaxyGate <https://galaxygate.net/>`_ is a VPS and dedicated server provider
@@ -114,7 +113,7 @@ Average Providers
| `LowEndBox <http://lowendbox.com/>`_ is a website where hosting providers are
discussed and curated, often with lower costs and less known providers.
| `AlphaVps <https://alphavps.com>`_ is a Bulgaria VPS and dedicated server provider
| `AlphaVps <https://alphavps.com>`_ is a Bulgarian VPS and dedicated server provider
with locations in Los Angeles, New York, England, Germany and Bulgaria.
--------------------
+11
View File
@@ -16,6 +16,17 @@ For Developers
Removals
~~~~~~~~
``SimpleMenu.select_menu`` attribute
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated-removed:: 3.5.14 60
The `SimpleMenu.select_menu` attribute has been deprecated.
Any behaviour enabled by the usage of this attribute should no longer be depended on.
If you need this for something and cannot replace it with the other functionality,
create an issue on Red's issue tracker.
Downloader's shared libraries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+1
View File
@@ -10,3 +10,4 @@ Backward incompatible changes
future
3.5
v2_migration
@@ -38,6 +38,49 @@ per-server/member/user/role/channel or global basis. Be sure to check
out :doc:`/framework_config` for the API docs for Config as well as a
tutorial on using Config.
*************
V2 Data Usage
*************
There has been much conversation on how to bring V2 data into V3 and, officially, we recommend that cog developers
make use of the public interface in Config (using the categories as described in these docs) rather than simply
copying and pasting your V2 data into V3. Using Config as recommended will result in a much better experience for
you in the long run and will simplify cog creation and maintenance.
However.
We realize that many of our cog creators have expressed disinterest in writing converters for V2 to V3 style data.
As a result we have opened up config to take standard V2 data and allow cog developers to manipulate it in V3 in
much the same way they would in V2. The following examples will demonstrate how to accomplish this.
.. warning::
By following this method to use V2 data in V3 you may be at risk of data corruption if your cog is used on a bot
with multiple shards. USE AT YOUR OWN RISK.
.. code-block:: python
from redbot.core import Config, commands
class ExampleCog(commands.Cog):
def __init__(self):
self.config = Config.get_conf(self, 1234567890)
self.config.init_custom("V2", 1)
self.data = {}
async def load_data(self):
self.data = await self.config.custom("V2", "V2").all()
async def save_data(self):
await self.config.custom("V2", "V2").set(self.data)
async def setup(bot):
cog = ExampleCog()
await cog.load_data()
await bot.add_cog(cog)
----
Bank
----
+1 -1
View File
@@ -15,6 +15,7 @@ Welcome to Red - Discord Bot's documentation!
install_guides/index
bot_application_guide
update_red
backup_red
about_venv
autostart_windows
autostart_mac
@@ -61,7 +62,6 @@ Welcome to Red - Discord Bot's documentation!
:maxdepth: 2
:caption: Red Development Framework Reference:
guide_migration
guide_cog_creation
guide_slash_and_interactions
guide_publish_cogs
@@ -8,6 +8,7 @@ To install without additional config backend support:
.. prompt:: bash
:prompts: (redenv) $
:modifiers: red-install-guide-install-normal
python -m pip install -U pip wheel
python -m pip install -U Red-DiscordBot
@@ -16,6 +17,7 @@ Or, to install with PostgreSQL support:
.. prompt:: bash
:prompts: (redenv) $
:modifiers: red-install-guide-install-postgres
python -m pip install -U pip wheel
python -m pip install -U "Red-DiscordBot[postgres]"
@@ -29,6 +31,7 @@ After installation, set up your instance with the following command:
.. prompt:: bash
:prompts: (redenv) $
:modifiers: red-install-guide-setup
redbot-setup
@@ -40,6 +43,7 @@ Once done setting up the instance, run the following command to run Red:
.. prompt:: bash
:prompts: (redenv) $
:modifiers: red-install-guide-run
redbot <your instance name>
@@ -6,7 +6,7 @@
Installing the pre-requirements
-------------------------------
Red Hat Enterprise Linux (RHEL) 8.8-8.x and its derivatives have all required packages available in official repositories.
Red Hat Enterprise Linux (RHEL) 8.10 and its derivatives have all required packages available in official repositories.
Install them with dnf:
.. prompt:: bash
@@ -6,7 +6,7 @@
Installing the pre-requirements
-------------------------------
Red Hat Enterprise Linux (RHEL) 9.2-9.x and its derivatives have all required packages available in official repositories.
Red Hat Enterprise Linux (RHEL) 9.4-9.x and its derivatives have all required packages available in official repositories.
Install them with dnf:
.. prompt:: bash
@@ -2,4 +2,4 @@
For safety reasons, DO NOT install Red with a root user. If you are unsure how to create
a new user on Linux, see `DigitalOcean's tutorial: How To Create a New Sudo-enabled User
<https://www.digitalocean.com/community/tutorials/how-to-create-a-new-sudo-enabled-user-on-ubuntu-20-04-quickstart>`_.
<https://www.digitalocean.com/community/tutorials/how-to-create-a-new-sudo-enabled-user-on-ubuntu>`_.
-27
View File
@@ -1,27 +0,0 @@
.. _install-debian-11:
====================================
Installing Red on Debian 11 Bullseye
====================================
.. include:: _includes/supported-arch-x64+aarch64+armv7l.rst
.. include:: _includes/linux-preamble.rst
-------------------------------
Installing the pre-requirements
-------------------------------
Debian 11 "Bullseye" has all required packages available in official repositories. Install them
with apt:
.. prompt:: bash
sudo apt update
sudo apt -y install python3 python3-dev python3-venv git openjdk-17-jre-headless build-essential nano
.. Include common instructions:
.. include:: _includes/create-env-with-venv3.9.rst
.. include:: _includes/install-and-setup-red-unix.rst
+4 -2
View File
@@ -12,12 +12,14 @@ Installing Red on Fedora Linux
Installing the pre-requirements
-------------------------------
Fedora Linux 39 and above has all required packages available in official repositories. Install
Fedora Linux 42 and above has all required packages available in official repositories. Install
them with dnf:
.. prompt:: bash
sudo dnf -y install python3.11 python3.11-devel git java-17-openjdk-headless @development-tools nano
sudo dnf -y install python3.11 python3.11-devel git adoptium-temurin-java-repository @development-tools nano
sudo dnf config-manager setopt adoptium-temurin-java-repository.enabled=1
sudo dnf -y install temurin-17-jre
.. Include common instructions:
-3
View File
@@ -18,20 +18,17 @@ we recommend **Ubuntu 24.04 LTS**.
amazon-linux-2023
arch
centos-stream-9
debian-11
debian-12
fedora
opensuse-leap-15
opensuse-tumbleweed
oracle-linux-8
oracle-linux-9
raspberry-pi-os-11
raspberry-pi-os-12
rhel-8
rhel-9
rocky-linux-8
rocky-linux-9
ubuntu-2004
ubuntu-2204
ubuntu-2404
ubuntu-non-lts
+2 -2
View File
@@ -1,7 +1,7 @@
.. _install-opensuse-leap-15:
=====================================
Installing Red on openSUSE Leap 15.5+
Installing Red on openSUSE Leap 15.6+
=====================================
.. include:: _includes/supported-arch-x64+aarch64.rst
@@ -12,7 +12,7 @@ Installing Red on openSUSE Leap 15.5+
Installing the pre-requirements
-------------------------------
openSUSE Leap 15.5+ has all required dependencies available in official repositories. Install them
openSUSE Leap 15.6+ has all required dependencies available in official repositories. Install them
with zypper:
.. prompt:: bash
@@ -1,44 +0,0 @@
.. _install-raspberry-pi-os-11:
======================================================
Installing Red on Raspberry Pi OS (Legacy) 11 Bullseye
======================================================
.. include:: _includes/supported-arch-aarch64+armv7l.rst
.. note::
While we do provide support and install instructions for running Red
on Raspberry Pi OS (Legacy) 11 Bullseye, we highly recommend installing/upgrading to
the new version - Raspberry Pi OS 12 Bookworm.
If you're not sure what version you are using,
you can check your version of Raspberry Pi OS by running:
.. prompt:: bash
lsb_release -a
If you're running Bookworm already, read `install-raspberry-pi-os-12` document instead.
If you're using Bullseye, please consider performing a clean install of Bookworm if possible.
.. include:: _includes/linux-preamble.rst
-------------------------------
Installing the pre-requirements
-------------------------------
Raspberry Pi OS "Bullseye" has all required packages available in official repositories. Install them
with apt:
.. prompt:: bash
sudo apt update
sudo apt -y install python3 python3-dev python3-venv git openjdk-17-jre-headless build-essential nano
.. Include common instructions:
.. include:: _includes/create-env-with-venv3.9.rst
.. include:: _includes/install-and-setup-red-unix.rst
+3 -3
View File
@@ -1,8 +1,8 @@
.. _install-raspberry-pi-os-12:
=============================================
Installing Red on Raspberry Pi OS 12 Bookworm
=============================================
======================================================
Installing Red on Raspberry Pi OS (Legacy) 12 Bookworm
======================================================
.. include:: _includes/supported-arch-aarch64+armv7l.rst
+3 -3
View File
@@ -1,7 +1,7 @@
.. _install-rhel-8:
=========================================================
Installing Red on Red Hat Enterprise Linux (RHEL) 8.8-8.x
=========================================================
======================================================
Installing Red on Red Hat Enterprise Linux (RHEL) 8.10
======================================================
.. include:: _includes/install-guide-rhel8-derivatives.rst
+1 -1
View File
@@ -1,7 +1,7 @@
.. _install-rhel-9:
=========================================================
Installing Red on Red Hat Enterprise Linux (RHEL) 9.2-9.x
Installing Red on Red Hat Enterprise Linux (RHEL) 9.4-9.x
=========================================================
.. include:: _includes/install-guide-rhel9-derivatives.rst
-27
View File
@@ -1,27 +0,0 @@
.. _install-ubuntu-2004:
==================================
Installing Red on Ubuntu 20.04 LTS
==================================
.. include:: _includes/supported-arch-x64+aarch64.rst
.. include:: _includes/linux-preamble.rst
-------------------------------
Installing the pre-requirements
-------------------------------
Ubuntu 20.04 LTS has all required packages available in official repositories. Install them
with apt:
.. prompt:: bash
sudo apt update
sudo apt -y install python3.9 python3.9-dev python3.9-venv git openjdk-17-jre-headless build-essential nano
.. Include common instructions:
.. include:: _includes/create-env-with-venv3.9.rst
.. include:: _includes/install-and-setup-red-unix.rst
+5 -18
View File
@@ -4,23 +4,10 @@
Installing Red on Ubuntu non-LTS versions
=========================================
.. include:: _includes/supported-arch-x64+aarch64.rst
Latest Ubuntu non-LTS version (24.10 at the time of writing) is not supported at current time
due to lack of availability of Python 3.11 or older in its repositories.
.. include:: _includes/linux-preamble.rst
The support should come back once we get back on track with supporting current Python versions.
-------------------------------
Installing the pre-requirements
-------------------------------
Now install the pre-requirements with apt:
.. prompt:: bash
sudo apt update
sudo apt -y install python3.11 python3.11-dev python3.11-venv git openjdk-17-jre-headless build-essential nano
.. Include common instructions:
.. include:: _includes/create-env-with-venv3.11.rst
.. include:: _includes/install-and-setup-red-unix.rst
We recommend usage of latest Ubuntu **LTS** versions instead, you can find
`an install guide for Ubuntu 24.04 <ubuntu-2404>` in our docs.
+4
View File
@@ -133,6 +133,7 @@ Run **one** of the following set of commands, depending on what extras you want
.. prompt:: batch
:prompts: (redenv) C:\\>
:modifiers: red-install-guide-install-normal
python -m pip install -U pip wheel
python -m pip install -U Red-DiscordBot
@@ -141,6 +142,7 @@ Run **one** of the following set of commands, depending on what extras you want
.. prompt:: batch
:prompts: (redenv) C:\\>
:modifiers: red-install-guide-install-postgres
python -m pip install -U pip wheel
python -m pip install -U Red-DiscordBot[postgres]
@@ -153,6 +155,7 @@ After installation, set up your instance with the following command:
.. prompt:: batch
:prompts: (redenv) C:\\>
:modifiers: red-install-guide-setup
redbot-setup
@@ -164,6 +167,7 @@ Once done setting up the instance, run the following command to run Red:
.. prompt:: batch
:prompts: (redenv) C:\\>
:modifiers: red-install-guide-run
redbot <your instance name>
+42 -57
View File
@@ -8,40 +8,20 @@ About (privileged) intents and public bots
==========================================
This page aims to explain Red's current intents requirements,
our stance regarding "public bots" and the impact of some announced
Discord changes coming in April 2022.
our stance regarding "public bots", and the discord bot verification process.
To clarify:
- **Small bots** are bots under 100 servers. They currently do not need to undergo Discord's
bot verification process
- **Public bots** (or big bots) are bots that have reached 100 servers. They need to be
`verified <https://support.discord.com/hc/en-us/articles/360040720412-Bot-Verification-and-Data-Whitelisting>`_
`verified <https://support-dev.discord.com/hc/en-us/articles/23926564536471-How-Do-I-Get-My-App-Verified>`_
by Discord to join more than 100 servers and gain privileged intents
.. warning::
It is **very** important that you fully read this page if you're the owner of a public bot or strive to scale your bot at that level.
.. _intents-intents:
-------
Intents
-------
Red currently requires **all intents** to be active in order to function properly.
The reason for this requirement is that there are some technical challenges that need
to be overcome before we're able to adapt Red to function with only *some* intents:
these challenges are mainly due to the modular / extensible nature of Red and the fact
that Red has a long history (dating back to 2016!), making big changes naturally slower
to happen. In comparison, intents have been introduced fairly recently. |br|
This is not a problem if you have a small bot: you can simply go to the
`Discord development portal <https://discord.com/developers/applications/me>`_
and enable them. However, if you have a public bot Discord will want you to attain
verified status: you should read :ref:`our stance regarding public bots <intents-public-bots>`
and our guidelines for the :ref:`verification process <intents-bot-verification-process>`.
.. _intents-public-bots:
-----------
@@ -54,8 +34,10 @@ Red was designed with one single goal in mind: a bot that you can host on your o
and customize to your needs, making it really *your* bot. **The target audience of Red are server
owners with a few servers**, often with specific needs that can be covered by the vast cog ecosystem
that the community has built over the years. |br| Red was never built with big bots in mind,
bots with thousands upon thousands of servers: these bots face unique challenges.
Such Red instances *do exist*, it is not impossible to adapt Red and meet those criteria,
bots with thousands upon thousands of servers: these bots face unique challenges. Large bots need
to be extremely efficient to handle the large amount of requests they receive, and often need to
distribute this work across multiple processes or machines to keep up.
Such Red instances *do exist*, and it is not impossible to adapt Red and meet those criteria,
but it requires work and bot owners with the technical knowledge to make it happen.
It is **not** something that we support. |br|
When your bot reaches the public bot scale and it is therefore required to be verified it
@@ -75,8 +57,8 @@ the verification process.
Regardless of our stance, we do feel the need to give some pointers: many bot owners reach this point
and become fairly lost, as they've simply been *users* so far.
They have installed their bot, some cogs, personalized it, yadda yadda. Again, they have been users,
not developers. Unless they also have an interest in development, they will likely not have a clue about
They have installed their bot, some cogs, personalized it, but have not needed to write any code.
Unless they also have an interest in development, they will likely not have a clue about
what's going under the hood, much like you're not expected to be a mechanic to drive your car. And there's
nothing wrong with that! Red has been designed to be as user friendly as possible. |br|
The problem is this: Red is an outlier. Discord has built the bot verification process with the expectation
@@ -94,41 +76,44 @@ out your application:
of people that in their naivety went with the bad answer and it seems that at this point merely mentioning Red
is a guaranteed way to have your application rejected.
.. _intents-slash-commands:
.. _intents-intents:
---------------------------------
Message intent and slash commands
---------------------------------
-------
Intents
-------
.. warning::
Red expects **all intents** to be active. It is possible, but not recommended, to disable
specific intents using the ``--disable-intent`` flag. If an intent is missing, you may
experience errors due to Red expecting information provided by the intent to be present.
If you own a public bot it is extremely important that you read this section.
Discord currently considers 3 intents to be
`privileged <https://support-dev.discord.com/hc/en-us/articles/6205754771351-How-do-I-get-Privileged-Intents-for-my-bot>`_,
and requires large bots to additionally apply for access to these intents. **If you have a small
bot**, you can simply follow :ref:`these instructions <enabling-privileged-intents>` to enable them.
Discord has announced that **starting April 2022** the content of users' messages
`will be "locked" behind message intent <https://support-dev.discord.com/hc/en-us/articles/4404772028055>`_ |br|
If you're the owner of a small bot, fear not, this is yet another box that you have to tick from the
`Discord development portal <https://discord.com/developers/applications/me>`_. |br|
But if you're the owner of a public bot, things might be a lot less pleasant.
A breakdown of how privileged intents are used in Red is provided below.
To recap, unless you have
message intent, you will only receive message content for:
The **Message Content** intent is required to use text based commands and inputs for
configuration and all built in functionality. App commands (also known as slash commands)
are limited to a total of 100 top level commands, which is difficult to manage on
a modular bot. The approach we have taken to address this issue is to allow 3rd party
cogs to provide slash commands, but require bot owners to pick which slash commands
they actually want to use with the ``[p]slash`` command.
Under this system, bot management commands that are not exposed to users are still
expected to be provided as text commands, which requires the bot to be able to access
message content. There are no current plans to provide slash versions of core commands.
- Messages that your bot sends
- Messages that your bot receives in DM
- Messages in which your bot is mentioned
.. note::
It is possible to work around this intent by using the ``--mentionable``
flag, and using the bot mention as a prefix to use text based commands.
In case it's not clear by now, your bot needs message content to parse (see) the commands it receives. And if
you don't attain message intent, your bot will not be able to... well, do anything. |br|
The *bandaid fix* is for you to change your bot's prefix to a mention and a good portion of your commands will likely
still work. You will however lose many functions, namely anything that relies on seeing message content to act. |br|
The more *proper fix* is also not easy. You will need to justify your need for the message intent to Discord and
they will only accept "compelling use cases".
`It is not known what those even entail <https://gist.github.com/spiralw/091714718718379b6efcdbcaf807a024#q-what-usecases-will-be-valid>`_ at this point, but they have already stated that "parsing commands" is not a valid justification. |br|
To make the matter worse, Discord is making `a huge push for all bot developers to implement slash commands <https://support.discord.com/hc/en-us/articles/1500000368501-Slash-Commands-FAQ>`_, which at the moment
are rather lacking in features and cannot cover all the functionalities that standard commands offer. |br|
Discord staff
`stated that they will want your bot to have slash commands when you ask for message intent <https://gist.github.com/spiralw/091714718718379b6efcdbcaf807a024#q-if-we-are-granted-this-intent-will-bots-be-sanctioned-if-they-use-it-for-their-own-use-case-but-also-to-continue-to-run-normal-non-slash-commands-or-do-we-assume-that-if-you-are-granted-the-intent-you-are-trusted-with-it-and-are-allowed-to-use-it-for-additional-uses>`_. |br|
Slash commands might very well turn out to be a big undertaking for the Red team to implement, even more now that our
underlying library, `discord.py <https://github.com/Rapptz/discord.py>`_, has been discontinued. |br|
The time window that Discord is giving us to adapt is very narrow: **Red will likely not be able to support slash
commands for April 2022** and you should plan accordingly.
The **Guild Members** intent is required to properly cache member information, including
what users are in each server, what roles they have, what their name is, etc. It is also
required to receive events corresponding to when members join or leave a server, and when
they change their nickname or other server options. Almost all cogs expect to be able
to reference the member cache in order to avoid making API requests, and are not set
up to check if the intent is present before doing so.
The **Guild Presences** intent is required to view the activities and status of
users. Cogs which perform actions on users based on their activity or status will
be unable to access this information if this intent is not enabled.
+48 -5
View File
@@ -25,13 +25,57 @@ Updating differs depending on the version you currently have. Next sections will
:depth: 1
Red 3.5.0 or newer
******************
Red 3.5.25 or newer
*******************
Windows
-------
If you have Red 3.5.0 or newer, you can upgrade by following these steps:
If you have Red 3.5.25 or newer, you can upgrade by following these steps:
#. Shut your bot down.
#. Activate your venv with the following command:
.. prompt:: batch
"%userprofile%\redenv\Scripts\activate.bat"
#. Update Red with this command:
.. prompt:: batch
:prompts: (redenv) C:\\>
redbot-update
#. Start your bot.
Linux & Mac
-----------
If you have Red 3.5.25 or newer, you can upgrade by following these steps:
#. Shut your bot down.
#. Activate your virtual environment.
If you used ``venv`` for your virtual environment, use:
.. prompt:: bash
source ~/redenv/bin/activate
#. Update Red with this command:
.. prompt:: bash
:prompts: (redenv) $
redbot-update
#. Start your bot.
Red 3.5.0-3.5.24
****************
Windows
-------
If you have a Red version between 3.5.0 and 3.5.24, you can upgrade by following these steps:
#. Shut your bot down.
#. Activate your venv with the following command:
@@ -55,7 +99,7 @@ If you have Red 3.5.0 or newer, you can upgrade by following these steps:
Linux & Mac
-----------
If you have Red 3.5.0 or newer, you can upgrade by following these steps:
If you have a Red version between 3.5.0 and 3.5.24, you can upgrade by following these steps:
#. Shut your bot down.
#. Activate your virtual environment.
@@ -108,7 +152,6 @@ If you have a Red version between 3.2.0 and 3.4.19, you can upgrade by following
#. Start your bot with ``--no-cogs --load-cogs downloader`` flags, for example:
.. prompt:: batch
:prompts: (redenv) C:\\>
redbot <your instance name> --no-cogs --load-cogs downloader
+14 -19
View File
@@ -50,37 +50,32 @@ their end-of-life date.
================================ ======================= ============================================================
Operating system version Supported architectures Ideally supported until
================================ ======================= ============================================================
Windows 10 x86-64 2025-10-14 (`End/Retirement Date <https://docs.microsoft.com/en-us/lifecycle/products/windows-10-home-and-pro>`__)
Windows 10 x86-64 2026-10-13 (`End of Consumer Extended Security Updates (ESU) program <https://www.microsoft.com/en-us/windows/extended-security-updates>`__)
Windows 11 x86-64 `Retirement Date <https://docs.microsoft.com/en-us/lifecycle/products/windows-11-home-and-pro-version-21h2>`__
macOS 12 (Monterey) x86-64, aarch64 ~2024-10
macOS 13 (Ventura) x86-64, aarch64 ~2025-10
macOS 14 (Sonoma) x86-64, aarch64 ~2026-10
Alma Linux 8 x86-64, aarch64 2029-05-31 (`How long will CloudLinux support AlmaLinux? <https://wiki.almalinux.org/FAQ.html#how-long-will-almalinux-be-supported>`__)
Alma Linux 9 x86-64, aarch64 2032-05-31
macOS 15 (Sequoia) x86-64, aarch64 ~2027-10
macOS 26 (Tahoe) x86-64, aarch64 ~2028-10
Alma Linux 8 x86-64, aarch64 2029-05-31 (`security support <https://wiki.almalinux.org/release-notes/>`__)
Alma Linux 9 x86-64, aarch64 2032-05-31 (`security support <https://wiki.almalinux.org/release-notes/>`__)
Amazon Linux 2023 x86-64, aarch64 2028-03-15 (`end-of-life <https://docs.aws.amazon.com/linux/al2023/release-notes/support-info-by-support-statement.html#support-info-by-support-statement-eol>`__)
Arch Linux x86-64 forever (support is only provided for an up-to-date system)
CentOS Stream 9 x86-64, aarch64 2027-05-31 (`expected EOL <https://centos.org/stream9/#timeline>`__)
Debian 11 Bullseye x86-64, aarch64, armv7l ~2024-07 (`End of life <https://wiki.debian.org/DebianReleases#Production_Releases>`__)
Debian 12 Bookworm x86-64, aarch64, armv7l ~2026-09 (`End of life <https://wiki.debian.org/DebianReleases#Production_Releases>`__)
Fedora Linux 39 x86-64, aarch64 2024-11-12 (`End of Life <https://docs.fedoraproject.org/en-US/releases/lifecycle/#_maintenance_schedule>`__)
Fedora Linux 40 x86-64, aarch64 2025-05-13 (`End of Life <https://docs.fedoraproject.org/en-US/releases/lifecycle/#_maintenance_schedule>`__)
openSUSE Leap 15.5 x86-64, aarch64 2024-12-31 (`end of maintenance life cycle <https://en.opensuse.org/Lifetime#openSUSE_Leap>`__)
CentOS Stream 9 x86-64, aarch64 2027-05-31 (`Expected EOL <https://centos.org/stream9/#timeline>`__)
Debian 12 Bookworm x86-64, aarch64, armv7l 2026-06-10 (`End of life <https://wiki.debian.org/DebianReleases#Production_Releases>`__)
Fedora Linux 42 x86-64, aarch64 2026-05-13 (`End of Life <https://fedorapeople.org/groups/schedule/f-42/f-42-key-tasks.html>`__)
Fedora Linux 43 x86-64, aarch64 2026-12-09 (`End of Life <https://fedorapeople.org/groups/schedule/f-43/f-43-key-tasks.html>`__)
openSUSE Leap 15.6 x86-64, aarch64 2025-12-31 (`end of maintenance lifecycle <https://en.opensuse.org/Lifetime#openSUSE_Leap>`__)
openSUSE Tumbleweed x86-64, aarch64 forever (support is only provided for an up-to-date system)
Oracle Linux 8 x86-64, aarch64 2029-07-31 (`End of Premier Support <https://www.oracle.com/us/support/library/elsp-lifetime-069338.pdf>`__)
Oracle Linux 9 x86-64, aarch64 2032-06-31 (`End of Premier Support <https://www.oracle.com/us/support/library/elsp-lifetime-069338.pdf>`__)
Raspberry Pi OS (Legacy) 11 armv7l ~2025-10 (approximate date of release of Raspberry Pi OS 13)
Raspberry Pi OS 12 aarch64, armv7l ~2025-10 (approximate date of release of Raspberry Pi OS 13)
Raspberry Pi OS (Legacy) 12 aarch64, armv7l ~2027-10 (approximate date of release of Raspberry Pi OS 14)
RHEL 8 (latest) x86-64, aarch64 2029-05-31 (`End of Maintenance Support <https://access.redhat.com/support/policy/updates/errata#Life_Cycle_Dates>`__)
RHEL 8.8 x86-64, aarch64 2025-05-31 (`End of Extended Update Support <https://access.redhat.com/support/policy/updates/errata#Extended_Update_Support>`__)
RHEL 8.10 x86-64, aarch64 2029-05-31 (`End of Extended Update Support <https://access.redhat.com/support/policy/updates/errata#Extended_Update_Support>`__)
RHEL 9 (latest) x86-64, aarch64 2032-05-31 (`End of Maintenance Support <https://access.redhat.com/support/policy/updates/errata#Life_Cycle_Dates>`__)
RHEL 9.2 x86-64, aarch64 2025-05-31 (`End of Extended Update Support <https://access.redhat.com/support/policy/updates/errata#Extended_Update_Support>`__)
RHEL 9.4 x86-64, aarch64 2026-04-30 (`End of Extended Update Support <https://access.redhat.com/support/policy/updates/errata#Extended_Update_Support>`__)
Rocky Linux 8 x86-64, aarch64 2029-05-31 (`end-of-life <https://rockylinux.org/download/>`__)
Rocky Linux 9 x86-64, aarch64 2032-05-31 (`end-of-life <https://rockylinux.org/download/>`__)
Ubuntu 20.04 LTS x86-64, aarch64 2025-06-30 (`End of Standard Support <https://wiki.ubuntu.com/Releases#Current>`__)
RHEL 9.6 x86-64, aarch64 2027-05-31 (`End of Extended Update Support <https://access.redhat.com/support/policy/updates/errata#Extended_Update_Support>`__)
Rocky Linux 8 x86-64, aarch64 2029-05-31 (`End of Life <https://wiki.rockylinux.org/rocky/version/>`__)
Rocky Linux 9 x86-64, aarch64 2032-05-31 (`End of Life <https://wiki.rockylinux.org/rocky/version/>`__)
Ubuntu 22.04 LTS x86-64, aarch64 2027-06-30 (`End of Standard Support <https://wiki.ubuntu.com/Releases#Current>`__)
Ubuntu 23.10 x86-64, aarch64 2024-07-31 (`End of Standard Support <https://wiki.ubuntu.com/Releases#Current>`__)
Ubuntu 24.04 LTS x86-64, aarch64 2029-06-30 (`End of Standard Support <https://wiki.ubuntu.com/Releases#Current>`__)
================================ ======================= ============================================================
+1 -15
View File
@@ -289,19 +289,6 @@ class VersionInfo:
return version("Red-DiscordBot")
def _update_event_loop_policy():
if _sys.implementation.name == "cpython":
# Let's not force this dependency, uvloop is much faster on cpython
try:
import uvloop
except ImportError:
pass
else:
import asyncio
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
def _ensure_no_colorama():
# a hacky way to ensure that nothing initialises colorama
# if we're not running with legacy Windows command line mode
@@ -334,12 +321,11 @@ def _early_init():
# This function replaces logger so we preferably (though not necessarily) want that to happen
# before importing anything that calls `logging.getLogger()`, i.e. `asyncio`.
_update_logger_class()
_update_event_loop_policy()
_ensure_no_colorama()
# This is bumped automatically by release workflow (`.github/workflows/scripts/bump_version.py`)
_VERSION = "3.5.11"
_VERSION = "3.5.25.dev1"
__version__, version_info = VersionInfo._get_version()
+28 -52
View File
@@ -25,9 +25,9 @@ import rich
import redbot.logging
from redbot import __version__
from redbot.core.bot import Red, ExitCodes, _NoOwnerSet
from redbot.core._cli import interactive_config, confirm, parse_cli_flags
from redbot.core._cli import interactive_config, confirm, parse_cli_flags, new_event_loop
from redbot.setup import get_data_dir, get_name, save_config
from redbot.core import data_manager, _drivers
from redbot.core import data_manager, _drivers, _downloader
from redbot.core._debuginfo import DebugInfo
from redbot.core._sharedlibdeprecation import SharedLibImportWarner
@@ -182,32 +182,10 @@ async def _edit_owner(red, owner, no_prompt):
def _edit_instance_name(old_name, new_name, confirm_overwrite, no_prompt):
if new_name:
name = new_name
if name in _get_instance_names() and not confirm_overwrite:
name = old_name
print(
"An instance with this name already exists.\n"
"If you want to remove the existing instance and replace it with this one,"
" run this command with --overwrite-existing-instance flag."
)
name = get_name(new_name, confirm_overwrite=confirm_overwrite)
elif not no_prompt and confirm("Would you like to change the instance name?", default=False):
name = get_name("")
if name in _get_instance_names():
print(
"WARNING: An instance already exists with this name. "
"Continuing will overwrite the existing instance config."
)
if not confirm(
"Are you absolutely certain you want to continue with this instance name?",
default=False,
):
print("Instance name will remain unchanged.")
name = old_name
else:
print("Instance name updated.")
else:
print("Instance name updated.")
print()
name = get_name(confirm_overwrite=confirm_overwrite)
print("Instance name updated.\n")
else:
name = old_name
return name
@@ -272,7 +250,7 @@ def early_exit_runner(
"""
This one exists to not log all the things like it's a full run of the bot.
"""
loop = asyncio.new_event_loop()
loop = new_event_loop()
asyncio.set_event_loop(loop)
try:
if not cli_flags.instance_name:
@@ -281,7 +259,7 @@ def early_exit_runner(
return
data_manager.load_basic_configuration(cli_flags.instance_name)
red = Red(cli_flags=cli_flags, description="Red V3", dm_help=None)
red = Red(cli_flags=cli_flags)
driver_cls = _drivers.get_driver_class()
loop.run_until_complete(driver_cls.initialize(**data_manager.storage_details()))
loop.run_until_complete(func(red, cli_flags))
@@ -317,19 +295,23 @@ async def run_bot(red: Red, cli_flags: Namespace) -> None:
redbot.logging.init_logging(
level=cli_flags.logging_level,
location=data_manager.core_data_path() / "logs",
cli_flags=cli_flags,
rich_logging=cli_flags.rich_logging,
rich_tracebacks=cli_flags.rich_tracebacks,
rich_traceback_extra_lines=cli_flags.rich_traceback_extra_lines,
rich_traceback_show_locals=cli_flags.rich_traceback_show_locals,
)
log.debug("====Basic Config====")
log.debug("Data Path: %s", data_manager._base_data_path())
log.debug("Storage Type: %s", data_manager.storage_type())
await _downloader._init(red)
# lib folder has to be in sys.path before trying to load any 3rd-party cog (GH-3061)
# We might want to change handling of requirements in Downloader at later date
LIB_PATH = data_manager.cog_data_path(raw_name="Downloader") / "lib"
LIB_PATH.mkdir(parents=True, exist_ok=True)
if str(LIB_PATH) not in sys.path:
sys.path.append(str(LIB_PATH))
lib_path = str(_downloader.LIB_PATH)
if lib_path not in sys.path:
sys.path.append(lib_path)
# "It's important to note that the global `working_set` object is initialized from
# `sys.path` when `pkg_resources` is first imported, but is only updated if you do
@@ -339,7 +321,7 @@ async def run_bot(red: Red, cli_flags: Namespace) -> None:
# Source: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#workingset-objects
pkg_resources = sys.modules.get("pkg_resources")
if pkg_resources is not None:
pkg_resources.working_set.add_entry(str(LIB_PATH))
pkg_resources.working_set.add_entry(lib_path)
sys.meta_path.insert(0, SharedLibImportWarner())
if cli_flags.token:
@@ -422,20 +404,13 @@ def handle_early_exit_flags(cli_flags: Namespace):
sys.exit(ExitCodes.INVALID_CLI_USAGE)
async def shutdown_handler(red, signal_type=None, exit_code=None):
if signal_type:
async def signal_shutdown_handler(red: Red, signal_type: signal.Signals) -> NoReturn:
log.info("%s received. Quitting...", signal_type.name)
# Do not collapse the below line into other logic
# We need to renter this function
# after it interrupts the event loop.
sys.exit(ExitCodes.SHUTDOWN)
elif exit_code is None:
log.info("Shutting down from unhandled exception")
red._shutdown_mode = ExitCodes.CRITICAL
if exit_code is not None:
async def shutdown_handler(red: Red, exit_code: int) -> None:
red._shutdown_mode = exit_code
try:
if not red.is_closed():
await red.close()
@@ -473,7 +448,8 @@ def red_exception_handler(red, red_task: asyncio.Future):
except Exception as exc:
log.critical("The main bot task didn't handle an exception and has crashed", exc_info=exc)
log.warning("Attempting to die as gracefully as possible...")
asyncio.create_task(shutdown_handler(red))
log.info("Shutting down from unhandled exception")
sys.exit(ExitCodes.CRITICAL)
def main():
@@ -484,7 +460,7 @@ def main():
early_exit_runner(cli_flags, edit_instance)
return
try:
loop = asyncio.new_event_loop()
loop = new_event_loop()
asyncio.set_event_loop(loop)
if cli_flags.no_instance:
@@ -499,7 +475,7 @@ def main():
data_manager.load_basic_configuration(cli_flags.instance_name)
red = Red(cli_flags=cli_flags, description="Red V3", dm_help=None)
red = Red(cli_flags=cli_flags)
if os.name != "nt":
# None of this works on windows.
@@ -507,7 +483,7 @@ def main():
signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT)
for s in signals:
loop.add_signal_handler(
s, lambda s=s: asyncio.create_task(shutdown_handler(red, s))
s, lambda s=s: asyncio.create_task(signal_shutdown_handler(red, s))
)
exc_handler = functools.partial(global_exception_handler, red)
@@ -524,7 +500,7 @@ def main():
log.warning("Please do not use Ctrl+C to Shutdown Red! (attempting to die gracefully...)")
log.error("Received KeyboardInterrupt, treating as interrupt")
if red is not None:
loop.run_until_complete(shutdown_handler(red, signal.SIGINT))
loop.run_until_complete(signal_shutdown_handler(red, signal.SIGINT))
except SystemExit as exc:
# We also have to catch this one here. Basically any exception which normally
# Kills the python interpreter (Base Exceptions minus asyncio.cancelled)
@@ -536,11 +512,11 @@ def main():
exit_code_name = "UNKNOWN"
log.info("Shutting down with exit code: %s (%s)", exit_code, exit_code_name)
if red is not None:
loop.run_until_complete(shutdown_handler(red, None, exc.code))
loop.run_until_complete(shutdown_handler(red, exc.code))
except Exception as exc: # Non standard case.
log.exception("Unexpected exception (%s): ", type(exc), exc_info=exc)
if red is not None:
loop.run_until_complete(shutdown_handler(red, None, ExitCodes.CRITICAL))
loop.run_until_complete(shutdown_handler(red, ExitCodes.CRITICAL))
finally:
# Allows transports to close properly, and prevent new ones from being opened.
# Transports may still not be closed correctly on windows, see below
+237
View File
@@ -0,0 +1,237 @@
import asyncio
from pathlib import Path
from typing import Any, Final, Optional, Tuple
import click
from packaging.version import Version
from python_discovery import PythonInfo
from redbot.core._cli import asyncio_run
from . import cmd, common, updater
_CHECK_OTHER_PYTHON_INSTALLS_CMD_ARG_NAME: Final = "--check-other-python-installs"
def _help_major_update_example() -> str:
version = common.get_current_red_version().__replace__(dev=None, local=None)
release = (version.major, version.minor + 1) + (0,) * (len(version.release) - 2)
next_major_version = version.__replace__(release=release)
return f"updating from Red {version} to Red {next_major_version}"
def _help_minor_update_example() -> str:
version = common.get_current_red_version().__replace__(dev=None, local=None)
release = (version.major, version.minor, version.micro + 1) + (0,) * (len(version.release) - 3)
next_minor_version = version.__replace__(release=release)
return f"updating from Red {version} to Red {next_minor_version}"
class _PythonInfoParamType(click.ParamType):
name = "Python interpreter"
def convert(
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
) -> PythonInfo:
if isinstance(value, PythonInfo):
return value
try:
return PythonInfo.from_exe(value)
except RuntimeError:
self.fail(f"{value!r} is not a valid Python executable.", param, ctx)
@click.group(invoke_without_command=True)
# command-specific options
@click.option(
"--include-instance",
"included_instances",
multiple=True,
type=click.Choice(common.INSTANCE_LIST),
help="The list of instances to backup and check cog compatibility for. If not specified,"
" all instances that use the current virtual environment will be backed up and checked.",
)
@click.option(
"--exclude-instance",
"excluded_instances",
multiple=True,
type=click.Choice(common.INSTANCE_LIST),
help="Exclude an instance from the list of instances to backup"
" and check cog compatibility for.",
)
@click.option(
"--backup-dir",
default=None,
type=click.Path(
dir_okay=True, file_okay=False, resolve_path=True, writable=True, path_type=Path
),
help="The directory to place the backups of the virtual environment and instances.",
)
@click.option(
"--no-backup",
help="Do not make backups of the virtual environment and instances before update.",
is_flag=True,
)
@click.option(
"--red-version",
"--version",
type=common.VersionParamType(),
default=None,
help="Version of Red to update to instead of the latest.",
)
@click.option(
"--no-major-updates",
help=f"Skip major updates. For example: {_help_major_update_example()} is a major update"
f" but {_help_minor_update_example()} isn't.",
is_flag=True,
)
@click.option(
"--no-full-changelog",
help='Skip showing full changelog in a terminal user interface. The "Read before updating"'
" sections will still be printed.",
is_flag=True,
)
@click.option(
"--no-cog-compatibility-check",
help="Skip performing cog compatibility check before the update.",
is_flag=True,
)
@click.option(
"--new-python-interpreter",
type=_PythonInfoParamType(),
help="The new Python interpreter that should be used when creating a virtual environment"
" for Red. This can either be a path to a Python executable or a name of a Python executable"
" on the PATH.",
)
@click.option(
"--update-cogs/--no-update-cogs",
default=None,
help="When this option is used, it determines whether the cogs should be updated after Red"
" is updated. By default, you'll be asked, if you want to update.\n"
"In non-interactive mode, cogs will be updated unless this option is used to override"
" the default behavior.",
)
@click.option(
# `pip install` having an option with the same name is coincidental,
# this does not call `pip install` with the `--force-reinstall` option.
# Not that there would be any point in doing so - we create a fresh virtual environment.
"--force-reinstall",
type=bool,
is_flag=True,
help="Force the update process to proceed even, if there is no new version detected."
" This will essentially reinstall latest Red version into a fresh virtual environment. You can"
" combine it with the --new-python-interpreter option to change Red's Python interpreter.",
)
@click.option(
"--no-prompt",
"interactive",
type=bool,
is_flag=True,
default=True,
help="Don't ask for user input during the process (non-interactive mode).\n"
"NOTE: If you want to use this to automate Red updates, consider specifying --no-major-update"
" to avoid performing major updates without making an explicit decision to.\n"
"When performing a major update where the current Python interpreter is no longer compatible,"
" the --new-python-interpreter option has to be specified or the command will fail.",
)
# global options
@click.option(
cmd.arg_names.DEBUG,
"--verbose",
"-v",
"logging_level",
count=True,
help=(
"Increase the verbosity of the logs, each usage of this flag increases the verbosity"
" level by 1."
),
)
@click.option(
"--check-other-venvs",
_CHECK_OTHER_PYTHON_INSTALLS_CMD_ARG_NAME,
"ignore_prefix",
help="Check the compatibility of cogs for instances that are normally ran with"
" a different Python installation and/or virtual environment than the current one.",
is_flag=True,
)
@click.pass_context
def cli(
ctx: click.Context,
included_instances: Tuple[str, ...],
excluded_instances: Tuple[str, ...],
backup_dir: Optional[Path],
no_backup: bool,
red_version: Optional[Version],
no_major_updates: bool,
no_full_changelog: bool,
no_cog_compatibility_check: bool,
new_python_interpreter: Optional[PythonInfo],
update_cogs: Optional[bool],
force_reinstall: bool,
interactive: bool,
logging_level: int,
ignore_prefix: bool,
) -> None:
common.ensure_supported_env()
common.configure_logging(logging_level)
ctx.ensure_object(dict)
ctx.obj["IGNORE_PREFIX"] = ignore_prefix
if ctx.invoked_subcommand is None:
if included_instances:
# de-duplicate with order intact
instances = list(dict.fromkeys(included_instances))
else:
instances = list(common.INSTANCE_LIST)
options = updater.UpdaterOptions(
instances=instances,
excluded_instances=set(excluded_instances),
ignore_prefix=ignore_prefix,
backup_dir=backup_dir,
no_backup=no_backup,
red_version=red_version,
no_major_updates=no_major_updates,
no_full_changelog=no_full_changelog,
no_cog_compatibility_check=no_cog_compatibility_check,
new_python_interpreter=new_python_interpreter,
update_cogs=update_cogs,
force_reinstall=force_reinstall,
interactive=interactive,
)
app = updater.Updater(options)
asyncio_run(app.run())
# these should not be available to subcommands
elif included_instances:
raise click.NoSuchOption("--include-instance", ctx=ctx)
elif excluded_instances:
raise click.NoSuchOption("--exclude-instance", ctx=ctx)
elif backup_dir is not None:
raise click.NoSuchOption("--backup-dir", ctx=ctx)
elif no_backup:
raise click.NoSuchOption("--no-backup", ctx=ctx)
elif red_version:
raise click.NoSuchOption("--red-version", ctx=ctx)
elif no_major_updates:
raise click.NoSuchOption("--no-major-updates", ctx=ctx)
elif no_cog_compatibility_check:
raise click.NoSuchOption("--no-cog-compatibility-check", ctx=ctx)
elif new_python_interpreter:
raise click.NoSuchOption("--new-python-interpreter", ctx=ctx)
elif update_cogs is True:
raise click.NoSuchOption("--update-cogs", ctx=ctx)
elif update_cogs is False:
raise click.NoSuchOption("--no-update-cogs", ctx=ctx)
elif not interactive:
raise click.NoSuchOption("--no-prompt", ctx=ctx)
elif force_reinstall:
raise click.NoSuchOption("--force-reinstall", ctx=ctx)
cli.add_command(cmd.cog_compatibility.check_cog_compatibility)
if __name__ == "__main__":
cli()
+144
View File
@@ -0,0 +1,144 @@
import dataclasses
import datetime
import functools
import os
import re
from typing import Any, Dict, List
import aiohttp
import yarl
from packaging.version import Version
from typing_extensions import Self
_CHANGELOG_PATTERN = re.compile(
r"\n<!--+ +RED-CHANGELOG-BEGIN: (?P<version>.+) +--+>\n"
r"(?P<content>[\s\S]+?)"
r"\n<!--+ +RED-CHANGELOG-END +--+>"
)
_RTD_CANONICAL_URL = os.getenv("_RED_RTD_CANONICAL_URL") or "https://docs.discord.red/en/stable/"
@dataclasses.dataclass
class VersionChangelog:
version: Version
content: str
_RELEASE_DATE_PATTERN = re.compile(
r"^<!--+ +RED-CHANGELOG-RELEASE-DATE: (\d{4})-(\d{2})-(\d{2}) +--+>$",
re.MULTILINE,
)
_CONTRIBUTORS_PATTERN = re.compile(
r"^<!--+ +RED-CHANGELOG-CONTRIBUTORS: (?P<contributors>.+) +--+>$",
re.MULTILINE,
)
_READ_BEFORE_UPDATING_SECTION_PATTERN = re.compile(
r"\n<!--+ +RED-CHANGELOG-READ-BEFORE-UPDATE-BEGIN +--+>\n"
r"(?P<content>[\s\S]+?)"
r"\n<!--+ +RED-CHANGELOG-READ-BEFORE-UPDATE-END +--+>"
)
_USER_CHANGELOG_SECTION_PATTERN = re.compile(
r"\n<!--+ +RED-CHANGELOG-USER-CHANGELOG-BEGIN +--+>\n"
r"(?P<content>[\s\S]+?)"
r"\n<!--+ +RED-CHANGELOG-USER-CHANGELOG-END +--+>"
)
@classmethod
def from_json_dict(cls, data: Dict[str, Any]) -> Self:
return cls(version=Version(data["version"]), content=data["content"])
def to_json_dict(self) -> Dict[str, Any]:
return {"version": str(self.version), "content": self.content}
@functools.cached_property
def release_date(self) -> datetime.date:
return datetime.date(*map(int, self._RELEASE_DATE_PATTERN.search(self.content).groups()))
@functools.cached_property
def contributors(self) -> List[str]:
match = self._CONTRIBUTORS_PATTERN.search(self.content)
if match is None:
return []
return match["contributors"].split()
@functools.cached_property
def read_before_updating_section(self) -> str:
return "\n".join(
match["content"].strip()
for match in self._READ_BEFORE_UPDATING_SECTION_PATTERN.finditer(self.content)
)
@functools.cached_property
def user_changelog_section(self) -> str:
return "\n".join(
match["content"].strip()
for match in self._USER_CHANGELOG_SECTION_PATTERN.finditer(self.content)
)
Changelogs = Dict[Version, VersionChangelog]
def parse_changelogs(content: str) -> Changelogs:
changelogs = {}
for match in _CHANGELOG_PATTERN.finditer(content):
changelog = VersionChangelog(Version(match["version"]), match["content"])
changelogs[changelog.version] = changelog
return changelogs
def render_markdown(changelogs: Changelogs, *, minimal: bool = False) -> str:
if not changelogs:
return ""
parts = ["# Read before updating"]
for changelog in reversed(changelogs.values()):
parts.append(f"## {changelog.version}")
parts.append(changelog.read_before_updating_section)
contributors = sorted(
{
contributor
for changelog in changelogs.values()
for contributor in changelog.contributors
}
)
if contributors:
contributor_thanks = (
" \n**The releases below were made with help from the following people:** \n"
)
contributor_thanks += ", ".join(
f"[@{contributor}](https://github.com/sponsors/{contributor})"
for contributor in contributors
)
contributor_thanks += " \n**Thank you** \N{HEAVY BLACK HEART}\N{VARIATION SELECTOR-16}"
parts.append(contributor_thanks)
# show the header both at the top and the bottom
parts.append(parts[0])
return "\n".join(parts)
def get_changelogs_between(
changelogs: Changelogs, newer_than: Version, not_newer_than: Version
) -> Changelogs:
return {
changelog_version: changelog
for changelog_version, changelog in changelogs.items()
if newer_than < changelog_version <= not_newer_than
}
async def fetch_changelogs() -> Changelogs:
"""
Fetch the Markdown-formatted changelog from Red's docs site.
Returns
-------
Dict[Version, VersionChangelog]
A dict mapping versions to their changelogs. Sorted by version, newest first.
"""
async with aiohttp.ClientSession(raise_for_status=True) as session:
async with session.get(yarl.URL(_RTD_CANONICAL_URL) / "_markdown/changelog.md") as resp:
return parse_changelogs(await resp.text())
+6
View File
@@ -0,0 +1,6 @@
from . import arg_names, cog_compatibility
__all__ = (
"arg_names",
"cog_compatibility",
)
+6
View File
@@ -0,0 +1,6 @@
from typing import Final
DEBUG: Final = "--debug"
RED_VERSION: Final = "--red-version"
PYTHON_VERSION: Final = "--python-version"
CHECK_OTHER_PYTHON_INSTALLS: Final = "--check-other-python-installs"
+215
View File
@@ -0,0 +1,215 @@
import asyncio
import json
import os
import sys
import tempfile
from typing import Final, Optional, Tuple
import click
from packaging.version import Version
from rich.text import Text
from redbot._update import cog_compatibility_checker, common
from redbot._update.cog_compatibility_checker import CompatibilitySummary
from redbot.core import _drivers
from redbot.core._cli import asyncio_run
from redbot.core.utils._internal_utils import fetch_latest_red_version
from . import arg_names
EXIT_INSTANCE_SITE_PREFIX_MISMATCH: Final = 4
EXIT_INSTANCE_BACKEND_UNSUPPORTED: Final = 5
CMD_NAME: Final = "check-cog-compatibility"
_COMPATIBILITY_RESULTS_ENV_VAR = "_RED_UPDATE_COMPATIBILITY_RESULTS_FILE"
@click.command(CMD_NAME)
@click.argument(
"instances",
nargs=-1,
type=click.Choice(common.INSTANCE_LIST),
default=None,
metavar="[INSTANCE_NAME]",
)
@click.option(
arg_names.RED_VERSION,
type=common.VersionParamType(),
default=None,
help="The Red version to check cog compatibility for."
" If not provided, the information about latest available version will be fetched"
" and the command will check whether installed cogs support that version.\n"
"If this option is provided, --python-version also has to be provided.",
)
@click.option(
arg_names.PYTHON_VERSION,
type=common.VersionParamType(),
default=None,
help="The Python version to check cog compatibility for."
" If not provided, the command will either use the current interpreter's version or,"
" if that version is not compatible with the latest Red version, it will try to"
" find the latest available CPython interpreter on the system and will check whether"
" installed cogs support it.\n"
"If this option is provided, --red-version also has to be provided.",
)
@click.pass_context
def check_cog_compatibility(
ctx: click.Context,
instances: Tuple[str, ...],
red_version: Optional[Version],
python_version: Optional[Version],
) -> None:
"""
Check if the installed cogs are compatible with the given version.
"""
if (red_version, python_version).count(None) == 1:
raise click.BadParameter(
"Either both --red-version and --python-version options"
" have to be specified or neither.",
param_hint=[arg_names.RED_VERSION, arg_names.PYTHON_VERSION],
)
asyncio_run(
_check_cog_compatibility_command_impl(
red_version=red_version,
python_version=python_version,
instances=instances,
ignore_prefix=ctx.obj["IGNORE_PREFIX"],
)
)
async def _check_cog_compatibility_command_impl(
*,
red_version: Optional[Version],
python_version: Optional[Version],
instances: Tuple[str, ...] = (),
ignore_prefix: bool = False,
) -> None:
console = common.get_console()
if red_version is None or python_version is None:
with console.status("Checking latest version..."):
latest = await fetch_latest_red_version()
red_version = latest.version
python_version = Version(".".join(map(str, sys.version_info[:3])))
if python_version not in latest.requires_python:
interpreters = common.search_for_interpreters(latest.requires_python)
_, python_version, _ = interpreters[0]
if len(instances) == 1:
results_file = os.getenv(_COMPATIBILITY_RESULTS_ENV_VAR, "")
try:
results = await cog_compatibility_checker.check_instance(
instances[0],
latest_version=red_version,
interpreter_version=python_version,
ignore_prefix=ignore_prefix,
)
except _drivers.MissingExtraRequirements:
if not results_file:
common.print_with_prefix_column(
common.ICON_ERROR,
Text(instances[0], style="bold"),
" instance could not be checked as it uses a storage backend"
" that is not supported by the current Red installation"
" (some requirements are missing).",
)
raise SystemExit(EXIT_INSTANCE_BACKEND_UNSUPPORTED)
except cog_compatibility_checker.InstanceSitePrefixMismatchError as exc:
if not results_file:
common.print_with_prefix_column(
common.ICON_ERROR,
Text(exc.instance_name, style="bold"),
" instance could not be checked as it is a part of"
" a different Python installation and/or virtual environment.",
)
raise SystemExit(EXIT_INSTANCE_SITE_PREFIX_MISMATCH)
if results_file:
with open(results_file, "w", encoding="utf-8") as fp:
json.dump(results.to_json_dict(), fp)
return
if not instances:
instances = tuple(common.INSTANCE_LIST)
checked_instances = []
for instance_name in instances:
exit_code, _, _ = await call(
instance_name,
red_version=red_version,
python_version=python_version,
ignore_prefix=ignore_prefix,
)
if exit_code != EXIT_INSTANCE_SITE_PREFIX_MISMATCH:
if exit_code:
raise SystemExit(exit_code)
checked_instances.append(instance_name)
if not checked_instances:
common.print_with_prefix_column(
common.ICON_ERROR, "There were no instances to check cog compatibility for."
)
raise SystemExit(1)
async def call(
instance_name: str,
*,
red_version: Version,
python_version: Version,
ignore_prefix: bool = False,
return_results: bool = False,
stdout: Optional[int] = None,
) -> Tuple[int, Optional[str], Optional[CompatibilitySummary]]:
debug_args = (arg_names.DEBUG,) * common.get_log_cli_level()
args = [
"-m",
"redbot._update",
*debug_args,
CMD_NAME,
instance_name,
arg_names.RED_VERSION,
str(red_version),
arg_names.PYTHON_VERSION,
str(python_version),
]
if ignore_prefix:
args.append(arg_names.CHECK_OTHER_PYTHON_INSTALLS)
env = os.environ.copy()
# terminal woes
console = common.get_console()
if console.is_terminal:
env["TTY_COMPATIBLE"] = "1"
# Rich only checks stdout for Windows console features:
# https://github.com/Textualize/rich/blob/fc41075a3206d2a5fd846c6f41c4d2becab814fa/rich/_windows.py#L46
env[common.INTERNAL_LEGACY_WINDOWS_ENV_VAR] = "1" if console.legacy_windows else "0"
else:
# Rich does not set legacy_windows correctly when is_terminal is False
# https://github.com/Textualize/rich/issues/3647
env[common.INTERNAL_LEGACY_WINDOWS_ENV_VAR] = "0"
env["PYTHONIOENCODING"] = sys.stdout.encoding
results = None
results_file = None
if return_results:
results_file = tempfile.NamedTemporaryFile(delete=False)
try:
if results_file is not None:
results_file.close()
env[_COMPATIBILITY_RESULTS_ENV_VAR] = str(results_file.name)
proc = await asyncio.create_subprocess_exec(sys.executable, *args, env=env, stdout=stdout)
stdout_data, _ = await proc.communicate()
decoded_stdout = None
if stdout_data is not None:
decoded_stdout = stdout_data.decode()
exit_code = await proc.wait()
if not exit_code and results_file is not None:
with open(results_file.name, encoding="utf-8") as fp:
results = CompatibilitySummary.from_json_dict(json.load(fp))
finally:
if results_file is not None:
os.remove(results_file.name)
return exit_code, decoded_stdout, results
+551
View File
@@ -0,0 +1,551 @@
import dataclasses
import enum
import functools
import itertools
import os
import sys
from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Set, Tuple
import rich
from packaging.version import Version
from rich.text import Text
from typing_extensions import Self
from redbot.core import _downloader, _drivers, data_manager
from redbot.core._cli import parse_cli_flags
from redbot.core.bot import Red
from redbot.core.utils._internal_utils import detailed_progress
from . import common
class InstanceSitePrefixMismatchError(Exception):
"""The instance's last known sys.prefix is different from the current one."""
def __init__(self, instance_name: str, last_known_prefix: Optional[str]) -> None:
self.instance_name = instance_name
self.last_known_prefix = last_known_prefix
super().__init__(
f"The last known sys.prefix of {instance_name!r} is different from"
" current process's sys.prefix.",
)
class SimpleCompatibilityStatus(common.OrderedEnum):
UNSUPPORTED = enum.auto()
POTENTIALLY_SUPPORTED = enum.auto()
EXPLICITLY_SUPPORTED = enum.auto()
class CompatibilityStatus(enum.Enum):
# unsupported is <100, 200)
UNSUPPORTED_PYTHON_VERSION = 100
UNSUPPORTED_BOT_VERSION = 101
# potentially supported is <200, 300)
POTENTIALLY_SUPPORTED = 200
# explicitly supported is <300, 400)
EXPLICITLY_SUPPORTED_NON_BREAKING = 300
EXPLICITLY_SUPPORTED_MIN_BOT_VERSION = 301
EXPLICITLY_SUPPORTED_MAX_BOT_VERSION = 302
EXPLICITLY_SUPPORTED_READY_TAG = 303
@property
def simple_status(self) -> SimpleCompatibilityStatus:
if self.unsupported:
return SimpleCompatibilityStatus.UNSUPPORTED
if self.potentially_supported:
return SimpleCompatibilityStatus.POTENTIALLY_SUPPORTED
if self.explicitly_supported:
return SimpleCompatibilityStatus.EXPLICITLY_SUPPORTED
raise RuntimeError("unreachable")
@property
def unsupported(self) -> bool:
return 100 <= self.value < 200
@property
def potentially_supported(self) -> bool:
return 200 <= self.value < 300
@property
def explicitly_supported(self) -> bool:
return 300 <= self.value < 400
def __ge__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.simple_status >= other.simple_status
return NotImplemented
def __gt__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.simple_status > other.simple_status
return NotImplemented
def __le__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.simple_status <= other.simple_status
return NotImplemented
def __lt__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.simple_status < other.simple_status
return NotImplemented
@dataclasses.dataclass
class CogCompatibilityInfo:
name: str
repo_name: str
min_bot_version: Version
max_bot_version: Version
min_python_version: Version
tags: Tuple[str, ...]
compatibility_status: CompatibilityStatus = CompatibilityStatus.POTENTIALLY_SUPPORTED
@classmethod
def from_installable(cls, installable: _downloader.Installable) -> Self:
return cls(
name=installable.name,
repo_name=installable.repo_name,
min_bot_version=installable.min_bot_version,
max_bot_version=installable.max_bot_version,
min_python_version=installable.min_python_version,
tags=installable.tags,
)
@classmethod
def from_json_dict(cls, data: Dict[str, Any]) -> Self:
return cls(
name=data["name"],
repo_name=data["repo_name"],
min_bot_version=Version(data["min_bot_version"]),
max_bot_version=Version(data["max_bot_version"]),
min_python_version=Version(data["min_python_version"]),
tags=tuple(data["tags"]),
compatibility_status=CompatibilityStatus(data["compatibility_status"]),
)
def to_json_dict(self) -> Dict[str, Any]:
return {
"name": self.name,
"repo_name": self.repo_name,
"min_bot_version": str(self.min_bot_version),
"max_bot_version": str(self.max_bot_version),
"min_python_version": str(self.min_python_version),
"tags": self.tags,
"compatibility_status": self.compatibility_status.value,
}
CogSupportDict = Dict[str, CogCompatibilityInfo]
@dataclasses.dataclass(frozen=True)
class CompatibilityResults(Mapping[str, CogCompatibilityInfo]):
latest_version: Version
interpreter_version: Version
explicitly_supported: CogSupportDict = dataclasses.field(default_factory=dict)
potentially_supported: CogSupportDict = dataclasses.field(default_factory=dict)
incompatible_python_version: CogSupportDict = dataclasses.field(default_factory=dict)
incompatible_bot_version: CogSupportDict = dataclasses.field(default_factory=dict)
@classmethod
def from_json_dict(cls, data: Dict[str, Any]) -> Self:
return cls(
latest_version=Version(data["latest_version"]),
interpreter_version=Version(data["interpreter_version"]),
explicitly_supported={
cog_name: CogCompatibilityInfo.from_json_dict(info_data)
for cog_name, info_data in data["explicitly_supported"].items()
},
potentially_supported={
cog_name: CogCompatibilityInfo.from_json_dict(info_data)
for cog_name, info_data in data["potentially_supported"].items()
},
incompatible_python_version={
cog_name: CogCompatibilityInfo.from_json_dict(info_data)
for cog_name, info_data in data["incompatible_python_version"].items()
},
incompatible_bot_version={
cog_name: CogCompatibilityInfo.from_json_dict(info_data)
for cog_name, info_data in data["incompatible_bot_version"].items()
},
)
def to_json_dict(self) -> Dict[str, Any]:
return {
"latest_version": str(self.latest_version),
"interpreter_version": str(self.interpreter_version),
"explicitly_supported": {
cog_name: info.to_json_dict()
for cog_name, info in self.explicitly_supported.items()
},
"potentially_supported": {
cog_name: info.to_json_dict()
for cog_name, info in self.potentially_supported.items()
},
"incompatible_python_version": {
cog_name: info.to_json_dict()
for cog_name, info in self.incompatible_python_version.items()
},
"incompatible_bot_version": {
cog_name: info.to_json_dict()
for cog_name, info in self.incompatible_bot_version.items()
},
}
def __getitem__(self, key: str) -> CogCompatibilityInfo:
for data in (
self.explicitly_supported,
self.potentially_supported,
self.incompatible_python_version,
self.incompatible_bot_version,
):
try:
return data[key]
except KeyError:
pass
raise KeyError(key)
def __iter__(self) -> Iterator[str]:
return itertools.chain(
self.explicitly_supported.keys(),
self.potentially_supported.keys(),
self.incompatible_python_version.keys(),
self.incompatible_bot_version.keys(),
)
def __len__(self) -> int:
count = 0
for data in (
self.explicitly_supported,
self.potentially_supported,
self.incompatible_python_version,
self.incompatible_bot_version,
):
count += len(data)
return count
def __bool__(self) -> bool:
return any(
(
self.explicitly_supported,
self.potentially_supported,
self.incompatible_python_version,
self.incompatible_bot_version,
)
)
def print(self) -> None:
major_version = Text(f"{self.latest_version.major}.{self.latest_version.minor}")
if self.explicitly_supported:
common.print_with_prefix_column(
common.ICON_SUCCESS,
"The following cogs are explicitly marked as supporting Red ",
major_version,
":\n",
Text(", ").join(Text(cog, style="bold") for cog in self.explicitly_supported),
)
if self.potentially_supported:
common.print_with_prefix_column(
common.ICON_WARN,
"The following cogs may support Red ",
major_version,
" but they haven't been explicitly marked as such:\n",
Text(", ").join(Text(cog, style="bold") for cog in self.potentially_supported),
)
if self.incompatible_bot_version:
common.print_with_prefix_column(
common.ICON_ERROR,
"The following cogs do not support Red ",
Text(str(self.latest_version)),
":\n",
Text(", ").join(Text(cog, style="bold") for cog in self.incompatible_bot_version),
)
if self.incompatible_python_version:
common.print_with_prefix_column(
common.ICON_ERROR,
"The following cogs do not support Python ",
Text(str(self.interpreter_version)),
":\n",
Text(", ").join(
Text(cog, style="bold") for cog in self.incompatible_python_version
),
)
if not self.explicitly_supported and (
self.potentially_supported
or self.incompatible_bot_version
or self.incompatible_python_version
):
common.print_with_prefix_column(
common.ICON_INFO,
"None of the checked cogs were explicitly marked as supporting Red ",
major_version,
".",
)
@dataclasses.dataclass(frozen=True)
class CompatibilitySummary:
instance_name: str
before_update: CompatibilityResults
after_update: CompatibilityResults
@classmethod
def from_json_dict(cls, data: Dict[str, Any]) -> Self:
return cls(
instance_name=data["instance_name"],
before_update=CompatibilityResults.from_json_dict(data["before_update"]),
after_update=CompatibilityResults.from_json_dict(data["after_update"]),
)
def to_json_dict(self) -> Dict[str, Any]:
return {
"instance_name": self.instance_name,
"before_update": self.before_update.to_json_dict(),
"after_update": self.after_update.to_json_dict(),
}
class CogCompatibilityChecker:
def __init__(
self,
bot: Red,
*,
latest_version: Version,
interpreter_version: Version,
ignore_prefix: bool = False,
) -> None:
self.bot = bot
self.latest_version = latest_version
self.interpreter_version = interpreter_version
self.ignore_prefix = ignore_prefix
self._console = common.get_console(stderr=True)
self._stdout_console = common.get_console()
@functools.cached_property
def current_version(self) -> Version:
return common.get_current_red_version()
async def check(self) -> CompatibilitySummary:
instance_name = data_manager.instance_name()
if not self.ignore_prefix:
last_known_prefix = await self.bot._config.last_system_info.python_prefix()
same_install = False
if last_known_prefix is not None:
try:
same_install = os.path.samefile(last_known_prefix, sys.prefix)
except OSError:
pass
if not same_install:
raise InstanceSitePrefixMismatchError(instance_name, last_known_prefix)
common.print_with_prefix_column(
common.ICON_INFO,
"Started checking cog compatibility for the ",
Text(instance_name, style="bold"),
" instance.",
console=self._console,
)
status = Text.assemble(
"Checking compatibility of cogs installed on the ",
(instance_name, "bold"),
" instance...",
)
with self._console.status(status):
await _downloader._init_without_bot(self.bot._cog_mgr)
await self._update_repos()
installed_cogs = await _downloader.installed_cogs()
repo_unknown = []
to_check = set()
for cog in installed_cogs:
if cog.repo is None:
repo_unknown.append(cog)
else:
to_check.add(cog)
with self._console.status("Checking available cog updates..."):
update_check_result = await _downloader.check_cog_updates(
cogs=to_check,
update_repos=False,
env=_downloader.Environment(
red_version=self.latest_version, python_version=self.interpreter_version
),
)
self._console.print("Available cog updates checked.")
summary = CompatibilitySummary(
instance_name=instance_name,
before_update=self._evaluate_before_update_compatibility(to_check),
after_update=self._evaluate_after_update_compatibility(
to_check, update_check_result
),
)
common.print_with_prefix_column(
common.ICON_INFO,
"Finished checking cog compatibility for the ",
Text(instance_name, style="bold"),
" instance.",
console=self._console,
)
self._stdout_console.print()
# Note that when a cog can be updated
# and its up-to-date version does not support the Red version we're updating to,
# we don't check whether currently installed version of the cog supports that Red version.
# This is intentional - we want to allow cog creators to mark something incompatible
# after the fact.
summary.after_update.print()
return summary
async def _update_repos(self) -> None:
with detailed_progress(unit="repos", console=self._console) as progress:
task_id = progress.add_task(
"Updating repos", total=len(_downloader._repo_manager.repos)
)
updated_count = 0
already_up_to_date_count = 0
failed_count = 0
for repo in _downloader._repo_manager.repos:
progress.update(task_id, description=f"Updating {repo.name!r} repo")
try:
old, new = await repo.update()
except _downloader.errors.UpdateError:
common.print_with_prefix_column(
common.ICON_WARN,
"Could not update repo ",
Text(repo.name, style="bold"),
", the results for cogs from it may be inaccurate.",
console=self._console,
)
failed_count += 1
else:
if old != new:
updated_count += 1
self._console.print("Updated repo", Text(repo.name, style="bold"))
else:
already_up_to_date_count += 1
self._console.print(
"Repo", Text(repo.name, style="bold"), "is already up-to-date."
)
progress.advance(task_id)
self._stdout_console.print(
f"Successfully updated {updated_count} repos, failed to update {failed_count} repos.\n"
f"{already_up_to_date_count} repos were already up-to-date.",
highlight=True,
)
def _fill_compatibility_results(
self, results: CompatibilityResults, cogs: Iterable[_downloader.Installable]
) -> None:
latest_version = self.latest_version
interpreter_version = self.interpreter_version
breaking_update = self.current_version.release[:2] != self.latest_version.release[:2]
for cog in cogs:
info = CogCompatibilityInfo.from_installable(cog)
if cog.min_python_version > interpreter_version:
info.compatibility_status = CompatibilityStatus.UNSUPPORTED_PYTHON_VERSION
results.incompatible_python_version[cog.name] = info
elif cog.min_bot_version > latest_version or (
# max version should be ignored when it's lower than min version
cog.min_bot_version <= cog.max_bot_version
and cog.max_bot_version < latest_version
):
info.compatibility_status = CompatibilityStatus.UNSUPPORTED_BOT_VERSION
results.incompatible_bot_version[cog.name] = info
elif not breaking_update:
info.compatibility_status = CompatibilityStatus.EXPLICITLY_SUPPORTED_NON_BREAKING
results.explicitly_supported[cog.name] = info
elif latest_version.release[:2] == cog.min_bot_version.release[:2]:
# If cog creator explicitly set min_bot_version to 3.x.y,
# then 3.x is explicitly supported.
info.compatibility_status = (
CompatibilityStatus.EXPLICITLY_SUPPORTED_MIN_BOT_VERSION
)
results.explicitly_supported[cog.name] = info
elif latest_version.release[:2] == cog.max_bot_version.release[:2]:
# If cog creator explicitly set max_bot_version to 3.x.y,
# then 3.x is explicitly supported.
info.compatibility_status = (
CompatibilityStatus.EXPLICITLY_SUPPORTED_MAX_BOT_VERSION
)
results.explicitly_supported[cog.name] = info
elif f"red-{latest_version.major}-{latest_version.minor}-ready" in cog.tags:
# If cog creator explicitly added a "red-3.x-ready" tag,
# then 3.x is explicitly supported.
# This is similar to the meaning of "Programming Language :: Python :: 3.x"
# classifiers in Python packaging.
info.compatibility_status = CompatibilityStatus.EXPLICITLY_SUPPORTED_READY_TAG
results.explicitly_supported[cog.name] = info
else:
# If we don't have any explicit signals from the cog's metadata that
# Red 3.x is supported, the cog is only *potentially* supported by that version.
info.compatibility_status = CompatibilityStatus.POTENTIALLY_SUPPORTED
results.potentially_supported[cog.name] = info
def _evaluate_before_update_compatibility(
self, to_check: Iterable[_downloader.Installable]
) -> CompatibilityResults:
results = CompatibilityResults(
latest_version=self.latest_version, interpreter_version=self.interpreter_version
)
self._fill_compatibility_results(results, to_check)
return results
def _evaluate_after_update_compatibility(
self,
to_check: Iterable[_downloader.Installable],
update_check_result: _downloader.CogUpdateCheckResult,
) -> CompatibilityResults:
not_updatable = set(to_check)
results = CompatibilityResults(
latest_version=self.latest_version, interpreter_version=self.interpreter_version
)
not_updatable.difference_update(update_check_result.incompatible_python_version)
not_updatable.difference_update(update_check_result.incompatible_bot_version)
not_updatable.difference_update(update_check_result.updatable_cogs)
self._fill_compatibility_results(results, update_check_result.incompatible_python_version)
self._fill_compatibility_results(results, update_check_result.incompatible_bot_version)
self._fill_compatibility_results(results, update_check_result.updatable_cogs)
# not_updatable should now only have cogs that were not updateable. Those cogs
# are filled based on metadata of the currently installed ("before update") version.
self._fill_compatibility_results(results, not_updatable)
return results
async def check_instance(
instance: str,
*,
latest_version: Version,
interpreter_version: Version,
ignore_prefix: bool = False,
) -> CompatibilitySummary:
data_manager.load_basic_configuration(instance)
red = Red(cli_flags=parse_cli_flags([instance]))
driver_cls = _drivers.get_driver_class()
await driver_cls.initialize(**data_manager.storage_details())
try:
checker = CogCompatibilityChecker(
red,
latest_version=latest_version,
interpreter_version=interpreter_version,
ignore_prefix=ignore_prefix,
)
return await checker.check()
finally:
await driver_cls.teardown()
+221
View File
@@ -0,0 +1,221 @@
import enum
import logging
import os
import sys
from operator import itemgetter
from typing import Any, Final, Iterable, List, Literal, Optional, Tuple, Union
import click
import rich
from packaging.specifiers import SpecifierSet
from packaging.version import Version
from python_discovery import PythonInfo, get_interpreter
from rich.console import Console, RenderableType
from rich.logging import RichHandler
from rich.table import Table
from rich.text import Text
from redbot import __version__
from redbot.core.utils._internal_utils import (
cli_level_to_log_level,
get_installed_extras,
log_level_to_cli_level,
)
from redbot.core import data_manager
_instance_data = data_manager.load_existing_config()
INSTANCE_LIST: Final = () if _instance_data is None else tuple(_instance_data.keys())
ICON_SUCCESS = "[green]:white_heavy_check_mark-emoji:[/]"
ICON_INFO = "[blue]:information-emoji:[/]"
ICON_WARN = "[yellow]:warning-emoji:[/]"
ICON_ERROR = "[red]:cross_mark-emoji:[/]"
INTERNAL_LEGACY_WINDOWS_ENV_VAR = "_RED_UPDATE_INTERNAL_LEGACY_WINDOWS"
INTERNAL_UPDATER_METADATA_ENV_VAR = "_RED_UPDATE_INTERNAL_UPDATER_METADATA"
_STDERR_CONSOLE: Optional[Console] = None
RUNNER_DIR_ENV_VAR: Final = "REDBOT_UPDATE_RUNNER_DIR"
RUNNER_WRAPPER_EXE_ENV_VAR: Final = "REDBOT_UPDATE_RUNNER_WRAPPER_EXE"
OLD_VENV_BACKUP_DIR_NAME: Final = "redbot-update-old-venv-backup"
def get_red_dependency_specifier(version: Version, extras: Iterable[str]) -> str:
specifier_template = (
os.getenv("_RED_UPDATE_PRETEND_SPECIFIER_TEMPLATE")
or "Red-DiscordBot {extras} {versionspec}"
)
joined_extras = ",".join(extras)
return specifier_template.format(
extras=f"[{joined_extras}]" if joined_extras else "",
versionspec=f"=={version}",
)
def get_current_red_version() -> Version:
return Version(os.getenv("_RED_UPDATE_PRETEND_VERSION") or __version__)
def get_current_python_version() -> Version:
return Version(".".join(map(str, sys.version_info[:3])))
def prefix_column(prefix: RenderableType, *parts: Union[str, Text]) -> Table:
output = Table.grid(padding=(0, 2))
output.add_column()
output.add_column()
text = Text()
for renderable in parts:
if isinstance(renderable, str):
text.append_text(Text.from_markup(renderable))
else:
text.append_text(renderable)
output.add_row(prefix, text)
return output
def print_with_prefix_column(
prefix: RenderableType, *parts: Union[str, Text], console: Optional[Console] = None
) -> None:
if console is None:
console = rich.get_console()
console.print(prefix_column(prefix, *parts))
def _apply_legacy_windows_workaround() -> None:
# Rich does not properly support printing to stderr, when stdout is redirected...
# This monkeypatch should be enough to workaround this for our purposes.
# https://github.com/Textualize/rich/issues/4071
if sys.platform == "win32" and not sys.stdout.isatty():
import rich._win32_console
rich._win32_console.STDOUT = -12
def configure_rich() -> None:
_apply_legacy_windows_workaround()
value = os.getenv(INTERNAL_LEGACY_WINDOWS_ENV_VAR, "")
legacy_windows = int(value) if value else None
rich.reconfigure(highlight=False, legacy_windows=legacy_windows)
global _STDERR_CONSOLE
_STDERR_CONSOLE = Console(highlight=False, stderr=True, legacy_windows=legacy_windows)
def get_console(stderr: bool = False) -> Console:
global _STDERR_CONSOLE
if _STDERR_CONSOLE is None:
raise RuntimeError("_STDERR_CONSOLE is not set")
return _STDERR_CONSOLE if stderr else rich.get_console()
def configure_logging(logging_level: int) -> None:
configure_rich()
level = cli_level_to_log_level(logging_level)
base_logger = logging.getLogger("red")
base_logger.setLevel(level)
base_logger.addHandler(RichHandler(console=get_console(stderr=True), show_path=False))
def get_logging_level() -> int:
return logging.getLogger("red").level
def get_log_cli_level() -> int:
return log_level_to_cli_level(logging.getLogger("red").level)
def ensure_supported_env() -> None:
if sys.prefix == sys.base_prefix:
print("redbot-update cannot be used when Red is installed outside a virtual environment.")
raise SystemExit(1)
if not (
os.environ.get(RUNNER_DIR_ENV_VAR, "") and os.environ.get(RUNNER_WRAPPER_EXE_ENV_VAR, "")
):
print("redbot-update was called incorrectly.")
raise SystemExit(1)
def _get_system_interpreters(
requires_python: SpecifierSet,
) -> List[Tuple[str, Version, PythonInfo]]:
interpreters = {}
def _append_interpreter(info: PythonInfo) -> Literal[False]:
version = Version(info.version_str)
if version in requires_python:
# realpath call is needed because get_interpreter lists
# /usr/bin and /bin as separate even though they're the same path
interpreters[os.path.realpath(info.executable)] = (version, info)
return False
get_interpreter("cpython", predicate=_append_interpreter)
ret = [(key, *value) for key, value in interpreters.items()]
ret.sort(key=itemgetter(1), reverse=True)
return ret
def search_for_interpreters(
requires_python: SpecifierSet,
) -> List[Tuple[str, Version, PythonInfo]]:
console = get_console()
with console.status("Searching for compatible Python interpreters on your system..."):
interpreters = _get_system_interpreters(requires_python)
if not interpreters:
url = "https://docs.discord.red/en/stable/install_guides/"
console.print(
f"{ICON_ERROR} Could not find a compatible Python interpreter!\n"
'Please follow the steps from the "Installing the pre-requirements" section'
" of the install guide for your system:"
)
console.print(Text(url, style=f"link {url}"))
console.print("Once you finish installing the pre-requirements, run this command again.")
raise SystemExit(1)
return interpreters
class OrderedEnum(enum.Enum):
def __ge__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
def __gt__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.value > other.value
return NotImplemented
def __le__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.value <= other.value
return NotImplemented
def __lt__(self, other: Any) -> bool:
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
class VersionParamType(click.ParamType):
name = "version"
def convert(
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
) -> Version:
if isinstance(value, Version):
if len(value.release) < 2:
self.fail(
f"{value!r} needs to have at least 2 release components (major and minor).",
param,
ctx,
)
return value
try:
return self.convert(Version(value), param, ctx)
except ValueError:
self.fail(f"{value!r} is not a valid version number", param, ctx)
+453
View File
@@ -0,0 +1,453 @@
import asyncio
import os
import subprocess
import shutil
import sys
import sysconfig
from pathlib import Path
from typing import Tuple
import click
from rich.markdown import Markdown
from rich.panel import Panel
from rich.prompt import Confirm
from rich.text import Text
from redbot import __version__
from redbot.core import _downloader, _drivers, data_manager
from redbot.core._cli import asyncio_run, parse_cli_flags
from redbot.core.bot import Red
from . import changelog, cmd, common, runner
from .updater import UpdaterMetadata, get_updater_metadata
FINISH_UPDATE_CMD_NAME = "finish-update"
_UPDATE_COGS_CMD_NAME = "update-cogs"
_UPDATE_REPOS_OPTION_NAME = "--update-repos"
_EXIT_INSTANCE_SITE_PREFIX_MISMATCH = 4
_EXIT_INSTANCE_BACKEND_UNSUPPORTED = 5
@click.group(invoke_without_command=True)
@click.option(cmd.arg_names.DEBUG, "logging_level", count=True)
def cli(logging_level: int) -> None:
common.ensure_supported_env()
common.configure_logging(logging_level)
@cli.command(_UPDATE_COGS_CMD_NAME)
@click.argument("instance_name")
@click.option(_UPDATE_REPOS_OPTION_NAME, default=False, is_flag=True)
def update_cogs(instance_name: str, update_repos: bool) -> None:
asyncio.run(_update_cogs(instance_name, update_repos))
async def _update_cogs(instance: str, update_repos: bool) -> None:
data_manager.load_basic_configuration(instance)
red = Red(cli_flags=parse_cli_flags([instance]))
driver_cls = _drivers.get_driver_class()
await driver_cls.initialize(**data_manager.storage_details())
try:
await _run_cog_update(red, update_repos=update_repos)
except _drivers.MissingExtraRequirements:
raise SystemExit(_EXIT_INSTANCE_BACKEND_UNSUPPORTED)
finally:
await driver_cls.teardown()
async def _run_cog_update(bot: Red, *, update_repos: bool) -> None:
stdout_console = common.get_console()
console = common.get_console(stderr=True)
instance_name = data_manager.instance_name()
last_known_prefix = await bot._config.last_system_info.python_prefix()
same_install = False
if last_known_prefix is not None:
try:
same_install = os.path.samefile(last_known_prefix, sys.prefix)
except OSError:
pass
if not same_install:
raise SystemExit(_EXIT_INSTANCE_SITE_PREFIX_MISMATCH)
common.print_with_prefix_column(
common.ICON_INFO,
"Started updating cogs for the ",
Text(instance_name, style="bold"),
" instance.",
console=console,
)
status = Text.assemble(
"Update cogs installed on the ", (instance_name, "bold"), " instance..."
)
with console.status(status):
await _downloader._init_without_bot(bot._cog_mgr)
result = await _downloader.update_cogs(update_repos=update_repos)
common.print_with_prefix_column(
common.ICON_INFO,
"Finished updating cogs for the ",
Text(instance_name, style="bold"),
" instance.",
console=console,
)
if not result.checked_cogs:
stdout_console.print("There were no cogs to check.")
return
if not result.updates_available:
stdout_console.print("All installed cogs are already up to date.")
return
current_cog_versions_map = {cog.name: cog for cog in result.checked_cogs}
if result.failed_reqs:
console.print(
"Failed to install requirements:",
Text(", ").join(Text(req, style="bold") for req in result.failed_reqs),
)
return
message = Text("Cog update completed successfully.")
if result.updated_cogs:
cogs_with_changed_eud_statement = set()
for cog in result.updated_cogs:
current_eud_statement = current_cog_versions_map[cog.name].end_user_data_statement
if current_eud_statement != cog.end_user_data_statement:
cogs_with_changed_eud_statement.add(cog.name)
message.append("\nUpdated: ")
message.append_text(
Text(", ").join(Text(cog.name, style="bold") for cog in result.updated_cogs)
)
if cogs_with_changed_eud_statement:
message.append("\nEnd user data statements of these cogs have changed: ")
message.append_text(
Text(", ").join(
Text(cog_name, style="bold") for cog_name in cogs_with_changed_eud_statement
)
)
message.append("\nYou can use ")
message.append("[p]cog info <repo> <cog>", style="bold")
message.append(" to see the updated statements.\n")
# If the bot has any slash commands enabled, warn them to sync
enabled_slash = await bot.list_enabled_app_commands()
if any(enabled_slash.values()):
message.append("\nYou may need to resync your slash commands with ")
message.append("[p]slash sync")
message.append(".")
if result.failed_cogs:
message.append("\nFailed to update cogs: ")
message.append_text(
Text(", ").join(Text(cog.name, style="bold") for cog in result.failed_cogs)
)
if not result.outdated_cogs:
message = Text("No cogs were updated.")
if result.failed_libs:
message.append("\nFailed to install shared libraries: ")
message.append_text(
Text(", ").join(Text(lib.name, style="bold") for lib in result.failed_libs)
)
stdout_console.print(message)
@cli.command(FINISH_UPDATE_CMD_NAME)
def finish_update() -> None:
"""
Entrypoint for finishing up the update that runs with the new version of Red.
"""
asyncio_run(_finish_update())
async def _finish_update() -> None:
assert runner.get_request_output().request_type is runner.RequestType.exec
updater_metadata = get_updater_metadata()
console = common.get_console()
console.print()
if updater_metadata.options.interactive and not updater_metadata.options.update_cogs:
msg = Text("It is highly recommended to update 3rd-party cogs after updating Red")
if updater_metadata.breaking_update:
msg.append(", especially after a major update")
msg.append(".")
console.print(msg)
cog_compatibility = updater_metadata.cog_compatibility
if cog_compatibility is not None:
unsupported_cogs = set()
cogs_with_improved_compatibility = set()
unaffected_cogs = set()
for summary in cog_compatibility.checked.values():
for before in summary.before_update.values():
cog_name = before.name
after = summary.after_update[cog_name]
if after.compatibility_status.unsupported:
unsupported_cogs.add(cog_name)
elif after.compatibility_status.explicitly_supported:
if before.compatibility_status.explicitly_supported:
unaffected_cogs.add(cog_name)
else:
cogs_with_improved_compatibility.add(cog_name)
elif before.compatibility_status.unsupported:
cogs_with_improved_compatibility.add(cog_name)
else:
unaffected_cogs.add(cog_name)
if cogs_with_improved_compatibility:
common.print_with_prefix_column(
common.ICON_INFO,
"Updating will improve compatibility of ",
Text(str(len(cogs_with_improved_compatibility)), style="bold"),
" cogs.",
)
if unsupported_cogs:
common.print_with_prefix_column(
common.ICON_WARN,
Text(str(len(unsupported_cogs)), style="bold"),
" cogs will remain unsupported after updating:\n",
Text(", ").join(
Text(cog_name, style="bold") for cog_name in sorted(unsupported_cogs)
),
)
update_cogs = updater_metadata.options.update_cogs
if update_cogs is None:
if updater_metadata.options.interactive:
update_cogs = Confirm.ask("Do you want to update all your cogs?", default=True)
else:
update_cogs = True
if update_cogs:
await _handle_cog_updates(updater_metadata)
with console.status("Cleaning up..."):
backup_dir = Path(sys.prefix) / common.OLD_VENV_BACKUP_DIR_NAME
shutil.rmtree(backup_dir)
changelog_markdown = changelog.render_markdown(updater_metadata.changelogs)
if changelog_markdown:
console.print(Panel(Markdown(changelog_markdown)))
console.print()
common.print_with_prefix_column(
common.ICON_SUCCESS,
"Update to Red ",
Text(__version__, style="bold"),
" has been finished!",
)
if changelog_markdown:
common.print_with_prefix_column(
common.ICON_INFO,
'Remember to follow instructions from the "Read before updating" section,'
" if any were provided.",
)
if updater_metadata.backup_dir:
additional_text = ""
if not updater_metadata.options.backup_dir:
additional_text = (
"\nNote that this is a temporary directory and may eventually get auto-removed"
" by your system."
)
common.print_with_prefix_column(
common.ICON_INFO,
"If needed, you can find the backups of the virtual environment"
" and the instances at: ",
Text(str(updater_metadata.backup_dir), style="bold"),
additional_text,
)
async def _handle_cog_updates(updater_metadata: UpdaterMetadata) -> None:
cog_compatibility = updater_metadata.cog_compatibility
console = common.get_console()
instances = (
list(cog_compatibility.checked)
if cog_compatibility is not None
else updater_metadata.options.instances
)
checked_instances = {}
failed_instances = []
unsupported_storage_instances = []
for instance_name in instances:
if instance_name in updater_metadata.options.excluded_instances:
continue
exit_code, stdout = await _call_cog_update(
instance_name, update_repos=cog_compatibility is None
)
if exit_code == _EXIT_INSTANCE_BACKEND_UNSUPPORTED:
unsupported_storage_instances.append(instance_name)
elif exit_code == _EXIT_INSTANCE_SITE_PREFIX_MISMATCH:
pass
elif exit_code:
failed_instances.append(instance_name)
print(stdout, end="")
Text.assemble(
"\N{UPWARDS ARROW} " * 3, "Failure for ", (instance_name, "bold"), " instance"
)
console.rule(
Text.assemble(
"\N{UPWARDS ARROW} " * 3,
"Failure for ",
(instance_name, "bold"),
" instance above",
" \N{UPWARDS ARROW}" * 3,
),
style="red",
)
else:
checked_instances[instance_name] = stdout
if stdout:
console.print()
if checked_instances:
for instance_name, stdout in checked_instances.items():
console.rule(Text(instance_name, style="bold"))
print(stdout, end="")
console.rule()
common.print_with_prefix_column(
common.ICON_INFO,
"Finished updating cogs.",
"\nThe results for each instance are shown above." if checked_instances else "",
)
if failed_instances:
common.print_with_prefix_column(
common.ICON_ERROR,
"Failure occurred while trying to perform update for following instances: ",
Text(", ").join(
Text(instance_name, style="bold") for instance_name in failed_instances
),
"\nScroll above to find the errors.",
)
if unsupported_storage_instances:
common.print_with_prefix_column(
common.ICON_INFO,
"The following instances were skipped as they use a storage backend that is"
" not supported by the current Red installation (some requirements are missing): ",
Text(", ").join(
Text(instance_name, style="bold")
for instance_name in unsupported_storage_instances
),
)
if not checked_instances:
common.print_with_prefix_column(
common.ICON_INFO,
"There were no",
(" other" if failed_instances or unsupported_storage_instances else ""),
" instances to update cogs for.",
)
async def _call_cog_update(instance_name: str, *, update_repos: bool) -> Tuple[int, str]:
debug_args = (cmd.arg_names.DEBUG,) * common.get_log_cli_level()
args = [
"-m",
"redbot._update.internal",
*debug_args,
_UPDATE_COGS_CMD_NAME,
instance_name,
]
if update_repos:
args.append(_UPDATE_REPOS_OPTION_NAME)
env = os.environ.copy()
# terminal woes
console = common.get_console()
if console.is_terminal:
env["TTY_COMPATIBLE"] = "1"
# Rich only checks stdout for Windows console features:
# https://github.com/Textualize/rich/blob/fc41075a3206d2a5fd846c6f41c4d2becab814fa/rich/_windows.py#L46
env[common.INTERNAL_LEGACY_WINDOWS_ENV_VAR] = "1" if console.legacy_windows else "0"
else:
# Rich does not set legacy_windows correctly when is_terminal is False
# https://github.com/Textualize/rich/issues/3647
env[common.INTERNAL_LEGACY_WINDOWS_ENV_VAR] = "0"
env["PYTHONIOENCODING"] = sys.stdout.encoding
proc = await asyncio.create_subprocess_exec(
sys.executable, *args, env=env, stdout=asyncio.subprocess.PIPE
)
stdout_data, _ = await proc.communicate()
decoded_stdout = stdout_data.decode()
exit_code = await proc.wait()
return exit_code, decoded_stdout
@cli.command()
@click.argument("base_executable")
@click.argument("venv_dir", type=click.Path(path_type=Path))
@click.argument("scripts_path", type=click.Path(path_type=Path))
@click.argument("dependency_specifier")
def reinstall(
base_executable: str, venv_dir: Path, scripts_path: Path, dependency_specifier: str
) -> None:
assert runner.get_request_output().request_type is runner.RequestType.exec
console = common.get_console()
with console.status("Creating a new virtual environment..."):
subprocess.check_call((base_executable, "-m", "venv", str(venv_dir)))
console.print("Created a new virtual environment.")
executable = str(scripts_path / f"python{sysconfig.get_config_var('EXE')}")
common.print_with_prefix_column(common.ICON_INFO, "Starting the install process...")
try:
subprocess.check_call((executable, "-m", "pip", "install", "-U", "pip"))
subprocess.check_call((executable, "-m", "pip", "install", dependency_specifier))
except subprocess.CalledProcessError:
console.print()
common.print_with_prefix_column(
common.ICON_ERROR,
"Failed to install new version of Red.",
)
status = console.status("Attempting to restore old virtual environment...")
status.start()
try:
_remove_new_venv(venv_dir)
except Exception:
status.stop()
common.print_with_prefix_column(
common.ICON_ERROR, "Failed to remove newly created virtual environment."
)
raise SystemExit(1)
try:
_restore_old_venv(venv_dir)
except Exception:
status.stop()
common.print_with_prefix_column(
common.ICON_ERROR, "Failed to restore old virtual environment."
)
else:
common.print_with_prefix_column(
common.ICON_INFO, "The old virtual environment has been restored."
)
raise SystemExit(1)
# NOTE: this will run with the updated version of Red
runner.make_exec_request(executable, "finish-update")
def _remove_new_venv(venv_dir: Path) -> None:
backup_dir = venv_dir / common.OLD_VENV_BACKUP_DIR_NAME
wrapper_exe = runner.get_wrapper_executable()
for path in venv_dir.iterdir():
if path == backup_dir or path == wrapper_exe:
continue
if path.is_dir():
shutil.rmtree(path)
else:
path.unlink()
def _restore_old_venv(venv_dir: Path) -> None:
backup_dir = venv_dir / common.OLD_VENV_BACKUP_DIR_NAME
for path in backup_dir.iterdir():
path.rename(venv_dir / path.name)
if __name__ == "__main__":
cli()
+119
View File
@@ -0,0 +1,119 @@
import enum
import dataclasses
import json
import os
import sys
from pathlib import Path
from typing import Any, ClassVar, Dict, Iterable, NoReturn, Optional, Tuple, Union
from . import cmd, common
_RUNNER_DIR = Path(os.environ.get(common.RUNNER_DIR_ENV_VAR, ""))
class RequestType(enum.Enum):
exec = "exec"
spawn_command = "spawn_command"
@dataclasses.dataclass(frozen=True)
class RequestInput:
request_type: ClassVar[RequestType]
request_new_python_exe: str
request_new_start_args: Tuple[str, ...]
request_set_env_vars: Dict[str, Optional[str]]
@dataclasses.dataclass(frozen=True)
class RequestOutput:
request_type: RequestType
@dataclasses.dataclass(frozen=True)
class ExecRequestInput(RequestInput):
request_type: ClassVar = RequestType.exec
@dataclasses.dataclass(frozen=True)
class ExecRequestOutput(RequestOutput):
pass
@dataclasses.dataclass(frozen=True)
class SpawnProcessRequestInput(RequestInput):
request_type: ClassVar = RequestType.spawn_command
command: str
args: Tuple[str, ...]
env: Optional[Dict[str, str]]
@dataclasses.dataclass(frozen=True)
class SpawnProcessRequestOutput(RequestOutput):
exit_code: int
exited: bool
pid: int
sys: Any
sys_usage: Dict[str, Any]
system_time: int
user_time: int
def make_request(request: RequestInput) -> NoReturn:
with open(_RUNNER_DIR / "request_input.json", "w", encoding="utf-8") as fp:
data = dataclasses.asdict(request)
data["request_type"] = request.request_type.value
json.dump(data, fp)
raise SystemExit(3)
def get_request_output() -> Union[ExecRequestOutput, SpawnProcessRequestOutput]:
with open(_RUNNER_DIR / "request_output.json", encoding="utf-8") as fp:
data = json.load(fp)
request_type = RequestType(data.pop("request_type"))
if request_type == RequestType.exec:
return ExecRequestOutput(request_type=request_type)
elif request_type == RequestType.spawn_command:
return SpawnProcessRequestOutput(request_type=request_type, **data)
raise RuntimeError("unreachable code")
def make_spawn_process_request(
command: str,
*args: str,
env: Optional[Dict[str, str]] = None,
new_start_args: Iterable[str],
new_python_exe: str = sys.executable,
set_env_vars: Optional[Dict[str, Optional[str]]] = None,
) -> NoReturn:
if set_env_vars is None:
set_env_vars = {}
debug_args = (cmd.arg_names.DEBUG,) * common.get_log_cli_level()
request = SpawnProcessRequestInput(
request_new_python_exe=new_python_exe,
request_new_start_args=("-m", "redbot._update.internal", *debug_args, *new_start_args),
request_set_env_vars=set_env_vars,
command=command,
args=args,
env=env,
)
make_request(request)
def make_exec_request(
new_python_exe: str,
*new_start_args: str,
set_env_vars: Optional[Dict[str, Optional[str]]] = None,
) -> NoReturn:
if set_env_vars is None:
set_env_vars = {}
debug_args = (cmd.arg_names.DEBUG,) * common.get_log_cli_level()
request = ExecRequestInput(
request_new_python_exe=new_python_exe,
request_new_start_args=("-m", "redbot._update.internal", *debug_args, *new_start_args),
request_set_env_vars=set_env_vars,
)
make_request(request)
def get_wrapper_executable() -> Path:
return Path(os.environ[common.RUNNER_WRAPPER_EXE_ENV_VAR])
+129
View File
@@ -0,0 +1,129 @@
import enum
from rich.text import Text
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.events import Click
from textual.widgets import Footer, Markdown, MarkdownViewer, Static
from typing_extensions import Self
from .changelog import Changelogs
# See https://github.com/Textualize/textual/discussions/6449
class MarkdownLinkTooltip(Static, inherit_css=False):
DEFAULT_CSS = """
MarkdownLinkTooltip {
layer: _tooltips;
margin: 1 0;
padding: 1 2;
background: $panel;
width: auto;
height: auto;
constrain: inside inflect;
max-width: 40;
display: none;
offset-x: -50%;
}
"""
class _MarkdownViewer(MarkdownViewer):
DEFAULT_CSS = """
_MarkdownViewer {
layers: default _tooltips;
}
"""
def compose(self) -> ComposeResult:
yield from super().compose()
yield MarkdownLinkTooltip()
def on_markdown_link_clicked(self, message: Markdown.LinkClicked) -> None:
# We don't want the default behavior of opening the browser/navigating to a file on click.
message.prevent_default()
tooltip = self.get_child_by_type(MarkdownLinkTooltip)
tooltip.display = True
# You can't cycle over the links in MarkdownViewer (see Textualize/textual#3555)
# so using mouse position is fine.
# Textualize/textual#3555: https://github.com/Textualize/textual/discussions/3555
tooltip.absolute_offset = self.app.mouse_position
# For some reason, links only render correctly when Text has a span over the whole text
# with a link but not when Text just has a style applied to it directly, i.e.:
# Text(message.href, style=f"link {message.href}")
# will not work.
tooltip.update(Text().append(message.href, style=f"link {message.href}"))
def on_click(self, message: Click) -> None:
tooltip = self.get_child_by_type(MarkdownLinkTooltip)
tooltip.display = False
class ChangelogReaderResult(enum.Enum):
QUIT = enum.auto()
CONTINUE = enum.auto()
class ChangelogReaderApp(App[ChangelogReaderResult], inherit_bindings=False):
ENABLE_COMMAND_PALETTE = False
BINDINGS = [
Binding(key="ctrl+c", action="quit", description="Exit redbot-update"),
Binding(key="q", action="continue", description="Finish reading the changelog"),
]
def __init__(self, markdown_content: str) -> None:
self.markdown_content = markdown_content
super().__init__()
@classmethod
def from_changelogs(cls, changelogs: Changelogs) -> Self:
if not changelogs:
return cls("")
parts = []
contributors = sorted(
{
contributor
for changelog in changelogs.values()
for contributor in changelog.contributors
}
)
if contributors:
contributor_thanks = (
"# Thanks to our contributors \N{HEAVY BLACK HEART}\N{VARIATION SELECTOR-16}\n"
"**The releases below were made with help from the following people:** \n"
)
contributor_thanks += ", ".join(
f"[@{contributor}](https://github.com/sponsors/{contributor})"
for contributor in contributors
)
parts.append(contributor_thanks)
parts.append("# Read before updating")
for changelog in reversed(changelogs.values()):
if changelog.read_before_updating_section:
parts.append(f"## {changelog.version}")
parts.append(changelog.read_before_updating_section)
parts.append("# User changelog")
for changelog in reversed(changelogs.values()):
if changelog.user_changelog_section:
parts.append(f"## {changelog.version}")
parts.append(changelog.user_changelog_section)
return cls("\n".join(parts))
def compose(self) -> ComposeResult:
markdown_viewer = _MarkdownViewer(
self.markdown_content, show_table_of_contents=True, open_links=False
)
markdown_viewer.code_indent_guides = False
yield markdown_viewer
yield Footer()
def action_quit(self) -> None:
self.exit(ChangelogReaderResult.QUIT)
def action_continue(self) -> None:
self.exit(ChangelogReaderResult.CONTINUE)
+736
View File
@@ -0,0 +1,736 @@
import asyncio
import dataclasses
import json
import os
import shutil
import sys
import tarfile
import tempfile
from pathlib import Path
from typing import Any, Dict, List, NoReturn, Optional, Set
import click
from packaging.version import Version
from python_discovery import PythonInfo
from rich.markdown import Markdown
from rich.panel import Panel
from rich.prompt import Confirm, IntPrompt, Prompt
from rich.text import Text
from typing_extensions import Self
from redbot.core.utils._internal_utils import (
AvailableVersion,
detailed_progress,
fetch_available_red_versions,
get_installed_extras,
)
from . import changelog, cmd, common, runner
from .cog_compatibility_checker import CompatibilitySummary
from .tui import ChangelogReaderApp, ChangelogReaderResult
@dataclasses.dataclass
class UpdaterOptions:
"""Update options specified by the user."""
instances: List[str]
excluded_instances: Set[str]
ignore_prefix: bool
backup_dir: Optional[Path]
no_backup: bool
red_version: Optional[Version]
no_major_updates: bool
no_full_changelog: bool
no_cog_compatibility_check: bool
new_python_interpreter: Optional[PythonInfo]
update_cogs: Optional[bool]
force_reinstall: bool
interactive: bool
@classmethod
def from_json_dict(cls, data: Dict[str, Any]) -> Self:
backup_dir = data["backup_dir"]
red_version = data["red_version"]
return cls(
instances=data["instances"],
excluded_instances=set(data["excluded_instances"]),
ignore_prefix=data["ignore_prefix"],
backup_dir=backup_dir and Path(data["backup_dir"]),
no_backup=data["no_backup"],
red_version=red_version and Version(red_version),
no_major_updates=data["no_major_updates"],
no_full_changelog=data["no_full_changelog"],
no_cog_compatibility_check=data["no_cog_compatibility_check"],
new_python_interpreter=(
data["new_python_interpreter"]
and PythonInfo.from_dict(data["new_python_interpreter"])
),
update_cogs=data["update_cogs"],
force_reinstall=data["force_reinstall"],
interactive=data["interactive"],
)
def to_json_dict(self) -> Dict[str, Any]:
data = dataclasses.asdict(self)
data["excluded_instances"] = list(self.excluded_instances)
data["backup_dir"] = self.backup_dir and str(self.backup_dir)
data["red_version"] = self.red_version and str(self.red_version)
data["new_python_interpreter"] = (
self.new_python_interpreter and self.new_python_interpreter.to_dict()
)
return data
@dataclasses.dataclass
class UpdaterCompatibilitySummary:
checked: Dict[str, CompatibilitySummary]
failed: List[str]
skipped: List[str]
@classmethod
def from_json_dict(cls, data: Dict[str, Any]) -> Self:
return cls(
checked={
instance_name: CompatibilitySummary.from_json_dict(results_data)
for instance_name, results_data in data["checked"].items()
},
failed=data["failed"],
skipped=data["skipped"],
)
def to_json_dict(self) -> Dict[str, Any]:
return {
"checked": {
instance_name: results.to_json_dict()
for instance_name, results in self.checked.items()
},
"failed": self.failed,
"skipped": self.skipped,
}
@dataclasses.dataclass
class BackupResults:
checked: List[str]
failed: List[str]
skipped: List[str] = dataclasses.field(default_factory=list)
@classmethod
def from_json_dict(cls, data: Dict[str, List[str]]) -> Self:
return cls(checked=data["checked"], failed=data["failed"], skipped=data["skipped"])
def to_json_dict(self) -> Dict[str, List[str]]:
return dataclasses.asdict(self)
_PYTHON_VERSION_PLACEHOLDER = Version("0.0.dev0")
@dataclasses.dataclass
class UpdaterMetadata:
"""Metadata about the update process."""
# options specified by the user
options: UpdaterOptions
# info about Red version to update to (latest available or latest non-major update)
latest: AvailableVersion
latest_major: AvailableVersion
# info about Red/Python versions that we're updating from
current_version: Version = dataclasses.field(default_factory=common.get_current_red_version)
current_python_version: Version = dataclasses.field(
default_factory=common.get_current_python_version
)
# details about the interpreter that will be used for the new venv
interpreter_info: PythonInfo = dataclasses.field(default_factory=PythonInfo.current_system)
interpreter_version: Version = _PYTHON_VERSION_PLACEHOLDER
interpreter_exe: str = ""
# changelogs for version in (current_version, latest> range
changelogs: changelog.Changelogs = dataclasses.field(default_factory=dict)
# cog compatibility check results
cog_compatibility: Optional[UpdaterCompatibilitySummary] = None
# backup info
to_backup: List[str] = dataclasses.field(default_factory=list)
backup_dir: Optional[Path] = None
backup_results: Optional[BackupResults] = None
def __post_init__(self) -> None:
if self.interpreter_version is _PYTHON_VERSION_PLACEHOLDER:
self.interpreter_version = Version(
".".join(map(str, self.interpreter_info.version_info[:3]))
)
if not self.interpreter_exe:
self.interpreter_exe = self.interpreter_info.system_executable
@classmethod
def from_json_dict(cls, data: Dict[str, Any]) -> Self:
"""
Make an instance of this class from a dictionary,
as returned by the `to_json_dict()` method.
This aims to maintain backwards compatibility with data generated by
earlier Red versions as it may be called with such data
after the last update step.
"""
backup_dir = data.get("backup_dir")
return cls(
options=UpdaterOptions.from_json_dict(data["options"]),
latest=AvailableVersion.from_json_dict(data["latest"]),
latest_major=AvailableVersion.from_json_dict(data["latest_major"]),
current_version=Version(data["current_version"]),
current_python_version=Version(data["current_python_version"]),
interpreter_version=Version(data["interpreter_version"]),
interpreter_info=PythonInfo.from_dict(data["interpreter_info"]),
interpreter_exe=data["interpreter_exe"],
changelogs={
Version(raw_version): changelog.VersionChangelog.from_json_dict(raw_changelog)
for raw_version, raw_changelog in data["changelogs"].items()
},
cog_compatibility=UpdaterCompatibilitySummary.from_json_dict(
data["cog_compatibility"]
),
to_backup=data["to_backup"],
backup_dir=backup_dir and Path(backup_dir),
backup_results=BackupResults.from_json_dict(data["backup_results"]),
)
def to_json_dict(self) -> Dict[str, Any]:
return {
"options": self.options.to_json_dict(),
"latest": self.latest.to_json_dict(),
"latest_major": self.latest_major.to_json_dict(),
"current_version": str(self.current_version),
"current_python_version": str(self.current_python_version),
"interpreter_version": str(self.interpreter_version),
"interpreter_info": self.interpreter_info.to_dict(),
"interpreter_exe": self.interpreter_exe,
"changelogs": {str(v): c.to_json_dict() for v, c in self.changelogs.items()},
"cog_compatibility": self.cog_compatibility and self.cog_compatibility.to_json_dict(),
"to_backup": self.to_backup,
"backup_dir": self.backup_dir and str(self.backup_dir),
"backup_results": self.backup_results and self.backup_results.to_json_dict(),
}
@property
def breaking_update(self) -> bool:
return self.current_version.release[:2] != self.latest.version.release[:2]
class Updater:
metadata: UpdaterMetadata
def __init__(self, options: UpdaterOptions) -> None:
self.options = options
self.console = common.get_console()
@property
def latest(self) -> AvailableVersion:
return self.metadata.latest
@property
def current_version(self) -> Version:
return self.metadata.current_version
async def run(self) -> None:
await self._prepare_metadata()
new_version_available = self.current_version < self.latest.version
if not self.options.force_reinstall and not new_version_available:
if self.current_version >= self.metadata.latest_major.version:
common.print_with_prefix_column(
common.ICON_SUCCESS,
"You are already running the latest available version of Red.",
)
else:
common.print_with_prefix_column(
common.ICON_INFO,
"There are no non-major updates available.\n",
"There is a new major version available: ",
Text(str(self.metadata.latest_major.version), style="bold"),
)
return
if new_version_available:
common.print_with_prefix_column(
common.ICON_SUCCESS,
"New version available: ",
Text(str(self.latest.version), style="bold"),
)
await self._show_changelog()
self._check_python_requires()
if self.options.no_cog_compatibility_check:
self.console.print(
"Will not make backups as --no-cog-compatibility-check option was passed."
)
else:
await self._check_cog_compatibility()
if self.options.no_backup:
common.print_with_prefix_column(
common.ICON_INFO, "Will not make backups as --no-backup option was passed."
)
else:
common.print_with_prefix_column(
common.ICON_INFO,
"The following instances will be backed up before performing the update: ",
Text(", ").join(
Text(instance_name, style="bold") for instance_name in self.metadata.to_backup
),
)
if self.metadata.breaking_update:
self.console.print(
"[b]Remember that this is a major release and it may have some breaking changes"
" that the bot or its cogs may be affected by.[/]"
)
if self.options.interactive and not Confirm.ask(
f"Do you want to continue with the update to [b]Red {self.latest.version}[/]?"
):
return
self.console.print()
if self.options.no_backup:
self.console.print("Will not make backups as --no-backup option was passed.")
else:
await self._make_backups()
await self._update_with_fresh_venv()
async def _prepare_metadata(self) -> None:
interpreter_info = self.options.new_python_interpreter or PythonInfo.current_system()
with self.console.status("Checking latest version..."):
available_versions = await fetch_available_red_versions()
latest_major = available_versions[0]
self.metadata = UpdaterMetadata(
self.options,
latest=latest_major,
latest_major=latest_major,
interpreter_info=interpreter_info,
)
if self.options.red_version:
if self.options.red_version <= self.current_version:
common.print_with_prefix_column(
common.ICON_ERROR, "You can only update to a newer version of Red."
)
raise SystemExit(2)
if (
self.options.no_major_updates
and self.options.red_version.release[:2] != self.current_version.release[:2]
):
common.print_with_prefix_column(
common.ICON_ERROR,
"Updating to the specified version would be a major update"
" but --no-major-updates option was specified.",
)
raise SystemExit(2)
for available_version in available_versions:
if available_version.version == self.options.red_version:
break
else:
common.print_with_prefix_column(
common.ICON_ERROR, "The provided version does not seem to exist."
)
raise SystemExit(2)
self.metadata.latest = available_version
elif self.options.no_major_updates:
for available_version in available_versions:
if available_version.version.release[:2] == self.current_version.release[:2]:
self.metadata.latest = available_version
break
else:
if self.current_version < latest_major.version:
common.print_with_prefix_column(
common.ICON_ERROR,
"Could not find any version of Red that would not be a major update.",
)
raise SystemExit(1)
async def _show_changelog(self) -> None:
with self.console.status("Fetching changelogs..."):
changelogs = await changelog.fetch_changelogs()
self.metadata.changelogs = changelogs = changelog.get_changelogs_between(
changelogs, self.current_version, self.latest.version
)
common.print_with_prefix_column(common.ICON_SUCCESS, "Changelogs fetched.")
if not changelogs:
return
if not self.options.interactive or self.options.no_full_changelog:
self.console.print(Panel(Markdown(changelog.render_markdown(changelogs))))
if self.options.interactive and not Confirm.ask("Do you want to continue?"):
raise click.Abort()
return
first_changelog_version = min(changelogs)
last_changelog_version = max(changelogs)
parts = []
if first_changelog_version == last_changelog_version:
parts.append(
"You will now be presented with the changelog for"
f" [b]Red {first_changelog_version}[/]."
)
else:
parts.append(
"You will now be presented with the changelogs for"
f" [b]Red {first_changelog_version}[/]-[b]{last_changelog_version}[/]."
)
parts.append(
f"\n[bold]{common.ICON_WARN}"
' Make sure to read through the [green]"Read before updating"[/] section'
f" before continuing. {common.ICON_WARN}[/bold]\n"
)
if self.metadata.breaking_update:
parts.append(
f"[bold]{common.ICON_WARN}"
" Please note that this is a major release and it may have some changes that"
" your bot or its cogs are affected by.[/bold]\n"
)
parts.append(
"After the changelog is open and you're ready to continue, hit the [b]Q[/] key"
" to close the changelog and continue the update process.\n\n"
"Hit the [b]Enter[/] key to view the changelog."
)
self.console.input(Panel("".join(parts)), password=True)
viewer = ChangelogReaderApp.from_changelogs(changelogs)
result = await viewer.run_async()
if result is None:
raise RuntimeError("Unexpected state")
if result is ChangelogReaderResult.QUIT:
raise click.Abort()
self.console.print("Changelog has been closed.\n")
def _check_python_requires(self) -> None:
if self.metadata.interpreter_version in self.latest.requires_python:
return
if self.options.new_python_interpreter:
common.print_with_prefix_column(
common.ICON_ERROR,
"The latest version of Red requires a different Python version (",
Text(str(self.latest.requires_python), style="bold"),
") from the version of the interpreter passed to with the --new-python-interpreter"
" option (",
Text(str(self.metadata.interpreter_version), style="bold"),
")",
)
raise SystemExit(1)
common.print_with_prefix_column(
common.ICON_WARN if self.options.interactive else common.ICON_ERROR,
"The latest version of Red requires a different Python version (",
Text(str(self.latest.requires_python), style="bold"),
") from the one that you are currently using (",
Text(str(self.metadata.interpreter_version), style="bold"),
")",
(
"\nredbot-update will have to recreate the virtual environment"
" with a compatible version of Python."
if self.options.interactive
else ""
),
)
if not self.options.interactive:
raise SystemExit(1)
interpreters = common.search_for_interpreters(self.latest.requires_python)
def _render_interpreter(interpreter_exe: str, interpreter_version: Version) -> Text:
return Text.assemble(
"CPython ",
(str(interpreter_version), "repr.number"),
" (",
(interpreter_exe, "log.path"),
")",
)
text = Text("Found the following compatible Python interpreters on your system:")
for idx, (interpreter_exe, interpreter_version, python_info) in enumerate(interpreters, 1):
text.append_text(Text(f"\n{idx}. ", style="markdown.item.number"))
text.append_text(_render_interpreter(interpreter_exe, interpreter_version))
self.console.print(Panel(text))
while True:
result = IntPrompt.ask(
"\nEnter the number of the Python interpreter above that you want to use"
" or type 0 to input the path to it yourself. Generally, you should choose"
" the interpreter with the latest version on the above list.\n"
"Enter your selection",
default=1,
)
if result < 0 or result > len(interpreters):
self.console.print("[prompt.invalid] This is not a valid choice.")
continue
if result == 0:
response = Prompt.ask(
"Please input the path to the Python interpreter that you want to use"
)
if not response:
self.console.print("[prompt.invalid] No path was provided.")
continue
info = PythonInfo.from_exe(response)
interpreter_version = Version(info.version_str)
if (
info.implementation != "CPython"
or interpreter_version not in self.latest.requires_python
):
self.console.print(
"[prompt.invalid] The provided path points to an incompatible Python"
" interpreter. Latest version requires CPython"
f" {self.latest.requires_python} but the provided interpreter is"
f" {info.implementation} {interpreter_version}."
)
continue
self.metadata.interpreter_version = interpreter_version
self.metadata.interpreter_info = info
self.metadata.interpreter_exe = info.executable
else:
(
self.metadata.interpreter_exe,
self.metadata.interpreter_version,
self.metadata.interpreter_info,
) = interpreters[result - 1]
self.console.print(
"\n[b]You selected:[/]",
_render_interpreter(
self.metadata.interpreter_exe, self.metadata.interpreter_version
),
)
if Confirm.ask("Do you want to continue with this choice?"):
self.console.print()
break
async def _check_cog_compatibility(self) -> None:
outputs = {}
checked_instances = {}
skipped_instances = []
failed_instances = []
unsupported_storage_instances = []
for instance_name in self.options.instances:
if instance_name in self.options.excluded_instances:
skipped_instances.append(instance_name)
continue
exit_code, stdout, results = await cmd.cog_compatibility.call(
instance_name,
red_version=self.latest.version,
python_version=self.metadata.interpreter_version,
ignore_prefix=self.options.ignore_prefix,
return_results=True,
stdout=asyncio.subprocess.PIPE,
)
if exit_code == cmd.cog_compatibility.EXIT_INSTANCE_BACKEND_UNSUPPORTED:
skipped_instances.append(instance_name)
unsupported_storage_instances.append(instance_name)
elif exit_code == cmd.cog_compatibility.EXIT_INSTANCE_SITE_PREFIX_MISMATCH:
skipped_instances.append(instance_name)
elif exit_code:
failed_instances.append(instance_name)
print(stdout, end="")
Text.assemble(
"\N{UPWARDS ARROW} " * 3,
"Failure for ",
(instance_name, "bold"),
" instance",
)
self.console.rule(
Text.assemble(
"\N{UPWARDS ARROW} " * 3,
"Failure for ",
(instance_name, "bold"),
" instance above",
" \N{UPWARDS ARROW}" * 3,
),
style="red",
)
else:
assert results is not None
outputs[instance_name] = stdout
checked_instances[instance_name] = results
if stdout:
self.console.print()
self.console.print()
if not self.options.no_backup:
self.metadata.to_backup = [*checked_instances, *failed_instances]
if outputs:
for instance_name, stdout in outputs.items():
self.console.rule(Text(instance_name, style="bold"))
print(stdout, end="")
self.console.rule()
common.print_with_prefix_column(
common.ICON_INFO,
"Finished checking cog compatibility.",
(
"\nThe results for each of the checked instances are shown above."
if checked_instances
else ""
),
)
if failed_instances:
common.print_with_prefix_column(
common.ICON_ERROR,
"Failure occurred while trying to check compatibility for following instances: ",
Text(", ").join(
Text(instance_name, style="bold") for instance_name in failed_instances
),
"\nScroll above to find the errors.",
)
if unsupported_storage_instances:
common.print_with_prefix_column(
common.ICON_INFO,
"The following instances were skipped as they use a storage backend that is"
" not supported by the current Red installation (some requirements are missing): ",
Text(", ").join(
Text(instance_name, style="bold")
for instance_name in unsupported_storage_instances
),
)
if not checked_instances:
common.print_with_prefix_column(
common.ICON_INFO,
"There were no",
(" other" if failed_instances or unsupported_storage_instances else ""),
" instances to check cog compatibility for.",
)
self.console.print()
self.metadata.cog_compatibility = UpdaterCompatibilitySummary(
checked=checked_instances, failed=failed_instances, skipped=skipped_instances
)
async def _make_backups(self) -> None:
self.metadata.backup_dir = backup_dir = self.options.backup_dir or Path(
tempfile.mkdtemp(prefix="redbot-update-backup-")
)
console = common.get_console()
console.print("Backups will be created at:", Text(str(backup_dir), style="bold"))
venv_archive = backup_dir / "venv.tar.gz"
with console.status("Making a backup of the virtual environment directory..."):
venv_dir = Path(sys.prefix)
venv_files = []
for current_dir, _, filenames in os.walk(venv_dir):
target_dir = os.path.relpath(current_dir, venv_dir)
if target_dir == ".":
target_dir = ""
for name in filenames:
venv_files.append(
(os.path.join(current_dir, name), os.path.join(target_dir, name))
)
with tarfile.open(venv_archive, "w:gz", compresslevel=6) as tar:
with detailed_progress(unit="files") as progress:
for src, arcname in progress.track(venv_files, description="Compressing..."):
tar.add(src, arcname=arcname, recursive=False)
console.print(
"Created a backup of the virtual environment directory at:",
Text(str(venv_archive), style="bold"),
)
checked = []
failed = []
instance_backups_dir = backup_dir / "instance_backups"
instance_backups_dir.mkdir()
for instance_name in self.metadata.to_backup:
console.print(
"Making a backup of the", Text(instance_name, style="bold"), "instance..."
)
debug_args = (cmd.arg_names.DEBUG,) * common.get_log_cli_level()
proc = await asyncio.create_subprocess_exec(
sys.executable,
"-m",
"redbot.setup",
"backup",
*debug_args,
instance_name,
str(instance_backups_dir),
)
if await proc.wait():
failed.append(instance_name)
else:
checked.append(instance_name)
self.metadata.backup_results = BackupResults(checked=checked, failed=failed)
if self.metadata.cog_compatibility:
self.metadata.backup_results.skipped.extend(self.metadata.cog_compatibility.skipped)
if failed:
common.print_with_prefix_column(
common.ICON_ERROR,
"The following instances failed during backup: ",
Text(", ").join(Text(instance_name, style="bold") for instance_name in failed),
"\nScroll above to find the errors.",
)
# If a backup fails, we cannot allow non-interactive update to continue.
# The user can choose to use options such as `--no-backup`, `--instance`,
# and `--exclude-instance` to not have the backup step try to backup something
# that it can't.
if not self.options.interactive or not Confirm.ask(
"Do you want to continue with the update regardless?"
):
raise SystemExit(1)
async def _update_with_fresh_venv(self) -> NoReturn:
console = common.get_console()
venv_dir = Path(sys.prefix)
backup_dir = venv_dir / common.OLD_VENV_BACKUP_DIR_NAME
try:
backup_dir.mkdir()
except FileExistsError:
console.print(
"Found that a partial backup of a virtual environment from a past failed update"
" exists at",
Text(str(backup_dir), style="bold"),
"\nThe update will not proceed to avoid overriding it. If you are certain that"
" you don't need to restore anything from it, remove it and try updating again.",
)
raise SystemExit(1)
with console.status("Determining extras to install..."):
try:
metadata = await self.latest.fetch_core_metadata()
except TypeError:
extras = get_installed_extras()
else:
known_extras = metadata.provides_extra or []
extras = [extra for extra in get_installed_extras() if extra in known_extras]
console.print("Extras to install have been determined.")
old_executable = Path(sys.executable)
rel_executable = old_executable.relative_to(venv_dir)
new_executable = backup_dir / rel_executable
wrapper_exe = runner.get_wrapper_executable()
with console.status("Moving old virtual environment..."):
for path in venv_dir.iterdir():
if path == backup_dir or path == wrapper_exe:
continue
path.rename(backup_dir / path.name)
console.print("Old virtual environment moved.")
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", prefix="redbot-update-metadata-", suffix=".json", delete=False
) as metadata_file:
json.dump(self.metadata.to_json_dict(), metadata_file)
console.print()
runner.make_exec_request(
str(new_executable),
"reinstall",
# base executable for venv creation
self.metadata.interpreter_exe,
# venv dir
str(venv_dir),
# scripts path
self.metadata.interpreter_info.sysconfig_path("scripts", {"base": str(venv_dir)}),
# Red dependency specifier
common.get_red_dependency_specifier(self.latest.version, extras),
set_env_vars={common.INTERNAL_UPDATER_METADATA_ENV_VAR: metadata_file.name},
)
def get_updater_metadata() -> UpdaterMetadata:
with open(os.environ[common.INTERNAL_UPDATER_METADATA_ENV_VAR], encoding="utf-8") as fp:
return UpdaterMetadata.from_json_dict(json.load(fp))
+323
View File
@@ -0,0 +1,323 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2023-06-14 02:57+0000\n"
"Last-Translator: \n"
"Language-Team: Estonian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: red-discordbot\n"
"X-Crowdin-Project-ID: 289505\n"
"X-Crowdin-Language: et\n"
"X-Crowdin-File-ID: 42\n"
"Language: et_EE\n"
#: redbot/cogs/admin/admin.py:20
msgid "I attempted to do something that Discord denied me permissions for. Your command failed to successfully complete."
msgstr ""
#: redbot/cogs/admin/admin.py:25
msgid "I can not give {role.name} to {member.display_name} because that role is higher than or equal to my highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:31
msgid "I can not remove {role.name} from {member.display_name} because that role is higher than or equal to my highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:37
msgid "I can not edit {role.name} because that role is higher than my or equal to highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:43
msgid "I can not let you give {role.name} to {member.display_name} because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:49
msgid "I can not let you remove {role.name} from {member.display_name} because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:55
msgid "I can not let you edit {role.name} because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:61
msgid "I need the \"Manage Roles\" permission to do that."
msgstr ""
#: redbot/cogs/admin/admin.py:63
msgid "I am already announcing something. If you would like to make a different announcement please use `{prefix}announce cancel` first."
msgstr ""
#: redbot/cogs/admin/admin.py:73
#, docstring
msgid "A collection of server administration utilities."
msgstr ""
#: redbot/cogs/admin/admin.py:159
msgid "{member.display_name} already has the role {role.name}."
msgstr ""
#: redbot/cogs/admin/admin.py:180
msgid "I successfully added {role.name} to {member.display_name}"
msgstr ""
#: redbot/cogs/admin/admin.py:190
msgid "{member.display_name} does not have the role {role.name}."
msgstr ""
#: redbot/cogs/admin/admin.py:211
msgid "I successfully removed {role.name} from {member.display_name}"
msgstr ""
#: redbot/cogs/admin/admin.py:226
#, docstring
msgid "\n"
" Add a role to a user.\n\n"
" Use double quotes if the role contains spaces.\n"
" If user is left blank it defaults to the author of the command.\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:244
#, docstring
msgid "\n"
" Remove a role from a user.\n\n"
" Use double quotes if the role contains spaces.\n"
" If user is left blank it defaults to the author of the command.\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:256
#, docstring
msgid "Edit role settings."
msgstr ""
#: redbot/cogs/admin/admin.py:263
#, docstring
msgid "\n"
" Edit a role's colour.\n\n"
" Use double quotes if the role contains spaces.\n"
" Colour must be in hexadecimal format.\n"
" [Online colour picker](http://www.w3schools.com/colors/colors_picker.asp)\n\n"
" Examples:\n"
" `[p]editrole colour \"The Transistor\" #ff0000`\n"
" `[p]editrole colour Test #ff9900`\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:275
msgid "{author} ({author.id}) changed the colour of role '{role.name}'"
msgstr ""
#: redbot/cogs/admin/admin.py:294 redbot/cogs/admin/admin.py:327
msgid "Done."
msgstr ""
#: redbot/cogs/admin/admin.py:298
#, docstring
msgid "\n"
" Edit a role's name.\n\n"
" Use double quotes if the role or the name contain spaces.\n\n"
" Example:\n"
" `[p]editrole name \"The Transistor\" Test`\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:308
msgid "{author} ({author.id}) changed the name of role '{old_name}' to '{name}'"
msgstr ""
#: redbot/cogs/admin/admin.py:332
#, docstring
msgid "Announce a message to all servers the bot is in."
msgstr ""
#: redbot/cogs/admin/admin.py:339
msgid "The announcement has begun."
msgstr ""
#: redbot/cogs/admin/admin.py:346
#, docstring
msgid "Cancel a running announce."
msgstr ""
#: redbot/cogs/admin/admin.py:348
msgid "There is no currently running announcement."
msgstr ""
#: redbot/cogs/admin/admin.py:351
msgid "The current announcement has been cancelled."
msgstr ""
#: redbot/cogs/admin/admin.py:357
#, docstring
msgid "Change how announcements are sent in this guild."
msgstr ""
#: redbot/cogs/admin/admin.py:367
#, docstring
msgid "Change the channel where the bot will send announcements."
msgstr ""
#: redbot/cogs/admin/admin.py:370
msgid "The announcement channel has been set to {channel.mention}"
msgstr ""
#: redbot/cogs/admin/admin.py:375
#, docstring
msgid "Unsets the channel for announcements."
msgstr ""
#: redbot/cogs/admin/admin.py:400
#, docstring
msgid "\n"
" Add or remove a selfrole from yourself.\n\n"
" Server admins must have configured the role as user settable.\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:413
#, docstring
msgid "\n"
" Add a selfrole to yourself.\n\n"
" Server admins must have configured the role as user settable.\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:424
#, docstring
msgid "\n"
" Remove a selfrole from yourself.\n\n"
" Server admins must have configured the role as user settable.\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:435
#, docstring
msgid "\n"
" Lists all available selfroles.\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:445
msgid "Available Selfroles:\n"
"{selfroles}"
msgstr ""
#: redbot/cogs/admin/admin.py:451
#, docstring
msgid "Manage selfroles."
msgstr ""
#: redbot/cogs/admin/admin.py:456
#, docstring
msgid "\n"
" Add a role, or a selection of roles, to the list of available selfroles.\n\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:465
msgid "I cannot let you add {role.name} as a selfrole because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:475
msgid "The role \"{role.name}\" is already a selfrole."
msgstr ""
#: redbot/cogs/admin/admin.py:481
msgid "Added {count} selfroles."
msgstr ""
#: redbot/cogs/admin/admin.py:483
msgid "Added 1 selfrole."
msgstr ""
#: redbot/cogs/admin/admin.py:489
#, docstring
msgid "\n"
" Remove a role, or a selection of roles, from the list of available selfroles.\n\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
#: redbot/cogs/admin/admin.py:498
msgid "I cannot let you remove {role.name} from being a selfrole because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:508
msgid "Removed {count} selfroles."
msgstr ""
#: redbot/cogs/admin/admin.py:510
msgid "Removed 1 selfrole."
msgstr ""
#: redbot/cogs/admin/admin.py:516
#, docstring
msgid "Clear the list of available selfroles for this server."
msgstr ""
#: redbot/cogs/admin/admin.py:520
msgid "There are currently no selfroles."
msgstr ""
#: redbot/cogs/admin/admin.py:523
msgid "Are you sure you want to clear this server's selfrole list?"
msgstr ""
#: redbot/cogs/admin/admin.py:529
msgid "You took too long to respond."
msgstr ""
#: redbot/cogs/admin/admin.py:538
msgid "I cannot clear the selfroles because the selfrole '{role.name}' is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
#: redbot/cogs/admin/admin.py:544
msgid "Selfrole list cleared."
msgstr ""
#: redbot/cogs/admin/admin.py:546
msgid "No changes have been made."
msgstr ""
#: redbot/cogs/admin/admin.py:551
#, docstring
msgid "Lock a bot to its current servers only."
msgstr ""
#: redbot/cogs/admin/admin.py:556
msgid "The bot is no longer serverlocked."
msgstr ""
#: redbot/cogs/admin/admin.py:558
msgid "The bot is now serverlocked."
msgstr ""
#: redbot/cogs/admin/announcer.py:68
msgid "I could not announce to the following server: "
msgstr ""
#: redbot/cogs/admin/announcer.py:70
msgid "I could not announce to the following servers: "
msgstr ""
#: redbot/cogs/admin/converters.py:13
msgid "The Admin cog is not loaded."
msgstr ""
#: redbot/cogs/admin/converters.py:30
msgid "The role \"{role_name}\" is not a valid selfrole."
msgstr ""
#: redbot/cogs/admin/converters.py:36
msgid "This selfrole has more than one case insensitive match. Please ask a moderator to resolve the ambiguity, or use the role ID to reference the role."
msgstr ""
+5 -5
View File
@@ -118,7 +118,7 @@ msgstr "\n"
" Bewerk een rolkleur.\n\n"
" Gebruik dubbele aanhalingstekens als de rol spaties bevat.\n"
" Kleur moet in hexadecimaal formaat zijn.\n"
" [Online kleur kiezer](http://www.w3schools. om/colors/colors_picker. sp)\n\n"
" [Online kleur kiezer](http://www.w3schools.com/colors/colors_picker. sp)\n\n"
" Voorbeelden:\n"
" `[p]editrole colour \"The Transistor\" #ff0000`\n"
" `[p]editrole colour Test #ff9900`\n"
@@ -126,7 +126,7 @@ msgstr "\n"
#: redbot/cogs/admin/admin.py:275
msgid "{author} ({author.id}) changed the colour of role '{role.name}'"
msgstr ""
msgstr "{author} ({author.id}) heeft de kleur van rol '{role.name} ' gewijzigd"
#: redbot/cogs/admin/admin.py:294 redbot/cogs/admin/admin.py:327
msgid "Done."
@@ -149,7 +149,7 @@ msgstr "\n"
#: redbot/cogs/admin/admin.py:308
msgid "{author} ({author.id}) changed the name of role '{old_name}' to '{name}'"
msgstr ""
msgstr "{author} ({author.id}) heeft de naam van rol '{old_name}' veranderd naar '{name}'"
#: redbot/cogs/admin/admin.py:332
#, docstring
@@ -181,7 +181,7 @@ msgstr "Verander hoe aankondigingen worden verzonden in deze gilde."
#: redbot/cogs/admin/admin.py:367
#, docstring
msgid "Change the channel where the bot will send announcements."
msgstr "Bewerk het kanaal waar deze bot aankondigingen maakt."
msgstr "Bewerk het kanaal waar de bot aankondigingen maakt."
#: redbot/cogs/admin/admin.py:370
msgid "The announcement channel has been set to {channel.mention}"
@@ -361,5 +361,5 @@ msgstr "De rol \"{role_name}\" is geen geldige zelfrol."
#: redbot/cogs/admin/converters.py:36
msgid "This selfrole has more than one case insensitive match. Please ask a moderator to resolve the ambiguity, or use the role ID to reference the role."
msgstr ""
msgstr "Deze zelfrol heeft meer dan één hoofdlettergevoelig resultaat. Vraag een moderator om het dubbele resultaat op te lossen of gebruik de rol-ID om te verwijzen naar de rol."
+1 -1
View File
@@ -21,7 +21,7 @@ msgstr "Tentei fazer uma coisa que o Discord negou permissão para fazer. O coma
#: redbot/cogs/admin/admin.py:25
msgid "I can not give {role.name} to {member.display_name} because that role is higher than or equal to my highest role in the Discord hierarchy."
msgstr "Eu não posso dar {role.name} a {member.display_name} porque esse cargo é igual ou maior a meu cargo na hierarquia do Discord."
msgstr "Eu não posso dar {role.name} a {member.display_name} porque esse cargo é igual ou maior ao meu cargo na hierarquia do Discord."
#: redbot/cogs/admin/admin.py:31
msgid "I can not remove {role.name} from {member.display_name} because that role is higher than or equal to my highest role in the Discord hierarchy."
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
"X-Crowdin-Project: red-discordbot\n"
"X-Crowdin-Project-ID: 289505\n"
"X-Crowdin-Language: sl\n"
+1 -1
View File
@@ -361,5 +361,5 @@ msgstr "Rollen \"{role_name}\" är inte en giltig självroll."
#: redbot/cogs/admin/converters.py:36
msgid "This selfrole has more than one case insensitive match. Please ask a moderator to resolve the ambiguity, or use the role ID to reference the role."
msgstr ""
msgstr ".."
+101 -60
View File
@@ -21,56 +21,56 @@ msgstr "Я намагався зробити щось, на що Discord від
#: redbot/cogs/admin/admin.py:25
msgid "I can not give {role.name} to {member.display_name} because that role is higher than or equal to my highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу надати роль {role.name} {member.display_name}, тому що ця роль є вищою за мою найвищу роль."
#: redbot/cogs/admin/admin.py:31
msgid "I can not remove {role.name} from {member.display_name} because that role is higher than or equal to my highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу прибрати {role.name} у {member.display_name}, тому що ця роль є вищою за мою найвищу роль."
#: redbot/cogs/admin/admin.py:37
msgid "I can not edit {role.name} because that role is higher than my or equal to highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу змінити {role.name}, тому що ця роль є вищою ніж моя найвища роль."
#: redbot/cogs/admin/admin.py:43
msgid "I can not let you give {role.name} to {member.display_name} because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу надати {role.name} {member.display_name}, тому що ця роль вища за вашу найвищу роль."
#: redbot/cogs/admin/admin.py:49
msgid "I can not let you remove {role.name} from {member.display_name} because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу прибрати {role.name} у {member.display_name}, тому що ця роль є вищою за вашу найвищу роль."
#: redbot/cogs/admin/admin.py:55
msgid "I can not let you edit {role.name} because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
msgstr "Я неможу дозволити вам редагувати {role.name}, тому що ця роль вища за вашу найвищу роль."
#: redbot/cogs/admin/admin.py:61
msgid "I need the \"Manage Roles\" permission to do that."
msgstr ""
msgstr "Мені потрібен дозвіл \"Керувати ролями\", щоб це зробити."
#: redbot/cogs/admin/admin.py:63
msgid "I am already announcing something. If you would like to make a different announcement please use `{prefix}announce cancel` first."
msgstr ""
msgstr "Я вже щось оголошую. Якщо ви хочете зробити інше оголошення, будь ласка, спочатку використайте '{prefix}announce cancel`."
#: redbot/cogs/admin/admin.py:73
#, docstring
msgid "A collection of server administration utilities."
msgstr ""
msgstr "Колекція утилітів адміністрації сервера."
#: redbot/cogs/admin/admin.py:159
msgid "{member.display_name} already has the role {role.name}."
msgstr ""
msgstr "{member.display_name} вже має роль {role.name}."
#: redbot/cogs/admin/admin.py:180
msgid "I successfully added {role.name} to {member.display_name}"
msgstr ""
msgstr "Успішно додав(ла) {role.name} до {member.display_name}"
#: redbot/cogs/admin/admin.py:190
msgid "{member.display_name} does not have the role {role.name}."
msgstr ""
msgstr "{member.display_name} не має ролі {role.name}."
#: redbot/cogs/admin/admin.py:211
msgid "I successfully removed {role.name} from {member.display_name}"
msgstr ""
msgstr "Я успішно прибрав(ла) {role.name} у {member.display_name}"
#: redbot/cogs/admin/admin.py:226
#, docstring
@@ -79,7 +79,11 @@ msgid "\n"
" Use double quotes if the role contains spaces.\n"
" If user is left blank it defaults to the author of the command.\n"
" "
msgstr ""
msgstr "\n"
" Додати роль до користувача\n\n"
" Використовуйте подвійні лапки, якщо роль має пробіли.\n"
" Якщо користувач не буде вказаним, то за замовчуванням роль буде надана автору команди.\n"
" "
#: redbot/cogs/admin/admin.py:244
#, docstring
@@ -88,12 +92,16 @@ msgid "\n"
" Use double quotes if the role contains spaces.\n"
" If user is left blank it defaults to the author of the command.\n"
" "
msgstr ""
msgstr "\n"
" Додати роль до користувача\n\n"
" Використовуйте подвійні лапки, якщо роль має пробіли.\n"
" Якщо користувач не буде вказаним, то за замовчуванням роль буде надана автору команди.\n"
" "
#: redbot/cogs/admin/admin.py:256
#, docstring
msgid "Edit role settings."
msgstr ""
msgstr "Зміна налаштувань ролі."
#: redbot/cogs/admin/admin.py:263
#, docstring
@@ -106,15 +114,23 @@ msgid "\n"
" `[p]editrole colour \"The Transistor\" #ff0000`\n"
" `[p]editrole colour Test #ff9900`\n"
" "
msgstr ""
msgstr "\n"
" Редагування кольору ролі.\n\n"
" Використовуйте подвійні лапки, якщо роль містить пробіли.\n"
" Колір повинен бути в шістнадцятковому форматі.\n"
" [Онлайн-палітра кольорів](http://www.w3schools.com/colors/colors_picker.asp)\n\n"
" Приклади:\n"
" `[p]editrole colour \"The Transistor\" #ff0000`\n"
" `[p]editrole colour Test #ff9900`\n"
" "
#: redbot/cogs/admin/admin.py:275
msgid "{author} ({author.id}) changed the colour of role '{role.name}'"
msgstr ""
msgstr "{author} ({author.id}) змінив колір ролі '{role.name}'"
#: redbot/cogs/admin/admin.py:294 redbot/cogs/admin/admin.py:327
msgid "Done."
msgstr ""
msgstr "Готово."
#: redbot/cogs/admin/admin.py:298
#, docstring
@@ -124,52 +140,57 @@ msgid "\n"
" Example:\n"
" `[p]editrole name \"The Transistor\" Test`\n"
" "
msgstr ""
msgstr "\n"
" Редагування назви ролі.\n\n"
" Використовуйте подвійні лапки, якщо роль або ім'я містять пробіли.\n\n"
" Наприклад:\n"
" `[p]editrole name \"The Transistor\" Test`\n"
" "
#: redbot/cogs/admin/admin.py:308
msgid "{author} ({author.id}) changed the name of role '{old_name}' to '{name}'"
msgstr ""
msgstr "{author} ({author.id}) змінив ім'я ролі '{old_name}' на '{name}'"
#: redbot/cogs/admin/admin.py:332
#, docstring
msgid "Announce a message to all servers the bot is in."
msgstr ""
msgstr "Оголосити повідомлення в усіх серверах, де знаходиться бот."
#: redbot/cogs/admin/admin.py:339
msgid "The announcement has begun."
msgstr ""
msgstr "Оголошення почалося."
#: redbot/cogs/admin/admin.py:346
#, docstring
msgid "Cancel a running announce."
msgstr ""
msgstr "Відмінити запущене оголошення."
#: redbot/cogs/admin/admin.py:348
msgid "There is no currently running announcement."
msgstr ""
msgstr "На даний момент немає оголошення."
#: redbot/cogs/admin/admin.py:351
msgid "The current announcement has been cancelled."
msgstr ""
msgstr "Поточне оголошення було скасовано."
#: redbot/cogs/admin/admin.py:357
#, docstring
msgid "Change how announcements are sent in this guild."
msgstr ""
msgstr "Змінити спосіб надсилання оголошень у цю спільноту."
#: redbot/cogs/admin/admin.py:367
#, docstring
msgid "Change the channel where the bot will send announcements."
msgstr ""
msgstr "Змініть канал, в якому бот буде надсилати оголошення."
#: redbot/cogs/admin/admin.py:370
msgid "The announcement channel has been set to {channel.mention}"
msgstr ""
msgstr "Канал для оголошень встановлений до {channel.mention}"
#: redbot/cogs/admin/admin.py:375
#, docstring
msgid "Unsets the channel for announcements."
msgstr ""
msgstr "Прибирає канал для оголошень."
#: redbot/cogs/admin/admin.py:400
#, docstring
@@ -178,7 +199,10 @@ msgid "\n"
" Server admins must have configured the role as user settable.\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
msgstr "\n"
" Додайте чи приберіть собі селф-ролі\n\n"
" Адміністратори серверу повинні були налаштувати роль як \"користувацьку\"\n"
" ПОМІТКА: Ролі чутливі до реєстру "
#: redbot/cogs/admin/admin.py:413
#, docstring
@@ -187,7 +211,11 @@ msgid "\n"
" Server admins must have configured the role as user settable.\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
msgstr "\n"
" Додайте собі роль себе.\n\n"
" Адміністратори сервера повинні налаштувати роль як таку, що може бути встановлена користувачем.\n"
" ПРИМІТКА: У ролі враховується регістр символів!\n"
" "
#: redbot/cogs/admin/admin.py:424
#, docstring
@@ -196,24 +224,31 @@ msgid "\n"
" Server admins must have configured the role as user settable.\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
msgstr "\n"
" Приберіть собі селф-роль.\n\n"
" Адміністратори сервера повинні налаштувати роль як таку, що може бути встановлена користувачем.\n"
" ПРИМІТКА: У ролі враховується регістр символів!\n"
" "
#: redbot/cogs/admin/admin.py:435
#, docstring
msgid "\n"
" Lists all available selfroles.\n"
" "
msgstr ""
msgstr "\n"
" Перераховує усі доступні селф-ролі.\n"
" "
#: redbot/cogs/admin/admin.py:445
msgid "Available Selfroles:\n"
"{selfroles}"
msgstr ""
msgstr "Доступні селф-ролі:\n"
"{selfroles}"
#: redbot/cogs/admin/admin.py:451
#, docstring
msgid "Manage selfroles."
msgstr ""
msgstr "Керувати селф-ролями."
#: redbot/cogs/admin/admin.py:456
#, docstring
@@ -221,23 +256,26 @@ msgid "\n"
" Add a role, or a selection of roles, to the list of available selfroles.\n\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
msgstr "\n"
" Додайте роль або вибір ролей до списку доступних саморолей.\n\n"
" ПРИМІТКА: У ролі враховується регістр символів!\n"
" "
#: redbot/cogs/admin/admin.py:465
msgid "I cannot let you add {role.name} as a selfrole because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу дозволити вам додати {role.name} як селф-роль, бо ця роль є вищою за вашу найвищу роль."
#: redbot/cogs/admin/admin.py:475
msgid "The role \"{role.name}\" is already a selfrole."
msgstr ""
msgstr "Роль \"{role.name}\" вже є селф-роллю."
#: redbot/cogs/admin/admin.py:481
msgid "Added {count} selfroles."
msgstr ""
msgstr "Додано {count} селф-ролей."
#: redbot/cogs/admin/admin.py:483
msgid "Added 1 selfrole."
msgstr ""
msgstr "Додано 1 селф-роль."
#: redbot/cogs/admin/admin.py:489
#, docstring
@@ -245,79 +283,82 @@ msgid "\n"
" Remove a role, or a selection of roles, from the list of available selfroles.\n\n"
" NOTE: The role is case sensitive!\n"
" "
msgstr ""
msgstr "\n"
" Видалити роль або вибір ролей зі списку доступних саморолей.\n\n"
" ПРИМІТКА: У ролі враховується регістр символів!\n"
" "
#: redbot/cogs/admin/admin.py:498
msgid "I cannot let you remove {role.name} from being a selfrole because that role is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу дозволити вам прибрати {role.name} зі списку селф-ролей, бо ця роль є вищою за вашу найвищу роль."
#: redbot/cogs/admin/admin.py:508
msgid "Removed {count} selfroles."
msgstr ""
msgstr "Вилучено {count} селф-ролей."
#: redbot/cogs/admin/admin.py:510
msgid "Removed 1 selfrole."
msgstr ""
msgstr "Прибрано 1 селф-роль."
#: redbot/cogs/admin/admin.py:516
#, docstring
msgid "Clear the list of available selfroles for this server."
msgstr ""
msgstr "Очистити список доступних селф-ролей для цього сервера."
#: redbot/cogs/admin/admin.py:520
msgid "There are currently no selfroles."
msgstr ""
msgstr "Наразі немає селф-ролей."
#: redbot/cogs/admin/admin.py:523
msgid "Are you sure you want to clear this server's selfrole list?"
msgstr ""
msgstr "Ви впевнені, що хочете очистити список селф-ролей цього сервера?"
#: redbot/cogs/admin/admin.py:529
msgid "You took too long to respond."
msgstr ""
msgstr "Ви зайняли занадто довго часу, щоб відповісти."
#: redbot/cogs/admin/admin.py:538
msgid "I cannot clear the selfroles because the selfrole '{role.name}' is higher than or equal to your highest role in the Discord hierarchy."
msgstr ""
msgstr "Я не можу прибрати селф-ролі, тому що селф-роль '{role.name}' вища аніж усі ваші ролі."
#: redbot/cogs/admin/admin.py:544
msgid "Selfrole list cleared."
msgstr ""
msgstr "Список з селф-ролями був очищений."
#: redbot/cogs/admin/admin.py:546
msgid "No changes have been made."
msgstr ""
msgstr "Жодних змін не внесено."
#: redbot/cogs/admin/admin.py:551
#, docstring
msgid "Lock a bot to its current servers only."
msgstr ""
msgstr "Заблокувати бота тільки на його поточних серверах."
#: redbot/cogs/admin/admin.py:556
msgid "The bot is no longer serverlocked."
msgstr ""
msgstr "Бот більше не прив'язаний до сервера."
#: redbot/cogs/admin/admin.py:558
msgid "The bot is now serverlocked."
msgstr ""
msgstr "Бот тепер заблокований сервером."
#: redbot/cogs/admin/announcer.py:68
msgid "I could not announce to the following server: "
msgstr ""
msgstr "Не вдалося зробити оголошення у наступній спільноті: "
#: redbot/cogs/admin/announcer.py:70
msgid "I could not announce to the following servers: "
msgstr ""
msgstr "Не вдалося зробити оголошення у наступних спільнотах: "
#: redbot/cogs/admin/converters.py:13
msgid "The Admin cog is not loaded."
msgstr ""
msgstr "Адмін-ког не завантажено."
#: redbot/cogs/admin/converters.py:30
msgid "The role \"{role_name}\" is not a valid selfrole."
msgstr ""
msgstr "Роль \"{role_name}\" не є дійсною селф-роллю."
#: redbot/cogs/admin/converters.py:36
msgid "This selfrole has more than one case insensitive match. Please ask a moderator to resolve the ambiguity, or use the role ID to reference the role."
msgstr ""
msgstr "Ця селф-роль має більше ніж один збіг. Будь ласка, попросіть модератора вирішити цю неоднозначність, або використайте ID ролі для уточнення ролі."
+4
View File
@@ -59,7 +59,11 @@ class AliasEntry:
extra = []
while not view.eof:
prev = view.index
try:
word = view.get_quoted_word()
except discord.ext.commands.errors.UnexpectedQuoteError:
view.skip_ws()
continue
if len(word) < view.index - prev:
word = "".join((view.buffer[prev], word, view.buffer[view.index - 1]))
extra.append(word.strip(" "))
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"MIME-Version: 1.0\n"
@@ -179,11 +179,11 @@ msgstr "قائمة الأسماء المستعارة العامة المتاحة
msgid "There are no global aliases."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr "يجب أن يتم تحديد الحجج برقم."
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr "يجب أن تكون الحجج متتابعة. الحجج المفقودة: "
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"MIME-Version: 1.0\n"
@@ -179,11 +179,11 @@ msgstr ""
msgid "There are no global aliases."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr ""
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"MIME-Version: 1.0\n"
@@ -187,11 +187,11 @@ msgstr "Seznam dostupných globálních aliasů pro tohoto bota."
msgid "There are no global aliases."
msgstr "Neexistují žádné globální aliasy."
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr "Argumenty musí být zadány s číslem."
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr "Argumenty musí být sekvenční. Chybějící argumenty: "
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"MIME-Version: 1.0\n"
@@ -179,11 +179,11 @@ msgstr ""
msgid "There are no global aliases."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr ""
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
@@ -187,11 +187,11 @@ msgstr "Zeige die verfügbaren globalen Alias von diesem Bot."
msgid "There are no global aliases."
msgstr "Es gibt keine globalen Aliasse."
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr "Die Argumente müssen mit einer Nummer angegeben werden."
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr "Argumente müssen sequentiell sein. Fehlende Argumente: "
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
@@ -186,11 +186,11 @@ msgstr "Muestra una lista de los alias globales disponibles en este bot."
msgid "There are no global aliases."
msgstr "No hay alias globales."
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr "Los argumentos deben especificarse con un número."
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr "Los argumentos deben ser secuenciales. Faltan argumentos: "
+189
View File
@@ -0,0 +1,189 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Estonian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: red-discordbot\n"
"X-Crowdin-Project-ID: 289505\n"
"X-Crowdin-Language: et\n"
"X-Crowdin-File-ID: 8\n"
"Language: et_EE\n"
#: redbot/cogs/alias/alias.py:35
#, docstring
msgid "Create aliases for commands.\n\n"
" Aliases are alternative names/shortcuts for commands. They\n"
" can act as both a lambda (storing arguments for repeated use)\n"
" or as simply a shortcut to saying \"x y z\".\n\n"
" When run, aliases will accept any additional arguments\n"
" and append them to the stored alias.\n"
" "
msgstr ""
#: redbot/cogs/alias/alias.py:190
msgid "Aliases:\n"
msgstr ""
#: redbot/cogs/alias/alias.py:192
msgid "\n\n"
"Page {page}/{total}"
msgstr ""
#: redbot/cogs/alias/alias.py:202
#, docstring
msgid "Manage command aliases."
msgstr ""
#: redbot/cogs/alias/alias.py:207
#, docstring
msgid "Manage global aliases."
msgstr ""
#: redbot/cogs/alias/alias.py:214
#, docstring
msgid "Add an alias for a command."
msgstr ""
#: redbot/cogs/alias/alias.py:219
msgid "You attempted to create a new alias with the name {name} but that name is already a command on this bot."
msgstr ""
#: redbot/cogs/alias/alias.py:230
msgid "You attempted to create a new alias with the name {name} but that alias already exists."
msgstr ""
#: redbot/cogs/alias/alias.py:241
msgid "You attempted to create a new alias with the name {name} but that name is an invalid alias name. Alias names may not contain spaces."
msgstr ""
#: redbot/cogs/alias/alias.py:253 redbot/cogs/alias/alias.py:312
msgid "You attempted to create a new alias for a command that doesn't exist."
msgstr ""
#: redbot/cogs/alias/alias.py:267
msgid "A new alias with the trigger `{name}` has been created."
msgstr ""
#: redbot/cogs/alias/alias.py:273
#, docstring
msgid "Add a global alias for a command."
msgstr ""
#: redbot/cogs/alias/alias.py:278
msgid "You attempted to create a new global alias with the name {name} but that name is already a command on this bot."
msgstr ""
#: redbot/cogs/alias/alias.py:289
msgid "You attempted to create a new global alias with the name {name} but that alias already exists."
msgstr ""
#: redbot/cogs/alias/alias.py:300
msgid "You attempted to create a new global alias with the name {name} but that name is an invalid alias name. Alias names may not contain spaces."
msgstr ""
#: redbot/cogs/alias/alias.py:323
msgid "A new global alias with the trigger `{name}` has been created."
msgstr ""
#: redbot/cogs/alias/alias.py:332
#, docstring
msgid "Edit an existing alias in this server."
msgstr ""
#: redbot/cogs/alias/alias.py:337 redbot/cogs/alias/alias.py:372
msgid "The alias with the name {name} does not exist."
msgstr ""
#: redbot/cogs/alias/alias.py:343 redbot/cogs/alias/alias.py:378
msgid "You attempted to edit an alias to a command that doesn't exist."
msgstr ""
#: redbot/cogs/alias/alias.py:352 redbot/cogs/alias/alias.py:385
msgid "The alias with the trigger `{name}` has been edited successfully."
msgstr ""
#: redbot/cogs/alias/alias.py:359 redbot/cogs/alias/alias.py:392
msgid "Alias with the name `{name}` was not found."
msgstr ""
#: redbot/cogs/alias/alias.py:367
#, docstring
msgid "Edit an existing global alias."
msgstr ""
#: redbot/cogs/alias/alias.py:399
#, docstring
msgid "Try to execute help for the base command of the alias."
msgstr ""
#: redbot/cogs/alias/alias.py:404
msgid "No such alias exists."
msgstr ""
#: redbot/cogs/alias/alias.py:408
#, docstring
msgid "Show what command the alias executes."
msgstr ""
#: redbot/cogs/alias/alias.py:413
msgid "The `{alias_name}` alias will execute the command `{command}`"
msgstr ""
#: redbot/cogs/alias/alias.py:418
msgid "There is no alias with the name `{name}`"
msgstr ""
#: redbot/cogs/alias/alias.py:424
#, docstring
msgid "Delete an existing alias on this server."
msgstr ""
#: redbot/cogs/alias/alias.py:426 redbot/cogs/alias/alias.py:458
msgid "There are no aliases on this server."
msgstr ""
#: redbot/cogs/alias/alias.py:431 redbot/cogs/alias/alias.py:446
msgid "Alias with the name `{name}` was successfully deleted."
msgstr ""
#: redbot/cogs/alias/alias.py:434 redbot/cogs/alias/alias.py:449
msgid "Alias with name `{name}` was not found."
msgstr ""
#: redbot/cogs/alias/alias.py:439
#, docstring
msgid "Delete an existing global alias."
msgstr ""
#: redbot/cogs/alias/alias.py:441
msgid "There are no global aliases on this bot."
msgstr ""
#: redbot/cogs/alias/alias.py:455
#, docstring
msgid "List the available aliases on this server."
msgstr ""
#: redbot/cogs/alias/alias.py:464
#, docstring
msgid "List the available global aliases on this bot."
msgstr ""
#: redbot/cogs/alias/alias.py:467
msgid "There are no global aliases."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr ""
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"MIME-Version: 1.0\n"
@@ -180,11 +180,11 @@ msgstr "Listaa saatavilla olevat globaalit aliakset tässä botissa."
msgid "There are no global aliases."
msgstr "Tässä botissa ei ole globaaleja aliaksia."
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr "Argumentit on määriteltävä numerolla."
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr "Argumenttien on oltava peräkkäisiä. Puuttuvia argumentteja: "
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
@@ -185,11 +185,11 @@ msgstr "Liste les alias globaux disponibles sur ce bot."
msgid "There are no global aliases."
msgstr "Il n'y a aucun alias global."
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr "Les arguments doivent être spécifiés avec un nombre."
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr "Les arguments doivent être séquentiels. Arguments manquants : "
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"MIME-Version: 1.0\n"
@@ -179,11 +179,11 @@ msgstr ""
msgid "There are no global aliases."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr ""
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"MIME-Version: 1.0\n"
@@ -186,11 +186,11 @@ msgstr "Navedite dostupne globalne nadimke na ovom botu."
msgid "There are no global aliases."
msgstr "Nema globalnih nadimaka."
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr "Argumenti se moraju navesti brojem."
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr "Argumenti moraju biti uzastopni. Nedostajući argumenti: "
+3 -3
View File
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: red-discordbot\n"
"POT-Creation-Date: 2024-01-09 00:35+0000\n"
"POT-Creation-Date: 2025-08-09 20:56+0000\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"MIME-Version: 1.0\n"
@@ -180,11 +180,11 @@ msgstr "A boton elérhető globális aliaszok listázása."
msgid "There are no global aliases."
msgstr "Nincsenek globális aliaszok."
#: redbot/cogs/alias/alias_entry.py:196
#: redbot/cogs/alias/alias_entry.py:200
msgid "Arguments must be specified with a number."
msgstr ""
#: redbot/cogs/alias/alias_entry.py:203
#: redbot/cogs/alias/alias_entry.py:207
msgid "Arguments must be sequential. Missing arguments: "
msgstr ""

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