アプリ(プラグイン)の作成
アプリはAntimatterの内部から機能を追加します。サーバーにフックするGoで書かれたサーバー部分と、メニュー、パネル、メッセージタイプ、ページを追加するReactで書かれたWebアプリ部分があります。このページでは、バンドルされているPollsアプリを例に、アプリの構成と、AntimatterのFusionインターフェースでの変更点について説明します。
アプリの仕組み
- サーバー部分はGoプログラムです。サーバーはこれを独立したプロセスとして起動し、RPCを介して通信します。サーバーはアプリのフック(メッセージが投稿されようとしている、コマンドが実行されたなど)を呼び出し、アプリはサーバーのプラグインAPI(投稿の作成、ユーザーの読み取り、データの保存など)を呼び出します。また、
/plugins/<app id>/配下のHTTPルートを提供することもできます。 - Webアプリ部分はWebアプリとともにブラウザに読み込まれるJavaScriptバンドルです。レジストリにReactコンポーネントとコールバックを登録します:メニュー内のボタン、パネル、メッセージタイプの描画方法などです。
- どちらの部分も省略可能です。アプリは
.tar.gzバンドルとしてインストールされ、システム管理者が有効化、無効化、設定を行います。
AntimatterはMattermost向けに書かれたアプリを実行でき、その逆も可能です(アプリが両方に共通するフックのみを使用する場合)。Antimatter固有のフックを参照してください。
バンドル構成
個別のスターターテンプレートはありません:antimatter-plugin-pollsやantimatter-plugin-todoなどの小さなバンドル済みアプリから始め、IDとコードを変更してください。Pollsの構成は以下の通りです:
antimatter-plugin-polls/
├── plugin.json the manifest
├── Makefile build, test, bundle, deploy
├── build/ the manifest and pluginctl helper tools (Go), setup.mk
├── assets/icon.svg files shipped with the bundle
├── go.mod
├── server/
│ ├── main.go plugin.ClientMain(&Plugin{})
│ ├── plugin.go the Plugin type, OnActivate, ServeHTTP
│ ├── manifest.go generated from plugin.json
│ ├── api.go, command.go, hooks.go, store.go …
│ └── dist/ compiled binaries, one per platform
└── webapp/
├── package.json, webpack.config.js, tsconfig.json
├── i18n/ en.json and one file per language
├── src/index.tsx registers the app with the web app
├── src/manifest.ts generated from plugin.json
├── src/translations.ts
└── dist/main.js the bundle
任意:public/フォルダ。/plugins/<app id>/public/でそのまま配信されます。
plugin.json
{
"id": "com.example.weather",
"name": "Weather",
"description": "Forecasts in the chat.",
"homepage_url": "https://github.com/example/weather",
"support_url": "https://github.com/example/weather/issues",
"icon_path": "assets/icon.svg",
"version": "1.0.0",
"min_server_version": "12.0.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"linux-arm64": "server/dist/plugin-linux-arm64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"darwin-arm64": "server/dist/plugin-darwin-arm64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"webapp": {"bundle_path": "webapp/dist/main.js"},
"settings_schema": {"settings": []}
}
id:一意かつ永続的である必要があります。逆ドメイン名が一般的です。ルート、保存される設定、WebSocketイベントの名前になります。version:セマンティックバージョニング。バンドル済みアプリはplugin.jsonからこれを省略しており、ビルドツールが最新のv*gitタグから取得します。min_server_version:Antimatterは12.0.0を報告します。settings_schema: システムコンソールのアプリ設定ページ(設定を参照)。
make apply はマニフェストを server/manifest.go と webapp/src/manifest.ts にコピーします。make manifest-check はその検証を行います。
サーバー部分
サーバー部分は、Mattermost アプリと同じ Go モジュール github.com/mattermost/mattermost/server/public を Go モジュールプロキシからインポートします。その main はアプリの起動のみを行います。
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
Plugin 型は plugin.MattermostPlugin を埋め込み、p.API を取得するとともに、必要なフックを実装します。
type Plugin struct {
plugin.MattermostPlugin
router *mux.Router
}
func (p *Plugin) OnActivate() error {
p.router = p.newRouter()
return p.API.RegisterCommand(&model.Command{
Trigger: "weather",
AutoComplete: true,
AutoCompleteDesc: "Show the forecast",
AutoCompleteHint: "[city]",
})
}
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
city := strings.TrimSpace(strings.TrimPrefix(args.Command, "/weather"))
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
Text: "Forecast for " + city + ": sunny",
}, nil
}
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
p.router.ServeHTTP(w, r)
}
一般的なフック
| フック | 実行タイミング |
|---|---|
OnActivate, OnDeactivate | アプリの有効化または無効化時、およびサーバーの起動と停止時。OnActivate でコマンドとボットを登録します。 |
OnConfigurationChange | 設定が変更されたとき。p.API.LoadPluginConfiguration(&cfg) で読み込みます。 |
ServeHTTP | /plugins/<app id>/… へのリクエスト。ログイン済みユーザーからのリクエストの場合、サーバーは Mattermost-User-Id ヘッダーを設定します。公開目的でない限り、このヘッダーがないリクエストは拒否してください。 |
ExecuteCommand | アプリが登録したコマンドが実行されたとき。 |
MessageWillBePosted, MessageWillBeUpdated | メッセージが保存される前:変更された投稿を返すか、拒否理由を返します。 |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | 事後に、副作用のために実行されます。 |
ReactionHasBeenAdded, ReactionHasBeenRemoved | リアクション。 |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | メンバーシップとチャンネルの変更。チームはコミュニティです。 |
FileWillBeUploaded | ファイルが保存される前:変更するか拒否します。 |
OnPluginClusterEvent | クラスタ内の別のサーバー上の同じアプリからのメッセージ。 |
正確なシグネチャ付きの完全なリストは server/public/plugin/hooks.go にあります。API は server/public/plugin/api.go にあります。pluginapi パッケージ(server/public/pluginapi)は、API をより使いやすいヘルパーでラップしています。
便利な API 呼び出し
CreatePost、UpdatePost、SendEphemeralPost:アプリのボットまたはユーザーとして投稿します。EnsureBotUser(&model.Bot{Username, DisplayName}):アプリのボットを一度作成し、その ID を取得します。PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}):Web アプリにイベントを送信します。custom_<app id>_poll_updatedとして届きます。OpenInteractiveDialog:ダイアログを開きます。LogInfo、LogErrorなどの関数:アプリの ID をタグとしてサーバーログに書き込みます。GetUser、GetChannel、HasPermissionToChannel:ユーザーに代わって操作する前に、誰が何をできるかを確認します。
アプリはクラスタのすべてのサーバーで実行されます。状態はメモリだけでなく、キーバリューストアまたはデータベースに保持してください。
AI サービス
Antimatter のサーバーは、管理者が設定したモデル接続(管理ガイドの AI を参照)を通じて、アプリのために AI モデルを呼び出します。アプリはプロバイダーのキーを保持しません。アプリは、誰のために、どこで動作しているかを指定するだけで、サーバーがその人物がその場所で接続を使用できるかを確認し、制限と予算を適用し、トークンを人物とアプリに対して計数し、コンテンツを含まずに呼び出しを監査します。
| メソッド | 機能 |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | チャット補完:メッセージ、ツール、構造化出力(JSONSchema)。 |
AIChatStream(…) | ストリーミング版:チャネルが閉じるまでストリームの Events を読み取るか、Close を呼び出して呼び出しを停止します。 |
AIEmbed(…, *ai.EmbedRequest) | テキストの埋め込み(Embeddings)。 |
AITranscribe(…, *ai.TranscribeRequest) | 音声からテキストへの変換。 |
AIDecide(…, *ai.DecideRequest) | テキストに関する決定モデルへの型付き質問(はい/いいえ、選択肢、スコア)。 |
AIConnectionsForUser(userID, channelID, teamID) | その場所で人物が使用できる接続、そのモデルと機能(キーは含まれません)。 |
各リクエストの ConnectionID が接続を選択し、Model がその中のモデルを選択します(空の場合は接続のデフォルト)。Operation は使用量と監査記録用の短いラベルであり、コンテンツではありません。これらにはサーバーバージョン 11.11 以上が必要です。
actingUserID は、サーバーが検証したソースからのみ取得してください:ServeHTTP へのリクエストの Mattermost-User-Id ヘッダー、またはフックのコンテキスト内のユーザー(投稿の作成者、コマンドを実行したユーザー)。メッセージ、Webhook ペイロード、URL、または他のアプリのリクエストから取得しないでください。そうすると、他の人のアクセス権を消費することになります。これらのメソッドと ai パッケージ(server/public/ai)は、Mattermost が公開したバージョンではなく、Antimatter のモジュールのコピーにのみ存在します。これらを使用するには、go.mod に replace 行を追加して、Antimatter の server/public に対してビルドしてください。例:replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public。このようなアプリは Antimatter でのみ実行できます。
データの保存
各アプリには、サーバーのデータベースに独自のキーバリューストアがあります。
KVSet(key, value)、KVGet(key)、KVDelete(key):値はバイト列(通常は JSON)です。キーは最大 150 文字です。KVSetWithExpiry(key, value, seconds):指定した時間後に値が削除されます。KVCompareAndSet(key, old, new)およびAtomicを使用したKVSetWithOptions:複数のリクエストやサーバーが同じキーに書き込む場合の安全な更新に使用します。Polls はこの方法で各投票を保存し、競合が発生した場合は再試行します。KVList(page, perPage):ページごとにキーを列挙します。
Web アプリ部分
バンドルは window.registerPlugin でクラスを登録します。その initialize はレジストリと Redux ストアを受け取ります。
import manifest from './manifest';
class Plugin {
initialize(registry, store) {
registry.registerTranslations((locale) => translationsFor(locale));
registry.registerPostTypeComponent('custom_poll', PollPost);
registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
});
registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
}
}
window.registerPlugin(manifest.id, new Plugin());
- 共有ライブラリ。 Web アプリは React、ReactDOM、Redux、React Redux、React Intl を提供します。これらを webpack の
externals(React、ReactDOM、Redux、ReactRedux、ReactIntl)として列挙し、独自のコピーをバンドルしないでください。Polls のwebpack.config.jsを参照してください。 - サーバー部分への呼び出し。
Client4.getOptions()(mattermost-redux/clientから)のオプションを使用して、${window.basename}/plugins/<app id>/…をフェッチします。これにより、サーバーがブラウザからのリクエストを受け入れるために必要なヘッダーが送信されます。 - 独自の状態。
registerReducerはリデューサーを追加し、state['plugins-<app id>']以下に保存されます。 - 型。 Antimatter の追加レジストリメソッドは、Mattermost の公開されている型には含まれていません。Polls は使用するレジストリを
webapp/src/types/host.tsで宣言しています。
レジストリのメソッドとそのパラメータは、webapp/fusion/src/plugins/registry.ts(Fusion)および webapp/channels/src/plugins/registry.ts(クラシック)のコード内でドキュメント化されています。
Fusion インターフェースへの対応
Antimatter には Fusion(デフォルト)とクラシックという 2 つの Web インターフェースがあります。どちらも同じアプリバンドルを読み込み、同じレジストリを持ちますが、Fusion は多くの要素を独自に描画し、プラグインコンポーネントも独自の方式で配置します。両方のインターフェースでアプリをテストしてください。
インターフェースの検出
アプリが読み込まれる前に、両方のインターフェースは window.antimatterWebUI を 'fusion' または 'classic' に設定し、ページのルート要素にも data-am-web-ui として同じ値を設定します。Mattermost では、これらは設定されません。
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
バンドルアプリは、これを使用して異なる描画を行います。Pollsは、Fusionのスタイルで投票カードと作成ダイアログを描画し、Fusion内の/pollには独自のダイアログを開きます。CallsとVoice channelsは、Fusionが独自の通話インターフェースで置き換えるサイドバーとトーストコンポーネントを省略します。Agentsアプリは、ホバーツールバーではなく、その他のアクションの下にあるグループとして、AIアクションをメッセージメニューに配置します。Fusion独自のCSSクラス(am-プレフィックス付き)とダイアログレイヤー(am-layer要素)は、これらのアプリがインターフェースに合わせるために使用するものであり、文書化された安定したAPIではありません。
Fusionでプラグインコンポーネントが表示される場所
| レジストリメソッド | Fusionでの表示場所 |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | アプリレール(右端のアイコン列)で、アバターとAntimatterについてボタンの間です。チャンネルヘッダーのボタンもここに表示され、チャンネルヘッダー内ではありません。システム管理者がApp Barを無効にする(ExperimentalSettings.DisableAppBar、デフォルトではオフ)をオンにした場合、非表示になります。 |
registerRightHandSidebarComponent | 右側のパネルです。ユーザーはエッジをドラッグしてサイズを変更でき、新しいウィンドウで開くこともできます。アプリがプロダクトでもある場合、パネルにはページ全体で開くが提供されます。新しいウィンドウでのパネルを参照してください。 |
registerChannelHeaderIcon | チャンネルヘッダー内の通話ボタンの後のアイコンです。スマートフォンでは、ヘッダーのさらにメニュー内に表示されます。 |
registerCallButtonAction | チャンネルヘッダーの通話ボタンです。複数のプロバイダーがその他の通話方法メニューを共有します。 |
registerPostDropdownMenuAction | メッセージのメッセージアクション(⋯)メニューのその他のアクションサブメニュー内、およびスレッドパネルのスレッドの操作メニューのその他のアクション下にあります。Fusionは、各アクションの前にアプリのアイコンをグレーで描画します。バンドルアプリの場合はその独自のアイコン、それ以外の場合はアプリレールに表示されるアイコン、さらにそれがない場合はニュートラルなアイコンです。アクションテキストの先頭にテキストがない要素(アプリが独自に描画するアイコン)は非表示になります。 |
registerPostDropdownSubMenuAction | 単一アクションの後、同じその他のアクションサブメニュー内の見出し付きグループです。メッセージメニューにアプリ固有のエントリは作成されません。 |
registerPostActionComponent | メッセージのホバーツールバーで、⋯ボタンの前です。 |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | メッセージ本体です。テキストの下、時刻の後、リンクプレビューの代わりに表示されます。 |
registerChannelHeaderMenuAction | チャンネルのチャンネルオプションメニュー(サイドバーのチャンネルの⋯、または右クリック)で、プラグインアイコン付きの項目として表示されます。 |
registerMainMenuAction | コミュニティメニュー(チャンネルリストの上部にあるコミュニティ名)で、その他のアクションの下にアプリごとにグループ化されて表示されます。 |
registerSidebarBrowseOrAddChannelMenuAction | コミュニティメニューで、チャンネルを作成とカテゴリを作成の後です。 |
registerFileUploadMethod | コンポーザーの+メニュー(メッセージに追加)で、ファイルをアップロードの後です。 |
registerPostEditorActionComponent | コンポーザーのフォーマットバーで、フォーマットが表示されている場合です。 |
registerAIActionMenuItemComponent | コンポーザーのAIメニューです。 |
registerFileDropdownMenuAction, registerFilePreviewComponent | ファイルのオプションメニューと、ファイルプレビューです。 |
registerUserSettings | プロフィール & 設定で、プラグインの見出しの下にアプリごとに1つのタブがあります。 |
registerChannelSettingsTab | チャンネル設定ダイアログで、アプリの見出しの下です。 |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | 名前やアバターをクリックしたときに表示されるプロフィールカードです。 |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | コミュニティのチャンネルリストの上部、チャンネル名とアイコンの横、左側のコミュニティレールの下部。 |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | チャンネルの履歴の開始部分、メッセージリストの上部、未読ライン、コンポーザーの上、コンポーザーのプレースホルダー。 |
registerChannelTypeOption | チャンネルを作成ダイアログのチャンネルタイプ。 |
registerSearchComponents | チャンネルヘッダーの検索ボックス:空欄の場合にアプリのスコープが表示されます。 |
registerUserGuideDropdownMenuAction | Antimatterについて(アプリレールの下部)、ヘルプの下。 |
registerProduct | プロダクトのURLにある全ページで、Antimatterについて内のプロダクトの下にリスト表示されます。プロダクトページはクラシックなページフレームを維持し、そのプロダクトメニューは、アプリがプロダクトを登録するか、エントリを追加した場合にのみ表示されます。バンドルされたKanbanとPlaybooksアプリはもはやプロダクトを登録しません。これらのページはサイドパネルで開かれます。 |
registerRootComponent、registerGlobalComponent、registerMessageWillBePostedHook、registerSlashCommandWillBePostedHook、registerMessageWillFormatHook、registerFilesWillUploadHook、registerDesktopNotificationHook、registerWebSocketEventHandler、registerReducer、registerTranslationsなどのフック | クラシックなインターフェースと同様に動作します。 |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | システムコンソール。両方のインターフェースでクラシックなものです。 |
Fusionが表示または実行しないもの
registerPostDropdownMenuComponent(メッセージメニュー内のコンポーネント)、registerPostCardTypeComponent(右側パネル内の投稿カード)、registerProductSwitcherMenuItemは、Fusionのメインビューでは表示されません。最初のものではなく、代わりにregisterPostDropdownMenuActionを使用してください。registerMessageWillBeUpdatedHookは、Fusionでメッセージが編集された際に実行されません。代わりに、MessageWillBeUpdatedフックでサーバー側で編集を確認してください。registerActionAfterChannelCreationは、クラシックなチャンネル作成ダイアログからのみ実行されます。Fusionは、そのチャンネルを作成ダイアログ内のその他のオプション…からこれを開きます。- アプリバーのコンポーネントは、
supportedProductIdsに関わらず表示されます。 registerNeedsTeamRouteおよびregisterCustomRouteからのルートは、プロダクトと同様にクラシックなページフレームを維持します。
Antimatterにのみ存在するフック
これらのレジストリメソッドはMattermostには存在しません。アプリがMattermostでも動作するように、呼び出す前にメソッドが存在するか確認してください:
if (typeof registry.registerComposerMenuItem === 'function') {
registry.registerComposerMenuItem({
text: 'Create a poll',
icon: <i className='icon icon-chart-bar'/>,
fusionIcon: 'poll',
action: ({channelId, rootId}) => openPollCreation(channelId, rootId),
});
} else {
registry.registerChannelHeaderButtonAction(icon, (channel) => openPollCreation(channel.id), 'Create a poll', 'Create a poll');
}
| メソッド | 追加されるもの |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | 両方のインターフェースで、メッセージボックスの+メニュー内の項目。actionは{channelId, rootId}を受け取ります。 |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | メッセージボックスの絵文字ピッカーのタブで、GIFやステッカーなどが該当します。Fusionでは、orderが10未満のタブは絵文字タブの前に表示されます。コンポーネントはchannelId、rootId、filter、onFilterChange、onSelectDone、insertText、isFusion、およびFusionではpickerKit(下記参照)を受け取ります。 |
registerChannelViewPanel({matcher, component}) | チャンネルヘッダーとメッセージの間に配置されるパネルです。matcher(state, channel)が選択したチャンネルに表示されます。音声チャンネルのように、メッセージとコンポーザーを非表示にすることもできます(setMessagesVisible)。 |
registerSidebarChannelFooterComponent(component) | サイドバーのチャンネルの下に表示される行です。例えば、その通話に参加しているメンバーなどが該当します。channelを受け取ります。 |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | Fusion専用: 統合機能ページの項目で、カスタム絵文字の隣にあります。GIFアプリは、そのカスタムGIFやステッカーをここに表示します。 |
絵文字ピッカーでのビューの追加と管理
Fusionでは、絵文字ピッカーのタブにpickerKitプロパティが渡されます。これは、絵文字タブが独自の追加ビューと管理ビューに使用するコンポーネントであり、タブのビューが同じ外観を保ち、ピッカー内に留まることを可能にします。GIFsとMemesアプリは、GIF、ステッカー、ミームテンプレートの追加と管理にこれを使用します。このプロパティにはversion(1)、フックのusePickerViewとuseOnEscape、そしてコンポーネントのPickerViewButtons(検索ボックス横の+ボタンと管理ボタン)、PickerViewFrame(戻る矢印付きビュー)、PickerNotice、PickerField、PickerSearch、PickerDropZone、PickerManageListが含まれます。Escapeキーは、ビューからタブのグリッドへ戻ります。このプロパティが存在することを確認してください。他のホストではこれが省略されることがあり、その場合タブは独自のダイアログを使用します。型はwebapp/fusion/src/types/store/plugins.ts(EmojiPickerTabProps)に、コンポーネントはwebapp/fusion/src/fusion/popovers/picker_views.tsxにあります。
新しいウィンドウ内のパネル
Fusionは、registerRightHandSidebarComponentのコンポーネントに2つのプロパティを追加します。これにより、新しいウィンドウで開くを実行した際、パネルがアプリのホームではなく、元に表示されていた場所で開かれます:
setPanelState(state):パネルの場所が変わった際に呼び出します。引数には、'note/<id>'や'board/<id>/card/<id>'のような、アプリが定義する短い文字列を渡します。ホームに戻す場合はnullを渡します。この値はウィンドウのアドレスバーに反映されるため、秘密情報を含めないでください。512文字を超える状態、または制御文字を含む状態は破棄されます。initialPanelState:新しいウィンドウでは、パネルが保持していた状態、またはundefinedです。これを信頼できない入力として扱ってください。ログインユーザーとしてアイテムを再取得し、アクセスできない場合はアプリのホームまたは「見つかりません」ビューを表示してください。
パネルが閉じられたり、別のアプリのパネルがその場所を占めたりすると、状態は破棄されます。パネルのウィンドウを自分で開くには、popoutRhsPlugin(window.WebappUtils.popoutsにも存在)を使用し、状態を任意の第5引数として渡します。Notes、Whiteboard、Todo、Kanban、Playbooksはこの機能を使用しています。
fusionIconは、Fusionのアイコンセットのアイコンを指定します(例: poll、sticker、calendar、image。詳細はwebapp/fusion/src/fusion/components/iconを参照)。Fusionではiconの代わりにこれを表示するため、他の項目と一貫した外観になります。
設定
plugin.json内のsettings_schemaは、システムコンソール › プラグインのアプリページを記述します:
"settings_schema": {
"header": "Forecasts come from the weather service below.",
"settings": [
{"key": "ServiceURL", "display_name": "Service URL", "type": "text", "default": "https://api.example.com"},
{"key": "Units", "display_name": "Units", "type": "dropdown", "default": "metric",
"options": [{"display_name": "Celsius", "value": "metric"}, {"display_name": "Fahrenheit", "value": "imperial"}]},
{"key": "EnableAlerts", "display_name": "Severe weather alerts", "type": "bool", "default": false,
"help_text": "Post alerts in the channels that follow a city."}
]
}
設定のタイプ: bool、text、longtext、number、dropdown、radio、generated(ランダムなシークレット)、username、custom(Webアプリ部分でregisterAdminConsoleCustomSettingに登録したコンポーネントによって描画されます)。設定はsectionsにグループ化できます。
サーバー部分は、キーと一致するフィールドを持つ構造体としてこれらを読み込みます:
type configuration struct {
ServiceURL string
Units string
EnableAlerts bool
}
func (p *Plugin) OnConfigurationChange() error {
var cfg configuration
if err := p.API.LoadPluginConfiguration(&cfg); err != nil {
return err
}
p.setConfiguration(&cfg)
return nil
}
管理者は、設定ファイルのPluginSettings.Plugins.<app id>の下でもこれらを設定できます(キーは小文字を使用。詳細は管理者ガイドを参照)。
翻訳
Antimatterは37言語に翻訳されており、バンドルされたアプリも同様です。Webアプリ部分は次のように処理します:
- すべての文字列はReact Intlで記述し、IDと英語のテキストを指定します:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>またはdefineMessages。IDにはアプリ名をプレフィックスとして付与します。 - 英語の文字列を
webapp/i18n/en.jsonに抽出します(バンドルされたアプリはformatjs extractを使用)。 - 同じIDを持つファイルを言語ごとに1つずつ隣に追加します:
fr.json、de.json、pt-BR.json、zh-CN.json… - Web アプリに渡します。Web アプリはユーザーの言語を要求します。
import fr from '../i18n/fr.json'; import ptBR from '../i18n/pt-BR.json'; const catalogs = {fr, 'pt-BR': ptBR}; export function translationsFor(locale) { return catalogs[locale] || catalogs[locale.split('-')[0]] || {}; } registry.registerTranslations(translationsFor);
文字列が欠落している場合、defaultMessage の英語にフォールバックします。サーバー側が送信する文字列(コマンドの応答、ボットメッセージなど)については、pluginapi/i18n パッケージがバンドルに同梱されている翻訳ファイル(assets/i18n など)を読み込み、各ユーザーの言語に合わせたローカライザーを提供します。
ビルド、インストール、デバッグ
Go と Node.js(アプリの .nvmrc に記載されているバージョン)が必要です。アプリのフォルダ内で実行します。
| コマンド | 機能 |
|---|---|
make dist | すべてのプラットフォーム向けのサーバー部分と Web アプリ部分をビルドし、dist/<app id>-<version>.tar.gz にバンドルを書き出します。 |
make test | Go と JavaScript のテストを実行します。 |
make check-style | コードを Lint(golangci-lint、ESLint)し、TypeScript の型チェックを行います。 |
make deploy | ビルドした後、サーバー上にバンドルをインストールして有効化します。 |
make watch, make deploy-from-watch | 前者は保存するたびに Web アプリ部分を再ビルドし、後者はビルドした内容をバンドル化してサーバーにインストールします。 |
make enable, make disable, make reset | サーバー上のアプリを有効化、無効化、または再起動します。 |
make logs, make logs-watch | サーバーログの該当アプリの行を表示します。 |
make attach | 実行中のサーバー部分に Delve デバッガーをアタッチします(事前に AM_DEBUG=1 でビルドしてください)。 |
make deploy およびその他のサーバーコマンドは、ローカルモードのソケット(/var/tmp/antimatter_local.socket または AM_LOCALSOCKETPATH)が見つかった場合、それを使用します。その場合は、ローカルモードを有効にしたサーバーのマシン上で実行してください。見つからない場合は、MM_SERVICESETTINGS_SITEURL で指定されたサーバーに AM_ADMIN_TOKEN、または AM_ADMIN_USERNAME と AM_ADMIN_PASSWORD を使用してサインインします。ビルドツールは、サイト URL 用の AM_ 接頭辞付きの環境変数を読み込む機能はまだありません。
バンドルの手動インストール
- システム管理者は
AM_PLUGINSETTINGS_ENABLEUPLOADS=trueを設定することでアップロードを許可できます(デフォルトでは無効。システムコンソールには切り替えスイッチはありません)。 - その後、システムコンソール › プラグイン › プラグイン管理 › プラグインをアップロードする を選択するか、
amctl plugin add weather.tar.gzを実行し、続いてamctl plugin enable com.example.weatherを実行します。 - サーバーで プラグイン署名が必要 が有効になっている場合、サーバーが信頼する鍵で署名されたバンドルのみを受け入れます。その他のアプリのインストール を参照してください。
AM_SERVICESETTINGS_ENABLEDEVELOPER=true を設定してください。これにより、エラーに詳細情報が含まれるようになります。バンドルされたアプリの Makefile は、シェルで MM_SERVICESETTINGS_ENABLEDEVELOPER が設定されている場合のみ、現在のプラットフォーム向けのサーバー部分をビルドします。