@grammy-x/conversations 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedent.d.ts","sourceRoot":"","sources":["../../../../utils/src/dedent.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM,EAAE;IACjB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAA;CAIrB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare const timeout: (ms: number) => Promise<unknown>;
2
+ export declare const isProduction: () => boolean;
3
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../utils/src/helpers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,qBAAsD,CAAA;AAExF,eAAO,MAAM,YAAY,eAA8C,CAAA"}
@@ -0,0 +1,4 @@
1
+ export declare const code: (string: any) => string;
2
+ export declare const price: (price: number) => string;
3
+ export declare const spoiler: (text: string) => string;
4
+ //# sourceMappingURL=htmlformat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"htmlformat.d.ts","sourceRoot":"","sources":["../../../../utils/src/htmlformat.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,IAAI,GAAI,QAAQ,GAAG,WAAiC,CAAA;AAEjE,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,WAAiC,CAAA;AAEpE,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,WAAkD,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { code, price, spoiler } from "./htmlformat.js";
2
+ export { dedent } from "./dedent.js";
3
+ export { selectRandom, randomInteger, randomString, generateRandomHex } from "./random.js";
4
+ export { timeout, isProduction } from "./helpers.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../utils/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,5 @@
1
+ export declare const selectRandom: <T>(array: T[]) => T;
2
+ export declare const randomInteger: (minimum: number, maximum: number) => number;
3
+ export declare const randomString: (length: number) => string;
4
+ export declare const generateRandomHex: (length: number) => string;
5
+ //# sourceMappingURL=random.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random.d.ts","sourceRoot":"","sources":["../../../../utils/src/random.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,KAAG,CAK5C,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,EAAE,SAAS,MAAM,KAAG,MACA,CAAA;AAEjE,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,KAAG,MAU7C,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,MAI7B,CAAA"}
package/package.json CHANGED
@@ -1,19 +1,25 @@
1
1
  {
2
2
  "name": "@grammy-x/conversations",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
- "main": "src/index.ts",
6
- "types": "src/index.ts",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
7
  "private": false,
8
8
  "publishConfig": {
9
9
  "access": "restricted"
10
10
  },
11
11
  "dependencies": {
12
- "@grammy-x/core": "0.1.3"
12
+ "@grammy-x/core": "0.2.1"
13
13
  },
14
14
  "peerDependencies": {
15
15
  "grammy": "^1.24.0",
16
16
  "@grammyjs/conversations": "^1",
17
17
  "@grammyjs/hydrate": "^1.4.1"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "bun build ./src/index.ts --outdir ./dist --target node --packages external && tsc --emitDeclarationOnly --outDir ./dist"
18
24
  }
19
25
  }
@@ -1,26 +0,0 @@
1
- import type { ConversationConfig } from "@grammyjs/conversations"
2
- import { createConversation } from "@grammyjs/conversations"
3
- import type { Conversation } from "@grammyjs/conversations"
4
- import { hydrate } from "@grammyjs/hydrate"
5
- import type { Context, MiddlewareFn } from "grammy"
6
-
7
- export type ConversationFn<C extends Context = Context> = (
8
- conversation: Conversation<C>,
9
- ctx: C
10
- ) => unknown | Promise<unknown>
11
-
12
- export function createCustomConversation<C extends Context = Context>(
13
- builder: ConversationFn<C>,
14
- config?: string | ConversationConfig
15
- ): MiddlewareFn<any> {
16
- let cfg = typeof config === "string" ? { id: config } : config
17
- cfg = cfg ?? {}
18
- cfg.id = cfg.id ?? builder.name
19
- return createConversation(
20
- (async (conversation: any, ctx: any) => {
21
- await conversation.run(hydrate())
22
- return builder(conversation, ctx)
23
- }),
24
- cfg
25
- ) as MiddlewareFn<any>
26
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { QuestionHelper } from "./question-helper.js"
2
- export { createCustomConversation } from "./conversation.js"
3
- export type { ConversationFn } from "./conversation.js"
@@ -1,579 +0,0 @@
1
- import type { Conversation } from "@grammyjs/conversations"
2
- import { CallbackQueryContext, Context, InlineKeyboard, Keyboard } from "grammy"
3
- import type {
4
- ChatAdministratorRights,
5
- ChatShared,
6
- Contact,
7
- Location,
8
- Poll,
9
- UsersShared,
10
- } from "grammy/types"
11
- import { smartReply } from "@grammy-x/core"
12
-
13
- type MaybePromise<T> = PromiseLike<T> | T
14
- type ButtonsMarkup = InlineKeyboard | Keyboard
15
-
16
- interface QuestionParameters {
17
- markup?: ButtonsMarkup
18
- autoBold?: boolean
19
- entities?: any[]
20
- fastMenu?: boolean
21
- fastMenuCallbackData?: string
22
- fastMenuText?: string
23
- newMessage?: boolean
24
- }
25
-
26
- interface QuestionCallbackParameters extends QuestionParameters {
27
- columnCount?: number
28
- noChoiceAllowed?: boolean
29
- rowCount?: number
30
- continueButton?: string
31
- pagination?: {
32
- enabled: boolean
33
- extraControls?: boolean
34
- }
35
- }
36
-
37
- interface QuestionMultiParameters extends QuestionCallbackParameters {
38
- continueInlineEnd?: boolean
39
- }
40
-
41
- interface QuestionChatParameters extends QuestionParameters {
42
- chat: {
43
- requiredUserRights?: ChatAdministratorRights
44
- requiredBotRights?: ChatAdministratorRights
45
- botIsMember?: boolean
46
- isChannel: boolean
47
- requestTitle?: boolean
48
- requestUsername?: boolean
49
- requestPhoto?: boolean
50
- }
51
- }
52
-
53
- interface QuestionUserParameters extends QuestionParameters {
54
- user?: {
55
- isBot?: boolean
56
- isPremium?: boolean
57
- requestName?: boolean
58
- requestUsername?: boolean
59
- requestPhoto?: boolean
60
- }
61
- }
62
-
63
- interface QuestionUsersParameters extends QuestionParameters {
64
- users?: {
65
- maxQuantity?: number
66
- isBot?: boolean
67
- isPremium?: boolean
68
- requestName?: boolean
69
- requestUsername?: boolean
70
- requestPhoto?: boolean
71
- }
72
- }
73
-
74
- interface QuestionPollParameters extends QuestionParameters {
75
- poll?: {
76
- type?: "regular" | "quiz"
77
- }
78
- }
79
-
80
- type QuestionUniversalParameters = Partial<
81
- QuestionMultiParameters &
82
- QuestionChatParameters &
83
- QuestionUserParameters &
84
- QuestionUsersParameters &
85
- QuestionPollParameters
86
- >
87
-
88
- interface QuestionInternalParameters extends QuestionUniversalParameters {
89
- continueButton?: string
90
- sendContinueButton?: boolean
91
- }
92
-
93
- export interface TextParserResult<R> {
94
- result?: R
95
- callbackQuery?: string
96
- answerCtx?: any
97
- message?: any
98
- }
99
-
100
- interface QuestionHelperSession {
101
- questionHelper: {
102
- currentChoices: Set<string>
103
- currentPage: number
104
- }
105
- }
106
-
107
- function randomInteger(minimum: number, maximum: number): number {
108
- return Math.floor(Math.random() * (maximum - minimum + 1) + minimum)
109
- }
110
-
111
- export class QuestionHelper<
112
- C extends Context = Context,
113
- T extends Conversation<C> = Conversation<C>,
114
- > {
115
- private conversation: T
116
- private config: QuestionUniversalParameters
117
- private ctx: C
118
- public message_id?: number
119
-
120
- constructor(conversation: T, ctx: C, config?: QuestionUniversalParameters) {
121
- this.conversation = conversation
122
- this.config = config ?? {}
123
- this.ctx = ctx
124
- }
125
-
126
- private updateCtx = <U extends Context>(ctx: U) => {
127
- Object.keys(this.ctx).forEach((key) => delete (this.ctx as any)[key])
128
- Object.assign(this.ctx, ctx)
129
- }
130
-
131
- delete = async () => {
132
- if (this.message_id) {
133
- await this.ctx.api.deleteMessage(this.ctx.from!.id, this.message_id)
134
- }
135
- }
136
-
137
- private reply = async (text: string, options?: QuestionInternalParameters) => {
138
- let newText = text
139
- const mergedOptions = { ...this.config, ...options }
140
-
141
- if (mergedOptions.autoBold !== false) newText = `<b>${newText}</b>`
142
-
143
- if (options?.markup instanceof InlineKeyboard) mergedOptions.markup = options.markup
144
-
145
- if (!mergedOptions.markup) mergedOptions.markup = new InlineKeyboard()
146
-
147
- if (mergedOptions.markup instanceof InlineKeyboard && this.config.markup instanceof InlineKeyboard)
148
- mergedOptions.markup.append(this.config.markup)
149
-
150
- if (mergedOptions.fastMenu && mergedOptions.markup instanceof InlineKeyboard) {
151
- const markup = mergedOptions.markup
152
- markup.text(mergedOptions.fastMenuText ?? "↩️ В главное меню", mergedOptions.fastMenuCallbackData ?? "start")
153
- }
154
- if (
155
- options?.sendContinueButton &&
156
- mergedOptions.continueInlineEnd &&
157
- mergedOptions.markup instanceof InlineKeyboard
158
- ) {
159
- const markup = mergedOptions.markup
160
- markup.text(options?.continueButton ?? "➡️ Продолжить", "continue")
161
- }
162
-
163
- const message = await smartReply(this.ctx, text, {
164
- options: {
165
- entities: mergedOptions.entities,
166
- reply_markup: mergedOptions.markup as any,
167
- },
168
- messageToEdit: this.message_id,
169
- newMessage: mergedOptions?.newMessage,
170
- embolden: false,
171
- dedent: false,
172
- })
173
- this.message_id = message?.message_id ?? this.message_id
174
- return message
175
- }
176
-
177
- private getCallbackDataFromKeyboard = (keyboard: ButtonsMarkup | undefined): string[] => {
178
- if (keyboard && keyboard instanceof InlineKeyboard)
179
- return keyboard.inline_keyboard
180
- .flat()
181
- .map((b: any) => b.callback_data)
182
- .filter(Boolean)
183
- return []
184
- }
185
-
186
- private textParser = async <R>(
187
- text: string,
188
- parser: (data: string) => R,
189
- validator: (result: R) => boolean,
190
- options?: QuestionParameters
191
- ) => {
192
- const message = await this.reply(text, options)
193
- const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup)
194
-
195
- const answer = await this.conversation.waitUntil(
196
- (ctx) => Context.has.callbackQuery(additionalTriggers)(ctx) || ctx.has(":text")
197
- )
198
-
199
- this.updateCtx(answer)
200
-
201
- const callbackQuery = answer.callbackQuery?.data
202
- if (callbackQuery) return { callbackQuery, answerCtx: answer, message }
203
-
204
- const result = parser((answer as any).msg!.text) as NonNullable<R>
205
- await this.ctx.api.deleteMessage(this.ctx.from!.id, (answer as any).msg!.message_id).catch(() => {})
206
- if (!validator(result)) return await this.conversation.skip()
207
- return { result, answerCtx: answer, message }
208
- }
209
-
210
- text = (text: string, options?: QuestionParameters): Promise<TextParserResult<string>> =>
211
- this.textParser(
212
- text,
213
- (r) => r,
214
- () => true,
215
- options
216
- ) as Promise<TextParserResult<string>>
217
-
218
- int = (text: string, options?: QuestionParameters): Promise<TextParserResult<number>> =>
219
- this.textParser(text, parseInt, (r) => !isNaN(r), options) as Promise<TextParserResult<number>>
220
-
221
- float = (text: string, options?: QuestionParameters): Promise<TextParserResult<number>> =>
222
- this.textParser(text, parseFloat, (r) => !isNaN(r), options) as Promise<TextParserResult<number>>
223
-
224
- chat = async (text: string, options: QuestionChatParameters) => {
225
- const chat = options.chat
226
- const markup = new Keyboard()
227
- .requestChat("Выбрать чат 🔍", randomInteger(0, 999999), {
228
- chat_is_channel: chat.isChannel,
229
- bot_administrator_rights: chat.requiredBotRights,
230
- bot_is_member: chat.botIsMember,
231
- request_photo: chat.requestPhoto,
232
- request_title: chat.requestTitle,
233
- request_username: chat.requestUsername ?? true,
234
- user_administrator_rights: chat.requiredUserRights,
235
- })
236
- .oneTime(true)
237
- .resized(true)
238
- const message = await this.reply(text, { ...options, markup })
239
- const answer = await this.conversation.waitFor(":chat_shared")
240
-
241
- this.updateCtx(answer)
242
-
243
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
244
- return answer.msg.chat_shared as ChatShared
245
- }
246
-
247
- user = async (text: string, options?: QuestionUserParameters) => {
248
- const user = options?.user
249
- const markup = new Keyboard()
250
- .requestUsers("Выбрать пользователя 🔍", randomInteger(0, 999999), {
251
- max_quantity: 1,
252
- request_name: user?.requestName,
253
- request_photo: user?.requestPhoto,
254
- request_username: user?.requestUsername ?? true,
255
- user_is_bot: user?.isBot,
256
- user_is_premium: user?.isPremium,
257
- })
258
- .oneTime(true)
259
- .resized(true)
260
- const message = await this.reply(text, { ...options, markup })
261
- const answer = await this.conversation.waitFor(":users_shared")
262
-
263
- this.updateCtx(answer)
264
-
265
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
266
- return answer.msg.users_shared?.users[0] as UsersShared["users"][number]
267
- }
268
-
269
- users = async (text: string, options?: QuestionUsersParameters) => {
270
- const users = options?.users
271
- const markup = new Keyboard()
272
- .requestUsers("Выбрать пользователей 🔍", randomInteger(0, 999999), {
273
- max_quantity: users?.maxQuantity,
274
- request_name: users?.requestName,
275
- request_photo: users?.requestPhoto,
276
- request_username: users?.requestUsername ?? true,
277
- user_is_bot: users?.isBot,
278
- user_is_premium: users?.isPremium,
279
- })
280
- .oneTime(true)
281
- .resized(true)
282
- const message = await this.reply(text, { ...options, markup })
283
- const answer = await this.conversation.waitFor(":users_shared")
284
-
285
- this.updateCtx(answer)
286
-
287
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
288
- return answer.msg.users_shared as UsersShared
289
- }
290
-
291
- contact = async (text: string, options?: QuestionParameters) => {
292
- const markup = new Keyboard().requestContact("Поделиться контактом 📞").oneTime(true).resized(true)
293
- const message = await this.reply(text, { ...options, markup })
294
- const answer = await this.conversation.waitFor(":contact")
295
-
296
- this.updateCtx(answer)
297
-
298
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
299
- return answer.msg.contact as Contact
300
- }
301
-
302
- location = async (text: string, options?: QuestionParameters) => {
303
- const markup = new Keyboard().requestLocation("Поделиться геопозицией 📍").oneTime(true).resized(true)
304
- const message = await this.reply(text, { ...options, markup })
305
- const answer = await this.conversation.waitFor(":location")
306
-
307
- this.updateCtx(answer)
308
-
309
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
310
- return answer.msg.location as Location
311
- }
312
-
313
- poll = async (text: string, options?: QuestionPollParameters) => {
314
- const pollType = options?.poll?.type
315
- const markup = new Keyboard().requestPoll("Отправить опрос 📊", pollType).oneTime(true).resized(true)
316
- const message = await this.reply(text, { ...options, markup })
317
- const answer = await this.conversation.waitFor(":poll")
318
-
319
- this.updateCtx(answer)
320
-
321
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
322
- return answer.msg.poll as Poll
323
- }
324
-
325
- private renderPaginationButtons = (
326
- pagesCount: number,
327
- currentPage: number,
328
- markup: InlineKeyboard,
329
- columnCount: number,
330
- extraControls: boolean
331
- ) => {
332
- const oddColumnCount = columnCount % 2 === 0 ? columnCount + 1 : columnCount
333
- const emptyColumns = oddColumnCount >= 5 ? (extraControls ? 0 : 1) : 0
334
- for (let i = 0; i < emptyColumns; i++) {
335
- markup.text(" ", " ")
336
- }
337
- if (extraControls) {
338
- markup.text(currentPage > 2 ? "⋘" : " ", "first")
339
- }
340
- markup.text(currentPage > 1 ? "←" : " ", "prev")
341
- markup.text(currentPage + " / " + pagesCount, "page")
342
- markup.text(currentPage < pagesCount ? "→" : " ", "next")
343
- if (extraControls) {
344
- markup.text(currentPage < pagesCount - 1 ? "⋙" : " ", "last")
345
- }
346
- for (let i = 0; i < emptyColumns; i++) {
347
- markup.text(" ", " ")
348
- }
349
- return markup.row()
350
- }
351
-
352
- private resetChoice = (session: QuestionHelperSession) => {
353
- session.questionHelper = {
354
- currentChoices: new Set(),
355
- currentPage: 1,
356
- }
357
- }
358
-
359
- private async basicChoice<const R extends string>(
360
- text: string,
361
- choices: [string, R][],
362
- multi: false,
363
- options?: QuestionParameters,
364
- inProgress?: boolean
365
- ): Promise<{ result: R; message: any }>
366
- private async basicChoice<const R extends string>(
367
- text: string | ((choices: R[], currentPage: number, pagesCount: number) => MaybePromise<string>),
368
- choices: [string, R][],
369
- multi: true,
370
- options?: QuestionParameters,
371
- inProgress?: boolean
372
- ): Promise<{
373
- result: R[]
374
- callbackQuery?: string
375
- message: any
376
- }>
377
- private async basicChoice<const R extends string>(
378
- text: string | ((choices: R[], currentPage: number, pagesCount: number) => MaybePromise<string>),
379
- choices: [string, R][],
380
- multi: boolean = false,
381
- options?: QuestionMultiParameters,
382
- inProgress?: boolean
383
- ): Promise<
384
- | {
385
- result: R[]
386
- callbackQuery?: string
387
- message: any
388
- }
389
- | {
390
- result: R
391
- message: any
392
- }
393
- > {
394
- const markup = new InlineKeyboard()
395
- const session = (await this.conversation.external(
396
- () => (this.ctx as any).session
397
- )) as unknown as QuestionHelperSession
398
- if (!session.questionHelper) this.resetChoice(session)
399
- await this.conversation.external(() => !inProgress && this.resetChoice(session))
400
- let currentRow: [string, string][] = []
401
- const columnCount = options?.columnCount ?? this.config.columnCount ?? 1
402
- const rowCount = options?.rowCount ?? this.config.rowCount ?? 3
403
- const filledChoices = options?.pagination?.enabled
404
- ? choices.concat(
405
- Array.from({ length: columnCount * rowCount - (choices.length % (columnCount * rowCount)) }).map(
406
- () => [" ", "empty" as unknown as R]
407
- )
408
- )
409
- : choices
410
- const pagesCount = Math.ceil(choices.length / (columnCount * rowCount))
411
- const currentPage = session.questionHelper.currentPage ?? 1
412
-
413
- let currentRowId = 0
414
- for (const [choiceId, choice] of filledChoices.entries()) {
415
- if (options?.pagination?.enabled && choiceId < (currentPage - 1) * columnCount * rowCount) continue
416
- const checked = session.questionHelper.currentChoices.has(choice[1])
417
- currentRow.push([`${choice[0]}${checked ? " ✅" : ""}`, choice[1]])
418
-
419
- if (currentRow.length === columnCount) {
420
- currentRow.forEach(([label, value]) =>
421
- markup.text(label, value == "empty" ? "empty" : `answer:${value}`)
422
- )
423
- markup.row()
424
- currentRow = []
425
- currentRowId++
426
- if (
427
- options?.pagination?.enabled &&
428
- (currentRowId === rowCount || choiceId === filledChoices.length - 1)
429
- ) {
430
- this.renderPaginationButtons(
431
- pagesCount,
432
- currentPage,
433
- markup,
434
- columnCount,
435
- options?.pagination?.extraControls ?? false
436
- )
437
- break
438
- }
439
- }
440
- }
441
-
442
- if (currentRow.length > 0) {
443
- currentRow.forEach(([label, value]) => {
444
- markup.text(label, `answer:${value}`)
445
- })
446
- markup.row()
447
- }
448
-
449
- const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup)
450
- if (options?.markup instanceof InlineKeyboard) markup.append(options.markup).row()
451
-
452
- const sendContinueButton = session.questionHelper.currentChoices.size > 0
453
- const continueInlineEnd = options?.continueInlineEnd ?? (this.config as any).continueInlineEnd
454
- if (multi && !options?.noChoiceAllowed && !continueInlineEnd && sendContinueButton)
455
- markup.text(options?.continueButton ?? "➡️ Продолжить", "continue").row()
456
-
457
- const messageText =
458
- typeof text === "function"
459
- ? await text(
460
- Array.from(session.questionHelper.currentChoices) as R[],
461
- session.questionHelper.currentPage,
462
- pagesCount
463
- )
464
- : text
465
- const message = await this.reply(messageText, {
466
- ...options,
467
- markup,
468
- continueButton: options?.continueButton,
469
- continueInlineEnd,
470
- sendContinueButton: continueInlineEnd && sendContinueButton,
471
- })
472
- let additionalTriggerCalled: boolean
473
- let answer: CallbackQueryContext<C>
474
-
475
- const paginationsCallbacks = ["prev", "next", "page", "empty", "first", "last"]
476
-
477
- do {
478
- answer = await this.conversation.waitForCallbackQuery([
479
- ...choices.map((c) => `answer:${c[1]}`),
480
- ...(multi ? ["continue"] : []),
481
- ...(options?.pagination?.enabled ? paginationsCallbacks : []),
482
- ...additionalTriggers,
483
- ])
484
- this.updateCtx(answer)
485
- const callbackData = answer.callbackQuery.data
486
- additionalTriggerCalled = additionalTriggers.includes(callbackData)
487
-
488
- if (callbackData == "continue" || additionalTriggerCalled) {
489
- const choices = Array.from(session.questionHelper.currentChoices)
490
- ;((this.ctx as any).session as unknown as QuestionHelperSession).questionHelper.currentChoices = new Set()
491
- if (additionalTriggerCalled) return { result: choices as R[], callbackQuery: callbackData, message }
492
- return { result: choices as R[], message }
493
- }
494
- } while (additionalTriggerCalled)
495
-
496
- const questionData = session.questionHelper
497
- const callbackData = answer.callbackQuery.data
498
- const data = callbackData.split(":")[1]
499
- const skip = await this.conversation.external(async () => {
500
- if (callbackData == "prev") questionData.currentPage = Math.max(currentPage - 1, 1)
501
- if (callbackData == "next") questionData.currentPage = Math.min(currentPage + 1, pagesCount)
502
- if (callbackData == "first") questionData.currentPage = 1
503
- if (callbackData == "last") questionData.currentPage = pagesCount
504
- if (data) {
505
- if (questionData.currentChoices!.has(data)) questionData.currentChoices!.delete(data)
506
- else questionData.currentChoices!.add(data)
507
- }
508
- ;((this.ctx as any).session as unknown as QuestionHelperSession).questionHelper = session.questionHelper =
509
- questionData
510
- if (paginationsCallbacks.includes(callbackData) && questionData.currentPage == currentPage) {
511
- await this.ctx.answerCallbackQuery()
512
- return true
513
- }
514
- })
515
- if (skip) return this.conversation.skip({ drop: true })
516
- if (!multi && data) {
517
- ;((this.ctx as any).session as unknown as QuestionHelperSession).questionHelper.currentChoices = new Set()
518
- return {
519
- result: data as R,
520
- message,
521
- }
522
- }
523
-
524
- //@ts-expect-error bruh typescript
525
- return this.basicChoice(text, choices, multi, options, true)
526
- }
527
-
528
- choice = async <const R extends string>(
529
- text: string,
530
- choices: [string, R][],
531
- options?: QuestionCallbackParameters
532
- ): Promise<{
533
- result: R
534
- message: any
535
- }> => this.basicChoice(text, choices, false, options)
536
-
537
- multi = async <const R extends string>(
538
- text: string | ((choices: R[], currentPage: number, pagesCount: number) => MaybePromise<string>),
539
- choices: [string, R][],
540
- options?: QuestionCallbackParameters
541
- ): Promise<{
542
- result: R[]
543
- callbackData?: string
544
- message: any
545
- }> => this.basicChoice(text, choices, true, options)
546
-
547
- boolean = async (text: string, yesNoStrings?: string[], options?: QuestionCallbackParameters) => {
548
- const { result, message } = await this.choice(
549
- text,
550
- [
551
- [yesNoStrings?.[0] ?? "✅ Да", "true"],
552
- [yesNoStrings?.[1] ?? "❌ Нет", "false"],
553
- ],
554
- { columnCount: 2, ...options }
555
- )
556
- const boolResult = result === "true"
557
- return { result: boolResult, message }
558
- }
559
-
560
- photo = async (text: string, options?: QuestionParameters) => {
561
- const message = await this.reply(text, options)
562
- const answer = await this.conversation.waitFor(":photo")
563
- this.updateCtx(answer)
564
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
565
- const photo = answer.msg.photo?.[0]
566
- if (!photo) {
567
- throw new Error("No photo found in update")
568
- }
569
- return photo.file_id
570
- }
571
-
572
- file = async (text: string, options?: QuestionParameters) => {
573
- const message = await this.reply(text, options)
574
- const answer = await this.conversation.waitFor(":file")
575
- this.updateCtx(answer)
576
- await (answer?.msg as any)?.delete?.().catch?.(() => {})
577
- return answer.msg.document?.file_id as string
578
- }
579
- }