create-bunspace 0.2.5 → 0.3.1

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 (80) hide show
  1. package/dist/templates/monorepo/apps/example/package.json +1 -1
  2. package/dist/templates/monorepo/core/packages/utils/rolldown.config.ts +30 -0
  3. package/dist/templates/monorepo/tsconfig.json +3 -1
  4. package/dist/templates/telegram-bot/CLAUDE.deploy.md +2 -3
  5. package/dist/templates/telegram-bot/CLAUDE.dev.md +304 -5
  6. package/dist/templates/telegram-bot/CLAUDE.md +166 -89
  7. package/dist/templates/telegram-bot/README.md +252 -129
  8. package/dist/templates/telegram-bot/bun.lock +132 -3
  9. package/dist/templates/telegram-bot/core/.env.example +6 -0
  10. package/dist/templates/telegram-bot/core/package.json +11 -0
  11. package/dist/templates/telegram-bot/core/rolldown.config.ts +11 -0
  12. package/dist/templates/telegram-bot/core/src/config/env.ts +130 -1
  13. package/dist/templates/telegram-bot/core/src/config/logging.ts +4 -4
  14. package/dist/templates/telegram-bot/core/src/handlers/config-export.ts +123 -0
  15. package/dist/templates/telegram-bot/core/src/handlers/control.ts +46 -11
  16. package/dist/templates/telegram-bot/core/src/handlers/demo-full.ts +58 -0
  17. package/dist/templates/telegram-bot/core/src/handlers/demo-keyboard.ts +49 -0
  18. package/dist/templates/telegram-bot/core/src/handlers/demo-media.ts +163 -0
  19. package/dist/templates/telegram-bot/core/src/handlers/demo-text.ts +27 -0
  20. package/dist/templates/telegram-bot/core/src/handlers/health.ts +40 -37
  21. package/dist/templates/telegram-bot/core/src/handlers/info.ts +189 -0
  22. package/dist/templates/telegram-bot/core/src/handlers/listener.ts +168 -0
  23. package/dist/templates/telegram-bot/core/src/handlers/logs.ts +16 -7
  24. package/dist/templates/telegram-bot/core/src/index.ts +49 -1
  25. package/dist/templates/telegram-bot/core/src/utils/formatters.ts +14 -33
  26. package/dist/templates/telegram-bot/core/src/utils/instance-manager.ts +6 -2
  27. package/dist/templates/telegram-bot/core/src/utils/message-builder.ts +180 -0
  28. package/dist/templates/telegram-bot/core/tsconfig.json +2 -0
  29. package/dist/templates/telegram-bot/docs/automatizacion_integral_de_bots_de_telegram_con_type_script.md +326 -0
  30. package/dist/templates/telegram-bot/docs/cli-commands.md +514 -5
  31. package/dist/templates/telegram-bot/docs/environment.md +191 -3
  32. package/dist/templates/telegram-bot/docs/getting-started.md +202 -15
  33. package/dist/templates/telegram-bot/package.json +7 -3
  34. package/dist/templates/telegram-bot/packages/utils/package.json +12 -1
  35. package/dist/templates/telegram-bot/packages/utils/rolldown.config.ts +11 -0
  36. package/dist/templates/telegram-bot/packages/utils/src/logger.ts +1 -0
  37. package/dist/templates/telegram-bot/packages/utils/tsconfig.json +10 -0
  38. package/dist/templates/telegram-bot/tools/commands/doctor.ts +62 -0
  39. package/dist/templates/telegram-bot/tools/commands/setup.ts +984 -170
  40. package/dist/templates/telegram-bot/tsconfig.json +7 -2
  41. package/package.json +1 -1
  42. package/templates/monorepo/apps/example/package.json +1 -1
  43. package/templates/monorepo/core/packages/utils/rolldown.config.ts +30 -0
  44. package/templates/monorepo/tsconfig.json +3 -1
  45. package/templates/telegram-bot/CLAUDE.deploy.md +2 -3
  46. package/templates/telegram-bot/CLAUDE.dev.md +304 -5
  47. package/templates/telegram-bot/CLAUDE.md +166 -89
  48. package/templates/telegram-bot/README.md +252 -129
  49. package/templates/telegram-bot/bun.lock +132 -3
  50. package/templates/telegram-bot/core/.env.example +6 -0
  51. package/templates/telegram-bot/core/package.json +11 -0
  52. package/templates/telegram-bot/core/rolldown.config.ts +11 -0
  53. package/templates/telegram-bot/core/src/config/env.ts +130 -1
  54. package/templates/telegram-bot/core/src/config/logging.ts +4 -4
  55. package/templates/telegram-bot/core/src/handlers/config-export.ts +123 -0
  56. package/templates/telegram-bot/core/src/handlers/control.ts +46 -11
  57. package/templates/telegram-bot/core/src/handlers/demo-full.ts +58 -0
  58. package/templates/telegram-bot/core/src/handlers/demo-keyboard.ts +49 -0
  59. package/templates/telegram-bot/core/src/handlers/demo-media.ts +163 -0
  60. package/templates/telegram-bot/core/src/handlers/demo-text.ts +27 -0
  61. package/templates/telegram-bot/core/src/handlers/health.ts +40 -37
  62. package/templates/telegram-bot/core/src/handlers/info.ts +189 -0
  63. package/templates/telegram-bot/core/src/handlers/listener.ts +168 -0
  64. package/templates/telegram-bot/core/src/handlers/logs.ts +16 -7
  65. package/templates/telegram-bot/core/src/index.ts +49 -1
  66. package/templates/telegram-bot/core/src/utils/formatters.ts +14 -33
  67. package/templates/telegram-bot/core/src/utils/instance-manager.ts +6 -2
  68. package/templates/telegram-bot/core/tsconfig.json +2 -0
  69. package/templates/telegram-bot/docs/automatizacion_integral_de_bots_de_telegram_con_type_script.md +326 -0
  70. package/templates/telegram-bot/docs/cli-commands.md +514 -5
  71. package/templates/telegram-bot/docs/environment.md +191 -3
  72. package/templates/telegram-bot/docs/getting-started.md +202 -15
  73. package/templates/telegram-bot/package.json +7 -3
  74. package/templates/telegram-bot/packages/utils/package.json +12 -1
  75. package/templates/telegram-bot/packages/utils/rolldown.config.ts +11 -0
  76. package/templates/telegram-bot/packages/utils/src/logger.ts +1 -0
  77. package/templates/telegram-bot/packages/utils/tsconfig.json +10 -0
  78. package/templates/telegram-bot/tools/commands/doctor.ts +62 -0
  79. package/templates/telegram-bot/tools/commands/setup.ts +984 -170
  80. package/templates/telegram-bot/tsconfig.json +7 -2
@@ -0,0 +1,189 @@
1
+ import type { Context } from 'telegraf'
2
+ import { infoLogger, badge, kv, colors, colorText } from '../middleware/logging.js'
3
+ import { TelegramMessageBuilder } from '@mks2508/telegram-message-builder'
4
+
5
+ export async function handleGetInfo(ctx: Context): Promise<void> {
6
+ const userId = ctx.from?.id ?? 'unknown'
7
+ const botUsername = ctx.me
8
+
9
+ infoLogger.info(
10
+ `${badge('INFO', 'rounded')} ${kv({
11
+ cmd: '/getinfo',
12
+ user: colorText(String(userId), colors.user),
13
+ })}`
14
+ )
15
+
16
+ const msg = ctx.message
17
+ const from = ctx.from
18
+ const chat = ctx.chat
19
+
20
+ const builder = TelegramMessageBuilder.text()
21
+ .title('📋 Your Information')
22
+ .newline()
23
+
24
+ // User info
25
+ if (from) {
26
+ builder.section('👤 User Info:')
27
+ .line('User ID', String(from.id), { code: true })
28
+ if (from.username) builder.line('Username', `@${from.username}`)
29
+ if (from.first_name) builder.line('First Name', from.first_name)
30
+ if (from.last_name) builder.line('Last Name', from.last_name)
31
+ if (from.language_code) builder.line('Language', from.language_code)
32
+ if (from.is_premium) builder.line('Premium', 'Yes')
33
+ builder.newline()
34
+ }
35
+
36
+ // Chat info
37
+ if (chat) {
38
+ builder.section('đŸ’Ŧ Chat Info:')
39
+ .line('Chat ID', String(chat.id), { code: true })
40
+ .line('Type', chat.type)
41
+ if ('title' in chat && chat.title) builder.line('Title', chat.title)
42
+ if ('username' in chat && chat.username) builder.line('Username', `@${chat.username}`)
43
+ if (chat.type === 'supergroup' || chat.type === 'group') {
44
+ builder.line('Chat ID (for config)', String(chat.id), { code: true })
45
+ }
46
+ builder.newline()
47
+ }
48
+
49
+ // Bot mention detection
50
+ if (msg && 'entities' in msg && botUsername) {
51
+ const botMention = detectBotMention(msg as unknown as MaybeMessage, botUsername)
52
+ if (botMention.isMentioned) {
53
+ builder.section('🤖 Bot Mention:')
54
+ .line('Bot mentioned', 'Yes')
55
+ if (botMention.type) builder.line('Mention type', botMention.type)
56
+ if (botMention.replyToBot) builder.text('Replying to bot message')
57
+ builder.newline()
58
+ infoLogger.info(`Bot mentioned in chat ${chat?.id} by user ${userId}`)
59
+ }
60
+ }
61
+
62
+ // Message/Topic info
63
+ if (msg) {
64
+ builder.section('📮 Message Info:')
65
+ .line('Message ID', String(msg.message_id))
66
+ if ('reply_to_message' in msg && msg.reply_to_message) {
67
+ const replyTo = msg.reply_to_message
68
+ builder.line('Reply to message ID', String(replyTo.message_id))
69
+ }
70
+ }
71
+
72
+ // Thread/Topic info (from forum topics)
73
+ const threadId = getThreadId(msg as unknown as MaybeMessage)
74
+ if (threadId) {
75
+ builder.newline()
76
+ .section('đŸ§ĩ Thread/Topic:')
77
+ .line('Thread ID', String(threadId), { code: true })
78
+ .text('This message is in a topic')
79
+ .newline()
80
+ infoLogger.info(`Message sent in thread ${threadId} by user ${userId}`)
81
+ }
82
+
83
+ // Configuration tips
84
+ builder.newline()
85
+ .section('🔧 Configuration:')
86
+ .line('TG_CONTROL_CHAT_ID', String(chat?.id ?? 'N/A'), { code: true })
87
+ .line('TG_AUTHORIZED_USER_IDS', String(from?.id ?? 'N/A'))
88
+ if (threadId) {
89
+ builder.line('TG_CONTROL_TOPIC_ID', String(threadId), { code: true })
90
+ }
91
+
92
+ builder.newline()
93
+ .text('💡 Copy these values to your .env file')
94
+ .newline()
95
+ .newline()
96
+ .text('💡 Tip: In groups, you can also mention the bot with @username to get this info')
97
+
98
+ const message = builder.build()
99
+ infoLogger.info(`Replying to user ${userId} with info`)
100
+ await ctx.reply(message.text || '', { parse_mode: (message.parse_mode || 'HTML') as any })
101
+ infoLogger.success(`Info sent to user ${userId}`)
102
+ }
103
+
104
+ interface BotMentionResult {
105
+ isMentioned: boolean
106
+ type?: 'mention' | 'text_mention' | 'reply'
107
+ replyToBot?: boolean
108
+ }
109
+
110
+ function detectBotMention(msg: MaybeMessage, botUsername: string): BotMentionResult {
111
+ const result: BotMentionResult = {
112
+ isMentioned: false,
113
+ }
114
+
115
+ // Check if replying to a bot message
116
+ if ('reply_to_message' in msg && msg.reply_to_message) {
117
+ const replyTo = msg.reply_to_message
118
+ if ('from' in replyTo && replyTo.from?.id) {
119
+ result.replyToBot = true
120
+ result.isMentioned = true
121
+ result.type = 'reply'
122
+ return result
123
+ }
124
+ }
125
+
126
+ // Check for @mention entities
127
+ if ('entities' in msg && msg.entities && 'text' in msg && msg.text) {
128
+ const text = msg.text
129
+ for (const entity of msg.entities) {
130
+ if ('type' in entity && entity.type === 'mention') {
131
+ const mention = text.substring(entity.offset, entity.offset + entity.length)
132
+ if (mention === `@${botUsername}`) {
133
+ result.isMentioned = true
134
+ result.type = 'mention'
135
+ return result
136
+ }
137
+ }
138
+ if ('type' in entity && entity.type === 'text_mention') {
139
+ result.isMentioned = true
140
+ result.type = 'text_mention'
141
+ return result
142
+ }
143
+ }
144
+ }
145
+
146
+ return result
147
+ }
148
+
149
+ /**
150
+ * Get thread ID from a message
151
+ * Checks multiple possible locations where thread_id can be stored
152
+ */
153
+ function getThreadId(msg: MaybeMessage): number | undefined {
154
+ if (!msg) return undefined
155
+
156
+ // Direct thread_id property (forum topics)
157
+ if ('thread_id' in msg && typeof msg.thread_id === 'number') {
158
+ return msg.thread_id
159
+ }
160
+
161
+ // message_thread_id (alternative property)
162
+ if ('message_thread_id' in msg && typeof msg.message_thread_id === 'number') {
163
+ return msg.message_thread_id
164
+ }
165
+
166
+ // Check in reply_to_message
167
+ if ('reply_to_message' in msg && msg.reply_to_message) {
168
+ const replyTo = msg.reply_to_message
169
+
170
+ if ('thread_id' in replyTo && typeof replyTo.thread_id === 'number') {
171
+ return replyTo.thread_id
172
+ }
173
+
174
+ if ('message_thread_id' in replyTo && typeof replyTo.message_thread_id === 'number') {
175
+ return replyTo.message_thread_id
176
+ }
177
+ }
178
+
179
+ return undefined
180
+ }
181
+
182
+ // Type for message objects that might have various properties
183
+ type MaybeMessage = Record<string, unknown> & {
184
+ reply_to_message?: MaybeMessage & { from?: { id?: number } }
185
+ entities?: Array<{ type?: string; offset: number; length: number }>
186
+ text?: string
187
+ thread_id?: number
188
+ message_thread_id?: number
189
+ }
@@ -0,0 +1,168 @@
1
+ import type { Context } from 'telegraf'
2
+ import { success as logSuccess, error as logError } from '@mks2508/better-logger'
3
+
4
+ /**
5
+ * Collected IDs from listener mode
6
+ */
7
+ export interface CollectedIds {
8
+ authorizedUserIds: Set<number>
9
+ controlChatId: number | null
10
+ controlTopicId: number | null
11
+ logChatId: number | null
12
+ logTopicId: number | null
13
+ messagesCount: number
14
+ }
15
+
16
+ /**
17
+ * Get thread ID from a message
18
+ * Checks multiple possible locations where thread_id can be stored
19
+ */
20
+ function getThreadId(msg: MaybeMessage): number | undefined {
21
+ if (!msg) return undefined
22
+
23
+ // Direct thread_id property (forum topics)
24
+ if ('thread_id' in msg && typeof msg.thread_id === 'number') {
25
+ return msg.thread_id
26
+ }
27
+
28
+ // message_thread_id (alternative property)
29
+ if ('message_thread_id' in msg && typeof msg.message_thread_id === 'number') {
30
+ return msg.message_thread_id
31
+ }
32
+
33
+ // Check in reply_to_message
34
+ if ('reply_to_message' in msg && msg.reply_to_message) {
35
+ const replyTo = msg.reply_to_message
36
+
37
+ if ('thread_id' in replyTo && typeof replyTo.thread_id === 'number') {
38
+ return replyTo.thread_id
39
+ }
40
+
41
+ if ('message_thread_id' in replyTo && typeof replyTo.message_thread_id === 'number') {
42
+ return replyTo.message_thread_id
43
+ }
44
+ }
45
+
46
+ return undefined
47
+ }
48
+
49
+ // Type for message objects that might have various properties
50
+ type MaybeMessage = Record<string, unknown> & {
51
+ reply_to_message?: MaybeMessage & { from?: { id?: number } }
52
+ entities?: Array<{ type?: string; offset: number; length: number }>
53
+ text?: string
54
+ thread_id?: number
55
+ message_thread_id?: number
56
+ }
57
+
58
+ /**
59
+ * Handle messages in listener mode
60
+ * Captures User IDs, Chat IDs, and Topic IDs from incoming messages
61
+ */
62
+ export function handleListenerMessage(
63
+ ctx: Context,
64
+ collected: CollectedIds,
65
+ onStartCallback?: (info: string) => void,
66
+ onCaptureCallback?: (type: string, value: number | string) => void
67
+ ): void {
68
+ const msg = ctx.message
69
+ const from = ctx.from
70
+ const chat = ctx.chat
71
+
72
+ if (!msg) {
73
+ return
74
+ }
75
+
76
+ collected.messagesCount++
77
+
78
+ // Capture User ID (from any message)
79
+ if (from) {
80
+ const isNew = collected.authorizedUserIds.add(from.id)
81
+ if (isNew && onCaptureCallback) {
82
+ onCaptureCallback('User ID', from.id)
83
+ logSuccess(`Captured User ID: ${from.id}`)
84
+ }
85
+ }
86
+
87
+ // Capture Chat ID (from group/supergroup/channel messages)
88
+ if (chat && chat.type !== 'private') {
89
+ const isNew = collected.controlChatId !== chat.id
90
+ collected.controlChatId = chat.id
91
+ if (isNew && onCaptureCallback) {
92
+ onCaptureCallback('Chat ID', chat.id)
93
+ logSuccess(`Captured Chat ID: ${chat.id} (${chat.type})`)
94
+ }
95
+ }
96
+
97
+ // Capture Topic ID (from forum topics)
98
+ const threadId = getThreadId(msg as unknown as MaybeMessage)
99
+ if (threadId) {
100
+ // If we have a control chat but no control topic yet, this is likely the control topic
101
+ if (collected.controlChatId && !collected.controlTopicId) {
102
+ collected.controlTopicId = threadId
103
+ if (onCaptureCallback) {
104
+ onCaptureCallback('Control Topic ID', threadId)
105
+ logSuccess(`Captured Control Topic ID: ${threadId}`)
106
+ }
107
+ } else if (collected.controlChatId && collected.controlTopicId && threadId !== collected.controlTopicId) {
108
+ // If we already have a control topic, this might be the log topic
109
+ collected.logTopicId = threadId
110
+ collected.logChatId = collected.controlChatId
111
+ if (onCaptureCallback) {
112
+ onCaptureCallback('Log Topic ID', threadId)
113
+ logSuccess(`Captured Log Topic ID: ${threadId}`)
114
+ }
115
+ }
116
+ }
117
+
118
+ // Send acknowledgment back to user
119
+ const summary = buildListenerSummary(collected)
120
+ ctx.reply(summary, { parse_mode: 'HTML' }).catch((error) => {
121
+ logError('Failed to send listener summary:', error)
122
+ })
123
+ }
124
+
125
+ /**
126
+ * Build a summary message of captured IDs
127
+ */
128
+ function buildListenerSummary(collected: CollectedIds): string {
129
+ const parts: string[] = []
130
+
131
+ parts.push('<b>🔧 Auto-Configure Listener</b>')
132
+ parts.push('')
133
+
134
+ if (collected.authorizedUserIds.size > 0) {
135
+ parts.push(`<b>👤 User IDs:</b> <code>${[...collected.authorizedUserIds].join(', ')}</code>`)
136
+ }
137
+
138
+ if (collected.controlChatId) {
139
+ parts.push(`<b>đŸ’Ŧ Chat ID:</b> <code>${collected.controlChatId}</code>`)
140
+ }
141
+
142
+ if (collected.controlTopicId) {
143
+ parts.push(`<b>đŸ§ĩ Control Topic ID:</b> <code>${collected.controlTopicId}</code>`)
144
+ }
145
+
146
+ if (collected.logTopicId) {
147
+ parts.push(`<b>📋 Log Topic ID:</b> <code>${collected.logTopicId}</code>`)
148
+ }
149
+
150
+ parts.push('')
151
+ parts.push('<i>Send more messages from different contexts to capture more IDs...</i>')
152
+
153
+ return parts.join('\n')
154
+ }
155
+
156
+ /**
157
+ * Create initial collected IDs object
158
+ */
159
+ export function createInitialCollectedIds(): CollectedIds {
160
+ return {
161
+ authorizedUserIds: new Set<number>(),
162
+ controlChatId: null,
163
+ controlTopicId: null,
164
+ logChatId: null,
165
+ logTopicId: null,
166
+ messagesCount: 0,
167
+ }
168
+ }
@@ -2,6 +2,7 @@ import type { Context, Telegraf } from 'telegraf'
2
2
  import { getConfig, hasLoggingConfigured } from '../config/index.js'
3
3
  import { streamLogger, botLogger, badge, kv, colors, colorText } from '../middleware/logging.js'
4
4
  import { formatLogEntry } from '../utils/formatters.js'
5
+ import { TelegramMessageBuilder } from '@mks2508/telegram-message-builder'
5
6
 
6
7
  const LOG_BUFFER_SIZE = 10
7
8
  const LOG_BUFFER_TIMEOUT = 5000
@@ -102,7 +103,10 @@ export async function handleLogsCommand(ctx: Context): Promise<void> {
102
103
  const config = getConfig()
103
104
 
104
105
  if (!hasLoggingConfigured()) {
105
- await ctx.reply('❌ Logging is not configured. Set TG_LOG_CHAT_ID environment variable.')
106
+ const message = TelegramMessageBuilder.text()
107
+ .text('❌ Logging is not configured. Set TG_LOG_CHAT_ID environment variable.')
108
+ .build()
109
+ ctx.reply(message.text || '', { parse_mode: (message.parse_mode || 'HTML') as any })
106
110
  return
107
111
  }
108
112
 
@@ -117,10 +121,15 @@ export async function handleLogsCommand(ctx: Context): Promise<void> {
117
121
  })}`
118
122
  )
119
123
 
120
- await ctx.reply(
121
- `📝 *Log Streaming Status:* \`${status}\`\n\nChat ID: \`${config.logChatId}\`${
122
- config.logTopicId ? `\nTopic ID: \`${config.logTopicId}\`` : ''
123
- }`,
124
- { parse_mode: 'Markdown' }
125
- )
124
+ const builder = TelegramMessageBuilder.text()
125
+ .title('📝 Log Streaming Status')
126
+ .newline()
127
+ .line('Status', status, { code: true })
128
+ .line('Chat ID', String(config.logChatId), { code: true })
129
+ if (config.logTopicId) {
130
+ builder.line('Topic ID', String(config.logTopicId), { code: true })
131
+ }
132
+
133
+ const message = builder.build()
134
+ ctx.reply(message.text || '', { parse_mode: (message.parse_mode || 'HTML') as any })
126
135
  }
@@ -6,8 +6,14 @@ import { botLogger, kv, badge, colorText, colors } from './middleware/logging.js
6
6
  import { botManager } from './utils/bot-manager.js'
7
7
  import { getInstanceManager } from './utils/instance-manager.js'
8
8
  import { handleHealth, handleUptime, handleStats } from './handlers/health.js'
9
+ import { handleGetInfo } from './handlers/info.js'
9
10
  import { handleStop, handleRestart, handleMode, handleWebhook } from './handlers/control.js'
10
11
  import { handleLogsCommand, initializeLogStreamer } from './handlers/logs.js'
12
+ import { handleExportConfig } from './handlers/config-export.js'
13
+ import { handleTextDemo } from './handlers/demo-text.js'
14
+ import { handleKeyboardDemo, handleKeyboardCallback } from './handlers/demo-keyboard.js'
15
+ import { handleMediaDemo, handleMediaCallback } from './handlers/demo-media.js'
16
+ import { handleFullDemo, handleFullDemoCallback } from './handlers/demo-full.js'
11
17
  import { auth } from './middleware/auth.js'
12
18
  import { initializeFileLogging } from './config/logging.js'
13
19
 
@@ -72,12 +78,44 @@ async function main(): Promise<void> {
72
78
  '/uptime - Show bot uptime\n' +
73
79
  '/stats - Show statistics\n' +
74
80
  '/logs - Check log streaming status\n' +
75
- '/mode - Check or change bot mode',
81
+ '/getinfo - Get your user/chat info for configuration\n' +
82
+ '/mode - Check or change bot mode\n\n' +
83
+ '*🎨 Demo Commands (telegram-message-builder):*\n' +
84
+ '/textdemo - Text formatting showcase\n' +
85
+ '/keybdemo - Keyboard builder showcase\n' +
86
+ '/mediademo - Media types showcase\n' +
87
+ '/fulldemo - Complete feature demo',
76
88
  { parse_mode: 'Markdown' }
77
89
  )
78
90
  botManager.incrementMessages()
79
91
  })
80
92
 
93
+ bot.command('getinfo', handleGetInfo)
94
+ bot.command('exportconfig', handleExportConfig)
95
+
96
+ // Listen for bot mentions in groups
97
+ bot.use(async (ctx, next) => {
98
+ const botUsername = ctx.me
99
+ if (!botUsername) return next()
100
+
101
+ const msg = ctx.message
102
+ if (!msg || !('text' in msg) || !msg.text) return next()
103
+
104
+ // Check if bot is mentioned
105
+ const mention = `@${botUsername}`
106
+ if (msg.text.includes(mention)) {
107
+ botLogger.info(
108
+ `${badge('MENTION', 'rounded')} ${kv({
109
+ user: colorText(String(ctx.from?.id), colors.user),
110
+ chat: ctx.chat?.id,
111
+ })}`
112
+ )
113
+ await handleGetInfo(ctx)
114
+ } else {
115
+ return next()
116
+ }
117
+ })
118
+
81
119
  bot.command('health', handleHealth)
82
120
  bot.command('uptime', handleUptime)
83
121
  bot.command('stats', handleStats)
@@ -108,6 +146,16 @@ async function main(): Promise<void> {
108
146
 
109
147
  bot.command('logs', handleLogsCommand)
110
148
 
149
+ // Demo commands for telegram-message-builder showcase
150
+ bot.command('textdemo', handleTextDemo)
151
+ bot.command('keybdemo', handleKeyboardDemo)
152
+ bot.command('mediademo', handleMediaDemo)
153
+ bot.command('fulldemo', handleFullDemo)
154
+
155
+ // Callback handlers for demo keyboards
156
+ bot.action(/^(demo_yes|demo_no|demo_refresh|demo_search|full_demo_keyboard)$/, handleKeyboardCallback)
157
+ bot.action(/^(media_photo|media_video|media_document|media_audio|media_voice|full_demo_media)$/, handleMediaCallback)
158
+
111
159
  process.once('SIGINT', async () => {
112
160
  botLogger.info(`${badge('SHUTDOWN', 'pill')} ${colorText('SIGINT received', colors.warning)}`)
113
161
  await instanceManager.releaseLock()
@@ -1,6 +1,13 @@
1
1
  import { TimeConstants } from '../types/constants.js'
2
2
  import type { BotStatus } from '../types/bot.js'
3
+ import { TelegramMessageBuilder, fmt } from '@mks2508/telegram-message-builder'
3
4
 
5
+ // Re-export fmt from telegram-message-builder for convenience
6
+ export { fmt }
7
+
8
+ /**
9
+ * Format uptime in human-readable format
10
+ */
4
11
  export function formatUptime(uptimeMs: number): string {
5
12
  const seconds = Math.floor(uptimeMs / TimeConstants.SECOND)
6
13
  const minutes = Math.floor(seconds / 60)
@@ -18,6 +25,9 @@ export function formatUptime(uptimeMs: number): string {
18
25
  }
19
26
  }
20
27
 
28
+ /**
29
+ * Format memory usage in MB
30
+ */
21
31
  export function formatMemory(memoryUsage: {
22
32
  rss: number
23
33
  heapTotal: number
@@ -30,32 +40,9 @@ export function formatMemory(memoryUsage: {
30
40
  return `RSS: ${mb(memoryUsage.rss)}MB\nHeap: ${mb(memoryUsage.heapUsed)}/${mb(memoryUsage.heapTotal)}MB`
31
41
  }
32
42
 
33
- export function formatHealthMessage(status: BotStatus): string {
34
- const uptime = formatUptime(status.uptime)
35
- const memory = formatMemory(status.memoryUsage)
36
-
37
- return `đŸĨ *Bot Health Status*
38
- *Status:* ${status.status.toUpperCase()}
39
- *Mode:* ${status.mode.toUpperCase()}
40
- *Uptime:* ${uptime}
41
- *Memory Usage:*
42
- ${memory}`
43
- }
44
-
45
- export function formatStats(stats: {
46
- messagesProcessed: number
47
- commandsExecuted: number
48
- errorsEncountered: number
49
- }): string {
50
- return `📊 *Bot Statistics*
51
-
52
- *Performance:*
53
- Messages Processed: ${stats.messagesProcessed}
54
- Commands Executed: ${stats.commandsExecuted}
55
- Errors Encountered: ${stats.errorsEncountered}
56
- `
57
- }
58
-
43
+ /**
44
+ * Format a log entry message for Telegram
45
+ */
59
46
  export function formatLogEntry(
60
47
  timestamp: string,
61
48
  level: string,
@@ -72,11 +59,5 @@ export function formatLogEntry(
72
59
 
73
60
  const emoji = levelEmoji[level as keyof typeof levelEmoji] || 'â„šī¸'
74
61
 
75
- return `${emoji} [${component}] ${message}
76
- _${timestamp}_`
77
- }
78
-
79
- export function formatError(error: Error | string): string {
80
- const message = error instanceof Error ? error.message : String(error)
81
- return `❌ *Error:* ${message}`
62
+ return `${emoji} [${component}] ${message}\n${fmt.italic(timestamp)}`
82
63
  }
@@ -1,6 +1,9 @@
1
1
  import { writeFile, readFile, unlink } from 'fs/promises'
2
2
  import { existsSync as existsSyncSync } from 'fs'
3
- import { resolve } from 'path'
3
+ import { resolve, dirname } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url))
4
7
  import type { BotConfig } from '../types/bot.js'
5
8
  import { botLogger, badge, kv, colorText, colors } from '../middleware/logging.js'
6
9
  import { ok, type Result, err } from './result.js'
@@ -31,7 +34,8 @@ export class InstanceManager {
31
34
  this.instanceId = config.instanceId || this.generateInstanceId()
32
35
  this.lockBackend = config.lockBackend || 'pid'
33
36
 
34
- const tmpDir = resolve('./core/tmp')
37
+ // tmp is at core/tmp relative to this file (core/src/utils)
38
+ const tmpDir = resolve(__dirname, '../../tmp')
35
39
  this.pidFile = resolve(tmpDir, `${config.instanceName}.pid`)
36
40
  this.lockFile = resolve(tmpDir, `${config.instanceName}.lock`)
37
41
  }