A lightweight Discord bot library built on the raw Discord API.
Project description
suneord
suneord — библиотека для Discord-ботов на Python: прямой Gateway и REST API, без discord.py, disnake, nextcord и аналогов.
Возможности
- Gateway + REST (
aiohttp). - Prefix-команды и slash-команды.
- Cogs, глобальные проверки и хуки
before_invoke/after_invoke. - Модальные окна: декораторы, сборка через
ModalBuilder, переиспользуемые наборы полей (combine_text_inputs), корректное слияние сshow_modal. - Кнопки:
ActionRow/Button, обработчики поcustom_id,ComponentContext(в т.ч.defer_update,update_message). Embed,Color,Intents.- Вспомогательные методы модерации.
Установка
pip install suneord
Требования: Python 3.12+, пакет aiohttp.
Быстрый старт
from suneord import Bot
bot = Bot(
prefix="!",
status="dnd",
activity="слежу за сервером",
case_insensitive=True,
)
@bot.event
async def on_ready():
print(f"{bot.username} успешно запущен и работает")
@bot.command("p", "пинг", description="Показывает пинг")
async def ping(ctx):
await ctx.send("Понг!")
@bot.slash_command(name="ping", description="Показывает задержку бота")
async def ping_slash(ctx):
await ctx.respond(f"Понг! {ctx.latency} мс")
bot.run("TOKEN")
Bot
from suneord import Bot
bot = Bot(
prefix="!",
status="online",
activity="работаю",
case_insensitive=True,
strip_after_prefix=True,
)
Параметры конструктора:
| Параметр | Назначение |
|---|---|
prefix |
Префикс текстовых команд |
status |
Статус в Discord (online, idle, dnd, invisible) |
activity |
Текст активности |
case_insensitive |
Команды без учёта регистра |
strip_after_prefix |
Убирать пробелы после префикса |
intents |
Объект Intents или целое число |
Полезные свойства:
user,username,avatar_url,mention,latency,latency_mscommands,slash_commands,modals,button_listeners,intents
Модальные окна и кнопки:
bot.modal(...)/bot.add_modal/get_modal/remove_modalbot.button(...)/add_button_listener/get_button_listener/remove_button_listener
Прочее:
run(token),start(token),close(),wait_until_ready(),sync_commands()fetch_user,fetch_member,get_command,has_command,remove_commandadd_cog/await add_cog_async,get_cog,remove_cog/await remove_cog_async
Intents
Файл suneord/intents.py:
from suneord import Bot, Intents
bot = Bot(
prefix="!",
intents=Intents.default().add(Intents.GUILD_MEMBERS),
)
Если intents не указаны, используется Intents.default().
Методы: default(), all(), none(), from_names(...), add, remove, has.
События
@bot.event
async def on_ready():
print(bot.username)
@bot.event
async def on_message(ctx):
print(ctx.content)
@bot.event
async def on_command_error(ctx, error):
await ctx.send_error(str(error))
@bot.event
async def on_slash_command_error(ctx, error):
await ctx.send_error(str(error))
@bot.event
async def on_modal_error(ctx, error):
await ctx.send_error(str(error))
@bot.event
async def on_component_error(ctx, error):
await ctx.send_error(str(error))
Жизненный цикл:
on_command,on_command_completion,on_command_erroron_slash_command,on_slash_command_completion,on_slash_command_erroron_modal_submit,on_modal_completion,on_modal_erroron_component,on_component_completion,on_component_erroron_interaction— для любого interaction (slash, modal, кнопка и т.д.)
Prefix-команды
@bot.command(description="Пинг")
async def ping(ctx):
await ctx.send("Понг!")
@bot.command("p", "пинг", description="Пинг")
async def ping2(ctx):
await ctx.send("Понг!")
Параметры декоратора: name, description, aliases, usage, cooldown, checks, hidden, enabled.
Аргументы парсятся через shlex (кавычки поддерживаются): !say "привет мир".
Slash-команды
from suneord import OptionType, SlashOption
@bot.slash_command(
name="avatar",
description="Показывает аватар",
options=[
SlashOption(
name="user",
description="Пользователь",
type=OptionType.USER,
required=True,
)
],
)
async def avatar(ctx):
user = ctx.option("user")
await ctx.respond(f"ID: {user['id']}")
OptionType: SUB_COMMAND, SUB_COMMAND_GROUP, STRING, INTEGER, BOOLEAN, USER, CHANNEL, ROLE, MENTIONABLE, NUMBER, ATTACHMENT.
Контексты
MessageContext
Свойства: bot, message, guild_id, channel_id, message_id, author, author_id, author_name, author_mention, content, args, args_text, command, invoked_with, latency, jump_url, channel_mention.
Методы: send, reply, typing, react, pin, unpin, edit, delete, send_success, send_error.
У send / reply / edit есть необязательный аргумент components: list[ActionRow] для кнопок.
SlashContext
Дополнительно: command_name, options, resolved, responded, deferred.
Методы: option(...), respond, send, defer, followup, edit_original_response, delete_original_response, send_success, send_error, show_modal.
В методы ответа можно передать components для кнопок в сообщении.
ModalContext
После отправки формы: custom_id, options (словарь custom_id поля → значение), value(...), те же методы ответа, что у slash (в т.ч. components в respond / followup).
ComponentContext (кнопки)
custom_id, component_type, message, message_id, listener (зарегистрированный обработчик).
defer_update() — отложить ответ без «думает…» для последующего редактирования сообщения.
update_message(...) — обновить сообщение, к которому привязана кнопка (interaction response type 7).
Embed
from suneord import Color, Embed
embed = Embed(
title="Заголовок",
description="Описание",
color=Color.BLURPLE,
)
embed.set_author(name="Suneord")
embed.add_field(name="Поле", value="Значение", inline=False)
embed.set_thumbnail(url="https://example.com/avatar.png")
embed.set_image(url="https://example.com/banner.png")
embed.set_footer(text="Footer")
embed.set_timestamp()
Модальные окна
Ограничения Discord: до 5 полей ввода, заголовок до 45 символов. Константы: MAX_MODAL_TEXT_INPUTS, MAX_MODAL_TITLE_LENGTH.
Регистрация обработчика + открытие
Обработчик вешается на custom_id. В команде удобно открывать окно без колбэка — библиотека не затирает уже зарегистрированный обработчик: в show_modal подставляются новый заголовок и набор полей, а callback и базовые metadata берутся из модалки, созданной декоратором.
from suneord import Bot, Modal, TextInput, TextInputStyle
bot = Bot(prefix="!")
@bot.modal(
custom_id="feedback_modal",
title="Обратная связь",
components=[
TextInput(
custom_id="feedback_text",
label="Твой отзыв",
style=TextInputStyle.PARAGRAPH,
min_length=5,
max_length=400,
)
],
)
async def feedback_submit(ctx):
text = ctx.value("feedback_text")
await ctx.send_success(f"Спасибо за отзыв:\n{text}")
@bot.slash_command(name="feedback", description="Открыть форму отзыва")
async def feedback(ctx):
await ctx.show_modal(
Modal(
custom_id="feedback_modal",
title="Обратная связь",
components=(
TextInput(
custom_id="feedback_text",
label="Твой отзыв",
style=TextInputStyle.PARAGRAPH,
),
),
)
)
Если передать в show_modal модалку с callback, она полностью заменит запись в реестре (как и add_modal).
Модульная сборка полей
Переиспользуемые куски формы и ModalBuilder:
from suneord import (
ModalBuilder,
TextInput,
TextInputStyle,
combine_text_inputs,
)
def contact_fields() -> tuple[TextInput, ...]:
return (
TextInput(custom_id="email", label="Email", style=TextInputStyle.SHORT),
TextInput(custom_id="note", label="Комментарий", style=TextInputStyle.PARAGRAPH),
)
extra = (TextInput(custom_id="topic", label="Тема", style=TextInputStyle.SHORT),)
modal = (
ModalBuilder("ticket_modal", "Заявка")
.extend_from(combine_text_inputs(contact_fields(), extra))
.add_short("priority", label="Приоритет", required=False, max_length=20)
.build()
)
# или вручную:
modal2 = Modal(
custom_id="ticket_modal",
title="Заявка",
components=combine_text_inputs(contact_fields(), extra),
)
combine_text_inputs принимает отдельные TextInput и/или итерируемые наборы полей и проверяет лимит в 5 полей.
Cog
from suneord import Cog, modal, TextInput
class FeedbackCog(Cog):
@modal(
custom_id="report_modal",
title="Жалоба",
components=[TextInput(custom_id="reason", label="Причина")],
)
async def report_submit(self, ctx):
await ctx.respond(ctx.value("reason"), ephemeral=True)
Кнопки
from suneord import ActionRow, Bot, Button, ButtonStyle
bot = Bot(prefix="!")
@bot.slash_command(name="demo", description="Сообщение с кнопкой")
async def demo(ctx):
row = ActionRow(
(
Button(ButtonStyle.PRIMARY, label="Нажми", custom_id="demo_btn"),
)
)
await ctx.respond("Выбери действие", components=[row])
@bot.button(custom_id="demo_btn")
async def on_demo_btn(ctx):
await ctx.respond("Нажато", ephemeral=True)
Кнопка-ссылка: Button(ButtonStyle.LINK, label="Сайт", url="https://example.com") — без custom_id, колбэк не вызывается.
Moderation API
await bot.kick(guild_id, user_id, reason="Причина")
await bot.ban(guild_id, user_id, reason="Причина", delete_message_seconds=86400)
await bot.unban(guild_id, user_id, reason="Причина")
await bot.mute(guild_id, user_id, minutes=10, reason="Причина")
await bot.unmute(guild_id, user_id, reason="Причина")
await bot.set_nickname(guild_id, user_id, "Новый ник", reason="Причина")
await bot.set_slowmode(channel_id, 10)
deleted = await bot.purge_messages(channel_id, limit=25)
unmute проверяет активный timeout; если его нет — MemberNotMuted.
from suneord import MemberNotMuted
@bot.event
async def on_slash_command_error(ctx, error):
if isinstance(error, MemberNotMuted):
await ctx.send_error("У этого пользователя нет активного мута.")
return
await ctx.send_error(str(error))
Checks и hooks
@bot.check
async def only_guild(ctx):
return ctx.guild_id is not None
@bot.before_invoke
async def before_any_command(ctx):
print("before", ctx.command.name)
@bot.after_invoke
async def after_any_command(ctx):
print("after", ctx.command.name)
Cogs
from suneord import Bot, Cog, command, slash_command
bot = Bot(prefix="!")
class Utility(Cog):
@Cog.listener()
async def on_ready(self):
print(f"{self.bot.username} готов")
@command("p", "пинг", description="Пинг")
async def ping(self, ctx):
await ctx.send("Понг!")
@slash_command(name="hello", description="Приветствие")
async def hello(self, ctx):
await ctx.respond("Привет из cog!")
bot.add_cog(Utility(bot))
bot.run("TOKEN")
Хуки: cog_load, cog_unload (для асинхронных версий используйте add_cog_async / remove_cog_async).
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
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 suneord-1.0.4.tar.gz.
File metadata
- Download URL: suneord-1.0.4.tar.gz
- Upload date:
- Size: 27.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
197a1af99cfe2412a01cbc160bd9e3d38a009bb91a67d0d49dc52a2b1601d172
|
|
| MD5 |
21208f0e86a91c677ae8ec681f529c9b
|
|
| BLAKE2b-256 |
423b746be68a314e31f94e1ad569650599a8448f299a9942860ac12dcaf6cac7
|
File details
Details for the file suneord-1.0.4-py3-none-any.whl.
File metadata
- Download URL: suneord-1.0.4-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebd61ca73258a41b92c00c5d609afdea1f36103675b496c89101f40747d12bdd
|
|
| MD5 |
aa3f143150d3b81123d742a6135d560b
|
|
| BLAKE2b-256 |
528c24bef879fc2735afeee674d8553737cc6a6b3b34778d8abb69f14c73277e
|