feat(mattermost): add emoji reactions support

This commit is contained in:
Echo
2026-02-16 17:14:08 -05:00
committed by Peter Steinberger
parent b57d29d833
commit 2a2372cd6c
9 changed files with 607 additions and 2 deletions

View File

@@ -7,6 +7,8 @@ import {
migrateBaseNameToDefaultAccount,
normalizeAccountId,
setAccountEnabledInConfigSection,
type ChannelMessageActionAdapter,
type ChannelMessageActionName,
type ChannelPlugin,
} from "openclaw/plugin-sdk";
import { MattermostConfigSchema } from "./config-schema.js";
@@ -20,11 +22,102 @@ import {
import { normalizeMattermostBaseUrl } from "./mattermost/client.js";
import { monitorMattermostProvider } from "./mattermost/monitor.js";
import { probeMattermost } from "./mattermost/probe.js";
import { addMattermostReaction, removeMattermostReaction } from "./mattermost/reactions.js";
import { sendMessageMattermost } from "./mattermost/send.js";
import { looksLikeMattermostTargetId, normalizeMattermostMessagingTarget } from "./normalize.js";
import { mattermostOnboardingAdapter } from "./onboarding.js";
import { getMattermostRuntime } from "./runtime.js";
const mattermostMessageActions: ChannelMessageActionAdapter = {
listActions: ({ cfg }) => {
const accounts = listMattermostAccountIds(cfg)
.map((accountId) => resolveMattermostAccount({ cfg, accountId }))
.filter((account) => account.enabled)
.filter((account) => Boolean(account.botToken?.trim() && account.baseUrl?.trim()));
if (accounts.length === 0) {
return [];
}
const actions: ChannelMessageActionName[] = [];
const actionsConfig = cfg.channels?.mattermost?.actions as { reactions?: boolean } | undefined;
const reactionsEnabled = actionsConfig?.reactions !== false;
if (reactionsEnabled) {
actions.push("react");
}
return actions;
},
supportsAction: ({ action }) => {
return action === "react";
},
handleAction: async ({ action, params, cfg, accountId }) => {
if (action !== "react") {
throw new Error(`Mattermost action ${action} not supported`);
}
// Check reactions gate: per-account config takes precedence over base config
const mmBase = cfg?.channels?.mattermost as Record<string, unknown> | undefined;
const accounts = mmBase?.accounts as Record<string, Record<string, unknown>> | undefined;
const acctConfig = accountId && accounts ? accounts[accountId] : undefined;
const acctActions = acctConfig?.actions as { reactions?: boolean } | undefined;
const baseActions = mmBase?.actions as { reactions?: boolean } | undefined;
const reactionsEnabled = acctActions?.reactions ?? baseActions?.reactions ?? true;
if (!reactionsEnabled) {
throw new Error("Mattermost reactions are disabled in config");
}
const postIdRaw =
typeof (params as any)?.messageId === "string"
? (params as any).messageId
: typeof (params as any)?.postId === "string"
? (params as any).postId
: "";
const postId = postIdRaw.trim();
if (!postId) {
throw new Error("Mattermost react requires messageId (post id)");
}
const emojiRaw = typeof (params as any)?.emoji === "string" ? (params as any).emoji : "";
const emojiName = emojiRaw.trim().replace(/^:+|:+$/g, "");
if (!emojiName) {
throw new Error("Mattermost react requires emoji");
}
const remove = Boolean((params as any)?.remove);
if (remove) {
const result = await removeMattermostReaction({
cfg,
postId,
emojiName,
accountId: accountId ?? undefined,
});
if (!result.ok) {
throw new Error(result.error);
}
return {
content: [
{ type: "text" as const, text: `Removed reaction :${emojiName}: from ${postId}` },
],
details: {},
};
}
const result = await addMattermostReaction({
cfg,
postId,
emojiName,
accountId: accountId ?? undefined,
});
if (!result.ok) {
throw new Error(result.error);
}
return {
content: [{ type: "text" as const, text: `Reacted with :${emojiName}: on ${postId}` }],
details: {},
};
},
};
const meta = {
id: "mattermost",
label: "Mattermost",
@@ -146,6 +239,7 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
groups: {
resolveRequireMention: resolveMattermostGroupRequireMention,
},
actions: mattermostMessageActions,
messaging: {
normalizeTarget: normalizeMattermostMessagingTarget,
targetResolver: {