[
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Build/release\n\non: push\n\njobs:\n  release:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n    steps:\n      - name: Check out Git repository\n        uses: actions/checkout@v2\n        with:\n          submodules: true\n\n      - name: Install Node.js, NPM and Yarn\n        uses: actions/setup-node@v2\n        with:\n          node-version: 16\n\n      - name: Build/release Electron app\n        uses: samuelmeuli/action-electron-builder@v1\n        with:\n          # GitHub token, automatically provided to the action\n          # (No need to define this secret in the repo settings)\n          github_token: ${{ secrets.github_token }}\n\n          # If the commit is tagged with a version (e.g. \"v1.0.0\"),\n          # release the app after building\n          release: ${{ startsWith(github.ref, 'refs/tags/v') }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\nyarn.lock\n.vscode\n.DS_Store"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"app/listen1_chrome_extension\"]\n\tpath = app/listen1_chrome_extension\n\turl = https://github.com/listen1/listen1_chrome_extension.git\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"arrowParens\": \"always\",\n  \"endOfLine\": \"lf\",\n  \"bracketSpacing\": true\n}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Listen 1\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Listen 1 音乐播放器（桌面版）\n\nListen 1 可以搜索和播放来自多个主流音乐网站的歌曲，让你的曲库更全面。并支持收藏功能，方便的创建自己的歌单。\n\n支持音乐平台\n\n- 网易云音乐\n- QQ 音乐\n- 酷狗音乐\n- 酷我音乐\n- bilibili\n- 咪咕音乐\n- 千千音乐\n\n[![imgur](http://i.imgur.com/Ae6ItmA.png)]()\n\n- 支持 Windows，Mac，Linux 平台\n\n# 安装方式\n\n访问 github 主页下载安装包安装\n\n网址：https://listen1.github.io/listen1\n\n## 生成完整代码\n\n项目中包含了 listen1_chrome_extension 的引用，在 checkout 后需要把引用库初始化\n\n    git submodule update --init --recursive\n\n## 运行\n\n    npm run start\n\n## 生成安装包\n\n全平台安装包\n\n    npm run dist\n\nWindows 安装包\n\n    npm run dist:win32\n    npm run dist:win64\n\nMac 安装包\n\n    npm run dist:mac\n\nLinux 安装包\n\n    npm run dist:linux32\n    npm run dist:linux64\n"
  },
  {
    "path": "app/floatingWindow.html",
    "content": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n  <head>\r\n    <meta charset=\"UTF-8\" />\r\n    <link href=\"listen1_chrome_extension/css/icon.css\" rel=\"stylesheet\" />\r\n    <title>Listen1</title>\r\n    <style>\r\n      div.content {\r\n        border-radius: 5px;\r\n        min-height: 70px;\r\n        box-sizing: border-box;\r\n        overflow: hidden;\r\n        text-align: center;\r\n        font-size: 24px;\r\n        color: white;\r\n        display: flex;\r\n        flex-direction: column;\r\n        align-items: center;\r\n        justify-content: center;\r\n        user-select: none;\r\n        transition: background ease-in-out 0.1s;\r\n      }\r\n\r\n      /* span.contentTrans {\r\n        font-size: 18px;\r\n      }\r\n      body.unlocked-body:hover .content {\r\n        background: rgba(36, 36, 36, 0.8);\r\n      } */\r\n      body {\r\n        font-family: \"SF Pro Text\", \"SF Pro Icons\", \"Helvetica Neue\",\r\n          \"Microsoft Yahei\", \"Helvetica\", \"Arial\", sans-serif;\r\n        overflow: hidden;\r\n        margin: 0;\r\n        padding: 0;\r\n        width: 100%;\r\n        height: 100%;\r\n      }\r\n      .feather {\r\n        height: 16px;\r\n        width: 16px;\r\n        fill: none;\r\n        stroke: #999999;\r\n        display: inline-block;\r\n        background-color: rgba(255,255,255,0.01);\r\n      }\r\n      #toolbar {\r\n        user-select: none;\r\n        position: fixed;\r\n        top: 3px;\r\n        z-index: 9999;\r\n        margin: 0 auto;\r\n        left: 0;\r\n        right: 0;\r\n        text-align: center;\r\n      }\r\n      .icon:hover,\r\n      .icon:hover .feather {\r\n        stroke: #ffffff;\r\n      }\r\n      .group-first-icon {\r\n        margin-left: 10px;\r\n      }\r\n      #currentLyricAll {\r\n        width: 100%;\r\n        height: 100%;\r\n      }\r\n    </style>\r\n  </head>\r\n  <body>\r\n    <div\r\n      style=\"visibility: hidden; position: absolute; width: 0px; height: 0px\"\r\n    >\r\n      <!-- load feather icon svg sprite -->\r\n      <svg xmlns=\"http://www.w3.org/2000/svg\">\r\n        <defs>\r\n          <symbol id=\"x\" viewBox=\"0 0 24 24\">\r\n            <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n            <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n          </symbol>\r\n          <symbol id=\"lock\" viewBox=\"0 0 24 24\">\r\n            <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n            <path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path>\r\n          </symbol>\r\n          <symbol id=\"unlock\" viewBox=\"0 0 24 24\">\r\n            <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect>\r\n            <path d=\"M7 11V7a5 5 0 0 1 9.9-1\"></path>\r\n          </symbol>\r\n          <symbol id=\"type\" viewBox=\"0 0 24 24\">\r\n            <polyline points=\"4 7 4 4 20 4 20 7\"></polyline>\r\n            <line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"></line>\r\n            <line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"></line>\r\n          </symbol>\r\n          <symbol id=\"plus\" viewBox=\"0 0 24 24\">\r\n            <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"></line>\r\n            <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line>\r\n          </symbol>\r\n          <symbol id=\"minus\" viewBox=\"0 0 24 24\">\r\n            <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line>\r\n          </symbol>\r\n          <symbol id=\"sun\" viewBox=\"0 0 24 24\">\r\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\r\n            <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"></line>\r\n            <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"></line>\r\n            <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"></line>\r\n            <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"></line>\r\n            <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"></line>\r\n            <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"></line>\r\n            <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"></line>\r\n            <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"></line>\r\n          </symbol>\r\n          <symbol id=\"aperture\" viewBox=\"0 0 24 24\">\r\n            <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\r\n            <line x1=\"14.31\" y1=\"8\" x2=\"20.05\" y2=\"17.94\"></line>\r\n            <line x1=\"9.69\" y1=\"8\" x2=\"21.17\" y2=\"8\"></line>\r\n            <line x1=\"7.38\" y1=\"12\" x2=\"13.12\" y2=\"2.06\"></line>\r\n            <line x1=\"9.69\" y1=\"16\" x2=\"3.95\" y2=\"6.06\"></line>\r\n            <line x1=\"14.31\" y1=\"16\" x2=\"2.83\" y2=\"16\"></line>\r\n            <line x1=\"16.62\" y1=\"12\" x2=\"10.88\" y2=\"21.94\"></line>\r\n          </symbol>\r\n        </defs>\r\n      </svg>\r\n    </div>\r\n    <div id=\"currentLyricAll\" class=\"content lyric-content\">\r\n      <span id=\"currentLyric\" class=\"contentOriginal\">Listen1</span>\r\n      <span id=\"currentLyricTrans\" class=\"contentTrans\"></span>\r\n    </div>\r\n    <div id=\"toolbar\">\r\n      <div class=\"locked\">\r\n        <svg class=\"feather icon window-action\" id=\"lock-icon\">\r\n          <use href=\"#lock\"></use>\r\n        </svg>\r\n      </div>\r\n      <div class=\"unlocked\">\r\n        <svg\r\n          class=\"feather icon control-action\"\r\n          id=\"close-icon\"\r\n          data-msg=\"float_window_close\"\r\n        >\r\n          <use href=\"#x\"></use>\r\n        </svg>\r\n        <svg class=\"feather icon window-action\" id=\"unlock-icon\">\r\n          <use href=\"#unlock\"></use>\r\n        </svg>\r\n        <a\r\n          class=\"icon control-action group-first-icon\"\r\n          id=\"font-small-icon\"\r\n          data-msg=\"float_window_font_small\"\r\n        >\r\n          <svg class=\"feather\">\r\n            <use href=\"#type\"></use>\r\n          </svg>\r\n          <svg class=\"feather\" style=\"margin-left: -10px\">\r\n            <use href=\"#minus\"></use>\r\n          </svg>\r\n        </a>\r\n        <a\r\n          class=\"icon control-action\"\r\n          id=\"font-large-icon\"\r\n          data-msg=\"float_window_font_large\"\r\n        >\r\n          <svg class=\"feather\">\r\n            <use href=\"#type\"></use>\r\n          </svg>\r\n          <svg class=\"feather icon\" style=\"margin-left: -10px\">\r\n            <use href=\"#plus\"></use>\r\n          </svg>\r\n        </a>\r\n        <a\r\n          class=\"icon control-action group-first-icon\"\r\n          data-msg=\"float_window_background_dark\"\r\n        >\r\n          <svg class=\"feather\">\r\n            <use href=\"#sun\"></use>\r\n          </svg>\r\n          <svg class=\"feather icon\" style=\"margin-left: -10px\">\r\n            <use href=\"#minus\"></use>\r\n          </svg>\r\n        </a>\r\n        <a class=\"icon control-action\" data-msg=\"float_window_background_light\">\r\n          <svg class=\"feather\">\r\n            <use href=\"#sun\"></use>\r\n          </svg>\r\n          <svg class=\"feather icon\" style=\"margin-left: -10px\">\r\n            <use href=\"#plus\"></use>\r\n          </svg>\r\n        </a>\r\n        <a\r\n          class=\"icon control-action group-first-icon\"\r\n          id=\"font-large-icon\"\r\n          data-msg=\"float_window_font_change_color\"\r\n        >\r\n          <svg class=\"feather\">\r\n            <use href=\"#aperture\"></use>\r\n          </svg>\r\n        </a>\r\n      </div>\r\n    </div>\r\n    <script>\r\n      currentLyric = document.getElementById(\"currentLyric\");\r\n      window.api.onLyric((arg) => {\r\n        currentLyric.innerHTML = arg;\r\n      });\r\n      currentLyricTrans = document.getElementById(\"currentLyricTrans\");\r\n      window.api.onTranslLyric((arg) => {\r\n        currentLyricTrans.innerHTML = arg;\r\n      });\r\n    </script>\r\n    <script>\r\n      let IS_WINDOW_LOCKED = false;\r\n\r\n      function renderToolBar() {\r\n        if (IS_WINDOW_LOCKED) {\r\n          document.getElementsByClassName(\"locked\")[0].style.display = \"block\";\r\n          document.getElementsByClassName(\"unlocked\")[0].style.display = \"none\";\r\n          document.querySelector(\"body\").classList.remove(\"unlocked-body\");\r\n        } else {\r\n          document.getElementsByClassName(\"locked\")[0].style.display = \"none\";\r\n          document.getElementsByClassName(\"unlocked\")[0].style.display =\r\n            \"block\";\r\n          document.querySelector(\"body\").classList.add(\"unlocked-body\");\r\n        }\r\n      }\r\n\r\n      // enter lyric window zone, show/hide lock icon\r\n      document.getElementById(\"toolbar\").style.display = \"none\";\r\n\r\n      document.addEventListener(\"mouseenter\", (event) => {\r\n        document.getElementById(\"toolbar\").style.display = \"block\";\r\n      });\r\n      document.addEventListener(\"mouseleave\", (event) => {\r\n        document.getElementById(\"toolbar\").style.display = \"none\";\r\n      });\r\n\r\n      // enter lock icon zone, make window start to receive mouse event\r\n      document\r\n        .getElementById(\"lock-icon\")\r\n        .addEventListener(\"mouseenter\", (event) => {\r\n          if (!IS_WINDOW_LOCKED) {\r\n            return;\r\n          }\r\n          const message = \"float_window_accept_mouse_event\";\r\n          window.api.send(\"control\", message);\r\n        });\r\n\r\n      document\r\n        .getElementById(\"lock-icon\")\r\n        .addEventListener(\"mouseleave\", (event) => {\r\n          if (!IS_WINDOW_LOCKED) {\r\n            return;\r\n          }\r\n          const message = \"float_window_ignore_mouse_event\";\r\n          window.api.send(\"control\", message);\r\n        });\r\n\r\n      Array.from(document.getElementsByClassName(\"window-action\")).forEach(\r\n        (elem) => {\r\n          elem.addEventListener(\r\n            \"click\",\r\n            function () {\r\n              // toggle floating window lock/unlock status\r\n              IS_WINDOW_LOCKED = !IS_WINDOW_LOCKED;\r\n              renderToolBar();\r\n              const message = IS_WINDOW_LOCKED\r\n                ? \"float_window_ignore_mouse_event\"\r\n                : \"float_window_accept_mouse_event\";\r\n              window.api.send(\"control\", message);\r\n            },\r\n            false\r\n          );\r\n        }\r\n      );\r\n\r\n      Array.from(document.getElementsByClassName(\"control-action\")).forEach(\r\n        (elem) => {\r\n          elem.addEventListener(\"click\", (event) => {\r\n            const message = event.currentTarget.dataset.msg;\r\n            window.api.send(\"control\", message);\r\n          });\r\n        }\r\n      );\r\n      // drag can't use webkit-app-region becse mouse enter will not trigger\r\n      // we use custom handle, more details please read link below\r\n      // https://github.com/electron/electron/issues/1354#issuecomment-404348957\r\n      let animationId;\r\n      let mouseX;\r\n      let mouseY;\r\n\r\n      function onMouseDown(e) {\r\n        mouseX = e.clientX;\r\n        mouseY = e.clientY;\r\n\r\n        document.addEventListener(\"mouseup\", onMouseUp);\r\n        requestAnimationFrame(this.moveWindow);\r\n      }\r\n\r\n      function onMouseUp(e) {\r\n        document.removeEventListener(\"mouseup\", onMouseUp);\r\n        cancelAnimationFrame(animationId);\r\n      }\r\n\r\n      function moveWindow() {\r\n        window.api.send(\"floatWindowMoving\", {\r\n          mouseX,\r\n          mouseY,\r\n        });\r\n        animationId = requestAnimationFrame(moveWindow);\r\n      }\r\n\r\n      document\r\n        .getElementById(\"currentLyricAll\")\r\n        .addEventListener(\"mousedown\", (event) => {\r\n          onMouseDown(event);\r\n        });\r\n\r\n      renderToolBar();\r\n    </script>\r\n  </body>\r\n</html>\r\n"
  },
  {
    "path": "app/functions.js",
    "content": "const { existsSync } = require(\"fs\");\nconst { readFile } = require(\"fs/promises\");\nconst { parseFile } = require(\"music-metadata\");\nconst { basename, extname, parse, format } = require(\"path\");\nconst { detect } = require(\"chardet\");\nmodule.exports = {\n  async readAudioTags(filePath) {\n    const fileName = basename(filePath, extname(filePath));\n    try {\n      const metaData = await parseFile(filePath);\n      metaData.common.title ||= fileName;\n      const lyric_url = format({\n        ...parse(filePath),\n        ext: \".lrc\",\n        base: undefined,\n      });\n      //if metadata doesn't include lyric, then try to read from local lyric file\n      if (!metaData.common.lyrics && existsSync(lyric_url)) {\n        metaData.common.lyrics = [];\n        const fileBuffer = await readFile(lyric_url);\n        const encoding = detect(fileBuffer);\n        const decoder = new TextDecoder(encoding);\n        metaData.common.lyrics[0] = decoder.decode(fileBuffer);\n      }\n      return metaData;\n    } catch (error) {\n      return {\n        error,\n        common: {\n          title: fileName,\n          album: \"\",\n          artist: \"\",\n        },\n      };\n    }\n  },\n};\n"
  },
  {
    "path": "app/main.js",
    "content": "const electron = require(\"electron\");\r\nconst {\r\n  app,\r\n  BrowserWindow,\r\n  globalShortcut,\r\n  ipcMain,\r\n  Menu,\r\n  session,\r\n  screen,\r\n  Tray,\r\n} = electron;\r\nconst Store = require(\"electron-store\");\r\nconst { autoUpdater } = require(\"electron-updater\");\r\nconst remoteMain = require(\"@electron/remote/main\");\r\nconst { join } = require(\"path\");\r\n\r\nconst store = new Store();\r\nconst iconPath = join(__dirname, \"/listen1_chrome_extension/images/logo.png\");\r\n\r\nautoUpdater.checkForUpdatesAndNotify();\r\n\r\nlet floatingWindowCssKey = undefined,\r\n  appIcon = null,\r\n  willQuitApp = false,\r\n  transparent = false,\r\n  trayIconPath;\r\n/** @type {electron.BrowserWindow} */\r\nlet mainWindow;\r\n/** @type {electron.BrowserWindow} */\r\nlet floatingWindow;\r\n/** @type {electron.Tray} */\r\nlet appTray;\r\n//platform-specific\r\nswitch (process.platform) {\r\n  case \"darwin\":\r\n    trayIconPath = join(__dirname, \"/resources/logo_16.png\");\r\n    transparent = true;\r\n    break;\r\n  case \"linux\":\r\n    trayIconPath = join(__dirname, \"/resources/logo_32.png\");\r\n    // fix transparent window not working in linux bug\r\n    app.disableHardwareAcceleration();\r\n    break;\r\n  case \"win32\":\r\n    trayIconPath = join(__dirname, \"/resources/logo_32.png\");\r\n    break;\r\n  default:\r\n    break;\r\n}\r\n// Keep a global reference of the window object, if you don't, the window will\r\n// be closed automatically when the JavaScript object is garbage collected.\r\n/** @type {{ width: number; height: number; maximized: boolean; zoomLevel: number}} */\r\nconst windowState = store.get(\"windowState\") || {\r\n  width: 1000,\r\n  height: 670,\r\n  maximized: false,\r\n  zoomLevel: 0,\r\n};\r\n/** @type {electron.Config} */\r\nlet proxyConfig = store.get(\"proxyConfig\") || {\r\n  mode: \"system\",\r\n};\r\n\r\nconst globalShortcutMapping = {\r\n  \"CmdOrCtrl+Alt+Left\": \"left\",\r\n  \"CmdOrCtrl+Alt+Right\": \"right\",\r\n  \"CmdOrCtrl+Alt+Space\": \"space\",\r\n  MediaNextTrack: \"right\",\r\n  MediaPreviousTrack: \"left\",\r\n  MediaPlayPause: \"space\",\r\n};\r\n/**\r\n * @param {electron.BrowserWindow} mainWindow\r\n * @param {{ title: string; artist: string; }} [track]\r\n */\r\nfunction initialTray(mainWindow, track) {\r\n  track ||= {\r\n    title: \"暂无歌曲\",\r\n    artist: \"  \",\r\n  };\r\n\r\n  let nowPlayingTitle = `${track.title}`;\r\n  let nowPlayingArtist = `歌手: ${track.artist}`;\r\n\r\n  function toggleVisiable() {\r\n    mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();\r\n  }\r\n  const menuTemplate = [\r\n    {\r\n      label: nowPlayingTitle,\r\n      click() {\r\n        mainWindow.show();\r\n      },\r\n    },\r\n    {\r\n      label: nowPlayingArtist,\r\n      click() {\r\n        mainWindow.show();\r\n      },\r\n    },\r\n    { type: \"separator\" },\r\n    {\r\n      label: \"播放/暂停\",\r\n      click() {\r\n        mainWindow.webContents.send(\"globalShortcut\", \"space\");\r\n      },\r\n    },\r\n    {\r\n      label: \"上一首\",\r\n      click() {\r\n        mainWindow.webContents.send(\"globalShortcut\", \"left\");\r\n      },\r\n    },\r\n    {\r\n      label: \"下一首\",\r\n      click() {\r\n        mainWindow.webContents.send(\"globalShortcut\", \"right\");\r\n      },\r\n    },\r\n    {\r\n      label: \"显示/隐藏窗口\",\r\n      click() {\r\n        toggleVisiable();\r\n      },\r\n    },\r\n    {\r\n      label: \"退出\",\r\n      click() {\r\n        app.quit();\r\n      },\r\n    },\r\n  ];\r\n\r\n  const contextMenu = Menu.buildFromTemplate(menuTemplate);\r\n\r\n  if (appTray?.destroy != undefined) {\r\n    // appTray had create, just refresh tray menu here\r\n    appTray?.setContextMenu(contextMenu);\r\n    return;\r\n  }\r\n\r\n  appTray = new Tray(trayIconPath);\r\n  appTray.setContextMenu(contextMenu);\r\n  appTray.on(\"click\", () => {\r\n    toggleVisiable();\r\n  });\r\n}\r\n\r\n/**\r\n * @param {string | electron.Accelerator} key\r\n * @param {string} message\r\n */\r\nfunction setKeyMapping(key, message) {\r\n  globalShortcut.register(key, () => {\r\n    mainWindow.webContents.send(\"globalShortcut\", message);\r\n  });\r\n}\r\n\r\nfunction enableGlobalShortcuts() {\r\n  // initial global shortcuts\r\n  for (const [key, value] of Object.entries(globalShortcutMapping)) {\r\n    setKeyMapping(key, value);\r\n  }\r\n}\r\n\r\nfunction disableGlobalShortcuts() {\r\n  globalShortcut.unregisterAll();\r\n}\r\n/**\r\n * @param {string} cssStyle\r\n */\r\nasync function updateFloatingWindow(cssStyle) {\r\n  if (cssStyle === undefined) {\r\n    return;\r\n  }\r\n  try {\r\n    const newCssKey = await floatingWindow.webContents.insertCSS(cssStyle, {\r\n      cssOrigin: \"author\",\r\n    });\r\n    if (floatingWindowCssKey !== undefined) {\r\n      await floatingWindow.webContents.removeInsertedCSS(floatingWindowCssKey);\r\n    }\r\n    floatingWindowCssKey = newCssKey;\r\n  } catch (err) {\r\n    console.log(err);\r\n  }\r\n}\r\n/**\r\n * @param {electron.Config} params\r\n */\r\nasync function updateProxyConfig(params) {\r\n  proxyConfig = params;\r\n\r\n  await mainWindow.webContents.session.setProxy(params);\r\n  await mainWindow.webContents.session.forceReloadProxyConfig();\r\n}\r\n/**\r\n * @param {string} cssStyle\r\n */\r\nfunction createFloatingWindow(cssStyle) {\r\n  const display = screen.getPrimaryDisplay();\r\n  if (process.platform === \"linux\") {\r\n    // fix transparent window not working in linux bug\r\n    floatingWindow?.destroy();\r\n    floatingWindow = null;\r\n  }\r\n  if (!floatingWindow) {\r\n    /** @type {Electron.Rectangle} */\r\n    const winBounds = store.get(\"floatingWindowBounds\");\r\n\r\n    floatingWindow = new BrowserWindow({\r\n      width: 1000,\r\n      minWidth: 640,\r\n      maxWidth: 1920,\r\n      height: 70,\r\n      titleBarStyle: \"hidden\",\r\n      transparent: true,\r\n      frame: false,\r\n      resizable: true,\r\n      hasShadow: false,\r\n      alwaysOnTop: true,\r\n      webPreferences: {\r\n        sandbox: true,\r\n        preload: join(__dirname, \"preload.js\"),\r\n      },\r\n      ...winBounds,\r\n    });\r\n\r\n    if (winBounds === undefined) {\r\n      floatingWindow.setPosition(\r\n        floatingWindow.getPosition()[0],\r\n        display.bounds.height - 150\r\n      );\r\n    }\r\n    floatingWindow.setVisibleOnAllWorkspaces(true);\r\n    floatingWindow.setSkipTaskbar(true);\r\n    floatingWindow.loadURL(`file://${__dirname}/floatingWindow.html`);\r\n    floatingWindow.setAlwaysOnTop(true, \"floating\");\r\n    floatingWindow.setIgnoreMouseEvents(false);\r\n    // NOTICE: setResizable should be set, otherwise mouseleave event won't trigger in windows environment\r\n    floatingWindow.webContents.on(\"did-finish-load\", async () => {\r\n      await updateFloatingWindow(cssStyle);\r\n    });\r\n    floatingWindow.on(\"closed\", () => {\r\n      floatingWindow = null;\r\n    });\r\n\r\n    // floatingWindow.webContents.openDevTools();\r\n  }\r\n  floatingWindow.showInactive();\r\n}\r\n\r\nconst previousButton = {\r\n  tooltip: \"Previous\",\r\n  icon: join(__dirname, \"/resources/prev-song.png\"),\r\n  click() {\r\n    mainWindow.webContents.send(\"globalShortcut\", \"left\");\r\n  },\r\n};\r\nconst nextButton = {\r\n  tooltip: \"Next\",\r\n  icon: join(__dirname, \"/resources/next-song.png\"),\r\n  click() {\r\n    mainWindow.webContents.send(\"globalShortcut\", \"right\");\r\n  },\r\n};\r\nconst playButton = {\r\n  tooltip: \"Play\",\r\n  icon: join(__dirname, \"/resources/play-song.png\"),\r\n  click() {\r\n    mainWindow.webContents.send(\"globalShortcut\", \"space\");\r\n  },\r\n};\r\nconst pauseButton = {\r\n  tooltip: \"Pause\",\r\n  icon: join(__dirname, \"/resources/pause-song.png\"),\r\n  click() {\r\n    mainWindow.webContents.send(\"globalShortcut\", \"space\");\r\n  },\r\n};\r\nconst setThumbarPause = () => {\r\n  mainWindow?.setThumbarButtons([previousButton, playButton, nextButton]);\r\n};\r\nconst setThumbbarPlay = () => {\r\n  mainWindow?.setThumbarButtons([previousButton, pauseButton, nextButton]);\r\n};\r\n\r\nfunction createWindow() {\r\n  const filter = {\r\n    urls: [\r\n      \"*://*.music.163.com/*\",\r\n      \"*://music.163.com/*\",\r\n      \"*://*.xiami.com/*\",\r\n      \"*://i.y.qq.com/*\",\r\n      \"*://c.y.qq.com/*\",\r\n      \"*://*.kugou.com/*\",\r\n      \"*://*.kuwo.cn/*\",\r\n      \"*://*.bilibili.com/*\",\r\n      \"*://*.bilivideo.com/*\",\r\n      \"*://*.bilivideo.cn/*\",\r\n      \"*://*.migu.cn/*\",\r\n      \"*://*.githubusercontent.com/*\",\r\n      \"https://listen1.github.io/listen1/callback.html?code=*\",\r\n    ],\r\n  };\r\n\r\n  session.defaultSession.webRequest.onBeforeSendHeaders(\r\n    filter,\r\n    (details, callback) => {\r\n      if (\r\n        details.url.startsWith(\r\n          \"https://listen1.github.io/listen1/callback.html?code=\"\r\n        )\r\n      ) {\r\n        const { url } = details;\r\n        const code = url.split(\"=\")[1];\r\n        mainWindow.webContents.executeJavaScript(\r\n          'GithubClient.github.handleCallback(\"' + code + '\");'\r\n        );\r\n      } else {\r\n        hack_referer_header(details);\r\n      }\r\n      callback({ cancel: false, requestHeaders: details.requestHeaders });\r\n    }\r\n  );\r\n  // Create the browser window.\r\n  mainWindow = new BrowserWindow({\r\n    width: windowState.width,\r\n    height: windowState.height,\r\n    minHeight: 300,\r\n    minWidth: 600,\r\n    webPreferences: {\r\n      nodeIntegration: true,\r\n      enableRemoteModule: true,\r\n      contextIsolation: false,\r\n    },\r\n    icon: iconPath,\r\n    titleBarStyle: \"hiddenInset\",\r\n    transparent: transparent,\r\n    vibrancy: \"light\",\r\n    frame: false,\r\n    hasShadow: true,\r\n  });\r\n\r\n  mainWindow.on(\"ready-to-show\", () => {\r\n    if (windowState.maximized) {\r\n      mainWindow.maximize();\r\n    }\r\n    mainWindow.webContents.send(\"setZoomLevel\", windowState.zoomLevel);\r\n  });\r\n\r\n  mainWindow.on(\"resized\", () => {\r\n    if (!mainWindow.isMaximized() && !mainWindow.isFullScreen()) {\r\n      const [width, height] = mainWindow.getSize();\r\n      windowState.width = width;\r\n      windowState.height = height;\r\n    }\r\n  });\r\n  mainWindow.on(\"close\", (e) => {\r\n    if (willQuitApp) {\r\n      /* the user tried to quit the app */\r\n      mainWindow = null;\r\n    } else {\r\n      /* the user only tried to close the window */\r\n      //if (process.platform != 'linux') {\r\n      e.preventDefault();\r\n      mainWindow.hide();\r\n      //mainWindow.minimize();\r\n      //}\r\n    }\r\n  });\r\n\r\n  // and load the index.html of the app.\r\n  const ua =\r\n    \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36\";\r\n\r\n  mainWindow.webContents.session.setProxy(proxyConfig).then(() => {\r\n    mainWindow.loadURL(\r\n      `file://${__dirname}/listen1_chrome_extension/listen1.html`,\r\n      { userAgent: ua }\r\n    );\r\n  });\r\n\r\n  setThumbarPause();\r\n  // Emitted when the window is closed.\r\n  mainWindow.on(\"closed\", () => {\r\n    // Dereference the window object, usually you would store windows\r\n    // in an array if your app supports multi windows, this is the time\r\n    // when you should delete the corresponding element.\r\n    mainWindow = null;\r\n  });\r\n\r\n  // define global menu content, also add support for cmd+c and cmd+v shortcuts\r\n  const template = [\r\n    {\r\n      label: \"Application\",\r\n      submenu: [\r\n        {\r\n          label: \"Zoom Out\",\r\n          accelerator: \"CmdOrCtrl+=\",\r\n          click() {\r\n            if (windowState.zoomLevel <= 2.5) {\r\n              windowState.zoomLevel += 0.5;\r\n              mainWindow.webContents.send(\r\n                \"setZoomLevel\",\r\n                windowState.zoomLevel\r\n              );\r\n            }\r\n          },\r\n        },\r\n        {\r\n          label: \"Zoom in\",\r\n          accelerator: \"CmdOrCtrl+-\",\r\n          click() {\r\n            if (windowState.zoomLevel >= -1) {\r\n              windowState.zoomLevel -= 0.5;\r\n              mainWindow.webContents.send(\r\n                \"setZoomLevel\",\r\n                windowState.zoomLevel\r\n              );\r\n            }\r\n          },\r\n        },\r\n        {\r\n          label: \"Toggle Developer Tools\",\r\n          accelerator: \"F12\",\r\n          click() {\r\n            mainWindow.webContents.toggleDevTools();\r\n          },\r\n        },\r\n        {\r\n          label: \"About Application\",\r\n          selector: \"orderFrontStandardAboutPanel:\",\r\n        },\r\n        { type: \"separator\" },\r\n        {\r\n          label: \"Close Window\",\r\n          accelerator: \"CmdOrCtrl+W\",\r\n          click() {\r\n            mainWindow.close();\r\n          },\r\n        },\r\n        {\r\n          label: \"Quit\",\r\n          accelerator: \"Command+Q\",\r\n          click() {\r\n            app.quit();\r\n          },\r\n        },\r\n      ],\r\n    },\r\n    {\r\n      label: \"Edit\",\r\n      submenu: [\r\n        { label: \"Undo\", accelerator: \"CmdOrCtrl+Z\", selector: \"undo:\" },\r\n        { label: \"Redo\", accelerator: \"Shift+CmdOrCtrl+Z\", selector: \"redo:\" },\r\n        { type: \"separator\" },\r\n        { label: \"Cut\", accelerator: \"CmdOrCtrl+X\", selector: \"cut:\" },\r\n        { label: \"Copy\", accelerator: \"CmdOrCtrl+C\", selector: \"copy:\" },\r\n        { label: \"Paste\", accelerator: \"CmdOrCtrl+V\", selector: \"paste:\" },\r\n        {\r\n          label: \"Select All\",\r\n          accelerator: \"CmdOrCtrl+A\",\r\n          selector: \"selectAll:\",\r\n        },\r\n      ],\r\n    },\r\n  ];\r\n\r\n  mainWindow.setMenu(null);\r\n\r\n  Menu.setApplicationMenu(Menu.buildFromTemplate(template));\r\n\r\n  initialTray(mainWindow);\r\n}\r\n\r\nconst MOBILE_UA =\r\n  \"Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30\";\r\n\r\n/**\r\n * @param {electron.OnBeforeSendHeadersListenerDetails} details\r\n */\r\nfunction hack_referer_header(details) {\r\n  let replace_referer = true;\r\n  let replace_origin = true;\r\n  let add_referer = true;\r\n  let add_origin = true;\r\n  let referer_value = \"\";\r\n  let origin_value = \"\";\r\n  let ua_value = \"\";\r\n\r\n  if (details.url.includes(\"://music.163.com/\")) {\r\n    referer_value = \"http://music.163.com/\";\r\n  }\r\n  if (details.url.includes(\"://interface3.music.163.com/\")) {\r\n    referer_value = \"http://music.163.com/\";\r\n  }\r\n  if (details.url.includes(\"://gist.githubusercontent.com/\")) {\r\n    referer_value = \"https://gist.githubusercontent.com/\";\r\n  }\r\n\r\n  if (details.url.includes(\".xiami.com/\")) {\r\n    add_origin = false;\r\n    referer_value = \"https://www.xiami.com/\";\r\n  }\r\n  if (details.url.includes(\"www.xiami.com/api/search/searchSongs\")) {\r\n    const key = /key%22:%22(.*?)%22/.exec(details.url)[1];\r\n    add_origin = false;\r\n    referer_value = `https://www.xiami.com/search?key=${key}`;\r\n  }\r\n  if (details.url.includes(\"c.y.qq.com/\")) {\r\n    referer_value = \"https://y.qq.com/\";\r\n    origin_value = \"https://y.qq.com\";\r\n  }\r\n  if (\r\n    details.url.includes(\"y.qq.com/\") ||\r\n    details.url.includes(\"qqmusic.qq.com/\") ||\r\n    details.url.includes(\"music.qq.com/\") ||\r\n    details.url.includes(\"imgcache.qq.com/\")\r\n  ) {\r\n    referer_value = \"http://y.qq.com/\";\r\n  }\r\n  if (details.url.includes(\".kugou.com/\")) {\r\n    referer_value = \"https://www.kugou.com/\";\r\n    ua_value = MOBILE_UA;\r\n  }\r\n  if (details.url.includes(\"m.kugou.com/\")) {\r\n    ua_value = MOBILE_UA;\r\n  }\r\n  if (details.url.includes(\".kuwo.cn/\")) {\r\n    referer_value = \"http://www.kuwo.cn/\";\r\n  }\r\n  if (\r\n    details.url.includes(\".bilibili.com/\") ||\r\n    details.url.includes(\".bilivideo.com/\")\r\n  ) {\r\n    referer_value = \"https://www.bilibili.com/\";\r\n    replace_origin = false;\r\n    add_origin = false;\r\n  }\r\n  if (details.url.includes('.bilivideo.cn')) {\r\n    referer_value = 'https://www.bilibili.com/';\r\n    origin_value = 'https://www.bilibili.com/';\r\n    add_referer = true;\r\n    add_origin = true;\r\n  }\r\n  if (details.url.includes(\".migu.cn\")) {\r\n    referer_value = \"http://music.migu.cn/v3/music/player/audio?from=migu\";\r\n  }\r\n  if (details.url.includes(\"m.music.migu.cn\")) {\r\n    referer_value = \"https://m.music.migu.cn/\";\r\n  }\r\n  if (origin_value == \"\") {\r\n    origin_value = referer_value;\r\n  }\r\n  let isRefererSet = false;\r\n  let isOriginSet = false;\r\n  let isUASet = false;\r\n  let headers = details.requestHeaders;\r\n\r\n  for (let i = 0, l = headers.length; i < l; ++i) {\r\n    if (\r\n      replace_referer &&\r\n      headers[i].name == \"Referer\" &&\r\n      referer_value != \"\"\r\n    ) {\r\n      headers[i].value = referer_value;\r\n      isRefererSet = true;\r\n    }\r\n    if (replace_origin && headers[i].name == \"Origin\" && referer_value != \"\") {\r\n      headers[i].value = origin_value;\r\n      isOriginSet = true;\r\n    }\r\n    if (headers[i].name === \"User-Agent\" && ua_value !== \"\") {\r\n      headers[i].value = ua_value;\r\n      isUASet = true;\r\n    }\r\n  }\r\n\r\n  if (add_referer && !isRefererSet && referer_value != \"\") {\r\n    headers[\"Referer\"] = referer_value;\r\n  }\r\n\r\n  if (add_origin && !isOriginSet && referer_value != \"\") {\r\n    headers[\"Origin\"] = origin_value;\r\n  }\r\n\r\n  if (!isUASet && ua_value !== \"\") {\r\n    headers[\"User-Agent\"] = ua_value;\r\n  }\r\n\r\n  details.requestHeaders = headers;\r\n}\r\n\r\nipcMain.on(\"currentLyric\", (event, arg) => {\r\n  if (floatingWindow && floatingWindow !== null) {\r\n    if (typeof arg === \"string\") {\r\n      floatingWindow.webContents.send(\"currentLyric\", arg);\r\n      floatingWindow.webContents.send(\"currentLyricTrans\", \"\");\r\n    } else {\r\n      floatingWindow.webContents.send(\"currentLyric\", arg.lyric);\r\n      floatingWindow.webContents.send(\"currentLyricTrans\", arg.tlyric);\r\n    }\r\n  }\r\n});\r\n\r\nipcMain.on(\"trackPlayingNow\", (event, track) => {\r\n  if (mainWindow != null) {\r\n    initialTray(mainWindow, track);\r\n  }\r\n});\r\n\r\nipcMain.on(\"isPlaying\", (event, isPlaying) => {\r\n  isPlaying ? setThumbbarPlay() : setThumbarPause();\r\n});\r\n\r\nipcMain.on(\"control\", async (event, arg, params) => {\r\n  switch (arg) {\r\n    case \"enable_global_shortcut\":\r\n      enableGlobalShortcuts();\r\n      break;\r\n\r\n    case \"disable_global_shortcut\":\r\n      disableGlobalShortcuts();\r\n      break;\r\n\r\n    case \"enable_lyric_floating_window\":\r\n      createFloatingWindow(params);\r\n      break;\r\n\r\n    case \"disable_lyric_floating_window\":\r\n      floatingWindow?.hide();\r\n      break;\r\n\r\n    case \"window_min\":\r\n      mainWindow.minimize();\r\n      break;\r\n\r\n    case \"window_max\":\r\n      windowState.maximized ? mainWindow.unmaximize() : mainWindow.maximize();\r\n      windowState.maximized = !windowState.maximized;\r\n      break;\r\n\r\n    case \"window_close\":\r\n      mainWindow.close();\r\n      break;\r\n\r\n    case \"float_window_accept_mouse_event\":\r\n      floatingWindow.setIgnoreMouseEvents(false);\r\n      break;\r\n\r\n    case \"float_window_ignore_mouse_event\":\r\n      floatingWindow.setIgnoreMouseEvents(true, { forward: true });\r\n      break;\r\n\r\n    case \"float_window_close\":\r\n    case \"float_window_font_small\":\r\n    case \"float_window_font_large\":\r\n    case \"float_window_background_light\":\r\n    case \"float_window_background_dark\":\r\n    case \"float_window_font_change_color\":\r\n      mainWindow.webContents.send(\"lyricWindow\", arg);\r\n      break;\r\n\r\n    case \"update_lyric_floating_window_css\":\r\n      await updateFloatingWindow(params);\r\n      break;\r\n\r\n    case \"get_proxy_config\":\r\n      mainWindow.webContents.send(\"proxyConfig\", proxyConfig);\r\n      break;\r\n\r\n    case \"update_proxy_config\":\r\n      await updateProxyConfig(params);\r\n      break;\r\n\r\n    default:\r\n      break;\r\n  }\r\n  // event.sender.send('asynchronous-reply', 'pong')\r\n});\r\n\r\nipcMain.on(\"openUrl\", (event, arg, params) => {\r\n  const bWindow = new BrowserWindow({\r\n    parent: mainWindow,\r\n    height: 700,\r\n    resizable: true,\r\n    width: 985,\r\n    frame: true,\r\n    fullscreen: false,\r\n    maximizable: true,\r\n    minimizable: true,\r\n    autoHideMenuBar: true,\r\n    webPreferences: {\r\n      // sandbox is necessary for website js to work\r\n      // thanks to https://github.com/sunzongzheng/music\r\n      sandbox: true,\r\n    },\r\n  });\r\n  bWindow.loadURL(arg);\r\n  bWindow.setMenu(null);\r\n});\r\n\r\nipcMain.on(\"floatWindowMoving\", (e, { mouseX, mouseY }) => {\r\n  const { x, y } = screen.getCursorScreenPoint();\r\n  floatingWindow?.setPosition(x - mouseX, y - mouseY);\r\n});\r\n\r\nconst gotTheLock = app.requestSingleInstanceLock();\r\n\r\nif (!gotTheLock) {\r\n  app.quit();\r\n} else {\r\n  app.on(\"second-instance\", (event, commandLine, workingDirectory) => {\r\n    // Someone tried to run a second instance, we should focus our window.\r\n    if (mainWindow) {\r\n      if (mainWindow.isMinimized()) mainWindow.restore();\r\n      mainWindow.focus();\r\n      // When start a new instance, show the main window and active in taskbar.\r\n      mainWindow.show();\r\n      mainWindow.setSkipTaskbar(false);\r\n    }\r\n  });\r\n\r\n  // Create myWindow, load the rest of the app, etc...\r\n  app.on(\"ready\", () => {\r\n    createWindow();\r\n    remoteMain.initialize();\r\n    remoteMain.enable(mainWindow.webContents);\r\n  });\r\n}\r\n\r\n// Quit when all windows are closed.\r\napp.on(\"window-all-closed\", () => {\r\n  // On OS X it is common for applications and their menu bar\r\n  // to stay active until the user quits explicitly with Cmd + Q\r\n  if (process.platform !== \"darwin\") {\r\n    app.quit();\r\n  }\r\n});\r\n\r\n/* 'activate' is emitted when the user clicks the Dock icon (OS X) */\r\napp.on(\"activate\", () => mainWindow.show());\r\n\r\n/* 'before-quit' is emitted when Electron receives\r\n * the signal to exit and wants to start closing windows */\r\napp.on(\"before-quit\", () => {\r\n  if (mainWindow.webContents.isDevToolsOpened()) {\r\n    mainWindow.webContents.closeDevTools();\r\n  }\r\n  if (floatingWindow) {\r\n    store.set(\"floatingWindowBounds\", floatingWindow.getBounds());\r\n  }\r\n  store.set(\"windowState\", windowState);\r\n  store.set(\"proxyConfig\", proxyConfig);\r\n\r\n  willQuitApp = true;\r\n});\r\n\r\napp.on(\"will-quit\", () => {\r\n  disableGlobalShortcuts();\r\n});\r\n"
  },
  {
    "path": "app/package.json",
    "content": "{\n  \"name\": \"listen1\",\n  \"version\": \"2.33.0\",\n  \"description\": \"One for all free music in China\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"start\": \"electron main.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/listen1/listen1_desktop.git\"\n  },\n  \"keywords\": [\n    \"Electron\",\n    \"Listen 1\",\n    \"music player\"\n  ],\n  \"author\": \"Listen 1 <githublisten1@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/listen1/listen1_desktop/issues\"\n  },\n  \"homepage\": \"https://github.com/listen1/listen1_desktop#readme\",\n  \"dependencies\": {\n    \"@electron/remote\": \"^2.1.2\",\n    \"chardet\": \"^1.4.0\",\n    \"electron-store\": \"^8.0.1\",\n    \"electron-updater\": \"^5.0.1\",\n    \"music-metadata\": \"^7.12.3\"\n  }\n}\n"
  },
  {
    "path": "app/preload.js",
    "content": "const { contextBridge, ipcRenderer } = require(\"electron\");\n\ncontextBridge.exposeInMainWorld(\"api\", {\n  ipcRenderer: ipcRenderer,\n  send: (channel, data) => {\n    ipcRenderer.send(channel, data);\n  },\n  onLyric: (fn) => {\n    // Deliberately strip event as it includes `sender`\n    ipcRenderer.on(\"currentLyric\", (event, ...args) => fn(...args));\n  },\n  onTranslLyric: (fn) => {\n    // Deliberately strip event as it includes `sender`\n    ipcRenderer.on(\"currentLyricTrans\", (event, ...args) => fn(...args));\n  },\n});\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"listen1\",\n  \"version\": \"2.33.0\",\n  \"description\": \"One for all free music in China\",\n  \"main\": \"app/main.js\",\n  \"scripts\": {\n    \"postinstall\": \"electron-builder install-app-deps\",\n    \"start\": \"electron ./app --enable-logging\",\n    \"dev\": \"NODE_ENV='development' npm run start\",\n    \"dist:mac\": \"CSC_IDENTITY_AUTO_DISCOVERY=false DEBUG=electron-builder electron-builder --mac\",\n    \"dist\": \"electron-builder .\",\n    \"dist:linux\": \"electron-builder --linux --ia32 --x64\",\n    \"dist:linux32\": \"electron-builder --linux --ia32\",\n    \"dist:linux64\": \"electron-builder --linux --x64\",\n    \"dist:linuxArm64\": \"electron-builder --linux --arm64\",\n    \"dist:linuxArmv7l\": \"electron-builder --linux --armv7l\",\n    \"dist:win\": \"electron-builder --win\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/listen1/listen1_desktop.git\"\n  },\n  \"keywords\": [\n    \"Electron\",\n    \"Listen 1\"\n  ],\n  \"author\": \"Listen 1 <githublisten1@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/listen1/listen1_desktop/issues\"\n  },\n  \"homepage\": \"https://github.com/listen1/listen1_desktop#readme\",\n  \"build\": {\n    \"appId\": \"com.listen1.listen1\",\n    \"productName\": \"Listen1\",\n    \"asar\": true,\n    \"artifactName\": \"${name}_${version}_${os}_${arch}.${ext}\",\n    \"dmg\": {\n      \"icon\": \"build/disk.icns\",\n      \"contents\": [\n        {\n          \"x\": 192,\n          \"y\": 344\n        },\n        {\n          \"x\": 448,\n          \"y\": 344,\n          \"type\": \"link\",\n          \"path\": \"/Applications\"\n        }\n      ]\n    },\n    \"mac\": {\n      \"target\": [\n        {\n          \"target\": \"dmg\",\n          \"arch\": [\n            \"x64\",\n            \"arm64\",\n            \"universal\"\n          ]\n        }\n      ],\n      \"category\": \"public.app-category.music\"\n    },\n    \"linux\": {\n      \"target\": [\n        \"tar.gz\",\n        \"appImage\",\n        \"deb\"\n      ],\n      \"category\": \"Audio\"\n    },\n    \"nsis\": {\n      \"runAfterFinish\": false,\n      \"deleteAppDataOnUninstall\": true,\n      \"allowToChangeInstallationDirectory\": true,\n      \"oneClick\": false,\n      \"installerLanguages\": \"zh_CN\",\n      \"language\": 2052,\n      \"perMachine\": true,\n      \"createDesktopShortcut\": true\n    },\n    \"win\": {\n      \"target\": [\n        {\n          \"target\": \"nsis\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\",\n            \"arm64\"\n          ]\n        },\n        {\n          \"target\": \"7z\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\",\n            \"arm64\"\n          ]\n        }\n      ],\n      \"icon\": \"build/icon.ico\"\n    }\n  },\n  \"devDependencies\": {\n    \"electron\": \"^32.3.2\",\n    \"electron-builder\": \"^25.1.8\",\n    \"prettier\": \"^2.6.2\"\n  }\n}\n"
  }
]