一、背景
最近公司群里有人反应说怎么能够将微信的表情包导出来,要增加到内部的沟通软件上运用。群里的同学纷繁出招,有人说经过网页文件助手发送,然后右键保存导出。我也尝试了一下这个计划,的确能够导出。只需求发送完结后在网页上右键保存到本地即可(动图的话需求保存时增加.gif后缀)。
这个计划的确是一种解决办法,可是缺陷也比较显着,便是只能一个个去操作保存,效率较低。假如需求批量导出,就略显繁琐。怎么能够批量导出微信的表情包,便是接下来本篇文章要讲的内容。
二、计划调研
经过搜索引擎搜了一下,在mac渠道下有这样一个解决计划。
核心计划便是拜访mac版微信在沙盒的缓存文件fav.archive
,找到里边的表情包url地址,然后粘贴到浏览器拜访下载。
这个沙盒途径如下,翻开后找到一个比较长字符串命名的文件夹,里边有许多子文件夹,其间有个文件夹叫Stickers,这里便是存放fav的地方了。
open ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/
fav.archive
本质是一个二进制的plist数据,里边的数据结构如下。其间在$objects
这个key下面存放了表情包的相关数据,当然也包含一些其他类型的数据。咱们需求的便是以http开头的网络图片数据。
测验了下直接把url地址复制出来到浏览器,图片直接翻开是没问题的,也便是说url自身是存在鉴权信息的。至于鉴权有效期是多久就不清楚了。反正是微信担任保护他的有效期,咱们直接取这个地址就行了。url结构如下。
vweixinf.tc.qq.com/110/20401/s…
所以只要咱们遍历$objects
这个数组,取出http开头的字符串,然后下载图片不就ok了吗。一个批量导出表情的工具不就有了吗。毕竟我可是显贵的loser开发。
三、mac软件开发
首要画个流程图,简略介绍一下完成流程。
graph TD
id1([app启动]) --> 拜访fav.archive --> 读取plist数据到内存 --> 取出$objects数据 --> 增加http开头字符串到数组 --> 遍历数组加载图片 --> 图片批量导出
软件效果图如下。默许读取~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/xxxx/Stickers/fav.archive
的数据。假如没有读取到,也支撑在下面自己挑选fav.archive文件。
3.1 画UI
根据AppKit的mac原生代码开发非常痛苦,AppKit并不像UIKit那样强壮,许多功用和api用起来都很别扭,所以mac原生开发简略的页面最好仍是运用storyboard来完成。
页面自身不杂乱,一个NSCollecionView用来展现表情数据,能够跟随窗口巨细拖动自适应不同列数展现,设置datasource为Viewcontroller;两个操作按钮,用来挑选archive文件和导出表情操作;一个error label用来展现运用过程的反常;终究有个progressView用来展现表情导出进展。这样一个表情查看器的UI就写好了。
3.2 写逻辑
3.2.1 结构集成
图片的展现及下载当时必不可少SDWebImage,AFNetworking也是有或许用到的,这俩结构先经过pod集成到工程中。
platform :osx, '10.13'
target "StickerViewer" do
pod 'AFNetworking'
pod 'SDWebImage'
end
3.2.2 文件读取
经过NSFileManager对微信的沙盒文件进行拜访。这里有一点需求注意的是有必要运用完整途径进行拜访,假如直接运用~Library
的方式拜访,NSFileManager会拜访报错(这个问题当时折腾了良久才发现)。
NSString *username = NSUserName();
NSString *basePath = [NSString stringWithFormat:@"/Users/%@/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9", username];
NSString *stickerComponent = @"Stickers";
NSString *stickerPath = nil;
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *contents = [fileManager contentsOfDirectoryAtPath:basePath error:&error];
找到2.0b4.0.9
目录后,接下来就需求找到自己那一长串用户id的目录,再定位到Stickers目录。这里没什么好的计划,遍历2.0b4.0.9
目录,查看是否存在Stickers目录就能够了。
NSString *stickerComponent = @"Stickers";
for (NSString *item in contents) {
NSString *currentPath = [[basePath stringByAppendingPathComponent:item] stringByAppendingPathComponent:stickerComponent];
BOOL isDir;
if ([fileManager fileExistsAtPath:currentPath isDirectory:&isDir] && isDir) {
//找到途径
stickerPath = [[basePath stringByAppendingPathComponent:item] stringByAppendingPathComponent:stickerComponent];
break;
}
}
if (stickerPath.length) {
NSString *stickerFavPath = [stickerPath stringByAppendingPathComponent:@"fav.archive"];
if ([fileManager fileExistsAtPath:stickerFavPath isDirectory:nil]) {
//加载xml数据
self.favData = [NSData dataWithContentsOfFile:stickerFavPath];
[self parsePlistData];
}
}
增加bookmark: 增加bookmark的作用是只会弹出一次授权拜访的弹窗,否则每次翻开app都会提示授权,比较麻烦。
//保存bookmark
NSURL *basePathUrl = [NSURL fileURLWithPath:basePath];
NSError *bookMarkError = nil;
NSData *bookmarkData =[basePathUrl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&bookMarkError];
if (bookMarkError) {
NSLog(@"bookmark犯错:%@", bookMarkError);
} else {
[[NSUserDefaults standardUserDefaults] setObject:bookmarkData forKey:@"bookmarkdata"];
NSLog(@"bookmarkdata保存成功");
}
//拜访bookmark
BOOL bookmarkDataIsStale;
NSURL *allowedUrl = [NSURL URLByResolvingBookmarkData:self.bookMarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL];
[allowedUrl startAccessingSecurityScopedResource];
//读取文件的代码
3.2.3 文件解析
经过NSPropertyListSerialization对数据进行解析,获取到表情数组self.stickersArray
。
NSError *error;
NSDictionary *plistObject = [NSPropertyListSerialization propertyListWithData:self.favData options:NSPropertyListImmutable format:NULL error:&error];
if (plistObject) {
self.errorLabel.hidden = YES;
NSArray *stickersData = plistObject[@"$objects"];
if (stickersData && [stickersData isKindOfClass:NSArray.class]) {
//遍历数据
for (id item in stickersData) {
if ([item isKindOfClass:NSString.class] && [(NSString *)item hasPrefix:@"http"]) {
[self.stickersArray addObject:item];
}
}
}
} else {
self.errorLabel.hidden = NO;
self.errorLabel.stringValue = [NSString stringWithFormat:@"plist解析失败:%@", error.localizedDescription];
}
//视图改写
[self.collectionView reloadData];
3.2.4 图片加载
运用SDWebImage加载图片到collectionview的item上。
[self.stickerImageView sd_setImageWithURL:[NSURL URLWithString:urlString]
placeholderImage:[NSImage imageNamed:@"sticker_holder"]
completed:^(NSImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
//图片加载完毕
}];
3.2.5 图片批量导出
运用loadImageWithURL:
办法能够优先运用磁盘及内存缓存将图片导出。
[[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:imageUrlString]
options:SDWebImageRetryFailed
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
//下载进展
} completed:^(NSImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
//图片数据
NSData *imageData = data;
if (imageData) {
//图片保存
NSString *fileName = [NSString stringWithFormat:@"%03ld.%@", i, image.sd_isAnimated ? @"gif" : @"png"];
NSString *imagePath = [stickerPath stringByAppendingPathComponent:fileName];
[imageData writeToFile:imagePath atomically:YES];
//更新进展等
}
}];
实践测验下来上面的办法部分表情数据会只回来NSImage目标,未回来NSData目标,尝试用NSImage目标转化为NSData,无论是转成位图仍是其他方式,转化出来的图片都是偏小的,gif也不会动,所以只能经过SDWebImageDownloader
对这种反常的图片从头进行下载(测验下来运用这种方式后图片下载下来是正常的)。
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:imageUrlString]
options:SDWebImageDownloaderAllowInvalidSSLCertificates|SDWebImageDownloaderHighPriority
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
//下载进展
}
completed:^(NSImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
//图片数据
NSData *imageData = data ?: [image TIFFRepresentation];
//图片保存
NSString *fileName = [NSString stringWithFormat:@"%03ld.%@", index, image.sd_isAnimated ? @"gif" : @"png"];
NSString *imagePath = [savePath stringByAppendingPathComponent:fileName];
[imageData writeToFile:imagePath atomically:YES];
if (successCount == self.stickersArray.count) {
//导出完结
self.progressView.hidden = YES;
[FWAlertUtils showAlertWithTitle:@"导出完结"];
//翻开文件夹
[[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:savePath]];
}
}];
以上便是一切的核心代码了,能够发现也是比较简略的。终究一个能够拜访读取fav.archive文件的微信表情查看器就完成好了。
导出后的表情按序号放在文件夹中。
四、一些感触
其实最开端我的思路是,已然mac版微信表情面板是把一切的表情加载好的,那只需求找到沙盒中他的表情缓存然后导出就能够了。但是鸡贼的wx对他的表情数据进行了加密。其间fav.archive
文件的同级目录下有个文件夹叫Persistence
,里边存放了许多文件,这个文件夹全体巨细200M左右,与我自己导出的一切表情170M巨细差不多。但这个数据是无法直接翻开的,修正后缀为png也是无法翻开的。这些文件一种或许是一种本地存储的切片文件,另一种或许是加密后的单个表情的二进制数据。这个我们有爱好能够研究一下。
终究,该软件现已开源到github中,欢迎下载体验及star。有问题欢迎我们一同沟通。 github.com/zhouxing531…