Fix segfault: keep JSON body alive for parsed string refs

- Root cause: parseFromSlice returns slices pointing into the raw JSON
  body, but body was freed immediately after parsing in getUpdates
- Fix: return OwnedParsed wrapper that keeps both parsed result and
  raw body alive together
- Switch HTTP layer to curl subprocess (more reliable than std.http
  with static linking)
- Fix downloadToFile: use spawnAndWait instead of collectOutput
  (collectOutput requires both pipes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-02-18 16:39:52 +03:00
parent 89f98f02be
commit 01bf41c61c
4 changed files with 138 additions and 82 deletions

View File

@@ -24,14 +24,14 @@ pub fn main() !void {
var offset: i64 = 0;
while (true) {
const updates = bot.getUpdates(offset, 30) catch |err| {
var updates = bot.getUpdates(offset, 30) catch |err| {
log.err("getUpdates failed: {s}", .{@errorName(err)});
std.Thread.sleep(5 * std.time.ns_per_s);
continue;
};
defer updates.deinit();
for (updates.value.result) |update| {
for (updates.parsed.value.result) |update| {
offset = update.update_id + 1;
processUpdate(allocator, &bot, update, whisper_url, language);
}
@@ -80,15 +80,19 @@ fn handleTranscription(
language: []const u8,
) !void {
// Get file path from Telegram
log.info("Step 1: getFilePath", .{});
const file_path = try bot.getFilePath(file_id);
defer allocator.free(file_path);
log.info("Step 1 done: {s}", .{file_path});
// Download file
const ext: []const u8 = if (is_video) ".mp4" else ".ogg";
const tmp_input = try std.fmt.allocPrint(allocator, "/tmp/tg_{d}{s}", .{ message.message_id, ext });
defer allocator.free(tmp_input);
log.info("Step 2: downloadFile to {s}", .{tmp_input});
try bot.downloadFile(file_path, tmp_input);
log.info("Step 2 done", .{});
defer std.fs.deleteFileAbsolute(tmp_input) catch {};
// Convert video to audio if needed
@@ -100,6 +104,7 @@ fn handleTranscription(
};
if (is_video) {
log.info("Step 2.5: ffmpeg conversion", .{});
const out_path = try std.fmt.allocPrint(allocator, "/tmp/tg_{d}.ogg", .{message.message_id});
tmp_audio = out_path;
@@ -117,15 +122,21 @@ fn handleTranscription(
}
audio_path = out_path;
log.info("Step 2.5 done", .{});
}
// Transcribe
log.info("Step 3: transcribe {s}", .{audio_path});
const text = try whisper.transcribe(allocator, whisper_url, audio_path, language);
defer allocator.free(text);
log.info("Step 3 done, text length: {d}", .{text.len});
// Send response
log.info("Step 4: sendMessage", .{});
if (text.len == 0) {
try bot.sendMessage(message.chat.id, "(empty transcription)", message.message_id);
} else {
try bot.sendMessage(message.chat.id, text, message.message_id);
}
log.info("Step 4 done", .{});
}