/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>
86 lines
2.6 KiB
Dart
86 lines
2.6 KiB
Dart
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
class TelegramClient {
|
|
final String token;
|
|
final http.Client _http;
|
|
final String _base;
|
|
|
|
TelegramClient(this.token)
|
|
: _http = http.Client(),
|
|
_base = 'https://api.telegram.org/bot$token';
|
|
|
|
// Долгий polling — ждёт до timeout секунд, возвращает новые апдейты
|
|
Future<List<Map<String, dynamic>>> getUpdates({
|
|
required int offset,
|
|
int timeout = 30,
|
|
}) async {
|
|
try {
|
|
final response = await _http.get(
|
|
Uri.parse('$_base/getUpdates?offset=$offset&timeout=$timeout&allowed_updates=["message"]'),
|
|
headers: {'content-type': 'application/json'},
|
|
).timeout(Duration(seconds: timeout + 5));
|
|
|
|
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
if (data['ok'] != true) return [];
|
|
return (data['result'] as List<dynamic>).cast<Map<String, dynamic>>();
|
|
} catch (_) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<void> sendMessage(int chatId, String text, {int? replyToMessageId}) async {
|
|
final chunks = _splitMessage(text);
|
|
for (final chunk in chunks) {
|
|
final payload = <String, dynamic>{
|
|
'chat_id': chatId,
|
|
'text': chunk,
|
|
'parse_mode': 'Markdown',
|
|
};
|
|
if (replyToMessageId != null) payload['reply_to_message_id'] = replyToMessageId;
|
|
try {
|
|
await _http.post(
|
|
Uri.parse('$_base/sendMessage'),
|
|
headers: {'content-type': 'application/json'},
|
|
body: jsonEncode(payload),
|
|
);
|
|
} catch (_) {
|
|
try {
|
|
final plain = <String, dynamic>{'chat_id': chatId, 'text': chunk};
|
|
if (replyToMessageId != null) plain['reply_to_message_id'] = replyToMessageId;
|
|
await _http.post(
|
|
Uri.parse('$_base/sendMessage'),
|
|
headers: {'content-type': 'application/json'},
|
|
body: jsonEncode(plain),
|
|
);
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> sendTyping(int chatId) async {
|
|
try {
|
|
await _http.post(
|
|
Uri.parse('$_base/sendChatAction'),
|
|
headers: {'content-type': 'application/json'},
|
|
body: jsonEncode({'chat_id': chatId, 'action': 'typing'}),
|
|
);
|
|
} catch (_) {}
|
|
}
|
|
|
|
List<String> _splitMessage(String text) {
|
|
const maxLen = 4000;
|
|
if (text.length <= maxLen) return [text];
|
|
final chunks = <String>[];
|
|
var start = 0;
|
|
while (start < text.length) {
|
|
final end = (start + maxLen).clamp(0, text.length);
|
|
chunks.add(text.substring(start, end));
|
|
start = end;
|
|
}
|
|
return chunks;
|
|
}
|
|
|
|
void dispose() => _http.close();
|
|
}
|