Ir para o conteúdo principal

Plugin Manifest reference

Cada plugin tem um arquivo plugin.manifest.json em sua raiz. Esta é a fonte da verdade para a identidade do plugin, as permissões que ele precisa, os destinos de rede que é permitido chamar, as páginas de administração que ele contribui e os botões de ação que ele adiciona à interface do visualizador.

Plugin manifests require Owncast v0.3.0

Plugins require Owncast 0.3.0 or later.

O manifesto é o que um administrador revisa antes de instalar o plugin. O host o analisa na hora do carregamento e aplica cada declaração. Nada no plugin compilado pode conceder uma capacidade que o manifesto não solicitou.

Owncat informs youDisponível em todos os SDKs

O manifesto é JSON simples que descreve o plugin para o host, independentemente da linguagem que você usou para escrever o código. Para detalhes específicos da linguagem, consulte a referência do SDK JavaScript ou Python.

Manifesto mínimo

{
"api": "1",
"name": "My Plugin",
"version": "0.1.0",
"description": "Short description for admins",
"permissions": []
}

api, name, e version são obrigatórios. Todo o resto é opcional e só necessário quando você utiliza o recurso correspondente.

Campos de nível superior

CampoTipoObrigatórioDescrição
apistringsimVersão do esquema do manifesto. Atualmente "1".
namestringsimNome de exibição legível por humanos exibido nas listas de administradores e nos cartões de registro. Exemplo: "Awesome Echo Bot".
slugstringnãoIdentificador canônico (prefixo da URL, namespace de configuração, nome do arquivo). Derivado automaticamente de name se omitido. Veja abaixo.
versionstringsimA versão do seu plugin. SemVer recommended. Informational metadata for admins and the registry: the host doesn't gate loading on it.
descriptionstringnãoResumo de uma frase que o administrador vê na lista de plugins e durante a instalação.
categorystringnãoRegistry browse category. See category.
permissionsstring[]nãoLista de capacidades que seu plugin precisa. Veja Permissões.
configobjectnãoConfigurações configuráveis pelo administrador que seu plugin lê em tempo de execução. Veja Configuração.
botobjectnãoConfiguração do chatbot. Veja bot.
networkobjectnãoLista de permissão de saída HTTP, necessária quando network.fetch é concedido. Veja abaixo.
actionsobject[]nãoBotões de ação a serem adicionados à interface do visualizador. Veja UI: Botões de ação.
adminobjectnãoPáginas de administração a serem adicionadas à interface administrativa do Owncast. Veja UI: Páginas administrativas.
stylesstring[]nãoArquivos CSS incorporados na página do visualizador. Veja styles.
scriptsstring[]nãoArquivos JavaScript incorporados na página do visualizador. Veja scripts.
extraPageContentobjectnãoUm objeto declarando um slug e um arquivo HTML opcional prependido ao bloco de conteúdo extra do visualizador. Veja extraPageContent.
tabsobjectnoViewer-page tabs keyed by stable slug. Veja tabs.

name e slug

name é o nome de exibição legível por humanos. Pode conter quaisquer caracteres, incluindo espaços e pontuação, e é o que os administradores veem na lista de plugins, o que aparece nos cartões de navegação do registro e a identidade padrão do chatbot.

slug é o identificador canônico. Ele controla:

  • O prefixo da URL do plugin: /plugins/<slug>/...
  • O namespace do armazenamento de configuração (chave-valor)
  • O nome do arquivo do artefato construído (<slug>.ocpkg)
  • A chave primária no registro de plugins

Slugs são letras minúsculas, dígitos e hifens, começando com uma letra, até 64 caracteres. O SDK automaticamente deriva um de name quando slug é omitido: espaços e pontuações colapsam em hífens simples, letras em minúsculas. "Awesome Echo Bot" se torna awesome-echo-bot. Fixe o slug explicitamente quando a derivação automática não é o que você deseja, ou quando seu nome de exibição usa caracteres fora do ASCII ("Café Helper" resultaria em caf-helper).

Evite mudar o slug após o lançamento: a renomeação parecerá ser um plugin diferente para os administradores, com um novo armazenamento de configuração. Mudar name (apenas exibição) é seguro. Isso não muda a identidade.

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: identidade do chatbot

Plugins que postam no chat (usando owncast.chat.send) aparecem sob um usuário de chatbot. Por padrão, o bot aparece sob o name de exibição do plugin. Substitua isso com bot.displayName:

{
"name": "Stream Sidekick",
"bot": {
"displayName": "Sidekick"
}
}

No chat, o bot posta como "Apoio" ao invés de "Apoio ao Stream". Na primeira vez que o plugin carrega, o Owncast provisiona um usuário de chat persistente chaveado no slug do plugin (então a identidade do bot sobrevive reinstalações e mudanças de nome de exibição).

bot.displayName é apenas relevante para plugins que têm a permissão chat.send. É ignorado de outra forma.

config: configurações configuráveis pelo administrador

Declare as configurações tipadas aqui e o Owncast renderiza um formulário editável para elas no administrador, que seu plugin lê em tempo de execução usando owncast.config.get. Cada entrada tem um type (string, number, ou boolean), um default, e uma 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.

Cobertura total, incluindo como o formulário é renderizado, mascaramento de credenciais, validação, e onde as substituições são armazenadas, em Configuração.

permissions

Cada entrada desbloqueia um segmento das APIs do host. O host rejeita chamadas a um método cuja permissão você não declarou.

{
"permissions": ["chat.send", "storage.kv", "network.fetch"]
}

Veja a referência de permissões para a lista completa de identificadores e o que cada um concede.

network: lista de permissões HTTP de saída

network.fetch é controlado por uma lista explícita de nomes de host permitidos. Se você declarar network.fetch em permissions, você também precisa de um campo network.allowedHosts listando os hosts que você chamará:

{
"permissions": ["network.fetch"],
"network": {
"allowedHosts": ["api.discord.com", "*.weather.com"]
}
}

As entradas são coringas de nome de host. Nomes simples como api.discord.com correspondem exatamente. * é um segmento curinga, então *.weather.com corresponde a api.weather.com e data.weather.com, mas não a weather.com em si ou evil.com.

O coringa "*" corresponde a qualquer host, mas você deve escrevê-lo explicitamente:

{
"network": { "allowedHosts": ["*"] }
}

Isso é intencional. Administradores revisando o manifesto veem o escopo que estão concedendo. A maioria dos plugins deve listar os hosts específicos que eles chamam.

O host rejeita o carregamento se network.fetch for concedido sem uma entrada allowedHosts.

actions: botões de ação

Os botões de ação são entradas clicáveis que o Owncast exibe sob a transmissão. Enquanto seu plugin estiver habilitado, o host mescla suas entradas na lista que o Owncast já exibe.

{
"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>"
}
]
}

Cada entrada:

CampoTipoNotas
titlestringObrigatório. O rótulo do botão.
urlstringOu uma URL absoluta https://... ou um caminho. Mutuamente exclusivo com html.
htmlstringHTML bruto renderizado em um modal embutido. Mutuamente exclusivo com url.
iconstringURL de imagem opcional mostrada no botão. Mesmas regras de caminho como url.
colorstringCor hexadecimal opcional para o fundo do botão.
descriçãostringOpcional. Mostrado no modal que se abre para ações baseadas em URL.
openExternallybooleanoSe true, a URL se abre em uma nova aba em vez de um modal embutido.

Regras que o host aplica no momento do carregamento:

  • A permissão ui.modify é necessária. Sem isso, o manifesto é rejeitado.
  • Exatamente uma de url ou html por entrada.
  • URLs relativas (e ícones) que começam com / são prefixadas automaticamente ao namespace do seu plugin. "/" se torna /plugins/meu-plugin/. "/star.png" se torna /plugins/meu-plugin/star.png. Evita que você tenha que codificar o nome do seu plugin.
  • URLs (e ícones) que se resolvem em seu namespace requerem http.serve, pois você é quem os fornece.
  • URLs (e ícones) apontando para o namespace de outro plugin são rejeitados. Cata erros de digitação e impede que um plugin anuncie a interface de outro.

Cobertura completa em UI: Botões de ação.

admin: páginas administrativas

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" }
}
}
}

Cada entrada tem:

PartTipoNotas
object keystringRequired path glob under the plugin's namespace, such as "/admin" or "/admin/*".
títulostringObrigatório. O rótulo da aba mostrado na interface administrativa.
íconestringOpcional. Um nome semântico curto (gear, wrench, user e assim por diante).

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.

Cobertura completa em UI: Páginas administrativas.

styles: injeção de CSS

Uma lista de arquivos CSS que o plugin contribui para a página do visualizador. O conteúdo de cada arquivo é incorporado no mesmo bloco <style> que o Owncast já usa para o CSS personalizado do administrador, permitindo que os plugins temas a página sem que cada contribuição precise de sua própria tag <link>.

{
"permissions": ["ui.modify"],
"styles": ["theme.css", "overrides.css"]
}

As regras de caminho correspondem às URLs dos botões de ação:

  • Caminhos simples como "theme.css" são prefixados automaticamente ao namespace do seu plugin.
  • Caminhos de barra única como "/theme.css" recebem o mesmo tratamento.
  • Caminhos totalmente qualificados /plugins/<seu-slug>/... são aceitos sem modificações.
  • Caminhos no namespace de outro plugin são rejeitados.
  • URLs http:// e https:// são rejeitadas. Agrupe ativos externos (fontes, imagens) e referencie-os com @font-face ou url(...) dentro do seu CSS, para que um administrador que revise o manifesto veja cada arquivo que estará em sua página.
  • Cada entrada deve terminar com .css.

Requer apenas ui.modify (o plugin é renderizado dentro do chrome do Owncast). http.serve não é necessário: os bytes de cada arquivo são lidos de assets/ e incorporados em customStyles em /api/config, não fornecidos em uma 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.

Para CSS que depende do estado do plugin, um manipulador onPageStyles retorna isso no momento da requisição, sem campo de manifesto. Sua saída é adicionada a customStyles após estes arquivos estáticos.

Cobertura completa em UI: Folhas de estilo do visualizador.

scripts: injeção de JavaScript

Uma lista de arquivos JavaScript que o plugin contribui para a página do visualizador. O conteúdo de cada arquivo é adicionado à mesma resposta de onde já vem o JavaScript personalizado do administrador (/customjavascript), permitindo que os plugins estendam a página sem que cada contribuição precise de sua própria tag <script>.

{
"permissions": ["ui.modify"],
"scripts": ["client.js"]
}

As regras de caminho e permissões necessárias correspondem a styles, aplicadas a arquivos .js (apenas ui.modify é necessário, e o host lê de assets/ e incorpora em /customjavascript). Encapsule seu script em um IIFE para que declarações de nível superior não colidam com o JavaScript do administrador ou outros plugins. O host emite um comentário // plugin: <seu-slug> ... na frente de cada contribuição e encapsula cada contribuição em um try/catch para que um erro de tempo de execução de um plugin não quebre os outros.

Para JavaScript que depende do estado do plugin, um manipulador onPageScripts retorna isso no momento da requisição, sem campo de manifesto. Sua saída é adicionada a /customjavascript após esses arquivos estáticos.

Cobertura completa em UI: Scripts do visualizador.

extraPageContent: bloco HTML

Um objeto que contribui com um bloco HTML para a área de conteúdo extra do visualizador, inserido acima do texto do administrador em /api/config.

{
"permissions": ["ui.modify"],
"extraPageContent": { "slug": "banner", "content": "content.html" }
}
CampoTipoNotas
slugstringObrigatório apenas quando content está ausente (o host o passa para onPageContent). Opcional caso contrário. Letras minúsculas, dígitos e hífens, começando com uma letra.
contentstringOpcional. Caminho relativo para um arquivo HTML estático em assets/. Quando presente, os bytes desse arquivo são incorporados diretamente. Quando omitido, o host chama onPageContent em vez disso.

Estático (com content): o host lê o arquivo no momento da requisição e incorpora os bytes. Mesmas regras de caminho que styles e scripts, aplicadas a uma única entrada .html. O HTML do plugin ignora o processador de markdown, de modo que as tags e atributos passam como escrito.

Dynamic (without content): implement onPageContent({ slug, user? }) in your plugin to return HTML at request time. Use isso quando o conteúdo deve variar por visualizador ou depender de dados ao vivo (por exemplo, cumprimentos personalizados ou estatísticas de stream atuais). user é a identidade de chat do visualizador, presente quando autenticado.

Requer ui.modify. http.serve não é necessário porque o HTML é incorporado na resposta de configuração, não servido como uma URL. Each contribution is wrapped with an <!-- plugin: <your-slug> ... --> comment so a reader can attribute the markup back.

Cobertura completa em UI: Conteúdo extra da página.

tabs: abas da página do visualizador

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:

PartNotas
object keyRequired stable slug. Letras minúsculas, dígitos e hífens, começando com uma letra. The host passes this key to onTabContent when content is omitted.
títuloObrigatório. O rótulo exibido na aba. Deve ser único dentro das abas do plugin.
contentOpcional. Caminho relativo para um arquivo HTML em assets/. Mesmas regras de caminho que extraPageContent (prefixo automático ao seu namespace, caminhos entre plugins e URLs http(s):// rejeitados, deve terminar em .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.

Requer 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.

Cobertura completa em UI: Abas da página do visualizador.

Contrato manifesto-runtime

Quando seu plugin é carregado, o host analisa o manifesto e pede ao runtime para se registrar. It compares the two and rejects the load when:

  • the slugs don't match (slug is 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.

Você não escreve o registro você mesmo: a SDK o gera a partir dos manipuladores que você define (veja sua referência de SDK sobre como os manipuladores são declarados em sua linguagem). Saber que esse contrato existe é útil ao depurar. Um erro "permissão solicitada em tempo de execução não declarada no manifesto" significa que você adicionou um manipulador que precisa de uma permissão que você esqueceu de listar.

Exemplo completo

Um manifesto não trivial que utiliza a maioria dos recursos:

{
"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.

Contributors to this documentation
Gabe KangasGabe Kangas
G
Gabe Kangas