From b441eececa4432225220dc728fa104425d46cd19 Mon Sep 17 00:00:00 2001 From: tianma-if Date: Wed, 23 Sep 2026 18:04:14 +0800 Subject: [PATCH] fix(desktop): timestamp generic WeChat chat titles --- apps/desktop/src/main/wechat-chat-archive.mjs | 21 ++++++++++++++++--- .../src/main/wechat-chat-archive.test.mjs | 7 +++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/main/wechat-chat-archive.mjs b/apps/desktop/src/main/wechat-chat-archive.mjs index 300985a7..9af7c5d4 100644 --- a/apps/desktop/src/main/wechat-chat-archive.mjs +++ b/apps/desktop/src/main/wechat-chat-archive.mjs @@ -220,8 +220,23 @@ const renderBody = (lines, mediaByName) => lines.map((line) => { return escapeMarkdownText(line); }).join("\n\n"); -const archiveTitle = (archiveName) => { - const base = posix.basename(archiveName || "").replace(/\.zip$/i, "").trim(); +const titleTimestamp = (time) => { + const match = /^(\d{4})年(\d{1,2})月(\d{1,2})日 (\d{1,2}):(\d{2})(?::\d{2})?$/.exec(time || ""); + if (!match) return null; + const [, year, month, day, hour, minute] = match; + const parsed = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute))); + if (parsed.getUTCFullYear() !== Number(year) || parsed.getUTCMonth() + 1 !== Number(month) + || parsed.getUTCDate() !== Number(day) || parsed.getUTCHours() !== Number(hour) + || parsed.getUTCMinutes() !== Number(minute)) return null; + return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")} ${hour.padStart(2, "0")}:${minute}`; +}; + +const archiveTitle = (archiveName, messages) => { + const base = posix.basename(archiveName || "").replace(/\.(?:zip|txt)$/i, "").trim(); + if (!base || base === "聊天记录") { + const timestamp = titleTimestamp(messages[0]?.time); + if (timestamp) return `聊天记录 · ${timestamp}`; + } return base || "聊天记录"; }; @@ -277,7 +292,7 @@ export const wechatChatNoteFromArchive = (bytes, archiveName = TRANSCRIPT_NAME) const markdown = [...sections, ...unused].filter(Boolean).join("\n\n").trim(); if (!markdown) throw new WeChatArchiveError("unrecognized"); return { - title: archiveTitle(archiveName), + title: archiveTitle(archiveName, messages), markdown, media: items.map(({ id, filename, mimeType, bytes: mediaBytes }) => ({ id, diff --git a/apps/desktop/src/main/wechat-chat-archive.test.mjs b/apps/desktop/src/main/wechat-chat-archive.test.mjs index 33ba122c..f0fda3f5 100644 --- a/apps/desktop/src/main/wechat-chat-archive.test.mjs +++ b/apps/desktop/src/main/wechat-chat-archive.test.mjs @@ -109,6 +109,13 @@ describe("WeChat chat archive", () => { ]); }); + test("adds the first message time when WeChat provides only a generic archive name", () => { + expect(wechatChatNoteFromArchive(sampleArchive(), "聊天记录.zip").title).toBe("聊天记录 · 2026-09-22 22:15"); + expect(wechatChatNoteFromArchive(sampleArchive()).title).toBe("聊天记录 · 2026-09-22 22:15"); + const withoutMessages = zipOf([{ name: "聊天记录.txt", data: "无法识别时间的旧聊天记录" }]); + expect(wechatChatNoteFromArchive(withoutMessages, "聊天记录.zip").title).toBe("聊天记录"); + }); + test("rejects archives that are not a WeChat transcript", () => { expect(() => wechatChatNoteFromArchive(zipOf([{ name: "readme.txt", data: "hello" }]))).toThrow(WeChatArchiveError); expect(() => wechatChatNoteFromArchive(Buffer.from("not a zip"))).toThrow(WeChatArchiveError);