Перейти до змісту
mosaic

Документація

Напиши свій модуль

Усе, що потрібно, щоб модуль потрапив у Mosaic: структура, маніфест, приклад коду й правила pull request.

01

Як це працює

Кожен модуль — окрема папка в каталозі modules/ репозиторію бота. Бот завантажує модулі під час старту, а сайт читає маніфест і сам будує сторінку модуля та форму налаштувань.

Модулі ізольовані: у кожного свої команди, події, налаштування й переклади. Модуль не може читати дані інших модулів напряму — лише через публічний API бота.

02

Швидкий старт

Потрібні Python 3.12+, uv і тестовий Discord-сервер.

  1. Зробіть форк репозиторію mosaicbot-dev1/mosaic-bot-core і клонуйте його.
  2. Встановіть залежності: uv sync.
  3. Створіть заготовку модуля: uv run mosaic create birthdays.
  4. Запустіть бота локально на тестовому сервері: uv run mosaic dev.
terminal
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 123456789012345678

03

Структура модуля

Мінімальний модуль — це маніфест і точка входу. Решта — за потреби.

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 → роль».

modules/birthdays/module.toml
[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 = 300

05

Приклад коду

Модуль — це Python-пакет з об’єктом Module, який читає маніфест. Налаштування типізуються зі схеми маніфесту, а переклади доступні через ctx.t.

modules/birthdays/__init__.py
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: так бот ізолює модулі й очищає дані при видаленні.

modules/birthdays/commands.py
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, який перевіряє маніфест, права й переклади.

terminal
uv run ruff check modules/birthdays
uv run pytest
uv run mosaic validate birthdays

07

Правила pull request

PR приймається, якщо модуль відповідає цим правилам:

  • Один модуль — один PR. Назва у форматі feat(module): birthdays.
  • ruff, pytest і mosaic validate проходять без помилок, публічні функції мають анотації типів.
  • Модуль запитує лише ті права Discord, які справді використовує, і пояснює їх у README.
  • Жодних зовнішніх запитів без поля network у маніфесті. Персональні дані — лише необхідні.
  • Переклади щонайменше українською та англійською.
  • README з описом, списком команд і скриншотом налаштувань.
  • Код під ліцензією MIT, без запозиченого коду з несумісною ліцензією.

08

Рев’ю і реліз

Мейнтейнери переглядають PR протягом семи днів. Автоматичні перевірки запускаються одразу: збірка, тести, аналіз безпеки й перевірка маніфесту.

Після злиття модуль з’являється в каталозі з позначкою «Від спільноти» в найближчому релізі, а ви — у списку контриб’юторів.