mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-23 00:21:28 +00:00
feat: edit existing messages for workflow run updates
This commit is contained in:
parent
4b3f5189a8
commit
78f0a4e28a
8 changed files with 187 additions and 16 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import type { RouteTarget, Env, NeutralMessage } from "../../types";
|
||||
import type { PlatformDriver, SendResult } from "../types";
|
||||
import { sendMessage } from "./rest";
|
||||
import { sendMessage, editMessage } from "./rest";
|
||||
import { renderNeutralMessage } from "./render";
|
||||
|
||||
export class DiscordDriver implements PlatformDriver {
|
||||
|
|
@ -14,4 +14,18 @@ export class DiscordDriver implements PlatformDriver {
|
|||
const token = env.DISCORD_TOKEN ?? "";
|
||||
return sendMessage(token, channelId, renderNeutralMessage(message), target.threadId);
|
||||
}
|
||||
|
||||
async edit(
|
||||
message: NeutralMessage,
|
||||
target: RouteTarget,
|
||||
env: Env,
|
||||
messageId: string,
|
||||
): Promise<SendResult> {
|
||||
const channelId = target.channelId ?? "";
|
||||
if (!channelId) {
|
||||
return { ok: false, error: "target.channelId is required", errorCode: "NO_TARGET" };
|
||||
}
|
||||
const token = env.DISCORD_TOKEN ?? "";
|
||||
return editMessage(token, channelId, messageId, renderNeutralMessage(message), target.threadId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,20 +7,17 @@ interface DiscordMessage {
|
|||
id?: string;
|
||||
}
|
||||
|
||||
export async function sendMessage(
|
||||
async function request(
|
||||
url: string,
|
||||
method: string,
|
||||
token: string,
|
||||
channelId: string,
|
||||
message: unknown,
|
||||
threadId?: string,
|
||||
channelId: string,
|
||||
): Promise<SendResult> {
|
||||
const url = threadId
|
||||
? `${DISCORD_API}/channels/${threadId}/messages`
|
||||
: `${DISCORD_API}/channels/${channelId}/messages`;
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bot ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -80,3 +77,27 @@ export async function sendMessage(
|
|||
|
||||
return { ok: false, error: "Max retries exceeded", errorCode: "RETRIES", attempts: 3 };
|
||||
}
|
||||
|
||||
export async function sendMessage(
|
||||
token: string,
|
||||
channelId: string,
|
||||
message: unknown,
|
||||
threadId?: string,
|
||||
): Promise<SendResult> {
|
||||
const url = threadId
|
||||
? `${DISCORD_API}/channels/${threadId}/messages`
|
||||
: `${DISCORD_API}/channels/${channelId}/messages`;
|
||||
return request(url, "POST", token, message, channelId);
|
||||
}
|
||||
|
||||
export async function editMessage(
|
||||
token: string,
|
||||
channelId: string,
|
||||
messageId: string,
|
||||
message: unknown,
|
||||
threadId?: string,
|
||||
): Promise<SendResult> {
|
||||
const base = threadId ?? channelId;
|
||||
const url = `${DISCORD_API}/channels/${base}/messages/${messageId}`;
|
||||
return request(url, "PATCH", token, message, channelId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { RouteTarget, Env, NeutralMessage } from "../../types";
|
||||
import type { PlatformDriver, SendResult } from "../types";
|
||||
import { sendMessage, sendPhoto } from "./rest";
|
||||
import { sendMessage, sendPhoto, editMessageText, editMessageCaption } from "./rest";
|
||||
import { renderNeutralMessage } from "./render";
|
||||
|
||||
function smallAvatar(url: string): string {
|
||||
|
|
@ -8,6 +8,14 @@ function smallAvatar(url: string): string {
|
|||
return `${url}${sep}s=64`;
|
||||
}
|
||||
|
||||
function richHeaderUrl(message: NeutralMessage, avatar: string, host: string): string {
|
||||
const params = new URLSearchParams();
|
||||
if (message.author?.name) params.set("title", message.author.name);
|
||||
if (message.title) params.set("content", message.title);
|
||||
params.set("avatar", avatar);
|
||||
return `${host.replace(/\/+$/, "")}/api/richheader?${params.toString()}`;
|
||||
}
|
||||
|
||||
export class TelegramDriver implements PlatformDriver {
|
||||
readonly id = "telegram";
|
||||
|
||||
|
|
@ -21,11 +29,7 @@ export class TelegramDriver implements PlatformDriver {
|
|||
const avatar = message.author?.iconUrl;
|
||||
const richHeaderHost = env.TELEGRAM_RICH_HEADER_HOST ?? env.BASE_URL;
|
||||
if (avatar && richHeaderHost) {
|
||||
const params = new URLSearchParams();
|
||||
if (message.author?.name) params.set("title", message.author.name);
|
||||
if (message.title) params.set("content", message.title);
|
||||
params.set("avatar", avatar);
|
||||
const rhUrl = `${richHeaderHost.replace(/\/+$/, "")}/api/richheader?${params.toString()}`;
|
||||
const rhUrl = richHeaderUrl(message, avatar, richHeaderHost);
|
||||
return sendMessage(token, chatId, text, target.topicId, rhUrl);
|
||||
}
|
||||
if (avatar) {
|
||||
|
|
@ -33,4 +37,28 @@ export class TelegramDriver implements PlatformDriver {
|
|||
}
|
||||
return sendMessage(token, chatId, text, target.topicId);
|
||||
}
|
||||
|
||||
async edit(
|
||||
message: NeutralMessage,
|
||||
target: RouteTarget,
|
||||
env: Env,
|
||||
messageId: string,
|
||||
): Promise<SendResult> {
|
||||
const chatId = target.chatId ?? "";
|
||||
if (!chatId) {
|
||||
return { ok: false, error: "target.chatId is required", errorCode: "NO_TARGET" };
|
||||
}
|
||||
const token = env.TELEGRAM_TOKEN ?? "";
|
||||
const text = renderNeutralMessage(message);
|
||||
const avatar = message.author?.iconUrl;
|
||||
const richHeaderHost = env.TELEGRAM_RICH_HEADER_HOST ?? env.BASE_URL;
|
||||
if (avatar && richHeaderHost) {
|
||||
const rhUrl = richHeaderUrl(message, avatar, richHeaderHost);
|
||||
return editMessageText(token, chatId, messageId, text, target.topicId, rhUrl);
|
||||
}
|
||||
if (avatar) {
|
||||
return editMessageCaption(token, chatId, messageId, text, target.topicId);
|
||||
}
|
||||
return editMessageText(token, chatId, messageId, text, target.topicId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,3 +134,56 @@ export async function sendPhoto(
|
|||
}
|
||||
return post(token, "sendPhoto", body, chatId);
|
||||
}
|
||||
|
||||
export async function editMessageText(
|
||||
token: string,
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
text: string,
|
||||
topicId?: string,
|
||||
linkPreviewUrl?: string,
|
||||
): Promise<SendResult> {
|
||||
if (!token) {
|
||||
return { ok: false, error: "TELEGRAM_TOKEN not configured", errorCode: "NO_TOKEN" };
|
||||
}
|
||||
const body: Record<string, unknown> = {
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
text,
|
||||
parse_mode: "HTML",
|
||||
disable_web_page_preview: !linkPreviewUrl,
|
||||
};
|
||||
if (linkPreviewUrl) {
|
||||
body.link_preview_options = {
|
||||
url: linkPreviewUrl,
|
||||
prefer_small_media: true,
|
||||
show_above_text: true,
|
||||
};
|
||||
}
|
||||
if (topicId) {
|
||||
body.message_thread_id = Number(topicId);
|
||||
}
|
||||
return post(token, "editMessageText", body, chatId);
|
||||
}
|
||||
|
||||
export async function editMessageCaption(
|
||||
token: string,
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
caption: string,
|
||||
topicId?: string,
|
||||
): Promise<SendResult> {
|
||||
if (!token) {
|
||||
return { ok: false, error: "TELEGRAM_TOKEN not configured", errorCode: "NO_TOKEN" };
|
||||
}
|
||||
const body: Record<string, unknown> = {
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
caption,
|
||||
parse_mode: "HTML",
|
||||
};
|
||||
if (topicId) {
|
||||
body.message_thread_id = Number(topicId);
|
||||
}
|
||||
return post(token, "editMessageCaption", body, chatId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,9 @@ export interface SendResult {
|
|||
export interface PlatformDriver {
|
||||
readonly id: string;
|
||||
send(message: NeutralMessage, target: RouteTarget, env: Env): Promise<SendResult>;
|
||||
/**
|
||||
* Edit an already-sent message in place (e.g. workflow run progress updates).
|
||||
* Must be implemented by drivers that support message updates.
|
||||
*/
|
||||
edit(message: NeutralMessage, target: RouteTarget, env: Env, messageId: string): Promise<SendResult>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue