01
Як це працює
Кожен модуль — окрема папка в каталозі modules/ репозиторію бота. Бот завантажує модулі під час старту, а сайт читає маніфест і сам будує сторінку модуля та форму налаштувань.
Модулі ізольовані: у кожного свої команди, події, налаштування й переклади. Модуль не може читати дані інших модулів напряму — лише через публічний API бота.
02
Швидкий старт
Потрібні Python 3.12+, uv і тестовий Discord-сервер.
- Зробіть форк репозиторію
mosaicbot-dev1/mosaic-bot-coreі клонуйте його. - Встановіть залежності:
uv sync. - Створіть заготовку модуля:
uv run mosaic create birthdays. - Запустіть бота локально на тестовому сервері:
uv run mosaic dev.
git clone https://github.com/<you>/mosaic-bot-core.git
cd mosaic-bot-core
uv sync
uv run mosaic create birthdays
uv run mosaic dev --guild 12345678901234567803
Структура модуля
Мінімальний модуль — це маніфест і точка входу. Решта — за потреби.
modules/birthdays/
├── module.toml
├── __init__.py
├── commands.py
├── events.py
├── locales/
│ ├── uk.json
│ ├── en.json
│ └── pl.json
└── README.md- module.toml
- маніфест: id, версія, автор, права й схема налаштувань
- __init__.py
- точка входу: Python-пакет, що реєструє команди, події й хуки
- commands.py
- слеш-команди та групи команд
- events.py
- обробники подій Discord і запланованих задач
- locales/
- переклади модуля, щонайменше uk та en
- README.md
- опис і список команд для каталогу
04
Маніфест
Сайт будує форму налаштувань саме з розділу settings. Поля групуються через section, доступні типи: boolean, number, text, select, color, channel і channels, role і roles, а також pairs — правила «число → значення», як-от «рівень 10 → роль».
[module]
id = "birthdays"
name = "Birthdays"
version = "1.2.0"
category = "community"
author = "olha-codes"
permissions = ["ManageRoles", "SendMessages"]
network = []
[[settings]]
section = "general"
key = "announceChannel"
type = "channel"
channel_type = "text"
[[settings]]
section = "general"
key = "birthdayRole"
type = "role"
[[settings]]
section = "general"
key = "timezone"
type = "select"
options = ["Europe/Kyiv", "Europe/Warsaw", "UTC"]
default = "Europe/Kyiv"
[[settings]]
section = "messages"
key = "birthdayMessage"
type = "text"
default = "Happy birthday, {user}!"
max_length = 30005
Приклад коду
Модуль — це Python-пакет з об’єктом Module, який читає маніфест. Налаштування типізуються зі схеми маніфесту, а переклади доступні через ctx.t.
from mosaic import Context, Module
module = Module.from_manifest(__file__)
@module.on_enable
async def on_enable(ctx: Context) -> None:
ctx.log.info("birthdays enabled on %s", ctx.guild.name)
# Importing the files registers their commands and events on `module`
from . import commands, events # noqa: E402,F401Команди — це async-функції з декоратором @birthday.command. Дані зберігайте лише через ctx.store: так бот ізолює модулі й очищає дані при видаленні.
from datetime import date
from discord import app_commands
from mosaic import CommandContext
from . import module
birthday = module.group("birthday", description="Birthdays")
@birthday.command("set", description="Set your birthday")
@app_commands.describe(day="Day of the month", month="Month, 1-12")
async def set_birthday(
ctx: CommandContext,
day: app_commands.Range[int, 1, 31],
month: app_commands.Range[int, 1, 12],
) -> None:
try:
when = date(2000, month, day) # a leap year, so 29 February is valid
except ValueError:
await ctx.reply(ctx.t("errors.invalidDate"), ephemeral=True)
return
# ctx.settings comes from the dashboard, ctx.store keeps this module's data
await ctx.store.user(ctx.user.id).set("birthday", when.strftime("%m-%d"))
await ctx.reply(ctx.t("set.success", date=when.strftime("%d.%m")), ephemeral=True)06
Перевірка локально
Перед PR запустіть ті самі перевірки, що й CI: ruff, pytest і mosaic validate, який перевіряє маніфест, права й переклади.
uv run ruff check modules/birthdays
uv run pytest
uv run mosaic validate birthdays07
Правила pull request
PR приймається, якщо модуль відповідає цим правилам:
- Один модуль — один PR. Назва у форматі
feat(module): birthdays. ruff,pytestіmosaic validateпроходять без помилок, публічні функції мають анотації типів.- Модуль запитує лише ті права Discord, які справді використовує, і пояснює їх у README.
- Жодних зовнішніх запитів без поля
networkу маніфесті. Персональні дані — лише необхідні. - Переклади щонайменше українською та англійською.
- README з описом, списком команд і скриншотом налаштувань.
- Код під ліцензією MIT, без запозиченого коду з несумісною ліцензією.
08
Рев’ю і реліз
Мейнтейнери переглядають PR протягом семи днів. Автоматичні перевірки запускаються одразу: збірка, тести, аналіз безпеки й перевірка маніфесту.
Після злиття модуль з’являється в каталозі з позначкою «Від спільноти» в найближчому релізі, а ви — у списку контриб’юторів.