xzcgram 0.0.1 → 0.0.2
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/package.json +1 -1
- package/src/bot.js +59 -18
- package/src/context.js +96 -49
package/package.json
CHANGED
package/src/bot.js
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
const { NewMessage } = require("telegram/events");
|
|
2
|
-
const { NewCallbackQuery } = require("telegram/events/NewCallbackQuery");
|
|
3
2
|
const { Context } = require("./context");
|
|
4
3
|
|
|
4
|
+
// CallbackQuery (inline button presses) — imported defensively since its
|
|
5
|
+
// export path has moved between "telegram" package versions. If it can't
|
|
6
|
+
// be found, bot.action() handlers simply won't fire, but everything else
|
|
7
|
+
// (commands, hears, media, etc.) keeps working.
|
|
8
|
+
let CallbackQuery = null;
|
|
9
|
+
try {
|
|
10
|
+
({ CallbackQuery } = require("telegram/events"));
|
|
11
|
+
} catch (err) {
|
|
12
|
+
/* ignore, try next path */
|
|
13
|
+
}
|
|
14
|
+
if (!CallbackQuery) {
|
|
15
|
+
try {
|
|
16
|
+
({ CallbackQuery } = require("telegram/events/CallbackQuery"));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
/* ignore, handled below */
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
/**
|
|
6
|
-
* CallbackContext wraps a GramJS
|
|
23
|
+
* CallbackContext wraps a GramJS CallbackQuery event, exposed to
|
|
7
24
|
* handlers registered via bot.action(...).
|
|
8
25
|
*/
|
|
9
26
|
class CallbackContext {
|
|
@@ -17,6 +34,20 @@ class CallbackContext {
|
|
|
17
34
|
this.data = event.data ? event.data.toString("utf-8") : "";
|
|
18
35
|
}
|
|
19
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the correct peer to send to/act on. See Context._peer() for
|
|
39
|
+
* why this is needed instead of a raw numeric chatId.
|
|
40
|
+
*/
|
|
41
|
+
async _peer() {
|
|
42
|
+
try {
|
|
43
|
+
const inputChat = await this.event.getInputChat();
|
|
44
|
+
if (inputChat) return inputChat;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// fall through to raw chatId below
|
|
47
|
+
}
|
|
48
|
+
return this.chatId;
|
|
49
|
+
}
|
|
50
|
+
|
|
20
51
|
/** Answer the callback query (e.g. show a small toast/alert to the user). */
|
|
21
52
|
answer(text = "", opts = {}) {
|
|
22
53
|
return this.event.answer({ message: text, ...opts });
|
|
@@ -28,8 +59,9 @@ class CallbackContext {
|
|
|
28
59
|
}
|
|
29
60
|
|
|
30
61
|
/** Send a new message in the same chat as the callback query. */
|
|
31
|
-
reply(text, opts = {}) {
|
|
32
|
-
|
|
62
|
+
async reply(text, opts = {}) {
|
|
63
|
+
const peer = await this._peer();
|
|
64
|
+
return this.client.sendMessage(peer, { message: text, ...opts });
|
|
33
65
|
}
|
|
34
66
|
}
|
|
35
67
|
|
|
@@ -135,21 +167,30 @@ class Bot {
|
|
|
135
167
|
}
|
|
136
168
|
}, new NewMessage({}));
|
|
137
169
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
if (CallbackQuery) {
|
|
171
|
+
this.client.addEventHandler(async (event) => {
|
|
172
|
+
try {
|
|
173
|
+
const ctx = new CallbackContext(this.client, event);
|
|
174
|
+
|
|
175
|
+
for (const h of this.handlers.action) {
|
|
176
|
+
const matched =
|
|
177
|
+
h.pattern instanceof RegExp
|
|
178
|
+
? h.pattern.test(ctx.data)
|
|
179
|
+
: ctx.data === h.pattern;
|
|
180
|
+
if (matched) return await h.fn(ctx);
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error("[xzcgram] action handler error:", err);
|
|
148
184
|
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
185
|
+
}, new CallbackQuery({}));
|
|
186
|
+
} else if (this.handlers.action.length > 0) {
|
|
187
|
+
console.warn(
|
|
188
|
+
"[xzcgram] bot.action() handlers were registered, but this version " +
|
|
189
|
+
"of the 'telegram' package doesn't expose a CallbackQuery event " +
|
|
190
|
+
"class — inline button presses won't be handled. Try updating " +
|
|
191
|
+
"the 'telegram' package."
|
|
192
|
+
);
|
|
193
|
+
}
|
|
153
194
|
}
|
|
154
195
|
}
|
|
155
196
|
|
package/src/context.js
CHANGED
|
@@ -33,7 +33,7 @@ class Context {
|
|
|
33
33
|
this.event = event;
|
|
34
34
|
/** Raw GramJS message object */
|
|
35
35
|
this.message = event.message;
|
|
36
|
-
/** Chat/peer id where the message was sent */
|
|
36
|
+
/** Chat/peer id where the message was sent (numeric, informational) */
|
|
37
37
|
this.chatId = this.message.chatId;
|
|
38
38
|
/** Full text of the incoming message */
|
|
39
39
|
this.text = this.message.message || "";
|
|
@@ -41,18 +41,38 @@ class Context {
|
|
|
41
41
|
this.args = this.text.split(" ").slice(1);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the correct peer to send to/act on.
|
|
46
|
+
* Uses message.getInputChat() first, since it carries the access_hash
|
|
47
|
+
* from the incoming update — this avoids "Could not find the input
|
|
48
|
+
* entity" errors that happen when using a raw numeric chatId for a
|
|
49
|
+
* user/chat the client hasn't cached yet (e.g. first DM from someone new).
|
|
50
|
+
* Falls back to the raw chatId if getInputChat() can't resolve anything.
|
|
51
|
+
*/
|
|
52
|
+
async _peer() {
|
|
53
|
+
try {
|
|
54
|
+
const inputChat = await this.message.getInputChat();
|
|
55
|
+
if (inputChat) return inputChat;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
// fall through to raw chatId below
|
|
58
|
+
}
|
|
59
|
+
return this.chatId;
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
// ---------------------------------------------------------------------
|
|
45
63
|
// Text
|
|
46
64
|
// ---------------------------------------------------------------------
|
|
47
65
|
|
|
48
66
|
/** Reply in the same chat the message came from. */
|
|
49
|
-
reply(text, opts = {}) {
|
|
50
|
-
|
|
67
|
+
async reply(text, opts = {}) {
|
|
68
|
+
const peer = await this._peer();
|
|
69
|
+
return this.client.sendMessage(peer, { message: text, ...opts });
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
/** Reply directly to the triggering message (quote-style reply). */
|
|
54
|
-
replyQuote(text, opts = {}) {
|
|
55
|
-
|
|
73
|
+
async replyQuote(text, opts = {}) {
|
|
74
|
+
const peer = await this._peer();
|
|
75
|
+
return this.client.sendMessage(peer, {
|
|
56
76
|
message: text,
|
|
57
77
|
replyTo: this.message.id,
|
|
58
78
|
...opts,
|
|
@@ -60,8 +80,9 @@ class Context {
|
|
|
60
80
|
}
|
|
61
81
|
|
|
62
82
|
/** Edit a message previously sent in this chat (must be your own message). */
|
|
63
|
-
editMessage(messageId, text, opts = {}) {
|
|
64
|
-
|
|
83
|
+
async editMessage(messageId, text, opts = {}) {
|
|
84
|
+
const peer = await this._peer();
|
|
85
|
+
return this.client.editMessage(peer, {
|
|
65
86
|
message: messageId,
|
|
66
87
|
text,
|
|
67
88
|
...opts,
|
|
@@ -73,13 +94,15 @@ class Context {
|
|
|
73
94
|
// ---------------------------------------------------------------------
|
|
74
95
|
|
|
75
96
|
/** Send a photo. `file` can be a path, Buffer, URL, or existing file id. */
|
|
76
|
-
replyWithPhoto(file, opts = {}) {
|
|
77
|
-
|
|
97
|
+
async replyWithPhoto(file, opts = {}) {
|
|
98
|
+
const peer = await this._peer();
|
|
99
|
+
return this.client.sendFile(peer, { file, ...opts });
|
|
78
100
|
}
|
|
79
101
|
|
|
80
102
|
/** Send a video. Pass `opts.supportsStreaming = true` for streamable playback. */
|
|
81
|
-
replyWithVideo(file, opts = {}) {
|
|
82
|
-
|
|
103
|
+
async replyWithVideo(file, opts = {}) {
|
|
104
|
+
const peer = await this._peer();
|
|
105
|
+
return this.client.sendFile(peer, {
|
|
83
106
|
file,
|
|
84
107
|
supportsStreaming: true,
|
|
85
108
|
...opts,
|
|
@@ -87,23 +110,27 @@ class Context {
|
|
|
87
110
|
}
|
|
88
111
|
|
|
89
112
|
/** Send a round "video note" (the circular video bubble). */
|
|
90
|
-
replyWithVideoNote(file, opts = {}) {
|
|
91
|
-
|
|
113
|
+
async replyWithVideoNote(file, opts = {}) {
|
|
114
|
+
const peer = await this._peer();
|
|
115
|
+
return this.client.sendFile(peer, { file, videoNote: true, ...opts });
|
|
92
116
|
}
|
|
93
117
|
|
|
94
118
|
/** Send an audio file (music, shown with player + duration/title). */
|
|
95
|
-
replyWithAudio(file, opts = {}) {
|
|
96
|
-
|
|
119
|
+
async replyWithAudio(file, opts = {}) {
|
|
120
|
+
const peer = await this._peer();
|
|
121
|
+
return this.client.sendFile(peer, { file, ...opts });
|
|
97
122
|
}
|
|
98
123
|
|
|
99
124
|
/** Send a voice note (the waveform bubble). */
|
|
100
|
-
replyWithVoice(file, opts = {}) {
|
|
101
|
-
|
|
125
|
+
async replyWithVoice(file, opts = {}) {
|
|
126
|
+
const peer = await this._peer();
|
|
127
|
+
return this.client.sendFile(peer, { file, voiceNote: true, ...opts });
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
/** Send any file as a generic document. */
|
|
105
|
-
replyWithDocument(file, opts = {}) {
|
|
106
|
-
|
|
131
|
+
async replyWithDocument(file, opts = {}) {
|
|
132
|
+
const peer = await this._peer();
|
|
133
|
+
return this.client.sendFile(peer, {
|
|
107
134
|
file,
|
|
108
135
|
forceDocument: true,
|
|
109
136
|
...opts,
|
|
@@ -111,13 +138,15 @@ class Context {
|
|
|
111
138
|
}
|
|
112
139
|
|
|
113
140
|
/** Send a sticker (.webp/.tgs file or existing file reference). */
|
|
114
|
-
replyWithSticker(file, opts = {}) {
|
|
115
|
-
|
|
141
|
+
async replyWithSticker(file, opts = {}) {
|
|
142
|
+
const peer = await this._peer();
|
|
143
|
+
return this.client.sendFile(peer, { file, ...opts });
|
|
116
144
|
}
|
|
117
145
|
|
|
118
146
|
/** Send an animated GIF. */
|
|
119
|
-
replyWithAnimation(file, opts = {}) {
|
|
120
|
-
|
|
147
|
+
async replyWithAnimation(file, opts = {}) {
|
|
148
|
+
const peer = await this._peer();
|
|
149
|
+
return this.client.sendFile(peer, {
|
|
121
150
|
file,
|
|
122
151
|
attributes: [new Api.DocumentAttributeAnimated()],
|
|
123
152
|
...opts,
|
|
@@ -125,8 +154,9 @@ class Context {
|
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
/** Send multiple files as an album/media group. `files` is an array. */
|
|
128
|
-
replyWithMediaGroup(files, opts = {}) {
|
|
129
|
-
|
|
157
|
+
async replyWithMediaGroup(files, opts = {}) {
|
|
158
|
+
const peer = await this._peer();
|
|
159
|
+
return this.client.sendFile(peer, { file: files, ...opts });
|
|
130
160
|
}
|
|
131
161
|
|
|
132
162
|
// ---------------------------------------------------------------------
|
|
@@ -139,8 +169,9 @@ class Context {
|
|
|
139
169
|
* @param {Array<Array<{text: string, data?: string, url?: string}>>} rows
|
|
140
170
|
* @param {object} [opts]
|
|
141
171
|
*/
|
|
142
|
-
replyWithButtons(text, rows, opts = {}) {
|
|
143
|
-
|
|
172
|
+
async replyWithButtons(text, rows, opts = {}) {
|
|
173
|
+
const peer = await this._peer();
|
|
174
|
+
return this.client.sendMessage(peer, {
|
|
144
175
|
message: text,
|
|
145
176
|
buttons: buildButtons(rows, true),
|
|
146
177
|
...opts,
|
|
@@ -153,8 +184,9 @@ class Context {
|
|
|
153
184
|
* @param {Array<Array<{text: string}>>} rows
|
|
154
185
|
* @param {object} [opts]
|
|
155
186
|
*/
|
|
156
|
-
replyWithKeyboard(text, rows, opts = {}) {
|
|
157
|
-
|
|
187
|
+
async replyWithKeyboard(text, rows, opts = {}) {
|
|
188
|
+
const peer = await this._peer();
|
|
189
|
+
return this.client.sendMessage(peer, {
|
|
158
190
|
message: text,
|
|
159
191
|
buttons: buildButtons(rows, false),
|
|
160
192
|
...opts,
|
|
@@ -167,17 +199,24 @@ class Context {
|
|
|
167
199
|
* @param {string[]} answers
|
|
168
200
|
* @param {object} [opts] e.g. { multipleChoice: true, quiz: false }
|
|
169
201
|
*/
|
|
170
|
-
replyWithPoll(question, answers, opts = {}) {
|
|
202
|
+
async replyWithPoll(question, answers, opts = {}) {
|
|
203
|
+
const peer = await this._peer();
|
|
171
204
|
return this.client.invoke(
|
|
172
205
|
new Api.messages.SendMedia({
|
|
173
|
-
peer
|
|
206
|
+
peer,
|
|
174
207
|
media: new Api.InputMediaPoll({
|
|
175
208
|
poll: new Api.Poll({
|
|
176
209
|
id: BigInt(Date.now()),
|
|
177
|
-
question
|
|
210
|
+
question: new Api.TextWithEntities({
|
|
211
|
+
text: question,
|
|
212
|
+
entities: [],
|
|
213
|
+
}),
|
|
178
214
|
answers: answers.map(
|
|
179
215
|
(text, i) =>
|
|
180
|
-
new Api.PollAnswer({
|
|
216
|
+
new Api.PollAnswer({
|
|
217
|
+
text: new Api.TextWithEntities({ text, entities: [] }),
|
|
218
|
+
option: Buffer.from([i]),
|
|
219
|
+
})
|
|
181
220
|
),
|
|
182
221
|
multipleChoice: !!opts.multipleChoice,
|
|
183
222
|
}),
|
|
@@ -189,10 +228,11 @@ class Context {
|
|
|
189
228
|
}
|
|
190
229
|
|
|
191
230
|
/** Send a geographic location. */
|
|
192
|
-
replyWithLocation(latitude, longitude, opts = {}) {
|
|
231
|
+
async replyWithLocation(latitude, longitude, opts = {}) {
|
|
232
|
+
const peer = await this._peer();
|
|
193
233
|
return this.client.invoke(
|
|
194
234
|
new Api.messages.SendMedia({
|
|
195
|
-
peer
|
|
235
|
+
peer,
|
|
196
236
|
media: new Api.InputMediaGeoPoint({
|
|
197
237
|
geoPoint: new Api.InputGeoPoint({ lat: latitude, long: longitude }),
|
|
198
238
|
}),
|
|
@@ -204,10 +244,11 @@ class Context {
|
|
|
204
244
|
}
|
|
205
245
|
|
|
206
246
|
/** Send a contact card. */
|
|
207
|
-
replyWithContact(phoneNumber, firstName, lastName = "", opts = {}) {
|
|
247
|
+
async replyWithContact(phoneNumber, firstName, lastName = "", opts = {}) {
|
|
248
|
+
const peer = await this._peer();
|
|
208
249
|
return this.client.invoke(
|
|
209
250
|
new Api.messages.SendMedia({
|
|
210
|
-
peer
|
|
251
|
+
peer,
|
|
211
252
|
media: new Api.InputMediaContact({
|
|
212
253
|
phoneNumber,
|
|
213
254
|
firstName,
|
|
@@ -222,10 +263,11 @@ class Context {
|
|
|
222
263
|
}
|
|
223
264
|
|
|
224
265
|
/** Send an animated dice/emoji reaction (🎲 🎯 🏀 ⚽ 🎰 🎳). */
|
|
225
|
-
replyWithDice(emoji = "
|
|
266
|
+
async replyWithDice(emoji = "🎲", opts = {}) {
|
|
267
|
+
const peer = await this._peer();
|
|
226
268
|
return this.client.invoke(
|
|
227
269
|
new Api.messages.SendMedia({
|
|
228
|
-
peer
|
|
270
|
+
peer,
|
|
229
271
|
media: new Api.InputMediaDice({ emoticon: emoji }),
|
|
230
272
|
message: "",
|
|
231
273
|
randomId: BigInt(Date.now()),
|
|
@@ -239,32 +281,37 @@ class Context {
|
|
|
239
281
|
// ---------------------------------------------------------------------
|
|
240
282
|
|
|
241
283
|
/** Delete the triggering message (revokes for everyone by default). */
|
|
242
|
-
deleteMessage() {
|
|
243
|
-
|
|
284
|
+
async deleteMessage() {
|
|
285
|
+
const peer = await this._peer();
|
|
286
|
+
return this.client.deleteMessages(peer, [this.message.id], {
|
|
244
287
|
revoke: true,
|
|
245
288
|
});
|
|
246
289
|
}
|
|
247
290
|
|
|
248
291
|
/** Forward the triggering message to another chat. */
|
|
249
|
-
forwardMessage(toChatId) {
|
|
292
|
+
async forwardMessage(toChatId) {
|
|
293
|
+
const peer = await this._peer();
|
|
250
294
|
return this.client.forwardMessages(toChatId, {
|
|
251
295
|
messages: [this.message.id],
|
|
252
|
-
fromPeer:
|
|
296
|
+
fromPeer: peer,
|
|
253
297
|
});
|
|
254
298
|
}
|
|
255
299
|
|
|
256
300
|
/** Pin the triggering message in the current chat. */
|
|
257
|
-
pinMessage(opts = {}) {
|
|
258
|
-
|
|
301
|
+
async pinMessage(opts = {}) {
|
|
302
|
+
const peer = await this._peer();
|
|
303
|
+
return this.client.pinMessage(peer, this.message.id, opts);
|
|
259
304
|
}
|
|
260
305
|
|
|
261
306
|
/** Unpin the triggering message in the current chat. */
|
|
262
|
-
unpinMessage() {
|
|
263
|
-
|
|
307
|
+
async unpinMessage() {
|
|
308
|
+
const peer = await this._peer();
|
|
309
|
+
return this.client.unpinMessage(peer, this.message.id);
|
|
264
310
|
}
|
|
265
311
|
|
|
266
312
|
/** Show the "typing..." / "sending photo..." indicator. */
|
|
267
|
-
sendChatAction(action = "typing") {
|
|
313
|
+
async sendChatAction(action = "typing") {
|
|
314
|
+
const peer = await this._peer();
|
|
268
315
|
const actions = {
|
|
269
316
|
typing: new Api.SendMessageTypingAction(),
|
|
270
317
|
photo: new Api.SendMessageUploadPhotoAction({ progress: 0 }),
|
|
@@ -275,7 +322,7 @@ class Context {
|
|
|
275
322
|
};
|
|
276
323
|
return this.client.invoke(
|
|
277
324
|
new Api.messages.SetTyping({
|
|
278
|
-
peer
|
|
325
|
+
peer,
|
|
279
326
|
action: actions[action] || actions.typing,
|
|
280
327
|
})
|
|
281
328
|
);
|