From 7363fb4a44281df17ce6ac4b6c1a862e0822b7d8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 12:25:51 +0100 Subject: [PATCH] refactor: move telegram poll visibility out of core --- .../telegram/src/poll-visibility.test.ts | 13 ++++ src/poll-params.test.ts | 13 ++-- src/poll-params.ts | 70 ++++++++++++++----- 3 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 extensions/telegram/src/poll-visibility.test.ts diff --git a/extensions/telegram/src/poll-visibility.test.ts b/extensions/telegram/src/poll-visibility.test.ts new file mode 100644 index 00000000000..3c62c8255f0 --- /dev/null +++ b/extensions/telegram/src/poll-visibility.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vitest"; +import { resolveTelegramPollVisibility } from "./poll-visibility.js"; + +describe("telegram poll visibility", () => { + it("resolves poll visibility aliases", () => { + expect(resolveTelegramPollVisibility({ pollAnonymous: true })).toBe(true); + expect(resolveTelegramPollVisibility({ pollPublic: true })).toBe(false); + expect(resolveTelegramPollVisibility({})).toBeUndefined(); + expect(() => resolveTelegramPollVisibility({ pollAnonymous: true, pollPublic: true })).toThrow( + /mutually exclusive/i, + ); + }); +}); diff --git a/src/poll-params.test.ts b/src/poll-params.test.ts index 1779d78ab27..334bf514557 100644 --- a/src/poll-params.test.ts +++ b/src/poll-params.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { hasPollCreationParams, resolveTelegramPollVisibility } from "./poll-params.js"; +import { hasPollCreationParams } from "./poll-params.js"; describe("poll params", () => { it("does not treat explicit false booleans as poll creation params", () => { @@ -60,12 +60,9 @@ describe("poll params", () => { expect(hasPollCreationParams({ poll_public: "true" })).toBe(true); }); - it("resolves telegram poll visibility flags", () => { - expect(resolveTelegramPollVisibility({ pollAnonymous: true })).toBe(true); - expect(resolveTelegramPollVisibility({ pollPublic: true })).toBe(false); - expect(resolveTelegramPollVisibility({})).toBeUndefined(); - expect(() => resolveTelegramPollVisibility({ pollAnonymous: true, pollPublic: true })).toThrow( - /mutually exclusive/i, - ); + it("ignores poll vote params when deciding whether send should become poll", () => { + expect(hasPollCreationParams({ pollId: "poll-1" })).toBe(false); + expect(hasPollCreationParams({ pollOptionId: "answer-1" })).toBe(false); + expect(hasPollCreationParams({ pollOptionIndexes: [1] })).toBe(false); }); }); diff --git a/src/poll-params.ts b/src/poll-params.ts index a19ac5fb142..441bc836de1 100644 --- a/src/poll-params.ts +++ b/src/poll-params.ts @@ -14,40 +14,67 @@ const SHARED_POLL_CREATION_PARAM_DEFS = { pollMulti: { kind: "boolean" }, } satisfies Record; -const TELEGRAM_POLL_CREATION_PARAM_DEFS = { - pollDurationSeconds: { kind: "number" }, - pollAnonymous: { kind: "boolean" }, - pollPublic: { kind: "boolean" }, -} satisfies Record; - -export const POLL_CREATION_PARAM_DEFS: Record = { - ...SHARED_POLL_CREATION_PARAM_DEFS, - ...TELEGRAM_POLL_CREATION_PARAM_DEFS, -}; +export const POLL_CREATION_PARAM_DEFS: Record = + SHARED_POLL_CREATION_PARAM_DEFS; type SharedPollCreationParamName = keyof typeof SHARED_POLL_CREATION_PARAM_DEFS; -const POLL_CREATION_PARAM_NAMES = Object.keys(POLL_CREATION_PARAM_DEFS); export const SHARED_POLL_CREATION_PARAM_NAMES = Object.keys( SHARED_POLL_CREATION_PARAM_DEFS, ) as SharedPollCreationParamName[]; +const SHARED_POLL_CREATION_PARAM_KEY_SET = new Set( + SHARED_POLL_CREATION_PARAM_NAMES.map(normalizePollParamKey), +); +const POLL_VOTE_PARAM_KEY_SET = new Set( + ["pollId", "pollOptionId", "pollOptionIds", "pollOptionIndex", "pollOptionIndexes"].map( + normalizePollParamKey, + ), +); function readPollParamRaw(params: Record, key: string): unknown { return readSnakeCaseParamRaw(params, key); } -export function resolveTelegramPollVisibility(params: { - pollAnonymous?: boolean; - pollPublic?: boolean; -}): boolean | undefined { - if (params.pollAnonymous && params.pollPublic) { - throw new Error("pollAnonymous and pollPublic are mutually exclusive"); +function normalizePollParamKey(key: string): string { + return normalizeLowercaseStringOrEmpty(key.replaceAll("_", "")); +} + +function isChannelPollCreationParamName(key: string): boolean { + const normalized = normalizePollParamKey(key); + return ( + normalized.startsWith("poll") && + !SHARED_POLL_CREATION_PARAM_KEY_SET.has(normalized) && + !POLL_VOTE_PARAM_KEY_SET.has(normalized) + ); +} + +function hasExplicitUnknownPollValue(key: string, value: unknown): boolean { + if (value === true) { + return true; } - return params.pollAnonymous ? true : params.pollPublic ? false : undefined; + if (typeof value === "number") { + return Number.isFinite(value) && value !== 0; + } + if (typeof value === "string") { + const trimmed = value.trim(); + if (trimmed.length === 0) { + return false; + } + if (normalizePollParamKey(key).includes("duration")) { + const parsed = Number(trimmed); + return Number.isFinite(parsed) && parsed !== 0; + } + const normalized = normalizeLowercaseStringOrEmpty(trimmed); + return normalized !== "false" && normalized !== "0"; + } + if (Array.isArray(value)) { + return value.some((entry) => hasExplicitUnknownPollValue(key, entry)); + } + return false; } export function hasPollCreationParams(params: Record): boolean { - for (const key of POLL_CREATION_PARAM_NAMES) { + for (const key of SHARED_POLL_CREATION_PARAM_NAMES) { const def = POLL_CREATION_PARAM_DEFS[key]; const value = readPollParamRaw(params, key); if (def.kind === "string" && typeof value === "string" && value.trim().length > 0) { @@ -88,5 +115,10 @@ export function hasPollCreationParams(params: Record): boolean } } } + for (const [key, value] of Object.entries(params)) { + if (isChannelPollCreationParamName(key) && hasExplicitUnknownPollValue(key, value)) { + return true; + } + } return false; }