create-bunspace 0.1.0

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 (182) hide show
  1. package/README.md +181 -0
  2. package/dist/bin.js +5755 -0
  3. package/dist/templates/monorepo/CLAUDE.md +164 -0
  4. package/dist/templates/monorepo/LICENSE +21 -0
  5. package/dist/templates/monorepo/MUST-FOLLOW-GUIDELINES.md +269 -0
  6. package/dist/templates/monorepo/README.md +74 -0
  7. package/dist/templates/monorepo/SYNC_VERIFICATION.md +1 -0
  8. package/dist/templates/monorepo/apps/example/package.json +19 -0
  9. package/dist/templates/monorepo/apps/example/src/index.ts +23 -0
  10. package/dist/templates/monorepo/apps/example/src/types/index.ts +7 -0
  11. package/dist/templates/monorepo/apps/example/src/utils/index.ts +7 -0
  12. package/dist/templates/monorepo/core/packages/main/package.json +41 -0
  13. package/dist/templates/monorepo/core/packages/main/rolldown.config.ts +24 -0
  14. package/dist/templates/monorepo/core/packages/main/src/index.ts +80 -0
  15. package/dist/templates/monorepo/core/packages/main/src/types/constants.ts +15 -0
  16. package/dist/templates/monorepo/core/packages/main/src/types/index.ts +8 -0
  17. package/dist/templates/monorepo/core/packages/main/src/types/main.types.ts +25 -0
  18. package/dist/templates/monorepo/core/packages/main/src/utils/index.ts +5 -0
  19. package/dist/templates/monorepo/core/packages/utils/package.json +43 -0
  20. package/dist/templates/monorepo/core/packages/utils/rolldown.config.ts +34 -0
  21. package/dist/templates/monorepo/core/packages/utils/src/index.ts +2 -0
  22. package/dist/templates/monorepo/core/packages/utils/src/logger.ts +68 -0
  23. package/dist/templates/monorepo/core/packages/utils/src/result.ts +146 -0
  24. package/dist/templates/monorepo/core/packages/utils/src/types/constants.ts +15 -0
  25. package/dist/templates/monorepo/core/packages/utils/src/types/index.ts +8 -0
  26. package/dist/templates/monorepo/core/packages/utils/src/types/utils.types.ts +32 -0
  27. package/dist/templates/monorepo/core/packages/utils/src/utils/index.ts +5 -0
  28. package/dist/templates/monorepo/oxlint.json +14 -0
  29. package/dist/templates/monorepo/package.json +39 -0
  30. package/dist/templates/monorepo/tsconfig.json +35 -0
  31. package/dist/templates/telegram-bot/.oxlintrc.json +33 -0
  32. package/dist/templates/telegram-bot/.prettierignore +5 -0
  33. package/dist/templates/telegram-bot/.prettierrc +26 -0
  34. package/dist/templates/telegram-bot/CLAUDE.deploy.md +356 -0
  35. package/dist/templates/telegram-bot/CLAUDE.dev.md +266 -0
  36. package/dist/templates/telegram-bot/CLAUDE.md +280 -0
  37. package/dist/templates/telegram-bot/Dockerfile +46 -0
  38. package/dist/templates/telegram-bot/README.md +245 -0
  39. package/dist/templates/telegram-bot/apps/.gitkeep +0 -0
  40. package/dist/templates/telegram-bot/bun.lock +208 -0
  41. package/dist/templates/telegram-bot/core/.env.example +71 -0
  42. package/dist/templates/telegram-bot/core/README.md +1067 -0
  43. package/dist/templates/telegram-bot/core/package.json +15 -0
  44. package/dist/templates/telegram-bot/core/src/config/env.ts +131 -0
  45. package/dist/templates/telegram-bot/core/src/config/index.ts +97 -0
  46. package/dist/templates/telegram-bot/core/src/config/logging.ts +110 -0
  47. package/dist/templates/telegram-bot/core/src/handlers/control.ts +85 -0
  48. package/dist/templates/telegram-bot/core/src/handlers/health.ts +83 -0
  49. package/dist/templates/telegram-bot/core/src/handlers/logs.ts +126 -0
  50. package/dist/templates/telegram-bot/core/src/index.ts +161 -0
  51. package/dist/templates/telegram-bot/core/src/middleware/auth.ts +41 -0
  52. package/dist/templates/telegram-bot/core/src/middleware/error-handler.ts +41 -0
  53. package/dist/templates/telegram-bot/core/src/middleware/logging.ts +1 -0
  54. package/dist/templates/telegram-bot/core/src/middleware/topics.ts +55 -0
  55. package/dist/templates/telegram-bot/core/src/types/bot.ts +92 -0
  56. package/dist/templates/telegram-bot/core/src/types/constants.ts +50 -0
  57. package/dist/templates/telegram-bot/core/src/types/result.ts +1 -0
  58. package/dist/templates/telegram-bot/core/src/utils/bot-manager.test.ts +111 -0
  59. package/dist/templates/telegram-bot/core/src/utils/bot-manager.ts +201 -0
  60. package/dist/templates/telegram-bot/core/src/utils/commands.ts +63 -0
  61. package/dist/templates/telegram-bot/core/src/utils/formatters.ts +82 -0
  62. package/dist/templates/telegram-bot/core/src/utils/instance-manager.ts +189 -0
  63. package/dist/templates/telegram-bot/core/src/utils/memory.ts +33 -0
  64. package/dist/templates/telegram-bot/core/src/utils/result.ts +26 -0
  65. package/dist/templates/telegram-bot/core/src/utils/telegram.ts +31 -0
  66. package/dist/templates/telegram-bot/core/src/utils/type-guards.ts +71 -0
  67. package/dist/templates/telegram-bot/core/tsconfig.json +9 -0
  68. package/dist/templates/telegram-bot/docker-compose.yml +37 -0
  69. package/dist/templates/telegram-bot/docs/cli-commands.md +377 -0
  70. package/dist/templates/telegram-bot/docs/development.md +363 -0
  71. package/dist/templates/telegram-bot/docs/environment.md +460 -0
  72. package/dist/templates/telegram-bot/docs/examples/middleware-auth.md +335 -0
  73. package/dist/templates/telegram-bot/docs/examples/simple-command.md +207 -0
  74. package/dist/templates/telegram-bot/docs/examples/webhook-setup.md +362 -0
  75. package/dist/templates/telegram-bot/docs/getting-started.md +223 -0
  76. package/dist/templates/telegram-bot/docs/troubleshooting.md +489 -0
  77. package/dist/templates/telegram-bot/package.json +49 -0
  78. package/dist/templates/telegram-bot/packages/utils/package.json +12 -0
  79. package/dist/templates/telegram-bot/packages/utils/src/index.ts +2 -0
  80. package/dist/templates/telegram-bot/packages/utils/src/logger.ts +72 -0
  81. package/dist/templates/telegram-bot/packages/utils/src/result.ts +80 -0
  82. package/dist/templates/telegram-bot/tools/README.md +47 -0
  83. package/dist/templates/telegram-bot/tools/commands/doctor.ts +460 -0
  84. package/dist/templates/telegram-bot/tools/commands/index.ts +35 -0
  85. package/dist/templates/telegram-bot/tools/commands/ngrok.ts +207 -0
  86. package/dist/templates/telegram-bot/tools/commands/setup.ts +368 -0
  87. package/dist/templates/telegram-bot/tools/commands/status.ts +140 -0
  88. package/dist/templates/telegram-bot/tools/index.ts +16 -0
  89. package/dist/templates/telegram-bot/tools/package.json +12 -0
  90. package/dist/templates/telegram-bot/tools/utils/index.ts +13 -0
  91. package/dist/templates/telegram-bot/tsconfig.json +22 -0
  92. package/dist/templates/telegram-bot/vitest.config.ts +29 -0
  93. package/package.json +35 -0
  94. package/templates/monorepo/CLAUDE.md +164 -0
  95. package/templates/monorepo/LICENSE +21 -0
  96. package/templates/monorepo/MUST-FOLLOW-GUIDELINES.md +269 -0
  97. package/templates/monorepo/README.md +74 -0
  98. package/templates/monorepo/apps/example/package.json +19 -0
  99. package/templates/monorepo/apps/example/src/index.ts +23 -0
  100. package/templates/monorepo/apps/example/src/types/index.ts +7 -0
  101. package/templates/monorepo/apps/example/src/utils/index.ts +7 -0
  102. package/templates/monorepo/core/packages/main/package.json +41 -0
  103. package/templates/monorepo/core/packages/main/rolldown.config.ts +24 -0
  104. package/templates/monorepo/core/packages/main/src/index.ts +80 -0
  105. package/templates/monorepo/core/packages/main/src/types/constants.ts +15 -0
  106. package/templates/monorepo/core/packages/main/src/types/index.ts +8 -0
  107. package/templates/monorepo/core/packages/main/src/types/main.types.ts +25 -0
  108. package/templates/monorepo/core/packages/main/src/utils/index.ts +5 -0
  109. package/templates/monorepo/core/packages/utils/package.json +43 -0
  110. package/templates/monorepo/core/packages/utils/rolldown.config.ts +34 -0
  111. package/templates/monorepo/core/packages/utils/src/index.ts +2 -0
  112. package/templates/monorepo/core/packages/utils/src/logger.ts +68 -0
  113. package/templates/monorepo/core/packages/utils/src/result.ts +146 -0
  114. package/templates/monorepo/core/packages/utils/src/types/constants.ts +15 -0
  115. package/templates/monorepo/core/packages/utils/src/types/index.ts +8 -0
  116. package/templates/monorepo/core/packages/utils/src/types/utils.types.ts +32 -0
  117. package/templates/monorepo/core/packages/utils/src/utils/index.ts +5 -0
  118. package/templates/monorepo/oxlint.json +14 -0
  119. package/templates/monorepo/package.json +39 -0
  120. package/templates/monorepo/tsconfig.json +35 -0
  121. package/templates/telegram-bot/.oxlintrc.json +33 -0
  122. package/templates/telegram-bot/.prettierignore +5 -0
  123. package/templates/telegram-bot/.prettierrc +26 -0
  124. package/templates/telegram-bot/CLAUDE.deploy.md +356 -0
  125. package/templates/telegram-bot/CLAUDE.dev.md +266 -0
  126. package/templates/telegram-bot/CLAUDE.md +280 -0
  127. package/templates/telegram-bot/Dockerfile +46 -0
  128. package/templates/telegram-bot/README.md +245 -0
  129. package/templates/telegram-bot/apps/.gitkeep +0 -0
  130. package/templates/telegram-bot/bun.lock +208 -0
  131. package/templates/telegram-bot/core/.env.example +71 -0
  132. package/templates/telegram-bot/core/README.md +1067 -0
  133. package/templates/telegram-bot/core/package.json +15 -0
  134. package/templates/telegram-bot/core/src/config/env.ts +131 -0
  135. package/templates/telegram-bot/core/src/config/index.ts +97 -0
  136. package/templates/telegram-bot/core/src/config/logging.ts +110 -0
  137. package/templates/telegram-bot/core/src/handlers/control.ts +85 -0
  138. package/templates/telegram-bot/core/src/handlers/health.ts +83 -0
  139. package/templates/telegram-bot/core/src/handlers/logs.ts +126 -0
  140. package/templates/telegram-bot/core/src/index.ts +161 -0
  141. package/templates/telegram-bot/core/src/middleware/auth.ts +41 -0
  142. package/templates/telegram-bot/core/src/middleware/error-handler.ts +41 -0
  143. package/templates/telegram-bot/core/src/middleware/logging.ts +1 -0
  144. package/templates/telegram-bot/core/src/middleware/topics.ts +55 -0
  145. package/templates/telegram-bot/core/src/types/bot.ts +92 -0
  146. package/templates/telegram-bot/core/src/types/constants.ts +50 -0
  147. package/templates/telegram-bot/core/src/types/result.ts +1 -0
  148. package/templates/telegram-bot/core/src/utils/bot-manager.test.ts +111 -0
  149. package/templates/telegram-bot/core/src/utils/bot-manager.ts +201 -0
  150. package/templates/telegram-bot/core/src/utils/commands.ts +63 -0
  151. package/templates/telegram-bot/core/src/utils/formatters.ts +82 -0
  152. package/templates/telegram-bot/core/src/utils/instance-manager.ts +189 -0
  153. package/templates/telegram-bot/core/src/utils/memory.ts +33 -0
  154. package/templates/telegram-bot/core/src/utils/result.ts +26 -0
  155. package/templates/telegram-bot/core/src/utils/telegram.ts +31 -0
  156. package/templates/telegram-bot/core/src/utils/type-guards.ts +71 -0
  157. package/templates/telegram-bot/core/tsconfig.json +9 -0
  158. package/templates/telegram-bot/docker-compose.yml +37 -0
  159. package/templates/telegram-bot/docs/cli-commands.md +377 -0
  160. package/templates/telegram-bot/docs/development.md +363 -0
  161. package/templates/telegram-bot/docs/environment.md +460 -0
  162. package/templates/telegram-bot/docs/examples/middleware-auth.md +335 -0
  163. package/templates/telegram-bot/docs/examples/simple-command.md +207 -0
  164. package/templates/telegram-bot/docs/examples/webhook-setup.md +362 -0
  165. package/templates/telegram-bot/docs/getting-started.md +223 -0
  166. package/templates/telegram-bot/docs/troubleshooting.md +489 -0
  167. package/templates/telegram-bot/package.json +49 -0
  168. package/templates/telegram-bot/packages/utils/package.json +12 -0
  169. package/templates/telegram-bot/packages/utils/src/index.ts +2 -0
  170. package/templates/telegram-bot/packages/utils/src/logger.ts +72 -0
  171. package/templates/telegram-bot/packages/utils/src/result.ts +80 -0
  172. package/templates/telegram-bot/tools/README.md +47 -0
  173. package/templates/telegram-bot/tools/commands/doctor.ts +460 -0
  174. package/templates/telegram-bot/tools/commands/index.ts +35 -0
  175. package/templates/telegram-bot/tools/commands/ngrok.ts +207 -0
  176. package/templates/telegram-bot/tools/commands/setup.ts +368 -0
  177. package/templates/telegram-bot/tools/commands/status.ts +140 -0
  178. package/templates/telegram-bot/tools/index.ts +16 -0
  179. package/templates/telegram-bot/tools/package.json +12 -0
  180. package/templates/telegram-bot/tools/utils/index.ts +13 -0
  181. package/templates/telegram-bot/tsconfig.json +22 -0
  182. package/templates/telegram-bot/vitest.config.ts +29 -0
@@ -0,0 +1,161 @@
1
+ import { Telegraf } from 'telegraf'
2
+ import { getConfig } from './config/index.js'
3
+ import { errorHandler } from './middleware/error-handler.js'
4
+ import { topicValidation } from './middleware/topics.js'
5
+ import { botLogger, kv, badge, colorText, colors } from './middleware/logging.js'
6
+ import { botManager } from './utils/bot-manager.js'
7
+ import { getInstanceManager } from './utils/instance-manager.js'
8
+ import { handleHealth, handleUptime, handleStats } from './handlers/health.js'
9
+ import { handleStop, handleRestart, handleMode, handleWebhook } from './handlers/control.js'
10
+ import { handleLogsCommand, initializeLogStreamer } from './handlers/logs.js'
11
+ import { auth } from './middleware/auth.js'
12
+ import { initializeFileLogging } from './config/logging.js'
13
+
14
+ async function main(): Promise<void> {
15
+ const config = getConfig()
16
+ const instanceManager = getInstanceManager(config)
17
+
18
+ // Initialize file logging first (before any logger usage)
19
+ initializeFileLogging()
20
+
21
+ botLogger.info(
22
+ `${badge('START', 'pill')} ${kv({
23
+ environment: colorText(config.environment, colors.info),
24
+ instance: colorText(config.instanceName, colors.dim),
25
+ mode: colorText(config.mode, colors.info),
26
+ logLevel: config.logLevel,
27
+ instanceCheck: config.instanceCheck ? colorText('enabled', colors.success) : colorText('disabled', colors.dim),
28
+ })}`
29
+ )
30
+
31
+ // Try to acquire instance lock
32
+ const lockResult = await instanceManager.acquireLock()
33
+ if (!lockResult.ok) {
34
+ botLogger.error(
35
+ `${badge('INSTANCE', 'rounded')} ${kv({
36
+ error: colorText('CONFLICT', colors.error),
37
+ message: lockResult.error.message,
38
+ })}`
39
+ )
40
+ botLogger.error('Cannot start bot due to instance conflict. Exiting...')
41
+ process.exit(1)
42
+ }
43
+
44
+ botLogger.info(
45
+ `${badge('CONFIG', 'rounded')} ${kv({
46
+ logging: hasLoggingConfigured()
47
+ ? colorText('yes', colors.success)
48
+ : colorText('no', colors.dim),
49
+ control: hasControlConfigured()
50
+ ? colorText('yes', colors.success)
51
+ : colorText('no', colors.dim),
52
+ })}`
53
+ )
54
+
55
+ const bot = new Telegraf(config.botToken)
56
+
57
+ botManager.setBot(bot)
58
+
59
+ initializeLogStreamer(bot)
60
+
61
+ bot.use(errorHandler())
62
+ bot.use(topicValidation())
63
+
64
+ bot.command('start', async (ctx) => {
65
+ botLogger.info(
66
+ `${badge('CMD', 'rounded')} ${kv({ cmd: '/start', user: colorText(String(ctx.from?.id), colors.user) })}`
67
+ )
68
+ ctx.reply(
69
+ '👋 *Welcome!* I am a Telegram bot template.\n\n' +
70
+ 'Available commands:\n' +
71
+ '/health - Check bot health\n' +
72
+ '/uptime - Show bot uptime\n' +
73
+ '/stats - Show statistics\n' +
74
+ '/logs - Check log streaming status\n' +
75
+ '/mode - Check or change bot mode',
76
+ { parse_mode: 'Markdown' }
77
+ )
78
+ botManager.incrementMessages()
79
+ })
80
+
81
+ bot.command('health', handleHealth)
82
+ bot.command('uptime', handleUptime)
83
+ bot.command('stats', handleStats)
84
+
85
+ bot.use((ctx, next) => {
86
+ if (ctx.message) {
87
+ botManager.incrementMessages()
88
+ }
89
+ return next()
90
+ })
91
+
92
+ if (hasControlConfigured()) {
93
+ bot.use(auth())
94
+
95
+ bot.command('stop', handleStop)
96
+ bot.command('restart', handleRestart)
97
+ bot.command('mode', handleMode)
98
+ bot.command('webhook', handleWebhook)
99
+
100
+ botLogger.info(
101
+ `${badge('CONTROL', 'rounded')} ${colorText('Commands registered', colors.success)}`
102
+ )
103
+ } else {
104
+ botLogger.warn(
105
+ `${badge('CONTROL', 'rounded')} ${colorText('Commands not registered', colors.warning)} ${kv({ reason: 'no chat ID configured' })}`
106
+ )
107
+ }
108
+
109
+ bot.command('logs', handleLogsCommand)
110
+
111
+ process.once('SIGINT', async () => {
112
+ botLogger.info(`${badge('SHUTDOWN', 'pill')} ${colorText('SIGINT received', colors.warning)}`)
113
+ await instanceManager.releaseLock()
114
+ bot.stop('SIGINT')
115
+ })
116
+
117
+ process.once('SIGTERM', async () => {
118
+ botLogger.info(`${badge('SHUTDOWN', 'pill')} ${colorText('SIGTERM received', colors.warning)}`)
119
+ await instanceManager.releaseLock()
120
+ bot.stop('SIGTERM')
121
+ })
122
+
123
+ if (config.mode === 'webhook') {
124
+ if (!config.webhookUrl) {
125
+ throw new Error('TG_WEBHOOK_URL is required for webhook mode')
126
+ }
127
+
128
+ botLogger.info(
129
+ `${badge('LAUNCH', 'pill')} ${kv({ mode: colorText('webhook', colors.info), url: config.webhookUrl })}`
130
+ )
131
+
132
+ await bot.launch({
133
+ webhook: {
134
+ domain: config.webhookUrl,
135
+ secretToken: config.webhookSecret,
136
+ },
137
+ })
138
+ } else {
139
+ botLogger.info(`${badge('LAUNCH', 'pill')} ${kv({ mode: colorText('polling', colors.info) })}`)
140
+ await bot.launch()
141
+ }
142
+
143
+ botLogger.success(
144
+ `${badge('READY', 'pill')} ${colorText('Bot started successfully', colors.success)}`
145
+ )
146
+ }
147
+
148
+ function hasLoggingConfigured(): boolean {
149
+ const config = getConfig()
150
+ return Boolean(config.logChatId)
151
+ }
152
+
153
+ function hasControlConfigured(): boolean {
154
+ const config = getConfig()
155
+ return Boolean(config.controlChatId)
156
+ }
157
+
158
+ main().catch((error) => {
159
+ botLogger.critical('Fatal error starting bot:', error)
160
+ process.exit(1)
161
+ })
@@ -0,0 +1,41 @@
1
+ import type { Context, Middleware } from 'telegraf'
2
+ import { botLogger, badge, kv, colors, colorText } from './logging.js'
3
+ import { botManager } from '../utils/bot-manager.js'
4
+
5
+ export function auth(): Middleware<Context> {
6
+ return async (ctx, next) => {
7
+ if (!ctx.from?.id) {
8
+ botLogger.warn(
9
+ `${badge('AUTH', 'rounded')} ${kv({
10
+ status: colorText('FAILED', colors.warning),
11
+ reason: 'no user ID',
12
+ })}`
13
+ )
14
+ return
15
+ }
16
+
17
+ const authResult = botManager.authorize(ctx.from.id)
18
+
19
+ if (!authResult.ok) {
20
+ botLogger.warn(
21
+ `${badge('AUTH', 'rounded')} ${kv({
22
+ status: colorText('DENIED', colors.error),
23
+ user: ctx.from.id,
24
+ username: ctx.from.username ?? 'no-username',
25
+ reason: authResult.error.message,
26
+ })}`
27
+ )
28
+ await ctx.reply(`⛔ ${authResult.error.message}`)
29
+ return
30
+ }
31
+
32
+ botLogger.debug(
33
+ `${badge('AUTH', 'rounded')} ${kv({
34
+ status: colorText('GRANTED', colors.success),
35
+ user: ctx.from.id,
36
+ })}`
37
+ )
38
+
39
+ return next()
40
+ }
41
+ }
@@ -0,0 +1,41 @@
1
+ import type { Context, Middleware } from 'telegraf'
2
+ import { botManager } from '../utils/bot-manager.js'
3
+ import { botLogger, badge, kv, colors, colorText } from './logging.js'
4
+
5
+ export function errorHandler<T extends Context>(): Middleware<T> {
6
+ return async (ctx, next) => {
7
+ try {
8
+ return await next()
9
+ } catch (error) {
10
+ const err = error instanceof Error ? error : new Error(String(error))
11
+ const errorMsg = err.message || 'Unknown error occurred'
12
+
13
+ botLogger.error(
14
+ `${badge('ERROR', 'rounded')} ${kv({
15
+ error: colorText(errorMsg, colors.error),
16
+ type: err.name,
17
+ user: ctx.from?.id ?? 'unknown',
18
+ chat: ctx.chat?.id ?? 'unknown',
19
+ })}`,
20
+ err
21
+ )
22
+
23
+ const message = `❌ *Error:*\n${errorMsg}`
24
+
25
+ try {
26
+ await ctx.reply(message, { parse_mode: 'Markdown' })
27
+ } catch (replyError) {
28
+ // eslint-disable-next-line -- Error is logged below
29
+ botLogger.critical(
30
+ `${badge('CRITICAL', 'rounded')} ${kv({
31
+ error: 'Failed to send error message to user',
32
+ originalError: errorMsg,
33
+ replyError: replyError instanceof Error ? replyError.message : String(replyError),
34
+ })}`
35
+ )
36
+ }
37
+
38
+ botManager.incrementErrors()
39
+ }
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ export * from '@mks2508/telegram-bot-utils'
@@ -0,0 +1,55 @@
1
+ import type { Context, Middleware } from 'telegraf'
2
+ import { getLogTopicIds, getControlTopicIds } from '../config/index.js'
3
+ import { botLogger, badge, kv, colors, colorText } from './logging.js'
4
+
5
+ export function topicValidation(): Middleware<Context> {
6
+ return async (ctx, next) => {
7
+ const logIds = getLogTopicIds()
8
+ const controlIds = getControlTopicIds()
9
+
10
+ if (!logIds && !controlIds) {
11
+ return next()
12
+ }
13
+
14
+ const chatId = ctx.chat?.id.toString()
15
+ const topicId = ctx.message?.message_thread_id
16
+
17
+ let isValid = false
18
+
19
+ if (logIds && chatId === logIds.chatId) {
20
+ if (!logIds.topicId || topicId === logIds.topicId) {
21
+ isValid = true
22
+ }
23
+ }
24
+
25
+ if (controlIds && chatId === controlIds.chatId) {
26
+ if (!controlIds.topicId || topicId === controlIds.topicId) {
27
+ isValid = true
28
+ }
29
+ }
30
+
31
+ if (!isValid && ctx.chat?.type !== 'private' && (logIds || controlIds)) {
32
+ botLogger.debug(
33
+ `${badge('TOPIC', 'rounded')} ${kv({
34
+ action: colorText('rejected', colors.warning),
35
+ chat: chatId ?? 'unknown',
36
+ topic: topicId ?? 'none',
37
+ type: ctx.chat?.type ?? 'unknown',
38
+ })}`
39
+ )
40
+ return
41
+ }
42
+
43
+ if (topicId && (logIds || controlIds)) {
44
+ botLogger.debug(
45
+ `${badge('TOPIC', 'rounded')} ${kv({
46
+ action: colorText('accepted', colors.success),
47
+ chat: chatId ?? 'unknown',
48
+ topic: topicId,
49
+ })}`
50
+ )
51
+ }
52
+
53
+ return next()
54
+ }
55
+ }
@@ -0,0 +1,92 @@
1
+ export interface EnvConfig {
2
+ botToken: string
3
+ mode: 'polling' | 'webhook'
4
+ webhookUrl?: string
5
+ webhookSecret?: string
6
+ logChatId?: string
7
+ logTopicId?: number
8
+ controlChatId?: string
9
+ controlTopicId?: number
10
+ logLevel: 'debug' | 'info' | 'warn' | 'error'
11
+
12
+ // Environment identification
13
+ environment: 'local' | 'staging' | 'production'
14
+ instanceName: string
15
+ instanceId?: string
16
+
17
+ // Instance detection
18
+ instanceCheck: boolean
19
+ lockBackend: 'pid' | 'redis'
20
+ redisUrl?: string
21
+
22
+ // ngrok configuration
23
+ ngrokEnabled: boolean
24
+ ngrokPort: number
25
+ ngrokDomain?: string
26
+ ngrokRegion: string
27
+ ngrokAuthToken?: string
28
+ }
29
+
30
+ export interface BotConfig extends EnvConfig {
31
+ authorizedUserIds: Set<number>
32
+ debug?: boolean
33
+ rateLimit?: number
34
+ timeout?: number
35
+ maxRetries?: number
36
+ commandPrefix?: string
37
+ }
38
+
39
+ export interface BotStatus {
40
+ status: 'running' | 'stopped' | 'restarting' | 'error'
41
+ mode: 'polling' | 'webhook'
42
+ startTime: number | null
43
+ uptime: number
44
+ memoryUsage: {
45
+ rss: number
46
+ heapTotal: number
47
+ heapUsed: number
48
+ external: number
49
+ arrayBuffers: number
50
+ }
51
+ }
52
+
53
+ export interface LogEntry {
54
+ timestamp: string
55
+ level: string
56
+ component: string
57
+ message: string
58
+ metadata?: Record<string, unknown>
59
+ }
60
+
61
+ export interface TelegramLogEntry extends LogEntry {
62
+ chatId?: string
63
+ topicId?: number
64
+ }
65
+
66
+ export interface HealthCheckResult {
67
+ healthy: boolean
68
+ uptime: number
69
+ startTime: number
70
+ memoryUsage: {
71
+ rss: number
72
+ heapTotal: number
73
+ heapUsed: number
74
+ external: number
75
+ arrayBuffers: number
76
+ }
77
+ messageCount: number
78
+ errorCount: number
79
+ }
80
+
81
+ export interface BotStats {
82
+ messagesProcessed: number
83
+ commandsExecuted: number
84
+ errorsEncountered: number
85
+ uptimeStart: number
86
+ lastActivity: number
87
+ }
88
+
89
+ export interface TopicIds {
90
+ chatId: string
91
+ topicId?: number
92
+ }
@@ -0,0 +1,50 @@
1
+ export const TimeConstants = {
2
+ SECOND: 1_000,
3
+ MINUTE: 60_000,
4
+ HOUR: 3_600_000,
5
+ DAY: 86_400_000,
6
+ } as const
7
+
8
+ export const BotTimeouts = {
9
+ STARTUP: 30_000,
10
+ STOP: 10_000,
11
+ COMMAND_RESPONSE: 5_000,
12
+ WEBHOOK_SETUP: 10_000,
13
+ LOG_BUFFER_FLUSH: 5_000,
14
+ POLLING_TIMEOUT: 20_000,
15
+ } as const
16
+
17
+ export const BotLimits = {
18
+ LOG_BUFFER_SIZE: 10,
19
+ MAX_MESSAGE_LENGTH: 4_096,
20
+ MAX_CAPTION_LENGTH: 1_024,
21
+ MAX_RETRIES: 3,
22
+ RATE_LIMIT_PER_MINUTE: 60,
23
+ MAX_WEBHOOK_CONNECTIONS: 40,
24
+ } as const
25
+
26
+ export const EnvKeys = {
27
+ TG_BOT_TOKEN: 'TG_BOT_TOKEN',
28
+ TG_MODE: 'TG_MODE',
29
+ TG_WEBHOOK_URL: 'TG_WEBHOOK_URL',
30
+ TG_WEBHOOK_SECRET: 'TG_WEBHOOK_SECRET',
31
+ TG_LOG_CHAT_ID: 'TG_LOG_CHAT_ID',
32
+ TG_LOG_TOPIC_ID: 'TG_LOG_TOPIC_ID',
33
+ TG_CONTROL_CHAT_ID: 'TG_CONTROL_CHAT_ID',
34
+ TG_CONTROL_TOPIC_ID: 'TG_CONTROL_TOPIC_ID',
35
+ TG_AUTHORIZED_USER_IDS: 'TG_AUTHORIZED_USER_IDS',
36
+ LOG_LEVEL: 'LOG_LEVEL',
37
+ TG_DEBUG: 'TG_DEBUG',
38
+ TG_RATE_LIMIT: 'TG_RATE_LIMIT',
39
+ TG_TIMEOUT: 'TG_TIMEOUT',
40
+ TG_MAX_RETRIES: 'TG_MAX_RETRIES',
41
+ TG_COMMAND_PREFIX: 'TG_COMMAND_PREFIX',
42
+ } as const
43
+
44
+ export function getEnvTimeout(timeout: number): number {
45
+ return timeout || BotTimeouts.STARTUP
46
+ }
47
+
48
+ export function getEnvLimit(limit?: number): number {
49
+ return limit || BotLimits.RATE_LIMIT_PER_MINUTE
50
+ }
@@ -0,0 +1 @@
1
+ export * from '@mks2508/telegram-bot-utils'
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @fileoverview Tests for BotManager
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
6
+ import { botManager } from './bot-manager.js'
7
+ import { Telegraf } from 'telegraf'
8
+
9
+ describe('BotManager', () => {
10
+ let bot: Telegraf
11
+
12
+ beforeEach(() => {
13
+ bot = new Telegraf('test:token')
14
+ })
15
+
16
+ afterEach(() => {
17
+ // Clean up after each test
18
+ botManager.stop('test cleanup').catch(() => {})
19
+ })
20
+
21
+ describe('Lifecycle', () => {
22
+ test('setBot initializes the bot', () => {
23
+ botManager.setBot(bot)
24
+
25
+ const status = botManager.getStatus()
26
+ expect(status.ok).toBe(true)
27
+ if (status.ok) {
28
+ expect(status.value.status).toBe('running')
29
+ }
30
+ })
31
+
32
+ test('getStatus returns bot status after setBot', () => {
33
+ botManager.setBot(bot)
34
+ const status = botManager.getStatus()
35
+
36
+ expect(status.ok).toBe(true)
37
+ if (status.ok) {
38
+ expect(['running', 'stopped']).toContain(status.value.status)
39
+ }
40
+ })
41
+ })
42
+
43
+ describe('Stats', () => {
44
+ test('getStats returns initial stats', () => {
45
+ const stats = botManager.getStats()
46
+ expect(stats.ok).toBe(true)
47
+ if (stats.ok) {
48
+ expect(stats.value.messagesProcessed).toBe(0)
49
+ expect(stats.value.commandsExecuted).toBe(0)
50
+ expect(stats.value.errorsEncountered).toBe(0)
51
+ }
52
+ })
53
+
54
+ test('incrementMessages updates message count', () => {
55
+ botManager.setBot(bot)
56
+ botManager.incrementMessages()
57
+
58
+ const stats = botManager.getStats()
59
+ if (stats.ok) {
60
+ expect(stats.value.messagesProcessed).toBe(1)
61
+ }
62
+ })
63
+
64
+ test('incrementCommands updates command count', () => {
65
+ botManager.setBot(bot)
66
+ botManager.incrementCommands()
67
+
68
+ const stats = botManager.getStats()
69
+ if (stats.ok) {
70
+ expect(stats.value.commandsExecuted).toBe(1)
71
+ }
72
+ })
73
+
74
+ test('incrementErrors updates error count', () => {
75
+ botManager.setBot(bot)
76
+ botManager.incrementErrors()
77
+
78
+ const stats = botManager.getStats()
79
+ if (stats.ok) {
80
+ expect(stats.value.errorsEncountered).toBe(1)
81
+ }
82
+ })
83
+
84
+ test('resetStats resets all stats', () => {
85
+ botManager.setBot(bot)
86
+ botManager.incrementMessages()
87
+ botManager.incrementCommands()
88
+ botManager.incrementErrors()
89
+
90
+ const result = botManager.resetStats()
91
+ expect(result.ok).toBe(true)
92
+
93
+ const stats = botManager.getStats()
94
+ if (stats.ok) {
95
+ expect(stats.value.messagesProcessed).toBe(0)
96
+ expect(stats.value.commandsExecuted).toBe(0)
97
+ expect(stats.value.errorsEncountered).toBe(0)
98
+ }
99
+ })
100
+ })
101
+
102
+ describe('Authorization', () => {
103
+ test('authorize returns ok for authorized users', () => {
104
+ // This test depends on env configuration
105
+ // In real tests, you'd mock getConfig() or set TG_AUTHORIZED_USER_IDS
106
+ // For now, we test the type safety
107
+ const result = botManager.authorize(123456789)
108
+ expect(result).toBeDefined()
109
+ })
110
+ })
111
+ })