@pixelbyte-software/pixcode 1.53.3 → 1.53.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/dist/assets/{index-CZZiJpxs.js → index-DlwfTw9d.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/server/services/telegram/bot.js +6 -5
- package/dist-server/server/services/telegram/bot.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +16 -0
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/telegram-http-client.js +86 -45
- package/dist-server/server/services/telegram/telegram-http-client.js.map +1 -1
- package/package.json +1 -1
- package/server/services/telegram/bot.js +6 -5
- package/server/services/telegram/control-center.js +17 -0
- package/server/services/telegram/telegram-http-client.js +93 -45
|
@@ -53,15 +53,18 @@ async function callApi(token, method, params, { signal } = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export class TelegramHttpBot extends EventEmitter {
|
|
56
|
-
constructor(token, { polling = true, pollTimeoutSec = 30 } = {}) {
|
|
56
|
+
constructor(token, { polling = true, pollTimeoutSec = 30, pollLimit = 25, dropPendingUpdates = true } = {}) {
|
|
57
57
|
super();
|
|
58
58
|
if (!token) throw new Error('TelegramHttpBot: token is required');
|
|
59
59
|
this._token = token;
|
|
60
60
|
this._pollTimeoutSec = pollTimeoutSec;
|
|
61
|
+
this._pollLimit = pollLimit;
|
|
62
|
+
this._dropPendingUpdates = dropPendingUpdates;
|
|
61
63
|
this._offset = 0;
|
|
62
64
|
this._polling = false;
|
|
63
65
|
this._abortController = null;
|
|
64
|
-
|
|
66
|
+
this._pollingLoop = null;
|
|
67
|
+
if (polling) this.startPolling();
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
// ---------- Public API (mirrors node-telegram-bot-api surface) ----------
|
|
@@ -96,56 +99,101 @@ export class TelegramHttpBot extends EventEmitter {
|
|
|
96
99
|
this._polling = false;
|
|
97
100
|
try { this._abortController?.abort(); } catch { /* ignore */ }
|
|
98
101
|
this._abortController = null;
|
|
102
|
+
if (this._pollingLoop) {
|
|
103
|
+
await this._pollingLoop.catch(() => {});
|
|
104
|
+
this._pollingLoop = null;
|
|
105
|
+
}
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
// ---------- Polling loop ----------
|
|
102
109
|
|
|
103
|
-
async
|
|
110
|
+
async startPolling({ dropPendingUpdates = this._dropPendingUpdates } = {}) {
|
|
111
|
+
if (this._polling) return;
|
|
104
112
|
this._polling = true;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
113
|
+
this._pollingLoop = this._runPollingLoop({ dropPendingUpdates });
|
|
114
|
+
await Promise.resolve();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async _dropPendingUpdatesBeforePolling() {
|
|
118
|
+
try {
|
|
119
|
+
await callApi(this._token, 'deleteWebhook', { drop_pending_updates: true });
|
|
120
|
+
return;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
this.emit('polling_error', err);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Fallback for environments where deleteWebhook is rejected: a negative
|
|
126
|
+
// offset asks Telegram to forget older queued updates.
|
|
127
|
+
try {
|
|
128
|
+
const updates = await callApi(this._token, 'getUpdates', {
|
|
129
|
+
offset: -1,
|
|
130
|
+
limit: 1,
|
|
131
|
+
timeout: 0,
|
|
132
|
+
allowed_updates: ['message', 'callback_query'],
|
|
133
|
+
});
|
|
134
|
+
const lastUpdate = Array.isArray(updates) ? updates.at(-1) : null;
|
|
135
|
+
if (typeof lastUpdate?.update_id === 'number') {
|
|
136
|
+
this._offset = lastUpdate.update_id + 1;
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
this.emit('polling_error', err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async _emitSerial(eventName, payload) {
|
|
144
|
+
const listeners = this.listeners(eventName);
|
|
145
|
+
for (const listener of listeners) {
|
|
146
|
+
try {
|
|
147
|
+
await listener(payload);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
this.emit('polling_error', err);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async _runPollingLoop({ dropPendingUpdates }) {
|
|
155
|
+
if (dropPendingUpdates) {
|
|
156
|
+
await this._dropPendingUpdatesBeforePolling();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Each iteration long-polls getUpdates for up to pollTimeoutSec, then
|
|
160
|
+
// loops immediately. We deliberately serialize update handling because a
|
|
161
|
+
// stale Telegram backlog can otherwise fan out into many expensive scans.
|
|
162
|
+
while (this._polling) {
|
|
163
|
+
this._abortController = new AbortController();
|
|
164
|
+
try {
|
|
165
|
+
const updates = await callApi(
|
|
166
|
+
this._token,
|
|
167
|
+
'getUpdates',
|
|
168
|
+
{
|
|
169
|
+
offset: this._offset,
|
|
170
|
+
limit: this._pollLimit,
|
|
171
|
+
timeout: this._pollTimeoutSec,
|
|
172
|
+
allowed_updates: ['message', 'callback_query'],
|
|
173
|
+
},
|
|
174
|
+
{ signal: this._abortController.signal },
|
|
175
|
+
);
|
|
176
|
+
for (const update of updates) {
|
|
177
|
+
if (typeof update.update_id === 'number') {
|
|
178
|
+
this._offset = Math.max(this._offset, update.update_id + 1);
|
|
179
|
+
}
|
|
180
|
+
if (update.message) {
|
|
181
|
+
await this._emitSerial('message', update.message);
|
|
182
|
+
}
|
|
183
|
+
if (update.callback_query) {
|
|
184
|
+
await this._emitSerial('callback_query', update.callback_query);
|
|
137
185
|
}
|
|
138
|
-
} catch (err) {
|
|
139
|
-
// AbortError is the expected path when stopPolling() is called.
|
|
140
|
-
if (err?.name === 'AbortError' || !this._polling) break;
|
|
141
|
-
this.emit('polling_error', err);
|
|
142
|
-
// Back off before retrying — rapid retries on 401/409 would
|
|
143
|
-
// otherwise spin at 100% CPU. Upstream consumer's polling_error
|
|
144
|
-
// handler may also call stopBot() on 401/409 which flips _polling
|
|
145
|
-
// off and breaks the loop on the next tick.
|
|
146
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
147
186
|
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
// AbortError is the expected path when stopPolling() is called.
|
|
189
|
+
if (err?.name === 'AbortError' || !this._polling) break;
|
|
190
|
+
this.emit('polling_error', err);
|
|
191
|
+
// Back off before retrying — rapid retries on 401/409 would
|
|
192
|
+
// otherwise spin at 100% CPU. Upstream consumer's polling_error
|
|
193
|
+
// handler may also call stopBot() on 401/409 which flips _polling
|
|
194
|
+
// off and breaks the loop on the next tick.
|
|
195
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
148
196
|
}
|
|
149
|
-
}
|
|
197
|
+
}
|
|
150
198
|
}
|
|
151
199
|
}
|