react,electron,typescript实践魔兽世界插件更新工具

前言

本人是一个魔兽世界老玩家,因为今年twitch的curse客户端已国内不发正常使用了,致使如今更新插件每次要去网站上下载,而后在手动拖到魔兽插件的根目录替换,很是麻烦并且容易搞错。因此就萌生了本身实现一个简版的twitch功能,curse在国内能够访问因此打算就用electron来作,内置nodejs环境这样我能够用爬虫爬取curse网站的插件数据,获取插件列表,插件下载地址,而后下载zip包解压安装到wow插件目录,彻底自动化。html

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 });
  };
复制代码

总结

  1. 本人以前项目里都是使用的es5,es6开发业务项目,这种课余项目使用typescript练手。给个人感觉就是使用typescript后代码自成约束,确实很是适合多人写做开发的一种代码规范,并且代码的可读性很高。可是本人typescript不是很熟练因此有些地方使用的any
  2. electron这的出现使得前端能够作一些须要访问本地文件,或者删除文件操做,在业务上可能比较适合播放器,中后台平台那种须要接入打印机或者其余硬件的时候,他有nodejs环境表明着若是有个c的工程师能够是直接操做硬件的。
  3. 这个插件更新器解决老夫多年的插件更新下载半天的问题,目前放入了某社区里你们使用的反馈很是不错,一些玩wow的玩家也用了我这个工具更新插件了,好像有100多人吧。
  4. 项目地址
  5. ui截图
  6. 若是你也玩魔兽不用整合包,很是建议你使用这个工具,我会一直维护下去,哪怕我魔兽afk了!下载地址在项目地址里的说明里,固然你也能够克隆代码,定制化本身须要的功能。
  7. 最近在找工做,本人4年前端经验,就是学历不太好是大专,不嫌弃的hr大佬留个言
相关文章
相关标签/搜索