Initial agent-news: Dart Telegram bot + Claude Code runner + MCP bridge
/news spawns claude-code as a local subprocess (single pod, no cross-container exec on K3s); Claude delivers the digest itself via the send_to_telegram MCP tool. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
98
bot/bin/agent_news_bot.dart
Normal file
98
bot/bin/agent_news_bot.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import '../lib/config.dart';
|
||||
import '../lib/telegram.dart';
|
||||
import '../lib/claude_runner.dart';
|
||||
|
||||
void main() async {
|
||||
final config = Config.fromEnv();
|
||||
|
||||
if (config.telegramToken.isEmpty) {
|
||||
stderr.writeln('ERROR: TELEGRAM_BOT_TOKEN must be set');
|
||||
exit(1);
|
||||
}
|
||||
if (config.allowedChatIds.isEmpty) {
|
||||
stderr.writeln('ERROR: ALLOWED_CHAT_IDS must be set (comma-separated chat ids)');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
final telegram = TelegramClient(config.telegramToken);
|
||||
final claude = ClaudeRunner(config);
|
||||
|
||||
stderr.writeln('agent-news-bot starting, allowed chats: ${config.allowedChatIds}');
|
||||
|
||||
var offset = 0;
|
||||
while (true) {
|
||||
final updates = await telegram.getUpdates(offset: offset);
|
||||
|
||||
for (final update in updates) {
|
||||
offset = (update['update_id'] as int) + 1;
|
||||
|
||||
final message = update['message'];
|
||||
if (message == null) continue;
|
||||
|
||||
final chatId = message['chat']['id'] as int;
|
||||
final messageId = message['message_id'] as int;
|
||||
final text = message['text']?.toString().trim();
|
||||
if (text == null || text.isEmpty) continue;
|
||||
|
||||
if (!config.allowedChatIds.contains(chatId)) {
|
||||
stderr.writeln(' → отклонено, чат $chatId не в ALLOWED_CHAT_IDS');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (text == '/start') {
|
||||
await telegram.sendMessage(
|
||||
chatId,
|
||||
'Привет! Я agent-news. Команда /news — свежая выжимка по ИИ '
|
||||
'(ресёрч делает Claude Code, займёт 1-3 минуты).',
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (text == '/news' || text.startsWith('/news ')) {
|
||||
final topic = text.startsWith('/news ') ? text.substring(6).trim() : null;
|
||||
|
||||
if (claude.isBusy) {
|
||||
await telegram.sendMessage(chatId, 'Уже выполняю другой запрос, подожди.',
|
||||
replyToMessageId: messageId);
|
||||
continue;
|
||||
}
|
||||
|
||||
await telegram.sendMessage(
|
||||
chatId,
|
||||
'🔎 Ищу свежую информацию${topic != null ? ' по теме «$topic»' : ' по ИИ'}, '
|
||||
'это займёт пару минут…',
|
||||
replyToMessageId: messageId,
|
||||
);
|
||||
await telegram.sendTyping(chatId);
|
||||
|
||||
unawaited(_runNews(claude, telegram, chatId, topic));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runNews(
|
||||
ClaudeRunner claude,
|
||||
TelegramClient telegram,
|
||||
int chatId,
|
||||
String? topic,
|
||||
) async {
|
||||
final result = await claude.runNews(chatId: chatId, topic: topic);
|
||||
switch (result.outcome) {
|
||||
case ClaudeOutcome.success:
|
||||
stderr.writeln('news for $chatId done');
|
||||
break;
|
||||
case ClaudeOutcome.busy:
|
||||
break; // уже сообщили выше
|
||||
case ClaudeOutcome.timeout:
|
||||
await telegram.sendMessage(chatId, 'Не успел уложиться по времени, попробуй позже.');
|
||||
break;
|
||||
case ClaudeOutcome.failure:
|
||||
stderr.writeln('news for $chatId failed: ${result.detail}');
|
||||
await telegram.sendMessage(chatId, 'Не получилось выполнить ресёрч, попробуй позже.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user