@ebowwa/glm-daemon 0.4.5 → 0.4.7

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.
@@ -92156,7 +92156,86 @@ var require_src2 = __commonJS((exports) => {
92156
92156
  __exportStar(require_dist3(), exports);
92157
92157
  __exportStar(require_dist11(), exports);
92158
92158
  });
92159
-
92159
+ // ../channel-types/dist/plugins/registry.js
92160
+ class PluginRegistryImpl {
92161
+ channels = new Map;
92162
+ channelOrder = [];
92163
+ registerChannel(registration) {
92164
+ const id = registration.plugin.id;
92165
+ if (typeof id !== "string") {
92166
+ throw new Error("Channel plugin id must be a string");
92167
+ }
92168
+ const normalizedId = id.trim().toLowerCase();
92169
+ if (this.channels.has(normalizedId)) {
92170
+ console.warn(`[PluginRegistry] Channel "${normalizedId}" already registered, replacing`);
92171
+ }
92172
+ this.channels.set(normalizedId, registration);
92173
+ if (!this.channelOrder.includes(normalizedId)) {
92174
+ this.channelOrder.push(normalizedId);
92175
+ }
92176
+ console.log(`[PluginRegistry] Registered channel: ${normalizedId} (source: ${registration.source})`);
92177
+ }
92178
+ unregisterChannel(channelId) {
92179
+ const normalizedId = channelId.trim().toLowerCase();
92180
+ const existed = this.channels.delete(normalizedId);
92181
+ if (existed) {
92182
+ this.channelOrder = this.channelOrder.filter((id) => id !== normalizedId);
92183
+ }
92184
+ return existed;
92185
+ }
92186
+ getChannel(channelId) {
92187
+ const normalizedId = this.normalizeChannelId(channelId);
92188
+ const registration = this.channels.get(normalizedId);
92189
+ return registration?.plugin;
92190
+ }
92191
+ getChannelRegistration(channelId) {
92192
+ const normalizedId = this.normalizeChannelId(channelId);
92193
+ return this.channels.get(normalizedId);
92194
+ }
92195
+ getChannels() {
92196
+ return this.channelOrder.map((id) => this.channels.get(id)?.plugin).filter((p) => p !== undefined);
92197
+ }
92198
+ getChannelRegistrations() {
92199
+ return this.channelOrder.map((id) => this.channels.get(id)).filter((r) => r !== undefined);
92200
+ }
92201
+ getChannelOrder() {
92202
+ return [...this.channelOrder];
92203
+ }
92204
+ setChannelOrder(order) {
92205
+ this.channelOrder = order.map((id) => id.toLowerCase().trim());
92206
+ }
92207
+ getChannelsByCapability(capability) {
92208
+ return this.getChannels().filter((plugin) => plugin.capabilities.supports[capability] === true);
92209
+ }
92210
+ getChannelsByChatType(chatType) {
92211
+ return this.getChannels().filter((plugin) => plugin.capabilities.chatTypes?.includes(chatType));
92212
+ }
92213
+ hasChannel(channelId) {
92214
+ return this.channels.has(this.normalizeChannelId(channelId));
92215
+ }
92216
+ normalizeChannelId(channelId) {
92217
+ const id = channelId.trim().toLowerCase();
92218
+ const aliases = {
92219
+ tg: "telegram",
92220
+ imsg: "imessage",
92221
+ wa: "whatsapp",
92222
+ dc: "discord",
92223
+ sl: "slack"
92224
+ };
92225
+ return aliases[id] || id;
92226
+ }
92227
+ clear() {
92228
+ this.channels.clear();
92229
+ this.channelOrder = [];
92230
+ }
92231
+ getStats() {
92232
+ return {
92233
+ channelCount: this.channels.size,
92234
+ channels: this.getChannelOrder()
92235
+ };
92236
+ }
92237
+ }
92238
+ var pluginRegistry = new PluginRegistryImpl;
92160
92239
  // ../channel-types/dist/index.js
92161
92240
  function createChannelId(platform, accountId, instanceId) {
92162
92241
  return { platform, accountId, instanceId };
@@ -98838,6 +98917,266 @@ function registerAllCommands(bot, memory, tools) {
98838
98917
  registerInnerthoughtsCommand(bot, memory, tools);
98839
98918
  register(bot, tools);
98840
98919
  }
98920
+
98921
+ // ../channel-telegram/dist/plugin.js
98922
+ var telegramMeta = {
98923
+ label: "Telegram",
98924
+ blurb: "Telegram Bot API for direct and group chats",
98925
+ icon: "\uD83D\uDCF1",
98926
+ docsPath: "/docs/channels/telegram",
98927
+ quickstartAllowFrom: true
98928
+ };
98929
+ var telegramConfigAdapter = {
98930
+ listAccountIds: async (config) => {
98931
+ const botToken = config.botToken;
98932
+ if (!botToken)
98933
+ return [];
98934
+ return ["default"];
98935
+ },
98936
+ resolveAccount: async (config, accountId) => {
98937
+ return {
98938
+ botToken: config.botToken,
98939
+ allowedUsers: config.allowedUsers,
98940
+ allowedChats: config.allowedChats,
98941
+ pollingInterval: config.pollingInterval || 1000
98942
+ };
98943
+ },
98944
+ validateAccount: async (account) => {
98945
+ if (!account.botToken)
98946
+ return false;
98947
+ return true;
98948
+ }
98949
+ };
98950
+ var createOutboundAdapter = (getBot) => ({
98951
+ deliveryMode: "direct",
98952
+ textChunkLimit: 4096,
98953
+ chunker: (text, limit) => {
98954
+ const chunks = [];
98955
+ for (let i = 0;i < text.length; i += limit) {
98956
+ chunks.push(text.slice(i, i + limit));
98957
+ }
98958
+ return { chunks, totalChunks: chunks.length };
98959
+ },
98960
+ sendText: async ({ to, text, accountId, account, replyToId, threadId }) => {
98961
+ const bot = getBot();
98962
+ if (!bot) {
98963
+ return { success: false, error: "Bot not initialized", timestamp: new Date };
98964
+ }
98965
+ try {
98966
+ const chatId = parseInt(to, 10);
98967
+ const options = {
98968
+ parse_mode: "Markdown"
98969
+ };
98970
+ if (replyToId) {
98971
+ options.reply_to_message_id = parseInt(replyToId, 10);
98972
+ }
98973
+ if (threadId) {
98974
+ options.message_thread_id = parseInt(threadId, 10);
98975
+ }
98976
+ const result = await bot.sendMessage(chatId, text, options);
98977
+ return {
98978
+ success: true,
98979
+ messageId: result.message_id.toString(),
98980
+ timestamp: new Date
98981
+ };
98982
+ } catch (error) {
98983
+ return {
98984
+ success: false,
98985
+ error: error.message,
98986
+ timestamp: new Date
98987
+ };
98988
+ }
98989
+ },
98990
+ sendMedia: async ({ to, text, mediaUrl, mediaType, accountId, account }) => {
98991
+ const bot = getBot();
98992
+ if (!bot) {
98993
+ return { success: false, error: "Bot not initialized", timestamp: new Date };
98994
+ }
98995
+ try {
98996
+ const chatId = parseInt(to, 10);
98997
+ let result;
98998
+ if (mediaType === "image") {
98999
+ result = await bot.sendPhoto(chatId, mediaUrl, { caption: text });
99000
+ } else if (mediaType === "video") {
99001
+ result = await bot.sendVideo(chatId, mediaUrl, { caption: text });
99002
+ } else if (mediaType === "audio") {
99003
+ result = await bot.sendAudio(chatId, mediaUrl, { caption: text });
99004
+ } else {
99005
+ result = await bot.sendDocument(chatId, mediaUrl, { caption: text });
99006
+ }
99007
+ return {
99008
+ success: true,
99009
+ messageId: result.message_id.toString(),
99010
+ timestamp: new Date
99011
+ };
99012
+ } catch (error) {
99013
+ return {
99014
+ success: false,
99015
+ error: error.message,
99016
+ timestamp: new Date
99017
+ };
99018
+ }
99019
+ }
99020
+ });
99021
+ var telegramThreadingAdapter = {
99022
+ replyModes: ["quote", "thread"],
99023
+ defaultReplyMode: "quote"
99024
+ };
99025
+ var createGatewayAdapter = (createBot) => {
99026
+ const activeBots = new Map;
99027
+ const messageHandlers = new Map;
99028
+ return {
99029
+ startAccount: async ({ accountId, account, onMessage }) => {
99030
+ const bot = createBot(account.botToken);
99031
+ const handler = (msg) => {
99032
+ if (account.allowedUsers?.length && !account.allowedUsers.includes(msg.from?.id || 0)) {
99033
+ return;
99034
+ }
99035
+ if (account.allowedChats?.length && !account.allowedChats.includes(msg.chat.id)) {
99036
+ return;
99037
+ }
99038
+ const channelId = {
99039
+ platform: "telegram",
99040
+ accountId: msg.chat.id.toString()
99041
+ };
99042
+ const channelMessage = {
99043
+ messageId: msg.message_id.toString(),
99044
+ channelId,
99045
+ timestamp: new Date(msg.date * 1000),
99046
+ sender: {
99047
+ id: msg.from?.id.toString() || "unknown",
99048
+ username: msg.from?.username,
99049
+ displayName: msg.from?.first_name,
99050
+ isBot: msg.from?.is_bot || false
99051
+ },
99052
+ text: msg.text || "",
99053
+ context: {
99054
+ isDM: msg.chat.type === "private",
99055
+ groupName: msg.chat.title,
99056
+ threadId: msg.message_thread_id?.toString()
99057
+ },
99058
+ replyTo: msg.reply_to_message ? {
99059
+ messageId: msg.reply_to_message.message_id.toString(),
99060
+ channelId
99061
+ } : undefined
99062
+ };
99063
+ onMessage(channelMessage);
99064
+ };
99065
+ bot.on("message", handler);
99066
+ activeBots.set(accountId, bot);
99067
+ messageHandlers.set(accountId, handler);
99068
+ },
99069
+ stopAccount: async ({ accountId, account }) => {
99070
+ const bot = activeBots.get(accountId);
99071
+ const handler = messageHandlers.get(accountId);
99072
+ if (bot && handler) {
99073
+ bot.off("message", handler);
99074
+ if (bot.isPolling()) {
99075
+ await bot.stopPolling();
99076
+ }
99077
+ activeBots.delete(accountId);
99078
+ messageHandlers.delete(accountId);
99079
+ }
99080
+ },
99081
+ isAccountActive: (accountId) => {
99082
+ return activeBots.has(accountId);
99083
+ }
99084
+ };
99085
+ };
99086
+ function createTelegramPlugin(config = {}, TelegramBotClass) {
99087
+ let bot = null;
99088
+ const createBot = (token) => {
99089
+ if (!TelegramBotClass) {
99090
+ throw new Error("TelegramBot class not provided");
99091
+ }
99092
+ return new TelegramBotClass(token, { polling: true });
99093
+ };
99094
+ return {
99095
+ id: "telegram",
99096
+ meta: telegramMeta,
99097
+ capabilities: {
99098
+ chatTypes: ["direct", "group", "channel", "thread"],
99099
+ supports: {
99100
+ text: true,
99101
+ media: true,
99102
+ replies: true,
99103
+ threads: true,
99104
+ reactions: true,
99105
+ editing: true,
99106
+ streaming: false,
99107
+ buttons: true
99108
+ },
99109
+ nativeCommands: true,
99110
+ rateLimits: {
99111
+ messagesPerMinute: 30,
99112
+ charactersPerMessage: 4096
99113
+ },
99114
+ media: {
99115
+ maxFileSize: 50 * 1024 * 1024,
99116
+ supportedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"]
99117
+ }
99118
+ },
99119
+ config: telegramConfigAdapter,
99120
+ outbound: createOutboundAdapter(() => bot),
99121
+ gateway: createGatewayAdapter((token) => {
99122
+ bot = createBot(token);
99123
+ return bot;
99124
+ }),
99125
+ threading: telegramThreadingAdapter
99126
+ };
99127
+ }
99128
+ var telegramPlugin = createTelegramPlugin();
99129
+ if (process.env.TELEGRAM_BOT_TOKEN) {
99130
+ pluginRegistry.registerChannel({
99131
+ pluginId: "telegram",
99132
+ plugin: telegramPlugin,
99133
+ source: "core"
99134
+ });
99135
+ }
99136
+ // ../channel-telegram/dist/utils/normalize.js
99137
+ function normalizeTelegramMessagingTarget(raw) {
99138
+ const trimmed = raw.trim();
99139
+ if (!trimmed)
99140
+ return;
99141
+ let normalized = trimmed;
99142
+ if (normalized.startsWith("telegram:")) {
99143
+ normalized = normalized.slice("telegram:".length).trim();
99144
+ } else if (normalized.startsWith("tg:")) {
99145
+ normalized = normalized.slice("tg:".length).trim();
99146
+ }
99147
+ if (!normalized)
99148
+ return;
99149
+ const tmeMatch = /^https?:\/\/t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized) ?? /^t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized);
99150
+ if (tmeMatch?.[1]) {
99151
+ normalized = `@${tmeMatch[1]}`;
99152
+ }
99153
+ if (!normalized)
99154
+ return;
99155
+ return `telegram:${normalized}`.toLowerCase();
99156
+ }
99157
+ function looksLikeTelegramTargetId(raw) {
99158
+ const trimmed = raw.trim();
99159
+ if (!trimmed)
99160
+ return false;
99161
+ if (/^(telegram|tg):/i.test(trimmed))
99162
+ return true;
99163
+ if (trimmed.startsWith("@"))
99164
+ return true;
99165
+ return /^-?\d{6,}$/.test(trimmed);
99166
+ }
99167
+ function extractRawTarget(normalized) {
99168
+ const trimmed = normalized.trim();
99169
+ if (!trimmed.startsWith("telegram:"))
99170
+ return;
99171
+ const raw = trimmed.slice("telegram:".length);
99172
+ return raw || undefined;
99173
+ }
99174
+ function isNumericIdTarget(normalized) {
99175
+ const raw = extractRawTarget(normalized);
99176
+ if (!raw)
99177
+ return false;
99178
+ return /^-?\d+$/.test(raw);
99179
+ }
98841
99180
  // ../channel-telegram/dist/index.js
98842
99181
  class TelegramChannel {
98843
99182
  id;
@@ -99202,6 +99541,41 @@ class GLMTelegramChannel extends BaseChannel {
99202
99541
  isAllowed(userId, chatId) {
99203
99542
  return this.protocolAdapter.isAllowed(userId, chatId);
99204
99543
  }
99544
+ normalizeTarget(raw) {
99545
+ return normalizeTelegramMessagingTarget(raw);
99546
+ }
99547
+ isValidTarget(raw) {
99548
+ return looksLikeTelegramTargetId(raw);
99549
+ }
99550
+ parseTargetFromInput(input) {
99551
+ const words = input.split(/\s+/);
99552
+ for (const word of words) {
99553
+ if (looksLikeTelegramTargetId(word)) {
99554
+ const normalized = normalizeTelegramMessagingTarget(word);
99555
+ if (normalized) {
99556
+ return extractRawTarget(normalized);
99557
+ }
99558
+ }
99559
+ }
99560
+ return;
99561
+ }
99562
+ async sendToTarget(target, text) {
99563
+ const normalized = normalizeTelegramMessagingTarget(target);
99564
+ if (!normalized) {
99565
+ throw new Error(`Invalid Telegram target: ${target}`);
99566
+ }
99567
+ const rawTarget = extractRawTarget(normalized);
99568
+ if (!rawTarget) {
99569
+ throw new Error(`Failed to extract target from: ${normalized}`);
99570
+ }
99571
+ let chatId;
99572
+ if (isNumericIdTarget(normalized)) {
99573
+ chatId = parseInt(rawTarget, 10);
99574
+ } else {
99575
+ throw new Error(`Username targets require resolution. Use numeric chat ID instead, or implement username resolution. Target: ${rawTarget}`);
99576
+ }
99577
+ await this.sendMessage(chatId, text);
99578
+ }
99205
99579
  }
99206
99580
  // src/channels/discord.ts
99207
99581
  var import_discord = __toESM(require_src2(), 1);
@@ -92156,7 +92156,86 @@ var require_src2 = __commonJS((exports) => {
92156
92156
  __exportStar(require_dist3(), exports);
92157
92157
  __exportStar(require_dist11(), exports);
92158
92158
  });
92159
-
92159
+ // ../channel-types/dist/plugins/registry.js
92160
+ class PluginRegistryImpl {
92161
+ channels = new Map;
92162
+ channelOrder = [];
92163
+ registerChannel(registration) {
92164
+ const id = registration.plugin.id;
92165
+ if (typeof id !== "string") {
92166
+ throw new Error("Channel plugin id must be a string");
92167
+ }
92168
+ const normalizedId = id.trim().toLowerCase();
92169
+ if (this.channels.has(normalizedId)) {
92170
+ console.warn(`[PluginRegistry] Channel "${normalizedId}" already registered, replacing`);
92171
+ }
92172
+ this.channels.set(normalizedId, registration);
92173
+ if (!this.channelOrder.includes(normalizedId)) {
92174
+ this.channelOrder.push(normalizedId);
92175
+ }
92176
+ console.log(`[PluginRegistry] Registered channel: ${normalizedId} (source: ${registration.source})`);
92177
+ }
92178
+ unregisterChannel(channelId) {
92179
+ const normalizedId = channelId.trim().toLowerCase();
92180
+ const existed = this.channels.delete(normalizedId);
92181
+ if (existed) {
92182
+ this.channelOrder = this.channelOrder.filter((id) => id !== normalizedId);
92183
+ }
92184
+ return existed;
92185
+ }
92186
+ getChannel(channelId) {
92187
+ const normalizedId = this.normalizeChannelId(channelId);
92188
+ const registration = this.channels.get(normalizedId);
92189
+ return registration?.plugin;
92190
+ }
92191
+ getChannelRegistration(channelId) {
92192
+ const normalizedId = this.normalizeChannelId(channelId);
92193
+ return this.channels.get(normalizedId);
92194
+ }
92195
+ getChannels() {
92196
+ return this.channelOrder.map((id) => this.channels.get(id)?.plugin).filter((p) => p !== undefined);
92197
+ }
92198
+ getChannelRegistrations() {
92199
+ return this.channelOrder.map((id) => this.channels.get(id)).filter((r) => r !== undefined);
92200
+ }
92201
+ getChannelOrder() {
92202
+ return [...this.channelOrder];
92203
+ }
92204
+ setChannelOrder(order) {
92205
+ this.channelOrder = order.map((id) => id.toLowerCase().trim());
92206
+ }
92207
+ getChannelsByCapability(capability) {
92208
+ return this.getChannels().filter((plugin) => plugin.capabilities.supports[capability] === true);
92209
+ }
92210
+ getChannelsByChatType(chatType) {
92211
+ return this.getChannels().filter((plugin) => plugin.capabilities.chatTypes?.includes(chatType));
92212
+ }
92213
+ hasChannel(channelId) {
92214
+ return this.channels.has(this.normalizeChannelId(channelId));
92215
+ }
92216
+ normalizeChannelId(channelId) {
92217
+ const id = channelId.trim().toLowerCase();
92218
+ const aliases = {
92219
+ tg: "telegram",
92220
+ imsg: "imessage",
92221
+ wa: "whatsapp",
92222
+ dc: "discord",
92223
+ sl: "slack"
92224
+ };
92225
+ return aliases[id] || id;
92226
+ }
92227
+ clear() {
92228
+ this.channels.clear();
92229
+ this.channelOrder = [];
92230
+ }
92231
+ getStats() {
92232
+ return {
92233
+ channelCount: this.channels.size,
92234
+ channels: this.getChannelOrder()
92235
+ };
92236
+ }
92237
+ }
92238
+ var pluginRegistry = new PluginRegistryImpl;
92160
92239
  // ../channel-types/dist/index.js
92161
92240
  function createChannelId(platform, accountId, instanceId) {
92162
92241
  return { platform, accountId, instanceId };
@@ -98838,6 +98917,266 @@ function registerAllCommands(bot, memory, tools) {
98838
98917
  registerInnerthoughtsCommand(bot, memory, tools);
98839
98918
  register(bot, tools);
98840
98919
  }
98920
+
98921
+ // ../channel-telegram/dist/plugin.js
98922
+ var telegramMeta = {
98923
+ label: "Telegram",
98924
+ blurb: "Telegram Bot API for direct and group chats",
98925
+ icon: "\uD83D\uDCF1",
98926
+ docsPath: "/docs/channels/telegram",
98927
+ quickstartAllowFrom: true
98928
+ };
98929
+ var telegramConfigAdapter = {
98930
+ listAccountIds: async (config) => {
98931
+ const botToken = config.botToken;
98932
+ if (!botToken)
98933
+ return [];
98934
+ return ["default"];
98935
+ },
98936
+ resolveAccount: async (config, accountId) => {
98937
+ return {
98938
+ botToken: config.botToken,
98939
+ allowedUsers: config.allowedUsers,
98940
+ allowedChats: config.allowedChats,
98941
+ pollingInterval: config.pollingInterval || 1000
98942
+ };
98943
+ },
98944
+ validateAccount: async (account) => {
98945
+ if (!account.botToken)
98946
+ return false;
98947
+ return true;
98948
+ }
98949
+ };
98950
+ var createOutboundAdapter = (getBot) => ({
98951
+ deliveryMode: "direct",
98952
+ textChunkLimit: 4096,
98953
+ chunker: (text, limit) => {
98954
+ const chunks = [];
98955
+ for (let i = 0;i < text.length; i += limit) {
98956
+ chunks.push(text.slice(i, i + limit));
98957
+ }
98958
+ return { chunks, totalChunks: chunks.length };
98959
+ },
98960
+ sendText: async ({ to, text, accountId, account, replyToId, threadId }) => {
98961
+ const bot = getBot();
98962
+ if (!bot) {
98963
+ return { success: false, error: "Bot not initialized", timestamp: new Date };
98964
+ }
98965
+ try {
98966
+ const chatId = parseInt(to, 10);
98967
+ const options = {
98968
+ parse_mode: "Markdown"
98969
+ };
98970
+ if (replyToId) {
98971
+ options.reply_to_message_id = parseInt(replyToId, 10);
98972
+ }
98973
+ if (threadId) {
98974
+ options.message_thread_id = parseInt(threadId, 10);
98975
+ }
98976
+ const result = await bot.sendMessage(chatId, text, options);
98977
+ return {
98978
+ success: true,
98979
+ messageId: result.message_id.toString(),
98980
+ timestamp: new Date
98981
+ };
98982
+ } catch (error) {
98983
+ return {
98984
+ success: false,
98985
+ error: error.message,
98986
+ timestamp: new Date
98987
+ };
98988
+ }
98989
+ },
98990
+ sendMedia: async ({ to, text, mediaUrl, mediaType, accountId, account }) => {
98991
+ const bot = getBot();
98992
+ if (!bot) {
98993
+ return { success: false, error: "Bot not initialized", timestamp: new Date };
98994
+ }
98995
+ try {
98996
+ const chatId = parseInt(to, 10);
98997
+ let result;
98998
+ if (mediaType === "image") {
98999
+ result = await bot.sendPhoto(chatId, mediaUrl, { caption: text });
99000
+ } else if (mediaType === "video") {
99001
+ result = await bot.sendVideo(chatId, mediaUrl, { caption: text });
99002
+ } else if (mediaType === "audio") {
99003
+ result = await bot.sendAudio(chatId, mediaUrl, { caption: text });
99004
+ } else {
99005
+ result = await bot.sendDocument(chatId, mediaUrl, { caption: text });
99006
+ }
99007
+ return {
99008
+ success: true,
99009
+ messageId: result.message_id.toString(),
99010
+ timestamp: new Date
99011
+ };
99012
+ } catch (error) {
99013
+ return {
99014
+ success: false,
99015
+ error: error.message,
99016
+ timestamp: new Date
99017
+ };
99018
+ }
99019
+ }
99020
+ });
99021
+ var telegramThreadingAdapter = {
99022
+ replyModes: ["quote", "thread"],
99023
+ defaultReplyMode: "quote"
99024
+ };
99025
+ var createGatewayAdapter = (createBot) => {
99026
+ const activeBots = new Map;
99027
+ const messageHandlers = new Map;
99028
+ return {
99029
+ startAccount: async ({ accountId, account, onMessage }) => {
99030
+ const bot = createBot(account.botToken);
99031
+ const handler = (msg) => {
99032
+ if (account.allowedUsers?.length && !account.allowedUsers.includes(msg.from?.id || 0)) {
99033
+ return;
99034
+ }
99035
+ if (account.allowedChats?.length && !account.allowedChats.includes(msg.chat.id)) {
99036
+ return;
99037
+ }
99038
+ const channelId = {
99039
+ platform: "telegram",
99040
+ accountId: msg.chat.id.toString()
99041
+ };
99042
+ const channelMessage = {
99043
+ messageId: msg.message_id.toString(),
99044
+ channelId,
99045
+ timestamp: new Date(msg.date * 1000),
99046
+ sender: {
99047
+ id: msg.from?.id.toString() || "unknown",
99048
+ username: msg.from?.username,
99049
+ displayName: msg.from?.first_name,
99050
+ isBot: msg.from?.is_bot || false
99051
+ },
99052
+ text: msg.text || "",
99053
+ context: {
99054
+ isDM: msg.chat.type === "private",
99055
+ groupName: msg.chat.title,
99056
+ threadId: msg.message_thread_id?.toString()
99057
+ },
99058
+ replyTo: msg.reply_to_message ? {
99059
+ messageId: msg.reply_to_message.message_id.toString(),
99060
+ channelId
99061
+ } : undefined
99062
+ };
99063
+ onMessage(channelMessage);
99064
+ };
99065
+ bot.on("message", handler);
99066
+ activeBots.set(accountId, bot);
99067
+ messageHandlers.set(accountId, handler);
99068
+ },
99069
+ stopAccount: async ({ accountId, account }) => {
99070
+ const bot = activeBots.get(accountId);
99071
+ const handler = messageHandlers.get(accountId);
99072
+ if (bot && handler) {
99073
+ bot.off("message", handler);
99074
+ if (bot.isPolling()) {
99075
+ await bot.stopPolling();
99076
+ }
99077
+ activeBots.delete(accountId);
99078
+ messageHandlers.delete(accountId);
99079
+ }
99080
+ },
99081
+ isAccountActive: (accountId) => {
99082
+ return activeBots.has(accountId);
99083
+ }
99084
+ };
99085
+ };
99086
+ function createTelegramPlugin(config = {}, TelegramBotClass) {
99087
+ let bot = null;
99088
+ const createBot = (token) => {
99089
+ if (!TelegramBotClass) {
99090
+ throw new Error("TelegramBot class not provided");
99091
+ }
99092
+ return new TelegramBotClass(token, { polling: true });
99093
+ };
99094
+ return {
99095
+ id: "telegram",
99096
+ meta: telegramMeta,
99097
+ capabilities: {
99098
+ chatTypes: ["direct", "group", "channel", "thread"],
99099
+ supports: {
99100
+ text: true,
99101
+ media: true,
99102
+ replies: true,
99103
+ threads: true,
99104
+ reactions: true,
99105
+ editing: true,
99106
+ streaming: false,
99107
+ buttons: true
99108
+ },
99109
+ nativeCommands: true,
99110
+ rateLimits: {
99111
+ messagesPerMinute: 30,
99112
+ charactersPerMessage: 4096
99113
+ },
99114
+ media: {
99115
+ maxFileSize: 50 * 1024 * 1024,
99116
+ supportedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"]
99117
+ }
99118
+ },
99119
+ config: telegramConfigAdapter,
99120
+ outbound: createOutboundAdapter(() => bot),
99121
+ gateway: createGatewayAdapter((token) => {
99122
+ bot = createBot(token);
99123
+ return bot;
99124
+ }),
99125
+ threading: telegramThreadingAdapter
99126
+ };
99127
+ }
99128
+ var telegramPlugin = createTelegramPlugin();
99129
+ if (process.env.TELEGRAM_BOT_TOKEN) {
99130
+ pluginRegistry.registerChannel({
99131
+ pluginId: "telegram",
99132
+ plugin: telegramPlugin,
99133
+ source: "core"
99134
+ });
99135
+ }
99136
+ // ../channel-telegram/dist/utils/normalize.js
99137
+ function normalizeTelegramMessagingTarget(raw) {
99138
+ const trimmed = raw.trim();
99139
+ if (!trimmed)
99140
+ return;
99141
+ let normalized = trimmed;
99142
+ if (normalized.startsWith("telegram:")) {
99143
+ normalized = normalized.slice("telegram:".length).trim();
99144
+ } else if (normalized.startsWith("tg:")) {
99145
+ normalized = normalized.slice("tg:".length).trim();
99146
+ }
99147
+ if (!normalized)
99148
+ return;
99149
+ const tmeMatch = /^https?:\/\/t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized) ?? /^t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized);
99150
+ if (tmeMatch?.[1]) {
99151
+ normalized = `@${tmeMatch[1]}`;
99152
+ }
99153
+ if (!normalized)
99154
+ return;
99155
+ return `telegram:${normalized}`.toLowerCase();
99156
+ }
99157
+ function looksLikeTelegramTargetId(raw) {
99158
+ const trimmed = raw.trim();
99159
+ if (!trimmed)
99160
+ return false;
99161
+ if (/^(telegram|tg):/i.test(trimmed))
99162
+ return true;
99163
+ if (trimmed.startsWith("@"))
99164
+ return true;
99165
+ return /^-?\d{6,}$/.test(trimmed);
99166
+ }
99167
+ function extractRawTarget(normalized) {
99168
+ const trimmed = normalized.trim();
99169
+ if (!trimmed.startsWith("telegram:"))
99170
+ return;
99171
+ const raw = trimmed.slice("telegram:".length);
99172
+ return raw || undefined;
99173
+ }
99174
+ function isNumericIdTarget(normalized) {
99175
+ const raw = extractRawTarget(normalized);
99176
+ if (!raw)
99177
+ return false;
99178
+ return /^-?\d+$/.test(raw);
99179
+ }
98841
99180
  // ../channel-telegram/dist/index.js
98842
99181
  class TelegramChannel {
98843
99182
  id;
@@ -99202,6 +99541,41 @@ class GLMTelegramChannel extends BaseChannel {
99202
99541
  isAllowed(userId, chatId) {
99203
99542
  return this.protocolAdapter.isAllowed(userId, chatId);
99204
99543
  }
99544
+ normalizeTarget(raw) {
99545
+ return normalizeTelegramMessagingTarget(raw);
99546
+ }
99547
+ isValidTarget(raw) {
99548
+ return looksLikeTelegramTargetId(raw);
99549
+ }
99550
+ parseTargetFromInput(input) {
99551
+ const words = input.split(/\s+/);
99552
+ for (const word of words) {
99553
+ if (looksLikeTelegramTargetId(word)) {
99554
+ const normalized = normalizeTelegramMessagingTarget(word);
99555
+ if (normalized) {
99556
+ return extractRawTarget(normalized);
99557
+ }
99558
+ }
99559
+ }
99560
+ return;
99561
+ }
99562
+ async sendToTarget(target, text) {
99563
+ const normalized = normalizeTelegramMessagingTarget(target);
99564
+ if (!normalized) {
99565
+ throw new Error(`Invalid Telegram target: ${target}`);
99566
+ }
99567
+ const rawTarget = extractRawTarget(normalized);
99568
+ if (!rawTarget) {
99569
+ throw new Error(`Failed to extract target from: ${normalized}`);
99570
+ }
99571
+ let chatId;
99572
+ if (isNumericIdTarget(normalized)) {
99573
+ chatId = parseInt(rawTarget, 10);
99574
+ } else {
99575
+ throw new Error(`Username targets require resolution. Use numeric chat ID instead, or implement username resolution. Target: ${rawTarget}`);
99576
+ }
99577
+ await this.sendMessage(chatId, text);
99578
+ }
99205
99579
  }
99206
99580
  // src/channels/discord.ts
99207
99581
  var import_discord = __toESM(require_src2(), 1);
package/dist/index.js CHANGED
@@ -97992,6 +97992,86 @@ class StringConversationMemory extends ConversationMemory {
97992
97992
  super(config);
97993
97993
  }
97994
97994
  }
97995
+ // ../channel-types/dist/plugins/registry.js
97996
+ class PluginRegistryImpl {
97997
+ channels = new Map;
97998
+ channelOrder = [];
97999
+ registerChannel(registration) {
98000
+ const id = registration.plugin.id;
98001
+ if (typeof id !== "string") {
98002
+ throw new Error("Channel plugin id must be a string");
98003
+ }
98004
+ const normalizedId = id.trim().toLowerCase();
98005
+ if (this.channels.has(normalizedId)) {
98006
+ console.warn(`[PluginRegistry] Channel "${normalizedId}" already registered, replacing`);
98007
+ }
98008
+ this.channels.set(normalizedId, registration);
98009
+ if (!this.channelOrder.includes(normalizedId)) {
98010
+ this.channelOrder.push(normalizedId);
98011
+ }
98012
+ console.log(`[PluginRegistry] Registered channel: ${normalizedId} (source: ${registration.source})`);
98013
+ }
98014
+ unregisterChannel(channelId) {
98015
+ const normalizedId = channelId.trim().toLowerCase();
98016
+ const existed = this.channels.delete(normalizedId);
98017
+ if (existed) {
98018
+ this.channelOrder = this.channelOrder.filter((id) => id !== normalizedId);
98019
+ }
98020
+ return existed;
98021
+ }
98022
+ getChannel(channelId) {
98023
+ const normalizedId = this.normalizeChannelId(channelId);
98024
+ const registration = this.channels.get(normalizedId);
98025
+ return registration?.plugin;
98026
+ }
98027
+ getChannelRegistration(channelId) {
98028
+ const normalizedId = this.normalizeChannelId(channelId);
98029
+ return this.channels.get(normalizedId);
98030
+ }
98031
+ getChannels() {
98032
+ return this.channelOrder.map((id) => this.channels.get(id)?.plugin).filter((p) => p !== undefined);
98033
+ }
98034
+ getChannelRegistrations() {
98035
+ return this.channelOrder.map((id) => this.channels.get(id)).filter((r) => r !== undefined);
98036
+ }
98037
+ getChannelOrder() {
98038
+ return [...this.channelOrder];
98039
+ }
98040
+ setChannelOrder(order) {
98041
+ this.channelOrder = order.map((id) => id.toLowerCase().trim());
98042
+ }
98043
+ getChannelsByCapability(capability) {
98044
+ return this.getChannels().filter((plugin) => plugin.capabilities.supports[capability] === true);
98045
+ }
98046
+ getChannelsByChatType(chatType) {
98047
+ return this.getChannels().filter((plugin) => plugin.capabilities.chatTypes?.includes(chatType));
98048
+ }
98049
+ hasChannel(channelId) {
98050
+ return this.channels.has(this.normalizeChannelId(channelId));
98051
+ }
98052
+ normalizeChannelId(channelId) {
98053
+ const id = channelId.trim().toLowerCase();
98054
+ const aliases = {
98055
+ tg: "telegram",
98056
+ imsg: "imessage",
98057
+ wa: "whatsapp",
98058
+ dc: "discord",
98059
+ sl: "slack"
98060
+ };
98061
+ return aliases[id] || id;
98062
+ }
98063
+ clear() {
98064
+ this.channels.clear();
98065
+ this.channelOrder = [];
98066
+ }
98067
+ getStats() {
98068
+ return {
98069
+ channelCount: this.channels.size,
98070
+ channels: this.getChannelOrder()
98071
+ };
98072
+ }
98073
+ }
98074
+ var pluginRegistry = new PluginRegistryImpl;
97995
98075
  // ../channel-types/dist/index.js
97996
98076
  function createChannelId(platform, accountId, instanceId) {
97997
98077
  return { platform, accountId, instanceId };
@@ -99032,6 +99112,266 @@ function registerAllCommands(bot, memory, tools) {
99032
99112
  registerInnerthoughtsCommand(bot, memory, tools);
99033
99113
  register(bot, tools);
99034
99114
  }
99115
+
99116
+ // ../channel-telegram/dist/plugin.js
99117
+ var telegramMeta = {
99118
+ label: "Telegram",
99119
+ blurb: "Telegram Bot API for direct and group chats",
99120
+ icon: "\uD83D\uDCF1",
99121
+ docsPath: "/docs/channels/telegram",
99122
+ quickstartAllowFrom: true
99123
+ };
99124
+ var telegramConfigAdapter = {
99125
+ listAccountIds: async (config) => {
99126
+ const botToken = config.botToken;
99127
+ if (!botToken)
99128
+ return [];
99129
+ return ["default"];
99130
+ },
99131
+ resolveAccount: async (config, accountId) => {
99132
+ return {
99133
+ botToken: config.botToken,
99134
+ allowedUsers: config.allowedUsers,
99135
+ allowedChats: config.allowedChats,
99136
+ pollingInterval: config.pollingInterval || 1000
99137
+ };
99138
+ },
99139
+ validateAccount: async (account) => {
99140
+ if (!account.botToken)
99141
+ return false;
99142
+ return true;
99143
+ }
99144
+ };
99145
+ var createOutboundAdapter = (getBot) => ({
99146
+ deliveryMode: "direct",
99147
+ textChunkLimit: 4096,
99148
+ chunker: (text, limit) => {
99149
+ const chunks = [];
99150
+ for (let i = 0;i < text.length; i += limit) {
99151
+ chunks.push(text.slice(i, i + limit));
99152
+ }
99153
+ return { chunks, totalChunks: chunks.length };
99154
+ },
99155
+ sendText: async ({ to, text, accountId, account, replyToId, threadId }) => {
99156
+ const bot = getBot();
99157
+ if (!bot) {
99158
+ return { success: false, error: "Bot not initialized", timestamp: new Date };
99159
+ }
99160
+ try {
99161
+ const chatId = parseInt(to, 10);
99162
+ const options = {
99163
+ parse_mode: "Markdown"
99164
+ };
99165
+ if (replyToId) {
99166
+ options.reply_to_message_id = parseInt(replyToId, 10);
99167
+ }
99168
+ if (threadId) {
99169
+ options.message_thread_id = parseInt(threadId, 10);
99170
+ }
99171
+ const result = await bot.sendMessage(chatId, text, options);
99172
+ return {
99173
+ success: true,
99174
+ messageId: result.message_id.toString(),
99175
+ timestamp: new Date
99176
+ };
99177
+ } catch (error) {
99178
+ return {
99179
+ success: false,
99180
+ error: error.message,
99181
+ timestamp: new Date
99182
+ };
99183
+ }
99184
+ },
99185
+ sendMedia: async ({ to, text, mediaUrl, mediaType, accountId, account }) => {
99186
+ const bot = getBot();
99187
+ if (!bot) {
99188
+ return { success: false, error: "Bot not initialized", timestamp: new Date };
99189
+ }
99190
+ try {
99191
+ const chatId = parseInt(to, 10);
99192
+ let result;
99193
+ if (mediaType === "image") {
99194
+ result = await bot.sendPhoto(chatId, mediaUrl, { caption: text });
99195
+ } else if (mediaType === "video") {
99196
+ result = await bot.sendVideo(chatId, mediaUrl, { caption: text });
99197
+ } else if (mediaType === "audio") {
99198
+ result = await bot.sendAudio(chatId, mediaUrl, { caption: text });
99199
+ } else {
99200
+ result = await bot.sendDocument(chatId, mediaUrl, { caption: text });
99201
+ }
99202
+ return {
99203
+ success: true,
99204
+ messageId: result.message_id.toString(),
99205
+ timestamp: new Date
99206
+ };
99207
+ } catch (error) {
99208
+ return {
99209
+ success: false,
99210
+ error: error.message,
99211
+ timestamp: new Date
99212
+ };
99213
+ }
99214
+ }
99215
+ });
99216
+ var telegramThreadingAdapter = {
99217
+ replyModes: ["quote", "thread"],
99218
+ defaultReplyMode: "quote"
99219
+ };
99220
+ var createGatewayAdapter = (createBot) => {
99221
+ const activeBots = new Map;
99222
+ const messageHandlers = new Map;
99223
+ return {
99224
+ startAccount: async ({ accountId, account, onMessage }) => {
99225
+ const bot = createBot(account.botToken);
99226
+ const handler = (msg) => {
99227
+ if (account.allowedUsers?.length && !account.allowedUsers.includes(msg.from?.id || 0)) {
99228
+ return;
99229
+ }
99230
+ if (account.allowedChats?.length && !account.allowedChats.includes(msg.chat.id)) {
99231
+ return;
99232
+ }
99233
+ const channelId = {
99234
+ platform: "telegram",
99235
+ accountId: msg.chat.id.toString()
99236
+ };
99237
+ const channelMessage = {
99238
+ messageId: msg.message_id.toString(),
99239
+ channelId,
99240
+ timestamp: new Date(msg.date * 1000),
99241
+ sender: {
99242
+ id: msg.from?.id.toString() || "unknown",
99243
+ username: msg.from?.username,
99244
+ displayName: msg.from?.first_name,
99245
+ isBot: msg.from?.is_bot || false
99246
+ },
99247
+ text: msg.text || "",
99248
+ context: {
99249
+ isDM: msg.chat.type === "private",
99250
+ groupName: msg.chat.title,
99251
+ threadId: msg.message_thread_id?.toString()
99252
+ },
99253
+ replyTo: msg.reply_to_message ? {
99254
+ messageId: msg.reply_to_message.message_id.toString(),
99255
+ channelId
99256
+ } : undefined
99257
+ };
99258
+ onMessage(channelMessage);
99259
+ };
99260
+ bot.on("message", handler);
99261
+ activeBots.set(accountId, bot);
99262
+ messageHandlers.set(accountId, handler);
99263
+ },
99264
+ stopAccount: async ({ accountId, account }) => {
99265
+ const bot = activeBots.get(accountId);
99266
+ const handler = messageHandlers.get(accountId);
99267
+ if (bot && handler) {
99268
+ bot.off("message", handler);
99269
+ if (bot.isPolling()) {
99270
+ await bot.stopPolling();
99271
+ }
99272
+ activeBots.delete(accountId);
99273
+ messageHandlers.delete(accountId);
99274
+ }
99275
+ },
99276
+ isAccountActive: (accountId) => {
99277
+ return activeBots.has(accountId);
99278
+ }
99279
+ };
99280
+ };
99281
+ function createTelegramPlugin(config = {}, TelegramBotClass) {
99282
+ let bot = null;
99283
+ const createBot = (token) => {
99284
+ if (!TelegramBotClass) {
99285
+ throw new Error("TelegramBot class not provided");
99286
+ }
99287
+ return new TelegramBotClass(token, { polling: true });
99288
+ };
99289
+ return {
99290
+ id: "telegram",
99291
+ meta: telegramMeta,
99292
+ capabilities: {
99293
+ chatTypes: ["direct", "group", "channel", "thread"],
99294
+ supports: {
99295
+ text: true,
99296
+ media: true,
99297
+ replies: true,
99298
+ threads: true,
99299
+ reactions: true,
99300
+ editing: true,
99301
+ streaming: false,
99302
+ buttons: true
99303
+ },
99304
+ nativeCommands: true,
99305
+ rateLimits: {
99306
+ messagesPerMinute: 30,
99307
+ charactersPerMessage: 4096
99308
+ },
99309
+ media: {
99310
+ maxFileSize: 50 * 1024 * 1024,
99311
+ supportedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"]
99312
+ }
99313
+ },
99314
+ config: telegramConfigAdapter,
99315
+ outbound: createOutboundAdapter(() => bot),
99316
+ gateway: createGatewayAdapter((token) => {
99317
+ bot = createBot(token);
99318
+ return bot;
99319
+ }),
99320
+ threading: telegramThreadingAdapter
99321
+ };
99322
+ }
99323
+ var telegramPlugin = createTelegramPlugin();
99324
+ if (process.env.TELEGRAM_BOT_TOKEN) {
99325
+ pluginRegistry.registerChannel({
99326
+ pluginId: "telegram",
99327
+ plugin: telegramPlugin,
99328
+ source: "core"
99329
+ });
99330
+ }
99331
+ // ../channel-telegram/dist/utils/normalize.js
99332
+ function normalizeTelegramMessagingTarget(raw) {
99333
+ const trimmed = raw.trim();
99334
+ if (!trimmed)
99335
+ return;
99336
+ let normalized = trimmed;
99337
+ if (normalized.startsWith("telegram:")) {
99338
+ normalized = normalized.slice("telegram:".length).trim();
99339
+ } else if (normalized.startsWith("tg:")) {
99340
+ normalized = normalized.slice("tg:".length).trim();
99341
+ }
99342
+ if (!normalized)
99343
+ return;
99344
+ const tmeMatch = /^https?:\/\/t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized) ?? /^t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized);
99345
+ if (tmeMatch?.[1]) {
99346
+ normalized = `@${tmeMatch[1]}`;
99347
+ }
99348
+ if (!normalized)
99349
+ return;
99350
+ return `telegram:${normalized}`.toLowerCase();
99351
+ }
99352
+ function looksLikeTelegramTargetId(raw) {
99353
+ const trimmed = raw.trim();
99354
+ if (!trimmed)
99355
+ return false;
99356
+ if (/^(telegram|tg):/i.test(trimmed))
99357
+ return true;
99358
+ if (trimmed.startsWith("@"))
99359
+ return true;
99360
+ return /^-?\d{6,}$/.test(trimmed);
99361
+ }
99362
+ function extractRawTarget(normalized) {
99363
+ const trimmed = normalized.trim();
99364
+ if (!trimmed.startsWith("telegram:"))
99365
+ return;
99366
+ const raw = trimmed.slice("telegram:".length);
99367
+ return raw || undefined;
99368
+ }
99369
+ function isNumericIdTarget(normalized) {
99370
+ const raw = extractRawTarget(normalized);
99371
+ if (!raw)
99372
+ return false;
99373
+ return /^-?\d+$/.test(raw);
99374
+ }
99035
99375
  // ../channel-telegram/dist/index.js
99036
99376
  class TelegramChannel {
99037
99377
  id;
@@ -99396,6 +99736,41 @@ class GLMTelegramChannel extends BaseChannel {
99396
99736
  isAllowed(userId, chatId) {
99397
99737
  return this.protocolAdapter.isAllowed(userId, chatId);
99398
99738
  }
99739
+ normalizeTarget(raw) {
99740
+ return normalizeTelegramMessagingTarget(raw);
99741
+ }
99742
+ isValidTarget(raw) {
99743
+ return looksLikeTelegramTargetId(raw);
99744
+ }
99745
+ parseTargetFromInput(input) {
99746
+ const words = input.split(/\s+/);
99747
+ for (const word of words) {
99748
+ if (looksLikeTelegramTargetId(word)) {
99749
+ const normalized = normalizeTelegramMessagingTarget(word);
99750
+ if (normalized) {
99751
+ return extractRawTarget(normalized);
99752
+ }
99753
+ }
99754
+ }
99755
+ return;
99756
+ }
99757
+ async sendToTarget(target, text) {
99758
+ const normalized = normalizeTelegramMessagingTarget(target);
99759
+ if (!normalized) {
99760
+ throw new Error(`Invalid Telegram target: ${target}`);
99761
+ }
99762
+ const rawTarget = extractRawTarget(normalized);
99763
+ if (!rawTarget) {
99764
+ throw new Error(`Failed to extract target from: ${normalized}`);
99765
+ }
99766
+ let chatId;
99767
+ if (isNumericIdTarget(normalized)) {
99768
+ chatId = parseInt(rawTarget, 10);
99769
+ } else {
99770
+ throw new Error(`Username targets require resolution. Use numeric chat ID instead, or implement username resolution. Target: ${rawTarget}`);
99771
+ }
99772
+ await this.sendMessage(chatId, text);
99773
+ }
99399
99774
  }
99400
99775
  // src/channels/discord.ts
99401
99776
  var import_discord = __toESM(require_src2(), 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/glm-daemon",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Autonomous GLM 4.7 daemon with hooks, tools, teammates, and communication channels (Telegram, Discord)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -63,10 +63,10 @@
63
63
  "zod": "^4.3.5"
64
64
  },
65
65
  "devDependencies": {
66
- "@types/bun": "catalog:dev",
67
- "@types/node": "catalog:dev",
68
- "typescript": "catalog:",
69
- "bun-types": "catalog:dev"
66
+ "@types/bun": "latest",
67
+ "@types/node": "latest",
68
+ "typescript": "latest",
69
+ "bun-types": "latest"
70
70
  },
71
71
  "files": [
72
72
  "dist",
@@ -12,12 +12,27 @@ import {
12
12
  import {
13
13
  TelegramChannel as TelegramProtocolAdapter,
14
14
  type TelegramConfig as ProtocolConfig,
15
+ // Telegram target normalization utilities
16
+ normalizeTelegramMessagingTarget,
17
+ looksLikeTelegramTargetId,
18
+ extractRawTarget,
19
+ isUsernameTarget,
20
+ isNumericIdTarget,
15
21
  } from "@ebowwa/channel-telegram";
16
22
  import {
17
23
  BaseChannel,
18
24
  type GLMChannelConfig,
19
25
  } from "./base.js";
20
26
 
27
+ // Re-export normalization utilities for consumers
28
+ export {
29
+ normalizeTelegramMessagingTarget,
30
+ looksLikeTelegramTargetId,
31
+ extractRawTarget,
32
+ isUsernameTarget,
33
+ isNumericIdTarget,
34
+ };
35
+
21
36
  // ============================================================
22
37
  // TELEGRAM CHANNEL CONFIG
23
38
  // ============================================================
@@ -163,6 +178,96 @@ export class GLMTelegramChannel extends BaseChannel {
163
178
  isAllowed(userId?: number, chatId?: number): boolean {
164
179
  return this.protocolAdapter.isAllowed(userId, chatId);
165
180
  }
181
+
182
+ // ============================================================
183
+ // Target Normalization Helpers
184
+ // ============================================================
185
+
186
+ /**
187
+ * Normalize a Telegram target (username, URL, or ID) to standard format.
188
+ *
189
+ * @param raw - Raw input (@username, t.me/user, 123456, etc.)
190
+ * @returns Normalized target or undefined if invalid
191
+ *
192
+ * @example
193
+ * channel.normalizeTarget("@durov") // "telegram:@durov"
194
+ * channel.normalizeTarget("t.me/durov") // "telegram:@durov"
195
+ * channel.normalizeTarget("123456") // "telegram:123456"
196
+ */
197
+ normalizeTarget(raw: string): string | undefined {
198
+ return normalizeTelegramMessagingTarget(raw);
199
+ }
200
+
201
+ /**
202
+ * Check if a string looks like a Telegram target.
203
+ *
204
+ * @param raw - Raw input string
205
+ * @returns true if it looks like a Telegram target
206
+ */
207
+ isValidTarget(raw: string): boolean {
208
+ return looksLikeTelegramTargetId(raw);
209
+ }
210
+
211
+ /**
212
+ * Parse a target from user input and extract the usable form.
213
+ *
214
+ * @param input - User input like "send to @user" or "dm t.me/user"
215
+ * @returns Extracted raw target (@username or numeric ID) or undefined
216
+ *
217
+ * @example
218
+ * channel.parseTargetFromInput("send to @durov") // "@durov"
219
+ * channel.parseTargetFromInput("dm 123456") // "123456"
220
+ */
221
+ parseTargetFromInput(input: string): string | undefined {
222
+ const words = input.split(/\s+/);
223
+ for (const word of words) {
224
+ if (looksLikeTelegramTargetId(word)) {
225
+ const normalized = normalizeTelegramMessagingTarget(word);
226
+ if (normalized) {
227
+ return extractRawTarget(normalized);
228
+ }
229
+ }
230
+ }
231
+ return undefined;
232
+ }
233
+
234
+ /**
235
+ * Send a message to a target (username or chat ID).
236
+ *
237
+ * @param target - Telegram target (@username, numeric ID, or normalized format)
238
+ * @param text - Message text
239
+ *
240
+ * @example
241
+ * await channel.sendToTarget("@durov", "Hello!")
242
+ * await channel.sendToTarget("123456", "Hello!")
243
+ */
244
+ async sendToTarget(target: string, text: string): Promise<void> {
245
+ const normalized = normalizeTelegramMessagingTarget(target);
246
+ if (!normalized) {
247
+ throw new Error(`Invalid Telegram target: ${target}`);
248
+ }
249
+
250
+ const rawTarget = extractRawTarget(normalized);
251
+ if (!rawTarget) {
252
+ throw new Error(`Failed to extract target from: ${normalized}`);
253
+ }
254
+
255
+ // Parse chat ID (numeric or resolve username)
256
+ let chatId: number;
257
+
258
+ if (isNumericIdTarget(normalized)) {
259
+ chatId = parseInt(rawTarget, 10);
260
+ } else {
261
+ // Username target - need to resolve via bot API
262
+ // Note: This requires the bot to have seen the user/channel before
263
+ // For now, we'll try direct message with username (may fail if not cached)
264
+ throw new Error(
265
+ `Username targets require resolution. Use numeric chat ID instead, or implement username resolution. Target: ${rawTarget}`
266
+ );
267
+ }
268
+
269
+ await this.sendMessage(chatId, text);
270
+ }
166
271
  }
167
272
 
168
273
  // ============================================================