74 lines
2.9 KiB
C#
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 <wxid>_<random>; 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);
|
|
}
|
|
} |