前言
本人是一个魔兽世界老玩家,由于今年twitch的curse客户端已国内不发正常使用了,导致现在更新插件每次要去网站上下载,然后在手动拖到魔兽插件的根目录替换,非常麻烦而且容易搞错。所以就萌生了自己实现一个简版的twitch功能,curse在国内可以访问所以打算就用electron来做,内置nodejs环境这样我可以用爬虫爬取curse网站的插件数据,获取插件列表,插件下载地址,然后下载zip包解压安装到wow插件目录,完全自动化。
1.爬取curse数据
- 网站地址:www.curseforge.com/wow/addons
- 分析网站布局,由于左侧基本不会有大变化所以我爬取一次后就直接存本地了,增加性能
- 右侧必须每次启动客户端进行爬取,使用superagent爬取网站的html然后再使用cheerio解析html结构它是一个能早nodejs环境里使用的jQuery库,能非常高效获取到需要爬取的dom里的内容或者属性,和jQuery方法没有区别。
2.使用electron的进程通讯把爬取的内容传递给ui层
- 应用打开后通过electron里的ipcRenderer方法给主进程发消息,主进程接收到消息就去curse爬取当前分类里的插件列表,然后ui层使用electron.ipcRenderer.on('xxx', (e, data))方法接收到主进程返回的消息,拿到数据后需要使用electron.ipcRenderer.removeListener方法把消息监听移除掉
getAddonsList = (path:string, page?:number) : Promise<any> => {
return new Promise((resolve) => {
let timer = setTimeout(():void => {
electron.ipcRenderer.removeListener('getBaseAddons', () => {});
resolve(TIME_OUT_KEY)
}, AJAX_TIME_OUT);
electron.ipcRenderer.send('baseAddons', path, page);
electron.ipcRenderer.on('getBaseAddons', (e:any, data:any) => {
clearTimeout(timer);
electron.ipcRenderer.removeListener('getBaseAddons', () => {});
resolve(data)
})
})
};
- curse是点击换页码,在我的客户端里我改成无限滚动模式,增加体验
3.获取下载地址下载插件安装插件
- ui层点击安装或者更新时候还是使用electron.ipcRenderer.send方法给主进程发消息,我需要xxx插件的下载地址,这时候主进程的nodejs的service去爬取xxx插件的下载地址,在通过ui层的electron.ipRenderer.on来收到爬取的下载地址
getAddonDownUrl = (rowData: any = {}): Promise<any> => {
const installFilePath = localStorage.getItem(WOW_ADDONS_FILE_PATH_KEY);
return new Promise((resolve) => {
let timer = setTimeout((): void => {
electron.ipcRenderer.removeListener(`${rowData.path}-getDownAddonUrl`, () => {});
resolve(TIME_OUT_KEY);
}, AJAX_TIME_OUT);
electron.ipcRenderer.send('downAddon', rowData, installFilePath);
electron.ipcRenderer.on(`${rowData.path}-getDownAddonUrl`, (e:any, data:any) => {
if (data) {
resolve(data);
}
electron.ipcRenderer.removeListener(`${rowData.path}-getDownAddonUrl`, () => {});
clearTimeout(timer);
});
})
};
- 获取到下载地址,下载插件,使用的是node的fs写入下载的流和request请求下载文件流
handleDownloading = (downloadUrl: string, rowData: any): void => { // 下载插件
let file = `${this.state.installFilePath}/${rowData.label}_${Date.now()}.zip`;
let writeStream = fs.createWriteStream(file);
this.setState({ loading: true, btnTxt: LOAD_BTN_TXT, zipFile: file });
request.get(downloadUrl).pipe(writeStream);
// 开始下载
writeStream.on('drain', () : void => {});
// 下载成功
writeStream.on('finish', (): void => {
this.setState({ btnTxt: INSTALL_BTN_TXT });
this.unzipAddon().then((folderList: Array<any>): void => {
this.handleInstallDown(folderList, rowData);
})
});
// 下载失败
writeStream.on('error', ():void => {
rimraf(file, () => {});
this.setState({ loading: false, btnTxt: `下载插件失败重试` });
})
};
- 下载完毕,解压zip
unzipAddon = (): Promise<any> => { // 解压插件zip包
return new Promise((resolve) => {
const zip = new AdmZip(this.state.zipFile);
const folderList: Array<any> = [];
zip.getEntries().forEach((entry:any) => {
const entryName = entry.entryName;
const folderName = entryName.split('/')[0];
if (folderList.indexOf(folderName) === -1) {
folderList.push(folderName);
}
});
// 执行解压
zip.extractAllTo(this.state.installFilePath, true);
resolve(folderList)
})
};
- 解压完毕,然后安装插件,并且本地写一个数据库文件保存本次安装的插件数据,方便以后做对比看是否需要插件更新
handleInstallDown = (folderList:Array<any>, rowData:any): void => {
const fileJson = `${this.state.installFilePath}/${INSTALL_ADDONS}`;
const { setMyAddonList, updateAddonList, setUpdateAddonList } = this.props.store!;
if (!fs.existsSync(fileJson)) {
fs.writeFileSync(fileJson, '[]')
}
const item = JSON.parse(JSON.stringify(rowData));
item.folderList = folderList;
// 同步本地插件列表
myAddon.setAddon(item);
// 重新获取一次安装插件列表
setMyAddonList(myAddon.getAddonList());
// 删除zip包
rimraf(this.state.zipFile, () => {});
// 若安装的插件在需要更新的列表里存在则删除掉
let cacheUpdateAddonList = JSON.parse(JSON.stringify(updateAddonList));
if (cacheUpdateAddonList.filter((v:any) => v.path === rowData.path).length !== 0) {
setUpdateAddonList(cacheUpdateAddonList.filter((v:any) => v.path !== rowData.path))
}
this.setState({ btnTxt: SUCCESS_BTN_TXT, loading: false });
};
总结
- 本人之前项目里都是使用的es5,es6开发业务项目,这种课余项目使用typescript练手。给我的感受就是使用typescript后代码自成约束,确实非常适合多人写作开发的一种代码规范,而且代码的可读性很高。但是本人typescript不是很熟练所以有些地方使用的any
- electron这的出现使得前端可以做一些需要访问本地文件,或者删除文件操作,在业务上可能比较适合播放器,中后台平台那种需要接入打印机或者其他硬件的时候,他有nodejs环境代表着如果有个c的工程师可以是直接操作硬件的。
- 这个插件更新器解决老夫多年的插件更新下载半天的问题,目前放入了某社区里大家使用的反馈非常不错,一些玩wow的玩家也用了我这个工具更新插件了,好像有100多人吧。
- 项目地址
- ui截图



- 如果你也玩魔兽不用整合包,非常建议你使用这个工具,我会一直维护下去,哪怕我魔兽afk了!下载地址在项目地址里的说明里,当然你也可以克隆代码,定制化自己需要的功能。
- 最近在找工作,本人4年前端经验,就是学历不太好是大专,不嫌弃的hr大佬留个言