146 lines · 5.1 KB
import type { EventSummary, EventTone } from "./summarize";

const SLACK_TEXT_LIMIT = 2900;
const GCHAT_TEXT_LIMIT = 3500;
const DIGEST_MAX_ITEMS = 50;

const SLACK_EMOJI: Record<EventTone, string> = {
  good: ":large_green_circle:",
  bad: ":red_circle:",
  info: ":large_blue_circle:",
  neutral: ":white_circle:",
};

const GCHAT_ICON: Record<EventTone, string> = {
  good: "STAR",
  bad: "BOOKMARK",
  info: "DESCRIPTION",
  neutral: "DESCRIPTION",
};

/** Slack mrkdwn treats `&`, `<` and `>` as control characters; everything user-authored is escaped. */
export function escapeSlack(text: string): string {
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}

function slackLink(url: string, label: string): string {
  return `<${url.replaceAll("|", "%7C").replaceAll(">", "%3E")}|${escapeSlack(label)}>`;
}

function limit(text: string, max: number): string {
  return text.length > max ? `${text.slice(0, max - 1)}…` : text;
}

type SlackBlock = Record<string, unknown>;

export type SlackMessage = { text: string; blocks: SlackBlock[]; unfurl_links: false; unfurl_media: false };

/** Block Kit message: linked title, optional excerpt and fields, repo/actor context, and a "View" button. */
export function slackMessage(summary: EventSummary): SlackMessage {
  const heading = `${SLACK_EMOJI[summary.tone]} *${slackLink(summary.url, summary.title)}*`;