├── server ├── favicon.png ├── index.js └── api.js ├── targetBiz.json ├── posts_screenshot.png ├── rule ├── replaceImg.png ├── index.js └── wechatRule.js ├── client ├── app │ ├── config.js │ ├── style │ │ └── style.css │ ├── index.html │ ├── components │ │ ├── loading.jsx │ │ ├── searchInput.jsx │ │ └── Paginator.jsx │ ├── containers │ │ ├── search.jsx │ │ ├── categories.jsx │ │ ├── profile.jsx │ │ ├── profiles.jsx │ │ └── posts.jsx │ ├── reducers.js │ ├── index.jsx │ └── actions.js ├── package.json └── webpack.config.js ├── 开启步骤 ├── auto_driver ├── config.py ├── tes.py ├── auto_operate_phone.py └── upload_data.py ├── utils ├── redis.js ├── correctWechatId.js ├── contentHandler.js └── exportData.js ├── models ├── Profile.js ├── Category.js ├── Comment.js ├── index.js ├── Post.js └── plugins │ └── paginator.js ├── LICENSE ├── package.json ├── .eslintrc.js ├── .gitignore ├── config.js ├── README.md ├── index_no_script.js ├── index.js ├── scripts └── checkWechatId.js └── ts.js /server/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark2016goog/wechat_spider/HEAD/server/favicon.png -------------------------------------------------------------------------------- /targetBiz.json: -------------------------------------------------------------------------------- 1 | [ 2 | "MjM5MjAxNDM4MA==", 3 | "MjM5ODIyMTE0MA==", 4 | "MzA4NDEzNTMyMA==" 5 | ] -------------------------------------------------------------------------------- /posts_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark2016goog/wechat_spider/HEAD/posts_screenshot.png -------------------------------------------------------------------------------- /rule/replaceImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mark2016goog/wechat_spider/HEAD/rule/replaceImg.png -------------------------------------------------------------------------------- /client/app/config.js: -------------------------------------------------------------------------------- 1 | // const ENV = process.env.NODE_ENV || 'development'; 2 | 3 | const config = { 4 | posts: '/api/posts', 5 | profiles: '/api/profiles', 6 | profile: '/api/profile', 7 | cates: '/api/categories' 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /client/app/style/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-left: 100px; 3 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier; 4 | } 5 | 6 | .wrapper { 7 | box-sizing: border-box; 8 | width: 100%; 9 | padding:0 20px; 10 | margin: 10px auto; 11 | } -------------------------------------------------------------------------------- /开启步骤: -------------------------------------------------------------------------------- 1 | 1. appium 服务端开启 (长期开启) 2 | 2. node 微信爬虫开启 [npm start] (长期开启) 3 | 3. appium script 点击手机触发爬虫 (定时运行触发爬虫) 4 | 5 | @ windows有计划任务 会定时运行 auto_driver/auto_operate_phone.py 去操作手机打开微信爬取, 然后存数据到远程服务器db 6 | 7 | @ 第一次数据量可能会很大,可以先手动运行一下脚本(防止爬取到第二天9点 操作微信脚本 再次运行造成冲突),之后每天爬取的量少, 能保证24小时内爬完 8 | -------------------------------------------------------------------------------- /auto_driver/config.py: -------------------------------------------------------------------------------- 1 | MONGO = { 2 | 'host': 'localhost', 3 | 'port': 27017 4 | } 5 | 6 | # 微信数据库信息 7 | WECHAT_DB_NAME = 'wechat_spider' 8 | 9 | # 微信集合名 10 | POST_COL = 'posts' 11 | PROFILE_COL = 'profiles' 12 | COMMENTS_COL = 'comments' 13 | CATE_COL = 'categories' 14 | 15 | REMOTE_HOST = 'http://192.168.1.6:5001' -------------------------------------------------------------------------------- /client/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 微信数据展示 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /utils/redis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const redis = require('redis'); 4 | const { promisify } = require('util'); 5 | const config = require('../config'); 6 | 7 | const { port = 6379, host = '127.0.0.1' } = config.redis; 8 | 9 | const redisClient = redis.createClient(port, host); 10 | 11 | module.exports = asyncRedis; 12 | 13 | function asyncRedis(cmd, ...args) { 14 | return promisify(redisClient[cmd]).call(redisClient, ...args); 15 | } 16 | -------------------------------------------------------------------------------- /models/Profile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const Schema = mongoose.Schema; 5 | 6 | // 数据结构:公众号账号 7 | const Profile = new Schema({ 8 | title: String, 9 | wechatId: String, 10 | desc: String, 11 | msgBiz: String, 12 | headimg: String, 13 | openHistoryPageAt: Date, 14 | // 无关的字段,可忽略 15 | property: String 16 | }); 17 | 18 | Profile.plugin(require('motime')); 19 | 20 | Profile.index({ msgBiz: 1 }); 21 | 22 | mongoose.model('Profile', Profile); 23 | -------------------------------------------------------------------------------- /models/Category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const Schema = mongoose.Schema; 5 | 6 | // 数据结构:公众号账号 7 | const Category = new Schema({ 8 | name: String, 9 | msgBizs: [String] 10 | }, { toJSON: { virtuals: true } }); 11 | 12 | Category.plugin(require('motime')); 13 | 14 | Category.virtual('profiles', { 15 | ref: 'Profile', 16 | localField: 'msgBizs', 17 | foreignField: 'msgBiz' 18 | }); 19 | 20 | Category.index({ name: 1 }); 21 | 22 | mongoose.model('Category', Category); 23 | -------------------------------------------------------------------------------- /models/Comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const Schema = mongoose.Schema; 5 | 6 | const Comment = new Schema({ 7 | postId: { type: 'ObjectId', ref: 'Post' }, 8 | contentId: String, 9 | nickName: String, 10 | logoUrl: String, 11 | content: String, 12 | createTime: Date, 13 | likeNum: Number, 14 | replies: [{ 15 | content: String, 16 | createTime: Date, 17 | likeNum: Number 18 | }] 19 | }); 20 | 21 | Comment.plugin(require('motime')); 22 | 23 | Comment.index({ contentId: 1 }); 24 | 25 | mongoose.model('Comment', Comment); 26 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const path = require('path'); 5 | 6 | mongoose.Promise = global.Promise; 7 | 8 | // 载入 mongoose 插件 9 | require('./plugins/paginator'); 10 | 11 | const config = require('../config'); 12 | 13 | mongoose.connect(config.mongodb.db); 14 | 15 | mongoose.set('debug', false); 16 | 17 | // Load All Models 18 | [ 19 | 'Post', 20 | 'Profile', 21 | 'Category', 22 | 'Comment' 23 | ].forEach(function(modelName) { 24 | require(path.join(__dirname, modelName)); 25 | exports[modelName] = mongoose.model(modelName); 26 | }); 27 | -------------------------------------------------------------------------------- /client/app/components/loading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import RefreshIndicator from 'material-ui/RefreshIndicator'; 3 | 4 | const style = { 5 | container: { 6 | position: 'fixed', 7 | top: '50%', 8 | left: '50%' 9 | }, 10 | refresh: { 11 | display: 'inline-block', 12 | position: 'relative' 13 | }, 14 | }; 15 | 16 | const Loading = () => ( 17 |
18 | 25 |
26 | ); 27 | 28 | export default Loading; 29 | -------------------------------------------------------------------------------- /models/Post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const Schema = mongoose.Schema; 5 | 6 | // 数据结构:文章 7 | const Post = new Schema({ 8 | title: String, 9 | link: String, 10 | publishAt: Date, 11 | readNum: Number, 12 | likeNum: Number, 13 | msgBiz: String, 14 | msgMid: String, 15 | msgIdx: String, 16 | sourceUrl: String, 17 | cover: String, 18 | digest: String, 19 | isFail: Boolean, 20 | wechatId: String, 21 | updateNumAt: Date, 22 | // 文章正文html代码 23 | content: String 24 | }, { toJSON: { virtuals: true } }); 25 | 26 | Post.plugin(require('motime')); 27 | 28 | Post.virtual('profile', { 29 | ref: 'Profile', 30 | localField: 'msgBiz', 31 | foreignField: 'msgBiz', 32 | justOne: true 33 | }); 34 | 35 | // 索引 36 | Post.index({ publishAt: -1, msgIdx: 1 }); 37 | Post.index({ publishAt: 1, msgIdx: 1 }); 38 | Post.index({ updateNumAt: -1 }); 39 | Post.index({ updateNumAt: 1 }); 40 | Post.index({ msgBiz: 1, publishAt: 1, msgIdx: 1 }); 41 | Post.index({ msgBiz: 1, msgMid: 1, msgIdx: 1 }); 42 | 43 | mongoose.model('Post', Post); 44 | -------------------------------------------------------------------------------- /client/app/components/searchInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import TextField from 'material-ui/TextField'; 4 | 5 | export default class SearchInput extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | q: props.value || '' 10 | }; 11 | } 12 | 13 | render() { 14 | const { q } = this.state; 15 | const { onEnter, hintText = '', fullWidth = false } = this.props; 16 | return ( 17 | { 20 | this.setState({ 21 | q: event.target.value 22 | }); 23 | }} 24 | onKeyPress={event => { 25 | if (event.key == 'Enter') { 26 | onEnter(q); 27 | } 28 | }} 29 | hintText={hintText} 30 | fullWidth={fullWidth} 31 | /> 32 | ); 33 | } 34 | } 35 | 36 | SearchInput.propTypes = { 37 | onEnter: PropTypes.func.isRequired, 38 | value: PropTypes.string, 39 | hintText: PropTypes.string, 40 | fullWidth: PropTypes.bool 41 | }; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 liqiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/app/containers/search.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { assembleUrl } from '../actions'; 3 | import SearchInput from '../components/searchInput.jsx'; 4 | 5 | class Search extends React.Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | render() { 12 | const { location, history, searchArgs, defaultText } = this.props; 13 | const { pathname } = location; 14 | let { q = '' } = searchArgs; 15 | q = decodeURIComponent(q); 16 | const nextQuery = { ...searchArgs }; 17 | 18 | // 去掉分页query 19 | if (nextQuery.page) delete nextQuery.page; 20 | return ( 21 |
24 | { 29 | if (q) nextQuery.q = q; 30 | if (!q && nextQuery.q) delete nextQuery.q; 31 | const path = assembleUrl(pathname, nextQuery); 32 | history.push(path); 33 | }} 34 | /> 35 |
36 | ); 37 | } 38 | } 39 | 40 | export default Search; 41 | -------------------------------------------------------------------------------- /utils/correctWechatId.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const models = require('../models'); 4 | 5 | module.exports = class CorrectWechatId { 6 | 7 | constructor(options = {}) { 8 | const { msgBiz, wechatId } = options; 9 | if (!msgBiz || !wechatId) throw new Error('请传入正确参数'); 10 | 11 | this.msgBiz = msgBiz; 12 | this.wechatId = wechatId; 13 | } 14 | 15 | async checkPost() { 16 | const res = await this.updateWechatId('Post'); 17 | if (res.nModified) { 18 | console.log(`msgBiz: ${this.msgBiz}, wechatId: ${this.wechatId}`); 19 | console.log(`文章数据表中更新了${res.nModified}条记录`); 20 | console.log(); 21 | } 22 | } 23 | 24 | async checkProfile() { 25 | const res = await this.updateWechatId('Profile'); 26 | if (res.nModified) { 27 | console.log(`msgBiz: ${this.msgBiz}, wechatId: ${this.wechatId}`); 28 | console.log(`账号数据表中更新了${res.nModified}条记录`); 29 | console.log(); 30 | } 31 | } 32 | 33 | async updateWechatId(modelName) { 34 | return await models[modelName].updateMany( 35 | { msgBiz: this.msgBiz, wechatId: { $ne: this.wechatId } }, 36 | { wechatId: this.wechatId } 37 | ); 38 | } 39 | 40 | }; 41 | -------------------------------------------------------------------------------- /utils/contentHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cheerio = require('cheerio'); 4 | const rp = require('request-promise'); 5 | 6 | module.exports = class ContentHandler { 7 | 8 | constructor(options = {}) { 9 | const { link, body } = options; 10 | if (!link && !body) throw new Error('至少传入link或body'); 11 | this.link = link; 12 | this.body = body; 13 | this.text = ''; 14 | this.html = ''; 15 | } 16 | 17 | /** 18 | * 获取微信正文html 19 | * @api public 20 | */ 21 | async toHtml() { 22 | if (this.html) return this.html; 23 | this.html = (await this.parseBodyToHtml()).html().trim() || ''; 24 | return this.html; 25 | } 26 | 27 | /** 28 | * 获取微信正文text 29 | * @api public 30 | */ 31 | async toText() { 32 | if (this.text) return this.text; 33 | this.text = (await this.parseBodyToHtml()).text().trim() || ''; 34 | return this.text; 35 | } 36 | 37 | async parseBodyToHtml() { 38 | if (!this.body) await this.getBody(); 39 | const $ = cheerio.load(this.body, { decodeEntities: false }); 40 | return $('#js_content'); 41 | } 42 | 43 | async getBody() { 44 | this.body = await rp(this.link); 45 | } 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat_spider", 3 | "version": "1.1.0", 4 | "description": "wechat spider by Man-in-the-middle attack", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index.js --ignore client/", 8 | "build": "pm2 start index.js --name=\"wechat_spider\"" 9 | }, 10 | "author": "liqiang", 11 | "license": "MIT", 12 | "dependencies": { 13 | "anyproxy": "^4.0.5", 14 | "body-parser": "^1.18.3", 15 | "cheerio": "^1.0.0-rc.2", 16 | "cookie-parser": "^1.4.3", 17 | "ejs": "^2.5.7", 18 | "express": "^4.16.3", 19 | "ip": "^1.1.5", 20 | "json2csv": "^3.11.2", 21 | "moment": "^2.21.0", 22 | "mongoose": "^5.0.10", 23 | "morgan": "^1.8.2", 24 | "motime": "^0.0.2", 25 | "multer": "^1.3.0", 26 | "redis": "^2.8.0", 27 | "request": "^2.83.0", 28 | "request-promise": "^4.2.2" 29 | }, 30 | "devDependencies": { 31 | "nodemon": "^1.11.0" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/lqqyt2423/wechat_spider.git" 36 | }, 37 | "keywords": [ 38 | "wechat", 39 | "spider" 40 | ], 41 | "bugs": { 42 | "url": "https://github.com/lqqyt2423/wechat_spider/issues" 43 | }, 44 | "homepage": "https://github.com/lqqyt2423/wechat_spider#readme" 45 | } 46 | -------------------------------------------------------------------------------- /client/app/reducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | REQUEST_POSTS, 3 | RECEIVE_POSTS, 4 | REQUEST_PROFILES, 5 | RECEIVE_PROFILES, 6 | REQUEST_PROFILE, 7 | RECEIVE_PROFILE, 8 | REQUEST_CATES, 9 | RECEIVE_CATES 10 | } from './actions'; 11 | 12 | const initialState = { 13 | posts: {}, 14 | profiles: {}, 15 | profile: {}, 16 | cates: [], 17 | isFetching: false 18 | }; 19 | 20 | function reducer(state = initialState, action) { 21 | switch (action.type) { 22 | case REQUEST_POSTS: 23 | case REQUEST_PROFILES: 24 | case REQUEST_PROFILE: 25 | case REQUEST_CATES: 26 | return Object.assign({}, state, { 27 | isFetching: true 28 | }); 29 | case RECEIVE_POSTS: 30 | return Object.assign({}, state, { 31 | isFetching: false, 32 | posts: action.posts 33 | }); 34 | case RECEIVE_PROFILES: 35 | return Object.assign({}, state, { 36 | isFetching: false, 37 | profiles: action.profiles 38 | }); 39 | case RECEIVE_PROFILE: 40 | return { 41 | ...state, 42 | isFetching: false, 43 | profile: action.profile 44 | }; 45 | case RECEIVE_CATES: 46 | return Object.assign({}, state, { 47 | isFetching: false, 48 | cates: action.cates 49 | }); 50 | default: 51 | return state; 52 | } 53 | } 54 | 55 | export default reducer; 56 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat_spider_client", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "build": "NODE_ENV=production webpack", 8 | "start": "webpack-dev-server" 9 | }, 10 | "author": "liqiang", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bootstrap": "^3.3.7", 14 | "classnames": "^2.2.3", 15 | "font-awesome": "^4.7.0", 16 | "lodash": "^4.13.1", 17 | "lodash.assign": "^4.0.9", 18 | "lodash.camelcase": "^4.3.0", 19 | "lodash.clonedeep": "^4.5.0", 20 | "material-ui": "^0.19.2", 21 | "moment": "^2.21.0", 22 | "prop-types": "^15.5.10", 23 | "react": "^15.6.1", 24 | "react-dom": "^15.6.1", 25 | "react-redux": "^5.0.6", 26 | "react-router": "^2.0.1", 27 | "redux": "^3.7.2", 28 | "redux-logger": "^3.0.6", 29 | "redux-thunk": "^2.2.0" 30 | }, 31 | "devDependencies": { 32 | "babel": "^6.23.0", 33 | "babel-core": "^6.25.0", 34 | "babel-loader": "^7.1.1", 35 | "babel-preset-env": "^1.6.0", 36 | "babel-preset-es2015": "^6.24.1", 37 | "babel-preset-react": "^6.24.1", 38 | "babel-preset-stage-2": "^6.24.1", 39 | "css-loader": "^0.28.4", 40 | "file-loader": "^1.1.4", 41 | "html-webpack-plugin": "^2.29.0", 42 | "style-loader": "^0.18.2", 43 | "webpack": "^3.3.0", 44 | "webpack-dev-server": "^2.6.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "globals": { 9 | "_": true, 10 | "$": true, 11 | "WeixinJSBridge": true, 12 | "sequelize": true, 13 | "Sequelize": true, 14 | "models": true, 15 | "glue": true, 16 | "ovt": true 17 | }, 18 | "extends": ["eslint:recommended", "plugin:react/recommended"], 19 | "parserOptions": { 20 | "ecmaFeatures": { 21 | "experimentalObjectRestSpread": true, 22 | "jsx": true 23 | }, 24 | "sourceType": "module", 25 | "ecmaVersion": 2017 26 | }, 27 | "plugins": [ 28 | "react" 29 | ], 30 | "rules": { 31 | "no-unused-vars": [ 32 | 1 33 | ], 34 | "no-console": [ 35 | 0 36 | ], 37 | "react/prop-types": [ 38 | 0 39 | ], 40 | "react/no-danger": [ 41 | 1 42 | ], 43 | "indent": [ 44 | 1, 45 | 2, 46 | { "SwitchCase": 1 } 47 | ], 48 | "linebreak-style": [ 49 | 2, 50 | "unix" 51 | ], 52 | "quotes": [ 53 | 1, 54 | "single" 55 | ], 56 | "semi": [ 57 | 2, 58 | "always" 59 | ], 60 | "require-yield": [0] 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /client/app/containers/categories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchCates } from '../actions'; 4 | import Loading from '../components/loading.jsx'; 5 | import { Card, CardActions, CardTitle } from 'material-ui/Card'; 6 | import FlatButton from 'material-ui/FlatButton'; 7 | 8 | 9 | class Categories extends React.Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | } 14 | 15 | componentDidMount() { 16 | let { dispatch } = this.props; 17 | dispatch(fetchCates()); 18 | } 19 | 20 | render() { 21 | let { cates, isFetching, history } = this.props; 22 | if (isFetching || !cates.length) return ; 23 | return ( 24 |
25 | { 26 | cates.map(cate => { 27 | return ( 28 |
29 | 30 | 31 | 32 | { history.push(`/profiles?category=${cate._id}`); }} /> 33 | { history.push(`/posts?category=${cate._id}`); }} /> 34 | 35 | 36 |
37 | ); 38 | }) 39 | } 40 |
41 | ); 42 | } 43 | } 44 | 45 | export default connect(state => state)(Categories); 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # pycharm 107 | .idea -------------------------------------------------------------------------------- /client/app/containers/profile.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchProfile } from '../actions'; 4 | import Loading from '../components/loading.jsx'; 5 | import Paper from 'material-ui/Paper'; 6 | import Avatar from 'material-ui/Avatar'; 7 | import moment from 'moment'; 8 | 9 | function f(date) { 10 | if (date) { 11 | return moment(new Date(date)).format('YYYY-MM-DD HH:mm'); 12 | } else { 13 | return date; 14 | } 15 | } 16 | 17 | class Profile extends React.Component { 18 | 19 | constructor(props) { 20 | super(props); 21 | } 22 | 23 | componentDidMount() { 24 | const { params, dispatch } = this.props; 25 | const { id } = params; 26 | dispatch(fetchProfile(id)); 27 | } 28 | 29 | render() { 30 | const { isFetching, profile, params } = this.props; 31 | const { id } = params; 32 | if (isFetching) return ; 33 | if (id !== profile.id) return ; 34 | return ( 35 | 41 |

42 | 47 | {profile.title} 48 |

49 |
53 |

微信ID:{profile.wechatId}

54 |

msgBiz:{profile.msgBiz}

55 |

创建时间:{f(profile.createdAt)}

56 |

更新时间:{f(profile.updatedAt)}

57 |

上次打开历史页面时间:{f(profile.openHistoryPageAt)}

58 |

属性:{profile.property}

59 |
60 |
61 | ); 62 | } 63 | } 64 | 65 | export default connect(state => state)(Profile); 66 | -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const webpack = require('webpack'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | 7 | let NODE_ENV = process.env.NODE_ENV || 'development'; 8 | 9 | let publicPath = '/'; 10 | 11 | const babelLoader = { 12 | loader: 'babel-loader', 13 | options: { 14 | cacheDirectory: true, 15 | presets: ['es2015', 'react', 'stage-2'] 16 | } 17 | }; 18 | 19 | const plugins = [ 20 | new webpack.HotModuleReplacementPlugin(), 21 | new HtmlWebpackPlugin({ 22 | title: 'react', 23 | template: './app/index.html' 24 | }) 25 | ]; 26 | 27 | if (NODE_ENV != 'development') { 28 | publicPath = ''; 29 | plugins.push( 30 | new webpack.DefinePlugin({ 31 | 'process.env': { 32 | NODE_ENV: JSON.stringify('production') 33 | } 34 | }), 35 | new webpack.optimize.UglifyJsPlugin() 36 | ); 37 | } 38 | 39 | module.exports = { 40 | entry: './app/index.jsx', 41 | output: { 42 | filename: 'bundle.js', 43 | path: path.resolve(__dirname, './build'), 44 | publicPath: publicPath 45 | }, 46 | plugins: plugins, 47 | devtool: NODE_ENV == 'development' ? 'eval' : undefined, 48 | devServer: { 49 | hot: true, 50 | contentBase: './', 51 | historyApiFallback: true, 52 | proxy: { 53 | '/api': 'http://localhost:8104', 54 | '/favicon.png': 'http://localhost:8104' 55 | } 56 | }, 57 | module: { 58 | rules: [ 59 | { 60 | test: /\.js|jsx$/, 61 | use: [ 62 | babelLoader 63 | ], 64 | exclude: /(node_modules|bower_components)/ 65 | }, 66 | { 67 | test: /\.css$/, 68 | use: [ 69 | 'style-loader', 70 | 'css-loader' 71 | ] 72 | }, 73 | { 74 | test: /\.(woff|woff2|eot|ttf|otf|svg)$/, 75 | use: [ 76 | 'file-loader' 77 | ] 78 | } 79 | ] 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const express = require('express'); 5 | const logger = require('morgan'); 6 | const path = require('path'); 7 | const app = express(); 8 | const spiderConfig = require('../config'); 9 | const models = require('../models'); 10 | const { Category } = models; 11 | 12 | const api = require('./api'); 13 | 14 | app.use(logger('dev')); 15 | 16 | app.use(express.json()); 17 | app.use(express.urlencoded({ extended: false })); 18 | 19 | app.use('/api', api); 20 | 21 | // 接口设置抓取此分类内的账号 22 | // curl localhost:8104/spider -XPOST -H "Content-Type: application/json" -d '{ "categoryId": "5a50cacbb7c8a46b635878c6" }' 23 | app.post('/spider', async (req, res, next) => { 24 | try { 25 | const { categoryId } = req.body; 26 | if (!categoryId) return next(new Error('请传入categoryId')); 27 | const category = await Category.findOne({ _id: categoryId }); 28 | if (!category) return next(new Error('请传入正确的categoryId')); 29 | const msgBizs = category.msgBizs; 30 | if (!msgBizs.length) return next(new Error('请传入正确的categoryId')); 31 | spiderConfig.insertJsToNextProfile.targetBiz = msgBizs; 32 | spiderConfig.insertJsToNextPage.targetBiz = msgBizs; 33 | res.send('设置成功'); 34 | } catch(e) { 35 | next(e); 36 | } 37 | }); 38 | 39 | // 前端页面 40 | // eslint-disable-next-line 41 | app.get('/favicon.png', (req, res, next) => { 42 | res.sendFile(path.join(__dirname, './favicon.png')); 43 | }); 44 | app.use('/', express.static(path.join(__dirname, '../client/build'))); 45 | // eslint-disable-next-line 46 | app.get('/*', (req, res, next) => { 47 | res.sendFile(path.join(__dirname, '../client/build/index.html')); 48 | }); 49 | 50 | // handle error 参数next不能省略 51 | // eslint-disable-next-line 52 | app.use((error, req, res, next) => { 53 | if (!res.finished) { 54 | res.status(500).send(error.message); 55 | } 56 | }); 57 | 58 | const server = http.createServer(app); 59 | 60 | module.exports = server; 61 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | const config = { 6 | read_mongo_count:0, //redis没有待爬去公众号列表,去读Mongo计数,为了防止一直没有了就读mongo 7 | mongodb: { 8 | db: 'mongodb://127.0.0.1:27017/wechat_spider' 9 | }, 10 | redis: { 11 | port: 6379, 12 | host: '127.0.0.1', 13 | POST_LIST_KEY: 'wechat_spider:post_list', 14 | PROFILE_LIST_KEY: 'wechat_spider:profile_list' 15 | }, 16 | // 是否用本地图片替换所有的图片请求 加快网络速度 17 | isReplaceImg: true, 18 | // 是否替换显示在手机上的微信文章正文内容 加快网路速度 19 | isReplacePostBody: true, 20 | insertJsToNextPage: { 21 | // 是否关闭自动跳转页面 22 | disable: false, 23 | // 跳转时间间隔 s 24 | jumpInterval: 8, 25 | // 跳转文章发布时间范围 26 | minTime: new Date(2017, 12, 1), // minTime跟 下面的 minTime 间隔2个月, 要不可能抓取到更早的文章链接,不去抓取,有没正文的情况 27 | maxTime: new Date(2050, 6, 14), 28 | // 已有数据的文章是否再抓取 29 | isCrawlExist: false, 30 | // if true updateNumAt - publishAt 31 | crawlExistInterval: 1000 * 60 * 60 * 24 * 3, 32 | // 抓取公众号biz范围 33 | targetBiz: [], 34 | // 是否保存文章内容 35 | isSavePostContent: true, 36 | // 保存内容的形式: html/text 37 | saveContentType: 'text', 38 | }, 39 | insertJsToNextProfile: { 40 | // 是否关闭自动跳转页面 41 | disable: false, 42 | // 仅scroll 不跳转 43 | onlyScroll: true, 44 | // 跳转时间间隔 s 45 | jumpInterval: 5, 46 | // 抓取到minTime就跳转至下一公众号 47 | minTime: new Date(2018, 1, 1), 48 | // 自定义最近多久更新的公众号本次就不用抓取 49 | maxUpdatedAt: new Date(2050, 6, 7), 50 | // 抓取公众号biz范围 51 | targetBiz: [], 52 | // 程序开始时间 53 | beginTime: new Date() 54 | }, 55 | // 是否抓取评论 56 | isCrawlComments: true 57 | }; 58 | 59 | // try { 60 | // // 引入外部biz文件 61 | // fs.accessSync('./targetBiz.json'); 62 | // config.insertJsToNextProfile.targetBiz = require('./targetBiz.json'); 63 | // config.insertJsToNextPage.targetBiz = require('./targetBiz.json'); 64 | // } catch(e) { 65 | // // Do nothing 66 | // } 67 | 68 | module.exports = config; 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 声明: 此项目nodejs微信爬虫原地址为 https://github.com/lqqyt2423/wechat_spider 2 | 本人在此基础: 3 | ### bug: 4 | - 修复循环爬取公众号死循环 5 | - 替换正文的正则更新 6 | 7 | ### new: 8 | - 加入appium和python脚本, 利用redis作为通信设施, 达到python操作手机脚本 和 原nodejs 程序通信配合, 让整个爬虫全自动化运行 9 | 10 | ### ps: 11 | 本人不是很懂nodejs,原作者的bug,新加的功能都是很简单的代码,重点是实现思路,如果有问题或者bug,欢迎指正 12 | 13 | ### ------以下为原作者readme------ 14 | 15 | # wechat_spider 微信爬虫 16 | 17 | 基于Node 的微信爬虫,通过中间人代理的原理,批量获取微信文章数据,包括阅读量、点赞量和评论等数据。 18 | 19 | 使用代理模块AnyProxy。代码已支持AnyProxy 4版本。 20 | 21 | ## 开始 22 | 23 | ### 安装前准备 24 | 25 | - 安装Node,版本大于 8.8.1 26 | - 安装MongoDB,版本大于 3.4.6 27 | - 安装Redis 28 | - 安装Node 全局模块nodemon 和pm2 29 | 30 | ### 安装 31 | 32 | ```shell 33 | git clone https://github.com/lqqyt2423/wechat_spider.git 34 | cd wechat_spider 35 | npm install 36 | ``` 37 | 38 | 本项目基于代理模块AnyProxy,解析微信HTTPS 请求需在电脑和手机上都安装证书。可参考:[AnyProxy 文档](http://anyproxy.io/cn/#%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE)。 39 | 40 | ## 使用 41 | 42 | ```shell 43 | cd wechat_spider 44 | npm start 45 | ``` 46 | 47 | 1. 确保电脑和手机连接同一WIFI ,`npm start` 之后,命令行输出`请配置代理: xx.xx.xx.xx:8101` 类似语句,手机设置代理为此IP 和端口 48 | 2. 手机上测试打开任一公众号历史文章详情页和文章页,观察电脑命令行的输出,查看数据是否保存至MongoDB 49 | 3. 自动翻页抓取数据需配置`config.js` 50 | 51 | ### 自定义配置 52 | 53 | 目前可支持的配置项举例如下: 54 | 55 | - 控制是否开启文章或历史详情页自动跳转 56 | - 控制跳转时间间隔 57 | - 根据文章发布时间控制抓取范围 58 | - 是否保存文章正文内容 59 | - 是否保存文章评论 60 | 61 | 可编辑`index.js` ,`config.js` 和`targetBiz.json` 进行自定义配置。文件中注释有详细说明。 62 | 63 | ### 可视化界面 64 | 65 | 前端页面已打包好,启动项目后,如无修改默认`server port` 配置,浏览器直接访问`http://localhost:8104` 即可。检测数据有无抓取保存直接刷新此页面即可。 66 | 67 | ![可视化界面](posts_screenshot.png) 68 | 69 | 前端页面由`React` 编写,如需修改,可编辑`client` 文件中的代码。 70 | 71 | ### MongoDB 数据信息 72 | 73 | 数据库database: wechat_spider 74 | 75 | 数据表collections: 76 | 77 | - posts - 文章数据 78 | - profiles - 公众号数据 79 | - comments - 评论数据 80 | - categories - 自定义的公众号分类 81 | 82 | 83 | ### 从MongoDB 导出数据 84 | 85 | ```shell 86 | mongoexport --db wechat_spider --collection posts --type=csv --fields title,link,publishAt,readNum,likeNum,msgBiz,msgMid,msgIdx,sourceUrl,cover,digest,isFail --out ~/Desktop/posts.csv 87 | ``` 88 | 89 | 以上命令会导出数据至桌面的`posts.csv` 中。具体的个性化导出请参考MongoDB 文档或者自己编写。 90 | 91 | ## License 92 | 93 | [MIT](LICENSE) 94 | -------------------------------------------------------------------------------- /rule/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | getReadAndLikeNum, 5 | getPostBasicInfo, 6 | handlePostHtml, 7 | getComments, 8 | getProfileBasicInfo, 9 | getPostList, 10 | handleProfileHtml 11 | } = require('./wechatRule'); 12 | const config = require('../config'); 13 | const fs = require('fs'); 14 | const path = require('path'); 15 | 16 | const { isReplaceImg } = config; 17 | let imgBuf; 18 | if (isReplaceImg) imgBuf = fs.readFileSync(path.join(__dirname, './replaceImg.png')); 19 | 20 | const sendResFns = [ 21 | getReadAndLikeNum, 22 | getPostBasicInfo, 23 | handlePostHtml, 24 | getComments, 25 | getProfileBasicInfo, 26 | getPostList, 27 | handleProfileHtml 28 | ]; 29 | 30 | const rule = { 31 | // 模块介绍 32 | summary: 'The rule for wechat spider, written by liqiang.', 33 | 34 | // 发送请求前拦截处理 35 | *beforeSendRequest(requestDetail) { 36 | const { requestOptions } = requestDetail; 37 | const { headers } = requestOptions; 38 | const { Accept } = headers; 39 | 40 | // 处理图片返回 41 | if (isReplaceImg && /^image/.test(Accept)) { 42 | return { 43 | response: { 44 | statusCode: 200, 45 | header: { 'content-type': 'image/png' }, 46 | body: imgBuf 47 | } 48 | }; 49 | } 50 | }, 51 | 52 | // 发送响应前处理 53 | *beforeSendResponse(requestDetail, responseDetail) { 54 | const fnLens = sendResFns.length; 55 | if (fnLens === 0) return; 56 | let i = 0; 57 | const ctx = { req: requestDetail, res: responseDetail }; 58 | const handleFn = () => { 59 | const fn = sendResFns[i]; 60 | return fn(ctx).then(res => { 61 | if (res) return res; 62 | i += 1; 63 | if (i >= fnLens) return; 64 | return handleFn(); 65 | }); 66 | }; 67 | return handleFn().catch(e => { 68 | throw e; 69 | }); 70 | } 71 | 72 | // 是否处理https请求 已全局开启解析https请求 此处注释掉即可 73 | // *beforeDealHttpsRequest(requestDetail) { /* ... */ }, 74 | 75 | // 请求出错的事件 76 | // *onError(requestDetail, error) { /* ... */ }, 77 | 78 | // https连接服务器出错 79 | // *onConnectError(requestDetail, error) { /* ... */ } 80 | }; 81 | 82 | module.exports = rule; 83 | -------------------------------------------------------------------------------- /models/plugins/paginator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Pagination Plugin 5 | */ 6 | const util = require('util'); 7 | const Query = require('mongoose').Query; 8 | 9 | const defaults = { 10 | perPage: 20, // 每页条数 11 | page : 1, // 初始页数 12 | offset : 0, // 偏移数 13 | maxPerPage: 100 // 最大单页条数 14 | }; 15 | 16 | /** 17 | * paginate 18 | * 19 | * @param {Object} options 20 | */ 21 | Query.prototype.paginate = function(options, callback) { 22 | let opts = util._extend({}, defaults); 23 | opts = util._extend(opts, options); 24 | 25 | // 转换值为数字 26 | Object.keys(defaults).forEach(function(k) { 27 | opts[k] = Number(opts[k]); 28 | }); 29 | 30 | let query = this; 31 | let model = query.model; 32 | let conditions = query._conditions; 33 | 34 | return new Promise(function(resolve, reject) { 35 | model.count(conditions, function(err, count) { 36 | 37 | opts.perPage = opts.perPage >= opts.maxPerPage ? opts.maxPerPage : opts.perPage; 38 | 39 | let _skip = (opts.page - 1) * opts.perPage; 40 | _skip += opts.offset; 41 | 42 | query.skip(_skip).limit(opts.perPage).exec(function(err, data) { 43 | if (err) { 44 | typeof callback === 'function' ? reject(callback(err)) : reject(err); 45 | return; 46 | } 47 | 48 | data = data || []; 49 | 50 | let current = parseInt(opts.page, 10) || 1; 51 | 52 | let offsetCount = count - opts.offset; 53 | offsetCount = offsetCount > 0 ? offsetCount : 0; 54 | 55 | let totalPages = Math.ceil(offsetCount / opts.perPage); 56 | 57 | let prev = !count || current === 1 ? null : current - 1; 58 | let next = !count || current === totalPages ? null : current + 1; 59 | 60 | if (!offsetCount) { 61 | prev = next = null; 62 | } 63 | 64 | let pager = { 65 | data: data, 66 | options: opts, 67 | current: current, 68 | next: next, 69 | prev: prev, 70 | totalPages: totalPages, 71 | count: count 72 | }; 73 | 74 | typeof callback === 'function' ? resolve(callback(err, pager)) : resolve(pager); 75 | }); 76 | }); 77 | }); 78 | }; 79 | -------------------------------------------------------------------------------- /index_no_script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AnyProxy = require('anyproxy'); 4 | const exec = require('child_process').exec; 5 | const ip = require('ip'); 6 | const { log } = console; 7 | const config = require('./config'); 8 | const redis = require('./utils/redis'); 9 | 10 | const { POST_LIST_KEY, PROFILE_LIST_KEY } = config.redis; 11 | 12 | // 引导安装HTTPS证书 13 | if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { 14 | AnyProxy.utils.certMgr.generateRootCA((error, keyPath) => { 15 | if (!error) { 16 | const certDir = require('path').dirname(keyPath); 17 | log('The cert is generated at', certDir); 18 | const isWin = /^win/.test(process.platform); 19 | if (isWin) { 20 | exec('start .', { cwd: certDir }); 21 | } else { 22 | exec('open .', { cwd: certDir }); 23 | } 24 | } else { 25 | console.error('error when generating rootCA', error); 26 | } 27 | }); 28 | } 29 | 30 | const options = { 31 | port: 8101, 32 | rule: require('./rule'), 33 | webInterface: { 34 | enable: false, 35 | webPort: 8102 36 | }, 37 | 38 | // 默认不限速 39 | // throttle: 10000, 40 | 41 | // 强制解析所有HTTPS流量 42 | forceProxyHttps: true, 43 | 44 | // 不开启websocket代理 45 | wsIntercept: false, 46 | 47 | silent: true 48 | }; 49 | 50 | const proxyServer = new AnyProxy.ProxyServer(options); 51 | 52 | proxyServer.on('ready', () => { 53 | const ipAddress = ip.address(); 54 | log(`请配置代理: ${ipAddress}:8101`); 55 | log('可视化界面: http://localhost:8104\n'); 56 | }); 57 | proxyServer.on('error', (e) => { 58 | throw e; 59 | }); 60 | 61 | // 删除redis中对应缓存后再启动 62 | redis('del', POST_LIST_KEY, PROFILE_LIST_KEY).then(() => { 63 | proxyServer.start(); 64 | }); 65 | 66 | // when finished 67 | // proxyServer.close(); 68 | 69 | require('./server').listen(8104); 70 | 71 | // 启动python脚本控制手机开始到 历史消息 触发爬虫 72 | 73 | // setTimeout(function() { 74 | // console.info('等3秒开始运行 python 脚本.'); 75 | // 76 | // var filename = 'auto_driver/operate_nokia.py' 77 | // exec('python'+' '+filename,function(err,stdout,stderr){ 78 | // if(err) 79 | // { 80 | // console.log('stderr',err); 81 | // } 82 | // if(stdout) 83 | // { 84 | // console.log('stdout',stdout); 85 | // } 86 | // }); 87 | // 88 | // }, 3000); 89 | 90 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AnyProxy = require('anyproxy'); 4 | const exec = require('child_process').exec; 5 | const ip = require('ip'); 6 | const { log } = console; 7 | const config = require('./config'); 8 | const redis = require('./utils/redis'); 9 | 10 | const { POST_LIST_KEY, PROFILE_LIST_KEY } = config.redis; 11 | 12 | // 引导安装HTTPS证书 13 | if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) { 14 | AnyProxy.utils.certMgr.generateRootCA((error, keyPath) => { 15 | if (!error) { 16 | const certDir = require('path').dirname(keyPath); 17 | log('The cert is generated at', certDir); 18 | const isWin = /^win/.test(process.platform); 19 | if (isWin) { 20 | exec('start .', { cwd: certDir }); 21 | } else { 22 | exec('open .', { cwd: certDir }); 23 | } 24 | } else { 25 | console.error('error when generating rootCA', error); 26 | } 27 | }); 28 | } 29 | 30 | const options = { 31 | port: 8101, 32 | rule: require('./rule'), 33 | webInterface: { 34 | enable: false, 35 | webPort: 8102 36 | }, 37 | 38 | // 默认不限速 39 | // throttle: 10000, 40 | 41 | // 强制解析所有HTTPS流量 42 | forceProxyHttps: true, 43 | 44 | // 不开启websocket代理 45 | wsIntercept: false, 46 | 47 | silent: true 48 | }; 49 | 50 | const proxyServer = new AnyProxy.ProxyServer(options); 51 | 52 | proxyServer.on('ready', () => { 53 | const ipAddress = ip.address(); 54 | log(`请配置代理: ${ipAddress}:8101`); 55 | log('可视化界面: http://localhost:8104\n'); 56 | }); 57 | proxyServer.on('error', (e) => { 58 | throw e; 59 | }); 60 | 61 | // 删除redis中对应缓存后再启动 62 | redis('del', POST_LIST_KEY, PROFILE_LIST_KEY).then(() => { 63 | proxyServer.start(); 64 | }); 65 | 66 | // redis('del', POST_LIST_KEY, PROFILE_LIST_KEY, 'profile_finish', 'post_finish' ).then(() => { 67 | // proxyServer.start(); 68 | // }); 69 | 70 | // when finished 71 | // proxyServer.close(); 72 | 73 | require('./server').listen(8104); 74 | 75 | 76 | // 启动python脚本控制手机开始到 历史消息 触发爬虫 77 | 78 | // setTimeout(function() { 79 | // console.info('等3秒开始运行 python 脚本.'); 80 | // 81 | // var filename = 'auto_driver/auto_operate_phone.py' 82 | // exec('python'+' '+filename,function(err,stdout,stderr){ 83 | // if(err) 84 | // { 85 | // console.log('stderr',err); 86 | // } 87 | // if(stdout) 88 | // { 89 | // console.log('stdout',stdout); 90 | // } 91 | // }); 92 | // 93 | // }, 3000); 94 | 95 | 96 | setTimeout(function() { 97 | console.info('等待appium脚本运行触发微信爬虫...'); 98 | }, 3000); 99 | 100 | -------------------------------------------------------------------------------- /auto_driver/tes.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | from redis import * 3 | from appium import webdriver 4 | import os 5 | 6 | desire_caps = {} 7 | desire_caps['platformName'] = 'Android' 8 | # 诺基亚6 9 | # desire_caps['platformVersion'] = '8.1.0' 10 | # desire_caps['deviceName'] = 'PL2GAM1810904175' 11 | 12 | # 小米6 13 | # desire_caps['platformVersion'] = '8.0.0' 14 | # desire_caps['deviceName'] = '1231acb' 15 | 16 | # 红米5 17 | desire_caps['platformVersion'] = '7.1.2' 18 | desire_caps['deviceName'] = '2466979e7cf5' 19 | 20 | desire_caps['appPackage'] = 'com.tencent.mm' 21 | desire_caps['appActivity'] = '.ui.LauncherUI' 22 | # 以下两项主要是在点击输入框的时候,会触发系统输入法,导致可能我们发送的是字符 `234`,但是九宫格中文输入法有可能给出的是 `bei` ,这两个属性就是屏蔽系统输入法,使用appium自己的,但是测试完成后,得自己去系统设置中将输入法切换过来 23 | desire_caps['unicodeKeyboard'] = True 24 | desire_caps['resetKeyboard'] = True 25 | # 不重置apk 26 | desire_caps['noReset'] = True 27 | desire_caps["newCommandTimeout"] = 172800 # 等待下一条命令延时 2 天 28 | # desire_caps['chromeOptions'] = {'androidProcess': 'com.tencent.mm:tools'} 29 | 30 | # ip地址在pc上的 appium客户端-设置 中可以看到 `server address` 和 `port`,保持一致即可 31 | driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desire_caps) 32 | sleep(6) 33 | 34 | # 1.点击通讯录 35 | el_contact = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"通讯录\")")[0] 36 | el_contact.click() 37 | 38 | # 2.点击公众号 39 | el_public = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"公众号\")")[0] 40 | el_public.click() 41 | 42 | # 3.第一个公众号 43 | # driver.tap([[210, 334], [306, 399]], 20) 44 | # el_public = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"差评\")")[0] 45 | el_public = driver.find_element_by_id("com.tencent.mm:id/v9").find_element_by_class_name("android.widget.LinearLayout") 46 | el_public.click() 47 | 48 | # 4.查看历史消息 49 | el_info = driver.find_element_by_accessibility_id("聊天信息") 50 | el_info.click() 51 | 52 | sleep(2) 53 | 54 | # driver.swipe(530, 1900, 530, 200, 200) # 滑动到底部 1080p 55 | 56 | driver.swipe(350, 1260, 360, 480, 200) # 滑动到底部 720p 红米5 57 | 58 | history = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"全部消息\")")[0] 59 | history.click() 60 | 61 | 62 | # 5.点击一个详情页触发详情页 63 | sleep(20) 64 | # driver.tap([(530, 1300)], 200) # 点击第一篇文章的位置进入详情 65 | driver.tap([(360, 920)], 200) # 点击第一篇文章的位置进入详情 66 | 67 | sleep(20) # 给个缓冲时间 68 | print('[NOTICE]' + ' Appium python script quit') 69 | 70 | driver.quit() 71 | 72 | print('[NOTICE]' + ' Upload Wechat info to Remote Server') 73 | current_file_path = os.path.dirname(__file__) 74 | script_name = 'upload_data.py' 75 | script_path = os.path.join(current_file_path, script_name).replace('\\', '/') 76 | os.system('python %s' % script_path) 77 | sleep(10) 78 | print('[NOTICE]' + ' Upload Compelete') 79 | -------------------------------------------------------------------------------- /scripts/checkWechatId.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const models = require('../models'); 4 | const CorrectWechatId = require('../utils/correctWechatId'); 5 | 6 | (async function start() { 7 | await fixWechatId(); 8 | await checkWechatId(); 9 | process.exit(); 10 | })(); 11 | 12 | async function checkWechatId() { 13 | const posts = await models.Post.find({ 14 | msgBiz: { $exists: true }, 15 | wechatId: { $exists: true } 16 | }).select('msgBiz wechatId'); 17 | 18 | // 所有账号对象 19 | const bizObj = {}; 20 | posts.forEach(post => { 21 | const { msgBiz, wechatId } = post; 22 | if (!bizObj[msgBiz]) { 23 | bizObj[msgBiz] = [wechatId]; 24 | } else { 25 | if (bizObj[msgBiz].indexOf(wechatId) === -1) bizObj[msgBiz].push(wechatId); 26 | } 27 | }); 28 | 29 | // 所有账号数组 30 | const bizArray = Object.keys(bizObj).map(msgBiz => { 31 | return { msgBiz: msgBiz, wechatIds: bizObj[msgBiz] }; 32 | }); 33 | 34 | // 仅有一个wechatId的msgBiz 35 | const singleWechatIdArray = bizArray.filter(item => { 36 | const wechatIds = item.wechatIds.filter(id => id); 37 | if (wechatIds.length === 1) { 38 | // 过滤掉中文的字符 39 | if (/[\u4e00-\u9fa5]/.test(wechatIds[0])) return false; 40 | return true; 41 | } 42 | return false; 43 | }).map(item => { 44 | return { 45 | msgBiz: item.msgBiz, 46 | wechatId: item.wechatIds.filter(id => id)[0] 47 | }; 48 | }); 49 | 50 | // 更新数据表记录 51 | for (let item of singleWechatIdArray) { 52 | const { msgBiz, wechatId } = item; 53 | const correctRecord = new CorrectWechatId({ msgBiz, wechatId }); 54 | await correctRecord.checkPost(); 55 | await correctRecord.checkProfile(); 56 | } 57 | 58 | const singleMsgBizs = singleWechatIdArray.map(item => item.msgBiz); 59 | 60 | // 其余有问题的需手动解决 61 | const hasToFix = bizArray.filter(item => { 62 | return singleMsgBizs.indexOf(item.msgBiz) === -1; 63 | }); 64 | 65 | hasToFix.forEach(item => { 66 | console.log('msgBiz:', item.msgBiz); 67 | console.log('wechatIds', item.wechatIds.filter(id => id).join(', ')); 68 | console.log(); 69 | }); 70 | } 71 | 72 | // 手动修复 73 | async function fixWechatId() { 74 | const array = [ 75 | ['st_gov', 'MzI0MDU1NzcxNg=='], 76 | ['gh_255e073818cb', 'MzI0OTMyMzAyMg=='], 77 | ['gh_f6346759e760', 'MzI3MjU0ODk5OQ=='], 78 | ['gh_add771035570', 'MzI3MzExODg1MA=='], 79 | ['gh_309eed5521e7', 'MzIxODg2NjQ5MQ=='], 80 | ['gh_541d9f5f914f', 'MzIyMTczMjI1NA=='], 81 | ['gh_609018f1f8e5', 'MzU2MDExMzI5OQ=='], 82 | ['gh_6db4b6109707', 'MzU3MjI2NDgxNw=='], 83 | ['gh_22372daf9473', 'MzA5MDg2MTAxOA=='], 84 | ['gh_9af59cf5576e', 'MzIzOTE2MTUyNQ=='] 85 | ]; 86 | 87 | if (array.length === 0) return; 88 | 89 | // 更新数据表记录 90 | for (let item of array) { 91 | const [wechatId, msgBiz] = item; 92 | const correctRecord = new CorrectWechatId({ msgBiz, wechatId }); 93 | await correctRecord.checkPost(); 94 | await correctRecord.checkProfile(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /client/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider, connect } from 'react-redux'; 4 | import { createStore, applyMiddleware } from 'redux'; 5 | import reducer from './reducers'; 6 | import { Router, Route, IndexRoute } from 'react-router'; 7 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 8 | import AppBar from 'material-ui/AppBar'; 9 | import Drawer from 'material-ui/Drawer'; 10 | import { List, ListItem } from 'material-ui/List'; 11 | import { createHistory, useBasename } from 'history'; 12 | import 'bootstrap/dist/css/bootstrap.css'; 13 | import 'font-awesome/css/font-awesome.min.css'; 14 | import './style/style.css'; 15 | const ENV = process.env.NODE_ENV || 'development'; 16 | const BASE_URI = '/'; 17 | 18 | import thunkMiddleware from 'redux-thunk'; 19 | import createLogger from 'redux-logger'; 20 | 21 | let reduxMiddlewares = [thunkMiddleware]; 22 | if (ENV === 'development') { 23 | reduxMiddlewares.push(createLogger); 24 | } 25 | let store = createStore( 26 | reducer, 27 | applyMiddleware(...reduxMiddlewares) 28 | ); 29 | 30 | import Posts from './containers/posts.jsx'; 31 | import Profiles from './containers/profiles.jsx'; 32 | import Profile from './containers/profile.jsx'; 33 | import Categories from './containers/categories.jsx'; 34 | 35 | class App extends React.Component { 36 | 37 | constructor(props) { 38 | super(props); 39 | } 40 | 41 | render() { 42 | let { history } = this.props; 43 | return ( 44 | 45 |
46 | 47 | { history.push('/'); }} /> 48 | 49 | { history.push('/posts'); }} /> 50 | { history.push('/profiles'); }} /> 51 | { history.push('/categories'); }} /> 52 | 53 | 54 |
55 | {this.props.children} 56 |
57 |
58 |
59 | ); 60 | } 61 | } 62 | 63 | const connectedApp = connect(state => state)(App); 64 | 65 | const browserHistory = useBasename(createHistory)({ 66 | basename: BASE_URI 67 | }); 68 | 69 | render( 70 | ( 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | ), 83 | document.getElementById('app') 84 | ); 85 | -------------------------------------------------------------------------------- /client/app/actions.js: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | 3 | export function assembleUrl(path, params, method) { 4 | path = path || ''; 5 | params = params || {}; 6 | method = method ? method.toLowerCase() : 'get'; 7 | Object.keys(params).forEach(function(key) { 8 | let _path = path.replace(`:${key}`, params[key]); 9 | if (_path === path) { 10 | if (method === 'get') { 11 | if (_path.indexOf('?') === -1) { 12 | _path = `${_path}?${key}=${params[key]}`; 13 | } else { 14 | _path = `${_path}&${key}=${params[key]}`; 15 | } 16 | delete params[key]; 17 | } 18 | } else { 19 | delete params[key]; 20 | } 21 | path = _path; 22 | }); 23 | return path; 24 | } 25 | 26 | export const REQUEST_POSTS = 'REQUEST_POSTS'; 27 | 28 | export function requestPosts() { 29 | return { 30 | type: REQUEST_POSTS 31 | }; 32 | } 33 | 34 | export const RECEIVE_POSTS = 'RECEIVE_POSTS'; 35 | 36 | export function receivePosts(posts) { 37 | return { 38 | type: RECEIVE_POSTS, 39 | posts 40 | }; 41 | } 42 | 43 | export function fetchPosts(query) { 44 | let path = assembleUrl(config.posts, query); 45 | return function(dispatch) { 46 | dispatch(requestPosts()); 47 | return fetch(path).then(res => res.json()).then(posts => { 48 | dispatch(receivePosts(posts)); 49 | }); 50 | }; 51 | } 52 | 53 | export const REQUEST_PROFILES = 'REQUEST_PROFILES'; 54 | 55 | export function requestProfiles() { 56 | return { 57 | type: REQUEST_PROFILES 58 | }; 59 | } 60 | 61 | export const RECEIVE_PROFILES = 'RECEIVE_PROFILES'; 62 | 63 | export function receiveProfiles(profiles) { 64 | return { 65 | type: RECEIVE_PROFILES, 66 | profiles 67 | }; 68 | } 69 | 70 | export function fetchProfiles(query) { 71 | let path = assembleUrl(config.profiles, query); 72 | return function(dispatch) { 73 | dispatch(requestProfiles()); 74 | return fetch(path).then(res => res.json()).then(profiles => { 75 | dispatch(receiveProfiles(profiles)); 76 | }); 77 | }; 78 | } 79 | 80 | export const REQUEST_PROFILE = 'REQUEST_PROFILE'; 81 | 82 | export function requestProfile(id) { 83 | return { 84 | type: REQUEST_PROFILE, 85 | id 86 | }; 87 | } 88 | 89 | export const RECEIVE_PROFILE = 'RECEIVE_PROFILE'; 90 | 91 | export function receiveProfile(profile) { 92 | return { 93 | type: RECEIVE_PROFILE, 94 | profile 95 | }; 96 | } 97 | 98 | export function fetchProfile(id) { 99 | return function (dispatch) { 100 | dispatch(requestProfile(id)); 101 | return fetch(`${config.profile}/${id}`).then(res => res.json()).then(profile => { 102 | dispatch(receiveProfile(profile)); 103 | }); 104 | }; 105 | } 106 | 107 | export const REQUEST_CATES = 'REQUEST_CATES'; 108 | 109 | export function requestCates() { 110 | return { 111 | type: REQUEST_CATES 112 | }; 113 | } 114 | 115 | export const RECEIVE_CATES = 'RECEIVE_CATES'; 116 | 117 | export function receiveCates(cates) { 118 | return { 119 | type: RECEIVE_CATES, 120 | cates 121 | }; 122 | } 123 | 124 | export function fetchCates() { 125 | return function(dispatch) { 126 | dispatch(requestCates()); 127 | return fetch(config.cates).then(res => res.json()).then(cates => { 128 | dispatch(receiveCates(cates)); 129 | }); 130 | }; 131 | } 132 | -------------------------------------------------------------------------------- /auto_driver/auto_operate_phone.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | from redis import * 3 | from appium import webdriver 4 | import os 5 | 6 | desire_caps = {} 7 | desire_caps['platformName'] = 'Android' 8 | # 诺基亚6 9 | # desire_caps['platformVersion'] = '8.1.0' 10 | # desire_caps['deviceName'] = 'PL2GAM1810904175' 11 | 12 | # 小米6 13 | # desire_caps['platformVersion'] = '8.0.0' 14 | # desire_caps['deviceName'] = '1231acb' 15 | 16 | # 红米5 17 | desire_caps['platformVersion'] = '7.1.2' 18 | desire_caps['deviceName'] = '2466979e7cf5' 19 | 20 | desire_caps['appPackage'] = 'com.tencent.mm' 21 | desire_caps['appActivity'] = '.ui.LauncherUI' 22 | # 以下两项主要是在点击输入框的时候,会触发系统输入法,导致可能我们发送的是字符 `234`,但是九宫格中文输入法有可能给出的是 `bei` ,这两个属性就是屏蔽系统输入法,使用appium自己的,但是测试完成后,得自己去系统设置中将输入法切换过来 23 | desire_caps['unicodeKeyboard'] = True 24 | desire_caps['resetKeyboard'] = True 25 | # 不重置apk 26 | desire_caps['noReset'] = True 27 | desire_caps["newCommandTimeout"] = 172800 # 等待下一条命令延时 2 天 28 | # desire_caps['chromeOptions'] = {'androidProcess': 'com.tencent.mm:tools'} 29 | 30 | # ip地址在pc上的 appium客户端-设置 中可以看到 `server address` 和 `port`,保持一致即可 31 | driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desire_caps) 32 | 33 | sleep(10) # 等加载出通讯录。。。 34 | 35 | # 1.点击通讯录 36 | el_contact = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"通讯录\")")[0] 37 | el_contact.click() 38 | 39 | # 2.点击公众号 40 | el_public = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"公众号\")")[0] 41 | el_public.click() 42 | 43 | # 3.第一个公众号 44 | # driver.tap([[210, 334], [306, 399]], 20) 45 | # el_public = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"差评\")")[0] 46 | el_public = driver.find_element_by_id("com.tencent.mm:id/v9").find_element_by_class_name("android.widget.LinearLayout") 47 | el_public.click() 48 | 49 | # 4.查看历史消息 50 | el_info = driver.find_element_by_accessibility_id("聊天信息") 51 | el_info.click() 52 | 53 | 54 | sleep(3) 55 | # driver.swipe(530, 1900, 530, 200, 200) # 滑动到底部 1080p 56 | driver.swipe(350, 1260, 360, 480, 200) # 滑动到底部 720p 红米5 57 | 58 | history = driver.find_elements_by_android_uiautomator("new UiSelector().text(\"全部消息\")")[0] 59 | history.click() 60 | 61 | sleep(10) # 设置100秒等待,100秒后再循环问数据库是否爬完公众号列表 62 | 63 | # ------------------------------------------ 64 | # a.循环询问redis是否爬完公众号列表 redis_key:'profile_finish' 65 | sr = StrictRedis() 66 | while True: 67 | sleep(1) # 1秒查询一次redis 68 | result = sr.get('profile_finish') 69 | if result == b'1': 70 | print('[NOTICE]' + ' Appium will click detail page') 71 | sr.delete('profile_finish') # 删掉redis 公众号爬完记录 【修改到node代码一开始启动删除】 72 | break 73 | 74 | # 5.点击一个详情页触发详情页 75 | sleep(10) 76 | # driver.tap([(530, 1300)], 200) # 点击第一篇文章的位置进入详情 1080p 77 | print('[NOTICE]' + 'click a detail') 78 | driver.tap([(360, 920)], 200) # 点击第一篇文章的位置进入详情 红米 79 | 80 | # b.再次循环询问redis是否爬玩所有文章 redis_key:'post_finish' 81 | while True: 82 | sleep(1) # 1秒查询一次redis 83 | result = sr.get('post_finish') 84 | if result == b'1': 85 | print('[NOTICE]' + ' Finish crawl') 86 | sr.delete('post_finish') # 删掉redis 文章列表爬完记录 87 | break 88 | 89 | sleep(10) # 给个缓冲时间 90 | print('[NOTICE]' + ' Appium python script quit') 91 | 92 | driver.quit() 93 | 94 | print('[NOTICE]' + ' Upload Wechat info to Remote Server') 95 | current_file_path = os.path.dirname(__file__) 96 | script_name = 'upload_data.py' 97 | script_path = os.path.join(current_file_path, script_name).replace('\\', '/') 98 | os.system('python %s' % script_path) 99 | print('[NOTICE]' + ' Upload Compelete') 100 | 101 | print('[NOTICE]' + ' 60s later close') 102 | sleep(60) -------------------------------------------------------------------------------- /client/app/containers/profiles.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchProfiles } from '../actions'; 4 | import Loading from '../components/loading.jsx'; 5 | import moment from 'moment'; 6 | import Paginator from '../components/paginator.jsx'; 7 | import { Link } from 'react-router'; 8 | import Search from './search.jsx'; 9 | 10 | class Profiles extends React.Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | this.returnCurrentSearchArgs = this.returnCurrentSearchArgs.bind(this); 15 | } 16 | 17 | componentDidMount() { 18 | let { dispatch, location } = this.props; 19 | dispatch(fetchProfiles(location.query)); 20 | } 21 | 22 | componentWillReceiveProps(nextProps) { 23 | if (nextProps.location.search !== this.props.location.search) { 24 | let { dispatch } = this.props; 25 | dispatch(fetchProfiles(nextProps.location.query)); 26 | } 27 | } 28 | 29 | returnCurrentSearchArgs() { 30 | const { location } = this.props; 31 | const { search } = location; 32 | const searchArgs = {}; 33 | search.replace('?', '').split('&').forEach(item => { 34 | let key = item.split('=')[0]; 35 | let value = item.replace(`${key}=`, ''); 36 | if (key && value) searchArgs[key] = value; 37 | }); 38 | return searchArgs; 39 | } 40 | 41 | render() { 42 | let { isFetching, profiles, history, location } = this.props; 43 | let { search, pathname } = location; 44 | if (isFetching || !profiles.data) return ; 45 | let metadata = profiles.metadata; 46 | return ( 47 |
48 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | { 71 | profiles.data.map(profile => { 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ); 86 | }) 87 | } 88 | 89 |
更新时间头像公众号最新最旧文章数有数据MsgBizDetail
{profile.openHistoryPageAt ? moment(profile.openHistoryPageAt).format('YY-MM-DD HH:mm') : ''}{profile.title}{profile.newestPostTime ? moment(profile.newestPostTime).format('YY-MM-DD'): ''}{profile.oldestPostTime ? moment(profile.oldestPostTime).format('YY-MM-DD'): ''}{profile.postsAllCount}{profile.postsHasDataCount}{profile.postsAllCount - profile.postsHasDataCount}{profile.msgBiz}detail
90 | 91 |
92 | ); 93 | } 94 | } 95 | 96 | export default connect(state => state)(Profiles); 97 | -------------------------------------------------------------------------------- /auto_driver/upload_data.py: -------------------------------------------------------------------------------- 1 | from pymongo import * 2 | from config import * 3 | import datetime 4 | import requests 5 | import json 6 | 7 | mongo_client = MongoClient(host=MONGO['host'], port=MONGO['port']) 8 | wechat_db = mongo_client.get_database(WECHAT_DB_NAME) 9 | 10 | posts_col = wechat_db.get_collection(POST_COL) # 文章列表集合 11 | profile_col = wechat_db.get_collection(PROFILE_COL) # 公众号集合 12 | comment_col = wechat_db.get_collection(COMMENTS_COL) # 评论集合 13 | 14 | # db.getCollection('posts').find({'updateNumAt':{$gte:ISODate('2018-06-11 T00:00:00.00Z')}}) 15 | # 2018-06-12 09:56:39.989755 16 | # 2017-04-11 T00:00:00.00Z 17 | 18 | ''' 19 | 先判断远端数据量是否为0,如果是0则是第一次上传,那么上传全部。 20 | 否则只上传当天 21 | ''' 22 | count_dict = requests.get('%s/if_allupload_wechat' % REMOTE_HOST) 23 | 24 | counts = json.loads(count_dict.content.decode('utf-8')) 25 | 26 | print(counts) 27 | 28 | if counts['post_count']==0 or counts['comment_count']==0 or counts['profile_count'] == 0: 29 | '''上传全部''' 30 | post_res = posts_col.find() 31 | comment_res = comment_col.find() 32 | profile_res = profile_col.find() 33 | 34 | else: 35 | # utc_now = datetime.datetime.now() - datetime.timedelta(days=1) 36 | utc_now = datetime.datetime.now() 37 | post_res = posts_col.find({'updatedAt': {'$gte': datetime.datetime(utc_now.year, utc_now.month, utc_now.day)}}) 38 | comment_res = comment_col.find({'updatedAt': {'$gte': datetime.datetime(utc_now.year, utc_now.month, utc_now.day)}}) 39 | # profile_list 基本每天都会更新 updatedAt 的日期,所以本质跟上面 profile_col.find() 一样 40 | profile_res = profile_col.find({'updatedAt': {'$gte': datetime.datetime(utc_now.year, utc_now.month, utc_now.day)}}) 41 | 42 | # a. 文章列表 43 | post_list = [] 44 | for item in post_res: 45 | if 'content' in item: 46 | item['_id'] = str(item['_id']) 47 | # 把时间变为本地时间 48 | item['createdAt'] = str(item['createdAt'] + datetime.timedelta(hours=8)) 49 | item['publishAt'] = str(item['publishAt'] + datetime.timedelta(hours=8)) 50 | item['updatedAt'] = str(item['updatedAt'] + datetime.timedelta(hours=8)) 51 | item['updateNumAt'] = str(item['updateNumAt'] + datetime.timedelta(hours=8)) 52 | post_list.append(item) 53 | else: 54 | print(item['title'], ' | 没有爬到正文') 55 | # b. 评论列表 56 | comment_list = [] 57 | for item in comment_res: 58 | 59 | 60 | item['_id'] = str(item['_id']) 61 | item['postId'] = str(item['postId']) 62 | # 每个评论的回复id也要str化 63 | if len(item['replies']) != 0: 64 | for i in item['replies']: 65 | i['_id'] = str(i['_id']) 66 | i['createTime'] = str(i['createTime'] + datetime.timedelta(hours=8)) 67 | 68 | 69 | # 把时间变为本地时间 70 | item['createTime'] = str(item['createTime'] + datetime.timedelta(hours=8)) 71 | item['createdAt'] = str(item['createdAt'] + datetime.timedelta(hours=8)) 72 | item['updatedAt'] = str(item['updatedAt'] + datetime.timedelta(hours=8)) 73 | comment_list.append(item) 74 | # c 公众号列表 75 | profile_list = [] 76 | for item in profile_res: 77 | item['_id'] = str(item['_id']) 78 | # 把时间变为本地时间 79 | item['createdAt'] = str(item['createdAt'] + datetime.timedelta(hours=8)) 80 | item['openHistoryPageAt'] = str(item['openHistoryPageAt'] + datetime.timedelta(hours=8)) 81 | item['updatedAt'] = str(item['updatedAt'] + datetime.timedelta(hours=8)) 82 | profile_list.append(item) 83 | 84 | 85 | # 加个密码验证, 防止接口泄露, 被恶意注入数据 86 | secret_key = '234sf3gsbx443gsgsrts34gsd43tsd' 87 | 88 | final_dict = dict( 89 | secret_key=secret_key, 90 | post_list=post_list, 91 | comment_list=comment_list, 92 | profile_list=profile_list 93 | ) 94 | 95 | print('公众号数量:{}\r\n文章数量:{}\r\n评论数量:{}\r\n'.format(len(profile_list), len(post_list), len(comment_list))) 96 | 97 | json_data = json.dumps(final_dict, ensure_ascii=False) 98 | 99 | # 向服务器api发送新爬取的数据 100 | headers = {'Content-Type': 'application/json'} 101 | requests.post('%s/receive_wechat' % REMOTE_HOST, headers=headers, data=json_data.encode('utf-8')) 102 | 103 | print('已向远程主机发出数据') -------------------------------------------------------------------------------- /server/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const api = express(); 5 | const config = require('../config').insertJsToNextProfile; 6 | const models = require('../models'); 7 | const { Category, Profile, Post } = models; 8 | 9 | function wrap(fn) { 10 | return function(req, res, next) { 11 | fn.call(this, req, res, next).catch(next); 12 | }; 13 | } 14 | 15 | api.get('/posts', (req, res, next) => { 16 | const { 17 | target, 18 | mainData, 19 | msgBiz, 20 | category, 21 | sortWay, 22 | q 23 | } = req.query; 24 | 25 | const query = { title: { $exists: true } }; 26 | 27 | if (q) { 28 | query.title = new RegExp(q, 'i'); 29 | } 30 | if (target === 'true') { 31 | query.msgBiz = { $in: config.targetBiz }; 32 | } 33 | if (mainData === 'true') { 34 | query.readNum = { $exists: true }; 35 | } 36 | if (mainData === 'false') { 37 | query.readNum = { $exists: false }; 38 | } 39 | if (msgBiz) { 40 | query.msgBiz = msgBiz; 41 | } 42 | 43 | let sortWayResult = { publishAt: -1, msgIdx: 1 }; 44 | if (sortWay === '-updateNumAt') { 45 | sortWayResult = { updateNumAt: -1 }; 46 | } 47 | if (sortWay === 'updateNumAt') { 48 | sortWayResult = { updateNumAt: 1 }; 49 | } 50 | if (sortWay === '-publishAt') { 51 | sortWayResult = { publishAt: -1, msgIdx: 1 }; 52 | } 53 | if (sortWay === 'publishAt') { 54 | sortWayResult = { publishAt: 1, msgIdx: 1 }; 55 | } 56 | 57 | let promise = Promise.resolve(); 58 | if (category) { 59 | promise = promise.then(() => { 60 | return Category.findOne({ _id: category }).then(category => { 61 | if (!category) return; 62 | query.msgBiz = { $in: category.msgBizs }; 63 | }); 64 | }); 65 | } 66 | return promise.then(() => { 67 | return Post.find(query).sort(sortWayResult).populate('profile').paginate(req.query).then(result => { 68 | const data = result.data; 69 | const metadata = { 70 | options: result.options, 71 | perPage: result.options.perPage, 72 | currentPage: result.current, 73 | next: result.next, 74 | prev: result.prev, 75 | totalPages: result.totalPages, 76 | count: result.count 77 | }; 78 | res.json({ 79 | metadata, 80 | data 81 | }); 82 | }); 83 | }).catch(e => { 84 | next(e); 85 | }); 86 | }); 87 | 88 | api.get('/profiles', (req, res, next) => { 89 | const { 90 | target, 91 | category, 92 | q 93 | } = req.query; 94 | 95 | let query = {}; 96 | if (target === 'true') { 97 | query.msgBiz = { $in: config.targetBiz }; 98 | } 99 | if (q) query.title = new RegExp(q, 'i'); 100 | let promise = Promise.resolve(); 101 | if (category) { 102 | promise = promise.then(() => { 103 | return Category.findOne({ _id: category }).then(category => { 104 | if (!category) return; 105 | query.msgBiz = { $in: category.msgBizs }; 106 | }); 107 | }); 108 | } 109 | return promise.then(() => { 110 | return Profile.find(query).sort({ openHistoryPageAt: -1 }).paginate(req.query).then(result => { 111 | const data = result.data; 112 | const metadata = { 113 | options: result.options, 114 | perPage: result.options.perPage, 115 | currentPage: result.current, 116 | next: result.next, 117 | prev: result.prev, 118 | totalPages: result.totalPages, 119 | count: result.count 120 | }; 121 | Promise.all(data.map(item => { 122 | return Promise.all([ 123 | Post.count({ msgBiz: item.msgBiz }).then(count => { 124 | item._doc.postsAllCount = count; 125 | }), 126 | Post.count({ msgBiz: item.msgBiz, readNum: { $exists: true } }).then(count => { 127 | item._doc.postsHasDataCount = count; 128 | }), 129 | Post.find({ msgBiz: item.msgBiz, publishAt: { $exists: true } }).sort({ publishAt: -1 }).limit(1).then(posts => { 130 | if (!posts.length) return; 131 | item._doc.newestPostTime = posts[0].publishAt; 132 | }), 133 | Post.find({ msgBiz: item.msgBiz, publishAt: { $exists: true } }).sort({ publishAt: 1 }).limit(1).then(posts => { 134 | if (!posts.length) return; 135 | item._doc.oldestPostTime = posts[0].publishAt; 136 | }) 137 | ]); 138 | })).then(() => { 139 | res.json({ 140 | metadata, 141 | data 142 | }); 143 | }); 144 | }); 145 | }).catch(e => { 146 | next(e); 147 | }); 148 | }); 149 | 150 | api.get('/profile/:id', wrap(async (req, res) => { 151 | const { id } = req.params; 152 | let profile = await Profile.findById(id); 153 | profile = profile.toObject(); 154 | 155 | // eslint-disable-next-line 156 | const { _id, __v, ...newProfile } = profile; 157 | profile = { id: _id, ...newProfile }; 158 | res.json(profile); 159 | })); 160 | 161 | api.put('/profile/:id', wrap(async (req, res) => { 162 | const { params, query } = req; 163 | const { id } = params; 164 | const { property } = query; 165 | if (!property) throw new Error('请传入property参数'); 166 | await Profile.findByIdAndUpdate(id, { property }); 167 | res.send('ok'); 168 | })); 169 | 170 | // 新建分类 171 | api.post('/categories', (req, res, next) => { 172 | const { name, msgBizs } = req.query; 173 | if (!name || !msgBizs) return next(new Error('请传入正确的参数')); 174 | Category.findOne({ name: name }).then(category => { 175 | if (category) return next(new Error('已存在同名称分类')); 176 | category = new Category({ 177 | name, 178 | msgBizs: msgBizs.split(',') 179 | }); 180 | return category.save(); 181 | }).then(() => { 182 | res.status(201).send('创建分类成功'); 183 | }).catch(e => { 184 | next(e); 185 | }); 186 | }); 187 | 188 | api.get('/categories', (req, res, next) => { 189 | Category.find({}).populate('profiles').then(categories => { 190 | res.json(categories); 191 | }).catch(e => { 192 | next(e); 193 | }); 194 | }); 195 | 196 | module.exports = api; 197 | -------------------------------------------------------------------------------- /client/app/components/Paginator.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import assign from 'lodash.assign'; 5 | import get from 'lodash/get'; 6 | import set from 'lodash/set'; 7 | import { assembleUrl } from '../actions'; 8 | 9 | class Paginator extends React.Component { 10 | static get propTypes() { 11 | return { 12 | // action: PropTypes.func.isRequired, 13 | // dispatch: PropTypes.func.isRequired, 14 | action: PropTypes.func, 15 | dispatch: PropTypes.func, 16 | currentPage: PropTypes.number, 17 | perPage: PropTypes.number, 18 | totalPages: PropTypes.number, 19 | pager: PropTypes.number, 20 | query: PropTypes.object, 21 | onChange: PropTypes.func 22 | }; 23 | } 24 | 25 | constructor(props) { 26 | super(props); 27 | this.state = { 28 | perPage: props.perPage || 10, 29 | Page: 1 30 | }; 31 | } 32 | 33 | getValue(name) { 34 | return get(this.state, name); 35 | } 36 | 37 | handleChange(name, callback) { 38 | return (e) => { 39 | let self = this; 40 | if (e.target.type === 'file') { 41 | if (!e.target.files.length) return; 42 | let file = e.target.files[0]; 43 | this.handleFileUpload(file).then(function(data) { 44 | let newState = assign({}, self.state); 45 | set(newState, name, data.data.id); 46 | self.setState(newState, callback); 47 | }); 48 | } else { 49 | let newState = assign({}, self.state); 50 | set(newState, name, e.target.value); 51 | self.setState(newState, callback); 52 | } 53 | }; 54 | } 55 | 56 | loadPage(page, overwrite) { 57 | return () => { 58 | let { query, onChange, pathname, search, history } = this.props; 59 | let { perPage } = this.state; 60 | if (perPage > 50) perPage = 50; 61 | if (search && search.indexOf('?') === 0) { 62 | let searchObj = {}; 63 | search.replace('?', '').split('&').forEach(item => { 64 | let key = item.split('=')[0]; 65 | let value = item.replace(`${key}=`, ''); 66 | searchObj[key] = value; 67 | }); 68 | query = assign({}, query, searchObj); 69 | } 70 | query = assign({}, query, { page, perPage }); 71 | if (overwrite) query._overwrite = true; 72 | 73 | if (typeof onChange === 'function') onChange(page, perPage); 74 | 75 | // dispatch(action(query)); 76 | let path = assembleUrl(pathname, query); 77 | history.push(path); 78 | }; 79 | } 80 | 81 | renderPage(obj) { 82 | obj = obj || {}; 83 | return ( 84 |
  • 85 | 86 | 87 | 88 |
  • 89 | ); 90 | } 91 | 92 | handlePerPageChange() { 93 | const { currentPage } = this.props; 94 | if (this._isRefreshingOnPerPageChange) clearTimeout(this._isRefreshingOnPerPageChange); 95 | 96 | this._isRefreshingOnPerPageChange = setTimeout(() => { 97 | this.loadPage(currentPage, true)(); 98 | }, 300); 99 | } 100 | 101 | changePage(e) { 102 | const { totalPages } = this.props; 103 | let Page = e.target.previousSibling.value||1; 104 | if(Page>totalPages) Page = totalPages; 105 | if(Page<1) Page = 1; 106 | 107 | this.loadPage(Page)(); 108 | } 109 | 110 | handleChangePage(e) { 111 | const { totalPages } = this.props; 112 | let value = e.target.value; 113 | if(value > totalPages) value = totalPages; 114 | if(value < 1) value = ''; 115 | this.setState({Page: value}); 116 | } 117 | 118 | render() { 119 | let self = this; 120 | let { currentPage, totalPages, pager, count } = this.props; 121 | currentPage = currentPage || 1; 122 | totalPages = totalPages || 1; 123 | pager = pager || 5; 124 | let minPage = currentPage - pager; 125 | let maxPage = currentPage + pager; 126 | 127 | function renderMiddlePages() { 128 | let pages = []; 129 | for (let i = 1; i <= totalPages; i++) { 130 | if (i > minPage && i < maxPage) { 131 | pages.push(self.renderPage({ active: currentPage == i, page: i, name: i })); 132 | } 133 | } 134 | 135 | return pages; 136 | } 137 | 138 | if (totalPages == 1) return null; 139 | 140 | return ( 141 | 172 | ); 173 | } 174 | } 175 | 176 | export default Paginator; 177 | -------------------------------------------------------------------------------- /client/app/containers/posts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchPosts, assembleUrl } from '../actions'; 4 | import Loading from '../components/loading.jsx'; 5 | import Paginator from '../components/paginator.jsx'; 6 | import RaisedButton from 'material-ui/RaisedButton'; 7 | import moment from 'moment'; 8 | import { Link } from 'react-router'; 9 | import Search from './search.jsx'; 10 | 11 | function timeDiff(update, publish) { 12 | let updateMoment = moment(update); 13 | let publishMoment = moment(publish); 14 | let days = updateMoment.diff(publishMoment, 'days'); 15 | if (days < 31) return `${days}天`; 16 | let months = updateMoment.diff(publishMoment, 'months'); 17 | if (months < 13) return `${months}月`; 18 | let years = updateMoment.diff(publishMoment, 'years'); 19 | return `${years}年`; 20 | } 21 | 22 | class Posts extends React.Component { 23 | 24 | constructor(props) { 25 | super(props); 26 | this.sortByTime = this.sortByTime.bind(this); 27 | this.judeMainDataShow = this.judeMainDataShow.bind(this); 28 | this.returnCurrentSearchArgs = this.returnCurrentSearchArgs.bind(this); 29 | } 30 | 31 | componentDidMount() { 32 | let { dispatch, location } = this.props; 33 | dispatch(fetchPosts(location.query)); 34 | } 35 | 36 | componentWillReceiveProps(nextProps) { 37 | if (nextProps.location.search !== this.props.location.search) { 38 | let { dispatch } = this.props; 39 | dispatch(fetchPosts(nextProps.location.query)); 40 | } 41 | } 42 | 43 | returnCurrentSearchArgs() { 44 | const { location } = this.props; 45 | const { search } = location; 46 | const searchArgs = {}; 47 | search.replace('?', '').split('&').forEach(item => { 48 | let key = item.split('=')[0]; 49 | let value = item.replace(`${key}=`, ''); 50 | if (key && value) searchArgs[key] = value; 51 | }); 52 | return searchArgs; 53 | } 54 | 55 | sortByTime(sortType) { 56 | const { location, history } = this.props; 57 | const { search, pathname } = location; 58 | const searchArgs = this.returnCurrentSearchArgs(); 59 | let iconClass = 'fa-sort'; 60 | let nextSortType = `-${sortType}`; 61 | if (search && search.indexOf('?') === 0) { 62 | if (searchArgs.sortWay) { 63 | if (searchArgs.sortWay === sortType) { 64 | iconClass = 'fa-sort-asc'; 65 | nextSortType = `-${sortType}`; 66 | } 67 | if (searchArgs.sortWay === `-${sortType}`) { 68 | iconClass = 'fa-sort-desc'; 69 | nextSortType = sortType; 70 | } 71 | } 72 | } 73 | const nextQuery = Object.assign({}, searchArgs, { 74 | sortWay: nextSortType 75 | }); 76 | const path = assembleUrl(pathname, nextQuery); 77 | return ( { history.push(path); }} className={`fa ${iconClass}`}>); 78 | } 79 | 80 | judeMainDataShow(key) { 81 | const searchArgs = this.returnCurrentSearchArgs(); 82 | const mainDataVal = searchArgs.mainData; 83 | const primary = { primary: true }; 84 | if (key === 'all' && !mainDataVal) return primary; 85 | if (key === 'yes' && mainDataVal === 'true') return primary; 86 | if (key === 'no' && mainDataVal === 'false') return primary; 87 | return null; 88 | } 89 | 90 | renderFilter() { 91 | const { location, history, posts } = this.props; 92 | const { pathname } = location; 93 | const searchArgs = this.returnCurrentSearchArgs(); 94 | const style = { 95 | margin: '10px 15px 10px 0' 96 | }; 97 | const { metadata } = posts; 98 | let count; 99 | if (metadata) count = metadata.count; 100 | return ( 101 |
    102 | { 103 | const nextQuery = { ...searchArgs }; 104 | delete nextQuery.mainData; 105 | const path = assembleUrl(pathname, nextQuery); 106 | history.push(path); 107 | }} label="全部数据" style={style} /> 108 | { 109 | const nextQuery = { ...searchArgs, mainData: 'true' }; 110 | const path = assembleUrl(pathname, nextQuery); 111 | history.push(path); 112 | }} label="有阅读量" style={style} /> 113 | { 114 | const nextQuery = { ...searchArgs, mainData: 'false' }; 115 | const path = assembleUrl(pathname, nextQuery); 116 | history.push(path); 117 | }} label="无阅读量" style={style} /> 118 | { !count ? '' : 共{count}条数据 } 119 |
    120 | ); 121 | } 122 | 123 | render() { 124 | let { isFetching, posts, history, location } = this.props; 125 | let { search, pathname } = location; 126 | if (isFetching || !posts.data) return ; 127 | let metadata = posts.metadata; 128 | return ( 129 |
    130 | {this.renderFilter()} 131 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | { 152 | posts.data.map(post => { 153 | return ( 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | ); 165 | }) 166 | } 167 | 168 |
    发布时间 {this.sortByTime('publishAt')}文章标题位置阅读数点赞数更新时间 {this.sortByTime('updateNumAt')}时间间隔公众号
    {moment(post.publishAt).format('YY-MM-DD HH:mm')}{post.title.substr(0, 25)}{post.msgIdx}{post.readNum >= 0 ? post.readNum : ''}{post.likeNum >= 0 ? post.likeNum : ''}{post.updateNumAt ? moment(post.updateNumAt).format('YY-MM-DD HH:mm') : ''}{post.updateNumAt ? timeDiff(post.updateNumAt, post.publishAt) : ''}{post.profile ? ({post.profile.title}) : post.msgBiz}
    169 | 170 |
    171 | ); 172 | } 173 | } 174 | 175 | export default connect(state => state)(Posts); 176 | -------------------------------------------------------------------------------- /utils/exportData.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const models = require('../models'); 4 | const json2csv = require('json2csv'); 5 | const moment = require('moment'); 6 | 7 | const profileMap = { 8 | 公众号: 'title', 9 | 公众号ID: 'wechatId', 10 | 公众号属性: 'property' 11 | }; 12 | 13 | const postMap = { 14 | msgBiz: 'msgBiz', 15 | 标题: 'title', 16 | 链接: 'link', 17 | 发布时间: 'publishAt', 18 | 发布位置: 'msgIdx', 19 | 阅读量: 'readNum', 20 | 点赞量: 'likeNum', 21 | 摘要: 'digest', 22 | 封面: 'cover', 23 | 内容: 'content', 24 | 阅读原文: 'sourceUrl' 25 | }; 26 | 27 | module.exports = class ExportData { 28 | 29 | constructor(options = {}) { 30 | const { msgBiz, category } = options; 31 | this.msgBiz = []; 32 | this.category = []; 33 | this.shouldGetMsgBiz = false; 34 | this.bizCategoryNameMap = {}; 35 | 36 | if (msgBiz) this.msgBiz = this.msgBiz.concat(msgBiz); 37 | if (category) this.category = this.category.concat(category); 38 | if (this.msgBiz.length === 0 && this.category.length === 0) throw new Error('请传入参数'); 39 | if (this.category.length > 0) this.shouldGetMsgBiz = true; 40 | } 41 | 42 | /** 43 | * 导出为json字符串 44 | * @param {Date} minDate 45 | * @param {Date} maxDate 46 | * @param {Object} options 47 | * @return {String} 48 | * @api public 49 | */ 50 | async toJson(minDate, maxDate, options = {}) { 51 | const posts = await this.findPosts(minDate, maxDate); 52 | const keys = Object.keys(profileMap).concat(Object.keys(postMap)); 53 | 54 | let replacer = null; 55 | const optionKeys = Object.keys(options); 56 | if (optionKeys.length > 0) { 57 | // 传入的key必须得在keys中存在 58 | const isContain = optionKeys.every(key => keys.indexOf(key) > -1); 59 | if (!isContain) throw new Error('确保格式化字段传入正确'); 60 | 61 | // 确保value全为1或全为-1 62 | const onlyOrExcept = options[optionKeys[0]]; 63 | const isAllow = optionKeys.every(key => options[key] === onlyOrExcept); 64 | if (!isAllow) throw new Error('确保value全为1或全为-1'); 65 | 66 | // 更改replacer 67 | replacer = keys.filter(key => { 68 | if (onlyOrExcept === 1) return (optionKeys.indexOf(key) > -1); 69 | return (optionKeys.indexOf(key) === -1); 70 | }); 71 | } 72 | 73 | return JSON.stringify(posts, replacer, 4); 74 | } 75 | 76 | /** 77 | * 导出为csv字符串 78 | * @param {Date} minDate 79 | * @param {Date} maxDate 80 | * @param {Object} options 81 | * @return {String} 82 | * @api public 83 | */ 84 | async toCsv(minDate, maxDate, options = {}) { 85 | const json = await this.toJson(minDate, maxDate, options); 86 | const obj = JSON.parse(json); 87 | let csv = json2csv({ data: obj }); 88 | csv = addBom(csv); 89 | return csv; 90 | } 91 | 92 | /** 93 | * 导出统计信息json字符串 94 | * @param {Date} minDate 95 | * @param {Date} maxDate 96 | * @return {String} 97 | * @api public 98 | */ 99 | async toStaJson(minDate, maxDate) { 100 | const data = await this.calcStatistic(minDate, maxDate); 101 | return JSON.stringify(data, null, 4); 102 | } 103 | 104 | /** 105 | * 导出统计信息csv字符串 106 | * @param {Date} minDate 107 | * @param {Date} maxDate 108 | * @return {String} 109 | * @api public 110 | */ 111 | async toStaCsv(minDate, maxDate) { 112 | const data = await this.calcStatistic(minDate, maxDate); 113 | let csv = json2csv({ data }); 114 | csv = addBom(csv); 115 | return csv; 116 | } 117 | 118 | /** 119 | * 查找文章 120 | * @api private 121 | */ 122 | async findPosts(minDate, maxDate) { 123 | if (this.shouldGetMsgBiz) await this.getMsgBiz(); 124 | const posts = await models.Post.find({ 125 | msgBiz: { $in: this.msgBiz }, 126 | publishAt: { $gte: minDate, $lt: maxDate }, 127 | isFail: { $ne: true } 128 | }).sort({ msgBiz: 1, publishAt: 1, msgIdx: 1 }).populate('profile'); 129 | 130 | const handledPosts = posts.map(post => { 131 | const { profile, msgBiz } = post; 132 | const postObj = {}; 133 | 134 | // 分类 135 | const category = this.bizCategoryNameMap[msgBiz]; 136 | if (category) postObj.分类 = category; 137 | 138 | // 公众号信息 139 | Object.keys(profileMap).forEach(key => { 140 | const value = profile[profileMap[key]]; 141 | if (value) postObj[key] = value; 142 | }); 143 | 144 | // 文章信息 145 | Object.keys(postMap).forEach(key => { 146 | const value = post[postMap[key]]; 147 | if (value) postObj[key] = value; 148 | 149 | // 时间格式转换 150 | if (key === '发布时间' && Object.prototype.toString.call(value) == '[object Date]') postObj[key] = moment(value).format('YYYY-MM-DD HH:mm'); 151 | }); 152 | 153 | // 用0替换undefined 154 | if (!postObj.阅读量) postObj.阅读量 = 0; 155 | if (!postObj.点赞量) postObj.点赞量 = 0; 156 | 157 | return postObj; 158 | }); 159 | 160 | return handledPosts; 161 | } 162 | 163 | /** 164 | * 计算统计信息 165 | * @api private 166 | */ 167 | async calcStatistic(...args) { 168 | const json = await this.toJson(...args); 169 | const data = JSON.parse(json); 170 | let aggrObj = {}; 171 | let aggrArray = []; 172 | data.forEach(item => { 173 | let key = item.msgBiz; 174 | if (key in aggrObj) { 175 | aggrObj[key].总阅读量 += item.阅读量 || 0; 176 | aggrObj[key].总点赞量 += item.点赞量 || 0; 177 | aggrObj[key].总发文数 += 1; 178 | if (item.发布位置 == '1') { 179 | aggrObj[key].头条总阅读量 += item.阅读量 || 0; 180 | aggrObj[key].头条总点赞量 += item.点赞量 || 0; 181 | aggrObj[key].推送次数 += 1; 182 | } 183 | if (item.阅读量 > aggrObj[key].单篇最高阅读量) { 184 | aggrObj[key].单篇最高阅读量 = item.阅读量; 185 | } 186 | } else { 187 | aggrObj[key] = { 188 | 分类: item.分类, 189 | 公众号属性: item.公众号属性, 190 | 公众号: item.公众号, 191 | 公众号ID: item.公众号ID, 192 | 总阅读量: item.阅读量 || 0, 193 | 总点赞量: item.点赞量 || 0, 194 | 总发文数: 1, 195 | 头条总阅读量: 0, 196 | 头条总点赞量: 0, 197 | 推送次数: 0, 198 | 单篇最高阅读量: item.阅读量 || 0 199 | }; 200 | if (item.发布位置 == '1') { 201 | aggrObj[key].头条总阅读量 = item.阅读量 || 0; 202 | aggrObj[key].头条总点赞量 = item.点赞量 || 0; 203 | aggrObj[key].推送次数 = 1; 204 | } 205 | } 206 | }); 207 | Object.keys(aggrObj).forEach(key => { 208 | let item = aggrObj[key]; 209 | let 公众号 = item.公众号; 210 | aggrArray.push({ 211 | 公众号: 公众号, 212 | 公众号ID: item.公众号ID, 213 | 分类: item.分类, 214 | 公众号属性: item.公众号属性, 215 | msgBiz: key, 216 | 总阅读量: item.总阅读量, 217 | 平均阅读量: Math.round(item.总阅读量 / item.总发文数), 218 | 头条总阅读量: item.头条总阅读量, 219 | 推送次数: item.推送次数, 220 | 总点赞量: item.总点赞量, 221 | 平均点赞量: Math.round(item.总点赞量 / item.总发文数), 222 | 头条总点赞量: item.头条总点赞量, 223 | 单篇最高阅读量: item.单篇最高阅读量, 224 | 总发文数: item.总发文数 225 | }); 226 | }); 227 | return aggrArray; 228 | } 229 | 230 | /** 231 | * 通过category获取msgbizs 232 | * @api private 233 | */ 234 | async getMsgBiz() { 235 | const categories = await models.Category.find({ _id: { $in: this.category } }); 236 | if (!(categories && categories.length)) return; 237 | 238 | categories.forEach(category => { 239 | const { name, msgBizs } = category; 240 | 241 | // 找到所有的msgBiz都加入进来 242 | this.msgBiz = this.msgBiz.concat(msgBizs); 243 | 244 | // 添加msgBiz和分类名称的映射 245 | msgBizs.forEach(msgBiz => { 246 | this.bizCategoryNameMap[msgBiz] = name; 247 | }); 248 | }); 249 | } 250 | 251 | }; 252 | 253 | function addBom(csv) { 254 | const bom = Buffer.from('\uFEFF'); 255 | const csvBuf = Buffer.from(csv); 256 | return Buffer.concat([bom, csvBuf]).toString(); 257 | } 258 | -------------------------------------------------------------------------------- /rule/wechatRule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const url = require('url'); 4 | const moment = require('moment'); 5 | const models = require('../models'); 6 | const { log } = console; 7 | const config = require('../config'); 8 | const cheerio = require('cheerio'); 9 | const redis = require('../utils/redis'); 10 | 11 | // 链接数组的缓存 每次重启程序后都会清空 12 | const { POST_LIST_KEY, PROFILE_LIST_KEY } = config.redis; 13 | 14 | const getReadAndLikeNum = async function(ctx) { 15 | const { req, res } = ctx; 16 | const link = req.url; 17 | if (!/mp\/getappmsgext/.test(link)) return; 18 | 19 | try { 20 | const body = res.response.body.toString(); 21 | const data = JSON.parse(body); 22 | const { read_num, like_num } = data.appmsgstat; 23 | const [readNum, likeNum] = [read_num, like_num]; 24 | 25 | const { requestData } = req; 26 | const reqData = String(requestData); 27 | const reqArgs = reqData.split('&').map(s => s.split('=')); 28 | const reqObj = reqArgs.reduce((obj, arr) => { 29 | const [key, value] = arr; 30 | obj[key] = decodeURIComponent(value); 31 | return obj; 32 | }, {}); 33 | const { __biz, mid, idx } = reqObj; 34 | const [msgBiz, msgMid, msgIdx] = [__biz, mid, idx]; 35 | 36 | const post = await models.Post.findOneAndUpdate( 37 | { msgBiz, msgMid, msgIdx }, 38 | { readNum, likeNum, updateNumAt: new Date() }, 39 | { new: true, upsert: true } 40 | ); 41 | const { id, title } = post; 42 | if (title) { 43 | log('文章标题:',title); 44 | } else { 45 | log('文章id:',id); 46 | } 47 | log('阅读量:', readNum, '点赞量:', likeNum); 48 | log(); 49 | 50 | await redis('llen', POST_LIST_KEY).then(len => { 51 | log('剩余文章抓取长度:', len); 52 | log(); 53 | }); 54 | 55 | } catch(e) { 56 | throw e; 57 | } 58 | }; 59 | 60 | const getPostBasicInfo = async function(ctx) { 61 | if (!isPostPage(ctx)) return; 62 | 63 | const { req, res } = ctx; 64 | const link = req.url; 65 | const body = res.response.body.toString(); 66 | 67 | const urlObj = url.parse(link, true); 68 | const { query } = urlObj; 69 | const { __biz, mid, idx } = query; 70 | const [msgBiz, msgMid, msgIdx] = [__biz, mid, idx]; 71 | 72 | // 判断此文是否失效 73 | if (body.indexOf('global_error_msg') > -1 || body.indexOf('icon_msg warn') > -1) { 74 | await models.Post.findOneAndUpdate( 75 | { msgBiz, msgMid, msgIdx }, 76 | { isFail: true }, 77 | { upsert: true } 78 | ); 79 | return; 80 | } 81 | 82 | 83 | // 若数据库中不存在此篇文章 则更新基础信息 84 | await models.Post.findOne({ msgBiz, msgMid, msgIdx }).then(post => { 85 | if (post && post.title && post.link && post.wechatId) return; 86 | 87 | const getTarget = regexp => { 88 | let target; 89 | body.replace(regexp, (_, t) => { 90 | target = t; 91 | }); 92 | return target; 93 | }; 94 | 95 | let wechatId = getTarget(/(.+?)<\/span>/); 96 | // 如果上面找到的微信id中包含中文字符 则证明此微信号没有设置微信id 则取微信给定的user_name初始字段 97 | if (wechatId && /[\u4e00-\u9fa5]/.test(wechatId)) { 98 | wechatId = getTarget(/var user_name = "(.+?)"/); 99 | } 100 | 101 | // 更新wechatId 102 | if (wechatId && post && (!post.wechatId) && post.title && post.link) { 103 | return models.Post.findOneAndUpdate( 104 | { msgBiz, msgMid, msgIdx }, 105 | { wechatId }, 106 | { upsert: true } 107 | ); 108 | } 109 | 110 | const title = getTarget(/var msg_title = "(.+?)";/); 111 | let publishAt = getTarget(/var ct = "(\d+)";/); 112 | if (publishAt) publishAt = new Date(parseInt(publishAt) * 1000); 113 | const sourceUrl = getTarget(/var msg_source_url = '(.*?)';/); 114 | const cover = getTarget(/var msg_cdn_url = "(.+?)";/); 115 | const digest = getTarget(/var msg_desc = "(.+?)";/); 116 | 117 | return models.Post.findOneAndUpdate( 118 | { msgBiz, msgMid, msgIdx }, 119 | { title, link, publishAt, sourceUrl, cover, digest, wechatId }, 120 | { upsert: true } 121 | ); 122 | }); 123 | 124 | // 保存正文内容 125 | if (config.insertJsToNextPage.isSavePostContent) { 126 | const $ = cheerio.load(body, { decodeEntities: false }); 127 | let content; 128 | if (config.insertJsToNextPage.saveContentType === 'html') { 129 | content = $('#js_content').html() || ''; 130 | } else { 131 | content = $('#js_content').text() || ''; 132 | } 133 | content = content.trim(); 134 | await models.Post.findOneAndUpdate( 135 | { msgBiz, msgMid, msgIdx }, 136 | { content }, 137 | { upsert: true } 138 | ); 139 | } 140 | 141 | }; 142 | 143 | const handlePostHtml = async function(ctx) { 144 | if (!isPostPage(ctx)) return; 145 | 146 | const { res } = ctx; 147 | let body = res.response.body.toString(); 148 | 149 | // 替换显示在手机上的正文 加速网络 150 | if (config.isReplacePostBody) { 151 | const len = await redis('llen', POST_LIST_KEY); 152 | body.replace(/
    ((\s|\S)+?)<\/div>\s+?`; 391 | body = body.replace('','').replace('',''); 392 | body = body.replace('',insertJsStr + '\n'); 393 | return { 394 | response: { ...res.response, body } 395 | }; 396 | 397 | 398 | 399 | }; 400 | 401 | async function savePostsData(postList) { 402 | const posts = []; 403 | postList.forEach(post => { 404 | const appMsg = post.app_msg_ext_info; 405 | if (!appMsg) return; 406 | const publishAt = new Date(post.comm_msg_info.datetime * 1000); 407 | posts.push({ appMsg, publishAt }); 408 | 409 | const multiAppMsg = appMsg.multi_app_msg_item_list; 410 | if (!(multiAppMsg && multiAppMsg.length > 0)) return; 411 | multiAppMsg.forEach(appMsg => { 412 | posts.push({ appMsg, publishAt }); 413 | }); 414 | }); 415 | 416 | await Promise.all(posts.map(post => { 417 | const { appMsg, publishAt } = post; 418 | const title = appMsg.title; 419 | const link = appMsg.content_url; 420 | if (!(title && link)) return; 421 | 422 | const urlObj = url.parse(link, true); 423 | const { query } = urlObj; 424 | let { __biz, mid, idx } = query; 425 | if (!mid) mid = query['amp;mid']; 426 | if (!idx) idx = query['amp;idx']; 427 | const [msgBiz, msgMid, msgIdx] = [__biz, mid, idx]; 428 | 429 | const [ cover, digest, sourceUrl ] = [ appMsg.cover, appMsg.digest, appMsg.source_url ]; 430 | 431 | return models.Post.findOneAndUpdate( 432 | { msgBiz, msgMid, msgIdx }, 433 | { title, link, publishAt, cover, digest, sourceUrl }, 434 | { new: true, upsert: true } 435 | ).then(post => { 436 | log('发布时间:', post.publishAt ? moment(post.publishAt).format('YYYY-MM-DD HH:mm') : ''); 437 | log('文章标题:', post.title, '\n'); 438 | }); 439 | })); 440 | 441 | await redis('llen', PROFILE_LIST_KEY).then(len => { 442 | log('剩余公众号抓取长度:', len); 443 | log(); 444 | }); 445 | } 446 | 447 | function isPostPage(ctx) { 448 | const { req } = ctx; 449 | const link = req.url; 450 | const isPost = /mp\.weixin\.qq\.com\/s\?__biz/.test(link); 451 | const isOldPost = /mp\/appmsg\/show/.test(link); 452 | if (!(isPost || isOldPost)) return false; 453 | return true; 454 | } 455 | 456 | // 文章跳转链接放在redis中 457 | async function getNextPostLink() { 458 | // 先从redis中取链接 459 | let nextLink = await redis('lpop', POST_LIST_KEY); 460 | if (nextLink) return nextLink; 461 | 462 | // 没有拿到链接则从数据库中查 463 | const { insertJsToNextPage } = config; 464 | const { minTime, maxTime, isCrawlExist, targetBiz, crawlExistInterval } = insertJsToNextPage; 465 | 466 | const searchQuery = { 467 | isFail: null, 468 | link: { $exists: true }, 469 | publishAt: { $gte: minTime, $lte: maxTime } 470 | }; 471 | 472 | if (targetBiz && targetBiz.length > 0) searchQuery.msgBiz = { $in: targetBiz }; 473 | 474 | if (!isCrawlExist) searchQuery.updateNumAt = null; 475 | 476 | const links = await models.Post.find(searchQuery).select('link publishAt updateNumAt').then(posts => { 477 | if (!(posts && posts.length > 0)) return []; 478 | 479 | // 根据config中的是否抓取已经抓去过的文章来判断逻辑 480 | if (!isCrawlExist) { 481 | return posts.map(post => post.link); 482 | } else { 483 | return posts.filter(post => { 484 | const { publishAt, updateNumAt } = post; 485 | if (!updateNumAt) return true; 486 | if (new Date(updateNumAt).getTime() - new Date(publishAt).getTime() > crawlExistInterval) { 487 | return false; 488 | } else { 489 | return true; 490 | } 491 | }).map(post => post.link); 492 | } 493 | }); 494 | 495 | // 如果还查不到 则证明已经抓取完毕了 返回undefined 496 | if (links.length === 0) return; 497 | 498 | // 将从数据库中查到的链接放入redis中 499 | await redis('rpush', POST_LIST_KEY, links); 500 | 501 | // 再查一次就有下一个链接了 502 | return getNextPostLink(); 503 | } 504 | 505 | // 公众号跳转链接放在redis中 506 | async function getNextProfileLink() { 507 | // 先从redis中取链接 508 | let nextLink = await redis('lpop', PROFILE_LIST_KEY); 509 | 510 | if (nextLink) return nextLink; 511 | 512 | // 没有拿到链接则从数据库中查 513 | if (config.read_mongo_count == 1) return; //mongo读过了,不读了 514 | 515 | const { insertJsToNextProfile } = config; 516 | const { maxUpdatedAt, targetBiz } = insertJsToNextProfile; 517 | 518 | const searchQuery = { 519 | $or: [ 520 | { openHistoryPageAt: { $lte: maxUpdatedAt } }, 521 | { openHistoryPageAt: { $exists: false } } 522 | ] 523 | }; 524 | 525 | 526 | // if (targetBiz && targetBiz.length > 0) searchQuery.msgBiz = { $in: targetBiz }; 527 | 528 | const links = await models.Profile.find(searchQuery).select('msgBiz').then(profiles => { 529 | if (!(profiles && profiles.length > 0)) return []; 530 | return profiles.map(profile => { 531 | const link = `https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=${profile.msgBiz}&scene=124#wechat_redirect`; 532 | return link; 533 | }); 534 | }); 535 | 536 | config.read_mongo_count += 1 //mongo读过了,+1, 下次判断不再读 537 | // 如果还查不到 则证明已经抓取完毕了 返回undefined 538 | // if (links.length === 0) return; 539 | 540 | // 将从数据库中查到的链接放入redis中 541 | await redis('rpush', PROFILE_LIST_KEY, links); 542 | 543 | // 再查一次就有下一个链接了 544 | return getNextProfileLink(); 545 | } 546 | 547 | module.exports = { 548 | getReadAndLikeNum, 549 | getPostBasicInfo, 550 | handlePostHtml, 551 | getComments, 552 | getProfileBasicInfo, 553 | getPostList, 554 | handleProfileHtml 555 | }; 556 | -------------------------------------------------------------------------------- /ts.js: -------------------------------------------------------------------------------- 1 | (function(b) { 2 | function e(g) { 3 | if (a[g]) 4 | return a[g].exports; 5 | var l = a[g] = { 6 | i: g, 7 | l: !1, 8 | exports: {} 9 | }; 10 | b[g].call(l.exports, l, l.exports, e); 11 | l.l = !0; 12 | return l.exports 13 | } 14 | var a = {}; 15 | e.m = b; 16 | e.c = a; 17 | e.d = function(g, a, f) { 18 | e.o(g, a) || Object.defineProperty(g, a, { 19 | configurable: !1, 20 | enumerable: !0, 21 | get: f 22 | }) 23 | } 24 | ; 25 | e.n = function(g) { 26 | var a = g && g.__esModule ? function() { 27 | return g["default"] 28 | } 29 | : function() { 30 | return g 31 | } 32 | ; 33 | e.d(a, "a", a); 34 | return a 35 | } 36 | ; 37 | e.o = function(a, l) { 38 | return Object.prototype.hasOwnProperty.call(a, l) 39 | } 40 | ; 41 | e.p = "//vr0.6rooms.com/tao/v11/"; 42 | return e(e.s = "sZR/") 43 | } 44 | )({ 45 | "+itZ": function(b, e, a) { 46 | a.d(e, "q", function() { 47 | return g 48 | }); 49 | a.d(e, "p", function() { 50 | return l 51 | }); 52 | a.d(e, "j", function() { 53 | return f 54 | }); 55 | a.d(e, "i", function() { 56 | return c 57 | }); 58 | a.d(e, "h", function() { 59 | return n 60 | }); 61 | a.d(e, "g", function() { 62 | return d 63 | }); 64 | a.d(e, "f", function() { 65 | return h 66 | }); 67 | a.d(e, "e", function() { 68 | return k 69 | }); 70 | a.d(e, "m", function() { 71 | return m 72 | }); 73 | a.d(e, "l", function() { 74 | return B 75 | }); 76 | a.d(e, "k", function() { 77 | return r 78 | }); 79 | a.d(e, "b", function() { 80 | return t 81 | }); 82 | a.d(e, "a", function() { 83 | return p 84 | }); 85 | a.d(e, "c", function() { 86 | return u 87 | }); 88 | a.d(e, "d", function() { 89 | return v 90 | }); 91 | a.d(e, "s", function() { 92 | return y 93 | }); 94 | a.d(e, "n", function() { 95 | return F 96 | }); 97 | a.d(e, "r", function() { 98 | return Z 99 | }); 100 | a.d(e, "t", function() { 101 | return O 102 | }); 103 | a.d(e, "o", function() { 104 | return A 105 | }); 106 | var g = [{ 107 | text: "\u5e93\u5b58", 108 | inventory: 1 109 | }, { 110 | text: "\u521d\u7ea7", 111 | flag: 1 112 | }, { 113 | text: "\u4e2d\u7ea7", 114 | flag: 2 115 | }, { 116 | text: "\u9ad8\u7ea7", 117 | flag: 3 118 | }, { 119 | text: "\u8c6a\u534e", 120 | flag: 4 121 | }, { 122 | text: "\u7279\u6b8a", 123 | flag: 5 124 | }, { 125 | text: "\u8da3\u5473", 126 | flag: 6 127 | }, { 128 | text: "\u4f34\u821e", 129 | flag: 7 130 | }, { 131 | text: "\u8d35\u65cf", 132 | flag: 8 133 | }, { 134 | text: "\u5957\u793c", 135 | flag: 9 136 | }, { 137 | text: "\u5b88\u62a4", 138 | flag: 10 139 | }] 140 | , l = [["1\u4e2a", "1"], ["5\u4e2a", "5"], ["10\u4e2a", "10"], ["20\u4e2a", "20"], ["V(50\u4e2a)", "50"], ["\u5fc3(99\u4e2a)", "99"], ["\u7b11\u8138(100\u4e2a)", "100"], ["LOVE(300\u4e2a)", "300"], ["\u7231\u4e4b\u7bad(520\u4e2a)", "520"], ["\u6bd4\u7ffc\u53cc\u98de(999\u4e2a)", "999"], ["\u4e00\u751f\u4e00\u4e16(1314\u4e2a)", "1314"], ["\u751f\u751f\u4e16\u4e16(3344\u4e2a)", "3344"]] 141 | , f = "//vr0.6rooms.com/img/emotion" 142 | , c = [32, 32] 143 | , n = { 144 | 0: "/\u72c2\u7b11", 145 | 1: "/\u5927\u7b11", 146 | 2: "/\u60ca\u8bb6", 147 | 3: "/\u5bb3\u7f9e", 148 | 4: "/\u7a83\u7b11", 149 | 5: "/\u53d1\u6012", 150 | 6: "/\u5927\u54ed", 151 | 7: "/\u8272\u8272", 152 | 8: "/\u574f\u7b11", 153 | 9: "/\u706b\u5927", 154 | 10: "/\u6c57", 155 | 11: "/\u5978\u7b11", 156 | 12: "/\u6b22\u8fce", 157 | 13: "/\u518d\u89c1", 158 | 14: "/\u767d\u773c", 159 | 15: "/\u6316\u9f3b", 160 | 16: "/\u9876", 161 | 17: "/\u80dc\u5229", 162 | 18: "/\u6b27\u8036", 163 | 19: "/\u62b1\u62f3", 164 | 20: "/\u56e7", 165 | 21: "/\u6de1\u5b9a", 166 | 22: "/\u7f8e\u5973", 167 | 23: "/\u9753\u4ed4", 168 | 24: "/\u795e\u9a6c", 169 | 25: "/\u5f00\u5fc3", 170 | 26: "/\u7ed9\u529b", 171 | 27: "/\u98de\u543b", 172 | 28: "/\u7728\u773c", 173 | 29: "/V5", 174 | 30: "/\u6765\u5427", 175 | 31: "/\u56f4\u89c2", 176 | 32: "/\u98d8\u8fc7", 177 | 33: "/\u5730\u96f7", 178 | 34: "/\u83dc\u5200", 179 | 35: "/\u5e05", 180 | 36: "/\u5ba1\u89c6", 181 | 37: "/\u65e0\u8bed", 182 | 38: "/\u65e0\u5948", 183 | 39: "/\u4eb2\u4eb2", 184 | 40: "/\u52fe\u5f15", 185 | 41: "/\u540e\u540e", 186 | 42: "/\u5410\u8840", 187 | 44: "/\u5a9a\u773c", 188 | 45: "/\u6101\u4eba", 189 | 46: "/\u80bf\u4e48\u4e86", 190 | 47: "/\u8c03\u620f", 191 | 48: "/\u62bd", 192 | 49: "/\u54fc\u54fc", 193 | 50: "/bs", 194 | 52: "/\u9e21\u51bb", 195 | 53: "/\u773c\u998b", 196 | 54: "/\u70ed\u6c57", 197 | 55: "/\u8f93", 198 | 56: "/\u77f3\u5316", 199 | 57: "/\u8511\u89c6", 200 | 58: "/\u54ed", 201 | 59: "/\u9a82" 202 | } 203 | , d = "//vr0.6rooms.com/img/emotion-guard" 204 | , h = [56, 59.5] 205 | , k = { 206 | 10: "/\u88ab\u6241", 207 | 11: "/\u53d8\u8138", 208 | 12: "/\u5403\u996d", 209 | 13: "/\u5439\u88d9\u5b50", 210 | 14: "/\u6253\u52ab", 211 | 15: "/\u61a8\u7b11", 212 | 16: "/\u6cea\u6d41\u6ee1\u9762", 213 | 17: "/\u50bb\u7b11", 214 | 18: "/\u60ca\u5413", 215 | 19: "/\u60ca\u6050", 216 | 20: "/\u597d\u56e7", 217 | 21: "/\u8e72\u5899\u89d2", 218 | 22: "/\u53ef\u7231", 219 | 23: "/\u59d4\u5c48\u843d\u6cea", 220 | 24: "/\u62a0\u9f3b", 221 | 25: "/\u4eb2\u4e00\u4e2a", 222 | 26: "/\u8272\u8ff7\u8ff7", 223 | 27: "/\u95ea\u95ea\u53d1\u5149", 224 | 28: "/\u8650", 225 | 29: "/\u5e78\u798f", 226 | 30: "/\u88c5\u5e05", 227 | 31: "/\u62cd\u7816", 228 | 32: "/\u5de6\u5410", 229 | 33: "/\u53f3\u5410", 230 | 34: "/\u5de6\u95ea", 231 | 35: "/\u53f3\u8eb2", 232 | 0: "/\u767d\u5bcc\u7f8e", 233 | 1: "/\u5fc3\u52a8\u7684\u611f\u89c9", 234 | 2: "/\u5144\u5f1f\u4eec\u4e0a", 235 | 3: "/\u6c42\u4ea4\u5f80", 236 | 4: "/\u5ac1\u7ed9\u6211\u5427", 237 | 5: "/\u5728\u4e00\u8d77", 238 | 6: "/\u770b\u597d\u8001\u5a46", 239 | 7: "/\u597d\u57fa\u53cb", 240 | 8: "/\u5c4c\u7206\u4e86", 241 | 9: "/\u8d70\u4f60" 242 | } 243 | , m = "//vr0.6rooms.com/img/emotion-vip" 244 | , B = [84, 17] 245 | , r = { 246 | 0: "/\u55e8\u8d77\u6765", 247 | 1: "/\u771f\u597d\u542c", 248 | 2: "/\u9738\u6c14", 249 | 3: "/\u7ea2\u5305\u5237\u8d77\u6765", 250 | 4: "/\u592a\u6f02\u4eae\u4e86", 251 | 5: "/\u9a6c\u4e0a\u6295\u7968", 252 | 6: "/\u73ab\u7470\u5728\u54ea\u91cc", 253 | 7: "/\u571f\u8c6a\u6765\u5566", 254 | 8: "/\u7231\u6b7b\u4f60\u4e86", 255 | 9: "/\u5575\u4e00\u4e2a", 256 | 10: "/\u65b0\u8d27\u6c42\u5173\u6ce8", 257 | 11: "/\u8981\u62b1\u62b1", 258 | 12: "/\u5192\u4e2a\u6ce1", 259 | 13: "/\u6709\u9ed1\u5e55", 260 | 14: "/\u7231\u4f601314", 261 | 15: "/\u597d\u751c\u5440", 262 | 16: "/\u5751\u7239", 263 | 17: "/\u5973\u6c49\u5b50", 264 | 18: "/\u9f13\u638c", 265 | 19: "/\u52a0\u6cb9", 266 | 20: "/\u5929\u7136\u5446", 267 | 21: "/\u8d5e" 268 | } 269 | , t = "\u62db\u5546\u94f6\u884c \u4e2d\u56fd\u5de5\u5546\u94f6\u884c \u4e2d\u56fd\u519c\u4e1a\u94f6\u884c \u4e2d\u56fd\u5efa\u8bbe\u94f6\u884c \u4ea4\u901a\u94f6\u884c \u4e0a\u6d77\u6d66\u4e1c\u53d1\u5c55\u94f6\u884c \u4e2d\u56fd\u6c11\u751f\u94f6\u884c \u5149\u5927\u94f6\u884c \u5174\u4e1a\u94f6\u884c \u5e7f\u53d1\u94f6\u884c \u5e73\u5b89\u94f6\u884c \u4e2d\u56fd\u94f6\u884c \u4e2d\u4fe1\u94f6\u884c \u534e\u590f\u94f6\u884c \u4e0a\u6d77\u94f6\u884c \u83b1\u5546\u94f6\u884c \u5e7f\u4e1c\u5357\u7ca4\u94f6\u884c \u91d1\u534e\u94f6\u884c \u5b81\u590f\u94f6\u884c \u664b\u4e2d\u94f6\u884c \u65e5\u7167\u94f6\u884c \u629a\u987a\u94f6\u884c \u519c\u6751\u4fe1\u7528\u5408\u4f5c\u793e \u5176\u4ed6".split(" ") 270 | , p = "allowEdit" 271 | , u = "denyEdit" 272 | , v = "denyEditIfExit" 273 | , y = "_null_" 274 | , F = [463] 275 | , Z = 44 276 | , O = 58 277 | , A = /^(?:1[3-8]\d)\d{5}(\d{3}|\*{3})$/ 278 | }, 279 | "+z1K": function(b, e, a) { 280 | Object.defineProperty(e, "__esModule", { 281 | value: !0 282 | }); 283 | e.default = { 284 | randrange: function(a, l) { 285 | return a + Math.floor(Math.random() * (l - a)) 286 | }, 287 | shuffleArray: function(a) { 288 | for (var l = a.length, f, c; l; ) 289 | c = Math.floor(Math.random() * l--), 290 | f = a[l], 291 | a[l] = a[c], 292 | a[c] = f; 293 | return a 294 | } 295 | } 296 | }, 297 | "592r": function(b, e, a) { 298 | Object.defineProperty(e, "__esModule", { 299 | value: !0 300 | }); 301 | b = (b = a("vzCy")) && b.__esModule ? b : { 302 | default: b 303 | }; 304 | a = a("agns"); 305 | var g = function f(c, a) { 306 | f.superclass.constructor.call(this); 307 | this.onOpen = this.onOpen.bind(this); 308 | this.onClose = this.onClose.bind(this); 309 | this.onMessage = this.onMessage.bind(this); 310 | this.onError = this.onError.bind(this); 311 | this._core = null; 312 | this._queue = []; 313 | c && this.connect(c, a) 314 | }; 315 | (0, 316 | a.extend)(g, b.default, { 317 | connect: function(f, c) { 318 | if ("WebSocket"in self) { 319 | this._core && (this.cleanQueue(), 320 | this.destroyCore()); 321 | var a = c ? new self.WebSocket(f,c) : new self.WebSocket(f); 322 | a.onopen = this.onOpen; 323 | a.onclose = this.onClose; 324 | a.onmessage = this.onMessage; 325 | a.onerror = this.onError; 326 | this.url = a.url; 327 | this.protocol = a.protocol; 328 | this._core = a 329 | } else 330 | this.emit("nonsupport") 331 | }, 332 | send: function(f) { 333 | this.isOpen() ? (this._core.send(f), 334 | this.emit("send", f)) : this._queue.push(f) 335 | }, 336 | close: function() { 337 | this.destroyCore(); 338 | this.cleanQueue() 339 | }, 340 | sendQueue: function() { 341 | for (var f = this._queue, c; c = f.shift(); ) 342 | this._core.send(c), 343 | this.emit("send", c) 344 | }, 345 | destroyCore: function() { 346 | var f = this._core; 347 | f && (f.onopen = f.onmessage = f.onclose = f.onerror = null, 348 | f.close(), 349 | this._core = null) 350 | }, 351 | cleanQueue: function() { 352 | this._queue = [] 353 | }, 354 | onOpen: function(f) { 355 | this.emit("open", f); 356 | this.sendQueue() 357 | }, 358 | onClose: function(f) { 359 | this.emit("close", f) 360 | }, 361 | onMessage: function(f) { 362 | this.emit("message", f.data, f) 363 | }, 364 | onError: function(f) { 365 | this.emit("error", f) 366 | }, 367 | isConnecting: function() { 368 | return this._core && 0 == this._core.readyState 369 | }, 370 | isOpen: function() { 371 | return this._core && 1 == this._core.readyState 372 | }, 373 | isClosing: function() { 374 | return this._core && 2 == this._core.readyState 375 | }, 376 | isClosed: function() { 377 | return !this._core || 3 == this._core.readyState 378 | } 379 | }); 380 | e.default = g 381 | }, 382 | CPiQ: function(b, e, a) { 383 | b = function() { 384 | this.uid = this.ticket = ""; 385 | this.userInfo = null; 386 | self.addEventListener("message", this._messageHanlder.bind(this)) 387 | } 388 | ; 389 | b.prototype = { 390 | isLogin: function() { 391 | return !!this.ticket 392 | }, 393 | _messageHanlder: function(a) { 394 | a = JSON.parse(a.data); 395 | var l = a.content; 396 | "user-state-update" == a.cmd && (this.ticket = l.ticket, 397 | this.uid = l.uid, 398 | this.userInfo = l.userInfo) 399 | } 400 | }; 401 | e.a = new b 402 | }, 403 | Jvqk: function(b, e, a) { 404 | b = a("agns"); 405 | a.n(b); 406 | var g = a("+z1K") 407 | , l = a.n(g) 408 | , f = a("fiJj") 409 | , c = a("zx4i") 410 | , n = a("kFTT") 411 | , d = "function" === typeof Symbol && "symbol" === typeof Symbol.iterator ? function(c) { 412 | return typeof c 413 | } 414 | : function(c) { 415 | return c && "function" === typeof Symbol && c.constructor === Symbol && c !== Symbol.prototype ? "symbol" : typeof c 416 | } 417 | , h = function() { 418 | return function(c, d) { 419 | if (Array.isArray(c)) 420 | return c; 421 | if (Symbol.iterator in Object(c)) { 422 | var f = [] 423 | , a = !0 424 | , h = !1 425 | , n = void 0; 426 | try { 427 | for (var g = c[Symbol.iterator](), l; !(a = (l = g.next()).done) && (f.push(l.value), 428 | !d || f.length !== d); a = !0) 429 | ; 430 | } catch (e) { 431 | h = !0, 432 | n = e 433 | } finally { 434 | try { 435 | if (!a && g["return"]) 436 | g["return"]() 437 | } finally { 438 | if (h) 439 | throw n; 440 | } 441 | } 442 | return f 443 | } 444 | throw new TypeError("Invalid attempt to destructure non-iterable instance"); 445 | } 446 | }(); 447 | a = function m() { 448 | m.superclass.constructor.call(this); 449 | this._addressPool = []; 450 | this._addressPoolIndex = 0; 451 | this._authKey = ""; 452 | this._waitQueue = []; 453 | this._onGlobalBatchMessage = this._onGlobalBatchMessage.bind(this) 454 | } 455 | ; 456 | Object(b.extend)(a, c.a, { 457 | getAddressPool: function() { 458 | var c = this 459 | , d = this._loginData; 460 | return Object(f.b)("coop-mobile-chatConf.php", { 461 | type: "chat", 462 | ruid: d.roomid, 463 | uid: d.uid 464 | }).then(function(d) { 465 | if ("001" == d.flag) 466 | return c._addressPool = l.a.shuffleArray(d.content.websock), 467 | c._addressPool; 468 | throw Error(d.content); 469 | }) 470 | }, 471 | getAddress: function() { 472 | var c = this._addressPool; 473 | return c[this._addressPoolIndex++ % c.length] 474 | }, 475 | onReceiveMessage: function(c) { 476 | if ("001" != c.flag) 477 | this.emit("receiveerr", c), 478 | this.emit("receiveerr:" + c.flag, c); 479 | else { 480 | var d = c.content; 481 | this._emitReceiveEvent(d); 482 | if (408 == d.typeID || 404 == d.typeID) 483 | this._authKey = (d.content["404"] ? d.content["404"].content : d.content).authKey, 484 | this._dumpWaitQueue(); 485 | if (1413 == d.typeID) 486 | for (var a = d.content, h = 0; h < a.length; h++) 487 | this._emitReceiveEvent(a[h]); 488 | 416 == d.typeID && Object(f.b)("room-getRoomMsgSys.php", { 489 | t: c.content.content 490 | }).then(this._onGlobalBatchMessage) 491 | } 492 | 701 == c.content.typeID && (this.emit("receiveres", c.content.content), 493 | this.emit("receiveres:" + c.content.content.t, c.content.content)) 494 | }, 495 | sendMsg: function(d, f) { 496 | this._authKey || "priv_info" == d ? c.a.prototype.sendMsg.call(this, d, f) : this._waitQueue.push([d, f]) 497 | }, 498 | close: function() { 499 | this._authKey = ""; 500 | this._waitQueue = []; 501 | c.a.prototype.close.call(this) 502 | }, 503 | _emitReceiveEvent: function(c) { 504 | n.a.filter(c) && this.emit("receive:" + c.typeID, c) 505 | }, 506 | _onGlobalBatchMessage: function(c) { 507 | for (var d = 0; d < c.content.length; d++) 508 | this._emitReceiveEvent(c.content[d]) 509 | }, 510 | _dumpWaitQueue: function() { 511 | for (var c; c = this._waitQueue.shift(); ) { 512 | var f = h(c, 2); 513 | c = f[0]; 514 | (f = f[1]) && "object" == ("undefined" === typeof f ? "undefined" : d(f)) && (f.ak = f.ak || this._authKey); 515 | this.sendMsg(c, f) 516 | } 517 | } 518 | }); 519 | e.a = new a 520 | }, 521 | "Qfv+": function(b, e, a) { 522 | Object.defineProperty(e, "__esModule", { 523 | value: !0 524 | }); 525 | var g, l, f = null, c, n, d, h, k, m, B, r, t, p, u, v, y, F, Z, O = [0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535], A = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0], K = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99], q = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577], S = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13], N = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], C = function() { 526 | this.list = this.next = null 527 | }, da = function() { 528 | this.n = this.b = this.e = 0; 529 | this.t = null 530 | }, W = function(c, d, f, a, h, n) { 531 | this.BMAX = 16; 532 | this.N_MAX = 288; 533 | this.status = 0; 534 | this.root = null; 535 | this.m = 0; 536 | var g = Array(this.BMAX + 1), m, l, e, p, k, b, r, t = Array(this.BMAX + 1), u, B, v, q = new da, w = Array(this.BMAX); 537 | p = Array(this.N_MAX); 538 | var y, z = Array(this.BMAX + 1), A, F, G; 539 | G = this.root = null; 540 | for (k = 0; k < g.length; k++) 541 | g[k] = 0; 542 | for (k = 0; k < t.length; k++) 543 | t[k] = 0; 544 | for (k = 0; k < w.length; k++) 545 | w[k] = null; 546 | for (k = 0; k < p.length; k++) 547 | p[k] = 0; 548 | for (k = 0; k < z.length; k++) 549 | z[k] = 0; 550 | m = 256 < d ? c[256] : this.BMAX; 551 | u = c; 552 | B = 0; 553 | k = d; 554 | do 555 | g[u[B]]++, 556 | B++; 557 | while (0 < --k);if (g[0] == d) 558 | this.root = null, 559 | this.status = this.m = 0; 560 | else { 561 | for (b = 1; b <= this.BMAX && 0 == g[b]; b++) 562 | ; 563 | r = b; 564 | n < b && (n = b); 565 | for (k = this.BMAX; 0 != k && 0 == g[k]; k--) 566 | ; 567 | e = k; 568 | n > k && (n = k); 569 | for (A = 1 << b; b < k; b++, 570 | A <<= 1) 571 | if (0 > (A -= g[b])) { 572 | this.status = 2; 573 | this.m = n; 574 | return 575 | } 576 | if (0 > (A -= g[k])) 577 | this.status = 2, 578 | this.m = n; 579 | else { 580 | g[k] += A; 581 | z[1] = b = 0; 582 | u = g; 583 | B = 1; 584 | for (v = 2; 0 < --k; ) 585 | z[v++] = b += u[B++]; 586 | u = c; 587 | k = B = 0; 588 | do 589 | 0 != (b = u[B++]) && (p[z[b]++] = k); 590 | while (++k < d);d = z[e]; 591 | z[0] = k = 0; 592 | u = p; 593 | B = 0; 594 | p = -1; 595 | y = t[0] = 0; 596 | v = null; 597 | for (F = 0; r <= e; r++) 598 | for (c = g[r]; 0 < c--; ) { 599 | for (; r > y + t[1 + p]; ) { 600 | y += t[1 + p]; 601 | p++; 602 | F = (F = e - y) > n ? n : F; 603 | if ((l = 1 << (b = r - y)) > c + 1) 604 | for (l -= c + 1, 605 | v = r; ++b < F && !((l <<= 1) <= g[++v]); ) 606 | l -= g[v]; 607 | y + b > m && y < m && (b = m - y); 608 | F = 1 << b; 609 | t[1 + p] = b; 610 | v = Array(F); 611 | for (l = 0; l < F; l++) 612 | v[l] = new da; 613 | G = null == G ? this.root = new C : G.next = new C; 614 | G.next = null; 615 | G.list = v; 616 | w[p] = v; 617 | 0 < p && (z[p] = k, 618 | q.b = t[p], 619 | q.e = 16 + b, 620 | q.t = v, 621 | b = (k & (1 << y) - 1) >> y - t[p], 622 | w[p - 1][b].e = q.e, 623 | w[p - 1][b].b = q.b, 624 | w[p - 1][b].n = q.n, 625 | w[p - 1][b].t = q.t) 626 | } 627 | q.b = r - y; 628 | B >= d ? q.e = 99 : u[B] < f ? (q.e = 256 > u[B] ? 16 : 15, 629 | q.n = u[B++]) : (q.e = h[u[B] - f], 630 | q.n = a[u[B++] - f]); 631 | l = 1 << r - y; 632 | for (b = k >> y; b < F; b += l) 633 | v[b].e = q.e, 634 | v[b].b = q.b, 635 | v[b].n = q.n, 636 | v[b].t = q.t; 637 | for (b = 1 << r - 1; 0 != (k & b); b >>= 1) 638 | k ^= b; 639 | for (k ^= b; (k & (1 << y) - 1) != z[p]; ) 640 | y -= t[p], 641 | p-- 642 | } 643 | this.m = t[1]; 644 | this.status = 0 != A && 1 != e ? 1 : 0 645 | } 646 | } 647 | }, z = function(c) { 648 | for (; k < c; ) { 649 | var d = h, f; 650 | f = F.length == Z ? -1 : F.charCodeAt(Z++) & 255; 651 | h = d | f << k; 652 | k += 8 653 | } 654 | }, G = function(c) { 655 | return h & O[c] 656 | }, w = function(c) { 657 | h >>= c; 658 | k -= c 659 | }, L = function(c, d, f) { 660 | var a, h, k; 661 | if (0 == f) 662 | return 0; 663 | for (k = 0; ; ) { 664 | z(v); 665 | h = p.list[G(v)]; 666 | for (a = h.e; 16 < a; ) { 667 | if (99 == a) 668 | return -1; 669 | w(h.b); 670 | a -= 16; 671 | z(a); 672 | h = h.t[G(a)]; 673 | a = h.e 674 | } 675 | w(h.b); 676 | if (16 == a) 677 | l &= 32767, 678 | c[d + k++] = g[l++] = h.n; 679 | else { 680 | if (15 == a) 681 | break; 682 | z(a); 683 | r = h.n + G(a); 684 | w(a); 685 | z(y); 686 | h = u.list[G(y)]; 687 | for (a = h.e; 16 < a; ) { 688 | if (99 == a) 689 | return -1; 690 | w(h.b); 691 | a -= 16; 692 | z(a); 693 | h = h.t[G(a)]; 694 | a = h.e 695 | } 696 | w(h.b); 697 | z(a); 698 | t = l - h.n - G(a); 699 | for (w(a); 0 < r && k < f; ) 700 | r--, 701 | t &= 32767, 702 | l &= 32767, 703 | c[d + k++] = g[l++] = g[t++] 704 | } 705 | if (k == f) 706 | return f 707 | } 708 | m = -1; 709 | return k 710 | }, R = function(c, d, f) { 711 | var a, h, k, n, b, g, l, m = Array(316); 712 | for (a = 0; a < m.length; a++) 713 | m[a] = 0; 714 | z(5); 715 | g = 257 + G(5); 716 | w(5); 717 | z(5); 718 | l = 1 + G(5); 719 | w(5); 720 | z(4); 721 | a = 4 + G(4); 722 | w(4); 723 | if (286 < g || 30 < l) 724 | return -1; 725 | for (h = 0; h < a; h++) 726 | z(3), 727 | m[N[h]] = G(3), 728 | w(3); 729 | for (; 19 > h; h++) 730 | m[N[h]] = 0; 731 | v = 7; 732 | h = new W(m,19,19,null,null,v); 733 | if (0 != h.status) 734 | return -1; 735 | p = h.root; 736 | v = h.m; 737 | n = g + l; 738 | for (a = k = 0; a < n; ) 739 | if (z(v), 740 | b = p.list[G(v)], 741 | h = b.b, 742 | w(h), 743 | h = b.n, 744 | 16 > h) 745 | m[a++] = k = h; 746 | else if (16 == h) { 747 | z(2); 748 | h = 3 + G(2); 749 | w(2); 750 | if (a + h > n) 751 | return -1; 752 | for (; 0 < h--; ) 753 | m[a++] = k 754 | } else { 755 | 17 == h ? (z(3), 756 | h = 3 + G(3), 757 | w(3)) : (z(7), 758 | h = 11 + G(7), 759 | w(7)); 760 | if (a + h > n) 761 | return -1; 762 | for (; 0 < h--; ) 763 | m[a++] = 0; 764 | k = 0 765 | } 766 | v = 9; 767 | h = new W(m,g,257,A,K,v); 768 | 0 == v && (h.status = 1); 769 | if (0 != h.status) 770 | return -1; 771 | p = h.root; 772 | v = h.m; 773 | for (a = 0; a < l; a++) 774 | m[a] = m[a + g]; 775 | y = 6; 776 | h = new W(m,l,0,q,S,y); 777 | u = h.root; 778 | y = h.m; 779 | return 0 == y && 257 < g || 0 != h.status ? -1 : L(c, d, f) 780 | }, X = function(a, b, e) { 781 | var D, E; 782 | for (D = 0; D < e && (!B || -1 != m); ) { 783 | if (0 < r) { 784 | if (0 != m) 785 | for (; 0 < r && D < e; ) 786 | r--, 787 | t &= 32767, 788 | l &= 32767, 789 | a[b + D++] = g[l++] = g[t++]; 790 | else { 791 | for (; 0 < r && D < e; ) 792 | r--, 793 | l &= 32767, 794 | z(8), 795 | a[b + D++] = g[l++] = G(8), 796 | w(8); 797 | 0 == r && (m = -1) 798 | } 799 | if (D == e) 800 | break 801 | } 802 | if (-1 == m) { 803 | if (B) 804 | break; 805 | z(1); 806 | 0 != G(1) && (B = !0); 807 | w(1); 808 | z(2); 809 | m = G(2); 810 | w(2); 811 | p = null; 812 | r = 0 813 | } 814 | switch (m) { 815 | case 0: 816 | E = a; 817 | var F = b + D 818 | , C = e - D 819 | , H = void 0 820 | , H = k & 7; 821 | w(H); 822 | z(16); 823 | H = G(16); 824 | w(16); 825 | z(16); 826 | if (H != (~h & 65535)) 827 | E = -1; 828 | else { 829 | w(16); 830 | r = H; 831 | for (H = 0; 0 < r && H < C; ) 832 | r--, 833 | l &= 32767, 834 | z(8), 835 | E[F + H++] = g[l++] = G(8), 836 | w(8); 837 | 0 == r && (m = -1); 838 | E = H 839 | } 840 | break; 841 | case 1: 842 | if (null != p) 843 | E = L(a, b + D, e - D); 844 | else 845 | a: { 846 | E = a; 847 | F = b + D; 848 | C = e - D; 849 | if (null == f) { 850 | for (var x = void 0, H = Array(288), x = void 0, x = 0; 144 > x; x++) 851 | H[x] = 8; 852 | for (; 256 > x; x++) 853 | H[x] = 9; 854 | for (; 280 > x; x++) 855 | H[x] = 7; 856 | for (; 288 > x; x++) 857 | H[x] = 8; 858 | n = 7; 859 | x = new W(H,288,257,A,K,n); 860 | if (0 != x.status) { 861 | alert("HufBuild error: " + x.status); 862 | E = -1; 863 | break a 864 | } 865 | f = x.root; 866 | n = x.m; 867 | for (x = 0; 30 > x; x++) 868 | H[x] = 5; 869 | d = 5; 870 | x = new W(H,30,0,q,S,d); 871 | if (1 < x.status) { 872 | f = null; 873 | alert("HufBuild error: " + x.status); 874 | E = -1; 875 | break a 876 | } 877 | c = x.root; 878 | d = x.m 879 | } 880 | p = f; 881 | u = c; 882 | v = n; 883 | y = d; 884 | E = L(E, F, C) 885 | } 886 | break; 887 | case 2: 888 | E = null != p ? L(a, b + D, e - D) : R(a, b + D, e - D); 889 | break; 890 | default: 891 | E = -1 892 | } 893 | if (-1 == E) 894 | return B ? 0 : -1; 895 | D += E 896 | } 897 | return D 898 | }; 899 | e.default = function(c) { 900 | var d; 901 | null == g && (g = Array(65536)); 902 | k = h = l = 0; 903 | m = -1; 904 | B = !1; 905 | r = t = 0; 906 | p = null; 907 | F = c; 908 | Z = 0; 909 | for (var a = Array(1024), f = []; 0 < (c = X(a, 0, a.length)); ) { 910 | var b = Array(c); 911 | for (d = 0; d < c; d++) 912 | b[d] = String.fromCharCode(a[d]); 913 | f[f.length] = b.join("") 914 | } 915 | F = null; 916 | return f.join("") 917 | } 918 | }, 919 | RfPX: function(b, e, a) { 920 | Object.defineProperty(e, "__esModule", { 921 | value: !0 922 | }); 923 | e.default = function(a) { 924 | var c = 1 < arguments.length && void 0 !== arguments[1] ? arguments[1] : {}, b = c.method, b = void 0 === b ? "GET" : b, d = c.data, d = void 0 === d ? null : d, h = c.dataType, k = void 0 === h ? "json" : h, h = c.timeout, h = void 0 === h ? 0 : h, m = c.withCredentials, m = void 0 === m ? !1 : m, c = c.context, e = void 0 === c ? null : c, r = new XMLHttpRequest, t, p, c = new Promise(function(c, a) { 925 | t = c; 926 | p = a 927 | } 928 | ), u = function(c) { 929 | var d = this.status 930 | , h = this.statusText 931 | , b = this.responseText; 932 | if ("timeout" == c.type) 933 | p(l("Timeout error", a, e)); 934 | else if (200 <= d && 300 > d || 304 == d) 935 | try { 936 | b = "xml" == k ? this.responseXML : "json" == k ? JSON.parse(b) : b, 937 | t(e ? { 938 | context: e, 939 | response: b 940 | } : b) 941 | } catch (g) { 942 | p(l("Parse JSON error", a, e)) 943 | } 944 | else 945 | p(l("Network error " + h, a, e)); 946 | r = u = null 947 | }; 948 | "POST" != b && d && (a = g.default.make(a, d)); 949 | r.open(b, a, !0); 950 | r.timeout = h; 951 | r.withCredentials = m; 952 | r.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); 953 | r.onload = r.onerror = r.ontimeout = u; 954 | r.send("POST" == b && d ? g.default.param(d, !0) : null); 955 | c.abort = function() { 956 | r && (r.abort(), 957 | p(l("XMLHttpRequest aborted manually", a, e))) 958 | } 959 | ; 960 | return c 961 | } 962 | ; 963 | var g = (b = a("TkXE")) && b.__esModule ? b : { 964 | default: b 965 | } 966 | , l = function(a) { 967 | var c = 1 < arguments.length && void 0 !== arguments[1] ? arguments[1] : "" 968 | , b = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null 969 | , d = Error(a + (c ? " - " + c : c)); 970 | d.url = c; 971 | d.context = b; 972 | return d 973 | } 974 | }, 975 | TkXE: function(b, e, a) { 976 | Object.defineProperty(e, "__esModule", { 977 | value: !0 978 | }); 979 | var g = "function" === typeof Symbol && "symbol" === typeof Symbol.iterator ? function(c) { 980 | return typeof c 981 | } 982 | : function(c) { 983 | return c && "function" === typeof Symbol && c.constructor === Symbol && c !== Symbol.prototype ? "symbol" : typeof c 984 | } 985 | , l = a("agns") 986 | , f = /^(https?:)?(?:\/\/)?((?:[\d\w.-]+\.[a-z]{2,6}|[\d.]+))?(:\d+)?([^?#]+)?(\?[^#]*)?(#.*)?$/; 987 | e.default = { 988 | rurl: f, 989 | parse: function(c) { 990 | var a; 991 | if ("string" == typeof c) { 992 | var d = a = f.exec(c); 993 | if (d) { 994 | a = d[1] || ""; 995 | var h = d[2] || "" 996 | , b = d[3] ? d[3].slice(1) : ""; 997 | a = { 998 | protocol: a, 999 | hostname: h, 1000 | port: b, 1001 | host: h + (b ? ":" + b : ""), 1002 | pathname: d[4] || "", 1003 | search: d[5] && "?" != d[5] ? d[5] : "", 1004 | hash: d[6] && "#" != d[6] ? d[6] : "", 1005 | href: c 1006 | } 1007 | } 1008 | } else 1009 | "object" == ("undefined" === typeof c ? "undefined" : g(c)) && (a = (c.protocol ? c.protocol + "//" : c.hostname ? "//" : "") + (c.hostname || "") + (c.port ? ":" + c.port : "") + (c.pathname || "") + (c.search || "") + (c.hash || "")); 1010 | return a 1011 | }, 1012 | make: function(c, a) { 1013 | var d = this.parse(c) 1014 | , h = this.param(d.search) 1015 | , h = this.param((0, 1016 | l.merge)(h, a)); 1017 | d.search = h ? "?" + h : ""; 1018 | return this.parse(d) 1019 | }, 1020 | param: function(c) { 1021 | var a; 1022 | if ("object" == ("undefined" === typeof c ? "undefined" : g(c))) { 1023 | var d = []; 1024 | Object.keys(c).forEach(function(a) { 1025 | var f = c[a]; 1026 | "undefined" !== typeof f && d.push(a + "=" + encodeURIComponent(f)) 1027 | }); 1028 | a = d.join("&") 1029 | } else 1030 | "string" == typeof c && (c = 0 == c.indexOf("?") || 0 == c.indexOf("#") ? c.slice(1) : c, 1031 | a = {}, 1032 | c && c.split("&").forEach(function(c, d) { 1033 | var f = c.split("="); 1034 | a[f[0]] = decodeURIComponent(f[1]) 1035 | })); 1036 | return a 1037 | } 1038 | } 1039 | }, 1040 | V6zD: function(b, e, a) { 1041 | Object.defineProperty(e, "__esModule", { 1042 | value: !0 1043 | }); 1044 | b = (b = a("vzCy")) && b.__esModule ? b : { 1045 | default: b 1046 | }; 1047 | a = a("agns"); 1048 | var g = function f() { 1049 | f.superclass.constructor.call(this) 1050 | }; 1051 | (0, 1052 | a.extend)(g, b.default, { 1053 | subscribe: function(a, c) { 1054 | this.on(a, c) 1055 | }, 1056 | unsubscribe: function(a, c) { 1057 | c ? this.removeListener(a, c) : this.removeAllListeners(a) 1058 | }, 1059 | publish: function(a) { 1060 | this.emit.apply(this, [].slice.call(arguments, 0)) 1061 | } 1062 | }); 1063 | e.default = new g 1064 | }, 1065 | agns: function(b, e, a) { 1066 | Object.defineProperty(e, "__esModule", { 1067 | value: !0 1068 | }); 1069 | var g = "function" === typeof Symbol && "symbol" === typeof Symbol.iterator ? function(a) { 1070 | return typeof a 1071 | } 1072 | : function(a) { 1073 | return a && "function" === typeof Symbol && a.constructor === Symbol && a !== Symbol.prototype ? "symbol" : typeof a 1074 | } 1075 | , l = e.isObject = function(a) { 1076 | return "object" == ("undefined" === typeof a ? "undefined" : g(a)) && !!a 1077 | } 1078 | , f = e.mix = function d(a, c, f, b, g) { 1079 | var e, p, u; 1080 | if (b && b.length) 1081 | for (e = 0, 1082 | p = b.length; e < p; ++e) 1083 | u = b[e], 1084 | c.hasOwnProperty(u) && (g && l(a[u]) ? d(a[u], c[u]) : !f && u in a || (a[u] = c[u])); 1085 | else 1086 | for (e in c) 1087 | c.hasOwnProperty(e) && (g && l(a[e]) ? d(a[e], c[e], f, b, !0) : !f && e in a || (a[e] = c[e])); 1088 | return a 1089 | } 1090 | , c = e.merge = function() { 1091 | for (var a = arguments, c = {}, b = 0, g = a.length; b < g; b++) 1092 | f(c, a[b], !0); 1093 | return c 1094 | } 1095 | ; 1096 | e.extend = function(a, c, b, g) { 1097 | if (!c || !a) 1098 | return a; 1099 | var e = Object.prototype 1100 | , l = c.prototype 1101 | , t = Object.create(l); 1102 | a.prototype = t; 1103 | t.constructor = a; 1104 | a.superclass = l; 1105 | c !== Object && l.constructor === e.constructor && (l.constructor = c); 1106 | b && f(t, b, !0); 1107 | g && f(a, g, !0); 1108 | return a 1109 | } 1110 | ; 1111 | e.omit = function() { 1112 | var a = arguments[0] 1113 | , f = [].slice.call(arguments, 1) 1114 | , b = "function" == typeof f[0] ? f[0] : null 1115 | , g = c({}, a); 1116 | b ? Object.keys(g).forEach(function(a) { 1117 | b(a) && delete g[a] 1118 | }) : f.forEach(function(a) { 1119 | delete g[a] 1120 | }); 1121 | return g 1122 | } 1123 | }, 1124 | fiJj: function(b, e, a) { 1125 | e.a = function() { 1126 | Object.keys(h).forEach(function(a) { 1127 | return h[a].abort() 1128 | }); 1129 | h = {} 1130 | } 1131 | ; 1132 | e.b = function(a) { 1133 | var d = 1 < arguments.length && void 0 !== arguments[1] ? arguments[1] : {} 1134 | , f = arguments[2] 1135 | , b = arguments[3] 1136 | , e = "object" == ("undefined" === typeof __pda ? "undefined" : n(__pda)) ? __pda : {} 1137 | , B = l()() 1138 | , O = e.apidomain 1139 | , d = Object(g.mix)(d, { 1140 | padapi: a, 1141 | av: e.av, 1142 | encpass: m, 1143 | logiuid: k 1144 | }) 1145 | , e = 0 == a.indexOf("yl-") ? "/yl/index.php" : "/coop/mobile/index.php" 1146 | , f = c()("//" + O + e, { 1147 | data: d, 1148 | method: f ? "POST" : "GET", 1149 | dataType: b, 1150 | context: { 1151 | id: B, 1152 | ticket: d.encpass 1153 | } 1154 | }) 1155 | , b = f.then(r, t); 1156 | h[B] = f; 1157 | b.abort = f.abort; 1158 | return b 1159 | } 1160 | ; 1161 | var g = a("agns"); 1162 | a.n(g); 1163 | b = a("jFXU"); 1164 | var l = a.n(b); 1165 | b = a("V6zD"); 1166 | var f = a.n(b); 1167 | b = a("RfPX"); 1168 | var c = a.n(b) 1169 | , n = "function" === typeof Symbol && "symbol" === typeof Symbol.iterator ? function(a) { 1170 | return typeof a 1171 | } 1172 | : function(a) { 1173 | return a && "function" === typeof Symbol && a.constructor === Symbol && a !== Symbol.prototype ? "symbol" : typeof a 1174 | } 1175 | , d = "undefined" != typeof WorkerGlobalScope 1176 | , h = {} 1177 | , k = "" 1178 | , m = ""; 1179 | d ? self.addEventListener("message", function(a) { 1180 | a = JSON.parse(a.data); 1181 | "user-state-update" == a.cmd && (a = a.content || {}, 1182 | k = a.uid, 1183 | m = a.ticket) 1184 | }, !1) : f.a.subscribe("user-state-update", function(a, c, d) { 1185 | k = a; 1186 | m = c 1187 | }); 1188 | var B = function(a, c) { 1189 | d ? self.postMessge(JSON.stringify({ 1190 | cmd: a, 1191 | content: c 1192 | })) : f.a.publish(a, c) 1193 | } 1194 | , r = function(a) { 1195 | var c = a.context; 1196 | a = a.response; 1197 | var d = c.ticket; 1198 | delete h[c.id]; 1199 | a.key && d && a.key != d && B("user-state-receive-new-ticket", a.key); 1200 | a.flag && "203" == a.flag && B("user-state-broken", d); 1201 | return a 1202 | } 1203 | , t = function(a) { 1204 | delete h[a.context.id]; 1205 | throw a; 1206 | } 1207 | }, 1208 | jFXU: function(b, e, a) { 1209 | Object.defineProperty(e, "__esModule", { 1210 | value: !0 1211 | }); 1212 | e.default = function() { 1213 | return (++g).toString(36) 1214 | } 1215 | ; 1216 | var g = Date.now() 1217 | }, 1218 | kFTT: function(b, e, a) { 1219 | var g = a("CPiQ") 1220 | , l = a("+itZ"); 1221 | b = function() { 1222 | this._record = { 1223 | time: 0, 1224 | count: 0 1225 | } 1226 | } 1227 | ; 1228 | b.prototype = { 1229 | filter: function(a) { 1230 | var c = !0; 1231 | switch (a.typeID) { 1232 | case 123: 1233 | c = this.filterWelcome(a); 1234 | break; 1235 | case 201: 1236 | c = this.filterGift(a) 1237 | } 1238 | return c 1239 | }, 1240 | filterWelcome: function(a) { 1241 | return this.throttle(a.tm, g.a.info && g.a.info.rid == a.content.rid) 1242 | }, 1243 | filterGift: function(a) { 1244 | var c = g.a.info && g.a.info.uid == a.fid 1245 | , b = -1 < l.n.indexOf(a.content.item); 1246 | return this.throttle(a.tm, c || !b) 1247 | }, 1248 | throttle: function(a, c) { 1249 | var b = this._record 1250 | , d = !0; 1251 | b.time != a ? (b.time = a, 1252 | b.count = 1) : (b.count++, 1253 | !c && 3 < b.count && (d = !1)); 1254 | return d 1255 | } 1256 | }; 1257 | e.a = new b 1258 | }, 1259 | "sZR/": function(b, e, a) { 1260 | Object.defineProperty(e, "__esModule", { 1261 | value: !0 1262 | }); 1263 | var g = a("Jvqk") 1264 | , l = {} 1265 | , f = function(a) { 1266 | return function(f) { 1267 | self.postMessage(JSON.stringify({ 1268 | cmd: "eventEmit", 1269 | content: { 1270 | event: a, 1271 | data: f 1272 | } 1273 | })) 1274 | } 1275 | }; 1276 | self.addEventListener("message", function(a) { 1277 | a = JSON.parse(a.data); 1278 | switch (a.cmd) { 1279 | case "init": 1280 | self.__pda = a.content; 1281 | self.postMessage(JSON.stringify({ 1282 | cmd: "ready", 1283 | content: void 0 1284 | })); 1285 | break; 1286 | case "login": 1287 | (g.a.isOpen() || g.a.isConnecting()) && g.a.close(); 1288 | g.a.login(a.content); 1289 | break; 1290 | case "sendMsg": 1291 | g.a.sendMsg(a.content[0], a.content[1]); 1292 | break; 1293 | case "newListener": 1294 | a = a.content; 1295 | l[a] || (g.a.on(a, f(a)), 1296 | l[a] = 1); 1297 | break; 1298 | case "closeSocket": 1299 | g.a.close(); 1300 | break; 1301 | case "close": 1302 | g.a.close(), 1303 | self.close() 1304 | } 1305 | }) 1306 | }, 1307 | twhX: function(b, e, a) { 1308 | Object.defineProperty(e, "__esModule", { 1309 | value: !0 1310 | }); 1311 | var g, l, f, c, n = null, d, h, k, m, B, r, t, p, u, v, y, F, Z, O, A, K, q, S, N, C, da, W, z, G, w, L, R, X, J, P, Q, D, E, I, T, H, x, ea, aa, ra, ja, ka, U, la, sa, ba, fa, V, ca, ma, ta, ga = function() { 1312 | this.dl = this.fc = 0 1313 | }, ua = function() { 1314 | this.extra_bits = this.static_tree = this.dyn_tree = null; 1315 | this.max_code = this.max_length = this.elems = this.extra_base = 0 1316 | }; 1317 | b = function(a, c, d, f) { 1318 | this.good_length = a; 1319 | this.max_lazy = c; 1320 | this.nice_length = d; 1321 | this.max_chain = f 1322 | } 1323 | ; 1324 | var Ma = function() { 1325 | this.next = null; 1326 | this.len = 0; 1327 | this.ptr = Array(8192); 1328 | this.off = 0 1329 | } 1330 | , va = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0] 1331 | , ha = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13] 1332 | , Na = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7] 1333 | , Aa = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15] 1334 | , wa = [new b(0,0,0,0), new b(4,4,8,4), new b(4,5,16,8), new b(4,6,32,32), new b(4,4,16,16), new b(8,16,32,32), new b(8,16,128,128), new b(8,32,128,256), new b(32,128,258,1024), new b(32,258,258,4096)] 1335 | , na = function(a) { 1336 | n[h + d++] = a; 1337 | if (8192 == h + d && 0 != d) { 1338 | var c; 1339 | null != g ? (a = g, 1340 | g = g.next) : a = new Ma; 1341 | a.next = null; 1342 | a.len = a.off = 0; 1343 | null == l ? l = f = a : f = f.next = a; 1344 | a.len = d - h; 1345 | for (c = 0; c < a.len; c++) 1346 | a.ptr[c] = n[h + c]; 1347 | d = h = 0 1348 | } 1349 | } 1350 | , oa = function(a) { 1351 | a &= 65535; 1352 | 8190 > h + d ? (n[h + d++] = a & 255, 1353 | n[h + d++] = a >>> 8) : (na(a & 255), 1354 | na(a >>> 8)) 1355 | } 1356 | , pa = function() { 1357 | y = (y << 5 ^ m[q + 3 - 1] & 255) & 8191; 1358 | F = t[32768 + y]; 1359 | t[q & 32767] = F; 1360 | t[32768 + y] = q 1361 | } 1362 | , Y = function(a, c) { 1363 | M(c[a].fc, c[a].dl) 1364 | } 1365 | , Ba = function(a, c, d) { 1366 | return a[c].fc < a[d].fc || a[c].fc == a[d].fc && x[c] <= x[d] 1367 | } 1368 | , Ca = function(a, c, d) { 1369 | var f; 1370 | for (f = 0; f < d && ta < ma.length; f++) 1371 | a[c + f] = ma.charCodeAt(ta++) & 255; 1372 | return f 1373 | } 1374 | , Da = function(a) { 1375 | var c = da, d = q, f, b = K, h = 32506 < q ? q - 32506 : 0, g = q + 258, e = m[d + b - 1], l = m[d + b]; 1376 | K >= G && (c >>= 2); 1377 | do 1378 | if (f = a, 1379 | m[f + b] == l && m[f + b - 1] == e && m[f] == m[d] && m[++f] == m[d + 1]) { 1380 | d += 2; 1381 | for (f++; m[++d] == m[++f] && m[++d] == m[++f] && m[++d] == m[++f] && m[++d] == m[++f] && m[++d] == m[++f] && m[++d] == m[++f] && m[++d] == m[++f] && m[++d] == m[++f] && d < g; ) 1382 | ; 1383 | f = 258 - (g - d); 1384 | d = g - 258; 1385 | if (f > b) { 1386 | S = a; 1387 | b = f; 1388 | if (258 <= f) 1389 | break; 1390 | e = m[d + b - 1]; 1391 | l = m[d + b] 1392 | } 1393 | } 1394 | while ((a = t[a & 32767]) > h && 0 != --c);return b 1395 | } 1396 | , xa = function() { 1397 | var a, c, d = 65536 - C - q; 1398 | if (-1 == d) 1399 | d--; 1400 | else if (65274 <= q) { 1401 | for (a = 0; 32768 > a; a++) 1402 | m[a] = m[a + 32768]; 1403 | S -= 32768; 1404 | q -= 32768; 1405 | v -= 32768; 1406 | for (a = 0; 8192 > a; a++) 1407 | c = t[32768 + a], 1408 | t[32768 + a] = 32768 <= c ? c - 32768 : 0; 1409 | for (a = 0; 32768 > a; a++) 1410 | c = t[a], 1411 | t[a] = 32768 <= c ? c - 32768 : 0; 1412 | d += 32768 1413 | } 1414 | N || (a = Ca(m, q + C, d), 1415 | 0 >= a ? N = !0 : C += a) 1416 | } 1417 | , Oa = function(a, f, b) { 1418 | var g; 1419 | if (!c) { 1420 | if (!N) { 1421 | u = p = 0; 1422 | var e, n; 1423 | if (0 == X[0].dl) { 1424 | P.dyn_tree = w; 1425 | P.static_tree = R; 1426 | P.extra_bits = va; 1427 | P.extra_base = 257; 1428 | P.elems = 286; 1429 | P.max_length = 15; 1430 | P.max_code = 0; 1431 | Q.dyn_tree = L; 1432 | Q.static_tree = X; 1433 | Q.extra_bits = ha; 1434 | Q.extra_base = 0; 1435 | Q.elems = 30; 1436 | Q.max_length = 15; 1437 | Q.max_code = 0; 1438 | D.dyn_tree = J; 1439 | D.static_tree = null; 1440 | D.extra_bits = Na; 1441 | D.extra_base = 0; 1442 | D.elems = 19; 1443 | D.max_length = 7; 1444 | for (n = e = D.max_code = 0; 28 > n; n++) 1445 | for (ra[n] = e, 1446 | g = 0; g < 1 << va[n]; g++) 1447 | ea[e++] = n; 1448 | ea[e - 1] = n; 1449 | for (n = e = 0; 16 > n; n++) 1450 | for (ja[n] = e, 1451 | g = 0; g < 1 << ha[n]; g++) 1452 | aa[e++] = n; 1453 | for (e >>= 7; 30 > n; n++) 1454 | for (ja[n] = e << 7, 1455 | g = 0; g < 1 << ha[n] - 7; g++) 1456 | aa[256 + e++] = n; 1457 | for (g = 0; 15 >= g; g++) 1458 | E[g] = 0; 1459 | for (g = 0; 143 >= g; ) 1460 | R[g++].dl = 8, 1461 | E[8]++; 1462 | for (; 255 >= g; ) 1463 | R[g++].dl = 9, 1464 | E[9]++; 1465 | for (; 279 >= g; ) 1466 | R[g++].dl = 7, 1467 | E[7]++; 1468 | for (; 287 >= g; ) 1469 | R[g++].dl = 8, 1470 | E[8]++; 1471 | Ea(R, 287); 1472 | for (g = 0; 30 > g; g++) 1473 | X[g].dl = 5, 1474 | X[g].fc = Fa(g, 5); 1475 | Ga() 1476 | } 1477 | for (g = 0; 8192 > g; g++) 1478 | t[32768 + g] = 0; 1479 | W = wa[z].max_lazy; 1480 | G = wa[z].good_length; 1481 | da = wa[z].max_chain; 1482 | v = q = 0; 1483 | C = Ca(m, 0, 65536); 1484 | if (0 >= C) 1485 | N = !0, 1486 | C = 0; 1487 | else { 1488 | for (N = !1; 262 > C && !N; ) 1489 | xa(); 1490 | for (g = y = 0; 2 > g; g++) 1491 | y = (y << 5 ^ m[g] & 255) & 8191 1492 | } 1493 | l = null; 1494 | h = d = 0; 1495 | 3 >= z ? (K = 2, 1496 | A = 0) : (A = 2, 1497 | O = 0); 1498 | k = !1 1499 | } 1500 | c = !0; 1501 | if (0 == C) 1502 | return k = !0, 1503 | 0 1504 | } 1505 | if ((g = Ha(a, f, b)) == b) 1506 | return b; 1507 | if (k) 1508 | return g; 1509 | if (3 >= z) 1510 | for (; 0 != C && null == l; ) { 1511 | pa(); 1512 | 0 != F && 32506 >= q - F && (A = Da(F), 1513 | A > C && (A = C)); 1514 | if (3 <= A) 1515 | if (n = ia(q - S, A - 3), 1516 | C -= A, 1517 | A <= W) { 1518 | A--; 1519 | do 1520 | q++, 1521 | pa(); 1522 | while (0 != --A);q++ 1523 | } else 1524 | q += A, 1525 | A = 0, 1526 | y = m[q] & 255, 1527 | y = (y << 5 ^ m[q + 1] & 255) & 8191; 1528 | else 1529 | n = ia(0, m[q] & 255), 1530 | C--, 1531 | q++; 1532 | n && (qa(0), 1533 | v = q); 1534 | for (; 262 > C && !N; ) 1535 | xa() 1536 | } 1537 | else 1538 | for (; 0 != C && null == l; ) { 1539 | pa(); 1540 | K = A; 1541 | Z = S; 1542 | A = 2; 1543 | 0 != F && K < W && 32506 >= q - F && (A = Da(F), 1544 | A > C && (A = C), 1545 | 3 == A && 4096 < q - S && A--); 1546 | if (3 <= K && A <= K) { 1547 | n = ia(q - 1 - Z, K - 3); 1548 | C -= K - 1; 1549 | K -= 2; 1550 | do 1551 | q++, 1552 | pa(); 1553 | while (0 != --K);O = 0; 1554 | A = 2; 1555 | q++; 1556 | n && (qa(0), 1557 | v = q) 1558 | } else 1559 | 0 != O ? ia(0, m[q - 1] & 255) && (qa(0), 1560 | v = q) : O = 1, 1561 | q++, 1562 | C--; 1563 | for (; 262 > C && !N; ) 1564 | xa() 1565 | } 1566 | 0 == C && (0 != O && ia(0, m[q - 1] & 255), 1567 | qa(1), 1568 | k = !0); 1569 | return g + Ha(a, g + f, b - g) 1570 | } 1571 | , Ha = function(a, c, f) { 1572 | var b, e, k; 1573 | for (b = 0; null != l && b < f; ) { 1574 | e = f - b; 1575 | e > l.len && (e = l.len); 1576 | for (k = 0; k < e; k++) 1577 | a[c + b + k] = l.ptr[l.off + k]; 1578 | l.off += e; 1579 | l.len -= e; 1580 | b += e; 1581 | 0 == l.len && (e = l, 1582 | l = l.next, 1583 | e.next = g, 1584 | g = e) 1585 | } 1586 | if (b == f) 1587 | return b; 1588 | if (h < d) { 1589 | e = f - b; 1590 | e > d - h && (e = d - h); 1591 | for (k = 0; k < e; k++) 1592 | a[c + b + k] = n[h + k]; 1593 | h += e; 1594 | b += e; 1595 | d == h && (d = h = 0) 1596 | } 1597 | return b 1598 | } 1599 | , Ga = function() { 1600 | var a; 1601 | for (a = 0; 286 > a; a++) 1602 | w[a].fc = 0; 1603 | for (a = 0; 30 > a; a++) 1604 | L[a].fc = 0; 1605 | for (a = 0; 19 > a; a++) 1606 | J[a].fc = 0; 1607 | w[256].fc = 1; 1608 | ba = U = la = sa = V = ca = 0; 1609 | fa = 1 1610 | } 1611 | , ya = function(a, c) { 1612 | for (var d = I[c], f = c << 1; f <= T; ) { 1613 | f < T && Ba(a, I[f + 1], I[f]) && f++; 1614 | if (Ba(a, d, I[f])) 1615 | break; 1616 | I[c] = I[f]; 1617 | c = f; 1618 | f <<= 1 1619 | } 1620 | I[c] = d 1621 | } 1622 | , Ea = function(a, c) { 1623 | var d = Array(16), f = 0, b; 1624 | for (b = 1; 15 >= b; b++) 1625 | f = f + E[b - 1] << 1, 1626 | d[b] = f; 1627 | for (f = 0; f <= c; f++) 1628 | b = a[f].dl, 1629 | 0 != b && (a[f].fc = Fa(d[b]++, b)) 1630 | } 1631 | , za = function(a) { 1632 | var c = a.dyn_tree, d = a.static_tree, f = a.elems, b, g = -1, e = f; 1633 | T = 0; 1634 | H = 573; 1635 | for (b = 0; b < f; b++) 1636 | 0 != c[b].fc ? (I[++T] = g = b, 1637 | x[b] = 0) : c[b].dl = 0; 1638 | for (; 2 > T; ) 1639 | b = I[++T] = 2 > g ? ++g : 0, 1640 | c[b].fc = 1, 1641 | x[b] = 0, 1642 | V--, 1643 | null != d && (ca -= d[b].dl); 1644 | a.max_code = g; 1645 | for (b = T >> 1; 1 <= b; b--) 1646 | ya(c, b); 1647 | do 1648 | b = I[1], 1649 | I[1] = I[T--], 1650 | ya(c, 1), 1651 | d = I[1], 1652 | I[--H] = b, 1653 | I[--H] = d, 1654 | c[e].fc = c[b].fc + c[d].fc, 1655 | x[e] = x[b] > x[d] + 1 ? x[b] : x[d] + 1, 1656 | c[b].dl = c[d].dl = e, 1657 | I[1] = e++, 1658 | ya(c, 1); 1659 | while (2 <= T);I[--H] = I[1]; 1660 | e = a.dyn_tree; 1661 | b = a.extra_bits; 1662 | var f = a.extra_base, d = a.max_code, h = a.max_length, l = a.static_tree, k, n, m, q, p = 0; 1663 | for (n = 0; 15 >= n; n++) 1664 | E[n] = 0; 1665 | e[I[H]].dl = 0; 1666 | for (a = H + 1; 573 > a; a++) 1667 | k = I[a], 1668 | n = e[e[k].dl].dl + 1, 1669 | n > h && (n = h, 1670 | p++), 1671 | e[k].dl = n, 1672 | k > d || (E[n]++, 1673 | m = 0, 1674 | k >= f && (m = b[k - f]), 1675 | q = e[k].fc, 1676 | V += q * (n + m), 1677 | null != l && (ca += q * (l[k].dl + m))); 1678 | if (0 != p) { 1679 | do { 1680 | for (n = h - 1; 0 == E[n]; ) 1681 | n--; 1682 | E[n]--; 1683 | E[n + 1] += 2; 1684 | E[h]--; 1685 | p -= 2 1686 | } while (0 < p);for (n = h; 0 != n; n--) 1687 | for (k = E[n]; 0 != k; ) 1688 | b = I[--a], 1689 | b > d || (e[b].dl != n && (V += (n - e[b].dl) * e[b].fc, 1690 | e[b].fc = n), 1691 | k--) 1692 | } 1693 | Ea(c, g) 1694 | } 1695 | , Ia = function(a, c) { 1696 | var d, b = -1, f, g = a[0].dl, e = 0, h = 7, k = 4; 1697 | 0 == g && (h = 138, 1698 | k = 3); 1699 | a[c + 1].dl = 65535; 1700 | for (d = 0; d <= c; d++) 1701 | f = g, 1702 | g = a[d + 1].dl, 1703 | ++e < h && f == g || (e < k ? J[f].fc += e : 0 != f ? (f != b && J[f].fc++, 1704 | J[16].fc++) : 10 >= e ? J[17].fc++ : J[18].fc++, 1705 | e = 0, 1706 | b = f, 1707 | 0 == g ? (h = 138, 1708 | k = 3) : f == g ? (h = 6, 1709 | k = 3) : (h = 7, 1710 | k = 4)) 1711 | } 1712 | , Ja = function(a, c) { 1713 | var d, f = -1, b, g = a[0].dl, e = 0, h = 7, k = 4; 1714 | 0 == g && (h = 138, 1715 | k = 3); 1716 | for (d = 0; d <= c; d++) 1717 | if (b = g, 1718 | g = a[d + 1].dl, 1719 | !(++e < h && b == g)) { 1720 | if (e < k) { 1721 | do 1722 | Y(b, J); 1723 | while (0 != --e) 1724 | } else 1725 | 0 != b ? (b != f && (Y(b, J), 1726 | e--), 1727 | Y(16, J), 1728 | M(e - 3, 2)) : 10 >= e ? (Y(17, J), 1729 | M(e - 3, 3)) : (Y(18, J), 1730 | M(e - 11, 7)); 1731 | e = 0; 1732 | f = b; 1733 | 0 == g ? (h = 138, 1734 | k = 3) : b == g ? (h = 6, 1735 | k = 3) : (h = 7, 1736 | k = 4) 1737 | } 1738 | } 1739 | , qa = function(a) { 1740 | var c, d, b, f; 1741 | f = q - v; 1742 | ka[sa] = ba; 1743 | za(P); 1744 | za(Q); 1745 | Ia(w, P.max_code); 1746 | Ia(L, Q.max_code); 1747 | za(D); 1748 | for (b = 18; 3 <= b && 0 == J[Aa[b]].dl; b--) 1749 | ; 1750 | V += 3 * (b + 1) + 14; 1751 | c = V + 3 + 7 >> 3; 1752 | d = ca + 3 + 7 >> 3; 1753 | d <= c && (c = d); 1754 | if (f + 4 <= c && 0 <= v) 1755 | for (M(0 + a, 3), 1756 | Ka(), 1757 | oa(f), 1758 | oa(~f), 1759 | b = 0; b < f; b++) 1760 | na(m[v + b]); 1761 | else if (d == c) 1762 | M(2 + a, 3), 1763 | La(R, X); 1764 | else { 1765 | M(4 + a, 3); 1766 | f = P.max_code + 1; 1767 | c = Q.max_code + 1; 1768 | b += 1; 1769 | M(f - 257, 5); 1770 | M(c - 1, 5); 1771 | M(b - 4, 4); 1772 | for (d = 0; d < b; d++) 1773 | M(J[Aa[d]].dl, 3); 1774 | Ja(w, f - 1); 1775 | Ja(L, c - 1); 1776 | La(w, L) 1777 | } 1778 | Ga(); 1779 | 0 != a && Ka() 1780 | } 1781 | , ia = function(a, c) { 1782 | r[U++] = c; 1783 | 0 == a ? w[c].fc++ : (a--, 1784 | w[ea[c] + 256 + 1].fc++, 1785 | L[(256 > a ? aa[a] : aa[256 + (a >> 7)]) & 255].fc++, 1786 | B[la++] = a, 1787 | ba |= fa); 1788 | fa <<= 1; 1789 | 0 == (U & 7) && (ka[sa++] = ba, 1790 | ba = 0, 1791 | fa = 1); 1792 | if (2 < z && 0 == (U & 4095)) { 1793 | var d = 8 * U, b = q - v, f; 1794 | for (f = 0; 30 > f; f++) 1795 | d += L[f].fc * (5 + ha[f]); 1796 | d >>= 3; 1797 | if (la < parseInt(U / 2) && d < parseInt(b / 2)) 1798 | return !0 1799 | } 1800 | return 8191 == U || 8192 == la 1801 | } 1802 | , La = function(a, c) { 1803 | var d, b = 0, f = 0, g = 0, e = 0, h, k; 1804 | if (0 != U) { 1805 | do 1806 | 0 == (b & 7) && (e = ka[g++]), 1807 | d = r[b++] & 255, 1808 | 0 == (e & 1) ? Y(d, a) : (h = ea[d], 1809 | Y(h + 256 + 1, a), 1810 | k = va[h], 1811 | 0 != k && (d -= ra[h], 1812 | M(d, k)), 1813 | d = B[f++], 1814 | h = (256 > d ? aa[d] : aa[256 + (d >> 7)]) & 255, 1815 | Y(h, c), 1816 | k = ha[h], 1817 | 0 != k && (d -= ja[h], 1818 | M(d, k))), 1819 | e >>= 1; 1820 | while (b < U) 1821 | } 1822 | Y(256, a) 1823 | } 1824 | , M = function(a, c) { 1825 | u > 16 - c ? (p |= a << u, 1826 | oa(p), 1827 | p = a >> 16 - u, 1828 | u += c - 16) : (p |= a << u, 1829 | u += c) 1830 | } 1831 | , Fa = function(a, c) { 1832 | var d = 0; 1833 | do 1834 | d |= a & 1, 1835 | a >>= 1, 1836 | d <<= 1; 1837 | while (0 < --c);return d >> 1 1838 | } 1839 | , Ka = function() { 1840 | 8 < u ? oa(p) : 0 < u && na(p); 1841 | u = p = 0 1842 | }; 1843 | e.default = function(a, d) { 1844 | var b, e; 1845 | ma = a; 1846 | ta = 0; 1847 | "undefined" == typeof d && (d = 6); 1848 | (b = d) ? 1 > b ? b = 1 : 9 < b && (b = 9) : b = 6; 1849 | z = b; 1850 | N = c = !1; 1851 | if (null == n) { 1852 | g = l = f = null; 1853 | n = Array(8192); 1854 | m = Array(65536); 1855 | B = Array(8192); 1856 | r = Array(32832); 1857 | t = Array(65536); 1858 | w = Array(573); 1859 | for (b = 0; 573 > b; b++) 1860 | w[b] = new ga; 1861 | L = Array(61); 1862 | for (b = 0; 61 > b; b++) 1863 | L[b] = new ga; 1864 | R = Array(288); 1865 | for (b = 0; 288 > b; b++) 1866 | R[b] = new ga; 1867 | X = Array(30); 1868 | for (b = 0; 30 > b; b++) 1869 | X[b] = new ga; 1870 | J = Array(39); 1871 | for (b = 0; 39 > b; b++) 1872 | J[b] = new ga; 1873 | P = new ua; 1874 | Q = new ua; 1875 | D = new ua; 1876 | E = Array(16); 1877 | I = Array(573); 1878 | x = Array(573); 1879 | ea = Array(256); 1880 | aa = Array(512); 1881 | ra = Array(29); 1882 | ja = Array(30); 1883 | ka = Array(1024) 1884 | } 1885 | for (var h = Array(1024), k = []; 0 < (b = Oa(h, 0, h.length)); ) { 1886 | var p = Array(b); 1887 | for (e = 0; e < b; e++) 1888 | p[e] = String.fromCharCode(h[e]); 1889 | k[k.length] = p.join("") 1890 | } 1891 | ma = null; 1892 | return k.join("") 1893 | } 1894 | }, 1895 | vzCy: function(b, e) { 1896 | function a() { 1897 | this._events = this._events || {}; 1898 | this._maxListeners = this._maxListeners || void 0 1899 | } 1900 | function g(a) { 1901 | return "function" === typeof a 1902 | } 1903 | function l(a) { 1904 | return "object" === typeof a && null !== a 1905 | } 1906 | b.exports = a; 1907 | a.EventEmitter = a; 1908 | a.prototype._events = void 0; 1909 | a.prototype._maxListeners = void 0; 1910 | a.defaultMaxListeners = 10; 1911 | a.prototype.setMaxListeners = function(a) { 1912 | if ("number" !== typeof a || 0 > a || isNaN(a)) 1913 | throw TypeError("n must be a positive number"); 1914 | this._maxListeners = a; 1915 | return this 1916 | } 1917 | ; 1918 | a.prototype.emit = function(a) { 1919 | var c, b, d, e; 1920 | this._events || (this._events = {}); 1921 | if ("error" === a && (!this._events.error || l(this._events.error) && !this._events.error.length)) { 1922 | c = arguments[1]; 1923 | if (c instanceof Error) 1924 | throw c; 1925 | b = Error('Uncaught, unspecified "error" event. (' + c + ")"); 1926 | b.context = c; 1927 | throw b; 1928 | } 1929 | b = this._events[a]; 1930 | if (void 0 === b) 1931 | return !1; 1932 | if (g(b)) 1933 | switch (arguments.length) { 1934 | case 1: 1935 | b.call(this); 1936 | break; 1937 | case 2: 1938 | b.call(this, arguments[1]); 1939 | break; 1940 | case 3: 1941 | b.call(this, arguments[1], arguments[2]); 1942 | break; 1943 | default: 1944 | c = Array.prototype.slice.call(arguments, 1), 1945 | b.apply(this, c) 1946 | } 1947 | else if (l(b)) 1948 | for (c = Array.prototype.slice.call(arguments, 1), 1949 | e = b.slice(), 1950 | b = e.length, 1951 | d = 0; d < b; d++) 1952 | e[d].apply(this, c); 1953 | return !0 1954 | } 1955 | ; 1956 | a.prototype.addListener = function(b, c) { 1957 | var e; 1958 | if (!g(c)) 1959 | throw TypeError("listener must be a function"); 1960 | this._events || (this._events = {}); 1961 | this._events.newListener && this.emit("newListener", b, g(c.listener) ? c.listener : c); 1962 | this._events[b] ? l(this._events[b]) ? this._events[b].push(c) : this._events[b] = [this._events[b], c] : this._events[b] = c; 1963 | l(this._events[b]) && !this._events[b].warned && (e = void 0 !== this._maxListeners ? this._maxListeners : a.defaultMaxListeners) && 0 < e && this._events[b].length > e && (this._events[b].warned = !0, 1964 | console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.", this._events[b].length), 1965 | "function" === typeof console.trace && console.trace()); 1966 | return this 1967 | } 1968 | ; 1969 | a.prototype.on = a.prototype.addListener; 1970 | a.prototype.once = function(a, b) { 1971 | function e() { 1972 | this.removeListener(a, e); 1973 | d || (d = !0, 1974 | b.apply(this, arguments)) 1975 | } 1976 | if (!g(b)) 1977 | throw TypeError("listener must be a function"); 1978 | var d = !1; 1979 | e.listener = b; 1980 | this.on(a, e); 1981 | return this 1982 | } 1983 | ; 1984 | a.prototype.removeListener = function(a, b) { 1985 | var e, d, h; 1986 | if (!g(b)) 1987 | throw TypeError("listener must be a function"); 1988 | if (!this._events || !this._events[a]) 1989 | return this; 1990 | e = this._events[a]; 1991 | h = e.length; 1992 | d = -1; 1993 | if (e === b || g(e.listener) && e.listener === b) 1994 | delete this._events[a], 1995 | this._events.removeListener && this.emit("removeListener", a, b); 1996 | else if (l(e)) { 1997 | for (; 0 < h--; ) 1998 | if (e[h] === b || e[h].listener && e[h].listener === b) { 1999 | d = h; 2000 | break 2001 | } 2002 | if (0 > d) 2003 | return this; 2004 | 1 === e.length ? (e.length = 0, 2005 | delete this._events[a]) : e.splice(d, 1); 2006 | this._events.removeListener && this.emit("removeListener", a, b) 2007 | } 2008 | return this 2009 | } 2010 | ; 2011 | a.prototype.removeAllListeners = function(a) { 2012 | var b; 2013 | if (!this._events) 2014 | return this; 2015 | if (!this._events.removeListener) 2016 | return 0 === arguments.length ? this._events = {} : this._events[a] && delete this._events[a], 2017 | this; 2018 | if (0 === arguments.length) { 2019 | for (b in this._events) 2020 | "removeListener" !== b && this.removeAllListeners(b); 2021 | this.removeAllListeners("removeListener"); 2022 | this._events = {}; 2023 | return this 2024 | } 2025 | b = this._events[a]; 2026 | if (g(b)) 2027 | this.removeListener(a, b); 2028 | else if (b) 2029 | for (; b.length; ) 2030 | this.removeListener(a, b[b.length - 1]); 2031 | delete this._events[a]; 2032 | return this 2033 | } 2034 | ; 2035 | a.prototype.listeners = function(a) { 2036 | return this._events && this._events[a] ? g(this._events[a]) ? [this._events[a]] : this._events[a].slice() : [] 2037 | } 2038 | ; 2039 | a.prototype.listenerCount = function(a) { 2040 | if (this._events) { 2041 | a = this._events[a]; 2042 | if (g(a)) 2043 | return 1; 2044 | if (a) 2045 | return a.length 2046 | } 2047 | return 0 2048 | } 2049 | ; 2050 | a.listenerCount = function(a, b) { 2051 | return a.listenerCount(b) 2052 | } 2053 | }, 2054 | zx4i: function(b, e, a) { 2055 | b = a("agns"); 2056 | a.n(b); 2057 | var g = a("Qfv+") 2058 | , l = a.n(g) 2059 | , g = a("twhX") 2060 | , f = a.n(g) 2061 | , g = a("592r"); 2062 | a = a.n(g); 2063 | var c = function() { 2064 | return function(a, b) { 2065 | if (Array.isArray(a)) 2066 | return a; 2067 | if (Symbol.iterator in Object(a)) { 2068 | var c = [] 2069 | , e = !0 2070 | , f = !1 2071 | , g = void 0; 2072 | try { 2073 | for (var l = a[Symbol.iterator](), t; !(e = (t = l.next()).done) && (c.push(t.value), 2074 | !b || c.length !== b); e = !0) 2075 | ; 2076 | } catch (p) { 2077 | f = !0, 2078 | g = p 2079 | } finally { 2080 | try { 2081 | if (!e && l["return"]) 2082 | l["return"]() 2083 | } finally { 2084 | if (f) 2085 | throw g; 2086 | } 2087 | } 2088 | return c 2089 | } 2090 | throw new TypeError("Invalid attempt to destructure non-iterable instance"); 2091 | } 2092 | }() 2093 | , g = function d() { 2094 | d.superclass.constructor.call(this); 2095 | this._logined = !1; 2096 | this._loginData = null; 2097 | this._loginFailCount = 0; 2098 | this._doLogin = this._doLogin.bind(this); 2099 | this._heartbeatTimer = null; 2100 | this._heartbeat = this._heartbeat.bind(this); 2101 | this._timeoutTimer = null; 2102 | this._doTimeout = this._doTimeout.bind(this); 2103 | this.on("login.success", this._onLoginSuccess) 2104 | }; 2105 | Object(b.extend)(g, a.a, { 2106 | getAddressPool: function() {}, 2107 | getAddress: function() {}, 2108 | onReceiveMessage: function(a) {}, 2109 | onMessage: function(a) { 2110 | a = this.explode(a.data); 2111 | "receivemessage" == a.command ? (a.content = "yes" == a.enc ? this.decode(a.content) : atob(a.content), 2112 | a.content = this.stripZeroCharCode(a.content), 2113 | this._debugLog("[" + this.url + "] receivemessage: \n" + a.content), 2114 | a.content = JSON.parse(a.content), 2115 | this.onReceiveMessage(a.content)) : "result" == a.command ? this.emit(a.content, a) : "network.error" != a.command && "SecurityError" != a.command || this.emit(a.command, a); 2116 | this.emit("message", a.command, a.content) 2117 | }, 2118 | login: function(a) { 2119 | this._loginData = a; 2120 | this.on("network.error", this._onNetworkError); 2121 | this.on("SecurityError", this._onSecurityError); 2122 | this.on("login.failed", this._onLoginFailed); 2123 | this.on("close", this._onClose); 2124 | this.on("error", this._onError); 2125 | this.getAddressPool().then(this._doLogin) 2126 | }, 2127 | sendMsg: function(a, b) { 2128 | var c; 2129 | c = JSON.stringify({ 2130 | t: a, 2131 | content: b 2132 | }); 2133 | this._debugLog("%c[" + this.url + "] send: \n" + c, "color:blue;"); 2134 | c = ["command=sendmessage", "content=" + this.encode(c)]; 2135 | this.convey(c) 2136 | }, 2137 | convey: function(a) { 2138 | var b = a[0]; 2139 | (this._logined || "command=login" == b) && this.send(this.implode(a)) 2140 | }, 2141 | implode: function(a) { 2142 | a.push(""); 2143 | return a.join("\r\n") 2144 | }, 2145 | explode: function(a) { 2146 | var b = {}; 2147 | a.split("\r\n").reduce(function(a, b) { 2148 | if (b && -1 < b.indexOf("=")) { 2149 | var d = b.split("=") 2150 | , d = c(d, 2); 2151 | a[d[0]] = d[1] 2152 | } 2153 | return a 2154 | }, b); 2155 | return b 2156 | }, 2157 | decode: function(a) { 2158 | a = a.replace(/\(|\)|@/g, function(a) { 2159 | switch (a) { 2160 | case "(": 2161 | return "+"; 2162 | case ")": 2163 | return "/"; 2164 | case "@": 2165 | return "=" 2166 | } 2167 | }); 2168 | a = atob(a); 2169 | return a = l()(a, 6) 2170 | }, 2171 | encode: function(a) { 2172 | a = f()(this.encodeToUtf8(a), 6); 2173 | a = btoa(a); 2174 | return a = a.replace(/\+|\/|=/g, function(a) { 2175 | switch (a) { 2176 | case "+": 2177 | return "("; 2178 | case "/": 2179 | return ")"; 2180 | case "=": 2181 | return "@" 2182 | } 2183 | }) 2184 | }, 2185 | encodeToUtf8: function(a) { 2186 | return unescape(encodeURIComponent(a)) 2187 | }, 2188 | stripZeroCharCode: function(a) { 2189 | for (var b = [], c = 0; c < a.length; c++) 2190 | 0 < a.charCodeAt(c) && b.push(a[c]); 2191 | return b.join("") 2192 | }, 2193 | startHeartbeat: function() { 2194 | this._heartbeatTimer = setTimeout(this._heartbeat, 1E3) 2195 | }, 2196 | stopHeartbeat: function() { 2197 | clearTimeout(this._heartbeatTimer) 2198 | }, 2199 | close: function() { 2200 | this._debugLog("Manually close " + this.url + "."); 2201 | clearTimeout(this._doLoginTimer); 2202 | this._doLoginTimer = null; 2203 | this._logined = !1; 2204 | this.stopHeartbeat(); 2205 | this.removeAllListeners("network.error"); 2206 | this.removeAllListeners("SecurityError"); 2207 | this.removeAllListeners("login.failed"); 2208 | this.removeAllListeners("close"); 2209 | this.removeAllListeners("error"); 2210 | this.destroyCore(); 2211 | this.cleanQueue() 2212 | }, 2213 | _doLogin: function() { 2214 | var a = this._loginData 2215 | , b = this.getAddress() 2216 | , c = Object.keys(a).map(function(b) { 2217 | return b + "=" + a[b] 2218 | }); 2219 | c.unshift("command=login"); 2220 | this.connect("ws://" + b); 2221 | this.convey(c); 2222 | this._doLoginTimer = null; 2223 | this._cancelTimeout(); 2224 | this._timeoutTimer = setTimeout(this._doTimeout, 2E3); 2225 | this._debugLog(this._loginFailCount + "th login to: " + b + "\n" + c.join("\r\n")) 2226 | }, 2227 | _heartbeat: function() { 2228 | this.stopHeartbeat(); 2229 | this.convey(["command=sendmessage", "content=y8vPLwAA"]); 2230 | this._heartbeatTimer = setTimeout(this._heartbeat, 16E3) 2231 | }, 2232 | _doTimeout: function() { 2233 | this._debugLog("Timeout (2000ms) " + this.url); 2234 | this._onLoginFail() 2235 | }, 2236 | _cancelTimeout: function() { 2237 | clearTimeout(this._timeoutTimer) 2238 | }, 2239 | _onLoginFail: function() { 2240 | this._doLoginTimer || (this.stopHeartbeat(), 2241 | this._cancelTimeout(), 2242 | this._logined = !1, 2243 | this._loginFailCount++, 2244 | this._doLoginTimer = setTimeout(this._doLogin, 1E3)) 2245 | }, 2246 | _onLoginSuccess: function() { 2247 | this._debugLog("Login Success " + this.url); 2248 | this._logined = !0; 2249 | this._loginFailCount = 0; 2250 | this.startHeartbeat(); 2251 | this._cancelTimeout() 2252 | }, 2253 | _onNetworkError: function() { 2254 | this._debugLog("Network Error " + this.url); 2255 | this._onLoginFail() 2256 | }, 2257 | _onSecurityError: function() { 2258 | this._debugLog("Security Error " + this.url); 2259 | this._onLoginFail() 2260 | }, 2261 | _onLoginFailed: function() { 2262 | this._debugLog("Login Fail " + this.url); 2263 | this._onLoginFail() 2264 | }, 2265 | _onClose: function(a) { 2266 | this._debugLog(this.url + " " + a.code + " closed."); 2267 | this._onLoginFail() 2268 | }, 2269 | _onError: function(a) { 2270 | this._debugLog(this.url + " error."); 2271 | this._onLoginFail() 2272 | }, 2273 | _debugLog: function(a, b) { 2274 | this.emit("debuglog", a) 2275 | } 2276 | }); 2277 | e.a = g 2278 | } 2279 | }); 2280 | --------------------------------------------------------------------------------