tgplus 1.0.0
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 +52 -0
- package/README.md +1512 -0
- package/dist/adapters.d.ts +46 -0
- package/dist/adapters.js +83 -0
- package/dist/adapters.js.map +1 -0
- package/dist/bot.d.ts +50 -0
- package/dist/bot.js +180 -0
- package/dist/bot.js.map +1 -0
- package/dist/client.d.ts +1139 -0
- package/dist/client.js +529 -0
- package/dist/client.js.map +1 -0
- package/dist/composer.d.ts +88 -0
- package/dist/composer.js +233 -0
- package/dist/composer.js.map +1 -0
- package/dist/context.d.ts +72 -0
- package/dist/context.js +149 -0
- package/dist/context.js.map +1 -0
- package/dist/decorators.d.ts +33 -0
- package/dist/decorators.js +39 -0
- package/dist/decorators.js.map +1 -0
- package/dist/filters.d.ts +19 -0
- package/dist/filters.js +81 -0
- package/dist/filters.js.map +1 -0
- package/dist/i18n.d.ts +30 -0
- package/dist/i18n.js +41 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/keyboard.d.ts +62 -0
- package/dist/keyboard.js +96 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/rateLimit.d.ts +19 -0
- package/dist/rateLimit.js +32 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/richMessage.d.ts +111 -0
- package/dist/richMessage.js +221 -0
- package/dist/richMessage.js.map +1 -0
- package/dist/scenes.d.ts +57 -0
- package/dist/scenes.js +105 -0
- package/dist/scenes.js.map +1 -0
- package/dist/session-redis.d.ts +27 -0
- package/dist/session-redis.js +36 -0
- package/dist/session-redis.js.map +1 -0
- package/dist/session-sqlite.d.ts +29 -0
- package/dist/session-sqlite.js +34 -0
- package/dist/session-sqlite.js.map +1 -0
- package/dist/session.d.ts +30 -0
- package/dist/session.js +35 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +2485 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RichMessage = exports.RichBlocksBuilder = exports.RichHtmlBuilder = exports.RichMarkdownBuilder = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Two ways to build a Rich Message (Bot API 10.1, block API added 10.2):
|
|
6
|
+
*
|
|
7
|
+
* 1. `RichMessage.markdown()` / `.html()` — write "Rich Markdown"/"Rich HTML"
|
|
8
|
+
* text (GFM-flavored markdown: **bold**, ==marked==, ||spoiler||, `- [ ]`
|
|
9
|
+
* task items, GFM tables, etc. — see the official docs for the full
|
|
10
|
+
* syntax). Simplest for AI-generated or hand-written content. Inline
|
|
11
|
+
* photos here (`.photo()`) use Rich Markdown's own `` image
|
|
12
|
+
* syntax directly in the text — confirmed working against a real server.
|
|
13
|
+
* 2. `RichMessage.blocks()` — build the structured block tree directly
|
|
14
|
+
* (Bot API 10.2+), for when you want guaranteed structure (e.g. a table
|
|
15
|
+
* with real colspan/rowspan) rather than round-tripping through text.
|
|
16
|
+
* Media blocks (photo/video/audio/animation/voice_note) reference a
|
|
17
|
+
* separately-declared media item by id — confirmed via two live 400
|
|
18
|
+
* errors that Telegram rejects both a missing `id` on the media item and
|
|
19
|
+
* an inline file where it expects an `{ id }` reference. Use `.photo()`/
|
|
20
|
+
* `.video()`/etc. here (not the markdown builder's `.photo()`) and the id
|
|
21
|
+
* bookkeeping is handled for you.
|
|
22
|
+
*
|
|
23
|
+
* Both produce an `InputRichMessage` for `ctx.replyRich()` / `api.sendRichMessage()`.
|
|
24
|
+
*/
|
|
25
|
+
class RichMarkdownBuilder {
|
|
26
|
+
parts = [];
|
|
27
|
+
text(str) {
|
|
28
|
+
this.parts.push(str);
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
heading(str, level = 2) {
|
|
32
|
+
this.parts.push(`${"#".repeat(level)} ${str}`);
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
blockquote(str) {
|
|
36
|
+
this.parts.push(str.split("\n").map((l) => `> ${l}`).join("\n"));
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
checklist(items) {
|
|
40
|
+
const normalized = items.map((i) => (typeof i === "string" ? { label: i, done: false } : i));
|
|
41
|
+
this.parts.push(normalized.map((i) => `- [${i.done ? "x" : " "}] ${i.label}`).join("\n"));
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
list(items, ordered = false) {
|
|
45
|
+
this.parts.push(items.map((item, i) => (ordered ? `${i + 1}. ${item}` : `- ${item}`)).join("\n"));
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
table(headers, rows) {
|
|
49
|
+
const line = (cells) => `| ${cells.join(" | ")} |`;
|
|
50
|
+
this.parts.push([line(headers), line(headers.map(() => "---")), ...rows.map(line)].join("\n"));
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
codeBlock(code, language) {
|
|
54
|
+
this.parts.push("```" + (language ?? "") + "\n" + code + "\n```");
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
divider() {
|
|
58
|
+
this.parts.push("---");
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Embeds a photo inline via Rich Markdown's own ``
|
|
63
|
+
* image syntax. This does NOT use InputRichMessage.media — that field is
|
|
64
|
+
* only for RichMessage.blocks()'s structured media blocks, and is a
|
|
65
|
+
* separate mechanism. This method's output is the confirmed-working path.
|
|
66
|
+
*/
|
|
67
|
+
photo(url, caption) {
|
|
68
|
+
this.parts.push(caption ? `` : ``);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
build() {
|
|
72
|
+
return { markdown: this.parts.join("\n\n") };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.RichMarkdownBuilder = RichMarkdownBuilder;
|
|
76
|
+
class RichHtmlBuilder {
|
|
77
|
+
parts = [];
|
|
78
|
+
raw(html) {
|
|
79
|
+
this.parts.push(html);
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
paragraph(html) {
|
|
83
|
+
this.parts.push(`<p>${html}</p>`);
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
heading(html, level = 2) {
|
|
87
|
+
this.parts.push(`<h${level}>${html}</h${level}>`);
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
build() {
|
|
91
|
+
return { html: this.parts.join("\n") };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.RichHtmlBuilder = RichHtmlBuilder;
|
|
95
|
+
function findMediaRef(b) {
|
|
96
|
+
switch (b.type) {
|
|
97
|
+
case "photo": return b.photo;
|
|
98
|
+
case "video": return b.video;
|
|
99
|
+
case "audio": return b.audio;
|
|
100
|
+
case "animation": return b.animation;
|
|
101
|
+
case "voice_note": return b.voice_note;
|
|
102
|
+
case "document": return b.document;
|
|
103
|
+
default: return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Structural block-based builder (Bot API 10.2+) — produces InputRichBlock[] directly. */
|
|
107
|
+
class RichBlocksBuilder {
|
|
108
|
+
blocks = [];
|
|
109
|
+
mediaItems = [];
|
|
110
|
+
mediaCounter = 0;
|
|
111
|
+
/**
|
|
112
|
+
* Declares a media item, returning its id. Prefer the .photo()/.video()/
|
|
113
|
+
* .audio()/.animation()/.voiceNote() convenience methods below, which call
|
|
114
|
+
* this for you and add the referencing block in one step — use this
|
|
115
|
+
* directly only if you're building a raw block yourself via .raw().
|
|
116
|
+
*/
|
|
117
|
+
media(type, file) {
|
|
118
|
+
const id = `media-${++this.mediaCounter}`;
|
|
119
|
+
this.mediaItems.push({ id, type, media: file });
|
|
120
|
+
return id;
|
|
121
|
+
}
|
|
122
|
+
paragraph(text) {
|
|
123
|
+
this.blocks.push({ type: "paragraph", text });
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
heading(text, size = 2) {
|
|
127
|
+
this.blocks.push({ type: "heading", text, size });
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
divider() {
|
|
131
|
+
this.blocks.push({ type: "divider" });
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
table(cells, opts = {}) {
|
|
135
|
+
this.blocks.push({ type: "table", cells, is_bordered: opts.bordered ? true : undefined, is_striped: opts.striped ? true : undefined, caption: opts.caption });
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
list(items) {
|
|
139
|
+
this.blocks.push({ type: "list", items });
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
details(summary, blocks, open = false) {
|
|
143
|
+
this.blocks.push({ type: "details", summary, blocks, is_open: open ? true : undefined });
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
/** Adds a photo block, declaring its media item for you. */
|
|
147
|
+
photo(file, opts = {}) {
|
|
148
|
+
const id = this.media("photo", file);
|
|
149
|
+
this.blocks.push({ type: "photo", photo: { id }, has_spoiler: opts.hasSpoiler ? true : undefined, caption: opts.caption });
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
video(file, opts = {}) {
|
|
153
|
+
const id = this.media("video", file);
|
|
154
|
+
this.blocks.push({ type: "video", video: { id }, has_spoiler: opts.hasSpoiler ? true : undefined, caption: opts.caption });
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
audio(file, opts = {}) {
|
|
158
|
+
const id = this.media("audio", file);
|
|
159
|
+
this.blocks.push({ type: "audio", audio: { id }, caption: opts.caption });
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
animation(file, opts = {}) {
|
|
163
|
+
const id = this.media("animation", file);
|
|
164
|
+
this.blocks.push({ type: "animation", animation: { id }, has_spoiler: opts.hasSpoiler ? true : undefined, caption: opts.caption });
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
voiceNote(file, opts = {}) {
|
|
168
|
+
const id = this.media("voice_note", file);
|
|
169
|
+
this.blocks.push({ type: "voice_note", voice_note: { id }, caption: opts.caption });
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
/** Bot API 10.3+: adds a document (file) block. */
|
|
173
|
+
document(file, opts = {}) {
|
|
174
|
+
const id = this.media("document", file);
|
|
175
|
+
this.blocks.push({ type: "document", document: { id }, caption: opts.caption });
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
/** Bot API 10.3+: adds a row of buttons to the rich message. */
|
|
179
|
+
buttons(buttons) {
|
|
180
|
+
this.blocks.push({ type: "buttons", buttons });
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
/** Bot API 10.3+: adds an expandable/collapsible block quotation. */
|
|
184
|
+
expandableBlockquote(blocks, opts = {}) {
|
|
185
|
+
this.blocks.push({ type: "expandable_blockquote", blocks, credit: opts.credit });
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
/** Escape hatch for block types without a dedicated method. If it references media by id, declare that media via .media() first, or build() will throw before you ever hit Telegram's 400. */
|
|
189
|
+
raw(block) {
|
|
190
|
+
this.blocks.push(block);
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
build() {
|
|
194
|
+
// Dev-time check: catch a block referencing an undeclared media id
|
|
195
|
+
// locally, with a clear message, instead of letting Telegram's 400 be
|
|
196
|
+
// the first signal.
|
|
197
|
+
const declaredIds = new Set(this.mediaItems.map((m) => m.id));
|
|
198
|
+
const walk = (blocks) => {
|
|
199
|
+
for (const b of blocks) {
|
|
200
|
+
const ref = findMediaRef(b);
|
|
201
|
+
if (ref && !declaredIds.has(ref.id)) {
|
|
202
|
+
throw new Error(`tgplus: RichBlocksBuilder block references media id "${ref.id}" that was never declared. ` +
|
|
203
|
+
`Use .photo()/.video()/.audio()/.animation()/.voiceNote() (which declare the media for you), ` +
|
|
204
|
+
`or call .media(type, file) yourself first and use the returned id.`);
|
|
205
|
+
}
|
|
206
|
+
if ("blocks" in b && Array.isArray(b.blocks)) {
|
|
207
|
+
walk(b.blocks);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
walk(this.blocks);
|
|
212
|
+
return { blocks: this.blocks, media: this.mediaItems.length ? this.mediaItems : undefined };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.RichBlocksBuilder = RichBlocksBuilder;
|
|
216
|
+
exports.RichMessage = {
|
|
217
|
+
markdown: () => new RichMarkdownBuilder(),
|
|
218
|
+
html: () => new RichHtmlBuilder(),
|
|
219
|
+
blocks: () => new RichBlocksBuilder(),
|
|
220
|
+
};
|
|
221
|
+
//# sourceMappingURL=richMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richMessage.js","sourceRoot":"","sources":["../src/richMessage.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAa,mBAAmB;IACtB,KAAK,GAAa,EAAE,CAAC;IAE7B,IAAI,CAAC,GAAW;QACd,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,GAAW,EAAE,QAA+B,CAAC;QACnD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU,CAAC,GAAW;QACpB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,CAAC,KAAqD;QAC7D,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7F,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1F,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,KAAe,EAAE,OAAO,GAAG,KAAK;QACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAClG,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,OAAiB,EAAE,IAAgB;QACvC,MAAM,IAAI,GAAG,CAAC,KAAe,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAC7D,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/F,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,QAAiB;QACvC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAW,EAAE,OAAgB;QACjC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK;QACH,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IAC/C,CAAC;CACF;AA3DD,kDA2DC;AAED,MAAa,eAAe;IAClB,KAAK,GAAa,EAAE,CAAC;IAE7B,GAAG,CAAC,IAAY;QACd,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,IAAY,EAAE,QAA+B,CAAC;QACpD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK;QACH,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACzC,CAAC;CACF;AArBD,0CAqBC;AAED,SAAS,YAAY,CAAC,CAAmB;IACvC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC;QAC7B,KAAK,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC;QAC7B,KAAK,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC;QAC7B,KAAK,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC;QACrC,KAAK,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;QACvC,KAAK,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;QACnC,OAAO,CAAC,CAAC,OAAO,SAAS,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,2FAA2F;AAC3F,MAAa,iBAAiB;IACpB,MAAM,GAAuB,EAAE,CAAC;IAChC,UAAU,GAA8B,EAAE,CAAC;IAC3C,YAAY,GAAG,CAAC,CAAC;IAEzB;;;;;OAKG;IACH,KAAK,CAAC,IAAqC,EAAE,IAAiB;QAC5D,MAAM,EAAE,GAAG,SAAS,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,SAAS,CAAC,IAAgB;QACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,CAAC,IAAgB,EAAE,OAA8B,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,KAAK,CAAC,KAA+B,EAAE,OAAwE,EAAE;QAC/G,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9J,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,KAA4B;QAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,CAAC,OAAmB,EAAE,MAA0B,EAAE,IAAI,GAAG,KAAK;QACnE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QACzF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,IAAiB,EAAE,OAA+D,EAAE;QACxF,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3H,OAAO,IAAI,CAAC;IACd,CAAC;IACD,KAAK,CAAC,IAAiB,EAAE,OAA+D,EAAE;QACxF,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3H,OAAO,IAAI,CAAC;IACd,CAAC;IACD,KAAK,CAAC,IAAiB,EAAE,OAAyC,EAAE;QAClE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,SAAS,CAAC,IAAiB,EAAE,OAA+D,EAAE;QAC5F,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACnI,OAAO,IAAI,CAAC;IACd,CAAC;IACD,SAAS,CAAC,IAAiB,EAAE,OAAyC,EAAE;QACtE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACpF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mDAAmD;IACnD,QAAQ,CAAC,IAAiB,EAAE,OAAyC,EAAE;QACrE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAChF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gEAAgE;IAChE,OAAO,CAAC,OAA8B;QACpC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qEAAqE;IACrE,oBAAoB,CAAC,MAA0B,EAAE,OAAgC,EAAE;QACjF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACjF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8LAA8L;IAC9L,GAAG,CAAC,KAAuB;QACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK;QACH,mEAAmE;QACnE,sEAAsE;QACtE,oBAAoB;QACpB,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,CAAC,MAA0B,EAAE,EAAE;YAC1C,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;gBACvB,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;gBAC5B,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;oBACpC,MAAM,IAAI,KAAK,CACb,wDAAwD,GAAG,CAAC,EAAE,6BAA6B;wBACzF,8FAA8F;wBAC9F,oEAAoE,CACvE,CAAC;gBACJ,CAAC;gBACD,IAAI,QAAQ,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,CAAE,CAA0B,CAAC,MAAM,CAAC,EAAE,CAAC;oBACvE,IAAI,CAAE,CAAoC,CAAC,MAAM,CAAC,CAAC;gBACrD,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAElB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IAC9F,CAAC;CACF;AAtHD,8CAsHC;AAEY,QAAA,WAAW,GAAG;IACzB,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,EAAE;IACzC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,eAAe,EAAE;IACjC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;CACtC,CAAC"}
|
package/dist/scenes.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Context } from "./context";
|
|
2
|
+
import { Middleware } from "./composer";
|
|
3
|
+
/**
|
|
4
|
+
* Multi-step conversations: "ask name → ask age → confirm". Requires
|
|
5
|
+
* `session()` middleware mounted first (Scenes stores its cursor + collected
|
|
6
|
+
* answers inside ctx.session).
|
|
7
|
+
* * const signup = new WizardScene<Context>('signup', [
|
|
8
|
+
* async (ctx) => { await ctx.reply('What is your name?'); await ctx.wizard.next(); },
|
|
9
|
+
* async (ctx) => { ctx.wizard.state.name = ctx.text; await ctx.reply('How old are you?'); await ctx.wizard.next(); },
|
|
10
|
+
* async (ctx) => { ctx.wizard.state.age = ctx.text; await ctx.reply(`Thanks ${ctx.wizard.state.name}!`); await ctx.scene.leave(); },
|
|
11
|
+
* ]);
|
|
12
|
+
*
|
|
13
|
+
* const stage = new Stage([signup]);
|
|
14
|
+
* bot.use(session());
|
|
15
|
+
* bot.use(stage.middleware());
|
|
16
|
+
* bot.command('signup', (ctx) => ctx.scene.enter('signup'));
|
|
17
|
+
*
|
|
18
|
+
* Each step function runs once per incoming update while the scene is active.
|
|
19
|
+
* Call `ctx.wizard.next()` at the end of a step once you're ready to move on —
|
|
20
|
+
* it only advances the pointer; the next step's body runs when the *next*
|
|
21
|
+
* update arrives, not immediately (so "ask for name" and "ask for age" don't
|
|
22
|
+
* both fire in the same turn).
|
|
23
|
+
*/
|
|
24
|
+
export interface WizardState {
|
|
25
|
+
cursor: number;
|
|
26
|
+
state: Record<string, unknown>;
|
|
27
|
+
/** Advance to the next step within this same update (e.g. to skip a step programmatically). */
|
|
28
|
+
next: () => Promise<void>;
|
|
29
|
+
/** Jump straight to a specific step index (0-based), e.g. to go "back". */
|
|
30
|
+
selectStep: (index: number) => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export interface SceneApi {
|
|
33
|
+
enter: (sceneId: string, initialState?: Record<string, unknown>) => Promise<void>;
|
|
34
|
+
leave: () => Promise<void>;
|
|
35
|
+
current: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
export type WizardContext<C extends Context = Context> = C & {
|
|
38
|
+
wizard: WizardState;
|
|
39
|
+
scene: SceneApi;
|
|
40
|
+
};
|
|
41
|
+
export type SceneStep<C extends Context = Context> = (ctx: WizardContext<C>) => unknown | Promise<unknown>;
|
|
42
|
+
export declare class WizardScene<C extends Context = Context> {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
private steps;
|
|
45
|
+
constructor(id: string, steps: SceneStep<C>[]);
|
|
46
|
+
runStep(ctx: C, index: number): Promise<void>;
|
|
47
|
+
get stepCount(): number;
|
|
48
|
+
}
|
|
49
|
+
/** Registers scenes and installs the routing middleware that dispatches updates to whichever scene is active. */
|
|
50
|
+
export declare class Stage<C extends Context = Context> {
|
|
51
|
+
private scenes;
|
|
52
|
+
constructor(scenes?: WizardScene<C>[]);
|
|
53
|
+
register(...scenes: WizardScene<C>[]): this;
|
|
54
|
+
middleware(): Middleware<C>;
|
|
55
|
+
private buildWizardState;
|
|
56
|
+
private runCurrent;
|
|
57
|
+
}
|
package/dist/scenes.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Stage = exports.WizardScene = void 0;
|
|
4
|
+
const SESSION_KEY = "__tgplus_scene";
|
|
5
|
+
function sceneSession(ctx) {
|
|
6
|
+
if (!ctx.session) {
|
|
7
|
+
throw new Error("Scenes require session() middleware to be mounted before stage.middleware(). See tgplus README.");
|
|
8
|
+
}
|
|
9
|
+
const existing = ctx.session[SESSION_KEY];
|
|
10
|
+
if (existing)
|
|
11
|
+
return existing;
|
|
12
|
+
const fresh = { cursor: 0, state: {} };
|
|
13
|
+
ctx.session[SESSION_KEY] = fresh;
|
|
14
|
+
return fresh;
|
|
15
|
+
}
|
|
16
|
+
class WizardScene {
|
|
17
|
+
id;
|
|
18
|
+
steps;
|
|
19
|
+
constructor(id, steps) {
|
|
20
|
+
this.id = id;
|
|
21
|
+
this.steps = steps;
|
|
22
|
+
}
|
|
23
|
+
async runStep(ctx, index) {
|
|
24
|
+
const step = this.steps[index];
|
|
25
|
+
if (!step)
|
|
26
|
+
return; // past the last step
|
|
27
|
+
await step(ctx);
|
|
28
|
+
}
|
|
29
|
+
get stepCount() {
|
|
30
|
+
return this.steps.length;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.WizardScene = WizardScene;
|
|
34
|
+
/** Registers scenes and installs the routing middleware that dispatches updates to whichever scene is active. */
|
|
35
|
+
class Stage {
|
|
36
|
+
scenes = new Map();
|
|
37
|
+
constructor(scenes = []) {
|
|
38
|
+
for (const s of scenes)
|
|
39
|
+
this.scenes.set(s.id, s);
|
|
40
|
+
}
|
|
41
|
+
register(...scenes) {
|
|
42
|
+
for (const s of scenes)
|
|
43
|
+
this.scenes.set(s.id, s);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
middleware() {
|
|
47
|
+
return async (ctx, next) => {
|
|
48
|
+
const sess = sceneSession(ctx);
|
|
49
|
+
const wizardCtx = ctx;
|
|
50
|
+
wizardCtx.scene = {
|
|
51
|
+
get current() { return sess.current; },
|
|
52
|
+
enter: async (sceneId, initialState = {}) => {
|
|
53
|
+
if (!this.scenes.has(sceneId))
|
|
54
|
+
throw new Error(`tgplus: no scene registered with id "${sceneId}"`);
|
|
55
|
+
sess.current = sceneId;
|
|
56
|
+
sess.cursor = 0;
|
|
57
|
+
sess.state = initialState;
|
|
58
|
+
await this.runCurrent(ctx, sess);
|
|
59
|
+
},
|
|
60
|
+
leave: async () => {
|
|
61
|
+
sess.current = undefined;
|
|
62
|
+
sess.cursor = 0;
|
|
63
|
+
sess.state = {};
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
if (sess.current && this.scenes.has(sess.current)) {
|
|
67
|
+
wizardCtx.wizard = this.buildWizardState(ctx, sess);
|
|
68
|
+
await this.runCurrent(ctx, sess);
|
|
69
|
+
return; // a scene owns this update — don't fall through to other handlers
|
|
70
|
+
}
|
|
71
|
+
return next();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
buildWizardState(ctx, sess) {
|
|
75
|
+
return {
|
|
76
|
+
cursor: sess.cursor,
|
|
77
|
+
state: sess.state,
|
|
78
|
+
// Advances the pointer only — the next step body runs when the *next* update arrives,
|
|
79
|
+
// not immediately, so e.g. "ask for name" and "ask for age" don't both fire in one turn.
|
|
80
|
+
next: async () => {
|
|
81
|
+
sess.cursor += 1;
|
|
82
|
+
const scene = this.scenes.get(sess.current);
|
|
83
|
+
if (!scene || sess.cursor >= scene.stepCount) {
|
|
84
|
+
sess.current = undefined;
|
|
85
|
+
sess.cursor = 0;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
// Jumps the pointer AND runs that step immediately — useful for "go back to step 2 right now".
|
|
89
|
+
selectStep: async (index) => {
|
|
90
|
+
sess.cursor = index;
|
|
91
|
+
ctx.wizard = this.buildWizardState(ctx, sess);
|
|
92
|
+
await this.scenes.get(sess.current)?.runStep(ctx, index);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async runCurrent(ctx, sess) {
|
|
97
|
+
const scene = this.scenes.get(sess.current);
|
|
98
|
+
if (!scene)
|
|
99
|
+
return;
|
|
100
|
+
ctx.wizard = this.buildWizardState(ctx, sess);
|
|
101
|
+
await scene.runStep(ctx, sess.cursor);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.Stage = Stage;
|
|
105
|
+
//# sourceMappingURL=scenes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenes.js","sourceRoot":"","sources":["../src/scenes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SessionStore } from "./session";
|
|
2
|
+
/**
|
|
3
|
+
* Duck-typed on purpose: works with `ioredis`, `redis` (node-redis v4+), or
|
|
4
|
+
* anything else exposing get/set/del as async methods — no hard dependency
|
|
5
|
+
* on any specific Redis client package.
|
|
6
|
+
*/
|
|
7
|
+
export interface RedisLikeClient {
|
|
8
|
+
get(key: string): Promise<string | null>;
|
|
9
|
+
set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
|
|
10
|
+
del(key: string): Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface RedisSessionStoreOptions {
|
|
13
|
+
/** Key prefix so tgplus sessions don't collide with other data in the same Redis instance. Default: "tgplus:session:" */
|
|
14
|
+
prefix?: string;
|
|
15
|
+
/** Expire sessions after N seconds of inactivity (e.g. 86400 for one day). Omit for no expiry. */
|
|
16
|
+
ttlSeconds?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class RedisSessionStore<S = unknown> implements SessionStore<S> {
|
|
19
|
+
private client;
|
|
20
|
+
private opts;
|
|
21
|
+
private prefix;
|
|
22
|
+
constructor(client: RedisLikeClient, opts?: RedisSessionStoreOptions);
|
|
23
|
+
private key;
|
|
24
|
+
get(key: string): Promise<S | undefined>;
|
|
25
|
+
set(key: string, value: S): Promise<void>;
|
|
26
|
+
delete(key: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisSessionStore = void 0;
|
|
4
|
+
class RedisSessionStore {
|
|
5
|
+
client;
|
|
6
|
+
opts;
|
|
7
|
+
prefix;
|
|
8
|
+
constructor(client, opts = {}) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
this.opts = opts;
|
|
11
|
+
this.prefix = opts.prefix ?? "tgplus:session:";
|
|
12
|
+
}
|
|
13
|
+
key(k) {
|
|
14
|
+
return `${this.prefix}${k}`;
|
|
15
|
+
}
|
|
16
|
+
async get(key) {
|
|
17
|
+
const raw = await this.client.get(this.key(key));
|
|
18
|
+
return raw ? JSON.parse(raw) : undefined;
|
|
19
|
+
}
|
|
20
|
+
async set(key, value) {
|
|
21
|
+
const raw = JSON.stringify(value);
|
|
22
|
+
if (this.opts.ttlSeconds) {
|
|
23
|
+
// Works for both ioredis (`set(key, val, 'EX', n)`) and node-redis v4 (`set(key, val, {EX: n})`) callers
|
|
24
|
+
// who pass ttlSeconds — we call the lowest common denominator form here and let it no-op if unsupported.
|
|
25
|
+
await this.client.set(this.key(key), raw, "EX", this.opts.ttlSeconds);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
await this.client.set(this.key(key), raw);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async delete(key) {
|
|
32
|
+
await this.client.del(this.key(key));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.RedisSessionStore = RedisSessionStore;
|
|
36
|
+
//# sourceMappingURL=session-redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-redis.js","sourceRoot":"","sources":["../src/session-redis.ts"],"names":[],"mappings":";;;AAoBA,MAAa,iBAAiB;IAGR;IAAiC;IAF7C,MAAM,CAAS;IAEvB,YAAoB,MAAuB,EAAU,OAAiC,EAAE;QAApE,WAAM,GAAN,MAAM,CAAiB;QAAU,SAAI,GAAJ,IAAI,CAA+B;QACtF,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,iBAAiB,CAAC;IACjD,CAAC;IAEO,GAAG,CAAC,CAAS;QACnB,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAQ;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACzB,yGAAyG;YACzG,yGAAyG;YACzG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,CAAC;CACF;AA9BD,8CA8BC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SessionStore } from "./session";
|
|
2
|
+
/**
|
|
3
|
+
* Duck-typed on purpose: works with `better-sqlite3` (the common choice for
|
|
4
|
+
* self-hosted bots that don't want to run Redis) without a hard dependency
|
|
5
|
+
* on it. Any object exposing this synchronous shape works — including an
|
|
6
|
+
* in-memory fake for tests.
|
|
7
|
+
*/
|
|
8
|
+
export interface SqliteLikeDatabase {
|
|
9
|
+
exec(sql: string): unknown;
|
|
10
|
+
prepare(sql: string): {
|
|
11
|
+
run(...params: unknown[]): unknown;
|
|
12
|
+
get(...params: unknown[]): unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface SqliteSessionStoreOptions {
|
|
16
|
+
/** Table name to store sessions in. Must be a plain identifier (letters, numbers, underscores) — validated to avoid building unsafe SQL. Default: "tgplus_sessions". */
|
|
17
|
+
tableName?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class SqliteSessionStore<S = unknown> implements SessionStore<S> {
|
|
20
|
+
private db;
|
|
21
|
+
private tableName;
|
|
22
|
+
private getStmt;
|
|
23
|
+
private setStmt;
|
|
24
|
+
private deleteStmt;
|
|
25
|
+
constructor(db: SqliteLikeDatabase, opts?: SqliteSessionStoreOptions);
|
|
26
|
+
get(key: string): S | undefined;
|
|
27
|
+
set(key: string, value: S): void;
|
|
28
|
+
delete(key: string): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqliteSessionStore = void 0;
|
|
4
|
+
const VALID_TABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
5
|
+
class SqliteSessionStore {
|
|
6
|
+
db;
|
|
7
|
+
tableName;
|
|
8
|
+
getStmt;
|
|
9
|
+
setStmt;
|
|
10
|
+
deleteStmt;
|
|
11
|
+
constructor(db, opts = {}) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.tableName = opts.tableName ?? "tgplus_sessions";
|
|
14
|
+
if (!VALID_TABLE_NAME.test(this.tableName)) {
|
|
15
|
+
throw new Error(`tgplus: invalid SqliteSessionStore tableName "${this.tableName}" — must match ${VALID_TABLE_NAME}`);
|
|
16
|
+
}
|
|
17
|
+
this.db.exec(`CREATE TABLE IF NOT EXISTS ${this.tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`);
|
|
18
|
+
this.getStmt = this.db.prepare(`SELECT value FROM ${this.tableName} WHERE key = ?`);
|
|
19
|
+
this.setStmt = this.db.prepare(`INSERT INTO ${this.tableName} (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`);
|
|
20
|
+
this.deleteStmt = this.db.prepare(`DELETE FROM ${this.tableName} WHERE key = ?`);
|
|
21
|
+
}
|
|
22
|
+
get(key) {
|
|
23
|
+
const row = this.getStmt.get(key);
|
|
24
|
+
return row ? JSON.parse(row.value) : undefined;
|
|
25
|
+
}
|
|
26
|
+
set(key, value) {
|
|
27
|
+
this.setStmt.run(key, JSON.stringify(value));
|
|
28
|
+
}
|
|
29
|
+
delete(key) {
|
|
30
|
+
this.deleteStmt.run(key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.SqliteSessionStore = SqliteSessionStore;
|
|
34
|
+
//# sourceMappingURL=session-sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-sqlite.js","sourceRoot":"","sources":["../src/session-sqlite.ts"],"names":[],"mappings":";;;AAqBA,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAEpD,MAAa,kBAAkB;IAMT;IALZ,SAAS,CAAS;IAClB,OAAO,CAA4C;IACnD,OAAO,CAA4C;IACnD,UAAU,CAA4C;IAE9D,YAAoB,EAAsB,EAAE,OAAkC,EAAE;QAA5D,OAAE,GAAF,EAAE,CAAoB;QACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,iBAAiB,CAAC;QACrD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,iDAAiD,IAAI,CAAC,SAAS,kBAAkB,gBAAgB,EAAE,CAAC,CAAC;QACvH,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,8BAA8B,IAAI,CAAC,SAAS,8CAA8C,CAAC,CAAC;QACzG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,qBAAqB,IAAI,CAAC,SAAS,gBAAgB,CAAC,CAAC;QACpF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC5B,eAAe,IAAI,CAAC,SAAS,mFAAmF,CACjH,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,eAAe,IAAI,CAAC,SAAS,gBAAgB,CAAC,CAAC;IACnF,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAkC,CAAC;QACnE,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACxD,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAQ;QACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;CACF;AA/BD,gDA+BC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context } from "./context";
|
|
2
|
+
import { Middleware } from "./composer";
|
|
3
|
+
/**
|
|
4
|
+
* Pluggable session storage. Swap MemorySessionStore for RedisSessionStore
|
|
5
|
+
* (see session-redis.ts) in production — same interface, no other code changes.
|
|
6
|
+
*/
|
|
7
|
+
export interface SessionStore<S = unknown> {
|
|
8
|
+
get(key: string): Promise<S | undefined> | S | undefined;
|
|
9
|
+
set(key: string, value: S): Promise<void> | void;
|
|
10
|
+
delete(key: string): Promise<void> | void;
|
|
11
|
+
}
|
|
12
|
+
/** Default store: fine for local dev/single-process bots. State is lost on restart. */
|
|
13
|
+
export declare class MemorySessionStore<S = unknown> implements SessionStore<S> {
|
|
14
|
+
private map;
|
|
15
|
+
get(key: string): S | undefined;
|
|
16
|
+
set(key: string, value: S): void;
|
|
17
|
+
delete(key: string): void;
|
|
18
|
+
}
|
|
19
|
+
export interface SessionOptions<S> {
|
|
20
|
+
store?: SessionStore<S>;
|
|
21
|
+
/** Defaults to one session per chat. Use e.g. `ctx => ctx.from?.id` for a per-user session instead. */
|
|
22
|
+
getSessionKey?: (ctx: Context) => string | undefined;
|
|
23
|
+
defaultSession?: () => S;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Attaches `ctx.session` (persisted via `store` across updates, keyed per chat
|
|
27
|
+
* by default) and saves it back automatically after your handlers run —
|
|
28
|
+
* you just read/write `ctx.session.whatever` like a plain object.
|
|
29
|
+
*/
|
|
30
|
+
export declare function session<S extends object = Record<string, unknown>>(opts?: SessionOptions<S>): Middleware<Context>;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemorySessionStore = void 0;
|
|
4
|
+
exports.session = session;
|
|
5
|
+
/** Default store: fine for local dev/single-process bots. State is lost on restart. */
|
|
6
|
+
class MemorySessionStore {
|
|
7
|
+
map = new Map();
|
|
8
|
+
get(key) { return this.map.get(key); }
|
|
9
|
+
set(key, value) { this.map.set(key, value); }
|
|
10
|
+
delete(key) { this.map.delete(key); }
|
|
11
|
+
}
|
|
12
|
+
exports.MemorySessionStore = MemorySessionStore;
|
|
13
|
+
/**
|
|
14
|
+
* Attaches `ctx.session` (persisted via `store` across updates, keyed per chat
|
|
15
|
+
* by default) and saves it back automatically after your handlers run —
|
|
16
|
+
* you just read/write `ctx.session.whatever` like a plain object.
|
|
17
|
+
*/
|
|
18
|
+
function session(opts = {}) {
|
|
19
|
+
const store = opts.store ?? new MemorySessionStore();
|
|
20
|
+
const getKey = opts.getSessionKey ?? ((ctx) => (ctx.chat ? String(ctx.chat.id) : undefined));
|
|
21
|
+
const makeDefault = opts.defaultSession ?? (() => ({}));
|
|
22
|
+
return async (ctx, next) => {
|
|
23
|
+
const key = getKey(ctx);
|
|
24
|
+
if (key === undefined)
|
|
25
|
+
return next();
|
|
26
|
+
const existing = await store.get(key);
|
|
27
|
+
ctx.session = (existing ?? makeDefault());
|
|
28
|
+
await next();
|
|
29
|
+
if (ctx.session === undefined)
|
|
30
|
+
await store.delete(key);
|
|
31
|
+
else
|
|
32
|
+
await store.set(key, ctx.session);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":";;;AAiCA,0BAiBC;AArCD,uFAAuF;AACvF,MAAa,kBAAkB;IACrB,GAAG,GAAG,IAAI,GAAG,EAAa,CAAC;IACnC,GAAG,CAAC,GAAW,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9C,GAAG,CAAC,GAAW,EAAE,KAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,CAAC,GAAW,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAC9C;AALD,gDAKC;AASD;;;;GAIG;AACH,SAAgB,OAAO,CAA6C,OAA0B,EAAE;IAC9F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,kBAAkB,EAAK,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACtG,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,IAAI,CAAC,GAAM,EAAE,CAAC,CAAC,EAAQ,CAAA,CAAC,CAAC;IAEhE,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,EAAE,CAAC;QAErC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,GAAG,CAAC,OAAO,GAAG,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAuC,CAAC;QAEhF,MAAM,IAAI,EAAE,CAAC;QAEb,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;YAAE,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;;YAClD,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,OAAuB,CAAC,CAAC;IACzD,CAAC,CAAC;AACJ,CAAC"}
|