LammaDevelopers Open Lamma

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:

Text
- 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_commands
Terminal
export 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 usesOn Lamma
user.send(), create_dm(), DM commandsBots 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_updateNever sent to bots. intents.presences reads as False, so code that checks it switches itself off
channel.typing(), on_typingDoes nothing, with one warning
change_presence(), activity= and status= optionsDoes nothing for now, with one warning
discord.ui.View, Button, Select, ModalNot yet. Rewrite those flows as app command parameters
app_commands.Group, commands.GroupCog, autocomplete, context menus, Attachment parametersNot yet. Group and GroupCog raise so you notice. Use separate commands
discord.File, file= and files=, embed images and thumbnailsNot yet. Images are dropped, and files aren't sent, with one warning
member.ban(), kick(), timeout(), add_roles(), deleting other people's messagesNot in v1. Bots can still hear about bans, kicks and timeouts
Voice (VoiceClient, connect())Not offered to bots
Threads, forums, stages, stickersLamma doesn't have them yet
channel.permissions_for(member) for someone other than the botRaises 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_idNot needed: one connection per bot. Use commands.Bot
discord.com/channels/… linksUse message.jump_url or Lamma's own links
Libraries that look people up on Discord by idRemove 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

Terminal
pip uninstall discord.py
pip install lamma.py

lamma.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:

Python
# 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.pyIn your app's settingsNotes
guildsServer, channel and role changes
guild_messages (messages)New, edited and deleted messages
message_contentThe text of every message it can seePrivileged
guild_reactions (reactions)Reactions
membersMembers joining, leaving and changingPrivileged
moderation (bans)Bans, timeouts and audit log entriesAlso needs View audit log
expressions (emojis)Custom emoji changesEmoji only
presences, typing, dm_messages, voice_states, invites, webhooks, guild_scheduled_events, auto_moderationnoneIgnored 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:

Text
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 listener

These 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_add and on_reaction_remove code to on_raw_reaction_add and on_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:

Python
if not interaction.permissions.manage_messages:
    await interaction.response.send_message('You need Manage messages.', ephemeral=True)
    return
  • interaction.permissions is what the person who ran the command may do in that channel, with roles and channel overrides.
  • interaction.app_permissions is what the bot may do there.
  • member.guild_permissions is 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, so permissions_for() raises ClientException for anyone else rather than guess. Use interaction.permissions or member.guild_permissions instead.
  • Lamma's Mention roles permission has no Discord flag, so it isn't part of Permissions.

The permission checks work as they are:

CheckUses
@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.

Python
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_to tricks while testing.
  • default_permissions and guild_only are 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 TextChannel parameter, the command fails with app_commands.TransformerError.
  • Split groups into separate commands, for example /config-show and /config-set instead of /config show and /config set. app_commands.Group and commands.GroupCog raise NotImplementedError so 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 TextChannel and voice channels are VoiceChannel, each with channel.type. Lamma voice channels have a text chat, so bots can send messages there. Categories are CategoryChannel, with category.text_channels and category.voice_channels; channel.category and guild.categories work 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 in AllowedMentions works.
  • 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_after is in milliseconds. lamma.py waits and retries for you.

#10. Test it

  1. Run the bot against a test server you own.
  2. Watch the log: lamma.py warns once for each intent, call or option Lamma doesn't support, so nothing fails silently.
  3. Try every command and the events your bot relies on.
  4. 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.