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大佬留個言
相關文章
相關標籤/搜索