44 Commits

Author SHA1 Message Date
Suxue
3da9a923b4 1.优化批量导出时的体验。现在起记录为空的用户将不会被导出。
2.修复批量导出的部分问题。
2024-01-10 23:20:15 +08:00
Suxue
f07d93c461 修复超大文件解密问题 2024-01-09 17:17:54 +08:00
Suxue
6653bcd035 update 2024-01-09 16:16:46 +08:00
Suxue
85f77c468e log 2024-01-09 16:08:43 +08:00
Suxue
f16e0b020b 1.解密内存优化
2.记录分页支持
3.模版选择初步支持
2024-01-09 15:01:36 +08:00
Suxue
00b6596237 update 2024-01-06 11:22:31 +08:00
Suxue
3dfb81b990 新增部分消息类型容错
新增已删除联系人记录导出
2024-01-06 11:20:50 +08:00
Suxue
f3d73d2507 修复bug
dev分支预下载暂时不可用,拿来调试功能了
2023-12-25 23:41:18 +08:00
Suxue
b276815960 新增引用消息支持
新增词云分词异常时,错误提示
2023-12-23 00:05:14 +08:00
Suxue
c507e68080 合并0.9.6.2 2023-12-21 15:32:23 +08:00
Suxue
932b2d099f v0.9.6.2 Release!
1.初步支持转发消息导出。
2.新增HTML导出全局异常处理,新增LOG方便直接定位
2023-12-19 23:17:39 +08:00
Suxue
49c39b13cf v0.9.6.1 Release!
1.支持词云蒙版,现在可以自定义词云形状啦!
2.导出附件按用户id归集。
3.修复图片不存在导致导出线程退出的问题
2023-12-18 22:41:10 +08:00
Suxue
cc63fe94a6 词云蒙版支持 2023-12-16 12:26:13 +08:00
Suxue
ddfec75201 导出附件按用户id归集 2023-12-16 09:15:23 +08:00
Suxue
e174f6d00a 合并 2023-12-16 08:37:02 +08:00
Suxue
6234ad3084 v0.9.6.0 Release!
1.新增词云功能,导出时选择与他的词云即可。
2.修复公钥头推断失败BUG
3.修复创建工作区与已有工作区切换不成功BUG
2023-12-15 22:48:40 +08:00
Suxue
ce393253a8 新增词云
修复创建工作区与已有工作区之间切换的bug
导出文件名优化
2023-12-15 18:46:15 +08:00
Suxue
db79e51305 v0.9.5.0 Release!
1.修复查询微信时,可能出现假死的问题。
2.修复导出HTML时,Emoji异常导致导出线程退出的问题。
3.更换了图片解密算法,修复部分情况下不能正常导出图片的问题
4.新增公钥头推断Key
2023-12-14 23:25:23 +08:00
Suxue
cf7d75625d 新增公钥头推断查找 2023-12-14 23:11:55 +08:00
Suxue
cca9e0626d 合并 2023-12-14 21:54:44 +08:00
Suxue
f02a1164cf 1.修复导出html时,emoji xml导致线程退出的问题
2.修复图片导出问题
2023-12-14 21:50:07 +08:00
Suxue
5ae3e6ef5d v0.9.4.0 Release!
1.导出HTML新增了文件与表情的支持,需要导出表情,请先使用表情预下载功能,由于略微做了一些频率限制,预下载可能会较久,请耐心等待。
2.调整了软件本身的显示逻辑
3.旧版消息工具已经移动至管理界面,如需使用,工作区,右键,管理即可
2023-12-13 18:35:32 +08:00
Suxue
2399fa5f4a v0.9.3.0
1.修复一处内存泄露问题。
2.修复部分用户在创建工作区时遇到“未找到适用于完成此操作的图像处理组件。”的问题
3.支持Type=10000的系统消息。
4.新增表情预下载功能,为后期表情支持做准备。
2023-12-12 22:52:29 +08:00
Suxue
a49f9ec9de v0.9.3.0
1.修复一处内存泄露问题。
2.修复部分用户在创建工作区时遇到“未找到适用于完成此操作的图像处理组件。”的问题
3.支持Type=10000的系统消息。
4.新增表情预下载功能,为后期表情支持做准备。
2023-12-12 22:50:30 +08:00
Suxue
eaacb554ac Merge branch 'master' into dev 2023-12-11 21:55:07 +08:00
Suxue
eb4d4fa3fa 新增批量导出 2023-12-11 16:18:42 +08:00
Suxue
9bf5d1c7d3 1.异步创建工作区,同时创建工作区支持状态显示了
2.异步导出聊天记录,html导出平滑优化
3.支持拖拽窗体
2023-12-11 13:50:27 +08:00
Suxue
07f58afd98 fix style 2023-12-07 15:56:49 +08:00
Suxue
375f17fe49 优化导出按钮
新增txt导出
2023-12-07 14:56:52 +08:00
Suxue
985505b2e7 导出改造
新增txt导出
2023-12-06 23:06:54 +08:00
Suxue
9ad5f6b311 Merge branch 'master' into dev 2023-12-06 22:07:42 +08:00
Suxue
6c7614dcac merge master 2023-12-06 21:58:17 +08:00
Suxue
3957f2462e 更新url 2023-12-06 20:37:19 +08:00
Suxue
74ad9297c2 清理旧版本文件和部分方法
修复联系人列表没有显示备注的问题
2023-12-06 19:50:58 +08:00
Suxue
ffbc855a0f 新增用户数和消息数统计
群聊导出支持用户名
2023-12-06 19:32:55 +08:00
Suxue
50c3b22c74 迁移解密至WXWorkspace
解密方式选择切换MVVM
完善新界面功能
2023-12-06 18:22:22 +08:00
Suxue
d1a0f3897f update readme 2023-12-06 15:34:20 +08:00
Suxue
4b5554cbe1 update readme 2023-12-06 15:27:57 +08:00
Suxue
585e6683cd 项目更名 2023-12-06 14:13:14 +08:00
Suxue
e83d3869cc 调整部分局部变量可见性
完善新版工作区创建
群聊数据改用标准方式获取
2023-12-06 12:05:34 +08:00
Suxue
191849e9c2 1.用户头像切换到本地缓存
2.修复多工作区切换,页面不变的问题
3.WXContact切换到ObservableCollection
4.群聊名称支持
2023-12-05 14:50:32 +08:00
Suxue
d4aa12cf07 WindowsAPI调用重构,改用SYSTEM_EXTENDED_HANDLE_INFORMATION解决 2023-12-04 15:46:55 +08:00
Suxue
85b0b1db52 features:
1.workspace page 引入mvvm
2.wxcontact修改为联合模型
2023-11-30 17:24:13 +08:00
Suxue
8305a58c39 dev init 2023-11-30 09:03:49 +08:00
40 changed files with 849526 additions and 526 deletions

View File

@@ -11,7 +11,7 @@ namespace WechatBakTool.Export
public interface IExport
{
void InitTemplate(WXContact session,string path);
void SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel);
bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel);
void SetEnd();
void Save(string path = "");
}

View File

@@ -10,6 +10,9 @@ using WechatBakTool.Model;
using System.Xml;
using Newtonsoft.Json;
using WechatBakTool.ViewModel;
using System.Security.Policy;
using System.Windows;
using System.Xml.Linq;
namespace WechatBakTool.Export
{
@@ -25,7 +28,6 @@ namespace WechatBakTool.Export
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>与 {0}({1}) 的聊天记录</b></p>", Session.NickName, Session.UserName);
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>导出时间:{0}</b></p><hr/>", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
File.WriteAllText(Path, HtmlBody);
}
public void InitTemplate(WXContact contact, string p)
@@ -48,7 +50,7 @@ namespace WechatBakTool.Export
File.AppendAllText(Path, HtmlBody);
}
public void SetMsg(WXUserReader reader, WXContact contact,WorkspaceViewModel viewModel)
public bool SetMsg(WXUserReader reader, WXContact contact,WorkspaceViewModel viewModel)
{
if (Session == null)
throw new Exception("请初始化模版Not Use InitTemplate");
@@ -57,107 +59,270 @@ namespace WechatBakTool.Export
if (msgList == null)
throw new Exception("获取消息失败,请确认数据库读取正常");
if(msgList.Count == 0)
{
viewModel.ExportCount = "没有消息,忽略";
return false;
}
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
bool err = false;
int msgCount = 0;
HtmlBody = "";
StreamWriter streamWriter = new StreamWriter(Path, true);
foreach (var msg in msgList)
{
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\">{0} <span style=\"padding-left:10px;\">{1}</span></p>", msg.IsSender ? "我" : msg.NickName, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
try
{
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\">{0} <span style=\"padding-left:10px;\">{1}</span></p>", msg.IsSender ? "我" : msg.NickName, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
if (msg.Type == 1)
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", msg.StrContent);
else if (msg.Type == 3)
{
string? path = reader.GetAttachment(WXMsgType.Image, msg);
if (path == null)
if (msg.Type == 1)
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", msg.StrContent);
else if (msg.Type == 3)
{
#if DEBUG
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Img Error Path=>", path));
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),"Img Error Msg=>",JsonConvert.SerializeObject(msg)));
#endif
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "图片转换出现错误或文件不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:1000px;max-width:1000px;\"/></p></div>", path);
}
else if (msg.Type == 43)
{
string? path = reader.GetAttachment(WXMsgType.Video, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
}
else if (msg.Type == 49)
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
string? path = reader.GetAttachment(WXMsgType.Image, msg);
if (path == null)
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
#if DEBUG
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Img Error Path=>", path));
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Img Error Msg=>", JsonConvert.SerializeObject(msg)));
#endif
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "图片转换出现错误或文件不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:1000px;max-width:1000px;\"/></p></div>", path);
}
else if (msg.Type == 43)
{
string? path = reader.GetAttachment(WXMsgType.Video, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
}
else if (msg.Type == 47)
{
string? path = reader.GetAttachment(WXMsgType.Emoji, msg);
if (path == null)
{
#if DEBUG
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Emoji Error Path=>", path));
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Emoji Error Msg=>", JsonConvert.SerializeObject(msg)));
#endif
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "表情未预下载或加密表情");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:300px;max-width:300px;\"/></p></div>", path);
}
else if (msg.Type == 49)
{
if (msg.SubType == 6 || msg.SubType == 40)
{
string? path = reader.GetAttachment(WXMsgType.File, msg);
if (path == null)
{
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
{
url = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}|{1}</p><p><a href=\"{2}\">点击访问</a></p></div>", appName, title, url);
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "文件不存在");
continue;
}
else
{
HtmlBody += string.Format("<p class=\"content\">{0}</p><p><a href=\"{1}\">点击访问</a></p></div>", "文件:" + path, path);
}
}
else if (msg.SubType == 19)
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
string title = "";
string record = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}</p>", title);
try
{
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/recorditem");
if (findNode != null)
{
if (findNode.Count > 0)
{
XmlDocument itemObj = new XmlDocument();
itemObj.LoadXml(findNode[0]!.InnerText);
XmlNodeList? itemNode = itemObj.DocumentElement.SelectNodes("/recordinfo/datalist/dataitem");
if (itemNode.Count > 0)
{
foreach (XmlNode node in itemNode)
{
string nodeMsg;
string name = node["sourcename"].InnerText;
if (node.Attributes["datatype"].InnerText == "1")
nodeMsg = node["datadesc1"].InnerText;
else if (node.Attributes["datatype"].InnerText == "2")
nodeMsg = "不支持的消息";
else
nodeMsg = node["datatitle"].InnerText;
HtmlBody += string.Format("<p class=\"content\">{0}{1}</p>", name, nodeMsg);
}
}
}
}
}
catch
{
HtmlBody += string.Format("<p class=\"content\">{0}</p>", "解析异常");
}
}
}
}
}
else if (msg.SubType == 57)
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
string title = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}</p>", title);
XmlNode? type = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/type");
if(type != null)
{
XmlNode? source = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/displayname");
XmlNode? text = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/content");
if(type.InnerText == "1" && source != null && text != null)
{
HtmlBody += string.Format("<p class=\"content\">[引用]{0}:{1}</p>", source.InnerText, text.InnerText);
}
else if(type.InnerText != "1" && source != null && text != null)
{
HtmlBody += string.Format("<p class=\"content\">[引用]{0}:非文本消息类型-{1}</p>", source.InnerText, type);
}
else
{
HtmlBody += string.Format("<p class=\"content\">未知的引用消息</p>");
}
}
}
}
}
}
else
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
{
url = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}|{1}</p><p><a href=\"{2}\">点击访问</a></p></div>", appName, title, url);
}
}
}
}
}
}
else if (msg.Type == 34)
{
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
if (path == null)
else if (msg.Type == 34)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
continue;
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
}
else
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
}
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
}
else
catch(Exception ex)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
err = true;
File.AppendAllText("Err.log", JsonConvert.SerializeObject(msg));
File.AppendAllText("Err.log", ex.ToString());
}
msgCount++;
if(msgCount % 50 == 0)
{
@@ -172,10 +337,14 @@ namespace WechatBakTool.Export
streamWriter.WriteLine(HtmlBody);
HtmlBody = "";
viewModel.ExportCount = msgCount.ToString();
if (err)
{
MessageBox.Show("本次导出发生了异常部分消息被跳过更新至最新版本后还有此问题请将Err.log反馈给开发谢谢。", "错误");
}
}
streamWriter.Close();
streamWriter.Dispose();
return true;
}
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
{

View File

@@ -40,7 +40,7 @@ namespace WechatBakTool.Export
}
public void SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
public bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
{
if (Contact == null)
throw new Exception("请初始化模版Not Use InitTemplate");
@@ -70,67 +70,74 @@ namespace WechatBakTool.Export
txtMsg = "[视频]";
break;
case 49:
try
if (msg.SubType == 6 || msg.SubType == 19 || msg.SubType == 40)
{
using (var decoder = LZ4Decoder.Create(true, 64))
txtMsg = "[文件]";
}
else
{
try
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
using (var decoder = LZ4Decoder.Create(true, 64))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
if (findNode.Count > 0)
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
title = findNode[0]!.InnerText;
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
{
url = findNode[0]!.InnerText;
}
}
txtMsg = string.Format("{0},标题:{1},链接:{2}", appName, title, url);
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
else
{
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
txtMsg = "[分享链接出错了]";
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
{
url = findNode[0]!.InnerText;
}
}
txtMsg = string.Format("{0},标题:{1},链接:{2}", appName, title, url);
}
else
{
txtMsg = "[分享链接出错了]";
}
}
else
{
txtMsg = "[分享链接出错了]";
}
}
}
catch
{
txtMsg = "[分享链接出错了]";
catch
{
txtMsg = "[分享链接出错了]";
}
}
break;
}
@@ -139,6 +146,7 @@ namespace WechatBakTool.Export
msgCount++;
viewModel.ExportCount = msgCount.ToString();
}
return true;
}
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
@@ -18,14 +19,14 @@ namespace WechatBakTool.Helpers
{
public class DecryptionHelper
{
const int IV_SIZE = 16;
const long IV_SIZE = 16;
const int HMAC_SHA1_SIZE = 20;
const int KEY_SIZE = 32;
const int AES_BLOCK_SIZE = 16;
const int DEFAULT_ITER = 64000;
const int DEFAULT_PAGESIZE = 4096; //4048数据 + 16IV + 20 HMAC + 12
const long DEFAULT_PAGESIZE = 4096; //4048数据 + 16IV + 20 HMAC + 12
const string SQLITE_HEADER = "SQLite format 3";
public static byte[]? GetWechatKey(string pid, bool mem_find_key, string account)
public static byte[]? GetWechatKey(string pid, int find_key_type, string account)
{
Process process = Process.GetProcessById(int.Parse(pid));
ProcessModule? module = ProcessHelper.FindProcessModule(process.Id, "WeChatWin.dll");
@@ -39,9 +40,7 @@ namespace WechatBakTool.Helpers
return null;
}
if (!mem_find_key)
if (find_key_type == 1)
{
List<VersionInfo>? info = null;
string json = File.ReadAllText("version.json");
@@ -68,60 +67,199 @@ namespace WechatBakTool.Helpers
}
}
}
else
else if(find_key_type == 2)
{
List<int> read = ProcessHelper.FindProcessMemory(process.Handle, module, account);
if (read.Count >= 2)
{
byte[] buffer = new byte[8];
int key_offset = read[1] - 64;
if (ProcessHelper.ReadProcessMemory(process.Handle, module.BaseAddress + key_offset, buffer, buffer.Length, out _))
if (NativeAPI.ReadProcessMemory(process.Handle, module.BaseAddress + key_offset, buffer, buffer.Length, out _))
{
ulong addr = BitConverter.ToUInt64(buffer, 0);
byte[] key_bytes = new byte[32];
if (ProcessHelper.ReadProcessMemory(process.Handle, (IntPtr)addr, key_bytes, key_bytes.Length, out _))
if (NativeAPI.ReadProcessMemory(process.Handle, (IntPtr)addr, key_bytes, key_bytes.Length, out _))
{
return key_bytes;
}
}
}
}
else if (find_key_type == 3)
{
string searchString = "-----BEGIN PUBLIC KEY-----";
List<long> addr = NativeAPIHelper.SearchProcessAllMemory(process, searchString);
if (addr.Count > 0)
{
foreach (long a in addr)
{
byte[] buffer = new byte[module.ModuleMemorySize];
byte[] search = BitConverter.GetBytes(a);
Array.Resize(ref search, 8);
int read = 0;
List<int> offset = new List<int>();
if (NativeAPI.ReadProcessMemory(process.Handle, module.BaseAddress, buffer, buffer.Length, out read))
{
for (int i = 0; i < buffer.Length - 1; i++)
{
if (buffer[i] == search[0])
{
for (int s = 1; s < search.Length; s++)
{
if (buffer[i + s] != search[s])
break;
if (s == search.Length - 1)
{
long iii = (long)module.BaseAddress + i - 0xd8;
byte[] key = new byte[8];
if (NativeAPI.ReadProcessMemory(process.Handle, new IntPtr(iii), key, key.Length, out _))
{
ulong key_addr = BitConverter.ToUInt64(key, 0);
byte[] key_bytes = new byte[32];
NativeAPI.ReadProcessMemory(process.Handle, (IntPtr)key_addr, key_bytes, key_bytes.Length, out _);
string key1 = BitConverter.ToString(key_bytes, 0);
return key_bytes;
}
}
}
}
}
}
}
}
else
{
throw new Exception("搜索不到微信账号,请确认用户名是否正确,如错误请重新新建工作区,务必确认账号是否正确");
}
}
else if (find_key_type == 3)
{
string searchString = "-----BEGIN PUBLIC KEY-----";
}
return null;
}
public static byte[] DecryptDB(byte[] db_file_bytes, byte[] password_bytes)
public static string GetMD5(string text)
{
MD5 md5 = MD5.Create();
byte[] bs = Encoding.UTF8.GetBytes(text);
byte[] hs = md5.ComputeHash(bs);
StringBuilder sb = new StringBuilder();
foreach(byte b in hs)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
public static void DecryptDB(string file, string to_file, byte[] password_bytes)
{
//数据库头16字节是盐值
var salt = db_file_bytes.Take(16).ToArray();
byte[] salt_key = new byte[16];
FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
fileStream.Read(salt_key, 0, 16);
//HMAC验证时用的盐值需要亦或0x3a
byte[] hmac_salt = new byte[16];
for (int i = 0; i < salt.Length; i++)
for (int i = 0; i < salt_key.Length; i++)
{
hmac_salt[i] = (byte)(salt[i] ^ 0x3a);
hmac_salt[i] = (byte)(salt_key[i] ^ 0x3a);
}
//计算保留段长度
int reserved = IV_SIZE;
long reserved = IV_SIZE;
reserved += HMAC_SHA1_SIZE;
reserved = ((reserved % AES_BLOCK_SIZE) == 0) ? reserved : ((reserved / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
//密钥扩展分别对应AES解密密钥和HMAC验证密钥
byte[] key = new byte[KEY_SIZE];
byte[] hmac_key = new byte[KEY_SIZE];
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(password_bytes, password_bytes.Length, salt, salt.Length, DEFAULT_ITER, key.Length, key);
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(password_bytes, password_bytes.Length, salt_key, salt_key.Length, DEFAULT_ITER, key.Length, key);
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(key, key.Length, hmac_salt, hmac_salt.Length, 2, hmac_key.Length, hmac_key);
int page_no = 0;
int offset = 16;
long page_no = 0;
long offset = 16;
Console.WriteLine("开始解密...");
var hmac_sha1 = HMAC.Create("HMACSHA1");
hmac_sha1!.Key = hmac_key;
List<byte> decrypted_file_bytes = new List<byte>();
while (page_no < db_file_bytes.Length / DEFAULT_PAGESIZE)
FileStream tofileStream = new FileStream(to_file, FileMode.OpenOrCreate, FileAccess.Write);
using (fileStream)
{
try
{
// 当前分页小于计算分页数
while (page_no < fileStream.Length / DEFAULT_PAGESIZE)
{
// 读内容
byte[] decryped_page_bytes = new byte[DEFAULT_PAGESIZE];
byte[] going_to_hashed = new byte[DEFAULT_PAGESIZE - reserved - offset + IV_SIZE + 4];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + offset, SeekOrigin.Begin);
fileStream.Read(going_to_hashed, 0, Convert.ToInt32(DEFAULT_PAGESIZE - reserved - offset + IV_SIZE));
// 分页标志
var page_bytes = BitConverter.GetBytes(page_no + 1);
page_bytes.CopyTo(going_to_hashed, DEFAULT_PAGESIZE - reserved - offset + IV_SIZE);
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Length);
// 取分页hash
byte[] hash_mac_cached = new byte[hash_mac_compute.Length];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved + IV_SIZE, SeekOrigin.Begin);
fileStream.Read(hash_mac_cached, 0, hash_mac_compute.Length);
if (!hash_mac_compute.SequenceEqual(hash_mac_cached) && page_no == 0)
{
Console.WriteLine("Hash错误...");
return;
}
else
{
if (page_no == 0)
{
var header_bytes = Encoding.ASCII.GetBytes(SQLITE_HEADER);
header_bytes.CopyTo(decryped_page_bytes, 0);
}
// 加密内容
byte[] page_content = new byte[DEFAULT_PAGESIZE - reserved - offset];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + offset, SeekOrigin.Begin);
fileStream.Read(page_content, 0, Convert.ToInt32(DEFAULT_PAGESIZE - reserved - offset));
// iv
byte[] iv = new byte[16];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + (DEFAULT_PAGESIZE - reserved), SeekOrigin.Begin);
fileStream.Read(iv, 0, 16);
var decrypted_content = AESDecrypt(page_content, key, iv);
decrypted_content.CopyTo(decryped_page_bytes, offset);
// 保留
byte[] reserved_byte = new byte[reserved];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved, SeekOrigin.Begin);
fileStream.Read(reserved_byte, 0, Convert.ToInt32(reserved));
reserved_byte.CopyTo(decryped_page_bytes, DEFAULT_PAGESIZE - reserved);
tofileStream.Write(decryped_page_bytes, 0, decryped_page_bytes.Length);
}
page_no++;
offset = 0;
}
}catch(Exception ex)
{
File.AppendAllText("err.log", "page=>" + page_no.ToString() + "\r\n");
File.AppendAllText("err.log", "size=>" + fileStream.Length.ToString() + "\r\n");
File.AppendAllText("err.log", "postion=>" + ((page_no * DEFAULT_PAGESIZE) + offset).ToString() + "\r\n");
File.AppendAllText("err.log", ex.ToString() + "\r\n");
}
}
/*
* 旧版解密
while (page_no < fileStream.Length / DEFAULT_PAGESIZE)
{
byte[] decryped_page_bytes = new byte[DEFAULT_PAGESIZE];
byte[] going_to_hashed = new byte[DEFAULT_PAGESIZE - reserved - offset + IV_SIZE + 4];
@@ -129,8 +267,7 @@ namespace WechatBakTool.Helpers
var page_bytes = BitConverter.GetBytes(page_no + 1);
page_bytes.CopyTo(going_to_hashed, DEFAULT_PAGESIZE - reserved - offset + IV_SIZE);
//计算分页的Hash
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Count());
//取出分页中存储的Hash
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Length);
var hash_mac_cached = db_file_bytes.Skip((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved + IV_SIZE).Take(hash_mac_compute.Length).ToArray();
//对比两个Hash
if (!hash_mac_compute.SequenceEqual(hash_mac_cached))
@@ -159,8 +296,9 @@ namespace WechatBakTool.Helpers
{
decrypted_file_bytes.Add(item);
}
}
return decrypted_file_bytes.ToArray();
}*/
tofileStream.Close();
tofileStream.Dispose();
}
public static byte[] AESDecrypt(byte[] content, byte[] key, byte[] iv)
{
@@ -179,99 +317,50 @@ namespace WechatBakTool.Helpers
{
return BitConverter.ToString(bytes, 0).Replace("-", string.Empty).ToLower().ToUpper();
}
private readonly static List<byte[]> ImgHeader = new List<byte[]>()
{
new byte[] { 0xFF, 0xD8 },//JPG
new byte[] { 0x89, 0x50 },//PNG
new byte[] { 0x42, 0x4D },//BMP
new byte[] { 0x47, 0x49 },//GIF
new byte[] { 0x49, 0x49 },//TIF
new byte[] { 0x4D, 0x4D },//TIF
};
public static byte[] DecImage(string source)
{
//读取数据
byte[] fileBytes = File.ReadAllBytes(source);
//算差异转换
byte key = GetImgKey(fileBytes);
fileBytes = ConvertData(fileBytes, key);
return fileBytes;
foreach (byte[] b in ImgHeader)
{
byte t = (byte)(fileBytes[0] ^ b[0]);
byte[] decData = fileBytes.Select(b => (byte)(b ^ t)).ToArray();
if (b[1] != decData[1])
continue;
else
{
return decData;
}
}
return new byte[0];
}
public static string CheckFileType(byte[] data)
{
switch (data[0])
{
case 0XFF: //byte[] jpg = new byte[] { 0xFF, 0xD8, 0xFF };
{
if (data[1] == 0xD8 && data[2] == 0xFF)
{
return ".jpg";
}
break;
}
case 0x89: //byte[] png = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
{
if (data[1] == 0x50 && data[2] == 0x4E && data[7] == 0x0A)
{
return ".png";
}
break;
}
case 0x42: //byte[] bmp = new byte[] { 0x42, 0x4D };
{
if (data[1] == 0X4D)
{
return ".bmp";
}
break;
}
case 0x47: //byte[] gif = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39(0x37), 0x61 };
{
if (data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38 && data[5] == 0x61)
{
return ".gif";
}
break;
}
case 0x49: // byte[] tif = new byte[] { 0x49, 0x49, 0x2A, 0x00 };
{
if (data[1] == 0x49 && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
case 0x4D: //byte[] tif = new byte[] { 0x4D, 0x4D, 0x2A, 0x00 };
{
if (data[1] == 0x4D && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
}
return ".dat";
}
private static byte GetImgKey(byte[] fileRaw)
{
byte[] raw = new byte[8];
for (int i = 0; i < 8; i++)
{
raw[i] = fileRaw[i];
}
for (byte key = 0x01; key < 0xFF; key++)
{
byte[] buf = new byte[8];
raw.CopyTo(buf, 0);
if (CheckFileType(ConvertData(buf, key)) != ".dat")
{
return key;
}
}
return 0x00;
}
private static byte[] ConvertData(byte[] data, byte key)
{
for (int i = 0; i < data.Length; i++)
{
data[i] ^= key;
}
return data;
if (data[0] == 0xFF && data[1] == 0xD8)
return ".jpg";
else if (data[0] == 0x89 && data[1] == 0x50)
return ".png";
else if (data[0] == 0x42 && data[1] == 0X4D)
return ".bmp";
else if (data[0] == 0x47 && data[1] == 0x49)
return ".gif";
else if (data[0] == 0x49 && data[1] == 0x49)
return ".tif";
else if (data[0] == 0x4D && data[1] == 0x4D)
return ".tif";
else
return ".dat";
}
public static string SaveDecImage(byte[] fileRaw,string source,string to_dir,string type)
{
@@ -297,16 +386,8 @@ namespace WechatBakTool.Helpers
{
FileInfo info = new FileInfo(file);
viewModel.LabelStatus = "正在解密" + info.Name;
var db_bytes = File.ReadAllBytes(file);
var decrypted_file_bytes = DecryptDB(db_bytes, key);
if (decrypted_file_bytes == null || decrypted_file_bytes.Length == 0)
{
Console.WriteLine("解密后的数组为空");
}
else
{
File.WriteAllBytes(Path.Combine(decPath, info.Name), decrypted_file_bytes);
}
string to_file = Path.Combine(decPath, info.Name);
DecryptDB(file,to_file, key);
}
}
}

View File

@@ -15,7 +15,10 @@ namespace WechatBakTool.Helpers
public static string FromDevicePath(string devicePath)
{
var drive = Array.Find(DriveInfo.GetDrives(), d => devicePath.StartsWith(d.GetDevicePath(), StringComparison.InvariantCultureIgnoreCase));
var drive = Array.Find(
DriveInfo.GetDrives(), d =>
devicePath.StartsWith(d.GetDevicePath() + "\\", StringComparison.InvariantCultureIgnoreCase)
);
return drive != null ?
devicePath.ReplaceFirst(drive.GetDevicePath(), drive.GetDriveLetter()) :
null;

View File

@@ -12,6 +12,12 @@ namespace WechatBakTool.Helpers
internal static uint NTSTATUS_STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
internal static uint NTSTATUS_STATUS_ACCESS_DENIED = 0xC0000022;
internal static uint MEM_COMMIT = 0x1000;
internal static uint PAGE_READONLY = 0x02;
internal static uint PAGE_READWRITE = 0x04;
internal static uint PAGE_EXECUTE = 0x10;
internal static uint PAGE_EXECUTE_READ = 0x20;
// API Constants
internal static uint SystemExtendedHandleInformation = 0x40;
internal static uint DUPLICATE_SAME_ACCESS = 0x2;
@@ -115,6 +121,20 @@ namespace WechatBakTool.Helpers
public uint Reserved;
}
public struct MEMORY_BASIC_INFORMATION64
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public uint __alignment1;
public ulong RegionSize;
public uint State;
public uint Protect;
public uint Type;
public uint __alignment2;
}
// Enums
//=================================================
@@ -208,5 +228,12 @@ namespace WechatBakTool.Helpers
[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll")]
internal static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, uint dwLength);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
}
}

View File

@@ -48,17 +48,19 @@ namespace WechatBakTool.Helpers
uint nLength = 0;
hObjectName = AllocManagedMemory(256 * 1024);
// 查询句柄名称
while (NtQueryObject(ipHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength) == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
Task.Run(() =>
{
FreeManagedMemory(hObjectName);
if (nLength == 0)
// 查询句柄名称
while (NtQueryObject(ipHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength) == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
{
Console.WriteLine("Length returned at zero!");
return "";
FreeManagedMemory(hObjectName);
if (nLength == 0)
{
Console.WriteLine("Length returned at zero!");
}
hObjectName = AllocManagedMemory(nLength);
}
hObjectName = AllocManagedMemory(nLength);
}
}).Wait(100);
OBJECT_NAME_INFORMATION? objObjectName = new OBJECT_NAME_INFORMATION();
objObjectName = Marshal.PtrToStructure(hObjectName, objObjectName.GetType()) as OBJECT_NAME_INFORMATION?;
if (objObjectName == null)
@@ -147,5 +149,52 @@ namespace WechatBakTool.Helpers
// Return list
return ltei;
}
public static List<long> SearchProcessAllMemory(Process process, string searchString)
{
IntPtr minAddress = IntPtr.Zero;
IntPtr maxAddress = IntPtr.MaxValue;
List<long> addrList = new List<long>();
while (minAddress.ToInt64() < maxAddress.ToInt64())
{
MEMORY_BASIC_INFORMATION64 memInfo;
int result = VirtualQueryEx(process.Handle, minAddress, out memInfo, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION64)));
if (result == 0)
{
break;
}
if (memInfo.State == MEM_COMMIT && (memInfo.Protect == PAGE_EXECUTE || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_READWRITE || memInfo.Protect == PAGE_READONLY))
{
byte[] buffer = new byte[(long)memInfo.RegionSize];
bool success = ReadProcessMemory(process.Handle, memInfo.BaseAddress, buffer, buffer.Length, out _);
if (success)
{
byte[] search = Encoding.ASCII.GetBytes(searchString);
for (int i = 0; i < buffer.Length - 8; i++)
{
if (buffer[i] == search[0])
{
for (int s = 1; s < search.Length; s++)
{
if (buffer[i + s] != search[s])
break;
if (s == search.Length - 1)
{
addrList.Add((long)memInfo.BaseAddress + i);
}
}
}
}
}
}
minAddress = new IntPtr(memInfo.BaseAddress.ToInt64() + (long)memInfo.RegionSize);
}
return addrList;
}
}
}

View File

@@ -32,7 +32,7 @@ namespace WechatBakTool.Helpers
List<int> offset = new List<int>();
int readBytes;
bool success = ReadProcessMemory(processHandle, module.BaseAddress, buffer, buffer.Length,out readBytes);
bool success = NativeAPI.ReadProcessMemory(processHandle, module.BaseAddress, buffer, buffer.Length,out readBytes);
if (!success || readBytes == 0)
{
@@ -64,14 +64,12 @@ namespace WechatBakTool.Helpers
{
byte[] array = new byte[nSize];
int readByte;
if (!ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, out readByte))
if (!NativeAPI.ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, out readByte))
return null;
else
return array;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
}

View File

@@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d" WindowStartupLocation="CenterScreen" WindowStyle="None" WindowState="Normal" Background="Transparent" AllowsTransparency="True" ResizeMode="NoResize"
Title="Main2" Height="550" Width="950" >
Title="WechatBakTool" Height="550" Width="950" >
<Window.Resources>
<Style TargetType="local:Main2">
<!-- 设置窗体的WindowChrome -->

View File

@@ -93,6 +93,7 @@ namespace WechatBakTool
private void new_workspace_fill_MouseDown(object sender, MouseButtonEventArgs e)
{
list_workspace.SelectedItem = null;
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/CreateWork.xaml?datatime=" + DateTime.Now.Ticks));
}

View File

@@ -10,7 +10,7 @@ using System.Windows.Media.Imaging;
namespace WechatBakTool.Model
{
public class UserBakConfig : INotifyPropertyChanged
public class UserBakConfig
{
public string UserResPath { get; set; } = "";
public string UserWorkspacePath { get; set; } = "";
@@ -25,12 +25,6 @@ namespace WechatBakTool.Model
public string Account { get; set; } = "";
public string Friends_Number { get; set; } = "-";
public string Msg_Number { get; set; } = "-";
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WXCount
@@ -120,8 +114,12 @@ namespace WechatBakTool.Model
{
[Column("localId")]
public int LocalId { get; set; }
[Column("MsgSequence")]
public int MsgSequence { get; set; }
[Column("Type")]
public int Type { get; set; }
[Column("SubType")]
public int SubType { get; set; }
[Column("CreateTime")]
public long CreateTime { get; set; }
[Column("IsSender")]
@@ -132,6 +130,7 @@ namespace WechatBakTool.Model
public string StrTalker { get; set; } = "";
[Column("StrContent")]
public string StrContent { get; set; } = "";
public string DisplayContent { get; set; } = "";
[Column("CompressContent")]
public byte[]? CompressContent { get; set; }
[Column("BytesExtra")]
@@ -161,6 +160,15 @@ namespace WechatBakTool.Model
public string Reserved0 { get; set; } = "";
}
public class WXContactHT
{
public string UserName { get; set; } = "";
public string NickName { get; set; } = "";
public string LastMsg { get; set; } = "";
public int FileCount { get; set; } = 1;
public string AvatarString { get; set; } = "";
public bool Hidden { get; set; } = false;
}
[Table("Contact")]
public class WXContact
{

View File

@@ -26,8 +26,9 @@
<TextBox IsEnabled="{Binding IsEnable}" x:Name="txt_username" Margin="35,300,0,0" Width="280" HorizontalAlignment="Left" VerticalAlignment="Top" BorderThickness="0,0,0,1" Text="{Binding UserName}" />
<Label Margin="30,350,0,0" Content="请选择解密方式:" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<RadioButton Margin="35,380,0,0" Content="固定地址查找" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=1}" />
<RadioButton Margin="35,405,0,0" Content="用户名推断查找" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=2}"/>
<RadioButton Margin="35,380,0,0" Content="固定地址查找【保底】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=1}" />
<RadioButton Margin="35,405,0,0" Content="用户名推断查找【不稳定】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=2}"/>
<RadioButton Margin="35,430,0,0" Content="公钥头推断查找【推荐】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=3}"/>
<Button Name="btn_create_worksapce" Margin="0,0,35,50" Height="60" Width="100" HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="创建工作区" BorderThickness="0" IsEnabled="{Binding IsEnable}" Background="#2775b6" Foreground="White" Click="btn_create_worksapce_Click">
<Button.Resources>
@@ -38,7 +39,7 @@
</Button>
<Label Margin="210,350,0,0" Content="其他选项:" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Margin="215,380,0,0" Content="打包资源文件夹(功能规划中)" IsEnabled="False" HorizontalAlignment="Left" VerticalAlignment="Top" />
<CheckBox Margin="215,405,0,0" Content="手动模式(功能规划中)" IsEnabled="False" HorizontalAlignment="Left" VerticalAlignment="Top" />
<CheckBox Name="cb_manual" Checked="cb_manual_Checked" Margin="215,405,0,0" Content="手动模式" Visibility="Visible" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Label Name="lab_status" Content="{Binding LabelStatus}" HorizontalAlignment="Left" Margin="30,450,0,0" VerticalAlignment="Top"/>
</Grid>
</Page>

View File

@@ -97,6 +97,7 @@ namespace WechatBakTool.Pages
private void btn_create_worksapce_Click(object sender, RoutedEventArgs e)
{
ViewModel.IsEnable = false;
Task.Run(() => {
if (ViewModel.KeyType != -1)
{
@@ -147,5 +148,31 @@ namespace WechatBakTool.Pages
ViewModel.IsEnable = true;
});
}
private void cb_manual_Checked(object sender, RoutedEventArgs e)
{
MessageBox.Show("该功能仅限用于网络安全研究用途使用,红队同学请在合规授权下进行相关操作","重要提醒!!!!!!!!!");
if (MessageBox.Show("我确认获取到合规授权,仅用于网络安全用途使用", "信息确认", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
if (File.Exists("auth.txt"))
{
string auth = File.ReadAllText("auth.txt");
// 我已知晓手动模式可能潜在的法律及道德风险,我明白非法使用将要承担相关法律责任。
if (DecryptionHelper.GetMD5(auth) == "295f634af60d61dfa52a5f35849ac42b")
{
MessageBox.Show("已经创建空的配置文件,请完善该配置文件后,点击开始解密","提示");
MessageBox.Show("该功能现阶段暂未启用","错误");
}
}
else
{
MessageBox.Show("未完成声明文件,请先确认声明", "错误");
}
}
else
{
cb_manual.IsChecked = false;
}
}
}
}

View File

@@ -14,5 +14,12 @@
<CheckBox Name="cb_user" Margin="90,85" Content="好友聊天" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Button Name="btn_export_all" Margin="35,110" Height="26" Width="60" Content="导出" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_export_all_Click"></Button>
<Label Content="{Binding LabelStatus}" HorizontalAlignment="Left" Margin="110,110,0,0" VerticalAlignment="Top"/>
<Label Margin="30,155,0,0" Content="表情包预下载" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
<Button Name="btn_emoji_download" Margin="35,185,0,0" Height="26" Width="60" Content="导出" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_emoji_download_Click"></Button>
<Label Margin="30,225,0,0" Content="旧版分析工具" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
<Button Name="btn_analyse" Margin="35,255,0,0" Height="26" Width="60" Content="打开" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_analyse_Click" ></Button>
</Grid>
</Page>

View File

@@ -1,9 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -13,6 +17,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Xml;
using WechatBakTool.Export;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
@@ -26,6 +31,8 @@ namespace WechatBakTool.Pages
{
private WorkspaceViewModel workspaceViewModel = new WorkspaceViewModel();
public WXUserReader? UserReader;
private List<WXContact>? ExpContacts;
private bool Suspend = false;
public Manager()
{
DataContext = workspaceViewModel;
@@ -57,15 +64,25 @@ namespace WechatBakTool.Pages
});
if (UserReader != null)
{
List<WXContact>? contacts = UserReader.GetWXContacts().ToList();
foreach (var contact in contacts)
if (!Suspend)
ExpContacts = UserReader.GetWXContacts().ToList();
else
Suspend = false;
foreach (var contact in ExpContacts!)
{
if (Suspend)
{
workspaceViewModel.ExportCount = "已暂停";
return;
}
if (group && contact.UserName.Contains("@chatroom"))
{
workspaceViewModel.WXContact = contact;
ExportMsg(contact);
}
if (user)
if (user && !contact.UserName.Contains("@chatroom") && !contact.UserName.Contains("gh_"))
{
workspaceViewModel.WXContact = contact;
ExportMsg(contact);
@@ -79,12 +96,50 @@ namespace WechatBakTool.Pages
private void ExportMsg(WXContact contact)
{
workspaceViewModel.ExportCount = "";
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
// string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
string name = contact.NickName;
name = name.Replace(@"\", "");
name = Regex.Replace(name, "[ \\[ \\] \\^ \\-_*×――(^)$%~!/@#$…&%¥—+=<>《》|!??::•`·、。,;,.;\"‘’“”-]", "");
string path = Path.Combine(
Main2.CurrentUserBakConfig!.UserWorkspacePath,
string.Format(
"{0}-{1}.html",
contact.UserName,
contact.Remark == "" ? name : contact.Remark
)
);
IExport export = new HtmlExport();
export.InitTemplate(contact, path);
export.SetMsg(UserReader!, contact, workspaceViewModel);
export.SetEnd();
export.Save(path);
if(export.SetMsg(UserReader!, contact, workspaceViewModel))
{
export.SetEnd();
export.Save(path);
}
}
private void btn_emoji_download_Click(object sender, RoutedEventArgs e)
{
if (UserReader != null)
{
Task.Run(() =>
{
UserReader.PreDownloadEmoji();
MessageBox.Show("所有表情预下载完毕");
});
}
}
private void btn_analyse_Click(object sender, RoutedEventArgs e)
{
if (UserReader == null || Main2.CurrentUserBakConfig == null)
{
MessageBox.Show("请先读取数据");
return;
}
Analyse analyse = new Analyse(Main2.CurrentUserBakConfig, UserReader);
analyse.Show();
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using WechatBakTool.Model;
namespace WechatBakTool.Pages
{
public class MsgTemplateSelector : DataTemplateSelector
{
public override DataTemplate? SelectTemplate(object item, DependencyObject container)
{
FrameworkElement? element = container as FrameworkElement;
if (element != null && item != null && item is WXMsg)
{
WXMsg? wxmsg = item as WXMsg;
if (wxmsg == null)
return null;
if (wxmsg.Type == 1)
return
element.FindResource("MsgText") as DataTemplate;
else if (wxmsg.Type == 3)
return
element.FindResource("MsgImage") as DataTemplate;
else
return
element.FindResource("MsgText") as DataTemplate;
}
return null;
}
}
}

View File

@@ -3,11 +3,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WechatBakTool.Pages"
xmlns:local="clr-namespace:WechatBakTool.Pages"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="720"
Title="Workspace" Background="White">
<Page.Resources>
<local:MsgTemplateSelector x:Key="MsgTemplateSelector"/>
<Style TargetType="ToggleButton" x:Key="ComboxStyleBtn">
<Setter Property="Template">
<Setter.Value>
@@ -126,7 +127,7 @@
</Setter.Value>
</Setter>
</Style>
<!-- 这里是listview滚动条的滑动块部分样式-->
<Style x:Key="ScrollBarThumbVertical" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
@@ -158,15 +159,14 @@
<Grid Margin="0">
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" Content="{Binding NickName}"/>
<Label Margin="60,25,0,8" VerticalAlignment="Top" HorizontalAlignment="Left" Width="380">
<TextBlock Text="{Binding StrContent}" TextWrapping="Wrap" />
<TextBlock Text="{Binding DisplayContent}" TextWrapping="Wrap" />
</Label>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MsgImage">
<Grid Margin="0">
<Image Width="40" Height="40" Margin="10" VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Avatar}" />
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="{Binding LastMsg}"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="1111"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MsgAudio">
@@ -214,8 +214,8 @@
</ListView.Resources>
</ListView>
<Label Content="{Binding WXContact.NickName}" HorizontalAlignment="Left" Margin="258,21,0,0" VerticalAlignment="Top"/>
<ListView x:Name="list_msg" Margin="230,60,0,60" Background="Transparent" BorderThickness="0,1,0,1" BorderBrush="#BB2775b6" ItemTemplate="{DynamicResource MsgText}">
<ListView x:Name="list_msg" Margin="230,60,0,60" Background="Transparent" BorderThickness="0,1,0,1" BorderBrush="#BB2775b6" ItemTemplateSelector="{StaticResource MsgTemplateSelector}" ItemsSource="{Binding WXMsgs}" ScrollViewer.ScrollChanged="list_msg_ScrollChanged" >
</ListView>
<ComboBox Name="cb_export" Width="120" Height="30" Style="{StaticResource ComboBoxStyle}" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="30,15" ItemsSource="{Binding ExportItems}" SelectedItem="{Binding SelectExportItem}" DisplayMemberPath="Name" SelectedValuePath="Value" IsEnabled="{Binding SelectContact}" Background="#2775b6" />
<Button x:Name="btn_open_workspace" Width="80" Height="30" Style="{StaticResource ButtonStyle}" Content="打开文件夹" BorderBrush="Transparent" BorderThickness="0" Background="#2775b6" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,390,15" Click="btn_open_workspace_Click">
@@ -225,7 +225,7 @@
</Style>
</Button.Resources>
</Button>
<Button x:Name="btn_analyse" Width="80" Height="30" Style="{StaticResource ButtonStyle}" Content="旧版消息工具" BorderBrush="Transparent" BorderThickness="0" Background="#2775b6" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,292,15" Click="btn_analyse_Click" >
<Button x:Name="btn_pre_emoji" Width="80" Height="30" Style="{StaticResource ButtonStyle}" Content="表情预下载" BorderBrush="Transparent" BorderThickness="0" Background="#2775b6" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,292,15" IsEnabled="{Binding SelectContact}" Click="btn_pre_emoji_Click" >
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3"/>

View File

@@ -1,4 +1,6 @@
using System;
using JiebaNet.Segmenter;
using JiebaNet.Segmenter.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
@@ -8,7 +10,6 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
@@ -17,8 +18,17 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using WechatBakTool.Export;
using WechatBakTool.Helpers;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
using WordCloudSharp;
using System.Drawing;
using System.Windows.Controls;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using System.Drawing.Imaging;
using System.Threading;
using System.Runtime.CompilerServices;
namespace WechatBakTool.Pages
{
@@ -29,11 +39,15 @@ namespace WechatBakTool.Pages
{
public WXUserReader? UserReader;
private WorkspaceViewModel ViewModel = new WorkspaceViewModel();
private int PageSize = 100;
private int Postion = 0;
private bool Loading = false;
public Workspace()
{
ViewModel.ExportItems = new System.Collections.ObjectModel.ObservableCollection<ExportItem> {
new ExportItem(){ Name="导出为HTML",Value=1 },
new ExportItem(){ Name="导出为TXT",Value=2 },
new ExportItem(){ Name="与他的词云",Value=3 },
};
ViewModel.SelectExportItem = ViewModel.ExportItems[0];
InitializeComponent();
@@ -47,7 +61,11 @@ namespace WechatBakTool.Pages
UserReader = new WXUserReader(config);
if (config.Decrypt)
{
ViewModel.Contacts = UserReader.GetWXContacts();
ViewModel.Contacts = null;
Task.Run(() => {
ViewModel.Contacts = UserReader.GetWXContacts();
});
}
}
}
@@ -63,14 +81,43 @@ namespace WechatBakTool.Pages
private void list_users_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.WXMsgs.Clear();
Postion = 0;
ViewModel.ExportCount = "";
ViewModel.WXContact = list_users.SelectedItem as WXContact;
if(ViewModel.WXContact == null || UserReader == null)
{
loadMsg();
if (ViewModel.WXMsgs.Count == 0)
return;
}
private void loadMsg()
{
Loading = true;
ViewModel.WXContact = list_users.SelectedItem as WXContact;
if (ViewModel.WXContact == null || UserReader == null)
return;
List<WXMsg>? list = UserReader.GetWXMsgs(ViewModel.WXContact.UserName, Postion, PageSize);
// Trace.WriteLine(string.Format("{0}->{1}", PageSize, Postion));
if (list == null)
return;
if (list.Count == 0)
return;
foreach (WXMsg w in list)
{
ViewModel.WXMsgs.Add(w);
}
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
list_msg.ItemsSource = msgs;
Postion = int.Parse(list.Max(x => x.CreateTime).ToString());
list_msg.ScrollIntoView(list[0]);
Task.Run(() => {
Thread.Sleep(500);
Loading = false;
});
}
private void txt_find_user_TextChanged(object sender, TextChangedEventArgs e)
@@ -82,7 +129,25 @@ namespace WechatBakTool.Pages
if (txt_find_user.Text == "搜索...")
findName = "";
ViewModel.Contacts = UserReader.GetWXContacts(findName);
Task.Run(() =>
{
ViewModel.Contacts = UserReader.GetWXContacts(findName);
// 保底回落搜索已删除人员
if (ViewModel.Contacts.Count == 0)
{
var i = UserReader.GetWXMsgs(txt_find_user.Text);
if (i != null)
{
var g = i.GroupBy(x => x.StrTalker);
ViewModel.Contacts = new System.Collections.ObjectModel.ObservableCollection<WXContact>();
foreach (var x in g)
{
string name = x.Key;
ViewModel.Contacts.Add(new WXContact() { UserName = name, NickName = name });
}
}
}
});
}
private void txt_find_user_GotFocus(object sender, RoutedEventArgs e)
@@ -100,12 +165,20 @@ namespace WechatBakTool.Pages
MessageBox.Show("请选择联系人", "错误");
return;
}
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName + ".txt");
IExport export = new TXTExport();
export.InitTemplate(ViewModel.WXContact, path);
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
export.SetEnd();
export.Save(path);
try
{
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName + ".txt");
IExport export = new TXTExport();
export.InitTemplate(ViewModel.WXContact, path);
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
export.SetEnd();
export.Save(path);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
MessageBox.Show("导出完成");
}
@@ -114,23 +187,10 @@ namespace WechatBakTool.Pages
Process.Start("explorer.exe ", Main2.CurrentUserBakConfig!.UserWorkspacePath);
}
private void btn_analyse_Click(object sender, RoutedEventArgs e)
{
if (UserReader == null || Main2.CurrentUserBakConfig == null)
{
MessageBox.Show("请先读取数据");
return;
}
Analyse analyse = new Analyse(Main2.CurrentUserBakConfig, UserReader);
analyse.Show();
}
private void Export_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
if (ViewModel.WXContact == null || UserReader == null)
{
MessageBox.Show("请选择联系人", "错误");
@@ -141,7 +201,95 @@ namespace WechatBakTool.Pages
MessageBox.Show("请选择导出方式", "错误");
return;
}
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName);
if(ViewModel.SelectExportItem.Value == 3)
{
if(UserReader != null && ViewModel.WXContact != null)
{
System.Drawing.Image? mask = null;
if (File.Exists("mask.png"))
mask = System.Drawing.Image.FromFile("mask.png");
WordCloudSettingViewModel setting = new WordCloudSettingViewModel() {
ImgWidth = mask == null ? "1000": mask.Width.ToString(),
ImgHeight = mask == null ? "1000" : mask.Height.ToString(),
EnableRemoveOneKey = true,
};
Dispatcher.Invoke(() => {
WordCloudSetting wordCloudSetting = new WordCloudSetting(setting);
wordCloudSetting.ShowDialog();
});
var jieba = new JiebaSegmenter();
Counter<string> counter = new Counter<string>();
try
{
ViewModel.ExportCount = "词频统计ing...";
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
if (msgs != null)
{
foreach (WXMsg msg in msgs)
{
if (msg.Type == 1)
{
List<string> list = jieba.Cut(msg.StrContent).ToList();
counter.Add(list);
}
}
}
}
catch
{
ViewModel.ExportCount = "异常";
MessageBox.Show("词频统计发生异常,请检查字典文件是否存在", "错误");
return;
}
var orderBy = counter.MostCommon();
ViewModel.ExportCount = "移除部分词语...";
string[] remove_string_list = setting.RemoveKey.Split(",");
foreach(string remove_string in remove_string_list)
{
counter.Remove(remove_string);
}
foreach(var key in orderBy)
{
if (key.Key.Length == 1 && setting.EnableRemoveOneKey)
counter.Remove(key.Key);
}
ViewModel.ExportCount = "渲染词云结果";
string resultPath = "result.jpg";
WordCloud wordCloud;
if(mask != null)
wordCloud = new WordCloud(int.Parse(setting.ImgWidth), int.Parse(setting.ImgHeight), mask: mask, allowVerical: true, fontname: setting.Font);
else
wordCloud = new WordCloud(int.Parse(setting.ImgWidth), int.Parse(setting.ImgHeight), allowVerical: true, fontname: setting.Font);
if (orderBy.Count() >= setting.MaxKeyCount)
orderBy = orderBy.Take(setting.MaxKeyCount);
var result = wordCloud.Draw(orderBy.Select(it => it.Key).ToList(), orderBy.Select(it => it.Value).ToList());
result.Save(resultPath);
ViewModel.ExportCount = "完成";
MessageBox.Show("生成完毕请查看软件根目录result.jpg", "提示");
}
return;
}
string name = ViewModel.WXContact.NickName;
name = name.Replace(@"\", "");
name = Regex.Replace(name, "[ \\[ \\] \\^ \\-_*×――(^)$%~!/@#$…&%¥—+=<>《》|!??::•`·、。,;,.;\"‘’“”-]", "");
string path = Path.Combine(
Main2.CurrentUserBakConfig!.UserWorkspacePath,
string.Format(
"{0}-{1}",
ViewModel.WXContact.UserName,
ViewModel.WXContact.Remark == "" ? name : ViewModel.WXContact.Remark
)
);
IExport export;
#if DEBUG
@@ -170,5 +318,28 @@ namespace WechatBakTool.Pages
});
}
private void btn_pre_emoji_Click(object sender, RoutedEventArgs e)
{
if(UserReader != null && ViewModel.WXContact != null)
{
Task.Run(() => {
UserReader.PreDownloadEmoji(ViewModel.WXContact.UserName);
MessageBox.Show("用户所有表情预下载完毕");
});
}
}
private void list_msg_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (ViewModel.WXMsgs.Count == 0)
return;
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight && !Loading)
{
// 滚动条到达底部的处理逻辑
loadMsg();
}
}
}
}

View File

@@ -2,7 +2,7 @@
# WechatBakTool
基于C#开发的微信聊天记录备份分析工具,努力做最好用的微信备份工具。
- 理论支持64位版本所有微信[1]
- 理论支持64位版本所有微信支持两种方式非直接地址获取Key[1]
- 工作区概念,支持多微信切换操作。
- 支持导出Html文件TXT文件支持批量导出
- 支持聊天频率分析,全消息库内容搜索
@@ -12,36 +12,57 @@
- [x] 语音
- [x] 分享链接
- [x] 群聊
- [ ] 文件
- [ ] 表情
- [x] 系统消息
- [x] 文件
- [x] 引用/转发消息
- [x] 表情(需要预下载)
如果有什么好的建议或意见或者遇到什么问题欢迎提issue看到会回。
> [!NOTE]
> 反馈群815054692<br/>
> 如果觉得不错欢迎右上角点个star这是对作者的鼓励谢谢<br/>
> 进群请先Star项目然后问答消息留id<br/>
<br/>
### 免责声明
**本项目仅供学习使用,严禁商业使用**<br/>
**本项目仅供学习、研究使用,严禁商业使用**<br/>
**用于网络安全用途的,请确保在国家法律法规下使用**<br/>
**本项目完全免费,问你要钱的都是骗子**<br/>
**使用本项目初衷是作者研究微信数据库的运行使用,您使用本软件导致的后果,包含但不限于数据损坏,记录丢失等问题,作者不承担相关责任。**<br/>
**因软件特殊性质,请在使用时获得微信账号所有人授权**
**因软件特殊性质,请在使用时获得微信账号所有人授权,你当确保不侵犯他人个人隐私权,后果自行承担**<br/>
<br/>
### 隐私声明
**本项目不会上传任何你的数据至任何第三方系统**<br/>
**如果发生任何回传行为,请检查是否为第三方修改版本**<br/>
<br/>
### 近期开发规划
本项目技术栈为:
C# + .NET6.0 + WPF MVVM目前MVVM不是特别完全莫喷 <br/>
C# + .NET6.0 + WPF <br/>
- [x] ~~新版本UI界面开发~~
- [ ] 完善各类消息支持
- [x] 完善各类消息支持(已经初步完成)
- [x] ~~词云~~
- [ ] 性能优化
- [ ] 打包资源文件夹
- [ ] 手动模式(合适离线分析)
- [ ] 年度报告类分析(等美术资源中,没有资源不做)
<br/>
### 部分问题
Q支持手机端吗<br/>
A<b>在手机端</b>使用迁移功能即可,路径:我->设置->聊天->聊天记录迁移与备份->迁移<br/>
<br/>
Q怎么导出全部的记录<br/>
A工作区->右键->管理,就见了。<br/>
<br/>
Q解密工作区提示no such teble:MSG怎么办<br/>
A基本上都是因为刚迁移完缓存没写入到数据库导致的建议迁移完重启一次微信后再创建工作区<br/>
<br/>
### 使用说明
**本说明为新版本说明,即将发版**<br/>
0.安装.NET Desktop Runtime(如已经安装忽略)<br/>
0.安装.NET Desktop Runtime(注意是6.0版本的Desktop Runtime如已经安装忽略)<br/>
1.打开微信,并登录。<br/>
2.在软件左侧下方点击**新建工作区**<br/>
3.在**新建工作区界面**,选择要创建工作区的微信进程,并**确认下方微信号是否正确**<br/>
@@ -53,12 +74,13 @@ C# + .NET6.0 + WPF MVVM目前MVVM不是特别完全莫喷 <br/>
### 参考/引用
项目在开发过程中参考了以下项目或资料,有引用相关代码,如有需要,推荐您可以去参考下相关资料:
1. C#使用OpenSSL解密微信数据库这里注意一下64位适配问题注意dll引用 [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)<br/>
1. C#使用OpenSSL解密微信数据库这里注意一下64位适配问题注意dll引用另外解密的资源优化不是很好可以参考一下我改写的C#还需要注意一下超大文件的问题 [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)<br/>
2. C#使用地址获取微信Key [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
3. 解密微信语音我是直接调用解密反正都要ffmpeg多一个也是多多两个也是多懒得头铁实现 [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
4. 解密微信图片 [吾爱破解chenhahacjl/微信 DAT 图片解密 C#](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
5. 参考了句柄名称实现,注意获取句柄别看这里,#10 这个issue就是血泪 [huiyadanli/RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher)
6. 参考了句柄获取 [FuzzySecurity/Sharp-Suite](https://github.com/FuzzySecurity/Sharp-Suite)
7. 这个获取秘钥更通用一些 [SnowMeteors/GetWeChatKey](https://github.com/SnowMeteors/GetWeChatKey) ,用户名不是很稳定
### 其他声明
[1] 理论支持所有64位版本指用户名推断获取Key模式地址直接获取方式需要version.json支持更新不是很及时。
[1] 理论支持所有64位版本指用户名推断和公钥头推断地址直接获取方式需要version.json支持更新不是很及时。

79460
Resources/char_state_tab.json Normal file

File diff suppressed because it is too large Load Diff

17817
Resources/cn_synonym.txt Normal file

File diff suppressed because one or more lines are too long

349046
Resources/dict.txt Normal file

File diff suppressed because it is too large Load Diff

270132
Resources/idf.txt Normal file

File diff suppressed because it is too large Load Diff

89711
Resources/pos_prob_emit.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,258 @@
{
"E-e": -3.14e+100,
"E-d": -3.14e+100,
"E-g": -3.14e+100,
"E-f": -3.14e+100,
"E-a": -3.14e+100,
"E-c": -3.14e+100,
"E-b": -3.14e+100,
"E-m": -3.14e+100,
"S-rg": -10.275268591948773,
"E-o": -3.14e+100,
"E-n": -3.14e+100,
"E-i": -3.14e+100,
"E-h": -3.14e+100,
"E-k": -3.14e+100,
"E-j": -3.14e+100,
"E-u": -3.14e+100,
"E-t": -3.14e+100,
"E-w": -3.14e+100,
"E-v": -3.14e+100,
"E-q": -3.14e+100,
"E-p": -3.14e+100,
"E-s": -3.14e+100,
"M-bg": -3.14e+100,
"M-uj": -3.14e+100,
"E-y": -3.14e+100,
"E-x": -3.14e+100,
"E-z": -3.14e+100,
"B-uz": -3.14e+100,
"S-d": -3.903919764181873,
"M-rg": -3.14e+100,
"E-nt": -3.14e+100,
"B-d": -3.9750475297585357,
"B-uv": -3.14e+100,
"E-vi": -3.14e+100,
"B-mq": -6.78695300139688,
"M-rr": -3.14e+100,
"S-ag": -6.954113917960154,
"M-jn": -3.14e+100,
"E-l": -3.14e+100,
"M-rz": -3.14e+100,
"B-ud": -3.14e+100,
"S-an": -12.84021794941031,
"B-qg": -3.14e+100,
"B-ug": -3.14e+100,
"M-y": -3.14e+100,
"S-qg": -3.14e+100,
"S-z": -3.14e+100,
"S-y": -6.1970794699489575,
"S-x": -8.427419656069674,
"S-w": -3.14e+100,
"S-v": -3.053292303412302,
"S-u": -6.940320595827818,
"S-t": -3.14e+100,
"B-nrt": -4.985642733519195,
"S-r": -2.7635336784127853,
"S-q": -4.888658618255058,
"M-zg": -3.14e+100,
"S-o": -8.464460927750023,
"S-n": -3.8551483897645107,
"B-zg": -3.14e+100,
"S-l": -3.14e+100,
"S-k": -6.940320595827818,
"S-in": -3.14e+100,
"S-i": -3.14e+100,
"S-h": -8.650563207383884,
"S-g": -6.507826815331734,
"B-f": -5.491630418482717,
"S-e": -5.942513006281674,
"M-en": -3.14e+100,
"S-c": -4.786966795861212,
"S-b": -6.472888763970454,
"S-a": -3.9025396831295227,
"B-g": -3.14e+100,
"B-b": -5.018374362109218,
"B-c": -3.423880184954888,
"M-ug": -3.14e+100,
"B-a": -4.762305214596967,
"E-qe": -3.14e+100,
"M-x": -3.14e+100,
"E-nz": -3.14e+100,
"M-z": -3.14e+100,
"M-u": -3.14e+100,
"B-k": -3.14e+100,
"M-w": -3.14e+100,
"B-jn": -3.14e+100,
"S-yg": -13.533365129970255,
"B-o": -8.433498702146057,
"B-l": -4.905883584659895,
"B-m": -3.6524299819046386,
"M-m": -3.14e+100,
"M-l": -3.14e+100,
"M-o": -3.14e+100,
"M-n": -3.14e+100,
"M-i": -3.14e+100,
"M-h": -3.14e+100,
"B-t": -3.3647479094528574,
"M-ul": -3.14e+100,
"B-z": -7.045681111485645,
"M-d": -3.14e+100,
"M-mg": -3.14e+100,
"B-y": -9.844485675856319,
"M-a": -3.14e+100,
"S-nrt": -3.14e+100,
"M-c": -3.14e+100,
"M-uz": -3.14e+100,
"E-mg": -3.14e+100,
"B-i": -6.1157847275557105,
"M-b": -3.14e+100,
"E-uz": -3.14e+100,
"B-n": -1.6966257797548328,
"E-uv": -3.14e+100,
"M-ud": -3.14e+100,
"M-p": -3.14e+100,
"E-ul": -3.14e+100,
"E-mq": -3.14e+100,
"M-s": -3.14e+100,
"M-yg": -3.14e+100,
"E-uj": -3.14e+100,
"E-ud": -3.14e+100,
"S-ln": -3.14e+100,
"M-r": -3.14e+100,
"E-ng": -3.14e+100,
"B-r": -3.4098187790818413,
"E-en": -3.14e+100,
"M-qg": -3.14e+100,
"B-s": -5.522673590839954,
"S-rr": -3.14e+100,
"B-p": -4.200984132085048,
"B-dg": -3.14e+100,
"M-uv": -3.14e+100,
"S-zg": -3.14e+100,
"B-v": -2.6740584874265685,
"S-tg": -6.272842531880403,
"B-w": -3.14e+100,
"B-e": -8.563551830394255,
"M-k": -3.14e+100,
"M-j": -3.14e+100,
"B-df": -8.888974230828882,
"M-e": -3.14e+100,
"E-tg": -3.14e+100,
"M-t": -3.14e+100,
"E-nr": -3.14e+100,
"M-nrfg": -3.14e+100,
"B-nr": -2.2310495913769506,
"E-df": -3.14e+100,
"E-dg": -3.14e+100,
"S-jn": -3.14e+100,
"M-q": -3.14e+100,
"B-mg": -3.14e+100,
"B-ln": -3.14e+100,
"M-f": -3.14e+100,
"E-ln": -3.14e+100,
"E-yg": -3.14e+100,
"S-bg": -3.14e+100,
"E-ns": -3.14e+100,
"B-tg": -3.14e+100,
"E-qg": -3.14e+100,
"S-nr": -4.483663103956885,
"S-ns": -3.14e+100,
"M-vn": -3.14e+100,
"S-nt": -12.147070768850364,
"S-nz": -3.14e+100,
"S-ad": -11.048458480182255,
"B-yg": -3.14e+100,
"M-v": -3.14e+100,
"E-vn": -3.14e+100,
"S-ng": -4.913434861102905,
"M-g": -3.14e+100,
"M-nt": -3.14e+100,
"S-en": -3.14e+100,
"M-nr": -3.14e+100,
"M-ns": -3.14e+100,
"S-vq": -3.14e+100,
"B-uj": -3.14e+100,
"M-nz": -3.14e+100,
"B-qe": -3.14e+100,
"M-in": -3.14e+100,
"M-ng": -3.14e+100,
"S-vn": -11.453923588290419,
"E-zg": -3.14e+100,
"S-vi": -3.14e+100,
"S-vg": -5.9430181843676895,
"S-vd": -3.14e+100,
"B-ad": -6.680066036784177,
"E-rz": -3.14e+100,
"B-ag": -3.14e+100,
"B-vd": -9.044728760238115,
"S-mq": -3.14e+100,
"B-vi": -12.434752841302146,
"E-rr": -3.14e+100,
"B-rr": -12.434752841302146,
"M-vq": -3.14e+100,
"E-jn": -3.14e+100,
"B-vn": -4.3315610890163585,
"S-mg": -10.825314928868044,
"B-in": -3.14e+100,
"M-vi": -3.14e+100,
"M-an": -3.14e+100,
"M-vd": -3.14e+100,
"B-rg": -3.14e+100,
"M-vg": -3.14e+100,
"M-ad": -3.14e+100,
"M-ag": -3.14e+100,
"E-rg": -3.14e+100,
"S-uz": -9.299258625372996,
"B-en": -3.14e+100,
"S-uv": -8.15808672228609,
"S-df": -3.14e+100,
"S-dg": -8.948397651299683,
"M-qe": -3.14e+100,
"B-ng": -3.14e+100,
"E-bg": -3.14e+100,
"S-ul": -8.4153713175535,
"S-uj": -6.85251045118004,
"S-ug": -7.5394037026636855,
"B-ns": -2.8228438314969213,
"S-ud": -7.728230161053767,
"B-nt": -4.846091668182416,
"B-ul": -3.14e+100,
"E-in": -3.14e+100,
"B-bg": -3.14e+100,
"M-df": -3.14e+100,
"M-dg": -3.14e+100,
"M-nrt": -3.14e+100,
"B-j": -5.0576191284681915,
"E-ug": -3.14e+100,
"E-vq": -3.14e+100,
"B-vg": -3.14e+100,
"B-nz": -3.94698846057672,
"S-qe": -3.14e+100,
"B-rz": -7.946116471570005,
"B-nrfg": -5.873722175405573,
"E-ad": -3.14e+100,
"E-ag": -3.14e+100,
"B-u": -9.163917277503234,
"M-ln": -3.14e+100,
"B-an": -8.697083223018778,
"M-mq": -3.14e+100,
"E-an": -3.14e+100,
"S-s": -3.14e+100,
"B-q": -6.998123858956596,
"E-nrt": -3.14e+100,
"B-h": -13.533365129970255,
"E-r": -3.14e+100,
"S-p": -2.9868401813596317,
"M-tg": -3.14e+100,
"S-rz": -3.14e+100,
"S-nrfg": -3.14e+100,
"B-vq": -12.147070768850364,
"B-x": -3.14e+100,
"E-vd": -3.14e+100,
"E-nrfg": -3.14e+100,
"S-m": -3.269200652116097,
"E-vg": -3.14e+100,
"S-f": -5.194820249981676,
"S-j": -4.911992119644354
}

File diff suppressed because it is too large Load Diff

35234
Resources/prob_emit.json Normal file

File diff suppressed because it is too large Load Diff

18
Resources/prob_trans.json Normal file
View File

@@ -0,0 +1,18 @@
{
"M": {
"M": -1.2603623820268226,
"E": -0.33344856811948514
},
"S": {
"S": -0.6658631448798212,
"B": -0.7211965654669841
},
"B": {
"M": -0.916290731874155,
"E": -0.51082562376599
},
"E": {
"S": -0.8085250474669937,
"B": -0.5897149736854513
}
}

653
Resources/stopwords.txt Normal file
View File

@@ -0,0 +1,653 @@
i
me
my
myself
we
our
ours
ourselves
you
your
yours
yourself
yourselves
he
him
his
himself
she
her
hers
herself
it
its
itself
they
them
their
theirs
themselves
what
which
who
whom
this
that
these
those
am
is
are
was
were
be
been
being
have
has
had
having
do
does
did
doing
a
an
the
and
but
if
or
because
as
until
while
of
at
by
for
with
about
against
between
into
through
during
before
after
above
below
to
from
up
down
in
out
on
off
over
under
again
further
then
once
here
there
when
where
why
how
all
any
both
each
few
more
most
other
some
such
no
nor
not
only
own
same
so
than
too
very
s
t
can
will
just
don
should
now
一番
一直
一个
一些
许多
有的是
也就是说
哎呀
哎哟
俺们
按照
吧哒
罢了
本着
比方
比如
鄙人
彼此
别的
别说
并且
不比
不成
不单
不但
不独
不管
不光
不过
不仅
不拘
不论
不怕
不然
不如
不特
不惟
不问
不只
朝着
趁着
除此之外
除非
除了
此间
此外
从而
但是
当着
的话
等等
叮咚
对于
多少
而况
而且
而是
而外
而言
而已
尔后
反过来
反过来说
反之
非但
非徒
否则
嘎登
各个
各位
各种
各自
根据
故此
固然
关于
果然
果真
何处
何况
何时
哼唷
呼哧
还是
还有
换句话说
换言之
或是
或者
极了
及其
及至
即便
即或
即令
即若
即使
几时
既然
既是
继而
加之
假如
假若
假使
鉴于
较之
接着
结果
紧接着
进而
尽管
经过
就是
就是说
具体地说
具体说来
开始
开外
可见
可是
可以
况且
来着
例如
连同
两者
另外
另一方面
慢说
漫说
每当
莫若
某个
某些
哪边
哪儿
哪个
哪里
哪年
哪怕
哪天
哪些
哪样
那边
那儿
那个
那会儿
那里
那么
那么些
那么样
那时
那些
那样
乃至
你们
宁可
宁肯
宁愿
啪达
旁人
凭借
其次
其二
其他
其它
其一
其余
其中
起见
起见
岂但
恰恰相反
前后
前者
然而
然后
然则
人家
任何
任凭
如此
如果
如何
如其
如若
如上所述
若非
若是
上下
尚且
设若
设使
甚而
甚么
甚至
省得
时候
什么
什么样
使得
是的
首先
顺着
似的
虽然
虽说
虽则
随着
所以
他们
他人
它们
她们
倘或
倘然
倘若
倘使
通过
同时
万一
为何
为了
为什么
为着
嗡嗡
我们
呜呼
乌乎
无论
无宁
毋宁
相对而言
向着
沿
沿着
要不
要不然
要不是
要么
要是
也罢
也好
一旦
一方面
一来
一切
一样
一则
依照
以便
以及
以免
以至
以至于
以致
抑或
因此
因而
因为
由此可见
由于
有的
有关
有些
于是
于是乎
与此同时
与否
与其
越是
云云
再说
再者
在下
咱们
怎么办
怎么样
照着
这边
这儿
这个
这会儿
这就是说
这里
这么
这么点儿
这么些
这么样
这时
这些
这样
正如
之类
之所以
之一
只是
只限
只要
只有
至于
诸位
着呢
自从
自个儿
自各儿
自己
自家
自身
综上所述
总的来看
总的来说
总的说来
总而言之
总之
纵令
纵然
纵使
遵照
作为
喔唷
.
,
:
;
"
"
[
]
<
>
(
)
@
#
*
&
%
$
-
+
=
|
\

View File

@@ -0,0 +1,33 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WechatBakTool.ViewModel
{
public partial class WordCloudSettingViewModel : ObservableObject
{
[ObservableProperty]
private string imgHeight = "";
[ObservableProperty]
private string imgWidth = "";
[ObservableProperty]
private bool enableRemoveOneKey = true;
[ObservableProperty]
private string removeKey = "";
[ObservableProperty]
private int maxKeyCount = 200;
[ObservableProperty]
private string font = "微软雅黑";
[ObservableProperty]
private List<string> fontList = new List<string>();
}
}

View File

@@ -16,6 +16,9 @@ namespace WechatBakTool.ViewModel
[NotifyPropertyChangedFor(nameof(LabelStatus))]
private WXContact? wXContact = null;
[ObservableProperty]
private ObservableCollection<WXMsg> wXMsgs = new ObservableCollection<WXMsg>();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(LabelStatus))]
private string exportCount = "";

View File

@@ -18,6 +18,10 @@ using System.Xml.Linq;
using WechatBakTool.Helpers;
using WechatBakTool.Model;
using System.Windows;
using System.Net.Http;
using System.Reflection.Metadata;
using System.Threading;
using Newtonsoft.Json;
namespace WechatBakTool
{
@@ -27,11 +31,14 @@ namespace WechatBakTool
private UserBakConfig? UserBakConfig = null;
private Hashtable HeadImgCache = new Hashtable();
private Hashtable UserNameCache = new Hashtable();
private Hashtable EmojiCache = new Hashtable();
private HttpClient httpClient = new HttpClient();
public WXUserReader(UserBakConfig userBakConfig) {
string path = Path.Combine(userBakConfig.UserWorkspacePath, "DecDB");
UserBakConfig = userBakConfig;
LoadDB(path);
InitCache();
EmojiCacheInit();
}
public void LoadDB(string path)
@@ -48,9 +55,21 @@ namespace WechatBakTool
}
}
private SQLiteConnection? getCon(string name)
{
if (DBInfo.ContainsKey(name))
{
return DBInfo[name];
}
else
{
return null;
}
}
public void InitCache()
{
SQLiteConnection con = DBInfo["Misc"];
SQLiteConnection? con = getCon("Misc");
if (con == null)
return;
@@ -72,6 +91,107 @@ namespace WechatBakTool
}
}
public void EmojiCacheInit()
{
string emoji_path = Path.Combine(UserBakConfig!.UserWorkspacePath, "Emoji");
if (Directory.Exists(emoji_path))
{
string[] files = Directory.GetFiles(emoji_path);
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
string[] names = fileInfo.Name.Split(".");
if (!EmojiCache.ContainsKey(names[0]))
{
EmojiCache.Add(names[0], 1);
}
}
}
}
public void PreDownloadEmoji(string username = "")
{
if (UserBakConfig == null)
return;
HttpClientHandler handler = new HttpClientHandler() { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate };
HttpClient httpClient = new HttpClient(handler);
List<WXMsg> msgs = GetTypeMsg("47", username);
int i = 0;
// 下载前的Emoji Cache不用做了在Init的时候已经做了
foreach (var msg in msgs)
{
i++;
if (i % 5 == 0)
{
// 每5次让下载线程休息1秒
Thread.Sleep(1000);
}
try
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(msg.StrContent);
XmlNode? node = xmlDocument.SelectSingleNode("/msg/emoji");
if (node != null)
{
if (node.Attributes != null)
{
string type = "";
string md5 = "";
string url = "";
XmlNode? item = node.Attributes.GetNamedItem("type");
type = item != null ? item.InnerText : "";
item = node.Attributes.GetNamedItem("md5");
md5 = item != null ? item.InnerText : "";
item = node.Attributes.GetNamedItem("cdnurl");
url = item != null ? item.InnerText : "";
if (EmojiCache.ContainsKey(md5))
{
i--;
continue;
}
if (url == "")
{
i--;
continue;
}
else
{
string path = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Emoji", md5 + ".gif");
try
{
HttpResponseMessage res = httpClient.GetAsync(url).Result;
if (res.IsSuccessStatusCode)
{
using (FileStream fs = File.Create(path))
{
res.Content.ReadAsStream().CopyTo(fs);
}
}
}
catch (Exception ex)
{
}
}
}
}
}
catch (Exception ex)
{
}
}
// 下载完成后可能变化,检查一下
EmojiCacheInit();
}
public byte[]? GetHeadImgCahce(string username)
{
if (HeadImgCache.ContainsKey(username))
@@ -87,7 +207,7 @@ namespace WechatBakTool
public int[] GetWXCount()
{
SQLiteConnection con = DBInfo["MicroMsg"];
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return new int[] { 0, 0 };
@@ -97,22 +217,19 @@ namespace WechatBakTool
int msgCount = 0;
for (int i = 0; i <= 99; i++)
{
if (DBInfo.ContainsKey("MSG" + i.ToString()))
{
con = DBInfo["MSG" + i.ToString()];
if (con == null)
return new int[] { userCount, 0 };
con = getCon("MSG" + i.ToString());
if (con == null)
return new int[] { userCount, msgCount };
query = "select count(*) as count from MSG";
msgCount += con.Query<WXCount>(query)[0].Count;
}
query = "select count(*) as count from MSG";
msgCount += con.Query<WXCount>(query)[0].Count;
}
return new int[] { userCount, msgCount };
}
public ObservableCollection<WXContact> GetWXContacts(string? name = null,bool all = false)
{
SQLiteConnection con = DBInfo["MicroMsg"];
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return new ObservableCollection<WXContact>();
string query = @"select contact.*,session.strContent,contactHeadImgUrl.smallHeadImgUrl,contactHeadImgUrl.bigHeadImgUrl from contact
@@ -146,13 +263,23 @@ namespace WechatBakTool
byte[]? imgBytes = GetHeadImgCahce(contact.UserName);
if (imgBytes != null)
{
MemoryStream stream = new MemoryStream(imgBytes);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
contact.Avatar = bitmapImage;
try
{
MemoryStream stream = new MemoryStream(imgBytes);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
bitmapImage.Freeze();
contact.Avatar = bitmapImage;
}
catch
{
#if DEBUG
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "BitmapConvert Err=>Length", imgBytes.Length));
#endif
}
}
else
continue;
@@ -163,7 +290,7 @@ namespace WechatBakTool
public List<WXUserImg>? GetUserImgs()
{
SQLiteConnection con = DBInfo["MicroMsg"];
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return null;
string query = "select * from contactHeadImgUrl";
@@ -172,107 +299,187 @@ namespace WechatBakTool
public List<WXChatRoom>? GetWXChatRooms()
{
SQLiteConnection con = DBInfo["MicroMsg"];
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return null;
string query = "select * from ChatRoom";
return con.Query<WXChatRoom>(query);
}
public List<WXMsg> GetTypeMsg(string type,string username)
{
List<WXMsg> tmp = new List<WXMsg>();
for (int i = 0; i <= 99; i++)
{
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return tmp;
List<WXMsg> wXMsgs;
if (username == "")
{
string query = "select * from MSG where Type=?";
wXMsgs = con.Query<WXMsg>(query, type);
}
else
{
string query = "select * from MSG where Type=? and StrTalker = ?";
wXMsgs = con.Query<WXMsg>(query, type, username);
}
tmp.AddRange(wXMsgs);
}
return tmp;
}
public List<WXMsg>? GetWXMsgs(string uid,int time,int page)
{
List<WXMsg> tmp = new List<WXMsg>();
for (int i = 0; i <= 99; i++)
{
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return tmp;
List<WXMsg>? wXMsgs = null;
string query = "select * from MSG where StrTalker=? and CreateTime>? Limit ?";
wXMsgs = con.Query<WXMsg>(query, uid, time, page);
if (wXMsgs.Count != 0) {
return ProcessMsg(wXMsgs, uid);
}
}
return tmp;
}
public List<WXMsg>? GetWXMsgs(string uid,string msg = "")
{
List<WXMsg> tmp = new List<WXMsg>();
for (int i = 0; i <= 99; i++)
{
if(DBInfo.ContainsKey("MSG" + i.ToString()))
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return tmp;
List<WXMsg>? wXMsgs = null;
if (msg == "")
{
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
if (con == null)
return tmp;
List<WXMsg>? wXMsgs = null;
if (msg == "")
{
string query = "select * from MSG where StrTalker=?";
wXMsgs = con.Query<WXMsg>(query, uid);
}
else if(uid == "")
{
string query = "select * from MSG where StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
}
else
{
string query = "select * from MSG where StrTalker=? and StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
}
foreach (WXMsg w in wXMsgs)
{
if (UserNameCache.ContainsKey(w.StrTalker))
{
WXContact? contact = UserNameCache[w.StrTalker] as WXContact;
if (contact != null)
{
if (contact.Remark != "")
w.NickName = contact.Remark;
else
w.NickName = contact.NickName;
}
}
if (uid.Contains("@chatroom"))
{
string userId = "";
if (w.BytesExtra == null)
continue;
string sl = BitConverter.ToString(w.BytesExtra).Replace("-", "");
ProtoMsg protoMsg;
using (MemoryStream stream = new MemoryStream(w.BytesExtra))
{
protoMsg = ProtoBuf.Serializer.Deserialize<ProtoMsg>(stream);
}
if(protoMsg.TVMsg != null)
{
foreach(TVType _tmp in protoMsg.TVMsg)
{
if (_tmp.Type == 1)
userId = _tmp.TypeValue;
}
}
if (!w.IsSender)
{
if(UserNameCache.ContainsKey(userId))
{
WXContact? contact = UserNameCache[userId] as WXContact;
if (contact != null)
w.NickName = contact.Remark == "" ? contact.NickName : contact.Remark;
}
else
{
w.NickName = userId;
}
}
else
{
w.NickName = "我";
}
}
tmp.Add(w);
}
string query = "select * from MSG where StrTalker=?";
wXMsgs = con.Query<WXMsg>(query, uid);
}
else if (uid == "")
{
string query = "select * from MSG where StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
}
else
{
string query = "select * from MSG where StrTalker=? and StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
}
tmp.AddRange(ProcessMsg(wXMsgs, uid));
}
return tmp;
}
private List<WXMsg> ProcessMsg(List<WXMsg> msgs,string uid)
{
foreach (WXMsg w in msgs)
{
if (UserNameCache.ContainsKey(w.StrTalker))
{
WXContact? contact = UserNameCache[w.StrTalker] as WXContact;
if (contact != null)
{
if (contact.Remark != "")
w.NickName = contact.Remark;
else
w.NickName = contact.NickName;
w.StrTalker = contact.UserName;
}
}
else
{
w.NickName = uid;
}
// 群聊处理
if (uid.Contains("@chatroom"))
{
string userId = "";
if (w.BytesExtra == null)
continue;
string sl = BitConverter.ToString(w.BytesExtra).Replace("-", "");
ProtoMsg protoMsg;
using (MemoryStream stream = new MemoryStream(w.BytesExtra))
{
protoMsg = ProtoBuf.Serializer.Deserialize<ProtoMsg>(stream);
}
if (protoMsg.TVMsg != null)
{
foreach (TVType _tmp in protoMsg.TVMsg)
{
if (_tmp.Type == 1)
userId = _tmp.TypeValue;
}
}
if (!w.IsSender)
{
if (UserNameCache.ContainsKey(userId))
{
WXContact? contact = UserNameCache[userId] as WXContact;
if (contact != null)
w.NickName = contact.Remark == "" ? contact.NickName : contact.Remark;
}
else
{
w.NickName = userId;
}
}
}
// 发送人名字处理
if (w.IsSender)
w.NickName = "我";
w.DisplayContent = w.StrContent;
// 额外格式处理
if (w.Type != 1)
{
if (w.Type == 10000)
{
w.Type = 1;
w.NickName = "系统消息";
w.DisplayContent = w.StrContent.Replace("<revokemsg>", "").Replace("</revokemsg>", "");
}
else if (w.Type == 49 && (w.SubType == 6 || w.SubType == 19 || w.SubType == 40))
{
WXSessionAttachInfo? attachInfos = GetWXMsgAtc(w);
if (attachInfos == null)
{
w.DisplayContent = "附件不存在";
}
else
{
w.DisplayContent = Path.Combine(UserBakConfig!.UserResPath, attachInfos.attachPath);
}
}
else
{
w.DisplayContent = "[界面未支持格式]Type=" + w.Type;
}
}
}
return msgs;
}
public List<WXSessionAttachInfo>? GetWXMsgAtc()
{
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
SQLiteConnection? con = getCon("MultiSearchChatMsg");
if (con == null)
return null;
@@ -287,7 +494,7 @@ namespace WechatBakTool
}
public WXSessionAttachInfo? GetWXMsgAtc(WXMsg msg)
{
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
SQLiteConnection? con = getCon("MultiSearchChatMsg");
if (con == null)
return null;
@@ -316,17 +523,14 @@ namespace WechatBakTool
{
for (int i = 0; i <= 99; i++)
{
if(DBInfo.ContainsKey("MediaMSG" + i.ToString()))
{
SQLiteConnection con = DBInfo["MediaMSG" + i.ToString()];
if (con == null)
continue;
SQLiteConnection? con = getCon("MediaMSG" + i.ToString());
if (con == null)
continue;
string query = "select * from Media where Reserved0=?";
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msg.MsgSvrID);
if (wXMsgs.Count != 0)
return wXMsgs[0];
}
string query = "select * from Media where Reserved0=?";
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msg.MsgSvrID);
if (wXMsgs.Count != 0)
return wXMsgs[0];
}
return null;
}
@@ -335,13 +539,14 @@ namespace WechatBakTool
if (UserBakConfig == null)
return null;
string? tmpPath = Path.Combine(UserBakConfig.UserWorkspacePath, "Temp");
string? tmpPath = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Temp");
if (!Directory.Exists(tmpPath))
Directory.CreateDirectory(tmpPath);
// 这部分是查找
// 如果是图片和视频,从附件库中搜索
string? path = null;
if (type == WXMsgType.Image || type == WXMsgType.Video)
if (type == WXMsgType.Image || type == WXMsgType.Video || type == WXMsgType.File)
{
WXSessionAttachInfo? atcInfo = GetWXMsgAtc(msg);
if (atcInfo == null)
@@ -365,39 +570,69 @@ namespace WechatBakTool
}
path = tmp_file_path;
}
else if (type == WXMsgType.Emoji)
{
try
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(msg.StrContent);
XmlNode? node = xmlDocument.SelectSingleNode("/msg/emoji");
if (node != null)
{
if (node.Attributes != null)
{
XmlNode? item = node.Attributes.GetNamedItem("md5");
string md5 = item != null ? item.InnerText : "";
if (EmojiCache.ContainsKey(md5))
{
path = string.Format("Emoji\\{0}.gif", md5);
}
}
}
}
catch
{
return null;
}
}
if (path == null)
return null;
// 这部分是解密
// 获取到原路径后,开始进行解密转移,只有图片和语音需要解密,解密后是直接归档目录
if(type == WXMsgType.Image || type== WXMsgType.Audio)
if (type == WXMsgType.Image || type == WXMsgType.Audio)
{
path = DecryptAttachment(type, path);
path = DecryptAttachment(type, path, msg.StrTalker);
}
else if (type == WXMsgType.Video)
else if (type == WXMsgType.Video || type == WXMsgType.File)
{
string video_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Video");
if(!Directory.Exists(video_dir))
Directory.CreateDirectory(video_dir);
string to_dir;
if (type == WXMsgType.Video)
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Video");
else
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "File");
if (!Directory.Exists(to_dir))
Directory.CreateDirectory(to_dir);
FileInfo fileInfo = new FileInfo(path);
// 目标视频路径
string video_file_path = Path.Combine(video_dir, fileInfo.Name);
string to_file_path = Path.Combine(to_dir, fileInfo.Name);
// 视频的路径是相对路径,需要加上资源目录
path = Path.Combine(UserBakConfig.UserResPath, path);
// 原文件存在,目标不存在
if (!File.Exists(video_file_path) && File.Exists(path))
if (!File.Exists(to_file_path) && File.Exists(path))
{
// 复制
File.Copy(path, video_file_path);
path = video_file_path;
File.Copy(path, to_file_path);
path = to_file_path;
}
else if (File.Exists(video_file_path))
else if (File.Exists(to_file_path))
{
path = video_file_path;
path = to_file_path;
}
else
return null;
}
if (path == null)
@@ -408,7 +643,7 @@ namespace WechatBakTool
return path;
}
public string? DecryptAttachment(WXMsgType type, string path)
public string? DecryptAttachment(WXMsgType type, string path,string username)
{
if (UserBakConfig == null)
return null;
@@ -417,17 +652,21 @@ namespace WechatBakTool
switch (type)
{
case WXMsgType.Image:
string img_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Image");
string img_dir = Path.Combine(UserBakConfig.UserWorkspacePath, username, "Image");
if (!Directory.Exists(img_dir))
Directory.CreateDirectory(img_dir);
// 图片的路径是相对路径,需要加上资源目录
path = Path.Combine(UserBakConfig.UserResPath, path);
if (!File.Exists(path))
return null;
byte[] decFileByte = DecryptionHelper.DecImage(path);
if (decFileByte.Length < 2)
new Exception("解密失败,可能是未支持的格式");
string decFiletype = DecryptionHelper.CheckFileType(decFileByte);
file_path = DecryptionHelper.SaveDecImage(decFileByte, path, img_dir, decFiletype);
break;
case WXMsgType.Audio:
string audio_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Audio");
string audio_dir = Path.Combine(UserBakConfig.UserWorkspacePath, username, "Audio");
if (!Directory.Exists(audio_dir))
Directory.CreateDirectory(audio_dir);
FileInfo fileInfo = new FileInfo(path);
@@ -443,23 +682,21 @@ namespace WechatBakTool
List<WXMsgGroup> g = new List<WXMsgGroup>();
for (int i = 0; i <= 99; i++)
{
if (DBInfo.ContainsKey("MSG" + i.ToString()))
{
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
if (con == null)
return g;
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return g;
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
foreach (WXMsgGroup w in wXMsgs)
{
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
if (tmp == null)
g.Add(w);
else
tmp.MsgCount += g.Count;
}
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
foreach (WXMsgGroup w in wXMsgs)
{
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
if (tmp == null)
g.Add(w);
else
tmp.MsgCount += g.Count;
}
}
return g;
}
@@ -471,5 +708,6 @@ namespace WechatBakTool
Video = 1,
Audio = 2,
File = 3,
Emoji = 4,
}
}

View File

@@ -18,7 +18,7 @@ namespace WechatBakTool
{
private UserBakConfig UserBakConfig = new UserBakConfig();
public WXWorkspace(string path,string account = "") {
string checkResult = Init(path, account);
string checkResult = Init(path, false, account);
if (checkResult != "")
new Exception(checkResult);
}
@@ -28,7 +28,7 @@ namespace WechatBakTool
UserBakConfig = userBakConfig;
}
public void DecryptDB(string pid,int type,CreateWorkViewModel viewModel)
public void DecryptDB(string pid,int type,CreateWorkViewModel viewModel,string pwd = "")
{
if (UserBakConfig == null)
{
@@ -38,12 +38,26 @@ namespace WechatBakTool
if (!UserBakConfig.Decrypt)
{
byte[]? key = null;
key = DecryptionHelper.GetWechatKey(pid, type == 2, UserBakConfig.Account);
viewModel.LabelStatus = "正在获取秘钥需要1 - 10秒左右";
if(pwd == "")
key = DecryptionHelper.GetWechatKey(pid, type, UserBakConfig.Account);
else
{
key = new byte[pwd.Length / 2];
for(int i = 0;i<pwd.Length / 2; i++)
{
key[i] = Convert.ToByte(pwd.Substring(i * 2, 2), 16);
}
}
#if DEBUG
File.WriteAllText("key.log", BitConverter.ToString(key!));
#endif
if (key == null)
{
throw new Exception("获取到的密钥为空,获取失败");
}
string key_string = BitConverter.ToString(key, 0).Replace("-", string.Empty).ToLower().ToUpper();
string source = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB");
string to = Path.Combine(UserBakConfig.UserWorkspacePath, "DecDB");
@@ -103,7 +117,7 @@ namespace WechatBakTool
}
}
}
private string Init(string path,string account = "")
private string Init(string path,bool manual,string account = "")
{
string curPath = AppDomain.CurrentDomain.BaseDirectory;
string md5 = GetMd5Hash(path);

View File

@@ -6,17 +6,27 @@
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.9.2.0</AssemblyVersion>
<FileVersion>0.9.2.0</FileVersion>
<Version>0.9.2.0</Version>
<AssemblyVersion>0.9.6.4</AssemblyVersion>
<FileVersion>0.9.6.4</FileVersion>
<Version>0.9.6.4</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="YearReport\**" />
<EmbeddedResource Remove="YearReport\**" />
<None Remove="YearReport\**" />
<Page Remove="YearReport\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="jieba.NET" Version="0.42.2" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
<PackageReference Include="WordCloudSharp" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
@@ -26,6 +36,39 @@
<None Update="libssl-1_1.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="mask.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\char_state_tab.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\cn_synonym.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\dict.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\idf.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\pos_prob_emit.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\pos_prob_start.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\pos_prob_trans.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\prob_emit.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\prob_trans.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\stopwords.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tools\ffmpeg.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -37,8 +80,4 @@
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="YearReport\" />
</ItemGroup>
</Project>

25
WordCloudSetting.xaml Normal file
View File

@@ -0,0 +1,25 @@
<Window x:Class="WechatBakTool.WordCloudSetting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d"
Title="词云渲染设置" Height="360" Width="300" WindowStartupLocation="CenterScreen">
<Grid>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,10,0,0">渲染高度</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,35,0,0" Width="220" Text="{Binding ImgHeight}"/>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,60,0,0">渲染宽度</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,85,0,0" Width="220" Text="{Binding ImgWidth}"/>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,110,0,0">最大词数</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,135,0,0" Width="220" Text="{Binding MaxKeyCount}"/>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,160,0,0">渲染字体</Label>
<ComboBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,185,0,0" Width="220" SelectedItem="{Binding Font}" ItemsSource="{Binding FontList}"/>
<CheckBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,215,0,0" Content="过滤只有一个字的关键词【推荐】" IsChecked="{Binding EnableRemoveOneKey}" />
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,235,0,0">自定义过滤词(英文,号分隔)</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,260,0,0" Width="220" Text="{Binding RemoveKey}"/>
<Button Content="确定" HorizontalAlignment="Left" Margin="91,293,0,0" VerticalAlignment="Top" Width="100" Click="Button_Click"/>
</Grid>
</Window>

48
WordCloudSetting.xaml.cs Normal file
View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WechatBakTool.ViewModel;
namespace WechatBakTool
{
/// <summary>
/// WordCloudSetting.xaml 的交互逻辑
/// </summary>
public partial class WordCloudSetting : Window
{
public WordCloudSetting(WordCloudSettingViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
viewModel.FontList = LoadFont();
}
private List<string> LoadFont()
{
InstalledFontCollection installedFontCollection = new InstalledFontCollection();
var fontFamilies = installedFontCollection.Families;
List<string> list = new List<string>();
foreach ( var fontFamily in fontFamilies )
{
list.Add(fontFamily.Name);
}
return list;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
}

View File

@@ -1,11 +0,0 @@
<Window x:Class="WechatBakTool.YearReport"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d"
Title="YearReport" Height="660" Width="330" WindowStyle="None" WindowStartupLocation="CenterScreen" >
<Grid>
</Grid>
</Window>

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WechatBakTool
{
/// <summary>
/// YearReport.xaml 的交互逻辑
/// </summary>
public partial class YearReport : Window
{
public YearReport()
{
InitializeComponent();
}
}
}

BIN
mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -11,5 +11,8 @@
},{
"Version":"3.9.8.15",
"BaseAddr": 64997904
},{
"Version":"3.9.8.25",
"BaseAddr": 65002192
}
]