@pixelbyte-software/pixcode 1.53.2 → 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-CP2HfVKD.js → index-DlwfTw9d.js} +179 -173
- package/dist/index.html +1 -1
- package/dist-server/server/index.js +61 -0
- package/dist-server/server/index.js.map +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/index.js +66 -0
- 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
|
@@ -234,7 +234,7 @@ export const startBot = async ({ token, persist = true } = {}) => {
|
|
|
234
234
|
// 409 conflicts on every long-poll.
|
|
235
235
|
if (bot) await stopBot();
|
|
236
236
|
|
|
237
|
-
const instance = new TelegramHttpBot(token, { polling: true });
|
|
237
|
+
const instance = new TelegramHttpBot(token, { polling: false, dropPendingUpdates: true });
|
|
238
238
|
// Validate the token first — if getMe fails we never want to persist a
|
|
239
239
|
// broken token or leave the poller running.
|
|
240
240
|
let me;
|
|
@@ -255,17 +255,18 @@ export const startBot = async ({ token, persist = true } = {}) => {
|
|
|
255
255
|
|
|
256
256
|
if (persist) telegramConfigDb.set(token, me.username);
|
|
257
257
|
|
|
258
|
-
bot.on('message', (msg) => {
|
|
259
|
-
handleMessage(msg).catch((err) => {
|
|
258
|
+
bot.on('message', async (msg) => {
|
|
259
|
+
await handleMessage(msg).catch((err) => {
|
|
260
260
|
console.error('[telegram] handleMessage crashed:', err);
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
|
-
bot.on('callback_query', (query) => {
|
|
264
|
-
handleCallbackQuery(query).catch((err) => {
|
|
263
|
+
bot.on('callback_query', async (query) => {
|
|
264
|
+
await handleCallbackQuery(query).catch((err) => {
|
|
265
265
|
console.error('[telegram] handleCallbackQuery crashed:', err);
|
|
266
266
|
});
|
|
267
267
|
});
|
|
268
268
|
wirePollingErrors();
|
|
269
|
+
await bot.startPolling({ dropPendingUpdates: true });
|
|
269
270
|
|
|
270
271
|
console.log(`[telegram] bot started as @${me.username}`);
|
|
271
272
|
telegramEvents.emit('started', { username: me.username });
|
|
@@ -9,6 +9,7 @@ import { SUPPORTED_LANGUAGES, t } from './translations.js';
|
|
|
9
9
|
const PROVIDERS = ['claude', 'cursor', 'codex', 'gemini', 'qwen', 'opencode'];
|
|
10
10
|
const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
|
|
11
11
|
const CALLBACK_TTL_MS = 10 * 60 * 1000;
|
|
12
|
+
const MAX_CALLBACK_ACTIONS = 1000;
|
|
12
13
|
const MAX_TELEGRAM_TEXT = 3600;
|
|
13
14
|
const callbackActions = new Map();
|
|
14
15
|
const runMonitors = new Map();
|
|
@@ -94,7 +95,23 @@ export function updateTelegramControlState(userId, patch) {
|
|
|
94
95
|
return telegramLinksDb.updateControlState(userId, patch);
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function pruneCallbackActions() {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
for (const [id, entry] of callbackActions.entries()) {
|
|
101
|
+
if (entry.expiresAt < now) {
|
|
102
|
+
callbackActions.delete(id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
while (callbackActions.size > MAX_CALLBACK_ACTIONS) {
|
|
107
|
+
const oldestId = callbackActions.keys().next().value;
|
|
108
|
+
if (!oldestId) break;
|
|
109
|
+
callbackActions.delete(oldestId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
function registerAction(action, payload = {}) {
|
|
114
|
+
pruneCallbackActions();
|
|
98
115
|
const id = crypto.randomBytes(8).toString('hex');
|
|
99
116
|
callbackActions.set(id, {
|
|
100
117
|
action,
|
|
@@ -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
|
}
|