@fre4x/telegram 1.1.4 → 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/dist/index.js +475 -198
- package/package.json +1 -1
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(),
|
|
@@ -28634,24 +28740,281 @@ var server = new Server(
|
|
|
28634
28740
|
instructions: "Telegram MCP server. Delivers inbound Telegram messages as channel notifications via notifications/message."
|
|
28635
28741
|
}
|
|
28636
28742
|
);
|
|
28637
|
-
var isInitialized = false;
|
|
28638
|
-
server.oninitialized = () => {
|
|
28639
|
-
isInitialized = true;
|
|
28640
|
-
};
|
|
28641
|
-
var seenMessageIds = /* @__PURE__ */ new Set();
|
|
28642
|
-
var MAX_SEEN_IDS = 500;
|
|
28643
28743
|
var isImage = (filePath) => {
|
|
28644
28744
|
const ext = path.extname(filePath).toLowerCase();
|
|
28645
28745
|
return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext);
|
|
28646
28746
|
};
|
|
28647
28747
|
var messageHistory = [];
|
|
28648
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
|
+
}
|
|
28649
29012
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
28650
29013
|
return {
|
|
28651
29014
|
tools: [
|
|
28652
29015
|
{
|
|
28653
|
-
name: "
|
|
28654
|
-
description: "Send
|
|
29016
|
+
name: "send_message",
|
|
29017
|
+
description: "Send text, photo, or document to a Telegram user by numeric ID.",
|
|
28655
29018
|
inputSchema: {
|
|
28656
29019
|
type: "object",
|
|
28657
29020
|
properties: {
|
|
@@ -28661,7 +29024,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28661
29024
|
},
|
|
28662
29025
|
recipientId: {
|
|
28663
29026
|
type: "string",
|
|
28664
|
-
description: "
|
|
29027
|
+
description: "Numeric Telegram user ID."
|
|
28665
29028
|
},
|
|
28666
29029
|
mediaPath: {
|
|
28667
29030
|
type: "string",
|
|
@@ -28689,7 +29052,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28689
29052
|
},
|
|
28690
29053
|
{
|
|
28691
29054
|
name: "list_active_chats",
|
|
28692
|
-
description: "List
|
|
29055
|
+
description: "List chats the bot has sent to or received messages from.",
|
|
28693
29056
|
inputSchema: {
|
|
28694
29057
|
type: "object",
|
|
28695
29058
|
properties: {
|
|
@@ -28703,16 +29066,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28703
29066
|
description: "Get detailed information about a specific Telegram chat.",
|
|
28704
29067
|
inputSchema: {
|
|
28705
29068
|
type: "object",
|
|
28706
|
-
properties: {
|
|
29069
|
+
properties: {
|
|
29070
|
+
chatId: {
|
|
29071
|
+
type: "string",
|
|
29072
|
+
description: "Numeric Telegram chat ID."
|
|
29073
|
+
}
|
|
29074
|
+
},
|
|
28707
29075
|
required: ["chatId"]
|
|
28708
29076
|
}
|
|
28709
29077
|
},
|
|
28710
29078
|
{
|
|
28711
|
-
name: "
|
|
29079
|
+
name: "leave_chat",
|
|
28712
29080
|
description: "Request the bot to leave a specific group or channel.",
|
|
28713
29081
|
inputSchema: {
|
|
28714
29082
|
type: "object",
|
|
28715
|
-
properties: {
|
|
29083
|
+
properties: {
|
|
29084
|
+
chatId: {
|
|
29085
|
+
type: "string",
|
|
29086
|
+
description: "Numeric Telegram chat ID."
|
|
29087
|
+
}
|
|
29088
|
+
},
|
|
28716
29089
|
required: ["chatId"]
|
|
28717
29090
|
}
|
|
28718
29091
|
}
|
|
@@ -28723,7 +29096,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28723
29096
|
const { name, arguments: args } = request.params;
|
|
28724
29097
|
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
28725
29098
|
try {
|
|
28726
|
-
if (name === "
|
|
29099
|
+
if (name === "send_message") {
|
|
29100
|
+
reloadState();
|
|
28727
29101
|
const { content, recipientId, mediaPath, mediaType } = SendMessageSchema.parse(args);
|
|
28728
29102
|
const targetId = recipientId || currentAllowedUserId;
|
|
28729
29103
|
if (!targetId) {
|
|
@@ -28738,7 +29112,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28738
29112
|
429
|
|
28739
29113
|
);
|
|
28740
29114
|
}
|
|
28741
|
-
|
|
29115
|
+
validateSendRecipient(targetId);
|
|
28742
29116
|
let safePath;
|
|
28743
29117
|
if (mediaPath) {
|
|
28744
29118
|
safePath = validateFilePath(mediaPath);
|
|
@@ -28748,19 +29122,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28748
29122
|
if (mediaPath)
|
|
28749
29123
|
mockResponse += `
|
|
28750
29124
|
[Mock] Media: ${mediaPath} (${mediaType || "auto"})`;
|
|
29125
|
+
await recordChatInteraction(targetId);
|
|
28751
29126
|
return { content: [{ type: "text", text: mockResponse }] };
|
|
28752
29127
|
}
|
|
28753
29128
|
if (mediaPath && safePath) {
|
|
28754
29129
|
const source = { source: safePath };
|
|
28755
29130
|
const isPhoto = mediaType === "photo" || !mediaType && isImage(safePath);
|
|
28756
29131
|
if (isPhoto) {
|
|
28757
|
-
await bot.telegram.sendPhoto(targetId, source, {
|
|
29132
|
+
const sent = await bot.telegram.sendPhoto(targetId, source, {
|
|
28758
29133
|
caption: content
|
|
28759
29134
|
});
|
|
29135
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28760
29136
|
} else {
|
|
28761
|
-
await bot.telegram.sendDocument(
|
|
28762
|
-
|
|
28763
|
-
|
|
29137
|
+
const sent = await bot.telegram.sendDocument(
|
|
29138
|
+
targetId,
|
|
29139
|
+
source,
|
|
29140
|
+
{ caption: content }
|
|
29141
|
+
);
|
|
29142
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28764
29143
|
}
|
|
28765
29144
|
return {
|
|
28766
29145
|
content: [
|
|
@@ -28771,7 +29150,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28771
29150
|
]
|
|
28772
29151
|
};
|
|
28773
29152
|
} else {
|
|
28774
|
-
await bot.telegram.sendMessage(targetId, content);
|
|
29153
|
+
const sent = await bot.telegram.sendMessage(targetId, content);
|
|
29154
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28775
29155
|
return {
|
|
28776
29156
|
content: [
|
|
28777
29157
|
{ type: "text", text: `Message sent successfully.` }
|
|
@@ -28781,6 +29161,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28781
29161
|
}
|
|
28782
29162
|
if (name === "get_recent_messages") {
|
|
28783
29163
|
const { limit, offset } = GetRecentMessagesSchema.parse(args || {});
|
|
29164
|
+
await fetchNewUpdates();
|
|
28784
29165
|
const paginated = applyPagination(
|
|
28785
29166
|
messageHistory.slice().reverse(),
|
|
28786
29167
|
{ limit, offset }
|
|
@@ -28809,18 +29190,12 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
|
|
|
28809
29190
|
}
|
|
28810
29191
|
if (name === "list_active_chats") {
|
|
28811
29192
|
const { limit, offset } = GetRecentMessagesSchema.parse(args || {});
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
);
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
id,
|
|
28819
|
-
title: lastMsg?.chatTitle || "Unknown",
|
|
28820
|
-
type: lastMsg?.metadata?.chatType || "unknown"
|
|
28821
|
-
};
|
|
28822
|
-
});
|
|
28823
|
-
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 });
|
|
28824
29199
|
const formatted = paginated.items.map((c) => `- ${c.title} (ID: ${c.id}, Type: ${c.type})`).join("\n");
|
|
28825
29200
|
return {
|
|
28826
29201
|
content: [
|
|
@@ -28829,14 +29204,14 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
|
|
|
28829
29204
|
text: formatted ? `Active chats:
|
|
28830
29205
|
${formatted}
|
|
28831
29206
|
|
|
28832
|
-
${formatPaginationFooter(offset, limit,
|
|
29207
|
+
${formatPaginationFooter(offset, limit, activeChats.length)}` : "No active chats found."
|
|
28833
29208
|
}
|
|
28834
29209
|
]
|
|
28835
29210
|
};
|
|
28836
29211
|
}
|
|
28837
29212
|
if (name === "get_chat_info") {
|
|
28838
29213
|
const { chatId } = GetChatInfoSchema.parse(args);
|
|
28839
|
-
|
|
29214
|
+
validateChatId(chatId);
|
|
28840
29215
|
if (IS_MOCK || !bot) {
|
|
28841
29216
|
return {
|
|
28842
29217
|
content: [
|
|
@@ -28862,9 +29237,9 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
|
|
|
28862
29237
|
]
|
|
28863
29238
|
};
|
|
28864
29239
|
}
|
|
28865
|
-
if (name === "
|
|
29240
|
+
if (name === "leave_chat") {
|
|
28866
29241
|
const { chatId } = LeaveChatSchema.parse(args);
|
|
28867
|
-
|
|
29242
|
+
validateChatId(chatId);
|
|
28868
29243
|
if (IS_MOCK || !bot) {
|
|
28869
29244
|
return {
|
|
28870
29245
|
content: [
|
|
@@ -28906,127 +29281,29 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
|
|
|
28906
29281
|
if (msg.includes("limit exceeded")) {
|
|
28907
29282
|
return createApiError("Rate limit exceeded.", 429);
|
|
28908
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
|
+
}
|
|
28909
29296
|
}
|
|
28910
29297
|
return createInternalError(
|
|
28911
29298
|
"An unexpected error occurred while processing the request."
|
|
28912
29299
|
);
|
|
28913
29300
|
}
|
|
28914
29301
|
});
|
|
28915
|
-
if (bot) {
|
|
28916
|
-
bot.on("text", async (ctx) => {
|
|
28917
|
-
const senderId = ctx.from.id.toString();
|
|
28918
|
-
const chatId = ctx.chat.id.toString();
|
|
28919
|
-
const chatTitle = ctx.chat.title || ctx.chat.username || ctx.chat.first_name || "Private Chat";
|
|
28920
|
-
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
28921
|
-
if (currentAllowedUserId) {
|
|
28922
|
-
if (ctx.chat.type === "private") {
|
|
28923
|
-
if (senderId !== currentAllowedUserId) {
|
|
28924
|
-
console.warn(
|
|
28925
|
-
`Unauthorized private message from ${senderId}`
|
|
28926
|
-
);
|
|
28927
|
-
await ctx.reply(
|
|
28928
|
-
"\u274C Access denied. Only the authorized user can interact with this bot."
|
|
28929
|
-
).catch(() => {
|
|
28930
|
-
});
|
|
28931
|
-
return;
|
|
28932
|
-
}
|
|
28933
|
-
} else if (ctx.chat.type === "group" || ctx.chat.type === "supergroup") {
|
|
28934
|
-
try {
|
|
28935
|
-
const chatMember = await ctx.getChatMember(ctx.from.id);
|
|
28936
|
-
const isAdmin = ["creator", "administrator"].includes(
|
|
28937
|
-
chatMember.status
|
|
28938
|
-
);
|
|
28939
|
-
if (!isAdmin && senderId !== currentAllowedUserId) {
|
|
28940
|
-
console.warn(
|
|
28941
|
-
`Unauthorized group message from ${senderId} in ${chatTitle}`
|
|
28942
|
-
);
|
|
28943
|
-
return;
|
|
28944
|
-
}
|
|
28945
|
-
} catch (error48) {
|
|
28946
|
-
console.error(
|
|
28947
|
-
"Failed to check group member status:",
|
|
28948
|
-
error48
|
|
28949
|
-
);
|
|
28950
|
-
return;
|
|
28951
|
-
}
|
|
28952
|
-
} else if (ctx.chat.type === "channel") {
|
|
28953
|
-
return;
|
|
28954
|
-
}
|
|
28955
|
-
}
|
|
28956
|
-
let messageContent = ctx.message.text;
|
|
28957
|
-
messageContent = DOMPurify.sanitize(messageContent, {
|
|
28958
|
-
ALLOWED_TAGS: [],
|
|
28959
|
-
ALLOWED_ATTR: []
|
|
28960
|
-
});
|
|
28961
|
-
messageContent = messageContent.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/[*_~]/g, "\\$&");
|
|
28962
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
28963
|
-
const metadata = {
|
|
28964
|
-
chatId,
|
|
28965
|
-
chatType: ctx.chat?.type,
|
|
28966
|
-
message_id: ctx.message.message_id
|
|
28967
|
-
};
|
|
28968
|
-
messageHistory.push({
|
|
28969
|
-
senderId,
|
|
28970
|
-
chatId,
|
|
28971
|
-
chatTitle,
|
|
28972
|
-
content: messageContent,
|
|
28973
|
-
timestamp,
|
|
28974
|
-
metadata
|
|
28975
|
-
});
|
|
28976
|
-
if (messageHistory.length > MAX_HISTORY) {
|
|
28977
|
-
messageHistory.shift();
|
|
28978
|
-
}
|
|
28979
|
-
if (!isInitialized) {
|
|
28980
|
-
console.warn(
|
|
28981
|
-
`[telegram] Pre-init notification dropped (message_id=${metadata.message_id})`
|
|
28982
|
-
);
|
|
28983
|
-
return;
|
|
28984
|
-
}
|
|
28985
|
-
const msgId = metadata.message_id;
|
|
28986
|
-
if (seenMessageIds.has(msgId)) {
|
|
28987
|
-
return;
|
|
28988
|
-
}
|
|
28989
|
-
if (seenMessageIds.size >= MAX_SEEN_IDS) {
|
|
28990
|
-
seenMessageIds.delete(seenMessageIds.values().next().value);
|
|
28991
|
-
}
|
|
28992
|
-
seenMessageIds.add(msgId);
|
|
28993
|
-
try {
|
|
28994
|
-
await server.notification({
|
|
28995
|
-
method: "notifications/message",
|
|
28996
|
-
params: {
|
|
28997
|
-
_meta: {
|
|
28998
|
-
channel: "telegram"
|
|
28999
|
-
},
|
|
29000
|
-
content: [
|
|
29001
|
-
{
|
|
29002
|
-
type: "text",
|
|
29003
|
-
text: `New message from ${senderId} in ${chatTitle} (${chatId}): ${messageContent}`
|
|
29004
|
-
}
|
|
29005
|
-
]
|
|
29006
|
-
}
|
|
29007
|
-
});
|
|
29008
|
-
} catch (error48) {
|
|
29009
|
-
console.error("Failed to push channel notification:", error48);
|
|
29010
|
-
}
|
|
29011
|
-
});
|
|
29012
|
-
}
|
|
29013
29302
|
async function run() {
|
|
29303
|
+
loadState();
|
|
29014
29304
|
const transport = new StdioServerTransport();
|
|
29015
29305
|
await server.connect(transport);
|
|
29016
29306
|
console.error("Telegram MCP Server running on stdio");
|
|
29017
|
-
if (bot) {
|
|
29018
|
-
bot.launch().catch((err) => {
|
|
29019
|
-
console.error("Failed to launch Telegram bot:", err);
|
|
29020
|
-
});
|
|
29021
|
-
process.once("SIGINT", () => {
|
|
29022
|
-
bot.stop("SIGINT");
|
|
29023
|
-
process.exit(0);
|
|
29024
|
-
});
|
|
29025
|
-
process.once("SIGTERM", () => {
|
|
29026
|
-
bot.stop("SIGTERM");
|
|
29027
|
-
process.exit(0);
|
|
29028
|
-
});
|
|
29029
|
-
}
|
|
29030
29307
|
}
|
|
29031
29308
|
if (process.env.NODE_ENV !== "test") {
|
|
29032
29309
|
run().catch((error48) => {
|
package/package.json
CHANGED