Анкета: телефон обязателен, кнопка «Отправить мой номер»

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zabudko-ruandClaude Opus 5.5 committed 2026-10-26 03:07:01 +04:00
1 parent 2da10169b8
commit 7fdabd07a4
6 files changed
+91 -23

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest';
import { HttpTelegram, REMOVE_KEYBOARD } from './client.ts';
/** Что ушло в Bot API в поле reply_markup. */
const sentMarkup = async (keyboard: Parameters<HttpTelegram['sendMessage']>[2]) => {
let body: { reply_markup?: unknown } = {};
const fetchFn = (async (_url: string, init: { body: string }) => {
body = JSON.parse(init.body);
return new Response(JSON.stringify({ ok: true, result: { message_id: 1 } }));
}) as unknown as typeof fetch;
await new HttpTelegram('t', fetchFn).sendMessage(1, 'x', keyboard);
return body.reply_markup;
};
describe('клавиатуры Bot API', () => {
it('кнопки под сообщением, «поделиться номером» и её удаление', async () => {
expect(await sentMarkup([[{ text: 'Да', callback: 'yes' }, { text: 'Сайт', url: 'https://x.am' }]])).toEqual({
inline_keyboard: [[{ text: 'Да', callback_data: 'yes' }, { text: 'Сайт', url: 'https://x.am' }]],
});
expect(await sentMarkup([[{ text: 'Отправить мой номер', requestContact: true }]])).toEqual({
keyboard: [[{ text: 'Отправить мой номер', request_contact: true }]], resize_keyboard: true, one_time_keyboard: true,
});
expect(await sentMarkup(REMOVE_KEYBOARD)).toEqual({ remove_keyboard: true });
});
});
+32 -12
View File
@@ -1,7 +1,14 @@
/** Кнопка под сообщением: callback — нажатие обрабатывает бот; url — ссылка; webApp — открыть Mini App. */
export type Button = { text: string } & ({ callback: string } | { url: string } | { webApp: string });
/**
* Кнопка под сообщением: callback — нажатие обрабатывает бот; url — ссылка; webApp — открыть Mini App.
* requestContact — кнопка внизу экрана «поделиться номером» (обычная клавиатура, не под сообщением);
* removeKeyboard — убрать такую клавиатуру.
*/
export type Button = { text: string } & ({ callback: string } | { url: string } | { webApp: string } | { requestContact: true } | { removeKeyboard: true });
export type Keyboard = Button[][];
/** Клавиатура, которая убирает кнопку «поделиться номером». */
export const REMOVE_KEYBOARD: Keyboard = [[{ text: '', removeKeyboard: true }]];
export interface SentMessage {
chatId: number;
messageId: number;
@@ -58,17 +65,30 @@ export interface TelegramApi {
getUpdates(offset: number, timeoutSec: number): Promise<TgUpdate[]>;
}
const markup = (keyboard?: Keyboard) => ({
inline_keyboard: (keyboard ?? []).map((row) =>
row.map((b) =>
'callback' in b
? { text: b.text, callback_data: b.callback }
: 'url' in b
? { text: b.text, url: b.url }
: { text: b.text, web_app: { url: b.webApp } },
const markup = (keyboard?: Keyboard) => {
const buttons = (keyboard ?? []).flat();
if (buttons.some((b) => 'removeKeyboard' in b)) return { remove_keyboard: true };
if (buttons.some((b) => 'requestContact' in b)) {
return {
keyboard: (keyboard ?? []).map((row) => row.map((b) => ({ text: b.text, ...('requestContact' in b ? { request_contact: true } : {}) }))),
resize_keyboard: true,
one_time_keyboard: true,
};
}
return {
inline_keyboard: (keyboard ?? []).map((row) =>
row.flatMap((b): Record<string, unknown>[] =>
'callback' in b
? [{ text: b.text, callback_data: b.callback }]
: 'url' in b
? [{ text: b.text, url: b.url }]
: 'webApp' in b
? [{ text: b.text, web_app: { url: b.webApp } }]
: [],
),
),
),
});
};
};
export class HttpTelegram implements TelegramApi {
constructor(