- Volume was mounted at ~/.claude only, but claude-code writes auth to ~/.claude.json too — that file lived on the ephemeral container layer and was lost on every recreate. Now the whole $HOME is on the named volume, and claude-config moved outside HOME (/app) so it isn't shadowed by it. - Default /news topic was hardcoded to AI; now it's a configurable DEFAULT_TOPIC (plain "главные новости дня"), topic is fully free-form. - Prompt now instructs Claude to send interim "⏳ ..." progress updates via send_to_telegram while researching, not just the final digest — a bare request like "call this tool" was already taking 3+ minutes end-to-end, so silence read as broken. - All user-facing/prompt text moved into bot/lib/lexicon.dart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
95 lines
2.8 KiB
Dart
95 lines
2.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
import '../lib/config.dart';
|
|
import '../lib/telegram.dart';
|
|
import '../lib/claude_runner.dart';
|
|
import '../lib/lexicon.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;
|
|
}
|
|
|
|
stderr.writeln('[$chatId] $text');
|
|
|
|
if (text == '/start') {
|
|
await telegram.sendMessage(chatId, Lexicon.welcome);
|
|
continue;
|
|
}
|
|
|
|
if (text == '/news' || text.startsWith('/news ')) {
|
|
final topic = text.startsWith('/news ') ? text.substring(6).trim() : null;
|
|
|
|
if (claude.isBusy) {
|
|
await telegram.sendMessage(chatId, Lexicon.alreadyBusy, replyToMessageId: messageId);
|
|
continue;
|
|
}
|
|
|
|
final effectiveTopic = (topic == null || topic.isEmpty) ? config.defaultTopic : topic;
|
|
await telegram.sendMessage(chatId, Lexicon.starting(effectiveTopic),
|
|
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:
|
|
stderr.writeln('news for $chatId timed out');
|
|
await telegram.sendMessage(chatId, Lexicon.timedOut);
|
|
break;
|
|
case ClaudeOutcome.failure:
|
|
stderr.writeln('news for $chatId failed: ${result.detail}');
|
|
await telegram.sendMessage(chatId, Lexicon.failed);
|
|
break;
|
|
}
|
|
}
|