Files
oh-my-pi-telegram/lib/menu-settings.ts
T
evandro 6a6e75fe32 refactor: cut cyclomatic complexity of five hot spots and pin their behavior with tests
Risk labels, menu callback parsing, settings callbacks, table cell width and
markdown link targets now use tables and small helpers (CC <= 10).
2026-10-10 03:59:46 -03:00

771 lines
26 KiB
TypeScript

/**
* Telegram settings menu UI helpers
* Zones: telegram ui, settings controls, menu composition
* Owns hidden settings-menu rendering, settings callbacks, and persisted toggle wiring
*/
import {
getTelegramExtensionSettingsRows,
type TelegramSectionRegistry,
} from "./sections.ts";
import type {
TelegramAssistantRenderingMode,
TelegramTimeMode,
} from "./config.ts";
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
import type { TelegramModelMenuState } from "./menu-model.ts";
import type { MenuModel } from "./model.ts";
import type { TelegramVoiceReplyMode } from "./voice.ts";
export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
export interface TelegramSettingsStateDeps {
isProactivePushEnabled: () => boolean;
areDraftPreviewsEnabled: () => boolean;
getAssistantRenderingMode: () => TelegramAssistantRenderingMode;
getTimeInjectionMode: () => TelegramTimeMode;
getVoiceReplyMode: () => TelegramVoiceReplyMode;
isVoiceReplyModeConfigured: () => boolean;
}
export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps {
setProactivePushEnabled: (enabled: boolean) => Promise<void>;
setDraftPreviewsEnabled: (enabled: boolean) => Promise<void>;
setAssistantRenderingMode: (
mode: TelegramAssistantRenderingMode,
) => Promise<void>;
setVoiceReplyMode: (
mode: TelegramVoiceReplyMode | undefined,
) => Promise<void>;
setTimeInjectionMode: (mode: TelegramTimeMode) => Promise<void>;
}
export interface TelegramSettingsMenuOpenDeps<
TModel extends MenuModel = MenuModel,
> extends TelegramSettingsStateDeps {
getModelMenuState: () => Promise<TelegramModelMenuState<TModel>>;
sendSettingsMenu: (
state: TelegramModelMenuState<TModel>,
text: string,
replyMarkup: TelegramSettingsMenuReplyMarkup,
) => Promise<number | undefined>;
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
}
export interface TelegramSettingsMenuCallbackDeps extends TelegramSettingsMutationDeps {
updateSettingsMessage: (
text: string,
replyMarkup: TelegramSettingsMenuReplyMarkup,
) => Promise<void>;
answerCallbackQuery: (
callbackQueryId: string,
text?: string,
) => Promise<void>;
sectionRegistry?: TelegramSectionRegistry;
}
export interface TelegramSettingsMenuRuntime<TContext> {
openSettingsMenu: (
chatId: number,
replyToMessageId: number,
ctx: TContext,
) => Promise<void>;
handleCallbackQuery: (
query: {
id: string;
data?: string;
message?: { message_id?: number; chat?: { id?: number } };
},
ctx: TContext,
) => Promise<boolean>;
updateSettingsMenuMessage: (
state: TelegramModelMenuState,
ctx: TContext,
) => Promise<void>;
}
export interface TelegramSettingsMenuMessageUpdateDeps extends TelegramSettingsStateDeps {
updateSettingsMessage: (
text: string,
replyMarkup: TelegramSettingsMenuReplyMarkup,
) => Promise<void>;
}
export interface TelegramSettingsMenuRuntimeDeps<
TContext,
TModel extends MenuModel = MenuModel,
> extends TelegramSettingsMutationDeps {
getModelMenuState: (
chatId: number,
ctx: TContext,
) => Promise<TelegramModelMenuState<TModel>>;
getStoredModelMenuState: (
messageId: number | undefined,
chatId?: number,
) => TelegramModelMenuState<TModel> | undefined;
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
editInteractiveMessage: (
chatId: number,
messageId: number,
text: string,
mode: "markdown" | "html" | "plain",
replyMarkup: TelegramSettingsMenuReplyMarkup,
) => Promise<void>;
sendInteractiveMessage: (
chatId: number,
text: string,
mode: "markdown" | "html" | "plain",
replyMarkup: TelegramSettingsMenuReplyMarkup,
) => Promise<number | undefined>;
answerCallbackQuery: (
callbackQueryId: string,
text?: string,
) => Promise<void>;
}
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
export const DRAFT_PREVIEWS_SETTINGS_TITLE = "<b>📝 Draft previews:</b>";
export const ASSISTANT_RENDERING_SETTINGS_TITLE =
"<b>🧾 Assistant rendering:</b>";
export const TIME_INJECTION_MODE_SETTINGS_TITLE =
"<b>🕒 Time injection mode:</b>";
export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
type TelegramVoiceReplyModeSetting = TelegramVoiceReplyMode | "hidden";
function getVoiceReplyModeLabel(mode: TelegramVoiceReplyModeSetting): string {
return mode;
}
function getTelegramSettingsStateValueLabel(value: string): string {
return value.toLowerCase();
}
function getVoiceReplyModeSetting(
mode: TelegramVoiceReplyMode,
configured: boolean,
): TelegramVoiceReplyModeSetting {
return configured ? mode : "hidden";
}
export function buildTelegramSettingsMenuText(): string {
return SETTINGS_MENU_TITLE;
}
export function buildProactivePushSettingsText(
proactivePushEnabled: boolean,
): string {
return [
`${PROACTIVE_PUSH_SETTINGS_TITLE} <code>${proactivePushEnabled ? "on" : "off"}</code>`,
"",
"Send successful local Pi task results to Telegram when the bridge is connected.",
].join("\n");
}
export function buildDraftPreviewsSettingsText(enabled: boolean): string {
return [
`${DRAFT_PREVIEWS_SETTINGS_TITLE} <code>${enabled ? "on" : "off"}</code>`,
"",
"Show live answer drafts while the model is answering.",
"",
"<code>-</code> <code>off</code> (default): show native active status, then send one final answer.",
"<code>-</code> <code>on</code>: stream safe Telegram Rich Draft frames before the final answer.",
].join("\n");
}
export function buildAssistantRenderingSettingsText(
mode: TelegramAssistantRenderingMode,
): string {
return [
`${ASSISTANT_RENDERING_SETTINGS_TITLE} <code>${mode}</code>`,
"",
"Choose how final assistant Markdown answers are delivered.",
"",
"<code>-</code> <code>rich</code> (default): use Telegram Native Rich Markdown.",
"<code>-</code> <code>html</code>: use the legacy Markdown-to-HTML renderer.",
].join("\n");
}
export function buildVoiceReplyModeSettingsText(
mode: TelegramVoiceReplyMode,
configured = true,
): string {
return [
`${VOICE_REPLY_MODE_SETTINGS_TITLE} <code>${getVoiceReplyModeLabel(
getVoiceReplyModeSetting(mode, configured),
)}</code>`,
"",
"Controls when pi-telegram converts assistant text replies into Telegram voice messages.",
"",
"<code>-</code> <code>hidden</code> (default): same behavior as 'manual', but no voice policy is added to prompt context.",
"<code>-</code> <code>manual</code>: agent decides; explicit 'telegram_voice' markup still works and reply mode is visible in prompt context.",
"<code>-</code> <code>mirror</code>: voice input prefers a voice reply; text input gracefully follows 'manual' behavior.",
"<code>-</code> <code>always</code>: every reply is converted to voice when delivery succeeds.",
].join("\n");
}
export function buildTimeInjectionModeSettingsText(
mode: TelegramTimeMode,
): string {
return [
`${TIME_INJECTION_MODE_SETTINGS_TITLE} <code>${mode}</code>`,
"",
"Controls whether Telegram-originated prompts include a compact wall-clock [time] line.",
"",
"<code>-</code> <code>hidden</code> (default): no time line is added to prompt context.",
"<code>-</code> <code>always</code>: add time to every Telegram turn.",
"<code>-</code> <code>interval</code>: add time at most once per chat interval (default: 1 hour).",
].join("\n");
}
export function buildTelegramSettingsMenuReplyMarkup(
proactivePushEnabled: boolean,
draftPreviewsEnabled: boolean,
assistantRenderingModeOrVoiceReplyMode: TelegramAssistantRenderingMode | TelegramVoiceReplyMode,
voiceReplyModeOrTimeInjectionMode: TelegramVoiceReplyMode | TelegramTimeMode,
timeInjectionModeOrSectionRegistry?: TelegramTimeMode | TelegramSectionRegistry,
sectionRegistryOrVoiceReplyModeConfigured?: TelegramSectionRegistry | boolean,
voiceReplyModeConfigured = true,
): TelegramSettingsMenuReplyMarkup {
const hasRenderingMode =
assistantRenderingModeOrVoiceReplyMode === "rich" ||
assistantRenderingModeOrVoiceReplyMode === "html";
const assistantRenderingMode: TelegramAssistantRenderingMode = hasRenderingMode
? assistantRenderingModeOrVoiceReplyMode
: "rich";
const voiceReplyMode = hasRenderingMode
? (voiceReplyModeOrTimeInjectionMode as TelegramVoiceReplyMode)
: (assistantRenderingModeOrVoiceReplyMode as TelegramVoiceReplyMode);
const timeInjectionMode = hasRenderingMode
? (timeInjectionModeOrSectionRegistry as TelegramTimeMode)
: (voiceReplyModeOrTimeInjectionMode as TelegramTimeMode);
const sectionRegistry = hasRenderingMode
? (sectionRegistryOrVoiceReplyModeConfigured as
| TelegramSectionRegistry
| undefined)
: (timeInjectionModeOrSectionRegistry as TelegramSectionRegistry | undefined);
const effectiveVoiceReplyModeConfigured = hasRenderingMode
? voiceReplyModeConfigured
: typeof sectionRegistryOrVoiceReplyModeConfigured === "boolean"
? sectionRegistryOrVoiceReplyModeConfigured
: true;
const rows: Array<Array<{ text: string; callback_data: string }>> = [
[{ text: "⬆️ Main menu", callback_data: "menu:back" }],
];
// Extension settings rows before built-in controls
if (sectionRegistry) {
const settingsRows = getTelegramExtensionSettingsRows(sectionRegistry);
for (const row of settingsRows) {
rows.push([{ text: row.label, callback_data: row.callback_data }]);
}
}
rows.push(
[
{
text: `👄 Voice reply: ${getTelegramSettingsStateValueLabel(
getVoiceReplyModeLabel(
getVoiceReplyModeSetting(voiceReplyMode, effectiveVoiceReplyModeConfigured),
),
)}`,
callback_data: "settings:open:voice-reply",
},
],
[
{
text: `🕒 Time injection: ${getTelegramSettingsStateValueLabel(timeInjectionMode)}`,
callback_data: "settings:open:time-injection",
},
],
[
{
text: `📝 Draft previews: ${draftPreviewsEnabled ? "on" : "off"}`,
callback_data: "settings:open:draft-previews",
},
],
[
{
text: `🧾 Rendering: ${assistantRenderingMode}`,
callback_data: "settings:open:assistant-rendering",
},
],
[
{
text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
callback_data: "settings:open:proactive",
},
],
);
return { inline_keyboard: rows };
}
export async function openTelegramSettingsMenu<
TModel extends MenuModel = MenuModel,
>(
deps: TelegramSettingsMenuOpenDeps<TModel>,
sectionRegistry?: TelegramSectionRegistry,
): Promise<void> {
const state = await deps.getModelMenuState();
const messageId = await deps.sendSettingsMenu(
state,
buildTelegramSettingsMenuText(),
buildTelegramSettingsMenuReplyMarkup(
deps.isProactivePushEnabled(),
deps.areDraftPreviewsEnabled(),
deps.getAssistantRenderingMode(),
deps.getVoiceReplyMode(),
deps.getTimeInjectionMode(),
sectionRegistry,
deps.isVoiceReplyModeConfigured(),
),
);
if (messageId === undefined) return;
state.messageId = messageId;
state.mode = "settings";
deps.storeModelMenuState(state);
}
export function buildProactivePushSettingsReplyMarkup(
proactivePushEnabled: boolean,
): TelegramSettingsMenuReplyMarkup {
return {
inline_keyboard: [
[{ text: "⬆️ Back", callback_data: "settings:list" }],
[
{
text: proactivePushEnabled ? "🟢 On" : "⚫️ On",
callback_data: "settings:set:proactive:on",
},
{
text: proactivePushEnabled ? "⚫️ Off" : "🟡 Off",
callback_data: "settings:set:proactive:off",
},
],
],
};
}
export function buildDraftPreviewsSettingsReplyMarkup(
enabled: boolean,
): TelegramSettingsMenuReplyMarkup {
return {
inline_keyboard: [
[{ text: "⬆️ Back", callback_data: "settings:list" }],
[
{
text: enabled ? "🟢 On" : "⚫️ On",
callback_data: "settings:set:draft-previews:on",
},
{
text: enabled ? "⚫️ Off" : "🟡 Off",
callback_data: "settings:set:draft-previews:off"
},
],
],
};
}
export function buildAssistantRenderingSettingsReplyMarkup(
mode: TelegramAssistantRenderingMode,
): TelegramSettingsMenuReplyMarkup {
const modes: TelegramAssistantRenderingMode[] = ["rich", "html"];
return {
inline_keyboard: [
[{ text: "⬆️ Back", callback_data: "settings:list" }],
...modes.map((value) => [
{
text: `${value === mode ? "🟢 " : ""}${value}`,
callback_data: `settings:set:assistant-rendering:${value}`,
},
]),
],
};
}
export function buildTimeInjectionModeSettingsReplyMarkup(
mode: TelegramTimeMode,
): TelegramSettingsMenuReplyMarkup {
const modes: TelegramTimeMode[] = ["hidden", "always", "interval"];
return {
inline_keyboard: [
[{ text: "⬆️ Back", callback_data: "settings:list" }],
...modes.map((value) => [
{
text: `${value === mode ? "🟢 " : ""}${value}`,
callback_data: `settings:set:time-injection:${value}`,
},
]),
],
};
}
export function buildVoiceReplyModeSettingsReplyMarkup(
mode: TelegramVoiceReplyMode,
configured = true,
): TelegramSettingsMenuReplyMarkup {
const activeMode = getVoiceReplyModeSetting(mode, configured);
const modes: TelegramVoiceReplyModeSetting[] = [
"hidden",
"manual",
"mirror",
"always",
];
return {
inline_keyboard: [
[{ text: "⬆️ Back", callback_data: "settings:list" }],
...modes.map((value) => [
{
text: `${value === activeMode ? "🟢 " : ""}${getVoiceReplyModeLabel(value)}`,
callback_data: `settings:set:voice-reply:${value}`,
},
]),
],
};
}
export async function updateTelegramSettingsMenuMessage(
deps: TelegramSettingsMenuMessageUpdateDeps,
sectionRegistry?: TelegramSectionRegistry,
): Promise<void> {
await deps.updateSettingsMessage(
buildTelegramSettingsMenuText(),
buildTelegramSettingsMenuReplyMarkup(
deps.isProactivePushEnabled(),
deps.areDraftPreviewsEnabled(),
deps.getAssistantRenderingMode(),
deps.getVoiceReplyMode(),
deps.getTimeInjectionMode(),
sectionRegistry,
deps.isVoiceReplyModeConfigured(),
),
);
}
export async function updateProactivePushSettingsMessage(
deps: TelegramSettingsMenuCallbackDeps,
): Promise<void> {
const proactivePushEnabled = deps.isProactivePushEnabled();
await deps.updateSettingsMessage(
buildProactivePushSettingsText(proactivePushEnabled),
buildProactivePushSettingsReplyMarkup(proactivePushEnabled),
);
}
export async function updateDraftPreviewsSettingsMessage(
deps: TelegramSettingsMenuCallbackDeps,
): Promise<void> {
const enabled = deps.areDraftPreviewsEnabled();
await deps.updateSettingsMessage(
buildDraftPreviewsSettingsText(enabled),
buildDraftPreviewsSettingsReplyMarkup(enabled),
);
}
export async function updateAssistantRenderingSettingsMessage(
deps: TelegramSettingsMenuCallbackDeps,
): Promise<void> {
const mode = deps.getAssistantRenderingMode();
await deps.updateSettingsMessage(
buildAssistantRenderingSettingsText(mode),
buildAssistantRenderingSettingsReplyMarkup(mode),
);
}
export async function updateTimeInjectionModeSettingsMessage(
deps: TelegramSettingsMenuCallbackDeps,
): Promise<void> {
const mode = deps.getTimeInjectionMode();
await deps.updateSettingsMessage(
buildTimeInjectionModeSettingsText(mode),
buildTimeInjectionModeSettingsReplyMarkup(mode),
);
}
export async function updateVoiceReplyModeSettingsMessage(
deps: TelegramSettingsMenuCallbackDeps,
): Promise<void> {
const mode = deps.getVoiceReplyMode();
const configured = deps.isVoiceReplyModeConfigured();
await deps.updateSettingsMessage(
buildVoiceReplyModeSettingsText(mode, configured),
buildVoiceReplyModeSettingsReplyMarkup(mode, configured),
);
}
type TelegramSettingsPageUpdater = (
deps: TelegramSettingsMenuCallbackDeps,
) => Promise<void>;
const TELEGRAM_SETTINGS_PAGE_UPDATERS = new Map<
string,
TelegramSettingsPageUpdater
>([
[
"settings:list",
(deps) => updateTelegramSettingsMenuMessage(deps, deps.sectionRegistry),
],
["settings:open:proactive", updateProactivePushSettingsMessage],
["settings:open:draft-previews", updateDraftPreviewsSettingsMessage],
["settings:open:rich-drafts", updateDraftPreviewsSettingsMessage],
["settings:open:assistant-rendering", updateAssistantRenderingSettingsMessage],
["settings:open:voice-reply", updateVoiceReplyModeSettingsMessage],
["settings:open:time-injection", updateTimeInjectionModeSettingsMessage],
["settings:open:time", updateTimeInjectionModeSettingsMessage],
]);
/** Applies one setting value; resolves the toast text, or undefined when the value is invalid */
type TelegramSettingsValueApplier = (
value: string,
deps: TelegramSettingsMenuCallbackDeps,
) => Promise<string | undefined>;
async function applyVoiceReplyModeSetting(
mode: string,
deps: TelegramSettingsMenuCallbackDeps,
): Promise<string | undefined> {
if (
mode !== "hidden" &&
mode !== "manual" &&
mode !== "mirror" &&
mode !== "always"
) {
return undefined;
}
await deps.setVoiceReplyMode(mode === "hidden" ? undefined : mode);
await updateVoiceReplyModeSettingsMessage(deps);
return `Voice reply mode: ${mode}`;
}
async function applyTimeInjectionModeSetting(
mode: string,
deps: TelegramSettingsMenuCallbackDeps,
): Promise<string | undefined> {
const normalizedMode = mode === "off" ? "hidden" : mode;
if (
normalizedMode !== "hidden" &&
normalizedMode !== "always" &&
normalizedMode !== "interval"
) {
return undefined;
}
await deps.setTimeInjectionMode(normalizedMode);
await updateTimeInjectionModeSettingsMessage(deps);
return `Time injection: ${normalizedMode}`;
}
async function applyDraftPreviewsSetting(
value: string,
deps: TelegramSettingsMenuCallbackDeps,
): Promise<string | undefined> {
if (value !== "on" && value !== "off") return undefined;
const enabled = value === "on";
await deps.setDraftPreviewsEnabled(enabled);
await updateDraftPreviewsSettingsMessage(deps);
return `Draft previews ${enabled ? "enabled" : "disabled"}`;
}
async function applyAssistantRenderingSetting(
mode: string,
deps: TelegramSettingsMenuCallbackDeps,
): Promise<string | undefined> {
if (mode !== "rich" && mode !== "html") return undefined;
await deps.setAssistantRenderingMode(mode);
await updateAssistantRenderingSettingsMessage(deps);
return `Rendering: ${mode}`;
}
async function applyProactivePushSetting(
value: string,
deps: TelegramSettingsMenuCallbackDeps,
): Promise<string | undefined> {
if (value !== "on" && value !== "off") return undefined;
const enabled = value === "on";
await deps.setProactivePushEnabled(enabled);
await updateProactivePushSettingsMessage(deps);
return `Proactive push ${enabled ? "enabled" : "disabled"}`;
}
const TELEGRAM_SETTINGS_VALUE_APPLIERS: Array<
[prefix: string, apply: TelegramSettingsValueApplier]
> = [
["settings:set:voice-reply:", applyVoiceReplyModeSetting],
["settings:set:time-injection:", applyTimeInjectionModeSetting],
["settings:set:time:", applyTimeInjectionModeSetting],
["settings:set:draft-previews:", applyDraftPreviewsSetting],
["settings:set:rich-drafts:", applyDraftPreviewsSetting],
["settings:set:assistant-rendering:", applyAssistantRenderingSetting],
["settings:set:proactive:", applyProactivePushSetting],
];
export async function handleTelegramSettingsMenuCallbackAction(
callbackQueryId: string,
data: string | undefined,
deps: TelegramSettingsMenuCallbackDeps,
): Promise<boolean> {
if (!data?.startsWith("settings:")) return false;
const updatePage = TELEGRAM_SETTINGS_PAGE_UPDATERS.get(data);
if (updatePage) {
await updatePage(deps);
await deps.answerCallbackQuery(callbackQueryId);
return true;
}
const applier = TELEGRAM_SETTINGS_VALUE_APPLIERS.find(([prefix]) =>
data.startsWith(prefix),
);
const toast = applier
? await applier[1](data.slice(applier[0].length), deps)
: undefined;
await deps.answerCallbackQuery(callbackQueryId, toast);
return true;
}
export function createTelegramSettingsMenuRuntime<
TContext,
TModel extends MenuModel = MenuModel,
>(
deps: TelegramSettingsMenuRuntimeDeps<TContext, TModel>,
sectionRegistry?: TelegramSectionRegistry,
): TelegramSettingsMenuRuntime<TContext> {
return {
openSettingsMenu: (chatId, _replyToMessageId, ctx) =>
openTelegramSettingsMenu(
{
getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
isProactivePushEnabled: deps.isProactivePushEnabled,
areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
getAssistantRenderingMode: deps.getAssistantRenderingMode,
getVoiceReplyMode: deps.getVoiceReplyMode,
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
getTimeInjectionMode: deps.getTimeInjectionMode,
sendSettingsMenu: (state, text, replyMarkup) =>
deps.sendInteractiveMessage(
state.chatId,
text,
"html",
replyMarkup,
),
storeModelMenuState: deps.storeModelMenuState,
},
sectionRegistry,
),
updateSettingsMenuMessage: (state) =>
updateTelegramSettingsMenuMessage(
{
isProactivePushEnabled: deps.isProactivePushEnabled,
areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
getAssistantRenderingMode: deps.getAssistantRenderingMode,
getVoiceReplyMode: deps.getVoiceReplyMode,
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
getTimeInjectionMode: deps.getTimeInjectionMode,
updateSettingsMessage: (text, replyMarkup) =>
deps.editInteractiveMessage(
state.chatId,
state.messageId,
text,
"html",
replyMarkup,
),
},
sectionRegistry,
),
handleCallbackQuery: async (query) => {
if (!query.data?.startsWith("settings:")) return false;
const state = deps.getStoredModelMenuState(
query.message?.message_id,
query.message?.chat?.id,
);
if (!state) {
const voiceMode = query.data.slice("settings:set:voice-reply:".length);
if (
query.data.startsWith("settings:set:voice-reply:") &&
(voiceMode === "hidden" ||
voiceMode === "manual" ||
voiceMode === "mirror" ||
voiceMode === "always")
) {
await deps.setVoiceReplyMode(
voiceMode === "hidden" ? undefined : voiceMode,
);
await deps.answerCallbackQuery(
query.id,
`Voice reply mode: ${voiceMode}`,
);
return true;
}
if (
query.data === "settings:set:draft-previews:on" ||
query.data === "settings:set:draft-previews:off" ||
query.data === "settings:set:rich-drafts:on" ||
query.data === "settings:set:rich-drafts:off"
) {
const enabled = query.data.endsWith(":on");
await deps.setDraftPreviewsEnabled(enabled);
await deps.answerCallbackQuery(
query.id,
`Draft previews ${enabled ? "enabled" : "disabled"}`,
);
return true;
}
if (query.data.startsWith("settings:set:assistant-rendering:")) {
const mode = query.data.slice("settings:set:assistant-rendering:".length);
if (mode === "rich" || mode === "html") {
await deps.setAssistantRenderingMode(mode);
await deps.answerCallbackQuery(query.id, `Rendering: ${mode}`);
return true;
}
}
const hasTimeInjectionPrefix = query.data.startsWith(
"settings:set:time-injection:",
);
const timeMode = hasTimeInjectionPrefix
? query.data.slice("settings:set:time-injection:".length)
: query.data.slice("settings:set:time:".length);
if (
(hasTimeInjectionPrefix ||
query.data.startsWith("settings:set:time:")) &&
(timeMode === "off" ||
timeMode === "hidden" ||
timeMode === "always" ||
timeMode === "interval")
) {
const normalizedMode = timeMode === "off" ? "hidden" : timeMode;
await deps.setTimeInjectionMode(normalizedMode);
await deps.answerCallbackQuery(
query.id,
`Time injection: ${normalizedMode}`,
);
return true;
}
await deps.answerCallbackQuery(
query.id,
"Interactive message expired.",
);
return true;
}
return handleTelegramSettingsMenuCallbackAction(query.id, query.data, {
isProactivePushEnabled: deps.isProactivePushEnabled,
areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
getAssistantRenderingMode: deps.getAssistantRenderingMode,
getVoiceReplyMode: deps.getVoiceReplyMode,
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
getTimeInjectionMode: deps.getTimeInjectionMode,
setProactivePushEnabled: deps.setProactivePushEnabled,
setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
setAssistantRenderingMode: deps.setAssistantRenderingMode,
setVoiceReplyMode: deps.setVoiceReplyMode,
setTimeInjectionMode: deps.setTimeInjectionMode,
updateSettingsMessage: (text, replyMarkup) =>
deps.editInteractiveMessage(
state.chatId,
state.messageId,
text,
"html",
replyMarkup,
),
answerCallbackQuery: deps.answerCallbackQuery,
sectionRegistry,
});
},
};
}