Python SDK for building bots on Banter (banterchat.org) — discord.py-style.
Project description
banterbotapi
Python SDK for building bots on Banter.
pip install banterbotapi
Install name is banterbotapi, import name is banterapi (same pattern as Pillow/PIL).
Quick start
from banterapi import Bot, Intents
bot = Bot(intents=Intents.default() | Intents.MESSAGE_CONTENT)
@bot.event
async def on_ready():
print(f"logged in as {bot.user.username}")
@bot.event
async def on_message(message):
if message.content == "!ping":
await message.reply("pong")
bot.run("YOUR_BOT_TOKEN")
bot.run() blocks until disconnect. Ctrl-C is handled cleanly.
Prefix commands
@bot.command()
async def echo(ctx, *, text: str):
await ctx.reply(text)
@bot.command(name="add", help="Add two numbers.")
async def add(ctx, a: int, b: int):
await ctx.reply(str(a + b))
Type annotations drive arg parsing (int, float, bool). *args collects the rest, *, text: str consumes the remainder verbatim. Default prefix is !; pass command_prefix="?" to Bot() to change it.
Slash commands
from banterapi import SlashOption, Optional, OPTION_STRING, OPTION_INTEGER
@bot.slash_command(
name="roll",
description="Roll an N-sided die",
options=[Optional("sides", type=OPTION_INTEGER, description="default 6")],
)
async def roll(interaction):
sides = interaction.options.get("sides", 6)
await interaction.respond(f"🎲 {sides}-sided die")
Optional(name, ...) is shorthand for SlashOption(name, ..., required=False).
Option types: OPTION_STRING, OPTION_INTEGER, OPTION_BOOLEAN, OPTION_USER, OPTION_CHANNEL, OPTION_ROLE.
Slash command handlers take a single Interaction. Response methods:
| Method | Use for |
|---|---|
respond(content, embed=, ephemeral=, components=) |
The visible reply. Once per interaction. |
defer(ephemeral=) |
Ack now, reply within 15 minutes. Shows a thinking indicator. |
followup(content, embed=, ...) |
Additional messages after respond. Repeatable. |
update(content, embed=, components=) |
Button only. Edits the source message in place. |
ephemeral=True makes the message visible only to the invoker.
The SDK calls bot.sync_commands() automatically on on_ready so registered slash commands appear in autocomplete immediately. Two bots in the same guild can register the same command name; the server dispatches the click to the correct bot via the application id.
Buttons
from banterapi import Embed
@bot.slash_command(name="confirm", description="Confirm or cancel")
async def confirm(interaction):
embed = Embed(title="Are you sure?")
embed.add_button("Yes", style="success", custom_id="confirm_yes")
embed.add_button("No", style="danger", custom_id="confirm_no")
await interaction.respond(embed=embed, ephemeral=True)
@bot.on_button("confirm_yes")
async def yes(interaction):
await interaction.update(content="Confirmed.", components=[])
@bot.on_button("confirm_no")
async def no(interaction):
await interaction.update(content="Cancelled.", components=[])
Match a family of buttons with a trailing * — useful for pagination where the page rides in the id:
@bot.on_button("page_*")
async def page(interaction):
n = int(interaction.custom_id.removeprefix("page_"))
await interaction.update(embed=build_page(n))
Button styles: primary, secondary, success, danger, link. link requires url; everything else needs custom_id to fire a handler. Up to 5 buttons per row, up to 5 rows per message.
Buttons work everywhere a message goes: bot.send_message(...), message.reply(...), interaction.respond(...), interaction.followup(...), interaction.update(...).
Embeds
embed = Embed(title="Status", description="All systems nominal.", color=0x57F287)
embed.add_field("Uptime", "12d 4h", inline=True)
embed.add_field("Users", "1,204", inline=True)
embed.set_footer("Last checked just now")
embed.set_thumbnail("https://example.com/icon.png")
await message.channel.send(embed=embed)
Color accepts an int (0x5865F2) or a CSS hex string ("#5865f2").
Events
@bot.event
async def on_ready(): ...
@bot.event
async def on_message(message): ...
@bot.event
async def on_message_edit(payload): ...
@bot.event
async def on_message_delete(payload): ...
@bot.event
async def on_reaction_add(payload): ...
@bot.event
async def on_reaction_remove(payload): ...
@bot.event
async def on_member_join(payload): ...
@bot.event
async def on_member_remove(payload): ...
@bot.event
async def on_interaction(interaction): ...
Each event handler receives one positional argument — typically a model (Message, Interaction) or the raw event dict. For events that haven't been wrapped in a model yet, the dict has actor pair fields (actor_type, actor_id) plus legacy aliases (user_id, is_bot) so handlers reading either form work.
Handlers may be sync or async. Exceptions inside a handler are logged and swallowed.
Permissions
from banterapi import Permissions, has_permissions, MissingPermissions
@bot.command()
@has_permissions(Permissions.BAN_MEMBERS)
async def ban(ctx, target: str):
...
@bot.event
async def on_command_error(ctx, exc):
if isinstance(exc, MissingPermissions):
await ctx.reply("you can't do that")
Permissions is a flag class — combine with |, check with has_perm(mask, required). The Administrator bit bypasses individual perm checks.
Constants: SEND_MESSAGES, MANAGE_CHANNELS, MANAGE_ROLES, MANAGE_MESSAGES, ADMINISTRATOR, MENTION_EVERYONE, VIEW_CHANNELS, ATTACH_FILES, BAN_MEMBERS, USE_SLASH_COMMANDS, MANAGE_GUILD, KICK_MEMBERS.
@has_permissions(...) raises MissingPermissions before the command body runs — catch via on_command_error (or on_error for slash commands).
Intents
intents = Intents.default() | Intents.MESSAGE_CONTENT
bot = Bot(intents=intents)
Intents.default() covers the common cases (guilds, members, messages without content, reactions, bot events). Add MESSAGE_CONTENT to receive message text in on_message.
Intent constants: GUILDS, GUILD_MEMBERS, GUILD_MODERATION, GUILD_PRESENCES, GUILD_MESSAGES, GUILD_REACTIONS, GUILD_TYPING, GUILD_VOICE_STATES, DIRECT_MESSAGES, DIRECT_REACTIONS, DIRECT_TYPING, MESSAGE_CONTENT, BOT_EVENTS. Use Intents.all() for all of them or Intents.none() to start from zero.
Sending files
from banterapi import File
await message.channel.send(content="Here's the report.", file=File("report.pdf"))
await message.channel.send(files=[File("a.png"), File("b.png")])
File accepts a path, a file-like with .read(), or bytes (with filename=).
Bot identity in messages
@bot.event
async def on_message(message):
if message.actor_type == "bot":
return # ignore other bots
if message.user_id == bot.user.id:
return # ignore self
Messages carry both the new actor pair (message.actor_type, message.actor_id) and the legacy alias (message.user_id is the same value as actor_id). Use whichever you prefer; new code should branch on actor_type to distinguish bot-authored messages.
bot.process_commands() already skips bot-authored messages by default — including the bot's own messages — so prefix commands won't trigger on bot output.
Error handling
from banterapi import Forbidden, NotFound, RateLimited
try:
await message.channel.send("hi")
except Forbidden:
pass # bot lacks permission
except RateLimited as e:
await asyncio.sleep(e.retry_after)
Exception hierarchy:
BanterError— base. Catch all.HTTPException— any non-2xx REST response. Has.status,.code,.message.Forbidden— 403NotFound— 404RateLimited— 429. Has.retry_after.
GatewayError— websocket-level failure. Usually transient; the bot reconnects.LoginFailure— auth rejected. Terminal — rotate the token.MissingPermissions— raised by@has_permissionsdecorator.
Bot methods
The bot object exposes wrappers around the REST API. All are async; all return models or raise.
| Method | Description |
|---|---|
send_message(channel_id, content="", embed=, reply_to=, file=, files=, components=) |
Send a message. |
get_user(user_id) |
Fetch a user. |
get_guild(guild_id) |
Fetch a guild. |
get_member(guild_id, user_id) |
Fetch a guild member. |
edit_guild(guild_id, *, name=, description=, welcome_channel_id=) |
Edit a guild. |
list_channels(guild_id) / get_channel(channel_id) |
Channel reads. |
create_channel(guild_id, name, **kw) / edit_channel(channel_id, **patch) / delete_channel(channel_id) |
Channel CRUD. |
reorder_channels(guild_id, items) |
Bulk reorder. |
purge_channel(channel_id, limit=100) |
Bulk-delete recent messages. |
set_channel_permissions(channel_id, role_id, *, allow=, deny=) |
Channel role override. |
list_roles(guild_id) / everyone_role(guild_id) |
Role reads. |
create_role(guild_id, name, **kw) |
Create a role. |
list_categories(guild_id) / create_category(...) / edit_category(...) / delete_category(...) |
Category CRUD. |
reorder_categories(guild_id, items) |
Bulk reorder. |
set_category_permissions(category_id, role_id, *, allow=, deny=) |
Category role override. |
create_task(coro) |
Schedule a background task on the bot's event loop. |
Models (Message, Channel, Member) carry their own bound helpers (message.reply(), channel.send(), etc.) when constructed with a client — those go through the same HTTP path.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file banterbotapi-1.0.21.tar.gz.
File metadata
- Download URL: banterbotapi-1.0.21.tar.gz
- Upload date:
- Size: 24.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0aa53056942c44325e12944851256e5ed9cd2fcbe5b8c6bd665c974c3790d4e
|
|
| MD5 |
1fdbb78121a1a7a9204bff4f9a4a3706
|
|
| BLAKE2b-256 |
de6c228d984344b93655ec75f65496ef52fee8f5909cb1878dd673c174f124dc
|
File details
Details for the file banterbotapi-1.0.21-py3-none-any.whl.
File metadata
- Download URL: banterbotapi-1.0.21-py3-none-any.whl
- Upload date:
- Size: 24.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1718aa88bba927122d988f87c97a0c5062ebfaf73feea51302f2eee3d02cf96d
|
|
| MD5 |
6ba88c7bcd58cf858ae72b28278e1f7b
|
|
| BLAKE2b-256 |
feafbb01e9a3c0e9d77d989c9a95794538c33aa4996099769deda39123e958c4
|