@grammy-x/conversations 0.2.0 → 2.0.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/index.cjs +451 -0
- package/dist/index.d.cts +116 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +423 -0
- package/package.json +29 -6
- package/src/conversation.ts +0 -26
- package/src/index.ts +0 -3
- package/src/question-helper.ts +0 -579
- package/tsconfig.json +0 -8
package/dist/index.js
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
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
|
+
var QuestionHelper = class {
|
|
8
|
+
conversation;
|
|
9
|
+
config;
|
|
10
|
+
ctx;
|
|
11
|
+
message_id;
|
|
12
|
+
constructor(conversation, ctx, config) {
|
|
13
|
+
this.conversation = conversation;
|
|
14
|
+
this.config = config ?? {};
|
|
15
|
+
this.ctx = ctx;
|
|
16
|
+
}
|
|
17
|
+
updateCtx = (ctx) => {
|
|
18
|
+
Object.keys(this.ctx).forEach((key) => delete this.ctx[key]);
|
|
19
|
+
Object.assign(this.ctx, ctx);
|
|
20
|
+
};
|
|
21
|
+
delete = async () => {
|
|
22
|
+
if (this.message_id) {
|
|
23
|
+
await this.ctx.api.deleteMessage(this.ctx.from.id, this.message_id);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
back = async () => {
|
|
27
|
+
const gx = this.ctx.session?._gx;
|
|
28
|
+
if (gx?.history.length > 0) {
|
|
29
|
+
gx.history.pop();
|
|
30
|
+
}
|
|
31
|
+
const targetId = gx?.history.length > 0 ? gx.history.pop() : void 0;
|
|
32
|
+
if (targetId) {
|
|
33
|
+
const menu = GlobalMenuRegistry.get(targetId);
|
|
34
|
+
if (menu) return menu.send(this.ctx);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
reply = async (text, options) => {
|
|
38
|
+
let newText = text;
|
|
39
|
+
const mergedOptions = { ...this.config, ...options };
|
|
40
|
+
if (mergedOptions.autoBold !== false) newText = `<b>${newText}</b>`;
|
|
41
|
+
if (options?.markup instanceof InlineKeyboard) mergedOptions.markup = options.markup;
|
|
42
|
+
if (!mergedOptions.markup) mergedOptions.markup = new InlineKeyboard();
|
|
43
|
+
if (mergedOptions.markup instanceof InlineKeyboard && this.config.markup instanceof InlineKeyboard)
|
|
44
|
+
mergedOptions.markup.append(this.config.markup);
|
|
45
|
+
if (mergedOptions.fastMenu && mergedOptions.markup instanceof InlineKeyboard) {
|
|
46
|
+
const markup = mergedOptions.markup;
|
|
47
|
+
markup.text(mergedOptions.fastMenuText ?? this.config?.fastMenuText ?? "Main Menu", mergedOptions.fastMenuCallbackData ?? "start");
|
|
48
|
+
}
|
|
49
|
+
if (options?.sendContinueButton && mergedOptions.continueInlineEnd && mergedOptions.markup instanceof InlineKeyboard) {
|
|
50
|
+
const markup = mergedOptions.markup;
|
|
51
|
+
markup.text(options?.continueButton ?? this.config?.continueButton ?? "Continue", "continue");
|
|
52
|
+
}
|
|
53
|
+
const message = await smartReply(this.ctx, text, {
|
|
54
|
+
options: {
|
|
55
|
+
entities: mergedOptions.entities,
|
|
56
|
+
reply_markup: mergedOptions.markup
|
|
57
|
+
},
|
|
58
|
+
messageToEdit: this.message_id,
|
|
59
|
+
newMessage: mergedOptions?.newMessage,
|
|
60
|
+
embolden: false,
|
|
61
|
+
dedent: false
|
|
62
|
+
});
|
|
63
|
+
this.message_id = message?.message_id ?? this.message_id;
|
|
64
|
+
return message;
|
|
65
|
+
};
|
|
66
|
+
getCallbackDataFromKeyboard = (keyboard) => {
|
|
67
|
+
if (keyboard && keyboard instanceof InlineKeyboard)
|
|
68
|
+
return keyboard.inline_keyboard.flat().map((b) => b.callback_data).filter(Boolean);
|
|
69
|
+
return [];
|
|
70
|
+
};
|
|
71
|
+
textParser = async (text, parser, validator, options) => {
|
|
72
|
+
const message = await this.reply(text, options);
|
|
73
|
+
const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup);
|
|
74
|
+
const answer = await this.conversation.waitUntil(
|
|
75
|
+
(ctx) => Context.has.callbackQuery(additionalTriggers)(ctx) || ctx.has(":text")
|
|
76
|
+
);
|
|
77
|
+
this.updateCtx(answer);
|
|
78
|
+
const callbackQuery = answer.callbackQuery?.data;
|
|
79
|
+
if (callbackQuery) return { callbackQuery, answerCtx: answer, message };
|
|
80
|
+
if (answer.message?.text?.startsWith("/")) {
|
|
81
|
+
await this.ctx.api.deleteMessage(this.ctx.from.id, answer.message.message_id).catch(() => {
|
|
82
|
+
});
|
|
83
|
+
const cmd = answer.message.text.split(" ")[0];
|
|
84
|
+
const err = new Error(`CommandInterrupt:${cmd}`);
|
|
85
|
+
err.name = "CommandInterruptError";
|
|
86
|
+
err.ctx = answer;
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
const result = parser(answer.msg.text);
|
|
90
|
+
await this.ctx.api.deleteMessage(this.ctx.from.id, answer.msg.message_id).catch(() => {
|
|
91
|
+
});
|
|
92
|
+
if (!validator(result)) return await this.conversation.skip();
|
|
93
|
+
return { result, answerCtx: answer, message };
|
|
94
|
+
};
|
|
95
|
+
text = (text, options) => this.textParser(
|
|
96
|
+
text,
|
|
97
|
+
(r) => r,
|
|
98
|
+
() => true,
|
|
99
|
+
options
|
|
100
|
+
);
|
|
101
|
+
int = (text, options) => this.textParser(text, parseInt, (r) => !isNaN(r), options);
|
|
102
|
+
float = (text, options) => this.textParser(text, parseFloat, (r) => !isNaN(r), options);
|
|
103
|
+
chat = async (text, options) => {
|
|
104
|
+
const chat = options.chat;
|
|
105
|
+
const markup = new Keyboard().requestChat("\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0447\u0430\u0442 \u{1F50D}", randomInteger(0, 999999), {
|
|
106
|
+
chat_is_channel: chat.isChannel,
|
|
107
|
+
bot_administrator_rights: chat.requiredBotRights,
|
|
108
|
+
bot_is_member: chat.botIsMember,
|
|
109
|
+
request_photo: chat.requestPhoto,
|
|
110
|
+
request_title: chat.requestTitle,
|
|
111
|
+
request_username: chat.requestUsername ?? true,
|
|
112
|
+
user_administrator_rights: chat.requiredUserRights
|
|
113
|
+
}).oneTime(true).resized(true);
|
|
114
|
+
const message = await this.reply(text, { ...options, markup });
|
|
115
|
+
const answer = await this.conversation.waitFor(":chat_shared");
|
|
116
|
+
this.updateCtx(answer);
|
|
117
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
118
|
+
});
|
|
119
|
+
return answer.msg.chat_shared;
|
|
120
|
+
};
|
|
121
|
+
user = async (text, options) => {
|
|
122
|
+
const user = options?.user;
|
|
123
|
+
const markup = new Keyboard().requestUsers("\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u{1F50D}", randomInteger(0, 999999), {
|
|
124
|
+
max_quantity: 1,
|
|
125
|
+
request_name: user?.requestName,
|
|
126
|
+
request_photo: user?.requestPhoto,
|
|
127
|
+
request_username: user?.requestUsername ?? true,
|
|
128
|
+
user_is_bot: user?.isBot,
|
|
129
|
+
user_is_premium: user?.isPremium
|
|
130
|
+
}).oneTime(true).resized(true);
|
|
131
|
+
const message = await this.reply(text, { ...options, markup });
|
|
132
|
+
const answer = await this.conversation.waitFor(":users_shared");
|
|
133
|
+
this.updateCtx(answer);
|
|
134
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
135
|
+
});
|
|
136
|
+
return answer.msg.users_shared?.users[0];
|
|
137
|
+
};
|
|
138
|
+
users = async (text, options) => {
|
|
139
|
+
const users = options?.users;
|
|
140
|
+
const markup = new Keyboard().requestUsers("\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u0439 \u{1F50D}", randomInteger(0, 999999), {
|
|
141
|
+
max_quantity: users?.maxQuantity,
|
|
142
|
+
request_name: users?.requestName,
|
|
143
|
+
request_photo: users?.requestPhoto,
|
|
144
|
+
request_username: users?.requestUsername ?? true,
|
|
145
|
+
user_is_bot: users?.isBot,
|
|
146
|
+
user_is_premium: users?.isPremium
|
|
147
|
+
}).oneTime(true).resized(true);
|
|
148
|
+
const message = await this.reply(text, { ...options, markup });
|
|
149
|
+
const answer = await this.conversation.waitFor(":users_shared");
|
|
150
|
+
this.updateCtx(answer);
|
|
151
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
152
|
+
});
|
|
153
|
+
return answer.msg.users_shared;
|
|
154
|
+
};
|
|
155
|
+
contact = async (text, options) => {
|
|
156
|
+
const markup = new Keyboard().requestContact("\u041F\u043E\u0434\u0435\u043B\u0438\u0442\u044C\u0441\u044F \u043A\u043E\u043D\u0442\u0430\u043A\u0442\u043E\u043C \u{1F4DE}").oneTime(true).resized(true);
|
|
157
|
+
const message = await this.reply(text, { ...options, markup });
|
|
158
|
+
const answer = await this.conversation.waitFor(":contact");
|
|
159
|
+
this.updateCtx(answer);
|
|
160
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
161
|
+
});
|
|
162
|
+
return answer.msg.contact;
|
|
163
|
+
};
|
|
164
|
+
location = async (text, options) => {
|
|
165
|
+
const markup = new Keyboard().requestLocation("\u041F\u043E\u0434\u0435\u043B\u0438\u0442\u044C\u0441\u044F \u0433\u0435\u043E\u043F\u043E\u0437\u0438\u0446\u0438\u0435\u0439 \u{1F4CD}").oneTime(true).resized(true);
|
|
166
|
+
const message = await this.reply(text, { ...options, markup });
|
|
167
|
+
const answer = await this.conversation.waitFor(":location");
|
|
168
|
+
this.updateCtx(answer);
|
|
169
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
170
|
+
});
|
|
171
|
+
return answer.msg.location;
|
|
172
|
+
};
|
|
173
|
+
poll = async (text, options) => {
|
|
174
|
+
const pollType = options?.poll?.type;
|
|
175
|
+
const markup = new Keyboard().requestPoll("\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u043F\u0440\u043E\u0441 \u{1F4CA}", pollType).oneTime(true).resized(true);
|
|
176
|
+
const message = await this.reply(text, { ...options, markup });
|
|
177
|
+
const answer = await this.conversation.waitFor(":poll");
|
|
178
|
+
this.updateCtx(answer);
|
|
179
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
180
|
+
});
|
|
181
|
+
return answer.msg.poll;
|
|
182
|
+
};
|
|
183
|
+
renderPaginationButtons = (pagesCount, currentPage, markup, columnCount, extraControls) => {
|
|
184
|
+
const oddColumnCount = columnCount % 2 === 0 ? columnCount + 1 : columnCount;
|
|
185
|
+
const emptyColumns = oddColumnCount >= 5 ? extraControls ? 0 : 1 : 0;
|
|
186
|
+
for (let i = 0; i < emptyColumns; i++) {
|
|
187
|
+
markup.text(" ", " ");
|
|
188
|
+
}
|
|
189
|
+
if (extraControls) {
|
|
190
|
+
markup.text(currentPage > 2 ? "\u22D8" : " ", "first");
|
|
191
|
+
}
|
|
192
|
+
markup.text(currentPage > 1 ? "\u2190" : " ", "prev");
|
|
193
|
+
markup.text(currentPage + " / " + pagesCount, "page");
|
|
194
|
+
markup.text(currentPage < pagesCount ? "\u2192" : " ", "next");
|
|
195
|
+
if (extraControls) {
|
|
196
|
+
markup.text(currentPage < pagesCount - 1 ? "\u22D9" : " ", "last");
|
|
197
|
+
}
|
|
198
|
+
for (let i = 0; i < emptyColumns; i++) {
|
|
199
|
+
markup.text(" ", " ");
|
|
200
|
+
}
|
|
201
|
+
return markup.row();
|
|
202
|
+
};
|
|
203
|
+
resetChoice = (session) => {
|
|
204
|
+
if (!session._gx.conversations) session._gx.conversations = {
|
|
205
|
+
currentChoices: /* @__PURE__ */ new Set(),
|
|
206
|
+
currentPage: 1
|
|
207
|
+
};
|
|
208
|
+
else {
|
|
209
|
+
session._gx.conversations.currentChoices = /* @__PURE__ */ new Set();
|
|
210
|
+
session._gx.conversations.currentPage = 1;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
async basicChoice(text, choices, multi = false, options, inProgress) {
|
|
214
|
+
const markup = new InlineKeyboard();
|
|
215
|
+
const session = await this.conversation.external(
|
|
216
|
+
() => this.ctx.session
|
|
217
|
+
);
|
|
218
|
+
if (!session._gx.conversations) this.resetChoice(session);
|
|
219
|
+
await this.conversation.external(() => !inProgress && this.resetChoice(session));
|
|
220
|
+
let currentRow = [];
|
|
221
|
+
const columnCount = options?.columnCount ?? this.config.columnCount ?? 1;
|
|
222
|
+
const rowCount = options?.rowCount ?? this.config.rowCount ?? 3;
|
|
223
|
+
const filledChoices = options?.pagination?.enabled ? choices.concat(
|
|
224
|
+
Array.from({ length: columnCount * rowCount - choices.length % (columnCount * rowCount) }).map(
|
|
225
|
+
() => [" ", "empty"]
|
|
226
|
+
)
|
|
227
|
+
) : choices;
|
|
228
|
+
const pagesCount = Math.ceil(choices.length / (columnCount * rowCount));
|
|
229
|
+
const currentPage = session._gx.conversations.currentPage ?? 1;
|
|
230
|
+
let currentRowId = 0;
|
|
231
|
+
for (const [choiceId, choice] of filledChoices.entries()) {
|
|
232
|
+
if (options?.pagination?.enabled && choiceId < (currentPage - 1) * columnCount * rowCount) continue;
|
|
233
|
+
const checked = session._gx.conversations.currentChoices.has(choice[1]);
|
|
234
|
+
currentRow.push([`${choice[0]}${checked ? " \u2705" : ""}`, choice[1]]);
|
|
235
|
+
if (currentRow.length === columnCount) {
|
|
236
|
+
currentRow.forEach(
|
|
237
|
+
([label, value]) => markup.text(label, value == "empty" ? "empty" : `answer:${value}`)
|
|
238
|
+
);
|
|
239
|
+
markup.row();
|
|
240
|
+
currentRow = [];
|
|
241
|
+
currentRowId++;
|
|
242
|
+
if (options?.pagination?.enabled && (currentRowId === rowCount || choiceId === filledChoices.length - 1)) {
|
|
243
|
+
this.renderPaginationButtons(
|
|
244
|
+
pagesCount,
|
|
245
|
+
currentPage,
|
|
246
|
+
markup,
|
|
247
|
+
columnCount,
|
|
248
|
+
options?.pagination?.extraControls ?? false
|
|
249
|
+
);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (currentRow.length > 0) {
|
|
255
|
+
currentRow.forEach(([label, value]) => {
|
|
256
|
+
markup.text(label, `answer:${value}`);
|
|
257
|
+
});
|
|
258
|
+
markup.row();
|
|
259
|
+
}
|
|
260
|
+
const additionalTriggers = this.getCallbackDataFromKeyboard(options?.markup);
|
|
261
|
+
if (options?.markup instanceof InlineKeyboard) markup.append(options.markup).row();
|
|
262
|
+
const sendContinueButton = session._gx.conversations.currentChoices.size > 0;
|
|
263
|
+
const continueInlineEnd = options?.continueInlineEnd ?? this.config.continueInlineEnd;
|
|
264
|
+
if (multi && !options?.noChoiceAllowed && !continueInlineEnd && sendContinueButton)
|
|
265
|
+
markup.text(options?.continueButton ?? this.config?.continueButton ?? "Continue", "continue").row();
|
|
266
|
+
const messageText = typeof text === "function" ? await text(
|
|
267
|
+
Array.from(session._gx.conversations.currentChoices),
|
|
268
|
+
session._gx.conversations.currentPage,
|
|
269
|
+
pagesCount
|
|
270
|
+
) : text;
|
|
271
|
+
const message = await this.reply(messageText, {
|
|
272
|
+
...options,
|
|
273
|
+
markup,
|
|
274
|
+
continueButton: options?.continueButton,
|
|
275
|
+
continueInlineEnd,
|
|
276
|
+
sendContinueButton: continueInlineEnd && sendContinueButton
|
|
277
|
+
});
|
|
278
|
+
let additionalTriggerCalled;
|
|
279
|
+
let answer;
|
|
280
|
+
const paginationsCallbacks = ["prev", "next", "page", "empty", "first", "last"];
|
|
281
|
+
do {
|
|
282
|
+
answer = await this.conversation.waitForCallbackQuery([
|
|
283
|
+
...choices.map((c) => `answer:${c[1]}`),
|
|
284
|
+
...multi ? ["continue"] : [],
|
|
285
|
+
...options?.pagination?.enabled ? paginationsCallbacks : [],
|
|
286
|
+
...additionalTriggers
|
|
287
|
+
]);
|
|
288
|
+
this.updateCtx(answer);
|
|
289
|
+
const callbackData2 = answer.callbackQuery.data;
|
|
290
|
+
additionalTriggerCalled = additionalTriggers.includes(callbackData2);
|
|
291
|
+
if (callbackData2 == "continue" || additionalTriggerCalled) {
|
|
292
|
+
const choices2 = Array.from(session._gx.conversations.currentChoices);
|
|
293
|
+
this.ctx.session._gx.conversations.currentChoices = /* @__PURE__ */ new Set();
|
|
294
|
+
if (additionalTriggerCalled) return { result: choices2, callbackQuery: callbackData2, message };
|
|
295
|
+
return { result: choices2, message };
|
|
296
|
+
}
|
|
297
|
+
} while (additionalTriggerCalled);
|
|
298
|
+
const questionData = session._gx.conversations;
|
|
299
|
+
const callbackData = answer.callbackQuery.data;
|
|
300
|
+
const data = callbackData.split(":")[1];
|
|
301
|
+
const skip = await this.conversation.external(async () => {
|
|
302
|
+
if (callbackData == "prev") questionData.currentPage = Math.max(currentPage - 1, 1);
|
|
303
|
+
if (callbackData == "next") questionData.currentPage = Math.min(currentPage + 1, pagesCount);
|
|
304
|
+
if (callbackData == "first") questionData.currentPage = 1;
|
|
305
|
+
if (callbackData == "last") questionData.currentPage = pagesCount;
|
|
306
|
+
if (data) {
|
|
307
|
+
if (questionData.currentChoices.has(data)) questionData.currentChoices.delete(data);
|
|
308
|
+
else questionData.currentChoices.add(data);
|
|
309
|
+
}
|
|
310
|
+
;
|
|
311
|
+
this.ctx.session._gx.conversations = questionData;
|
|
312
|
+
if (paginationsCallbacks.includes(callbackData) && questionData.currentPage == currentPage) {
|
|
313
|
+
await this.ctx.answerCallbackQuery();
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
if (skip) return this.conversation.skip({ drop: true });
|
|
318
|
+
if (!multi && data) {
|
|
319
|
+
;
|
|
320
|
+
this.ctx.session._gx.conversations.currentChoices = /* @__PURE__ */ new Set();
|
|
321
|
+
return {
|
|
322
|
+
result: data,
|
|
323
|
+
message
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return this.basicChoice(text, choices, multi, options, true);
|
|
327
|
+
}
|
|
328
|
+
choice = async (text, choices, options) => this.basicChoice(text, choices, false, options);
|
|
329
|
+
multi = async (text, choices, options) => this.basicChoice(text, choices, true, options);
|
|
330
|
+
boolean = async (text, yesNoStrings, options) => {
|
|
331
|
+
const { result, message } = await this.choice(
|
|
332
|
+
text,
|
|
333
|
+
[
|
|
334
|
+
[yesNoStrings?.[0] ?? "\u2705 \u0414\u0430", "true"],
|
|
335
|
+
[yesNoStrings?.[1] ?? "\u274C \u041D\u0435\u0442", "false"]
|
|
336
|
+
],
|
|
337
|
+
{ columnCount: 2, ...options }
|
|
338
|
+
);
|
|
339
|
+
const boolResult = result === "true";
|
|
340
|
+
return { result: boolResult, message };
|
|
341
|
+
};
|
|
342
|
+
photo = async (text, options) => {
|
|
343
|
+
const message = await this.reply(text, options);
|
|
344
|
+
const answer = await this.conversation.waitFor(":photo");
|
|
345
|
+
this.updateCtx(answer);
|
|
346
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
347
|
+
});
|
|
348
|
+
const photo = answer.msg.photo?.[0];
|
|
349
|
+
if (!photo) {
|
|
350
|
+
throw new Error("No photo found in update");
|
|
351
|
+
}
|
|
352
|
+
return photo.file_id;
|
|
353
|
+
};
|
|
354
|
+
file = async (text, options) => {
|
|
355
|
+
const message = await this.reply(text, options);
|
|
356
|
+
const answer = await this.conversation.waitFor(":file");
|
|
357
|
+
this.updateCtx(answer);
|
|
358
|
+
await answer?.msg?.delete?.().catch?.(() => {
|
|
359
|
+
});
|
|
360
|
+
return answer.msg.document?.file_id;
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// src/conversation.ts
|
|
365
|
+
import { createConversation } from "@grammyjs/conversations";
|
|
366
|
+
import { hydrate } from "@grammyjs/hydrate";
|
|
367
|
+
import { GlobalMenuRegistry as GlobalMenuRegistry2, getGrammyXOptions } from "@grammy-x/core";
|
|
368
|
+
function createCustomConversation(builder, config) {
|
|
369
|
+
let cfg = typeof config === "string" ? { id: config } : config;
|
|
370
|
+
cfg = cfg ?? {};
|
|
371
|
+
const id = cfg.id ?? builder.name;
|
|
372
|
+
cfg.id = id;
|
|
373
|
+
GlobalMenuRegistry2.register({
|
|
374
|
+
menuName: id,
|
|
375
|
+
send: async (ctx) => {
|
|
376
|
+
if (!ctx.conversation) {
|
|
377
|
+
console.error(`[Grammy-X] Cannot enter conversation '${id}': ctx.conversation is undefined. Make sure bot.use(conversations()) is registered before components that use it.`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (ctx.conversation.active) {
|
|
381
|
+
await ctx.conversation.exit();
|
|
382
|
+
}
|
|
383
|
+
return ctx.conversation.enter(id);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return createConversation(
|
|
387
|
+
(async (conversation, ctx) => {
|
|
388
|
+
await conversation.run(hydrate());
|
|
389
|
+
const gx = ctx.session?._gx;
|
|
390
|
+
if (gx && gx.history[gx.history.length - 1] !== id) {
|
|
391
|
+
gx.history.push(id);
|
|
392
|
+
if (gx.history.length > 20) gx.history.shift();
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
return await builder(conversation, ctx);
|
|
396
|
+
} catch (e) {
|
|
397
|
+
if (e.name === "CommandInterruptError") {
|
|
398
|
+
const cmd = e.message.split(":")[1];
|
|
399
|
+
const opts = getGrammyXOptions(e.ctx);
|
|
400
|
+
const targetMenu = opts?.interruptCommands?.[cmd];
|
|
401
|
+
if (targetMenu) {
|
|
402
|
+
try {
|
|
403
|
+
if (typeof targetMenu === "string") {
|
|
404
|
+
await e.ctx.menu.nav(targetMenu);
|
|
405
|
+
} else if (typeof targetMenu.send === "function") {
|
|
406
|
+
await targetMenu.send(e.ctx);
|
|
407
|
+
}
|
|
408
|
+
} catch (err) {
|
|
409
|
+
console.error(`Could not auto-nav to target menu for command ${cmd}`, err);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
throw e;
|
|
415
|
+
}
|
|
416
|
+
}),
|
|
417
|
+
cfg
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
export {
|
|
421
|
+
QuestionHelper,
|
|
422
|
+
createCustomConversation
|
|
423
|
+
};
|
package/package.json
CHANGED
|
@@ -1,19 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grammy-x/conversations",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
|
|
4
5
|
"type": "module",
|
|
5
|
-
|
|
6
|
-
"
|
|
6
|
+
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
7
19
|
"private": false,
|
|
20
|
+
|
|
8
21
|
"publishConfig": {
|
|
9
|
-
"access": "
|
|
22
|
+
"access": "public"
|
|
10
23
|
},
|
|
24
|
+
|
|
11
25
|
"dependencies": {
|
|
12
|
-
"@grammy-x/core": "0.1
|
|
26
|
+
"@grammy-x/core": "0.2.1"
|
|
13
27
|
},
|
|
28
|
+
|
|
14
29
|
"peerDependencies": {
|
|
15
30
|
"grammy": "^1.24.0",
|
|
16
31
|
"@grammyjs/conversations": "^1",
|
|
17
32
|
"@grammyjs/hydrate": "^1.4.1"
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean"
|
|
18
41
|
}
|
|
19
|
-
}
|
|
42
|
+
}
|
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