Moving from Discord
Migrate from discord.py
Move a discord.py 2.x bot to lamma.py step by step, with every change your code needs.
This guide moves a discord.py 2.x bot to lamma.py one step at a time. Most bots keep their cogs, prefix commands, app command trees and event handlers. What changes is the import, the address, the token, a few names, and any feature Lamma doesn't offer bots.
#The short version
For most bots, the port is two import lines and two environment variables:
- import discord
- from discord.ext import commands
- from discord import app_commands
+ import lamma as discord
+ from lamma.ext import commands
+ from lamma import app_commandsexport LAMMA_TOKEN=lmb1_...Lamma's address is built in, as Discord's is. bot.run() reads LAMMA_TOKEN when you pass no token. If you keep the Lamma token in DISCORD_TOKEN, bot.run(os.environ['DISCORD_TOKEN']) works too. Python can't load from discord import ... from a package imported under another name, so that line becomes from lamma import .... discord.app_commands and discord.ext.commands work as they are.
Everything else stays as it is: intents, cogs and extensions, the command tree, checks, discord.utils, discord.Embed and the rest. The steps below cover the features Lamma handles differently.
#1. Check what your bot uses
Search your code for the features below first. Each needs a change, or is switched off on Lamma.
| If your bot uses | On Lamma |
|---|---|
user.send(), create_dm(), DM commands | Bots can't DM people. The call raises Forbidden with code 50007, like a person with DMs closed |
intents.presences, member.status, member.activity, on_presence_update | Never sent to bots. intents.presences reads as False, so code that checks it switches itself off |
channel.typing(), on_typing | Does nothing, with one warning |
change_presence(), activity= and status= options | Does nothing for now, with one warning |
discord.ui.View, Button, Select, Modal | Not yet. Rewrite those flows as app command parameters |
app_commands.Group, commands.GroupCog, autocomplete, context menus, Attachment parameters | Not yet. Group and GroupCog raise so you notice. Use separate commands |
discord.File, file= and files=, embed images and thumbnails | Not yet. Images are dropped, and files aren't sent, with one warning |
member.ban(), kick(), timeout(), add_roles(), deleting other people's messages | Not in v1. Bots can still hear about bans, kicks and timeouts |
Voice (VoiceClient, connect()) | Not offered to bots |
| Threads, forums, stages, stickers | Lamma doesn't have them yet |
channel.permissions_for(member) for someone other than the bot | Raises ClientException. See step 7 |
@commands.has_permissions() | Checks the author's server-wide permissions, without channel overrides, with one warning. See step 7 |
AutoShardedBot, shard_id | Not needed: one connection per bot. Use commands.Bot |
discord.com/channels/… links | Use message.jump_url or Lamma's own links |
| Libraries that look people up on Discord by id | Remove them. Lamma ids aren't Discord ids, so they would find the wrong person |
#2. Make an app and get a token
Follow Build your first bot to create the app, copy its token and install it in a test server. Ask for the scopes and intents your bot really uses.
Discord ids saved in your database won't match anything on Lamma. Plan to set up each server's settings again.
#3. Swap the package
pip uninstall discord.py
pip install lamma.pylamma.py needs Python 3.10 or newer and aiohttp.
#4. Change the import and the bot
The quickest way is to import lamma.py under the name your code already uses:
# Before
import discord
from discord.ext import commands
from discord import app_commands
bot = commands.Bot(command_prefix='!', intents=discord.Intents.default())
bot.run(os.environ['DISCORD_TOKEN'])
# After
import lamma as discord
from lamma.ext import commands
from lamma import app_commands
bot = commands.Bot(command_prefix='!', intents=discord.Intents.default())
bot.run(os.environ['LAMMA_TOKEN'])run() with no token reads LAMMA_TOKEN. To reach another copy of Lamma, such as a local one, set LAMMA_URL or base_url.
#5. Check your intents
| discord.py | In your app's settings | Notes |
|---|---|---|
guilds | Server, channel and role changes | |
guild_messages (messages) | New, edited and deleted messages | |
message_content | The text of every message it can see | Privileged |
guild_reactions (reactions) | Reactions | |
members | Members joining, leaving and changing | Privileged |
moderation (bans) | Bans, timeouts and audit log entries | Also needs View audit log |
expressions (emojis) | Custom emoji changes | Emoji only |
presences, typing, dm_messages, voice_states, invites, webhooks, guild_scheduled_events, auto_moderation | none | Ignored with one warning, and read as False |
Keep using discord.Intents as you do now. lamma.py sends Lamma what it needs. Privileged intents work in up to five servers you own while you develop, and everywhere once the app is reviewed.
#6. Keep your events, cogs and extensions
A bot laid out the usual discord.py way runs as it is: a bot.py whose setup_hook calls await self.load_extension('cogs.fun') for each file in a cogs/ folder, and an async def setup(bot) in each file that adds a commands.Cog. unload_extension(), reload_extension() and bot.extensions work too. lamma.py's examples use this layout:
bot.py starts the bot and loads every file in cogs/ in setup_hook
cogs/general.py prefix commands
cogs/slash.py slash commands, one with a permission check
cogs/events.py an event listenerThese work as they are too: setup_hook, on_ready, on_message, on_message_edit, on_message_delete, the on_raw_* message events, on_guild_join, on_guild_remove, on_member_join, on_member_update, on_member_remove, on_member_ban, on_member_unban, on_audit_log_entry_create, the on_guild_role_* and on_guild_channel_* events, on_guild_emojis_update, on_interaction and on_error. So do @bot.command(), Context, converters, checks, commands.Cog with cog_check, cog_command_error and cog_app_command_error, Cog.listener(), bot.add_cog(), bot.wait_for(), discord.utils.get, utils.format_dt and utils.escape_markdown.
Two changes:
- Reaction events are raw only. Move
on_reaction_addandon_reaction_removecode toon_raw_reaction_addandon_raw_reaction_remove. client.fetch_user()only returns people the bot has already seen in its servers.
The message cache keeps up to 1,000 messages in memory, so on_message_edit and on_message_delete get the old message as in discord.py. Follow the message cache rule.
When the connection drops, the client reconnects by itself and on_ready runs again. There is no resume, so fetch anything you missed with channel.history(after=...).
#7. Check permissions
discord.Permissions works as before, with the same attribute names. role.permissions, member.guild_permissions, interaction.permissions and interaction.app_permissions are all Permissions:
if not interaction.permissions.manage_messages:
await interaction.response.send_message('You need Manage messages.', ephemeral=True)
returninteraction.permissionsis what the person who ran the command may do in that channel, with roles and channel overrides.interaction.app_permissionsis what the bot may do there.member.guild_permissionsis server-wide, from roles only, as in discord.py. The owner and administrators have every permission.channel.permissions_for(guild.me)gives the bot's own permissions in a channel. Lamma doesn't send channel overrides to bots, sopermissions_for()raisesClientExceptionfor anyone else rather than guess. Useinteraction.permissionsormember.guild_permissionsinstead.- Lamma's Mention roles permission has no Discord flag, so it isn't part of
Permissions.
The permission checks work as they are:
| Check | Uses |
|---|---|
@app_commands.checks.has_permissions() | interaction.permissions, exact for the channel. Raises app_commands.MissingPermissions |
@app_commands.checks.bot_has_permissions() | interaction.app_permissions, exact for the channel. Raises app_commands.BotMissingPermissions |
@commands.bot_has_permissions() | channel.permissions_for(guild.me), exact for the channel. Raises commands.BotMissingPermissions |
@commands.has_permissions() | The author's server-wide permissions, because Lamma doesn't send channel overrides. It warns once. Raises commands.MissingPermissions |
@commands.has_guild_permissions(), @commands.bot_has_guild_permissions() | guild_permissions, as in discord.py |
@app_commands.default_permissions() is accepted but not enforced yet, so add @app_commands.checks.has_permissions() to each sensitive command.
discord.py's flag classes are there with the same names and bits: Permissions with its presets such as Permissions.text(), MessageFlags, PublicUserFlags, MemberFlags, ChannelFlags, RoleFlags, AttachmentFlags, EmbedFlags, SystemChannelFlags, ApplicationFlags, MemberCacheFlags and the rest. Lamma has none of the features most of them describe, so message.flags, user.public_flags, member.flags, channel.flags and role.flags are always empty.
#8. Update app commands
app_commands.CommandTree, @tree.command, describe, choices, rename, Choice, Range and Literal work as they are, with str, int, float, bool, Member, User, TextChannel, VoiceChannel and Role parameters.
class MyBot(commands.Bot):
async def setup_hook(self):
await self.tree.sync() # every server, at once
# await self.tree.sync(guild=lamma.Object(id)) # one server- Global commands appear at once, so you don't need
copy_global_totricks while testing. default_permissionsandguild_onlyare accepted but not enforced yet. Keep a permission check in each sensitive command (step 7).- Channel options aren't limited by type yet. If someone picks a voice channel for a
TextChannelparameter, the command fails withapp_commands.TransformerError. - Split groups into separate commands, for example
/config-showand/config-setinstead of/config showand/config set.app_commands.Groupandcommands.GroupCograiseNotImplementedErrorso a group is never registered half working.
interaction.response.send_message(ephemeral=True), defer(), edit_original_response() and followup.send() work as before, within 3 seconds and 15 minutes.
#9. Small changes in messages and channels
- Ids are ints, as in discord.py. They are Lamma ids, so ids saved from Discord won't match.
- Text channels are
TextChanneland voice channels areVoiceChannel, each withchannel.type. Lamma voice channels have a text chat, so bots can send messages there. Categories areCategoryChannel, withcategory.text_channelsandcategory.voice_channels;channel.categoryandguild.categorieswork too. There are no threads, forums or stages. - Embeds keep their fields and limits. Images, thumbnails and video are dropped for now.
- Lamma doesn't notify on replies, so
AllowedMentions(replied_user=...)changes nothing. Everything else inAllowedMentionsworks. - An ephemeral reply is never stored, so editing or following up on one returns a plain dict, not a
Message. - On a rate limit,
retry_afteris in milliseconds. lamma.py waits and retries for you.
#10. Test it
- Run the bot against a test server you own.
- Watch the log: lamma.py warns once for each intent, call or option Lamma doesn't support, so nothing fails silently.
- Try every command and the events your bot relies on.
- When it works, share the install link with the servers that use it.
Anything missing that your bot needs? Email lamma.help@gmail.com and say what it is.