diff --git a/.matrix-env b/.matrix-env new file mode 100644 index 0000000..9845e26 --- /dev/null +++ b/.matrix-env @@ -0,0 +1 @@ +MATRIX_PASSWORD=primeZ-Cool3st--PsswRd diff --git a/src/matrix.zig b/src/matrix.zig index 1b8ed05..9677972 100644 --- a/src/matrix.zig +++ b/src/matrix.zig @@ -339,6 +339,8 @@ pub const Event = struct { url: ?[]u8 = null, // allocated; caller frees mimetype: []u8 = "", // allocated; caller frees size: i64 = 0, + /// Original attachment filename (distinct from body, which is the caption). + filename: []u8 = &.{}, // allocated; caller frees }; /// One sync poll. Returns message events + next `since` token. Caller frees. @@ -438,6 +440,11 @@ pub fn sync(alloc: std.mem.Allocator, io: Io, token: []const u8, since: []const // Attachment fields (m.file / m.image / m.audio / m.video). var url_copy: ?[]u8 = null; + var fname_copy: []u8 = ""; + if (content.get("filename")) |f| switch (f) { + .string => |v| fname_copy = alloc.dupe(u8, v) catch "", + else => {}, + }; if (content.get("url")) |u| switch (u) { .string => |v| url_copy = alloc.dupe(u8, v) catch null, else => {}, @@ -463,7 +470,7 @@ pub fn sync(alloc: std.mem.Allocator, io: Io, token: []const u8, since: []const alloc.free(sender_copy); continue; }; - events.append(alloc, .{ .sender = sender_copy, .body = body_copy, .ts = ts, .url = url_copy, .mimetype = mime, .size = size }) catch { + events.append(alloc, .{ .sender = sender_copy, .body = body_copy, .ts = ts, .url = url_copy, .filename = fname_copy, .mimetype = mime, .size = size }) catch { alloc.free(sender_copy); alloc.free(body_copy); }; diff --git a/src/matrix_harness.zig b/src/matrix_harness.zig index 38eade7..49c7e15 100644 --- a/src/matrix_harness.zig +++ b/src/matrix_harness.zig @@ -247,6 +247,7 @@ pub fn main(init: std.process.Init) !void { for (first.events) |ev| { defer alloc.free(ev.sender); defer alloc.free(ev.body); + defer if (ev.filename.len > 0) alloc.free(ev.filename); defer if (ev.url) |u| alloc.free(u); defer if (ev.mimetype.len > 0) alloc.free(ev.mimetype); if (ev.ts > new_max) new_max = ev.ts; @@ -303,6 +304,7 @@ pub fn main(init: std.process.Init) !void { for (result.events) |ev| { defer alloc.free(ev.sender); defer alloc.free(ev.body); + defer if (ev.filename.len > 0) alloc.free(ev.filename); defer if (ev.url) |u| alloc.free(u); defer if (ev.mimetype.len > 0) alloc.free(ev.mimetype); defer if (ev.ts > 0) saveSeenTs(io, ev.ts); @@ -321,7 +323,30 @@ pub fn main(init: std.process.Init) !void { // Attachment: download locally and forward the path to the agent. if (ev.url) |mxc| { - const local = saveAttachment(alloc, io, session.token, ev.ts, ev.body, mxc) catch |e| blk: { + // Use the attachment's REAL filename; the event body is just + // the caption ("This us?" is not a filename). Fall back to + // body, and ensure an extension via mimetype. + const fname_src = if (ev.filename.len > 0) ev.filename else ev.body; + var fname_buf: std.ArrayListUnmanaged(u8) = .empty; + var fname: []const u8 = fname_src; + if (std.mem.lastIndexOfScalar(u8, fname_src, '.') == null and ev.mimetype.len > 0) { + const ext_map = [_]struct{ m: []const u8, e: []const u8 }{ + .{ .m = "image/jpeg", .e = ".jpg" }, .{ .m = "image/png", .e = ".png" }, + .{ .m = "image/webp", .e = ".webp" }, .{ .m = "application/pdf", .e = ".pdf" }, + .{ .m = "application/gzip", .e = ".gz" }, .{ .m = "application/zip", .e = ".zip" }, + .{ .m = "text/plain", .e = ".txt" }, + }; + for (ext_map) |pair| { + if (std.mem.eql(u8, ev.mimetype, pair.m)) { + fname_buf.appendSlice(alloc, fname_src) catch {}; + fname_buf.appendSlice(alloc, pair.e) catch {}; + fname = fname_buf.items; + break; + } + } + } + defer fname_buf.deinit(alloc); + const local = saveAttachment(alloc, io, session.token, ev.ts, fname, mxc) catch |e| blk: { std.debug.print("matrix_harness: attachment download failed: {s}\n", .{@errorName(e)}); break :blk null; };