@fre4x/telegram 1.1.4 → 1.1.6
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 +768 -226
- 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,8 +28235,10 @@ var StdioServerTransport = class {
|
|
|
28144
28235
|
// src/index.ts
|
|
28145
28236
|
var import_dotenv = __toESM(require_main(), 1);
|
|
28146
28237
|
import { Telegraf } from "telegraf";
|
|
28147
|
-
import
|
|
28148
|
-
import * as
|
|
28238
|
+
import { createHash } from "node:crypto";
|
|
28239
|
+
import * as fs from "node:fs";
|
|
28240
|
+
import os from "node:os";
|
|
28241
|
+
import * as path from "node:path";
|
|
28149
28242
|
|
|
28150
28243
|
// ../node_modules/zod/index.js
|
|
28151
28244
|
var zod_exports = {};
|
|
@@ -28494,34 +28587,139 @@ function applyPagination(items, params) {
|
|
|
28494
28587
|
};
|
|
28495
28588
|
}
|
|
28496
28589
|
|
|
28590
|
+
// src/channel.ts
|
|
28591
|
+
var MAX_CHANNEL_CONTENT_BYTES = 8192;
|
|
28592
|
+
var ChannelPermissionRequestNotificationSchema = external_exports3.object({
|
|
28593
|
+
method: external_exports3.literal("notifications/claude/channel/permission_request"),
|
|
28594
|
+
params: external_exports3.object({
|
|
28595
|
+
request_id: external_exports3.string().min(1),
|
|
28596
|
+
tool_name: external_exports3.string(),
|
|
28597
|
+
description: external_exports3.string(),
|
|
28598
|
+
input_preview: external_exports3.string().optional()
|
|
28599
|
+
})
|
|
28600
|
+
});
|
|
28601
|
+
function coerceMetaValue(value) {
|
|
28602
|
+
if (value === null || value === void 0) return "";
|
|
28603
|
+
if (typeof value === "string") return value;
|
|
28604
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
28605
|
+
return String(value);
|
|
28606
|
+
}
|
|
28607
|
+
return JSON.stringify(value);
|
|
28608
|
+
}
|
|
28609
|
+
function buildChannelMeta(meta3) {
|
|
28610
|
+
const result = {};
|
|
28611
|
+
for (const [key, value] of Object.entries(meta3)) {
|
|
28612
|
+
result[key] = coerceMetaValue(value);
|
|
28613
|
+
}
|
|
28614
|
+
return result;
|
|
28615
|
+
}
|
|
28616
|
+
function truncateChannelContent(content) {
|
|
28617
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
28618
|
+
if (bytes > MAX_CHANNEL_CONTENT_BYTES) {
|
|
28619
|
+
console.error(
|
|
28620
|
+
`[telegram:channel] dropping message (${bytes} bytes > ${MAX_CHANNEL_CONTENT_BYTES})`
|
|
28621
|
+
);
|
|
28622
|
+
return null;
|
|
28623
|
+
}
|
|
28624
|
+
return content;
|
|
28625
|
+
}
|
|
28626
|
+
async function sendChannelNotification(server2, content, meta3) {
|
|
28627
|
+
const safeContent = truncateChannelContent(content);
|
|
28628
|
+
if (!safeContent) return false;
|
|
28629
|
+
try {
|
|
28630
|
+
await server2.notification({
|
|
28631
|
+
method: "claude/channel",
|
|
28632
|
+
params: {
|
|
28633
|
+
content: safeContent,
|
|
28634
|
+
meta: meta3 ?? {}
|
|
28635
|
+
}
|
|
28636
|
+
});
|
|
28637
|
+
return true;
|
|
28638
|
+
} catch (error48) {
|
|
28639
|
+
console.error("[telegram:channel] failed to send notification:", error48);
|
|
28640
|
+
return false;
|
|
28641
|
+
}
|
|
28642
|
+
}
|
|
28643
|
+
async function sendPermissionVerdict(server2, requestId, behavior) {
|
|
28644
|
+
try {
|
|
28645
|
+
await server2.notification({
|
|
28646
|
+
method: "claude/channel/permission",
|
|
28647
|
+
params: {
|
|
28648
|
+
request_id: requestId,
|
|
28649
|
+
behavior
|
|
28650
|
+
}
|
|
28651
|
+
});
|
|
28652
|
+
return true;
|
|
28653
|
+
} catch (error48) {
|
|
28654
|
+
console.error(
|
|
28655
|
+
`[telegram:channel] failed to send permission verdict (${behavior}):`,
|
|
28656
|
+
error48
|
|
28657
|
+
);
|
|
28658
|
+
return false;
|
|
28659
|
+
}
|
|
28660
|
+
}
|
|
28661
|
+
function formatPermissionRequestMessage(params) {
|
|
28662
|
+
const preview = params.input_preview?.trim();
|
|
28663
|
+
const lines = [
|
|
28664
|
+
"Tool approval requested",
|
|
28665
|
+
"",
|
|
28666
|
+
`Tool: ${params.tool_name}`,
|
|
28667
|
+
params.description
|
|
28668
|
+
];
|
|
28669
|
+
if (preview) {
|
|
28670
|
+
lines.push("", "Preview:", preview);
|
|
28671
|
+
}
|
|
28672
|
+
return lines.join("\n");
|
|
28673
|
+
}
|
|
28674
|
+
function parsePermissionCallbackData(data) {
|
|
28675
|
+
const match = /^perm:(allow|deny):([a-f0-9]{32})$/.exec(data);
|
|
28676
|
+
if (!match) return null;
|
|
28677
|
+
return {
|
|
28678
|
+
behavior: match[1],
|
|
28679
|
+
requestId: match[2]
|
|
28680
|
+
};
|
|
28681
|
+
}
|
|
28682
|
+
function buildPermissionCallbackData(behavior, requestId) {
|
|
28683
|
+
return `perm:${behavior}:${requestId}`;
|
|
28684
|
+
}
|
|
28685
|
+
|
|
28497
28686
|
// src/index.ts
|
|
28498
28687
|
import_dotenv.default.config();
|
|
28688
|
+
function isCallbackQueryUpdate(update) {
|
|
28689
|
+
return typeof update === "object" && update !== null && "callback_query" in update && update.callback_query !== void 0;
|
|
28690
|
+
}
|
|
28691
|
+
function getStatePath() {
|
|
28692
|
+
if (process.env.TELEGRAM_STATE_PATH) {
|
|
28693
|
+
return process.env.TELEGRAM_STATE_PATH;
|
|
28694
|
+
}
|
|
28695
|
+
const suffix = botToken && !IS_MOCK ? createHash("sha256").update(botToken).digest("hex").slice(0, 12) : "default";
|
|
28696
|
+
return path.join(os.homedir(), ".fre4x", `telegram-mcp-${suffix}.json`);
|
|
28697
|
+
}
|
|
28499
28698
|
var window = new JSDOM("").window;
|
|
28500
28699
|
var DOMPurify = createDOMPurify(window);
|
|
28501
28700
|
var IS_MOCK = process.env.MOCK === "true" || process.env.TELEGRAM_MOCK === "true";
|
|
28502
28701
|
var botToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
28702
|
+
var MAX_TOKEN_REDACT_LENGTH = 256;
|
|
28703
|
+
function redactBotToken(message) {
|
|
28704
|
+
if (!botToken || !message.includes(botToken)) {
|
|
28705
|
+
return message;
|
|
28706
|
+
}
|
|
28707
|
+
if (botToken.length > MAX_TOKEN_REDACT_LENGTH) {
|
|
28708
|
+
return message.split(botToken).join("[REDACTED_TOKEN]");
|
|
28709
|
+
}
|
|
28710
|
+
const escapedToken = botToken.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28711
|
+
return message.replace(new RegExp(escapedToken, "g"), "[REDACTED_TOKEN]");
|
|
28712
|
+
}
|
|
28503
28713
|
var allowedUserId = process.env.ALLOWED_USER_ID;
|
|
28504
28714
|
var allowedRecipients = process.env.ALLOWED_RECIPIENTS?.split(",").map((id) => id.trim()) || [];
|
|
28505
28715
|
var enableRecipientWhitelist = process.env.ENABLE_RECIPIENT_WHITELIST === "true";
|
|
28506
28716
|
var originalError = console.error;
|
|
28507
28717
|
console.error = (...args) => {
|
|
28508
|
-
|
|
28509
|
-
if (botToken && message.includes(botToken)) {
|
|
28510
|
-
message = message.replace(
|
|
28511
|
-
new RegExp(botToken.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"),
|
|
28512
|
-
"[REDACTED_TOKEN]"
|
|
28513
|
-
);
|
|
28514
|
-
}
|
|
28718
|
+
const message = redactBotToken(args.map((arg) => String(arg)).join(" "));
|
|
28515
28719
|
originalError(message);
|
|
28516
28720
|
};
|
|
28517
28721
|
process.on("uncaughtException", (err) => {
|
|
28518
|
-
|
|
28519
|
-
if (botToken && message.includes(botToken)) {
|
|
28520
|
-
message = message.replace(
|
|
28521
|
-
new RegExp(botToken.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"),
|
|
28522
|
-
"[REDACTED_TOKEN]"
|
|
28523
|
-
);
|
|
28524
|
-
}
|
|
28722
|
+
const message = redactBotToken(err.stack || err.message);
|
|
28525
28723
|
console.error("Uncaught Exception:", message);
|
|
28526
28724
|
process.exit(1);
|
|
28527
28725
|
});
|
|
@@ -28530,13 +28728,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
28530
28728
|
if (reason instanceof Error) {
|
|
28531
28729
|
message = reason.stack || reason.message;
|
|
28532
28730
|
}
|
|
28533
|
-
|
|
28534
|
-
message = message.replace(
|
|
28535
|
-
new RegExp(botToken.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"),
|
|
28536
|
-
"[REDACTED_TOKEN]"
|
|
28537
|
-
);
|
|
28538
|
-
}
|
|
28539
|
-
console.error("Unhandled Rejection:", message);
|
|
28731
|
+
console.error("Unhandled Rejection:", redactBotToken(message));
|
|
28540
28732
|
});
|
|
28541
28733
|
if (!IS_MOCK && !botToken) {
|
|
28542
28734
|
console.error(
|
|
@@ -28569,13 +28761,7 @@ function validateFilePath(filePath) {
|
|
|
28569
28761
|
}
|
|
28570
28762
|
return absolutePath;
|
|
28571
28763
|
}
|
|
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
|
-
}
|
|
28764
|
+
function assertRecipientAllowed(targetId) {
|
|
28579
28765
|
if (enableRecipientWhitelist && allowedRecipients.length > 0) {
|
|
28580
28766
|
if (!allowedRecipients.includes(targetId)) {
|
|
28581
28767
|
throw new Error(
|
|
@@ -28584,6 +28770,18 @@ function validateRecipient(targetId) {
|
|
|
28584
28770
|
}
|
|
28585
28771
|
}
|
|
28586
28772
|
}
|
|
28773
|
+
function validateSendRecipient(targetId) {
|
|
28774
|
+
if (!/^\d+$/.test(targetId)) {
|
|
28775
|
+
throw new Error(`Invalid Telegram user ID format: ${targetId}`);
|
|
28776
|
+
}
|
|
28777
|
+
assertRecipientAllowed(targetId);
|
|
28778
|
+
}
|
|
28779
|
+
function validateChatId(chatId) {
|
|
28780
|
+
if (!/^-?\d+$/.test(chatId)) {
|
|
28781
|
+
throw new Error(`Invalid Telegram chat ID format: ${chatId}`);
|
|
28782
|
+
}
|
|
28783
|
+
assertRecipientAllowed(chatId);
|
|
28784
|
+
}
|
|
28587
28785
|
var SendMessageSchema = external_exports3.object({
|
|
28588
28786
|
content: external_exports3.string().min(1).max(4096),
|
|
28589
28787
|
recipientId: external_exports3.string().optional(),
|
|
@@ -28624,34 +28822,441 @@ var server = new Server(
|
|
|
28624
28822
|
capabilities: {
|
|
28625
28823
|
tools: {},
|
|
28626
28824
|
logging: {},
|
|
28627
|
-
// Required by SDK for notifications/message
|
|
28628
28825
|
experimental: {
|
|
28629
28826
|
"claude/channel": {
|
|
28630
|
-
description: "Telegram events are delivered via channel notifications"
|
|
28827
|
+
description: "Telegram events are delivered via claude/channel notifications"
|
|
28828
|
+
},
|
|
28829
|
+
"claude/channel/permission": {
|
|
28830
|
+
description: "Tool approval requests are relayed to Telegram for allow/deny"
|
|
28631
28831
|
}
|
|
28632
28832
|
}
|
|
28633
28833
|
},
|
|
28634
|
-
instructions: "Telegram MCP server.
|
|
28834
|
+
instructions: "Telegram MCP server. Inbound Telegram messages are pushed via claude/channel. Tool approvals can be relayed through Telegram when claude/channel/permission is enabled."
|
|
28635
28835
|
}
|
|
28636
28836
|
);
|
|
28637
|
-
var isInitialized = false;
|
|
28638
|
-
server.oninitialized = () => {
|
|
28639
|
-
isInitialized = true;
|
|
28640
|
-
};
|
|
28641
|
-
var seenMessageIds = /* @__PURE__ */ new Set();
|
|
28642
|
-
var MAX_SEEN_IDS = 500;
|
|
28643
28837
|
var isImage = (filePath) => {
|
|
28644
28838
|
const ext = path.extname(filePath).toLowerCase();
|
|
28645
28839
|
return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext);
|
|
28646
28840
|
};
|
|
28647
28841
|
var messageHistory = [];
|
|
28648
28842
|
var MAX_HISTORY = 100;
|
|
28843
|
+
var pollingOffset = 0;
|
|
28844
|
+
var channelReady = false;
|
|
28845
|
+
var channelPollTimer = null;
|
|
28846
|
+
var CHANNEL_POLL_INTERVAL_MS = 3e3;
|
|
28847
|
+
var pendingPermissionRequests = /* @__PURE__ */ new Map();
|
|
28848
|
+
var knownChats = /* @__PURE__ */ new Map();
|
|
28849
|
+
function loadState() {
|
|
28850
|
+
if (IS_MOCK) return;
|
|
28851
|
+
try {
|
|
28852
|
+
if (!fs.existsSync(getStatePath())) return;
|
|
28853
|
+
const raw = fs.readFileSync(getStatePath(), "utf-8");
|
|
28854
|
+
const state = JSON.parse(raw);
|
|
28855
|
+
for (const chat of state.knownChats ?? []) {
|
|
28856
|
+
if (chat?.id) {
|
|
28857
|
+
knownChats.set(chat.id, chat);
|
|
28858
|
+
}
|
|
28859
|
+
}
|
|
28860
|
+
if (typeof state.pollingOffset === "number") {
|
|
28861
|
+
pollingOffset = state.pollingOffset;
|
|
28862
|
+
}
|
|
28863
|
+
console.error(
|
|
28864
|
+
`[telegram:state] loaded ${knownChats.size} chat(s) from ${getStatePath()}`
|
|
28865
|
+
);
|
|
28866
|
+
} catch (error48) {
|
|
28867
|
+
console.error(
|
|
28868
|
+
`[telegram:state] failed to load ${getStatePath()}:`,
|
|
28869
|
+
error48
|
|
28870
|
+
);
|
|
28871
|
+
}
|
|
28872
|
+
}
|
|
28873
|
+
function persistState() {
|
|
28874
|
+
if (IS_MOCK) return;
|
|
28875
|
+
try {
|
|
28876
|
+
const state = {
|
|
28877
|
+
knownChats: Array.from(knownChats.values()),
|
|
28878
|
+
pollingOffset
|
|
28879
|
+
};
|
|
28880
|
+
fs.mkdirSync(path.dirname(getStatePath()), { recursive: true });
|
|
28881
|
+
fs.writeFileSync(
|
|
28882
|
+
getStatePath(),
|
|
28883
|
+
JSON.stringify(state, null, 2),
|
|
28884
|
+
"utf-8"
|
|
28885
|
+
);
|
|
28886
|
+
} catch (error48) {
|
|
28887
|
+
console.error(
|
|
28888
|
+
`[telegram:state] failed to persist ${getStatePath()}:`,
|
|
28889
|
+
error48
|
|
28890
|
+
);
|
|
28891
|
+
}
|
|
28892
|
+
}
|
|
28893
|
+
function reloadState() {
|
|
28894
|
+
if (IS_MOCK) return;
|
|
28895
|
+
try {
|
|
28896
|
+
if (!fs.existsSync(getStatePath())) return;
|
|
28897
|
+
const raw = fs.readFileSync(getStatePath(), "utf-8");
|
|
28898
|
+
const state = JSON.parse(raw);
|
|
28899
|
+
for (const chat of state.knownChats ?? []) {
|
|
28900
|
+
if (chat?.id) {
|
|
28901
|
+
knownChats.set(chat.id, chat);
|
|
28902
|
+
}
|
|
28903
|
+
}
|
|
28904
|
+
if (typeof state.pollingOffset === "number" && state.pollingOffset > pollingOffset) {
|
|
28905
|
+
pollingOffset = state.pollingOffset;
|
|
28906
|
+
}
|
|
28907
|
+
} catch (error48) {
|
|
28908
|
+
console.error(
|
|
28909
|
+
`[telegram:state] failed to reload ${getStatePath()}:`,
|
|
28910
|
+
error48
|
|
28911
|
+
);
|
|
28912
|
+
}
|
|
28913
|
+
}
|
|
28914
|
+
function chatTitleFromTelegramChat(chat) {
|
|
28915
|
+
return chat.title || chat.username || chat.first_name || "Private Chat";
|
|
28916
|
+
}
|
|
28917
|
+
function recordKnownChat(chat) {
|
|
28918
|
+
knownChats.set(chat.id, chat);
|
|
28919
|
+
persistState();
|
|
28920
|
+
}
|
|
28921
|
+
function recordKnownChatFromTelegramMessage(message) {
|
|
28922
|
+
recordKnownChat({
|
|
28923
|
+
id: message.chat.id.toString(),
|
|
28924
|
+
title: chatTitleFromTelegramChat(message.chat),
|
|
28925
|
+
type: message.chat.type ?? "unknown"
|
|
28926
|
+
});
|
|
28927
|
+
}
|
|
28928
|
+
function recordKnownChatFromUpdate(update) {
|
|
28929
|
+
const chat = update.message?.chat ?? update.edited_message?.chat ?? update.channel_post?.chat ?? update.my_chat_member?.chat;
|
|
28930
|
+
if (!chat) return;
|
|
28931
|
+
recordKnownChat({
|
|
28932
|
+
id: chat.id.toString(),
|
|
28933
|
+
title: chatTitleFromTelegramChat(chat),
|
|
28934
|
+
type: chat.type ?? "unknown"
|
|
28935
|
+
});
|
|
28936
|
+
}
|
|
28937
|
+
function logActiveChatsDebug(stage) {
|
|
28938
|
+
const active = getActiveChats();
|
|
28939
|
+
console.error(
|
|
28940
|
+
`[telegram:list_active_chats] ${stage}: knownChats=${knownChats.size} messageHistory=${messageHistory.length} active=${active.length} pollingOffset=${pollingOffset}`
|
|
28941
|
+
);
|
|
28942
|
+
if (knownChats.size > 0) {
|
|
28943
|
+
console.error(
|
|
28944
|
+
`[telegram:list_active_chats] knownChatIds: ${Array.from(knownChats.keys()).join(", ")}`
|
|
28945
|
+
);
|
|
28946
|
+
}
|
|
28947
|
+
if (active.length > 0) {
|
|
28948
|
+
console.error(
|
|
28949
|
+
`[telegram:list_active_chats] activeChatIds: ${active.map((c) => c.id).join(", ")}`
|
|
28950
|
+
);
|
|
28951
|
+
}
|
|
28952
|
+
}
|
|
28953
|
+
async function recordChatInteraction(chatId) {
|
|
28954
|
+
if (IS_MOCK || !bot) {
|
|
28955
|
+
recordKnownChat({
|
|
28956
|
+
id: chatId,
|
|
28957
|
+
title: `Chat ${chatId}`,
|
|
28958
|
+
type: "unknown"
|
|
28959
|
+
});
|
|
28960
|
+
console.error(
|
|
28961
|
+
`[telegram:recordChatInteraction] mock recorded chatId=${chatId} knownChats=${knownChats.size}`
|
|
28962
|
+
);
|
|
28963
|
+
return;
|
|
28964
|
+
}
|
|
28965
|
+
try {
|
|
28966
|
+
const chat = await bot.telegram.getChat(chatId);
|
|
28967
|
+
recordKnownChat({
|
|
28968
|
+
id: chat.id.toString(),
|
|
28969
|
+
title: chatTitleFromTelegramChat(chat),
|
|
28970
|
+
type: chat.type
|
|
28971
|
+
});
|
|
28972
|
+
console.error(
|
|
28973
|
+
`[telegram:recordChatInteraction] getChat ok chatId=${chat.id} knownChats=${knownChats.size}`
|
|
28974
|
+
);
|
|
28975
|
+
} catch (error48) {
|
|
28976
|
+
recordKnownChat({
|
|
28977
|
+
id: chatId,
|
|
28978
|
+
title: chatId,
|
|
28979
|
+
type: "unknown"
|
|
28980
|
+
});
|
|
28981
|
+
console.error(
|
|
28982
|
+
`[telegram:recordChatInteraction] getChat failed for ${chatId}, stored fallback:`,
|
|
28983
|
+
error48
|
|
28984
|
+
);
|
|
28985
|
+
}
|
|
28986
|
+
}
|
|
28987
|
+
function getConfiguredChatCandidates() {
|
|
28988
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
28989
|
+
if (allowedUserId) candidates.add(allowedUserId);
|
|
28990
|
+
for (const id of allowedRecipients) {
|
|
28991
|
+
if (id) candidates.add(id);
|
|
28992
|
+
}
|
|
28993
|
+
for (const chat of knownChats.values()) {
|
|
28994
|
+
candidates.add(chat.id);
|
|
28995
|
+
}
|
|
28996
|
+
for (const msg of messageHistory) {
|
|
28997
|
+
candidates.add(msg.chatId);
|
|
28998
|
+
}
|
|
28999
|
+
return candidates;
|
|
29000
|
+
}
|
|
29001
|
+
async function ensurePollingMode() {
|
|
29002
|
+
if (!bot || IS_MOCK) return;
|
|
29003
|
+
try {
|
|
29004
|
+
const info = await bot.telegram.getWebhookInfo();
|
|
29005
|
+
if (info.url) {
|
|
29006
|
+
console.error(
|
|
29007
|
+
`[telegram] active webhook detected (${info.url}); switching to getUpdates polling`
|
|
29008
|
+
);
|
|
29009
|
+
await bot.telegram.deleteWebhook({ drop_pending_updates: false });
|
|
29010
|
+
}
|
|
29011
|
+
} catch (error48) {
|
|
29012
|
+
console.error("[telegram] ensurePollingMode failed:", error48);
|
|
29013
|
+
}
|
|
29014
|
+
}
|
|
29015
|
+
async function discoverChatsFromApi() {
|
|
29016
|
+
if (IS_MOCK || !bot) return;
|
|
29017
|
+
const candidates = getConfiguredChatCandidates();
|
|
29018
|
+
console.error(
|
|
29019
|
+
`[telegram:list_active_chats] discoverChatsFromApi: probing ${candidates.size} candidate chat(s) from state=${getStatePath()}`
|
|
29020
|
+
);
|
|
29021
|
+
for (const chatId of candidates) {
|
|
29022
|
+
await recordChatInteraction(chatId);
|
|
29023
|
+
}
|
|
29024
|
+
try {
|
|
29025
|
+
await ensurePollingMode();
|
|
29026
|
+
const updates = await bot.telegram.getUpdates(0, 100, pollingOffset, [
|
|
29027
|
+
"message",
|
|
29028
|
+
"edited_message",
|
|
29029
|
+
"channel_post",
|
|
29030
|
+
"my_chat_member",
|
|
29031
|
+
"chat_member"
|
|
29032
|
+
]);
|
|
29033
|
+
for (const update of updates) {
|
|
29034
|
+
pollingOffset = update.update_id + 1;
|
|
29035
|
+
const msgLike = update;
|
|
29036
|
+
if (!msgLike.message && !msgLike.edited_message && !msgLike.channel_post && !msgLike.my_chat_member)
|
|
29037
|
+
continue;
|
|
29038
|
+
recordKnownChatFromUpdate(msgLike);
|
|
29039
|
+
}
|
|
29040
|
+
console.error(
|
|
29041
|
+
`[telegram:list_active_chats] discoverChatsFromApi: processed ${updates.length} update(s)`
|
|
29042
|
+
);
|
|
29043
|
+
persistState();
|
|
29044
|
+
} catch (error48) {
|
|
29045
|
+
console.error(
|
|
29046
|
+
"[telegram:list_active_chats] discoverChatsFromApi getUpdates failed:",
|
|
29047
|
+
error48
|
|
29048
|
+
);
|
|
29049
|
+
}
|
|
29050
|
+
}
|
|
29051
|
+
function getActiveChats() {
|
|
29052
|
+
const chats = /* @__PURE__ */ new Map();
|
|
29053
|
+
for (const chat of knownChats.values()) {
|
|
29054
|
+
chats.set(chat.id, chat);
|
|
29055
|
+
}
|
|
29056
|
+
for (const msg of messageHistory) {
|
|
29057
|
+
if (chats.has(msg.chatId)) continue;
|
|
29058
|
+
chats.set(msg.chatId, {
|
|
29059
|
+
id: msg.chatId,
|
|
29060
|
+
title: msg.chatTitle || "Unknown",
|
|
29061
|
+
type: msg.metadata?.chatType || "unknown"
|
|
29062
|
+
});
|
|
29063
|
+
}
|
|
29064
|
+
return Array.from(chats.values()).sort(
|
|
29065
|
+
(a, b) => a.title.localeCompare(b.title)
|
|
29066
|
+
);
|
|
29067
|
+
}
|
|
29068
|
+
async function fetchNewUpdates() {
|
|
29069
|
+
const added = [];
|
|
29070
|
+
if (!bot || IS_MOCK) return added;
|
|
29071
|
+
try {
|
|
29072
|
+
await ensurePollingMode();
|
|
29073
|
+
const updates = await bot.telegram.getUpdates(0, 100, pollingOffset, [
|
|
29074
|
+
"message",
|
|
29075
|
+
"edited_message",
|
|
29076
|
+
"channel_post",
|
|
29077
|
+
"my_chat_member",
|
|
29078
|
+
"callback_query"
|
|
29079
|
+
]);
|
|
29080
|
+
for (const update of updates) {
|
|
29081
|
+
pollingOffset = update.update_id + 1;
|
|
29082
|
+
if (isCallbackQueryUpdate(update)) {
|
|
29083
|
+
await handlePermissionCallback(update.callback_query);
|
|
29084
|
+
continue;
|
|
29085
|
+
}
|
|
29086
|
+
const msgLike = update;
|
|
29087
|
+
if (!msgLike.message && !msgLike.edited_message && !msgLike.channel_post && !msgLike.my_chat_member)
|
|
29088
|
+
continue;
|
|
29089
|
+
recordKnownChatFromUpdate(msgLike);
|
|
29090
|
+
const msg = msgLike.message;
|
|
29091
|
+
if (!msg?.text) continue;
|
|
29092
|
+
const senderId = msg.from?.id?.toString() ?? "unknown";
|
|
29093
|
+
const chatId = msg.chat.id.toString();
|
|
29094
|
+
const chatTitle = chatTitleFromTelegramChat(msg.chat);
|
|
29095
|
+
if (messageHistory.some(
|
|
29096
|
+
(m) => m.metadata?.message_id === msg.message_id
|
|
29097
|
+
))
|
|
29098
|
+
continue;
|
|
29099
|
+
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
29100
|
+
if (currentAllowedUserId && msg.chat.type === "private") {
|
|
29101
|
+
if (senderId !== currentAllowedUserId) continue;
|
|
29102
|
+
}
|
|
29103
|
+
let content = DOMPurify.sanitize(msg.text, {
|
|
29104
|
+
ALLOWED_TAGS: [],
|
|
29105
|
+
ALLOWED_ATTR: []
|
|
29106
|
+
});
|
|
29107
|
+
content = content.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/[*_~]/g, "\\$&");
|
|
29108
|
+
const entry = {
|
|
29109
|
+
senderId,
|
|
29110
|
+
chatId,
|
|
29111
|
+
chatTitle,
|
|
29112
|
+
content,
|
|
29113
|
+
timestamp: new Date(msg.date * 1e3).toISOString(),
|
|
29114
|
+
metadata: {
|
|
29115
|
+
chatId,
|
|
29116
|
+
chatType: msg.chat.type,
|
|
29117
|
+
message_id: msg.message_id
|
|
29118
|
+
}
|
|
29119
|
+
};
|
|
29120
|
+
messageHistory.push(entry);
|
|
29121
|
+
added.push(entry);
|
|
29122
|
+
if (messageHistory.length > MAX_HISTORY) {
|
|
29123
|
+
messageHistory.shift();
|
|
29124
|
+
}
|
|
29125
|
+
if (channelReady) {
|
|
29126
|
+
await pushMessageToChannel(entry);
|
|
29127
|
+
}
|
|
29128
|
+
}
|
|
29129
|
+
persistState();
|
|
29130
|
+
} catch (error48) {
|
|
29131
|
+
console.error("fetchNewUpdates failed:", error48);
|
|
29132
|
+
}
|
|
29133
|
+
return added;
|
|
29134
|
+
}
|
|
29135
|
+
async function pushMessageToChannel(entry) {
|
|
29136
|
+
const meta3 = buildChannelMeta({
|
|
29137
|
+
chat_id: entry.chatId,
|
|
29138
|
+
sender_id: entry.senderId,
|
|
29139
|
+
sender_name: entry.chatTitle ?? entry.senderId,
|
|
29140
|
+
message_id: entry.metadata?.message_id,
|
|
29141
|
+
timestamp: entry.timestamp
|
|
29142
|
+
});
|
|
29143
|
+
const sent = await sendChannelNotification(server, entry.content, meta3);
|
|
29144
|
+
if (sent) {
|
|
29145
|
+
console.error(
|
|
29146
|
+
`[telegram:channel] pushed message_id=${entry.metadata?.message_id ?? "unknown"} chat_id=${entry.chatId}`
|
|
29147
|
+
);
|
|
29148
|
+
}
|
|
29149
|
+
}
|
|
29150
|
+
async function relayPermissionRequestToTelegram(params) {
|
|
29151
|
+
pendingPermissionRequests.set(params.request_id, params);
|
|
29152
|
+
if (IS_MOCK || !bot) {
|
|
29153
|
+
console.error(
|
|
29154
|
+
`[telegram:channel] permission request (mock): ${params.tool_name} (${params.request_id})`
|
|
29155
|
+
);
|
|
29156
|
+
return;
|
|
29157
|
+
}
|
|
29158
|
+
const targetId = allowedUserId;
|
|
29159
|
+
if (!targetId) {
|
|
29160
|
+
console.error(
|
|
29161
|
+
"[telegram:channel] cannot relay permission \u2014 ALLOWED_USER_ID not set"
|
|
29162
|
+
);
|
|
29163
|
+
return;
|
|
29164
|
+
}
|
|
29165
|
+
try {
|
|
29166
|
+
await bot.telegram.sendMessage(
|
|
29167
|
+
targetId,
|
|
29168
|
+
formatPermissionRequestMessage(params),
|
|
29169
|
+
{
|
|
29170
|
+
reply_markup: {
|
|
29171
|
+
inline_keyboard: [
|
|
29172
|
+
[
|
|
29173
|
+
{
|
|
29174
|
+
text: "Allow",
|
|
29175
|
+
callback_data: buildPermissionCallbackData(
|
|
29176
|
+
"allow",
|
|
29177
|
+
params.request_id
|
|
29178
|
+
)
|
|
29179
|
+
},
|
|
29180
|
+
{
|
|
29181
|
+
text: "Deny",
|
|
29182
|
+
callback_data: buildPermissionCallbackData(
|
|
29183
|
+
"deny",
|
|
29184
|
+
params.request_id
|
|
29185
|
+
)
|
|
29186
|
+
}
|
|
29187
|
+
]
|
|
29188
|
+
]
|
|
29189
|
+
}
|
|
29190
|
+
}
|
|
29191
|
+
);
|
|
29192
|
+
} catch (error48) {
|
|
29193
|
+
console.error(
|
|
29194
|
+
"[telegram:channel] failed to relay permission request:",
|
|
29195
|
+
error48
|
|
29196
|
+
);
|
|
29197
|
+
}
|
|
29198
|
+
}
|
|
29199
|
+
async function handlePermissionCallback(callbackQuery) {
|
|
29200
|
+
if (!callbackQuery.data) return;
|
|
29201
|
+
const parsed = parsePermissionCallbackData(callbackQuery.data);
|
|
29202
|
+
if (!parsed) return;
|
|
29203
|
+
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
29204
|
+
if (currentAllowedUserId && callbackQuery.from) {
|
|
29205
|
+
if (callbackQuery.from.id.toString() !== currentAllowedUserId) {
|
|
29206
|
+
console.error(
|
|
29207
|
+
`[telegram:channel] permission callback rejected from ${callbackQuery.from.id}`
|
|
29208
|
+
);
|
|
29209
|
+
return;
|
|
29210
|
+
}
|
|
29211
|
+
}
|
|
29212
|
+
if (!pendingPermissionRequests.has(parsed.requestId)) {
|
|
29213
|
+
console.error(
|
|
29214
|
+
`[telegram:channel] unknown permission request_id=${parsed.requestId}`
|
|
29215
|
+
);
|
|
29216
|
+
return;
|
|
29217
|
+
}
|
|
29218
|
+
pendingPermissionRequests.delete(parsed.requestId);
|
|
29219
|
+
const sent = await sendPermissionVerdict(
|
|
29220
|
+
server,
|
|
29221
|
+
parsed.requestId,
|
|
29222
|
+
parsed.behavior
|
|
29223
|
+
);
|
|
29224
|
+
if (sent) {
|
|
29225
|
+
console.error(
|
|
29226
|
+
`[telegram:channel] permission verdict ${parsed.behavior} for ${parsed.requestId}`
|
|
29227
|
+
);
|
|
29228
|
+
}
|
|
29229
|
+
if (bot && !IS_MOCK) {
|
|
29230
|
+
try {
|
|
29231
|
+
await bot.telegram.answerCbQuery(
|
|
29232
|
+
callbackQuery.id,
|
|
29233
|
+
parsed.behavior === "allow" ? "Approved" : "Denied"
|
|
29234
|
+
);
|
|
29235
|
+
} catch (error48) {
|
|
29236
|
+
console.error("[telegram:channel] answerCbQuery failed:", error48);
|
|
29237
|
+
}
|
|
29238
|
+
}
|
|
29239
|
+
}
|
|
29240
|
+
function startChannelPolling() {
|
|
29241
|
+
if (channelPollTimer || IS_MOCK || !bot) return;
|
|
29242
|
+
channelPollTimer = setInterval(() => {
|
|
29243
|
+
void fetchNewUpdates();
|
|
29244
|
+
}, CHANNEL_POLL_INTERVAL_MS);
|
|
29245
|
+
console.error(
|
|
29246
|
+
`[telegram:channel] background polling started (${CHANNEL_POLL_INTERVAL_MS}ms)`
|
|
29247
|
+
);
|
|
29248
|
+
}
|
|
29249
|
+
function stopChannelPolling() {
|
|
29250
|
+
if (!channelPollTimer) return;
|
|
29251
|
+
clearInterval(channelPollTimer);
|
|
29252
|
+
channelPollTimer = null;
|
|
29253
|
+
}
|
|
28649
29254
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
28650
29255
|
return {
|
|
28651
29256
|
tools: [
|
|
28652
29257
|
{
|
|
28653
|
-
name: "
|
|
28654
|
-
description: "Send
|
|
29258
|
+
name: "send_message",
|
|
29259
|
+
description: "Send text, photo, or document to a Telegram user by numeric ID.",
|
|
28655
29260
|
inputSchema: {
|
|
28656
29261
|
type: "object",
|
|
28657
29262
|
properties: {
|
|
@@ -28661,7 +29266,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28661
29266
|
},
|
|
28662
29267
|
recipientId: {
|
|
28663
29268
|
type: "string",
|
|
28664
|
-
description: "
|
|
29269
|
+
description: "Numeric Telegram user ID."
|
|
28665
29270
|
},
|
|
28666
29271
|
mediaPath: {
|
|
28667
29272
|
type: "string",
|
|
@@ -28689,7 +29294,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28689
29294
|
},
|
|
28690
29295
|
{
|
|
28691
29296
|
name: "list_active_chats",
|
|
28692
|
-
description: "List
|
|
29297
|
+
description: "List chats the bot has sent to or received messages from.",
|
|
28693
29298
|
inputSchema: {
|
|
28694
29299
|
type: "object",
|
|
28695
29300
|
properties: {
|
|
@@ -28703,16 +29308,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
28703
29308
|
description: "Get detailed information about a specific Telegram chat.",
|
|
28704
29309
|
inputSchema: {
|
|
28705
29310
|
type: "object",
|
|
28706
|
-
properties: {
|
|
29311
|
+
properties: {
|
|
29312
|
+
chatId: {
|
|
29313
|
+
type: "string",
|
|
29314
|
+
description: "Numeric Telegram chat ID."
|
|
29315
|
+
}
|
|
29316
|
+
},
|
|
28707
29317
|
required: ["chatId"]
|
|
28708
29318
|
}
|
|
28709
29319
|
},
|
|
28710
29320
|
{
|
|
28711
|
-
name: "
|
|
29321
|
+
name: "leave_chat",
|
|
28712
29322
|
description: "Request the bot to leave a specific group or channel.",
|
|
28713
29323
|
inputSchema: {
|
|
28714
29324
|
type: "object",
|
|
28715
|
-
properties: {
|
|
29325
|
+
properties: {
|
|
29326
|
+
chatId: {
|
|
29327
|
+
type: "string",
|
|
29328
|
+
description: "Numeric Telegram chat ID."
|
|
29329
|
+
}
|
|
29330
|
+
},
|
|
28716
29331
|
required: ["chatId"]
|
|
28717
29332
|
}
|
|
28718
29333
|
}
|
|
@@ -28723,7 +29338,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28723
29338
|
const { name, arguments: args } = request.params;
|
|
28724
29339
|
const currentAllowedUserId = process.env.ALLOWED_USER_ID;
|
|
28725
29340
|
try {
|
|
28726
|
-
if (name === "
|
|
29341
|
+
if (name === "send_message") {
|
|
29342
|
+
reloadState();
|
|
28727
29343
|
const { content, recipientId, mediaPath, mediaType } = SendMessageSchema.parse(args);
|
|
28728
29344
|
const targetId = recipientId || currentAllowedUserId;
|
|
28729
29345
|
if (!targetId) {
|
|
@@ -28738,7 +29354,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28738
29354
|
429
|
|
28739
29355
|
);
|
|
28740
29356
|
}
|
|
28741
|
-
|
|
29357
|
+
validateSendRecipient(targetId);
|
|
28742
29358
|
let safePath;
|
|
28743
29359
|
if (mediaPath) {
|
|
28744
29360
|
safePath = validateFilePath(mediaPath);
|
|
@@ -28748,19 +29364,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28748
29364
|
if (mediaPath)
|
|
28749
29365
|
mockResponse += `
|
|
28750
29366
|
[Mock] Media: ${mediaPath} (${mediaType || "auto"})`;
|
|
29367
|
+
await recordChatInteraction(targetId);
|
|
28751
29368
|
return { content: [{ type: "text", text: mockResponse }] };
|
|
28752
29369
|
}
|
|
28753
29370
|
if (mediaPath && safePath) {
|
|
28754
29371
|
const source = { source: safePath };
|
|
28755
29372
|
const isPhoto = mediaType === "photo" || !mediaType && isImage(safePath);
|
|
28756
29373
|
if (isPhoto) {
|
|
28757
|
-
await bot.telegram.sendPhoto(
|
|
28758
|
-
|
|
28759
|
-
|
|
29374
|
+
const sent = await bot.telegram.sendPhoto(
|
|
29375
|
+
targetId,
|
|
29376
|
+
source,
|
|
29377
|
+
{
|
|
29378
|
+
caption: content
|
|
29379
|
+
}
|
|
29380
|
+
);
|
|
29381
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28760
29382
|
} else {
|
|
28761
|
-
await bot.telegram.sendDocument(
|
|
28762
|
-
|
|
28763
|
-
|
|
29383
|
+
const sent = await bot.telegram.sendDocument(
|
|
29384
|
+
targetId,
|
|
29385
|
+
source,
|
|
29386
|
+
{ caption: content }
|
|
29387
|
+
);
|
|
29388
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28764
29389
|
}
|
|
28765
29390
|
return {
|
|
28766
29391
|
content: [
|
|
@@ -28771,7 +29396,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28771
29396
|
]
|
|
28772
29397
|
};
|
|
28773
29398
|
} else {
|
|
28774
|
-
await bot.telegram.sendMessage(targetId, content);
|
|
29399
|
+
const sent = await bot.telegram.sendMessage(targetId, content);
|
|
29400
|
+
recordKnownChatFromTelegramMessage(sent);
|
|
28775
29401
|
return {
|
|
28776
29402
|
content: [
|
|
28777
29403
|
{ type: "text", text: `Message sent successfully.` }
|
|
@@ -28781,6 +29407,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
28781
29407
|
}
|
|
28782
29408
|
if (name === "get_recent_messages") {
|
|
28783
29409
|
const { limit, offset } = GetRecentMessagesSchema.parse(args || {});
|
|
29410
|
+
await fetchNewUpdates();
|
|
28784
29411
|
const paginated = applyPagination(
|
|
28785
29412
|
messageHistory.slice().reverse(),
|
|
28786
29413
|
{ limit, offset }
|
|
@@ -28809,18 +29436,12 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
|
|
|
28809
29436
|
}
|
|
28810
29437
|
if (name === "list_active_chats") {
|
|
28811
29438
|
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 });
|
|
29439
|
+
reloadState();
|
|
29440
|
+
await fetchNewUpdates();
|
|
29441
|
+
await discoverChatsFromApi();
|
|
29442
|
+
logActiveChatsDebug("after fetch and discover");
|
|
29443
|
+
const activeChats = getActiveChats();
|
|
29444
|
+
const paginated = applyPagination(activeChats, { limit, offset });
|
|
28824
29445
|
const formatted = paginated.items.map((c) => `- ${c.title} (ID: ${c.id}, Type: ${c.type})`).join("\n");
|
|
28825
29446
|
return {
|
|
28826
29447
|
content: [
|
|
@@ -28829,14 +29450,14 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
|
|
|
28829
29450
|
text: formatted ? `Active chats:
|
|
28830
29451
|
${formatted}
|
|
28831
29452
|
|
|
28832
|
-
${formatPaginationFooter(offset, limit,
|
|
29453
|
+
${formatPaginationFooter(offset, limit, activeChats.length)}` : "No active chats found."
|
|
28833
29454
|
}
|
|
28834
29455
|
]
|
|
28835
29456
|
};
|
|
28836
29457
|
}
|
|
28837
29458
|
if (name === "get_chat_info") {
|
|
28838
29459
|
const { chatId } = GetChatInfoSchema.parse(args);
|
|
28839
|
-
|
|
29460
|
+
validateChatId(chatId);
|
|
28840
29461
|
if (IS_MOCK || !bot) {
|
|
28841
29462
|
return {
|
|
28842
29463
|
content: [
|
|
@@ -28862,9 +29483,9 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
|
|
|
28862
29483
|
]
|
|
28863
29484
|
};
|
|
28864
29485
|
}
|
|
28865
|
-
if (name === "
|
|
29486
|
+
if (name === "leave_chat") {
|
|
28866
29487
|
const { chatId } = LeaveChatSchema.parse(args);
|
|
28867
|
-
|
|
29488
|
+
validateChatId(chatId);
|
|
28868
29489
|
if (IS_MOCK || !bot) {
|
|
28869
29490
|
return {
|
|
28870
29491
|
content: [
|
|
@@ -28906,128 +29527,49 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
|
|
|
28906
29527
|
if (msg.includes("limit exceeded")) {
|
|
28907
29528
|
return createApiError("Rate limit exceeded.", 429);
|
|
28908
29529
|
}
|
|
29530
|
+
if (msg.includes("invalid telegram user id format")) {
|
|
29531
|
+
return createValidationError(
|
|
29532
|
+
"recipientId",
|
|
29533
|
+
'Use a numeric user ID only (e.g., "123456789"). @username is not supported.'
|
|
29534
|
+
);
|
|
29535
|
+
}
|
|
29536
|
+
if (msg.includes("invalid telegram chat id format")) {
|
|
29537
|
+
return createValidationError(
|
|
29538
|
+
"chatId",
|
|
29539
|
+
'Use a numeric chat ID only (e.g., "123456789"). @username is not supported.'
|
|
29540
|
+
);
|
|
29541
|
+
}
|
|
28909
29542
|
}
|
|
28910
29543
|
return createInternalError(
|
|
28911
29544
|
"An unexpected error occurred while processing the request."
|
|
28912
29545
|
);
|
|
28913
29546
|
}
|
|
28914
29547
|
});
|
|
28915
|
-
|
|
28916
|
-
|
|
28917
|
-
|
|
28918
|
-
|
|
28919
|
-
|
|
28920
|
-
|
|
28921
|
-
|
|
28922
|
-
|
|
28923
|
-
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
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
|
-
}
|
|
29548
|
+
server.setNotificationHandler(
|
|
29549
|
+
ChannelPermissionRequestNotificationSchema,
|
|
29550
|
+
async (notification) => {
|
|
29551
|
+
await relayPermissionRequestToTelegram(notification.params);
|
|
29552
|
+
}
|
|
29553
|
+
);
|
|
29554
|
+
server.oninitialized = () => {
|
|
29555
|
+
channelReady = true;
|
|
29556
|
+
console.error(
|
|
29557
|
+
"[telegram:channel] client initialized \u2014 channel push enabled"
|
|
29558
|
+
);
|
|
29559
|
+
startChannelPolling();
|
|
29560
|
+
};
|
|
29013
29561
|
async function run() {
|
|
29562
|
+
loadState();
|
|
29014
29563
|
const transport = new StdioServerTransport();
|
|
29015
29564
|
await server.connect(transport);
|
|
29016
29565
|
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
29566
|
}
|
|
29567
|
+
process.on("SIGINT", () => {
|
|
29568
|
+
stopChannelPolling();
|
|
29569
|
+
});
|
|
29570
|
+
process.on("SIGTERM", () => {
|
|
29571
|
+
stopChannelPolling();
|
|
29572
|
+
});
|
|
29031
29573
|
if (process.env.NODE_ENV !== "test") {
|
|
29032
29574
|
run().catch((error48) => {
|
|
29033
29575
|
console.error("Fatal error in run():", error48);
|
package/package.json
CHANGED