diff --git a/src/drivers/telegram/index.ts b/src/drivers/telegram/index.ts index 0c10441..29464aa 100644 --- a/src/drivers/telegram/index.ts +++ b/src/drivers/telegram/index.ts @@ -19,6 +19,15 @@ export class TelegramDriver implements PlatformDriver { 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 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()}`; + return sendMessage(token, chatId, text, target.topicId, rhUrl); + } if (avatar) { return sendPhoto(token, chatId, smallAvatar(avatar), text, target.topicId); } diff --git a/src/drivers/telegram/rest.ts b/src/drivers/telegram/rest.ts index a02c91d..90d3c50 100644 --- a/src/drivers/telegram/rest.ts +++ b/src/drivers/telegram/rest.ts @@ -87,6 +87,7 @@ export async function sendMessage( chatId: string, text: string, topicId?: string, + linkPreviewUrl?: string, ): Promise { if (!token) { return { ok: false, error: "TELEGRAM_TOKEN not configured", errorCode: "NO_TOKEN" }; @@ -95,8 +96,15 @@ export async function sendMessage( chat_id: chatId, text, parse_mode: "HTML", - disable_web_page_preview: true, + 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); } diff --git a/src/server.ts b/src/server.ts index a4de53a..2b4e914 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,6 +10,7 @@ import { createActionRoutes } from "./web/action-routes"; import { createAdminRoutes } from "./web/admin-routes"; import { createLegalRoutes } from "./web/legal-routes"; import { createHomeRoutes } from "./web/home-routes"; +import { createRichHeaderRoutes } from "./web/richheader-routes"; import { loadConfig } from "./config"; import { log } from "./lib/log"; @@ -25,6 +26,7 @@ export function createServer(): Hono<{ Bindings: Env }> { app.route("/", createLegalRoutes()); app.route("/", createHomeRoutes()); app.route("/admin", createAdminRoutes()); + app.route("/api", createRichHeaderRoutes()); app.post("/webhook", async (c) => { const contentLength = Number(c.req.header("content-length") ?? 0); diff --git a/src/types.ts b/src/types.ts index 8f63be7..ad379c7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,6 +15,7 @@ export interface Env { DISCORD_APPLICATION_ID?: string; TELEGRAM_TOKEN?: string; TELEGRAM_WEBHOOK_SECRET?: string; + TELEGRAM_RICH_HEADER_HOST?: string; ASSETS?: Fetcher; KV: KVNamespace; DB: D1Database; diff --git a/src/web/richheader-routes.ts b/src/web/richheader-routes.ts new file mode 100644 index 0000000..62c7b83 --- /dev/null +++ b/src/web/richheader-routes.ts @@ -0,0 +1,40 @@ +import { Hono } from "hono"; + +function esc(s: string): string { + return String(s) + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); +} + +export function createRichHeaderRoutes(): Hono { + const app = new Hono(); + + app.get("/richheader", (c) => { + const title = c.req.query("title") ?? ""; + const content = c.req.query("content") ?? ""; + const avatar = c.req.query("avatar") ?? ""; + + const html = ` + + + + + + ${content ? `` : ""} + ${avatar ? `` : ""} + + + ${avatar ? `Avatar` : ""} + ${title ? `

${esc(title)}

` : ""} + ${content ? `

${esc(content)}

` : ""} +

This page is used for Open Graph meta tags (richheader) only.

+ +`; + + return c.html(html); + }); + + return app; +}