├── .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 |
--------------------------------------------------------------------------------