一、背景

最近公司群里有人反应说怎么能够将微信的表情包导出来,要增加到内部的沟通软件上运用。群里的同学纷繁出招,有人说经过网页文件助手发送,然后右键保存导出。我也尝试了一下这个计划,的确能够导出。只需求发送完结后在网页上右键保存到本地即可(动图的话需求保存时增加.gif后缀)。

filehelper.weixin.qq.com/

导出微信的自定义表情?简略!

这个计划的确是一种解决办法,可是缺陷也比较显着,便是只能一个个去操作保存,效率较低。假如需求批量导出,就略显繁琐。怎么能够批量导出微信的表情包,便是接下来本篇文章要讲的内容。

二、计划调研

经过搜索引擎搜了一下,在mac渠道下有这样一个解决计划。

blog.jogle.top/2022/08/14/…

核心计划便是拜访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…