UNPKG

@grammyjs/conversations

Version:

Conversational interfaces for grammY

603 lines (602 loc) 23.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConversationForm = void 0; /** * A container for form building utilities. * * Each method on this class represents a differnt type of form field which can * validate context objects and extract data from it. */ class ConversationForm { /** Constructs a new form based on wait and skip callbacks */ constructor(conversation) { this.conversation = conversation; } async build(builder) { const { validate, action, otherwise, next, ...waitOptions } = builder; const ctx = await this.conversation.wait({ collationKey: "form", ...waitOptions, }); const result = await validate(ctx); if (result.ok) { if (action !== undefined) await action(ctx, result.value); return result.value; } else { if (otherwise !== undefined) { if ("error" in result) { const callback = otherwise; const reason = result.error; await callback(ctx, reason); } else { const callback = otherwise; await callback(ctx); } } return await this.conversation.skip({ next }); } } /** * Form field that checks if the incoming update contains a message or * channel post with text, and returns this text as string. Does not check * for captions. * * Accepts an optional options object that lets you perform actions when * text is received, when a non-text update is received, and more. * * @param options Optional options */ async text(options) { return await this.build({ collationKey: "form-text", ...options, validate: (ctx) => { var _a, _b; const text = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.text; if (text === undefined) return { ok: false }; return { ok: true, value: text }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with text that can be parsed to a number, and returns this * number. Does not check captions. * * The conversion to number uses `parseFloat`. * * Accepts an optional options object that lets you perform actions when a * number is received, when a non-number update is received, and more. * * @param options Optional options */ async number(options) { return await this.build({ collationKey: "form-number", ...options, validate: (ctx) => { var _a, _b; const text = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.text; if (text === undefined) return { ok: false }; const num = parseFloat(text); if (isNaN(num)) return { ok: false }; return { ok: true, value: num }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with text that can be parsed to an integer, and returns this * integer as a `number`. Does not check for captions. * * The conversion to number uses `parseInt`. * * Accepts an optional options object that lets you specify the radix to use * as well as perform actions when a number is received, when a non-number * update is received, and more. * * @param options Optional options */ async int(options) { const { radix, ...opts } = options !== null && options !== void 0 ? options : {}; return await this.build({ collationKey: "form-int", ...opts, validate: (ctx) => { var _a, _b; const text = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.text; if (text === undefined) return { ok: false }; const num = parseInt(text, radix); if (isNaN(num)) return { ok: false }; return { ok: true, value: num }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with one of several predefined strings, and returns the * actual text as string. Does not check captions. * * This is especially useful when working with custom keyboards. * * ```ts * const keyboard = new Keyboard() * .text("A").text("B") * .text("C").text("D") * .oneTime() * await ctx.reply("A, B, C, or D?", { reply_markup: keyboard }) * const answer = await conversation.form.select(["A", "B", "C", "D"], { * otherwise: ctx => ctx.reply("Please use one of the buttons!") * }) * switch (answer) { * case "A": * case "B": * case "C": * case "D": * // ... * } * ``` * * Accepts an optional options object that lets you perform actions when * text is received, when a non-text update is received, and more. * * @param entries A string array of accepted values * @param options Optional options */ async select(entries, options) { const e = entries; return await this.build({ collationKey: "form-select", ...options, validate: (ctx) => { var _a, _b; const text = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.text; if (text === undefined) return { ok: false }; if (!e.includes(text)) return { ok: false }; return { ok: true, value: text }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a given type of message entity, and returns this * entity. The form field relies on `ctx.entities()` for data extraction, so * both texts and captions are checked. * * Accepts an optional options object that lets you perform actions when * text is received, when a non-text update is received, and more. * * @param type One or more types of message entities to accept * @param options Optional options */ async entity(type, options) { return await this.build({ collationKey: "form-entity", ...options, validate: (ctx) => { const entities = ctx.entities(type); if (entities.length === 0) return { ok: false }; return { ok: true, value: entities[0] }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with an animation, and returns the received animation * object. * * Accepts an optional options object that lets you perform actions when an * animation is received, when a non-animation update is received, and more. * * @param options Optional options */ async animation(options) { return await this.build({ collationKey: "form-animation", ...options, validate: (ctx) => { var _a, _b; const animation = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.animation; if (animation === undefined) return { ok: false }; return { ok: true, value: animation }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with an audio message, and returns the received audio * object. * * Accepts an optional options object that lets you perform actions when an * audio message is received, when a non-audio update is received, and more. * * @param options Optional options */ async audio(options) { return await this.build({ collationKey: "form-audio", ...options, validate: (ctx) => { var _a, _b; const audio = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.audio; if (audio === undefined) return { ok: false }; return { ok: true, value: audio }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a document message, and returns the received document * object. * * Accepts an optional options object that lets you perform actions when a * document message is received, when a non-document update is received, and * more. * * @param options Optional options */ async document(options) { return await this.build({ collationKey: "form-document", ...options, validate: (ctx) => { var _a, _b; const document = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.document; if (document === undefined) return { ok: false }; return { ok: true, value: document }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with paid media, and returns the received paid media object. * * Accepts an optional options object that lets you perform actions when a * paid media message is received, when a non-paid media update is received, * and more. * * @param options Optional options */ async paidMedia(options) { return await this.build({ collationKey: "form-paid_media", ...options, validate: (ctx) => { var _a, _b; const paid_media = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.paid_media; if (paid_media === undefined) return { ok: false }; return { ok: true, value: paid_media }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a photo, and returns the received array of `PhotoSize` * objects. * * Accepts an optional options object that lets you perform actions when a * photo is received, when a non-photo update is received, and more. * * @param options Optional options */ async photo(options) { return await this.build({ collationKey: "form-photo", ...options, validate: (ctx) => { var _a, _b; const photo = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.photo; if (photo === undefined) return { ok: false }; return { ok: true, value: photo }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a sticker, and returns the received sticker object. * * Accepts an optional options object that lets you perform actions when a * sticker is received, when a non-sticker update is received, and more. * * @param options Optional options */ async sticker(options) { return await this.build({ collationKey: "form-sticker", ...options, validate: (ctx) => { var _a, _b; const sticker = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.sticker; if (sticker === undefined) return { ok: false }; return { ok: true, value: sticker }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a story, and returns the received story object. * * Accepts an optional options object that lets you perform actions when a * story is received, when a non-story update is received, and more. * * @param options Optional options */ async story(options) { return await this.build({ collationKey: "form-story", ...options, validate: (ctx) => { var _a, _b; const story = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.story; if (story === undefined) return { ok: false }; return { ok: true, value: story }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a video, and returns the received video object. * * Accepts an optional options object that lets you perform actions when a * video is received, when a non-video update is received, and more. * * @param options Optional options */ async video(options) { return await this.build({ collationKey: "form-video", ...options, validate: (ctx) => { var _a, _b; const video = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.video; if (video === undefined) return { ok: false }; return { ok: true, value: video }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a video note, and returns the received video note * object. * * Accepts an optional options object that lets you perform actions when a * video note is received, when a non-video note update is received, and * more. * * @param options Optional options */ async video_note(options) { return await this.build({ collationKey: "form-video_note", ...options, validate: (ctx) => { var _a, _b; const video_note = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.video_note; if (video_note === undefined) return { ok: false }; return { ok: true, value: video_note }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a voice message, and returns the received voice object. * * Accepts an optional options object that lets you perform actions when a * voice message is received, when a non-voice message update is received, * and more. * * @param options Optional options */ async voice(options) { return await this.build({ collationKey: "form-voice", ...options, validate: (ctx) => { var _a, _b; const voice = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.voice; if (voice === undefined) return { ok: false }; return { ok: true, value: voice }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a contact, and returns the received contact object. * * Accepts an optional options object that lets you perform actions when a * contact is received, when a non-contact update is received, and more. * * @param options Optional options */ async contact(options) { return await this.build({ collationKey: "form-contact", ...options, validate: (ctx) => { var _a, _b; const contact = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.contact; if (contact === undefined) return { ok: false }; return { ok: true, value: contact }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with dice, and returns the received dice object. * * Accepts an optional options object that lets you perform actions when * dice are received, when a non-dice update is received, and more. * * @param options Optional options */ async dice(options) { return await this.build({ collationKey: "form-dice", ...options, validate: (ctx) => { var _a, _b; const dice = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.dice; if (dice === undefined) return { ok: false }; return { ok: true, value: dice }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a game, and returns the received game object. * * Accepts an optional options object that lets you perform actions when a * game is received, when a non-game update is received, and more. * * @param options Optional options */ async game(options) { return await this.build({ collationKey: "form-game", ...options, validate: (ctx) => { var _a, _b; const game = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.game; if (game === undefined) return { ok: false }; return { ok: true, value: game }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a poll, and returns the received poll object. * * Accepts an optional options object that lets you perform actions when a * poll is received, when a non-poll update is received, and more. * * @param options Optional options */ async poll(options) { return await this.build({ collationKey: "form-poll", ...options, validate: (ctx) => { var _a, _b; const poll = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.poll; if (poll === undefined) return { ok: false }; return { ok: true, value: poll }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a venue, and returns the received venue object. * * Accepts an optional options object that lets you perform actions when a * venue is received, when a non-venue update is received, and more. * * @param options Optional options */ async venue(options) { return await this.build({ collationKey: "form-venue", ...options, validate: (ctx) => { var _a, _b; const venue = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.venue; if (venue === undefined) return { ok: false }; return { ok: true, value: venue }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a location, and returns the received location object. * * Accepts an optional options object that lets you perform actions when a * location is received, when a non-location update is received, and more. * * @param options Optional options */ async location(options) { return await this.build({ collationKey: "form-location", ...options, validate: (ctx) => { var _a, _b; const location = (_b = ((_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost)) === null || _b === void 0 ? void 0 : _b.location; if (location === undefined) return { ok: false }; return { ok: true, value: location }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a photo or video, and returns the received media * object. * * Accepts an optional options object that lets you perform actions when a * media is received, when a non-media update is received, and more. * * @param options Optional options */ async media(options) { return await this.build({ collationKey: "form-location", ...options, validate: (ctx) => { var _a, _b; const msg = (_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost; const media = (_b = msg === null || msg === void 0 ? void 0 : msg.photo) !== null && _b !== void 0 ? _b : msg === null || msg === void 0 ? void 0 : msg.video; if (media === undefined) return { ok: false }; return { ok: true, value: media }; }, }); } /** * Form field that checks if the incoming update contains a message or * channel post with a file, calls `await ctx.getFile()`, and returns the * received file object. * * Accepts an optional options object that lets you perform actions when a * file is received, when a non-file update is received, and more. * * @param options Optional options */ async file(options) { return await this.build({ collationKey: "form-location", ...options, validate: async (ctx) => { if (!ctx.has(":file")) return { ok: false }; const file = await ctx.getFile(); return { ok: true, value: file }; }, }); } } exports.ConversationForm = ConversationForm;