@fre4x/telegram 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +475 -198
  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,7 +28235,9 @@ var StdioServerTransport = class {
28144
28235
  // src/index.ts
28145
28236
  var import_dotenv = __toESM(require_main(), 1);
28146
28237
  import { Telegraf } from "telegraf";
28238
+ import { createHash } from "node:crypto";
28147
28239
  import * as fs from "fs";
28240
+ import os from "node:os";
28148
28241
  import * as path from "path";
28149
28242
 
28150
28243
  // ../node_modules/zod/index.js
@@ -28496,6 +28589,13 @@ function applyPagination(items, params) {
28496
28589
 
28497
28590
  // src/index.ts
28498
28591
  import_dotenv.default.config();
28592
+ function getStatePath() {
28593
+ if (process.env.TELEGRAM_STATE_PATH) {
28594
+ return process.env.TELEGRAM_STATE_PATH;
28595
+ }
28596
+ const suffix = botToken && !IS_MOCK ? createHash("sha256").update(botToken).digest("hex").slice(0, 12) : "default";
28597
+ return path.join(os.homedir(), ".fre4x", `telegram-mcp-${suffix}.json`);
28598
+ }
28499
28599
  var window = new JSDOM("").window;
28500
28600
  var DOMPurify = createDOMPurify(window);
28501
28601
  var IS_MOCK = process.env.MOCK === "true" || process.env.TELEGRAM_MOCK === "true";
@@ -28569,13 +28669,7 @@ function validateFilePath(filePath) {
28569
28669
  }
28570
28670
  return absolutePath;
28571
28671
  }
28572
- function 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
- }
28672
+ function assertRecipientAllowed(targetId) {
28579
28673
  if (enableRecipientWhitelist && allowedRecipients.length > 0) {
28580
28674
  if (!allowedRecipients.includes(targetId)) {
28581
28675
  throw new Error(
@@ -28584,6 +28678,18 @@ function validateRecipient(targetId) {
28584
28678
  }
28585
28679
  }
28586
28680
  }
28681
+ function validateSendRecipient(targetId) {
28682
+ if (!/^\d+$/.test(targetId)) {
28683
+ throw new Error(`Invalid Telegram user ID format: ${targetId}`);
28684
+ }
28685
+ assertRecipientAllowed(targetId);
28686
+ }
28687
+ function validateChatId(chatId) {
28688
+ if (!/^-?\d+$/.test(chatId)) {
28689
+ throw new Error(`Invalid Telegram chat ID format: ${chatId}`);
28690
+ }
28691
+ assertRecipientAllowed(chatId);
28692
+ }
28587
28693
  var SendMessageSchema = external_exports3.object({
28588
28694
  content: external_exports3.string().min(1).max(4096),
28589
28695
  recipientId: external_exports3.string().optional(),
@@ -28634,24 +28740,281 @@ var server = new Server(
28634
28740
  instructions: "Telegram MCP server. Delivers inbound Telegram messages as channel notifications via notifications/message."
28635
28741
  }
28636
28742
  );
28637
- var isInitialized = false;
28638
- server.oninitialized = () => {
28639
- isInitialized = true;
28640
- };
28641
- var seenMessageIds = /* @__PURE__ */ new Set();
28642
- var MAX_SEEN_IDS = 500;
28643
28743
  var isImage = (filePath) => {
28644
28744
  const ext = path.extname(filePath).toLowerCase();
28645
28745
  return [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext);
28646
28746
  };
28647
28747
  var messageHistory = [];
28648
28748
  var MAX_HISTORY = 100;
28749
+ var pollingOffset = 0;
28750
+ var knownChats = /* @__PURE__ */ new Map();
28751
+ function loadState() {
28752
+ if (IS_MOCK) return;
28753
+ try {
28754
+ if (!fs.existsSync(getStatePath())) return;
28755
+ const raw = fs.readFileSync(getStatePath(), "utf-8");
28756
+ const state = JSON.parse(raw);
28757
+ for (const chat of state.knownChats ?? []) {
28758
+ if (chat?.id) {
28759
+ knownChats.set(chat.id, chat);
28760
+ }
28761
+ }
28762
+ if (typeof state.pollingOffset === "number") {
28763
+ pollingOffset = state.pollingOffset;
28764
+ }
28765
+ console.error(
28766
+ `[telegram:state] loaded ${knownChats.size} chat(s) from ${getStatePath()}`
28767
+ );
28768
+ } catch (error48) {
28769
+ console.error(`[telegram:state] failed to load ${getStatePath()}:`, error48);
28770
+ }
28771
+ }
28772
+ function persistState() {
28773
+ if (IS_MOCK) return;
28774
+ try {
28775
+ const state = {
28776
+ knownChats: Array.from(knownChats.values()),
28777
+ pollingOffset
28778
+ };
28779
+ fs.mkdirSync(path.dirname(getStatePath()), { recursive: true });
28780
+ fs.writeFileSync(getStatePath(), JSON.stringify(state, null, 2), "utf-8");
28781
+ } catch (error48) {
28782
+ console.error(`[telegram:state] failed to persist ${getStatePath()}:`, error48);
28783
+ }
28784
+ }
28785
+ function reloadState() {
28786
+ if (IS_MOCK) return;
28787
+ try {
28788
+ if (!fs.existsSync(getStatePath())) return;
28789
+ const raw = fs.readFileSync(getStatePath(), "utf-8");
28790
+ const state = JSON.parse(raw);
28791
+ for (const chat of state.knownChats ?? []) {
28792
+ if (chat?.id) {
28793
+ knownChats.set(chat.id, chat);
28794
+ }
28795
+ }
28796
+ if (typeof state.pollingOffset === "number" && state.pollingOffset > pollingOffset) {
28797
+ pollingOffset = state.pollingOffset;
28798
+ }
28799
+ } catch (error48) {
28800
+ console.error(`[telegram:state] failed to reload ${getStatePath()}:`, error48);
28801
+ }
28802
+ }
28803
+ function chatTitleFromTelegramChat(chat) {
28804
+ return chat.title || chat.username || chat.first_name || "Private Chat";
28805
+ }
28806
+ function recordKnownChat(chat) {
28807
+ knownChats.set(chat.id, chat);
28808
+ persistState();
28809
+ }
28810
+ function recordKnownChatFromTelegramMessage(message) {
28811
+ recordKnownChat({
28812
+ id: message.chat.id.toString(),
28813
+ title: chatTitleFromTelegramChat(message.chat),
28814
+ type: message.chat.type ?? "unknown"
28815
+ });
28816
+ }
28817
+ function recordKnownChatFromUpdate(update) {
28818
+ const chat = update.message?.chat ?? update.edited_message?.chat ?? update.channel_post?.chat ?? update.my_chat_member?.chat;
28819
+ if (!chat) return;
28820
+ recordKnownChat({
28821
+ id: chat.id.toString(),
28822
+ title: chatTitleFromTelegramChat(chat),
28823
+ type: chat.type ?? "unknown"
28824
+ });
28825
+ }
28826
+ function logActiveChatsDebug(stage) {
28827
+ const active = getActiveChats();
28828
+ console.error(
28829
+ `[telegram:list_active_chats] ${stage}: knownChats=${knownChats.size} messageHistory=${messageHistory.length} active=${active.length} pollingOffset=${pollingOffset}`
28830
+ );
28831
+ if (knownChats.size > 0) {
28832
+ console.error(
28833
+ `[telegram:list_active_chats] knownChatIds: ${Array.from(knownChats.keys()).join(", ")}`
28834
+ );
28835
+ }
28836
+ if (active.length > 0) {
28837
+ console.error(
28838
+ `[telegram:list_active_chats] activeChatIds: ${active.map((c) => c.id).join(", ")}`
28839
+ );
28840
+ }
28841
+ }
28842
+ async function recordChatInteraction(chatId) {
28843
+ if (IS_MOCK || !bot) {
28844
+ recordKnownChat({
28845
+ id: chatId,
28846
+ title: `Chat ${chatId}`,
28847
+ type: "unknown"
28848
+ });
28849
+ console.error(
28850
+ `[telegram:recordChatInteraction] mock recorded chatId=${chatId} knownChats=${knownChats.size}`
28851
+ );
28852
+ return;
28853
+ }
28854
+ try {
28855
+ const chat = await bot.telegram.getChat(chatId);
28856
+ recordKnownChat({
28857
+ id: chat.id.toString(),
28858
+ title: chatTitleFromTelegramChat(chat),
28859
+ type: chat.type
28860
+ });
28861
+ console.error(
28862
+ `[telegram:recordChatInteraction] getChat ok chatId=${chat.id} knownChats=${knownChats.size}`
28863
+ );
28864
+ } catch (error48) {
28865
+ recordKnownChat({
28866
+ id: chatId,
28867
+ title: chatId,
28868
+ type: "unknown"
28869
+ });
28870
+ console.error(
28871
+ `[telegram:recordChatInteraction] getChat failed for ${chatId}, stored fallback:`,
28872
+ error48
28873
+ );
28874
+ }
28875
+ }
28876
+ function getConfiguredChatCandidates() {
28877
+ const candidates = /* @__PURE__ */ new Set();
28878
+ if (allowedUserId) candidates.add(allowedUserId);
28879
+ for (const id of allowedRecipients) {
28880
+ if (id) candidates.add(id);
28881
+ }
28882
+ for (const chat of knownChats.values()) {
28883
+ candidates.add(chat.id);
28884
+ }
28885
+ for (const msg of messageHistory) {
28886
+ candidates.add(msg.chatId);
28887
+ }
28888
+ return candidates;
28889
+ }
28890
+ async function ensurePollingMode() {
28891
+ if (!bot || IS_MOCK) return;
28892
+ try {
28893
+ const info = await bot.telegram.getWebhookInfo();
28894
+ if (info.url) {
28895
+ console.error(
28896
+ `[telegram] active webhook detected (${info.url}); switching to getUpdates polling`
28897
+ );
28898
+ await bot.telegram.deleteWebhook({ drop_pending_updates: false });
28899
+ }
28900
+ } catch (error48) {
28901
+ console.error("[telegram] ensurePollingMode failed:", error48);
28902
+ }
28903
+ }
28904
+ async function discoverChatsFromApi() {
28905
+ if (IS_MOCK || !bot) return;
28906
+ const candidates = getConfiguredChatCandidates();
28907
+ console.error(
28908
+ `[telegram:list_active_chats] discoverChatsFromApi: probing ${candidates.size} candidate chat(s) from state=${getStatePath()}`
28909
+ );
28910
+ for (const chatId of candidates) {
28911
+ await recordChatInteraction(chatId);
28912
+ }
28913
+ try {
28914
+ await ensurePollingMode();
28915
+ const updates = await bot.telegram.getUpdates(0, 100, pollingOffset, [
28916
+ "message",
28917
+ "edited_message",
28918
+ "channel_post",
28919
+ "my_chat_member",
28920
+ "chat_member"
28921
+ ]);
28922
+ for (const update of updates) {
28923
+ pollingOffset = update.update_id + 1;
28924
+ const msgLike = update;
28925
+ if (!msgLike.message && !msgLike.edited_message && !msgLike.channel_post && !msgLike.my_chat_member)
28926
+ continue;
28927
+ recordKnownChatFromUpdate(msgLike);
28928
+ }
28929
+ console.error(
28930
+ `[telegram:list_active_chats] discoverChatsFromApi: processed ${updates.length} update(s)`
28931
+ );
28932
+ persistState();
28933
+ } catch (error48) {
28934
+ console.error(
28935
+ "[telegram:list_active_chats] discoverChatsFromApi getUpdates failed:",
28936
+ error48
28937
+ );
28938
+ }
28939
+ }
28940
+ function getActiveChats() {
28941
+ const chats = /* @__PURE__ */ new Map();
28942
+ for (const chat of knownChats.values()) {
28943
+ chats.set(chat.id, chat);
28944
+ }
28945
+ for (const msg of messageHistory) {
28946
+ if (chats.has(msg.chatId)) continue;
28947
+ chats.set(msg.chatId, {
28948
+ id: msg.chatId,
28949
+ title: msg.chatTitle || "Unknown",
28950
+ type: msg.metadata?.chatType || "unknown"
28951
+ });
28952
+ }
28953
+ return Array.from(chats.values()).sort(
28954
+ (a, b) => a.title.localeCompare(b.title)
28955
+ );
28956
+ }
28957
+ async function fetchNewUpdates() {
28958
+ if (!bot || IS_MOCK) return;
28959
+ try {
28960
+ await ensurePollingMode();
28961
+ const updates = await bot.telegram.getUpdates(0, 100, pollingOffset, [
28962
+ "message",
28963
+ "edited_message",
28964
+ "channel_post",
28965
+ "my_chat_member"
28966
+ ]);
28967
+ for (const update of updates) {
28968
+ pollingOffset = update.update_id + 1;
28969
+ const msgLike = update;
28970
+ if (!msgLike.message && !msgLike.edited_message && !msgLike.channel_post && !msgLike.my_chat_member)
28971
+ continue;
28972
+ recordKnownChatFromUpdate(msgLike);
28973
+ const msg = msgLike.message;
28974
+ if (!msg?.text) continue;
28975
+ const senderId = msg.from?.id?.toString() ?? "unknown";
28976
+ const chatId = msg.chat.id.toString();
28977
+ const chatTitle = msg.chat.title || msg.chat.username || msg.chat.first_name || "Private Chat";
28978
+ if (messageHistory.some(
28979
+ (m) => m.metadata?.message_id === msg.message_id
28980
+ ))
28981
+ continue;
28982
+ const currentAllowedUserId = process.env.ALLOWED_USER_ID;
28983
+ if (currentAllowedUserId && msg.chat.type === "private") {
28984
+ if (senderId !== currentAllowedUserId) continue;
28985
+ }
28986
+ let content = DOMPurify.sanitize(msg.text, {
28987
+ ALLOWED_TAGS: [],
28988
+ ALLOWED_ATTR: []
28989
+ });
28990
+ content = content.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/[*_~]/g, "\\$&");
28991
+ messageHistory.push({
28992
+ senderId,
28993
+ chatId,
28994
+ chatTitle,
28995
+ content,
28996
+ timestamp: new Date(msg.date * 1e3).toISOString(),
28997
+ metadata: {
28998
+ chatId,
28999
+ chatType: msg.chat.type,
29000
+ message_id: msg.message_id
29001
+ }
29002
+ });
29003
+ if (messageHistory.length > MAX_HISTORY) {
29004
+ messageHistory.shift();
29005
+ }
29006
+ }
29007
+ persistState();
29008
+ } catch (error48) {
29009
+ console.error("fetchNewUpdates failed:", error48);
29010
+ }
29011
+ }
28649
29012
  server.setRequestHandler(ListToolsRequestSchema, async () => {
28650
29013
  return {
28651
29014
  tools: [
28652
29015
  {
28653
- name: "send_telegram_message",
28654
- description: "Send a text message, photo, or document to a specific Telegram user, group, or channel.",
29016
+ name: "send_message",
29017
+ description: "Send text, photo, or document to a Telegram user by numeric ID.",
28655
29018
  inputSchema: {
28656
29019
  type: "object",
28657
29020
  properties: {
@@ -28661,7 +29024,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
28661
29024
  },
28662
29025
  recipientId: {
28663
29026
  type: "string",
28664
- description: "Target ID or @username."
29027
+ description: "Numeric Telegram user ID."
28665
29028
  },
28666
29029
  mediaPath: {
28667
29030
  type: "string",
@@ -28689,7 +29052,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
28689
29052
  },
28690
29053
  {
28691
29054
  name: "list_active_chats",
28692
- description: "List all unique Telegram chats the bot has interacted with.",
29055
+ description: "List chats the bot has sent to or received messages from.",
28693
29056
  inputSchema: {
28694
29057
  type: "object",
28695
29058
  properties: {
@@ -28703,16 +29066,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
28703
29066
  description: "Get detailed information about a specific Telegram chat.",
28704
29067
  inputSchema: {
28705
29068
  type: "object",
28706
- properties: { chatId: { type: "string" } },
29069
+ properties: {
29070
+ chatId: {
29071
+ type: "string",
29072
+ description: "Numeric Telegram chat ID."
29073
+ }
29074
+ },
28707
29075
  required: ["chatId"]
28708
29076
  }
28709
29077
  },
28710
29078
  {
28711
- name: "leave_telegram_chat",
29079
+ name: "leave_chat",
28712
29080
  description: "Request the bot to leave a specific group or channel.",
28713
29081
  inputSchema: {
28714
29082
  type: "object",
28715
- properties: { chatId: { type: "string" } },
29083
+ properties: {
29084
+ chatId: {
29085
+ type: "string",
29086
+ description: "Numeric Telegram chat ID."
29087
+ }
29088
+ },
28716
29089
  required: ["chatId"]
28717
29090
  }
28718
29091
  }
@@ -28723,7 +29096,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
28723
29096
  const { name, arguments: args } = request.params;
28724
29097
  const currentAllowedUserId = process.env.ALLOWED_USER_ID;
28725
29098
  try {
28726
- if (name === "send_telegram_message") {
29099
+ if (name === "send_message") {
29100
+ reloadState();
28727
29101
  const { content, recipientId, mediaPath, mediaType } = SendMessageSchema.parse(args);
28728
29102
  const targetId = recipientId || currentAllowedUserId;
28729
29103
  if (!targetId) {
@@ -28738,7 +29112,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
28738
29112
  429
28739
29113
  );
28740
29114
  }
28741
- validateRecipient(targetId);
29115
+ validateSendRecipient(targetId);
28742
29116
  let safePath;
28743
29117
  if (mediaPath) {
28744
29118
  safePath = validateFilePath(mediaPath);
@@ -28748,19 +29122,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
28748
29122
  if (mediaPath)
28749
29123
  mockResponse += `
28750
29124
  [Mock] Media: ${mediaPath} (${mediaType || "auto"})`;
29125
+ await recordChatInteraction(targetId);
28751
29126
  return { content: [{ type: "text", text: mockResponse }] };
28752
29127
  }
28753
29128
  if (mediaPath && safePath) {
28754
29129
  const source = { source: safePath };
28755
29130
  const isPhoto = mediaType === "photo" || !mediaType && isImage(safePath);
28756
29131
  if (isPhoto) {
28757
- await bot.telegram.sendPhoto(targetId, source, {
29132
+ const sent = await bot.telegram.sendPhoto(targetId, source, {
28758
29133
  caption: content
28759
29134
  });
29135
+ recordKnownChatFromTelegramMessage(sent);
28760
29136
  } else {
28761
- await bot.telegram.sendDocument(targetId, source, {
28762
- caption: content
28763
- });
29137
+ const sent = await bot.telegram.sendDocument(
29138
+ targetId,
29139
+ source,
29140
+ { caption: content }
29141
+ );
29142
+ recordKnownChatFromTelegramMessage(sent);
28764
29143
  }
28765
29144
  return {
28766
29145
  content: [
@@ -28771,7 +29150,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
28771
29150
  ]
28772
29151
  };
28773
29152
  } else {
28774
- await bot.telegram.sendMessage(targetId, content);
29153
+ const sent = await bot.telegram.sendMessage(targetId, content);
29154
+ recordKnownChatFromTelegramMessage(sent);
28775
29155
  return {
28776
29156
  content: [
28777
29157
  { type: "text", text: `Message sent successfully.` }
@@ -28781,6 +29161,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
28781
29161
  }
28782
29162
  if (name === "get_recent_messages") {
28783
29163
  const { limit, offset } = GetRecentMessagesSchema.parse(args || {});
29164
+ await fetchNewUpdates();
28784
29165
  const paginated = applyPagination(
28785
29166
  messageHistory.slice().reverse(),
28786
29167
  { limit, offset }
@@ -28809,18 +29190,12 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
28809
29190
  }
28810
29191
  if (name === "list_active_chats") {
28811
29192
  const { limit, offset } = GetRecentMessagesSchema.parse(args || {});
28812
- 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 });
29193
+ reloadState();
29194
+ await fetchNewUpdates();
29195
+ await discoverChatsFromApi();
29196
+ logActiveChatsDebug("after fetch and discover");
29197
+ const activeChats = getActiveChats();
29198
+ const paginated = applyPagination(activeChats, { limit, offset });
28824
29199
  const formatted = paginated.items.map((c) => `- ${c.title} (ID: ${c.id}, Type: ${c.type})`).join("\n");
28825
29200
  return {
28826
29201
  content: [
@@ -28829,14 +29204,14 @@ ${formatPaginationFooter(offset, limit, messageHistory.length)}`
28829
29204
  text: formatted ? `Active chats:
28830
29205
  ${formatted}
28831
29206
 
28832
- ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active chats found."
29207
+ ${formatPaginationFooter(offset, limit, activeChats.length)}` : "No active chats found."
28833
29208
  }
28834
29209
  ]
28835
29210
  };
28836
29211
  }
28837
29212
  if (name === "get_chat_info") {
28838
29213
  const { chatId } = GetChatInfoSchema.parse(args);
28839
- validateRecipient(chatId);
29214
+ validateChatId(chatId);
28840
29215
  if (IS_MOCK || !bot) {
28841
29216
  return {
28842
29217
  content: [
@@ -28862,9 +29237,9 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
28862
29237
  ]
28863
29238
  };
28864
29239
  }
28865
- if (name === "leave_telegram_chat") {
29240
+ if (name === "leave_chat") {
28866
29241
  const { chatId } = LeaveChatSchema.parse(args);
28867
- validateRecipient(chatId);
29242
+ validateChatId(chatId);
28868
29243
  if (IS_MOCK || !bot) {
28869
29244
  return {
28870
29245
  content: [
@@ -28906,127 +29281,29 @@ ${formatPaginationFooter(offset, limit, uniqueChatIds.length)}` : "No active cha
28906
29281
  if (msg.includes("limit exceeded")) {
28907
29282
  return createApiError("Rate limit exceeded.", 429);
28908
29283
  }
29284
+ if (msg.includes("invalid telegram user id format")) {
29285
+ return createValidationError(
29286
+ "recipientId",
29287
+ 'Use a numeric user ID only (e.g., "123456789"). @username is not supported.'
29288
+ );
29289
+ }
29290
+ if (msg.includes("invalid telegram chat id format")) {
29291
+ return createValidationError(
29292
+ "chatId",
29293
+ 'Use a numeric chat ID only (e.g., "123456789"). @username is not supported.'
29294
+ );
29295
+ }
28909
29296
  }
28910
29297
  return createInternalError(
28911
29298
  "An unexpected error occurred while processing the request."
28912
29299
  );
28913
29300
  }
28914
29301
  });
28915
- if (bot) {
28916
- bot.on("text", async (ctx) => {
28917
- const senderId = ctx.from.id.toString();
28918
- const chatId = ctx.chat.id.toString();
28919
- const chatTitle = ctx.chat.title || ctx.chat.username || ctx.chat.first_name || "Private Chat";
28920
- const currentAllowedUserId = process.env.ALLOWED_USER_ID;
28921
- if (currentAllowedUserId) {
28922
- if (ctx.chat.type === "private") {
28923
- if (senderId !== currentAllowedUserId) {
28924
- console.warn(
28925
- `Unauthorized private message from ${senderId}`
28926
- );
28927
- await ctx.reply(
28928
- "\u274C Access denied. Only the authorized user can interact with this bot."
28929
- ).catch(() => {
28930
- });
28931
- return;
28932
- }
28933
- } else if (ctx.chat.type === "group" || ctx.chat.type === "supergroup") {
28934
- try {
28935
- const chatMember = await ctx.getChatMember(ctx.from.id);
28936
- const isAdmin = ["creator", "administrator"].includes(
28937
- chatMember.status
28938
- );
28939
- if (!isAdmin && senderId !== currentAllowedUserId) {
28940
- console.warn(
28941
- `Unauthorized group message from ${senderId} in ${chatTitle}`
28942
- );
28943
- return;
28944
- }
28945
- } catch (error48) {
28946
- console.error(
28947
- "Failed to check group member status:",
28948
- error48
28949
- );
28950
- return;
28951
- }
28952
- } else if (ctx.chat.type === "channel") {
28953
- return;
28954
- }
28955
- }
28956
- let messageContent = ctx.message.text;
28957
- messageContent = DOMPurify.sanitize(messageContent, {
28958
- ALLOWED_TAGS: [],
28959
- ALLOWED_ATTR: []
28960
- });
28961
- messageContent = messageContent.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/[*_~]/g, "\\$&");
28962
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
28963
- const metadata = {
28964
- chatId,
28965
- chatType: ctx.chat?.type,
28966
- message_id: ctx.message.message_id
28967
- };
28968
- messageHistory.push({
28969
- senderId,
28970
- chatId,
28971
- chatTitle,
28972
- content: messageContent,
28973
- timestamp,
28974
- metadata
28975
- });
28976
- if (messageHistory.length > MAX_HISTORY) {
28977
- messageHistory.shift();
28978
- }
28979
- if (!isInitialized) {
28980
- console.warn(
28981
- `[telegram] Pre-init notification dropped (message_id=${metadata.message_id})`
28982
- );
28983
- return;
28984
- }
28985
- const msgId = metadata.message_id;
28986
- if (seenMessageIds.has(msgId)) {
28987
- return;
28988
- }
28989
- if (seenMessageIds.size >= MAX_SEEN_IDS) {
28990
- seenMessageIds.delete(seenMessageIds.values().next().value);
28991
- }
28992
- seenMessageIds.add(msgId);
28993
- try {
28994
- await server.notification({
28995
- method: "notifications/message",
28996
- params: {
28997
- _meta: {
28998
- channel: "telegram"
28999
- },
29000
- content: [
29001
- {
29002
- type: "text",
29003
- text: `New message from ${senderId} in ${chatTitle} (${chatId}): ${messageContent}`
29004
- }
29005
- ]
29006
- }
29007
- });
29008
- } catch (error48) {
29009
- console.error("Failed to push channel notification:", error48);
29010
- }
29011
- });
29012
- }
29013
29302
  async function run() {
29303
+ loadState();
29014
29304
  const transport = new StdioServerTransport();
29015
29305
  await server.connect(transport);
29016
29306
  console.error("Telegram MCP Server running on stdio");
29017
- if (bot) {
29018
- bot.launch().catch((err) => {
29019
- console.error("Failed to launch Telegram bot:", err);
29020
- });
29021
- process.once("SIGINT", () => {
29022
- bot.stop("SIGINT");
29023
- process.exit(0);
29024
- });
29025
- process.once("SIGTERM", () => {
29026
- bot.stop("SIGTERM");
29027
- process.exit(0);
29028
- });
29029
- }
29030
29307
  }
29031
29308
  if (process.env.NODE_ENV !== "test") {
29032
29309
  run().catch((error48) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fre4x/telegram",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
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",