# lamma.js

> Build Lamma bots in JavaScript with the patterns you know from discord.js.

lamma.js lets you build Lamma bots in JavaScript with the names and patterns you know from discord.js. 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.js isn't published to npm yet. This page describes it ahead of release. It needs Node.js 22.4 or newer and has no dependencies.

## Example

```js
import { Client, Events, GatewayIntentBits, EmbedBuilder, SlashCommandBuilder, MessageFlags, Colors } from 'lamma.js';

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent]
});

const commands = [
  new SlashCommandBuilder().setName('roll').setDescription('Roll a die')
    .addIntegerOption(option => option.setName('sides').setDescription('How many sides').setRequired(true).setMinValue(2).setMaxValue(100))
];

client.once(Events.ClientReady, async ready => {
  console.log(`Logged in as ${ready.user.username} in ${ready.guilds.cache.size} servers`);
  await ready.application.commands.set(commands); // global commands show up everywhere at once
});

client.on(Events.MessageCreate, async message => {
  if (message.author.bot || message.content !== '!ping') return;
  const embed = new EmbedBuilder().setTitle('Pong!').setDescription(`Gateway ping: ${client.ws.ping} ms`).setColor(Colors.Green);
  await message.reply({ embeds: [embed] });
});

client.on(Events.InteractionCreate, async interaction => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== 'roll') return;
  const sides = interaction.options.getInteger('sides', true);
  await interaction.reply({ content: `You rolled ${1 + Math.floor(Math.random() * sides)}`, flags: MessageFlags.Ephemeral });
});

client.login(); // reads LAMMA_TOKEN
```

## Options

| Option | Default | Notes |
| --- | --- | --- |
| `baseURL` | `LAMMA_URL` | Another copy of Lamma to reach, such as a local one. Defaults to Lamma's own address |
| `intents` | `[]` | Which events to receive |
| `gatewayURL` | from `GET /v1/gateway`, or `LAMMA_GATEWAY_URL` | Set it when your bot reaches Lamma by another address |
| `rest.retries` | `3` | How many times to retry after a rate limit |
| `allowedMentions` | every mention allowed | The default for every message |
| `messageCacheLimit` | `200` | Messages kept per channel, in memory only, for edit and delete events. `0` keeps none |

## Coming from discord.js

Most bots port by changing the import and the token.

| discord.js | lamma.js |
| --- | --- |
| `import { Client } from 'discord.js'` | `import { Client } from 'lamma.js'` |
| `new Client({ intents })` | The same |
| `GatewayIntentBits` (bits, arrays, flag names such as `'GuildMessages'`, `IntentsBitField`) | The same bits. Lamma sends `Guilds`, `GuildMembers`, `GuildModeration`, `GuildExpressions`, `GuildMessages`, `GuildMessageReactions` and `MessageContent`. Others are ignored with one warning |
| `client.login(token)` | The same. Defaults to `LAMMA_TOKEN` |
| `Events.ClientReady` (`'clientReady'`), `MessageCreate`, `InteractionCreate`, `GuildCreate`, `Raw`, … | The same names. The old `'ready'` isn't emitted; a listener on it warns once |
| `messageReactionAdd` and `messageReactionRemove`: `(reaction, user, { type, burst })` | The same. `reaction.emoji` has `id`, `name` and `identifier`. A reaction on an uncached message comes as a partial, as with `Partials.Message`: check `reaction.partial` and call `reaction.fetch()` |
| `guildMemberAdd`, `guildMemberUpdate`, `guildMemberRemove`, `guildBanAdd`, `guildBanRemove`, `guildAuditLogEntryCreate` | The same. Timeouts, bans and audit entries need [moderation access](https://lammapp.sa/developers/events#moderation-access) |
| `roleCreate`, `channelCreate`, `emojiCreate` and their update and delete events | The same. Caches stay up to date |
| `guild.fetchAuditLogs()`, `AuditLogEvent` | The same. Entries Discord has no number for have `action: null` and `entry.lammaAction` |
| `message.reply()`, `edit()`, `delete()`, `react()`, `message.reactions.cache` | The same. `react()` takes a Unicode emoji or a server emoji (`GuildEmoji`, `'<:name:id>'`, `'name:id'` or its id) |
| `ChannelType`, `channel.type`, `TextChannel`, `VoiceChannel`, `CategoryChannel`, `channel.parent`, `isTextBased()`, `isSendable()` | The same. Voice channels have a text chat, as on Discord |
| `PermissionsBitField`, `PermissionFlagsBits`, `role.permissions`, `member.permissions`, `guild.members.me` | The same |
| `interaction.appPermissions`, `interaction.memberPermissions` | The same: what the bot, and the person who ran the command, may do in that channel |
| `channel.permissionsFor()`, `member.permissionsIn()` | The same for the bot itself. For anyone else they return `null` and warn once, because Lamma doesn't send other members' channel permissions |
| `channel.send()`, `channel.messages.fetch()` | The same |
| `guild.members.fetch()` | The same. Needs `members.read` |
| `EmbedBuilder` | The same. Images and thumbnails aren't shown yet |
| `SlashCommandBuilder` | The same, for string, integer, number, boolean, user, channel and role options, with the same `toJSON()` as @discordjs/builders |
| A command handler: `{ data, execute }` files in `client.commands`, and a `deploy-commands.js` that calls `rest.put(Routes.applicationGuildCommands(clientId, guildId), { body })` | The same, so a bot laid out like the discord.js guide ports as it is |
| `BitField`, `UserFlagsBitField`, `ChannelFlagsBitField`, `GuildMemberFlagsBitField`, `RoleFlagsBitField` and the other bitfields | The same classes and values. Lamma has no account badges or channel, member and role flags, so those fields are empty |
| `client.application.commands.set(commands, guildId)` | The same. Global commands appear at once |
| `interaction.reply()`, `deferReply()`, `editReply()`, `followUp()` | The same, within 3 seconds and 15 minutes |
| `flags: MessageFlags.Ephemeral` | The same. Other message flags are dropped with one warning |
| `InteractionType`, `ApplicationCommandType`, `InteractionContextType`, `Partials` | The same values, so ported imports work |
| `interaction.options.getString()`, `getInteger()`, `getUser()`, `getChannel()`, `getRole()` | The same |
| `Collection` | The same helpers: `find`, `filter`, `map`, `first`, … |
| `REST` and `Routes` | The same idea, with Lamma's paths |

## What's different

- `setDefaultMemberPermissions` has no effect yet and warns once, so check `interaction.memberPermissions` in your command code.
- Lamma's Mention roles permission has no Discord flag, so permission bitfields leave it out.
- `reply({ withResponse: true })` gives `undefined` with one warning, and `fetchReply()` rejects. `editReply()` returns the message.
- There are no buttons, select menus, modals, autocomplete or subcommands yet. `addSubcommand` throws so you notice.
- Bots can't send DMs, show a status or show typing. `user.send()` and `createDM()` reject with code `50007`, as Discord does for someone with DMs closed. `sendTyping()`, `setPresence()` and `setActivity()` do nothing. Each warns once.
- Bots can't kick, ban or time out members yet.
- When the connection drops, the client reconnects on its own. There is no resume, so read anything you missed with `channel.messages.fetch()`.
- An ephemeral reply is never stored, so `editReply()` on one returns a plain object, not a `Message`.

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