Compare commits
4 Commits
main
...
fix/telegr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bc7cb7977 | ||
|
|
92f7fa3918 | ||
|
|
151503872a | ||
|
|
80480132d2 |
@ -73,6 +73,7 @@ Status: beta.
|
|||||||
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- Telegram: send a fallback reply when delivery is empty to avoid silent errors. (#3483) Thanks @kiranjd.
|
||||||
- Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R.
|
- Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R.
|
||||||
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.
|
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.
|
||||||
- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.
|
- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { SILENT_REPLY_TOKEN } from "../tokens.js";
|
||||||
import { normalizeReplyPayload } from "./normalize-reply.js";
|
import { normalizeReplyPayload } from "./normalize-reply.js";
|
||||||
|
|
||||||
// Keep channelData-only payloads so channel-specific replies survive normalization.
|
// Keep channelData-only payloads so channel-specific replies survive normalization.
|
||||||
@ -19,4 +20,30 @@ describe("normalizeReplyPayload", () => {
|
|||||||
expect(normalized?.text).toBeUndefined();
|
expect(normalized?.text).toBeUndefined();
|
||||||
expect(normalized?.channelData).toEqual(payload.channelData);
|
expect(normalized?.channelData).toEqual(payload.channelData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("records silent skips", () => {
|
||||||
|
const reasons: string[] = [];
|
||||||
|
const normalized = normalizeReplyPayload(
|
||||||
|
{ text: SILENT_REPLY_TOKEN },
|
||||||
|
{
|
||||||
|
onSkip: (reason) => reasons.push(reason),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(normalized).toBeNull();
|
||||||
|
expect(reasons).toEqual(["silent"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("records empty skips", () => {
|
||||||
|
const reasons: string[] = [];
|
||||||
|
const normalized = normalizeReplyPayload(
|
||||||
|
{ text: " " },
|
||||||
|
{
|
||||||
|
onSkip: (reason) => reasons.push(reason),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(normalized).toBeNull();
|
||||||
|
expect(reasons).toEqual(["empty"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import {
|
|||||||
} from "./response-prefix-template.js";
|
} from "./response-prefix-template.js";
|
||||||
import { hasLineDirectives, parseLineDirectives } from "./line-directives.js";
|
import { hasLineDirectives, parseLineDirectives } from "./line-directives.js";
|
||||||
|
|
||||||
|
export type NormalizeReplySkipReason = "empty" | "silent" | "heartbeat";
|
||||||
|
|
||||||
export type NormalizeReplyOptions = {
|
export type NormalizeReplyOptions = {
|
||||||
responsePrefix?: string;
|
responsePrefix?: string;
|
||||||
/** Context for template variable interpolation in responsePrefix */
|
/** Context for template variable interpolation in responsePrefix */
|
||||||
@ -15,6 +17,7 @@ export type NormalizeReplyOptions = {
|
|||||||
onHeartbeatStrip?: () => void;
|
onHeartbeatStrip?: () => void;
|
||||||
stripHeartbeat?: boolean;
|
stripHeartbeat?: boolean;
|
||||||
silentToken?: string;
|
silentToken?: string;
|
||||||
|
onSkip?: (reason: NormalizeReplySkipReason) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function normalizeReplyPayload(
|
export function normalizeReplyPayload(
|
||||||
@ -26,12 +29,18 @@ export function normalizeReplyPayload(
|
|||||||
payload.channelData && Object.keys(payload.channelData).length > 0,
|
payload.channelData && Object.keys(payload.channelData).length > 0,
|
||||||
);
|
);
|
||||||
const trimmed = payload.text?.trim() ?? "";
|
const trimmed = payload.text?.trim() ?? "";
|
||||||
if (!trimmed && !hasMedia && !hasChannelData) return null;
|
if (!trimmed && !hasMedia && !hasChannelData) {
|
||||||
|
opts.onSkip?.("empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const silentToken = opts.silentToken ?? SILENT_REPLY_TOKEN;
|
const silentToken = opts.silentToken ?? SILENT_REPLY_TOKEN;
|
||||||
let text = payload.text ?? undefined;
|
let text = payload.text ?? undefined;
|
||||||
if (text && isSilentReplyText(text, silentToken)) {
|
if (text && isSilentReplyText(text, silentToken)) {
|
||||||
if (!hasMedia && !hasChannelData) return null;
|
if (!hasMedia && !hasChannelData) {
|
||||||
|
opts.onSkip?.("silent");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
text = "";
|
text = "";
|
||||||
}
|
}
|
||||||
if (text && !trimmed) {
|
if (text && !trimmed) {
|
||||||
@ -43,14 +52,20 @@ export function normalizeReplyPayload(
|
|||||||
if (shouldStripHeartbeat && text?.includes(HEARTBEAT_TOKEN)) {
|
if (shouldStripHeartbeat && text?.includes(HEARTBEAT_TOKEN)) {
|
||||||
const stripped = stripHeartbeatToken(text, { mode: "message" });
|
const stripped = stripHeartbeatToken(text, { mode: "message" });
|
||||||
if (stripped.didStrip) opts.onHeartbeatStrip?.();
|
if (stripped.didStrip) opts.onHeartbeatStrip?.();
|
||||||
if (stripped.shouldSkip && !hasMedia && !hasChannelData) return null;
|
if (stripped.shouldSkip && !hasMedia && !hasChannelData) {
|
||||||
|
opts.onSkip?.("heartbeat");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
text = stripped.text;
|
text = stripped.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
text = sanitizeUserFacingText(text);
|
text = sanitizeUserFacingText(text);
|
||||||
}
|
}
|
||||||
if (!text?.trim() && !hasMedia && !hasChannelData) return null;
|
if (!text?.trim() && !hasMedia && !hasChannelData) {
|
||||||
|
opts.onSkip?.("empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse LINE-specific directives from text (quick_replies, location, confirm, buttons)
|
// Parse LINE-specific directives from text (quick_replies, location, confirm, buttons)
|
||||||
let enrichedPayload: ReplyPayload = { ...payload, text };
|
let enrichedPayload: ReplyPayload = { ...payload, text };
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { HumanDelayConfig } from "../../config/types.js";
|
import type { HumanDelayConfig } from "../../config/types.js";
|
||||||
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||||
import { normalizeReplyPayload } from "./normalize-reply.js";
|
import { normalizeReplyPayload, type NormalizeReplySkipReason } from "./normalize-reply.js";
|
||||||
import type { ResponsePrefixContext } from "./response-prefix-template.js";
|
import type { ResponsePrefixContext } from "./response-prefix-template.js";
|
||||||
import type { TypingController } from "./typing.js";
|
import type { TypingController } from "./typing.js";
|
||||||
|
|
||||||
@ -8,6 +8,11 @@ export type ReplyDispatchKind = "tool" | "block" | "final";
|
|||||||
|
|
||||||
type ReplyDispatchErrorHandler = (err: unknown, info: { kind: ReplyDispatchKind }) => void;
|
type ReplyDispatchErrorHandler = (err: unknown, info: { kind: ReplyDispatchKind }) => void;
|
||||||
|
|
||||||
|
type ReplyDispatchSkipHandler = (
|
||||||
|
payload: ReplyPayload,
|
||||||
|
info: { kind: ReplyDispatchKind; reason: NormalizeReplySkipReason },
|
||||||
|
) => void;
|
||||||
|
|
||||||
type ReplyDispatchDeliverer = (
|
type ReplyDispatchDeliverer = (
|
||||||
payload: ReplyPayload,
|
payload: ReplyPayload,
|
||||||
info: { kind: ReplyDispatchKind },
|
info: { kind: ReplyDispatchKind },
|
||||||
@ -42,6 +47,8 @@ export type ReplyDispatcherOptions = {
|
|||||||
onHeartbeatStrip?: () => void;
|
onHeartbeatStrip?: () => void;
|
||||||
onIdle?: () => void;
|
onIdle?: () => void;
|
||||||
onError?: ReplyDispatchErrorHandler;
|
onError?: ReplyDispatchErrorHandler;
|
||||||
|
/** onSkip lets channels detect silent/empty drops (e.g. Telegram empty-response fallback). */
|
||||||
|
onSkip?: ReplyDispatchSkipHandler;
|
||||||
/** Human-like delay between block replies for natural rhythm. */
|
/** Human-like delay between block replies for natural rhythm. */
|
||||||
humanDelay?: HumanDelayConfig;
|
humanDelay?: HumanDelayConfig;
|
||||||
};
|
};
|
||||||
@ -65,15 +72,16 @@ export type ReplyDispatcher = {
|
|||||||
getQueuedCounts: () => Record<ReplyDispatchKind, number>;
|
getQueuedCounts: () => Record<ReplyDispatchKind, number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NormalizeReplyPayloadInternalOptions = Pick<
|
||||||
|
ReplyDispatcherOptions,
|
||||||
|
"responsePrefix" | "responsePrefixContext" | "responsePrefixContextProvider" | "onHeartbeatStrip"
|
||||||
|
> & {
|
||||||
|
onSkip?: (reason: NormalizeReplySkipReason) => void;
|
||||||
|
};
|
||||||
|
|
||||||
function normalizeReplyPayloadInternal(
|
function normalizeReplyPayloadInternal(
|
||||||
payload: ReplyPayload,
|
payload: ReplyPayload,
|
||||||
opts: Pick<
|
opts: NormalizeReplyPayloadInternalOptions,
|
||||||
ReplyDispatcherOptions,
|
|
||||||
| "responsePrefix"
|
|
||||||
| "responsePrefixContext"
|
|
||||||
| "responsePrefixContextProvider"
|
|
||||||
| "onHeartbeatStrip"
|
|
||||||
>,
|
|
||||||
): ReplyPayload | null {
|
): ReplyPayload | null {
|
||||||
// Prefer dynamic context provider over static context
|
// Prefer dynamic context provider over static context
|
||||||
const prefixContext = opts.responsePrefixContextProvider?.() ?? opts.responsePrefixContext;
|
const prefixContext = opts.responsePrefixContextProvider?.() ?? opts.responsePrefixContext;
|
||||||
@ -82,6 +90,7 @@ function normalizeReplyPayloadInternal(
|
|||||||
responsePrefix: opts.responsePrefix,
|
responsePrefix: opts.responsePrefix,
|
||||||
responsePrefixContext: prefixContext,
|
responsePrefixContext: prefixContext,
|
||||||
onHeartbeatStrip: opts.onHeartbeatStrip,
|
onHeartbeatStrip: opts.onHeartbeatStrip,
|
||||||
|
onSkip: opts.onSkip,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +108,13 @@ export function createReplyDispatcher(options: ReplyDispatcherOptions): ReplyDis
|
|||||||
};
|
};
|
||||||
|
|
||||||
const enqueue = (kind: ReplyDispatchKind, payload: ReplyPayload) => {
|
const enqueue = (kind: ReplyDispatchKind, payload: ReplyPayload) => {
|
||||||
const normalized = normalizeReplyPayloadInternal(payload, options);
|
const normalized = normalizeReplyPayloadInternal(payload, {
|
||||||
|
responsePrefix: options.responsePrefix,
|
||||||
|
responsePrefixContext: options.responsePrefixContext,
|
||||||
|
responsePrefixContextProvider: options.responsePrefixContextProvider,
|
||||||
|
onHeartbeatStrip: options.onHeartbeatStrip,
|
||||||
|
onSkip: (reason) => options.onSkip?.(payload, { kind, reason }),
|
||||||
|
});
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
queuedCounts[kind] += 1;
|
queuedCounts[kind] += 1;
|
||||||
pending += 1;
|
pending += 1;
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import { createTelegramDraftStream } from "./draft-stream.js";
|
|||||||
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
||||||
import { resolveAgentDir } from "../agents/agent-scope.js";
|
import { resolveAgentDir } from "../agents/agent-scope.js";
|
||||||
|
|
||||||
|
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||||
|
|
||||||
async function resolveStickerVisionSupport(cfg, agentId) {
|
async function resolveStickerVisionSupport(cfg, agentId) {
|
||||||
try {
|
try {
|
||||||
const catalog = await loadModelCatalog({ config: cfg });
|
const catalog = await loadModelCatalog({ config: cfg });
|
||||||
@ -198,6 +200,15 @@ export const dispatchTelegramMessage = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const replyQuoteText =
|
||||||
|
ctxPayload.ReplyToIsQuote && ctxPayload.ReplyToBody
|
||||||
|
? ctxPayload.ReplyToBody.trim() || undefined
|
||||||
|
: undefined;
|
||||||
|
const deliveryState = {
|
||||||
|
delivered: false,
|
||||||
|
skippedNonSilent: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const { queuedFinal } = await dispatchReplyWithBufferedBlockDispatcher({
|
const { queuedFinal } = await dispatchReplyWithBufferedBlockDispatcher({
|
||||||
ctx: ctxPayload,
|
ctx: ctxPayload,
|
||||||
cfg,
|
cfg,
|
||||||
@ -209,12 +220,7 @@ export const dispatchTelegramMessage = async ({
|
|||||||
await flushDraft();
|
await flushDraft();
|
||||||
draftStream?.stop();
|
draftStream?.stop();
|
||||||
}
|
}
|
||||||
|
const result = await deliverReplies({
|
||||||
const replyQuoteText =
|
|
||||||
ctxPayload.ReplyToIsQuote && ctxPayload.ReplyToBody
|
|
||||||
? ctxPayload.ReplyToBody.trim() || undefined
|
|
||||||
: undefined;
|
|
||||||
await deliverReplies({
|
|
||||||
replies: [payload],
|
replies: [payload],
|
||||||
chatId: String(chatId),
|
chatId: String(chatId),
|
||||||
token: opts.token,
|
token: opts.token,
|
||||||
@ -229,6 +235,12 @@ export const dispatchTelegramMessage = async ({
|
|||||||
linkPreview: telegramCfg.linkPreview,
|
linkPreview: telegramCfg.linkPreview,
|
||||||
replyQuoteText,
|
replyQuoteText,
|
||||||
});
|
});
|
||||||
|
if (result.delivered) {
|
||||||
|
deliveryState.delivered = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSkip: (_payload, info) => {
|
||||||
|
if (info.reason !== "silent") deliveryState.skippedNonSilent += 1;
|
||||||
},
|
},
|
||||||
onError: (err, info) => {
|
onError: (err, info) => {
|
||||||
runtime.error?.(danger(`telegram ${info.kind} reply failed: ${String(err)}`));
|
runtime.error?.(danger(`telegram ${info.kind} reply failed: ${String(err)}`));
|
||||||
@ -260,7 +272,27 @@ export const dispatchTelegramMessage = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
draftStream?.stop();
|
draftStream?.stop();
|
||||||
if (!queuedFinal) {
|
let sentFallback = false;
|
||||||
|
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
||||||
|
const result = await deliverReplies({
|
||||||
|
replies: [{ text: EMPTY_RESPONSE_FALLBACK }],
|
||||||
|
chatId: String(chatId),
|
||||||
|
token: opts.token,
|
||||||
|
runtime,
|
||||||
|
bot,
|
||||||
|
replyToMode,
|
||||||
|
textLimit,
|
||||||
|
messageThreadId: resolvedThreadId,
|
||||||
|
tableMode,
|
||||||
|
chunkMode,
|
||||||
|
linkPreview: telegramCfg.linkPreview,
|
||||||
|
replyQuoteText,
|
||||||
|
});
|
||||||
|
sentFallback = result.delivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFinalResponse = queuedFinal || sentFallback;
|
||||||
|
if (!hasFinalResponse) {
|
||||||
if (isGroup && historyKey) {
|
if (isGroup && historyKey) {
|
||||||
clearHistoryEntriesIfEnabled({ historyMap: groupHistories, historyKey, limit: historyLimit });
|
clearHistoryEntriesIfEnabled({ historyMap: groupHistories, historyKey, limit: historyLimit });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,8 @@ import {
|
|||||||
import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
|
import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
|
||||||
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
||||||
|
|
||||||
|
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||||
|
|
||||||
type TelegramNativeCommandContext = Context & { match?: string };
|
type TelegramNativeCommandContext = Context & { match?: string };
|
||||||
|
|
||||||
type TelegramCommandAuthResult = {
|
type TelegramCommandAuthResult = {
|
||||||
@ -483,13 +485,18 @@ export const registerTelegramNativeCommands = ({
|
|||||||
: undefined;
|
: undefined;
|
||||||
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
|
||||||
|
|
||||||
|
const deliveryState = {
|
||||||
|
delivered: false,
|
||||||
|
skippedNonSilent: 0,
|
||||||
|
};
|
||||||
|
|
||||||
await dispatchReplyWithBufferedBlockDispatcher({
|
await dispatchReplyWithBufferedBlockDispatcher({
|
||||||
ctx: ctxPayload,
|
ctx: ctxPayload,
|
||||||
cfg,
|
cfg,
|
||||||
dispatcherOptions: {
|
dispatcherOptions: {
|
||||||
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
|
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
|
||||||
deliver: async (payload) => {
|
deliver: async (payload, _info) => {
|
||||||
await deliverReplies({
|
const result = await deliverReplies({
|
||||||
replies: [payload],
|
replies: [payload],
|
||||||
chatId: String(chatId),
|
chatId: String(chatId),
|
||||||
token: opts.token,
|
token: opts.token,
|
||||||
@ -502,6 +509,12 @@ export const registerTelegramNativeCommands = ({
|
|||||||
chunkMode,
|
chunkMode,
|
||||||
linkPreview: telegramCfg.linkPreview,
|
linkPreview: telegramCfg.linkPreview,
|
||||||
});
|
});
|
||||||
|
if (result.delivered) {
|
||||||
|
deliveryState.delivered = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSkip: (_payload, info) => {
|
||||||
|
if (info.reason !== "silent") deliveryState.skippedNonSilent += 1;
|
||||||
},
|
},
|
||||||
onError: (err, info) => {
|
onError: (err, info) => {
|
||||||
runtime.error?.(danger(`telegram slash ${info.kind} reply failed: ${String(err)}`));
|
runtime.error?.(danger(`telegram slash ${info.kind} reply failed: ${String(err)}`));
|
||||||
@ -512,6 +525,21 @@ export const registerTelegramNativeCommands = ({
|
|||||||
disableBlockStreaming,
|
disableBlockStreaming,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
||||||
|
await deliverReplies({
|
||||||
|
replies: [{ text: EMPTY_RESPONSE_FALLBACK }],
|
||||||
|
chatId: String(chatId),
|
||||||
|
token: opts.token,
|
||||||
|
runtime,
|
||||||
|
bot,
|
||||||
|
replyToMode,
|
||||||
|
textLimit,
|
||||||
|
messageThreadId: threadIdForSend,
|
||||||
|
tableMode,
|
||||||
|
chunkMode,
|
||||||
|
linkPreview: telegramCfg.linkPreview,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export async function deliverReplies(params: {
|
|||||||
linkPreview?: boolean;
|
linkPreview?: boolean;
|
||||||
/** Optional quote text for Telegram reply_parameters. */
|
/** Optional quote text for Telegram reply_parameters. */
|
||||||
replyQuoteText?: string;
|
replyQuoteText?: string;
|
||||||
}) {
|
}): Promise<{ delivered: boolean }> {
|
||||||
const {
|
const {
|
||||||
replies,
|
replies,
|
||||||
chatId,
|
chatId,
|
||||||
@ -58,6 +58,10 @@ export async function deliverReplies(params: {
|
|||||||
} = params;
|
} = params;
|
||||||
const chunkMode = params.chunkMode ?? "length";
|
const chunkMode = params.chunkMode ?? "length";
|
||||||
let hasReplied = false;
|
let hasReplied = false;
|
||||||
|
let hasDelivered = false;
|
||||||
|
const markDelivered = () => {
|
||||||
|
hasDelivered = true;
|
||||||
|
};
|
||||||
const chunkText = (markdown: string) => {
|
const chunkText = (markdown: string) => {
|
||||||
const markdownChunks =
|
const markdownChunks =
|
||||||
chunkMode === "newline"
|
chunkMode === "newline"
|
||||||
@ -114,6 +118,7 @@ export async function deliverReplies(params: {
|
|||||||
linkPreview,
|
linkPreview,
|
||||||
replyMarkup: shouldAttachButtons ? replyMarkup : undefined,
|
replyMarkup: shouldAttachButtons ? replyMarkup : undefined,
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
if (replyToId && !hasReplied) {
|
if (replyToId && !hasReplied) {
|
||||||
hasReplied = true;
|
hasReplied = true;
|
||||||
}
|
}
|
||||||
@ -165,18 +170,21 @@ export async function deliverReplies(params: {
|
|||||||
runtime,
|
runtime,
|
||||||
fn: () => bot.api.sendAnimation(chatId, file, { ...mediaParams }),
|
fn: () => bot.api.sendAnimation(chatId, file, { ...mediaParams }),
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
} else if (kind === "image") {
|
} else if (kind === "image") {
|
||||||
await withTelegramApiErrorLogging({
|
await withTelegramApiErrorLogging({
|
||||||
operation: "sendPhoto",
|
operation: "sendPhoto",
|
||||||
runtime,
|
runtime,
|
||||||
fn: () => bot.api.sendPhoto(chatId, file, { ...mediaParams }),
|
fn: () => bot.api.sendPhoto(chatId, file, { ...mediaParams }),
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
} else if (kind === "video") {
|
} else if (kind === "video") {
|
||||||
await withTelegramApiErrorLogging({
|
await withTelegramApiErrorLogging({
|
||||||
operation: "sendVideo",
|
operation: "sendVideo",
|
||||||
runtime,
|
runtime,
|
||||||
fn: () => bot.api.sendVideo(chatId, file, { ...mediaParams }),
|
fn: () => bot.api.sendVideo(chatId, file, { ...mediaParams }),
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
} else if (kind === "audio") {
|
} else if (kind === "audio") {
|
||||||
const { useVoice } = resolveTelegramVoiceSend({
|
const { useVoice } = resolveTelegramVoiceSend({
|
||||||
wantsVoice: reply.audioAsVoice === true, // default false (backward compatible)
|
wantsVoice: reply.audioAsVoice === true, // default false (backward compatible)
|
||||||
@ -195,6 +203,7 @@ export async function deliverReplies(params: {
|
|||||||
shouldLog: (err) => !isVoiceMessagesForbidden(err),
|
shouldLog: (err) => !isVoiceMessagesForbidden(err),
|
||||||
fn: () => bot.api.sendVoice(chatId, file, { ...mediaParams }),
|
fn: () => bot.api.sendVoice(chatId, file, { ...mediaParams }),
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
} catch (voiceErr) {
|
} catch (voiceErr) {
|
||||||
// Fall back to text if voice messages are forbidden in this chat.
|
// Fall back to text if voice messages are forbidden in this chat.
|
||||||
// This happens when the recipient has Telegram Premium privacy settings
|
// This happens when the recipient has Telegram Premium privacy settings
|
||||||
@ -221,6 +230,7 @@ export async function deliverReplies(params: {
|
|||||||
replyMarkup,
|
replyMarkup,
|
||||||
replyQuoteText,
|
replyQuoteText,
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
// Skip this media item; continue with next.
|
// Skip this media item; continue with next.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -233,6 +243,7 @@ export async function deliverReplies(params: {
|
|||||||
runtime,
|
runtime,
|
||||||
fn: () => bot.api.sendAudio(chatId, file, { ...mediaParams }),
|
fn: () => bot.api.sendAudio(chatId, file, { ...mediaParams }),
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await withTelegramApiErrorLogging({
|
await withTelegramApiErrorLogging({
|
||||||
@ -240,6 +251,7 @@ export async function deliverReplies(params: {
|
|||||||
runtime,
|
runtime,
|
||||||
fn: () => bot.api.sendDocument(chatId, file, { ...mediaParams }),
|
fn: () => bot.api.sendDocument(chatId, file, { ...mediaParams }),
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
}
|
}
|
||||||
if (replyToId && !hasReplied) {
|
if (replyToId && !hasReplied) {
|
||||||
hasReplied = true;
|
hasReplied = true;
|
||||||
@ -260,6 +272,7 @@ export async function deliverReplies(params: {
|
|||||||
linkPreview,
|
linkPreview,
|
||||||
replyMarkup: i === 0 ? replyMarkup : undefined,
|
replyMarkup: i === 0 ? replyMarkup : undefined,
|
||||||
});
|
});
|
||||||
|
markDelivered();
|
||||||
if (replyToId && !hasReplied) {
|
if (replyToId && !hasReplied) {
|
||||||
hasReplied = true;
|
hasReplied = true;
|
||||||
}
|
}
|
||||||
@ -268,6 +281,7 @@ export async function deliverReplies(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return { delivered: hasDelivered };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveMedia(
|
export async function resolveMedia(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user