# lamma.py

> Build Lamma bots in Python with the patterns you know from discord.py.

lamma.py lets you build Lamma bots in Python with the names and patterns you know from discord.py. It runs on Lamma's own [REST API](https://lammapp.sa/developers/api-reference) and [gateway](https://lammapp.sa/developers/gateway) and never talks to Discord.

> [!NOTE]
> lamma.py isn't published to PyPI yet. This page describes it ahead of release. It needs Python 3.10 or newer and `aiohttp`.

## Example

```python
import lamma
from lamma import app_commands
from lamma.ext import commands

intents = lamma.Intents.default()
intents.message_content = True


class MyBot(commands.Bot):
    async def setup_hook(self):
        await self.tree.sync()  # global commands show up everywhere at once


bot = MyBot(command_prefix='!', intents=intents)


@bot.command()
async def hello(ctx):
    await ctx.reply(f'Hello, {ctx.author.display_name}!')


@bot.tree.command(description='Roll a die')
@app_commands.describe(sides='How many sides')
async def roll(interaction: lamma.Interaction, sides: app_commands.Range[int, 2, 100]):
    await interaction.response.send_message(f'You rolled a d{sides}', ephemeral=True)


bot.run()  # reads LAMMA_TOKEN
```

## Options

| Option | Default | Notes |
| --- | --- | --- |
| `base_url` | `LAMMA_URL` | Another copy of Lamma to reach, such as a local one. Defaults to Lamma's own address |
| `intents` | required | Which events to receive |
| `gateway_url` | from `GET /v1/gateway`, or `LAMMA_GATEWAY_URL` | Set it when your bot reaches Lamma by another address |
| `max_messages` | `1000` | Messages kept, in memory only, for edit and delete events |
| `allowed_mentions` | every mention allowed | The default for every message |

## Coming from discord.py

Most bots port by changing `import discord` to `import lamma as discord`, `from discord.ext import commands` to `from lamma.ext import commands`, and setting `LAMMA_TOKEN`. The [migration guide](https://lammapp.sa/developers/migrate-discord-py) has the details.

| discord.py | lamma.py |
| --- | --- |
| `discord.Client(intents=...)`, `commands.Bot(...)` | The same |
| `Intents.default()`, `.message_content`, `.members`, `.guilds`, `.messages`, `.reactions`, `.moderation`, `.expressions` | The same. Other flags are accepted, ignored with one warning, and read as `False` |
| Int IDs, `guild.get_channel(id)`, `discord.utils.get(..., id=...)` | The same. Lookups also take an ID as a string |
| `TextChannel`, `VoiceChannel`, `CategoryChannel`, `channel.type`, `guild.text_channels`, `guild.voice_channels` | The same. Bots can post in a voice channel's text chat |
| `discord.Permissions` and its presets, `role.permissions`, `member.guild_permissions`, `interaction.permissions`, `interaction.app_permissions` | The same |
| `MessageFlags`, `PublicUserFlags`, `MemberFlags`, `ChannelFlags`, `RoleFlags`, `AttachmentFlags`, `EmbedFlags`, `SystemChannelFlags`, `ApplicationFlags`, `MemberCacheFlags`, `AppCommandContext` and the other flag classes | The same names and bits. Lamma has none of these features, so the flags on messages, users, members, channels and roles are always empty |
| `app_commands.checks.has_permissions()`, `bot_has_permissions()` | The same, exact for the channel |
| `commands.bot_has_permissions()`, `has_guild_permissions()`, `bot_has_guild_permissions()` | The same |
| `commands.has_permissions()` | Uses the author's server-wide permissions, because Lamma doesn't send channel overrides. It warns once |
| `channel.permissions_for(guild.me)` | The same for the bot itself. For anyone else it raises `ClientException` |
| `client.run(token)`, `await client.start(token)` | The same. `run()` defaults to `LAMMA_TOKEN` |
| `setup_hook`, `on_ready`, `on_message`, `on_message_edit`, `on_message_delete`, `on_raw_*`, `on_guild_join`, `on_guild_remove`, `on_interaction` | The same |
| `on_member_join`, `on_member_update`, `on_member_remove`, `on_member_ban`, `on_member_unban`, `on_audit_log_entry_create` | The same. Timeouts, bans and audit entries need [moderation access](https://lammapp.sa/developers/events#moderation-access) |
| `guild.audit_logs(...)` | The same. Entries Discord has no action for have `action` set to `None` and `entry.lamma_action` |
| `channel.send()`, `message.reply()`, `edit()`, `delete()`, `add_reaction()` | The same |
| `channel.history()`, `channel.fetch_message()` | The same |
| `guild.fetch_members()`, `guild.fetch_member()` | The same. Needs `members.read` |
| `discord.Embed` | The same. Images and thumbnails aren't shown yet |
| `app_commands.CommandTree`, `@tree.command`, `describe`, `choices`, `Range`, `Literal` | The same |
| Option types `str`, `int`, `float`, `bool`, `Member`, `User`, `TextChannel`, `VoiceChannel`, `Role` | The same |
| `tree.sync()`, `tree.sync(guild=...)` | The same. Global commands appear at once |
| `interaction.response.send_message(ephemeral=True)`, `defer()`, `edit_original_response()`, `followup.send()` | The same, within 3 seconds and 15 minutes |
| `commands.Cog`, `Cog.listener()`, `@bot.command()`, `Context`, converters, checks | The same |
| `await bot.load_extension('cogs.fun')` with `async def setup(bot)`, `unload_extension()`, `reload_extension()` | The same |
| `discord.utils.get`, `find`, `format_dt`, `escape_markdown`, `escape_mentions` | The same |

## What's different

- IDs are Lamma IDs, so a Discord ID won't find anything.
- `channel.permissions_for()` only works for the bot itself. Lamma doesn't send channel overwrites to bots, so it raises `ClientException` for anyone else rather than guess. Use `interaction.permissions` for the person who ran a command, or `member.guild_permissions` for server-wide permissions.
- Lamma's Mention roles permission has no Discord flag, so it isn't part of `Permissions`.
- `default_permissions` and `guild_only` are accepted but have no effect yet, so check `interaction.permissions` in your command code.
- 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`.
- There are no buttons, views, select menus, modals, autocomplete, context menus or subcommand groups yet. `app_commands.Group` and `commands.GroupCog` raise so you notice.
- Bots can't upload files yet. `discord.File` works, but `file=` and `files=` are left out of the message with one warning.
- Bots can't send DMs, show a status or show typing. `user.send()` raises `Forbidden` with code `50007`, as Discord does for someone with DMs closed. `typing()` and `change_presence()` do nothing. Each logs one warning.
- Bots can't kick, ban or time out members yet.
- `client.fetch_user()` only returns people the bot has already seen.
- Reaction events are raw only: use `on_raw_reaction_add` and `on_raw_reaction_remove`.
- When the connection drops, the client reconnects and `on_ready` runs again. There is no resume, so read anything you missed with `channel.history()`.

See [Coming from Discord](https://lammapp.sa/developers/from-discord) for everything that differs.
