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.21.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.21-py3-none-any.whl (24.8 kB view details)

Uploaded Python 3

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

Hashes for banterbotapi-1.0.21.tar.gz
Algorithm Hash digest
SHA256 b0aa53056942c44325e12944851256e5ed9cd2fcbe5b8c6bd665c974c3790d4e
MD5 1fdbb78121a1a7a9204bff4f9a4a3706
BLAKE2b-256 de6c228d984344b93655ec75f65496ef52fee8f5909cb1878dd673c174f124dc

See more details on using hashes here.

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

Hashes for banterbotapi-1.0.21-py3-none-any.whl
Algorithm Hash digest
SHA256 1718aa88bba927122d988f87c97a0c5062ebfaf73feea51302f2eee3d02cf96d
MD5 6ba88c7bcd58cf858ae72b28278e1f7b
BLAKE2b-256 feafbb01e9a3c0e9d77d989c9a95794538c33aa4996099769deda39123e958c4

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