telegram-bot.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #!/usr/bin/env node
  2. /**
  3. * Simple Telegram Bot for OpenCode
  4. * Sends notifications when session becomes idle
  5. */
  6. import https from 'https'
  7. import fs from 'fs'
  8. import path from 'path'
  9. import { fileURLToPath } from 'url'
  10. const __filename = fileURLToPath(import.meta.url)
  11. const __dirname = path.dirname(__filename)
  12. class SimpleTelegramBot {
  13. private botToken: string | undefined
  14. private chatId: string | undefined
  15. private botUsername: string
  16. private idleTimeout: number
  17. private checkInterval: number
  18. private lastActivity: number
  19. private idleTimer: NodeJS.Timeout | null
  20. private checkTimer: NodeJS.Timeout | null
  21. private isIdle: boolean
  22. constructor() {
  23. this.loadEnvFile();
  24. this.botToken = process.env.TELEGRAM_BOT_TOKEN;
  25. this.chatId = process.env.TELEGRAM_CHAT_ID;
  26. this.botUsername = process.env.TELEGRAM_BOT_USERNAME || '@OpenCode';
  27. this.idleTimeout = parseInt(process.env.TELEGRAM_IDLE_TIMEOUT || '300000'); // 5 minutes default
  28. this.checkInterval = parseInt(process.env.TELEGRAM_CHECK_INTERVAL || '30000'); // 30 seconds default
  29. this.lastActivity = Date.now();
  30. this.idleTimer = null;
  31. this.checkTimer = null;
  32. this.isIdle = false;
  33. this.validateConfig();
  34. }
  35. /**
  36. * Load environment variables from .env file
  37. */
  38. private loadEnvFile(): void {
  39. const envPath = path.join(__dirname, '..', '..', '.env');
  40. if (fs.existsSync(envPath)) {
  41. const envContent = fs.readFileSync(envPath, 'utf8');
  42. envContent.split('\n').forEach(line => {
  43. const trimmed = line.trim();
  44. if (trimmed && !trimmed.startsWith('#')) {
  45. const [key, ...valueParts] = trimmed.split('=');
  46. if (key && valueParts.length > 0) {
  47. process.env[key] = valueParts.join('=');
  48. }
  49. }
  50. });
  51. }
  52. }
  53. /**
  54. * Validate configuration
  55. */
  56. private validateConfig(): boolean {
  57. if (!this.botToken) {
  58. console.warn('⚠️ TELEGRAM_BOT_TOKEN not set');
  59. return false;
  60. }
  61. if (!this.chatId) {
  62. console.warn('⚠️ TELEGRAM_CHAT_ID not set');
  63. return false;
  64. }
  65. return true;
  66. }
  67. /**
  68. * Initialize the bot
  69. */
  70. init(): void {
  71. if (!this.validateConfig()) {
  72. // Removed: console.log('❌ Telegram bot disabled - missing configuration');
  73. return;
  74. }
  75. // Removed: console.log('📱 Telegram bot initialized');
  76. this.sendMessage('🚀 OpenCode session started');
  77. this.startIdleMonitoring();
  78. }
  79. /**
  80. * Start monitoring for idle sessions
  81. */
  82. private startIdleMonitoring(): void {
  83. this.resetActivity();
  84. // Check for idle state periodically
  85. this.checkTimer = setInterval(() => {
  86. const timeSinceLastActivity = Date.now() - this.lastActivity;
  87. if (timeSinceLastActivity > this.idleTimeout && !this.isIdle) {
  88. this.handleIdle();
  89. }
  90. }, this.checkInterval);
  91. }
  92. /**
  93. * Reset activity timer
  94. */
  95. resetActivity(): void {
  96. this.lastActivity = Date.now();
  97. if (this.isIdle) {
  98. this.isIdle = false;
  99. this.sendMessage('🟢 Session resumed - User is active again');
  100. }
  101. }
  102. /**
  103. * Handle idle state
  104. */
  105. private handleIdle(): void {
  106. this.isIdle = true;
  107. const minutes = Math.floor(this.idleTimeout / 60000);
  108. this.sendMessage(`🟡 Session idle - User has been inactive for ${minutes} minutes`);
  109. }
  110. /**
  111. * Send message to Telegram
  112. */
  113. async sendMessage(message: string): Promise<any> {
  114. if (!this.validateConfig()) {
  115. // Removed: console.log('Cannot send message - missing configuration');
  116. return;
  117. }
  118. if (!message || message.trim() === '') {
  119. // Removed: console.log('Cannot send empty message:', JSON.stringify(message));
  120. return;
  121. }
  122. const data = JSON.stringify({
  123. chat_id: this.chatId,
  124. text: message.trim()
  125. });
  126. const dataBuffer = Buffer.from(data, 'utf8');
  127. const options = {
  128. hostname: 'api.telegram.org',
  129. port: 443,
  130. path: `/bot${this.botToken}/sendMessage`,
  131. method: 'POST',
  132. headers: {
  133. 'Content-Type': 'application/json; charset=utf-8',
  134. 'Content-Length': dataBuffer.length
  135. }
  136. };
  137. return new Promise((resolve, reject) => {
  138. const req = https.request(options, (res) => {
  139. let responseData = '';
  140. res.on('data', (chunk) => {
  141. responseData += chunk;
  142. });
  143. res.on('end', () => {
  144. try {
  145. const response = JSON.parse(responseData);
  146. if (response.ok) {
  147. //console.log('📱 Message sent:', message);
  148. resolve(response);
  149. } else {
  150. //console.error('❌ Failed to send message:', response.description);
  151. reject(new Error(response.description));
  152. }
  153. } catch (error) {
  154. //console.error('❌ Error parsing response:', error);
  155. reject(error);
  156. }
  157. });
  158. });
  159. req.on('error', (error) => {
  160. //console.error('❌ Error sending message:', error);
  161. reject(error);
  162. });
  163. req.write(dataBuffer);
  164. req.end();
  165. });
  166. }
  167. /**
  168. * Cleanup resources
  169. */
  170. cleanup(sendEndMessage: boolean = true): void {
  171. if (this.checkTimer) {
  172. clearInterval(this.checkTimer);
  173. }
  174. if (sendEndMessage) {
  175. this.sendMessage('🏁 OpenCode session ended');
  176. }
  177. // Removed: console.log('📱 Telegram bot cleaned up');
  178. }
  179. }
  180. // Export for use as module
  181. export { SimpleTelegramBot }
  182. export default SimpleTelegramBot
  183. // Auto-initialize if run directly
  184. if (import.meta.url === `file://${process.argv[1]}`) {
  185. const bot = new SimpleTelegramBot();
  186. bot.init();
  187. // Handle cleanup on exit
  188. process.on('SIGINT', () => {
  189. bot.cleanup();
  190. setTimeout(() => process.exit(0), 1000);
  191. });
  192. process.on('SIGTERM', () => {
  193. bot.cleanup();
  194. setTimeout(() => process.exit(0), 1000);
  195. });
  196. // Demo: Simulate user activity every 2 minutes to prevent idle
  197. // Uncomment the next line for testing
  198. // setInterval(() => bot.resetActivity(), 120000);
  199. // Removed: console.log('📱 Telegram bot running... Press Ctrl+C to stop');
  200. }