Files
agent-news/bot/bin/agent_news_bot.dart
matt2dev be09de2ad7 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>
2026-07-21 20:40:32 +03:00

99 lines
3.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}