Plugin Manifest reference
Каждый плагин имеет файл plugin.manifest.json в корне. Это источник правды для идентичности плагина, необходимых ему разрешений, сетевых адресов, к которым он может обращаться, страниц администратора, которые он добавляет, и кнопок действий, которые он добавляет в интерфейс зрителя.
Plugins require Owncast 0.3.0 or later.
Манифест — это то, что администратор проверяет перед установкой плагина. Хост парсит его во время загрузки и обеспечивает выполнение каждого объявления. Ничто в собранном плагине не может предоставить возможности, которые манифест не запросил.
Манифест — это простой JSON, который описывает плагин для хоста, независимо от языка, на котором вы написали код. Для деталей, специфичных для языка, смотрите JavaScript или Python справочник по SDK.
Минимальный манифест
{
"api": "1",
"name": "My Plugin",
"version": "0.1.0",
"description": "Short description for admins",
"permissions": []
}
api, name и version обязательны. Все остальное является необязательным и требуется только при использовании соответствующей функции.
Поле верхнего уровня
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
api | строка | да | Версия схемы манифеста. В настоящее время "1". |
name | строка | да | Читаемое имя, отображаемое в списках администраторов и карточках реестра. Пример: "Потрясающий Эхо Бот". |
slug | строка | нет | Канонический идентификатор (префикс URL, пространство имен конфигурации, имя файла). Автоматически выводится из name, если опущен. Смотрите ниже. |
version | строка | да | Версия вашего плагина. SemVer recommended. Informational metadata for admins and the registry: the host doesn't gate loading on it. |
description | строка | нет | Краткое резюме, которое администратор видит в списке плагинов и во время установки. |
category | строка | нет | Registry browse category. See category. |
permissions | строка[] | нет | Список возможностей, необходимых вашему плагину. Смотрите Разрешения. |
config | объект | нет | Настройки, которые можно настроить администратором и которые ваш плагин считывает во время выполнения. Смотрите Конфигурация. |
bot | объект | нет | Конфигурация чат-бота. Смотрите bot. |
network | объект | нет | Список разрешенных HTTP-адресов, необходимый при предоставлении network.fetch. Смотрите ниже. |
actions | объект[] | нет | Кнопки действий, которые нужно добавить в интерфейс зрителя. Смотрите UI: Кнопки действий. |
admin | объект | нет | Страницы администратора, которые нужно добавить в интерфейс администратора Owncast. Смотрите UI: Страницы администратора. |
styles | строка[] | нет | CSS-файлы, встроенные в страницу зрителя. Смотрите styles. |
scripts | строка[] | нет | JavaScript-файлы, встроенные в страницу зрителя. Смотрите scripts. |
extraPageContent | объект | нет | Объект, объявляющий slug и необязательный HTML-файл, добавляемый в блок дополнительного содержимого зрителя. Смотрите extraPageContent. |
tabs | object | no | Viewer-page tabs keyed by stable slug. Смотрите tabs. |
name и slug
name — это читаемое имя. Он может содержать любые символы, включая пробелы и знаки препинания, и именно его видят администраторы в списке плагинов, что отображается на карточках реестра и является идентичностью чат-бота по умолчанию.
slug — это канонический идентификатор. Он контролирует:
- Префикс URL плагина:
/plugins/<slug>/... - Пространство имен конфигурации (хранилище ключ-значение)
- Имя файла собранного артефакта (
<slug>.ocpkg) - Первичный ключ в реестре плагинов
Slug — это строчные буквы, цифры и дефисы, начинающиеся с буквы, до 64 символов. SDK автоматически выводит его из name, если slug опущен: пробелы и знаки препинания сворачиваются в одиночные дефисы, буквы становятся строчными. "Потрясающий Эхо Бот" становится awesome-echo-bot. Закрепите slug явно, когда автоматическое выведение не соответствует вашим требованиям или когда ваше отображаемое имя использует символы за пределами ASCII ("Café Helper" в противном случае даст caf-helper).
Избегайте изменения slug после выпуска: переименование будет выглядеть как другой плагин для администраторов, с новым хранилищем конфигурации. Изменение name (только отображение) безопасно. Это не изменяет идентичность.
category: registry browse category
An optional label that places your plugin in a browse category on the registry and in the admin UI. The canonical values are chat-bots, chat-filters, moderation, authentication, themes, overlays, notifications, integrations, video, analytics, games, admin-utilities, examples, and other.
The SDK's packaging CLI warns when category isn't one of these, but nothing rejects it: the host and registry tolerate unknown categories, they just won't match any browse filter.
bot: идентичность чат-бота
Плагины, которые отправляют сообщения в чат (используя owncast.chat.send), появляются под пользователем чат-бота. По умолчанию бот отображается под отображаемым name плагина. Переопределите это с помощью bot.displayName:
{
"name": "Stream Sidekick",
"bot": {
"displayName": "Sidekick"
}
}
В чате бот отправляет сообщения как "Сопровождающий" вместо "Сопровождающий трансляции". В первый раз, когда плагин загружается, Owncast создает постоянного чат-пользователя, привязанного к slug плагина (так что идентичность бота сохраняется после переустановок и изменений отображаемого имени).
bot.displayName имеет значение только для плагинов, у которых есть разрешение chat.send. В противном случае оно игнорируется.
config: настройки, которые можно настроить администратором
Объявите типизированные настройки здесь, и Owncast отобразит редактируемую форму для них в администраторе, которую ваш плагин считывает во время выполнения с owncast.config.get. Каждая запись имеет type (string, number или boolean), default и description:
{
"config": {
"greeting": { "type": "string", "default": "welcome!", "description": "First-join message" },
"cooldownMs": { "type": "number", "default": 2000, "description": "Per-user command cooldown" },
"modOnly": { "type": "boolean", "default": false, "description": "Restrict to moderators" }
}
}
Config keys starting with __ are reserved: the host uses that prefix to inject per-instance state into the plugin runtime, and a manifest declaring one is rejected at load.
Полное покрытие, включая то, как отображается форма, маскировка учетных данных, проверка и где хранятся переопределения, в Конфигурации.
permissions
Каждая запись открывает доступ к части API хоста. Хост отклоняет вызовы к методу, разрешение на который вы не объявили.
{
"permissions": ["chat.send", "storage.kv", "network.fetch"]
}
Смотрите справочник по разрешениям для получения полного списка идентификаторов и того, что каждое из них предоставляет.
network: список разрешенных HTTP-адресов
network.fetch контролируется явным списком разрешенных имен хостов. Если вы объявляете network.fetch в permissions, вам также нужно поле network.allowedHosts, в котором перечислены хосты, к которым вы будете обращаться:
{
"permissions": ["network.fetch"],
"network": {
"allowedHosts": ["api.discord.com", "*.weather.com"]
}
}
Записи — это шаблоны имен хостов. Простые имена, такие как api.discord.com, совпадают точно. * — это знак подстановки, поэтому *.weather.com совпадает с api.weather.com и data.weather.com, но не с weather.com или evil.com.
Шаблон "*" совпадает с любым хостом, но вы должны записать его явно:
{
"network": { "allowedHosts": ["*"] }
}
Это намеренно. Администраторы, проверяющие манифест, видят объем разрешений, которые они предоставляют. Большинство плагинов должны перечислять конкретные хосты, к которым они обращаются.
Хост отклоняет загрузку, если network.fetch предоставлено без записи allowedHosts.
actions: кнопки действий
Кнопки действий — это кликабельные элементы, которые Owncast отображает под потоком. Пока ваш плагин активен, хост объединяет его записи в список, который уже отображает Owncast.
{
"permissions": ["ui.modify", "http.serve"],
"actions": [
{
"title": "Chat Overlay",
"description": "Open the live chat overlay",
"url": "/",
"icon": "/star.png",
"color": "#3b82f6"
},
{
"title": "Issue tracker",
"url": "https://github.com/example/my-plugin/issues",
"openExternally": true
},
{
"title": "About this stream",
"html": "<p>Live every weekday at 8pm UTC.</p>"
}
]
}
Каждая запись:
| Поле | Тип | Примечания |
|---|---|---|
title | string | Обязательно. Метка кнопки. |
url | string | Либо абсолютный https://... URL, либо путь. Взаимоисключающий с html. |
html | строка | Сырой HTML, отображаемый в модальном окне. Взаимоисключающий с url. |
icon | строка | Необязательный URL изображения, отображаемый на кнопке. Те же правила пути, что и для url. |
color | строка | Необязательный шестнадцатеричный цвет фона кнопки. |
описание | строка | Необязательно. Показано в модальном окне, которое открывается для действий по URL. |
openExternally | логическое | Если true, URL открывается в новой вкладке вместо инлайн-модального окна. |
Правила, которые хост применяет во время загрузки:
- Требуется разрешение
ui.modify. Без него манифест отклоняется. - Точно один из
urlилиhtmlна запись. - Относительные URL (и иконки), начинающиеся с
/, автоматически префиксируются пространством имен вашего плагина."/"становится/plugins/my-plugin/."/star.png"становится/plugins/my-plugin/star.png. Позволяет избежать жесткого кодирования имени вашего плагина. - URL (и иконки), которые разрешаются в пространстве имен вашего плагина, требуют
http.serve, поскольку это вы их обслуживаете. - URL (и иконки), указывающие на пространство имен другого плагина, отклоняются. Выявляет опечатки и предотвращает рекламу одного плагина в UI другого.
Полное покрытие в UI: Кнопки действий.
admin: страницы администратора
Plugins can register pages that appear in the Owncast admin UI under Plugins. The pages object is keyed by plugin-relative path glob:
{
"permissions": ["http.serve"],
"admin": {
"pages": {
"/admin": { "title": "Settings", "icon": "gear" }
}
}
}
Каждая запись имеет:
| Part | Тип | Примечания |
|---|---|---|
| object key | строка | Required path glob under the plugin's namespace, such as "/admin" or "/admin/*". |
title | строка | Обязательно. Метка вкладки, отображаемая в интерфейсе администратора. |
icon | строка | Необязательно. Короткое семантическое название (gear, wrench, user и др.). |
The host derives each page path from its object key. A key of "/admin" maps to /plugins/<your-slug>/admin. Requests matching any key are auth-gated by the host, so unauthenticated requests get a 401 before your plugin code runs.
JSON object order is not significant. Owncast displays admin pages in lexicographic path order. pages must be an object. Do not add a path member to a page value. The host rejects arrays and page values containing the legacy path member.
Полное покрытие в UI: Страницы администратора.
styles: Инъекция CSS
Список файлов CSS, которые плагин добавляет к странице зрителя. Содержимое каждого файла встроено в тот же блок <style>, который уже используется для пользовательского CSS администратора, поэтому плагины могут оформлять страницу без необходимости каждому вкладу иметь свой собственный тег <link>.
{
"permissions": ["ui.modify"],
"styles": ["theme.css", "overrides.css"]
}
Правила путей соответствуют URL кнопок действий:
- Пустые пути, такие как
"theme.css", автоматически префиксируются пространством имен вашего плагина. - Пути с одиночным слэшем, такие как
"/theme.css", обрабатываются аналогично. - Полностью квалифицированные пути
/plugins/<your-slug>/...проходят как есть. - Пути в пространстве имен другого плагина отклоняются.
- URL с
http://иhttps://отклоняются. Комплектуйте внешние ресурсы (шрифты, изображения) и ссылайтесь на них с помощью@font-faceилиurl(...)из вашего CSS, чтобы администратор, проверяющий манифест, видел каждый файл, который попадет на их страницу. - Каждая запись должна оканчиваться на
.css.
Требуется только ui.modify (плагин рендерит внутри интерфейса Owncast). http.serve не требуется: байты каждого файла считываются из assets/ и встраиваются в customStyles на /api/config, а не обслуживаются по URL. The host emits a /* plugin: <your-slug> ... */ comment in front of each contribution so a reader can attribute a rule back to whichever plugin shipped it.
Для CSS, который зависит от состояния плагина, обработчик onPageStyles возвращает его по запросу, без поля манифеста. Его вывод добавляется к customStyles после этих статических файлов.
Полное покрытие в UI: Стилевые таблицы зрителей.
scripts: Инъекция JavaScript
Список файлов JavaScript, которые плагин добавляет к странице зрителя. Содержимое каждого файла добавляется к тому же ответу, который уже приходит от пользовательского JavaScript администратора (/customjavascript), поэтому плагины могут расширять страницу без необходимости каждому вкладу иметь свой собственный тег <script>.
{
"permissions": ["ui.modify"],
"scripts": ["client.js"]
}
Правила путей и необходимые разрешения соответствуют styles, применяемым к .js файлам (требуется только ui.modify, а хост считывает из assets/ и встроит в /customjavascript). Оборачивайте ваш скрипт в IIFE, чтобы объявления верхнего уровня не конфликтовали с JavaScript администратора или другими плагинами. Хост добавляет комментарий // plugin: <your-slug> ... перед каждым вкладом и оборачивает каждый вклад в try/catch, чтобы ошибка выполнения одного плагина не сломала работу других.
Для JavaScript, который зависит от состояния плагина, обработчик onPageScripts возвращает его по запросу, без поля манифеста. Его вывод добавляется к /customjavascript после этих статических файлов.
Полное покрытие в UI: Скрипты зрителей.
extraPageContent: HTML блок
Объект, который добавляет HTML блок в область дополнительного контента зрителя, предшествует тексту администратора на /api/config.
{
"permissions": ["ui.modify"],
"extraPageContent": { "slug": "banner", "content": "content.html" }
}
| Поле | Тип | Примечания |
|---|---|---|
slug | строка | Обязательно только когда content не указан (хост передает это в onPageContent). В противном случае необязательно. Строчные буквы, цифры и дефисы, начинающиеся с буквы. |
content | string | По желанию. Относительный путь к статическому HTML файлу в assets/. Если файл присутствует, его байты встроены напрямую. Когда опущено, хост вызывает onPageContent вместо этого. |
Статический (с content): хост считывает файл во время запроса и встраивает байты. Тем же правилам, что и styles и scripts, применяемым к единственной записи .html. HTML плагина обходит процессор markdown, поэтому теги и атрибуты проходят как написаны.
Dynamic (without content): implement onPageContent({ slug, user? }) in your plugin to return HTML at request time. Используйте это, когда контент должен варьироваться для каждого зрителя или основываться на живых данных (например, персонализированные приветствия или текущая статистика стрима). user — это идентичность чата зрителя, присутствующая при аутентификации.
Требуется ui.modify. http.serve не требуется, так как HTML встроен в ответ конфигурации, а не обслуживается как URL. Each contribution is wrapped with an <!-- plugin: <your-slug> ... --> comment so a reader can attribute the markup back.
Полное покрытие в UI: Дополнительный контент страниц.
tabs: вкладки страницы зрителя
The tabs object contributes tabs to the viewer page's tab row next to the built-in About and Followers tabs. Each object key is the tab's stable slug. Every value requires title, and content is optional.
{
"permissions": ["ui.modify"],
"tabs": {
"music": { "title": "Music", "content": "music.html" },
"schedule": { "title": "Schedule", "content": "schedule.html" }
}
}
Each entry has:
| Part | Примечания |
|---|---|
| object key | Required stable slug. Строчные буквы, цифры и дефисы, начинающиеся с буквы. The host passes this key to onTabContent when content is omitted. |
title | Обязательно. Метка, отображаемая на вкладке. Должен быть уникальным среди вкладок плагина. |
content | По желанию. Относительный путь к HTML файлу в assets/. Тем же правилам, что и extraPageContent (автоматически префиксируется в ваше пространство имен, пути между плагинами и URL http(s):// отклоняются, должны оканчиваться на .html). When omitted, the host calls onTabContent. |
Within each plugin, Owncast displays tabs in lexicographic slug order. JSON object order is not significant. Ordering between tabs from different plugins is unspecified. tabs must be an object. Do not add a slug member to a tab value. The host rejects arrays and tab values containing the legacy slug member.
Требуется ui.modify. http.serve is not required: each static tab's HTML is read from assets/ and inlined into the pluginTabs[] array on /api/config. For a dynamic tab, the host passes the object key to onTabContent as slug and inlines the returned HTML.
Полное покрытие в UI: Вкладки страницы зрителя.
Контракт манифеста и времени выполнения
Когда ваш плагин загружается, хост разбирает манифест и запрашивает у времени выполнения регистрацию. It compares the two and rejects the load when:
- the slugs don't match (
slugis the canonical identity on both sides) - the runtime uses a permission that wasn't declared in the manifest
version is intentionally not compared. It's informational metadata the host gates nothing on, and the SDK bakes it into the registration from the same manifest at build time anyway.
Вы не пишете регистрацию самостоятельно: SDK генерирует это из обработчиков, которые вы объявляете (см. свое Справочное руководство SDK о том, как объявляются обработчики в вашем языке). Знание о существовании этого контракта полезно при отладке. Ошибка "разрешение, запрашиваемое во время выполнения, не объявлено в манифесте" означает, что вы добавили обработчик, которому требуется разрешение, которое вы забыли указать.
Полный пример
Не тривиальный манифест, использующий большинство функций:
{
"api": "1",
"name": "Stream Sidekick",
"slug": "stream-sidekick",
"version": "0.2.0",
"description": "Posts to Discord on stream start, shows an overlay, and adds a Donate button.",
"permissions": [
"chat.send",
"chat.filter",
"storage.kv",
"http.serve",
"http.sse",
"network.fetch",
"notifications.send",
"ui.modify"
],
"bot": {
"displayName": "Sidekick"
},
"network": {
"allowedHosts": ["api.discord.com", "*.example.com"]
},
"actions": [
{
"title": "Donate",
"url": "https://example.com/donate",
"openExternally": true
}
],
"admin": {
"pages": {
"/admin": { "title": "Sidekick settings", "icon": "gear" }
}
},
"styles": ["sidekick.css"],
"scripts": ["sidekick.js"],
"extraPageContent": { "slug": "intro", "content": "intro.html" },
"tabs": {
"schedule": { "title": "Schedule", "content": "schedule.html" }
}
}
Improve this page
See something missing or incorrect? Edit this page and improve the documentation for everyone.
Gabe Kangas