Files
twotalesanimation 0fbe856dce Initial commit
2026-05-19 22:20:29 +02:00

234 lines
5.3 KiB
TypeScript

export interface SlackMessage {
text?: string;
blocks?: SlackBlock[];
channel?: string;
}
interface SlackBlock {
type: string;
text?: { type: string; text: string };
elements?: unknown[];
}
/**
* Send a message to a Slack webhook URL.
*/
export async function sendSlackMessage(
webhookUrl: string,
message: SlackMessage
): Promise<void> {
const res = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(message),
});
if (!res.ok) {
const body = await res.text();
console.error("[Slack] Webhook failed:", res.status, body);
}
}
/**
* Send a plain text Slack notification.
*/
export async function sendSlackText(
webhookUrl: string,
text: string
): Promise<void> {
return sendSlackMessage(webhookUrl, { text });
}
// ── Pre-built notification templates ─────────────────────────────────────────
export async function slackNotifyVersionUploaded(
webhookUrl: string,
params: {
shotCode: string;
versionLabel: string;
artistName: string;
projectName: string;
reviewUrl: string;
}
) {
await sendSlackMessage(webhookUrl, {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `🎬 *New version uploaded*\n*${params.projectName}* · ${params.shotCode} ${params.versionLabel} by ${params.artistName}`,
},
},
{
type: "actions",
elements: [
{
type: "button",
text: { type: "plain_text", text: "Review Now" },
url: params.reviewUrl,
style: "primary",
},
],
},
],
});
}
export async function slackNotifyApproval(
webhookUrl: string,
params: {
shotCode: string;
versionLabel: string;
status: "APPROVED" | "REJECTED" | "NEEDS_CHANGES";
reviewerName: string;
projectName: string;
reviewUrl: string;
}
) {
const icons = {
APPROVED: "✅",
REJECTED: "❌",
NEEDS_CHANGES: "⚠️",
};
const labels = {
APPROVED: "approved",
REJECTED: "rejected",
NEEDS_CHANGES: "needs changes",
};
const icon = icons[params.status];
const label = labels[params.status];
await sendSlackMessage(webhookUrl, {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `${icon} *${params.shotCode} ${params.versionLabel} ${label}*\nBy ${params.reviewerName} · ${params.projectName}`,
},
},
{
type: "actions",
elements: [
{
type: "button",
text: { type: "plain_text", text: "View Version" },
url: params.reviewUrl,
},
],
},
],
});
}
export async function slackNotifyTaskReadyForReview(
webhookUrl: string,
params: {
taskTitle: string;
contextCode: string | null;
artistName: string;
projectName: string;
taskUrl: string;
}
) {
const label = params.contextCode ? `${params.contextCode}` : "";
await sendSlackMessage(webhookUrl, {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `👁 *Task ready for review*\n*${params.projectName}* · ${label}${params.taskTitle}\nSubmitted by ${params.artistName}`,
},
},
{
type: "actions",
elements: [
{
type: "button",
text: { type: "plain_text", text: "Review Task" },
url: params.taskUrl,
style: "primary",
},
],
},
],
});
}
export async function slackNotifyTaskAssigned(
webhookUrl: string,
params: {
taskTitle: string;
contextCode: string | null;
artistName: string;
assignedByName: string;
projectName: string;
taskUrl: string;
}
) {
const label = params.contextCode ? `${params.contextCode}` : "";
await sendSlackMessage(webhookUrl, {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `📋 *Task assigned*\n*${params.projectName}* · ${label}${params.taskTitle}\nAssigned to ${params.artistName} by ${params.assignedByName}`,
},
},
{
type: "actions",
elements: [
{
type: "button",
text: { type: "plain_text", text: "View Task" },
url: params.taskUrl,
},
],
},
],
});
}
export async function slackNotifyNewFeedback(
webhookUrl: string,
params: {
shotCode: string;
frameNumber: number;
authorName: string;
commentText: string;
reviewUrl: string;
}
) {
const truncated =
params.commentText.length > 120
? params.commentText.slice(0, 117) + "..."
: params.commentText;
await sendSlackMessage(webhookUrl, {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `💬 *New feedback* on ${params.shotCode} frame ${params.frameNumber}\n*${params.authorName}:* ${truncated}`,
},
},
{
type: "actions",
elements: [
{
type: "button",
text: { type: "plain_text", text: "Jump to Frame" },
url: params.reviewUrl,
},
],
},
],
});
}