mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
Revert "Default Telegram polls to public"
This reverts commit c43e95e011.
This commit is contained in:
@@ -70,7 +70,6 @@ Mattermost responds to DMs automatically. Channel behavior is controlled by `cha
|
||||
|
||||
- `oncall` (default): respond only when @mentioned in channels.
|
||||
- `onmessage`: respond to every channel message.
|
||||
- `always`: respond to every message in channels (same channel behavior as `onmessage`).
|
||||
- `onchar`: respond when a message starts with a trigger prefix.
|
||||
|
||||
Config example:
|
||||
@@ -90,25 +89,6 @@ Notes:
|
||||
|
||||
- `onchar` still responds to explicit @mentions.
|
||||
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
||||
- Current limitation: due to Mattermost plugin event behavior (`#11797`), `chatmode: "onmessage"` and
|
||||
`chatmode: "always"` may still require explicit group mention override to respond without @mentions.
|
||||
Use:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
groupPolicy: "open",
|
||||
groups: {
|
||||
"*": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Reference: [Bug: Mattermost plugin does not receive channel message events via WebSocket #11797](https://github.com/open-webui/open-webui/issues/11797).
|
||||
Related fix scope: [fix(mattermost): honor chatmode mention fallback in group mention gating #14995](https://github.com/open-webui/open-webui/pull/14995).
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
@@ -153,7 +133,6 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- No replies in channels: ensure the bot is in the channel and use the mode behavior correctly: mention it (`oncall`), use a trigger prefix (`onchar`), or use `onmessage`/`always` with:
|
||||
`channels.mattermost.groups["*"].requireMention = false` (and typically `groupPolicy: "open"`).
|
||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { createTelegramActionGate } from "../../telegram/accounts.js";
|
||||
import type { TelegramButtonStyle, TelegramInlineButtons } from "../../telegram/button-types.js";
|
||||
import { createTelegramActionGate } from "../../telegram/accounts.js";
|
||||
import {
|
||||
resolveTelegramInlineButtonsScope,
|
||||
resolveTelegramTargetChatType,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveTelegramAllowedUpdates } from "./allowed-updates.js";
|
||||
|
||||
describe("resolveTelegramAllowedUpdates", () => {
|
||||
it("includes poll_answer updates", () => {
|
||||
const updates = resolveTelegramAllowedUpdates();
|
||||
expect(updates).toContain("poll_answer");
|
||||
});
|
||||
});
|
||||
@@ -4,9 +4,6 @@ type TelegramUpdateType = (typeof API_CONSTANTS.ALL_UPDATE_TYPES)[number];
|
||||
|
||||
export function resolveTelegramAllowedUpdates(): ReadonlyArray<TelegramUpdateType> {
|
||||
const updates = [...API_CONSTANTS.DEFAULT_UPDATE_TYPES] as TelegramUpdateType[];
|
||||
if (!updates.includes("poll_answer")) {
|
||||
updates.push("poll_answer");
|
||||
}
|
||||
if (!updates.includes("message_reaction")) {
|
||||
updates.push("message_reaction");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { Message, ReactionTypeEmoji } from "@grammyjs/types";
|
||||
import type { TelegramGroupConfig, TelegramTopicConfig } from "../config/types.js";
|
||||
import type { TelegramMediaRef } from "./bot-message-context.js";
|
||||
import type { TelegramContext } from "./bot/types.js";
|
||||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||
import {
|
||||
@@ -14,7 +17,6 @@ import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { writeConfigFile } from "../config/io.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
||||
import type { TelegramGroupConfig, TelegramTopicConfig } from "../config/types.js";
|
||||
import { danger, logVerbose, warn } from "../globals.js";
|
||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
||||
@@ -26,7 +28,6 @@ import {
|
||||
normalizeAllowFromWithStore,
|
||||
type NormalizedAllowFrom,
|
||||
} from "./bot-access.js";
|
||||
import type { TelegramMediaRef } from "./bot-message-context.js";
|
||||
import { RegisterTelegramHandlerParams } from "./bot-native-commands.js";
|
||||
import { MEDIA_GROUP_TIMEOUT_MS, type MediaGroupEntry } from "./bot-updates.js";
|
||||
import { resolveMedia } from "./bot/delivery.js";
|
||||
@@ -36,7 +37,6 @@ import {
|
||||
resolveTelegramForumThreadId,
|
||||
resolveTelegramGroupAllowFromContext,
|
||||
} from "./bot/helpers.js";
|
||||
import type { TelegramContext } from "./bot/types.js";
|
||||
import {
|
||||
evaluateTelegramGroupBaseAccess,
|
||||
evaluateTelegramGroupPolicyAccess,
|
||||
@@ -51,7 +51,6 @@ import {
|
||||
parseModelCallbackData,
|
||||
type ProviderInfo,
|
||||
} from "./model-buttons.js";
|
||||
import { getSentPoll } from "./poll-vote-cache.js";
|
||||
import { buildInlineKeyboard } from "./send.js";
|
||||
import { wasSentByBot } from "./sent-message-cache.js";
|
||||
|
||||
@@ -844,65 +843,6 @@ export const registerTelegramHandlers = ({
|
||||
}
|
||||
});
|
||||
|
||||
bot.on("poll_answer", async (ctx) => {
|
||||
try {
|
||||
if (shouldSkipUpdate(ctx)) {
|
||||
return;
|
||||
}
|
||||
const pollAnswer = (ctx.update as { poll_answer?: unknown })?.poll_answer as
|
||||
| {
|
||||
poll_id?: string;
|
||||
user?: { id?: number; username?: string; first_name?: string };
|
||||
option_ids?: number[];
|
||||
}
|
||||
| undefined;
|
||||
if (!pollAnswer) {
|
||||
return;
|
||||
}
|
||||
const pollId = pollAnswer?.poll_id?.trim();
|
||||
if (!pollId) {
|
||||
return;
|
||||
}
|
||||
const pollMeta = getSentPoll(pollId);
|
||||
if (!pollMeta) {
|
||||
return;
|
||||
}
|
||||
if (pollMeta.accountId && pollMeta.accountId !== accountId) {
|
||||
return;
|
||||
}
|
||||
const userId = pollAnswer.user?.id;
|
||||
if (typeof userId !== "number") {
|
||||
return;
|
||||
}
|
||||
const optionIds = Array.isArray(pollAnswer.option_ids) ? pollAnswer.option_ids : [];
|
||||
const selected = optionIds.map((id) => pollMeta.options[id] ?? `option#${id + 1}`);
|
||||
const selectedText = selected.length > 0 ? selected.join(", ") : "(cleared vote)";
|
||||
const syntheticText = `Poll vote update: "${pollMeta.question}" -> ${selectedText}`;
|
||||
const syntheticMessage = {
|
||||
message_id: Date.now(),
|
||||
date: Math.floor(Date.now() / 1000),
|
||||
chat: {
|
||||
id: Number(pollMeta.chatId),
|
||||
type: String(pollMeta.chatId).startsWith("-") ? "supergroup" : "private",
|
||||
},
|
||||
from: {
|
||||
id: userId,
|
||||
is_bot: false,
|
||||
first_name: pollAnswer.user?.first_name ?? "User",
|
||||
username: pollAnswer.user?.username,
|
||||
},
|
||||
text: syntheticText,
|
||||
} as unknown as Message;
|
||||
const storeAllowFrom = await loadStoreAllowFrom();
|
||||
await processMessage(buildSyntheticContext(ctx, syntheticMessage), [], storeAllowFrom, {
|
||||
forceWasMentioned: true,
|
||||
messageIdOverride: `poll:${pollId}:${userId}:${Date.now()}`,
|
||||
});
|
||||
} catch (err) {
|
||||
runtime.error?.(danger(`poll_answer handler failed: ${String(err)}`));
|
||||
}
|
||||
});
|
||||
|
||||
// Handle group migration to supergroup (chat ID changes)
|
||||
bot.on("message:migrate_to_chat_id", async (ctx) => {
|
||||
try {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
const TTL_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
export type TelegramSentPoll = {
|
||||
pollId: string;
|
||||
chatId: string;
|
||||
question: string;
|
||||
options: string[];
|
||||
accountId?: string;
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
const pollById = new Map<string, TelegramSentPoll>();
|
||||
|
||||
function cleanupExpired() {
|
||||
const now = Date.now();
|
||||
for (const [pollId, poll] of pollById) {
|
||||
if (now - poll.createdAt > TTL_MS) {
|
||||
pollById.delete(pollId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function recordSentPoll(poll: Omit<TelegramSentPoll, "createdAt">) {
|
||||
cleanupExpired();
|
||||
pollById.set(poll.pollId, { ...poll, createdAt: Date.now() });
|
||||
}
|
||||
|
||||
export function getSentPoll(pollId: string): TelegramSentPoll | undefined {
|
||||
cleanupExpired();
|
||||
return pollById.get(pollId);
|
||||
}
|
||||
|
||||
export function clearSentPollCache() {
|
||||
pollById.clear();
|
||||
}
|
||||
@@ -1341,38 +1341,6 @@ describe("sendPollTelegram", () => {
|
||||
expect(sendPollMock.mock.calls[0]?.[3]).toMatchObject({ open_period: 60 });
|
||||
});
|
||||
|
||||
it("defaults polls to public (is_anonymous=false)", async () => {
|
||||
const api = {
|
||||
sendPoll: vi.fn(async () => ({ message_id: 123, chat: { id: 555 }, poll: { id: "p1" } })),
|
||||
};
|
||||
|
||||
await sendPollTelegram(
|
||||
"123",
|
||||
{ question: "Q", options: ["A", "B"] },
|
||||
{ token: "t", api: api as unknown as Bot["api"] },
|
||||
);
|
||||
|
||||
expect(api.sendPoll).toHaveBeenCalledTimes(1);
|
||||
const sendPollMock = api.sendPoll as ReturnType<typeof vi.fn>;
|
||||
expect(sendPollMock.mock.calls[0]?.[3]).toMatchObject({ is_anonymous: false });
|
||||
});
|
||||
|
||||
it("supports explicit anonymous polls", async () => {
|
||||
const api = {
|
||||
sendPoll: vi.fn(async () => ({ message_id: 123, chat: { id: 555 }, poll: { id: "p1" } })),
|
||||
};
|
||||
|
||||
await sendPollTelegram(
|
||||
"123",
|
||||
{ question: "Q", options: ["A", "B"] },
|
||||
{ token: "t", api: api as unknown as Bot["api"], isAnonymous: true },
|
||||
);
|
||||
|
||||
expect(api.sendPoll).toHaveBeenCalledTimes(1);
|
||||
const sendPollMock = api.sendPoll as ReturnType<typeof vi.fn>;
|
||||
expect(sendPollMock.mock.calls[0]?.[3]).toMatchObject({ is_anonymous: true });
|
||||
});
|
||||
|
||||
it("retries without message_thread_id on thread-not-found", async () => {
|
||||
const api = {
|
||||
sendPoll: vi.fn(
|
||||
|
||||
@@ -5,6 +5,8 @@ import type {
|
||||
ReactionTypeEmoji,
|
||||
} from "@grammyjs/types";
|
||||
import { type ApiClientOptions, Bot, HttpError, InputFile } from "grammy";
|
||||
import type { RetryConfig } from "../infra/retry.js";
|
||||
import type { TelegramInlineButtons } from "./button-types.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
@@ -12,7 +14,6 @@ import { recordChannelActivity } from "../infra/channel-activity.js";
|
||||
import { isDiagnosticFlagEnabled } from "../infra/diagnostic-flags.js";
|
||||
import { formatErrorMessage, formatUncaughtError } from "../infra/errors.js";
|
||||
import { createTelegramRetryRunner } from "../infra/retry-policy.js";
|
||||
import type { RetryConfig } from "../infra/retry.js";
|
||||
import { redactSensitiveText } from "../logging/redact.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { mediaKindFromMime } from "../media/constants.js";
|
||||
@@ -22,12 +23,10 @@ import { loadWebMedia } from "../web/media.js";
|
||||
import { type ResolvedTelegramAccount, resolveTelegramAccount } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import { buildTelegramThreadParams } from "./bot/helpers.js";
|
||||
import type { TelegramInlineButtons } from "./button-types.js";
|
||||
import { splitTelegramCaption } from "./caption.js";
|
||||
import { resolveTelegramFetch } from "./fetch.js";
|
||||
import { renderTelegramHtmlText } from "./format.js";
|
||||
import { isRecoverableTelegramNetworkError } from "./network-errors.js";
|
||||
import { recordSentPoll } from "./poll-vote-cache.js";
|
||||
import { makeProxyFetch } from "./proxy.js";
|
||||
import { recordSentMessage } from "./sent-message-cache.js";
|
||||
import { parseTelegramTarget, stripTelegramInternalPrefixes } from "./targets.js";
|
||||
@@ -991,7 +990,7 @@ type TelegramPollOpts = {
|
||||
messageThreadId?: number;
|
||||
/** Send message silently (no notification). Defaults to false. */
|
||||
silent?: boolean;
|
||||
/** Whether votes are anonymous. Defaults to false (public poll). */
|
||||
/** Whether votes are anonymous. Defaults to true (Telegram default). */
|
||||
isAnonymous?: boolean;
|
||||
};
|
||||
|
||||
@@ -1050,7 +1049,7 @@ export async function sendPollTelegram(
|
||||
// sendPoll(chat_id, question, options, other?, signal?)
|
||||
const pollParams = {
|
||||
allows_multiple_answers: normalizedPoll.maxSelections > 1,
|
||||
is_anonymous: opts.isAnonymous ?? false,
|
||||
is_anonymous: opts.isAnonymous ?? true,
|
||||
...(durationSeconds !== undefined ? { open_period: durationSeconds } : {}),
|
||||
...(Object.keys(threadParams).length > 0 ? threadParams : {}),
|
||||
...(opts.silent === true ? { disable_notification: true } : {}),
|
||||
@@ -1073,15 +1072,6 @@ export async function sendPollTelegram(
|
||||
if (result?.message_id) {
|
||||
recordSentMessage(chatId, result.message_id);
|
||||
}
|
||||
if (pollId) {
|
||||
recordSentPoll({
|
||||
pollId,
|
||||
chatId: resolvedChatId,
|
||||
question: normalizedPoll.question,
|
||||
options: normalizedPoll.options,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
}
|
||||
|
||||
recordChannelActivity({
|
||||
channel: "telegram",
|
||||
|
||||
Reference in New Issue
Block a user