17 KiB
Плагины Komet
Плагин добавляет в чат slash-команды. Пользователь пишет /weather Москва, Komet
запускает обработчик плагина в изолированном движке JavaScript, и тот работает с
чатом через модуль komet:api.
Плагин распространяется файлом .kinet — это zip-архив с manifest.json и
ES-модулями. Установка: настройки Komet → «Плагины» → «Установить .kinet» или
«Установить по URL».
В этом репозитории:
| Файл | Что это |
|---|---|
plugin_sdk/komet-api.d.ts |
типы komet:api для подсказок в редакторе |
plugin_sdk/manifest.schema.json |
JSON Schema для manifest.json |
plugin_sdk/update-manifest.schema.json |
JSON Schema для манифеста обновлений |
tool/kinet_sign.dart |
ключи и подпись пакетов |
assets/plugins/ |
встроенные плагины — готовые примеры |
Быстрый старт
hello/
├── manifest.json
└── main.js
manifest.json:
{
"$schema": "https://raw.githubusercontent.com/KometTeam/Komet/main/plugin_sdk/manifest.schema.json",
"schemaVersion": 1,
"id": "com.example.hello",
"name": "Hello",
"version": "1.0.0",
"apiVersion": 1,
"description": "Приветствие",
"author": "example",
"main": "main.js",
"permissions": ["chat.write"],
"commands": [
{
"name": "/hello",
"description": "поздороваться",
"handler": "hello",
"arguments": [
{"name": "name", "description": "кого приветствовать", "required": false, "rest": true}
]
}
]
}
main.js:
import { chat } from 'komet:api';
/** @param {import('komet:api').CommandContext} context */
export async function hello(context) {
const name = context.arguments.name || 'мир';
await chat.sendText(`Привет, ${name}!`);
}
Сборка пакета — manifest.json должен лежать в корне архива:
cd hello && zip -r ../hello.kinet . -i 'manifest.json' '*.js'
Для подсказок в редакторе держите komet-api.d.ts рядом с проектом, но не кладите в
пакет: в .kinet допустимы только manifest.json и файлы .js.
Манифест
| Поле | Обязательно | Описание |
|---|---|---|
schemaVersion |
да | всегда 1 |
id |
да | уникальный id в обратной доменной записи: com.example.hello. Префикс pw.komet. занят встроенными плагинами |
name |
да | название в списке плагинов |
version |
да | SemVer: 1.2.3, 1.2.3-beta.1, 1.2.3+5 |
apiVersion |
да | версия komet:api, которую ждёт плагин. Сейчас 1; плагин с большей версией не установится |
description |
нет | описание в списке плагинов |
author |
нет | автор |
main |
да | путь к главному модулю внутри пакета, .js |
permissions |
да | разрешения, см. ниже. Может быть пустым массивом |
commands |
да | хотя бы одна команда |
updateUrl |
нет | HTTPS-адрес манифеста обновлений, см. «Обновления» |
signature |
нет | подпись; её добавляет tool/kinet_sign.dart |
Команды
| Поле | Обязательно | Описание |
|---|---|---|
name |
да | / и латиница, цифры, _, -, до 32 символов после /. Ведущий / можно не писать |
description |
да | текст в подсказке команд |
handler |
да | имя функции, экспортированной из main |
arguments |
нет | аргументы, см. ниже |
hidden |
нет | true — команда работает, но не показывается в подсказках |
Имена команд внутри плагина не должны повторяться без учёта регистра. Если имя уже занято встроенной командой или другим плагином, команда не появится.
Аргументы
| Поле | По умолчанию | Описание |
|---|---|---|
name |
— | латиница, цифры, _, -, до 32 символов, начинается с буквы |
description |
"" |
подпись поля ввода |
required |
true |
без значения команда не запустится, Komet покажет формат вызова |
rest |
false |
забирает весь остаток строки; только у последнего аргумента |
Аргументы разделяются пробелами. Значение с пробелами берут в двойные или одинарные
кавычки, внутри кавычек \ экранирует следующий символ. Если у команды есть
аргументы, при её выборе в подсказке Komet показывает поле для каждого из них.
Контекст команды
Обработчик получает один объект:
| Поле | Тип | Описание |
|---|---|---|
args |
string |
всё, что написано после имени команды |
arguments |
Record<string, string> |
значения аргументов по имени; не заданные — пустая строка |
reply |
object | null |
сообщение, на которое отвечает пользователь. Только с разрешением message.readReply, иначе null |
apiVersion |
number |
версия komet:api в приложении |
reply: { id, senderId, text, time, attachments: [{ type }] }. time — миллисекунды
Unix, text может быть null, type — photo, video, audio, file, sticker и
другие значения из komet-api.d.ts.
Разрешения
Пользователь видит список разрешений при установке и выдаёт их все сразу. Вызов без
нужного разрешения завершается ошибкой Нет разрешения <id>.
| Разрешение | Что даёт |
|---|---|
chat.write |
chat.sendText |
chat.edit |
chat.editText |
chat.photo |
chat.sendPhoto |
chat.file |
chat.sendFile |
ui.notify |
ui.notify |
contact.read |
contact.getPeer |
message.readReply |
context.reply |
network |
network.fetch и загрузка по url в chat.sendPhoto / chat.sendFile |
storage |
storage.get, storage.set, storage.remove |
Методы runtime.* разрешений не требуют.
API komet:api
import { chat, network, ui, contact, runtime, storage } from 'komet:api';
Все методы возвращают Promise. При ошибке Promise отклоняется с Error, в
message — причина.
chat
chat.sendText(text) → Promise<string> — отправляет сообщение в текущий чат и
возвращает его id. Текст — до 65 536 символов. В зашифрованном чате текст шифруется
так же, как обычное сообщение.
chat.editText(messageId, text) → Promise<void> — меняет текст сообщения. Можно
редактировать только сообщения, отправленные этим же запуском команды.
chat.sendPhoto({ base64, url, filename, caption }) → Promise<void> — отправляет
фото до 15 МБ. Нужен base64 или url; если заданы оба, берётся base64. Загрузка
по url требует разрешения network.
chat.sendFile({ base64, url, filename }) → Promise<void> — отправляет файл до
25 МБ, источник задаётся так же. В зашифрованных чатах пока недоступен.
В filename символы кроме латиницы, кириллицы, цифр, ., _, - и пробела
заменяются на _, длина обрезается до 120 символов. Загрузка по url принимает
только HTTPS, следует не более чем 5 редиректам и делает до 3 попыток.
network
network.fetch(url, { method, headers, body }) →
Promise<{ status, headers, body, base64 }>.
- Только
https://.localhostи частные IP-адреса запрещены. - Методы:
GET(по умолчанию),POST,PUT,PATCH,DELETE. - Редиректы не выполняются: ответ 301, 302, 303, 307 или 308 завершается ошибкой.
- Заголовки
Host,Content-Length,Connection,Proxy-Authorizationигнорируются. body— строка. Объект сначала превратите в JSON:JSON.stringify(data).- Таймауты: соединение 10 с, заголовки ответа 15 с, тело 20 с. Ответ — до 1 МБ.
bodyответа — текст в UTF-8 (некорректные байты заменяются),base64— те же байты без перекодирования, например дляchat.sendPhoto.- Коды 4xx и 5xx не считаются ошибкой — проверяйте
status.
ui
ui.notify(message) → Promise<void> — показывает уведомление Komet внутри
приложения, пока открыт чат.
contact
contact.getPeer() → Promise<Peer | null> — данные собеседника в личном диалоге,
всегда свежие с сервера. Вне диалога возвращает null.
Peer: { id, displayName, country, registrationTime, updateTime, options }.
registrationTime и updateTime — миллисекунды Unix или null, options — массив
строк-флагов профиля.
runtime
runtime.sleep(milliseconds) → Promise<void> — пауза от 0 до 10 000 мс.
runtime.isOnline() → Promise<boolean> — есть ли соединение с сервером.
runtime.isActive() → Promise<boolean> — открыт ли ещё чат, из которого вызвана
команда. Проверяйте в длинных сценариях, чтобы не работать впустую.
storage
Постоянное хранилище плагина. Оно переживает обновления и удаляется вместе с плагином.
storage.get(key) → Promise<value | null> — значение или null, если ключа нет.
storage.set(key, value) → Promise<void> — сохраняет любое JSON-значение.
storage.remove(key) → Promise<void> — удаляет ключ.
Ключ — от 1 до 64 символов из A-Z a-z 0-9 . _ -. Всё хранилище плагина в JSON — до
64 КБ.
Среда выполнения
- Движок — QuickJS. Доступны стандартные объекты языка:
Promise,async/await,JSON,Date,Math,Map,RegExpи т. д. - Нет
console,setTimeout, глобальногоfetch,requireи модулей Node. Вместо них —runtime.sleepиnetwork.fetch. - Импортировать можно только
komet:apiи модули своего пакета по относительному пути (./utils.js). Выход за пределы пакета (../) запрещён. - Каждый запуск команды — новый движок: переменные модуля между запусками не
сохраняются, для этого есть
storage. - Лимиты: 16 МБ памяти, 256 КБ стека, 30 секунд на запуск. По истечении времени движок останавливается.
- Необработанное исключение показывается пользователю как «Ошибка плагина: …».
Пакет .kinet
- zip-архив до 5 МБ, не больше 128 файлов;
- каждый файл до 2 МБ, всё вместе в распакованном виде до 10 МБ;
- только
manifest.jsonв корне и файлы.js; - без символических ссылок, абсолютных путей и
..; - файл
mainдолжен быть в архиве.
Подпись
Подписанный плагин показывает при установке отпечаток ключа автора. Обновления подписанного плагина принимаются, только если они подписаны тем же ключом; снять подпись в обновлении тоже нельзя.
dart run tool/kinet_sign.dart generate-key ~/.komet/hello-key.json
dart run tool/kinet_sign.dart sign hello.kinet ~/.komet/hello-key.json hello-signed.kinet
dart run tool/kinet_sign.dart verify hello-signed.kinet
generate-key создаёт ключ Ed25519 с правами 0600 и печатает отпечаток. sign
добавляет в манифест поле signature; без третьего аргумента пакет перезаписывается
на месте. Подпись покрывает манифест без поля signature и SHA-256 каждого .js
модуля, поэтому после подписи пакет менять нельзя.
Храните приватный ключ в секрете. Если он потерян, обновить плагин у пользователей не получится — им придётся удалить его и установить заново.
Обновления
Укажите в манифесте updateUrl — HTTPS-адрес JSON-файла:
{
"$schema": "https://raw.githubusercontent.com/KometTeam/Komet/main/plugin_sdk/update-manifest.schema.json",
"version": "1.1.0",
"packageUrl": "hello-1.1.0.kinet",
"size": 2048,
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
}
| Поле | Описание |
|---|---|
version |
версия нового пакета, совпадает с version в его манифесте |
packageUrl |
адрес .kinet, абсолютный или относительный к updateUrl; итоговый адрес — только HTTPS |
size |
размер пакета в байтах; 0 отключает проверку |
sha256 |
SHA-256 пакета в hex (shasum -a 256 hello.kinet); пустая строка отключает проверку |
Пользователь проверяет обновления из меню плагина. Komet предлагает обновление, только
если version больше установленной по правилам SemVer, и отказывается его ставить,
если:
- у пакета другой
idилиversion; - не сошлись
sizeилиsha256; - обновление просит разрешения, которых нет у установленной версии, — такой плагин нужно переустановить;
- пакет подписан другим ключом или подпись снята.