illest.py 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. illest_py-2.1.0/LICENSE +24 -0
  2. illest_py-2.1.0/PKG-INFO +140 -0
  3. illest_py-2.1.0/README.md +103 -0
  4. illest_py-2.1.0/illest/__init__.py +163 -0
  5. illest_py-2.1.0/illest/_limits.py +240 -0
  6. illest_py-2.1.0/illest/_version.py +3 -0
  7. illest_py-2.1.0/illest/abc.py +253 -0
  8. illest_py-2.1.0/illest/activity.py +100 -0
  9. illest_py-2.1.0/illest/app_commands/__init__.py +76 -0
  10. illest_py-2.1.0/illest/app_commands/checks.py +161 -0
  11. illest_py-2.1.0/illest/app_commands/commands.py +807 -0
  12. illest_py-2.1.0/illest/app_commands/errors.py +130 -0
  13. illest_py-2.1.0/illest/app_commands/namespace.py +31 -0
  14. illest_py-2.1.0/illest/app_commands/tree.py +341 -0
  15. illest_py-2.1.0/illest/attachment.py +161 -0
  16. illest_py-2.1.0/illest/auth.py +286 -0
  17. illest_py-2.1.0/illest/buttons.py +160 -0
  18. illest_py-2.1.0/illest/client.py +2156 -0
  19. illest_py-2.1.0/illest/colour.py +156 -0
  20. illest_py-2.1.0/illest/content.py +428 -0
  21. illest_py-2.1.0/illest/embeds.py +340 -0
  22. illest_py-2.1.0/illest/enums.py +249 -0
  23. illest_py-2.1.0/illest/envelope.py +97 -0
  24. illest_py-2.1.0/illest/errors.py +161 -0
  25. illest_py-2.1.0/illest/ext/__init__.py +1 -0
  26. illest_py-2.1.0/illest/ext/commands/__init__.py +127 -0
  27. illest_py-2.1.0/illest/ext/commands/bot.py +538 -0
  28. illest_py-2.1.0/illest/ext/commands/cog.py +174 -0
  29. illest_py-2.1.0/illest/ext/commands/converter.py +412 -0
  30. illest_py-2.1.0/illest/ext/commands/core.py +1164 -0
  31. illest_py-2.1.0/illest/ext/commands/errors.py +343 -0
  32. illest_py-2.1.0/illest/ext/commands/flags.py +156 -0
  33. illest_py-2.1.0/illest/ext/commands/help.py +382 -0
  34. illest_py-2.1.0/illest/ext/commands/hybrid.py +153 -0
  35. illest_py-2.1.0/illest/ext/tasks/__init__.py +391 -0
  36. illest_py-2.1.0/illest/file.py +59 -0
  37. illest_py-2.1.0/illest/gateway.py +461 -0
  38. illest_py-2.1.0/illest/intents.py +190 -0
  39. illest_py-2.1.0/illest/interactions.py +546 -0
  40. illest_py-2.1.0/illest/media.py +182 -0
  41. illest_py-2.1.0/illest/mentions.py +41 -0
  42. illest_py-2.1.0/illest/mls.py +843 -0
  43. illest_py-2.1.0/illest/models.py +2558 -0
  44. illest_py-2.1.0/illest/object.py +70 -0
  45. illest_py-2.1.0/illest/permissions.py +318 -0
  46. illest_py-2.1.0/illest/pins.py +152 -0
  47. illest_py-2.1.0/illest/py.typed +0 -0
  48. illest_py-2.1.0/illest/ratelimit.py +113 -0
  49. illest_py-2.1.0/illest/ui/__init__.py +38 -0
  50. illest_py-2.1.0/illest/ui/button.py +126 -0
  51. illest_py-2.1.0/illest/ui/components.py +49 -0
  52. illest_py-2.1.0/illest/ui/item.py +69 -0
  53. illest_py-2.1.0/illest/ui/modal.py +180 -0
  54. illest_py-2.1.0/illest/ui/select.py +286 -0
  55. illest_py-2.1.0/illest/ui/view.py +249 -0
  56. illest_py-2.1.0/illest/utils.py +217 -0
  57. illest_py-2.1.0/illest.py.egg-info/PKG-INFO +140 -0
  58. illest_py-2.1.0/illest.py.egg-info/SOURCES.txt +79 -0
  59. illest_py-2.1.0/illest.py.egg-info/dependency_links.txt +1 -0
  60. illest_py-2.1.0/illest.py.egg-info/requires.txt +12 -0
  61. illest_py-2.1.0/illest.py.egg-info/top_level.txt +1 -0
  62. illest_py-2.1.0/pyproject.toml +60 -0
  63. illest_py-2.1.0/setup.cfg +4 -0
  64. illest_py-2.1.0/tests/test_attachment.py +294 -0
  65. illest_py-2.1.0/tests/test_buttons.py +244 -0
  66. illest_py-2.1.0/tests/test_catch_up_by_itself.py +388 -0
  67. illest_py-2.1.0/tests/test_commands.py +130 -0
  68. illest_py-2.1.0/tests/test_commands_framework.py +346 -0
  69. illest_py-2.1.0/tests/test_content.py +101 -0
  70. illest_py-2.1.0/tests/test_discord_shape.py +684 -0
  71. illest_py-2.1.0/tests/test_envelope.py +42 -0
  72. illest_py-2.1.0/tests/test_events.py +193 -0
  73. illest_py-2.1.0/tests/test_gateway.py +295 -0
  74. illest_py-2.1.0/tests/test_group_spelling.py +49 -0
  75. illest_py-2.1.0/tests/test_limits.py +167 -0
  76. illest_py-2.1.0/tests/test_listed_admission.py +237 -0
  77. illest_py-2.1.0/tests/test_missed_messages.py +401 -0
  78. illest_py-2.1.0/tests/test_pins.py +94 -0
  79. illest_py-2.1.0/tests/test_pins_in_use.py +306 -0
  80. illest_py-2.1.0/tests/test_secret_files.py +86 -0
  81. illest_py-2.1.0/tests/test_sender.py +221 -0
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VERAKS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ The ILLEST name and marks are trademarks of VERAKS and are not licensed by
24
+ this notice.
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: illest.py
3
+ Version: 2.1.0
4
+ Summary: Write bots for ILLEST the way you write them for discord: discord.py's shape, end-to-end encrypted.
5
+ Author: VERAKS
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://illest.world
8
+ Keywords: illest,bot,discord.py,chat,e2ee,mls
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Framework :: AsyncIO
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Communications :: Chat
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: websockets>=12.0
27
+ Requires-Dist: cryptography>=42.0
28
+ Requires-Dist: illest-crypto<3,>=2.1
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.0; extra == "dev"
31
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs>=1.6; extra == "docs"
34
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
35
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
36
+ Dynamic: license-file
37
+
38
+ # illest.py
39
+
40
+ Write bots for [ILLEST](https://illest.world) the way you write them for discord. `illest.py` has discord.py's shape: the same
41
+ classes, decorators, events and arguments. Code written for discord.py 2.x usually runs once its import is changed.
42
+
43
+ ```bash
44
+ pip install illest.py
45
+ ```
46
+
47
+ Python 3.10 or newer. The encryption engine, `illest-crypto`, comes with it as a compiled wheel for Windows and Linux (x86-64
48
+ and ARM64).
49
+
50
+ ## A bot in twenty lines
51
+
52
+ ```python
53
+ import illest
54
+ from illest import app_commands
55
+ from illest.ext import commands
56
+
57
+ bot = commands.Bot(command_prefix="!", intents=illest.Intents.default())
58
+
59
+
60
+ @bot.event
61
+ async def setup_hook():
62
+ await bot.tree.sync() # registers the slash commands below
63
+
64
+
65
+ @bot.event
66
+ async def on_ready():
67
+ print(f"signed in as {bot.user}")
68
+
69
+
70
+ @bot.command()
71
+ async def ping(ctx: commands.Context):
72
+ await ctx.reply("pong")
73
+
74
+
75
+ @bot.tree.command(description="Roll a die")
76
+ @app_commands.describe(sides="How many sides it has")
77
+ async def roll(interaction: illest.Interaction, sides: app_commands.Range[int, 2, 100] = 6):
78
+ import random
79
+ await interaction.response.send_message(f"{random.randint(1, sides)}")
80
+
81
+
82
+ bot.run("illb_...")
83
+ ```
84
+
85
+ Make the bot and its token in ILLEST under **Settings → Developers**. Open the bot there, copy its invite link from
86
+ **Installation**, and add it to a server you manage.
87
+
88
+ To run against your own server instead of `chat.illest.world`, set `ILLEST_GATEWAY_URL` (for example
89
+ `ws://localhost:8766/gateway`) or pass `server_url=` to the client.
90
+
91
+ ## What you get
92
+
93
+ | discord.py | illest.py |
94
+ |---|---|
95
+ | `Client`, `commands.Bot`, `Intents`, `@bot.event`, `wait_for`, `setup_hook` | the same |
96
+ | `ext.commands`: commands, groups, cogs, extensions, checks, cooldowns, converters, `Greedy`, `Range`, flags, the help command, hybrid commands | the same |
97
+ | `app_commands`: `CommandTree`, `@tree.command`, `Group`, `describe`, `choices`, `autocomplete`, `rename`, `Range`, `Transform`, context menus, `default_permissions`, `guild_only`, checks and cooldowns, `tree.sync(guild=...)` | the same |
98
+ | `Interaction`: `response.send_message`, `defer` (with "thinking"), `edit_message`, `send_modal`, `followup`, `original_response` | the same |
99
+ | `ui`: `View`, `Button`, `Select`, `UserSelect`, `RoleSelect`, `ChannelSelect`, `MentionableSelect`, `Modal`, `TextInput`, persistent views (`timeout=None` and `custom_id`, `bot.add_view`) | the same |
100
+ | `ext.tasks.loop` with `before_loop`, `after_loop`, `change_interval`, times of day | the same |
101
+ | `Embed`, `File`, `AllowedMentions`, `Colour`, `Permissions`, `PermissionOverwrite`, `utils.get/find/format_dt/oauth_url` | the same |
102
+ | Moderation: `Member.ban/kick/timeout/add_roles/remove_roles/edit`, `Guild.unban/bans/audit_logs`, `TextChannel.purge`, creating and editing channels, categories, roles and invites | the same, behind the bot's scopes and the server's roles |
103
+ | Rate limits, reconnects, `run()`'s logging set-up | handled for you, as discord.py does |
104
+
105
+ ## Where it differs, and why
106
+
107
+ ILLEST is end-to-end encrypted: the server stores ciphertext and holds no keys. Most differences follow from that, and each is
108
+ deliberate. The full list, with what to write instead, is in [the documentation](docs/differences.md).
109
+
110
+ - **A bot is a device.** Its token enrols a device the first time it runs; the device key is kept under `~/.illest/bots/`. Keep
111
+ that folder: losing it is losing the device.
112
+ - **A bot reads from when it joined.** `channel.history()` and `fetch_message()` return the messages this bot holds, from when it
113
+ was let into the channel's group. There is nothing older to fetch: the server cannot read it either.
114
+ - **Commands, presses, picks and forms travel sealed**, as messages in the conversation that only the bot opens. The server never
115
+ sees which command was used or with what.
116
+ - **`ephemeral=True` is a rendering rule.** Every honest client shows the message to its one reader only; inside an encrypted
117
+ group nothing stronger exists, and the library does not pretend otherwise.
118
+ - **Who sent it is what their device proves.** `message.author_verified` and `interaction.sender_verified` say whether the
119
+ sender's own device vouched for the account. Authorise nothing on a sender that is not verified.
120
+ - **Permissions have discord's names and ILLEST's bits.** `illest.Permissions(ban_members=True).value` is the number to use in an
121
+ ILLEST invite link; a number from a discord permissions calculator means something else here.
122
+ - **Intents filter, scopes grant.** An intent decides which events reach your handlers; the bot's scopes (set by its owner under
123
+ Developers → Bot) decide what it may read and do. `Intents.default()` includes members and message content.
124
+ - **No voice.** A bot cannot join a call: calls are encrypted from each person's device. `VoiceChannel.connect()` raises.
125
+ - **No sharding.** One connection serves any number of servers; `AutoShardedBot` is `Bot`.
126
+ - **`tree.sync()` takes effect at once**, global commands included.
127
+
128
+ ## Examples
129
+
130
+ `examples/` holds runnable bots: [basic](examples/basic_bot.py) (events and prefix commands), [slash commands](examples/slash_commands.py)
131
+ (groups, choices, autocomplete, context menus), [views](examples/views.py) (buttons, select menus, a persistent view, a modal),
132
+ [moderation](examples/moderation.py) and [tasks and cogs](examples/tasks_and_cogs.py). Each reads its token from `ILLEST_TOKEN`.
133
+
134
+ ## Documentation
135
+
136
+ The API reference is built with MkDocs: `pip install "illest.py[docs]"`, then `mkdocs serve` in this folder.
137
+
138
+ ## Licence
139
+
140
+ MIT. `illest-crypto`, the compiled encryption engine it installs, is free to use and redistribute unmodified; see its own licence.
@@ -0,0 +1,103 @@
1
+ # illest.py
2
+
3
+ Write bots for [ILLEST](https://illest.world) the way you write them for discord. `illest.py` has discord.py's shape: the same
4
+ classes, decorators, events and arguments. Code written for discord.py 2.x usually runs once its import is changed.
5
+
6
+ ```bash
7
+ pip install illest.py
8
+ ```
9
+
10
+ Python 3.10 or newer. The encryption engine, `illest-crypto`, comes with it as a compiled wheel for Windows and Linux (x86-64
11
+ and ARM64).
12
+
13
+ ## A bot in twenty lines
14
+
15
+ ```python
16
+ import illest
17
+ from illest import app_commands
18
+ from illest.ext import commands
19
+
20
+ bot = commands.Bot(command_prefix="!", intents=illest.Intents.default())
21
+
22
+
23
+ @bot.event
24
+ async def setup_hook():
25
+ await bot.tree.sync() # registers the slash commands below
26
+
27
+
28
+ @bot.event
29
+ async def on_ready():
30
+ print(f"signed in as {bot.user}")
31
+
32
+
33
+ @bot.command()
34
+ async def ping(ctx: commands.Context):
35
+ await ctx.reply("pong")
36
+
37
+
38
+ @bot.tree.command(description="Roll a die")
39
+ @app_commands.describe(sides="How many sides it has")
40
+ async def roll(interaction: illest.Interaction, sides: app_commands.Range[int, 2, 100] = 6):
41
+ import random
42
+ await interaction.response.send_message(f"{random.randint(1, sides)}")
43
+
44
+
45
+ bot.run("illb_...")
46
+ ```
47
+
48
+ Make the bot and its token in ILLEST under **Settings → Developers**. Open the bot there, copy its invite link from
49
+ **Installation**, and add it to a server you manage.
50
+
51
+ To run against your own server instead of `chat.illest.world`, set `ILLEST_GATEWAY_URL` (for example
52
+ `ws://localhost:8766/gateway`) or pass `server_url=` to the client.
53
+
54
+ ## What you get
55
+
56
+ | discord.py | illest.py |
57
+ |---|---|
58
+ | `Client`, `commands.Bot`, `Intents`, `@bot.event`, `wait_for`, `setup_hook` | the same |
59
+ | `ext.commands`: commands, groups, cogs, extensions, checks, cooldowns, converters, `Greedy`, `Range`, flags, the help command, hybrid commands | the same |
60
+ | `app_commands`: `CommandTree`, `@tree.command`, `Group`, `describe`, `choices`, `autocomplete`, `rename`, `Range`, `Transform`, context menus, `default_permissions`, `guild_only`, checks and cooldowns, `tree.sync(guild=...)` | the same |
61
+ | `Interaction`: `response.send_message`, `defer` (with "thinking"), `edit_message`, `send_modal`, `followup`, `original_response` | the same |
62
+ | `ui`: `View`, `Button`, `Select`, `UserSelect`, `RoleSelect`, `ChannelSelect`, `MentionableSelect`, `Modal`, `TextInput`, persistent views (`timeout=None` and `custom_id`, `bot.add_view`) | the same |
63
+ | `ext.tasks.loop` with `before_loop`, `after_loop`, `change_interval`, times of day | the same |
64
+ | `Embed`, `File`, `AllowedMentions`, `Colour`, `Permissions`, `PermissionOverwrite`, `utils.get/find/format_dt/oauth_url` | the same |
65
+ | Moderation: `Member.ban/kick/timeout/add_roles/remove_roles/edit`, `Guild.unban/bans/audit_logs`, `TextChannel.purge`, creating and editing channels, categories, roles and invites | the same, behind the bot's scopes and the server's roles |
66
+ | Rate limits, reconnects, `run()`'s logging set-up | handled for you, as discord.py does |
67
+
68
+ ## Where it differs, and why
69
+
70
+ ILLEST is end-to-end encrypted: the server stores ciphertext and holds no keys. Most differences follow from that, and each is
71
+ deliberate. The full list, with what to write instead, is in [the documentation](docs/differences.md).
72
+
73
+ - **A bot is a device.** Its token enrols a device the first time it runs; the device key is kept under `~/.illest/bots/`. Keep
74
+ that folder: losing it is losing the device.
75
+ - **A bot reads from when it joined.** `channel.history()` and `fetch_message()` return the messages this bot holds, from when it
76
+ was let into the channel's group. There is nothing older to fetch: the server cannot read it either.
77
+ - **Commands, presses, picks and forms travel sealed**, as messages in the conversation that only the bot opens. The server never
78
+ sees which command was used or with what.
79
+ - **`ephemeral=True` is a rendering rule.** Every honest client shows the message to its one reader only; inside an encrypted
80
+ group nothing stronger exists, and the library does not pretend otherwise.
81
+ - **Who sent it is what their device proves.** `message.author_verified` and `interaction.sender_verified` say whether the
82
+ sender's own device vouched for the account. Authorise nothing on a sender that is not verified.
83
+ - **Permissions have discord's names and ILLEST's bits.** `illest.Permissions(ban_members=True).value` is the number to use in an
84
+ ILLEST invite link; a number from a discord permissions calculator means something else here.
85
+ - **Intents filter, scopes grant.** An intent decides which events reach your handlers; the bot's scopes (set by its owner under
86
+ Developers → Bot) decide what it may read and do. `Intents.default()` includes members and message content.
87
+ - **No voice.** A bot cannot join a call: calls are encrypted from each person's device. `VoiceChannel.connect()` raises.
88
+ - **No sharding.** One connection serves any number of servers; `AutoShardedBot` is `Bot`.
89
+ - **`tree.sync()` takes effect at once**, global commands included.
90
+
91
+ ## Examples
92
+
93
+ `examples/` holds runnable bots: [basic](examples/basic_bot.py) (events and prefix commands), [slash commands](examples/slash_commands.py)
94
+ (groups, choices, autocomplete, context menus), [views](examples/views.py) (buttons, select menus, a persistent view, a modal),
95
+ [moderation](examples/moderation.py) and [tasks and cogs](examples/tasks_and_cogs.py). Each reads its token from `ILLEST_TOKEN`.
96
+
97
+ ## Documentation
98
+
99
+ The API reference is built with MkDocs: `pip install "illest.py[docs]"`, then `mkdocs serve` in this folder.
100
+
101
+ ## Licence
102
+
103
+ MIT. `illest-crypto`, the compiled encryption engine it installs, is free to use and redistribute unmodified; see its own licence.
@@ -0,0 +1,163 @@
1
+ """illest: write a bot for this network the way you have always written bots.
2
+
3
+ from illest.ext import commands
4
+
5
+ bot = commands.Bot(command_prefix="/", token="illb_...")
6
+
7
+ @bot.command(name="ping")
8
+ async def ping(ctx):
9
+ await ctx.reply("pong")
10
+
11
+ bot.run()
12
+
13
+ ## What carries over from discord.py, unchanged
14
+
15
+ `Client`, `Intents`, `Embed`, `ButtonStyle`, `ui.Button`, `ext.commands.Bot`,
16
+ `Context`, `@bot.event`, `@bot.command()`, `ctx.send/reply`,
17
+ `message.reply`, `embed=`, `view=`, `ephemeral=True`. If your bot speaks
18
+ through those, it ports by changing one import and one token.
19
+
20
+ ## What is different, and why
21
+
22
+ This network is end-to-end encrypted. The server stores ciphertext and holds
23
+ no keys, so:
24
+
25
+ * **A bot is a real device.** Its token ENROLs it; its device key (generated
26
+ once, kept under `~/.illest/bots/`) is what proves it at sign-in. Losing
27
+ that file is losing the device, which is losing history only it held.
28
+ * **A bot joins groups like a member.** Invite it to your server; somebody's
29
+ client lets it into each channel's group. "Invited" and "able to read" are
30
+ different moments: usually seconds apart while it is online.
31
+ * **A bot that was stopped catches up by itself.** While it is away its groups move on (members join and leave, keys are
32
+ replaced). When it starts, and when it signs in again after the server no longer held its session, it brings every group it
33
+ holds up to the server's epoch, a channel at a time and unhurried, before it handles anything; a channel it cannot catch up is
34
+ logged by its id and the rest go on. A message that arrives sealed at an epoch ahead of the bot's makes it catch that channel up
35
+ once and try the message again; if that fails too the message is dropped, and logged without its content. Messages sent while the
36
+ bot was away are not fetched: it reads what arrives while it is connected.
37
+ * **Who sent it is what their device proves, not what the server says.** Every message and every button press carries
38
+ `author_id` (`sender_id` on an `Interaction`) from the server, and the library checks it against the sender's leaf in the group:
39
+ a frame that names an account other than the one the leaf proves is dropped. A sender whose leaf proves no account (a bot, or
40
+ a device with no certificate) is still delivered, but `Message.author_verified` (`Interaction.sender_verified`,
41
+ `Context.author_verified`) is False, and `author_id` is then only the server's word: do not authorise anything by it.
42
+ * **An account is the key it first showed.** A certificate proves that some account key vouched for a device, and anybody can
43
+ make a key. So the library remembers the first key each account shows (in `pins.json`, in its state folder) and holds the
44
+ account to it: an invitation written by a device under another key is turned down, a member the server lists who lets
45
+ themselves in under another key is not taken in, and a message from a device under another key is delivered with
46
+ `author_verified` False and `author_key_changed` True. The change is said once in the log and to `on_key_change(user_id)`.
47
+ It happens when a person sets their account up again, and when somebody else writes as them: ask them another way, then call
48
+ `client.accept_key_change(user_id)`. An invitation is opened only from a device the channel lists.
49
+ * **A button answers only to who could see it.** `@bot.on_button` runs only for a press on a message this bot sent, naming a
50
+ button that message carried and left enabled, from somebody who could see it (and, for a message meant for one reader, who is
51
+ proven to be that reader). The library remembers its buttons in its state folder, so they survive a restart.
52
+ * **Ephemeral is a rendering rule.** `ephemeral=True` marks a message
53
+ visible only to its one reader on every client that honours the rule. It
54
+ is not cryptography against other members of the group (no such thing
55
+ exists inside an MLS group), and this library says so rather than implying
56
+ otherwise.
57
+ """
58
+
59
+ from ._version import __version__
60
+ from . import abc, app_commands, ui, utils
61
+ from .activity import Activity, BaseActivity, CustomActivity, Game, Streaming
62
+ from .attachment import AttachmentError
63
+ from .client import Client
64
+ from .colour import Color, Colour
65
+ from .content import Button, ContentError
66
+ from .embeds import Embed
67
+ from .enums import * # noqa: F401,F403
68
+ from .enums import __all__ as _enums
69
+ from .errors import (
70
+ AuthFailed,
71
+ BadRequest,
72
+ BotForbidden,
73
+ ClientException,
74
+ ConnectionClosed,
75
+ DiscordException,
76
+ DiscordServerError,
77
+ Forbidden,
78
+ GatewayError,
79
+ GatewayNotFound,
80
+ HTTPException,
81
+ IllestException,
82
+ InteractionResponded,
83
+ InvalidData,
84
+ LoginFailure,
85
+ MissingScope,
86
+ NotFound,
87
+ PrivilegedIntentsRequired,
88
+ RateLimited,
89
+ ServerError,
90
+ StaleEpoch,
91
+ UnknownAction,
92
+ )
93
+ from .file import File
94
+ from .intents import Intents
95
+ from .interactions import Interaction, InteractionCallbackResponse, InteractionResponse, Webhook, WebhookMessage
96
+ from .mentions import AllowedMentions
97
+ from .models import * # noqa: F401,F403
98
+ from .models import __all__ as _models
99
+ from .object import Object
100
+ from .permissions import PermissionOverwrite, Permissions
101
+ from .ui.select import SelectOption
102
+ from .utils import setup_logging
103
+
104
+ #: The scopes a bot token can hold, as the server names them (`actions::bots::SCOPES`).
105
+ SCOPES = ("messages.read", "messages.write", "guilds.read", "members.read", "moderate", "guilds.manage")
106
+
107
+ __all__ = [
108
+ "SCOPES",
109
+ "AllowedMentions",
110
+ "Activity",
111
+ "AttachmentError",
112
+ "AuthFailed",
113
+ "BadRequest",
114
+ "BaseActivity",
115
+ "BotForbidden",
116
+ "Button",
117
+ "Client",
118
+ "ClientException",
119
+ "Color",
120
+ "Colour",
121
+ "ConnectionClosed",
122
+ "ContentError",
123
+ "CustomActivity",
124
+ "DiscordException",
125
+ "DiscordServerError",
126
+ "Embed",
127
+ "File",
128
+ "Forbidden",
129
+ "Game",
130
+ "GatewayError",
131
+ "GatewayNotFound",
132
+ "HTTPException",
133
+ "IllestException",
134
+ "Intents",
135
+ "Interaction",
136
+ "InteractionCallbackResponse",
137
+ "InteractionResponded",
138
+ "InteractionResponse",
139
+ "InvalidData",
140
+ "LoginFailure",
141
+ "MissingScope",
142
+ "NotFound",
143
+ "Object",
144
+ "PermissionOverwrite",
145
+ "Permissions",
146
+ "SelectOption",
147
+ "PrivilegedIntentsRequired",
148
+ "RateLimited",
149
+ "ServerError",
150
+ "StaleEpoch",
151
+ "Streaming",
152
+ "UnknownAction",
153
+ "Webhook",
154
+ "WebhookMessage",
155
+ "__version__",
156
+ "abc",
157
+ "app_commands",
158
+ "setup_logging",
159
+ "ui",
160
+ "utils",
161
+ *_enums,
162
+ *_models,
163
+ ]
@@ -0,0 +1,240 @@
1
+ """Every action's rate limit, as the server enforces it: generated by `scripts/gen_limits.py` from the server's action table.
2
+
3
+ Do not edit by hand. Each entry is `(rate, burst)`: a bucket of `burst` requests per account, refilled at `rate` a second. An
4
+ action not listed takes `DEFAULT`, which is also what the server gives an action registered without a limit of its own.
5
+ """
6
+
7
+ DEFAULT: tuple[float, int] = (2.0, 10)
8
+
9
+ LIMITS: dict[str, tuple[float, int]] = {
10
+ "account.credential.move.begin": (0.5, 3),
11
+ "account.credential.move.complete": (0.5, 3),
12
+ "account.delete": (0.2, 3),
13
+ "account.delete.begin": (0.2, 3),
14
+ "account.delete.complete": (0.2, 3),
15
+ "account.identity.unlock.begin": (0.2, 3),
16
+ "account.identity.unlock.complete": (0.2, 3),
17
+ "account.opaque.upgrade.begin": (0.5, 3),
18
+ "account.opaque.upgrade.complete": (0.5, 3),
19
+ "account.password.change": (0.5, 3),
20
+ "account.password.change.begin": (0.5, 3),
21
+ "account.password.change.complete": (0.5, 3),
22
+ "account.recovery.begin": (0.5, 3),
23
+ "account.recovery.complete": (0.5, 3),
24
+ "account.retention.set": (10.0, 60),
25
+ "account.step_up": (1.0, 5),
26
+ "admin.ban": (2.0, 10),
27
+ "admin.config": (4.0, 10),
28
+ "admin.config_set": (4.0, 10),
29
+ "admin.disconnect": (2.0, 10),
30
+ "admin.log": (2.0, 10),
31
+ "admin.role_set": (2.0, 10),
32
+ "admin.roster": (2.0, 10),
33
+ "admin.unban": (2.0, 10),
34
+ "auth.credential": (1.0, 10),
35
+ "auth.device.enroll": (0.2, 3),
36
+ "auth.device.enroll.verify": (0.2, 3),
37
+ "auth.login.begin": (1.0, 5),
38
+ "auth.login.complete": (1.0, 5),
39
+ "auth.login.verify": (1.0, 5),
40
+ "auth.recover.begin": (1.0, 5),
41
+ "auth.recover.complete": (0.5, 3),
42
+ "auth.recover.verify": (0.2, 3),
43
+ "auth.register.begin": (0.2, 3),
44
+ "auth.register.complete": (1.0, 5),
45
+ "auth.register.work": (0.5, 5),
46
+ "auth.reset.begin": (1.0, 5),
47
+ "auth.reset.complete": (0.2, 3),
48
+ "badge.elite": (4.0, 60),
49
+ "badge.grant": (4.0, 60),
50
+ "badge.revoke": (4.0, 60),
51
+ "bot.commands.get": (2.0, 20),
52
+ "bot.commands.set": (0.5, 5),
53
+ "bot.create": (0.5, 5),
54
+ "bot.delete": (1.0, 10),
55
+ "bot.identify": (1.0, 10),
56
+ "bot.identity.publish": (1.0, 5),
57
+ "bot.token.create": (1.0, 10),
58
+ "bot.update": (1.0, 10),
59
+ "channel.devices": (8.0, 40),
60
+ "channel.disappear.set": (10.0, 60),
61
+ "channel.dm.open": (1.0, 10),
62
+ "channel.get": (8.0, 40),
63
+ "channel.group.add": (10.0, 60),
64
+ "channel.group.create": (4.0, 60),
65
+ "channel.group.leave": (10.0, 60),
66
+ "channel.group.update": (10.0, 60),
67
+ "channel.messages.list": (8.0, 40),
68
+ "channel.messages.purge": (10.0, 30),
69
+ "channel.typing": (6.0, 10),
70
+ "friend.request": (0.5, 10),
71
+ "guild.application.submit": (1.0, 5),
72
+ "guild.audit": (4.0, 10),
73
+ "guild.create": (1.0, 30),
74
+ "guild.insights": (1.0, 5),
75
+ "guild.invite.create": (2.0, 20),
76
+ "guild.invite.preview": (3.0, 20),
77
+ "guild.join": (2.0, 30),
78
+ "guild.members": (3.0, 15),
79
+ "guild.onboarding.answer": (3.0, 20),
80
+ "guild.onboarding.preview": (3.0, 20),
81
+ "guild.onboarding.seen": (3.0, 20),
82
+ "guild.order.set": (1.0, 10),
83
+ "guild.overrides": (4.0, 10),
84
+ "guild.profile.get": (3.0, 15),
85
+ "guild.template.apply": (0.2, 2),
86
+ "guild.template.export": (1.0, 5),
87
+ "guild.vanity.check": (4.0, 30),
88
+ "guild.vanity.resolve": (3.0, 30),
89
+ "identity.certify": (1.0, 5),
90
+ "identity.publish": (1.0, 5),
91
+ "link.preview": (0.5, 10),
92
+ "links.add": (0.2, 6),
93
+ "links.mine": (2.0, 10),
94
+ "links.order": (1.0, 10),
95
+ "links.remove": (1.0, 10),
96
+ "links.services": (1.0, 5),
97
+ "links.update": (1.0, 10),
98
+ "links.verify": (0.1, 4),
99
+ "links.view": (2.0, 12),
100
+ "market.attach": (0.1, 6),
101
+ "market.attachment": (2.0, 20),
102
+ "market.browse": (2.0, 20),
103
+ "market.mine": (1.0, 10),
104
+ "market.name": (2.0, 20),
105
+ "market.status": (2.0, 20),
106
+ "market.voucher.convert": (0.05, 6),
107
+ "market.voucher.receive": (0.1, 10),
108
+ "market.voucher.recovery": (0.01, 3),
109
+ "market.wallet.unlink": (0.1, 6),
110
+ "media.ticket": (1.0, 10),
111
+ "message.pin": (10.0, 30),
112
+ "message.redact": (10.0, 30),
113
+ "message.send": (5.0, 20),
114
+ "message.unpin": (10.0, 30),
115
+ "mls.commit.submit": (5.0, 20),
116
+ "mls.commits.since": (12.0, 60),
117
+ "mls.grant.clear": (1.0, 5),
118
+ "mls.grant.issue": (5.0, 20),
119
+ "mls.grant.needed": (12.0, 60),
120
+ "mls.group.reset": (0.2, 2),
121
+ "mls.group_info.publish": (5.0, 20),
122
+ "mls.key_packages.claim": (6.0, 30),
123
+ "mls.key_packages.publish": (1.0, 5),
124
+ "mls.key_packages.purge": (1.0, 5),
125
+ "name.available": (2.0, 20),
126
+ "name.claim": (0.05, 4),
127
+ "name.keep": (0.1, 6),
128
+ "name.primary": (0.1, 6),
129
+ "name.release": (0.1, 6),
130
+ "name.resolve": (2.0, 20),
131
+ "names.list": (2.0, 20),
132
+ "page.create": (2.0, 20),
133
+ "page.preview": (1.0, 3),
134
+ "page.preview_inputs": (1.0, 3),
135
+ "page.publish": (3.0, 30),
136
+ "page.restore": (2.0, 20),
137
+ "page.save": (10.0, 20),
138
+ "page.stats": (1.0, 5),
139
+ "pins.get": (2.0, 20),
140
+ "pins.put": (0.5, 10),
141
+ "platform.account.find": (1.0, 8),
142
+ "platform.account.flag": (1.0, 6),
143
+ "platform.account.premium": (1.0, 6),
144
+ "platform.get": (2.0, 10),
145
+ "platform.market": (1.0, 6),
146
+ "platform.market.export": (0.5, 4),
147
+ "platform.market.keygen": (0.2, 6),
148
+ "platform.market.prepare": (0.5, 4),
149
+ "platform.market.refuse": (1.0, 6),
150
+ "platform.market.seen": (1.0, 6),
151
+ "platform.security": (1.0, 5),
152
+ "platform.set": (2.0, 14),
153
+ "platform.shop": (1.0, 6),
154
+ "platform.shop.allow": (0.5, 6),
155
+ "platform.shop.close": (0.5, 6),
156
+ "platform.shop.forbid": (0.5, 6),
157
+ "platform.shop.reopen": (0.5, 6),
158
+ "platform.shop.takedown": (0.5, 6),
159
+ "platform.test": (0.2, 3),
160
+ "presence.set": (1.0, 5),
161
+ "report.list": (1.0, 10),
162
+ "report.settle": (1.0, 10),
163
+ "shop.listing.create": (0.2, 8),
164
+ "shop.listing.get": (3.0, 30),
165
+ "shop.listing.list": (3.0, 30),
166
+ "shop.listing.report": (0.2, 6),
167
+ "shop.listing.takedown": (0.5, 10),
168
+ "shop.listing.update": (0.5, 10),
169
+ "shop.listing.withdraw": (0.5, 10),
170
+ "shop.order.cancel": (0.5, 10),
171
+ "shop.order.confirm": (0.3, 8),
172
+ "shop.order.create": (0.2, 8),
173
+ "shop.order.deliver": (0.5, 10),
174
+ "shop.order.get": (3.0, 30),
175
+ "shop.order.list": (2.0, 20),
176
+ "shop.order.look": (0.3, 8),
177
+ "shop.order.problem": (0.5, 10),
178
+ "shop.order.receive": (0.5, 10),
179
+ "shop.quote": (2.0, 20),
180
+ "shop.report.dismiss": (0.5, 10),
181
+ "shop.report.list": (1.0, 12),
182
+ "shop.settings.update": (0.2, 6),
183
+ "shop.status": (2.0, 20),
184
+ "shop.wallet.bind": (0.2, 6),
185
+ "shop.wallet.list": (2.0, 20),
186
+ "shop.wallet.unlink": (0.2, 6),
187
+ "staff.override.set": (2.0, 20),
188
+ "staff.overrides": (2.0, 10),
189
+ "stage.hand.raise": (1.0, 4),
190
+ "stage.open": (1.0, 5),
191
+ "subscription.confirm": (0.3, 8),
192
+ "subscription.gift": (0.1, 5),
193
+ "subscription.invoice": (0.05, 6),
194
+ "subscription.status": (2.0, 20),
195
+ "ticket.list": (1.0, 5),
196
+ "ticket.open": (0.1, 3),
197
+ "ticket.reopen": (0.2, 3),
198
+ "user.get": (4.0, 20),
199
+ "user.report": (0.2, 5),
200
+ "verify.complete": (0.2, 10),
201
+ "verify.start": (0.02, 5),
202
+ "verify.status": (2.0, 20),
203
+ "voice.call.start": (1.0, 3),
204
+ "voice.camera.start": (1.0, 5),
205
+ "voice.join": (2.0, 10),
206
+ "voice.member.deafen": (4.0, 12),
207
+ "voice.member.disconnect": (4.0, 12),
208
+ "voice.member.move": (4.0, 12),
209
+ "voice.member.mute": (4.0, 12),
210
+ "voice.mls.commit": (5.0, 20),
211
+ "voice.mls.join": (2.0, 10),
212
+ "voice.mls.ready": (5.0, 20),
213
+ "voice.resume": (1.0, 5),
214
+ "voice.share.start": (1.0, 5),
215
+ "voice.sound.play": (0.5, 3),
216
+ "voice.turn.refresh": (0.1, 3),
217
+ "voice.watch": (4.0, 12),
218
+ "wallet.allowance": (1.0, 6),
219
+ "wallet.balances": (0.5, 5),
220
+ "wallet.broadcast": (0.2, 4),
221
+ "wallet.coin": (1.0, 6),
222
+ "wallet.fee": (1.0, 5),
223
+ "wallet.find_coins": (2.0, 8),
224
+ "wallet.forget_address": (0.5, 10),
225
+ "wallet.networks": (1.0, 5),
226
+ "wallet.nfts": (0.3, 4),
227
+ "wallet.plan_bitcoin": (0.5, 6),
228
+ "wallet.portfolio": (0.2, 3),
229
+ "wallet.prepare": (0.5, 6),
230
+ "wallet.prices": (1.0, 10),
231
+ "wallet.rates": (1.0, 5),
232
+ "wallet.receipt": (1.0, 10),
233
+ "wallet.register_address": (0.5, 10),
234
+ "wallet.simulate": (0.5, 6),
235
+ "wallet.swap_build": (0.3, 4),
236
+ "wallet.swap_quote": (0.5, 6),
237
+ "wallet.swap_status": (1.0, 3),
238
+ "wallet.swap_targets": (1.0, 6),
239
+ "wallet.tokens": (1.0, 10),
240
+ }
@@ -0,0 +1,3 @@
1
+ """The version, in one place: `pyproject.toml` reads it from here."""
2
+
3
+ __version__ = "2.1.0"