import * as z from "zod" import { defineAction } from "../../../automation/actions" import { getSlackApi } from "../lib/api" import { SLACK_PINS_READ_SCOPE, SLACK_PINS_WRITE_SCOPE } from "../lib/scopes" import { SLACK_MESSAGE_SCHEMA, SLACK_PROVIDER_MESSAGE_SCHEMA, toSlackMessage, } from "@automate.ax/integration-contracts/slack" const PIN_MUTATION_RESPONSE_SCHEMA = z.looseObject({ ok: z.literal(true) }) const PINNED_ITEM_SCHEMA = z.object({ /** Unix timestamp when the message was pinned. */ createdAt: z.number().int().nonnegative(), /** Slack member who pinned the message. */ createdByUserId: z.string(), /** Pinned Slack message. */ message: SLACK_MESSAGE_SCHEMA, }) const LIST_PINS_RESPONSE_SCHEMA = z.looseObject({ items: z .discriminatedUnion("type", [ z.looseObject({ created: z.number().int().nonnegative(), created_by: z.string(), message: SLACK_PROVIDER_MESSAGE_SCHEMA, type: z.literal("message"), }), z.looseObject({ created: z.number().int().nonnegative(), created_by: z.string(), file: z.object({ id: z.string() }).catchall(z.json()), type: z.literal("file"), }), z.looseObject({ comment: z.object({ id: z.string() }).catchall(z.json()), created: z.number().int().nonnegative(), created_by: z.string(), file: z.object({ id: z.string() }).catchall(z.json()), type: z.literal("file_comment"), }), ]) .array(), ok: z.literal(true), }) const PIN_INPUT_SCHEMA = z.object({ /** Conversation containing the target message. */ conversationId: z.string().min(1), /** Timestamp identifying the target message. */ timestamp: z.string().min(1), }) /** Pins a Slack message to its conversation. */ export const pinSlackMessage = defineAction("Pin Slack message") .describe("Pins a Slack message to its conversation.") .account("slack", SLACK_PINS_WRITE_SCOPE) .input(PIN_INPUT_SCHEMA) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await mutatePin(account.secret, "pins.add", input) }) /** Removes a Slack message from its conversation's pins. */ export const unpinSlackMessage = defineAction("Unpin Slack message") .describe("Removes a Slack message from its conversation's pins.") .account("slack", SLACK_PINS_WRITE_SCOPE) .input(PIN_INPUT_SCHEMA) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await mutatePin(account.secret, "pins.remove", input) }) /** Lists messages pinned to a Slack conversation. */ export const listSlackPinnedMessages = defineAction( "List Slack pinned messages", ) .describe("Lists messages pinned to a Slack conversation.") .account("slack", SLACK_PINS_READ_SCOPE) .input(z.object({ conversationId: z.string().min(1) })) .output(PINNED_ITEM_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { return ( await getSlackApi(account.secret).call("pins.list", { query: { channel: input.conversationId }, responseSchema: LIST_PINS_RESPONSE_SCHEMA, }) ).items.flatMap((item) => item.type === "message" ? [ { createdAt: item.created, createdByUserId: item.created_by, message: toSlackMessage(item.message), }, ] : [], ) }) /** * Applies one pin mutation to a Slack message. * * @param secret - Resolved Slack integration secret. * @param method - Pin method to invoke. * @param input - Target conversation and message timestamp. */ async function mutatePin( secret: Record, method: "pins.add" | "pins.remove", input: z.output, ) { await getSlackApi(secret).call(method, { body: { channel: input.conversationId, timestamp: input.timestamp }, responseSchema: PIN_MUTATION_RESPONSE_SCHEMA, }) }