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.
- package/dist/templates/monorepo/apps/example/package.json +1 -1
- package/dist/templates/monorepo/core/packages/utils/rolldown.config.ts +30 -0
- package/dist/templates/monorepo/tsconfig.json +3 -1
- package/dist/templates/telegram-bot/CLAUDE.deploy.md +2 -3
- package/dist/templates/telegram-bot/CLAUDE.dev.md +304 -5
- package/dist/templates/telegram-bot/CLAUDE.md +166 -89
- package/dist/templates/telegram-bot/README.md +252 -129
- package/dist/templates/telegram-bot/bun.lock +132 -3
- package/dist/templates/telegram-bot/core/.env.example +6 -0
- package/dist/templates/telegram-bot/core/package.json +11 -0
- package/dist/templates/telegram-bot/core/rolldown.config.ts +11 -0
- package/dist/templates/telegram-bot/core/src/config/env.ts +130 -1
- package/dist/templates/telegram-bot/core/src/config/logging.ts +4 -4
- package/dist/templates/telegram-bot/core/src/handlers/config-export.ts +123 -0
- package/dist/templates/telegram-bot/core/src/handlers/control.ts +46 -11
- package/dist/templates/telegram-bot/core/src/handlers/demo-full.ts +58 -0
- package/dist/templates/telegram-bot/core/src/handlers/demo-keyboard.ts +49 -0
- package/dist/templates/telegram-bot/core/src/handlers/demo-media.ts +163 -0
- package/dist/templates/telegram-bot/core/src/handlers/demo-text.ts +27 -0
- package/dist/templates/telegram-bot/core/src/handlers/health.ts +40 -37
- package/dist/templates/telegram-bot/core/src/handlers/info.ts +189 -0
- package/dist/templates/telegram-bot/core/src/handlers/listener.ts +168 -0
- package/dist/templates/telegram-bot/core/src/handlers/logs.ts +16 -7
- package/dist/templates/telegram-bot/core/src/index.ts +49 -1
- package/dist/templates/telegram-bot/core/src/utils/formatters.ts +14 -33
- package/dist/templates/telegram-bot/core/src/utils/instance-manager.ts +6 -2
- package/dist/templates/telegram-bot/core/src/utils/message-builder.ts +180 -0
- package/dist/templates/telegram-bot/core/tsconfig.json +2 -0
- package/dist/templates/telegram-bot/docs/automatizacion_integral_de_bots_de_telegram_con_type_script.md +326 -0
- package/dist/templates/telegram-bot/docs/cli-commands.md +514 -5
- package/dist/templates/telegram-bot/docs/environment.md +191 -3
- package/dist/templates/telegram-bot/docs/getting-started.md +202 -15
- package/dist/templates/telegram-bot/package.json +7 -3
- package/dist/templates/telegram-bot/packages/utils/package.json +12 -1
- package/dist/templates/telegram-bot/packages/utils/rolldown.config.ts +11 -0
- package/dist/templates/telegram-bot/packages/utils/src/logger.ts +1 -0
- package/dist/templates/telegram-bot/packages/utils/tsconfig.json +10 -0
- package/dist/templates/telegram-bot/tools/commands/doctor.ts +62 -0
- package/dist/templates/telegram-bot/tools/commands/setup.ts +984 -170
- package/dist/templates/telegram-bot/tsconfig.json +7 -2
- package/package.json +1 -1
- package/templates/monorepo/apps/example/package.json +1 -1
- package/templates/monorepo/core/packages/utils/rolldown.config.ts +30 -0
- package/templates/monorepo/tsconfig.json +3 -1
- package/templates/telegram-bot/CLAUDE.deploy.md +2 -3
- package/templates/telegram-bot/CLAUDE.dev.md +304 -5
- package/templates/telegram-bot/CLAUDE.md +166 -89
- package/templates/telegram-bot/README.md +252 -129
- package/templates/telegram-bot/bun.lock +132 -3
- package/templates/telegram-bot/core/.env.example +6 -0
- package/templates/telegram-bot/core/package.json +11 -0
- package/templates/telegram-bot/core/rolldown.config.ts +11 -0
- package/templates/telegram-bot/core/src/config/env.ts +130 -1
- package/templates/telegram-bot/core/src/config/logging.ts +4 -4
- package/templates/telegram-bot/core/src/handlers/config-export.ts +123 -0
- package/templates/telegram-bot/core/src/handlers/control.ts +46 -11
- package/templates/telegram-bot/core/src/handlers/demo-full.ts +58 -0
- package/templates/telegram-bot/core/src/handlers/demo-keyboard.ts +49 -0
- package/templates/telegram-bot/core/src/handlers/demo-media.ts +163 -0
- package/templates/telegram-bot/core/src/handlers/demo-text.ts +27 -0
- package/templates/telegram-bot/core/src/handlers/health.ts +40 -37
- package/templates/telegram-bot/core/src/handlers/info.ts +189 -0
- package/templates/telegram-bot/core/src/handlers/listener.ts +168 -0
- package/templates/telegram-bot/core/src/handlers/logs.ts +16 -7
- package/templates/telegram-bot/core/src/index.ts +49 -1
- package/templates/telegram-bot/core/src/utils/formatters.ts +14 -33
- package/templates/telegram-bot/core/src/utils/instance-manager.ts +6 -2
- package/templates/telegram-bot/core/tsconfig.json +2 -0
- package/templates/telegram-bot/docs/automatizacion_integral_de_bots_de_telegram_con_type_script.md +326 -0
- package/templates/telegram-bot/docs/cli-commands.md +514 -5
- package/templates/telegram-bot/docs/environment.md +191 -3
- package/templates/telegram-bot/docs/getting-started.md +202 -15
- package/templates/telegram-bot/package.json +7 -3
- package/templates/telegram-bot/packages/utils/package.json +12 -1
- package/templates/telegram-bot/packages/utils/rolldown.config.ts +11 -0
- package/templates/telegram-bot/packages/utils/src/logger.ts +1 -0
- package/templates/telegram-bot/packages/utils/tsconfig.json +10 -0
- package/templates/telegram-bot/tools/commands/doctor.ts +62 -0
- package/templates/telegram-bot/tools/commands/setup.ts +984 -170
- 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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
{
|
|
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
|
-
'/
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
}
|