Where it differs from discord.py
illest.py keeps discord.py's names, arguments and behaviour wherever ILLEST allows it. Each difference below is deliberate.
Most follow from one fact: ILLEST is end-to-end encrypted. The server stores ciphertext and holds no keys, so it cannot read
anything for a bot.
Signing in and joining
A bot is a device.
Its token signs it in. The first run enrols a device with its own key, kept under
~/.illest/bots/<name>/. Keep that folder with the bot: losing it is losing the device, and history only that device held. A second machine running the same token is a second device.Joining a server is two steps, not one.
The invite link adds the bot to the server. The clients of people in the server then let its device into each channel's encrypted group, usually within seconds while it is online. Until a channel has let it in, the bot cannot read or send there.
on_guild_joinfires on the first step; a channel's messages arrive after the second.A bot that was stopped catches up by itself.
When it starts, it brings every group it holds up to date before handling anything. What was sent while it was away arrives in
on_missed_message(the message'slateis True), noton_message. Turn that off withClient(read_missed=False).
Reading
History starts when the bot joined.
channel.history(),fetch_message()andchannel.pins()return the messages this bot holds, from when it was let into the channel's group. There is nothing older to fetch, because the server cannot read it either.Who sent it is what their device proves.
message.author_verified,interaction.user_verifiedandctx.author_verifiedare True when the sender's own device vouched for the account. When False,authoris only the server's word: authorise nothing on it. The library remembers each account's key. If it changes,author_key_changedis True andon_key_change(user_id)fires once. Callclient.accept_key_change(user_id)once you have checked with the person another way.
Commands and interactions
Commands, presses, picks and forms travel sealed.
Using a slash command, pressing a button, picking from a select menu or sending a form seals a message into the conversation that only the bot acts on. The server never learns which command was used or with what. So there is no interaction token, no webhook URL, and no 3-second deadline.
defer()still shows "thinking" until the answer replaces it. Answers and follow-ups are taken for 15 minutes, as discord takes them.ephemeral=Trueis a rendering rule.Every ILLEST app shows an ephemeral message to its one reader only. The bytes still reach every device in the conversation, because inside an encrypted group nothing stronger exists. A form (
send_modal) and autocomplete suggestions are delivered the same way. Put nothing in one that the other members must not be able to read.Pictures travel with the message.
An address in an embed (
set_image,set_thumbnail, an author or footer icon) or in a layout (Thumbnail,MediaGallery) is fetched once, by the bot's own server, when the message is sent. The picture is sealed with the message as a file namedpicture-1.pngand so on, and the address becomesattachment://picture-1.png. Nobody's app ever fetches an outside address, so a picture cannot tell its host who read the message, or when. A server that turned this off leaves the address as it is, and apps show it as a link to open.attachment://namenames a file sent with the same message, as before.A layout needs no flag.
A
LayoutViewis sent asview=, besidecontentandembedsif you like, and an app that has never seen a layout shows the text and buttons it holds. Up to 40 parts, 4000 characters of text in all, one container deep.An answer can be a short notice.
interaction.response.send_toast("Saved")shows a few words to the person who pressed, for a moment, andalert=Truekeeps them up until dismissed. Nothing is added to the conversation they see.A message can ask for a reply.
channel.send("What should the poll ask?", ask_reply=True)opens the reply box on that message, for everyone or for the one personAskReply(user=...)names. What they write is an ordinary reply, read like any other message.Forms hold more than text.
Beside
TextInput, a form takesNumberInput, any select menu,RadioGroup,CheckboxGroup,Checkbox,FileUploadandTextDisplay, each titled withui.Label. The app checks every answer before it goes, and the library checks again; when an answer breaks a field's rules, the person is told privately andon_submitdoes not run.tree.sync()takes effect at once.Global commands appear immediately; nothing propagates over an hour. A bot built on
commands.Botwith no slash commands lists its prefix commands in the app's/menu by itself on each start (sync_commands=Falseturns that off). A bot with slash commands lists both when it callstree.sync().default_permissionsis checked twice.The app hides a command from members who lack the permissions. The library refuses it again with
MissingPermissions, since the app's menu is only a convenience.Prefix commands also answer to
/.While the
/menu is synced, picking a prefix command there sends/name ...as typed words, so/is read as a prefix too.
Permissions, intents and scopes
Permissions have discord's names and ILLEST's bits.
illest.Permissions(ban_members=True)works as in discord.py, but.valueis ILLEST's number. A number from a discord permissions calculator means something else here, so build invite links withPermissionsor the portal's Installation page. Where discord has a name ILLEST does not, the name maps to the bit that covers it:read_message_historyisview_channel, andembed_linksissend_messages.Permissions.ILLEST_ONLYlists ILLEST's own bits, such asshare_screenandmanage_stage.Intents filter; scopes grant.
The server pushes what the bot is a member of, and an intent decides which of those events reach your handlers. A message whose intent is off is not even decrypted. What the bot may read and do is set by its scopes, chosen by its owner in the portal:
Scope What it allows messages.readreading messages messages.writesending messages guilds.readreading servers members.readreading member lists moderatemoderation actions guilds.managemaking and changing channels, roles and invites Intents.default()includesmembersandmessage_content, which are scopes here rather than privileged intents.The server's roles still apply.
A bot with the
moderatescope can ban only where its role holdsban_members, above the member it bans, as on discord.
Ids and time
Ids.
User ids are integers, as on discord. Message, channel, server and role ids are 20-digit snowflakes, which the library hands you as
int.created_atis read from the id as discord.py reads it.
What a bot cannot do
| discord.py | illest.py | Why |
|---|---|---|
VoiceChannel.connect(), VoiceClient | raises ClientException | Calls are encrypted from each person's device, and a bot has no microphone. |
Guild.edit(), Guild.delete() | raises | A server's own settings belong to its managers, in the app. |
ClientUser.edit(username=...) | raises | A bot's name is changed by its owner, in the portal. |
Message.remove_reaction (someone else's), clear_reactions | raises | Reactions are sealed in the conversation; only their author can take one back. |
TextChannel.follow(), channel webhooks | raises / empty | ILLEST has no channel following. Outbound webhooks are a server setting. |
Sharding (AutoShardedClient, shard_id) | AutoShardedBot is Bot | One connection serves any number of servers. |
| Scheduled events, stickers, polls through the API | not present | ILLEST's polls are sealed messages; the others are not yet there for bots. |
Rate limits and reconnects
The library paces each action to the server's own limits before sending, so a busy bot waits rather than being refused. It
reconnects on its own with backoff, and run() sets up logging as discord.py does.