@fre4x/telegram 1.1.3 → 1.1.5
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/README.md +10 -5
- package/dist/index.js +485 -175
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Telegram MCP server implementing Claude Code Channels for bidirectional communic
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Push Notifications:** Sends incoming Telegram messages
|
|
7
|
+
- **Push Notifications:** Sends incoming Telegram messages to the connected MCP client via `notifications/message` (claude/channel protocol).
|
|
8
8
|
- **Bidirectional Chat:** Allows Claude to send messages to Telegram using the `send_telegram_message` tool.
|
|
9
9
|
- **Security:** Filters incoming messages based on an allowed user ID.
|
|
10
10
|
- **Remote Control:** (Planned) Approve or deny Claude's tool-use prompts from your mobile device.
|
|
@@ -62,14 +62,19 @@ If you are using this plugin as a bidirectional Claude Code channel rather than
|
|
|
62
62
|
claude --channels plugin:telegram --dangerously-load-development-channels
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## Mock Mode
|
|
66
|
+
|
|
67
|
+
Run without a real bot token (returns fixture data of identical shape):
|
|
66
68
|
|
|
67
69
|
```bash
|
|
68
|
-
|
|
70
|
+
MOCK=true npx @fre4x/telegram
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
## Development
|
|
72
74
|
|
|
73
75
|
```bash
|
|
74
|
-
npm run
|
|
76
|
+
npm run dev # tsx, no build
|
|
77
|
+
npm run build # esbuild → dist/
|
|
78
|
+
npm test # vitest unit tests (MOCK=true)
|
|
79
|
+
npm run inspector # MCP Inspector (MOCK=true)
|
|
75
80
|
```
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
13
13
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
14
|
});
|
|
15
15
|
var __commonJS = (cb, mod) => function __require2() {
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
throw mod = 0, e;
|
|
20
|
+
}
|
|
17
21
|
};
|
|
18
22
|
var __export = (target, all) => {
|
|
19
23
|
for (var name in all)
|
|
@@ -3112,6 +3116,9 @@ var require_utils = __commonJS({
|
|
|
3112
3116
|
"use strict";
|
|
3113
3117
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3114
3118
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3119
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3120
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3121
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
3115
3122
|
function stringArrayToHexStripped(input) {
|
|
3116
3123
|
let acc = "";
|
|
3117
3124
|
let code = 0;
|
|
@@ -3304,27 +3311,77 @@ var require_utils = __commonJS({
|
|
|
3304
3311
|
}
|
|
3305
3312
|
return output.join("");
|
|
3306
3313
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3314
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3315
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3316
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3317
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
3318
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3319
|
+
re.lastIndex = 0;
|
|
3320
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3321
|
+
}
|
|
3322
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
3323
|
+
if (input.indexOf("%") === -1) {
|
|
3324
|
+
return input;
|
|
3317
3325
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3326
|
+
let output = "";
|
|
3327
|
+
for (let i = 0; i < input.length; i++) {
|
|
3328
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3329
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3330
|
+
if (isHexPair(hex3)) {
|
|
3331
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3332
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3333
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
3334
|
+
output += decoded;
|
|
3335
|
+
} else {
|
|
3336
|
+
output += "%" + normalizedHex;
|
|
3337
|
+
}
|
|
3338
|
+
i += 2;
|
|
3339
|
+
continue;
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
output += input[i];
|
|
3320
3343
|
}
|
|
3321
|
-
|
|
3322
|
-
|
|
3344
|
+
return output;
|
|
3345
|
+
}
|
|
3346
|
+
function normalizePathEncoding(input) {
|
|
3347
|
+
let output = "";
|
|
3348
|
+
for (let i = 0; i < input.length; i++) {
|
|
3349
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3350
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3351
|
+
if (isHexPair(hex3)) {
|
|
3352
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3353
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3354
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
3355
|
+
output += decoded;
|
|
3356
|
+
} else {
|
|
3357
|
+
output += "%" + normalizedHex;
|
|
3358
|
+
}
|
|
3359
|
+
i += 2;
|
|
3360
|
+
continue;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
if (isPathCharacter(input[i])) {
|
|
3364
|
+
output += input[i];
|
|
3365
|
+
} else {
|
|
3366
|
+
output += escape(input[i]);
|
|
3367
|
+
}
|
|
3323
3368
|
}
|
|
3324
|
-
|
|
3325
|
-
|
|
3369
|
+
return output;
|
|
3370
|
+
}
|
|
3371
|
+
function escapePreservingEscapes(input) {
|
|
3372
|
+
let output = "";
|
|
3373
|
+
for (let i = 0; i < input.length; i++) {
|
|
3374
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3375
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3376
|
+
if (isHexPair(hex3)) {
|
|
3377
|
+
output += "%" + hex3.toUpperCase();
|
|
3378
|
+
i += 2;
|
|
3379
|
+
continue;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
output += escape(input[i]);
|
|
3326
3383
|
}
|
|
3327
|
-
return
|
|
3384
|
+
return output;
|
|
3328
3385
|
}
|
|
3329
3386
|
function recomposeAuthority(component) {
|
|
3330
3387
|
const uriTokens = [];
|
|
@@ -3339,7 +3396,7 @@ var require_utils = __commonJS({
|
|
|
3339
3396
|
if (ipV6res.isIPV6 === true) {
|
|
3340
3397
|
host = `[${ipV6res.escapedHost}]`;
|
|
3341
3398
|
} else {
|
|
3342
|
-
host =
|
|
3399
|
+
host = reescapeHostDelimiters(host, false);
|
|
3343
3400
|
}
|
|
3344
3401
|
}
|
|
3345
3402
|
uriTokens.push(host);
|
|
@@ -3353,7 +3410,10 @@ var require_utils = __commonJS({
|
|
|
3353
3410
|
module.exports = {
|
|
3354
3411
|
nonSimpleDomain,
|
|
3355
3412
|
recomposeAuthority,
|
|
3356
|
-
|
|
3413
|
+
reescapeHostDelimiters,
|
|
3414
|
+
normalizePercentEncoding,
|
|
3415
|
+
normalizePathEncoding,
|
|
3416
|
+
escapePreservingEscapes,
|
|
3357
3417
|
removeDotSegments,
|
|
3358
3418
|
isIPv4,
|
|
3359
3419
|
isUUID,
|
|
@@ -3577,12 +3637,12 @@ var require_schemes = __commonJS({
|
|
|
3577
3637
|
var require_fast_uri = __commonJS({
|
|
3578
3638
|
"../node_modules/fast-uri/index.js"(exports, module) {
|
|
3579
3639
|
"use strict";
|
|
3580
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
3640
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3581
3641
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3582
3642
|
function normalize2(uri, options) {
|
|
3583
3643
|
if (typeof uri === "string") {
|
|
3584
3644
|
uri = /** @type {T} */
|
|
3585
|
-
|
|
3645
|
+
normalizeString(uri, options);
|
|
3586
3646
|
} else if (typeof uri === "object") {
|
|
3587
3647
|
uri = /** @type {T} */
|
|
3588
3648
|
parse3(serialize(uri, options), options);
|
|
@@ -3649,19 +3709,9 @@ var require_fast_uri = __commonJS({
|
|
|
3649
3709
|
return target;
|
|
3650
3710
|
}
|
|
3651
3711
|
function equal(uriA, uriB, options) {
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
} else if (typeof uriA === "object") {
|
|
3656
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
3657
|
-
}
|
|
3658
|
-
if (typeof uriB === "string") {
|
|
3659
|
-
uriB = unescape(uriB);
|
|
3660
|
-
uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
|
|
3661
|
-
} else if (typeof uriB === "object") {
|
|
3662
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
3663
|
-
}
|
|
3664
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
3712
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
3713
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3714
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
3665
3715
|
}
|
|
3666
3716
|
function serialize(cmpts, opts) {
|
|
3667
3717
|
const component = {
|
|
@@ -3686,12 +3736,12 @@ var require_fast_uri = __commonJS({
|
|
|
3686
3736
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3687
3737
|
if (component.path !== void 0) {
|
|
3688
3738
|
if (!options.skipEscape) {
|
|
3689
|
-
component.path =
|
|
3739
|
+
component.path = escapePreservingEscapes(component.path);
|
|
3690
3740
|
if (component.scheme !== void 0) {
|
|
3691
3741
|
component.path = component.path.split("%3A").join(":");
|
|
3692
3742
|
}
|
|
3693
3743
|
} else {
|
|
3694
|
-
component.path =
|
|
3744
|
+
component.path = normalizePercentEncoding(component.path);
|
|
3695
3745
|
}
|
|
3696
3746
|
}
|
|
3697
3747
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -3726,7 +3776,16 @@ var require_fast_uri = __commonJS({
|
|
|
3726
3776
|
return uriTokens.join("");
|
|
3727
3777
|
}
|
|
3728
3778
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3729
|
-
function
|
|
3779
|
+
function getParseError(parsed, matches) {
|
|
3780
|
+
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3781
|
+
return 'URI path must start with "/" when authority is present.';
|
|
3782
|
+
}
|
|
3783
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
3784
|
+
return "URI port is malformed.";
|
|
3785
|
+
}
|
|
3786
|
+
return void 0;
|
|
3787
|
+
}
|
|
3788
|
+
function parseWithStatus(uri, opts) {
|
|
3730
3789
|
const options = Object.assign({}, opts);
|
|
3731
3790
|
const parsed = {
|
|
3732
3791
|
scheme: void 0,
|
|
@@ -3737,6 +3796,7 @@ var require_fast_uri = __commonJS({
|
|
|
3737
3796
|
query: void 0,
|
|
3738
3797
|
fragment: void 0
|
|
3739
3798
|
};
|
|
3799
|
+
let malformedAuthorityOrPort = false;
|
|
3740
3800
|
let isIP = false;
|
|
3741
3801
|
if (options.reference === "suffix") {
|
|
3742
3802
|
if (options.scheme) {
|
|
@@ -3757,6 +3817,11 @@ var require_fast_uri = __commonJS({
|
|
|
3757
3817
|
if (isNaN(parsed.port)) {
|
|
3758
3818
|
parsed.port = matches[5];
|
|
3759
3819
|
}
|
|
3820
|
+
const parseError = getParseError(parsed, matches);
|
|
3821
|
+
if (parseError !== void 0) {
|
|
3822
|
+
parsed.error = parsed.error || parseError;
|
|
3823
|
+
malformedAuthorityOrPort = true;
|
|
3824
|
+
}
|
|
3760
3825
|
if (parsed.host) {
|
|
3761
3826
|
const ipv4result = isIPv4(parsed.host);
|
|
3762
3827
|
if (ipv4result === false) {
|
|
@@ -3795,14 +3860,18 @@ var require_fast_uri = __commonJS({
|
|
|
3795
3860
|
parsed.scheme = unescape(parsed.scheme);
|
|
3796
3861
|
}
|
|
3797
3862
|
if (parsed.host !== void 0) {
|
|
3798
|
-
parsed.host = unescape(parsed.host);
|
|
3863
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
3799
3864
|
}
|
|
3800
3865
|
}
|
|
3801
3866
|
if (parsed.path) {
|
|
3802
|
-
parsed.path =
|
|
3867
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
3803
3868
|
}
|
|
3804
3869
|
if (parsed.fragment) {
|
|
3805
|
-
|
|
3870
|
+
try {
|
|
3871
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3872
|
+
} catch {
|
|
3873
|
+
parsed.error = parsed.error || "URI malformed";
|
|
3874
|
+
}
|
|
3806
3875
|
}
|
|
3807
3876
|
}
|
|
3808
3877
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -3811,7 +3880,29 @@ var require_fast_uri = __commonJS({
|
|
|
3811
3880
|
} else {
|
|
3812
3881
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
3813
3882
|
}
|
|
3814
|
-
return parsed;
|
|
3883
|
+
return { parsed, malformedAuthorityOrPort };
|
|
3884
|
+
}
|
|
3885
|
+
function parse3(uri, opts) {
|
|
3886
|
+
return parseWithStatus(uri, opts).parsed;
|
|
3887
|
+
}
|
|
3888
|
+
function normalizeString(uri, opts) {
|
|
3889
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
3890
|
+
}
|
|
3891
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
3892
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
3893
|
+
return {
|
|
3894
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
3895
|
+
malformedAuthorityOrPort
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
function normalizeComparableURI(uri, opts) {
|
|
3899
|
+
if (typeof uri === "string") {
|
|
3900
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
3901
|
+
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
3902
|
+
}
|
|
3903
|
+
if (typeof uri === "object") {
|
|
3904
|
+
return serialize(uri, opts);
|
|
3905
|
+
}
|
|
3815
3906
|
}
|
|
3816
3907
|
var fastUri = {
|
|
3817
3908
|
SCHEMES,
|
|
@@ -6878,7 +6969,7 @@ var require_main = __commonJS({
|
|
|
6878
6969
|
"../node_modules/dotenv/lib/main.js"(exports, module) {
|
|
6879
6970
|
var fs2 = __require("fs");
|
|
6880
6971
|
var path2 = __require("path");
|
|
6881
|
-
var
|
|
6972
|
+
var os2 = __require("os");
|
|
6882
6973
|
var crypto = __require("crypto");
|
|
6883
6974
|
var packageJson = require_package();
|
|
6884
6975
|
var version2 = packageJson.version;
|
|
@@ -7001,7 +7092,7 @@ var require_main = __commonJS({
|
|
|
7001
7092
|
return null;
|
|
7002
7093
|
}
|
|
7003
7094
|
function _resolveHome(envPath) {
|
|
7004
|
-
return envPath[0] === "~" ? path2.join(
|
|
7095
|
+
return envPath[0] === "~" ? path2.join(os2.homedir(), envPath.slice(1)) : envPath;
|
|
7005
7096
|
}
|
|
7006
7097
|
function _configVault(options) {
|
|
7007
7098
|
const debug = Boolean(options && options.debug);
|
|
@@ -28144,7 +28235,9 @@ var StdioServerTransport = class {
|
|
|
28144
28235
|
// src/index.ts
|
|
28145
28236
|
var import_dotenv = __toESM(require_main(), 1);
|
|
28146
28237
|
import { Telegraf } from "telegraf";
|
|
28238
|
+
import { createHash } from "node:crypto";
|
|
28147
28239
|
import * as fs from "fs";
|
|
28240
|
+
import os from "node:os";
|
|
28148
28241
|
import * as path from "path";
|
|
28149
28242
|
|
|
28150
28243
|
// ../node_modules/zod/index.js
|
|
@@ -28496,6 +28589,13 @@ function applyPagination(items, params) {
|
|
|
28496
28589
|
|
|
28497
28590
|
// src/index.ts
|
|
28498
28591
|
import_dotenv.default.config();
|
|
28592
|
+
function getStatePath() {
|
|
28593
|
+
if (process.env.TELEGRAM_STATE_PATH) {
|
|
28594
|
+
return process.env.TELEGRAM_STATE_PATH;
|
|
28595
|
+
}
|
|
28596
|
+
const suffix = botToken && !IS_MOCK ? createHash("sha256").update(botToken).digest("hex").slice(0, 12) : "default";
|
|
28597
|
+
return path.join(os.homedir(), ".fre4x", `telegram-mcp-${suffix}.json`);
|
|
28598
|
+
}
|
|
28499
28599
|
var window = new JSDOM("").window;
|
|
28500
28600
|
var DOMPurify = createDOMPurify(window);
|
|
28501
28601
|
var IS_MOCK = process.env.MOCK === "true" || process.env.TELEGRAM_MOCK === "true";
|
|
@@ -28569,13 +28669,7 @@ function validateFilePath(filePath) {
|
|
|
28569
28669
|
}
|
|
28570
28670
|
return absolutePath;
|
|
28571
28671
|
}
|
|
28572
|
-
function
|
|
28573
|
-
const userPattern = /^\d+$/;
|
|
28574
|
-
const groupPattern = /^-?\d+$/;
|
|
28575
|
-
const usernamePattern = /^@[a-zA-Z0-9_]+$/;
|
|
28576
|
-
if (!userPattern.test(targetId) && !groupPattern.test(targetId) && !usernamePattern.test(targetId)) {
|
|
28577
|
-
throw new Error(`Invalid Telegram ID format: ${targetId}`);
|
|
28578
|
-
}
|
|
28672
|
+
function assertRecipientAllowed(targetId) {
|
|
28579
28673
|
if (enableRecipientWhitelist && allowedRecipients.length > 0) {
|
|
28580
28674
|
if (!allowedRecipients.includes(targetId)) {
|
|
28581
28675
|
throw new Error(
|
|
@@ -28584,6 +28678,18 @@ function validateRecipient(targetId) {
|
|
|
28584
28678
|
}
|
|
28585
28679
|
}
|
|
28586
28680
|
}
|
|
28681
|
+
function validateSendRecipient(targetId) {
|
|
28682
|
+
if (!/^\d+$/.test(targetId)) {
|
|
28683
|
+
throw new Error(`Invalid Telegram user ID format: ${targetId}`);
|
|
28684
|
+
}
|
|
28685
|
+
assertRecipientAllowed(targetId);
|
|
28686
|
+
}
|
|
28687
|
+
function validateChatId(chatId) {
|
|
28688
|
+
if (!/^-?\d+$/.test(chatId)) {
|
|
28689
|
+
throw new Error(`Invalid Telegram chat ID format: ${chatId}`);
|
|
28690
|
+
}
|
|
28691
|
+
assertRecipientAllowed(chatId);
|
|
28692
|
+
}
|
|
28587
28693
|
var SendMessageSchema = external_exports3.object({
|
|
28588
28694
|
content: external_exports3.string().min(1).max(4096),
|
|
28589
28695
|
recipientId: external_exports3.string().optional(),
|
|
@@ -28622,8 +28728,16 @@ var server = new Server(
|
|
|
28622
28728
|
},
|
|
28623
28729
|
{
|
|
28624
28730
|
capabilities: {
|
|
28625
|
-
tools: {}
|
|
28626
|
-
|
|
28731
|
+
tools: {},
|
|
28732
|
+
logging: {},
|
|
28733
|
+
// Required by SDK for notifications/message
|
|
28734
|
+
experimental: {
|
|
28735
|
+
"claude/channel": {
|
|
28736
|
+
description: "Telegram events are delivered via channel notifications"
|
|
28737
|
+
}
|
|
28738
|
+
}
|
|
28739
|
+
},
|
|
28740
|
+
instructions: "Telegram MCP server. Delivers inbound Telegram messages as channel notifications via notifications/message."
|
|
28627
28741
|
}
|
|
28628
28742
|
);
|
|
28629
28743
|
var isImage = (filePath) => {
|
|
@@ -28632,12 +28746,275 @@ var isImage = (filePath) => {
|
|
|
28632
28746
|
};
|
|
28633
28747
|
var messageHistory = [];
|
|
28634
28748
|
var MAX_HISTORY = 100;
|
|
28749
|
+
var pollingOffset = 0;
|
|
28750
|
+
var knownChats = /* @__PURE__ */ new Map();
|
|
28751
|
+
function loadState() {
|
|
28752
|
+
if (IS_MOCK) return;
|
|
28753
|
+
try {
|
|
28754
|
+
if (!fs.existsSync(getStatePath())) return;
|
|
28755
|
+
const raw = fs.readFileSync(getStatePath(), "utf-8");
|
|
28756
|
+
const state = JSON.parse(raw);
|
|
28757
|
+
for (const chat of state.knownChats ?? []) {
|
|
28758
|
+
if (chat?.id) {
|
|
28759
|
+
knownChats.set(chat.id, chat);
|
|
28760
|
+
}
|
|
28761
|
+
}
|
|
28762
|
+
if (typeof state.pollingOffset === "number") {
|
|
28763
|
+
pollingOffset = state.pollingOffset;
|
|
28764
|
+
}
|
|
28765
|
+
console.error(
|
|
28766
|
+
`[telegram:state] loaded ${knownChats.size} chat(s) from ${getStatePath()}`
|
|
28767
|
+
);
|
|
28768
|
+
} catch (error48) {
|
|
28769
|
+
console.error(`[telegram:state] failed to load ${getStatePath()}:`, error48);
|
|
28770
|
+
}
|
|
28771
|
+
}
|
|
28772
|
+
function persistState() {
|
|
28773
|
+
if (IS_MOCK) return;
|
|
28774
|
+
try {
|
|
28775
|
+
const state = {
|
|
28776
|
+
knownChats: Array.from(knownChats.values()),
|
|
28777
|
+
pollingOffset
|
|
28778
|
+
};
|
|
28779
|
+
fs.mkdirSync(path.dirname(getStatePath()), { recursive: true });
|
|
28780
|
+
fs.writeFileSync(getStatePath(), JSON.stringify(state, null, 2), "utf-8");
|
|
28781
|
+
} catch (error48) {
|
|
28782
|
+
console.error(`[telegram:state] failed to persist ${getStatePath()}:`, error48);
|
|
28783
|
+
}
|
|
28784
|
+
}
|
|
28785
|
+
function reloadState() {
|
|
28786
|
+
if (IS_MOCK) return;
|
|
28787
|
+
try {
|
|
28788
|
+
if (!fs.existsSync(getStatePath())) return;
|
|
28789
|
+
const raw = fs.readFileSync(getStatePath(), "utf-8");
|
|
28790
|
+
const state = JSON.parse(raw);
|
|
28791
|
+
for (const chat of state.knownChats ?? []) {
|
|
28792
|
+
if (chat?.id) {
|
|
28793
|
+
knownChats.set(chat.id, chat);
|
|
28794
|
+
}
|
|
28795
|
+
}
|
|
28796
|
+
if (typeof state.pollingOffset === "number" && state.pollingOffset > pollingOffset) {
|
|
28797
|
+
pollingOffset = state.pollingOffset;
|
|
28798
|
+
}
|
|
28799
|
+
} catch (error48) {
|
|
28800
|
+
console.error(`[telegram:state] failed to reload ${getStatePath()}:`, error48);
|
|
28801
|
+
}
|
|
28802
|
+
}
|
|
28803
|
+
function chatTitleFromTelegramChat(chat) {
|
|
28804
|
+
return chat.title || chat.username || chat.first_name || "Private Chat";
|
|
28805
|
+
}
|
|
28806
|
+
function recordKnownChat(chat) {
|
|
28807
|
+
knownChats.set(chat.id, chat);
|
|
28808
|
+
persistState();
|
|
28809
|
+
}
|
|
28810
|
+
function recordKnownChatFromTelegramMessage(message) {
|
|
28811
|
+
recordKnownChat({
|
|
28812
|
+
id: message.chat.id.toString(),
|
|
28813
|
+
title: chatTitleFromTelegramChat(message.chat),
|
|
28814
|
+
type: message.chat.type ?? "unknown"
|
|
28815
|
+
});
|
|
28816
|
+
}
|
|
28817
|
+
function recordKnownChatFromUpdate(update) {
|
|
28818
|
+
const chat = update.message?.chat ?? update.edited_message?.chat ?? update.channel_post?.chat ?? update.my_chat_member?.chat;
|
|
28819
|
+
if (!chat) return;
|
|
28820
|
+
recordKnownChat({
|
|
28821
|
+
id: chat.id.toString(),
|
|
28822
|
+
title: chatTitleFromTelegramChat(chat),
|
|
28823
|
+
type: chat.type ?? "unknown"
|
|
28824
|
+
});
|
|
28825
|
+
}
|
|
28826
|
+
function logActiveChatsDebug(stage) {
|
|
28827
|
+
const active = getActiveChats();
|
|
28828
|
+
console.error(
|
|
28829
|
+
`[telegram:list_active_chats] ${stage}: knownChats=${knownChats.size} messageHistory=${messageHistory.length} active=${active.length} pollingOffset=${pollingOffset}`
|
|
28830
|
+
);
|
|
28831
|
+
if (knownChats.size > 0) {
|
|
28832
|
+
console.error(
|
|
28833
|
+
`[telegram:list_active_chats] knownChatIds: ${Array.from(knownChats.keys()).join(", ")}`
|
|
28834
|
+
);
|
|
28835
|
+
}
|
|
28836
|
+
if (active.length > 0) {
|
|
28837
|
+
console.error(
|
|
28838
|
+
`[telegram:list_active_chats] activeChatIds: ${active.map((c) => c.id).join(", ")}`
|
|
28839
|
+
);
|
|
28840
|
+
}
|
|
28841
|
+
}
|
|
28842
|
+
async function recordChatInteraction(chatId) {
|
|
28843
|
+
if (IS_MOCK || !bot) {
|
|
28844
|
+
recordKnownChat({
|
|
28845
|
+
id: chatId,
|
|
28846
|
+
title: `Chat ${chatId}`,
|
|
28847
|
+
type: "unknown"
|
|
28848
|
+
});
|
|
28849
|
+
console.error(
|
|
28850
|
+
`[telegram:recordChatInteraction] mock recorded chatId=${chatId} knownChats=${knownChats.size}`
|
|
28851
|
+
);
|
|
28852
|
+
return;
|
|
28853
|
+
}
|
|
28854
|
+
try {
|
|
28855
|
+
const chat = await bot.telegram.getChat(chatId);
|
|
28856
|
+
recordKnownChat({
|
|
28857
|
+
id: chat.id.toString(),
|
|
28858
|
+
title: chatTitleFromTelegramChat(chat),
|
|
28859
|
+
type: chat.type
|
|
28860
|
+
});
|
|
28861
|
+
console.error(
|
|
28862
|
+
`[telegram:recordChatInteraction] getChat ok chatId=${chat.id} knownChats=${knownChats.size}`
|
|
28863
|
+
);
|
|
28864
|
+
} catch (error48) {
|
|
28865
|
+
recordKnownChat({
|
|
28866
|
+
id: chatId,
|
|
28867
|
+
title: chatId,
|
|
28868
|
+
type: "unknown"
|
|
28869
|
+
});
|
|
28870
|
+
console.error(
|
|
28871
|
+
`[telegram:recordChatInteraction] getChat failed for ${chatId}, stored fallback:`,
|
|
28872
|
+
error48
|
|
28873
|
+
);
|
|
28874
|
+
}
|
|
28875
|
+
}
|
|
28876
|
+
function getConfiguredChatCandidates() {
|
|
28877
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
28878
|
+
if (allowedUserId) candidates.add(allowedUserId);
|
|
28879
|
+
for (const id of allowedRecipients) {
|
|
28880
|
+
if (id) candidates.add(id);
|
|
28881
|
+
}
|
|
28882
|
+
for (const chat of knownChats.values()) {
|
|
28883
|
+
candidates.add(chat.id);
|
|
28884
|
+
}
|
|
28885
|
+
for (const msg of messageHistory) {
|
|
28886
|
+
candidates.add(msg.chatId);
|
|
28887
|
+
}
|
|
28888
|
+
return candidates;
|
|
28889
|
+
}
|
|
28890
|
+
async function ensurePollingMode() {
|
|
28891
|
+
if (!bot || IS_MOCK) return;
|
|
28892
|
+
try {
|
|
28893
|
+
const info = await bot.telegram.getWebhookInfo();
|
|
28894
|
+
if (info.url) {
|
|
28895
|
+
console.error(
|
|
28896
|
+
`[telegram] active webhook detected (${info.url}); switching to getUpdates polling`
|
|
28897
|
+
);
|
|
28898
|
+
await bot.telegram.deleteWebhook({ drop_pending_updates: false });
|
|
28899
|
+
}
|
|
28900
|
+
} catch (error48) {
|
|
28901
|
+
console.error("[telegram] ensurePollingMode failed:", error48);
|
|
28902
|
+
}
|
|
28903
|
+
}
|
|
28904
|
+
async function discoverChatsFromApi() {
|
|
28905
|
+
if (IS_MOCK || !bot) return;
|
|
28906
|
+
const candidates = getConfiguredChatCandidates();
|
|
28907
|
+
console.error(
|
|
28908
|
+
`[telegram:list_active_chats] discoverChatsFromApi: probing ${candidates.size} candidate chat(s) from state=${getStatePath()}`
|
|
28909
|
+
);
|
|
28910
|
+
for (const chatId of candidates) {
|
|
28911
|
+
await recordChatInteraction(chatId);
|
|
28912
|
+
}
|
|
28913
|
+
try {
|
|
28914
|
+
await ensurePollingMode();
|
|
28915
|
+
const updates = await bot.telegram.getUpdates(0, 100, pollingOffset, [
|
|
28916
|
+
"message",
|
|
28917
|
+
"edited_message",
|
|
28918
|
+
"channel_post",
|
|
28919
|
+
"my_chat_member",
|
|
28920
|
+
"chat_member"
|
|
28921
|
+
]);
|
|
28922
|
+
for (const update of updates) {
|
|
28923
|
+
pollingOffset = update.update_id + 1;
|
|
28924
|
+
const msgLike = update;
|
|
28925
|
+
if (!msgLike.message && !msgLike.edited_message && !msgLike.channel_post && !msgLike.my_chat_member)
|
|
28926
|
+
continue;
|
|
28927
|
+
recordKnownChatFromUpdate(msgLike);
|
|
28928
|
+
}
|
|
28929
|
+
console.error(
|
|
28930
|
+
`[telegram:list_active_chats] discoverChatsFromApi: processed ${updates.length} update(s)`
|
|
28931
|
+
);
|
|
28932
|
+
persistState();
|
|
28933
|
+
} catch (error48) {
|
|
28934
|
+
console.error(
|
|
28935
|
+
"[telegram:list_active_chats] discoverChatsFromApi getUpdates failed:",
|
|
28936
|
+
error48
|
|
28937
|
+
);
|
|
28938
|
+
}
|
|
28939
|
+
}
|
|
28940
|
+
function getActiveChats() {
|
|
28941
|
+
const chats = /* @__PURE__ */ new Map();
|
|
28942
|
+
for (const chat of knownChats.values()) {
|
|
28943
|
+
chats.set(chat.id, chat);
|
|
28944
|
+
}
|
|
28945
|
+
for (const msg of messageHistory) {
|
|
28946
|
+
if (chats.has(msg.chatId)) continue;
|
|
28947
|
+
chats.set(msg.chatId, {
|
|
28948
|
+
id: msg.chatId,
|
|
28949
|
+
title: msg.chatTitle || "Unknown",
|
|
28950
|
+
type: msg.metadata?.chatType || "unknown"
|
|
28951
|
+
});
|
|
28952
|
+
}
|
|
28953
|
+
return Array.from(chats.values()).sort(
|
|
28954
|
+
(a, b) => a.title.localeCompare(b.title)
|
|
28955
|
+
);
|
|
28956
|
+
}
|
|
28957
|
+
async function fetchNewUpdates() {
|
|
28958
|
+
if (!bot || IS_MOCK) return;
|
|
28959
|
+
try {
|
|
28960
|
+
await ensurePollingMode();
|
|
28961
|
+
const updates = await bot.telegram.getUpdates(0, 100, pollingOffset, [
|
|
28962
|
+
"message",
|
|
28963
|
+
"edited_message",
|
|
28964
|
+
"channel_post",
|
|
28965
|
+
"my_chat_member"
|
|
28966
|
+
]);
|
|
28967
|
+
for (const update of updates) {
|
|
28968
|
+
pollingOffset = update.update_id + 1;
|
|
28969
|
+
const msgLike = update;
|
|
28970
|
+
if (!msgLike.message && !msgLike.edited_message && !msgLike.channel_post && !msgLike.my_chat_member)
|
|
28971
|
+
continue;
|
|
28972
|
+
recordKnownChatFromUpdate(msgLike);
|
|
28973
|
+
const msg = msgLike.message;
|
|
28974
|
+
if (!msg?.text) continue;
|
|
28975
|
+
const senderId = msg.from?.id?.toString() ?? "unknown";
|
|
28976
|
+
const chatId = msg.chat.id.toString();
|
|
28977
|
+
const chatTitle = msg.chat.title || msg.chat.username || msg.chat.first_name || "Private Chat";
|
|
28978
|
+
if (messageHistory.some(
|
|
28979
|
+
(m) => m.metadata?.message_id === msg.message_id
|
|
28980
|
+
))
|
|
28981
|
+
continue;
|
|
28982
|
+
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
28983
|
+
if (currentAllowedUserId && msg.chat.type === "private") {
|
|
28984
|
+
if (senderId !== currentAllowedUserId) continue;
|
|
28985
|
+
}
|
|
28986
|
+
let content = DOMPurify.sanitize(msg.text, {
|
|
28987
|
+
ALLOWED_TAGS: [],
|
|
28988
|
+
ALLOWED_ATTR: []
|
|
28989
|
+
});
|
|
28990
|
+
content = content.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/[*_~]/g, "\\$&");
|
|
28991
|
+
messageHistory.push({
|
|
28992
|
+
senderId,
|
|
28993
|
+
chatId,
|
|
28994
|
+
chatTitle,
|
|
28995
|
+
content,
|
|
28996
|
+
timestamp: new Date(msg.date * 1e3).toISOString(),
|
|
28997
|
+
metadata: {
|
|
28998
|
+
chatId,
|
|
28999
|
+
chatType: msg.chat.type,
|
|
29000
|
+
message_id: msg.message_id
|
|
29001
|
+
}
|
|
29002
|
+
});
|
|
29003
|
+
if (messageHistory.length > MAX_HISTORY) {
|
|
29004
|
+
messageHistory.shift();
|
|
29005
|
+
}
|
|
29006
|
+
}
|
|
29007
|
+
persistState();
|
|
29008
|
+
} catch (error48) {
|
|
29009
|
+
console.error("fetchNewUpdates failed:", error48);
|
|
29010
|
+
}
|
|
29011
|
+
}
|
|
28635
29012
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
28636
29013
|
return {
|
|
28637
29014
|
tools: [
|
|
28638
29015
|
{
|
|
28639
|
-
name: "
|
|
28640
|
-
description: "Send
|
|
29016
|
+
name: "send_message",
|
|
29017
|
+
description: "Send text, photo, or document to a Telegram user by numeric ID.",
|
|
28641
29018
|
inputSchema: {
|
|
28642
29019
|
type: "object",
|
|
28643
29020
|
properties: {
|
|
@@ -28647,7 +29024,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28647
29024
|
},
|
|
28648
29025
|
recipientId: {
|
|
28649
29026
|
type: "string",
|
|
28650
|
-
description: "
|
|
29027
|
+
description: "Numeric Telegram user ID."
|
|
28651
29028
|
},
|
|
28652
29029
|
mediaPath: {
|
|
28653
29030
|
type: "string",
|
|
@@ -28675,7 +29052,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28675
29052
|
},
|
|
28676
29053
|
{
|
|
28677
29054
|
name: "list_active_chats",
|
|
28678
|
-
description: "List
|
|
29055
|
+
description: "List chats the bot has sent to or received messages from.",
|
|
28679
29056
|
inputSchema: {
|
|
28680
29057
|
type: "object",
|
|
28681
29058
|
properties: {
|
|
@@ -28689,16 +29066,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28689
29066
|
description: "Get detailed information about a specific Telegram chat.",
|
|
28690
29067
|
inputSchema: {
|
|
28691
29068
|
type: "object",
|
|
28692
|
-
properties: {
|
|
29069
|
+
properties: {
|
|
29070
|
+
chatId: {
|
|
29071
|
+
type: "string",
|
|
29072
|
+
description: "Numeric Telegram chat ID."
|
|
29073
|
+
}
|
|
29074
|
+
},
|
|
28693
29075
|
required: ["chatId"]
|
|
28694
29076
|
}
|
|
28695
29077
|
},
|
|
28696
29078
|
{
|
|
28697
|
-
name: "
|
|
29079
|
+
name: "leave_chat",
|
|
28698
29080
|
description: "Request the bot to leave a specific group or channel.",
|
|
28699
29081
|
inputSchema: {
|
|
28700
29082
|
type: "object",
|
|
28701
|
-
properties: {
|
|
29083
|
+
properties: {
|
|
29084
|
+
chatId: {
|
|
29085
|
+
type: "string",
|
|
29086
|
+
description: "Numeric Telegram chat ID."
|
|
29087
|
+
}
|
|
29088
|
+
},
|
|
28702
29089
|
required: ["chatId"]
|
|
28703
29090
|
}
|
|
28704
29091
|
}
|
|
@@ -28709,7 +29096,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28709
29096
|
const { name, arguments: args } = request.params;
|
|
28710
29097
|
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
28711
29098
|
try {
|
|
28712
|
-
if (name === "
|
|
29099
|
+
if (name === "send_message") {
|
|
29100
|
+
reloadState();
|
|
28713
29101
|
const { content, recipientId, mediaPath, mediaType } = SendMessageSchema.parse(args);
|
|
28714
29102
|
const targetId = recipientId || currentAllowedUserId;
|
|
28715
29103
|
if (!targetId) {
|
|
@@ -28724,7 +29112,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28724
29112
|
429
|
|
28725
29113
|
);
|
|
28726
29114
|
}
|
|
28727
|
-
|
|
29115
|
+
validateSendRecipient(targetId);
|
|
28728
29116
|
let safePath;
|
|
28729
29117
|
if (mediaPath) {
|
|
28730
29118
|
safePath = validateFilePath(mediaPath);
|
|
@@ -28734,19 +29122,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28734
29122
|
if (mediaPath)
|
|
28735
29123
|
mockResponse += `
|
|
28736
29124
|
[Mock] Media: ${mediaPath} (${mediaType || "auto"})`;
|
|
29125
|
+
await recordChatInteraction(targetId);
|
|
28737
29126
|
return { content: [{ type: "text", text: mockResponse }] };
|
|
28738
29127
|
}
|
|
28739
29128
|
if (mediaPath && safePath) {
|
|
28740
29129
|
const source = { source: safePath };
|
|
28741
29130
|
const isPhoto = mediaType === "photo" || !mediaType && isImage(safePath);
|
|
28742
29131
|
if (isPhoto) {
|
|
28743
|
-
await bot.telegram.sendPhoto(targetId, source, {
|
|
29132
|
+
const sent = await bot.telegram.sendPhoto(targetId, source, {
|
|
28744
29133
|
caption: content
|
|
28745
29134
|
});
|
|
29135
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28746
29136
|
} else {
|
|
28747
|
-
await bot.telegram.sendDocument(
|
|
28748
|
-
|
|
28749
|
-
|
|
29137
|
+
const sent = await bot.telegram.sendDocument(
|
|
29138
|
+
targetId,
|
|
29139
|
+
source,
|
|
29140
|
+
{ caption: content }
|
|
29141
|
+
);
|
|
29142
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28750
29143
|
}
|
|
28751
29144
|
return {
|
|
28752
29145
|
content: [
|
|
@@ -28757,7 +29150,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28757
29150
|
]
|
|
28758
29151
|
};
|
|
28759
29152
|
} else {
|
|
28760
|
-
await bot.telegram.sendMessage(targetId, content);
|
|
29153
|
+
const sent = await bot.telegram.sendMessage(targetId, content);
|
|
29154
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28761
29155
|
return {
|
|
28762
29156
|
content: [
|
|
28763
29157
|
{ type: "text", text: `Message sent successfully.` }
|
|
@@ -28767,6 +29161,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28767
29161
|
}
|
|
28768
29162
|
if (name === "get_recent_messages") {
|
|
28769
29163
|
const { limit, offset } = GetRecentMessagesSchema.parse(args || {});
|
|
29164
|
+
await fetchNewUpdates();
|
|
28770
29165
|
const paginated = applyPagination(
|
|
28771
29166
|
messageHistory.slice().reverse(),
|
|
28772
29167
|
{ limit, offset }
|
|
@@ -28795,18 +29190,12 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
|
|
|
28795
29190
|
}
|
|
28796
29191
|
if (name === "list_active_chats") {
|
|
28797
29192
|
const { limit, offset } = GetRecentMessagesSchema.parse(args || {});
|
|
28798
|
-
|
|
28799
|
-
|
|
28800
|
-
);
|
|
28801
|
-
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
id,
|
|
28805
|
-
title: lastMsg?.chatTitle || "Unknown",
|
|
28806
|
-
type: lastMsg?.metadata?.chatType || "unknown"
|
|
28807
|
-
};
|
|
28808
|
-
});
|
|
28809
|
-
const paginated = applyPagination(chatDetails, { limit, offset });
|
|
29193
|
+
reloadState();
|
|
29194
|
+
await fetchNewUpdates();
|
|
29195
|
+
await discoverChatsFromApi();
|
|
29196
|
+
logActiveChatsDebug("after fetch and discover");
|
|
29197
|
+
const activeChats = getActiveChats();
|
|
29198
|
+
const paginated = applyPagination(activeChats, { limit, offset });
|
|
28810
29199
|
const formatted = paginated.items.map((c) => `- ${c.title} (ID: ${c.id}, Type: ${c.type})`).join("\n");
|
|
28811
29200
|
return {
|
|
28812
29201
|
content: [
|
|
@@ -28815,14 +29204,14 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
|
|
|
28815
29204
|
text: formatted ? `Active chats:
|
|
28816
29205
|
${formatted}
|
|
28817
29206
|
|
|
28818
|
-
${formatPaginationFooter(offset, limit,
|
|
29207
|
+
${formatPaginationFooter(offset, limit, activeChats.length)}` : "No active chats found."
|
|
28819
29208
|
}
|
|
28820
29209
|
]
|
|
28821
29210
|
};
|
|
28822
29211
|
}
|
|
28823
29212
|
if (name === "get_chat_info") {
|
|
28824
29213
|
const { chatId } = GetChatInfoSchema.parse(args);
|
|
28825
|
-
|
|
29214
|
+
validateChatId(chatId);
|
|
28826
29215
|
if (IS_MOCK || !bot) {
|
|
28827
29216
|
return {
|
|
28828
29217
|
content: [
|
|
@@ -28848,9 +29237,9 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
|
|
|
28848
29237
|
]
|
|
28849
29238
|
};
|
|
28850
29239
|
}
|
|
28851
|
-
if (name === "
|
|
29240
|
+
if (name === "leave_chat") {
|
|
28852
29241
|
const { chatId } = LeaveChatSchema.parse(args);
|
|
28853
|
-
|
|
29242
|
+
validateChatId(chatId);
|
|
28854
29243
|
if (IS_MOCK || !bot) {
|
|
28855
29244
|
return {
|
|
28856
29245
|
content: [
|
|
@@ -28892,108 +29281,29 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
|
|
|
28892
29281
|
if (msg.includes("limit exceeded")) {
|
|
28893
29282
|
return createApiError("Rate limit exceeded.", 429);
|
|
28894
29283
|
}
|
|
29284
|
+
if (msg.includes("invalid telegram user id format")) {
|
|
29285
|
+
return createValidationError(
|
|
29286
|
+
"recipientId",
|
|
29287
|
+
'Use a numeric user ID only (e.g., "123456789"). @username is not supported.'
|
|
29288
|
+
);
|
|
29289
|
+
}
|
|
29290
|
+
if (msg.includes("invalid telegram chat id format")) {
|
|
29291
|
+
return createValidationError(
|
|
29292
|
+
"chatId",
|
|
29293
|
+
'Use a numeric chat ID only (e.g., "123456789"). @username is not supported.'
|
|
29294
|
+
);
|
|
29295
|
+
}
|
|
28895
29296
|
}
|
|
28896
29297
|
return createInternalError(
|
|
28897
29298
|
"An unexpected error occurred while processing the request."
|
|
28898
29299
|
);
|
|
28899
29300
|
}
|
|
28900
29301
|
});
|
|
28901
|
-
if (bot) {
|
|
28902
|
-
bot.on("text", async (ctx) => {
|
|
28903
|
-
const senderId = ctx.from.id.toString();
|
|
28904
|
-
const chatId = ctx.chat.id.toString();
|
|
28905
|
-
const chatTitle = ctx.chat.title || ctx.chat.username || ctx.chat.first_name || "Private Chat";
|
|
28906
|
-
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
28907
|
-
if (currentAllowedUserId) {
|
|
28908
|
-
if (ctx.chat.type === "private") {
|
|
28909
|
-
if (senderId !== currentAllowedUserId) {
|
|
28910
|
-
console.warn(
|
|
28911
|
-
`Unauthorized private message from ${senderId}`
|
|
28912
|
-
);
|
|
28913
|
-
await ctx.reply(
|
|
28914
|
-
"\u274C Access denied. Only the authorized user can interact with this bot."
|
|
28915
|
-
).catch(() => {
|
|
28916
|
-
});
|
|
28917
|
-
return;
|
|
28918
|
-
}
|
|
28919
|
-
} else if (ctx.chat.type === "group" || ctx.chat.type === "supergroup") {
|
|
28920
|
-
try {
|
|
28921
|
-
const chatMember = await ctx.getChatMember(ctx.from.id);
|
|
28922
|
-
const isAdmin = ["creator", "administrator"].includes(
|
|
28923
|
-
chatMember.status
|
|
28924
|
-
);
|
|
28925
|
-
if (!isAdmin && senderId !== currentAllowedUserId) {
|
|
28926
|
-
console.warn(
|
|
28927
|
-
`Unauthorized group message from ${senderId} in ${chatTitle}`
|
|
28928
|
-
);
|
|
28929
|
-
return;
|
|
28930
|
-
}
|
|
28931
|
-
} catch (error48) {
|
|
28932
|
-
console.error(
|
|
28933
|
-
"Failed to check group member status:",
|
|
28934
|
-
error48
|
|
28935
|
-
);
|
|
28936
|
-
return;
|
|
28937
|
-
}
|
|
28938
|
-
} else if (ctx.chat.type === "channel") {
|
|
28939
|
-
return;
|
|
28940
|
-
}
|
|
28941
|
-
}
|
|
28942
|
-
let messageContent = ctx.message.text;
|
|
28943
|
-
messageContent = DOMPurify.sanitize(messageContent, {
|
|
28944
|
-
ALLOWED_TAGS: [],
|
|
28945
|
-
ALLOWED_ATTR: []
|
|
28946
|
-
});
|
|
28947
|
-
messageContent = messageContent.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/[*_~]/g, "\\$&");
|
|
28948
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
28949
|
-
const metadata = {
|
|
28950
|
-
chatId,
|
|
28951
|
-
chatType: ctx.chat?.type,
|
|
28952
|
-
message_id: ctx.message.message_id
|
|
28953
|
-
};
|
|
28954
|
-
messageHistory.push({
|
|
28955
|
-
senderId,
|
|
28956
|
-
chatId,
|
|
28957
|
-
chatTitle,
|
|
28958
|
-
content: messageContent,
|
|
28959
|
-
timestamp,
|
|
28960
|
-
metadata
|
|
28961
|
-
});
|
|
28962
|
-
if (messageHistory.length > MAX_HISTORY) {
|
|
28963
|
-
messageHistory.shift();
|
|
28964
|
-
}
|
|
28965
|
-
try {
|
|
28966
|
-
await server.notification({
|
|
28967
|
-
method: "notifications/push_event",
|
|
28968
|
-
params: {
|
|
28969
|
-
type: "message",
|
|
28970
|
-
senderId,
|
|
28971
|
-
content: messageContent,
|
|
28972
|
-
metadata
|
|
28973
|
-
}
|
|
28974
|
-
});
|
|
28975
|
-
} catch (error48) {
|
|
28976
|
-
console.error("Failed to push notification:", error48);
|
|
28977
|
-
}
|
|
28978
|
-
});
|
|
28979
|
-
}
|
|
28980
29302
|
async function run() {
|
|
29303
|
+
loadState();
|
|
28981
29304
|
const transport = new StdioServerTransport();
|
|
28982
29305
|
await server.connect(transport);
|
|
28983
29306
|
console.error("Telegram MCP Server running on stdio");
|
|
28984
|
-
if (bot) {
|
|
28985
|
-
bot.launch().catch((err) => {
|
|
28986
|
-
console.error("Failed to launch Telegram bot:", err);
|
|
28987
|
-
});
|
|
28988
|
-
process.once("SIGINT", () => {
|
|
28989
|
-
bot.stop("SIGINT");
|
|
28990
|
-
process.exit(0);
|
|
28991
|
-
});
|
|
28992
|
-
process.once("SIGTERM", () => {
|
|
28993
|
-
bot.stop("SIGTERM");
|
|
28994
|
-
process.exit(0);
|
|
28995
|
-
});
|
|
28996
|
-
}
|
|
28997
29307
|
}
|
|
28998
29308
|
if (process.env.NODE_ENV !== "test") {
|
|
28999
29309
|
run().catch((error48) => {
|
package/package.json
CHANGED