@grammy-x/conversations 0.1.3 → 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.
- package/dist/conversations/src/conversation.d.ts +6 -0
- package/dist/conversations/src/conversation.d.ts.map +1 -0
- package/dist/conversations/src/index.d.ts +4 -0
- package/dist/conversations/src/index.d.ts.map +1 -0
- package/dist/conversations/src/question-helper.d.ts +111 -0
- package/dist/conversations/src/question-helper.d.ts.map +1 -0
- package/dist/core/src/index.d.ts +8 -0
- package/dist/core/src/index.d.ts.map +1 -0
- package/dist/core/src/message.d.ts +12 -0
- package/dist/core/src/message.d.ts.map +1 -0
- package/dist/core/src/plugin.d.ts +20 -0
- package/dist/core/src/plugin.d.ts.map +1 -0
- package/dist/core/src/registry/menu.d.ts +14 -0
- package/dist/core/src/registry/menu.d.ts.map +1 -0
- package/dist/core/src/session.d.ts +35 -0
- package/dist/core/src/session.d.ts.map +1 -0
- package/dist/index.js +398 -0
- package/dist/utils/src/dedent.d.ts +5 -0
- package/dist/utils/src/dedent.d.ts.map +1 -0
- package/dist/utils/src/helpers.d.ts +3 -0
- package/dist/utils/src/helpers.d.ts.map +1 -0
- package/dist/utils/src/htmlformat.d.ts +4 -0
- package/dist/utils/src/htmlformat.d.ts.map +1 -0
- package/dist/utils/src/index.d.ts +5 -0
- package/dist/utils/src/index.d.ts.map +1 -0
- package/dist/utils/src/random.d.ts +5 -0
- package/dist/utils/src/random.d.ts.map +1 -0
- package/package.json +10 -4
- package/src/conversation.ts +0 -26
- package/src/index.ts +0 -3
- package/src/question-helper.ts +0 -456
- package/tsconfig.json +0 -8
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConversationConfig } from "@grammyjs/conversations";
|
|
2
|
+
import type { Conversation } from "@grammyjs/conversations";
|
|
3
|
+
import type { Context, MiddlewareFn } from "grammy";
|
|
4
|
+
export type ConversationFn<C extends Context = Context> = (conversation: Conversation<C>, ctx: C) => unknown | Promise<unknown>;
|
|
5
|
+
export declare function createCustomConversation<C extends Context = Context>(builder: ConversationFn<C>, config?: string | ConversationConfig): MiddlewareFn<any>;
|
|
6
|
+
//# sourceMappingURL=conversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../src/conversation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAGnD,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,CACtD,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,EAC7B,GAAG,EAAE,CAAC,KACL,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAE/B,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAChE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,GACrC,YAAY,CAAC,GAAG,CAAC,CA2DnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Conversation } from "@grammyjs/conversations";
|
|
2
|
+
import { Context, InlineKeyboard, Keyboard } from "grammy";
|
|
3
|
+
import type { ChatAdministratorRights, ChatShared, Contact, Location, Poll, UsersShared } from "grammy/types";
|
|
4
|
+
type MaybePromise<T> = PromiseLike<T> | T;
|
|
5
|
+
type ButtonsMarkup = InlineKeyboard | Keyboard;
|
|
6
|
+
interface QuestionParameters {
|
|
7
|
+
markup?: ButtonsMarkup;
|
|
8
|
+
autoBold?: boolean;
|
|
9
|
+
entities?: any[];
|
|
10
|
+
fastMenu?: boolean;
|
|
11
|
+
fastMenuCallbackData?: string;
|
|
12
|
+
fastMenuText?: string;
|
|
13
|
+
newMessage?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface QuestionCallbackParameters extends QuestionParameters {
|
|
16
|
+
columnCount?: number;
|
|
17
|
+
noChoiceAllowed?: boolean;
|
|
18
|
+
rowCount?: number;
|
|
19
|
+
continueButton?: string;
|
|
20
|
+
pagination?: {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
extraControls?: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
interface QuestionMultiParameters extends QuestionCallbackParameters {
|
|
26
|
+
continueInlineEnd?: boolean;
|
|
27
|
+
}
|
|
28
|
+
interface QuestionChatParameters extends QuestionParameters {
|
|
29
|
+
chat: {
|
|
30
|
+
requiredUserRights?: ChatAdministratorRights;
|
|
31
|
+
requiredBotRights?: ChatAdministratorRights;
|
|
32
|
+
botIsMember?: boolean;
|
|
33
|
+
isChannel: boolean;
|
|
34
|
+
requestTitle?: boolean;
|
|
35
|
+
requestUsername?: boolean;
|
|
36
|
+
requestPhoto?: boolean;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
interface QuestionUserParameters extends QuestionParameters {
|
|
40
|
+
user?: {
|
|
41
|
+
isBot?: boolean;
|
|
42
|
+
isPremium?: boolean;
|
|
43
|
+
requestName?: boolean;
|
|
44
|
+
requestUsername?: boolean;
|
|
45
|
+
requestPhoto?: boolean;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
interface QuestionUsersParameters extends QuestionParameters {
|
|
49
|
+
users?: {
|
|
50
|
+
maxQuantity?: number;
|
|
51
|
+
isBot?: boolean;
|
|
52
|
+
isPremium?: boolean;
|
|
53
|
+
requestName?: boolean;
|
|
54
|
+
requestUsername?: boolean;
|
|
55
|
+
requestPhoto?: boolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
interface QuestionPollParameters extends QuestionParameters {
|
|
59
|
+
poll?: {
|
|
60
|
+
type?: "regular" | "quiz";
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
type QuestionUniversalParameters = Partial<QuestionMultiParameters & QuestionChatParameters & QuestionUserParameters & QuestionUsersParameters & QuestionPollParameters>;
|
|
64
|
+
export interface TextParserResult<R> {
|
|
65
|
+
result?: R;
|
|
66
|
+
callbackQuery?: string;
|
|
67
|
+
answerCtx?: any;
|
|
68
|
+
message?: any;
|
|
69
|
+
}
|
|
70
|
+
export declare class QuestionHelper<C extends Context = Context, T extends Conversation<C> = Conversation<C>> {
|
|
71
|
+
private conversation;
|
|
72
|
+
private config;
|
|
73
|
+
private ctx;
|
|
74
|
+
message_id?: number;
|
|
75
|
+
constructor(conversation: T, ctx: C, config?: QuestionUniversalParameters);
|
|
76
|
+
private updateCtx;
|
|
77
|
+
delete: () => Promise<void>;
|
|
78
|
+
back: () => Promise<unknown>;
|
|
79
|
+
private reply;
|
|
80
|
+
private getCallbackDataFromKeyboard;
|
|
81
|
+
private textParser;
|
|
82
|
+
text: (text: string, options?: QuestionParameters) => Promise<TextParserResult<string>>;
|
|
83
|
+
int: (text: string, options?: QuestionParameters) => Promise<TextParserResult<number>>;
|
|
84
|
+
float: (text: string, options?: QuestionParameters) => Promise<TextParserResult<number>>;
|
|
85
|
+
chat: (text: string, options: QuestionChatParameters) => Promise<ChatShared>;
|
|
86
|
+
user: (text: string, options?: QuestionUserParameters) => Promise<import("grammy/types").SharedUser>;
|
|
87
|
+
users: (text: string, options?: QuestionUsersParameters) => Promise<UsersShared>;
|
|
88
|
+
contact: (text: string, options?: QuestionParameters) => Promise<Contact>;
|
|
89
|
+
location: (text: string, options?: QuestionParameters) => Promise<Location>;
|
|
90
|
+
poll: (text: string, options?: QuestionPollParameters) => Promise<Poll>;
|
|
91
|
+
private renderPaginationButtons;
|
|
92
|
+
private resetChoice;
|
|
93
|
+
private basicChoice;
|
|
94
|
+
choice: <const R_1 extends string>(text: string, choices: [string, R_1][], options?: QuestionCallbackParameters) => Promise<{
|
|
95
|
+
result: R_1;
|
|
96
|
+
message: any;
|
|
97
|
+
}>;
|
|
98
|
+
multi: <const R_1 extends string>(text: string | ((choices: R_1[], currentPage: number, pagesCount: number) => MaybePromise<string>), choices: [string, R_1][], options?: QuestionCallbackParameters) => Promise<{
|
|
99
|
+
result: R_1[];
|
|
100
|
+
callbackData?: string;
|
|
101
|
+
message: any;
|
|
102
|
+
}>;
|
|
103
|
+
boolean: (text: string, yesNoStrings?: string[], options?: QuestionCallbackParameters) => Promise<{
|
|
104
|
+
result: boolean;
|
|
105
|
+
message: any;
|
|
106
|
+
}>;
|
|
107
|
+
photo: (text: string, options?: QuestionParameters) => Promise<string>;
|
|
108
|
+
file: (text: string, options?: QuestionParameters) => Promise<string>;
|
|
109
|
+
}
|
|
110
|
+
export {};
|
|
111
|
+
//# sourceMappingURL=question-helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-helper.d.ts","sourceRoot":"","sources":["../../../src/question-helper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { GrammyXSessionData, GrammyXInternalSession, GrammyXFlavor, WithGrammyXSession } from "./session.js";
|
|
2
|
+
export { smartReply, setActiveMessage } from "./message.js";
|
|
3
|
+
export type { ReplyOptions } from "./message.js";
|
|
4
|
+
export { grammyx, getGrammyXOptions, grammyXMenus } from "./plugin.js";
|
|
5
|
+
export type { GrammyXDefaultColor, GrammyXPluginOptions, GrammyXRuntimeOptions } from "./plugin.js";
|
|
6
|
+
export { GlobalMenuRegistry } from "./registry/menu.js";
|
|
7
|
+
export type { MenuLike } from "./registry/menu.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../core/src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjH,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC3D,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACtE,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnG,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,YAAY,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Context } from "grammy";
|
|
2
|
+
import type { Message } from "grammy/types";
|
|
3
|
+
export interface ReplyOptions {
|
|
4
|
+
embolden?: boolean;
|
|
5
|
+
dedent?: boolean;
|
|
6
|
+
newMessage?: boolean;
|
|
7
|
+
messageToEdit?: number;
|
|
8
|
+
options?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
export declare function smartReply(ctx: Context, text: string, options?: ReplyOptions, replyMarkup?: any): Promise<Message>;
|
|
11
|
+
export declare function setActiveMessage(ctx: Context, message: Message): void;
|
|
12
|
+
//# sourceMappingURL=message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../../core/src/message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAI3C,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAChC;AAiBD,wBAAsB,UAAU,CAC5B,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,YAAY,EACtB,WAAW,CAAC,EAAE,GAAG,GAClB,OAAO,CAAC,OAAO,CAAC,CA0BlB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAQrE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MiddlewareFn } from "grammy";
|
|
2
|
+
export type GrammyXDefaultColor = "primary" | "success" | "danger";
|
|
3
|
+
export interface GrammyXRuntimeOptions {
|
|
4
|
+
defaultColor?: GrammyXDefaultColor;
|
|
5
|
+
interruptCommands?: Record<string, any>;
|
|
6
|
+
backText?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GrammyXPluginOptions {
|
|
9
|
+
sessionInitial?: () => any;
|
|
10
|
+
defaultColor?: GrammyXDefaultColor;
|
|
11
|
+
interruptCommands?: Record<string, any>;
|
|
12
|
+
backText?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function getGrammyXOptions(ctx: unknown): GrammyXRuntimeOptions | undefined;
|
|
15
|
+
export declare function grammyx(options?: GrammyXPluginOptions): MiddlewareFn<any>;
|
|
16
|
+
/**
|
|
17
|
+
* The Global Menu Handler.
|
|
18
|
+
*/
|
|
19
|
+
export declare function grammyXMenus(): MiddlewareFn<any>;
|
|
20
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../core/src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,QAAQ,CAAA;AAMnD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;AAElE,MAAM,WAAW,qBAAqB;IAClC,YAAY,CAAC,EAAE,mBAAmB,CAAA;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,oBAAoB;IACjC,cAAc,CAAC,EAAE,MAAM,GAAG,CAAA;IAC1B,YAAY,CAAC,EAAE,mBAAmB,CAAA;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,GAAG,SAAS,CAEjF;AAgBD,wBAAgB,OAAO,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,YAAY,CAAC,GAAG,CAAC,CAqFzE;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAAC,GAAG,CAAC,CAahD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Context } from "grammy";
|
|
2
|
+
export interface MenuLike<C extends Context = Context> {
|
|
3
|
+
menuName: string;
|
|
4
|
+
send: (ctx: C, data?: any) => Promise<unknown>;
|
|
5
|
+
}
|
|
6
|
+
declare class MenuRegistry {
|
|
7
|
+
private menus;
|
|
8
|
+
register(menu: MenuLike<any>): void;
|
|
9
|
+
get<C extends Context>(name: string): MenuLike<C> | undefined;
|
|
10
|
+
getAll(): MenuLike<any>[];
|
|
11
|
+
}
|
|
12
|
+
export declare const GlobalMenuRegistry: MenuRegistry;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../../../core/src/registry/menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;IACjD,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACjD;AAED,cAAM,YAAY;IACd,OAAO,CAAC,KAAK,CAAmC;IAEhD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC;IAQ5B,GAAG,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS;IAI7D,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE;CAG5B;AAED,eAAO,MAAM,kBAAkB,cAAqB,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface GrammyXInternalSession {
|
|
2
|
+
menu?: {
|
|
3
|
+
data?: any;
|
|
4
|
+
messageId?: number;
|
|
5
|
+
};
|
|
6
|
+
history: string[];
|
|
7
|
+
lazy: Record<string, any>;
|
|
8
|
+
conversations?: {
|
|
9
|
+
currentChoices: any;
|
|
10
|
+
currentPage: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface GrammyXSessionData {
|
|
14
|
+
_gx: GrammyXInternalSession;
|
|
15
|
+
conversation?: Record<string, any>;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
import type { Context, SessionFlavor } from "grammy";
|
|
19
|
+
export interface GrammyXMenuControlPanel {
|
|
20
|
+
nav: (to: string, data?: any) => Promise<unknown>;
|
|
21
|
+
back: (data?: any) => Promise<unknown>;
|
|
22
|
+
update: (data?: any) => Promise<unknown>;
|
|
23
|
+
close: () => void;
|
|
24
|
+
}
|
|
25
|
+
export type GrammyXFlavor<C extends Context = Context> = C & SessionFlavor<GrammyXSessionData> & {
|
|
26
|
+
menu: GrammyXMenuControlPanel;
|
|
27
|
+
};
|
|
28
|
+
type SessionValue<C extends Context> = C extends {
|
|
29
|
+
session: infer S;
|
|
30
|
+
} ? Awaited<S> : never;
|
|
31
|
+
export type WithGrammyXSession<C extends Context> = C & SessionFlavor<SessionValue<C> & GrammyXSessionData> & {
|
|
32
|
+
menu: GrammyXMenuControlPanel;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../core/src/session.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE;QACH,IAAI,CAAC,EAAE,GAAG,CAAA;QACV,SAAS,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACzB,aAAa,CAAC,EAAE;QACZ,cAAc,EAAE,GAAG,CAAA;QACnB,WAAW,EAAE,MAAM,CAAA;KACtB,CAAA;CACJ;AAED,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,sBAAsB,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACrB;AAED,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAEpD,MAAM,WAAW,uBAAuB;IACpC,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACjD,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,KAAK,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,CAAC,GAAG,aAAa,CAAC,kBAAkB,CAAC,GAAG;IAAE,IAAI,EAAE,uBAAuB,CAAA;CAAE,CAAA;AAElI,KAAK,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,SAAS;IAAE,OAAO,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;AAE1F,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,GAAG;IAAE,IAAI,EAAE,uBAAuB,CAAA;CAAE,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
// src/question-helper.ts
|
|
2
|
+
import { Context, InlineKeyboard, Keyboard } from "grammy";
|
|
3
|
+
import { smartReply, GlobalMenuRegistry } from "@grammy-x/core";
|
|
4
|
+
function randomInteger(minimum, maximum) {
|
|
5
|
+
return Math.floor(Math.random() * (maximum - minimum + 1) + minimum);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class QuestionHelper {
|
|
9
|
+
conversation;
|
|
10
|
+
config;
|
|
11
|
+
ctx;
|
|
12
|
+
message_id;
|
|
13
|
+
constructor(conversation, ctx, config) {
|
|
14
|
+
this.conversation = conversation;
|
|
15
|
+
this.config = config ?? {};
|
|
16
|
+
this.ctx = ctx;
|
|
17
|
+
}
|
|
18
|
+
updateCtx = (ctx) => {
|
|
19
|
+
Object.keys(this.ctx).forEach((key) => delete this.ctx[key]);
|
|
20
|
+
Object.assign(this.ctx, ctx);
|
|
21
|
+
};
|
|
22
|
+
delete = async () => {
|
|
23
|
+
if (this.message_id) {
|
|
24
|
+
await this.ctx.api.deleteMessage(this.ctx.from.id, this.message_id);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
back = async () => {
|
|
28
|
+
const gx = this.ctx.session?._gx;
|
|
29
|
+
if (gx?.history.length > 0) {
|
|
30
|
+
gx.history.pop();
|
|
31
|
+
}
|
|
32
|
+
const targetId = gx?.history.length > 0 ? gx.history.pop() : undefined;
|
|
33
|
+
if (targetId) {
|
|
34
|
+
const menu = GlobalMenuRegistry.get(targetId);
|
|
35
|
+
if (menu)
|
|
36
|
+
return menu.send(this.ctx);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
reply = async (text, options) => {
|
|
40
|
+
let newText = text;
|
|
41
|
+
const mergedOptions = { ...this.config, ...options };
|
|
42
|
+
if (mergedOptions.autoBold !== false)
|
|
43
|
+
newText = `<b>${newText}</b>`;
|
|
44
|
+
if (options?.markup instanceof InlineKeyboard)
|
|
45
|
+
mergedOptions.markup = options.markup;
|
|
46
|
+
if (!mergedOptions.markup)
|
|
47
|
+
mergedOptions.markup = new InlineKeyboard;
|
|
48
|
+
if (mergedOptions.markup instanceof InlineKeyboard && this.config.markup instanceof InlineKeyboard)
|
|
49
|
+
mergedOptions.markup.append(this.config.markup);
|
|
50
|
+
if (mergedOptions.fastMenu && mergedOptions.markup instanceof InlineKeyboard) {
|
|
51
|
+
const markup = mergedOptions.markup;
|
|
52
|
+
markup.text(mergedOptions.fastMenuText ?? this.config?.fastMenuText ?? "Main Menu", mergedOptions.fastMenuCallbackData ?? "start");
|
|
53
|
+
}
|
|
54
|
+
if (options?.sendContinueButton && mergedOptions.continueInlineEnd && mergedOptions.markup instanceof InlineKeyboard) {
|
|
55
|
+
const markup = mergedOptions.markup;
|
|
56
|
+
markup.text(options?.continueButton ?? this.config?.continueButton ?? "Continue", "continue");
|
|
57
|
+
}
|
|
58
|
+
const message = await smartReply(this.ctx, text, {
|
|
59
|
+
options: {
|
|
60
|
+
entities: mergedOptions.entities,
|
|
61
|
+
reply_markup: mergedOptions.markup
|
|
62
|
+
},
|
|
63
|
+
messageToEdit: this.message_id,
|
|
64
|
+
newMessage: mergedOptions?.newMessage,
|
|
65
|
+
embolden: false,
|
|
66
|
+
dedent: false
|
|
67
|
+
});
|
|
68
|
+
this.message_id = message?.message_id ?? this.message_id;
|
|
69
|
+
return message;
|
|
70
|
+
};
|
|
71
|
+
getCallbackDataFromKeyboard = (keyboard) => {
|
|
72
|
+
if (keyboard && keyboard instanceof InlineKeyboard)
|
|
73
|
+
return keyboard.inline_keyboard.flat().map((b) => b.callback_data).filter(Boolean);
|
|
74
|
+
return [];
|
|
75
|
+
};
|
|
76
|
+
textParser = async (text, parser, validator, options) => {
|
|
77
|
+
const message = await this.reply(text, options);
|
|
78
|
+
const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup);
|
|
79
|
+
const answer = await this.conversation.waitUntil((ctx) => Context.has.callbackQuery(additionalTriggers)(ctx) || ctx.has(":text"));
|
|
80
|
+
this.updateCtx(answer);
|
|
81
|
+
const callbackQuery = answer.callbackQuery?.data;
|
|
82
|
+
if (callbackQuery)
|
|
83
|
+
return { callbackQuery, answerCtx: answer, message };
|
|
84
|
+
if (answer.message?.text?.startsWith("/")) {
|
|
85
|
+
await this.ctx.api.deleteMessage(this.ctx.from.id, answer.message.message_id).catch(() => {});
|
|
86
|
+
const cmd = answer.message.text.split(" ")[0];
|
|
87
|
+
const err = new Error(`CommandInterrupt:${cmd}`);
|
|
88
|
+
err.name = "CommandInterruptError";
|
|
89
|
+
err.ctx = answer;
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
const result = parser(answer.msg.text);
|
|
93
|
+
await this.ctx.api.deleteMessage(this.ctx.from.id, answer.msg.message_id).catch(() => {});
|
|
94
|
+
if (!validator(result))
|
|
95
|
+
return await this.conversation.skip();
|
|
96
|
+
return { result, answerCtx: answer, message };
|
|
97
|
+
};
|
|
98
|
+
text = (text, options) => this.textParser(text, (r) => r, () => true, options);
|
|
99
|
+
int = (text, options) => this.textParser(text, parseInt, (r) => !isNaN(r), options);
|
|
100
|
+
float = (text, options) => this.textParser(text, parseFloat, (r) => !isNaN(r), options);
|
|
101
|
+
chat = async (text, options) => {
|
|
102
|
+
const chat = options.chat;
|
|
103
|
+
const markup = new Keyboard().requestChat("Выбрать чат \uD83D\uDD0D", randomInteger(0, 999999), {
|
|
104
|
+
chat_is_channel: chat.isChannel,
|
|
105
|
+
bot_administrator_rights: chat.requiredBotRights,
|
|
106
|
+
bot_is_member: chat.botIsMember,
|
|
107
|
+
request_photo: chat.requestPhoto,
|
|
108
|
+
request_title: chat.requestTitle,
|
|
109
|
+
request_username: chat.requestUsername ?? true,
|
|
110
|
+
user_administrator_rights: chat.requiredUserRights
|
|
111
|
+
}).oneTime(true).resized(true);
|
|
112
|
+
const message = await this.reply(text, { ...options, markup });
|
|
113
|
+
const answer = await this.conversation.waitFor(":chat_shared");
|
|
114
|
+
this.updateCtx(answer);
|
|
115
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
116
|
+
return answer.msg.chat_shared;
|
|
117
|
+
};
|
|
118
|
+
user = async (text, options) => {
|
|
119
|
+
const user = options?.user;
|
|
120
|
+
const markup = new Keyboard().requestUsers("Выбрать пользователя \uD83D\uDD0D", randomInteger(0, 999999), {
|
|
121
|
+
max_quantity: 1,
|
|
122
|
+
request_name: user?.requestName,
|
|
123
|
+
request_photo: user?.requestPhoto,
|
|
124
|
+
request_username: user?.requestUsername ?? true,
|
|
125
|
+
user_is_bot: user?.isBot,
|
|
126
|
+
user_is_premium: user?.isPremium
|
|
127
|
+
}).oneTime(true).resized(true);
|
|
128
|
+
const message = await this.reply(text, { ...options, markup });
|
|
129
|
+
const answer = await this.conversation.waitFor(":users_shared");
|
|
130
|
+
this.updateCtx(answer);
|
|
131
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
132
|
+
return answer.msg.users_shared?.users[0];
|
|
133
|
+
};
|
|
134
|
+
users = async (text, options) => {
|
|
135
|
+
const users = options?.users;
|
|
136
|
+
const markup = new Keyboard().requestUsers("Выбрать пользователей \uD83D\uDD0D", randomInteger(0, 999999), {
|
|
137
|
+
max_quantity: users?.maxQuantity,
|
|
138
|
+
request_name: users?.requestName,
|
|
139
|
+
request_photo: users?.requestPhoto,
|
|
140
|
+
request_username: users?.requestUsername ?? true,
|
|
141
|
+
user_is_bot: users?.isBot,
|
|
142
|
+
user_is_premium: users?.isPremium
|
|
143
|
+
}).oneTime(true).resized(true);
|
|
144
|
+
const message = await this.reply(text, { ...options, markup });
|
|
145
|
+
const answer = await this.conversation.waitFor(":users_shared");
|
|
146
|
+
this.updateCtx(answer);
|
|
147
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
148
|
+
return answer.msg.users_shared;
|
|
149
|
+
};
|
|
150
|
+
contact = async (text, options) => {
|
|
151
|
+
const markup = new Keyboard().requestContact("Поделиться контактом \uD83D\uDCDE").oneTime(true).resized(true);
|
|
152
|
+
const message = await this.reply(text, { ...options, markup });
|
|
153
|
+
const answer = await this.conversation.waitFor(":contact");
|
|
154
|
+
this.updateCtx(answer);
|
|
155
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
156
|
+
return answer.msg.contact;
|
|
157
|
+
};
|
|
158
|
+
location = async (text, options) => {
|
|
159
|
+
const markup = new Keyboard().requestLocation("Поделиться геопозицией \uD83D\uDCCD").oneTime(true).resized(true);
|
|
160
|
+
const message = await this.reply(text, { ...options, markup });
|
|
161
|
+
const answer = await this.conversation.waitFor(":location");
|
|
162
|
+
this.updateCtx(answer);
|
|
163
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
164
|
+
return answer.msg.location;
|
|
165
|
+
};
|
|
166
|
+
poll = async (text, options) => {
|
|
167
|
+
const pollType = options?.poll?.type;
|
|
168
|
+
const markup = new Keyboard().requestPoll("Отправить опрос \uD83D\uDCCA", pollType).oneTime(true).resized(true);
|
|
169
|
+
const message = await this.reply(text, { ...options, markup });
|
|
170
|
+
const answer = await this.conversation.waitFor(":poll");
|
|
171
|
+
this.updateCtx(answer);
|
|
172
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
173
|
+
return answer.msg.poll;
|
|
174
|
+
};
|
|
175
|
+
renderPaginationButtons = (pagesCount, currentPage, markup, columnCount, extraControls) => {
|
|
176
|
+
const oddColumnCount = columnCount % 2 === 0 ? columnCount + 1 : columnCount;
|
|
177
|
+
const emptyColumns = oddColumnCount >= 5 ? extraControls ? 0 : 1 : 0;
|
|
178
|
+
for (let i = 0;i < emptyColumns; i++) {
|
|
179
|
+
markup.text(" ", " ");
|
|
180
|
+
}
|
|
181
|
+
if (extraControls) {
|
|
182
|
+
markup.text(currentPage > 2 ? "⋘" : " ", "first");
|
|
183
|
+
}
|
|
184
|
+
markup.text(currentPage > 1 ? "←" : " ", "prev");
|
|
185
|
+
markup.text(currentPage + " / " + pagesCount, "page");
|
|
186
|
+
markup.text(currentPage < pagesCount ? "→" : " ", "next");
|
|
187
|
+
if (extraControls) {
|
|
188
|
+
markup.text(currentPage < pagesCount - 1 ? "⋙" : " ", "last");
|
|
189
|
+
}
|
|
190
|
+
for (let i = 0;i < emptyColumns; i++) {
|
|
191
|
+
markup.text(" ", " ");
|
|
192
|
+
}
|
|
193
|
+
return markup.row();
|
|
194
|
+
};
|
|
195
|
+
resetChoice = (session) => {
|
|
196
|
+
if (!session._gx.conversations)
|
|
197
|
+
session._gx.conversations = {
|
|
198
|
+
currentChoices: new Set,
|
|
199
|
+
currentPage: 1
|
|
200
|
+
};
|
|
201
|
+
else {
|
|
202
|
+
session._gx.conversations.currentChoices = new Set;
|
|
203
|
+
session._gx.conversations.currentPage = 1;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
async basicChoice(text, choices, multi = false, options, inProgress) {
|
|
207
|
+
const markup = new InlineKeyboard;
|
|
208
|
+
const session = await this.conversation.external(() => this.ctx.session);
|
|
209
|
+
if (!session._gx.conversations)
|
|
210
|
+
this.resetChoice(session);
|
|
211
|
+
await this.conversation.external(() => !inProgress && this.resetChoice(session));
|
|
212
|
+
let currentRow = [];
|
|
213
|
+
const columnCount = options?.columnCount ?? this.config.columnCount ?? 1;
|
|
214
|
+
const rowCount = options?.rowCount ?? this.config.rowCount ?? 3;
|
|
215
|
+
const filledChoices = options?.pagination?.enabled ? choices.concat(Array.from({ length: columnCount * rowCount - choices.length % (columnCount * rowCount) }).map(() => [" ", "empty"])) : choices;
|
|
216
|
+
const pagesCount = Math.ceil(choices.length / (columnCount * rowCount));
|
|
217
|
+
const currentPage = session._gx.conversations.currentPage ?? 1;
|
|
218
|
+
let currentRowId = 0;
|
|
219
|
+
for (const [choiceId, choice] of filledChoices.entries()) {
|
|
220
|
+
if (options?.pagination?.enabled && choiceId < (currentPage - 1) * columnCount * rowCount)
|
|
221
|
+
continue;
|
|
222
|
+
const checked = session._gx.conversations.currentChoices.has(choice[1]);
|
|
223
|
+
currentRow.push([`${choice[0]}${checked ? " ✅" : ""}`, choice[1]]);
|
|
224
|
+
if (currentRow.length === columnCount) {
|
|
225
|
+
currentRow.forEach(([label, value]) => markup.text(label, value == "empty" ? "empty" : `answer:${value}`));
|
|
226
|
+
markup.row();
|
|
227
|
+
currentRow = [];
|
|
228
|
+
currentRowId++;
|
|
229
|
+
if (options?.pagination?.enabled && (currentRowId === rowCount || choiceId === filledChoices.length - 1)) {
|
|
230
|
+
this.renderPaginationButtons(pagesCount, currentPage, markup, columnCount, options?.pagination?.extraControls ?? false);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (currentRow.length > 0) {
|
|
236
|
+
currentRow.forEach(([label, value]) => {
|
|
237
|
+
markup.text(label, `answer:${value}`);
|
|
238
|
+
});
|
|
239
|
+
markup.row();
|
|
240
|
+
}
|
|
241
|
+
const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup);
|
|
242
|
+
if (options?.markup instanceof InlineKeyboard)
|
|
243
|
+
markup.append(options.markup).row();
|
|
244
|
+
const sendContinueButton = session._gx.conversations.currentChoices.size > 0;
|
|
245
|
+
const continueInlineEnd = options?.continueInlineEnd ?? this.config.continueInlineEnd;
|
|
246
|
+
if (multi && !options?.noChoiceAllowed && !continueInlineEnd && sendContinueButton)
|
|
247
|
+
markup.text(options?.continueButton ?? this.config?.continueButton ?? "Continue", "continue").row();
|
|
248
|
+
const messageText = typeof text === "function" ? await text(Array.from(session._gx.conversations.currentChoices), session._gx.conversations.currentPage, pagesCount) : text;
|
|
249
|
+
const message = await this.reply(messageText, {
|
|
250
|
+
...options,
|
|
251
|
+
markup,
|
|
252
|
+
continueButton: options?.continueButton,
|
|
253
|
+
continueInlineEnd,
|
|
254
|
+
sendContinueButton: continueInlineEnd && sendContinueButton
|
|
255
|
+
});
|
|
256
|
+
let additionalTriggerCalled;
|
|
257
|
+
let answer;
|
|
258
|
+
const paginationsCallbacks = ["prev", "next", "page", "empty", "first", "last"];
|
|
259
|
+
do {
|
|
260
|
+
answer = await this.conversation.waitForCallbackQuery([
|
|
261
|
+
...choices.map((c) => `answer:${c[1]}`),
|
|
262
|
+
...multi ? ["continue"] : [],
|
|
263
|
+
...options?.pagination?.enabled ? paginationsCallbacks : [],
|
|
264
|
+
...additionalTriggers
|
|
265
|
+
]);
|
|
266
|
+
this.updateCtx(answer);
|
|
267
|
+
const callbackData2 = answer.callbackQuery.data;
|
|
268
|
+
additionalTriggerCalled = additionalTriggers.includes(callbackData2);
|
|
269
|
+
if (callbackData2 == "continue" || additionalTriggerCalled) {
|
|
270
|
+
const choices2 = Array.from(session._gx.conversations.currentChoices);
|
|
271
|
+
this.ctx.session._gx.conversations.currentChoices = new Set;
|
|
272
|
+
if (additionalTriggerCalled)
|
|
273
|
+
return { result: choices2, callbackQuery: callbackData2, message };
|
|
274
|
+
return { result: choices2, message };
|
|
275
|
+
}
|
|
276
|
+
} while (additionalTriggerCalled);
|
|
277
|
+
const questionData = session._gx.conversations;
|
|
278
|
+
const callbackData = answer.callbackQuery.data;
|
|
279
|
+
const data = callbackData.split(":")[1];
|
|
280
|
+
const skip = await this.conversation.external(async () => {
|
|
281
|
+
if (callbackData == "prev")
|
|
282
|
+
questionData.currentPage = Math.max(currentPage - 1, 1);
|
|
283
|
+
if (callbackData == "next")
|
|
284
|
+
questionData.currentPage = Math.min(currentPage + 1, pagesCount);
|
|
285
|
+
if (callbackData == "first")
|
|
286
|
+
questionData.currentPage = 1;
|
|
287
|
+
if (callbackData == "last")
|
|
288
|
+
questionData.currentPage = pagesCount;
|
|
289
|
+
if (data) {
|
|
290
|
+
if (questionData.currentChoices.has(data))
|
|
291
|
+
questionData.currentChoices.delete(data);
|
|
292
|
+
else
|
|
293
|
+
questionData.currentChoices.add(data);
|
|
294
|
+
}
|
|
295
|
+
this.ctx.session._gx.conversations = questionData;
|
|
296
|
+
if (paginationsCallbacks.includes(callbackData) && questionData.currentPage == currentPage) {
|
|
297
|
+
await this.ctx.answerCallbackQuery();
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
if (skip)
|
|
302
|
+
return this.conversation.skip({ drop: true });
|
|
303
|
+
if (!multi && data) {
|
|
304
|
+
this.ctx.session._gx.conversations.currentChoices = new Set;
|
|
305
|
+
return {
|
|
306
|
+
result: data,
|
|
307
|
+
message
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return this.basicChoice(text, choices, multi, options, true);
|
|
311
|
+
}
|
|
312
|
+
choice = async (text, choices, options) => this.basicChoice(text, choices, false, options);
|
|
313
|
+
multi = async (text, choices, options) => this.basicChoice(text, choices, true, options);
|
|
314
|
+
boolean = async (text, yesNoStrings, options) => {
|
|
315
|
+
const { result, message } = await this.choice(text, [
|
|
316
|
+
[yesNoStrings?.[0] ?? "✅ Да", "true"],
|
|
317
|
+
[yesNoStrings?.[1] ?? "❌ Нет", "false"]
|
|
318
|
+
], { columnCount: 2, ...options });
|
|
319
|
+
const boolResult = result === "true";
|
|
320
|
+
return { result: boolResult, message };
|
|
321
|
+
};
|
|
322
|
+
photo = async (text, options) => {
|
|
323
|
+
const message = await this.reply(text, options);
|
|
324
|
+
const answer = await this.conversation.waitFor(":photo");
|
|
325
|
+
this.updateCtx(answer);
|
|
326
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
327
|
+
const photo = answer.msg.photo?.[0];
|
|
328
|
+
if (!photo) {
|
|
329
|
+
throw new Error("No photo found in update");
|
|
330
|
+
}
|
|
331
|
+
return photo.file_id;
|
|
332
|
+
};
|
|
333
|
+
file = async (text, options) => {
|
|
334
|
+
const message = await this.reply(text, options);
|
|
335
|
+
const answer = await this.conversation.waitFor(":file");
|
|
336
|
+
this.updateCtx(answer);
|
|
337
|
+
await answer?.msg?.delete?.().catch?.(() => {});
|
|
338
|
+
return answer.msg.document?.file_id;
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// src/conversation.ts
|
|
342
|
+
import { createConversation } from "@grammyjs/conversations";
|
|
343
|
+
import { hydrate } from "@grammyjs/hydrate";
|
|
344
|
+
import { GlobalMenuRegistry as GlobalMenuRegistry2, getGrammyXOptions } from "@grammy-x/core";
|
|
345
|
+
function createCustomConversation(builder, config) {
|
|
346
|
+
let cfg = typeof config === "string" ? { id: config } : config;
|
|
347
|
+
cfg = cfg ?? {};
|
|
348
|
+
const id = cfg.id ?? builder.name;
|
|
349
|
+
cfg.id = id;
|
|
350
|
+
GlobalMenuRegistry2.register({
|
|
351
|
+
menuName: id,
|
|
352
|
+
send: async (ctx) => {
|
|
353
|
+
if (!ctx.conversation) {
|
|
354
|
+
console.error(`[Grammy-X] Cannot enter conversation '${id}': ctx.conversation is undefined. Make sure bot.use(conversations()) is registered before components that use it.`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (ctx.conversation.active) {
|
|
358
|
+
await ctx.conversation.exit();
|
|
359
|
+
}
|
|
360
|
+
return ctx.conversation.enter(id);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
return createConversation(async (conversation, ctx) => {
|
|
364
|
+
await conversation.run(hydrate());
|
|
365
|
+
const gx = ctx.session?._gx;
|
|
366
|
+
if (gx && gx.history[gx.history.length - 1] !== id) {
|
|
367
|
+
gx.history.push(id);
|
|
368
|
+
if (gx.history.length > 20)
|
|
369
|
+
gx.history.shift();
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
return await builder(conversation, ctx);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
if (e.name === "CommandInterruptError") {
|
|
375
|
+
const cmd = e.message.split(":")[1];
|
|
376
|
+
const opts = getGrammyXOptions(e.ctx);
|
|
377
|
+
const targetMenu = opts?.interruptCommands?.[cmd];
|
|
378
|
+
if (targetMenu) {
|
|
379
|
+
try {
|
|
380
|
+
if (typeof targetMenu === "string") {
|
|
381
|
+
await e.ctx.menu.nav(targetMenu);
|
|
382
|
+
} else if (typeof targetMenu.send === "function") {
|
|
383
|
+
await targetMenu.send(e.ctx);
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.error(`Could not auto-nav to target menu for command ${cmd}`, err);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
throw e;
|
|
392
|
+
}
|
|
393
|
+
}, cfg);
|
|
394
|
+
}
|
|
395
|
+
export {
|
|
396
|
+
createCustomConversation,
|
|
397
|
+
QuestionHelper
|
|
398
|
+
};
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
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
|
|
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
|
}
|
package/src/conversation.ts
DELETED
|
@@ -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
package/src/question-helper.ts
DELETED
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
import type { Conversation } from "@grammyjs/conversations"
|
|
2
|
-
import { CallbackQueryContext, Context, InlineKeyboard, Keyboard } from "grammy"
|
|
3
|
-
import type { ChatAdministratorRights, ChatShared } from "grammy/types"
|
|
4
|
-
import { smartReply } from "@grammy-x/core"
|
|
5
|
-
|
|
6
|
-
type MaybePromise<T> = PromiseLike<T> | T
|
|
7
|
-
type ButtonsMarkup = InlineKeyboard | Keyboard
|
|
8
|
-
|
|
9
|
-
interface QuestionParameters {
|
|
10
|
-
markup?: ButtonsMarkup
|
|
11
|
-
autoBold?: boolean
|
|
12
|
-
entities?: any[]
|
|
13
|
-
fastMenu?: boolean
|
|
14
|
-
fastMenuCallbackData?: string
|
|
15
|
-
fastMenuText?: string
|
|
16
|
-
newMessage?: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface QuestionCallbackParameters extends QuestionParameters {
|
|
20
|
-
columnCount?: number
|
|
21
|
-
noChoiceAllowed?: boolean
|
|
22
|
-
rowCount?: number
|
|
23
|
-
continueButton?: string
|
|
24
|
-
pagination?: {
|
|
25
|
-
enabled: boolean
|
|
26
|
-
extraControls?: boolean
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface QuestionMultiParameters extends QuestionCallbackParameters {
|
|
31
|
-
continueInlineEnd?: boolean
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface QuestionChatParameters extends QuestionParameters {
|
|
35
|
-
chat: {
|
|
36
|
-
requiredUserRights?: ChatAdministratorRights
|
|
37
|
-
requiredBotRights?: ChatAdministratorRights
|
|
38
|
-
botIsMember?: boolean
|
|
39
|
-
isChannel: boolean
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
type QuestionUniversalParameters = Partial<QuestionMultiParameters & QuestionChatParameters>
|
|
44
|
-
|
|
45
|
-
interface QuestionInternalParameters extends QuestionUniversalParameters {
|
|
46
|
-
continueButton?: string
|
|
47
|
-
sendContinueButton?: boolean
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface TextParserResult<R> {
|
|
51
|
-
result?: R
|
|
52
|
-
callbackQuery?: string
|
|
53
|
-
answerCtx?: any
|
|
54
|
-
message?: any
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface QuestionHelperSession {
|
|
58
|
-
questionHelper: {
|
|
59
|
-
currentChoices: Set<string>
|
|
60
|
-
currentPage: number
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function randomInteger(minimum: number, maximum: number): number {
|
|
65
|
-
return Math.floor(Math.random() * (maximum - minimum + 1) + minimum)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export class QuestionHelper<
|
|
69
|
-
C extends Context = Context,
|
|
70
|
-
T extends Conversation<C> = Conversation<C>,
|
|
71
|
-
> {
|
|
72
|
-
private conversation: T
|
|
73
|
-
private config: QuestionUniversalParameters
|
|
74
|
-
private ctx: C
|
|
75
|
-
public message_id?: number
|
|
76
|
-
|
|
77
|
-
constructor(conversation: T, ctx: C, config?: QuestionUniversalParameters) {
|
|
78
|
-
this.conversation = conversation
|
|
79
|
-
this.config = config ?? {}
|
|
80
|
-
this.ctx = ctx
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private updateCtx = <U extends Context>(ctx: U) => {
|
|
84
|
-
Object.keys(this.ctx).forEach((key) => delete (this.ctx as any)[key])
|
|
85
|
-
Object.assign(this.ctx, ctx)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
delete = async () => {
|
|
89
|
-
if (this.message_id) {
|
|
90
|
-
await this.ctx.api.deleteMessage(this.ctx.from!.id, this.message_id)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private reply = async (text: string, options?: QuestionInternalParameters) => {
|
|
95
|
-
let newText = text
|
|
96
|
-
const mergedOptions = { ...this.config, ...options }
|
|
97
|
-
|
|
98
|
-
if (mergedOptions.autoBold !== false) newText = `<b>${newText}</b>`
|
|
99
|
-
|
|
100
|
-
if (options?.markup instanceof InlineKeyboard) mergedOptions.markup = options.markup
|
|
101
|
-
|
|
102
|
-
if (!mergedOptions.markup) mergedOptions.markup = new InlineKeyboard()
|
|
103
|
-
|
|
104
|
-
if (mergedOptions.markup instanceof InlineKeyboard && this.config.markup instanceof InlineKeyboard)
|
|
105
|
-
mergedOptions.markup.append(this.config.markup)
|
|
106
|
-
|
|
107
|
-
if (mergedOptions.fastMenu && mergedOptions.markup instanceof InlineKeyboard) {
|
|
108
|
-
const markup = mergedOptions.markup
|
|
109
|
-
markup.text(mergedOptions.fastMenuText ?? "↩️ В главное меню", mergedOptions.fastMenuCallbackData ?? "start")
|
|
110
|
-
}
|
|
111
|
-
if (
|
|
112
|
-
options?.sendContinueButton &&
|
|
113
|
-
mergedOptions.continueInlineEnd &&
|
|
114
|
-
mergedOptions.markup instanceof InlineKeyboard
|
|
115
|
-
) {
|
|
116
|
-
const markup = mergedOptions.markup
|
|
117
|
-
markup.text(options?.continueButton ?? "➡️ Продолжить", "continue")
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const message = await smartReply(this.ctx, text, {
|
|
121
|
-
options: {
|
|
122
|
-
entities: mergedOptions.entities,
|
|
123
|
-
reply_markup: mergedOptions.markup as any,
|
|
124
|
-
},
|
|
125
|
-
messageToEdit: this.message_id,
|
|
126
|
-
newMessage: mergedOptions?.newMessage,
|
|
127
|
-
embolden: false,
|
|
128
|
-
dedent: false,
|
|
129
|
-
})
|
|
130
|
-
this.message_id = message?.message_id ?? this.message_id
|
|
131
|
-
return message
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private getCallbackDataFromKeyboard = (keyboard: ButtonsMarkup | undefined): string[] => {
|
|
135
|
-
if (keyboard && keyboard instanceof InlineKeyboard)
|
|
136
|
-
return keyboard.inline_keyboard
|
|
137
|
-
.flat()
|
|
138
|
-
.map((b: any) => b.callback_data)
|
|
139
|
-
.filter(Boolean)
|
|
140
|
-
return []
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private textParser = async <R>(
|
|
144
|
-
text: string,
|
|
145
|
-
parser: (data: string) => R,
|
|
146
|
-
validator: (result: R) => boolean,
|
|
147
|
-
options?: QuestionParameters
|
|
148
|
-
) => {
|
|
149
|
-
const message = await this.reply(text, options)
|
|
150
|
-
const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup)
|
|
151
|
-
|
|
152
|
-
const answer = await this.conversation.waitUntil(
|
|
153
|
-
(ctx) => Context.has.callbackQuery(additionalTriggers)(ctx) || ctx.has(":text")
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
this.updateCtx(answer)
|
|
157
|
-
|
|
158
|
-
const callbackQuery = answer.callbackQuery?.data
|
|
159
|
-
if (callbackQuery) return { callbackQuery, answerCtx: answer, message }
|
|
160
|
-
|
|
161
|
-
const result = parser((answer as any).msg!.text) as NonNullable<R>
|
|
162
|
-
await this.ctx.api.deleteMessage(this.ctx.from!.id, (answer as any).msg!.message_id).catch(() => {})
|
|
163
|
-
if (!validator(result)) return await this.conversation.skip()
|
|
164
|
-
return { result, answerCtx: answer, message }
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
text = (text: string, options?: QuestionParameters): Promise<TextParserResult<string>> =>
|
|
168
|
-
this.textParser(
|
|
169
|
-
text,
|
|
170
|
-
(r) => r,
|
|
171
|
-
() => true,
|
|
172
|
-
options
|
|
173
|
-
) as Promise<TextParserResult<string>>
|
|
174
|
-
|
|
175
|
-
int = (text: string, options?: QuestionParameters): Promise<TextParserResult<number>> =>
|
|
176
|
-
this.textParser(text, parseInt, (r) => !isNaN(r), options) as Promise<TextParserResult<number>>
|
|
177
|
-
|
|
178
|
-
float = (text: string, options?: QuestionParameters): Promise<TextParserResult<number>> =>
|
|
179
|
-
this.textParser(text, parseFloat, (r) => !isNaN(r), options) as Promise<TextParserResult<number>>
|
|
180
|
-
|
|
181
|
-
chat = async (text: string, options: QuestionChatParameters) => {
|
|
182
|
-
const chat = options.chat
|
|
183
|
-
const markup = new Keyboard()
|
|
184
|
-
.requestChat("Выбрать чат 🔍", randomInteger(0, 999999), {
|
|
185
|
-
chat_is_channel: chat.isChannel,
|
|
186
|
-
bot_administrator_rights: chat.requiredBotRights,
|
|
187
|
-
bot_is_member: chat.botIsMember,
|
|
188
|
-
request_username: true,
|
|
189
|
-
user_administrator_rights: chat.requiredUserRights,
|
|
190
|
-
})
|
|
191
|
-
.oneTime(true)
|
|
192
|
-
.resized(true)
|
|
193
|
-
const message = await this.reply(text, { ...options, markup })
|
|
194
|
-
const answer = await this.conversation.waitFor(":chat_shared")
|
|
195
|
-
|
|
196
|
-
this.updateCtx(answer)
|
|
197
|
-
|
|
198
|
-
await (answer?.msg as any)?.delete?.().catch?.(() => {})
|
|
199
|
-
return answer.msg.chat_shared as ChatShared
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private renderPaginationButtons = (
|
|
203
|
-
pagesCount: number,
|
|
204
|
-
currentPage: number,
|
|
205
|
-
markup: InlineKeyboard,
|
|
206
|
-
columnCount: number,
|
|
207
|
-
extraControls: boolean
|
|
208
|
-
) => {
|
|
209
|
-
const oddColumnCount = columnCount % 2 === 0 ? columnCount + 1 : columnCount
|
|
210
|
-
const emptyColumns = oddColumnCount >= 5 ? (extraControls ? 0 : 1) : 0
|
|
211
|
-
for (let i = 0; i < emptyColumns; i++) {
|
|
212
|
-
markup.text(" ", " ")
|
|
213
|
-
}
|
|
214
|
-
if (extraControls) {
|
|
215
|
-
markup.text(currentPage > 2 ? "⋘" : " ", "first")
|
|
216
|
-
}
|
|
217
|
-
markup.text(currentPage > 1 ? "←" : " ", "prev")
|
|
218
|
-
markup.text(currentPage + " / " + pagesCount, "page")
|
|
219
|
-
markup.text(currentPage < pagesCount ? "→" : " ", "next")
|
|
220
|
-
if (extraControls) {
|
|
221
|
-
markup.text(currentPage < pagesCount - 1 ? "⋙" : " ", "last")
|
|
222
|
-
}
|
|
223
|
-
for (let i = 0; i < emptyColumns; i++) {
|
|
224
|
-
markup.text(" ", " ")
|
|
225
|
-
}
|
|
226
|
-
return markup.row()
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private resetChoice = (session: QuestionHelperSession) => {
|
|
230
|
-
session.questionHelper = {
|
|
231
|
-
currentChoices: new Set(),
|
|
232
|
-
currentPage: 1,
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
private async basicChoice<const R extends string>(
|
|
237
|
-
text: string,
|
|
238
|
-
choices: [string, R][],
|
|
239
|
-
multi: false,
|
|
240
|
-
options?: QuestionParameters,
|
|
241
|
-
inProgress?: boolean
|
|
242
|
-
): Promise<{ result: R; message: any }>
|
|
243
|
-
private async basicChoice<const R extends string>(
|
|
244
|
-
text: string | ((choices: R[], currentPage: number, pagesCount: number) => MaybePromise<string>),
|
|
245
|
-
choices: [string, R][],
|
|
246
|
-
multi: true,
|
|
247
|
-
options?: QuestionParameters,
|
|
248
|
-
inProgress?: boolean
|
|
249
|
-
): Promise<{
|
|
250
|
-
result: R[]
|
|
251
|
-
callbackQuery?: string
|
|
252
|
-
message: any
|
|
253
|
-
}>
|
|
254
|
-
private async basicChoice<const R extends string>(
|
|
255
|
-
text: string | ((choices: R[], currentPage: number, pagesCount: number) => MaybePromise<string>),
|
|
256
|
-
choices: [string, R][],
|
|
257
|
-
multi: boolean = false,
|
|
258
|
-
options?: QuestionMultiParameters,
|
|
259
|
-
inProgress?: boolean
|
|
260
|
-
): Promise<
|
|
261
|
-
| {
|
|
262
|
-
result: R[]
|
|
263
|
-
callbackQuery?: string
|
|
264
|
-
message: any
|
|
265
|
-
}
|
|
266
|
-
| {
|
|
267
|
-
result: R
|
|
268
|
-
message: any
|
|
269
|
-
}
|
|
270
|
-
> {
|
|
271
|
-
const markup = new InlineKeyboard()
|
|
272
|
-
const session = (await this.conversation.external(
|
|
273
|
-
() => (this.ctx as any).session
|
|
274
|
-
)) as unknown as QuestionHelperSession
|
|
275
|
-
if (!session.questionHelper) this.resetChoice(session)
|
|
276
|
-
await this.conversation.external(() => !inProgress && this.resetChoice(session))
|
|
277
|
-
let currentRow: [string, string][] = []
|
|
278
|
-
const columnCount = options?.columnCount ?? this.config.columnCount ?? 1
|
|
279
|
-
const rowCount = options?.rowCount ?? this.config.rowCount ?? 3
|
|
280
|
-
const filledChoices = options?.pagination?.enabled
|
|
281
|
-
? choices.concat(
|
|
282
|
-
Array.from({ length: columnCount * rowCount - (choices.length % (columnCount * rowCount)) }).map(
|
|
283
|
-
() => [" ", "empty" as unknown as R]
|
|
284
|
-
)
|
|
285
|
-
)
|
|
286
|
-
: choices
|
|
287
|
-
const pagesCount = Math.ceil(choices.length / (columnCount * rowCount))
|
|
288
|
-
const currentPage = session.questionHelper.currentPage ?? 1
|
|
289
|
-
|
|
290
|
-
let currentRowId = 0
|
|
291
|
-
for (const [choiceId, choice] of filledChoices.entries()) {
|
|
292
|
-
if (options?.pagination?.enabled && choiceId < (currentPage - 1) * columnCount * rowCount) continue
|
|
293
|
-
const checked = session.questionHelper.currentChoices.has(choice[1])
|
|
294
|
-
currentRow.push([`${choice[0]}${checked ? " ✅" : ""}`, choice[1]])
|
|
295
|
-
|
|
296
|
-
if (currentRow.length === columnCount) {
|
|
297
|
-
currentRow.forEach(([label, value]) =>
|
|
298
|
-
markup.text(label, value == "empty" ? "empty" : `answer:${value}`)
|
|
299
|
-
)
|
|
300
|
-
markup.row()
|
|
301
|
-
currentRow = []
|
|
302
|
-
currentRowId++
|
|
303
|
-
if (
|
|
304
|
-
options?.pagination?.enabled &&
|
|
305
|
-
(currentRowId === rowCount || choiceId === filledChoices.length - 1)
|
|
306
|
-
) {
|
|
307
|
-
this.renderPaginationButtons(
|
|
308
|
-
pagesCount,
|
|
309
|
-
currentPage,
|
|
310
|
-
markup,
|
|
311
|
-
columnCount,
|
|
312
|
-
options?.pagination?.extraControls ?? false
|
|
313
|
-
)
|
|
314
|
-
break
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (currentRow.length > 0) {
|
|
320
|
-
currentRow.forEach(([label, value]) => {
|
|
321
|
-
markup.text(label, `answer:${value}`)
|
|
322
|
-
})
|
|
323
|
-
markup.row()
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup)
|
|
327
|
-
if (options?.markup instanceof InlineKeyboard) markup.append(options.markup).row()
|
|
328
|
-
|
|
329
|
-
const sendContinueButton = session.questionHelper.currentChoices.size > 0
|
|
330
|
-
const continueInlineEnd = options?.continueInlineEnd ?? (this.config as any).continueInlineEnd
|
|
331
|
-
if (multi && !options?.noChoiceAllowed && !continueInlineEnd && sendContinueButton)
|
|
332
|
-
markup.text(options?.continueButton ?? "➡️ Продолжить", "continue").row()
|
|
333
|
-
|
|
334
|
-
const messageText =
|
|
335
|
-
typeof text === "function"
|
|
336
|
-
? await text(
|
|
337
|
-
Array.from(session.questionHelper.currentChoices) as R[],
|
|
338
|
-
session.questionHelper.currentPage,
|
|
339
|
-
pagesCount
|
|
340
|
-
)
|
|
341
|
-
: text
|
|
342
|
-
const message = await this.reply(messageText, {
|
|
343
|
-
...options,
|
|
344
|
-
markup,
|
|
345
|
-
continueButton: options?.continueButton,
|
|
346
|
-
continueInlineEnd,
|
|
347
|
-
sendContinueButton: continueInlineEnd && sendContinueButton,
|
|
348
|
-
})
|
|
349
|
-
let additionalTriggerCalled: boolean
|
|
350
|
-
let answer: CallbackQueryContext<C>
|
|
351
|
-
|
|
352
|
-
const paginationsCallbacks = ["prev", "next", "page", "empty", "first", "last"]
|
|
353
|
-
|
|
354
|
-
do {
|
|
355
|
-
answer = await this.conversation.waitForCallbackQuery([
|
|
356
|
-
...choices.map((c) => `answer:${c[1]}`),
|
|
357
|
-
...(multi ? ["continue"] : []),
|
|
358
|
-
...(options?.pagination?.enabled ? paginationsCallbacks : []),
|
|
359
|
-
...additionalTriggers,
|
|
360
|
-
])
|
|
361
|
-
this.updateCtx(answer)
|
|
362
|
-
const callbackData = answer.callbackQuery.data
|
|
363
|
-
additionalTriggerCalled = additionalTriggers.includes(callbackData)
|
|
364
|
-
|
|
365
|
-
if (callbackData == "continue" || additionalTriggerCalled) {
|
|
366
|
-
const choices = Array.from(session.questionHelper.currentChoices)
|
|
367
|
-
;((this.ctx as any).session as unknown as QuestionHelperSession).questionHelper.currentChoices = new Set()
|
|
368
|
-
if (additionalTriggerCalled) return { result: choices as R[], callbackQuery: callbackData, message }
|
|
369
|
-
return { result: choices as R[], message }
|
|
370
|
-
}
|
|
371
|
-
} while (additionalTriggerCalled)
|
|
372
|
-
|
|
373
|
-
const questionData = session.questionHelper
|
|
374
|
-
const callbackData = answer.callbackQuery.data
|
|
375
|
-
const data = callbackData.split(":")[1]
|
|
376
|
-
const skip = await this.conversation.external(async () => {
|
|
377
|
-
if (callbackData == "prev") questionData.currentPage = Math.max(currentPage - 1, 1)
|
|
378
|
-
if (callbackData == "next") questionData.currentPage = Math.min(currentPage + 1, pagesCount)
|
|
379
|
-
if (callbackData == "first") questionData.currentPage = 1
|
|
380
|
-
if (callbackData == "last") questionData.currentPage = pagesCount
|
|
381
|
-
if (data) {
|
|
382
|
-
if (questionData.currentChoices!.has(data)) questionData.currentChoices!.delete(data)
|
|
383
|
-
else questionData.currentChoices!.add(data)
|
|
384
|
-
}
|
|
385
|
-
;((this.ctx as any).session as unknown as QuestionHelperSession).questionHelper = session.questionHelper =
|
|
386
|
-
questionData
|
|
387
|
-
if (paginationsCallbacks.includes(callbackData) && questionData.currentPage == currentPage) {
|
|
388
|
-
await this.ctx.answerCallbackQuery()
|
|
389
|
-
return true
|
|
390
|
-
}
|
|
391
|
-
})
|
|
392
|
-
if (skip) return this.conversation.skip({ drop: true })
|
|
393
|
-
if (!multi && data) {
|
|
394
|
-
;((this.ctx as any).session as unknown as QuestionHelperSession).questionHelper.currentChoices = new Set()
|
|
395
|
-
return {
|
|
396
|
-
result: data as R,
|
|
397
|
-
message,
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
//@ts-expect-error bruh typescript
|
|
402
|
-
return this.basicChoice(text, choices, multi, options, true)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
choice = async <const R extends string>(
|
|
406
|
-
text: string,
|
|
407
|
-
choices: [string, R][],
|
|
408
|
-
options?: QuestionCallbackParameters
|
|
409
|
-
): Promise<{
|
|
410
|
-
result: R
|
|
411
|
-
message: any
|
|
412
|
-
}> => this.basicChoice(text, choices, false, options)
|
|
413
|
-
|
|
414
|
-
multi = async <const R extends string>(
|
|
415
|
-
text: string | ((choices: R[], currentPage: number, pagesCount: number) => MaybePromise<string>),
|
|
416
|
-
choices: [string, R][],
|
|
417
|
-
options?: QuestionCallbackParameters
|
|
418
|
-
): Promise<{
|
|
419
|
-
result: R[]
|
|
420
|
-
callbackData?: string
|
|
421
|
-
message: any
|
|
422
|
-
}> => this.basicChoice(text, choices, true, options)
|
|
423
|
-
|
|
424
|
-
boolean = async (text: string, yesNoStrings?: string[], options?: QuestionCallbackParameters) => {
|
|
425
|
-
const { result, message } = await this.choice(
|
|
426
|
-
text,
|
|
427
|
-
[
|
|
428
|
-
[yesNoStrings?.[0] ?? "✅ Да", "true"],
|
|
429
|
-
[yesNoStrings?.[1] ?? "❌ Нет", "false"],
|
|
430
|
-
],
|
|
431
|
-
{ columnCount: 2, ...options }
|
|
432
|
-
)
|
|
433
|
-
const boolResult = result === "true"
|
|
434
|
-
return { result: boolResult, message }
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
photo = async (text: string, options?: QuestionParameters) => {
|
|
438
|
-
const message = await this.reply(text, options)
|
|
439
|
-
const answer = await this.conversation.waitFor(":photo")
|
|
440
|
-
this.updateCtx(answer)
|
|
441
|
-
await (answer?.msg as any)?.delete?.().catch?.(() => {})
|
|
442
|
-
const photo = answer.msg.photo?.[0]
|
|
443
|
-
if (!photo) {
|
|
444
|
-
throw new Error("No photo found in update")
|
|
445
|
-
}
|
|
446
|
-
return photo.file_id
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
file = async (text: string, options?: QuestionParameters) => {
|
|
450
|
-
const message = await this.reply(text, options)
|
|
451
|
-
const answer = await this.conversation.waitFor(":file")
|
|
452
|
-
this.updateCtx(answer)
|
|
453
|
-
await (answer?.msg as any)?.delete?.().catch?.(() => {})
|
|
454
|
-
return answer.msg.document?.file_id as string
|
|
455
|
-
}
|
|
456
|
-
}
|