ultra-telegram-framework 1.0.3
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/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/adapters/gas.d.ts +10 -0
- package/dist/adapters/gas.js +90 -0
- package/dist/adapters/node.d.ts +52 -0
- package/dist/adapters/node.js +150 -0
- package/dist/adapters/web.d.ts +4 -0
- package/dist/adapters/web.js +90 -0
- package/dist/core/base-api.d.ts +40 -0
- package/dist/core/base-api.js +26 -0
- package/dist/core/bot.d.ts +1828 -0
- package/dist/core/bot.js +2753 -0
- package/dist/core/composer.d.ts +87 -0
- package/dist/core/composer.js +164 -0
- package/dist/core/context/base-context.d.ts +24 -0
- package/dist/core/context/base-context.js +56 -0
- package/dist/core/context/reply-context.d.ts +234 -0
- package/dist/core/context/reply-context.js +528 -0
- package/dist/core/context.d.ts +8 -0
- package/dist/core/context.js +34 -0
- package/dist/core/keyboard.d.ts +76 -0
- package/dist/core/keyboard.js +182 -0
- package/dist/core/menu.d.ts +58 -0
- package/dist/core/menu.js +87 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +24 -0
- package/dist/scenes/scene-manager.d.ts +39 -0
- package/dist/scenes/scene-manager.js +65 -0
- package/dist/scenes/stage.d.ts +8 -0
- package/dist/scenes/stage.js +34 -0
- package/dist/scenes/wizard.d.ts +18 -0
- package/dist/scenes/wizard.js +32 -0
- package/dist/session/gas-cache-storage.d.ts +34 -0
- package/dist/session/gas-cache-storage.js +49 -0
- package/dist/session/gas-hybrid-storage.d.ts +27 -0
- package/dist/session/gas-hybrid-storage.js +49 -0
- package/dist/session/gas-storage.d.ts +24 -0
- package/dist/session/gas-storage.js +47 -0
- package/dist/session/index.d.ts +28 -0
- package/dist/session/index.js +43 -0
- package/dist/session/memory-storage.d.ts +28 -0
- package/dist/session/memory-storage.js +35 -0
- package/dist/session/storage.d.ts +18 -0
- package/dist/session/storage.js +2 -0
- package/dist/types/telegram.d.ts +7560 -0
- package/dist/types/telegram.js +4 -0
- package/package.json +42 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract class containing common logic for all keyboard types.
|
|
3
|
+
* T is the button type (InlineKeyboardButton or KeyboardButton).
|
|
4
|
+
*/
|
|
5
|
+
export class BaseKeyboard {
|
|
6
|
+
constructor() {
|
|
7
|
+
// Protected property (available in child classes)
|
|
8
|
+
this.matrix = [[]];
|
|
9
|
+
}
|
|
10
|
+
/** Starts a new row */
|
|
11
|
+
row() {
|
|
12
|
+
this.matrix.push([]);
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Adds a custom emoji to the LAST added button.
|
|
17
|
+
* @param emojiId Unique emoji ID (available for Premium bots)
|
|
18
|
+
*/
|
|
19
|
+
customEmoji(emojiId) {
|
|
20
|
+
const lastButton = this.getLastButton();
|
|
21
|
+
if (lastButton)
|
|
22
|
+
lastButton.icon_custom_emoji_id = emojiId;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sets the style for the LAST added button.
|
|
27
|
+
* @param style 'danger' (red) | 'success' (green) | 'primary' (blue)
|
|
28
|
+
*/
|
|
29
|
+
style(style) {
|
|
30
|
+
const lastButton = this.getLastButton();
|
|
31
|
+
if (lastButton)
|
|
32
|
+
lastButton.style = style;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
// =====================================
|
|
36
|
+
// INTERNAL HELPERS
|
|
37
|
+
// =====================================
|
|
38
|
+
addButton(button) {
|
|
39
|
+
const currentRow = this.matrix[this.matrix.length - 1];
|
|
40
|
+
if (currentRow) {
|
|
41
|
+
currentRow.push(button);
|
|
42
|
+
}
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
getLastButton() {
|
|
46
|
+
const currentRow = this.matrix[this.matrix.length - 1];
|
|
47
|
+
if (!currentRow || currentRow.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
return currentRow[currentRow.length - 1];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// INLINE KEYBOARD (Buttons under the message)
|
|
54
|
+
// ============================================================================
|
|
55
|
+
export class InlineKeyboard extends BaseKeyboard {
|
|
56
|
+
text(text, callback_data) {
|
|
57
|
+
return this.addButton({ text, callback_data });
|
|
58
|
+
}
|
|
59
|
+
url(text, url) {
|
|
60
|
+
return this.addButton({ text, url });
|
|
61
|
+
}
|
|
62
|
+
webApp(text, webAppUrl) {
|
|
63
|
+
return this.addButton({ text, web_app: { url: webAppUrl } });
|
|
64
|
+
}
|
|
65
|
+
loginUrl(text, login_url) {
|
|
66
|
+
return this.addButton({ text, login_url });
|
|
67
|
+
}
|
|
68
|
+
switchInlineQuery(text, query = '') {
|
|
69
|
+
return this.addButton({ text, switch_inline_query: query });
|
|
70
|
+
}
|
|
71
|
+
switchInlineCurrentChat(text, query = '') {
|
|
72
|
+
return this.addButton({ text, switch_inline_query_current_chat: query });
|
|
73
|
+
}
|
|
74
|
+
switchInlineChosenChat(text, chosen_chat) {
|
|
75
|
+
return this.addButton({ text, switch_inline_query_chosen_chat: chosen_chat });
|
|
76
|
+
}
|
|
77
|
+
copyText(text, copy_text) {
|
|
78
|
+
return this.addButton({ text, copy_text });
|
|
79
|
+
}
|
|
80
|
+
game(text, callback_game = {}) {
|
|
81
|
+
return this.addButton({ text, callback_game });
|
|
82
|
+
}
|
|
83
|
+
pay(text) {
|
|
84
|
+
return this.addButton({ text, pay: true });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Special button for switching between InlineMenu pages
|
|
88
|
+
* @param text Button text
|
|
89
|
+
* @param menuId Menu ID
|
|
90
|
+
* @param pageId ID of the page to navigate to
|
|
91
|
+
*/
|
|
92
|
+
menu(text, menuId, pageId) {
|
|
93
|
+
return this.addButton({ text, callback_data: `menu:${menuId}:${pageId}` });
|
|
94
|
+
}
|
|
95
|
+
/** Builds the final object (uses this.matrix from the base class) */
|
|
96
|
+
build() {
|
|
97
|
+
const cleanKeyboard = this.matrix.filter(row => row.length > 0);
|
|
98
|
+
return { inline_keyboard: cleanKeyboard };
|
|
99
|
+
}
|
|
100
|
+
/** Allows automatic serialization of the object to JSON */
|
|
101
|
+
toJSON() {
|
|
102
|
+
return this.build();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// REPLY KEYBOARD (Buttons instead of the phone keyboard)
|
|
107
|
+
// ============================================================================
|
|
108
|
+
export class ReplyKeyboard extends BaseKeyboard {
|
|
109
|
+
constructor() {
|
|
110
|
+
super(...arguments);
|
|
111
|
+
this.options = {};
|
|
112
|
+
}
|
|
113
|
+
/** Adds a regular text button */
|
|
114
|
+
text(text) {
|
|
115
|
+
return this.addButton({ text });
|
|
116
|
+
}
|
|
117
|
+
requestContact(text) {
|
|
118
|
+
return this.addButton({ text, request_contact: true });
|
|
119
|
+
}
|
|
120
|
+
requestLocation(text) {
|
|
121
|
+
return this.addButton({ text, request_location: true });
|
|
122
|
+
}
|
|
123
|
+
requestUsers(text, request_users) {
|
|
124
|
+
return this.addButton({ text, request_users });
|
|
125
|
+
}
|
|
126
|
+
requestChat(text, request_chat) {
|
|
127
|
+
return this.addButton({ text, request_chat });
|
|
128
|
+
}
|
|
129
|
+
requestPoll(text, request_poll = {}) {
|
|
130
|
+
return this.addButton({ text, request_poll });
|
|
131
|
+
}
|
|
132
|
+
requestManagedBot(text, request_managed_bot) {
|
|
133
|
+
return this.addButton({ text, request_managed_bot });
|
|
134
|
+
}
|
|
135
|
+
webApp(text, webAppUrl) {
|
|
136
|
+
return this.addButton({ text, web_app: { url: webAppUrl } });
|
|
137
|
+
}
|
|
138
|
+
// =====================================
|
|
139
|
+
// KEYBOARD SETTINGS
|
|
140
|
+
// =====================================
|
|
141
|
+
persistent(is_persistent = true) {
|
|
142
|
+
this.options.is_persistent = is_persistent;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
selective(is_selective = true) {
|
|
146
|
+
this.options.selective = is_selective;
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
resized(is_resized = true) {
|
|
150
|
+
this.options.resize_keyboard = is_resized;
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
oneTime(is_one_time = true) {
|
|
154
|
+
this.options.one_time_keyboard = is_one_time;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
placeholder(text) {
|
|
158
|
+
this.options.input_field_placeholder = text;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
/** Builds the final object (uses this.matrix from the base class) */
|
|
162
|
+
build() {
|
|
163
|
+
const cleanKeyboard = this.matrix.filter(row => row.length > 0);
|
|
164
|
+
return {
|
|
165
|
+
keyboard: cleanKeyboard,
|
|
166
|
+
...this.options
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Allows automatic serialization of the object to JSON */
|
|
170
|
+
toJSON() {
|
|
171
|
+
return this.build();
|
|
172
|
+
}
|
|
173
|
+
// =====================================
|
|
174
|
+
// STATIC HELPERS
|
|
175
|
+
// =====================================
|
|
176
|
+
static remove(selective) {
|
|
177
|
+
return { remove_keyboard: true, selective };
|
|
178
|
+
}
|
|
179
|
+
static forceReply(selective, placeholder) {
|
|
180
|
+
return { force_reply: true, selective, input_field_placeholder: placeholder };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Context } from './context';
|
|
2
|
+
import { Composer, Middleware } from './composer';
|
|
3
|
+
import { InlineKeyboard } from './keyboard';
|
|
4
|
+
declare module './context' {
|
|
5
|
+
interface Context {
|
|
6
|
+
menu?: InlineMenuManager<any>;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export type MenuPageOptions = {
|
|
10
|
+
/** ID of the page the "Back" button leads to. If not specified, the button will not be shown. */
|
|
11
|
+
back?: string;
|
|
12
|
+
/** Text for the "Back" button (default is "⬅️ Back") */
|
|
13
|
+
backText?: string;
|
|
14
|
+
};
|
|
15
|
+
export type MenuPageRender<C extends Context> = (ctx: C) => Promise<{
|
|
16
|
+
text: string;
|
|
17
|
+
keyboard: InlineKeyboard;
|
|
18
|
+
}> | {
|
|
19
|
+
text: string;
|
|
20
|
+
keyboard: InlineKeyboard;
|
|
21
|
+
};
|
|
22
|
+
export type PageEntry<C extends Context> = {
|
|
23
|
+
render: MenuPageRender<C>;
|
|
24
|
+
options?: MenuPageOptions;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Class for creating interactive Inline menus.
|
|
28
|
+
*/
|
|
29
|
+
export declare class InlineMenu<C extends Context = Context> extends Composer<C> {
|
|
30
|
+
private pages;
|
|
31
|
+
private id;
|
|
32
|
+
constructor(id?: string);
|
|
33
|
+
/**
|
|
34
|
+
* Add a page to the menu
|
|
35
|
+
* @param id Unique page ID
|
|
36
|
+
* @param renderer Function that returns text and keyboard
|
|
37
|
+
* @param options Page settings (e.g., Back button)
|
|
38
|
+
*/
|
|
39
|
+
page(id: string, renderer: MenuPageRender<C>, options?: MenuPageOptions): this;
|
|
40
|
+
/**
|
|
41
|
+
* Main menu middleware
|
|
42
|
+
*/
|
|
43
|
+
middleware(): Middleware<C>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Manager for controlling menus inside handlers
|
|
47
|
+
*/
|
|
48
|
+
export declare class InlineMenuManager<C extends Context = Context> {
|
|
49
|
+
private ctx;
|
|
50
|
+
private pages;
|
|
51
|
+
private menuId;
|
|
52
|
+
constructor(ctx: C, pages: Map<string, PageEntry<C>>, menuId: string);
|
|
53
|
+
/**
|
|
54
|
+
* Go to the specified menu page
|
|
55
|
+
*/
|
|
56
|
+
setPage(pageId: string): Promise<void>;
|
|
57
|
+
url(pageId: string): string;
|
|
58
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Composer } from './composer';
|
|
2
|
+
/**
|
|
3
|
+
* Class for creating interactive Inline menus.
|
|
4
|
+
*/
|
|
5
|
+
export class InlineMenu extends Composer {
|
|
6
|
+
constructor(id = 'main') {
|
|
7
|
+
super();
|
|
8
|
+
this.pages = new Map();
|
|
9
|
+
this.id = id;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Add a page to the menu
|
|
13
|
+
* @param id Unique page ID
|
|
14
|
+
* @param renderer Function that returns text and keyboard
|
|
15
|
+
* @param options Page settings (e.g., Back button)
|
|
16
|
+
*/
|
|
17
|
+
page(id, renderer, options) {
|
|
18
|
+
this.pages.set(id, { render: renderer, options });
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Main menu middleware
|
|
23
|
+
*/
|
|
24
|
+
middleware() {
|
|
25
|
+
const composerHandler = super.middleware();
|
|
26
|
+
return async (ctx, next) => {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
// 1. Hydrate context with the menu manager
|
|
29
|
+
ctx.menu = new InlineMenuManager(ctx, this.pages, this.id);
|
|
30
|
+
// 2. First pass through Composer handlers (.action(), etc.)
|
|
31
|
+
let handledByComposer = false;
|
|
32
|
+
await composerHandler(ctx, async () => {
|
|
33
|
+
handledByComposer = true;
|
|
34
|
+
});
|
|
35
|
+
// If Composer caught the event - do not proceed further
|
|
36
|
+
if (!handledByComposer)
|
|
37
|
+
return;
|
|
38
|
+
// 3. If no handler found - check page navigation
|
|
39
|
+
if ((_b = (_a = ctx.callbackQuery) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.startsWith(`menu:${this.id}:`)) {
|
|
40
|
+
const pageId = ctx.callbackQuery.data.split(':')[2];
|
|
41
|
+
if (pageId) {
|
|
42
|
+
await ctx.menu.setPage(pageId);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
await next();
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Manager for controlling menus inside handlers
|
|
52
|
+
*/
|
|
53
|
+
export class InlineMenuManager {
|
|
54
|
+
constructor(ctx, pages, menuId) {
|
|
55
|
+
this.ctx = ctx;
|
|
56
|
+
this.pages = pages;
|
|
57
|
+
this.menuId = menuId;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Go to the specified menu page
|
|
61
|
+
*/
|
|
62
|
+
async setPage(pageId) {
|
|
63
|
+
var _a;
|
|
64
|
+
const entry = this.pages.get(pageId);
|
|
65
|
+
if (!entry)
|
|
66
|
+
throw new Error(`Menu page "${pageId}" not found.`);
|
|
67
|
+
const { text, keyboard } = await Promise.resolve(entry.render(this.ctx));
|
|
68
|
+
// If a "parent" page is specified in the page settings, add a Back button
|
|
69
|
+
if ((_a = entry.options) === null || _a === void 0 ? void 0 : _a.back) {
|
|
70
|
+
keyboard.row().menu(entry.options.backText || '⬅️ Back', this.menuId, entry.options.back);
|
|
71
|
+
}
|
|
72
|
+
const rawKeyboard = keyboard.toJSON();
|
|
73
|
+
if (this.ctx.callbackQuery) {
|
|
74
|
+
// answerCbQuery is executed together with editMessage to guarantee removing the loading indicator
|
|
75
|
+
await Promise.all([
|
|
76
|
+
this.ctx.editMessage(text, { reply_markup: rawKeyboard }),
|
|
77
|
+
this.ctx.answerCbQuery()
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
await this.ctx.reply(text, { reply_markup: rawKeyboard });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
url(pageId) {
|
|
85
|
+
return `menu:${this.menuId}:${pageId}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @link https://volodymyrdzola.github.io/tg-framework/
|
|
3
|
+
*/
|
|
4
|
+
export * from './core/bot';
|
|
5
|
+
export * from './core/composer';
|
|
6
|
+
export * from './core/context';
|
|
7
|
+
export * from './core/base-api';
|
|
8
|
+
export * from './core/keyboard';
|
|
9
|
+
export * from './core/menu';
|
|
10
|
+
export * from './adapters/gas';
|
|
11
|
+
export * from './adapters/node';
|
|
12
|
+
export * from './adapters/web';
|
|
13
|
+
export * from './session/index';
|
|
14
|
+
export * from './scenes/stage';
|
|
15
|
+
export * from './scenes/wizard';
|
|
16
|
+
export * from './scenes/scene-manager';
|
|
17
|
+
export * from './types/telegram';
|
|
18
|
+
export * from './core/bot';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @link https://volodymyrdzola.github.io/tg-framework/
|
|
3
|
+
*/
|
|
4
|
+
// CORE
|
|
5
|
+
export * from './core/bot';
|
|
6
|
+
export * from './core/composer';
|
|
7
|
+
export * from './core/context';
|
|
8
|
+
export * from './core/base-api';
|
|
9
|
+
export * from './core/keyboard';
|
|
10
|
+
export * from './core/menu';
|
|
11
|
+
// ADAPTERS
|
|
12
|
+
export * from './adapters/gas';
|
|
13
|
+
export * from './adapters/node';
|
|
14
|
+
export * from './adapters/web';
|
|
15
|
+
// SESSIONS & STORAGE
|
|
16
|
+
export * from './session/index';
|
|
17
|
+
// SCENES
|
|
18
|
+
export * from './scenes/stage';
|
|
19
|
+
export * from './scenes/wizard';
|
|
20
|
+
export * from './scenes/scene-manager';
|
|
21
|
+
// TYPES
|
|
22
|
+
export * from './types/telegram';
|
|
23
|
+
// ALIASES
|
|
24
|
+
export * from './core/bot';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface SceneSessionData {
|
|
2
|
+
name?: string;
|
|
3
|
+
step?: number;
|
|
4
|
+
state?: Record<string, any>;
|
|
5
|
+
}
|
|
6
|
+
export declare class SceneManager {
|
|
7
|
+
private ctx;
|
|
8
|
+
constructor(ctx: {
|
|
9
|
+
session?: Record<string, any>;
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Protected access to the system scene session object.
|
|
13
|
+
*/
|
|
14
|
+
get session(): SceneSessionData;
|
|
15
|
+
/**
|
|
16
|
+
* Storage for temporary data of a specific scene.
|
|
17
|
+
* Cleared when leaving the scene.
|
|
18
|
+
*/
|
|
19
|
+
get state(): Record<string, any>;
|
|
20
|
+
set state(value: Record<string, any>);
|
|
21
|
+
/**
|
|
22
|
+
* Enter a new scene
|
|
23
|
+
* @param name Scene name
|
|
24
|
+
* @param initialState Initial state (optional)
|
|
25
|
+
*/
|
|
26
|
+
enter(name: string, initialState?: Record<string, any>): void;
|
|
27
|
+
/**
|
|
28
|
+
* Leave the current scene (resets FSM)
|
|
29
|
+
*/
|
|
30
|
+
leave(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Go to the next step in the Wizard scene
|
|
33
|
+
*/
|
|
34
|
+
next(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Go to a specific step by its index
|
|
37
|
+
*/
|
|
38
|
+
selectStep(index: number): void;
|
|
39
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/scenes/scene-manager.ts
|
|
2
|
+
export class SceneManager {
|
|
3
|
+
constructor(ctx) {
|
|
4
|
+
this.ctx = ctx;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Protected access to the system scene session object.
|
|
8
|
+
*/
|
|
9
|
+
get session() {
|
|
10
|
+
var _a, _b;
|
|
11
|
+
var _c, _d;
|
|
12
|
+
(_a = (_c = this.ctx).session) !== null && _a !== void 0 ? _a : (_c.session = {});
|
|
13
|
+
(_b = (_d = this.ctx.session).__scene) !== null && _b !== void 0 ? _b : (_d.__scene = {});
|
|
14
|
+
return this.ctx.session.__scene;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Storage for temporary data of a specific scene.
|
|
18
|
+
* Cleared when leaving the scene.
|
|
19
|
+
*/
|
|
20
|
+
get state() {
|
|
21
|
+
var _a;
|
|
22
|
+
var _b;
|
|
23
|
+
(_a = (_b = this.session).state) !== null && _a !== void 0 ? _a : (_b.state = {});
|
|
24
|
+
return this.session.state;
|
|
25
|
+
}
|
|
26
|
+
set state(value) {
|
|
27
|
+
this.session.state = value;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Enter a new scene
|
|
31
|
+
* @param name Scene name
|
|
32
|
+
* @param initialState Initial state (optional)
|
|
33
|
+
*/
|
|
34
|
+
enter(name, initialState = {}) {
|
|
35
|
+
var _a;
|
|
36
|
+
var _b;
|
|
37
|
+
(_a = (_b = this.ctx).session) !== null && _a !== void 0 ? _a : (_b.session = {});
|
|
38
|
+
this.ctx.session.__scene = {
|
|
39
|
+
name,
|
|
40
|
+
step: 0,
|
|
41
|
+
state: initialState,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Leave the current scene (resets FSM)
|
|
46
|
+
*/
|
|
47
|
+
leave() {
|
|
48
|
+
if (this.ctx.session && this.ctx.session.__scene) {
|
|
49
|
+
delete this.ctx.session.__scene;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Go to the next step in the Wizard scene
|
|
54
|
+
*/
|
|
55
|
+
next() {
|
|
56
|
+
var _a;
|
|
57
|
+
this.session.step = ((_a = this.session.step) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Go to a specific step by its index
|
|
61
|
+
*/
|
|
62
|
+
selectStep(index) {
|
|
63
|
+
this.session.step = index;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Composer, Middleware } from '../core/composer';
|
|
2
|
+
import { SceneContext, WizardScene } from './wizard';
|
|
3
|
+
export declare class Stage<C extends SceneContext> extends Composer<C> {
|
|
4
|
+
private scenes;
|
|
5
|
+
constructor(scenes?: WizardScene<C>[]);
|
|
6
|
+
register(scene: WizardScene<C>): void;
|
|
7
|
+
middleware(): Middleware<C>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/scenes/stage.ts
|
|
2
|
+
import { Composer } from '../core/composer';
|
|
3
|
+
import { SceneManager } from './scene-manager';
|
|
4
|
+
export class Stage extends Composer {
|
|
5
|
+
constructor(scenes = []) {
|
|
6
|
+
super();
|
|
7
|
+
this.scenes = new Map();
|
|
8
|
+
scenes.forEach(scene => this.register(scene));
|
|
9
|
+
}
|
|
10
|
+
register(scene) {
|
|
11
|
+
this.scenes.set(scene.name, scene);
|
|
12
|
+
}
|
|
13
|
+
middleware() {
|
|
14
|
+
const globalHandler = super.middleware();
|
|
15
|
+
return async (ctx, next) => {
|
|
16
|
+
// 1. Hydrate context with scene manager
|
|
17
|
+
ctx.scene = new SceneManager(ctx);
|
|
18
|
+
const activeSceneName = ctx.scene.session.name;
|
|
19
|
+
// 2. If the user is NOT in a scene, pass the event to global bot handlers
|
|
20
|
+
if (!activeSceneName) {
|
|
21
|
+
return globalHandler(ctx, next);
|
|
22
|
+
}
|
|
23
|
+
// 3. If the scene is active, find it
|
|
24
|
+
const scene = this.scenes.get(activeSceneName);
|
|
25
|
+
if (!scene) {
|
|
26
|
+
// Protection against broken sessions: if the scene has been deleted from the code, exit
|
|
27
|
+
ctx.scene.leave();
|
|
28
|
+
return globalHandler(ctx, next);
|
|
29
|
+
}
|
|
30
|
+
// 4. Pass control to the active scene
|
|
31
|
+
await scene.middleware()(ctx, next);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Composer, Middleware } from '../core/composer';
|
|
2
|
+
import { Context } from '../core/context';
|
|
3
|
+
import { SceneManager } from './scene-manager';
|
|
4
|
+
import { SessionData } from '../session';
|
|
5
|
+
export interface SceneContext extends Context {
|
|
6
|
+
session: SessionData;
|
|
7
|
+
scene: SceneManager;
|
|
8
|
+
}
|
|
9
|
+
export declare class WizardScene<C extends SceneContext> extends Composer<C> {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
private steps;
|
|
12
|
+
/**
|
|
13
|
+
* @param name Unique name of the scene
|
|
14
|
+
* @param steps Handler functions for each step (Step 0, Step 1, ...)
|
|
15
|
+
*/
|
|
16
|
+
constructor(name: string, ...steps: Middleware<C>[]);
|
|
17
|
+
middleware(): Middleware<C>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/scenes/wizard.ts
|
|
2
|
+
import { Composer } from '../core/composer';
|
|
3
|
+
export class WizardScene extends Composer {
|
|
4
|
+
/**
|
|
5
|
+
* @param name Unique name of the scene
|
|
6
|
+
* @param steps Handler functions for each step (Step 0, Step 1, ...)
|
|
7
|
+
*/
|
|
8
|
+
constructor(name, ...steps) {
|
|
9
|
+
super();
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.steps = steps;
|
|
12
|
+
}
|
|
13
|
+
middleware() {
|
|
14
|
+
const globalHandler = super.middleware();
|
|
15
|
+
return async (ctx, next) => {
|
|
16
|
+
var _a;
|
|
17
|
+
let handledGlobally = false;
|
|
18
|
+
await globalHandler(ctx, async () => {
|
|
19
|
+
handledGlobally = true;
|
|
20
|
+
});
|
|
21
|
+
if (!handledGlobally)
|
|
22
|
+
return;
|
|
23
|
+
const currentStepIndex = (_a = ctx.scene.session.step) !== null && _a !== void 0 ? _a : 0;
|
|
24
|
+
const stepHandler = this.steps[currentStepIndex];
|
|
25
|
+
if (!stepHandler) {
|
|
26
|
+
ctx.scene.leave();
|
|
27
|
+
return next();
|
|
28
|
+
}
|
|
29
|
+
await stepHandler(ctx, next);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Storage } from './storage';
|
|
2
|
+
/**
|
|
3
|
+
* Session storage for Google Apps Script based on CacheService.
|
|
4
|
+
* Fast, but data lives for a maximum of 6 hours (21600 seconds).
|
|
5
|
+
*/
|
|
6
|
+
export declare class CacheStorage<T> implements Storage<T> {
|
|
7
|
+
private cache;
|
|
8
|
+
private prefix;
|
|
9
|
+
private ttl;
|
|
10
|
+
/**
|
|
11
|
+
* Fast session storage for Google Apps Script based on CacheService.
|
|
12
|
+
* Data lives for a maximum of 6 hours (21600 seconds).
|
|
13
|
+
* @param cache Which cache service to use (ScriptCache by default)
|
|
14
|
+
* @param ttl Time to live in seconds (21600 - 6 hours by default)
|
|
15
|
+
* @param prefix Prefix for keys
|
|
16
|
+
*/
|
|
17
|
+
constructor(cache?: GoogleAppsScript.Cache.Cache, ttl?: number, prefix?: string);
|
|
18
|
+
/**
|
|
19
|
+
* Gets the session.
|
|
20
|
+
* @param key Session key
|
|
21
|
+
*/
|
|
22
|
+
get(key: string): T | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Saves the session.
|
|
25
|
+
* @param key Session key
|
|
26
|
+
* @param value Session value
|
|
27
|
+
*/
|
|
28
|
+
set(key: string, value: T): void;
|
|
29
|
+
/**
|
|
30
|
+
* Deletes the session.
|
|
31
|
+
* @param key Session key
|
|
32
|
+
*/
|
|
33
|
+
delete(key: string): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session storage for Google Apps Script based on CacheService.
|
|
3
|
+
* Fast, but data lives for a maximum of 6 hours (21600 seconds).
|
|
4
|
+
*/
|
|
5
|
+
export class CacheStorage {
|
|
6
|
+
/**
|
|
7
|
+
* Fast session storage for Google Apps Script based on CacheService.
|
|
8
|
+
* Data lives for a maximum of 6 hours (21600 seconds).
|
|
9
|
+
* @param cache Which cache service to use (ScriptCache by default)
|
|
10
|
+
* @param ttl Time to live in seconds (21600 - 6 hours by default)
|
|
11
|
+
* @param prefix Prefix for keys
|
|
12
|
+
*/
|
|
13
|
+
constructor(cache = CacheService.getScriptCache(), ttl = 21600, prefix = 'session:') {
|
|
14
|
+
this.cache = cache;
|
|
15
|
+
this.ttl = ttl;
|
|
16
|
+
this.prefix = prefix;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Gets the session.
|
|
20
|
+
* @param key Session key
|
|
21
|
+
*/
|
|
22
|
+
get(key) {
|
|
23
|
+
const raw = this.cache.get(this.prefix + key);
|
|
24
|
+
if (!raw)
|
|
25
|
+
return undefined;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Saves the session.
|
|
35
|
+
* @param key Session key
|
|
36
|
+
* @param value Session value
|
|
37
|
+
*/
|
|
38
|
+
set(key, value) {
|
|
39
|
+
const raw = JSON.stringify(value);
|
|
40
|
+
this.cache.put(this.prefix + key, raw, this.ttl);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Deletes the session.
|
|
44
|
+
* @param key Session key
|
|
45
|
+
*/
|
|
46
|
+
delete(key) {
|
|
47
|
+
this.cache.remove(this.prefix + key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Storage } from './storage';
|
|
2
|
+
/**
|
|
3
|
+
* Hybrid storage for Google Apps Script (CacheService + PropertiesService).
|
|
4
|
+
* Combines cache speed and properties reliability.
|
|
5
|
+
*/
|
|
6
|
+
export declare class GasHybridStorage<T> implements Storage<T> {
|
|
7
|
+
private cache;
|
|
8
|
+
private properties;
|
|
9
|
+
/**
|
|
10
|
+
* Hybrid storage for Google Apps Script (CacheService + PropertiesService).
|
|
11
|
+
* Combines cache speed and properties reliability.
|
|
12
|
+
* @param options Configuration options
|
|
13
|
+
* @param options.cache Which cache service to use (ScriptCache by default)
|
|
14
|
+
* @param options.properties Which properties service to use (ScriptProperties by default)
|
|
15
|
+
* @param options.ttl Time to live in seconds (21600 - 6 hours by default)
|
|
16
|
+
* @param options.prefix Prefix for keys
|
|
17
|
+
*/
|
|
18
|
+
constructor(options?: {
|
|
19
|
+
cache?: GoogleAppsScript.Cache.Cache;
|
|
20
|
+
properties?: GoogleAppsScript.Properties.Properties;
|
|
21
|
+
ttl?: number;
|
|
22
|
+
prefix?: string;
|
|
23
|
+
});
|
|
24
|
+
get(key: string): Promise<T | undefined>;
|
|
25
|
+
set(key: string, value: T): Promise<void>;
|
|
26
|
+
delete(key: string): Promise<void>;
|
|
27
|
+
}
|