[
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": \"airbnb/base\",\n  \"rules\": {\n      \"strict\": \"off\",\n      \"max-len\": \"off\",\n      \"prefer-template\": \"warn\",\n      \"arrow-body-style\": \"off\",\n      \"no-unused-vars\": \"warn\",\n      \"no-undef\": \"off\",\n      \"array-callback-return\": \"off\",\n      \"no-confusing-arrow\": \"off\",\n      \"consistent-return\": \"warn\",\n      \"no-param-reassign\": \"off\",\n      \"default-case\": \"off\",\n      \"guard-for-in\": \"off\",\n      \"no-restricted-syntax\": \"off\",\n      \"no-underscore-dangle\": \"off\",\n      \"new-cap\": \"warn\",\n      \"no-console\": \"off\",\n      \"global-require\": \"off\",\n      \"class-methods-use-this\": \"warn\"\n    },\n}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n/dist\nnpm-debug.log*\n.idea\n\n# Dependency directories\nnode_modules\n\n*.sublime-project\n*.sublime-workspace\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nsudo: required\nnode_js:\n- '5.2'\nbranches:\n  only:\n  - master\n  - production\nbefore_script:\n- npm install\nscript:\n- ./scripts/build-all.sh\n- ./scripts/tar-all.sh\ndeploy:\n  provider: releases\n  api_key:\n    secure: ETudcaMBembv5mq5WcA0Zu5YCQt02A8sfMIYJ+XN0dTUCFRODYgyk8SiW3ndI4zLfhsc31KbYecSVfcrvYhPlkLucdhD0hY+v4mowrGaG6q3DUE4v9+qATOE5z51MPNTQO/suPNZpeFkSCKaWh6SY9oSd/tsD+YmbcpuD0//DMiFMpYqA8ueQ7yka4SmlZq8C48MsRbULAtyHNEVNJ4en9xdE9vFHZ45kM2A2IWYVikuCa5J6YoL7N2CyIFwtKMeF68d0vwidXUXEc7z1VOHwosG7V0vEfNRrIy4mft0tXyEYe/nM8GlYnirVRCy3xF4h4ssERXbLMuZSYGm+bg/pqReL+dvsN5oKszuo7IseZnE8QfmmhfbMB4dWf8Le5WXfFgJTG28lNvl2VwTTEW4Cj5qeJmfO524GydqRE+i3uQvW4c2tBTFmfpusPnaFqVXTPH7o54hT18hYvgaBvJQv6pyMNMLLXq0BbkzquTTWTwb8lSi8XiRr/fWkQreRZNofJc21ZUSI5YcuqZpzbz1fOLseC4QJ8YXQ9b2OU/LiFF3gvHTK6vSKMQmbOFg0zFXMi5FT1SzCi/mKduax/OR/H6lolVW83eXCG1Ni+sIrwUkp0d/UL6E1pVeJMibBrOEgriWIpD+AiVzNVyBdq/oDC6qG9IXRWzii9Ks6J9zH7k=\n  file:\n  - 'dist/mac-osx.tar.gz'\n  - 'dist/linux-x64.tar.gz'\n  - 'dist/linux-ia32.tar.gz'\n  skip_cleanup: true\n  on:\n    repo: geeeeeeeeek/electronic-wechat\n    branch: production\nnotifications:\n  webhooks:\n    urls:\n      - https://webhooks.gitter.im/e/d6bab2376f47ee992d78\n    on_success: always  # options: [always|never|change] default: always\n    on_failure: always  # options: [always|never|change] default: always\n    on_start: always     # options: [always|never|change] default: always\nafter_deploy:\n  - ./scripts/qiniu.sh\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\n\n**v2.0 (2017.02.13) CN**\n\n1. 升级 **Electron** 至 **V1.4.15**，**Chromium** 至 **54**\n2. 增加了**偏好设置**（感谢设计建议 @**[xiaoyusilen](https://github.com/xiaoyusilen)**）\n3. 增加了英文版本的支持\n4. 增加了一键隐藏窗口（**`ESC`** 键）\n5. 修复了 **macOS** 上窗口最小化时不显示新消息提示的红点（感谢 @wujysh 的贡献）\n6. 修复了聊天框内换行提示仅针对 macOS 的问题\n7. 增加了两个快捷键（感谢 @awmleer 的贡献）\n\t- 搜索联系人：**`Ctrl + F`**\n\t- 切换到全屏模式：**macOS** 下 **`Ctrl + Command + F`**，**Linux** 下为 **`F11`**\n8. 修复了在 Linux 系统下部分菜单按钮失效的问题（感谢 @qzchenwl 的贡献）\n8. 更新了依赖的第三方库的版本至最新兼容版本\n\n**v2.0 (2017.02.13) EN**\n\n1. Update Electron to V1.4.15, Chromium API level 54\n2. Add **Preference Panel** (Thanks for the design advises from @**[xiaoyusilen](https://github.com/xiaoyusilen)**](https://github.com/xiaoyusilen))\n3. Fully support English UI!\n4. Quick hide windows shortcut (**Press `ESC`**)\n5. Fix **macOS** new message red dot display improperly (Thanks to @wujysh)\n6. Tips in chat window now are adapted with platform\n7. Add two shortcuts (Thanks to @awmleer)\n\t- Search Contact人: **`Ctrl + F`**\n\t- Toggle Fullscreen Mode: **macOS** **`Ctrl + Command + F`**, **Linux** **`F11`**\n8. Fix unfunctional menu items on **Linux** (Thanks to @qzchenwl)\n8. All thrid party libraries are up-to-date\n\n\n**v1.3 (2016.05.19)**\n\n1. 升级 electron 至 1.1.0， Chrome 至 50.0.2661.102，Node 至 6.1.0 (感谢 @lfs1102 的贡献)\n2. 新增 `brew cask` 安装方式 (最新可下载版本为 v1.2.0)\n3. 新增 Windows 下的安装脚本 (感谢 @3dseals 的贡献)\n4. 新增 应用启动动画，缩短首次展现时间\n5. 优化 应用启动稳定性，增加超时重试\n6. 优化 主要文案均统一为英文\n7. 优化 减少 20M 应用体积\n8. 修复 关于页面版本号显示的 bug\n9. 修复 Linux 系统下左边栏组件重叠的 bug\n10. 修复 部分 Linux KDE 系统下托盘图标空白的 bug\n11. 其他修改 (感谢 @wzyboy, @rivershang, @hexchain, @samurai00, @boltomli 的贡献)\n\n\n**v1.2 (2016.04.21)**\n\n1. 新增 更新检测模块，应用内即可检查更新\n2. 新增 公众号文章的第三方分享功能。现支持一键分享到微博、QQ 空间、Facebook、Twitter、Evernote 和邮件 (感谢 @oblank 的贡献)\n3. 新增 群聊 @ 提及成员功能，但收到提醒需要服务端支持 (感谢 @iamcc 的贡献)\n4. 优化 登录界面使用单独的尺寸 (感谢 @xnfa 的贡献)\n5. 优化 修改 OS X 下隐藏其他窗口的快捷键为 `Command+Alt+H`\n6. 优化 Linux 下可执行文件文件名使用小写字母，去除空格\n7. 优化 Linux 下使用彩色图标\n8. 升级 `electron-prebuilt` 版本至 `0.37.6` ， `electron-packager` 版本至 `7.0.0`\n9. ~~降级 Emoji贴纸显示的功能。由于微信协议调整和官方代码缺陷，现有商店内贴纸及部分个人收藏的贴纸无法显示。后续跟进微信的修复进行调整。~~ (Update: 微信已修复，贴纸均可正常显示)\n\n\n**v1.1 (2016.03.17)**\n\n1. 新增 OS X 和 Linux 下的托盘菜单，点击可进入应用、退出应用 (仅Linux) (感谢 @iamcc 和 @wenLiangcan 的贡献)\n2. 新增 cnpm 镜像提醒\n3. 优化 应用的退出逻辑，Cmd+Q 退出应用，Cmd+W 或点击关闭隐藏应用\n4. 优化 OS X 和 Linux 下的应用菜单显示\n5. 优化 Emoji贴纸的实现方式，避免滑动时内容抖动，无法回到底部\n6. 优化 接管应用刷新的逻辑，Cmd+R 重新加载页面\n7. 优化 OS X 下 build 后将应用拷贝到 Application 文件夹\n8. 优化 Linux 下使用 Ctrl+Shift+I 打开开发者工具 (感谢 @wenLiangcan 的贡献)\n9. 修复 错误的微信站内重定向 (感谢 @gucheen 的贡献)\n10. 修复 Linux 下应用图标的显示\n11. 修复 聊天列表滑动性能问题\n12. 修复 公众号新窗口打开报错 (感谢 @gzzhanghao 的贡献)\n\n**v1.0 (2016.03.01)**\n\n1. 新增 阻止消息撤回的功能 (感谢 @arrowrowe 的贡献)\n2. 新增 引入了 Travis CI 和 Gitter.im\n3. 优化 贴纸显示的实现方式 (感谢 @arrowrowe 的贡献)\n4. 优化 build 脚本 (感谢 @gaocegege, @viko16 和 @htc550605125 的贡献)\n5. 优化 Linux 下自动隐藏菜单 (感谢 @wenLiangcan 的贡献)\n6. 优化 Linux 下用户头像的显示\n7. 优化 禁用缩放、选中文本、默认光标\n\n**v0.1 (2016.02.19)**\n\n1. Create the project.\n2. Auto resize web content.\n3. Drag to send pictures.\n4. Open inhibited links without additional redirect.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Electronic WeChat\n\nFirst of all, thanks for contributing to this project. It would be appreciated if you read through this contributing guide.\n\n## Issues\n\n- Check if your issue is already [there](https://github.com/geeeeeeeeek/electronic-wechat/issues). \n\n- Check if your issue is `Electronic WeChat` related rather than upstream related.\n\n- Follow the guide in the issue template.\n\n## Pull Requests\n\nPR are always welcomed. It's better if you put up an issue before firing a PR. **Remember**, the smaller your focus, the better chance to get merged.\n\n## Be a collaborator!\n\nIf you are excited about the project, and happen to have skills in Angular, Node, Electron, or else. Do not hesitate to contact me. Let's build together!\n"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "content": "#### Description\n\nFirst of all, thanks for your attention to open an issue for this project.\nPlease notice that if you are requesting a **feature**, then you should give a **brief description** of your request.\nIf you are reporting a **bug**, please **follow the template** below.\nA bug report **without detailed information** required will have a **very low priority** and even be **ignored** (closed directly)!\n\n#### Specifications\n\n- Version of Electron (run `$ electron --version`): `v0.0.0`\n- OS: `<OS>`\n- Stack trace from the error message (if any)\n\n```\n<Stack trace here>\n```\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Zhongyi Tong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject 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,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<img src=\"assets/icon.png\" alt=\"logo\" height=\"120\" align=\"right\" />\n\n# Electronic WeChat\n\n*A better WeChat on macOS and Linux. Built with [Electron](https://github.com/atom/electron).*\n\n> **⚠️⚠️ NO LONGER IN ACTIVE DEVELOPMENT | 项目不再维护 ⚠️⚠️** \n> \n> Thanks for supporting this project for **1000** days since Feb 16, 2016. \n> \n> It started with the idea to make WeChat better on MacOS when the official support was abscent. It was de facto dead when Tencent rolled out a new WeChat and started to block other third-party clients. For me, it's no longer worthwhile to hack a lot to accomplish little. Hope this project had been helpful to you in any way. You're welcome to fork or make copies with a reference. HAPPY HACKING.\n>\n> 感谢历史上的用户和贡献者，你们已经陪伴这个项目走过了 **1000** 个日子。我曾经想要打造一个更好的 Mac 微信客户端，因为官方版本几年没有更新、bug 层出。而在腾讯自己开始了定期更新并限制第三方客户端时，这个项目实际已经没有什么意义。这个项目目前作为一个存档供大家学习。希望它曾经对你有所帮助，你也可以 fork 或者转载（标注来源）来进行修改。祝你玩得愉快。\n>\n> **SPECIAL THANKS TO | 特别感谢**\n> \n> [Kulbear](https://github.com/Kulbear), \n> [arrowrowe](https://github.com/arrowrowe), \n> [Rocka](https://github.com/rocka), \n> [CC](https://github.com/iamcc), \n> [xgdgsc](https://github.com/xgdgsc), \n> [死水微澜](https://github.com/ripples-alive), \n> [Jason](https://github.com/gzzhanghao), \n> [Ce Gao](https://github.com/gaocegege), \n> [viko16](https://github.com/viko16), \n> [卡晨](https://github.com/awmleer), \n> [Ray](https://github.com/ray26), \n> [尹良灿](https://github.com/wenLiangcan), \n> [gehuangyi20](https://github.com/gehuangyi20), \n> [Kevin Tan](https://github.com/stkevintan), \n> [Jiaye Wu](https://github.com/wujysh), \n> [loufq](https://github.com/loufq), \n> [Miaow](https://github.com/miaowing), \n> [Chuan Ji](https://github.com/jichu4n), \n> [Oaker](https://github.com/cyio), \n> [Fengshuang Li](https://github.com/lfs1102), \n> [Song Li](https://github.com/boltomli), \n> [afon](https://github.com/samurai00), \n> [lional wang](https://github.com/3dseals), \n> [Haochen Tong](https://github.com/hexchain), \n> [Zhuoyun Wei](https://github.com/wzyboy), \n> [rivershang](https://github.com/rivershang), \n> [Ivan Jiang](https://github.com/iplus26), \n> [oBlank](https://github.com/oblank), \n> [Cheng Gu](https://github.com/gucheen), \n> [NullMDR](https://github.com/NullMDR), \n> [ReadmeCritic](https://github.com/ReadmeCritic).\n---\n\n**Important:** If you want to build the app by yourself rather than download the release directly, please consider to use the source code from [the production branch](https://github.com/geeeeeeeeek/electronic-wechat/tree/production), the master branch is under development and we cannot guarantee it to be stable.\n\n[![Gitter](https://badges.gitter.im/geeeeeeeeek/electronic-wechat.svg)](https://gitter.im/geeeeeeeeek/electronic-wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)\n[![Build Status](https://travis-ci.org/geeeeeeeeek/electronic-wechat.svg?branch=master)](https://travis-ci.org/geeeeeeeeek/electronic-wechat)\n[![Build Status](https://img.shields.io/github/stars/geeeeeeeeek/electronic-wechat.svg)](https://github.com/geeeeeeeeek/electronic-wechat)\n[![Build Status](https://img.shields.io/github/forks/geeeeeeeeek/electronic-wechat.svg)](https://github.com/geeeeeeeeek/electronic-wechat)\n[![Build Status](https://img.shields.io/badge/README-切换语言-yellow.svg)](README_zh.md)\n\n![qq20160428-0 2x](https://cloud.githubusercontent.com/assets/7262715/14876747/ff691ade-0d49-11e6-8435-cb1fac91b3c2.png)\n\n## Features ([CHANGELOG](CHANGELOG.md))\n\n- **Modern UI and all features from Web WeChat.**\n- **Block message recall.**\n- **Stickers showing support.** [[?]](https://github.com/geeeeeeeeek/electronic-wechat/issues/2)\n- Share subscribed passages on Weibo, Qzone, Facebook, Twitter, Evernote, and email.\n- Mention users in a group chat.\n- Drag and drop to send photos.\n- Behaves like a native app, based on dozens of optimization.\n- Removes URL link redirects and takes you directly to blocked websites (e.g. taobao.com).\n\n## How To Use\n\nTo clone and run this repository you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](https://www.npmjs.com/)) installed on your computer. From your command line:\n\n``` bash\n# Clone this repository\ngit clone https://github.com/geeeeeeeeek/electronic-wechat.git\n# Go into the repository\ncd electronic-wechat\n# Install dependencies and run the app\nnpm install && npm start\n```\n\nTo pack into an app, simply type one of these:\n\n``` shell\nnpm run build:osx\nnpm run build:linux\nnpm run build:win32\nnpm run build:win64\n```\n\n**New:** Install with your familiar package manager. Check out [images maintained by the community](https://github.com/geeeeeeeeek/electronic-wechat/wiki/System-Support-Matrix#%E7%A4%BE%E5%8C%BA%E8%B4%A1%E7%8C%AE%E7%9A%84%E5%AE%89%E8%A3%85%E5%8C%85)!\n\n**New:** Or, with homebrew!\n\n```bash\nbrew cask install electronic-wechat\n```\n\n#### [Download Released App](https://github.com/geeeeeeeeek/electronic-wechat/releases)\n\n#### License [MIT](LICENSE.md)\n\n*Electronic WeChat* is released by this open source project. While Web WeChat is a major component  in the app, it should be noted that this is a community release and not an official WeChat release.\n"
  },
  {
    "path": "README_zh.md",
    "content": "<img src=\"assets/icon.png\" alt=\"logo\" height=\"120\" align=\"right\" />\n\n# Electronic WeChat\n\n[![Gitter](https://badges.gitter.im/geeeeeeeeek/electronic-wechat.svg)](https://gitter.im/geeeeeeeeek/electronic-wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)  [![Build Status](https://travis-ci.org/geeeeeeeeek/electronic-wechat.svg?branch=master)](https://travis-ci.org/geeeeeeeeek/electronic-wechat)  [English](README.md)\n\n**Mac OS X 和 Linux 下更好用的微信客户端. 更多功能, 更少bug. 使用[Electron](https://github.com/atom/electron)构建.** \n\n**Important:** 如果你希望在自己的电脑上构建 Electronic WeChat，请使用 [production branch](https://github.com/geeeeeeeeek/electronic-wechat/tree/production)，master branch 包含正在开发的部分，并且不能保证是稳定的版本——尽管 production 版本也有bug ：D \n\n![qq20160428-0 2x](https://cloud.githubusercontent.com/assets/7262715/14876747/ff691ade-0d49-11e6-8435-cb1fac91b3c2.png)\n\n## 应用特性 ([更新日志](CHANGELOG.md))\n\n-  **来自网页版微信的更现代的界面和更丰富的功能**\n-  **阻止消息撤回**\n-  **显示表情贴纸** [[?]](https://github.com/geeeeeeeeek/electronic-wechat/issues/2)\n-  公众号文章支持一键分享到微博、QQ 空间、Facebook、Twitter、Evernote 和邮件\n-  拖入图片、文件即可发送\n-  群聊 @ 提及成员\n-  原生应用体验，未读消息小红点、消息通知等数十项优化\n-  去除外链重定向，直接打开淘宝等网站\n-  没有原生客户端万年不修复的bug\n\n## 如何使用\n\n在下载和运行这个项目之前，你需要在电脑上安装 [Git](https://git-scm.com) 和 [Node.js](https://nodejs.org/en/download/) (来自 [npm](https://www.npmjs.com/))。在命令行中输入:\n\n``` bash\n# 下载仓库\ngit clone https://github.com/geeeeeeeeek/electronic-wechat.git\n# 进入仓库\ncd electronic-wechat\n# 安装依赖, 运行应用\nnpm install && npm start\n```\n\n根据你的平台打包应用:\n\n``` shell\nnpm run build:osx\nnpm run build:linux\nnpm run build:win\n```\n\n**提示:** 如果 `npm install` 下载缓慢，你可以使用 [淘宝镜像(cnpm)](http://npm.taobao.org/) 替代 npm 。\n\n**新渠道:** 使用你熟悉的包管理工具安装。请查看 [社区贡献的镜像](https://github.com/geeeeeeeeek/electronic-wechat/wiki/System-Support-Matrix#%E7%A4%BE%E5%8C%BA%E8%B4%A1%E7%8C%AE%E7%9A%84%E5%AE%89%E8%A3%85%E5%8C%85) 。\n\n**新渠道:** homebrew 安装也已支持 (更新至 electronic-wechat v1.2.0)！\n\n```bash\nbrew cask install electronic-wechat\n```\n\n#### [下载开箱即用的稳定版应用](https://github.com/geeeeeeeeek/electronic-wechat/releases)\n\n#### 项目使用 [MIT](LICENSE.md) 许可\n\n*Electronic WeChat* 是这个开源项目发布的产品。网页版微信是其中重要的一部分，但请注意这是一个社区发布的产品，而 *不是* 官方微信团队发布的产品。\n"
  },
  {
    "path": "config.json",
    "content": "{\n  \"osx\" : {\n    \"title\": \"Electronic Wechat\",\n    \"background\": \"icon.png\",\n    \"icon\": \"icon.icns\",\n    \"icon-size\": 80,\n    \"contents\": [\n      { \"x\": 438, \"y\": 344, \"type\": \"link\", \"path\": \"/Applications\" },\n      { \"x\": 192, \"y\": 344, \"type\": \"file\" }\n    ]\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"electronic-wechat\",\n  \"version\": \"2.0.0\",\n  \"description\": \"An Electron application for WeChat\",\n  \"main\": \"src/main.js\",\n  \"scripts\": {\n    \"start\": \"electron src/main.js\",\n    \"build\": \"./scripts/build-all.sh\",\n    \"build:osx\": \"./scripts/build.sh darwin x64\",\n    \"build:osx64\": \"./scripts/build.sh darwin x64\",\n    \"build:linux32\": \"./scripts/build.sh linux ia32\",\n    \"build:linux\": \"./scripts/build.sh linux x64\",\n    \"build:linux64\": \"./scripts/build.sh linux x64\",\n    \"build:win\": \".\\\\scripts\\\\build-win32.bat win32 ia32\",\n    \"build:win32\": \".\\\\scripts\\\\build-win32.bat win32 ia32\",\n    \"build:win64\": \".\\\\scripts\\\\build-win32.bat win32 x64\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/geeeeeeeeek/wechat-electron.git\"\n  },\n  \"keywords\": [\n    \"Electron\",\n    \"WeChat\",\n    \"微信\",\n    \"Web\"\n  ],\n  \"author\": \"Zhongyi Tong\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/geeeeeeeeek/wechat-electron/issues\"\n  },\n  \"homepage\": \"https://github.com/geeeeeeeeek/wechat-electron/\",\n  \"dependencies\": {\n    \"electron\": \"1.4.15\",\n    \"electron-packager\": \"^8.5.1\",\n    \"nconf\": \"^0.8.4\",\n    \"pinyin\": \"^2.8.0\",\n    \"emojione\": \"^2.2.7\",\n    \"electron-localshortcut\": \"1.1.0\",\n    \"is-xfce\": \"^1.0.2\"\n  },\n  \"devDependencies\": {\n    \"babel-eslint\": \"^7.1.1\",\n    \"eslint\": \"^3.15.0\",\n    \"eslint-config-airbnb\": \"^14.1.0\",\n    \"eslint-plugin-import\": \"^2.2.0\",\n    \"eslint-plugin-jsx-a11y\": \"^4.0.0\",\n    \"eslint-plugin-react\": \"^6.9.0\"\n  }\n}\n"
  },
  {
    "path": "scripts/build-all.sh",
    "content": "#!/bin/bash\n\nif ! hash electron-packager 2>/dev/null; then\n  RED='\\033[0;31m'\n  NC='\\033[0m'\n  echo \"${RED}Error${NC}: you need to npm install electron-packager. Aborting.\"\n  exit 1\nfi\n\nfunction build() {\n\t./scripts/build.sh $@\n}\n\nbuild darwin x64\nbuild linux ia32\nbuild linux x64\n#build win32 ia32\n"
  },
  {
    "path": "scripts/build-win32.bat",
    "content": "set PLATFORM=%1%\nset ARCH=%2%\nset APP_NAME=\"Electronic WeChat\"\n\nset ignore_list=\"dist|scripts|\\.idea|.*\\.md|.*\\.yml|node_modules/nodejieba\"\n\nelectron-packager . \"%APP_NAME%\" --platform=%PLATFORM% --arch=%ARCH% --electronVersion=1.4.15  --app-version=1.4.0 --asar --icon=assets\\icon.png --overwrite --out=.\\dist --ignore=%ignore_list%\n"
  },
  {
    "path": "scripts/build.sh",
    "content": "#!/bin/bash\n\nif ! hash electron-packager 2>/dev/null; then\n  RED='\\033[0;31m'\n  NC='\\033[0m'\n  echo \"${RED}Error${NC}: you need to npm install electron-packager. Aborting.\"\n  exit 1\nfi\n\nif [ \"$#\" -ne 2 ]; then\n  echo -e \"Usage: ./script/build.sh <platform> <arch>\"\n  echo -e \"\tplatform:\tdarwin, linux, win32\"\n  echo -e \"\tarch:\t\tia32, x64\"\n  exit 1\nfi\n\nPLATFORM=$1\nARCH=$2\n\necho \"Start packaging for $PLATFORM $ARCH.\"\n\nif [ $PLATFORM = \"linux\" ]; then\n    APP_NAME=\"electronic-wechat\"\nelse\n    APP_NAME=\"Electronic WeChat\"\nfi\n\nignore_list=\"dist|scripts|\\.idea|.*\\.md|.*\\.yml|node_modules/nodejieba\"\n\nelectron-packager . \"${APP_NAME}\" --platform=$PLATFORM --arch=$ARCH --electronVersion=1.4.15 --app-version=1.4.0 --asar --icon=assets/icon.icns --overwrite --out=./dist --ignore=${ignore_list}\n\nif [ $? -eq 0 ]; then\n  echo -e \"$(tput setaf 2)Packaging for $PLATFORM $ARCH succeeded.$(tput sgr0)\\n\"\nfi\n\nif [ $PLATFORM = \"darwin\" ]; then\n    ditto -rsrcFork ./dist/Electronic\\ WeChat-darwin-x64/Electronic\\ WeChat.app /Applications/Electronic\\ WeChat.app\n    echo \"$(tput setaf 3)App copied to /Applications. You can open Electronic WeChat there or from Spotlight.$(tput sgr0)\"\nfi\n"
  },
  {
    "path": "scripts/qiniu.sh",
    "content": "#!/bin/bash\n\ncase \"$(uname -s)\" in\n\n    Linux*)\n        wget http://devtools.qiniu.com/qiniu-devtools-linux_amd64-current.tar.gz -O dist/qiniu-devtools.tar.gz\n        ;;\n\n    Darwin)\n        wget http://devtools.qiniu.io/qiniu-devtools-darwin_amd64-current.tar.gz -O dist/qiniu-devtools.tar.gz\n        ;;\n\n    *)\n        ;;\n\nesac\n\nmkdir dist/qiniu-devtools\ntar -xvf dist/qiniu-devtools.tar.gz -C dist/qiniu-devtools\nrm -rf /tmp/qiniu\nmkdir /tmp/qiniu\ncp dist/mac-osx.tar.gz /tmp/qiniu\ncp dist/linux-x64.tar.gz /tmp/qiniu\ncp dist/linux-ia32.tar.gz /tmp/qiniu\n\necho '{\n    \"src\": \"/tmp/qiniu\",\n    \"dest\": \"qiniu:access_key='$QINIU_ACCESS_KEY'&secret_key='$QINIU_SECRET_KEY'&bucket=flymeos-cancro\",\n    \"debug_level\": 1\n}' > qiniu-config.json\n./dist/qiniu-devtools/qrsync qiniu-config.json\n"
  },
  {
    "path": "scripts/tar-all.sh",
    "content": "#!/bin/bash\n\ncd dist\n\necho 'Start compressing for Mac OS X.'\ntar zcf 'mac-osx.tar.gz' 'Electronic WeChat-darwin-x64'\necho 'Compressing for Mac OS X succeed.'\n\necho 'Start compressing for Linux x64.'\ntar zcf 'linux-x64.tar.gz' 'electronic-wechat-linux-x64'\necho 'Compressing for Linux x64 succeed.'\n\necho 'Start compressing for Linux ia32.'\ntar zcf 'linux-ia32.tar.gz' 'electronic-wechat-linux-ia32'\necho 'Compressing for Linux ia32 succeed.'\n\ncd ..\n"
  },
  {
    "path": "src/common.js",
    "content": "/**\n * Created by Zhongyi on 3/26/16.\n */\n\n'use strict';\n\nclass Common {\n\n}\nCommon.ELECTRON = 'Electron';\nCommon.ELECTRONIC_WECHAT = 'Electronic WeChat';\nCommon.DEBUG_MODE = false;\nCommon.WINDOW_SIZE = {\n  width: 800,\n  height: 600,\n};\nCommon.WINDOW_SIZE_LOGIN = {\n  width: 380,\n  height: 540,\n};\nCommon.WINDOW_SIZE_LOADING = {\n  width: 380,\n  height: 120,\n};\nCommon.WINDOW_SIZE_SETTINGS = {\n  width: 800,\n  height: 600,\n};\n\nCommon.USER_AGENT = {\n  freebsd: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  sunos: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  win32: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  linux: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36',\n};\n\nCommon.WEB_WECHAT = 'https://wx.qq.com/?lang=en_US';\nCommon.GITHUB = 'https://github.com/geeeeeeeeek/electronic-wechat';\nCommon.GITHUB_RELEASES = 'https://github.com/geeeeeeeeek/electronic-wechat/releases';\nCommon.GITHUB_ISSUES = 'https://github.com/geeeeeeeeek/electronic-wechat/issues';\nCommon.GITHUB_API_HOST = 'api.github.com';\nCommon.GITHUB_API_RELEASE_LATEST_PATH = '/repos/geeeeeeeeek/electronic-wechat/releases/latest';\n\nCommon.UPDATE_ERROR_ELECTRON = `Failed to get the local version. If you are using debug mode(by \\`npm start\\`), this error would happen. Use packed app instead or manually check for updates.\\n\\n${Common.GITHUB_RELEASES}`;\nCommon.UPDATE_ERROR_EMPTY_RESPONSE = 'Failed to fetch release info.';\nCommon.UPDATE_ERROR_UNKNOWN = 'Something went wrong.';\nCommon.UPDATE_NA_TITLE = 'No Update Available';\nCommon.UPDATE_ERROR_NETWORK = 'Connection hang up unexpectedly. Check your network settings.';\nCommon.UPDATE_ERROR_LATEST = (version) => {\n  return `You are using the latest version(${version}).`;\n};\n\nCommon.MENTION_MENU_INITIAL_X = 300;\nCommon.MENTION_MENU_OFFSET_X = 30;\nCommon.MENTION_MENU_INITIAL_Y = 140;\nCommon.MENTION_MENU_OFFSET_Y = 45;\nCommon.MENTION_MENU_WIDTH = 120;\nCommon.MENTION_MENU_OPTION_HEIGHT = 30;\nCommon.MENTION_MENU_OPTION_DEFAULT_NUM = 4;\nCommon.MENTION_MENU_HINT_TEXT = 'Mention:';\n\nCommon.MESSAGE_PREVENT_RECALL = 'Blocked a message recall.';\nCommon.EMOJI_MAXIUM_SIZE = 120;\n\nCommon.languageTitle = 'Language（Need to Restart）';\nCommon.languageDesc = 'Select a default language for WeChat!';\nCommon.recallTitle = 'Prevent Message Recall';\nCommon.recallDesc = 'Message recall feature might be annoying';\nCommon.instanceTitle = 'Allow Multiple Instance';\nCommon.instanceDesc = 'Multiple instance can login with different accounts';\nCommon.iconTitle = 'File Path (In Development)';\nCommon.iconDesc = 'Set a default file path';\nCommon.trayTitle = 'Tray Icon color (Black/White)';\nCommon.trayDesc = 'Select a color to match your desktop theme';\n\nCommon.UPGRADE = 'UPGRADE';\nCommon.FEEDBACK = 'FEEDBACK';\n\nCommon.MENU = {\n  about: 'About Electronic Wechat',\n  service: 'Service',\n  hide: 'Hide Application',\n  hideOther: 'Hide Others',\n  showAll: 'Show All',\n  pref: 'Preference',\n  quit: 'Quit',\n  edit: 'Edit',\n  undo: 'Undo',\n  redo: 'Redo',\n  cut: 'Cut',\n  copy: 'Copy',\n  paste: 'Paste',\n  selectAll: 'Select All',\n  view: 'View',\n  reload: 'Reload This Window',\n  toggleFullScreen: 'Toggle Full Screen',\n  searchContacts: 'Search Contacts',\n  devtool: 'Toggle DevTools',\n  window: 'Window',\n  min: 'Minimize',\n  close: 'Close',\n  allFront: 'Bring All to Front',\n  help: 'Help',\n  repo: 'GitHub Repository',\n  feedback: 'Report Issue',\n  checkRelease: 'Check for New Release',\n};\n\nmodule.exports = Common;\n"
  },
  {
    "path": "src/common_cn.js",
    "content": "/**\n * Created by Zhongyi on 3/26/16.\n */\n'use strict';\nclass Common {\n\n}\nCommon.ELECTRON = 'Electron';\nCommon.ELECTRONIC_WECHAT = 'Electronic WeChat';\nCommon.DEBUG_MODE = false;\nCommon.WINDOW_SIZE = {\n  width: 800,\n  height: 600,\n};\nCommon.WINDOW_SIZE_LOGIN = {\n  width: 380,\n  height: 540,\n};\nCommon.WINDOW_SIZE_LOADING = {\n  width: 380,\n  height: 120,\n};\nCommon.WINDOW_SIZE_SETTINGS = {\n  width: 800,\n  height: 600,\n};\n\nCommon.USER_AGENT = {\n  'freebsd': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  'sunos': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  'win32': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  'linux': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',\n  'darwin': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36'\n}\n\nCommon.WEB_WECHAT = 'https://wx.qq.com/?lang=zh_CN';\nCommon.GITHUB = 'https://github.com/geeeeeeeeek/electronic-wechat';\nCommon.GITHUB_RELEASES = 'https://github.com/geeeeeeeeek/electronic-wechat/releases';\nCommon.GITHUB_ISSUES = 'https://github.com/geeeeeeeeek/electronic-wechat/issues';\nCommon.GITHUB_API_HOST = 'api.github.com';\nCommon.GITHUB_API_RELEASE_LATEST_PATH = '/repos/geeeeeeeeek/electronic-wechat/releases/latest';\n\nCommon.UPDATE_ERROR_ELECTRON = 'Failed to get the local version. If you are using debug mode(by `npm start`), this error would happen. Use packed app instead or manually check for updates.\\n\\n' + Common.GITHUB_RELEASES;\nCommon.UPDATE_ERROR_EMPTY_RESPONSE = '没能获取最新的更新信息';\nCommon.UPDATE_ERROR_UNKNOWN = '不造什么出错了...';\nCommon.UPDATE_NA_TITLE = '没有可用的更新';\nCommon.UPDATE_ERROR_NETWORK = '网络连接出错，请检查你的网络';\nCommon.UPDATE_ERROR_LATEST = (version) => {\n  return `已经在使用最新版 － (${version})`;\n};\n\nCommon.MENTION_MENU_INITIAL_X = 300;\nCommon.MENTION_MENU_OFFSET_X = 30;\nCommon.MENTION_MENU_INITIAL_Y = 140;\nCommon.MENTION_MENU_OFFSET_Y = 45;\nCommon.MENTION_MENU_WIDTH = 120;\nCommon.MENTION_MENU_OPTION_HEIGHT = 30;\nCommon.MENTION_MENU_OPTION_DEFAULT_NUM = 4;\n\nCommon.MENTION_MENU_HINT_TEXT = '选择回复的人:';\n\nCommon.MESSAGE_PREVENT_RECALL = '阻止了一次撤回';\n\nCommon.EMOJI_MAXIUM_SIZE = 120;\n\nCommon.MENU = {\n  about: '关于 Electronic Wechat',\n  service: '服务',\n  hide: '隐藏应用',\n  hideOther: '隐藏其他窗口',\n  showAll: '显示全部窗口',\n  pref: '偏好',\n  quit: '退出',\n  edit: '编辑',\n  undo: '撤销',\n  redo: '取消撤销',\n  cut: '剪切',\n  copy: '复制',\n  paste: '粘贴',\n  selectAll: '选择全部',\n  view: '视图',\n  reload: '重新加载当前窗口',\n  toggleFullScreen:'切换全屏',\n  searchContacts:'搜索联系人',\n  devtool: '开发者工具',\n  window: '窗口',\n  min: '最小化',\n  close: '关闭',\n  allFront: '全部打开',\n  help: '帮助',\n  repo: 'GitHub 目录',\n  feedback: '联系我们',\n  checkRelease: '检查更新',\n};\n\nmodule.exports = Common;\n"
  },
  {
    "path": "src/configuration.js",
    "content": "'use strict';\n\nfunction getUserHome() {\n  return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];\n}\n\nconst nconf = require('nconf').file({\n  file: `${getUserHome()}/.ew.json`,\n});\n\nfunction saveSettings(settingKey, settingValue) {\n  nconf.set(settingKey, settingValue);\n  nconf.save();\n}\n\nfunction readSettings(settingKey) {\n  nconf.load();\n  return nconf.get(settingKey);\n}\n\nmodule.exports = {\n  saveSettings,\n  readSettings,\n};\n"
  },
  {
    "path": "src/handlers/menu.js",
    "content": "'use strict';\n\nconst { remote, shell, ipcRenderer } = require('electron');\nconst AppConfig = require('../configuration');\n\nconst { Menu, app } = remote;\n\nconst lan = AppConfig.readSettings('language');\nlet Common;\nif (lan === 'zh-CN') {\n  Common = require('../common_cn');\n} else {\n  Common = require('../common');\n}\n\nclass MenuHandler {\n  create() {\n    const template = this.getTemplate(remote.process.platform);\n    if (template) {\n      const menuFromTemplate = Menu.buildFromTemplate(template);\n      Menu.setApplicationMenu(menuFromTemplate);\n    }\n  }\n\n  getTemplate(platform) {\n    const darwinTemplate = [\n      {\n        label: Common.ELECTRONIC_WECHAT,\n        submenu: [\n          {\n            label: Common.MENU.about,\n            selector: 'orderFrontStandardAboutPanel:',\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.service,\n            submenu: [],\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.hide,\n            accelerator: 'Command+H',\n            selector: 'hide:',\n          },\n          {\n            label: Common.MENU.hideOther,\n            accelerator: 'Command+Alt+H',\n            selector: 'hideOtherApplications:',\n          },\n          {\n            label: Common.MENU.showAll,\n            selector: 'unhideAllApplications:',\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.pref,\n            click: MenuHandler._preference,\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.quit,\n            accelerator: 'Command+Q',\n            click: MenuHandler._quitApp,\n          },\n        ],\n      },\n      {\n        label: Common.MENU.edit,\n        submenu: [\n          {\n            label: Common.MENU.undo,\n            accelerator: 'Command+Z',\n            selector: 'undo:',\n          },\n          {\n            label: Common.MENU.redo,\n            accelerator: 'Shift+Command+Z',\n            selector: 'redo:',\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.cut,\n            accelerator: 'Command+X',\n            selector: 'cut:',\n          },\n          {\n            label: Common.MENU.copy,\n            accelerator: 'Command+C',\n            selector: 'copy:',\n          },\n          {\n            label: Common.MENU.paste,\n            accelerator: 'Command+V',\n            selector: 'paste:',\n          },\n          {\n            label: Common.MENU.selectAll,\n            accelerator: 'Command+A',\n            selector: 'selectAll:',\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.searchContacts,\n            accelerator: 'Command+F',\n            click: () => {\n              $('#search_bar input')[0].focus();\n            },\n          },\n        ],\n      },\n      {\n        label: Common.MENU.view,\n        submenu: [\n          {\n            label: Common.MENU.reload,\n            accelerator: 'Command+R',\n            click: MenuHandler._reload,\n          },\n          {\n            label: Common.MENU.devtool,\n            accelerator: 'Alt+Command+I',\n            click: MenuHandler._devTools,\n          },\n        ],\n      },\n      {\n        label: Common.MENU.window,\n        submenu: [\n          {\n            label: Common.MENU.min,\n            accelerator: 'Command+M',\n            selector: 'performMiniaturize:',\n          },\n          {\n            label: Common.MENU.close,\n            accelerator: 'Command+W',\n            selector: 'performClose:',\n          },\n          {\n            label: Common.MENU.toggleFullScreen,\n            accelerator: 'Ctrl+Command+F',\n            click: (item, focusedWindow) => {\n              if (focusedWindow) {\n                focusedWindow.setFullScreen(!focusedWindow.isFullScreen());\n              }\n            },\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.allFront,\n            selector: 'arrangeInFront:',\n          },\n        ],\n      },\n      {\n        label: Common.MENU.help,\n        submenu: [\n          {\n            label: Common.MENU.repo,\n            click: MenuHandler._github,\n          },\n          {\n            type: 'separator',\n          }, {\n            label: Common.MENU.feedback,\n            click: MenuHandler._githubIssues,\n          }, {\n            label: Common.MENU.checkRelease,\n            click: MenuHandler._update,\n          }],\n      },\n    ];\n    const linuxTemplate = [\n      {\n        label: Common.MENU.window,\n        submenu: [\n          {\n            label: Common.MENU.pref,\n            click: MenuHandler._preference,\n          },\n          {\n            label: Common.MENU.reload,\n            accelerator: 'Ctrl+R',\n            click: MenuHandler._reload,\n          },\n          {\n            label: Common.MENU.toggleFullScreen,\n            accelerator: 'F11',\n            click: (item, focusedWindow) => {\n              if (focusedWindow) {\n                focusedWindow.setFullScreen(!focusedWindow.isFullScreen());\n              }\n            },\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.searchContacts,\n            accelerator: 'Ctrl+F',\n            click: () => {\n              $('#search_bar input')[0].focus();\n            },\n          },\n          {\n            label: Common.MENU.devtool,\n            accelerator: 'Ctrl+Shift+I',\n            click: MenuHandler._devTools,\n          },\n          {\n            type: 'separator',\n          },\n          {\n            label: Common.MENU.quit,\n            accelerator: 'Ctrl+Q',\n            click: MenuHandler._quitApp,\n          },\n        ],\n      },\n      {\n        label: Common.MENU.help,\n        submenu: [\n          {\n            label: Common.MENU.repo,\n            click: MenuHandler._github,\n          },\n          {\n            type: 'separator',\n          }, {\n            label: Common.MENU.feedback,\n            click: MenuHandler._githubIssues,\n          }, {\n            label: Common.MENU.checkRelease,\n            click: MenuHandler._update,\n          }],\n      },\n    ];\n\n    if (platform === 'darwin') {\n      return darwinTemplate;\n    } else if (platform === 'linux') {\n      return linuxTemplate;\n    }\n  }\n\n  static _quitApp() {\n    app.exit(0);\n  }\n\n  static _reload() {\n    ipcRenderer.send('reload');\n  }\n\n  static _devTools() {\n    remote.getCurrentWindow().toggleDevTools();\n  }\n\n  static _github() {\n    shell.openExternal(Common.GITHUB);\n  }\n\n  static _githubIssues() {\n    shell.openExternal(Common.GITHUB_ISSUES);\n  }\n\n  static _update() {\n    ipcRenderer.send('update');\n  }\n\n  static _preference() {\n    ipcRenderer.send('open-settings-window');\n  }\n}\nmodule.exports = MenuHandler;\n"
  },
  {
    "path": "src/handlers/message.js",
    "content": "'use strict';\n\nconst qs = require('querystring');\nconst url = require('url');\n\nclass MessageHandler {\n  handleRedirectMessage(origin) {\n    return qs.parse(url.parse(origin).query).requrl || origin;\n  }\n}\n\nmodule.exports = MessageHandler;\n"
  },
  {
    "path": "src/handlers/update.js",
    "content": "/**\n * Created by Zhongyi on 3/25/16.\n */\n\n'use strict';\n\nconst { dialog, shell, app, nativeImage } = require('electron');\nconst AppConfig = require('../configuration');\nconst https = require('https');\nconst path = require('path');\n\nconst lan = AppConfig.readSettings('language');\nlet Common;\nif (lan === 'zh-CN') {\n  Common = require('../common_cn');\n} else {\n  Common = require('../common');\n}\n\nclass UpdateHandler {\n  checkForUpdate(version, silent) {\n    UpdateHandler.CHECKED = true;\n    const promise = new Promise((res, rej) => {\n      if (Common.ELECTRON === app.getName()) {\n        rej(Common.UPDATE_ERROR_ELECTRON);\n      }\n      const req = https.get({\n        host: Common.GITHUB_API_HOST,\n        headers: { 'user-agent': Common.USER_AGENT },\n        path: Common.GITHUB_API_RELEASE_LATEST_PATH,\n      }, (response) => {\n        let body = '';\n        response.on('data', (d) => {\n          body += d;\n        });\n        response.on('end', () => {\n          this._parseUpdateData(body, version, res, rej);\n        });\n      });\n      req.on('error', (err) => {\n        rej(Common.UPDATE_ERROR_NETWORK);\n      });\n      req.end();\n    }).then((fetched) => {\n      this.showDialog(fetched.name, fetched.description, 'Update', (response) => {\n        if (!response) return;\n        shell.openExternal(fetched.url);\n      });\n    }).catch((message) => {\n      if (silent) return;\n      if (!message) {\n        message = Common.UPDATE_ERROR_UNKNOWN;\n      }\n      this.showDialog(Common.UPDATE_NA_TITLE, message, 'OK');\n    });\n  }\n\n  showDialog(message, detail, positiveButton, callback) {\n    const iconImage = nativeImage.createFromPath(path.join(__dirname, '../assets/icon.png'));\n\n    dialog.showMessageBox({\n      type: 'info',\n      buttons: ['Cancel', positiveButton],\n      defaultId: 1,\n      cancelId: 0,\n      title: message,\n      message,\n      detail,\n      icon: iconImage,\n    }, callback);\n  }\n\n  _parseUpdateData(body, version, res, rej) {\n    const data = JSON.parse(body);\n    if (!data || !data.tag_name) rej(Common.UPDATE_ERROR_EMPTY_RESPONSE);\n    const fetched = {\n      version: data.tag_name,\n      is_prerelease: data.prerelease,\n      name: data.name,\n      url: data.html_url,\n      description: data.body,\n    };\n\n    const versionRegex = /^v[0-9]+\\.[0-9]+\\.*[0-9]*$/;\n    if (versionRegex.test(fetched.version) && fetched.version > version && !fetched.is_prerelease) {\n      res(fetched);\n    } else {\n      rej(Common.UPDATE_ERROR_LATEST(version));\n    }\n  }\n}\n\nUpdateHandler.CHECKED = false;\n\nmodule.exports = UpdateHandler;\n"
  },
  {
    "path": "src/inject/badge_count.js",
    "content": "/**\n * Created by Zhongyi on 4/12/16.\n */\n'use strict';\nconst { ipcRenderer } = require('electron');\n\nclass BadgeCount {\n  static init() {\n    setInterval(() => {\n      let count = 0;\n      $('.icon.web_wechat_reddot_middle').each(function () {\n        count += parseInt(this.textContent, 10);\n      });\n      if (count > 0) {\n        ipcRenderer.send('badge-changed', count.toString());\n      } else {\n        ipcRenderer.send('badge-changed', '');\n      }\n    }, 1500);\n  }\n}\n\nmodule.exports = BadgeCount;\n"
  },
  {
    "path": "src/inject/css.js",
    "content": "/**\n * Created by Zhongyi on 2/23/16.\n */\n'use strict';\nconst Common = require('../common');\n\nclass CSSInjector {\n}\n\nCSSInjector.commonCSS = `\n    div.header, div.title_wrap {\n        -webkit-app-region: drag;\n    }\n    div.title.poi {\n        -webkit-app-region: no-drag;\n    }\n    div.header .avatar, div.header .info {\n        -webkit-app-region: no-drag;\n    }\n    div.main {\n      height: 100% !important;\n      min-height: 0 !important;\n      padding-top: 0 !important;\n    }\n    div.main_inner {\n      max-width: none !important;\n      min-width: 0 !important;\n    }\n    div.message_empty {\n      margin-top: 50px;\n    }\n    div.img_preview_container div.img_opr_container {\n      bottom: 50px !important;\n    }\n    p.copyright {\n      display: none !important\n    }\n    a.web_wechat_screencut {\n      display: none !important;\n    }\n    * {\n      -webkit-user-select: none;\n      cursor: default !important;\n      -webkit-user-drag: none;\n    }\n    pre, input {\n      -webkit-user-select: initial;\n      cursor: initial !important;\n    }\n    html, body {\n      width: 100%;\n      height: 100%;\n      overflow: hidden;\n    }\n\n    div.login_box {\n      top: initial;\n      left: initial;\n      margin-left: initial;\n      margin-top: initial;\n      width: 100%;\n      height: 100%;\n    }\n    div.login {\n      min-width: 0;\n      min-height: 0;\n      width: 100%;\n      height: 100%;\n      overflow: hidden;\n    }\n    div.lang, div.copyright {\n      display: none !important\n    }\n    /* Group mention: user selection box */\n    div#userSelectionBox select option:hover {\n      background: #eeeeee;\n    }\n    div#userSelectionBox select option {\n      padding: 4px 10px;\n      text-overflow: hidden;\n      font-size: 14px;\n    }\n    .user_select_hint_text {\n      padding: 4px 10px;\n      font-size: 14px;\n      background: #eeeeee;\n    }\n    div#userSelectionBox select {\n      width: 120px;\n      border: none;\n      outline: none;\n      height: inherit;\n    }\n    div#userSelectionBox {\n      box-shadow: 1px 1px 10px #ababab;\n      background: #fff;\n      display: none;\n      position: fixed;\n      bottom: ${Common.MENTION_MENU_INITIAL_Y}px;\n      left: ${Common.MENTION_MENU_INITIAL_X}px;\n    }\n    span.measure_text {\n      padding-left: 20px;\n      outline: 0;\n      border: 0;\n      font-size: 14px;\n    }\n    img.emojione {\n      width: 20px;\n      height: 20px;\n    }\n    @media (max-width: 512px) {\n      .panel {\n        width: 75px !important;\n        transition: width .3s;\n      }\n      .panel .header,\n      .chat_item {\n        padding: 8px 16px !important;\n      }\n      .header,\n      .panel .tab,\n      .search_bar,\n      .chat_item .info,\n      .chat_item .ext {\n        display: none !important\n      }\n      .nav_view {\n        top: 36px !important\n      }\n      .chat_item.active {\n        border-left: 2px solid #02b300 !important\n      }\n    }\n  `;\n\nCSSInjector.osxCSS = `\n    div.header div.avatar img.img {\n      width: 24px;\n      height: 24px;\n    }\n    div.header {\n      padding-top: 38px;\n      padding-bottom: 8px;\n    }\n    span.display_name {\n      width: 172px !important;\n    }\n    @media (max-width: 512px) {\n      .nav_view {\n        top: 36px !important\n      }\n    }\n`;\n\nmodule.exports = CSSInjector;\n"
  },
  {
    "path": "src/inject/emoji_parser.js",
    "content": "/**\n * Created by chenwl on 9/29/16.\n */\n\nvar emojione = require('emojione');\n\n// <span class=\"emoji emoji1f471\"></span>\nconst emojiSpanRegex = /<span class=\"emoji emoji([\\da-f]+)\"><\\/span>/g;\n\nfunction unicodeToString(point) {\n    const offset = point - 0x10000;\n    const lead = 0xd800 + (offset >> 10);\n    const trail = 0xdc00 + (offset & 0x3ff);\n    return String.fromCharCode(lead, trail);\n}\n\nclass EmojiParser {\n    static emojiSpanToString(str) {\n        return str.replace(emojiSpanRegex, function(span, emojiHex) {\n            const point = parseInt(emojiHex, 16);\n            return unicodeToString(point);\n        });\n    }\n\n    static emojiToImage(str) {\n        return emojione.unicodeToImage(EmojiParser.emojiSpanToString(str));\n    }\n}\n\nmodule.exports = EmojiParser;\n"
  },
  {
    "path": "src/inject/mention_menu.js",
    "content": "/**\n * Created by Zhongyi on 4/9/16.\n */\n\n'use strict';\nconst Common = require('../common');\nconst pinyin = require('pinyin');\n\nclass MentionMenu {\n\n  static init() {\n    const $box = $('<div id=\"userSelectionBox\"/>');\n\n    const $div = $('<div/>');\n    $div.html(Common.MENTION_MENU_HINT_TEXT);\n    $div.addClass('user_select_hint_text');\n    $box.append($div);\n\n    const $select = $('<select multiple/>');\n    $select.change(() => {\n      const $editArea = $('#editArea');\n      $editArea.focus();\n      const newMessage = $editArea.html().replace(/@\\S*$/ig, `@${$select.val()} `);\n      $editArea.html('');\n      $editArea.scope().insertToEditArea(newMessage);\n      $box.css('display', 'none');\n    });\n    $box.append($select);\n    $('body').append($box);\n  }\n\n  static inject($event) {\n    const $editArea = $($event.currentTarget);\n    const $box = $('#userSelectionBox');\n\n    const $probe = $('<span id=\"probe\"/>');\n    $editArea.append($probe);\n    const probePosition = $probe.position();\n    $probe.remove();\n    const menuPosition = MentionMenu.getMenuPosition($editArea, probePosition);\n\n    const delayInjection = () => {\n      const name = /@(\\S*)$/.exec($editArea.html());\n      if (!name) {\n        $box.css('display', 'none');\n        return;\n      }\n      const $scope = angular.element('#chatArea').scope();\n      const $select = $box.children('select');\n      $select.html('');\n      $scope.currentContact.MemberList.map(m => {\n        if (!MentionMenu.isValidNameHint(name, m.NickName)) return;\n\n        const $option = MentionMenu.generateOptionFromMember($scope, m);\n        if ($option) $select.append($option);\n      });\n      const membersCount = Math.min($select.children().length, Common.MENTION_MENU_OPTION_DEFAULT_NUM);\n      if (membersCount > 0) {\n        $select.val('');\n        $box.css({\n          display: 'block',\n          height: `${(membersCount + 1) * Common.MENTION_MENU_OPTION_HEIGHT}px`,\n        });\n        if (name[1].length === 0) {\n          $box.css({\n            left: `${menuPosition.left}px`,\n            bottom: `${menuPosition.bottom}px`,\n          });\n        }\n        $select.css({\n          height: `${membersCount * Common.MENTION_MENU_OPTION_HEIGHT}px`,\n        });\n        $box.focus();\n      } else {\n        $box.css('display', 'none');\n      }\n    };\n    setTimeout(delayInjection, 0);\n  }\n\n  static getMenuPosition($editArea, probePosition) {\n    const menuPosition = {};\n    const mentionMenuRightBoundX = probePosition.left + Common.MENTION_MENU_WIDTH + Common.MENTION_MENU_OFFSET_X;\n\n    if (!probePosition.left) {\n      menuPosition.left = Common.MENTION_MENU_INITIAL_X + Common.MENTION_MENU_OFFSET_X;\n    } else if (mentionMenuRightBoundX > $editArea.width()) {\n      menuPosition.left = (Common.MENTION_MENU_INITIAL_X + $editArea.width()) - Common.MENTION_MENU_WIDTH;\n    } else {\n      menuPosition.left = probePosition.left + Common.MENTION_MENU_INITIAL_X;\n    }\n    menuPosition.bottom = (Common.MENTION_MENU_INITIAL_Y - probePosition.top) + Common.MENTION_MENU_OFFSET_Y;\n    return menuPosition;\n  }\n\n  static isValidNameHint(nameHint, userName) {\n    const pinyinRaw = pinyin(userName, {\n      style: pinyin.STYLE_FIRST_LETTER,\n    });\n\n    let pinyinName = '';\n    for (const py of pinyinRaw) {\n      if (py[0] && py[0] !== ' ') {\n        pinyinName += py[0];\n      }\n    }\n\n    const nameRe = new RegExp(nameHint[1], 'ig');\n    return nameRe.test(userName) || nameRe.test(pinyinName);\n  }\n\n  static generateOptionFromMember($scope, member) {\n    const displayName = `${member.NickName}`;\n    let actualName = displayName;\n\n    if (member.DisplayName.length > 0) {\n      actualName = member.DisplayName;\n    } else {\n      const userContact = $scope.getUserContact(member.UserName);\n      if (!userContact) return null;\n      if (userContact.NickName.length > 0) {\n        actualName = userContact.NickName;\n      }\n    }\n\n    const $option = $('<option/>');\n    $option.val(actualName);\n    $option.html(displayName);\n\n    return $option;\n  }\n}\n\nmodule.exports = MentionMenu;\n"
  },
  {
    "path": "src/inject/preload.js",
    "content": "'use strict';\n\nconst { ipcRenderer, webFrame } = require('electron');\nconst MenuHandler = require('../handlers/menu');\nconst ShareMenu = require('./share_menu');\nconst MentionMenu = require('./mention_menu');\nconst BadgeCount = require('./badge_count');\nconst Common = require('../common');\n// const EmojiParser = require('./emoji_parser');\n// const emojione = require('emojione');\n\nconst AppConfig = require('../configuration');\n\nclass Injector {\n  init() {\n    if (Common.DEBUG_MODE) {\n      Injector.lock(window, 'console', window.console);\n    }\n    this.initInjectBundle();\n    this.initAngularInjection();\n    this.lastUser = null;\n    this.initIPC();\n    webFrame.setZoomLevelLimits(1, 1);\n\n    new MenuHandler().create();\n  }\n\n  initAngularInjection() {\n    const self = this;\n    const angular = window.angular = {};\n    let angularBootstrapReal;\n    Object.defineProperty(angular, 'bootstrap', {\n      get: () => angularBootstrapReal ? function (element, moduleNames) {\n        const moduleName = 'webwxApp';\n        if (moduleNames.indexOf(moduleName) < 0) return;\n        let constants = null;\n        angular.injector(['ng', 'Services']).invoke(['confFactory', (confFactory) => (constants = confFactory)]);\n        angular.module(moduleName).config(['$httpProvider', ($httpProvider) => {\n          $httpProvider.defaults.transformResponse.push((value) => {\n            return self.transformResponse(value, constants);\n          });\n        },\n        ]).run(['$rootScope', ($rootScope) => {\n          ipcRenderer.send('wx-rendered', MMCgi.isLogin);\n\n          $rootScope.$on('newLoginPage', () => {\n            ipcRenderer.send('user-logged', '');\n          });\n          $rootScope.shareMenu = ShareMenu.inject;\n          $rootScope.mentionMenu = MentionMenu.inject;\n        }]);\n        return angularBootstrapReal.apply(angular, arguments);\n      } : angularBootstrapReal,\n      set: (real) => (angularBootstrapReal = real),\n    });\n  }\n\n  initInjectBundle() {\n    const initModules = () => {\n      if (!window.$) {\n        return setTimeout(initModules, 3000);\n      }\n\n      MentionMenu.init();\n      BadgeCount.init();\n    };\n\n    window.onload = () => {\n      initModules();\n      window.addEventListener('online', () => {\n        ipcRenderer.send('reload', true);\n      });\n    };\n  }\n\n  transformResponse(value, constants) {\n    if (!value) return value;\n\n    switch (typeof value) {\n      case 'object':\n        /* Inject emoji stickers and prevent recalling. */\n        return this.checkEmojiContent(value, constants);\n      case 'string':\n        /* Inject share sites to menu. */\n        return this.checkTemplateContent(value);\n    }\n    return value;\n  }\n\n  static lock(object, key, value) {\n    return Object.defineProperty(object, key, {\n      get: () => value,\n      set: () => {},\n    });\n  }\n\n  checkEmojiContent(value, constants) {\n    if (!(value.AddMsgList instanceof Array)) return value;\n    value.AddMsgList.forEach((msg) => {\n      switch (msg.MsgType) {\n        // case constants.MSGTYPE_TEXT:\n        //   msg.Content = EmojiParser.emojiToImage(msg.Content);\n        //   break;\n        case constants.MSGTYPE_EMOTICON:\n          Injector.lock(msg, 'MMDigest', '[Emoticon]');\n          Injector.lock(msg, 'MsgType', constants.MSGTYPE_EMOTICON);\n          if (msg.ImgHeight >= Common.EMOJI_MAXIUM_SIZE) {\n            Injector.lock(msg, 'MMImgStyle', { height: `${Common.EMOJI_MAXIUM_SIZE}px`, width: 'initial' });\n          } else if (msg.ImgWidth >= Common.EMOJI_MAXIUM_SIZE) {\n            Injector.lock(msg, 'MMImgStyle', { width: `${Common.EMOJI_MAXIUM_SIZE}px`, height: 'initial' });\n          }\n          break;\n        case constants.MSGTYPE_RECALLED:\n          if (AppConfig.readSettings('prevent-recall') === 'on') {\n            Injector.lock(msg, 'MsgType', constants.MSGTYPE_SYS);\n            Injector.lock(msg, 'MMActualContent', Common.MESSAGE_PREVENT_RECALL);\n            Injector.lock(msg, 'MMDigest', Common.MESSAGE_PREVENT_RECALL);\n          }\n          break;\n      }\n    });\n    return value;\n  }\n\n  checkTemplateContent(value) {\n    const optionMenuReg = /optionMenu\\(\\);/;\n    const messageBoxKeydownReg = /editAreaKeydown\\(\\$event\\)/;\n    if (optionMenuReg.test(value)) {\n      value = value.replace(optionMenuReg, 'optionMenu();shareMenu();');\n    } else if (messageBoxKeydownReg.test(value)) {\n      value = value.replace(messageBoxKeydownReg, 'editAreaKeydown($event);mentionMenu($event);');\n    }\n    return value;\n  }\n\n  initIPC() {\n    // clear currentUser to receive reddot of new messages from the current chat user\n    ipcRenderer.on('hide-wechat-window', () => {\n      this.lastUser = angular.element('#chatArea').scope().currentUser;\n      angular.element('.chat_list').scope().itemClick(\"\");\n    });\n    // recover to the last chat user\n    ipcRenderer.on('show-wechat-window', () => {\n      if (this.lastUser != null) {\n        angular.element('.chat_list').scope().itemClick(this.lastUser);\n      }\n    });\n  }\n}\n\nnew Injector().init();\n"
  },
  {
    "path": "src/inject/share_menu.js",
    "content": "/**\n * Created by oBlank on 3/31/16.\n */\n'use strict';\n\nclass ShareMenu {\n  static inject() {\n    const dropdownMenu = $('.reader_menu .dropdown_menu');\n    const dropdownMenuItem = $('.reader_menu .dropdown_menu > li');\n    if (dropdownMenuItem.length > ShareMenu.shareMenuItemsCount) return;\n\n    ShareMenu.shareMenuItemsCount = dropdownMenuItem.length;\n    const readItem = angular.element('.reader').scope().readItem;\n    const menuHTML = ShareMenu.get({ url: readItem.Url, title: readItem.Title });\n    dropdownMenu.prepend(menuHTML);\n  }\n\n  static get(link) {\n    if (!link.url || !link.title) return '';\n\n    link.url = encodeURIComponent(link.url);\n    link.title = encodeURIComponent(link.title);\n\n    const shareTargets = {\n      weibo: {\n        url: `http://service.weibo.com/share/share.php?url=${link.url}&title=${link.title}#&searchPic=yes`,\n        text: '分享到微博',\n      },\n      qzone: {\n        url: `http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=${link.url}&title=${link.title}&pics=&summary=`,\n        text: '分享到 QQ 空间',\n      },\n      facebook: {\n        url: `https://www.facebook.com/sharer/sharer.php?s=100&p%5Btitle%5D=${link.title}&p%5Bsummary%5D=%21&p%5Burl%5D=${link.url}&p%5Bimages%5D=`,\n        text: '分享到 Facebook',\n      },\n      evernote: {\n        url: `https://www.evernote.com/clip.action?url=${link.url}&title=${link.title}`,\n        text: '分享到 Evernote',\n      },\n      twitter: {\n        url: `https://twitter.com/intent/tweet?text=${link.title}&url=${link.url}&original_referer=`,\n        text: '分享到 Twitter',\n      },\n      email: {\n        url: `mailto:?&subject=${link.title}&body=${link.title}%0A${link.url}`,\n        text: '分享到邮件',\n      },\n    };\n\n\n    let menuItemsTemplate = '';\n    for (const target in shareTargets) {\n      menuItemsTemplate += ShareMenu.genShareMenuItem(shareTargets[target]);\n    }\n\n    return menuItemsTemplate;\n  }\n\n  static genShareMenuItem(target) {\n    return `\n          <li>\n            <a href=\"javascript:;\" onclick=\"javascript:window.open('${target.url}', '_blank'); return;\">\n              <i class=\"menuicon_copylink\"></i>\n              ${target.text}\n            </a>\n          </li>\n          `;\n  }\n}\n\nShareMenu.shareMenuItemsCount = 256;\n\nmodule.exports = ShareMenu;\n"
  },
  {
    "path": "src/main.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst {app, ipcMain} = require('electron');\n\nconst UpdateHandler = require('./handlers/update');\nconst Common = require('./common');\nconst AppConfig = require('./configuration');\n\nconst SplashWindow = require('./windows/controllers/splash');\nconst WeChatWindow = require('./windows/controllers/wechat');\nconst SettingsWindow = require('./windows/controllers/settings')\nconst AppTray = require('./windows/controllers/app_tray');\n\nclass ElectronicWeChat {\n  constructor() {\n    this.wechatWindow = null;\n    this.splashWindow = null;\n    this.settingsWindow = null;\n    this.tray = null;\n  }\n\n  init() {\n    if(this.checkInstance()) {\n      this.initApp();\n      this.initIPC();\n    } else {\n      app.quit();\n    }\n  }\n  checkInstance() {\n    if (AppConfig.readSettings('multi-instance') === 'on') return true;\n    return !app.makeSingleInstance((commandLine, workingDirectory) => {\n      if(this.splashWindow && this.splashWindow.isShown){\n        this.splashWindow.show();\n        return\n      }\n      if(this.wechatWindow){\n        this.wechatWindow.show();\n      }\n      if(this.settingsWindow && this.settingsWindow.isShown){\n        this.settingsWindow.show();\n      }\n    });\n\n  }\n  initApp() {\n    app.on('ready', ()=> {\n      this.createSplashWindow();\n      this.createWeChatWindow();\n      this.createTray();\n\n      if (!AppConfig.readSettings('language')) {\n        AppConfig.saveSettings('language', 'en');\n        AppConfig.saveSettings('prevent-recall', 'on');\n        AppConfig.saveSettings('icon', 'black');\n        AppConfig.saveSettings('multi-instance','on');\n      }\n    });\n\n    app.on('activate', () => {\n      if (this.wechatWindow == null) {\n        this.createWeChatWindow();\n      } else {\n        this.wechatWindow.show();\n      }\n    });\n  };\n\n  initIPC() {\n    ipcMain.on('badge-changed', (event, num) => {\n      if (process.platform == \"darwin\") {\n        app.dock.setBadge(num);\n        if (num) {\n          this.tray.setTitle(` ${num}`);\n        } else {\n          this.tray.setTitle('');\n        }\n      } else if (process.platform === \"linux\" || process.platform === \"win32\") {\n          app.setBadgeCount(num * 1);\n          this.tray.setUnreadStat((num * 1 > 0)? 1 : 0);\n      }\n    });\n\n    ipcMain.on('user-logged', () => {\n      this.wechatWindow.resizeWindow(true, this.splashWindow)\n    });\n\n    ipcMain.on('wx-rendered', (event, isLogged) => {\n      this.wechatWindow.resizeWindow(isLogged, this.splashWindow)\n    });\n\n    ipcMain.on('log', (event, message) => {\n      console.log(message);\n    });\n\n    ipcMain.on('reload', (event, repetitive) => {\n      if (repetitive) {\n        this.wechatWindow.loginState.current = this.wechatWindow.loginState.NULL;\n        this.wechatWindow.connectWeChat();\n      } else {\n        this.wechatWindow.loadURL(Common.WEB_WECHAT);\n      }\n    });\n\n    ipcMain.on('update', (event, message) => {\n      let updateHandler = new UpdateHandler();\n      updateHandler.checkForUpdate(`v${app.getVersion()}`, false);\n    });\n\n    ipcMain.on('open-settings-window', (event, message) => {\n      if (this.settingsWindow) {\n        this.settingsWindow.show();\n      } else {\n        this.createSettingsWindow();\n        this.settingsWindow.show();\n      }\n    });\n\n    ipcMain.on('close-settings-window', (event, messgae) => {\n      this.settingsWindow.close();\n      this.settingsWindow = null;\n    })\n  };\n\n  createTray() {\n    this.tray = new AppTray(this.splashWindow, this.wechatWindow);\n  }\n\n  createSplashWindow() {\n    this.splashWindow = new SplashWindow();\n    this.splashWindow.show();\n  }\n\n  createWeChatWindow() {\n    this.wechatWindow = new WeChatWindow();\n  }\n\n  createSettingsWindow() {\n    this.settingsWindow = new SettingsWindow();\n  }\n\n}\n\nnew ElectronicWeChat().init();\n"
  },
  {
    "path": "src/windows/controllers/app_tray.js",
    "content": "/**\n * Created by Zhongyi on 5/2/16.\n */\n\n'use strict';\n\nconst path = require('path');\nconst { app, Menu, nativeImage, Tray, ipcMain } = require('electron');\n\nconst AppConfig = require('../../configuration');\n\nconst lan = AppConfig.readSettings('language');\n\nconst assetsPath = path.join(__dirname, '../../../assets');\n\nlet Common;\nif (lan === 'zh-CN') {\n  Common = require('../../common_cn');\n} else {\n  Common = require('../../common');\n}\n\nclass AppTray {\n  constructor(splashWindow, wechatWindow) {\n    this.splashWindow = splashWindow;\n    this.wechatWindow = wechatWindow;\n    this.lastUnreadStat = 0;\n    const trayColor = AppConfig.readSettings('tray-color');\n    if (trayColor === 'white' || trayColor === 'black') {\n      this.trayColor = trayColor;\n    } else {\n      this.trayColor = 'white';\n      AppConfig.saveSettings('tray-color', this.trayColor);\n    }\n    this.createTray();\n  }\n\n  createTray() {\n    let image;\n    if (process.platform === 'linux' || process.platform === 'win32') {\n      image = nativeImage.createFromPath(path.join(assetsPath, `tray_${this.trayColor}.png`));\n      this.trayIcon = image;\n      this.trayIconUnread = nativeImage.createFromPath(path.join(assetsPath, `tray_unread_${this.trayColor}.png`));\n    } else {\n      image = nativeImage.createFromPath(path.join(assetsPath, 'status_bar.png'));\n    }\n    image.setTemplateImage(true);\n\n    this.tray = new Tray(image);\n    this.tray.setToolTip(Common.ELECTRONIC_WECHAT);\n\n    ipcMain.on('refreshIcon', () => this.refreshIcon());\n\n    if (process.platform === 'linux' || process.platform === 'win32') {\n      const contextMenu = Menu.buildFromTemplate([\n        { label: 'Show', click: () => this.hideSplashAndShowWeChat() },\n        { label: 'Exit', click: () => app.exit(0) },\n      ]);\n      this.tray.setContextMenu(contextMenu);\n    }\n    this.tray.on('click', () => this.hideSplashAndShowWeChat());\n  }\n\n  setTitle(title) {\n    this.tray.setTitle(title);\n  }\n\n  hideSplashAndShowWeChat() {\n    if (this.splashWindow.isShown) return;\n    this.wechatWindow.show();\n  }\n\n  refreshIcon() {\n    this.trayColor = AppConfig.readSettings('tray-color');\n    this.trayIcon = nativeImage.createFromPath(path.join(assetsPath, `tray_${this.trayColor}.png`));\n    this.trayIconUnread = nativeImage.createFromPath(path.join(assetsPath, `tray_unread_${this.trayColor}.png`));\n    if (this.lastUnreadStat === 0) {\n      this.tray.setImage(this.trayIcon);\n    } else {\n      this.tray.setImage(this.trayIconUnread);\n    }\n  }\n\n  setUnreadStat(stat) {\n    if (stat === this.lastUnreadStat) return;\n    this.lastUnreadStat = stat;\n    if (stat === 0) {\n      this.tray.setImage(this.trayIcon);\n    } else {\n      this.tray.setImage(this.trayIconUnread);\n    }\n  }\n}\n\nmodule.exports = AppTray;\n"
  },
  {
    "path": "src/windows/controllers/settings.js",
    "content": "/**\n * Created by Ji on 9/15/16.\n */\n\n'use strict';\n\nconst path = require('path');\nconst { BrowserWindow } = require('electron');\nconst electronLocalShortcut = require('electron-localshortcut');\n\nconst AppConfig = require('../../configuration');\n\nconst lan = AppConfig.readSettings('language');\n\nlet Common;\nif (lan === 'zh-CN') {\n  Common = require('../../common_cn');\n} else {\n  Common = require('../../common');\n}\n\nclass SettingsWindow {\n  constructor() {\n    this.settingsWindow = null;\n    this.createSettingsWindow();\n  }\n\n  createSettingsWindow() {\n    this.settingsWindow = new BrowserWindow({\n      width: Common.WINDOW_SIZE_SETTINGS.width,\n      height: Common.WINDOW_SIZE_SETTINGS.height * 0.9,\n      resizable: false,\n      fullscreenable: false,\n      show: false,\n      frame: true,\n      alwaysOnTop: true,\n      icon: 'assets/icon.png',\n      titleBarStyle: 'hidden',\n    });\n\n    this.initWindowEvents();\n    this.initSettingsWindowShortcut();\n\n    this.settingsWindow.loadURL(`file://${path.join(__dirname, '/../views/settings.html')}`);\n  }\n\n  initWindowEvents() {\n    this.settingsWindow.on('close', () => {\n      this.unregisterLocalShortCut();\n      this.settingsWindow = null;\n      this.isShown = false;\n    });\n    this.settingsWindow.once('ready-to-show', () => {\n      this.settingsWindow.show();\n    });\n  }\n\n  show() {\n    if (!this.settingsWindow) {\n      this.createSettingsWindow();\n    }\n    this.settingsWindow.show();\n    this.isShown = true;\n  }\n\n  hide() {\n    this.settingsWindow.hide();\n    this.isShown = false;\n  }\n\n  registerLocalShortcut() {\n    electronLocalShortcut.register(this.settingsWindow, 'Esc', () => {\n      this.settingsWindow.close();\n    });\n  }\n\n  unregisterLocalShortCut() {\n    electronLocalShortcut.unregisterAll(this.settingsWindow);\n  }\n\n  initSettingsWindowShortcut() {\n    this.registerLocalShortcut();\n  }\n}\n\nmodule.exports = SettingsWindow;\n"
  },
  {
    "path": "src/windows/controllers/splash.js",
    "content": "/**\n * Created by Zhongyi on May 1, 2016\n */\n\n'use strict';\n\nconst path = require('path');\nconst { BrowserWindow } = require('electron');\n\nconst AppConfig = require('../../configuration');\n\nconst lan = AppConfig.readSettings('language');\n\nlet Common;\nif (lan === 'zh-CN') {\n  Common = require('../../common_cn');\n} else {\n  Common = require('../../common');\n}\n\nclass SplashWindow {\n  constructor() {\n    this.splashWindow = new BrowserWindow({\n      width: Common.WINDOW_SIZE_LOADING.width,\n      height: Common.WINDOW_SIZE_LOADING.height,\n      title: Common.ELECTRONIC_WECHAT,\n      resizable: false,\n      center: true,\n      show: true,\n      frame: false,\n      autoHideMenuBar: true,\n      alwaysOnTop: true,\n      icon: 'assets/icon.png',\n      titleBarStyle: 'hidden',\n    });\n\n    this.splashWindow.loadURL(`file://${path.join(__dirname, '/../views/splash.html')}`);\n    this.isShown = false;\n  }\n\n  show() {\n    this.splashWindow.show();\n    this.isShown = true;\n  }\n\n  hide() {\n    this.splashWindow.hide();\n    this.isShown = false;\n  }\n}\n\nmodule.exports = SplashWindow;\n"
  },
  {
    "path": "src/windows/controllers/wechat.js",
    "content": "/**\n * Created by Zhongyi on 5/2/16.\n */\n\n'use strict';\n\nconst path = require('path');\nconst isXfce = require('is-xfce');\nconst { app, shell, BrowserWindow } = require('electron');\nconst electronLocalShortcut = require('electron-localshortcut');\n\nconst AppConfig = require('../../configuration');\n\nconst CSSInjector = require('../../inject/css');\nconst MessageHandler = require('../../handlers/message');\nconst UpdateHandler = require('../../handlers/update');\n\nconst lan = AppConfig.readSettings('language');\n\nlet Common;\nif (lan === 'zh-CN') {\n  Common = require('../../common_cn');\n} else {\n  Common = require('../../common');\n}\n\nclass WeChatWindow {\n  constructor() {\n    this.isShown = false;\n    this.loginState = { NULL: -2, WAITING: -1, YES: 1, NO: 0 };\n    this.loginState.current = this.loginState.NULL;\n    this.inervals = {};\n    this.createWindow();\n    this.initWechatWindowShortcut();\n    this.initWindowEvents();\n    this.initWindowWebContent();\n  }\n\n  resizeWindow(isLogged, splashWindow) {\n    const size = isLogged ? Common.WINDOW_SIZE : Common.WINDOW_SIZE_LOGIN;\n\n    this.wechatWindow.setResizable(isLogged);\n    this.wechatWindow.setSize(size.width, size.height);\n    if (this.loginState.current === 1 - isLogged || this.loginState.current === this.loginState.WAITING) {\n      splashWindow.hide();\n      this.show();\n      this.wechatWindow.center();\n      this.loginState.current = isLogged;\n    }\n  }\n\n  createWindow() {\n    this.wechatWindow = new BrowserWindow({\n      title: Common.ELECTRONIC_WECHAT,\n      resizable: true,\n      center: true,\n      show: false,\n      frame: true,\n      autoHideMenuBar: true,\n      icon: path.join(__dirname, '../../../assets/icon.png'),\n      titleBarStyle: 'hidden-inset',\n      webPreferences: {\n        javascript: true,\n        plugins: true,\n        nodeIntegration: false,\n        webSecurity: false,\n        preload: path.join(__dirname, '../../inject/preload.js'),\n      },\n    });\n\n    /* menu is always visible on xfce session */\n    isXfce().then(data => {\n      if(data) {\n        this.wechatWindow.setMenuBarVisibility(true);\n        this.wechatWindow.setAutoHideMenuBar(false);\n      }\n    });\n  }\n\n  loadURL(url) {\n    this.wechatWindow.loadURL(url);\n  }\n\n  show() {\n    this.wechatWindow.show();\n    this.wechatWindow.focus();\n    this.wechatWindow.webContents.send('show-wechat-window');\n    this.isShown = true;\n  }\n\n  hide() {\n    this.wechatWindow.hide();\n    this.wechatWindow.webContents.send('hide-wechat-window');\n    this.isShown = false;\n  }\n\n  connectWeChat() {\n    Object.keys(this.inervals).forEach((key, index) => {\n      clearInterval(key);\n      delete this.inervals[key];\n    });\n\n    this.loadURL(Common.WEB_WECHAT);\n    const int = setInterval(() => {\n      if (this.loginState.current === this.loginState.NULL) {\n        this.loadURL(Common.WEB_WECHAT);\n        console.log('Reconnect.');\n      }\n    }, 5000);\n    this.inervals[int] = true;\n  }\n\n  initWindowWebContent() {\n    this.wechatWindow.webContents.setUserAgent(Common.USER_AGENT[process.platform]);\n    if (Common.DEBUG_MODE) {\n      this.wechatWindow.webContents.openDevTools();\n    }\n\n    this.connectWeChat();\n\n    this.wechatWindow.webContents.on('will-navigate', (ev, url) => {\n      if (/(.*wx.*\\.qq\\.com.*)|(web.*\\.wechat\\.com.*)/.test(url)) return;\n      ev.preventDefault();\n    });\n\n    this.wechatWindow.webContents.on('dom-ready', () => {\n      this.wechatWindow.webContents.insertCSS(CSSInjector.commonCSS);\n      if (process.platform === 'darwin') {\n        this.wechatWindow.webContents.insertCSS(CSSInjector.osxCSS);\n      }\n\n      if (!UpdateHandler.CHECKED) {\n        new UpdateHandler().checkForUpdate(`v${app.getVersion()}`, true);\n      }\n    });\n\n    this.wechatWindow.webContents.on('new-window', (event, url) => {\n      event.preventDefault();\n      shell.openExternal(new MessageHandler().handleRedirectMessage(url));\n    });\n\n    this.wechatWindow.webContents.on('will-navigate', (event, url) => {\n      if (url.endsWith('/fake')) event.preventDefault();\n    });\n  }\n\n  initWindowEvents() {\n    this.wechatWindow.on('close', (e) => {\n      if (this.wechatWindow.isVisible()) {\n        e.preventDefault();\n        this.hide();\n      }\n      this.unregisterLocalShortCut();\n    });\n\n    this.wechatWindow.on('page-title-updated', (ev) => {\n      if (this.loginState.current === this.loginState.NULL) {\n        this.loginState.current = this.loginState.WAITING;\n      }\n      ev.preventDefault();\n    });\n\n    this.wechatWindow.on('show', () => {\n      this.registerLocalShortcut();\n    });\n  }\n\n  registerLocalShortcut() {\n    electronLocalShortcut.register(this.wechatWindow, 'CommandOrControl + H', () => {\n      this.wechatWindow.hide();\n    });\n  }\n\n  unregisterLocalShortCut() {\n    electronLocalShortcut.unregisterAll(this.wechatWindow);\n  }\n\n  initWechatWindowShortcut() {\n    this.registerLocalShortcut();\n  }\n}\n\nmodule.exports = WeChatWindow;\n"
  },
  {
    "path": "src/windows/styles/settings.css",
    "content": "* {\n  -webkit-user-select: none;\n  cursor: default !important;\n  -webkit-user-drag: none;\n}\n\nbody {\n  margin: 0;\n  padding: 0;\n  color: #364854;\n  font-family: \"Microsoft Yahei\", \"微软雅黑\", STXihei, \"华文细黑\", sans-serif;\n  background-color: #F3F3F3;\n}\n\ndiv {\n  margin: 0;\n  padding: 0;\n}\n\nul {\n  list-style: none;\n}\n\nh1 {\n  color: #364854;\n}\n\nsection {\n  position: relative;\n  width: 75%;\n  left: 12.5%;\n  border-bottom: 1px solid gray;\n  overflow: hidden;\n}\n\n.page {\n  min-width: 500px;\n  min-height: 350px;\n}\n\n.setting-menu {\n  padding-top: 5px;\n}\n\n.menu-title {\n  position: relative;\n  left: 20%;\n}\n\n.menu-desc {\n  position: relative;\n  left: 20%;\n}\n\n.menu-button {\n  position: relative;\n  left: 20%;\n}\n\n.setting-top-bar {\n  padding: 0;\n  margin: 0 0 10px 0;\n  border: none;\n  height: 140px;\n  width: 100%;\n  overflow: hidden;\n  background-color: #CCC;\n  box-shadow: 0px 5px 5px #CCC;\n}\n\n.title-list {\n  padding-top: 1px;\n}\n\n.top-bar-icon {\n  width: 100px;\n  height: 100px;\n  float: left;\n  margin: 15px;\n  margin-left: 30px;\n}\n\n.top-bar-left {\n  float: left;\n  width: 12%;\n  padding: 1%;\n}\n\n.top-bar-middle {\n  text-align: center;\n}\n\n.top-bar-right {\n  text-align: center;\n  float: right;\n  width: 18%;\n  margin-right: 40px;\n  padding-top: 20px;\n}\n\n.upgrade-btn {\n  border-radius: 15px;\n  margin-bottom: 10px;\n  color: #c9c9c9;\n  font-size: 16px;\n  background: #364854;\n  padding: 10px 20px 10px 20px;\n  text-decoration: none;\n  width: 125px;\n}\n"
  },
  {
    "path": "src/windows/styles/splash.css",
    "content": "* {\n  -webkit-user-select: none;\n  cursor: default !important;\n  -webkit-user-drag: none;\n}\nbody {\n  overflow: hidden;\n  margin: 0;\n  background: #ECEFF1;\n}\n#splash_container {\n  margin: 10px 20px;\n}\n#splash_text {\n  font-family: sans-serif;\n  font-size: 40px;\n  color: #666;\n  vertical-align: top;\n  line-height: 100px;\n  animation: fadein 0.75s ease-out;\n}\n#splash_loading_img {\n  width: 100px;\n  margin: 15px -10px;\n  display: inline-block;\n  vertical-align: middle;\n  animation: increase 0.5s ease-out;\n}\n@keyframes fadein {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes increase {\n  from {\n    opacity: 0;\n    width: 0;\n    margin: 52px 40px;\n  }\n  to {\n    opacity: 1;\n    width: 100px;\n    margin: 15px -10px;\n  }\n}\n"
  },
  {
    "path": "src/windows/views/settings.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <link href=\"../styles/settings.css\" rel=\"stylesheet\">\n  <meta charset=\"UTF-8\">\n  <title>Settings</title>\n</head>\n\n<body class=\"page\">\n  <div class=\"setting-top-bar\">\n    <div class=\"top-bar-left\">\n      <img class=\"top-bar-icon\" src=\"../../../assets/icon.png\">\n    </div>\n    <div class=\"top-bar-right\">\n      <button type=\"button\" class=\"upgrade-btn\" id=\"upgrade-btn\" onclick=\"upgrade()\">检查更新</button>\n      <button type=\"button\" class=\"upgrade-btn\" id=\"feedback-btn\" onclick=\"feedback()\">联系我们</button>\n    </div>\n    <div class=\"top-bar-middle\">\n      <ul class=\"title-list\">\n        <li>\n          <h1>Electronic Wechat V2.0.0</h1></li>\n        <li>\n          <h1 style=\"font-size:95%;\">Powered by Electron V<span id=\"top-title-electron-ver\">process.versions.electron</span></h1></li>\n      </ul>\n    </div>\n  </div>\n  <div class=\"setting-menu\">\n    <section>\n      <ul>\n        <li class=\"menu-title\">\n          <h3 id=\"app-language-title\">语言（重启生效）</h3>\n        </li>\n        <li class=\"menu-desc\">\n          <h4 id=\"app-language-desc\">选择你希望使用的默认语言</h4>\n        </li>\n        <li class=\"menu-button\">\n          <select class=\"\" id=\"app-language-select\" required>\n            <option value=\"en\">English</option>\n            <option value=\"zh-CN\">简体中文</option>\n          </select>\n        </li>\n      </ul>\n    </section>\n    <section>\n      <ul>\n        <li class=\"menu-title\">\n          <h3 id=\"app-recall-title\">阻止消息撤回</h3>\n        </li>\n        <li class=\"menu-desc\">\n          <h4 id=\"app-recall-desc\">选择是否阻止微信的消息撤回功能</h4>\n        </li>\n        <li class=\"menu-button\">\n          <select class=\"\" id=\"app-recall-select\">\n            <option value=\"on\">On</option>\n            <option value=\"off\">Off</option>\n          </select>\n        </li>\n      </ul>\n    </section>\n    <section>\n      <ul>\n        <li class=\"menu-title\">\n          <h3 id=\"app-instance-title\">是否允许开启多个实例</h3>\n        </li>\n        <li class=\"menu-desc\">\n          <h4 id=\"app-instance-desc\">多个实例可以同时登录不同帐号</h4>\n        </li>\n        <li class=\"menu-button\">\n          <select id=\"app-instance-select\">\n            <option value=\"on\">On</option>\n            <option value=\"off\">Off</option>\n          </select>\n        </li>\n      </ul>\n    </section>\n    <section>\n      <ul>\n        <li class=\"menu-title\">\n          <h3 id=\"app-icon-title\">Flash 路径 (即将上线)</h3>\n        </li>\n        <li class=\"menu-desc\">\n          <h4 id=\"app-icon-desc\">默认路径</h4>\n        </li>\n        <li class=\"menu-button\">\n          <input type=\"text\" placeholder=\"File Path...\">\n        </li>\n      </ul>\n    </section>\n    <section id=\"app-tray\">\n      <ul>\n        <li class=\"menu-title\">\n          <h3 id=\"app-tray-title\">托盘图标颜色（黑/白）</h3>\n        </li>\n        <li class=\"menu-desc\">\n          <h4 id=\"app-tray-desc\">选择一个适合当前主题的颜色</h4>\n        </li>\n        <li class=\"menu-button\">\n          <select id=\"app-tray-select\">\n            <option value=\"black\">◆</option>\n            <option value=\"white\">◇</option>\n          </select>\n        </li>\n      </ul>\n    </section>\n  </div>\n\n  <script>\n    const AppConfig = require('../../configuration');\n    const {\n      remote,\n      shell,\n      ipcRenderer\n    } = require('electron');\n\n    const lan = AppConfig.readSettings('language');\n    const recall = AppConfig.readSettings('prevent-recall');\n    const instance = AppConfig.readSettings('multi-instance');\n    const trayColor = AppConfig.readSettings('tray-color');\n\n    const lanSelect = $('app-language-select');\n    const recallSelect = $('app-recall-select');\n    const instanceSelect = $('app-instance-select');\n    const trayColorSelect = $('app-tray-select');\n\n    function $(id) {\n      return document.getElementById(id);\n    }\n    \n    if(process.platform === 'darwin') {\n      $('process.platform').style.display = 'none';\n    }\n\n    $('top-title-electron-ver').innerText = process.versions.electron;\n    setConfig();\n    setListeners()\n\n    let Common;\n    if (lan === 'zh-CN') {\n      Common = require('../../common_cn');\n    } else {\n      Common = require('../../common');\n      setLocale();\n    }\n\n    function setListeners() {\n      lanSelect.addEventListener('change', function() {\n        AppConfig.saveSettings('language', lanSelect.value)\n      })\n      recallSelect.addEventListener('change', function() {\n        AppConfig.saveSettings('prevent-recall', recallSelect.value)\n      })\n      instanceSelect.addEventListener('change', function() {\n        AppConfig.saveSettings('multi-instance', instanceSelect.value)\n      })\n      trayColorSelect.addEventListener('change', function() {\n        AppConfig.saveSettings('tray-color', trayColorSelect.value)\n        ipcRenderer.send('refreshIcon')\n      })\n    }\n\n    function setConfig() {\n      $('app-language-select').value = lan;\n      $('app-recall-select').value = recall;\n      $('app-instance-select').value = instance;\n      $('app-tray-select').value = trayColor;\n    }\n\n    function setLocale() {\n      $('app-language-title').innerHTML = Common.languageTitle;\n      $('app-language-desc').innerHTML = Common.languageDesc;\n      $('app-recall-title').innerHTML = Common.recallTitle;\n      $('app-recall-desc').innerHTML = Common.recallDesc;\n      $('app-instance-title').innerHTML = Common.instanceTitle;\n      $('app-instance-desc').innerHTML = Common.instanceDesc;\n      $('app-icon-title').innerHTML = Common.iconTitle;\n      $('app-icon-desc').innerHTML = Common.iconDesc;\n      $('app-tray-title').innerHTML = Common.trayTitle;\n      $('app-tray-desc').innerHTML = Common.trayDesc;\n      $('upgrade-btn').innerHTML = Common.UPGRADE;\n      $('feedback-btn').innerHTML = Common.FEEDBACK;\n    }\n\n    function feedback() {\n      shell.openExternal(Common.GITHUB_ISSUES);\n    }\n\n    function upgrade() {\n      ipcRenderer.send('update');\n    }\n\n    function KeyDownFn(evt) {\n      if (evt.keyCode == 73 && evt.ctrlKey && evt.shiftKey) evt.preventDefault();\n    }\n\n  </script>\n</body>\n\n</html>\n"
  },
  {
    "path": "src/windows/views/splash.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <link href=\"../styles/splash.css\" rel=\"stylesheet\">\n  <meta charset=\"UTF-8\">\n  <title>Starting App</title>\n</head>\n\n<body>\n  <div id=\"splash_container\">\n    <img id=\"splash_loading_img\" src=\"../../../assets/loading.gif\">\n    <span id=\"splash_text\">Starting App</span>\n  </div>\n</body>\n\n</html>\n"
  }
]