

























@@ -17,6 +17,8 @@ import type { MattermostClient } from "./client.js";
17171818// ─── Types ───────────────────────────────────────────────────────────────────
191920+export const MATTERMOST_SLASH_POST_METHOD = "P";
21+2022export type MattermostSlashCommandConfig = {
2123/** Enable native slash commands. "auto" resolves to false for now (opt-in). */
2224native: boolean | "auto";
@@ -45,6 +47,7 @@ export type MattermostRegisteredCommand = {
4547trigger: string;
4648teamId: string;
4749token: string;
50+url: string;
4851/** True when this process created the command and should delete it on shutdown. */
4952managed: boolean;
5053};
@@ -84,7 +87,7 @@ export type MattermostSlashCommandResponse = {
8487type MattermostCommandCreate = {
8588team_id: string;
8689trigger: string;
87-method: "P" | "G";
90+method: typeof MATTERMOST_SLASH_POST_METHOD | "G";
8891url: string;
8992description?: string;
9093auto_complete: boolean;
@@ -98,15 +101,15 @@ type MattermostCommandUpdate = {
98101id: string;
99102team_id: string;
100103trigger: string;
101-method: "P" | "G";
104+method: typeof MATTERMOST_SLASH_POST_METHOD | "G";
102105url: string;
103106description?: string;
104107auto_complete: boolean;
105108auto_complete_desc?: string;
106109auto_complete_hint?: string;
107110};
108111109-type MattermostCommandResponse = {
112+export type MattermostCommandResponse = {
110113id: string;
111114token: string;
112115team_id: string;
@@ -192,9 +195,25 @@ export const DEFAULT_COMMAND_SPECS: MattermostCommandSpec[] = [
192195export async function listMattermostCommands(
193196client: MattermostClient,
194197teamId: string,
198+init?: Pick<RequestInit, "signal">,
195199): Promise<MattermostCommandResponse[]> {
196200return await client.request<MattermostCommandResponse[]>(
197201`/commands?team_id=${encodeURIComponent(teamId)}&custom_only=true`,
202+init,
203+);
204+}
205+206+/**
207+ * Get a custom slash command by id.
208+ */
209+export async function getMattermostCommand(
210+client: MattermostClient,
211+commandId: string,
212+init?: Pick<RequestInit, "signal">,
213+): Promise<MattermostCommandResponse> {
214+return await client.request<MattermostCommandResponse>(
215+`/commands/${encodeURIComponent(commandId)}`,
216+init,
198217);
199218}
200219@@ -303,31 +322,36 @@ export async function registerSlashCommands(params: {
303322304323const existingCmd = ownedCommands[0];
305324306-// Already registered with the correct callback URL
307-if (existingCmd && existingCmd.url === callbackUrl) {
325+const existingNeedsUpdate = existingCmd
326+ ? existingCmd.url !== callbackUrl || existingCmd.method !== MATTERMOST_SLASH_POST_METHOD
327+ : false;
328+329+// Already registered with the correct callback URL and method.
330+if (existingCmd && !existingNeedsUpdate) {
308331log?.(`mattermost: command /${spec.trigger} already registered (id=${existingCmd.id})`);
309332registered.push({
310333id: existingCmd.id,
311334trigger: spec.trigger,
312335 teamId,
313336token: existingCmd.token,
337+url: callbackUrl,
314338managed: false,
315339});
316340continue;
317341}
318342319-// Exists but points to a different URL: attempt to reconcile by updating
320-// (useful during callback URL migrations).
321-if (existingCmd && existingCmd.url !== callbackUrl) {
343+// Exists but has drifted critical callback fields: attempt to reconcile by
344+// updating (useful during callback URL migrations or method drift).
345+if (existingCmd && existingNeedsUpdate) {
322346log?.(
323-`mattermost: command /${spec.trigger} exists with different callback URL; updating (id=${existingCmd.id})`,
347+`mattermost: command /${spec.trigger} exists with different callback settings; updating (id=${existingCmd.id})`,
324348);
325349try {
326350const updated = await updateMattermostCommand(client, {
327351id: existingCmd.id,
328352team_id: teamId,
329353trigger: spec.trigger,
330-method: "P",
354+method: MATTERMOST_SLASH_POST_METHOD,
331355url: callbackUrl,
332356description: spec.description,
333357auto_complete: spec.autoComplete,
@@ -339,6 +363,7 @@ export async function registerSlashCommands(params: {
339363trigger: spec.trigger,
340364 teamId,
341365token: updated.token,
366+url: callbackUrl,
342367managed: false,
343368});
344369continue;
@@ -365,7 +390,7 @@ export async function registerSlashCommands(params: {
365390const created = await createMattermostCommand(client, {
366391team_id: teamId,
367392trigger: spec.trigger,
368-method: "P",
393+method: MATTERMOST_SLASH_POST_METHOD,
369394url: callbackUrl,
370395description: spec.description,
371396auto_complete: spec.autoComplete,
@@ -378,6 +403,7 @@ export async function registerSlashCommands(params: {
378403trigger: spec.trigger,
379404 teamId,
380405token: created.token,
406+url: callbackUrl,
381407managed: true,
382408});
383409} catch (err) {
@@ -499,6 +525,10 @@ export function resolveCommandText(
499525return args ? `/${commandName} ${args}` : `/${commandName}`;
500526}
501527528+export function normalizeSlashCommandTrigger(command: string): string {
529+return command.replace(/^\//, "").trim();
530+}
531+502532// ─── Config resolution ───────────────────────────────────────────────────────
503533504534const DEFAULT_CALLBACK_PATH = "/api/channels/mattermost/command";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。