Spaces:
Build error
Build error
| import { Bot } from 'grammy'; | |
| import { env } from './config.js'; | |
| import { handleMessage } from './agent.js'; | |
| export const bot = new Bot(env.TELEGRAM_BOT_TOKEN); | |
| // Security: Whitelist middleware | |
| bot.use(async (ctx, next) => { | |
| if (ctx.from?.id !== env.TELEGRAM_USER_ID) { | |
| console.log(`Blocked interaction from unauthorized user ID: ${ctx.from?.id}`); | |
| return; // Silently ignore as per constraints | |
| } | |
| await next(); | |
| }); | |
| import { getAllMemories } from './memory/db.js'; | |
| import os from 'os'; | |
| import { clearConversationHistory } from './agent.js'; | |
| import { currentModel, setModel } from './llm.js'; | |
| bot.command('memory', async (ctx) => { | |
| const memories = await getAllMemories(); | |
| if (memories.length === 0) { | |
| await ctx.reply('📭 Die Datenbank ist noch komplett leer.'); | |
| return; | |
| } | |
| let response = '🧠 **Gedächtnis-Datenbank:**\n\n'; | |
| for (const mem of memories) { | |
| const dateStr = new Date(mem.timestamp).toLocaleString('de-DE'); | |
| response += `• \`${dateStr}\`: ${mem.content}\n`; | |
| } | |
| if (response.length > 4000) { | |
| response = response.substring(0, 4000) + '\n... [Abgeschnitten]'; | |
| } | |
| await ctx.reply(response, { parse_mode: 'Markdown' }); | |
| }); | |
| bot.command('start', async (ctx) => { | |
| await ctx.reply('👋 Hallo! Ich bin Gravity Claw. Schreibe mir eine Nachricht oder nutze /help für eine Liste meiner Befehle.'); | |
| }); | |
| bot.command('help', async (ctx) => { | |
| const helpText = ` | |
| 🤖 **Gravity Claw Commands** 🤖 | |
| /help - Zeigt diese Hilfeseite | |
| /memory - Zeigt alle persistenten Erinnerungen an | |
| /clear - Löscht den aktuellen KI-Chatverlauf | |
| /ping - Prüft die Latenz des Bots | |
| /sysinfo - Zeigt Systeminformationen des Host-PCs an | |
| /uptime - Zeigt, wie lange der Bot schon läuft | |
| /id - Zeigt deine Telegram-ID | |
| /date - Zeigt das aktuelle Datum und die Uhrzeit des Servers | |
| /model [name] - Zeigt oder ändert das aktive Ollama KI-Modell | |
| /echo [text] - Der Bot wiederholt deinen Text | |
| /roll - Würfelt eine Zahl von 1 bis 100 | |
| /soul - Zeigt die aktuelle soul.md an (Persönlichkeit/Ziele) | |
| /heartbeat - Zeigt die aktuelle heartbeat.md an (System-Status) | |
| `; | |
| await ctx.reply(helpText, { parse_mode: 'Markdown' }); | |
| }); | |
| bot.command('clear', async (ctx) => { | |
| // Clear the current short-term conversation memory (keep system prompt) | |
| clearConversationHistory(); | |
| await ctx.reply('🧹 Der aktuelle Chatverlauf wurde gelöscht. Ich fange frisch an!'); | |
| }); | |
| bot.command('ping', async (ctx) => { | |
| const start = Date.now(); | |
| const msg = await ctx.reply('🏓 Pong!'); | |
| const diff = Date.now() - start; | |
| await ctx.api.editMessageText(ctx.chat.id, msg.message_id, `🏓 Pong! Latenz: ${diff}ms`); | |
| }); | |
| bot.command('sysinfo', async (ctx) => { | |
| const cpu = os.cpus()[0]?.model || 'Unknown CPU'; | |
| const ramTotal = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2); | |
| const ramFree = (os.freemem() / 1024 / 1024 / 1024).toFixed(2); | |
| const platform = os.platform(); | |
| await ctx.reply(`💻 **Host System:**\n\nCPU: ${cpu}\nRAM: ${ramFree} GB frei von ${ramTotal} GB\nOS: ${platform}`, { parse_mode: 'Markdown' }); | |
| }); | |
| let startTime = Date.now(); | |
| bot.command('uptime', async (ctx) => { | |
| const diff = Date.now() - startTime; | |
| const hours = Math.floor(diff / 3600000); | |
| const minutes = Math.floor((diff % 3600000) / 60000); | |
| const seconds = Math.floor((diff % 60000) / 1000); | |
| await ctx.reply(`⏳ Ich bin seit ${hours}h ${minutes}m ${seconds}s online.`); | |
| }); | |
| bot.command('id', async (ctx) => { | |
| await ctx.reply(`👤 Deine Telegram User ID lautet: \`${ctx.from?.id}\``, { parse_mode: 'Markdown' }); | |
| }); | |
| bot.command('date', async (ctx) => { | |
| await ctx.reply(`📅 Server Zeit: ${new Date().toLocaleString('de-DE')}`); | |
| }); | |
| bot.command('echo', async (ctx) => { | |
| const text = ctx.message?.text?.replace('/echo ', ''); | |
| if (!text || text === '/echo') return; | |
| await ctx.reply(text); | |
| }); | |
| bot.command('roll', async (ctx) => { | |
| const roll = Math.floor(Math.random() * 100) + 1; | |
| await ctx.reply(`🎲 Du hast eine **${roll}** gewürfelt!`, { parse_mode: 'Markdown' }); | |
| }); | |
| bot.command('model', async (ctx) => { | |
| const text = ctx.message?.text?.replace('/model', '').trim(); | |
| if (!text) { | |
| await ctx.reply(`🧠 Aktuelles Modell: \`${currentModel}\`\n\nNutze \`/model <modellname>\` um es zu ändern (z.B. \`/model llama3.2\`).`, { parse_mode: 'Markdown' }); | |
| return; | |
| } | |
| setModel(text); | |
| await ctx.reply(`✅ System-Modell wurde erfolgreich auf \`${text}\` geändert!`, { parse_mode: 'Markdown' }); | |
| }); | |
| bot.command('soul', async (ctx) => { | |
| try { | |
| const { executeReadFile } = await import('./tools/fs.js'); | |
| const content = await executeReadFile('soul.md'); | |
| if (content.includes('Error')) { | |
| await ctx.reply(`📝 *soul.md nicht gefunden oder leer*\nIch erstelle gerade eine neue Identität beim nächsten Denken...`, { parse_mode: 'Markdown' }); | |
| } else { | |
| const shortened = content.length > 3900 ? content.substring(0, 3900) + '...' : content; | |
| await ctx.reply(`🌌 *Meine Soul (soul.md):*\n\n${shortened}`, { parse_mode: 'Markdown' }); | |
| } | |
| } catch (e: any) { | |
| await ctx.reply(`Fehler beim Lesen der Soul: ${e.message}`); | |
| } | |
| }); | |
| bot.command('heartbeat', async (ctx) => { | |
| try { | |
| const { executeReadFile } = await import('./tools/fs.js'); | |
| const content = await executeReadFile('heartbeat.md'); | |
| if (content.includes('Error')) { | |
| await ctx.reply(`🫀 *heartbeat.md nicht gefunden*\nMein Herz hat im neuen Server noch nicht geschlagen...`, { parse_mode: 'Markdown' }); | |
| } else { | |
| const shortened = content.length > 3900 ? content.substring(0, 3900) + '...' : content; | |
| await ctx.reply(`🫀 *Aktueller System-Status (heartbeat.md):*\n\n${shortened}`, { parse_mode: 'Markdown' }); | |
| } | |
| } catch (e: any) { | |
| await ctx.reply(`Fehler beim Lesen des Heartbeats: ${e.message}`); | |
| } | |
| }); | |
| bot.on('message:text', async (ctx) => { | |
| // Show typing indicator | |
| await ctx.replyWithChatAction('typing'); | |
| try { | |
| const responseText = await handleMessage( | |
| ctx.message.text, | |
| async (toolName) => { | |
| let emoji = '🤔'; // Default thinking | |
| if (toolName.includes('memory')) emoji = '✍'; | |
| else if (toolName.includes('search') || toolName.includes('browser')) emoji = '👀'; | |
| else if (toolName.includes('shell') || toolName.includes('file')) emoji = '👨💻'; | |
| else if (toolName.includes('time') || toolName.includes('schedule')) emoji = '⚡'; | |
| try { | |
| // React to the user's message directly instead of sending a new text message | |
| await ctx.react(emoji as any); | |
| } catch (e) { | |
| console.log(`Could not react with ${emoji} to message.`); | |
| } | |
| } | |
| ); | |
| await ctx.reply(responseText); | |
| } catch (error) { | |
| console.error('Error handling message:', error); | |
| await ctx.reply('❌ An error occurred while processing your request.'); | |
| } | |
| }); | |
| bot.on('message_reaction', async (ctx) => { | |
| const newReactions = ctx.messageReaction.new_reaction; | |
| if (newReactions.length > 0) { | |
| const reaction = newReactions.find(r => r.type === 'emoji') as any; | |
| if (reaction && reaction.emoji) { | |
| try { | |
| // Mirror the user's reaction back onto the same message | |
| await ctx.api.setMessageReaction( | |
| ctx.chat?.id || env.TELEGRAM_USER_ID, | |
| ctx.messageReaction.message_id, | |
| [{ type: 'emoji', emoji: reaction.emoji }] | |
| ); | |
| } catch (e) { | |
| console.log(`Could not mirror reaction:`, e); | |
| } | |
| } | |
| } | |
| }); | |
| export const startBot = async () => { | |
| const webhookUrl = process.env.WEBHOOK_URL; | |
| if (webhookUrl) { | |
| // Run in Webhook Mode (Production / Cloud) | |
| try { | |
| await bot.api.setWebhook(`${webhookUrl}/api/telegram-webhook`); | |
| console.log(`🤖 Gravity Claw (@${(await bot.api.getMe()).username}) Webhook set to: ${webhookUrl}/api/telegram-webhook`); | |
| console.log(`🔒 Secure mode active. Locked to user ID: ${env.TELEGRAM_USER_ID}`); | |
| } catch (e: any) { | |
| console.error(`❌ Failed to set Telegram Webhook (DNS/Network error):`, e.message); | |
| console.log(`⚠️ Server will continue running for iOS App, but Telegram bot might be unresponsive limits.`); | |
| } | |
| } else { | |
| // Run in Polling Mode (Local Dev) | |
| bot.start({ | |
| onStart: (botInfo) => { | |
| console.log(`🤖 Gravity Claw (@${botInfo.username}) started successfully!`); | |
| console.log(`🔒 Secure mode active. Locked to user ID: ${env.TELEGRAM_USER_ID}`); | |
| console.log(`⏳ Using polling mode (No webhook configured)`); | |
| } | |
| }); | |
| } | |
| }; | |