@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.
Files changed (2) hide show
  1. package/dist/index.js +768 -226
  2. 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
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
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
- function normalizeComponentEncoding(component, esc2) {
3308
- const func = esc2 !== true ? escape : unescape;
3309
- if (component.scheme !== void 0) {
3310
- component.scheme = func(component.scheme);
3311
- }
3312
- if (component.userinfo !== void 0) {
3313
- component.userinfo = func(component.userinfo);
3314
- }
3315
- if (component.host !== void 0) {
3316
- component.host = func(component.host);
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
- if (component.path !== void 0) {
3319
- component.path = func(component.path);
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
- if (component.query !== void 0) {
3322
- component.query = func(component.query);
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
- if (component.fragment !== void 0) {
3325
- component.fragment = func(component.fragment);
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 component;
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 = component.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
- normalizeComponentEncoding,
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, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
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
- serialize(parse3(uri, options), options);
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
- if (typeof uriA === "string") {
3653
- uriA = unescape(uriA);
3654
- uriA = serialize(normalizeComponentEncoding(parse3(uriA, options), true), { ...options, skipEscape: true });
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 = escape(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 = unescape(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 parse3(uri, opts) {
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 = escape(unescape(parsed.path));
3867
+ parsed.path = normalizePathEncoding(parsed.path);
3803
3868
  }
3804
3869
  if (parsed.fragment) {
3805
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
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 os = __require("os");
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(os.homedir(), envPath.slice(1)) : envPath;
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 * as fs from "fs";
28148
- import * as path from "path";
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
- let message = args.map((arg) => String(arg)).join(" ");
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
- let message = err.stack || err.message;
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
- if (botToken && message.includes(botToken)) {
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 validateRecipient(targetId) {
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. Delivers inbound Telegram messages as channel notifications via notifications/message."
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: "send_telegram_message",
28654
- description: "Send a text message, photo, or document to a specific Telegram user, group, or channel.",
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: "Target ID or @username."
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 all unique Telegram chats the bot has interacted with.",
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: { chatId: { type: "string" } },
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: "leave_telegram_chat",
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: { chatId: { type: "string" } },
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 === "send_telegram_message") {
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
- validateRecipient(targetId);
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(targetId, source, {
28758
- caption: content
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(targetId, source, {
28762
- caption: content
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
- const uniqueChatIds = Array.from(
28813
- new Set(messageHistory.map((m) => m.chatId))
28814
- );
28815
- const chatDetails = uniqueChatIds.map((id) => {
28816
- const lastMsg = messageHistory.slice().reverse().find((m) => m.chatId === id);
28817
- return {
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, uniqueChatIds.length)}` : "No active chats found."
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
- validateRecipient(chatId);
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 === "leave_telegram_chat") {
29486
+ if (name === "leave_chat") {
28866
29487
  const { chatId } = LeaveChatSchema.parse(args);
28867
- validateRecipient(chatId);
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
- 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
- }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fre4x/telegram",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Telegram MCP server implementing Claude Code Channels for bidirectional communication and remote permission relay.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",