アプリ(プラグイン)の作成

アプリは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チャンネルヘッダーの検索ボックス:空欄の場合にアプリのスコープが表示されます。
registerUserGuideDropdownMenuActionAntimatterについて(アプリレールの下部)、ヘルプの下。
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アプリ部分は次のように処理します:

  1. すべての文字列はReact Intlで記述し、IDと英語のテキストを指定します: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/>またはdefineMessages。IDにはアプリ名をプレフィックスとして付与します。
  2. 英語の文字列をwebapp/i18n/en.jsonに抽出します(バンドルされたアプリはformatjs extractを使用)。
  3. 同じIDを持つファイルを言語ごとに1つずつ隣に追加します: fr.json、de.json、pt-BR.json、zh-CN.json…
  4. 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 testGo と 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 が設定されている場合のみ、現在のプラットフォーム向けのサーバー部分をビルドします。