Files
wx-win-agent/node-agent/WxAgent.Core/WechatDbMessage.cs
T

74 lines
2.9 KiB
C#

using System.Security.Cryptography;
using System.Text;
using ZstdSharp;
namespace WxAgent.Core;
/// <summary>Database-backed message with stable identity, sender and timestamps.</summary>
public sealed record DbMessage(
long LocalId,
long ServerId,
string ChatId,
string? SenderWxId,
string? SenderName,
string? SenderAvatarUrl,
long Type,
string Content,
DateTimeOffset Timestamp,
bool? IsSelf);
public static class WechatDbMessage
{
// WeChat 4.x stores message_content as hex; rows with WCDB_CT_message_content=1 are zstd-compressed (magic 28 b5 2f fd).
public static string TableNameFor(string chatId) =>
"Msg_" + Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(chatId))).ToLowerInvariant();
public static string DecodeContent(string hexContent, bool compressed)
{
if (string.IsNullOrEmpty(hexContent)) return string.Empty;
if (hexContent.Length > WechatMergedChatParser.MaxXmlCharacters * 2)
throw new InvalidDataException("Database message content exceeds the decoding limit.");
byte[] bytes;
try
{
bytes = Convert.FromHexString(hexContent);
}
catch (FormatException)
{
return string.Empty;
}
if (compressed && bytes.Length >= 4 && bytes[0] == 0x28 && bytes[1] == 0xb5 && bytes[2] == 0x2f && bytes[3] == 0xfd)
{
try
{
using var source = new MemoryStream(bytes);
using var decompressor = new DecompressionStream(source);
using var reader = new StreamReader(decompressor, Encoding.UTF8);
var decoded = new StringBuilder();
var buffer = new char[4096];
int read;
while ((read = reader.Read(buffer, 0, buffer.Length)) != 0)
{
if (decoded.Length + read > WechatMergedChatParser.MaxXmlCharacters)
throw new InvalidDataException("Decompressed message exceeds the decoding limit.");
decoded.Append(buffer, 0, read);
}
return decoded.ToString();
}
catch (Exception exception) when (exception is not (OutOfMemoryException or InvalidDataException))
{
return string.Empty;
}
}
return compressed ? string.Empty : Encoding.UTF8.GetString(bytes);
}
/// <summary>The account root directory name is &lt;wxid&gt;_&lt;random&gt;; the reference project treats the wxid prefix as self.</summary>
public static bool? IsSelf(string? senderWxId, string? accountRootDirectoryName)
{
if (string.IsNullOrEmpty(senderWxId) || string.IsNullOrEmpty(accountRootDirectoryName)) return null;
return accountRootDirectoryName.StartsWith(senderWxId + "_", StringComparison.Ordinal);
}
}