微信除了太封闭,其他都挺好。

疫情期间在家办公加入了小区一个业主微信群,加入时人不多,我就把群二维码贴到了电梯口,没过几天,好几百人加入了进来。

人少的时候成员列表一眼看过去大概就知道有那些楼层的邻居在群里,这人多了就很难看出分布情况了,想着可以做个可视化的表来看。

大概设计成下面这个样子

0 1 2 3 4 5 6
10 1-10-1 1-10-2 1-10-3 1-10-4 1-10-5 1-10-6
9 1-9-1 1-9-2 1-9-3 1-9-4 1-9-5 1-9-6
8 1-8-1 1-8-2 1-8-3 1-8-4 1-8-5 1-8-6
7 1-7-1 1-7-2 1-7-3 1-7-4 1-7-5 1-7-6
6 1-6-1 1-6-2 1-6-3 1-6-4 1-6-5 1-6-6
5 1-5-1 1-5-2 1-5-3 1-5-4 1-5-5 1-5-6
4 1-4-1 1-4-2 1-4-3 1-4-4 1-4-5 1-4-6
3 1-3-1 1-3-2 1-3-3 1-3-4 1-3-5 1-3-6
2 1-2-1 1-2-2 1-2-3 1-2-4 1-2-5 1-2-6
1 1-1-1 1-1-2 1-1-3 1-1-4 1-1-5 1-1-6

这是一栋楼的所有门牌号。当有业主加入群的时候,就把门牌号对应单元格的背景色设为绿色(比如: 1-9-1,1-7-2,1-6-4)。当然,这需要成员的群昵称包含有门牌号。

目标看起来非常简单,但实现的过程着实费了不少劲。时间主要耗在获取微信群成员列表上了。

一开始我以为微信有个网页版,登录网页版获取 HTML 源码再提起数据就行了。结果我的微信号无法登录网页版,提示说是为了我的账号安全着想,也尝试用老婆的微信登录网页版,结果也是同样的友好提示,这一点要给微信点个赞 👎

网页版用不了着实有些麻烦,我尝试抓取微信客户端的网络数据包,发现都是以二进制数据传输的。

搜了一圈,发现有人做了微信 Mac 版客户端的插件(WeChatPlugin-MacOS),而且功能丰富。我试着读了读源码,发现了我想要的东西。在这个插件中,作者实现了微信客户端相同的类库,在运行时替换微信原来的类库来处理数据,作者对于原理在这里有简短的描述。但不知道什么原因,作者已经停止了维护。不过我发现有另外一个项目(WeChatExtension-ForMac)还在继续,是基于前面那个项目进行的。

插件没有直接导出群成员列表的功能,但从代码中发现其实有在使用这些数据。这就简单了,加些代码把数据导出来。

if ([groupData.m_nsNickName isEqualToString:@"业主群"]) {
    NSMutableString *nicknames = [NSMutableString stringWithCapacity:0];
    
    [allDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) {
        NSString *origNick = [YMIMContactsManager getGroupMemberNickName:key];
        NSString *displayNick = [obj valueForKey:@"DisplayName"];
        
        [nicknames appendString:[NSString stringWithFormat:@"%@,%@\n", origNick, [displayNick length] == 0 ? @"" : displayNick]];
    }];

    [nicknames writeToFile:[@"~/Desktop/group.csv" stringByExpandingTildeInPath]
                atomically:YES
                  encoding:NSUTF8StringEncoding
                     error: nil];
}

有了成员列表数据,就可以开始做可视化了。我先是在 Excel 里设计了表格,也加上了 Conditional Formatting Rules,但后来发现要在 Excel 里把门牌号从成员昵称数据里提取出来还挺麻烦,成员昵称又比较乱,Excel 不支持正则提取。换成 Google Sheets 很快就实现了效果