Skip to main content

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 — 403
    • NotFound — 404
    • RateLimited — 429. Has .retry_after.
  • GatewayError — websocket-level failure. Usually transient; the bot reconnects.
  • LoginFailure — auth rejected. Terminal — rotate the token.
  • MissingPermissions — raised by @has_permissions decorator.

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

banterbotapi-1.0.22.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

banterbotapi-1.0.22-py3-none-any.whl (24.8 kB view details)

Uploaded Python 3

File details

Details for the file banterbotapi-1.0.22.tar.gz.

File metadata

  • Download URL: banterbotapi-1.0.22.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

Hashes for banterbotapi-1.0.22.tar.gz
Algorithm Hash digest
SHA256 bd29bead18647c907a9fc39cf0a3488c8ea1456affe2a286bbee6acdeebc5327
MD5 c6136f777e678733f127fac4256ad497
BLAKE2b-256 e37b0a61035e171fd0e25cef15df57dfd3d3c4fd486424f96caa0c2377c5884c

See more details on using hashes here.

File details

Details for the file banterbotapi-1.0.22-py3-none-any.whl.

File metadata

  • Download URL: banterbotapi-1.0.22-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

Hashes for banterbotapi-1.0.22-py3-none-any.whl
Algorithm Hash digest
SHA256 6f90a720d8ed7dccfba6615f69a8a0560b54919f1ec71b3653171bbbb5a990ef
MD5 ea79dca0729c6e968089bffd6f8573e2
BLAKE2b-256 8d85b046a08fe62ae9964de1465a0d08d0c42469176e283694973f4650aa4b47

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page