

























@@ -206,6 +206,7 @@ type TelegramReplyMarkup = {
206206type TelegramRichMessage = {
207207markdown?: string;
208208html?: string;
209+blocks?: unknown[];
209210};
210211211212type TelegramMessage = {
@@ -703,13 +704,99 @@ function detectMediaKinds(message: TelegramMessage) {
703704return kinds;
704705}
705706707+function flattenTelegramRichText(value: unknown): string {
708+if (typeof value === "string") {
709+return value;
710+}
711+if (Array.isArray(value)) {
712+return value.map((part) => flattenTelegramRichText(part)).join("");
713+}
714+if (!isRecord(value)) {
715+return "";
716+}
717+if ("text" in value) {
718+return flattenTelegramRichText(value.text);
719+}
720+if (typeof value.alternative_text === "string") {
721+return value.alternative_text;
722+}
723+if (typeof value.expression === "string") {
724+return value.expression;
725+}
726+return "";
727+}
728+729+function flattenTelegramRichBlock(value: unknown): string {
730+if (typeof value === "string" || Array.isArray(value)) {
731+return flattenTelegramRichText(value);
732+}
733+if (!isRecord(value)) {
734+return "";
735+}
736+const parts: string[] = [];
737+if ("text" in value) {
738+parts.push(flattenTelegramRichText(value.text));
739+}
740+if ("summary" in value) {
741+parts.push(flattenTelegramRichText(value.summary));
742+}
743+if (typeof value.label === "string") {
744+parts.push(value.label);
745+}
746+if (typeof value.expression === "string") {
747+parts.push(value.expression);
748+}
749+if ("blocks" in value) {
750+parts.push(flattenTelegramRichBlocks(value.blocks));
751+}
752+if ("items" in value) {
753+parts.push(flattenTelegramRichBlocks(value.items));
754+}
755+if ("cells" in value) {
756+parts.push(flattenTelegramRichTableCells(value.cells));
757+}
758+if ("caption" in value) {
759+parts.push(flattenTelegramRichBlock(value.caption));
760+}
761+if ("credit" in value) {
762+parts.push(flattenTelegramRichText(value.credit));
763+}
764+return parts.filter((part) => part.trim()).join("\n");
765+}
766+767+function flattenTelegramRichBlocks(value: unknown): string {
768+const blocks = Array.isArray(value) ? value : [value];
769+return blocks
770+.map((block) => flattenTelegramRichBlock(block))
771+.filter((part) => part.trim())
772+.join("\n");
773+}
774+775+function flattenTelegramRichTableCells(value: unknown): string {
776+if (!Array.isArray(value)) {
777+return flattenTelegramRichBlock(value);
778+}
779+return value
780+.map((row) => {
781+const cells = Array.isArray(row) ? row : [row];
782+return cells
783+.map((cell) => flattenTelegramRichBlock(cell))
784+.filter((cell) => cell.trim())
785+.join("\t");
786+})
787+.filter((row) => row.trim())
788+.join("\n");
789+}
790+791+function selectTelegramRichMessageText(richMessage: TelegramRichMessage | undefined) {
792+return (
793+richMessage?.markdown || richMessage?.html || flattenTelegramRichBlocks(richMessage?.blocks)
794+);
795+}
796+706797function selectTelegramObservedText(message: TelegramMessage) {
707798return (
708-message.text ||
709-message.caption ||
710-message.rich_message?.markdown ||
711-message.rich_message?.html ||
712-""
799+message.text || message.caption || selectTelegramRichMessageText(message.rich_message) || ""
713800);
714801}
715802@@ -911,6 +998,7 @@ async function waitForObservedMessage(params: {
911998observationScenarioId: string;
912999observationScenarioTitle: string;
9131000expectedTextIncludes?: string[];
1001+validateMatchedMessage?: (message: TelegramObservedMessage) => void;
9141002}) {
9151003const startedAt = Date.now();
9161004let offset = params.initialOffset;
@@ -963,10 +1051,14 @@ async function waitForObservedMessage(params: {
9631051params.observedMessages.push(observedMessage);
9641052if (matchedScenario) {
9651053try {
966-assertTelegramScenarioReply({
967-expectedTextIncludes: params.expectedTextIncludes,
968-message: observedMessage,
969-});
1054+if (params.validateMatchedMessage) {
1055+params.validateMatchedMessage(observedMessage);
1056+} else {
1057+assertTelegramScenarioReply({
1058+expectedTextIncludes: params.expectedTextIncludes,
1059+message: observedMessage,
1060+});
1061+}
9701062} catch (error) {
9711063lastExpectedMismatch =
9721064error instanceof Error ? error : new Error(formatErrorMessage(error));
@@ -1330,6 +1422,15 @@ function assertTelegramScenarioReply(params: {
13301422}
13311423}
133214241425+function assertTelegramCanaryPresenceReply(message: TelegramObservedMessage) {
1426+if (!message.senderIsBot) {
1427+throw new Error(`canary reply message ${message.messageId} was not sent by a bot`);
1428+}
1429+// Telegram rich-message updates can arrive to the driver bot with no text
1430+// body. The release canary proves command delivery plus threaded SUT output;
1431+// text assertions stay on explicit command/scenario checks.
1432+}
1433+13331434function isTelegramObservedMessageTimeoutError(error: unknown, timeoutMs: number) {
13341435return formatErrorMessage(error).startsWith(
13351436`timed out after ${timeoutMs}ms waiting for Telegram message`,
@@ -1451,6 +1552,7 @@ async function runCanary(params: {
14511552observedMessages: params.observedMessages,
14521553observationScenarioId: "telegram-canary",
14531554observationScenarioTitle: "Telegram canary",
1555+validateMatchedMessage: assertTelegramCanaryPresenceReply,
14541556predicate: (message) => {
14551557const classification = classifyCanaryReply({
14561558 message,
@@ -1497,18 +1599,6 @@ async function runCanary(params: {
14971599},
14981600);
14991601}
1500-if (!sutObserved.message.text.trim()) {
1501-throw new TelegramQaCanaryError(
1502-"sut_reply_empty",
1503-"SUT bot replied to the canary message but the reply text was empty.",
1504-{
1505-groupId: params.groupId,
1506-sutBotId: params.sutBotId,
1507-driverMessageId: driverMessage.message_id,
1508-sutMessageId: sutObserved.message.messageId,
1509-},
1510-);
1511-}
15121602return {
15131603 requestStartedAt,
15141604responseObservedAt: new Date(sutObserved.observedAtMs).toISOString(),
@@ -2096,6 +2186,7 @@ export const testing = {
20962186 buildObservedMessagesArtifact,
20972187 canaryFailureMessage,
20982188 callTelegramApi,
2189+ assertTelegramCanaryPresenceReply,
20992190 assertTelegramScenarioMessageSet,
21002191 isRecoverableTelegramQaPollError,
21012192 assertTelegramScenarioReply,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。