@xbibzlibrary/telebibz 3.1.2 → 3.1.4
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/CHANGELOG.md +31 -0
- package/LICENSE +1 -1
- package/NOTICE.md +15 -8
- package/README.id.md +312 -43
- package/README.md +312 -43
- package/examples/08-rich-message.js +34 -0
- package/index.d.ts +303 -96
- package/index.js +8 -2
- package/lib/api.js +20 -6
- package/lib/composer.js +14 -2
- package/lib/context.js +83 -21
- package/lib/file.js +8 -1
- package/lib/inline-query.js +6 -1
- package/lib/logger.js +44 -14
- package/lib/ratelimit.js +16 -3
- package/lib/rich.js +175 -0
- package/lib/runner.js +14 -5
- package/lib/session.js +12 -4
- package/lib/telebibz.js +40 -24
- package/lib/telegram-methods.js +192 -0
- package/package.json +14 -10
- package/test/audit.test.js +355 -0
- package/test/types.test.ts +40 -0
- package/types/telegram-bot-api/LICENSE +21 -0
- package/types/telegram-bot-api/api.d.ts +22 -0
- package/types/telegram-bot-api/checklist.d.ts +72 -0
- package/types/telegram-bot-api/inline.d.ts +692 -0
- package/types/telegram-bot-api/langs.d.ts +193 -0
- package/types/telegram-bot-api/manage.d.ts +1153 -0
- package/types/telegram-bot-api/markup.d.ts +278 -0
- package/types/telegram-bot-api/message.d.ts +1557 -0
- package/types/telegram-bot-api/methods.d.ts +2860 -0
- package/types/telegram-bot-api/mod.d.ts +14 -0
- package/types/telegram-bot-api/passport.d.ts +163 -0
- package/types/telegram-bot-api/payment.d.ts +576 -0
- package/types/telegram-bot-api/rich.d.ts +1198 -0
- package/types/telegram-bot-api/settings.d.ts +120 -0
- package/types/telegram-bot-api/story.d.ts +89 -0
- package/types/telegram-bot-api/update.d.ts +86 -0
package/lib/context.js
CHANGED
|
@@ -8,15 +8,18 @@ const fs = require('fs');
|
|
|
8
8
|
/** Objek "pesan" dari berbagai field update (gaya ctx.msg). */
|
|
9
9
|
function msgOf(update) {
|
|
10
10
|
return update.message || update.edited_message || update.channel_post ||
|
|
11
|
-
update.edited_channel_post || update.business_message || update.edited_business_message ||
|
|
11
|
+
update.edited_channel_post || update.business_message || update.edited_business_message ||
|
|
12
|
+
update.guest_message || null;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const UPDATE_KEYS = [
|
|
15
16
|
'message', 'edited_message', 'channel_post', 'edited_channel_post',
|
|
16
|
-
'business_message', 'edited_business_message', '
|
|
17
|
+
'business_message', 'edited_business_message', 'deleted_business_messages', 'guest_message',
|
|
18
|
+
'callback_query', 'inline_query',
|
|
17
19
|
'chosen_inline_result', 'chat_join_request', 'chat_member', 'my_chat_member',
|
|
18
20
|
'message_reaction', 'message_reaction_count', 'business_connection',
|
|
19
21
|
'poll', 'poll_answer', 'shipping_query', 'pre_checkout_query', 'purchased_paid_media',
|
|
22
|
+
'chat_boost', 'removed_chat_boost', 'stopped_message_generation',
|
|
20
23
|
];
|
|
21
24
|
|
|
22
25
|
class Context {
|
|
@@ -37,6 +40,10 @@ class Context {
|
|
|
37
40
|
if (this.chat_join_request) return this.chat_join_request.chat;
|
|
38
41
|
if (this.chat_member) return this.chat_member.chat;
|
|
39
42
|
if (this.my_chat_member) return this.my_chat_member.chat;
|
|
43
|
+
if (this.deleted_business_messages) return this.deleted_business_messages.chat;
|
|
44
|
+
if (this.chat_boost) return this.chat_boost.chat;
|
|
45
|
+
if (this.removed_chat_boost) return this.removed_chat_boost.chat;
|
|
46
|
+
if (this.stopped_message_generation) return this.stopped_message_generation.chat;
|
|
40
47
|
return undefined;
|
|
41
48
|
}
|
|
42
49
|
get from() {
|
|
@@ -49,9 +56,11 @@ class Context {
|
|
|
49
56
|
get chatId() { const c = this.chat; return c ? c.id : undefined; }
|
|
50
57
|
get msgId() { return this.msg ? this.msg.message_id : undefined; }
|
|
51
58
|
get businessConnectionId() {
|
|
59
|
+
const msg = this.msg || (this.callback_query && this.callback_query.message);
|
|
52
60
|
return (this.business_connection && this.business_connection.id) ||
|
|
53
|
-
(
|
|
61
|
+
(msg && msg.business_connection_id) || undefined;
|
|
54
62
|
}
|
|
63
|
+
get guestQueryId() { return this.msg && this.msg.guest_query_id; }
|
|
55
64
|
get senderChat() { return this.msg ? this.msg.sender_chat : undefined; }
|
|
56
65
|
get inlineMessageId() { return this.callback_query ? this.callback_query.inline_message_id : undefined; }
|
|
57
66
|
|
|
@@ -62,13 +71,33 @@ class Context {
|
|
|
62
71
|
return c;
|
|
63
72
|
}
|
|
64
73
|
_biz(extra = {}) {
|
|
65
|
-
//
|
|
74
|
+
// Sertakan business_connection_id untuk pesan business, termasuk callback atas pesannya.
|
|
75
|
+
const msg = this.msg || (this.callback_query && this.callback_query.message);
|
|
66
76
|
const bc = this.businessConnectionId;
|
|
67
|
-
return bc &&
|
|
77
|
+
return bc && msg && msg.business_connection_id ? { business_connection_id: bc, ...extra } : extra;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
/* ---------- kirim balasan ---------- */
|
|
71
81
|
reply(text, extra = {}) { return this.api.sendMessage(this._cid(), text, this._biz(extra)); }
|
|
82
|
+
replyWithRichMessage(richMessage, extra = {}) {
|
|
83
|
+
return this.api.sendRichMessage({ chat_id: this._cid(), rich_message: richMessage, ...this._biz(extra) });
|
|
84
|
+
}
|
|
85
|
+
replyWithLivePhoto(livePhoto, photo, extra = {}) {
|
|
86
|
+
return this.api.sendLivePhoto({ chat_id: this._cid(), live_photo: livePhoto, photo, ...this._biz(extra) });
|
|
87
|
+
}
|
|
88
|
+
replyEphemeral(text, receiverUserId, extra = {}) {
|
|
89
|
+
const { ephemeral_message_parameters = {}, ...other } = extra;
|
|
90
|
+
return this.api.sendMessage(this._cid(), text, {
|
|
91
|
+
...this._biz(other),
|
|
92
|
+
ephemeral_message_parameters: { ...ephemeral_message_parameters, receiver_user_id: receiverUserId },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
sendMessageDraft(draftId, text, extra = {}) {
|
|
96
|
+
return this.api.sendMessageDraft({ chat_id: this._cid(), draft_id: draftId, text, ...this._biz(extra) });
|
|
97
|
+
}
|
|
98
|
+
sendRichMessageDraft(draftId, richMessage, extra = {}) {
|
|
99
|
+
return this.api.sendRichMessageDraft({ chat_id: this._cid(), draft_id: draftId, rich_message: richMessage, ...this._biz(extra) });
|
|
100
|
+
}
|
|
72
101
|
replyWithHTML(text, extra = {}) { return this.reply(text, { parse_mode: 'HTML', ...extra }); }
|
|
73
102
|
replyWithMarkdown(text, extra = {}) { return this.reply(text, { parse_mode: 'MarkdownV2', ...extra }); }
|
|
74
103
|
replyWithPhoto(p, e = {}) { return this.api.sendPhoto(this._cid(), p, this._biz(e)); }
|
|
@@ -76,19 +105,19 @@ class Context {
|
|
|
76
105
|
replyWithAudio(a, e = {}) { return this.api.sendAudio(this._cid(), a, this._biz(e)); }
|
|
77
106
|
replyWithDocument(d, e = {}) { return this.api.sendDocument(this._cid(), d, this._biz(e)); }
|
|
78
107
|
replyWithAnimation(a, e = {}) { return this.api.sendAnimation(this._cid(), a, this._biz(e)); }
|
|
79
|
-
replyWithVoice(v, e = {}) { return this.api.sendVoice(this._cid(), v,
|
|
80
|
-
replyWithVideoNote(v, e = {}) { return this.api.sendVideoNote(this._cid(), v, e); }
|
|
81
|
-
replyWithSticker(s, e = {}) { return this.api.sendSticker(this._cid(), s, e); }
|
|
82
|
-
replyWithMediaGroup(media, e = {}) { return this.api.sendMediaGroup(this._cid(), media, e); }
|
|
83
|
-
replyWithLocation(lat, lon, e = {}) { return this.api.sendLocation(this._cid(), lat, lon, e); }
|
|
84
|
-
replyWithVenue(lat, lon, title, address, e = {}) { return this.api.sendVenue(this._cid(), lat, lon, title, address, e); }
|
|
85
|
-
replyWithContact(phone, first, e = {}) { return this.api.sendContact(this._cid(), phone, first, e); }
|
|
86
|
-
replyWithPoll(question, options, e = {}) { return this.api.sendPoll(this._cid(), question, options, e); }
|
|
87
|
-
replyWithDice(emoji, e = {}) { return this.api.sendDice(this._cid(), emoji, e); }
|
|
108
|
+
replyWithVoice(v, e = {}) { return this.api.sendVoice(this._cid(), v, this._biz(e)); }
|
|
109
|
+
replyWithVideoNote(v, e = {}) { return this.api.sendVideoNote(this._cid(), v, this._biz(e)); }
|
|
110
|
+
replyWithSticker(s, e = {}) { return this.api.sendSticker(this._cid(), s, this._biz(e)); }
|
|
111
|
+
replyWithMediaGroup(media, e = {}) { return this.api.sendMediaGroup(this._cid(), media, this._biz(e)); }
|
|
112
|
+
replyWithLocation(lat, lon, e = {}) { return this.api.sendLocation(this._cid(), lat, lon, this._biz(e)); }
|
|
113
|
+
replyWithVenue(lat, lon, title, address, e = {}) { return this.api.sendVenue(this._cid(), lat, lon, title, address, this._biz(e)); }
|
|
114
|
+
replyWithContact(phone, first, e = {}) { return this.api.sendContact(this._cid(), phone, first, this._biz(e)); }
|
|
115
|
+
replyWithPoll(question, options, e = {}) { return this.api.sendPoll(this._cid(), question, options, this._biz(e)); }
|
|
116
|
+
replyWithDice(emoji, e = {}) { return this.api.sendDice(this._cid(), emoji, this._biz(e)); }
|
|
88
117
|
replyWithInvoice(title, description, payload, currency, prices, e = {}) {
|
|
89
|
-
return this.api.sendInvoice(this._cid(), title, description, payload, currency, prices, e);
|
|
118
|
+
return this.api.sendInvoice(this._cid(), title, description, payload, currency, prices, this._biz(e));
|
|
90
119
|
}
|
|
91
|
-
replyWithChatAction(action = 'typing', e = {}) { return this.api.sendChatAction(this._cid(), action, e); }
|
|
120
|
+
replyWithChatAction(action = 'typing', e = {}) { return this.api.sendChatAction(this._cid(), action, this._biz(e)); }
|
|
92
121
|
|
|
93
122
|
/* ---------- teruskan & salin ---------- */
|
|
94
123
|
forwardMessage(to, from, messageId, e = {}) {
|
|
@@ -104,22 +133,50 @@ class Context {
|
|
|
104
133
|
editMessageText(text, e = {}) {
|
|
105
134
|
if (this.inlineMessageId) return this.api.callApi('editMessageText', { inline_message_id: this.inlineMessageId, text, ...e });
|
|
106
135
|
const m = this.callback_query && this.callback_query.message;
|
|
107
|
-
return this.api.editMessageText(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, text, e);
|
|
136
|
+
return this.api.editMessageText(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, text, this._biz(e));
|
|
137
|
+
}
|
|
138
|
+
editRichMessage(richMessage, e = {}) {
|
|
139
|
+
const m = this.callback_query && this.callback_query.message;
|
|
140
|
+
if (this.inlineMessageId) return this.api.callApi('editMessageText', { inline_message_id: this.inlineMessageId, rich_message: richMessage, ...e });
|
|
141
|
+
return this.api.callApi('editMessageText', {
|
|
142
|
+
chat_id: this._cid(m ? m.chat.id : undefined),
|
|
143
|
+
message_id: m ? m.message_id : this.msgId,
|
|
144
|
+
rich_message: richMessage,
|
|
145
|
+
...this._biz(e),
|
|
146
|
+
});
|
|
108
147
|
}
|
|
109
148
|
editMessageCaption(e = {}) {
|
|
110
149
|
const m = this.callback_query && this.callback_query.message;
|
|
111
150
|
if (this.inlineMessageId) return this.api.callApi('editMessageCaption', { inline_message_id: this.inlineMessageId, ...e });
|
|
112
|
-
return this.api.editMessageCaption(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, e);
|
|
151
|
+
return this.api.editMessageCaption(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, this._biz(e));
|
|
113
152
|
}
|
|
114
153
|
editMessageMedia(media, e = {}) {
|
|
115
154
|
const m = this.callback_query && this.callback_query.message;
|
|
116
155
|
if (this.inlineMessageId) return this.api.callApi('editMessageMedia', { inline_message_id: this.inlineMessageId, media, ...e });
|
|
117
|
-
return this.api.editMessageMedia(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, media, e);
|
|
156
|
+
return this.api.editMessageMedia(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, media, this._biz(e));
|
|
118
157
|
}
|
|
119
158
|
editMessageReplyMarkup(e = {}) {
|
|
120
159
|
const m = this.callback_query && this.callback_query.message;
|
|
121
160
|
if (this.inlineMessageId) return this.api.callApi('editMessageReplyMarkup', { inline_message_id: this.inlineMessageId, ...e });
|
|
122
|
-
return this.api.editMessageReplyMarkup(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, e);
|
|
161
|
+
return this.api.editMessageReplyMarkup(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, this._biz(e));
|
|
162
|
+
}
|
|
163
|
+
editEphemeralMessageText(receiverUserId, ephemeralMessageId, text, extra = {}) {
|
|
164
|
+
return this.api.editEphemeralMessageText({ chat_id: this._cid(), receiver_user_id: receiverUserId, ephemeral_message_id: ephemeralMessageId, text, ...extra });
|
|
165
|
+
}
|
|
166
|
+
editEphemeralRichMessage(receiverUserId, ephemeralMessageId, richMessage, extra = {}) {
|
|
167
|
+
return this.api.editEphemeralMessageText({ chat_id: this._cid(), receiver_user_id: receiverUserId, ephemeral_message_id: ephemeralMessageId, rich_message: richMessage, ...extra });
|
|
168
|
+
}
|
|
169
|
+
editEphemeralMessageMedia(receiverUserId, ephemeralMessageId, media, extra = {}) {
|
|
170
|
+
return this.api.editEphemeralMessageMedia({ chat_id: this._cid(), receiver_user_id: receiverUserId, ephemeral_message_id: ephemeralMessageId, media, ...extra });
|
|
171
|
+
}
|
|
172
|
+
editEphemeralMessageCaption(receiverUserId, ephemeralMessageId, extra = {}) {
|
|
173
|
+
return this.api.editEphemeralMessageCaption({ chat_id: this._cid(), receiver_user_id: receiverUserId, ephemeral_message_id: ephemeralMessageId, ...extra });
|
|
174
|
+
}
|
|
175
|
+
editEphemeralMessageReplyMarkup(receiverUserId, ephemeralMessageId, extra = {}) {
|
|
176
|
+
return this.api.editEphemeralMessageReplyMarkup({ chat_id: this._cid(), receiver_user_id: receiverUserId, ephemeral_message_id: ephemeralMessageId, ...extra });
|
|
177
|
+
}
|
|
178
|
+
deleteEphemeralMessage(receiverUserId, ephemeralMessageId) {
|
|
179
|
+
return this.api.deleteEphemeralMessage({ chat_id: this._cid(), receiver_user_id: receiverUserId, ephemeral_message_id: ephemeralMessageId });
|
|
123
180
|
}
|
|
124
181
|
deleteMessage(chatId, messageId) {
|
|
125
182
|
if (messageId !== undefined) return this.api.deleteMessage(chatId, messageId);
|
|
@@ -128,8 +185,9 @@ class Context {
|
|
|
128
185
|
}
|
|
129
186
|
deleteMessages(ids) { return this.api.deleteMessages(this._cid(), ids); }
|
|
130
187
|
react(emoji = '👍', e = {}) {
|
|
188
|
+
const m = this.callback_query && this.callback_query.message;
|
|
131
189
|
const reaction = [{ type: 'emoji', emoji }];
|
|
132
|
-
return this.api.setMessageReaction(this._cid(), this.msgId, reaction, e);
|
|
190
|
+
return this.api.setMessageReaction(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, reaction, e);
|
|
133
191
|
}
|
|
134
192
|
|
|
135
193
|
/* ---------- callback & inline ---------- */
|
|
@@ -142,6 +200,10 @@ class Context {
|
|
|
142
200
|
if (!this.inline_query) return Promise.resolve(false);
|
|
143
201
|
return this.api.answerInlineQuery(this.inline_query.id, results, e);
|
|
144
202
|
}
|
|
203
|
+
answerGuestQuery(result) {
|
|
204
|
+
if (!this.guestQueryId) return Promise.resolve(false);
|
|
205
|
+
return this.api.answerGuestQuery({ guest_query_id: this.guestQueryId, result });
|
|
206
|
+
}
|
|
145
207
|
|
|
146
208
|
/* ---------- admin & grup ---------- */
|
|
147
209
|
banChatMember(cid, uid, e = {}) { return this.api.banChatMember(cid, uid, e); }
|
package/lib/file.js
CHANGED
|
@@ -33,9 +33,16 @@ class InputFile extends File {}
|
|
|
33
33
|
const InputMediaBuilder = {
|
|
34
34
|
photo: (media, extra = {}) => ({ type: 'photo', media, ...extra }),
|
|
35
35
|
video: (media, extra = {}) => ({ type: 'video', media, ...extra }),
|
|
36
|
+
livePhoto: (media, photo, extra = {}) => ({ type: 'live_photo', media, photo, ...extra }),
|
|
36
37
|
document: (media, extra = {}) => ({ type: 'document', media, ...extra }),
|
|
37
38
|
audio: (media, extra = {}) => ({ type: 'audio', media, ...extra }),
|
|
38
39
|
animation: (media, extra = {}) => ({ type: 'animation', media, ...extra }),
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
const InputPaidMediaBuilder = {
|
|
43
|
+
photo: (media, extra = {}) => ({ type: 'photo', media, ...extra }),
|
|
44
|
+
video: (media, extra = {}) => ({ type: 'video', media, ...extra }),
|
|
45
|
+
livePhoto: (media, photo, extra = {}) => ({ type: 'live_photo', media, photo, ...extra }),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports = { File, InputFile, InputMediaBuilder, InputPaidMediaBuilder };
|
package/lib/inline-query.js
CHANGED
|
@@ -9,13 +9,18 @@ function matchInlineQuery(trigger) {
|
|
|
9
9
|
if (trigger === undefined || trigger === '*') return true;
|
|
10
10
|
const list = [].concat(trigger);
|
|
11
11
|
const text = q.query || '';
|
|
12
|
-
return list.some((t) =>
|
|
12
|
+
return list.some((t) => {
|
|
13
|
+
if (!(t instanceof RegExp)) return text.toLowerCase().includes(String(t).toLowerCase());
|
|
14
|
+
t.lastIndex = 0;
|
|
15
|
+
return t.test(text);
|
|
16
|
+
});
|
|
13
17
|
};
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/** Builder hasil inline (persis spek Bot API). */
|
|
17
21
|
const iq = {
|
|
18
22
|
article: (id, title, extra = {}) => ({ type: 'article', id: String(id), title, input_message_content: { message_text: extra.message_text || title, ...(extra.input_message_content || {}) }, ...omit(extra, ['message_text', 'input_message_content']) }),
|
|
23
|
+
richArticle: (id, title, richMessage, extra = {}) => ({ type: 'article', id: String(id), title, input_message_content: { rich_message: richMessage }, ...omit(extra, ['input_message_content']) }),
|
|
19
24
|
photo: (id, url, thumb, extra = {}) => ({ type: 'photo', id: String(id), photo_url: url, thumbnail_url: thumb || url, ...extra }),
|
|
20
25
|
gif: (id, url, thumb, extra = {}) => ({ type: 'gif', id: String(id), gif_url: url, thumbnail_url: thumb || url, ...extra }),
|
|
21
26
|
video: (id, url, thumb, title, extra = {}) => ({ type: 'video', id: String(id), video_url: url, thumbnail_url: thumb, title, mime_type: 'video/mp4', input_message_content: { message_text: url }, ...extra }),
|
package/lib/logger.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
// lib/logger.js — log terminal
|
|
1
|
+
// lib/logger.js — log terminal rapi, berwarna, dan mudah dibaca.
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const C = {
|
|
5
|
-
reset: '
|
|
6
|
-
red: '
|
|
7
|
-
magenta: '
|
|
5
|
+
reset: '\u001b[0m', dim: '\u001b[2m', bold: '\u001b[1m',
|
|
6
|
+
red: '\u001b[31m', green: '\u001b[32m', yellow: '\u001b[33m', blue: '\u001b[34m',
|
|
7
|
+
magenta: '\u001b[35m', cyan: '\u001b[36m', gray: '\u001b[90m',
|
|
8
|
+
developer: '\u001b[1;35m', border: '\u001b[36m',
|
|
9
|
+
title: '\u001b[1;97;44m', panel: '\u001b[97;44m',
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
function paint(code, s) {
|
|
11
|
-
|
|
13
|
+
const colorsEnabled = process.stdout && process.stdout.isTTY && !process.env.NO_COLOR;
|
|
14
|
+
return colorsEnabled ? `${C[code]}${s}${C.reset}` : String(s);
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
function stamp() {
|
|
@@ -23,16 +26,43 @@ const log = {
|
|
|
23
26
|
paint,
|
|
24
27
|
};
|
|
25
28
|
|
|
26
|
-
/** Banner
|
|
27
|
-
log.banner = (title, rows) => {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
/** Banner boot: identitas developer berada di atas panel warna. */
|
|
30
|
+
log.banner = (title, rows = [], developer = 'Xbibz Technology ID') => {
|
|
31
|
+
const innerWidth = Math.max(displayWidth(title), ...rows.map((r) => displayWidth(strip(r)))) + 4;
|
|
32
|
+
const top = `┏${'━'.repeat(innerWidth)}┓`;
|
|
33
|
+
const divider = `┣${'━'.repeat(innerWidth)}┫`;
|
|
34
|
+
const bottom = `┗${'━'.repeat(innerWidth)}┛`;
|
|
35
|
+
const line = (text, color) => {
|
|
36
|
+
const plain = ` ${text}${' '.repeat(Math.max(0, innerWidth - displayWidth(text) - 4))} `;
|
|
37
|
+
console.log(`${paint('border', '┃')}${paint(color, plain)}${paint('border', '┃')}`);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
console.log(paint('developer', `◆ DEVELOPER ${developer}`));
|
|
41
|
+
console.log(paint('border', top));
|
|
42
|
+
line(title, 'title');
|
|
43
|
+
console.log(paint('border', divider));
|
|
44
|
+
for (const row of rows) line(strip(row), 'panel');
|
|
45
|
+
console.log(paint('border', bottom));
|
|
34
46
|
};
|
|
35
47
|
|
|
36
|
-
function strip(s) { return String(s).replace(
|
|
48
|
+
function strip(s) { return String(s).replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, ''); }
|
|
49
|
+
|
|
50
|
+
// Hitung lebar tampilan terminal (emoji/CJK = 2 kolom; combining mark = 0).
|
|
51
|
+
function displayWidth(value) {
|
|
52
|
+
let width = 0;
|
|
53
|
+
for (const ch of String(value)) {
|
|
54
|
+
const cp = ch.codePointAt(0);
|
|
55
|
+
if (/\p{Mark}/u.test(ch) || cp === 0x200d || (cp >= 0xfe00 && cp <= 0xfe0f)) continue;
|
|
56
|
+
width += cp >= 0x1100 && (
|
|
57
|
+
cp <= 0x115f || cp === 0x2329 || cp === 0x232a ||
|
|
58
|
+
(cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
|
|
59
|
+
(cp >= 0xac00 && cp <= 0xd7a3) || (cp >= 0xf900 && cp <= 0xfaff) ||
|
|
60
|
+
(cp >= 0xfe10 && cp <= 0xfe19) || (cp >= 0xfe30 && cp <= 0xfe6f) ||
|
|
61
|
+
(cp >= 0xff00 && cp <= 0xff60) || (cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
62
|
+
(cp >= 0x1f300 && cp <= 0x1faff) || (cp >= 0x20000 && cp <= 0x3fffd)
|
|
63
|
+
) ? 2 : 1;
|
|
64
|
+
}
|
|
65
|
+
return width;
|
|
66
|
+
}
|
|
37
67
|
|
|
38
68
|
module.exports = log;
|
package/lib/ratelimit.js
CHANGED
|
@@ -28,24 +28,31 @@ function autoRetry({ maxRetry = 5, baseDelayMs = 500 } = {}) {
|
|
|
28
28
|
/** Transformer antrean: jaga maks `perSecond` panggilan API global.
|
|
29
29
|
* Pasang: bot.api.config.use(throttler()) */
|
|
30
30
|
function throttler({ perSecond = 28 } = {}) {
|
|
31
|
+
if (!Number.isFinite(perSecond) || perSecond <= 0) throw new RangeError('throttler: perSecond harus > 0');
|
|
31
32
|
const interval = 1000 / perSecond;
|
|
32
33
|
let last = 0; let chain = Promise.resolve();
|
|
33
34
|
return (prev, method, payload) => {
|
|
34
|
-
|
|
35
|
+
const task = chain.then(async () => {
|
|
35
36
|
const wait = last + interval - Date.now();
|
|
36
37
|
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
37
38
|
last = Date.now();
|
|
38
39
|
return prev(method, payload);
|
|
39
40
|
});
|
|
40
|
-
|
|
41
|
+
// Satu request gagal tidak boleh meracuni antrean request berikutnya.
|
|
42
|
+
chain = task.then(() => undefined, () => undefined);
|
|
43
|
+
return task;
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
/** Middleware anti-spam: maks `limit` update per user per `windowMs`. */
|
|
45
48
|
function limiter({ windowMs = 2000, limit = 3, onExceeded } = {}) {
|
|
49
|
+
if (!Number.isFinite(windowMs) || windowMs <= 0 || !Number.isInteger(limit) || limit <= 0) {
|
|
50
|
+
throw new RangeError('limiter: windowMs dan limit harus > 0');
|
|
51
|
+
}
|
|
46
52
|
const bucket = new Map();
|
|
53
|
+
let checks = 0;
|
|
47
54
|
return async (ctx, next) => {
|
|
48
|
-
const key = ctx.from ? ctx.from.id :
|
|
55
|
+
const key = ctx.from && ctx.from.id !== undefined ? `u:${ctx.from.id}` : `c:${ctx.chatId ?? '?'}`;
|
|
49
56
|
const now = Date.now();
|
|
50
57
|
const arr = bucket.get(key) || [];
|
|
51
58
|
const alive = arr.filter((t) => now - t < windowMs);
|
|
@@ -54,6 +61,12 @@ function limiter({ windowMs = 2000, limit = 3, onExceeded } = {}) {
|
|
|
54
61
|
return; // diamkan spam
|
|
55
62
|
}
|
|
56
63
|
alive.push(now); bucket.set(key, alive);
|
|
64
|
+
// Bersihkan bucket user/chat tidak aktif agar Map tidak tumbuh tanpa batas.
|
|
65
|
+
if (++checks % 256 === 0) {
|
|
66
|
+
for (const [k, times] of bucket) {
|
|
67
|
+
if (!times.some((t) => now - t < windowMs)) bucket.delete(k);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
57
70
|
return next();
|
|
58
71
|
};
|
|
59
72
|
}
|
package/lib/rich.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// lib/rich.js — builders for Telegram Bot API 10.3 Rich Messages.
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { File } = require('./file');
|
|
5
|
+
const CONTENT_FIELDS = ['html', 'markdown', 'blocks'];
|
|
6
|
+
const INPUT_BLOCK_TYPES = new Set([
|
|
7
|
+
'paragraph', 'heading', 'pre', 'footer', 'divider', 'mathematical_expression', 'anchor',
|
|
8
|
+
'list', 'blockquote', 'expandable_blockquote', 'pullquote', 'collage', 'slideshow',
|
|
9
|
+
'table', 'details', 'map', 'animation', 'audio', 'document', 'photo', 'video',
|
|
10
|
+
'voice_note', 'buttons', 'thinking',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function inputRichMessage(content, options = {}) {
|
|
14
|
+
const fields = CONTENT_FIELDS.filter((key) => Object.hasOwn(content, key));
|
|
15
|
+
if (fields.length !== 1) throw new TypeError('InputRichMessage harus memiliki tepat satu dari html, markdown, atau blocks');
|
|
16
|
+
if (fields[0] === 'blocks' && !Array.isArray(content.blocks)) throw new TypeError('InputRichMessage.blocks harus berupa array');
|
|
17
|
+
for (const key of CONTENT_FIELDS) {
|
|
18
|
+
if (Object.hasOwn(options, key)) throw new TypeError(`Gunakan rich.${key}() untuk menetapkan konten, jangan lewat options`);
|
|
19
|
+
}
|
|
20
|
+
return { ...content, ...options };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertDraftHasNoFileUpload(value) {
|
|
24
|
+
if (value instanceof File) throw new TypeError('Rich message draft tidak mendukung upload File baru; gunakan file_id Telegram bila perlu');
|
|
25
|
+
if (!value || typeof value !== 'object') return;
|
|
26
|
+
for (const child of Array.isArray(value) ? value : Object.values(value)) assertDraftHasNoFileUpload(child);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function draftRichMessage(content, options = {}) {
|
|
30
|
+
const result = inputRichMessage(content, options);
|
|
31
|
+
assertDraftHasNoFileUpload(result);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function block(type, props = {}) {
|
|
36
|
+
if (!INPUT_BLOCK_TYPES.has(type)) throw new TypeError(`Jenis InputRichBlock tidak dikenal: ${type}`);
|
|
37
|
+
const { type: _ignored, ...rest } = props;
|
|
38
|
+
return { ...rest, type };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const textEntity = (type, text, extra = {}) => ({ type, text, ...extra });
|
|
42
|
+
const caption = (text, credit) => ({ text, ...(credit !== undefined ? { credit } : {}) });
|
|
43
|
+
function richMessageButton(text, action, style) {
|
|
44
|
+
if (!action || typeof action !== 'object') throw new TypeError('rich.button() membutuhkan satu jenis aksi button');
|
|
45
|
+
const keys = ['url', 'callback_data', 'web_app', 'login_url', 'switch_inline_query', 'switch_inline_query_current_chat', 'switch_inline_query_chosen_chat', 'copy_text', 'disabled'];
|
|
46
|
+
const defined = keys.filter((key) => Object.hasOwn(action, key));
|
|
47
|
+
if (defined.length !== 1) throw new TypeError('RichMessageButton harus memiliki tepat satu field aksi');
|
|
48
|
+
if (style !== undefined && !['danger', 'success', 'primary', 'link'].includes(style)) {
|
|
49
|
+
throw new TypeError('Style rich button harus danger, success, primary, atau link');
|
|
50
|
+
}
|
|
51
|
+
if (style === 'link' && defined[0] !== 'callback_data') {
|
|
52
|
+
throw new TypeError('Style link hanya didukung pada rich callback button');
|
|
53
|
+
}
|
|
54
|
+
return { text, ...action, ...(style ? { style } : {}) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const rich = {
|
|
58
|
+
html: (html, options = {}) => inputRichMessage({ html: String(html) }, options),
|
|
59
|
+
markdown: (markdown, options = {}) => inputRichMessage({ markdown: String(markdown) }, options),
|
|
60
|
+
blocks: (blocks, options = {}) => inputRichMessage({ blocks: [...blocks] }, options),
|
|
61
|
+
draftHtml: (html, options = {}) => draftRichMessage({ html: String(html) }, options),
|
|
62
|
+
draftMarkdown: (markdown, options = {}) => draftRichMessage({ markdown: String(markdown) }, options),
|
|
63
|
+
draftBlocks: (blocks, options = {}) => draftRichMessage({ blocks: [...blocks] }, options),
|
|
64
|
+
block,
|
|
65
|
+
|
|
66
|
+
// RichText inline entities.
|
|
67
|
+
bold: (text) => textEntity('bold', text),
|
|
68
|
+
italic: (text) => textEntity('italic', text),
|
|
69
|
+
underline: (text) => textEntity('underline', text),
|
|
70
|
+
strikethrough: (text) => textEntity('strikethrough', text),
|
|
71
|
+
spoiler: (text) => textEntity('spoiler', text),
|
|
72
|
+
subscript: (text) => textEntity('subscript', text),
|
|
73
|
+
superscript: (text) => textEntity('superscript', text),
|
|
74
|
+
marked: (text) => textEntity('marked', text),
|
|
75
|
+
code: (text) => textEntity('code', text),
|
|
76
|
+
dateTime: (text, unixTime, format = 'r') => ({ type: 'date_time', text, unix_time: unixTime, date_time_format: format }),
|
|
77
|
+
textMention: (text, user) => ({ type: 'text_mention', text, user }),
|
|
78
|
+
customEmoji: (customEmojiId, alternativeText) => ({ type: 'custom_emoji', custom_emoji_id: String(customEmojiId), alternative_text: alternativeText }),
|
|
79
|
+
mathText: (expression) => ({ type: 'mathematical_expression', expression }),
|
|
80
|
+
url: (text, url) => textEntity('url', text, { url }),
|
|
81
|
+
email: (text, emailAddress) => textEntity('email_address', text, { email_address: emailAddress }),
|
|
82
|
+
phone: (text, phoneNumber) => textEntity('phone_number', text, { phone_number: phoneNumber }),
|
|
83
|
+
bankCard: (text, bankCardNumber) => textEntity('bank_card_number', text, { bank_card_number: bankCardNumber }),
|
|
84
|
+
mention: (text, username) => textEntity('mention', text, { username }),
|
|
85
|
+
hashtag: (text, hashtag) => textEntity('hashtag', text, { hashtag }),
|
|
86
|
+
cashtag: (text, cashtag) => textEntity('cashtag', text, { cashtag }),
|
|
87
|
+
botCommand: (text, botCommand) => textEntity('bot_command', text, { bot_command: botCommand }),
|
|
88
|
+
anchorText: (name) => ({ type: 'anchor', name }),
|
|
89
|
+
anchorLink: (text, anchorName) => textEntity('anchor_link', text, { anchor_name: anchorName }),
|
|
90
|
+
reference: (text, name) => textEntity('reference', text, { name }),
|
|
91
|
+
referenceLink: (text, referenceName) => textEntity('reference_link', text, { reference_name: referenceName }),
|
|
92
|
+
button: (text, action, style) => richMessageButton(text, action, style),
|
|
93
|
+
buttonText: (text, action, style) => ({ type: 'button', button: richMessageButton(text, action, style) }),
|
|
94
|
+
|
|
95
|
+
// InputRichBlock builders.
|
|
96
|
+
paragraph: (text) => block('paragraph', { text }),
|
|
97
|
+
heading: (text, size = 2) => {
|
|
98
|
+
if (!Number.isInteger(size) || size < 1 || size > 6) throw new RangeError('rich.heading(): size harus 1–6');
|
|
99
|
+
return block('heading', { text, size });
|
|
100
|
+
},
|
|
101
|
+
pre: (text, language) => block('pre', { text, ...(language ? { language } : {}) }),
|
|
102
|
+
footer: (text) => block('footer', { text }),
|
|
103
|
+
divider: () => block('divider'),
|
|
104
|
+
mathBlock: (expression) => block('mathematical_expression', { expression }),
|
|
105
|
+
anchor: (name) => block('anchor', { name }),
|
|
106
|
+
list: (items) => block('list', { items: items.map((item) => {
|
|
107
|
+
if (Array.isArray(item)) return { blocks: item };
|
|
108
|
+
if (typeof item === 'string' || typeof item === 'number') return { blocks: [block('paragraph', { text: String(item) })] };
|
|
109
|
+
return item;
|
|
110
|
+
}) }),
|
|
111
|
+
quote: (blocks, credit) => block('blockquote', { blocks, ...(credit !== undefined ? { credit } : {}) }),
|
|
112
|
+
expandableQuote: (text, credit) => block('expandable_blockquote', { text, ...(credit !== undefined ? { credit } : {}) }),
|
|
113
|
+
pullQuote: (text, credit) => block('pullquote', { text, ...(credit !== undefined ? { credit } : {}) }),
|
|
114
|
+
collage: (blocks, captionText, credit) => block('collage', { blocks, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
115
|
+
slideshow: (blocks, captionText, credit) => block('slideshow', { blocks, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
116
|
+
table: (cells, options = {}) => block('table', {
|
|
117
|
+
cells,
|
|
118
|
+
...(options.bordered ? { is_bordered: true } : {}),
|
|
119
|
+
...(options.striped ? { is_striped: true } : {}),
|
|
120
|
+
...(options.compact ? { is_compact: true } : {}),
|
|
121
|
+
...(options.caption !== undefined ? { caption: options.caption } : {}),
|
|
122
|
+
}),
|
|
123
|
+
details: (summary, blocks, open = false) => block('details', { summary, blocks, ...(open ? { is_open: true } : {}) }),
|
|
124
|
+
map: (location, zoom, width, height, captionText, credit) => block('map', {
|
|
125
|
+
location, zoom, width, height,
|
|
126
|
+
...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}),
|
|
127
|
+
}),
|
|
128
|
+
animation: (animation, captionText, credit) => block('animation', { animation, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
129
|
+
audio: (audio, captionText, credit) => block('audio', { audio, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
130
|
+
document: (document, captionText, credit) => block('document', { document, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
131
|
+
photo: (photo, captionText, credit) => block('photo', { photo, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
132
|
+
video: (video, captionText, credit) => block('video', { video, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
133
|
+
voiceNote: (voiceNote, captionText, credit) => block('voice_note', { voice_note: voiceNote, ...(captionText !== undefined ? { caption: caption(captionText, credit) } : {}) }),
|
|
134
|
+
buttons: (buttons, align) => {
|
|
135
|
+
if (!Array.isArray(buttons) || buttons.length < 1 || buttons.length > 8) {
|
|
136
|
+
throw new RangeError('rich.buttons(): harus berisi 1–8 button');
|
|
137
|
+
}
|
|
138
|
+
if (align !== undefined && !['left', 'center', 'right'].includes(align)) {
|
|
139
|
+
throw new TypeError('rich.buttons(): align harus left, center, atau right');
|
|
140
|
+
}
|
|
141
|
+
return block('buttons', { buttons, ...(align ? { align } : {}) });
|
|
142
|
+
},
|
|
143
|
+
thinking: (text = '') => block('thinking', { text }),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
class RichMessageBuilder {
|
|
147
|
+
constructor() { this._content = null; this._options = {}; }
|
|
148
|
+
_set(field, value) {
|
|
149
|
+
if (this._content) throw new Error('RichMessageBuilder: tentukan satu mode saja (html, markdown, atau blocks)');
|
|
150
|
+
this._content = { [field]: value };
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
html(value) { return this._set('html', String(value)); }
|
|
154
|
+
markdown(value) { return this._set('markdown', String(value)); }
|
|
155
|
+
blocks(value = []) { return this._set('blocks', [...value]); }
|
|
156
|
+
add(...items) {
|
|
157
|
+
if (!this._content) this._content = { blocks: [] };
|
|
158
|
+
if (!Array.isArray(this._content.blocks)) throw new Error('RichMessageBuilder.add() hanya bisa dipakai pada mode blocks');
|
|
159
|
+
this._content.blocks.push(...items);
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
media(items) { this._options.media = [...items]; return this; }
|
|
163
|
+
rtl(value = true) { this._options.is_rtl = Boolean(value); return this; }
|
|
164
|
+
skipEntityDetection(value = true) { this._options.skip_entity_detection = Boolean(value); return this; }
|
|
165
|
+
build() {
|
|
166
|
+
if (!this._content) throw new Error('RichMessageBuilder: konten belum diisi');
|
|
167
|
+
return inputRichMessage(this._content, this._options);
|
|
168
|
+
}
|
|
169
|
+
buildDraft() {
|
|
170
|
+
if (!this._content) throw new Error('RichMessageBuilder: konten belum diisi');
|
|
171
|
+
return draftRichMessage(this._content, this._options);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { rich, RichMessageBuilder, inputRichMessage };
|
package/lib/runner.js
CHANGED
|
@@ -16,7 +16,7 @@ async function pollLoop({ api, handleUpdate, allowedUpdates, dropPending, onConf
|
|
|
16
16
|
if (dropPending) {
|
|
17
17
|
try {
|
|
18
18
|
const u = await api.callApi('getUpdates', { offset: -1, timeout: 0, allowed_updates: allowedUpdates });
|
|
19
|
-
if (u.length) offset = u[u.length - 1].update_id + 1;
|
|
19
|
+
if (Array.isArray(u) && u.length) offset = u[u.length - 1].update_id + 1;
|
|
20
20
|
} catch { /* abaikan — loop utama yang menangani */ }
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -28,10 +28,19 @@ async function pollLoop({ api, handleUpdate, allowedUpdates, dropPending, onConf
|
|
|
28
28
|
} catch (err) {
|
|
29
29
|
const code = err && (err.error_code || err.code);
|
|
30
30
|
if (shouldStop()) break;
|
|
31
|
-
if (code === 409) { if (onConflict) onConflict(err); await sleep(conflictDelay); continue; }
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
if (code === 409) { if (onConflict) await onConflict(err); await sleep(conflictDelay); continue; }
|
|
32
|
+
if (code === 429) {
|
|
33
|
+
const retryAfter = Number(err && err.parameters && err.parameters.retry_after);
|
|
34
|
+
await sleep(Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1000 : 1000);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (Number.isInteger(code) && code >= 500 && code < 600) { await sleep(1000); continue; }
|
|
38
|
+
const networkCode = err && ['ECONNABORTED', 'ETIMEDOUT', 'ECONNRESET', 'EAI_AGAIN', 'ENOTFOUND', 'EPIPE', 'ENETUNREACH', 'ECONNREFUSED'].includes(err.code);
|
|
39
|
+
if (!err || err.network || networkCode || !err.error_code) {
|
|
40
|
+
await sleep(1000); continue; // jaringan putus/time-out: retry, bukan fatal
|
|
41
|
+
}
|
|
42
|
+
if (onFatal) await onFatal(err);
|
|
43
|
+
break;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
for (const u of updates) {
|
package/lib/session.js
CHANGED
|
@@ -24,12 +24,20 @@ function session(opts = {}) {
|
|
|
24
24
|
const key = getKey(ctx);
|
|
25
25
|
if (key === undefined) { ctx.session = {}; return next(); }
|
|
26
26
|
let s = await read(key);
|
|
27
|
-
if (s === undefined || s === null)
|
|
28
|
-
|
|
27
|
+
if (s === undefined || s === null) s = initial();
|
|
28
|
+
if (!s || typeof s !== 'object') throw new TypeError('session: initial/storage harus mengembalikan object');
|
|
29
|
+
// Proxy: simpan ulang setelah middleware agar storage hasil deserialisasi juga awet.
|
|
29
30
|
ctx.session = new Proxy(s, {
|
|
30
|
-
get: (t, p) => t[p],
|
|
31
|
+
get: (t, p) => t[p],
|
|
32
|
+
set: (t, p, v) => { t[p] = v; return true; },
|
|
33
|
+
deleteProperty: (t, p) => { delete t[p]; return true; },
|
|
31
34
|
});
|
|
32
|
-
|
|
35
|
+
try {
|
|
36
|
+
return await next();
|
|
37
|
+
} finally {
|
|
38
|
+
if (s.__deleted === true) await del(key);
|
|
39
|
+
else await write(key, s);
|
|
40
|
+
}
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
43
|
|