├── .gitignore ├── README-cn.md ├── README.md ├── art ├── 1.png ├── 2.png └── wxhelper-icons.sketch ├── bootstrap-sass.config.js ├── config.js ├── extension ├── _locales │ ├── en │ │ └── messages.json │ └── zh │ │ └── messages.json ├── app │ └── popup.html ├── img │ ├── icon_128x128.png │ ├── icon_16x16.png │ └── icon_48x48.png └── manifest.json ├── package.json ├── src ├── fonts │ ├── wxhelper-icons.eot │ ├── wxhelper-icons.svg │ ├── wxhelper-icons.ttf │ └── wxhelper-icons.woff ├── js │ ├── entries │ │ ├── app.js │ │ ├── background.js │ │ └── popup.js │ ├── node_modules │ │ ├── chrome │ │ │ ├── i18n.js │ │ │ └── local-cache.js │ │ ├── from-wechat │ │ │ └── tool.js │ │ ├── ut │ │ │ ├── ga-agent.js │ │ │ └── ga.js │ │ └── wxhelper │ │ │ ├── config.js │ │ │ ├── controller.js │ │ │ ├── template.js │ │ │ ├── ui.js │ │ │ └── wechat.js │ └── webpack-plugins │ │ └── auto-reload-extension.js ├── style │ ├── _bootstrap-customizations.scss │ ├── _pre-bootstrap-customizations.scss │ ├── app.scss │ ├── popup.scss │ └── wxhelper-icons.css └── templates │ ├── content.html │ ├── message.html │ └── popup.html └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | *.log 3 | .DS_Store 4 | dist 5 | output 6 | src/js/temp.js 7 | -------------------------------------------------------------------------------- /README-cn.md: -------------------------------------------------------------------------------- 1 | ## 微信助手 - Chrome 插件 2 | 3 | 1. 检查被谁删除 4 | 5 | 2. 检查黑名单 6 | 7 | 3. 支持大量好友 8 | 9 | ### 如何运行和调试 10 | 11 | > 微信助手 Chrome 插件开发手记 -- Chrome 插件开发参考: 12 | 13 | > http://www.liaohuqiu.net/cn/posts/wechat-helper/ 14 | 15 | 16 | 1. 安装依赖 17 | 18 | ``` 19 | npm install 20 | ``` 21 | 22 | 2. build 插件的 `content_scripts` 和 `background` 23 | 24 | ``` 25 | npm run build 26 | ``` 27 | 28 | 3. 另外一个终端,build 插件的 `popup`. 29 | 30 | ``` 31 | npm run build-popup 32 | ``` 33 | 34 | 4. 扩展管理里面,选中 `extension` 文件夹,加载扩展 35 | 36 | 从导航栏上图标点开网页版微信,插件会自动加载。 37 | 38 | 5. 代码修改后,自动会 build ,重新加载插件,刷新页面即可。 39 | 40 | 推荐使用 [Extensions Reloader](https://chrome.google.com/webstore/detail/extensions-reloader/fimgfedafeadlieiabdeeaodndnlbhid) + Spark 全局快捷键重新加载插件。 41 | 42 | 6. 支持 source-map 源码调试。 43 | 44 | > Happy coding! 45 | 46 | --- 47 | 48 | ### 使用方法 49 | 50 | 1. 安装 51 | 52 | 1. 从 Chrome 商店安装。 53 | 54 | https://chrome.google.com/webstore/detail/bdfbkchemknlpmmopkncahjdmocnambd/ 55 | 56 | 2. 本插件会频繁升级,暂不支持手动安装扩展。 57 | 58 | ~~**Windows版本** 也在开发中,即将发布。~~ 59 | 60 | **Windows 用户,请到这里**: [WeChat Helper -- Windows App](https://github.com/freedombird9/wechat-deletion-check) 61 | 62 | 2. 使用 63 | 64 | 1. 时间间隔设置为 **60** 秒以上 65 | 66 | 2. **如果提示操作过于频繁,请稍后再试,下次会跳过已经检查过的好友。** 67 | 68 | 69 | 3. 截图 70 | 71 |
72 | 73 |
74 | 75 | 76 | ### 原理 77 | 78 | 1. 检查联系人,即创建群组并加人,如果不是好友,无法加入。 79 | 80 | 2. 如果联系人多,将会分批操作,下次会略过之前检查过的联系人。 81 | 82 | ### 其他 83 | 84 | 1. 反馈 85 | 86 | 1. 有任何问题和建议,请提交到这里: https://github.com/liaohuqiu/wechat-helper/issues 87 | 88 | 2. 感谢 89 | 90 | 感谢 [@0x5e](https://github.com/0x5e) 同学的 [wechat-deleted-friends](https://github.com/0x5e/wechat-deleted-friends), 所谓巨人的肩膀。^_^ 91 | 92 | 93 | ### 许可 94 | 95 | MIT 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Wechart Helper - Chrome plug-in 2 | 3 | 1. check who deleted your wechat contact 4 | 5 | 2. check black list 6 | 7 | ### How to run and debug 8 | 9 | > Wechart helper Chrome plug-in developing note -- reference of Chrome plug-in: 10 | 11 | > http://www.liaohuqiu.net/cn/posts/wechat-helper/ 12 | 13 | 14 | 1. Installing dependencies 15 | 16 | ``` 17 | npm install 18 | ``` 19 | 20 | 2. build plug-in's `content_scripts` and `background` 21 | 22 | ``` 23 | npm run build 24 | ``` 25 | 26 | 3. Another terminal,build plug-in's `popup`. 27 | 28 | ``` 29 | npm run build-popup 30 | ``` 31 | 32 | 4. In the extension manage,select `extension` folder,load the extension. 33 | 34 | Click the icon to open web-wechart from navigation bar,plug-in will loading automatically. 35 | 36 | 5. After editing, code will build automatically. Please reloading the plug-in and fresh the page. 37 | 38 | Suggests: Use [Extensions Reloader](https://chrome.google.com/webstore/detail/extensions-reloader/fimgfedafeadlieiabdeeaodndnlbhid) + Spark shotcut key reloading plug-in. 39 | 40 | 6. Support source-map code debug. 41 | 42 | > Happy coding! 43 | 44 | --- 45 | 46 | ### How to use 47 | 48 | 1. Install 49 | 50 | 1. Install from Chrome plug-in store 51 | 52 | https://chrome.google.com/webstore/detail/bdfbkchemknlpmmopkncahjdmocnambd/ 53 | 54 | 2. wechart helper plug-in will upgrade continually, manually installing extension is not supported. 55 | 56 | **Windows users,Please click here**: [WeChat Helper -- Windows App](https://github.com/freedombird9/wechat-deletion-check) 57 | 58 | 2. Use 59 | 60 | 1. The interval between each time set would be more than **60s**. 61 | 62 | 2. **If the notic shows "You're doing it too frequently," please try later. Next time will skip the friends that already checked.** 63 | 64 | 65 | 3. Screenshot 66 | 67 |
68 | 69 |
70 | 71 | 72 | ### Theory 73 | 74 | 1. Checking contact and adding contact to chart group. Contacts don't have friendship with you would not be added into the chart group. 75 | 76 | 2. Large numbers of contacts will be checked in batches, and each check will skip the already-checked contacts. 77 | 78 | ### Other 79 | 80 | 1. Feedback 81 | 82 | 1. If you have any question or suggestion, please send to: https://github.com/liaohuqiu/wechat-helper/issues 83 | 84 | 2. Thanks 85 | 86 | Thanks [@0x5e](https://github.com/0x5e) 's [wechat-deleted-friends](https://github.com/0x5e/wechat-deleted-friends) 87 | 88 | 89 | ### permit 90 | 91 | MIT 92 | -------------------------------------------------------------------------------- /art/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/art/1.png -------------------------------------------------------------------------------- /art/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/art/2.png -------------------------------------------------------------------------------- /art/wxhelper-icons.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/art/wxhelper-icons.sketch -------------------------------------------------------------------------------- /bootstrap-sass.config.js: -------------------------------------------------------------------------------- 1 | var config = require('./config.js'); 2 | var dir = './src/style/'; 3 | 4 | var data = { 5 | // Use preBootstrapCustomizations to change $brand-primary. Ensure this preBootstrapCustomizations does not 6 | // depend on other bootstrap variables. 7 | preBootstrapCustomizations: dir + "_pre-bootstrap-customizations.scss", 8 | 9 | // Use bootstrapCustomizations to utilize other sass variables defined in preBootstrapCustomizations or the 10 | // _variables.scss file. This is useful to set one customization value based on another value. 11 | bootstrapCustomizations: dir + "_bootstrap-customizations.scss", 12 | 13 | // mainSass: "./_main.scss", 14 | verbose: true, 15 | debug: false, 16 | // Default for the style loading is to put in your js files 17 | // styleLoader: "style-loader!css-loader!sass-loader"; 18 | 19 | // If you want to use the ExtractTextPlugin 20 | // and you want compressed 21 | // styleLoader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader"), 22 | // or if you want expanded CSS 23 | // styleLoader: ExtractTextPlugin.extract("style-loader", "css-loader!sass?outputStyle=expanded"), 24 | 25 | // ### Scripts 26 | // Any scripts here set to false will never 27 | // make it to the client, it's not packaged 28 | // by webpack. 29 | scripts: { 30 | /* 31 | 'transition': true, 32 | 'alert': true, 33 | 'button': true, 34 | 'carousel': true, 35 | 'collapse': true, 36 | 'dropdown': true, 37 | 'modal': true, 38 | 'tooltip': true, 39 | 'popover': true, 40 | 'scrollspy': true, 41 | 'tab': true, 42 | 'affix': true 43 | */ 44 | }, 45 | // ### Styles 46 | // Enable or disable certain less components and thus remove 47 | // the css for them from the build. 48 | styles: config.getBootstrapConfig(), 49 | /* 50 | "mixins": true, 51 | "normalize": true, 52 | 53 | "print": true, 54 | "glyphicons": true, 55 | 56 | "scaffolding": true, 57 | "type": true, 58 | "code": true, 59 | "grid": true, 60 | "tables": true, 61 | "forms": true, 62 | "buttons": true, 63 | 64 | "component-animations": true, 65 | "dropdowns": true, 66 | "button-groups": true, 67 | "input-groups": true, 68 | "navs": true, 69 | "navbar": true, 70 | "breadcrumbs": true, 71 | "pagination": true, 72 | "pager": true, 73 | "labels": true, 74 | "badges": true, 75 | "jumbotron": true, 76 | "thumbnails": true, 77 | "alerts": true, 78 | "progress-bars": true, 79 | "media": true, 80 | "list-group": true, 81 | "panels": true, 82 | "wells": true, 83 | "responsive-embed": true, 84 | "close": true, 85 | 86 | "modals": true, 87 | "tooltip": true, 88 | "popovers": true, 89 | "carousel": true, 90 | 91 | "utilities": true, 92 | "responsive-utilities": true 93 | */ 94 | }; 95 | 96 | if (config.shouldExtractCss()) { 97 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 98 | data['styleLoader'] = ExtractTextPlugin.extract("style-loader", "css-loader!sass?outputStyle=expanded"); 99 | } 100 | 101 | module.exports = data; 102 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var cube = require('cube-js'); 2 | var env = process.env.env || 'prod'; 3 | var is_dev = env == 'dev'; 4 | var target = process.env.target; 5 | 6 | var path = require('path'); 7 | 8 | var root_dir = __dirname; 9 | var template_dir = path.join(root_dir, 'src/templates'); 10 | var style_dir = path.join(root_dir, 'src/style'); 11 | var swig = require('swig'); 12 | 13 | var i18n_default_file = path.join(root_dir, '/extension/_locales/en/messages.json'); 14 | var bootstrap_sass = 'bootstrap-sass!' + root_dir + '/bootstrap-sass.config.js'; 15 | 16 | // trace, debug, info, warn, error 17 | var loglevel = process.env.loglevel || 'error'; 18 | if (is_dev) { 19 | loglevel = 'trace'; 20 | } 21 | 22 | var config = { 23 | 24 | getDefineVar: function() { 25 | var data = { 26 | 27 | __env__: JSON.stringify(env), 28 | __is_dev__: is_dev, 29 | __log_level__: JSON.stringify(loglevel), 30 | 31 | __style_dir__: JSON.stringify(style_dir), 32 | __template_dir__: JSON.stringify(template_dir), 33 | 34 | __bootstrap_sass__: JSON.stringify(bootstrap_sass), 35 | __i18n_default_file__: JSON.stringify(i18n_default_file), 36 | 37 | __ga_tracking_code__: JSON.stringify('UA-43024238-11'), 38 | } 39 | return data; 40 | }, 41 | 42 | shouldExtractCss: function() { 43 | if (env != 'dev' || target == 'popup') { 44 | return true; 45 | } 46 | return false; 47 | }, 48 | 49 | getBootstrapConfig() { 50 | if (target == 'popup') { 51 | return { 52 | mixins: true, 53 | normalize: true, 54 | // glyphicons: true, 55 | // utilities: true, 56 | // alerts: true, 57 | buttons: true, 58 | }; 59 | } else { 60 | return { 61 | glyphicons: true, 62 | }; 63 | } 64 | }, 65 | 66 | getCssFileName: function() { 67 | var css_filename = 'app.css'; 68 | if (target == 'popup') { 69 | css_filename = 'popup.css'; 70 | } 71 | return css_filename; 72 | } 73 | 74 | }; 75 | 76 | module.exports = config; 77 | -------------------------------------------------------------------------------- /extension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": { 3 | "message": "WeChat Helper" 4 | }, 5 | "check_friend": { 6 | "message": "Check Friend" 7 | }, 8 | "check_friend_time_limit": { 9 | "message": "delay:" 10 | }, 11 | "clear_log": { 12 | "message": "Clear Log" 13 | }, 14 | "doc_check_init_start": { 15 | "message": "initializing..." 16 | }, 17 | "doc_check_init_done": { 18 | "message": "initialized." 19 | }, 20 | "doc_begin_check": { 21 | "message": "start checking..." 22 | }, 23 | "doc_get_contact_list": { 24 | "message": "updating contact list..." 25 | }, 26 | "doc_get_contact_list_done": { 27 | "message": "updating finished, %s contacts in total" 28 | }, 29 | "doc_get_contact_list_error": { 30 | "message": "error occured when updating contact list" 31 | }, 32 | "doc_many_friend_warning": { 33 | "message": "You have many contacts, it may take longer time than usual" 34 | }, 35 | "doc_unlogin": { 36 | "message": "Login undetected, please scan the QR code to login" 37 | }, 38 | "doc_resume_from_last": { 39 | "message": "skip %s contacts that have been inspected before" 40 | }, 41 | "doc_split_group": { 42 | "message": "%s contacts are to be divided into %s groups, with %s people each" 43 | }, 44 | "doc_check_friend_round": { 45 | "message": "processing group number %s, which may take %s seconds" 46 | }, 47 | "doc_delete_list_tip": { 48 | "message": "You have been deleted by: %s" 49 | }, 50 | "doc_black_list_tip": { 51 | "message": "You have been blocked by: %s" 52 | }, 53 | "doc_check_friend_done": { 54 | "message": "All your contacts have been successfully processed" 55 | }, 56 | "doc_all_contact_ok": { 57 | "message": "None of your contacts deleted or blocked you" 58 | }, 59 | "doc_try_add_member": { 60 | "message": "Trying to add the contacts to a temporary group, please hold on" 61 | }, 62 | "doc_try_remove_member": { 63 | "message": "Deleting the contacts from the group, please hold on" 64 | }, 65 | "create_chat_room_error": { 66 | "message": "Creating group failed, please try again" 67 | }, 68 | "unknow_error": { 69 | "message": "unknown network error" 70 | }, 71 | "donate_tip": { 72 | "message": "Your donations will be greatly appreciated" 73 | }, 74 | "open_web_wechat": { 75 | "message": "Open Web Wechat" 76 | }, 77 | "about": { 78 | "message": "About" 79 | }, 80 | "about_link": { 81 | "message": "http://www.liaohuqiu.net/cn/posts/wechat-helper/" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /extension/_locales/zh/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": { 3 | "message": "微信助手" 4 | }, 5 | "check_friend": { 6 | "message": "检查好友情况" 7 | }, 8 | "check_friend_time_limit": { 9 | "message": "间隔:" 10 | }, 11 | "clear_log": { 12 | "message": "清除日志" 13 | }, 14 | "doc_check_init_start": { 15 | "message": "开始初始化..." 16 | }, 17 | "doc_check_init_done": { 18 | "message": "初始化完成" 19 | }, 20 | "doc_begin_check": { 21 | "message": "开始检查..." 22 | }, 23 | "doc_get_contact_list": { 24 | "message": "更新联系人列表..." 25 | }, 26 | "doc_get_contact_list_done": { 27 | "message": "更新完成,共 %s 个联系人" 28 | }, 29 | "doc_get_contact_list_error": { 30 | "message": "更新联系人列表出错" 31 | }, 32 | "doc_many_friend_warning": { 33 | "message": "所需处理的好友数量较多,所需时间可能较长" 34 | }, 35 | "doc_unlogin": { 36 | "message": "未登录,请扫描二维码登录" 37 | }, 38 | "doc_resume_from_last": { 39 | "message": "跳过 %s 个之前已经处理过的联系人" 40 | }, 41 | "doc_split_group": { 42 | "message": "%s 个联系人被分割为 %s 组,每组 %s 人" 43 | }, 44 | "doc_check_friend_round": { 45 | "message": "处理第 %s 批联系人,需要 %s 秒,请稍侯" 46 | }, 47 | "doc_delete_list_tip": { 48 | "message": "%s 已经把你删除" 49 | }, 50 | "doc_black_list_tip": { 51 | "message": "%s 已经把你加入黑名单" 52 | }, 53 | "doc_check_friend_done": { 54 | "message": "所有联系人处理完毕" 55 | }, 56 | "doc_all_contact_ok": { 57 | "message": "未发现被删除或黑名单" 58 | }, 59 | "doc_try_add_member": { 60 | "message": "尝试将联系人加入临时群组,请稍侯" 61 | }, 62 | "doc_try_remove_member": { 63 | "message": "将联系人从临时群组移除,请稍侯" 64 | }, 65 | "create_chat_room_error": { 66 | "message": "创建临时群组失败,请重试。" 67 | }, 68 | "unknow_error": { 69 | "message": "未知网络错误。" 70 | }, 71 | "donate_tip": { 72 | "message": "如果你觉得这个插件不错,欢迎打赏。" 73 | }, 74 | "open_web_wechat": { 75 | "message": "打开微信网页版" 76 | }, 77 | "about": { 78 | "message": "关于" 79 | }, 80 | "about_link": { 81 | "message": "http://www.liaohuqiu.net/cn/posts/wechat-helper/" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /extension/app/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List sorter 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extension/img/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/extension/img/icon_128x128.png -------------------------------------------------------------------------------- /extension/img/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/extension/img/icon_16x16.png -------------------------------------------------------------------------------- /extension/img/icon_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/extension/img/icon_48x48.png -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_app_name__", 4 | "default_locale": "en", 5 | "version": "0.0.8", 6 | "description": "It helps you to manage your wechat.", 7 | "permissions": [ 8 | "tabs", 9 | "http://*/*", 10 | "https://*/*", 11 | "storage", 12 | "notifications", 13 | "" 14 | ], 15 | "background": { 16 | "scripts": ["dist/js/background.js"], 17 | "persistent": true 18 | }, 19 | "web_accessible_resources": [ 20 | "dist/*", 21 | "img/*" 22 | ], 23 | "content_scripts" : [ 24 | { 25 | "js" : [ 26 | "dist/js/app.js" 27 | ], 28 | "css" : [ 29 | "dist/css/app.css" 30 | ], 31 | "matches" : [ 32 | "*://*.blog.srain.in/*", 33 | "*://*.wechat.com/*", 34 | "*://web1.wechatapp.com/*", 35 | "*://wx.qq.com/*", 36 | "*://wx1.qq.com/*", 37 | "*://wx2.qq.com/*" 38 | ] 39 | } 40 | ], 41 | "content_security_policy": "script-src 'self' 'unsafe-eval' https://www.google-analytics.com https://ssl.google-analytics.com; object-src 'self'", 42 | "icons": { 43 | "16": "./img/icon_16x16.png", 44 | "48": "./img/icon_48x48.png", 45 | "128": "./img/icon_128x128.png" 46 | }, 47 | "browser_action": { 48 | "default_popup": "./app/popup.html", 49 | "default_title": "WeChat Helper", 50 | "default_icon": "./img/icon_128x128.png" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat-delete-friend", 3 | "engines": { 4 | "node": "^4.1.2" 5 | }, 6 | "version": "0.0.1", 7 | "private": true, 8 | "scripts": { 9 | "clean-map": "rm -rf ./extension/dist/*/*.map", 10 | "empty-css": "rm -rf ./extension/dist/css/*; echo '' > ./extension/dist/css/app.css", 11 | "build-popup": "env=dev target=popup webpack --progress --colors --watch -d", 12 | "build-popup-prod": "npm run clean-map && env=prod target=popup webpack --progress --colors -p", 13 | "build": "mkdir -p extension/dist/css && npm run empty-css && env=dev webpack --progress --colors --watch -d", 14 | "build-prod": "npm run clean-map && env=prod webpack --progress --colors -p", 15 | "update-cube": "rm -rf node_modules/cube-js/lib; cp -rf ../cube-js/lib node_modules/cube-js/" 16 | }, 17 | "author": "liaohuqiu@gmail.com", 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "babel-core": "^6.3.26", 21 | "babel-loader": "^6.2.0", 22 | "babel-preset-es2015": "^6.3.13", 23 | "bootstrap-sass": "~3.3.1", 24 | "bootstrap-sass-loader": "^1.0.9", 25 | "css-loader": "^0.23.0", 26 | "cube-js": "0.0.4", 27 | "expose-loader": "^0.7.1", 28 | "extract-text-webpack-plugin": "^0.9.1", 29 | "file-loader": "^0.8.4", 30 | "flexslider": "^2.5.0", 31 | "imports-loader": "^0.6.5", 32 | "jquery": "^2.1.4", 33 | "jquery-ui": "^1.10.5", 34 | "json-loader": "^0.5.4", 35 | "less": "^2.5.3", 36 | "less-loader": "^2.2.1", 37 | "loglevel": "^1.4.0", 38 | "node-sass": "^3.4.2", 39 | "q": "^1.4.1", 40 | "raw-loader": "^0.5.1", 41 | "sass-loader": "^3.1.1", 42 | "sprintf-js": "^1.0.3", 43 | "style-loader": "^0.13.0", 44 | "swig": "^1.4.2", 45 | "url-loader": "^0.5.6", 46 | "webpack": "^1.12.2" 47 | }, 48 | "babel": { 49 | "presets": [ 50 | "es2015" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/fonts/wxhelper-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/src/fonts/wxhelper-icons.eot -------------------------------------------------------------------------------- /src/fonts/wxhelper-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/fonts/wxhelper-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/src/fonts/wxhelper-icons.ttf -------------------------------------------------------------------------------- /src/fonts/wxhelper-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/src/fonts/wxhelper-icons.woff -------------------------------------------------------------------------------- /src/js/entries/app.js: -------------------------------------------------------------------------------- 1 | // load bootstrap 2 | require(__bootstrap_sass__); 3 | 4 | import cube from 'cube-js'; 5 | import log from 'loglevel'; 6 | import jquery from 'jquery' 7 | import Q from 'q'; 8 | 9 | import UI from 'wxhelper/ui.js' 10 | import ga from 'ut/ga-agent.js'; 11 | import Controller from 'wxhelper/controller.js' 12 | 13 | window.$ = jquery; 14 | log.setLevel(__log_level__); 15 | 16 | var App = function() { 17 | }; 18 | 19 | cube.mix(App.prototype, { 20 | 21 | run: function() { 22 | 23 | var controller = new Controller(); 24 | var ui = new UI(controller); 25 | 26 | ui.renderThenShow(); 27 | controller.start(); 28 | 29 | ga('send', 'pageview','/wechat'); 30 | ga.auto(); 31 | }, 32 | }); 33 | 34 | var app = new App(); 35 | app.run(); 36 | -------------------------------------------------------------------------------- /src/js/entries/background.js: -------------------------------------------------------------------------------- 1 | import ga from 'ut/ga'; 2 | 3 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 4 | if (request['action'] == 'ga') { 5 | var data = request['data']; 6 | ga.apply(null, data); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/js/entries/popup.js: -------------------------------------------------------------------------------- 1 | // load bootstrap 2 | require(__bootstrap_sass__); 3 | // load style 4 | require(__style_dir__ + '/popup.scss'); 5 | 6 | import cube from 'cube-js'; 7 | import log from 'loglevel'; 8 | import jquery from 'jquery' 9 | 10 | import i18n from 'chrome/i18n' 11 | import ga from 'ut/ga-agent'; 12 | import template from 'wxhelper/template.js' 13 | 14 | window.$ = jquery; 15 | log.setLevel(__log_level__); 16 | 17 | var App = function() { 18 | }; 19 | 20 | cube.mix(App.prototype, { 21 | 22 | run: function() { 23 | 24 | var data = { 25 | i18n: i18n 26 | }; 27 | 28 | var body = $(template.renderHtml('popup', data)); 29 | $('body').append(body); 30 | 31 | ga('send', 'pageview','/popup'); 32 | ga.auto(); 33 | }, 34 | }); 35 | 36 | $(window).load(function(){ 37 | var app = new App(); 38 | app.run(); 39 | }); 40 | -------------------------------------------------------------------------------- /src/js/node_modules/chrome/i18n.js: -------------------------------------------------------------------------------- 1 | import cube from 'cube-js'; 2 | 3 | var json = require('json!' + __i18n_default_file__); 4 | 5 | var keys = cube.keys(json); 6 | var message_list = {}; 7 | cube.forEach(keys, function(key) { 8 | message_list[key] = chrome.i18n.getMessage(key); 9 | }); 10 | export default message_list; 11 | -------------------------------------------------------------------------------- /src/js/node_modules/chrome/local-cache.js: -------------------------------------------------------------------------------- 1 | var cube = require('cube-js'); 2 | var log = require('loglevel'); 3 | 4 | var LocalCache = function() { 5 | }; 6 | 7 | cube.mix(LocalCache, { 8 | set: function(key, value, ttl) { 9 | log.debug('LocalCache::set', key, value); 10 | var data = {}; 11 | data[key] = { 12 | ctime: Date.now(), 13 | ttl: ttl, 14 | value: value 15 | }; 16 | chrome.storage.local.set(data); 17 | }, 18 | 19 | get: function(key, done) { 20 | 21 | chrome.storage.local.get(key, function(data) { 22 | log.debug('LocalCache::get', key, data); 23 | if (data && data[key]) { 24 | var rawData = data[key]; 25 | 26 | var value = rawData['value']; 27 | var ttl = rawData['ttl']; 28 | if (ttl && (Date.now() - rawData['ctime'] > ttl)) { 29 | value = null; 30 | } 31 | done(value); 32 | } else { 33 | done(null); 34 | } 35 | }); 36 | }, 37 | }); 38 | 39 | var PrefixLocalCache = function(prefix) { 40 | this.prefix = prefix; 41 | }; 42 | 43 | 44 | cube.mix(PrefixLocalCache.prototype, { 45 | setPrefix: function(prefix) { 46 | this.prefix = prefix; 47 | }, 48 | 49 | set: function(key, value, ttl) { 50 | if (!this.prefix) { 51 | throw new Error('prefix is empty'); 52 | } 53 | key = this.prefix + '_' + key; 54 | LocalCache.set(key, value, ttl); 55 | }, 56 | 57 | get: function(key, done) { 58 | if (!this.prefix) { 59 | throw new Error('prefix is empty'); 60 | } 61 | key = this.prefix + '_' + key; 62 | LocalCache.get(key, done); 63 | } 64 | }); 65 | 66 | module.exports.LocalCache = LocalCache; 67 | module.exports.PrefixLocalCache = PrefixLocalCache; 68 | -------------------------------------------------------------------------------- /src/js/node_modules/from-wechat/tool.js: -------------------------------------------------------------------------------- 1 | var tool = { 2 | isLog: !1, 3 | log: function() { 4 | this.isLog && console.log(arguments) 5 | }, 6 | now: function() { 7 | return +new Date 8 | }, 9 | getCookie: function(e) { 10 | for (var t = e + "=", o = document.cookie.split(";"), n = 0; n < o.length; n++) { 11 | for (var r = o[n]; " " == r.charAt(0); ) 12 | r = r.substring(1); 13 | if (-1 != r.indexOf(t)) 14 | return r.substring(t.length, r.length) 15 | } 16 | return "" 17 | }, 18 | setCookie: function(e, t, o) { 19 | var n = new Date; 20 | n.setTime(n.getTime() + 24 * o * 60 * 60 * 1e3); 21 | var r = "expires=" + n.toUTCString(); 22 | document.cookie = e + "=" + t + "; " + r 23 | }, 24 | clearCookie: function() { 25 | for (var e = document.cookie.split(";"), t = 0; t < e.length; t++) { 26 | var o = e[t] 27 | , n = o.indexOf("=") 28 | , r = n > -1 ? o.substr(0, n) : o; 29 | document.cookie = r + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT" 30 | } 31 | }, 32 | getLocalStorage: function() { 33 | return window.localStorage || { 34 | getItem: function() { 35 | return void 0 36 | }, 37 | setItem: function() {}, 38 | removeItem: function() {}, 39 | key: function() { 40 | return "" 41 | } 42 | } 43 | }, 44 | htmlEncode: function(e) { 45 | return angular.isString(e) ? e.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(//g, ">") : "" 46 | }, 47 | htmlDecode: function(e) { 48 | return e && 0 != e.length ? e.replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, '"').replace(/&/g, "&") : "" 49 | }, 50 | hrefEncode: function(e) { 51 | var t = this 52 | , o = e.match(/<a href=(?:'|").*?(?:'|").*?>.*?<\/a>/g); 53 | if (o) { 54 | for (var n, r, a = 0, c = o.length; c > a; ++a) 55 | n = /<a href=(?:'|")(.*?)(?:'|").*?>.*?<\/a>/.exec(o[a]), 56 | n && n[1] && (r = n[1], 57 | t.isUrl(r) && (e = e.replace(n[0], this.htmlDecode(n[0])).replace(n[1], u.genCheckURL(n[1])))); 58 | return e 59 | } 60 | return e.replace(new RegExp(i,"ig"), function() { 61 | return '' + arguments[0] + "" 62 | }) 63 | }, 64 | clearHtmlStr: function(e) { 65 | return e ? e.replace(/<[^>]*>/g, "") : e 66 | }, 67 | clearLinkTag: function(e) { 68 | return e 69 | }, 70 | setCheckUrl: function(e) { 71 | r = "&skey=" + encodeURIComponent(e.getSkey()) + "&deviceid=" + encodeURIComponent(e.getDeviceID()) + "&pass_ticket=" + encodeURIComponent(e.getPassticket()) + "&opcode=2&scene=1&username=" + e.getUserName() 72 | }, 73 | genCheckURL: function(e) { 74 | if (!r) 75 | throw "_checkURLsuffix is not ready!"; 76 | return o.API_webwxcheckurl + "?requrl=" + encodeURIComponent((0 == e.indexOf("http") ? "" : "http://") + u.clearHtmlStr(u.htmlDecode(e))) + r 77 | }, 78 | isUrl: function(e) { 79 | return new RegExp(i,"i").test(e) 80 | }, 81 | formatNum: function(e, t) { 82 | var o = (isNaN(e) ? 0 : e).toString() 83 | , n = t - o.length; 84 | return n > 0 ? [new Array(n + 1).join("0"), o].join("") : o 85 | }, 86 | getServerTime: function() { 87 | return (new Date).getTime() 88 | }, 89 | globalEval: function(e) { 90 | e && /\S/.test(e) && (window.execScript || function(e) { 91 | window.eval.call(window, e) 92 | } 93 | )(e) 94 | }, 95 | evalVal: function(e) { 96 | var t, o = "a" + this.now(); 97 | return this.globalEval(["(function(){try{window.", o, "=", e, ";}catch(_oError){}})();"].join("")), 98 | t = window[o], 99 | window[o] = null , 100 | t 101 | }, 102 | browser: function() { 103 | var e, t = navigator.userAgent.toLowerCase(); 104 | if (null != t.match(/trident/)) 105 | e = { 106 | browser: "msie", 107 | version: null != t.match(/msie ([\d.]+)/) ? t.match(/msie ([\d.]+)/)[1] : t.match(/rv:([\d.]+)/)[1] 108 | }; 109 | else { 110 | var o = /(msie) ([\w.]+)/.exec(t) || /(chrome)[ \/]([\w.]+)/.exec(t) || /(webkit)[ \/]([\w.]+)/.exec(t) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(t) || t.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(t) || []; 111 | e = { 112 | browser: o[1] || "", 113 | version: o[2] || "0" 114 | } 115 | } 116 | var n = {}; 117 | return e.browser && (n[e.browser] = !0, 118 | n.version = e.version), 119 | n.chrome ? n.webkit = !0 : n.webkit && (n.safari = !0), 120 | n 121 | }(), 122 | isSpUser: function(e) { 123 | for (var t = 0, o = c.length; o > t; t++) 124 | if (c[t] === e || /@qqim$/.test(e)) 125 | return !0; 126 | return !1 127 | }, 128 | isShieldUser: function(e) { 129 | if (/@lbsroom$/.test(e) || /@talkroom$/.test(e)) 130 | return !0; 131 | for (var t = 0, o = s.length; o > t; ++t) 132 | if (s[t] == e) 133 | return !0; 134 | return !1 135 | }, 136 | isRoomContact: function(e) { 137 | return e ? /^@@|@chatroom$/.test(e) : !1 138 | }, 139 | getContactHeadImgUrl: function(e) { 140 | return (this.isRoomContact(e.UserName) ? o.API_webwxgetheadimg : o.API_webwxgeticon) + "?seq=0&username=" + e.UserName + "&skey=" + e.Skey + (e.MsgId ? "&msgid=" + e.MsgId : "") + (e.EncryChatRoomId ? "&chatroomid=" + e.EncryChatRoomId : "") 141 | }, 142 | form: function(e, t) { 143 | t = t || {}; 144 | var o, n = []; 145 | n.push('
'); 146 | for (var r in t) 147 | n.push(''); 148 | n.push("
"), 149 | o = angular.element(n.join(""))[0], 150 | document.body.appendChild(o), 151 | o.submit() 152 | }, 153 | queryParser: function() { 154 | for (var e = {}, t = location.search.substring(1), o = t.split("&"), n = 0, r = o.length; r > n; n++) { 155 | var a = o[n].split("=") 156 | , i = decodeURIComponent(a[0]); 157 | e[i] = decodeURIComponent(a[1] || "") 158 | } 159 | return e 160 | }, 161 | getSize: function(e) { 162 | if (e = +e) { 163 | var t = 10 164 | , o = 10 165 | , n = 20 166 | , r = 1 << o 167 | , a = 1 << n; 168 | if (e >> n > 0) { 169 | var i = Math.round(e * t / a) / t; 170 | return "" + i + "MB" 171 | } 172 | if (e >> o - 1 > 0) { 173 | var c = Math.round(e * t / r) / t; 174 | return "" + c + "KB" 175 | } 176 | return "" + e + "B" 177 | } 178 | }, 179 | xml2json: function(e) { 180 | if (!e) 181 | return {}; 182 | try { 183 | var t = e.indexOf("<"); 184 | return t && (e = e.substr(t)), 185 | $.xml2json(e) 186 | } catch (o) { 187 | return console.error(o), 188 | {} 189 | } 190 | }, 191 | encodeEmoji: function(e) { 192 | return e = e || "", 193 | e = e.replace(/<\/span>/g, "###__EMOJI__$1__###") 194 | }, 195 | decodeEmoji: function(e) { 196 | return e = e || "", 197 | e = e.replace(/###__EMOJI__(emoji emoji[a-zA-Z0-9]+)__###/g, '') 198 | }, 199 | removeHtmlStrTag: function(e) { 200 | return e = e || "", 201 | e = this.encodeEmoji(e), 202 | e = this.htmlDecode(e), 203 | e = this.clearHtmlStr(e), 204 | e = this.decodeEmoji(e) 205 | }, 206 | safeDigest: function(e) { 207 | e = e || t, 208 | e.$$phase || e.$digest() 209 | }, 210 | wait: function(e, t, o) { 211 | var o = o || 10; 212 | setTimeout(function n() { 213 | e() ? t() : setTimeout(n, o) 214 | }, o) 215 | }, 216 | // fitRun: n, 217 | findIndex: function(e, t) { 218 | for (var o = 0; o < e.length; o++) 219 | if (e[o] == t) 220 | return o; 221 | return -1 222 | }, 223 | genEmoticonHTML: function(e, t) { 224 | return '' 225 | }, 226 | getShareObject: function(e) { 227 | return a[e] = a[e] || {}, 228 | a[e] 229 | }, 230 | isUserName: function() {}, 231 | isWindows: /windows/gi.test(navigator.userAgent), 232 | isMacOS: /macintosh/gi.test(navigator.userAgent), 233 | isIPad: /ipad/gi.test(navigator.userAgent) 234 | }; 235 | 236 | export default tool 237 | -------------------------------------------------------------------------------- /src/js/node_modules/ut/ga-agent.js: -------------------------------------------------------------------------------- 1 | import cube from 'cube-js'; 2 | 3 | var gaAgent = function() { 4 | chrome.runtime.sendMessage({ 5 | action: 'ga', 6 | data: cube.Array.toArray(arguments) 7 | }); 8 | } 9 | 10 | cube.mix(gaAgent, { 11 | auto: function() { 12 | document.body.addEventListener('click', function(e){ 13 | if(e.target && e.target.hasAttribute('data-ga')) { 14 | 15 | // you can add `data-ga="event category|event action[|event lable]"` attribe on an element 16 | var data = e.target.getAttribute('data-ga'); 17 | var args = data.split('|'); 18 | if (args.length >= 2) { 19 | args.unshift('event'); 20 | args.unshift('send'); 21 | gaAgent.apply(null, args); 22 | } 23 | } 24 | }); 25 | } 26 | }); 27 | 28 | module.exports = gaAgent; 29 | -------------------------------------------------------------------------------- /src/js/node_modules/ut/ga.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof window !== 'undefined' && typeof GA_TRACKING_CODE !== 'undefined') { 4 | (function(window, document, script, url, r, tag, firstScriptTag) { 5 | window['GoogleAnalyticsObject'] = r; 6 | window[r] = window[r] || function() { 7 | (window[r].q = window[r].q || []).push(arguments) 8 | }; 9 | window[r].l = 1 * new Date(); 10 | tag = document.createElement(script), 11 | firstScriptTag = document.getElementsByTagName(script)[0]; 12 | tag.async = 1; 13 | tag.src = url; 14 | firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 15 | })( 16 | window, 17 | document, 18 | 'script', 19 | 'https://www.google-analytics.com/analytics.js', 20 | 'ga' 21 | ); 22 | 23 | var ga = window.ga; 24 | ga('create', GA_TRACKING_CODE, 'auto'); 25 | 26 | // Removes failing protocol check. @see: http://stackoverflow.com/a/22152353/1958200 27 | // very important for chrome extension 28 | ga('set', 'checkProtocolTask', function(){}); 29 | 30 | module.exports = function() { 31 | return window.ga.apply(window.ga, arguments); 32 | }; 33 | } else { 34 | module.exports = function() { 35 | console.log(arguments) 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/js/node_modules/wxhelper/config.js: -------------------------------------------------------------------------------- 1 | var specialUsers = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail', 'fmessage', 'tmessage', 'qmessage', 'qqsync', 'floatbottle', 'lbsapp', 'shakeapp', 'medianote', 'qqfriend', 'readerapp', 'blogapp', 'facebookapp', 'masssendapp', 'meishiapp', 'feedsapp', 'voip', 'blogappweixin', 'weixin', 'brandsessionholder', 'weixinreminder', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c', 'officialaccounts', 'notification_messages', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c', 'wxitil', 'userexperience_alarm', 'notification_messages']; 2 | 3 | export { specialUsers as SpecialUsers }; 4 | 5 | export const runtime = { 6 | time_limit: 60000 7 | } 8 | -------------------------------------------------------------------------------- /src/js/node_modules/wxhelper/controller.js: -------------------------------------------------------------------------------- 1 | import cube from 'cube-js'; 2 | 3 | import Q from 'q'; 4 | import log from 'loglevel'; 5 | import S from 'sprintf-js'; 6 | 7 | import jquery from 'jquery' 8 | 9 | import WeChat from './wechat' 10 | import ga from 'ut/ga-agent'; 11 | import i18n from 'chrome/i18n' 12 | import { runtime } from './config' 13 | import {LocalCache, PrefixLocalCache} from 'chrome/local-cache' 14 | 15 | window.$ = jquery; 16 | log.setLevel('trace'); 17 | 18 | var Controller = function() { 19 | 20 | this.blackList = {}; 21 | this.deleteList = {}; 22 | 23 | this.lastUserName = null, 24 | this.tempChatRoomName = null; 25 | 26 | this.tempContactList = []; 27 | this.localCache = null; 28 | }; 29 | 30 | cube.mix(Controller.prototype, { 31 | 32 | blackList: {}, 33 | deleteList: {}, 34 | 35 | lastUserName: null, 36 | tempChatRoomName: null, 37 | 38 | roundTime: 1, 39 | tempContactList: [], 40 | localCache: null, 41 | 42 | loadLastData: function(done) { 43 | var me = this; 44 | this.localCache.get('last_data', function(data) { 45 | log.debug('loadLastData', data); 46 | cube.mix(me, data); 47 | done(); 48 | }); 49 | }, 50 | 51 | flushLastData: function() { 52 | var data = { 53 | blackList: this.blackList, 54 | deleteList: this.deleteList, 55 | lastUserName: this.lastUserName, 56 | tempChatRoomName: this.tempChatRoomName, 57 | } 58 | log.debug('flushLastData', data); 59 | this.localCache.set('last_data', data); 60 | }, 61 | 62 | start: function() { 63 | this.checkInit(); 64 | }, 65 | 66 | checkInit: function(done) { 67 | 68 | log.info('checkInit'); 69 | cube.fire('message', 'info', i18n['doc_check_init_start']) 70 | 71 | this.wechat = new WeChat(); 72 | 73 | var me = this; 74 | this.wechat.init(function(e){ 75 | 76 | log.info('checkInit done'); 77 | cube.fire('message', 'info', i18n['doc_check_init_done']) 78 | me.has_init = true; 79 | 80 | var userName = me.wechat.userInfo['UserName']; 81 | me.localCache = new PrefixLocalCache(userName); 82 | done(); 83 | }); 84 | }, 85 | 86 | checkFriend: function() { 87 | log.info('checkFriend'); 88 | var me = this; 89 | 90 | if (!this.has_init) { 91 | log.info('not login, login first'); 92 | this.checkInit(function() { 93 | me.checkFriend(); 94 | }); 95 | return; 96 | } 97 | 98 | cube.fire('message', 'info', i18n['doc_get_contact_list']) 99 | 100 | this.wechat.getContactList(function() { 101 | log.info('getContactList done'); 102 | me.loadLastData(function() { 103 | log.debug('loadLastData resolve'); 104 | me.divideListByGroup(); 105 | me.processMemberListGroup(); 106 | }); 107 | }); 108 | }, 109 | 110 | divideListByGroup: function() { 111 | var rawList = cube.Array.toArray(this.wechat.contactList); 112 | rawList = cube.sortBy(rawList, function(item, key) { 113 | return item['UserName']; 114 | }); 115 | 116 | var friendAmount = rawList.length; 117 | ga('send', 'event', 'friend_num', 'stat', 'friend_num', friendAmount); 118 | 119 | if (this.lastUserName) { 120 | for (var i = 0; i < friendAmount; i++) { 121 | if (rawList[i]['UserName'] == this.lastUserName) { 122 | var skip = i + 1; 123 | rawList = rawList.slice(skip); 124 | var msg = S.sprintf(i18n['doc_resume_from_last'], skip); 125 | cube.fire('message', 'info', msg); 126 | break; 127 | } 128 | } 129 | } 130 | 131 | var num_per_group = 35; 132 | var listGroup = cube.Array.chunk(rawList, num_per_group); 133 | this.listGroup = listGroup; 134 | 135 | var msg = S.sprintf(i18n['doc_split_group'], friendAmount, listGroup.length, num_per_group); 136 | cube.fire('message', 'info', msg); 137 | if (friendAmount > 500) { 138 | cube.fire('message', 'info', i18n['doc_many_friend_warning']); 139 | } 140 | }, 141 | 142 | finishWork: function() { 143 | 144 | this.displayBadList(this.deleteList, this.blackList); 145 | 146 | this.roundTime = 1; 147 | this.lastUserName = null; 148 | this.tempChatRoomName = null; 149 | this.flushLastData(); 150 | }, 151 | 152 | displayBadList: function(deleteList, blackList) { 153 | 154 | deleteList = cube.keys(deleteList); 155 | blackList = cube.keys(blackList); 156 | 157 | var contactList = this.wechat.contactList; 158 | 159 | var toArray = function(list) { 160 | 161 | var len = list.length; 162 | var nameList = []; 163 | 164 | for (var i = 0; i < len; i++) { 165 | var userName = list[i]; 166 | if (!contactList[userName]) { 167 | continue; 168 | } 169 | 170 | var item = contactList[userName]; 171 | if (item['RemarkName']) { 172 | nameList.push(item['RemarkName']); 173 | } else { 174 | nameList.push(item['NickName']); 175 | } 176 | } 177 | return nameList.join(', '); 178 | }; 179 | 180 | if (deleteList.length) { 181 | cube.fire('message', 'danger', S.sprintf(i18n['doc_delete_list_tip'], toArray(deleteList))); 182 | } 183 | if (blackList.length) { 184 | cube.fire('message', 'danger', S.sprintf(i18n['doc_black_list_tip'], toArray(blackList))); 185 | } 186 | if (deleteList.length == 0 && blackList.length == 0) { 187 | cube.fire('message', 'danger', i18n['doc_all_contact_ok']); 188 | } 189 | }, 190 | 191 | processMemberListGroup: function(listGroup) { 192 | 193 | if (!this.listGroup.length) { 194 | cube.fire('message', 'info', i18n['doc_check_friend_done']); 195 | this.finishWork(); 196 | return; 197 | } 198 | 199 | var userList = this.listGroup[0]; 200 | this.tempContactList = userList; 201 | 202 | var nameList = []; 203 | for (var i = 0; i < userList.length; i++) { 204 | var item = userList[i]; 205 | nameList.push(item['UserName']); 206 | } 207 | 208 | var msg = S.sprintf(i18n['doc_check_friend_round'], this.roundTime, runtime.time_limit * 2 / 1000); 209 | cube.fire('message', 'info', msg); 210 | 211 | var me = this; 212 | if (this.tempChatRoomName) { 213 | cube.fire('message', 'info', i18n['doc_try_add_member']); 214 | this.wechat.addMember(this.tempChatRoomName, nameList, function(e) { 215 | me.updateDeletedList(e); 216 | me.deleteMember(nameList); 217 | }); 218 | } else { 219 | 220 | cube.fire('message', 'info', i18n['doc_try_add_member']); 221 | this.wechat.createChatRoom(nameList, function(e) { 222 | if (e.ChatRoomName) { 223 | me.tempChatRoomName = e.ChatRoomName; 224 | 225 | me.updateDeletedList(e); 226 | me.deleteMember(nameList); 227 | 228 | } else if (e.BlackList) { 229 | 230 | me.updateDeletedList(e); 231 | me.processMemberListGroup(); 232 | 233 | } else { 234 | cube.fire('message', 'danger', i18n['create_chat_room_error'] + e.BaseResponse.ErrMsg); 235 | } 236 | }); 237 | } 238 | }, 239 | 240 | deleteMember: function(nameList) { 241 | var me = this; 242 | cube.fire('message', 'info', i18n['doc_try_remove_member']); 243 | me.wechat.deleteMember(me.tempChatRoomName, nameList, function(e) { 244 | me.processMemberListGroup(); 245 | }); 246 | }, 247 | 248 | updateDeletedList: function(e) { 249 | 250 | this.roundTime++; 251 | this.listGroup.shift(); 252 | 253 | // next time should resume after this user 254 | this.lastUserName = this.tempContactList[this.tempContactList.length - 1]['UserName']; 255 | var blackList = {}; 256 | var deleteList = {}; 257 | cube.forEach(e.MemberList, function(item) { 258 | var userName = item['UserName']; 259 | var status = item['MemberStatus']; 260 | if (status == 4) { 261 | deleteList[userName] = true; 262 | } else if (status == 3) { 263 | blackList[userName] = true; 264 | } 265 | }); 266 | 267 | cube.mix(this.blackList, blackList); 268 | cube.mix(this.deleteList, deleteList); 269 | 270 | this.flushLastData(); 271 | 272 | this.displayBadList(deleteList, blackList); 273 | }, 274 | }); 275 | 276 | module.exports = Controller; 277 | -------------------------------------------------------------------------------- /src/js/node_modules/wxhelper/template.js: -------------------------------------------------------------------------------- 1 | var swig = require('swig'); 2 | 3 | module.exports = { 4 | renderHtml: function(templateKey, data) { 5 | var template = swig.compile(require('raw!' + __template_dir__ + '/' + templateKey + '.html')); 6 | return template(data); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/js/node_modules/wxhelper/ui.js: -------------------------------------------------------------------------------- 1 | var style = require(__style_dir__ + '/app.scss'); 2 | import cube from 'cube-js'; 3 | 4 | import Q from 'q'; 5 | import log from 'loglevel'; 6 | import S from 'sprintf-js'; 7 | 8 | // just import it 9 | import jqueryUI from 'jquery-ui' 10 | 11 | import i18n from 'chrome/i18n.js' 12 | 13 | import { runtime } from 'wxhelper/config' 14 | import template from 'wxhelper/template.js' 15 | 16 | var UI = function(controller) { 17 | this.controller = controller; 18 | }; 19 | 20 | cube.mix(UI.prototype, { 21 | 22 | container: null, 23 | rightContainer: null, 24 | messageBox: null, 25 | menu_is_cloesd: false, 26 | toggleButton: null, 27 | 28 | events: { 29 | 'click .dev_test': 'clickDevTest', 30 | 'click .check_friend': 'clickCheckFriend', 31 | 'click .clear_log': 'clickClearLog', 32 | 'click .wxhelper_toggle': 'clickToggleButton', 33 | }, 34 | 35 | clickDevTest: function() { 36 | if (!__is_dev__) { 37 | return; 38 | } 39 | }, 40 | 41 | clickCheckFriend: function() { 42 | this.showMessage('info', i18n['doc_begin_check']); 43 | var time_limit = parseInt($('#time_limit').val()) || 30; 44 | runtime['time_limit'] = time_limit * 1000; 45 | this.controller.checkFriend(); 46 | }, 47 | 48 | clickClearLog: function() { 49 | this.messageBox.empty(); 50 | }, 51 | 52 | clickToggleButton: function() { 53 | var body = $('body'); 54 | if (body.hasClass('wxhelper_inject_hide')) { 55 | this.menu_is_cloesd = false; 56 | } else { 57 | this.menu_is_cloesd = true; 58 | } 59 | var data = {'menu_is_cloesd': this.menu_is_cloesd}; 60 | chrome.storage.local.set(data, function() { 61 | }); 62 | this.updateMenuDisplay(); 63 | }, 64 | 65 | updateMenuDisplay: function() { 66 | var me = this; 67 | var body = $('body'); 68 | 69 | var aniDone = function() { 70 | if (me.menu_is_cloesd) { 71 | body.addClass('wxhelper_inject_hide'); 72 | } else { 73 | body.removeClass('wxhelper_inject_hide'); 74 | } 75 | }; 76 | 77 | var aniToggle = function() { 78 | var deferred = Q.defer(); 79 | var right = 10; 80 | if (me.menu_is_cloesd) { 81 | right = -60; 82 | } 83 | me.toggleButton.animate({ 84 | right: right 85 | }, 200, 'swing', deferred.resolve); 86 | return deferred.promise; 87 | }; 88 | 89 | var aniMenu = function() { 90 | var deferred = Q.defer(); 91 | var leftWidth = me.container.width() || 280; 92 | var leftOffset = 0; 93 | var rightWidth = 0; 94 | var rightOffset = 0; 95 | 96 | if (me.menu_is_cloesd) { 97 | leftOffset = -leftWidth; 98 | rightWidth = me.window_width; 99 | rightOffset = 0; 100 | } else { 101 | leftOffset = 0; 102 | rightWidth = me.window_width - leftWidth; 103 | rightOffset = leftWidth; 104 | } 105 | me.container.animate({ 106 | left: leftOffset, 107 | width: leftWidth 108 | }, 200); 109 | 110 | me.rightContainer.animate({ 111 | left: rightOffset, 112 | width: rightWidth 113 | }, 200, deferred.resolve); 114 | 115 | return deferred.promise; 116 | } 117 | 118 | if (me.menu_is_cloesd) { 119 | aniMenu().then(aniToggle).then(aniDone); 120 | } else { 121 | aniToggle().then(aniMenu).then(aniDone); 122 | } 123 | }, 124 | 125 | init: function() { 126 | cube.on('unlogin', cube.bind(this.onUnlogin, this)); 127 | cube.on('message', cube.bind(this.showMessage, this)); 128 | }, 129 | 130 | onUnlogin: function() { 131 | log.info('onUnlogin'); 132 | this.showMessage('danger', i18n['doc_unlogin']); 133 | }, 134 | 135 | /** 136 | * success, info, warning, danger 137 | */ 138 | showMessage: function(type, message) { 139 | var data = { 140 | type: type, 141 | message: message 142 | } 143 | var message = $(template.renderHtml('message', data)); 144 | 145 | this.messageBox.show().append(message); 146 | this.messageBox.scrollTop(this.messageBox[0].scrollHeight); 147 | }, 148 | 149 | initView: function() { 150 | 151 | this.window_width = $(window).width(); 152 | 153 | var data = { 154 | i18n: i18n, 155 | link: { 156 | author: 'https://github.com/liaohuqiu', 157 | github: 'https://github.com/liaohuqiu/wechat-helper', 158 | }, 159 | version: chrome.runtime.getManifest().version, 160 | }; 161 | 162 | this.container = $(template.renderHtml('content', data)); 163 | $('body').prepend(this.container); 164 | 165 | this.messageBox = this.container.find('.message_box'); 166 | this.toggleButton = this.container.find('.wxhelper_toggle'); 167 | this.rightContainer = $('.wxhelper_inject_right'); 168 | 169 | var me = this; 170 | chrome.storage.local.get('menu_is_cloesd', function(data) { 171 | me.menu_is_cloesd = data['menu_is_cloesd']; 172 | me.updateMenuDisplay(); 173 | }); 174 | 175 | this.container.resizable({ 176 | handles: "e", 177 | resize: function(event, ui) { 178 | me.fitMenuAndContent(); 179 | } 180 | }); 181 | 182 | cube.bindEvents(this, this.events, this.container); 183 | }, 184 | 185 | showOrHideLeft: function(show) { 186 | }, 187 | 188 | fitMenuAndContent: function() { 189 | var width = this.container.width() || 280; 190 | var r = (width / this.window_width); 191 | if (r > 0.40) { 192 | r = 0.40; 193 | } 194 | 195 | var leftWidth = Math.floor(r * this.window_width); 196 | var rightOffset = leftWidth; 197 | var rightWidth = Math.floor((1 - r - 0.02) * this.window_width); 198 | 199 | this.container.css({ 200 | width: leftWidth 201 | }); 202 | this.rightContainer.css({ 203 | left: rightOffset, 204 | width: rightWidth 205 | }); 206 | }, 207 | 208 | hackStyle: function() { 209 | if (__is_dev__) { 210 | $('').appendTo('head') 211 | } 212 | $(document.body).addClass('wxhelper_inject'); 213 | $(document.body).addClass('wxhelper_inject_hide'); 214 | 215 | $('.main').addClass('wxhelper_inject_right'); 216 | $('.login').addClass('wxhelper_inject_right'); 217 | }, 218 | 219 | renderThenShow: function() { 220 | 221 | this.init(); 222 | 223 | this.hackStyle(); 224 | 225 | this.initView(); 226 | }, 227 | }); 228 | 229 | module.exports = UI; 230 | -------------------------------------------------------------------------------- /src/js/node_modules/wxhelper/wechat.js: -------------------------------------------------------------------------------- 1 | import cube from 'cube-js'; 2 | import Q from 'q'; 3 | import log from 'loglevel'; 4 | import S from 'sprintf-js'; 5 | 6 | import { 7 | SpecialUsers, runtime 8 | } from 'wxhelper/config.js'; 9 | import tool from 'from-wechat/tool.js'; 10 | import i18n from 'chrome/i18n.js' 11 | import { 12 | LocalCache, 13 | PrefixLocalCache 14 | } from 'chrome/local-cache' 15 | 16 | var WeChat = function() { 17 | }; 18 | 19 | cube.mix(WeChat.prototype, { 20 | 21 | sid: '', 22 | uin: '', 23 | skey: '', 24 | userInfo: '', 25 | contactList: {}, 26 | 27 | lang: '', 28 | 29 | getLang: function() { 30 | var lang = navigator.language || navigator.browserLanguage; 31 | lang || (lang = "zh-cn"), 32 | lang = lang.split("-"), 33 | lang = lang[0].toLowerCase() + "_" + (lang[1] || "").toUpperCase(); 34 | return lang; 35 | }, 36 | 37 | getBaseRequest: function() { 38 | return { 39 | BaseRequest: { 40 | Uin: this.getUin(), 41 | Sid: this.getSid(), 42 | Skey: this.getSkey(), 43 | DeviceID: this.getDeviceID() 44 | } 45 | } 46 | }, 47 | 48 | getDeviceID: function() { 49 | return "e" + ("" + Math.random().toFixed(15)).substring(2, 17) 50 | }, 51 | 52 | getSid: function() { 53 | return this.sid || (this.sid = tool.getCookie("wxsid")) 54 | }, 55 | 56 | getUin: function() { 57 | return this.uin || (this.uin = tool.getCookie("wxuin")) 58 | }, 59 | 60 | getSkey: function() { 61 | return this.skey || "" 62 | }, 63 | 64 | setSkey: function(key) { 65 | this.skey = key; 66 | }, 67 | 68 | init: function(done) { 69 | log.info('init'); 70 | this.lang = this.getLang(); 71 | var url = "/cgi-bin/mmwebwx-bin/webwxinit?r=" + ~new Date; 72 | var that = this; 73 | 74 | this._request(url, {}).then(function(e){ 75 | that.setSkey(e.SKey); 76 | that.userInfo = e.User; 77 | done(); 78 | }, function() { 79 | // TODO how to make sure is no login 80 | cube.fire('unlogin'); 81 | }); 82 | }, 83 | 84 | getContactList: function(done) { 85 | log.info('getContactList', runtime); 86 | 87 | var url = "/cgi-bin/mmwebwx-bin/webwxgetcontact"; 88 | var data = { 89 | skey: this.getSkey(), 90 | pass_ticket: '', 91 | r: Date.now() 92 | }; 93 | 94 | var me = this; 95 | this._request(url, data, 'GET').then(function(e){ 96 | me._updateContactList(e.MemberList); 97 | done(); 98 | }, function(e) { 99 | cube.fire('message', 'danger', i18n['doc_get_contact_list_error']) 100 | }); 101 | }, 102 | 103 | _updateContactList: function(rawList) { 104 | 105 | log.info('_updateContactList'); 106 | var me = this; 107 | var list = {}; 108 | var map = {} 109 | cube.forEach(SpecialUsers, function(name) { 110 | map[name] = true; 111 | }); 112 | 113 | var myName = me.userInfo['UserName']; 114 | var skipList = []; 115 | cube.forEach(rawList, function(item) { 116 | // 公众号/ 服务号 117 | if ((item['VerifyFlag'] & 8) != 0) { 118 | skipList.push(item['NickName']); 119 | return; 120 | } 121 | var userName = item['UserName']; 122 | if (userName.indexOf('@@') != -1 || userName == myName || map[userName]) { 123 | return; 124 | } 125 | 126 | list[userName] = item; 127 | }); 128 | 129 | log.info('skip list: ' + skipList.join(', ')); 130 | 131 | var contactNum = cube.Array.toArray(list).length; 132 | var msg = S.sprintf(i18n['doc_get_contact_list_done'], contactNum); 133 | cube.fire('message', 'info', msg) 134 | 135 | this.contactList = list; 136 | }, 137 | 138 | createChatRoom: function(list, done) { 139 | log.info('createChatRoom'); 140 | var url = "/cgi-bin/mmwebwx-bin/webwxcreatechatroom?r=" + ~new Date; 141 | 142 | list = cube.map(list, function(value) { 143 | return {UserName: value}; 144 | }); 145 | 146 | var data = { 147 | MemberCount: list.length, 148 | MemberList: list, 149 | }; 150 | 151 | this._request(url, data, 'POST', runtime.time_limit).then(function(e) { 152 | done(e); 153 | }); 154 | }, 155 | 156 | addMember: function(chatRoomName, list, done) { 157 | log.info('addMember'); 158 | 159 | var url = "/cgi-bin/mmwebwx-bin/webwxupdatechatroom?fun=addmember"; 160 | var data = { 161 | ChatRoomName: chatRoomName, 162 | AddMemberList: list.join(','), 163 | }; 164 | this._request(url, data, 'POST', runtime.time_limit).then(function(e) { 165 | done(e); 166 | }); 167 | }, 168 | 169 | deleteMember: function(chatRoomName, list, done) { 170 | log.info('deleteMember'); 171 | 172 | var url = "/cgi-bin/mmwebwx-bin/webwxupdatechatroom?fun=delmember"; 173 | var data = { 174 | ChatRoomName: chatRoomName, 175 | DelMemberList: list.join(','), 176 | }; 177 | this._request(url, data, 'POST', runtime.time_limit).then(function(e) { 178 | done(e); 179 | }); 180 | }, 181 | 182 | _request: function(url, data, method, delay) { 183 | 184 | method = method || 'POST'; 185 | delay = delay || 1000; 186 | 187 | if (method == 'POST') { 188 | data['BaseRequest'] = this.getBaseRequest(); 189 | data = JSON.stringify(data); 190 | } 191 | var uri = new cube.URI(url); 192 | url = uri.setQueryParam('lang', this.lang).toString(); 193 | 194 | var deferred = Q.defer(); 195 | $.ajax({ 196 | type: method, 197 | url: url, 198 | data: data, 199 | contentType: "application/json; charset=utf-8", 200 | dataType: "json", 201 | success: function(ret) { 202 | 203 | log.debug("response: ", ret); 204 | 205 | var ret_code = 0; 206 | if (ret['BaseResponse'] && (ret_code = ret['BaseResponse']['Ret'])) { 207 | var message = ret['BaseResponse']['ErrMsg']; 208 | // readable error message 209 | if (message) { 210 | cube.fire('message', 'danger', message); 211 | } else { 212 | message = 'unknow_error'; 213 | } 214 | log.error(message, ret); 215 | deferred.reject(new Error('request error')); 216 | } else { 217 | deferred.resolve(ret); 218 | } 219 | }, 220 | error: function(ret) { 221 | log.error('request error: ', ret); 222 | deferred.reject(new Error('network error')); 223 | }, 224 | }); 225 | 226 | return deferred.promise.delay(delay); 227 | }, 228 | 229 | }); 230 | 231 | module.exports = WeChat; 232 | -------------------------------------------------------------------------------- /src/js/webpack-plugins/auto-reload-extension.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var Cube = require('cube-js'); 4 | string = require('sprintf-js'); 5 | 6 | function plugin(options) { 7 | } 8 | 9 | plugin.prototype.apply = function(compiler) { 10 | compiler.plugin('done', function() { 11 | const exec = require('child_process').exec; 12 | const cmd = "open http://reload.extensions"; 13 | exec(cmd); 14 | }); 15 | }; 16 | 17 | module.exports = plugin; 18 | -------------------------------------------------------------------------------- /src/style/_bootstrap-customizations.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/src/style/_bootstrap-customizations.scss -------------------------------------------------------------------------------- /src/style/_pre-bootstrap-customizations.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liaohuqiu/wechat-helper/d77e5a3a12f49fed9423e75e51d3af14b642e817/src/style/_pre-bootstrap-customizations.scss -------------------------------------------------------------------------------- /src/style/app.scss: -------------------------------------------------------------------------------- 1 | @import "./wxhelper-icons.css"; 2 | 3 | // Primary text 4 | $primary_text: #4a4a4a; 5 | 6 | // Secondary text 7 | $secondary_text: #929292; 8 | 9 | // Hint text, disabled text and icons. unused now. 10 | $tertiary_text: #a7a9ac; 11 | 12 | $dividers_color: #dbdbdb; 13 | 14 | $secondary_color: #f9885e; 15 | 16 | $bg_color: #F7F7F7; 17 | $bg_color_highlight: #EEE; 18 | 19 | $primary_color: #3C9307; 20 | 21 | .wxhelper_inject_hide { 22 | &.wxhelper_inject { 23 | .wxhelper_inject_left { 24 | .wxhelper_toggle { 25 | .glyphicon-menu-left { 26 | display: none; 27 | } 28 | .glyphicon-menu-right { 29 | display: block; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | .wxhelper_inject { 37 | 38 | position: relative; 39 | 40 | .wxhelper_inject_left { 41 | 42 | position: absolute; 43 | height: 100%; 44 | min-height: 700px; 45 | min-width: 280px; 46 | width: 280px; 47 | left: -280px; 48 | 49 | .ui-resizable-e { 50 | position: absolute; 51 | cursor: col-resize; 52 | width: 5px; 53 | right: 10px; 54 | top: 0px; 55 | bottom: 0px; 56 | } 57 | } 58 | 59 | .wxhelper_inject_right { 60 | left: 0; 61 | position: absolute; 62 | min-width: 60%; 63 | width: 100%; 64 | } 65 | 66 | .main_inner { 67 | min-width: 0; 68 | } 69 | } 70 | 71 | .wxhelper_inject { 72 | 73 | .wxhelper_inject_left { 74 | 75 | font-size: 14px; 76 | color: $primary_text; 77 | 78 | a { 79 | color: $primary_color; 80 | &:visited { 81 | color: $primary_color; 82 | } 83 | } 84 | .btn { 85 | background: $bg_color; 86 | &:hover { 87 | background: $bg_color_highlight; 88 | } 89 | } 90 | 91 | input { 92 | display: inline-block; 93 | padding: 3px; 94 | background: $bg_color; 95 | border: 1px solid #ccc; 96 | border-radius: 4px; 97 | } 98 | 99 | .wxhelper_toggle { 100 | cursor: pointer; 101 | margin-top: 10px; 102 | margin-right: 10px; 103 | z-index: 2; 104 | position: absolute; 105 | top: 0; 106 | right: 10px; 107 | padding: 8px 10px; 108 | font-size: 12px; 109 | background: $bg_color; 110 | &:hover { 111 | } 112 | .glyphicon-menu-left { 113 | display: block; 114 | } 115 | .glyphicon-menu-right { 116 | display: none; 117 | } 118 | } 119 | 120 | #wxhelper_box { 121 | z-index: 1; 122 | position: absolute; 123 | 124 | top: 0; 125 | bottom: 0px; 126 | 127 | left: 0px; 128 | right: 0px; 129 | 130 | background: $bg_color; 131 | // padding: 0 10px; 132 | margin-right: 10px; 133 | height: 100%; 134 | border-right: 1px solid $dividers_color; 135 | 136 | .wxhelper_content { 137 | 138 | position: relative; 139 | height: 100%; 140 | 141 | .wxhelper_view_header { 142 | width: 94%; 143 | padding: 10px 3%; 144 | 145 | float: left; 146 | position: relative; 147 | border-bottom: 1px solid rgb(221, 221, 221); 148 | span { 149 | padding-right: 5px; 150 | } 151 | .title { 152 | font-size: 25px; 153 | } 154 | .author { 155 | } 156 | } 157 | .wxhelper_view { 158 | position: relative; 159 | float: left; 160 | width: 94%; 161 | padding: 0 3%; 162 | 163 | .message_box { 164 | padding: 3px; 165 | border-radius: 3px; 166 | background: black; 167 | height: 250px; 168 | overflow-y: auto; 169 | 170 | .alert { 171 | padding: 0; 172 | margin: 0; 173 | background: transparent; 174 | border: none; 175 | } 176 | .alert-info { 177 | color: white; 178 | } 179 | .alert-success { 180 | color: #26F31C; 181 | } 182 | .alert-warning { 183 | color: #E8C70D; 184 | } 185 | .alert-danger { 186 | color: red; 187 | } 188 | } 189 | 190 | .action_list { 191 | text-align: center; 192 | padding-top: 10px; 193 | padding-bottom: 20px; 194 | 195 | .time_limit { 196 | width: 35px; 197 | text-align: center; 198 | } 199 | } 200 | } 201 | .wxhelper_view_footer { 202 | border-top: 1px solid $dividers_color; 203 | 204 | width: 100%; 205 | position: absolute; 206 | bottom: 25px; 207 | left: 0; 208 | 209 | p { 210 | margin-top: 5px; 211 | font-size: 12px; 212 | color: $secondary_text; 213 | text-align: center; 214 | .glyphicon { 215 | color: red; 216 | } 217 | } 218 | 219 | .donate_tip { 220 | color: red; 221 | padding-left: 1em; 222 | } 223 | 224 | .make_with { 225 | } 226 | 227 | .qrcode_box { 228 | padding: 10px 0; 229 | line-height: 0; 230 | text-align: center; 231 | .hi { 232 | display: block; 233 | font-size: 100px; 234 | &:before { 235 | } 236 | } 237 | } 238 | } 239 | } 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/style/popup.scss: -------------------------------------------------------------------------------- 1 | .popup-box { 2 | width: 250px; 3 | padding: 10px; 4 | text-align: center; 5 | .btn { 6 | margin-right: 10px; 7 | display: inline-block; 8 | text-decoration: none; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/style/wxhelper-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "wxhelper-icons"; 3 | src: url('../fonts/wxhelper-icons.eot'); 4 | src: url('../fonts/wxhelper-icons.eot?#iefix') format('eot'), 5 | url('../fonts/wxhelper-icons.woff') format('woff'), 6 | url('../fonts/wxhelper-icons.ttf') format('truetype'), 7 | url('../fonts/wxhelper-icons.svg#wxhelper-icons') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | .hi:before { 13 | display: inline-block; 14 | font-family: "wxhelper-icons"; 15 | font-style: normal; 16 | font-weight: normal; 17 | line-height: 1; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-osx-font-smoothing: grayscale; 20 | } 21 | 22 | .hi-lg { 23 | font-size: 1.3333333333333333em; 24 | line-height: 0.75em; 25 | vertical-align: -15%; 26 | } 27 | .hi-2x { font-size: 2em; } 28 | .hi-3x { font-size: 3em; } 29 | .hi-4x { font-size: 4em; } 30 | .hi-5x { font-size: 5em; } 31 | .hi-fw { 32 | width: 1.2857142857142858em; 33 | text-align: center; 34 | } 35 | 36 | .hi-logo:before { content: "\EA01" } 37 | .hi-wechat_pay_link:before { content: "\EA02" } -------------------------------------------------------------------------------- /src/templates/content.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 |
8 |
9 |
10 | {{ i18n.app_name }} 11 |
12 |
13 | v{{ version }} 14 | by @liaohuqiu 15 | GitHub 16 | {{ i18n['about'] }} 17 |
18 |
19 |
20 |
21 | {{ i18n.check_friend_time_limit }} 22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 | 37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /src/templates/message.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/templates/popup.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var module_path = path.resolve(__dirname, 'node_modules'); 4 | var webpack = require('webpack'); 5 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | var root_dir = __dirname; 7 | var cube = require('cube-js'); 8 | 9 | var config = require('./config.js'); 10 | 11 | var name_for_img = '/img/[hash].[ext]'; 12 | var font_version = cube.formatDate(new Date(), 'yyyyMMddhhmmss'); 13 | var name_for_font = 'name=/fonts/[name].[ext]?v=' + font_version; 14 | 15 | var css_loader = 'css-loader?sourceMap!less-loader?sourceMap!sass-loader?sourceMap&outputStyle=expanded'; 16 | 17 | var env = process.env.NODE_ENV; 18 | var target = process.env.target; 19 | 20 | var loaders = [ 21 | { 22 | test: /\.js$/, 23 | exclude: module_path, 24 | loader: 'babel' 25 | }, 26 | { 27 | test: /.*\.(gif|png|jpe?g)$/i, 28 | loader: 'url?limit=10000&name=' + name_for_img 29 | }, 30 | { 31 | test: /bootstrap-sass\/assets\/javascripts\//, 32 | loader: 'imports?jQuery=jquery', 33 | }, 34 | { 35 | test: /bootstrap\/js\//, 36 | loader: 'imports?jQuery=jquery' 37 | }, 38 | { 39 | test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, 40 | loader: 'url?limit=100000&mimetype=application/font-woff&' + name_for_font, 41 | }, 42 | { 43 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 44 | loader: 'url?limit=100000&mimetype=application/octet-stream&' + name_for_font, 45 | }, 46 | { 47 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 48 | loader: 'url?limit=10000&mimetype=image/svg+xml&' + name_for_font, 49 | }, 50 | { 51 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 52 | loader: 'file?' + name_for_font, 53 | }]; 54 | 55 | var plugins = [ 56 | new webpack.ProvidePlugin({ 57 | $: 'jquery', 58 | jQuery: 'jquery', 59 | }), 60 | new webpack.DefinePlugin(config.getDefineVar()), 61 | ]; 62 | 63 | if (config.shouldExtractCss()) { 64 | var css_filename = config.getCssFileName(); 65 | plugins.push(new ExtractTextPlugin('css/' + css_filename, { allChunks: true })); 66 | loaders.push({ 67 | test: /\.(css|less|scss)$/, 68 | loader: ExtractTextPlugin.extract('style-loader', css_loader) 69 | }); 70 | } else { 71 | loaders.push({ 72 | test: /\.(css|less|scss)$/, 73 | loader: css_loader, 74 | }); 75 | } 76 | 77 | var entry = {}; 78 | if (target == 'popup') { 79 | entry = { 80 | popup: './src/js/entries/popup.js', 81 | } 82 | } else { 83 | entry = { 84 | app: './src/js/entries/app.js', 85 | background: './src/js/entries/background.js', 86 | } 87 | } 88 | 89 | var data = { 90 | entry: entry, 91 | output: { 92 | path: path.join(root_dir, '/extension/dist'), 93 | filename: 'js/[name].js', 94 | publicPath: '/dist' 95 | }, 96 | node: { 97 | fs: 'empty', 98 | }, 99 | module: { 100 | loaders: loaders, 101 | }, 102 | plugins: plugins 103 | }; 104 | 105 | if (env == 'dev') { 106 | data['devtool'] = 'source-map'; 107 | } 108 | 109 | module.exports = data; 110 | --------------------------------------------------------------------------------