Bläddra i källkod

v0.9.2.0版本!
1.异步优化,现在起创建工作区,导出聊天记录,都是独立线程操作了!体验更好!并且多了相关状态提示,体验更好。
2.新增批量导出模式,左侧工作区,右键->管理就见啦!
3.修复视频、图片导出时逻辑bug

Suxue 1 år sedan
förälder
incheckning
c996a89303

+ 3 - 2
Export/ExportInterface.cs

@@ -4,14 +4,15 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using WechatBakTool.Model;
+using WechatBakTool.ViewModel;
 
 namespace WechatBakTool.Export
 {
     public interface IExport
     {
         void InitTemplate(WXContact session,string path);
-        void SetMsg(WXUserReader reader, WXContact session);
+        void SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel);
         void SetEnd();
-        void Save(string path = "", bool append = false);
+        void Save(string path = "");
     }
 }

+ 32 - 12
Export/HtmlExport.cs

@@ -8,6 +8,8 @@ using System.Text;
 using System.Threading.Tasks;
 using WechatBakTool.Model;
 using System.Xml;
+using Newtonsoft.Json;
+using WechatBakTool.ViewModel;
 
 namespace WechatBakTool.Export
 {
@@ -23,6 +25,7 @@ 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)
@@ -34,25 +37,18 @@ namespace WechatBakTool.Export
             InitTemplate(session);
         }
 
-        public void Save(string path = "", bool append = false)
+        public void Save(string path = "")
         {
-            if (!append)
-            {
-                File.WriteAllText(path, HtmlBody);
-            }
-            else
-            {
-                File.AppendAllText(path, HtmlBody);
-                HtmlBody = "";
-            }
+
         }
 
         public void SetEnd()
         {
             HtmlBody += "</body></html>";
+            File.AppendAllText(Path, HtmlBody);
         }
 
-        public void SetMsg(WXUserReader reader, WXContact contact)
+        public void SetMsg(WXUserReader reader, WXContact contact,WorkspaceViewModel viewModel)
         {
             if (Session == null)
                 throw new Exception("请初始化模版:Not Use InitTemplate");
@@ -63,6 +59,9 @@ namespace WechatBakTool.Export
 
             msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
 
+            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"));
@@ -74,6 +73,10 @@ namespace WechatBakTool.Export
                     string? path = reader.GetAttachment(WXMsgType.Image, msg);
                     if (path == null)
                     {
+#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;
                     }
@@ -154,8 +157,25 @@ namespace WechatBakTool.Export
                 {
                     HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
                 }
+                
+                msgCount++;
+                if(msgCount % 50 == 0)
+                {
+                    streamWriter.WriteLine(HtmlBody);
+                    HtmlBody = "";
+                    viewModel.ExportCount = msgCount.ToString();
+                }
+                
             }
-
+            if(msgCount % 50 != 0)
+            {
+                streamWriter.WriteLine(HtmlBody);
+                HtmlBody = "";
+                viewModel.ExportCount = msgCount.ToString();
+            }
+            streamWriter.Close();
+            streamWriter.Dispose();
+                
         }
         private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
         {

+ 6 - 2
Export/TXTExport.cs

@@ -8,6 +8,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Xml;
 using WechatBakTool.Model;
+using WechatBakTool.ViewModel;
 
 namespace WechatBakTool.Export
 {
@@ -29,7 +30,7 @@ namespace WechatBakTool.Export
             File.AppendAllText(Path, string.Format("=================================================================\n\n\n"));
         }
 
-        void IExport.Save(string path, bool append)
+        void IExport.Save(string path)
         {
             
         }
@@ -39,7 +40,7 @@ namespace WechatBakTool.Export
             
         }
 
-        public void SetMsg(WXUserReader reader, WXContact session)
+        public void SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
         {
             if (Contact == null)
                 throw new Exception("请初始化模版:Not Use InitTemplate");
@@ -50,6 +51,7 @@ namespace WechatBakTool.Export
 
             msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
 
+            int msgCount = 0;
             foreach (var msg in msgList)
             {
                 string txtMsg = "";
@@ -134,6 +136,8 @@ namespace WechatBakTool.Export
                 }
                 string row = string.Format("{2} | {0}:{1}\n", msg.IsSender ? "我" : msg.NickName, txtMsg, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
                 File.AppendAllText(Path, row);
+                msgCount++;
+                viewModel.ExportCount = msgCount.ToString();
             }
         }
 

+ 4 - 1
Helpers/DecryptionHelper.cs

@@ -11,6 +11,8 @@ using System.Text.Json.Serialization;
 using System.Threading.Tasks;
 using System.Windows;
 using WechatBakTool.Model;
+using WechatBakTool.Pages;
+using WechatBakTool.ViewModel;
 
 namespace WechatBakTool.Helpers
 {
@@ -283,7 +285,7 @@ namespace WechatBakTool.Helpers
             }
             return saveFilePath;
         }
-        public static void DecryUserData(byte[] key, string source, string to)
+        public static void DecryUserData(byte[] key, string source, string to,CreateWorkViewModel viewModel)
         {
             string dbPath = source;
             string decPath = to;
@@ -294,6 +296,7 @@ namespace WechatBakTool.Helpers
             foreach (string file in filePath)
             {
                 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)

+ 14 - 5
Main2.xaml

@@ -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="Main2" Height="550" Width="950"  >
     <Window.Resources>
         <Style TargetType="local:Main2">
             <!-- 设置窗体的WindowChrome -->
@@ -15,7 +15,7 @@
                     <!-- CornerRadius:窗体圆角;-->
                     <!-- CaptionHeight顶部标题的高度;-->
                     <!-- GlassFrameThickness:默认边框的大小,0为不使用默认边框(这样定义的圆角才有效),-1为使用默认边框默认值-->
-                    <WindowChrome CornerRadius="5" CaptionHeight="0" GlassFrameThickness="0" />
+                    <WindowChrome CornerRadius="5" CaptionHeight="5" GlassFrameThickness="0" />
                 </Setter.Value>
             </Setter>
         </Style>
@@ -61,8 +61,8 @@
             </Grid>
         </DataTemplate>
     </Window.Resources>
-    <Grid Background="White">
-        <Image Panel.ZIndex="100" Name="img_btn_min" Source="{StaticResource svg_min}" Width="20" Height="20" HorizontalAlignment="Left" Margin="860,20,0,0" VerticalAlignment="Top">
+    <Grid Background="White" MouseDown="Grid_MouseDown">
+        <Image Panel.ZIndex="100" Name="img_btn_min" Source="{StaticResource svg_min}" Width="20" Height="20" HorizontalAlignment="Left" Margin="860,20,0,0" VerticalAlignment="Top" MouseLeftButtonDown="img_btn_min_MouseLeftButtonDown">
             <Image.RenderTransform>
                 <RotateTransform CenterX="0.5" CenterY="0.5" />
             </Image.RenderTransform>
@@ -122,8 +122,17 @@
                 </EventTrigger>
             </Image.Triggers>
         </Image>
+        
         <Grid Width="230" Background="#2775b6" HorizontalAlignment="Left" IsHitTestVisible="True">
-            <ListView BorderThickness="0" Background="Transparent" Margin="0,0,0,85" Name="list_workspace" ItemTemplate="{DynamicResource ListViewItemContentTemplate}" SelectionChanged="list_workspace_SelectionChanged"/>
+
+            <ListView BorderThickness="0" Background="Transparent" Margin="0,0,0,85" Name="list_workspace" ItemTemplate="{DynamicResource ListViewItemContentTemplate}" SelectionChanged="list_workspace_SelectionChanged">
+                <ListView.ContextMenu>
+                    <ContextMenu>
+                        <MenuItem Header="查看" Click="MenuItem_Click"  />
+                        <MenuItem Header="管理" Click="MenuItem_Click_1" />
+                    </ContextMenu>
+                </ListView.ContextMenu>
+            </ListView>
             <Grid Name="new_workspace" Width="170" Height="40" VerticalAlignment="Bottom" Margin="30,45" IsHitTestVisible="True">
                 <Rectangle Name="new_workspace_fill" Fill="Transparent"  RadiusX="0" RadiusY="0" Stroke="White" StrokeDashArray="5" MouseDown="new_workspace_fill_MouseDown">
                     <Rectangle.Triggers>

+ 27 - 1
Main2.xaml.cs

@@ -27,8 +27,9 @@ namespace WechatBakTool
         public Main2()
         {
             InitializeComponent();
+            // 获取文件版本
             lab_version.Content += $" {Application.ResourceAssembly.GetName().Version}";
-            // list_workspace.Items.Add(new { Name = "sxcoder", Friends_Number=23, Msg_Number=102302, Decrypt="已解密" });
+            //加载工作区
             LoadWorkspace();
         }
 
@@ -40,10 +41,12 @@ namespace WechatBakTool
         public void LoadWorkspace()
         {
             userBakConfigs.Clear();
+            // 根目录worksapce读工作区
             string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "workspace");
             if (Directory.Exists(path))
             {
                 string[] files = Directory.GetFiles(path);
+                //目录内json文件为各工作区配置文件
                 foreach (string file in files)
                 {
                     string type = file.Substring(file.Length - 5, 5);
@@ -92,5 +95,28 @@ namespace WechatBakTool
         {
             MainFrame.Navigate(new Uri("pack://application:,,,/Pages/CreateWork.xaml?datatime=" + DateTime.Now.Ticks));
         }
+
+        private void img_btn_min_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            WindowState = WindowState.Minimized;
+        }
+
+        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Pressed)
+            {
+                DragMove();
+            }
+        }
+
+        private void MenuItem_Click(object sender, RoutedEventArgs e)
+        {
+            MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Workspace.xaml?datatime=" + DateTime.Now.Ticks));
+        }
+
+        private void MenuItem_Click_1(object sender, RoutedEventArgs e)
+        {
+            MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Manager.xaml?datatime=" + DateTime.Now.Ticks));
+        }
     }
 }

+ 28 - 0
Model/YearReport.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WechatBakTool.Model
+{
+    public class YearReport
+    {
+        public List<ReportItem>? List { get; set; }
+        public int Version { get; set; }
+    }
+
+    public class ReportItem
+    {
+        public string ImgName { get; set; } = "";
+        public string Type { get; set; } = "";
+        public List<TextPostion>? TextPostions { get; set; }
+    }
+
+    public class TextPostion
+    {
+        public double X { get; set; }
+        public double Y { get; set; }
+        public string TextTemplate { get; set; } = "";
+    }
+}

+ 6 - 5
Pages/CreateWork.xaml

@@ -13,7 +13,7 @@
     <Grid>
         <Label FontSize="20" Margin="30,15" Content="新建工作区" HorizontalAlignment="Left" VerticalAlignment="Top" />
         <Label Margin="30,55,0,0" Content="请选择要创建工作区的微信,可以通过微信路径判断是哪一个微信哦!" HorizontalAlignment="Left" VerticalAlignment="Top" />
-        <ListView Name="list_process" Margin="35,95,35,0" VerticalAlignment="Top" Height="160" ItemsSource="{Binding ProcessInfos}" SelectionChanged="list_process_SelectionChanged" SelectedItem="{Binding SelectProcess}">
+        <ListView Name="list_process" Margin="35,95,35,0" IsEnabled="{Binding IsEnable}" VerticalAlignment="Top" Height="160" ItemsSource="{Binding ProcessInfos}" SelectionChanged="list_process_SelectionChanged" SelectedItem="{Binding SelectProcess}">
             <ListView.View>
                 <GridView>
                     <GridViewColumn Header="进程名" Width="120" DisplayMemberBinding="{Binding ProcessName}" />
@@ -23,13 +23,13 @@
             </ListView.View>
         </ListView>
         <Label Margin="30,275,0,0" Content="选择微信后,请确认下方自动获取的微信名是否正确。不正确请修改!" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
-        <TextBox x:Name="txt_username" Margin="35,300,0,0" Width="280" HorizontalAlignment="Left" VerticalAlignment="Top" BorderThickness="0,0,0,1" Text="{Binding UserName}" />
+        <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" 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" 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}"/>
 
-        <Button Name="btn_create_worksapce" Margin="0,0,35,50" Height="60" Width="100" HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="创建工作区" BorderThickness="0" Background="#2775b6" Foreground="White" Click="btn_create_worksapce_Click">
+        <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>
                 <Style TargetType="{x:Type Border}">
                     <Setter Property="CornerRadius" Value="8"/>
@@ -39,5 +39,6 @@
         <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" />
+        <Label Name="lab_status" Content="{Binding LabelStatus}" HorizontalAlignment="Left" Margin="30,450,0,0" VerticalAlignment="Top"/>
     </Grid>
 </Page>

+ 40 - 24
Pages/CreateWork.xaml.cs

@@ -96,40 +96,56 @@ namespace WechatBakTool.Pages
 
         private void btn_create_worksapce_Click(object sender, RoutedEventArgs e)
         {
-            if(ViewModel.KeyType != -1)
-            {
-                if (ViewModel.SelectProcess != null)
+            ViewModel.IsEnable = false;
+            Task.Run(() => {
+                if (ViewModel.KeyType != -1)
                 {
-                    string path = ViewModel.SelectProcess.DBPath.Replace("\\Msg\\MicroMsg.db", "");
-                    try
+                    if (ViewModel.SelectProcess != null)
                     {
-                        //创建工作区
-                        WXWorkspace wXWorkspace = new WXWorkspace(path, ViewModel.UserName);
-                        //DB移动
-                        wXWorkspace.MoveDB();
-                        //开始解密数据库
+                        ViewModel.LabelStatus = "数据准备";
+                        string path = ViewModel.SelectProcess.DBPath.Replace("\\Msg\\MicroMsg.db", "");
                         try
                         {
-                            wXWorkspace.DecryptDB(ViewModel.SelectProcess.ProcessId, ViewModel.KeyType);
+                            ViewModel.LabelStatus = "准备创建工作区";
+                            //创建工作区
+                            WXWorkspace wXWorkspace = new WXWorkspace(path, ViewModel.UserName);
+                            //DB移动
+                            wXWorkspace.MoveDB(ViewModel);
+                            if(ViewModel.SelectProcess == null)
+                                return;
+
+                            //开始解密数据库
+                            try
+                            {
+                                ViewModel.LabelStatus = "开始解密数据库";
+                                wXWorkspace.DecryptDB(ViewModel.SelectProcess.ProcessId, ViewModel.KeyType,ViewModel);
 
-                            MessageBox.Show("创建工作区成功");
-                            ((Main2)Window.GetWindow(this)).LoadWorkspace();
+                                MessageBox.Show("创建工作区成功");
+                                Dispatcher.Invoke(() =>
+                                {
+                                    ((Main2)Window.GetWindow(this)).LoadWorkspace();
+                                });
+                                
+                            }
+                            catch (Exception ex)
+                            {
+                                MessageBox.Show(ex.Message);
+                                ViewModel.IsEnable = true;
+                            }
                         }
-                        catch (Exception ex)
+                        catch (Exception)
                         {
-                            MessageBox.Show(ex.Message);
+                            MessageBox.Show("创建工作区失败,请检查路径是否正确");
+                            ViewModel.IsEnable = true;
                         }
                     }
-                    catch (Exception)
-                    {
-                        MessageBox.Show("创建工作区失败,请检查路径是否正确");
-                    }
                 }
-            }
-            else
-            {
-                MessageBox.Show("请选择Key获取方式", "错误");
-            }
+                else
+                {
+                    MessageBox.Show("请选择Key获取方式", "错误");
+                }
+                ViewModel.IsEnable = true;
+            });
         }
     }
 }

+ 18 - 0
Pages/Manager.xaml

@@ -0,0 +1,18 @@
+<Page x:Class="WechatBakTool.Pages.Manager"
+      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+      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"
+      mc:Ignorable="d" 
+      d:DesignHeight="450" d:DesignWidth="720"
+      Title="Manager" Background="White">
+    <Grid>
+        <Label FontSize="20" Margin="30,15" Content="管理" HorizontalAlignment="Left" VerticalAlignment="Top" />
+        <Label Margin="30,55" Content="批量导出聊天记录" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
+        <CheckBox Name="cb_group" Margin="35,85" Content="群聊" HorizontalAlignment="Left" VerticalAlignment="Top" />
+        <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"/>
+    </Grid>
+</Page>

+ 90 - 0
Pages/Manager.xaml.cs

@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+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.Navigation;
+using WechatBakTool.Export;
+using WechatBakTool.Model;
+using WechatBakTool.ViewModel;
+
+namespace WechatBakTool.Pages
+{
+    /// <summary>
+    /// Manager.xaml 的交互逻辑
+    /// </summary>
+    public partial class Manager : Page
+    {
+        private WorkspaceViewModel workspaceViewModel = new WorkspaceViewModel();
+        public WXUserReader? UserReader;
+        public Manager()
+        {
+            DataContext = workspaceViewModel;
+            InitializeComponent();
+            UserBakConfig? config = Main2.CurrentUserBakConfig;
+            if (config != null)
+            {
+                UserReader = new WXUserReader(config);
+                if (!config.Decrypt)
+                {
+                    MessageBox.Show("请先解密数据库", "错误");
+                    return;
+                }
+            }
+        }
+
+        private void btn_export_all_Click(object sender, RoutedEventArgs e)
+        {
+            Task.Run(() =>
+            {
+                bool group = false, user = false;
+                Dispatcher.Invoke(() =>
+                {
+                    if (cb_group.IsChecked == null || cb_user.IsChecked == null)
+                        return;
+
+                    group = (bool)cb_group.IsChecked;
+                    user = (bool)cb_user.IsChecked;
+                });
+                if (UserReader != null)
+                {
+                    List<WXContact>? contacts = UserReader.GetWXContacts().ToList();
+                    foreach (var contact in contacts)
+                    {
+                        if (group && contact.UserName.Contains("@chatroom"))
+                        {
+                            workspaceViewModel.WXContact = contact;
+                            ExportMsg(contact);
+                        }
+                        if (user)
+                        {
+                            workspaceViewModel.WXContact = contact;
+                            ExportMsg(contact);
+                        }
+                    }
+                    MessageBox.Show("批量导出完成", "提示");
+                }
+            });
+        }
+
+        private void ExportMsg(WXContact contact)
+        {
+            workspaceViewModel.ExportCount = "";
+            string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
+            IExport export = new HtmlExport();
+            export.InitTemplate(contact, path);
+            export.SetMsg(UserReader!, contact, workspaceViewModel);
+            export.SetEnd();
+            export.Save(path);
+        }
+    }
+}

+ 1 - 0
Pages/Workspace.xaml

@@ -232,5 +232,6 @@
                 </Style>
             </Button.Resources>
         </Button>
+        <Label Content="{Binding ExportCount}" HorizontalAlignment="Right" Margin="0,0,160,15" VerticalAlignment="Bottom"/>
     </Grid>
 </Page>

+ 46 - 32
Pages/Workspace.xaml.cs

@@ -27,8 +27,8 @@ namespace WechatBakTool.Pages
     /// </summary>
     public partial class Workspace : Page
     {
-        public WXUserReader? UserReader { get; set; }
-        private WorkspaceViewModel ViewModel { get; set; } = new WorkspaceViewModel();
+        public WXUserReader? UserReader;
+        private WorkspaceViewModel ViewModel = new WorkspaceViewModel();
         public Workspace()
         {
             ViewModel.ExportItems = new System.Collections.ObjectModel.ObservableCollection<ExportItem> {
@@ -37,6 +37,9 @@ namespace WechatBakTool.Pages
             };
             ViewModel.SelectExportItem = ViewModel.ExportItems[0];
             InitializeComponent();
+
+            list_users.Items.Clear();
+
             DataContext = ViewModel;
             UserBakConfig? config = Main2.CurrentUserBakConfig;
             if (config != null)
@@ -60,6 +63,7 @@ namespace WechatBakTool.Pages
 
         private void list_users_SelectionChanged(object sender, SelectionChangedEventArgs e)
         {
+            ViewModel.ExportCount = "";
             ViewModel.WXContact = list_users.SelectedItem as WXContact;
             if(ViewModel.WXContact == null || UserReader == null)
             {
@@ -99,7 +103,7 @@ namespace WechatBakTool.Pages
             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);
+            export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
             export.SetEnd();
             export.Save(path);
             MessageBox.Show("导出完成");
@@ -123,38 +127,48 @@ namespace WechatBakTool.Pages
 
         private void Export_Click(object sender, RoutedEventArgs e)
         {
-            if (ViewModel.WXContact == null || UserReader == null)
+            Task.Run(() =>
             {
-                MessageBox.Show("请选择联系人", "错误");
-                return;
-            }
-            if(ViewModel.SelectExportItem == null)
-            {
-                MessageBox.Show("请选择导出方式", "错误");
-                return;
-            }
-            string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName);
-            IExport export;
-            if (ViewModel.SelectExportItem.Value == 2)
-            {
-                path += ".txt";
-                export = new TXTExport();
-            }
-            else
-            {
-                path += ".html";
-                export = new HtmlExport();
-            }
-            export.InitTemplate(ViewModel.WXContact, path);
-            export.SetMsg(UserReader, ViewModel.WXContact);
-            export.SetEnd();
-            export.Save(path);
-            MessageBox.Show("导出完成");
-        }
+                
 
-        private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
+                if (ViewModel.WXContact == null || UserReader == null)
+                {
+                    MessageBox.Show("请选择联系人", "错误");
+                    return;
+                }
+                if (ViewModel.SelectExportItem == null)
+                {
+                    MessageBox.Show("请选择导出方式", "错误");
+                    return;
+                }
+                string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName);
+                IExport export;
 
+#if DEBUG
+                Stopwatch stopwatch = new Stopwatch();
+                stopwatch.Start();
+#endif
+                if (ViewModel.SelectExportItem.Value == 2)
+                {
+                    path += ".txt";
+                    export = new TXTExport();
+                }
+                else
+                {
+                    path += ".html";
+                    export = new HtmlExport();
+                }
+                export.InitTemplate(ViewModel.WXContact, path);
+                export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
+                export.SetEnd();
+                export.Save(path);
+#if DEBUG
+                stopwatch.Stop();
+                MessageBox.Show(stopwatch.Elapsed.ToString());
+#endif
+                MessageBox.Show("导出完成", "提示");
+            });
+            
         }
     }
 }

+ 1 - 1
README.md

@@ -4,7 +4,7 @@
 
 - 理论支持64位版本所有微信[1]
 - 工作区概念,支持多微信切换操作。
-- 支持导出Html文件
+- 支持导出Html文件,TXT文件,支持批量导出
 - 支持聊天频率分析,全消息库内容搜索
 - 目前支持以下类型消息解析
 - [x] 文本消息

+ 16 - 1
ViewModel/CreateWorkViewModel.cs

@@ -6,10 +6,11 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Data;
 using WechatBakTool.Model;
+using WechatBakTool.Pages;
 
 namespace WechatBakTool.ViewModel
 {
-    partial class CreateWorkViewModel : ObservableObject
+    public partial class CreateWorkViewModel : ObservableObject
     {
         [ObservableProperty]
         private List<ProcessInfo> processInfos = new List<ProcessInfo>();
@@ -22,6 +23,20 @@ namespace WechatBakTool.ViewModel
 
         [ObservableProperty]
         private int keyType = -1;
+
+        [ObservableProperty]
+        private bool isEnable = true;
+
+        private string labelStatus = "-";
+        public string LabelStatus
+        {
+            get { return "状态:" + labelStatus; }
+            set
+            {
+                labelStatus = value;
+                OnPropertyChanged("LabelStatus");
+            }
+        }
     }
 
     public class GetKeyConverter : IValueConverter

+ 21 - 15
ViewModel/WorkspaceViewModel.cs

@@ -9,15 +9,29 @@ using WechatBakTool.Model;
 
 namespace WechatBakTool.ViewModel
 {
-    partial class WorkspaceViewModel : ObservableObject
+    public partial class WorkspaceViewModel : ObservableObject
     {
+        [ObservableProperty]
+        [NotifyPropertyChangedFor(nameof(SelectContact))]
+        [NotifyPropertyChangedFor(nameof(LabelStatus))]
         private WXContact? wXContact = null;
-        public WXContact? WXContact {
-            get { return wXContact; }
-            set {  
-                wXContact = value;
-                OnPropertyChanged("WXContact");
-                OnPropertyChanged("SelectContact");
+
+        [ObservableProperty]
+        [NotifyPropertyChangedFor(nameof(LabelStatus))]
+        private string exportCount = "";
+
+        public string LabelStatus
+        {
+            get
+            {
+                if (WXContact == null)
+                    return ExportCount;
+
+                string name = WXContact.NickName;
+                if(WXContact.Remark != "")
+                    name = WXContact.Remark;
+
+                return string.Format("{0}:{1}", name, ExportCount);
             }
         }
 
@@ -59,13 +73,5 @@ namespace WechatBakTool.ViewModel
                 return searchString;
             }
         }
-
-        public string SearchRealString
-        {
-            get
-            {
-                return searchString;
-            }
-        }
     }
 }

+ 14 - 5
WXUserReader.cs

@@ -369,9 +369,6 @@ namespace WechatBakTool
             if (path == null)
                 return null;
 
-            if (!File.Exists(path))
-                return null;
-
             // 获取到原路径后,开始进行解密转移,只有图片和语音需要解密,解密后是直接归档目录
             if(type == WXMsgType.Image || type== WXMsgType.Audio)
             {
@@ -383,12 +380,24 @@ namespace WechatBakTool
                 if(!Directory.Exists(video_dir))
                     Directory.CreateDirectory(video_dir);
                 FileInfo fileInfo = new FileInfo(path);
+                // 目标视频路径
                 string video_file_path = Path.Combine(video_dir, fileInfo.Name);
                 // 视频的路径是相对路径,需要加上资源目录
                 path = Path.Combine(UserBakConfig.UserResPath, path);
-                if(!File.Exists(video_file_path))
+                // 原文件存在,目标不存在
+                if (!File.Exists(video_file_path) && File.Exists(path))
+                {
+                    // 复制
                     File.Copy(path, video_file_path);
-                path = video_file_path;
+                    path = video_file_path;
+                }
+                else if (File.Exists(video_file_path))
+                {
+                    path = video_file_path;
+                }
+                else
+                    return null;
+                    
             }
 
             if (path == null)

+ 7 - 5
WXWorkspace.cs

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
 using System.Windows;
 using WechatBakTool.Helpers;
 using WechatBakTool.Model;
-using WechatBakTool.Pages;
+using WechatBakTool.ViewModel;
 
 namespace WechatBakTool
 {
@@ -28,7 +28,7 @@ namespace WechatBakTool
             UserBakConfig = userBakConfig;
         }
 
-        public void DecryptDB(string pid,int type)
+        public void DecryptDB(string pid,int type,CreateWorkViewModel viewModel)
         {
             if (UserBakConfig == null)
             {
@@ -47,7 +47,7 @@ namespace WechatBakTool
                 string source = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB");
                 string to = Path.Combine(UserBakConfig.UserWorkspacePath, "DecDB");
 
-                DecryptionHelper.DecryUserData(key, source, to);
+                DecryptionHelper.DecryUserData(key, source, to, viewModel);
                 UserBakConfig.Decrypt = true;
 
                 WXUserReader reader = new WXUserReader(UserBakConfig);
@@ -58,7 +58,7 @@ namespace WechatBakTool
             }
         }
 
-        public void MoveDB()
+        public void MoveDB(CreateWorkViewModel viewModel)
         {
             string sourceBase = Path.Combine(UserBakConfig.UserResPath, "Msg");
             string sourceMulit = Path.Combine(UserBakConfig.UserResPath, "Msg/Multi");
@@ -66,8 +66,9 @@ namespace WechatBakTool
             foreach (string file in files)
             {
                 FileInfo fileInfo = new FileInfo(file);
-                if(fileInfo.Extension == ".db")
+                if (fileInfo.Extension == ".db")
                 {
+                    viewModel.LabelStatus = "正在迁移" + fileInfo.Name;
                     string to_path = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB", fileInfo.Name);
                     File.Copy(file, to_path, true);
                 }
@@ -79,6 +80,7 @@ namespace WechatBakTool
                 FileInfo fileInfo = new FileInfo(file);
                 if (fileInfo.Extension == ".db")
                 {
+                    viewModel.LabelStatus = "正在迁移" + fileInfo.Name;
                     string to_path = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB", fileInfo.Name);
                     File.Copy(file, to_path, true);
                 }

+ 7 - 3
WechatBakTool.csproj

@@ -6,9 +6,9 @@
     <Nullable>enable</Nullable>
     <UseWPF>true</UseWPF>
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
-    <AssemblyVersion>0.9.1.1</AssemblyVersion>
-    <FileVersion>0.9.1.1</FileVersion>
-    <Version>0.9.1.1</Version>
+    <AssemblyVersion>0.9.2.0</AssemblyVersion>
+    <FileVersion>0.9.2.0</FileVersion>
+    <Version>0.9.2.0</Version>
   </PropertyGroup>
 
   <ItemGroup>
@@ -37,4 +37,8 @@
     </None>
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="YearReport\" />
+  </ItemGroup>
+
 </Project>

+ 11 - 0
YearReport.xaml

@@ -0,0 +1,11 @@
+<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>

+ 27 - 0
YearReport.xaml.cs

@@ -0,0 +1,27 @@
+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();
+        }
+    }
+}