@patricktobias86/node-red-telegram-account 1.1.17 → 1.1.20
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 +5 -1
- package/docs/NODES.md +1 -1
- package/nodes/receiver.html +13 -1
- package/nodes/receiver.js +36 -1
- package/package.json +1 -1
- package/test/receiver-debug.test.js +7 -3
- package/test/receiver.test.js +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [1.1.
|
|
5
|
+
## [1.1.20] - 2026-01-05
|
|
6
|
+
### Added
|
|
7
|
+
- Receiver node now emits debug events on a second output when Debug is enabled, so you can see internal logs in the Node-RED debug sidebar.
|
|
6
8
|
### Fixed
|
|
9
|
+
- Receiver node now also supports GramJS `Integer { value: ... }` wrappers when deriving `payload.chatId` / `payload.senderId`.
|
|
10
|
+
- Receiver node now populates `payload.chatId` / `payload.senderId` when Telegram IDs arrive as numeric strings (common in debug/output), so downstream filters work reliably.
|
|
7
11
|
- Receiver node now listens to Raw MTProto updates and derives sender/chat identity safely so valid messages (channel posts, anonymous admins, service messages, missing fromId) are no longer dropped.
|
|
8
12
|
|
|
9
13
|
## [1.1.16] - 2025-09-22
|
package/docs/NODES.md
CHANGED
|
@@ -8,7 +8,7 @@ Below is a short description of each node. For a full list of configuration opti
|
|
|
8
8
|
|------|-------------|
|
|
9
9
|
| **config** | Configuration node storing API credentials and connection options. Other nodes reference this to share a Telegram client and reuse the session. Connections are tracked in a Map with a reference count so multiple nodes can wait for the same connection. |
|
|
10
10
|
| **auth** | Starts an interactive login flow. Produces a `stringSession` (available in both <code>msg.payload.stringSession</code> and <code>msg.stringSession</code>) that can be reused with the `config` node. |
|
|
11
|
-
| **receiver** | Emits an output message for every incoming Telegram message using Raw MTProto updates (so channel posts, anonymous admins and service messages are not missed). Can ignore specific user IDs, skip selected message types (e.g. videos or documents), and optionally drop media above a configurable size. Output includes derived `peer`, `sender`, `senderType`, `senderId`, `chatId`, `isSilent`, and `messageTypes`. Event handlers are automatically removed when the node is closed. |
|
|
11
|
+
| **receiver** | Emits an output message for every incoming Telegram message using Raw MTProto updates (so channel posts, anonymous admins and service messages are not missed). Can ignore specific user IDs, skip selected message types (e.g. videos or documents), and optionally drop media above a configurable size. Output includes derived `peer`, `sender`, `senderType`, `senderId`, `chatId`, `isSilent`, and `messageTypes`. `chatId` is normalized to the MTProto dialog id (userId for private chats, chatId for legacy groups, channelId for supergroups/channels — not the Bot API `-100...` form). When Debug is enabled, a second output emits debug messages that can be wired to a Node-RED debug node. Event handlers are automatically removed when the node is closed. |
|
|
12
12
|
| **command** | Listens for new messages and triggers when a message matches a configured command or regular expression. The event listener is cleaned up on node close to avoid duplicates. |
|
|
13
13
|
| **send-message** | Sends text messages or media files to a chat. Supports parse mode, buttons, scheduling, and more. |
|
|
14
14
|
| **send-files** | Uploads one or more files to a chat with optional caption, thumbnails and other parameters. |
|
package/nodes/receiver.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
maxFileSizeMb: { value: "" }
|
|
13
13
|
},
|
|
14
14
|
inputs: 1,
|
|
15
|
-
outputs:
|
|
15
|
+
outputs: 2,
|
|
16
16
|
paletteLabel: 'receiver',
|
|
17
17
|
label: function() {
|
|
18
18
|
return this.name||'receiver';
|
|
@@ -95,10 +95,22 @@
|
|
|
95
95
|
|
|
96
96
|
<h3>Outputs</h3>
|
|
97
97
|
<dl class="message-properties">
|
|
98
|
+
<dt>output 1
|
|
99
|
+
<span class="property-type">message</span>
|
|
100
|
+
</dt>
|
|
101
|
+
<dd>The parsed incoming Telegram message.</dd>
|
|
102
|
+
<dt>output 2
|
|
103
|
+
<span class="property-type">message</span>
|
|
104
|
+
</dt>
|
|
105
|
+
<dd>Debug messages emitted when <b>Debug</b> is enabled (suitable for wiring to a Node-RED debug node).</dd>
|
|
98
106
|
<dt>payload.update
|
|
99
107
|
<span class="property-type">object</span>
|
|
100
108
|
</dt>
|
|
101
109
|
<dd>The raw Telegram update object containing details about the incoming message, sender, chat, and metadata.</dd>
|
|
110
|
+
<dt>payload.chatId
|
|
111
|
+
<span class="property-type">number</span>
|
|
112
|
+
</dt>
|
|
113
|
+
<dd>The normalized conversation identifier (user ID for private chats, chat ID for legacy groups, channel ID for supergroups/channels).</dd>
|
|
102
114
|
</dl>
|
|
103
115
|
|
|
104
116
|
<h3>Configuration</h3>
|
package/nodes/receiver.js
CHANGED
|
@@ -185,9 +185,27 @@ const toPeerInfo = (peer) => {
|
|
|
185
185
|
};
|
|
186
186
|
|
|
187
187
|
const toSafeNumber = (value) => {
|
|
188
|
+
if (value && typeof value === 'object') {
|
|
189
|
+
// GramJS can represent large integers using custom Integer wrappers.
|
|
190
|
+
// Those often serialize/log as `Integer { value: 123n }`.
|
|
191
|
+
if ('value' in value) {
|
|
192
|
+
return toSafeNumber(value.value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
188
195
|
if (typeof value === 'number') {
|
|
189
196
|
return Number.isFinite(value) ? value : null;
|
|
190
197
|
}
|
|
198
|
+
if (typeof value === 'string') {
|
|
199
|
+
const trimmed = value.trim();
|
|
200
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
201
|
+
try {
|
|
202
|
+
return toSafeNumber(BigInt(trimmed));
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
191
209
|
if (typeof value === 'bigint') {
|
|
192
210
|
const result = Number(value);
|
|
193
211
|
return Number.isFinite(result) ? result : Number.MAX_SAFE_INTEGER;
|
|
@@ -413,11 +431,19 @@ module.exports = function (RED) {
|
|
|
413
431
|
}
|
|
414
432
|
};
|
|
415
433
|
|
|
434
|
+
const debugSend = (payload) => {
|
|
435
|
+
if (!node.debugEnabled) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
node.send([null, { payload }]);
|
|
439
|
+
};
|
|
440
|
+
|
|
416
441
|
const event = new Raw({});
|
|
417
442
|
const handler = (rawUpdate) => {
|
|
418
443
|
const debug = node.debugEnabled;
|
|
419
444
|
if (debug) {
|
|
420
445
|
node.log('receiver raw update: ' + util.inspect(rawUpdate, { depth: null }));
|
|
446
|
+
debugSend({ event: 'rawUpdate', rawUpdate });
|
|
421
447
|
}
|
|
422
448
|
|
|
423
449
|
const extracted = extractMessageEvents(rawUpdate);
|
|
@@ -425,12 +451,14 @@ module.exports = function (RED) {
|
|
|
425
451
|
// Raw emits *all* MTProto updates; many are not message-bearing updates (typing, reads, etc.).
|
|
426
452
|
// We do not output those by default, but we also do not silently hide that they occurred.
|
|
427
453
|
debugLog(`receiver ignoring non-message MTProto update: ${getClassName(rawUpdate) || 'unknown'}`);
|
|
454
|
+
debugSend({ event: 'ignored', reason: 'non-message', rawUpdateClassName: getClassName(rawUpdate) || 'unknown' });
|
|
428
455
|
return;
|
|
429
456
|
}
|
|
430
457
|
|
|
431
458
|
for (const { update, message } of extracted) {
|
|
432
459
|
if (!message) {
|
|
433
460
|
debugLog(`receiver ignoring message update without message payload: ${getClassName(update) || 'unknown'}`);
|
|
461
|
+
debugSend({ event: 'ignored', reason: 'missing-message', updateClassName: getClassName(update) || 'unknown' });
|
|
434
462
|
continue;
|
|
435
463
|
}
|
|
436
464
|
|
|
@@ -461,6 +489,7 @@ module.exports = function (RED) {
|
|
|
461
489
|
const shouldIgnoreType = Array.from(messageTypes).some((type) => ignoredMessageTypes.has(type));
|
|
462
490
|
if (shouldIgnoreType) {
|
|
463
491
|
debugLog(`receiver ignoring message due to ignoreMessageTypes; types=${Array.from(messageTypes).join(', ')}`);
|
|
492
|
+
debugSend({ event: 'ignored', reason: 'ignoreMessageTypes', messageTypes: Array.from(messageTypes) });
|
|
464
493
|
continue;
|
|
465
494
|
}
|
|
466
495
|
}
|
|
@@ -469,6 +498,7 @@ module.exports = function (RED) {
|
|
|
469
498
|
const mediaSize = extractMediaSize(message.media);
|
|
470
499
|
if (mediaSize != null && mediaSize > maxFileSizeBytes) {
|
|
471
500
|
debugLog(`receiver ignoring message due to maxFileSizeMb; mediaSize=${mediaSize} limitBytes=${maxFileSizeBytes}`);
|
|
501
|
+
debugSend({ event: 'ignored', reason: 'maxFileSizeMb', mediaSize, limitBytes: maxFileSizeBytes });
|
|
472
502
|
continue;
|
|
473
503
|
}
|
|
474
504
|
}
|
|
@@ -478,6 +508,7 @@ module.exports = function (RED) {
|
|
|
478
508
|
// Now we only apply the ignore list when we can confidently identify a user sender.
|
|
479
509
|
if (senderType === 'user' && senderId != null && ignore.includes(String(senderId))) {
|
|
480
510
|
debugLog(`receiver ignoring message due to ignore list; userId=${senderId}`);
|
|
511
|
+
debugSend({ event: 'ignored', reason: 'ignore', userId: senderId });
|
|
481
512
|
continue;
|
|
482
513
|
}
|
|
483
514
|
|
|
@@ -495,7 +526,11 @@ module.exports = function (RED) {
|
|
|
495
526
|
}
|
|
496
527
|
};
|
|
497
528
|
|
|
498
|
-
|
|
529
|
+
if (debug) {
|
|
530
|
+
node.send([out, { payload: { event: 'output', out } }]);
|
|
531
|
+
} else {
|
|
532
|
+
node.send(out);
|
|
533
|
+
}
|
|
499
534
|
if (debug) {
|
|
500
535
|
node.log('receiver output: ' + util.inspect(out, { depth: null }));
|
|
501
536
|
}
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ const proxyquire = require('proxyquire').noPreserveCache();
|
|
|
4
4
|
function load() {
|
|
5
5
|
const addCalls = [];
|
|
6
6
|
const logs = [];
|
|
7
|
+
const sends = [];
|
|
7
8
|
class TelegramClientStub {
|
|
8
9
|
addEventHandler(fn, event) { addCalls.push({fn, event}); }
|
|
9
10
|
removeEventHandler() {}
|
|
@@ -18,7 +19,7 @@ function load() {
|
|
|
18
19
|
node._events = {};
|
|
19
20
|
node.on = (e, fn) => { node._events[e] = fn; };
|
|
20
21
|
node.log = (msg) => logs.push(msg);
|
|
21
|
-
node.send = () =>
|
|
22
|
+
node.send = (msg) => sends.push(msg);
|
|
22
23
|
},
|
|
23
24
|
registerType(name, ctor) { NodeCtor = ctor; },
|
|
24
25
|
getNode() { return configNode; }
|
|
@@ -29,12 +30,12 @@ function load() {
|
|
|
29
30
|
'telegram/events': { Raw: RawStub }
|
|
30
31
|
})(RED);
|
|
31
32
|
|
|
32
|
-
return { NodeCtor, addCalls, logs };
|
|
33
|
+
return { NodeCtor, addCalls, logs, sends };
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
describe('Receiver node debug with BigInt', function() {
|
|
36
37
|
it('logs update and output without throwing', function() {
|
|
37
|
-
const { NodeCtor, addCalls, logs } = load();
|
|
38
|
+
const { NodeCtor, addCalls, logs, sends } = load();
|
|
38
39
|
const node = new NodeCtor({config:'c', ignore:'', debug:true});
|
|
39
40
|
const handler = addCalls[0].fn;
|
|
40
41
|
assert.doesNotThrow(() => handler({
|
|
@@ -43,5 +44,8 @@ describe('Receiver node debug with BigInt', function() {
|
|
|
43
44
|
}));
|
|
44
45
|
assert(logs.some(l => l.includes('receiver raw update')));
|
|
45
46
|
assert(logs.some(l => l.includes('receiver output')));
|
|
47
|
+
assert(sends.some((msg) => Array.isArray(msg) && msg.length === 2));
|
|
48
|
+
assert(sends.some((msg) => Array.isArray(msg) && msg[1] && msg[1].payload && msg[1].payload.event === 'rawUpdate'));
|
|
49
|
+
assert(sends.some((msg) => Array.isArray(msg) && msg[1] && msg[1].payload && msg[1].payload.event === 'output'));
|
|
46
50
|
});
|
|
47
51
|
});
|
package/test/receiver.test.js
CHANGED
|
@@ -100,4 +100,46 @@ describe('Receiver node', function() {
|
|
|
100
100
|
|
|
101
101
|
assert.strictEqual(sent.length, 1);
|
|
102
102
|
});
|
|
103
|
+
|
|
104
|
+
it('populates chatId and senderId for string ids', function() {
|
|
105
|
+
const { NodeCtor, addCalls } = load();
|
|
106
|
+
const sent = [];
|
|
107
|
+
const node = new NodeCtor({config:'c', ignore:'', ignoreMessageTypes:'', maxFileSizeMb:''});
|
|
108
|
+
node.send = (msg) => sent.push(msg);
|
|
109
|
+
const handler = addCalls[0].fn;
|
|
110
|
+
|
|
111
|
+
handler({
|
|
112
|
+
className: 'UpdateNewChannelMessage',
|
|
113
|
+
message: {
|
|
114
|
+
fromId: { userId: '6304354944', className: 'PeerUser' },
|
|
115
|
+
peerId: { channelId: '2877134366', className: 'PeerChannel' },
|
|
116
|
+
message: 'hello'
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
assert.strictEqual(sent.length, 1);
|
|
121
|
+
assert.strictEqual(sent[0].payload.chatId, 2877134366);
|
|
122
|
+
assert.strictEqual(sent[0].payload.senderId, 6304354944);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('populates chatId and senderId for Integer wrappers', function() {
|
|
126
|
+
const { NodeCtor, addCalls } = load();
|
|
127
|
+
const sent = [];
|
|
128
|
+
const node = new NodeCtor({config:'c', ignore:'', ignoreMessageTypes:'', maxFileSizeMb:''});
|
|
129
|
+
node.send = (msg) => sent.push(msg);
|
|
130
|
+
const handler = addCalls[0].fn;
|
|
131
|
+
|
|
132
|
+
handler({
|
|
133
|
+
className: 'UpdateNewChannelMessage',
|
|
134
|
+
message: {
|
|
135
|
+
fromId: { userId: { value: 6304354944n }, className: 'PeerUser' },
|
|
136
|
+
peerId: { channelId: { value: 2877134366n }, className: 'PeerChannel' },
|
|
137
|
+
message: 'hello'
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
assert.strictEqual(sent.length, 1);
|
|
142
|
+
assert.strictEqual(sent[0].payload.chatId, 2877134366);
|
|
143
|
+
assert.strictEqual(sent[0].payload.senderId, 6304354944);
|
|
144
|
+
});
|
|
103
145
|
});
|