├── .classpath ├── .gitignore ├── .project ├── README.md ├── pom.xml └── src ├── main └── java │ └── site │ └── xiaodong │ └── wechat │ ├── Bill.java │ └── Util.java └── test └── java └── site └── xiaodong └── wechat └── BillTest.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | /target/ 3 | *.db 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | wechat-db-parser 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 使用微信聊天记录统计信息 2 | 3 | 原文请查看 4 | > https://notes.zz-zigzag.com/2017/09/wechat-db.html -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | site.xiaodong 6 | wechat-db-parser 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | 11 | UTF-8 12 | 13 | 14 | 15 | 16 | junit 17 | junit 18 | 4.12 19 | test 20 | 21 | 22 | commons-cli 23 | commons-cli 24 | 1.2 25 | 26 | 27 | org.xerial 28 | sqlite-jdbc 29 | 3.20.0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-shade-plugin 39 | 40 | 41 | package 42 | 43 | shade 44 | 45 | 46 | 47 | 49 | site.xiaodong.wechat.Bill 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | maven-assembly-plugin 58 | 59 | 60 | 61 | site.xiaodong.wechat.Bill 62 | 63 | 64 | 65 | jar-with-dependencies 66 | 67 | 68 | 69 | 70 | make-assembly 71 | package 72 | 73 | single 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/site/xiaodong/wechat/Bill.java: -------------------------------------------------------------------------------- 1 | package site.xiaodong.wechat; 2 | 3 | import java.io.FileWriter; 4 | import java.math.BigDecimal; 5 | import java.sql.Connection; 6 | import java.sql.DriverManager; 7 | import java.sql.PreparedStatement; 8 | import java.sql.ResultSet; 9 | import java.sql.Timestamp; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import org.apache.commons.cli.BasicParser; 16 | import org.apache.commons.cli.CommandLine; 17 | import org.apache.commons.cli.CommandLineParser; 18 | import org.apache.commons.cli.HelpFormatter; 19 | import org.apache.commons.cli.Options; 20 | 21 | public class Bill { 22 | private static String filePath; 23 | private static String chatroom; 24 | private static String start; 25 | private static String end; 26 | private static String myWxid; 27 | private static boolean deleted; 28 | private static boolean someoneDeleted; 29 | private static String detailFile; 30 | 31 | private static Connection conn; 32 | private static PreparedStatement ps; 33 | private static ResultSet rs; 34 | 35 | private static List nameList = new ArrayList(); 36 | private static List sumList = new ArrayList(); 37 | private static Map wxidIndex = new HashMap(); 38 | private static List> detailList = new ArrayList>(); 39 | 40 | public static void main(String[] args) { 41 | try { 42 | getOption(args); 43 | getConnect(); 44 | getMyWxid(); 45 | if (deleted) { 46 | getAllMemberList(); 47 | } else { 48 | getChatroomMemberList(); 49 | } 50 | getSum(); 51 | output(); 52 | if (detailFile != null) { 53 | outputDetail(); 54 | } 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | System.exit(0); 58 | } finally { 59 | try { 60 | rs.close(); 61 | ps.close(); 62 | conn.close(); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | } 68 | 69 | private static void outputDetail() throws Exception { 70 | FileWriter fw = new FileWriter(detailFile); 71 | for (int i = 0; i < sumList.size(); ++i) { 72 | if (Double.parseDouble(sumList.get(i).toString()) == 0 && deleted) { 73 | continue; 74 | } 75 | fw.write(nameList.get(i)); 76 | for (int j = 0; j < detailList.get(i).size(); j++) { 77 | fw.write("\t" + detailList.get(i).get(j)); 78 | } 79 | fw.write("\r\n"); 80 | } 81 | fw.close(); 82 | } 83 | 84 | private static void output() { 85 | for (int i = 0; i < sumList.size(); ++i) { 86 | if (Double.parseDouble(sumList.get(i).toString()) == 0 && deleted) { 87 | continue; 88 | } 89 | System.out.println(nameList.get(i) + "\t" + sumList.get(i)); 90 | } 91 | if (someoneDeleted) { 92 | System.out.println("someone deleted may not be process, you can run with -d to include it."); 93 | } 94 | } 95 | 96 | /** 97 | * 统计 98 | * 99 | * @throws Exception 100 | */ 101 | private static void getSum() throws Exception { 102 | Timestamp startDate, endDate; 103 | startDate = Timestamp.valueOf(start); 104 | endDate = Timestamp.valueOf(end); 105 | ps = conn.prepareStatement( 106 | "select m.content, m.isSend from message m join chatroom c on m.talker = c.chatroomname " 107 | + "left join rcontact r on r.username = c.chatroomname " 108 | + "where r.nickname = ? and m.createTime > ? and m.createTime < ? "); 109 | ps.setString(1, chatroom); 110 | ps.setLong(2, startDate.getTime()); 111 | ps.setLong(3, endDate.getTime()); 112 | rs = ps.executeQuery(); 113 | while (rs.next()) { 114 | String m = rs.getString(1); 115 | int isSend = rs.getInt(2); 116 | if (!m.matches(Util.messageRegex)) { 117 | continue; 118 | } 119 | String wxid, valueStr; 120 | int index; 121 | if (isSend == 0) { 122 | wxid = m.substring(0, m.indexOf(":")); 123 | valueStr = m.substring(m.indexOf(":") + 2, m.length()).trim(); 124 | 125 | // member list have no this wxid 126 | if (!wxidIndex.containsKey(wxid)) { 127 | if (!someoneDeleted) 128 | someoneDeleted = true; 129 | continue; 130 | } 131 | } else { 132 | wxid = myWxid; 133 | valueStr = m; 134 | } 135 | index = wxidIndex.get(wxid); 136 | BigDecimal a = sumList.get(index).add(new BigDecimal(valueStr)); 137 | sumList.set(index, a); 138 | detailList.get(index).add(new BigDecimal(valueStr)); 139 | } 140 | } 141 | 142 | /** 143 | * 获取所有通讯录名单 为了应对, 144 | * 145 | * @throws Exception 146 | */ 147 | private static void getAllMemberList() throws Exception { 148 | ps = conn.prepareStatement("select username, conRemark, nickname from rcontact "); 149 | rs = ps.executeQuery(); 150 | pasperMemerList(rs); 151 | } 152 | 153 | private static void pasperMemerList(ResultSet rs) throws Exception { 154 | int i = 0; 155 | while (rs.next()) { 156 | String name = (rs.getString(2).equals("") ? rs.getString(3) : rs.getString(2)); 157 | nameList.add(name); 158 | sumList.add(new BigDecimal(0)); 159 | detailList.add(new ArrayList()); 160 | wxidIndex.put(rs.getString(1), i); 161 | i++; 162 | } 163 | } 164 | 165 | /** 166 | * 获取群聊通讯录名单 167 | * 168 | * @throws Exception 169 | */ 170 | private static void getChatroomMemberList() throws Exception { 171 | ps = conn.prepareStatement( 172 | "select memberlist from chatroom c left join rcontact r on r.username = c.chatroomname where r.nickname = ?"); 173 | 174 | ps.setString(1, chatroom); 175 | rs = ps.executeQuery(); 176 | String memberlist = null; 177 | if (rs.next()) { 178 | memberlist = rs.getString(1); 179 | } else { 180 | System.out.println(chatroom + " can not found"); 181 | System.exit(0); 182 | } 183 | memberlist = memberlist.replaceAll(";", "','"); 184 | 185 | ps = conn.prepareStatement( 186 | "select username, conRemark, nickname from rcontact where username in ('" + memberlist + "')"); 187 | rs = ps.executeQuery(); 188 | pasperMemerList(rs); 189 | } 190 | 191 | /** 192 | * 获取我自己的wxid 193 | * 194 | * @throws Exception 195 | */ 196 | private static void getMyWxid() throws Exception { 197 | ps = conn.prepareStatement("select value from userinfo where id= 2"); 198 | rs = ps.executeQuery(); 199 | if (rs.next()) { 200 | myWxid = rs.getString(1); 201 | } else { 202 | System.out.println("my wechat id can not found"); 203 | System.exit(0); 204 | } 205 | } 206 | 207 | private static void getConnect() throws Exception { 208 | Class.forName("org.sqlite.JDBC"); 209 | conn = DriverManager.getConnection("jdbc:sqlite:" + filePath); 210 | 211 | } 212 | 213 | private static void getOption(String[] args) { 214 | Options options = new Options(); 215 | options.addOption("h", "help", false, "print this usage information"); 216 | options.addOption("f", "dbfile", true, "path of wechat database file"); 217 | options.addOption("c", "chatname", true, "chatroom name"); 218 | options.addOption("s", "start", true, "start date eg. 2017-01-01"); 219 | options.addOption("e", "end", true, "chatroom name eg. 2017-01-01"); 220 | options.addOption("d", "deleted-members", false, "parser contains deleted members"); 221 | options.addOption("i", "infofile", true, "output detail info to the file"); 222 | 223 | CommandLineParser parser = new BasicParser(); 224 | CommandLine commandLine; 225 | try { 226 | commandLine = parser.parse(options, args); 227 | if (commandLine.hasOption('h')) { 228 | Usage(options); 229 | } 230 | filePath = commandLine.getOptionValue("f"); 231 | if (Util.isBlank(filePath)) { 232 | Usage(options); 233 | } 234 | chatroom = commandLine.getOptionValue("c"); 235 | if (Util.isBlank(chatroom)) { 236 | Usage(options); 237 | } 238 | start = commandLine.getOptionValue("s"); 239 | if (Util.isBlank(start) || !start.matches(Util.dateRegex)) { 240 | Usage(options); 241 | } 242 | start += " 00:00:00"; 243 | end = commandLine.getOptionValue("e"); 244 | if (Util.isBlank(end) || !end.matches(Util.dateRegex)) { 245 | Usage(options); 246 | } 247 | end += " 00:00:00"; 248 | deleted = commandLine.hasOption('d'); 249 | detailFile = commandLine.getOptionValue("i"); 250 | 251 | } catch (Exception e) { 252 | e.printStackTrace(); 253 | System.exit(0); 254 | } 255 | } 256 | 257 | private static void Usage(Options options) { 258 | HelpFormatter hf = new HelpFormatter(); 259 | hf.printHelp("Options", options); 260 | System.exit(0); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/main/java/site/xiaodong/wechat/Util.java: -------------------------------------------------------------------------------- 1 | package site.xiaodong.wechat; 2 | 3 | public class Util { 4 | // 日期正则,已考虑平闰年、月份日期 5 | public static String dateRegex = "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$"; 6 | // wxid_sdfsdfsdf:\n 6.7 7 | // 整数部分小于三位数 8 | public static String messageRegex = "^(\\w*:\\n)?\\s*\\d{1,3}(\\.\\d*)?\\s*$"; 9 | public static boolean isBlank(String s) { 10 | if (s == null || s.equals("")) 11 | return true; 12 | return false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/site/xiaodong/wechat/BillTest.java: -------------------------------------------------------------------------------- 1 | package site.xiaodong.wechat; 2 | 3 | import org.junit.Test; 4 | 5 | public class BillTest { 6 | 7 | @Test 8 | public void testHelp() { 9 | String args[] = { "-f", "EnMicroMsg.db", "-c", "没坏账饭友群", "-s", "2017-09-01", "-e", "2017-10-01" }; 10 | Bill.main(args); 11 | } 12 | 13 | @Test 14 | public void testDeleted() { 15 | String args[] = { "-f", "EnMicroMsg.db", "-c", "没坏账饭友群", "-s", "2017-09-01", "-e", "2017-10-01", "-d" }; 16 | Bill.main(args); 17 | } 18 | 19 | @Test 20 | public void testDetail() { 21 | String args[] = { "-f", "EnMicroMsg.db", "-c", "没坏账饭友群", "-s", "2017-09-01", "-e", "2017-10-01", "-i", "log.txt" }; 22 | Bill.main(args); 23 | } 24 | 25 | //java -jar wechat-db-parser.jar -f EnMicroMsg.db -c "没坏账饭友群" -s 2017-09-01 -e 2017-10-01 -i 2017-09_detail.txt 26 | } 27 | --------------------------------------------------------------------------------