├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config.coffee ├── config.demo.yaml ├── main.coffee ├── package.json ├── plugins ├── apiserver.coffee ├── chatlog.coffee ├── debug.coffee ├── help.coffee └── hitokoto.coffee ├── protocol.md ├── src ├── defaults.coffee ├── dispatcher.coffee ├── encrypt.js ├── httpclient.coffee ├── hubot-qq.coffee ├── qqapi.coffee ├── qqauth.coffee ├── qqauth_qrcode.js └── qqbot.coffee ├── tests ├── api.coffee ├── bot.coffee ├── encrypt.js ├── func_args.coffee ├── leak.coffee ├── login.coffee └── simple.coffee └── tmp └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.yaml 3 | *.log 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | > 2014.06 2 | 3 | * [插件]uptime -> 增加更多信息,接口调用,内存使用等 4 | * 出错后的自动重新登录支持 5 | 6 | > 2014.05 7 | 8 | * 升级群列表加密验证 9 | * 防掉线处理ing 10 | * 忽略长时间离线消息 11 | 12 | > 2014.03.21 13 | 14 | * 增加讨论组支持 15 | * 优化发消息随机数策略,希望能修复偶尔无法发送群消息的问题 16 | * 尝试优化编码风格 17 | * HTTP API Server 修改为插件支持 18 | * 增加插件重新加载功能 19 | 20 | > 2014.03.19 21 | > > PS. 暂时未使用 iced 特性 22 | 23 | * Hello IcedCoffeeScript 24 | 25 | 26 | > 2014.01.12 27 | 28 | * 增加 HTTP API 支持(群发消息,验证码输入等) 29 | 30 | > 2014.01.01 31 | 32 | * 支持hubot,现已加入豪华午餐! 33 | * 增加独立运行的入口文件 `main.coffee` 34 | 35 | > 2013.12.30 36 | 37 | * 08:37 有了第三方的资料文档后进展快了不少,增加获取群信息,发消息接口,抽离了httpclient功能,分离测试脚本auth,api 38 | * 13:11 学习Coffee Class的简单用法,提供了QQBot对象来处理各种接口信息。现已支持简单的poll事件解析 39 | * 18:05 增加回复响应功能,同时写了个比较挫的插件机制,但是至少似乎运作的还算正常!后续得看下hubot的代码学习下设计 40 | 41 | > 2013.12.29 42 | 43 | * 10:23 昨晚一直卡在登录账户验证处,调试到凌晨终于通过登录验证的第一个环节。PS.使用coffee写代码还真是蛮清爽的 44 | * 15:07 去了趟医院,怎么就突然感冒咳嗽头晕了。成功搞定node http post以及获取到qq登录最后一步token。睡会会。 45 | * 19:43 发现了一款开源的webqq协议的win客户端MingQQ,看截图完成度相当高。对了增加了验证码的支持 46 | * 20:44 增加在线(轮训功能),进度比预期的慢了些。但又发现了些资料和文档,补充在底部。分离qqauth和qqapi 47 | 48 | > 2013.12.28 49 | 50 | * 19:43 搜索了下果然已有好多轮子,但几乎所有的都是闭源付费以及是基于win系统。所以决定测试下基于webQQ协议的可能性。 51 | * 20:13 有点急功近利了,直接用coffeescript编码有点搞不定的感觉。看会Coffee和node的语法和api 52 | * 21:18 基本语法和http测试完成 :smile: 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 QiushiBaike.com. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | QQBot 2 | ------ 3 | A Hubot adapter for QQ! And also A independence robot lives on the real world. 4 | FYI: QQ is a instant messaging service widely used in china provided by Tencent. 5 | 6 | 基于[WebQQ协议](https://github.com/xhan/qqbot/blob/master/protocol.md)的QQ机器人。命令行工具,由不可思议的CoffeeScript提供支持。 7 | 8 | >DEMO 调戏用(测试和交流)QQ群:346167134 9 | 10 | 功能主治 Features 11 | ----- 12 | * :muscle: 登录和验证码支持 13 | * :muscle: 支持好友,群,讨论组的接入 14 | * :muscle: 插件化,目前支持消息的派发 15 | * :muscle: 可作为hubot adapter使用 16 | * :muscle: 提供HTTP API支持(比如群通知什么的都能做哦) 17 | 18 | 你可以用TA来 19 | 20 | * 辅助管理群成员,比如自动清理刷屏用户啊(请自己实现) 21 | * 聊天机器人(请自己实现AI) 22 | * 部署机器人(请了解hubot的概念) 23 | * 通知机器人(监控报警啊什么的,对于天天做电脑前报警还得通过邮件短信提醒多不直接呢) 24 | 25 | 26 | Acts as Hubot Adapter 27 | ------ 28 | * Add `hubot-qq` as a dependency in your hubots `package.json` 29 | * Run `npm install` in your hubots directory 30 | * Run hubot with `bin/hubot -a qq` 31 | 32 | Configurable Variables 33 | 34 | HUBOT_QQ_ID #QQ ID 35 | HUBOT_QQ_PASS #password 36 | HUBOT_QQ_GROUP #group name that hubot listens to 37 | HUBOT_QQ_IMGPORT #the port to serve verify-codes 38 | #for more debug variables plz check src/hubot-qq source file 39 | 40 | On LINUX or OSX use `export VARIABLE=VALUE` to set environment variables. 41 | 42 | 43 | 独立作为机器人运行 44 | ----- 45 | * 执行 `sudo npm install -g coffee-script` 安装 `CoffeeScript` 46 | * 执行 `npm install` 更新依赖 47 | * 配置一份你自己的 `config.yaml` 48 | * 执行 `./main.coffee` 让你的机器人躁起来~ 49 | 50 | 部署 51 | ----- 52 | > 部署环境下请确保你的机器人是不需要**验证码**登录的,否则可能会无法长时间在线 53 | 54 | 我常用的命令 `./main.coffee nologin &>> tmp/dev.log &` , 也可以使用进程管理工具比如 `pm2` 更省心 55 | 56 | 57 | API 58 | ---- 59 | TODO GET http://localhost:port/stdin?token=(token)&value=(value) 60 | 61 | 改动 62 | ---- 63 | https://github.com/xhan/qqbot/blob/master/CHANGELOG.md 64 | 65 | 资料 66 | ---- 67 | * WebQQ协议 https://github.com/xhan/qqbot/blob/master/protocol.md 68 | * Java版的另一个 http://webqq-core.googlecode.com/ 69 | 70 | TODO 71 | --- 72 | * 群成员拉取失败问题跟踪 73 | * 用户信息,qq号等 74 | * 机器人响应前缀 75 | * 图片发送支持 76 | 77 | -------------------------------------------------------------------------------- /config.coffee: -------------------------------------------------------------------------------- 1 | yaml = require 'js-yaml' 2 | fs = require 'fs' 3 | config = fs.readFileSync './config.yaml' , 'utf8' 4 | module.exports = yaml.load config -------------------------------------------------------------------------------- /config.demo.yaml: -------------------------------------------------------------------------------- 1 | # rename this file to config.yaml before start 2 | # qqbot帐号 3 | account: 2769546520 4 | password: "123" 5 | 6 | # 验证码图片 7 | port: 3100 8 | host: localhost 9 | 10 | 11 | # 消息处理,忽略20分钟外的离线消息 12 | # offline_msg_keeptime: 1200 13 | 14 | # 插件列表 15 | plugins: 16 | - help 17 | - debug 18 | - apiserver 19 | 20 | # apiserver 插件配置 21 | api_port: 3000 22 | api_token: '' -------------------------------------------------------------------------------- /main.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | ### 3 | QQBot 独立运行入口 4 | 启动命令:./main.coffee (nologin) 5 | @params nologin - 跳过登录模块,方便调试和测试 6 | ### 7 | 8 | 9 | log = new (require 'log')('debug') 10 | auth = require "./src/qqauth_qrcode" 11 | api = require "./src/qqapi" 12 | QQBot = require "./src/qqbot" 13 | defaults = require './src/defaults' 14 | config = require './config' 15 | 16 | KEY_COOKIES = 'qq-cookies' 17 | KEY_AUTH = 'qq-auth' 18 | 19 | ### 20 | # 获取接口需要的cookie和token 21 | # @param isneedlogin : 是否需要登录,or本地获取 22 | # @param options : 配置文件涉及的内容 23 | # @callback (cookies,auth_info) 24 | ### 25 | get_tokens = (isneedlogin, options,callback)-> 26 | 27 | if isneedlogin 28 | auth.login options , (cookies,auth_info)-> 29 | defaults.data KEY_COOKIES, cookies 30 | defaults.data KEY_AUTH , auth_info 31 | defaults.save() 32 | callback(cookies,auth_info) 33 | else 34 | cookies = defaults.data KEY_COOKIES 35 | auth_info = defaults.data KEY_AUTH 36 | log.info "skip login" 37 | callback(cookies , auth_info ) 38 | 39 | # 启动bot 40 | # 获取好友,群,群成员信息,然后进入守护模式 41 | # TODO: 获取信息 + 守护模式 同步状态 42 | run = -> 43 | "start qqbot..." 44 | params = process.argv.slice(-1)[0] or '' 45 | isneedlogin = params.trim() isnt 'nologin' 46 | get_tokens isneedlogin , config , (cookies,auth_info)-> 47 | bot = new QQBot(cookies,auth_info,config) 48 | 49 | # if config.keepalive 50 | bot.on_die -> run() if isneedlogin 51 | 52 | bot.update_all_members (ret)-> 53 | unless ret 54 | log.error "获取信息失败" 55 | process.exit(1) 56 | 57 | console.log "Group List:" 58 | console.log " #{v.name} (#{v.gid})" for k, v of bot.group_info.gnamelist 59 | 60 | console.log "Buddy List:" 61 | console.log " #{v.nick} (#{v.uin})" for k, v of bot.buddy_info.info 62 | 63 | log.info "Entering runloop, Enjoy!" 64 | bot.runloop() 65 | 66 | run() 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hubot-qq", 3 | "version": "1.4.0", 4 | 5 | "author": "xhan", 6 | 7 | "keywords": [ 8 | "qqbot", 9 | "hubot", 10 | "robot", 11 | "qq", 12 | "webqq" 13 | ], 14 | 15 | "description": "QQ adapter for Hubot, and also A dependence robot lives on webQQ", 16 | 17 | "licenses": [{ 18 | "type": "MIT", 19 | "url": "https://raw.github.com/xhan/qqbot/master/LICENSE" 20 | }], 21 | 22 | "repository" : { 23 | "type": "git", 24 | "url": "https://github.com/xhan/qqbot.git" 25 | }, 26 | 27 | "dependencies": { 28 | "request": "*", 29 | "log": ">= 1.4.0", 30 | "js-yaml" : ">= 3.0.0" 31 | }, 32 | 33 | "engines": { 34 | "node": ">= 0.10.x", 35 | "npm": ">= 1.1.x" 36 | }, 37 | 38 | "main": "./src/hubot-qq" 39 | 40 | } 41 | -------------------------------------------------------------------------------- /plugins/apiserver.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | QQBot Api Plugin 3 | ---------------- 4 | 通过HTTP API 方式和QQBot通信 5 | 6 | 配置依赖:config.yaml 7 | api_port: 3000 8 | api_token: '' 设置为 '' 为不需要密码 9 | 10 | 请求:GET/POST 方法都支持, 参数中token为必备字段 11 | exp: GET api/reload_plugin?token=values 12 | 13 | 返回:JSON { err:0 , msg:'error msg' } 14 | err:0 为成功响应,否则请看错误信息 15 | 16 | 17 | 接口: 18 | ---------------- 19 | 1. 发送消息 /send 20 | type = [buddy,group,discuss] 21 | to = 目标名字(不支持空格) 22 | msg = 消息文本 23 | 24 | 2. 发送图片验证码 /stdin 25 | value = 验证码 26 | 27 | 3. 重新加载插件 /reload 28 | 29 | ### 30 | 31 | 32 | URL = require 'url' 33 | http = require('http') 34 | jsons = JSON.stringify 35 | log = new (require 'log')('debug') 36 | querystring = require('querystring') 37 | 38 | 39 | # 处理post的信息,来自网络 40 | processPost = (request, response, callback) -> 41 | queryData = "" 42 | return null if typeof callback isnt 'function' 43 | request.on 'data', (data)-> 44 | queryData += data 45 | if queryData.length > 1e6 46 | queryData = "" 47 | response.writeHead(413, {'Content-Type': 'text/plain'}).end(); 48 | request.connection.destroy(); 49 | 50 | request.on 'end', -> 51 | data = querystring.parse(queryData) 52 | callback(data) 53 | 54 | request.on 'error', (error)-> 55 | callback(null, error) 56 | 57 | 58 | class APIServer 59 | constructor:(@qqbot)-> 60 | config = @qqbot.config 61 | @http_server = null 62 | [@port, @token] = [config.api_port, config.api_token] 63 | 64 | start: -> 65 | @http_server = @create_server(@port) 66 | stop: -> 67 | @http_server.close() if @http_server 68 | @http_server = null 69 | log.info "aip server stoped" 70 | 71 | create_server: (port)-> 72 | server = http.createServer (req, res) => 73 | log.debug '[api]' , req.url 74 | url = URL.parse req.url 75 | path = url.pathname 76 | if req.method == 'POST' 77 | processPost req, res, (body) => 78 | @handle_request(req, res, path, body) 79 | else if req.method == 'GET' 80 | query = querystring.parse url.query 81 | @handle_request(req, res, path, query) 82 | 83 | server.listen port 84 | log.info 'api server started at port',port 85 | return server 86 | 87 | 88 | handle_request: (req,res,path,params)-> 89 | res.endjson = (dict={})-> 90 | ret_dict = 91 | err: 0 92 | msg: 'ok' 93 | for key,value of dict 94 | ret_dict[key] = value 95 | 96 | res.writeHead 200 97 | res.end jsons ret_dict 98 | 99 | if @token and params.token isnt @token 100 | res.endjson {err:503,msg:'token failed'} 101 | return 102 | 103 | func = 104 | switch path 105 | when '/stdin' then @on_stdin(req, res, params) 106 | when '/send' then @on_sendmsg(req, res, params) 107 | when '/reload'then @on_reload_plugin(req, res, params) 108 | else res.endjson {err:404,msg:'request not fits'} 109 | 110 | 111 | on_stdin : (req,res,params)-> 112 | value = params.value.trim() 113 | log.info 'stdin value',value 114 | process.emit 'data', value 115 | res.endjson() 116 | 117 | on_reload_plugin : (req,res,params)-> 118 | res.endjson {err:1, msg:"method unimplemented"} 119 | 120 | on_sendmsg : (req,res,params)-> 121 | log.info "will send #{params.type} #{params.to} : #{params.msg}" 122 | if params.type == 'buddy' 123 | user = params.to 124 | @qqbot.send_message user, params.msg, (ret,e)-> 125 | resp_ret = {result:ret} 126 | if e 127 | resp_ret.err = 1 128 | resp_ret.msg = "#{e}" 129 | res.endjson resp_ret 130 | 131 | else if params.type == 'group' 132 | if parseInt(params.to) > 0 133 | group = params.to 134 | else 135 | group = @qqbot.get_group {name:params.to} 136 | @qqbot.send_message_to_group group, params.msg, (ret,e)-> 137 | resp_ret = {result:ret} 138 | if e 139 | resp_ret.err = 1 140 | resp_ret.msg = "#{e}" 141 | res.endjson resp_ret 142 | 143 | else if params.type == 'discuss' 144 | discuss_group = @qqbot.get_dgroup {name:params.to} 145 | unless discuss_group 146 | res.endjson {err:501, msg:"can't find discuss by name #{params.to}"} 147 | return 148 | @qqbot.send_message_to_discuss discuss_group.did, params.msg, (ret,e)-> 149 | resp_ret = {result:ret} 150 | if e 151 | resp_ret.err = 1 152 | resp_ret.msg = "#{e}" 153 | res.endjson resp_ret 154 | 155 | 156 | else 157 | msg = "unimplement type #{params.type}" 158 | log.warning msg 159 | res.endjson {err:100,msg:msg} 160 | 161 | 162 | ################################################ 163 | # QQBot Plugin 接口 164 | api_server = null 165 | exports.init = (qqbot)-> 166 | api_server = new APIServer(qqbot) 167 | api_server.start(); 168 | 169 | exports.stop = (qqbot)-> 170 | api_server.stop() 171 | 172 | -------------------------------------------------------------------------------- /plugins/chatlog.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | default behavior: log chat logs to file 3 | @command top (count) (chat-lines): show group's chat ranks in (count) depends on recently (chat-lines) history. 4 | need configs: 5 | - 6 | ### 7 | 8 | module.exports = (content ,send, robot, message)-> 9 | "nothing" 10 | -------------------------------------------------------------------------------- /plugins/debug.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | send group|buddy|discuss name message 3 | reload 4 | relogin 5 | run method 6 | ### 7 | 8 | 9 | 10 | module.exports = (content ,send, robot, message)-> 11 | if content.match /^die$/i 12 | robot.die("debug") 13 | 14 | if content.match /^reload$/i 15 | robot.dispatcher.reload_plugin() 16 | send "重新加载插件" 17 | 18 | if content.match /^relogin$/i 19 | robot.relogin (success)-> 20 | send if success then "成功" else "失败" 21 | 22 | # run 23 | ret = content.match /^run\s+(.*)/i 24 | if ret 25 | method = ret[1] 26 | console.log method 27 | robot[method]() 28 | 29 | # send 30 | ret = content.match /^send\s+(.*?)\s+(.*?)\s+(.*)/i 31 | if ret 32 | [type,to,msg] = ret[1..3] 33 | switch type 34 | when 'group' 35 | group = robot.get_group {name:to} 36 | robot.send_message_to_group group, msg, (ret,e)-> 37 | if e 38 | send "消息发送失败 #{e}" 39 | else 40 | send "消息已发送" 41 | 42 | when 'buddy' then "" 43 | when 'discuss' then "" 44 | 45 | -------------------------------------------------------------------------------- /plugins/help.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | 3 | 插件支持两个方法调用 4 | init(robot) 5 | received(content,send,robot,message) 6 | stop(robot) 7 | 8 | 1.直接使用 9 | module.exports = func 为快捷隐式调用 received 方法 10 | 2.或 11 | module.exports = { 12 | init: init_func # 初始化调用 13 | received: received_func # 接受消息 14 | stop: init_func # 停止插件(比如端口占用) 15 | } 16 | 17 | 18 | ### 19 | 20 | HELP_INFO = """ 21 | version/about #版本信息和关于 22 | plugins #查看载入的插件 23 | time #显示时间 24 | echo 爱你 #重复后面的话 25 | help #本内容 26 | uptime #服务运行时间 27 | roll #返回1-100随机值 28 | """ 29 | 30 | fs = require 'fs' 31 | Path = require 'path' 32 | file_path = Path.join __dirname, "..", "package.json" 33 | bundle = JSON.parse( fs.readFileSync file_path ) 34 | 35 | VERSION_INFO = """ 36 | v#{bundle.version} qqbot 37 | http://github.com/xhan/qqbot 38 | 本工具还由 糗事百科 热血赞助! 39 | """ 40 | 41 | ### 42 | @param content 消息内容 43 | @param send(content) 回复消息 44 | @param robot qqbot instance 45 | @param message 原消息对象 46 | ### 47 | 48 | # IMPROVE: 方式不优雅,应该是一个模式识别成功,别的就不应调用到 49 | module.exports = (content ,send, robot, message)-> 50 | 51 | if content.match /^help$/i 52 | send HELP_INFO 53 | 54 | if content.match /^VERSION|ABOUT$/i 55 | send VERSION_INFO 56 | 57 | if content.match /^plugins$/i 58 | send "插件列表:\n" + robot.dispatcher.plugins.join('\r\n') 59 | 60 | if content.match /^time$/i 61 | send "冥王星引力精准校时:" + new Date() 62 | 63 | if ret = content.match /^echo (.*)/i 64 | send ret[1] 65 | 66 | if content.match /^uptime$/i 67 | secs = process.uptime() 68 | [aday,ahour] = [86400 ,3600] 69 | [day,hour,minute,second] = [secs/ aday,secs%aday/ ahour,secs%ahour/ 60,secs%60].map (i)-> parseInt(i) 70 | t = (i)-> "0#{i}"[-2..] # 让时间更漂亮 71 | memory = process.memoryUsage().rss / 1024 / 1024 72 | send "up #{day} days, #{t hour}:#{t minute}:#{t second} | mem: #{memory.toFixed(1)}M" 73 | 74 | if content.match /^roll$/i 75 | # TODO:who? , need a reply method 76 | send Math.round( Math.random() * 100) -------------------------------------------------------------------------------- /plugins/hitokoto.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | http://hitokoto.us/ 3 | 简单来说,一言(ヒトコト)指的是就是一句话,可以是动漫中的台词,可以是小说中的语句,也可以是网络上的各种小段子。 4 | 或是感动,或是开心,又或是单纯的回忆,来到这里,留下你所喜欢的那一句句话,与大家分享,这就是一言存在的目的。 5 | ### 6 | 7 | module.exports = (content ,send, robot, message)-> 8 | 9 | if content.match /^comic$/i 10 | robot.request.get {url:"http://api.hitokoto.us/rand",json:true}, (e,r,data)-> 11 | if data and data.hitokoto 12 | send data.hitokoto + " --" + data.source 13 | else 14 | send e 15 | -------------------------------------------------------------------------------- /protocol.md: -------------------------------------------------------------------------------- 1 | WebQQ 协议文档 2 | =============== 3 | 更新时间: 2013.12.30 4 | > 后来发现以及有几个专门的论坛(见首页)在讨论WebQQ接口,所以这里不再更新具体协议,而是调试过程中的问题记录 5 | > 是否有需要的cookie,请求参数,以及http header 比如refer 都可能导致请求失败,最好的办法就是抓包后复制所有信息:D 6 | 7 | 1.第一次握手,获取加密信息以及是否需要验证码 8 | ----- 9 | ptui_checkVC('1','4GJSgm18Bw-8yw5JGKpSOXH-0idqvaCU','\x00\x00\x00\x00\xa5\x13\xed\x18'); 10 | * @param1 是否需要验证码 11 | * @param2 默认验证码,如果 @param1 为1 此字段没用 ,密码加密需要 12 | * @param2 密码加密需要 13 | 14 | > 这里会取得一些 cookie 15 | 16 | 17 | 2.获取验证码图片(非必要) 18 | ----- 19 | > 这里会取得一些 cookie 20 | 21 | 22 | 23 | 3.账户密码以及验证码的校验 24 | ----- 25 | 密码加密方式,略 26 | 27 | ptuiCB('7','0','','0','很遗憾,网络连接出现异常,请您稍后再试。(2220884626)', '123774072'); `参数不对就出现这个` 28 | ptuiCB('4','0','','0','您输入的验证码不正确,请重新输入。', '123774072'); 29 | ptuiCB('24','0','','0','很遗憾,网络连接出现异常,请您检查是否禁用cookies。(3972006392)', '123774072'); 30 | ptuiCB('4','2','','0','页面过期,请重试。*', '123774072'); 31 | ptuiCB('0','0','link','0','登录成功!', '呼吸 (糗百)'); 32 | [ '3', '0', '', '0', '您输入的帐号或密码不正确,请重新输入。', '2769546520' ] 33 | 34 | 35 | 4.获取额外的cookie 36 | ------- 37 | 步骤3中 @param3 需要请求一次,并得到登录所需要的所有cookie ,最简单的一步 38 | 39 | 40 | 5.获取登录token 41 | ----- 42 | 需要保存的几个参数,当然之前获得的cookie是必要的 43 | 44 | psessionid : 本次返回值 45 | client_id : 步骤@4 自己生成 46 | ptwebqq : 步骤@4 来源cookie 47 | uin : 本次返回值 48 | vfwebqq : 本次返回值 49 | 50 | 错误码: 51 | 52 | { retcode: 108, errmsg: '' } `缺少cookie` 53 | { retcode: 103, errmsg: '' } `缺少cookie` 54 | 55 | 56 | 6.长轮训 57 | ----- 58 | 错误码 108 应该也是 cookie 59 | 60 | 121 重复登录 61 | 62 | sys_g_msg 63 | kick_message 64 | 65 | ``` json 66 | { 67 | "result": [ 68 | { 69 | "poll_type": "message", 70 | "value": { 71 | "content": [ 72 | [ 73 | "font", 74 | { 75 | "color": "004faa", 76 | "name": "STKaiti", 77 | "size": 13, 78 | "style": [ 79 | 0, 80 | 0, 81 | 0 82 | ] 83 | } 84 | ], 85 | "hii " 86 | ], 87 | "from_uin": 2440652742, 88 | "msg_id": 24225, 89 | "msg_id2": 950054, 90 | "msg_type": 9, 91 | "reply_ip": 176498455, 92 | "time": 1388368696, 93 | "to_uin": 2769546520 94 | } 95 | } 96 | ], 97 | "retcode": 0 98 | } 99 | 100 | { 101 | "result": [ 102 | { 103 | "poll_type": "group_message", 104 | "value": { 105 | "content": [ 106 | [ 107 | "font", 108 | { 109 | "color": "004faa", 110 | "name": "STKaiti", 111 | "size": 13, 112 | "style": [ 113 | 0, 114 | 0, 115 | 0 116 | ] 117 | } 118 | ], 119 | "ooo " 120 | ], 121 | "from_uin": 2559225925, 122 | "group_code": 3483368056, 123 | "info_seq": 346167134, 124 | "msg_id": 13663, 125 | "msg_id2": 886022, 126 | "msg_type": 43, 127 | "reply_ip": 176756826, 128 | "send_uin": 2440652742, 129 | "seq": 27, 130 | "time": 1388368698, 131 | "to_uin": 2769546520 132 | } 133 | } 134 | ], 135 | "retcode": 0 136 | } 137 | ``` 138 | 139 | 140 | 7.获取用户,群分组 141 | ----- 142 | 群分组非常简单,用户接口有个hash的字段。相传这个加密函数更新的比较及时。 143 | 用js写的好处就不用每次跟着重新实现一套了,复制过来执行就好 144 | http://0.web.qstatic.com/webqqpic/pubapps/0/50/eqq.all.js 搜索 P=function 145 | 146 | {"retcode":100101} `没cookie` or `ua 未设置好` 147 | 148 | 149 | 150 | 用户分组信息格式 151 | ``` json 152 | { 153 | "categories": [ 154 | { 155 | "index": 1, 156 | "name": "Acquaintances", 157 | "sort": 1 158 | }, 159 | { 160 | "index": 2, 161 | "name": "Family", 162 | "sort": 2 163 | } 164 | ], 165 | "friends": [ 166 | { 167 | "categories": 0, 168 | "flag": 0, 169 | "uin": 2440652742 170 | } 171 | ], 172 | "info": [ 173 | { 174 | "face": 0, 175 | "flag": 25707010, 176 | "nick": "\u547c\u5438 (\u7cd7\u767e)", 177 | "uin": 2440652742 178 | } 179 | ], 180 | "marknames": [], 181 | "vipinfo": [ 182 | { 183 | "is_vip": 0, 184 | "u": 2440652742, 185 | "vip_level": 0 186 | } 187 | ] 188 | } 189 | 190 | ``` 191 | 192 | 群分组信息 193 | 194 | ``` json 195 | { 196 | "gmarklist": [], 197 | "gmasklist": [], 198 | "gnamelist": [ 199 | { 200 | "code": 3483368056, 201 | "flag": 1090519041, 202 | "gid": 2559225925, 203 | "name": "qqbot\u7fa4" 204 | } 205 | ] 206 | } 207 | flag 1090519041 208 | gid 2774835871 #发表用到 209 | code 1787526753 #获取群member用到 210 | 211 | 212 | ``` 213 | 214 | 群成员 215 | ``` json 216 | {"retcode":0,"result":{"stats":[{"client_type":1,"uin":2440652742,"stat":10},{"client_type":41,"uin":2769546520,"stat":10}],"minfo":[{"nick":"呼吸 (糗百)","province":"上海","gender":"male","uin":2440652742,"country":"中国","city":"浦东新区"},{"nick":"robot","province":"上海","gender":"female","uin":2899268194,"country":"中国","city":"杨浦"},{"nick":"qqbot","province":"广东","gender":"male","uin":2769546520,"country":"中国","city":"深圳"},{"nick":"robot ops","province":"上海","gender":"male","uin":2041084648,"country":"中国","city":"黄浦"}],"ginfo":{"face":0,"memo":"","class":10048,"fingermemo":"","code":3483368056,"createtime":1388307595,"flag":1090519041,"level":0,"name":"qqbot群","gid":2559225925,"owner":2769546520,"members":[{"muin":2440652742,"mflag":21},{"muin":2899268194,"mflag":20},{"muin":2769546520,"mflag":20},{"muin":2041084648,"mflag":0}],"option":1},"vipinfo":[{"vip_level":0,"u":2440652742,"is_vip":0},{"vip_level":0,"u":2899268194,"is_vip":0},{"vip_level":0,"u":2769546520,"is_vip":0},{"vip_level":0,"u":2041084648,"is_vip":0}]}} 217 | ``` 218 | 219 | 220 | 8. 讨论组 221 | 222 | 223 | 获取讨论组 224 | 225 | GET http://s.web2.qq.com/api/get_discus_list 226 | 227 | clientid 228 | psessionid 229 | vfwebqq 230 | t 231 | 232 | 233 | ret {"retcode":0,"result":{"dnamelist":[{"name":"good day","did":2006892653}]}} 234 | 235 | 236 | 获取讨论组 成员 237 | 238 | GET http://d.web2.qq.com/channel/get_discu_info 239 | did 240 | clientid 241 | psessionid 242 | vfwebqq 243 | t 244 | 245 | 246 | {"retcode":0,"result":{"info":{"did":2006892653,"discu_owner":1657605740,"discu_name":"good day","info_seq":2,"mem_list":[{"mem_uin":1960624993,"ruin":21984709},{"mem_uin":1657605740,"ruin":123774072},{"mem_uin":2769546520,"ruin":2769546520}]},"mem_status":[{"uin":1657605740,"status":"online","client_type":1},{"uin":2769546520,"status":"online","client_type":41}],"mem_info":[{"uin":1960624993,"nick":"21984709"},{"uin":1657605740,"nick":"\u547C\u5438 (\u7CD7\u767E)"},{"uin":2769546520,"nick":"qqbot"}]}} 247 | 248 | 249 | 发表消息: 250 | did -> 组标识 251 | 252 | POST http://d.web2.qq.com/channel/send_discu_msg2 253 | 254 | 255 | 256 | post: 257 | clientid 258 | psessionid 259 | r= 260 | {"did":"2006892653","content":"[\"gogo\\n\",[\"font\",{\"name\":\"宋体\",\"size\":\"10\",\"style\":[0,0,0],\"color\":\"000000\"}]]","msg_id":78330002,"clientid":"30833194","psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133332e34312e38340000687900000ac0026e040018ed13a56d0000000a404c3453313676556f5a6d0000002815ee4198a3f863ce8931fec0dd1fe0e6fc9c06887829c4f7342a936929d79271bdac9e0764af52f9"} 261 | 262 | clientid:84369220 263 | 30833194 264 | 265 | { 266 | "clientid": "30833194", 267 | "content": "[\"gogo\\n\",[\"font\",{\"name\":\"宋体",\"size\":\"10\",\"style\":[0,0,0],\"color\":\"000000\"}]]", 268 | "did": "2006892653", 269 | "msg_id": 78330002, 270 | "psessionid": "8368046764001d636f6e6e7365727665725f77656271714031302e3133332e34312e38340000687900000ac0026e040018ed13a56d0000000a404c3453313676556f5a6d0000002815ee4198a3f863ce8931fec0dd1fe0e6fc9c06887829c4f7342a936929d79271bdac9e0764af52f9" 271 | } 272 | 273 | 274 | 图片 {"did":"2006892653","key":"E3MR8qZFt9xvRYRp","sig":"70448d5ab241b34b39622d1d6eaebdd462af7e55a154fa0cf7fccac522cac89ae78abdc646825f2f3429b31d913823e200eb2a319b4ea034","content":"[[\"cface\",\"group\",\"8D100B2F7A9221FBD60D46AA97E038B9.jPg\"],\"\\n\",[\"font\",{\"name\":\"宋体\",\"size\":\"10\",\"style\":[0,0,0],\"color\":\"000000\"}]]","clientid":"72295937","psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133332e34312e38340000019000000ac1026e040018ed13a56d0000000a404c3453313676556f5a6d00000028402c0abcd7ca6e184430d72632b8cb5e965901346358ba0111a7df92673c6a86251c124ef348c8c0"} 275 | ``` 276 | 277 | 278 | 279 | 280 | 讨论组图片 281 | POST http://up.web2.qq.com/cgi-bin/cface_upload?time=1395248690537 282 | multipart/form-data; 283 | -- 284 | from control 285 | f EQQ.Model.ChatMsg.callbackSendPicGroup 286 | vfwebqq 402c0abcd7ca6e184430d72632b8cb5e965901346358ba0111a7df92673c6a86251c124ef348c8c0 287 | custom_face binary 288 | fileid 1 289 | 290 | http://web2.qq.com/cgi-bin/webqq_app/?cmd=2&bd=8D100B2F7A9221FBD60D46AA97E038B9.jPg&vfwebqq=402c0abcd7ca6e184430d72632b8cb5e965901346358ba0111a7df92673c6a86251c124ef348c8c0 291 | 292 | 8D100B2F7A9221FBD60D46AA97E038B9.jPg8D100B2F7A9221FBD60D46AA97E038B9.jPg -7001 293 | 294 | 295 | 讨论组图片2 296 | get http://d.web2.qq.com/channel/get_gface_sig2?clientid=72295937&psessionid=8368046764001d636f6e6e7365727665725f77656271714031302e3133332e34312e38340000019000000ac1026e040018ed13a56d0000000a404c3453313676556f5a6d00000028402c0abcd7ca6e184430d72632b8cb5e965901346358ba0111a7df92673c6a86251c124ef348c8c0&t=1395248986808 297 | 298 | {"retcode":0,"result":{"reply":0,"gface_key":"E3MR8qZFt9xvRYRp","gface_sig":"70448d5ab241b34b39622d1d6eaebdd462af7e55a154fa0cf7fccac522cac89ae78abdc646825f2f3429b31d913823e200eb2a319b4ea034"}} 299 | 300 | 301 | 302 | 接受消息: 303 | 304 | 305 | {"retcode":0,"result":[{"poll_type":"discu_message","value":{"msg_id":18803,"from_uin":10000,"to_uin":2769546520,"msg_id2":175418,"msg_type":42,"reply_ip":176488598,"did":2006892653,"send_uin":1657605740,"seq":8,"time":1395249174,"info_seq":2,"content":[["font",{"size":13,"color":"004faa","style":[0,0,0],"name":"STKaiti"}],"world "]}}]} 306 | 307 | 308 | ### 全局header 309 | 310 | ``` 311 | Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 312 | Accept-Encoding gzip, deflate 313 | Accept-Language en-US,en;q=0.5 314 | Connection keep-alive 315 | Content-Type utf-8 316 | Cookie pgv_pvid=200718902; ts_uid=3184827993; pt2gguin=o1953024456; RK=pXXKnGphdn; ptcz=7c391c6d99aa6721878e46168f055a4d896d4312382656cfa344ebafdaa73509; hideusehttpstips=1; pgv_info=ssid=s6396104556&pgvReferrer=; ptisp=ctc; verifysession=h02mz5_56T4mZkxOmYRLSTPDJf1Nw9ZHo0ooO9ClWROPye9FukVWemEHC6buD7N7INJXn2vBvy8q0oCcXpfa6r9_Q**; ptui_loginuin=1953024456; uin=o1953024456; skey=@SoxMZ1bd4; ptwebqq=636683b80e7b0b46e26cc6b07c33432a4ab692e52f96e105d4d06fabd0735986; p_uin=o1953024456; p_skey=vnhLt1L6rI9PxEcvDyJCKttVGcjynVh-v7IZlH8s*oM_; pt4_token=rcG*VvNuYkYzE8IcWUGgVg__ 317 | Host d.web2.qq.com 318 | Referer http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=2 319 | User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:29.0) Gecko/20100101 Firefox/29.0 320 | ``` 321 | 322 | ### 修改状态 323 | get http://d.web2.qq.com/channel/change_status2 324 | 325 | ``` 326 | clientid 34705872 327 | newstatus hidden 328 | psessionid 8368046764001d636f6e6e7365727665725f77656271714031302e3133332e342e313732000024b000001950026e0400c8c968746d0000000a40536f784d5a316264346d000000285859e6c03eb067fa5d389b278a62aeeba9ff31395b9e37a29f5c8f5ba42c3ad2a47cd83332dbe5d8 329 | t 1401343586702 330 | ``` 331 | 332 | [online,hidden,busy,callme,offline] 333 | 334 | resp: `{"retcode":0,"result":"ok"} ` 335 | 336 | 337 | 338 | ### 获取群号/用户qq号 339 | get http://s.web2.qq.com/api/get_friend_uin2 340 | 341 | ``` 群 342 | code 343 | t 1401344118978 344 | tuin 4292540855 345 | type 4 346 | verifysession 347 | vfwebqq 5859e6c03eb067fa5d389b278a62aeeba9ff31395b9e37a29f5c8f5ba42c3ad2a47cd83332dbe5d8 348 | ``` 349 | 350 | ``` 351 | {"retcode":0,"result":{"uiuin":"","account":346167134,"uin":4292540855}} 352 | 346167134 群号 353 | ``` 354 | 355 | ``` 用户 356 | code 357 | t 1401344326640 358 | tuin 10077914 359 | type 1 360 | verifysession 361 | vfwebqq 5859e6c03eb067fa5d389b278a62aeeba9ff31395b9e37a29f5c8f5ba42c3ad2a47cd83332dbe5d8 362 | ``` 363 | 364 | ``` 365 | {"retcode":0,"result":{"uiuin":"","account":2489288370,"uin":10077914}} 366 | ``` 367 | 368 | 369 | 370 | 371 | 372 | 373 | ### 未整理 374 | 375 | 376 | 错误代码说明: 377 | 378 | ptuiCB('7','0','','0','很遗憾,网络连接出现异常,请您稍后再试。(612369104)'); 379 | 380 | cookie或qq号码问题 381 | ptuiCB('0','0','http://aq.qq.com/cn/services/abnormal/abnormal_index? 。。。。。。!'); 382 | 帐号冻结 383 | 384 | {"retcode":102 ,"errmsg":""} 385 | 386 | 正常连接、没有消息。 387 | 388 | {"retcode":103,"errmsg":""} 389 | 390 | 掉线 391 | 392 | {"retcode":108,"errmsg":""} 393 | 394 | {"retcode":114,"errmsg":""} 395 | 396 | {"retcode":121,"t":"0"} 397 | 398 | 掉线 399 | 400 | {"retcode":122,"errmsg":"wrong web client3"} 401 | 402 | {"retcode":100001} 403 | 404 | 群编号有问题 405 | 406 | {"retcode":100006,"errmsg":""} 407 | 408 | 409 | 410 | 411 | 412 | -------------------------------------------------------------------------------- /src/defaults.coffee: -------------------------------------------------------------------------------- 1 | # a place to store user defaults 2 | defaults = {} 3 | fs = require 'fs' 4 | path = 'tmp/store.json' 5 | 6 | empty = (obj)-> 7 | Object.keys(obj).length == 0 8 | 9 | 10 | exports.set_path = (newpath)-> 11 | path = newpath 12 | 13 | exports.data = (key,value)-> 14 | read() if empty? defaults 15 | if key and value 16 | defaults[key] = value 17 | else if key 18 | defaults[key] 19 | else 20 | defaults 21 | 22 | exports.save = -> 23 | fs.writeFileSync path , JSON.stringify defaults 24 | 25 | read = exports.read = -> 26 | try 27 | defaults = JSON.parse( fs.readFileSync path ) 28 | catch error 29 | console.log error 30 | -------------------------------------------------------------------------------- /src/dispatcher.coffee: -------------------------------------------------------------------------------- 1 | Log = require 'log' 2 | log = new Log('debug') 3 | 4 | EventEmitter = require('events').EventEmitter 5 | 6 | requireForce = (module_name) -> 7 | try 8 | delete require.cache[require.resolve(module_name)] 9 | require(module_name) 10 | catch error 11 | log.error "Load module #{JSON.stringify module_name} failed" 12 | log.error JSON.stringify error 13 | 14 | 15 | class Dispatcher extends EventEmitter 16 | constructor: (@plugins=[], @robot) -> 17 | @listeners = [] 18 | @obj_listeners = [] 19 | @stop_funcs = [] 20 | @reload_plugin() 21 | 22 | dispatch: (params...)-> 23 | try 24 | for plugin in @listeners 25 | plugin(params...) 26 | for plugin in @obj_listeners 27 | [obj,method] = plugin 28 | obj[method](params...) 29 | catch error 30 | log.error error 31 | 32 | 33 | ### 34 | 针对 对象的方法 35 | 请传入 [obj,'methodname'] 36 | methodname 直接调用 methodname 会破坏内部变量 this.xxx 37 | ### 38 | add_listener: (listener)-> 39 | if listener instanceof Function 40 | @listeners.push listener 41 | else 42 | @obj_listeners.push listener 43 | 44 | # 注销插件 45 | stop_plugin: -> func(@robot) for func in @stop_funcs 46 | 47 | 48 | # 重新加载插件 49 | reload_plugin:-> 50 | @stop_plugin() 51 | @listeners = [] 52 | for plugin_name in @plugins 53 | log.debug "Loading Plugin #{plugin_name}" 54 | plugin = requireForce "../plugins/#{plugin_name}" 55 | if plugin 56 | if plugin instanceof Function 57 | @listeners.push plugin 58 | else 59 | # init , received, stop 函数 60 | @listeners.push plugin.received if plugin.received 61 | plugin.init(@robot) if plugin.init 62 | @stop_funcs.push plugin.stop if plugin.stop 63 | 64 | 65 | module.exports = Dispatcher -------------------------------------------------------------------------------- /src/encrypt.js: -------------------------------------------------------------------------------- 1 | var RSA = function() { 2 | function g(z, t) { 3 | return new ar(z, t) 4 | } 5 | function ah(aA, aB) { 6 | var t = ""; 7 | var z = 0; 8 | while (z + aB < aA.length) { 9 | t += aA.substring(z, z + aB) + "\n"; 10 | z += aB 11 | } 12 | return t + aA.substring(z, aA.length) 13 | } 14 | function r(t) { 15 | if (t < 16) { 16 | return "0" + t.toString(16) 17 | } else { 18 | return t.toString(16) 19 | } 20 | } 21 | function af(aB, aE) { 22 | if (aE < aB.length + 11) { 23 | console.log("Message too long for RSA"); 24 | return null 25 | } 26 | var aD = new Array(); 27 | var aA = aB.length - 1; 28 | while (aA >= 0 && aE > 0) { 29 | var aC = aB.charCodeAt(aA--); 30 | aD[--aE] = aC 31 | } 32 | aD[--aE] = 0; 33 | var z = new ad(); 34 | var t = new Array(); 35 | while (aE > 2) { 36 | t[0] = 0; 37 | while (t[0] == 0) { 38 | z.nextBytes(t) 39 | } 40 | aD[--aE] = t[0] 41 | } 42 | aD[--aE] = 2; 43 | aD[--aE] = 0; 44 | return new ar(aD) 45 | } 46 | function L() { 47 | this.n = null; 48 | this.e = 0; 49 | this.d = null; 50 | this.p = null; 51 | this.q = null; 52 | this.dmp1 = null; 53 | this.dmq1 = null; 54 | this.coeff = null 55 | } 56 | function o(z, t) { 57 | if (z != null && t != null && z.length > 0 && t.length > 0) { 58 | this.n = g(z, 16); 59 | this.e = parseInt(t, 16) 60 | } else { 61 | console.log("Invalid RSA public key") 62 | } 63 | } 64 | function W(t) { 65 | return t.modPowInt(this.e, this.n) 66 | } 67 | function p(aA) { 68 | var t = af(aA, (this.n.bitLength() + 7) >> 3); 69 | if (t == null) { 70 | return null 71 | } 72 | var aB = this.doPublic(t); 73 | if (aB == null) { 74 | return null 75 | } 76 | var z = aB.toString(16); 77 | if ((z.length & 1) == 0) { 78 | return z 79 | } else { 80 | return "0" + z 81 | } 82 | } 83 | L.prototype.doPublic = W; 84 | L.prototype.setPublic = o; 85 | L.prototype.encrypt = p; 86 | var aw; 87 | var ai = 244837814094590; 88 | var Z = ((ai & 16777215) == 15715070); 89 | function ar(z, t, aA) { 90 | if (z != null) { 91 | if ("number" == typeof z) { 92 | this.fromNumber(z, t, aA) 93 | } else { 94 | if (t == null && "string" != typeof z) { 95 | this.fromString(z, 256) 96 | } else { 97 | this.fromString(z, t) 98 | } 99 | } 100 | } 101 | } 102 | function h() { 103 | return new ar(null) 104 | } 105 | function b(aC, t, z, aB, aE, aD) { 106 | while (--aD >= 0) { 107 | var aA = t * this[aC++] + z[aB] + aE; 108 | aE = Math.floor(aA / 67108864); 109 | z[aB++] = aA & 67108863 110 | } 111 | return aE 112 | } 113 | function ay(aC, aH, aI, aB, aF, t) { 114 | var aE = aH & 32767, 115 | aG = aH >> 15; 116 | while (--t >= 0) { 117 | var aA = this[aC] & 32767; 118 | var aD = this[aC++] >> 15; 119 | var z = aG * aA + aD * aE; 120 | aA = aE * aA + ((z & 32767) << 15) + aI[aB] + (aF & 1073741823); 121 | aF = (aA >>> 30) + (z >>> 15) + aG * aD + (aF >>> 30); 122 | aI[aB++] = aA & 1073741823 123 | } 124 | return aF 125 | } 126 | function ax(aC, aH, aI, aB, aF, t) { 127 | var aE = aH & 16383, 128 | aG = aH >> 14; 129 | while (--t >= 0) { 130 | var aA = this[aC] & 16383; 131 | var aD = this[aC++] >> 14; 132 | var z = aG * aA + aD * aE; 133 | aA = aE * aA + ((z & 16383) << 14) + aI[aB] + aF; 134 | aF = (aA >> 28) + (z >> 14) + aG * aD; 135 | aI[aB++] = aA & 268435455 136 | } 137 | return aF 138 | } 139 | // if (Z && (navigator.appName == "Microsoft Internet Explorer")) { 140 | // ar.prototype.am = ay; 141 | // aw = 30 142 | // } else { 143 | // if (Z && (navigator.appName != "Netscape")) { 144 | // ar.prototype.am = b; 145 | // aw = 26 146 | // } else { 147 | // ar.prototype.am = ax; 148 | // aw = 28 149 | // } 150 | // } 151 | ar.prototype.am = ax; 152 | aw = 28 153 | 154 | ar.prototype.DB = aw; 155 | ar.prototype.DM = ((1 << aw) - 1); 156 | ar.prototype.DV = (1 << aw); 157 | var aa = 52; 158 | ar.prototype.FV = Math.pow(2, aa); 159 | ar.prototype.F1 = aa - aw; 160 | ar.prototype.F2 = 2 * aw - aa; 161 | var ae = "0123456789abcdefghijklmnopqrstuvwxyz"; 162 | var ag = new Array(); 163 | var ap, v; 164 | ap = "0".charCodeAt(0); 165 | for (v = 0; v <= 9; ++v) { 166 | ag[ap++] = v 167 | } 168 | ap = "a".charCodeAt(0); 169 | for (v = 10; v < 36; ++v) { 170 | ag[ap++] = v 171 | } 172 | ap = "A".charCodeAt(0); 173 | for (v = 10; v < 36; ++v) { 174 | ag[ap++] = v 175 | } 176 | function az(t) { 177 | return ae.charAt(t) 178 | } 179 | function A(z, t) { 180 | var aA = ag[z.charCodeAt(t)]; 181 | return (aA == null) ? -1 : aA 182 | } 183 | function Y(z) { 184 | for (var t = this.t - 1; t >= 0; --t) { 185 | z[t] = this[t] 186 | } 187 | z.t = this.t; 188 | z.s = this.s 189 | } 190 | function n(t) { 191 | this.t = 1; 192 | this.s = (t < 0) ? -1 : 0; 193 | if (t > 0) { 194 | this[0] = t 195 | } else { 196 | if (t < -1) { 197 | this[0] = t + DV 198 | } else { 199 | this.t = 0 200 | } 201 | } 202 | } 203 | function c(t) { 204 | var z = h(); 205 | z.fromInt(t); 206 | return z 207 | } 208 | function w(aE, z) { 209 | var aB; 210 | if (z == 16) { 211 | aB = 4 212 | } else { 213 | if (z == 8) { 214 | aB = 3 215 | } else { 216 | if (z == 256) { 217 | aB = 8 218 | } else { 219 | if (z == 2) { 220 | aB = 1 221 | } else { 222 | if (z == 32) { 223 | aB = 5 224 | } else { 225 | if (z == 4) { 226 | aB = 2 227 | } else { 228 | this.fromRadix(aE, z); 229 | return 230 | } 231 | } 232 | } 233 | } 234 | } 235 | } 236 | this.t = 0; 237 | this.s = 0; 238 | var aD = aE.length, 239 | aA = false, 240 | aC = 0; 241 | while (--aD >= 0) { 242 | var t = (aB == 8) ? aE[aD] & 255 : A(aE, aD); 243 | if (t < 0) { 244 | if (aE.charAt(aD) == "-") { 245 | aA = true 246 | } 247 | continue 248 | } 249 | aA = false; 250 | if (aC == 0) { 251 | this[this.t++] = t 252 | } else { 253 | if (aC + aB > this.DB) { 254 | this[this.t - 1] |= (t & ((1 << (this.DB - aC)) - 1)) << aC; 255 | this[this.t++] = (t >> (this.DB - aC)) 256 | } else { 257 | this[this.t - 1] |= t << aC 258 | } 259 | } 260 | aC += aB; 261 | if (aC >= this.DB) { 262 | aC -= this.DB 263 | } 264 | } 265 | if (aB == 8 && (aE[0] & 128) != 0) { 266 | this.s = -1; 267 | if (aC > 0) { 268 | this[this.t - 1] |= ((1 << (this.DB - aC)) - 1) << aC 269 | } 270 | } 271 | this.clamp(); 272 | if (aA) { 273 | ar.ZERO.subTo(this, this) 274 | } 275 | } 276 | function O() { 277 | var t = this.s & this.DM; 278 | while (this.t > 0 && this[this.t - 1] == t) {--this.t 279 | } 280 | } 281 | function q(z) { 282 | if (this.s < 0) { 283 | return "-" + this.negate().toString(z) 284 | } 285 | var aA; 286 | if (z == 16) { 287 | aA = 4 288 | } else { 289 | if (z == 8) { 290 | aA = 3 291 | } else { 292 | if (z == 2) { 293 | aA = 1 294 | } else { 295 | if (z == 32) { 296 | aA = 5 297 | } else { 298 | if (z == 4) { 299 | aA = 2 300 | } else { 301 | return this.toRadix(z) 302 | } 303 | } 304 | } 305 | } 306 | } 307 | var aC = (1 << aA) - 1, 308 | aF, 309 | t = false, 310 | aD = "", 311 | aB = this.t; 312 | var aE = this.DB - (aB * this.DB) % aA; 313 | if (aB-->0) { 314 | if (aE < this.DB && (aF = this[aB] >> aE) > 0) { 315 | t = true; 316 | aD = az(aF) 317 | } 318 | while (aB >= 0) { 319 | if (aE < aA) { 320 | aF = (this[aB] & ((1 << aE) - 1)) << (aA - aE); 321 | aF |= this[--aB] >> (aE += this.DB - aA) 322 | } else { 323 | aF = (this[aB] >> (aE -= aA)) & aC; 324 | if (aE <= 0) { 325 | aE += this.DB; --aB 326 | } 327 | } 328 | if (aF > 0) { 329 | t = true 330 | } 331 | if (t) { 332 | aD += az(aF) 333 | } 334 | } 335 | } 336 | return t ? aD: "0" 337 | } 338 | function R() { 339 | var t = h(); 340 | ar.ZERO.subTo(this, t); 341 | return t 342 | } 343 | function al() { 344 | return (this.s < 0) ? this.negate() : this 345 | } 346 | function G(t) { 347 | var aA = this.s - t.s; 348 | if (aA != 0) { 349 | return aA 350 | } 351 | var z = this.t; 352 | aA = z - t.t; 353 | if (aA != 0) { 354 | return aA 355 | } 356 | while (--z >= 0) { 357 | if ((aA = this[z] - t[z]) != 0) { 358 | return aA 359 | } 360 | } 361 | return 0 362 | } 363 | function j(z) { 364 | var aB = 1, 365 | aA; 366 | if ((aA = z >>> 16) != 0) { 367 | z = aA; 368 | aB += 16 369 | } 370 | if ((aA = z >> 8) != 0) { 371 | z = aA; 372 | aB += 8 373 | } 374 | if ((aA = z >> 4) != 0) { 375 | z = aA; 376 | aB += 4 377 | } 378 | if ((aA = z >> 2) != 0) { 379 | z = aA; 380 | aB += 2 381 | } 382 | if ((aA = z >> 1) != 0) { 383 | z = aA; 384 | aB += 1 385 | } 386 | return aB 387 | } 388 | function u() { 389 | if (this.t <= 0) { 390 | return 0 391 | } 392 | return this.DB * (this.t - 1) + j(this[this.t - 1] ^ (this.s & this.DM)) 393 | } 394 | function aq(aA, z) { 395 | var t; 396 | for (t = this.t - 1; t >= 0; --t) { 397 | z[t + aA] = this[t] 398 | } 399 | for (t = aA - 1; t >= 0; --t) { 400 | z[t] = 0 401 | } 402 | z.t = this.t + aA; 403 | z.s = this.s 404 | } 405 | function X(aA, z) { 406 | for (var t = aA; t < this.t; ++t) { 407 | z[t - aA] = this[t] 408 | } 409 | z.t = Math.max(this.t - aA, 0); 410 | z.s = this.s 411 | } 412 | function s(aF, aB) { 413 | var z = aF % this.DB; 414 | var t = this.DB - z; 415 | var aD = (1 << t) - 1; 416 | var aC = Math.floor(aF / this.DB), 417 | aE = (this.s << z) & this.DM, 418 | aA; 419 | for (aA = this.t - 1; aA >= 0; --aA) { 420 | aB[aA + aC + 1] = (this[aA] >> t) | aE; 421 | aE = (this[aA] & aD) << z 422 | } 423 | for (aA = aC - 1; aA >= 0; --aA) { 424 | aB[aA] = 0 425 | } 426 | aB[aC] = aE; 427 | aB.t = this.t + aC + 1; 428 | aB.s = this.s; 429 | aB.clamp() 430 | } 431 | function l(aE, aB) { 432 | aB.s = this.s; 433 | var aC = Math.floor(aE / this.DB); 434 | if (aC >= this.t) { 435 | aB.t = 0; 436 | return 437 | } 438 | var z = aE % this.DB; 439 | var t = this.DB - z; 440 | var aD = (1 << z) - 1; 441 | aB[0] = this[aC] >> z; 442 | for (var aA = aC + 1; aA < this.t; ++aA) { 443 | aB[aA - aC - 1] |= (this[aA] & aD) << t; 444 | aB[aA - aC] = this[aA] >> z 445 | } 446 | if (z > 0) { 447 | aB[this.t - aC - 1] |= (this.s & aD) << t 448 | } 449 | aB.t = this.t - aC; 450 | aB.clamp() 451 | } 452 | function ab(z, aB) { 453 | var aA = 0, 454 | aC = 0, 455 | t = Math.min(z.t, this.t); 456 | while (aA < t) { 457 | aC += this[aA] - z[aA]; 458 | aB[aA++] = aC & this.DM; 459 | aC >>= this.DB 460 | } 461 | if (z.t < this.t) { 462 | aC -= z.s; 463 | while (aA < this.t) { 464 | aC += this[aA]; 465 | aB[aA++] = aC & this.DM; 466 | aC >>= this.DB 467 | } 468 | aC += this.s 469 | } else { 470 | aC += this.s; 471 | while (aA < z.t) { 472 | aC -= z[aA]; 473 | aB[aA++] = aC & this.DM; 474 | aC >>= this.DB 475 | } 476 | aC -= z.s 477 | } 478 | aB.s = (aC < 0) ? -1 : 0; 479 | if (aC < -1) { 480 | aB[aA++] = this.DV + aC 481 | } else { 482 | if (aC > 0) { 483 | aB[aA++] = aC 484 | } 485 | } 486 | aB.t = aA; 487 | aB.clamp() 488 | } 489 | function D(z, aB) { 490 | var t = this.abs(), 491 | aC = z.abs(); 492 | var aA = t.t; 493 | aB.t = aA + aC.t; 494 | while (--aA >= 0) { 495 | aB[aA] = 0 496 | } 497 | for (aA = 0; aA < aC.t; ++aA) { 498 | aB[aA + t.t] = t.am(0, aC[aA], aB, aA, 0, t.t) 499 | } 500 | aB.s = 0; 501 | aB.clamp(); 502 | if (this.s != z.s) { 503 | ar.ZERO.subTo(aB, aB) 504 | } 505 | } 506 | function Q(aA) { 507 | var t = this.abs(); 508 | var z = aA.t = 2 * t.t; 509 | while (--z >= 0) { 510 | aA[z] = 0 511 | } 512 | for (z = 0; z < t.t - 1; ++z) { 513 | var aB = t.am(z, t[z], aA, 2 * z, 0, 1); 514 | if ((aA[z + t.t] += t.am(z + 1, 2 * t[z], aA, 2 * z + 1, aB, t.t - z - 1)) >= t.DV) { 515 | aA[z + t.t] -= t.DV; 516 | aA[z + t.t + 1] = 1 517 | } 518 | } 519 | if (aA.t > 0) { 520 | aA[aA.t - 1] += t.am(z, t[z], aA, 2 * z, 0, 1) 521 | } 522 | aA.s = 0; 523 | aA.clamp() 524 | } 525 | function E(aI, aF, aE) { 526 | var aO = aI.abs(); 527 | if (aO.t <= 0) { 528 | return 529 | } 530 | var aG = this.abs(); 531 | if (aG.t < aO.t) { 532 | if (aF != null) { 533 | aF.fromInt(0) 534 | } 535 | if (aE != null) { 536 | this.copyTo(aE) 537 | } 538 | return 539 | } 540 | if (aE == null) { 541 | aE = h() 542 | } 543 | var aC = h(), 544 | z = this.s, 545 | aH = aI.s; 546 | var aN = this.DB - j(aO[aO.t - 1]); 547 | if (aN > 0) { 548 | aO.lShiftTo(aN, aC); 549 | aG.lShiftTo(aN, aE) 550 | } else { 551 | aO.copyTo(aC); 552 | aG.copyTo(aE) 553 | } 554 | var aK = aC.t; 555 | var aA = aC[aK - 1]; 556 | if (aA == 0) { 557 | return 558 | } 559 | var aJ = aA * (1 << this.F1) + ((aK > 1) ? aC[aK - 2] >> this.F2: 0); 560 | var aR = this.FV / aJ, 561 | aQ = (1 << this.F1) / aJ, 562 | aP = 1 << this.F2; 563 | var aM = aE.t, 564 | aL = aM - aK, 565 | aD = (aF == null) ? h() : aF; 566 | aC.dlShiftTo(aL, aD); 567 | if (aE.compareTo(aD) >= 0) { 568 | aE[aE.t++] = 1; 569 | aE.subTo(aD, aE) 570 | } 571 | ar.ONE.dlShiftTo(aK, aD); 572 | aD.subTo(aC, aC); 573 | while (aC.t < aK) { 574 | aC[aC.t++] = 0 575 | } 576 | while (--aL >= 0) { 577 | var aB = (aE[--aM] == aA) ? this.DM: Math.floor(aE[aM] * aR + (aE[aM - 1] + aP) * aQ); 578 | if ((aE[aM] += aC.am(0, aB, aE, aL, 0, aK)) < aB) { 579 | aC.dlShiftTo(aL, aD); 580 | aE.subTo(aD, aE); 581 | while (aE[aM] < --aB) { 582 | aE.subTo(aD, aE) 583 | } 584 | } 585 | } 586 | if (aF != null) { 587 | aE.drShiftTo(aK, aF); 588 | if (z != aH) { 589 | ar.ZERO.subTo(aF, aF) 590 | } 591 | } 592 | aE.t = aK; 593 | aE.clamp(); 594 | if (aN > 0) { 595 | aE.rShiftTo(aN, aE) 596 | } 597 | if (z < 0) { 598 | ar.ZERO.subTo(aE, aE) 599 | } 600 | } 601 | function N(t) { 602 | var z = h(); 603 | this.abs().divRemTo(t, null, z); 604 | if (this.s < 0 && z.compareTo(ar.ZERO) > 0) { 605 | t.subTo(z, z) 606 | } 607 | return z 608 | } 609 | function K(t) { 610 | this.m = t 611 | } 612 | function V(t) { 613 | if (t.s < 0 || t.compareTo(this.m) >= 0) { 614 | return t.mod(this.m) 615 | } else { 616 | return t 617 | } 618 | } 619 | function ak(t) { 620 | return t 621 | } 622 | function J(t) { 623 | t.divRemTo(this.m, null, t) 624 | } 625 | function H(t, aA, z) { 626 | t.multiplyTo(aA, z); 627 | this.reduce(z) 628 | } 629 | function au(t, z) { 630 | t.squareTo(z); 631 | this.reduce(z) 632 | } 633 | K.prototype.convert = V; 634 | K.prototype.revert = ak; 635 | K.prototype.reduce = J; 636 | K.prototype.mulTo = H; 637 | K.prototype.sqrTo = au; 638 | function B() { 639 | if (this.t < 1) { 640 | return 0 641 | } 642 | var t = this[0]; 643 | if ((t & 1) == 0) { 644 | return 0 645 | } 646 | var z = t & 3; 647 | z = (z * (2 - (t & 15) * z)) & 15; 648 | z = (z * (2 - (t & 255) * z)) & 255; 649 | z = (z * (2 - (((t & 65535) * z) & 65535))) & 65535; 650 | z = (z * (2 - t * z % this.DV)) % this.DV; 651 | return (z > 0) ? this.DV - z: -z 652 | } 653 | function f(t) { 654 | this.m = t; 655 | this.mp = t.invDigit(); 656 | this.mpl = this.mp & 32767; 657 | this.mph = this.mp >> 15; 658 | this.um = (1 << (t.DB - 15)) - 1; 659 | this.mt2 = 2 * t.t 660 | } 661 | function aj(t) { 662 | var z = h(); 663 | t.abs().dlShiftTo(this.m.t, z); 664 | z.divRemTo(this.m, null, z); 665 | if (t.s < 0 && z.compareTo(ar.ZERO) > 0) { 666 | this.m.subTo(z, z) 667 | } 668 | return z 669 | } 670 | function at(t) { 671 | var z = h(); 672 | t.copyTo(z); 673 | this.reduce(z); 674 | return z 675 | } 676 | function P(t) { 677 | while (t.t <= this.mt2) { 678 | t[t.t++] = 0 679 | } 680 | for (var aA = 0; aA < this.m.t; ++aA) { 681 | var z = t[aA] & 32767; 682 | var aB = (z * this.mpl + (((z * this.mph + (t[aA] >> 15) * this.mpl) & this.um) << 15)) & t.DM; 683 | z = aA + this.m.t; 684 | t[z] += this.m.am(0, aB, t, aA, 0, this.m.t); 685 | while (t[z] >= t.DV) { 686 | t[z] -= t.DV; 687 | t[++z]++ 688 | } 689 | } 690 | t.clamp(); 691 | t.drShiftTo(this.m.t, t); 692 | if (t.compareTo(this.m) >= 0) { 693 | t.subTo(this.m, t) 694 | } 695 | } 696 | function am(t, z) { 697 | t.squareTo(z); 698 | this.reduce(z) 699 | } 700 | function y(t, aA, z) { 701 | t.multiplyTo(aA, z); 702 | this.reduce(z) 703 | } 704 | f.prototype.convert = aj; 705 | f.prototype.revert = at; 706 | f.prototype.reduce = P; 707 | f.prototype.mulTo = y; 708 | f.prototype.sqrTo = am; 709 | function i() { 710 | return ((this.t > 0) ? (this[0] & 1) : this.s) == 0 711 | } 712 | function x(aF, aG) { 713 | if (aF > 4294967295 || aF < 1) { 714 | return ar.ONE 715 | } 716 | var aE = h(), 717 | aA = h(), 718 | aD = aG.convert(this), 719 | aC = j(aF) - 1; 720 | aD.copyTo(aE); 721 | while (--aC >= 0) { 722 | aG.sqrTo(aE, aA); 723 | if ((aF & (1 << aC)) > 0) { 724 | aG.mulTo(aA, aD, aE) 725 | } else { 726 | var aB = aE; 727 | aE = aA; 728 | aA = aB 729 | } 730 | } 731 | return aG.revert(aE) 732 | } 733 | function an(aA, t) { 734 | var aB; 735 | if (aA < 256 || t.isEven()) { 736 | aB = new K(t) 737 | } else { 738 | aB = new f(t) 739 | } 740 | return this.exp(aA, aB) 741 | } 742 | ar.prototype.copyTo = Y; 743 | ar.prototype.fromInt = n; 744 | ar.prototype.fromString = w; 745 | ar.prototype.clamp = O; 746 | ar.prototype.dlShiftTo = aq; 747 | ar.prototype.drShiftTo = X; 748 | ar.prototype.lShiftTo = s; 749 | ar.prototype.rShiftTo = l; 750 | ar.prototype.subTo = ab; 751 | ar.prototype.multiplyTo = D; 752 | ar.prototype.squareTo = Q; 753 | ar.prototype.divRemTo = E; 754 | ar.prototype.invDigit = B; 755 | ar.prototype.isEven = i; 756 | ar.prototype.exp = x; 757 | ar.prototype.toString = q; 758 | ar.prototype.negate = R; 759 | ar.prototype.abs = al; 760 | ar.prototype.compareTo = G; 761 | ar.prototype.bitLength = u; 762 | ar.prototype.mod = N; 763 | ar.prototype.modPowInt = an; 764 | ar.ZERO = c(0); 765 | ar.ONE = c(1); 766 | var m; 767 | var U; 768 | var ac; 769 | function d(t) { 770 | U[ac++] ^= t & 255; 771 | U[ac++] ^= (t >> 8) & 255; 772 | U[ac++] ^= (t >> 16) & 255; 773 | U[ac++] ^= (t >> 24) & 255; 774 | if (ac >= M) { 775 | ac -= M 776 | } 777 | } 778 | function T() { 779 | d(new Date().getTime()) 780 | } 781 | if (U == null) { 782 | U = new Array(); 783 | ac = 0; 784 | var I; 785 | // if (navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto && window.crypto.random) { 786 | // var F = window.crypto.random(32); 787 | // for (I = 0; I < F.length; ++I) { 788 | // U[ac++] = F.charCodeAt(I) & 255 789 | // } 790 | // } 791 | while (ac < M) { 792 | I = Math.floor(65536 * Math.random()); 793 | U[ac++] = I >>> 8; 794 | U[ac++] = I & 255 795 | } 796 | ac = 0; 797 | T() 798 | } 799 | function C() { 800 | if (m == null) { 801 | T(); 802 | m = ao(); 803 | m.init(U); 804 | for (ac = 0; ac < U.length; ++ac) { 805 | U[ac] = 0 806 | } 807 | ac = 0 808 | } 809 | return m.next() 810 | } 811 | function av(z) { 812 | var t; 813 | for (t = 0; t < z.length; ++t) { 814 | z[t] = C() 815 | } 816 | } 817 | function ad() {} 818 | ad.prototype.nextBytes = av; 819 | function k() { 820 | this.i = 0; 821 | this.j = 0; 822 | this.S = new Array() 823 | } 824 | function e(aC) { 825 | var aB, z, aA; 826 | for (aB = 0; aB < 256; ++aB) { 827 | this.S[aB] = aB 828 | } 829 | z = 0; 830 | for (aB = 0; aB < 256; ++aB) { 831 | z = (z + this.S[aB] + aC[aB % aC.length]) & 255; 832 | aA = this.S[aB]; 833 | this.S[aB] = this.S[z]; 834 | this.S[z] = aA 835 | } 836 | this.i = 0; 837 | this.j = 0 838 | } 839 | function a() { 840 | var z; 841 | this.i = (this.i + 1) & 255; 842 | this.j = (this.j + this.S[this.i]) & 255; 843 | z = this.S[this.i]; 844 | this.S[this.i] = this.S[this.j]; 845 | this.S[this.j] = z; 846 | return this.S[(z + this.S[this.i]) & 255] 847 | } 848 | k.prototype.init = e; 849 | k.prototype.next = a; 850 | function ao() { 851 | return new k() 852 | } 853 | var M = 256; 854 | function S(aB, aA, z) { 855 | aA = "F20CE00BAE5361F8FA3AE9CEFA495362FF7DA1BA628F64A347F0A8C012BF0B254A30CD92ABFFE7A6EE0DC424CB6166F8819EFA5BCCB20EDFB4AD02E412CCF579B1CA711D55B8B0B3AEB60153D5E0693A2A86F3167D7847A0CB8B00004716A9095D9BADC977CBB804DBDCBA6029A9710869A453F27DFDDF83C016D928B3CBF4C7"; 856 | z = "3"; 857 | var t = new L(); 858 | t.setPublic(aA, z); 859 | return t.encrypt(aB) 860 | } 861 | return { 862 | rsa_encrypt: S 863 | } 864 | } (); 865 | 866 | (function(r) { 867 | var s = "", 868 | a = 0, 869 | g = [], 870 | x = [], 871 | y = 0, 872 | u = 0, 873 | m = [], 874 | t = [], 875 | n = true; 876 | function e() { 877 | return Math.round(Math.random() * 4294967295) 878 | } 879 | function i(C, D, z) { 880 | if (!z || z > 4) { 881 | z = 4 882 | } 883 | var A = 0; 884 | for (var B = D; B < D + z; B++) { 885 | A <<= 8; 886 | A |= C[B] 887 | } 888 | return (A & 4294967295) >>> 0 889 | } 890 | function b(A, B, z) { 891 | A[B + 3] = (z >> 0) & 255; 892 | A[B + 2] = (z >> 8) & 255; 893 | A[B + 1] = (z >> 16) & 255; 894 | A[B + 0] = (z >> 24) & 255 895 | } 896 | function w(C) { 897 | if (!C) { 898 | return "" 899 | } 900 | var z = ""; 901 | for (var A = 0; A < C.length; A++) { 902 | var B = Number(C[A]).toString(16); 903 | if (B.length == 1) { 904 | B = "0" + B 905 | } 906 | z += B 907 | } 908 | return z 909 | } 910 | function v(A) { 911 | var B = ""; 912 | for (var z = 0; z < A.length; z += 2) { 913 | B += String.fromCharCode(parseInt(A.substr(z, 2), 16)) 914 | } 915 | return B 916 | } 917 | function c(C, z) { 918 | if (!C) { 919 | return "" 920 | } 921 | if (z) { 922 | C = k(C) 923 | } 924 | var B = []; 925 | for (var A = 0; A < C.length; A++) { 926 | B[A] = C.charCodeAt(A) 927 | } 928 | return w(B) 929 | } 930 | function k(C) { 931 | var B, D, A = [], 932 | z = C.length; 933 | for (B = 0; B < z; B++) { 934 | D = C.charCodeAt(B); 935 | if (D > 0 && D <= 127) { 936 | A.push(C.charAt(B)) 937 | } else { 938 | if (D >= 128 && D <= 2047) { 939 | A.push(String.fromCharCode(192 | ((D >> 6) & 31)), String.fromCharCode(128 | (D & 63))) 940 | } else { 941 | if (D >= 2048 && D <= 65535) { 942 | A.push(String.fromCharCode(224 | ((D >> 12) & 15)), String.fromCharCode(128 | ((D >> 6) & 63)), String.fromCharCode(128 | (D & 63))) 943 | } 944 | } 945 | } 946 | } 947 | return A.join("") 948 | } 949 | function h(B) { 950 | g = new Array(8); 951 | x = new Array(8); 952 | y = u = 0; 953 | n = true; 954 | a = 0; 955 | var z = B.length; 956 | var C = 0; 957 | a = (z + 10) % 8; 958 | if (a != 0) { 959 | a = 8 - a 960 | } 961 | m = new Array(z + a + 10); 962 | g[0] = ((e() & 248) | a) & 255; 963 | for (var A = 1; A <= a; A++) { 964 | g[A] = e() & 255 965 | } 966 | a++; 967 | for (var A = 0; A < 8; A++) { 968 | x[A] = 0 969 | } 970 | C = 1; 971 | while (C <= 2) { 972 | if (a < 8) { 973 | g[a++] = e() & 255; 974 | C++ 975 | } 976 | if (a == 8) { 977 | p() 978 | } 979 | } 980 | var A = 0; 981 | while (z > 0) { 982 | if (a < 8) { 983 | g[a++] = B[A++]; 984 | z-- 985 | } 986 | if (a == 8) { 987 | p() 988 | } 989 | } 990 | C = 1; 991 | while (C <= 7) { 992 | if (a < 8) { 993 | g[a++] = 0; 994 | C++ 995 | } 996 | if (a == 8) { 997 | p() 998 | } 999 | } 1000 | return m 1001 | } 1002 | function q(D) { 1003 | var C = 0; 1004 | var A = new Array(8); 1005 | var z = D.length; 1006 | t = D; 1007 | if (z % 8 != 0 || z < 16) { 1008 | return null 1009 | } 1010 | x = l(D); 1011 | a = x[0] & 7; 1012 | C = z - a - 10; 1013 | if (C < 0) { 1014 | return null 1015 | } 1016 | for (var B = 0; B < A.length; B++) { 1017 | A[B] = 0 1018 | } 1019 | m = new Array(C); 1020 | u = 0; 1021 | y = 8; 1022 | a++; 1023 | var E = 1; 1024 | while (E <= 2) { 1025 | if (a < 8) { 1026 | a++; 1027 | E++ 1028 | } 1029 | if (a == 8) { 1030 | A = D; 1031 | if (!f()) { 1032 | return null 1033 | } 1034 | } 1035 | } 1036 | var B = 0; 1037 | while (C != 0) { 1038 | if (a < 8) { 1039 | m[B] = (A[u + a] ^ x[a]) & 255; 1040 | B++; 1041 | C--; 1042 | a++ 1043 | } 1044 | if (a == 8) { 1045 | A = D; 1046 | u = y - 8; 1047 | if (!f()) { 1048 | return null 1049 | } 1050 | } 1051 | } 1052 | for (E = 1; E < 8; E++) { 1053 | if (a < 8) { 1054 | if ((A[u + a] ^ x[a]) != 0) { 1055 | return null 1056 | } 1057 | a++ 1058 | } 1059 | if (a == 8) { 1060 | A = D; 1061 | u = y; 1062 | if (!f()) { 1063 | return null 1064 | } 1065 | } 1066 | } 1067 | return m 1068 | } 1069 | function p() { 1070 | for (var z = 0; z < 8; z++) { 1071 | if (n) { 1072 | g[z] ^= x[z] 1073 | } else { 1074 | g[z] ^= m[u + z] 1075 | } 1076 | } 1077 | var A = j(g); 1078 | for (var z = 0; z < 8; z++) { 1079 | m[y + z] = A[z] ^ x[z]; 1080 | x[z] = g[z] 1081 | } 1082 | u = y; 1083 | y += 8; 1084 | a = 0; 1085 | n = false 1086 | } 1087 | function j(A) { 1088 | var B = 16; 1089 | var G = i(A, 0, 4); 1090 | var F = i(A, 4, 4); 1091 | var I = i(s, 0, 4); 1092 | var H = i(s, 4, 4); 1093 | var E = i(s, 8, 4); 1094 | var D = i(s, 12, 4); 1095 | var C = 0; 1096 | var J = 2654435769 >>> 0; 1097 | while (B-->0) { 1098 | C += J; 1099 | C = (C & 4294967295) >>> 0; 1100 | G += ((F << 4) + I) ^ (F + C) ^ ((F >>> 5) + H); 1101 | G = (G & 4294967295) >>> 0; 1102 | F += ((G << 4) + E) ^ (G + C) ^ ((G >>> 5) + D); 1103 | F = (F & 4294967295) >>> 0 1104 | } 1105 | var K = new Array(8); 1106 | b(K, 0, G); 1107 | b(K, 4, F); 1108 | return K 1109 | } 1110 | function l(A) { 1111 | var B = 16; 1112 | var G = i(A, 0, 4); 1113 | var F = i(A, 4, 4); 1114 | var I = i(s, 0, 4); 1115 | var H = i(s, 4, 4); 1116 | var E = i(s, 8, 4); 1117 | var D = i(s, 12, 4); 1118 | var C = 3816266640 >>> 0; 1119 | var J = 2654435769 >>> 0; 1120 | while (B-->0) { 1121 | F -= ((G << 4) + E) ^ (G + C) ^ ((G >>> 5) + D); 1122 | F = (F & 4294967295) >>> 0; 1123 | G -= ((F << 4) + I) ^ (F + C) ^ ((F >>> 5) + H); 1124 | G = (G & 4294967295) >>> 0; 1125 | C -= J; 1126 | C = (C & 4294967295) >>> 0 1127 | } 1128 | var K = new Array(8); 1129 | b(K, 0, G); 1130 | b(K, 4, F); 1131 | return K 1132 | } 1133 | function f() { 1134 | var z = t.length; 1135 | for (var A = 0; A < 8; A++) { 1136 | x[A] ^= t[y + A] 1137 | } 1138 | x = l(x); 1139 | y += 8; 1140 | a = 0; 1141 | return true 1142 | } 1143 | 1144 | // 将字符串转换为int数组 1145 | // D:字符串 1146 | // C:字符串是否为非16进制字符串 1147 | function o(D, C) { 1148 | var B = []; 1149 | if (C) { 1150 | for (var A = 0; A < D.length; A++) { 1151 | B[A] = D.charCodeAt(A) & 255 1152 | } 1153 | } else { 1154 | var z = 0; 1155 | for (var A = 0; A < D.length; A += 2) { 1156 | B[z++] = parseInt(D.substr(A, 2), 16) 1157 | } 1158 | } 1159 | return B 1160 | } 1161 | r.TEA = { 1162 | encrypt: function(C, B) { 1163 | var A = o(C, B); 1164 | var z = h(A); 1165 | return w(z) 1166 | }, 1167 | 1168 | enAsBase64: function(E, D) { 1169 | var C = o(E, D); 1170 | var B = h(C); 1171 | var z = ""; 1172 | for (var A = 0; A < B.length; A++) { 1173 | z += String.fromCharCode(B[A]) 1174 | } 1175 | return btoa(z) 1176 | }, 1177 | decrypt: function(B) { 1178 | var A = o(B, false); 1179 | var z = q(A); 1180 | return w(z) 1181 | }, 1182 | initkey: function(z, A) { 1183 | s = o(z, A) 1184 | }, 1185 | bytesToStr: v, 1186 | strToBytes: c, 1187 | bytesInStr: w, 1188 | dataFromStr: o 1189 | }; 1190 | var d = {}; 1191 | d.PADCHAR = "="; 1192 | d.ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 1193 | d.getbyte = function(B, A) { 1194 | var z = B.charCodeAt(A); 1195 | if (z > 255) { 1196 | throw "INVALID_CHARACTER_ERR: DOM Exception 5" 1197 | } 1198 | return z 1199 | }; 1200 | d.encode = function(D) { 1201 | if (arguments.length != 1) { 1202 | throw "SyntaxError: Not enough arguments" 1203 | } 1204 | var A = d.PADCHAR; 1205 | var F = d.ALPHA; 1206 | var E = d.getbyte; 1207 | var C, G; 1208 | var z = []; 1209 | D = "" + D; 1210 | var B = D.length - D.length % 3; 1211 | if (D.length == 0) { 1212 | return D 1213 | } 1214 | for (C = 0; C < B; C += 3) { 1215 | G = (E(D, C) << 16) | (E(D, C + 1) << 8) | E(D, C + 2); 1216 | z.push(F.charAt(G >> 18)); 1217 | z.push(F.charAt((G >> 12) & 63)); 1218 | z.push(F.charAt((G >> 6) & 63)); 1219 | z.push(F.charAt(G & 63)) 1220 | } 1221 | switch (D.length - B) { 1222 | case 1: 1223 | G = E(D, C) << 16; 1224 | z.push(F.charAt(G >> 18) + F.charAt((G >> 12) & 63) + A + A); 1225 | break; 1226 | case 2: 1227 | G = (E(D, C) << 16) | (E(D, C + 1) << 8); 1228 | z.push(F.charAt(G >> 18) + F.charAt((G >> 12) & 63) + F.charAt((G >> 6) & 63) + A); 1229 | break 1230 | } 1231 | return z.join("") 1232 | }; 1233 | if (!global.btoa) { 1234 | global.btoa = d.encode 1235 | } 1236 | })(global); 1237 | 1238 | var Encryption = function() { 1239 | var hexcase = 1; 1240 | var b64pad = ""; 1241 | var chrsz = 8; 1242 | var mode = 32; 1243 | function md5(s) { 1244 | return hex_md5(s) 1245 | } 1246 | function hex_md5(s) { 1247 | return binl2hex(core_md5(str2binl(s), s.length * chrsz)) 1248 | } 1249 | function str_md5(s) { 1250 | return binl2str(core_md5(str2binl(s), s.length * chrsz)) 1251 | } 1252 | function hex_hmac_md5(key, data) { 1253 | return binl2hex(core_hmac_md5(key, data)) 1254 | } 1255 | function b64_hmac_md5(key, data) { 1256 | return binl2b64(core_hmac_md5(key, data)) 1257 | } 1258 | function str_hmac_md5(key, data) { 1259 | return binl2str(core_hmac_md5(key, data)) 1260 | } 1261 | function core_md5(x, len) { 1262 | x[len >> 5] |= 128 << ((len) % 32); 1263 | x[(((len + 64) >>> 9) << 4) + 14] = len; 1264 | var a = 1732584193; 1265 | var b = -271733879; 1266 | var c = -1732584194; 1267 | var d = 271733878; 1268 | for (var i = 0; i < x.length; i += 16) { 1269 | var olda = a; 1270 | var oldb = b; 1271 | var oldc = c; 1272 | var oldd = d; 1273 | a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936); 1274 | d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586); 1275 | c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819); 1276 | b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330); 1277 | a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897); 1278 | d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); 1279 | c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341); 1280 | b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983); 1281 | a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); 1282 | d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417); 1283 | c = md5_ff(c, d, a, b, x[i + 10], 17, -42063); 1284 | b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162); 1285 | a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); 1286 | d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101); 1287 | c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290); 1288 | b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); 1289 | a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510); 1290 | d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632); 1291 | c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713); 1292 | b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302); 1293 | a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691); 1294 | d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083); 1295 | c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335); 1296 | b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848); 1297 | a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438); 1298 | d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690); 1299 | c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961); 1300 | b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); 1301 | a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467); 1302 | d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784); 1303 | c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); 1304 | b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734); 1305 | a = md5_hh(a, b, c, d, x[i + 5], 4, -378558); 1306 | d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463); 1307 | c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); 1308 | b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556); 1309 | a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060); 1310 | d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); 1311 | c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632); 1312 | b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640); 1313 | a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174); 1314 | d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222); 1315 | c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979); 1316 | b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189); 1317 | a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487); 1318 | d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835); 1319 | c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520); 1320 | b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651); 1321 | a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844); 1322 | d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); 1323 | c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905); 1324 | b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055); 1325 | a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); 1326 | d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606); 1327 | c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523); 1328 | b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799); 1329 | a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); 1330 | d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744); 1331 | c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380); 1332 | b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); 1333 | a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070); 1334 | d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379); 1335 | c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259); 1336 | b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551); 1337 | a = safe_add(a, olda); 1338 | b = safe_add(b, oldb); 1339 | c = safe_add(c, oldc); 1340 | d = safe_add(d, oldd) 1341 | } 1342 | if (mode == 16) { 1343 | return Array(b, c) 1344 | } else { 1345 | return Array(a, b, c, d) 1346 | } 1347 | } 1348 | function md5_cmn(q, a, b, x, s, t) { 1349 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b) 1350 | } 1351 | function md5_ff(a, b, c, d, x, s, t) { 1352 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t) 1353 | } 1354 | function md5_gg(a, b, c, d, x, s, t) { 1355 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t) 1356 | } 1357 | function md5_hh(a, b, c, d, x, s, t) { 1358 | return md5_cmn(b ^ c ^ d, a, b, x, s, t) 1359 | } 1360 | function md5_ii(a, b, c, d, x, s, t) { 1361 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t) 1362 | } 1363 | function core_hmac_md5(key, data) { 1364 | var bkey = str2binl(key); 1365 | if (bkey.length > 16) { 1366 | bkey = core_md5(bkey, key.length * chrsz) 1367 | } 1368 | var ipad = Array(16), 1369 | opad = Array(16); 1370 | for (var i = 0; i < 16; i++) { 1371 | ipad[i] = bkey[i] ^ 909522486; 1372 | opad[i] = bkey[i] ^ 1549556828 1373 | } 1374 | var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); 1375 | return core_md5(opad.concat(hash), 512 + 128) 1376 | } 1377 | function safe_add(x, y) { 1378 | var lsw = (x & 65535) + (y & 65535); 1379 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 1380 | return (msw << 16) | (lsw & 65535) 1381 | } 1382 | function bit_rol(num, cnt) { 1383 | return (num << cnt) | (num >>> (32 - cnt)) 1384 | } 1385 | function str2binl(str) { 1386 | var bin = Array(); 1387 | var mask = (1 << chrsz) - 1; 1388 | for (var i = 0; i < str.length * chrsz; i += chrsz) { 1389 | bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32) 1390 | } 1391 | return bin 1392 | } 1393 | function binl2str(bin) { 1394 | var str = ""; 1395 | var mask = (1 << chrsz) - 1; 1396 | for (var i = 0; i < bin.length * 32; i += chrsz) { 1397 | str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask) 1398 | } 1399 | return str 1400 | } 1401 | function binl2hex(binarray) { 1402 | var hex_tab = hexcase ? "0123456789ABCDEF": "0123456789abcdef"; 1403 | var str = ""; 1404 | for (var i = 0; i < binarray.length * 4; i++) { 1405 | str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 15) + hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 15) 1406 | } 1407 | return str 1408 | } 1409 | function binl2b64(binarray) { 1410 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 1411 | var str = ""; 1412 | for (var i = 0; i < binarray.length * 4; i += 3) { 1413 | var triplet = (((binarray[i >> 2] >> 8 * (i % 4)) & 255) << 16) | (((binarray[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 255) << 8) | ((binarray[i + 2 >> 2] >> 8 * ((i + 2) % 4)) & 255); 1414 | for (var j = 0; j < 4; j++) { 1415 | if (i * 8 + j * 6 > binarray.length * 32) { 1416 | str += b64pad 1417 | } else { 1418 | str += tab.charAt((triplet >> 6 * (3 - j)) & 63) 1419 | } 1420 | } 1421 | } 1422 | return str 1423 | } 1424 | function hexchar2bin(str) { 1425 | var arr = []; 1426 | for (var i = 0; i < str.length; i = i + 2) { 1427 | arr.push("\\x" + str.substr(i, 2)) 1428 | } 1429 | arr = arr.join(""); 1430 | eval("var temp = '" + arr + "'"); 1431 | return temp 1432 | } 1433 | function __monitor(mid, probability) { 1434 | if (Math.random() > (probability || 1)) { 1435 | return 1436 | } 1437 | try { 1438 | var url = location.protocol + "//ui.ptlogin2.qq.com/cgi-bin/report?id=" + mid; 1439 | var s = document.createElement("img"); 1440 | s.src = url 1441 | } catch(e) {} 1442 | } 1443 | function getEncryption(password, salt, vcode, isMd5) { 1444 | vcode = vcode || ""; 1445 | password = password || ""; 1446 | 1447 | // md5加密 1448 | var md5Pwd = isMd5 ? password: md5(password), 1449 | 1450 | // 将md5转换为bin格式:\xff 1451 | h1 = hexchar2bin(md5Pwd), 1452 | 1453 | // 加盐md5 1454 | s2 = md5(h1 + salt), 1455 | 1456 | 1457 | rsaH1 = RSA.rsa_encrypt(h1), 1458 | rsaH1Len = (rsaH1.length / 2).toString(16), 1459 | hexVcode = TEA.strToBytes(vcode.toUpperCase(), true), 1460 | vcodeLen = Number(hexVcode.length / 2).toString(16); 1461 | 1462 | while (vcodeLen.length < 4) { 1463 | vcodeLen = "0" + vcodeLen 1464 | } 1465 | while (rsaH1Len.length < 4) { 1466 | rsaH1Len = "0" + rsaH1Len 1467 | } 1468 | TEA.initkey(s2); 1469 | var saltPwd = TEA.enAsBase64(rsaH1Len + rsaH1 + TEA.strToBytes(salt) + vcodeLen + hexVcode); 1470 | TEA.initkey(""); 1471 | 1472 | // setTimeout(function() { 1473 | // __monitor(488358, 1) 1474 | // }, 1475 | // 0); 1476 | return saltPwd.replace(/[\/\+=]/g, 1477 | function(a) { 1478 | return { 1479 | "/": "-", 1480 | "+": "*", 1481 | "=": "_" 1482 | } [a] 1483 | }) 1484 | } 1485 | function getRSAEncryption(password, vcode, isMd5) { 1486 | var str1 = isMd5 ? password: md5(password); 1487 | var str2 = str1 + vcode.toUpperCase(); 1488 | var str3 = $.RSA.rsa_encrypt(str2); 1489 | return str3 1490 | } 1491 | return { 1492 | getEncryption: getEncryption, 1493 | getRSAEncryption: getRSAEncryption, 1494 | md5: md5 1495 | } 1496 | } (); 1497 | 1498 | // var encrypted = RSA.rsa_encrypt('hello'); 1499 | // console.log('rsa_encrypt: ' + encrypted); 1500 | // 1501 | // console.log('strToBytes("abcdef"): ' + TEA.strToBytes('abcdef')); 1502 | // 1503 | // console.log('initkey("abcedf"): ' + TEA.initkey('abcdef')); 1504 | // 1505 | // console.log('enAsBase64("abcdefghijk")', TEA.enAsBase64('abcdefhijk')); 1506 | // 1507 | // var pass = 'LIshang(@!)@&'; 1508 | // var salt = 'Npw'; 1509 | // var verifyCode = 'tcac'; 1510 | // 1511 | // console.log(Encryption.getEncryption(pass, salt, verifyCode)) 1512 | module.exports = Encryption.getEncryption; 1513 | -------------------------------------------------------------------------------- /src/httpclient.coffee: -------------------------------------------------------------------------------- 1 | # 剥离出来的 HttpClient ,目前仅适合 qqapi 使用 2 | # 返回值:已经解析的json 3 | 4 | https = require "https" 5 | http = require 'http' 6 | querystring = require 'querystring' 7 | URL = require('url') 8 | jsons = JSON.stringify 9 | 10 | 11 | 12 | 13 | # 设置全局cookie 14 | all_cookies = [] 15 | global_cookies = (cookie)-> 16 | all_cookies = cookie if cookie 17 | return all_cookies 18 | 19 | # options url:url 20 | # method: GET/POST 21 | # debug:false 22 | # @params 请求参数 23 | # @callback( ret, error) ret为json序列对象 24 | http_request = (options , params , callback) -> 25 | aurl = URL.parse( options.url ) 26 | options.host = aurl.host 27 | options.path = aurl.path 28 | options.headers ||= {} 29 | 30 | client = if aurl.protocol == 'https:' then https else http 31 | body = '' 32 | if params and options.method == 'POST' 33 | data = querystring.stringify params 34 | options.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' 35 | options.headers['Content-Length']= Buffer.byteLength(data) 36 | if params and options.method == 'GET' 37 | query = querystring.stringify params 38 | append = if aurl.query then '&' else '?' 39 | options.path += append + query 40 | 41 | options.headers['Cookie'] = all_cookies 42 | options.headers['Referer'] = 'http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3' 43 | 44 | req = client.request options, (resp) -> 45 | if options.debug 46 | console.log "response: #{resp.statusCode}" 47 | console.log "cookie: #{resp.headers['set-cookie']}" 48 | resp.on 'data', (chunk) -> 49 | body += chunk 50 | resp.on 'end', -> 51 | handle_resp_body(body, options, callback) 52 | 53 | req.on "error" , (e)-> 54 | callback(null,e) 55 | 56 | if params and options.method == 'POST' 57 | req.write(data); 58 | req.end(); 59 | 60 | handle_resp_body = (body , options , callback) -> 61 | err = null 62 | try 63 | ret = JSON.parse body 64 | catch error 65 | console.log "解析出错", options.url, body 66 | console.log error 67 | err = error 68 | ret = null 69 | callback(ret,err) 70 | 71 | 72 | # 2 ways to call it 73 | # url, params, callback or 74 | # url, callback 75 | # 76 | http_get = (args...) -> 77 | [url,params,callback] = args 78 | [params,callback] = [null,params] unless callback 79 | options = 80 | method : 'GET' 81 | url : url 82 | http_request( options , params , callback) 83 | 84 | http_post = (options , body, callback) -> 85 | options.method = 'POST' 86 | http_request( options , body , callback) 87 | 88 | # 导出方法 89 | module.exports = 90 | global_cookies: global_cookies 91 | request: http_request 92 | get: http_get 93 | post: http_post 94 | -------------------------------------------------------------------------------- /src/hubot-qq.coffee: -------------------------------------------------------------------------------- 1 | {Robot, Adapter, EnterMessage, LeaveMessage, TextMessage} = require('hubot') 2 | 3 | auth = require "../src/qqauth" 4 | api = require "../src/qqapi" 5 | QQBot= require "../src/qqbot" 6 | defaults = require "../src/defaults" 7 | 8 | class QQHubotAdapter extends Adapter 9 | 10 | send: (envelope, strings...) -> 11 | @robot.logger.info "hubot is sending #{strings}" 12 | @group.send str for str in strings 13 | 14 | reply: (user, strings...) -> 15 | @send user, strings... 16 | 17 | emote: (envelope, strings...) -> 18 | @send envelope, "* #{str}" for str in strings 19 | 20 | run: -> 21 | self = @ 22 | 23 | options = 24 | account: process.env.HUBOT_QQ_ID or 2769546520 25 | password: process.env.HUBOT_QQ_PASS 26 | groupname: process.env.HUBOT_QQ_GROUP or 'qqbot群' 27 | port: process.env.HUBOT_QQ_IMGPORT or 3000 28 | host: process.env.HUBOT_QQ_IMGHOST or 'localhost' 29 | plugins: ['help'] 30 | 31 | skip_login = process.env.HUBOT_QQ_SKIP_LOGIN is 'true' 32 | 33 | unless options.account? and options.password? and options.groupname? 34 | @robot.logger.error "请配置qq 密码 和监听群名字,具体查阅帮助" 35 | process.exit(1) 36 | 37 | # TODO: login failed callback 38 | @login_qq skip_login,options,(cookies,auth_info)=> 39 | @qqbot = new QQBot(cookies,auth_info,options) 40 | @qqbot.update_buddy_list (ret,error)=> 41 | @robot.logger.info '√ buddy list fetched' if ret 42 | @qqbot.listen_group options.groupname , (@group,error)=> 43 | 44 | @robot.logger.info "enter long poll mode, have fun" 45 | @qqbot.runloop() 46 | @emit "connected" 47 | 48 | @group.on_message (content ,send, robot, message)=> 49 | 50 | @robot.logger.info "#{message.from_user.nick} : #{content}" 51 | # uin changed every-time 52 | user = @robot.brain.userForId message.from_uin , name:message.from_user.nick , room:options.groupname 53 | @receive new TextMessage user, content, message.uid 54 | 55 | 56 | 57 | # @callback (cookies,auth_info) 58 | login_qq: (skip_login, options,callback)-> 59 | defaults.set_path '/tmp/store.json' 60 | if skip_login 61 | cookies = defaults.data 'qq-cookies' 62 | auth_info = defaults.data 'qq-auth' 63 | @robot.logger.info "skip login",auth_info 64 | callback(cookies , auth_info ) 65 | else 66 | auth.login options , (cookies,auth_info)=> 67 | if process.env.HUBOT_QQ_DEBUG? 68 | defaults.data 'qq-cookies', cookies 69 | defaults.data 'qq-auth' , auth_info 70 | defaults.save() 71 | 72 | callback(cookies,auth_info) 73 | 74 | 75 | exports.use = (robot) -> 76 | new QQHubotAdapter robot 77 | -------------------------------------------------------------------------------- /src/qqapi.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | QQAPI 包含获取好友,群信息,发送消息,长轮询 3 | - 使用前需要设置 cookies() 4 | ### 5 | 6 | 7 | 8 | all_cookies = [] 9 | fs = require 'fs' 10 | jsons = JSON.stringify 11 | client = require './httpclient' 12 | log = new (require 'log')('debug') 13 | 14 | msg_id = 43690001 # 随机消息id,每次操作后需要增加 15 | # 49760001 16 | 17 | 18 | 19 | # 设置client cookie 20 | # 21 | cookies = (cookie)-> 22 | if cookie 23 | all_cookies = cookie 24 | client.global_cookies(all_cookies) 25 | return all_cookies 26 | 27 | 28 | ### 29 | # 长轮询,默认一分钟 30 | # @param : [clientid,psessionid] 31 | # @param callback: ret, e callback 返回值 true 才会循环 loop poll 32 | # @return ret retcode 102,正常空消息 33 | ### 34 | long_poll = (auth_opts, callback) -> 35 | log.debug "polling..." 36 | [clientid, psessionid, ptwebqq, uin, vfwebqq] = [auth_opts.clientid, auth_opts.psessionid, auth_opts.ptwebqq, auth_opts.uin, auth_opts.vfwebqq] 37 | url = "http://d.web2.qq.com/channel/poll2" 38 | r = 39 | ptwebqq: "#{ptwebqq}" 40 | clientid: "#{clientid}" 41 | psessionid: "#{psessionid}" 42 | key: "" 43 | params = 44 | r: jsons r 45 | log.debug "params: #{params.r}" 46 | client.post {url:url} , params , (ret,e)-> 47 | need_next_runloop = callback(ret,e) 48 | long_poll( auth_opts , callback ) if need_next_runloop 49 | 50 | 51 | # http://0.web.qstatic.com/webqqpic/pubapps/0/50/eqq.all.js 52 | # uin, ptwebqq 53 | hash_func = 54 | ` 55 | function(x, K) { 56 | x += ""; 57 | for (var N = [], T = 0; T < K.length; T++) N[T % 4] ^= K.charCodeAt(T); 58 | var U = ["EC", "OK"], 59 | V = []; 60 | V[0] = x >> 24 & 255 ^ U[0].charCodeAt(0); 61 | V[1] = x >> 16 & 255 ^ U[0].charCodeAt(1); 62 | V[2] = x >> 8 & 255 ^ U[1].charCodeAt(0); 63 | V[3] = x & 255 ^ U[1].charCodeAt(1); 64 | U = []; 65 | for (T = 0; T < 8; T++) U[T] = T % 2 == 0 ? N[T >> 1] : V[T >> 1]; 66 | N = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]; 67 | V = ""; 68 | for (T = 0; T < U.length; T++) { 69 | V += N[U[T] >> 4 & 15]; 70 | V += N[U[T] & 15] 71 | } 72 | return V 73 | } 74 | ` 75 | 76 | ################################################################################ 77 | # 好友 78 | 79 | # @param uin : 登录后获得 80 | # @param ptwebqq : cookie 81 | # @param vfwebqq : 登录后获得 82 | # @param callback: ret, e 83 | # retcode 0 84 | get_buddy_list = (auth_opts, callback)-> 85 | opt = auth_opts 86 | url = "http://s.web2.qq.com/api/get_user_friends2" 87 | r = 88 | hash: hash_func(opt.uin, opt.ptwebqq) 89 | vfwebqq: opt.vfwebqq 90 | client.post {url:url} , {r:jsons(r)} , (ret,e )-> 91 | callback(ret,e) 92 | 93 | # @param tuin: uin or gid 94 | # @param type: 获取 QQ 号码用 1 , 获取群号码用 4 95 | # @param auth_opts: [clientid,psessionid] 96 | # @param callback: ret, e 97 | # retcode 0 98 | get_friend_uin2 = (tuin, type, auth_opts, callback)-> 99 | opt = auth_opts 100 | url = "http://s.web2.qq.com/api/get_friend_uin2" 101 | params = 102 | tuin: tuin 103 | type: type 104 | vfwebqq: opt.vfwebqq 105 | t: new Date().getTime() 106 | 107 | client.get url, params, (ret,e)-> 108 | callback(ret, e) 109 | 110 | # @param to_uin: uin 111 | # @param msg, 消息 112 | # @param auth_opts: [clientid,psessionid] 113 | # @param callback: ret, e 114 | # @return ret retcode 0 115 | send_msg_2buddy = (to_uin , msg , auth_opts ,callback)-> 116 | url = "http://d.web2.qq.com/channel/send_buddy_msg2" 117 | opt = auth_opts 118 | r = 119 | to: to_uin 120 | face: 0 121 | msg_id: msg_id++ 122 | clientid: "#{opt.clientid}" 123 | psessionid: opt.psessionid 124 | content: jsons ["#{msg}", ["font", {name:"宋体", size:"10", style:[0,0,0], color:"000000" }] ] 125 | 126 | params = 127 | r: jsons r 128 | 129 | # log params 130 | client.post {url:url} , params , (ret,e) -> 131 | log.debug 'send2user',jsons ret 132 | callback(ret , e) if callback 133 | 134 | ################################################################################ 135 | # 临时消息 136 | 137 | # @param to_uin: uin 138 | # @param msg, 消息 139 | # @param auth_opts: [clientid,psessionid] 140 | # @param callback: ret, e 141 | # @return ret retcode 0 142 | send_msg_2sess = (to_gid , to_uin , msg , auth_opts ,callback)-> 143 | opt = auth_opts 144 | 145 | url = "http://d.web2.qq.com/channel/get_c2cmsg_sig2?id="+to_gid+"&to_uin="+to_uin+"&clientid="+opt.clientid+"&psessionid="+opt.psessionid+"&service_type=0&t="+new Date().getTime() 146 | 147 | client.get url , (ret,e) -> 148 | if !e 149 | url = "http://d.web2.qq.com/channel/send_sess_msg2" 150 | 151 | r = 152 | to: to_uin 153 | face: 594 154 | msg_id: msg_id++ 155 | clientid: "#{opt.clientid}" 156 | psessionid: opt.psessionid 157 | group_sig: ret.result.value 158 | content: jsons ["#{msg}", ["font", {name:"宋体", size:"10", style:[0,0,0], color:"000000" }] ] 159 | 160 | params = 161 | r: jsons r 162 | 163 | # log params 164 | client.post {url:url} , params , (ret,e) -> 165 | log.debug 'send2user',jsons ret 166 | callback(ret , e) if callback 167 | 168 | ################################################################################ 169 | # 群 170 | 171 | # 获取群列表 172 | # @param auth_opts vfwebqq : 登录后获得 173 | # @param callback: ret, e 174 | # retcode 0 175 | get_group_list = ( auth_opts, callback)-> 176 | aurl = "http://s.web2.qq.com/api/get_group_name_list_mask2" 177 | r = 178 | vfwebqq: auth_opts.vfwebqq 179 | hash: hash_func(auth_opts.uin, auth_opts.ptwebqq) 180 | 181 | client.post {url:aurl} , {r:jsons(r)} , (ret, e )-> 182 | callback(ret,e) if callback 183 | 184 | 185 | # @param group_code: code 186 | # @param auth_opts vfwebqq : 登录后获得 187 | # @param callback: ret, e 188 | # retcode 0 189 | get_group_member = (group_code, auth_opts, callback)-> 190 | url = "http://s.web2.qq.com/api/get_group_info_ext2" 191 | params = 192 | gcode : group_code 193 | vfwebqq: auth_opts.vfwebqq 194 | t : new Date().getTime() 195 | client.get url, params, callback 196 | 197 | 198 | # @param gid: gid 199 | # @param msg, 消息 200 | # @param auth_opts: [clientid,psessionid] 201 | # @param callback: ret, e 202 | # @return ret retcode 0 203 | send_msg_2group = (gid, msg , auth_opts, callback)-> 204 | url = 'http://d.web2.qq.com/channel/send_qun_msg2' 205 | opt = auth_opts 206 | r = 207 | group_uin: gid 208 | content: jsons ["#{msg}" , ["font", {name:"宋体", size:10, style:[0,0,0], color:"000000" }] ] 209 | face: 573 210 | clientid: "#{opt.clientid}" 211 | msg_id: msg_id++ 212 | psessionid: opt.psessionid 213 | params = 214 | r: jsons r 215 | client.post {url:url} , params , (ret,e)-> 216 | log.debug 'send2group',jsons ret 217 | callback(ret,e) if callback 218 | 219 | 220 | ################################################################################ 221 | # 讨论组 222 | 223 | 224 | get_discuss_list = (auth_opts, callback)-> 225 | url = "http://s.web2.qq.com/api/get_discus_list" 226 | params = 227 | clientid: auth_opts.clientid 228 | psessionid: auth_opts.psessionid 229 | vfwebqq: auth_opts.vfwebqq 230 | t: new Date().getTime() 231 | 232 | client.get url, params, callback 233 | 234 | # @discuss_id 讨论组id (did) 235 | get_discuss_member = (discuss_id, auth_opts, callback)-> 236 | url = "http://d.web2.qq.com/channel/get_discu_info" 237 | params = 238 | did: discuss_id 239 | clientid: auth_opts.clientid 240 | psessionid: auth_opts.psessionid 241 | vfwebqq: auth_opts.vfwebqq 242 | t: new Date().getTime() 243 | client.get url, params , callback 244 | 245 | send_msg_2discuss = (discuss_id, msg, auth_opts, callback)-> 246 | url = "http://d.web2.qq.com/channel/send_discu_msg2" 247 | opt = auth_opts 248 | r = 249 | did: "#{discuss_id}" #字符串 250 | msg_id: msg_id++ 251 | face: 573 252 | clientid: "#{opt.clientid}" 253 | psessionid: opt.psessionid 254 | content: jsons ["#{msg}" , ["font", {name:"宋体", size:10, style:[0,0,0], color:"000000" }] ] 255 | params = 256 | r: jsons r 257 | clientid: opt.clientid 258 | psessionid:opt.psessionid 259 | 260 | client.post {url:url} , params , (ret,e)-> 261 | log.debug 'send2discuss',jsons ret 262 | callback(ret,e) if callback 263 | 264 | 265 | change_status = (status, auth_opts, callback)-> 266 | # nothing 267 | 268 | 269 | module.exports = { 270 | cookies 271 | long_poll 272 | get_buddy_list 273 | get_friend_uin2 274 | send_msg_2buddy 275 | send_msg_2sess 276 | get_group_list 277 | get_group_member 278 | send_msg_2group 279 | get_discuss_list 280 | get_discuss_member 281 | send_msg_2discuss 282 | } 283 | -------------------------------------------------------------------------------- /src/qqauth.coffee: -------------------------------------------------------------------------------- 1 | https = require "https" 2 | http = require 'http' 3 | crypto = require 'crypto' 4 | querystring = require 'querystring' 5 | Url = require('url') 6 | all_cookies = [] 7 | int = (v) -> parseInt v 8 | Path = require 'path' 9 | Log = require 'log' 10 | log = new Log('debug'); 11 | encryptPass = require './encrypt' 12 | 13 | md5 = (str) -> 14 | md5sum = crypto.createHash 'md5' 15 | md5sum.update(str.toString()).digest('hex') 16 | 17 | 18 | exports.cookies = (cookies)-> 19 | all_cookies = cookies if cookies 20 | all_cookies 21 | 22 | # 是否需要 验证码 23 | # @param qq 24 | # @param callback -> [是否需要验证码 , token , bits ] 25 | exports.check_qq_verify = (qq, callback) -> 26 | # TODO: random -> r 27 | options = 28 | host: 'ssl.ptlogin2.qq.com' 29 | path: "/check?pt_tea=1&uin=#{qq}&appid=501004106&js_ver=10125&js_type=0&login_sig=&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html&r=0.6569391019121522" 30 | headers: 31 | 'Cookie' : "chkuin=#{qq}" 32 | 33 | body = ''; 34 | https.get options, (resp) -> 35 | all_cookies = resp.headers['set-cookie'] 36 | resp.on 'data', (chunk) -> 37 | body += chunk 38 | resp.on 'end', -> 39 | # log body 40 | ret = body.match(/\'(.*?)\'/g).map (i)-> 41 | last = i.length - 2 42 | i.substr(1 ,last) 43 | # log ret 44 | callback( ret ) 45 | .on "error", (e) -> 46 | log.error e 47 | 48 | 49 | # 获取验证码 50 | # 记得call finish_verify_code 51 | exports.get_verify_code = (qq , host, port, cap_cd, callback) -> 52 | url = "http://captcha.qq.com/getimage?aid=501004106&r=0.2509327069195215&uin=#{qq}&aid=501004106&cap_cd=#{cap_cd}" 53 | body = '' 54 | 55 | http.get url , (resp) -> 56 | # log "verify code: #{resp.statusCode}" 57 | # log resp.headers 58 | all_cookies = all_cookies.concat resp.headers['set-cookie'] 59 | resp.setEncoding 'binary' 60 | resp.on 'data', (chunk) -> 61 | body += chunk 62 | resp.on 'end', -> 63 | create_img_server(host,port,body,resp.headers) 64 | callback() 65 | .on "error", (e) -> 66 | log.error e 67 | callback(e) 68 | 69 | exports.finish_verify_code = -> stop_img_server() 70 | 71 | 72 | # 验证码图片服务 73 | img_server = null 74 | create_img_server = (host, port, body ,origin_headers) -> 75 | return if img_server 76 | 77 | fs = require 'fs' 78 | file_path = Path.join __dirname, "..", "tmp", "verifycode.jpg" 79 | fs.writeFileSync file_path , body , 'binary' 80 | 81 | img_server = http.createServer (req, res) -> 82 | res.writeHead 200 , origin_headers 83 | res.end body, 'binary' 84 | 85 | img_server.listen port 86 | 87 | 88 | stop_img_server = -> 89 | img_server.close() if img_server 90 | img_server = null 91 | 92 | 93 | # 生成加密密码 94 | # @param password 密码 95 | # @param token check_qq_verify 参数1 !UGX 96 | # @param bits check_qq_verify 参数2 \x00\x11\x00\x11 97 | exports.encode_password = (password , token , bits) -> 98 | bits = bits.replace(/\\x/g,'') 99 | 100 | hex2ascii = (hexstr) -> 101 | hexstr.match(/\w{2}/g) 102 | .map (byte_str) -> 103 | String.fromCharCode parseInt(byte_str,16) 104 | .join('') 105 | bits = hex2ascii bits 106 | 107 | return encryptPass(password, bits, token); 108 | # password = md5(password) 109 | # bits = bits.replace(/\\x/g,'') 110 | # 111 | # hex2ascii = (hexstr) -> 112 | # hexstr.match(/\w{2}/g) 113 | # .map (byte_str) -> 114 | # String.fromCharCode parseInt(byte_str,16) 115 | # .join('') 116 | # 117 | # ret = md5( hex2ascii(password) + hex2ascii(bits) ).toUpperCase() + token.toUpperCase() 118 | # ret = md5( ret ).toUpperCase() 119 | # 120 | # return ret 121 | 122 | # 登录 帐号密码验证码 校验 123 | exports.login_step1 = (qq, encode_password, verifycode, verifySession, callback) -> 124 | if verifySession is '' 125 | for c in all_cookies 126 | if c.indexOf('verifysession') > -1 127 | verifySession = c.match(/verifysession=(.*?);.*/)[1] 128 | 129 | path = "/login?u=#{qq}&p=#{encode_password}&verifycode=#{verifycode}&webqq_type=10&remember_uin=1&login2qq=1&aid=501004106&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=3-15-72115&mibao_css=m_webqq&t=1&g=1&js_type=0&js_ver=10125&login_sig=&pt_randsalt=0&pt_vcode_v1=0&pt_verifysession_v1=#{verifySession}" 130 | # log.debug path 131 | options = 132 | host: 'ssl.ptlogin2.qq.com' 133 | path: path 134 | headers: 135 | 'Cookie' : all_cookies 136 | 137 | body = ''; 138 | https.get options, (resp) -> 139 | # log "response: #{resp.statusCode}" 140 | # log all_cookies 141 | all_cookies = all_cookies.concat resp.headers['set-cookie'] 142 | # log.debug all_cookies 143 | resp.on 'data', (chunk) -> 144 | body += chunk 145 | resp.on 'end', -> 146 | ret = body.match(/\'(.*?)\'/g).map (i)-> 147 | last = i.length - 2 148 | i.substr(1 ,last) 149 | callback( ret ) 150 | .on "error", (e) -> 151 | log.error e 152 | 153 | # 验证成功后继续获取cookie 154 | exports.login_step2 = (url, callback) -> 155 | url = Url.parse(url) 156 | options = 157 | host: url.host 158 | path: url.path 159 | headers: 160 | 'Cookie' : all_cookies 161 | 162 | body = ''; 163 | http.get options, (resp) -> 164 | log.debug "response: #{resp.statusCode}" 165 | all_cookies = all_cookies.concat resp.headers['set-cookie'] 166 | callback( true ) 167 | # 只需要获取cookie 168 | .on "error", (e) -> 169 | log.error e 170 | 171 | # "http://d.web2.qq.com/channel/login2" 172 | # client_id : int 173 | # callback( ret , client_id , ptwebqq) 174 | exports.login_token = (client_id=null,psessionid=null,callback) -> 175 | # client 是长度8的随机数字 176 | client_id ||= parseInt(Math.random()* 89999999) + 10000000 177 | client_id = parseInt client_id 178 | ptwebqq = all_cookies.filter( (item)->item.match /ptwebqq/ ) 179 | .pop() 180 | .replace /ptwebqq\=(.*?);.*/ , '$1' 181 | # log all_cookies 182 | r = 183 | status: "online", 184 | ptwebqq: ptwebqq, 185 | clientid: "#{client_id}", 186 | psessionid: psessionid 187 | r = JSON.stringify(r) 188 | 189 | data = querystring.stringify { 190 | r: r 191 | } 192 | # log data 193 | 194 | body = '' 195 | options = 196 | host: 'd.web2.qq.com', 197 | path: '/channel/login2' 198 | method: 'POST', 199 | headers: 200 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:27.0) Gecko/20100101 Firefox/27.0', 201 | 'Referer': 'http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3', 202 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 203 | 'Content-Length': Buffer.byteLength(data), 204 | 'Cookie' : all_cookies 205 | 206 | req = http.request options, (resp) -> 207 | log.debug "login token response: #{resp.statusCode}" 208 | resp.on 'data', (chunk) -> 209 | body += chunk 210 | resp.on 'end', -> 211 | ret = JSON.parse(body) 212 | callback( ret , client_id ,ptwebqq) 213 | 214 | req.write(data) 215 | req.end() 216 | 217 | ### 218 | 全局登录函数,如果有验证码会建立一个 http-server ,同时写入 tmp/*.jpg (osx + open. 操作) 219 | http-server 的端口和显示地址可配置 220 | @param options {account,password,port,host} 221 | @callback( cookies , auth_options ) if login success 222 | ### 223 | exports.login = (options, callback) -> 224 | [auth,opt] = [exports,options] 225 | [qq,pass] = [opt.account,opt.password] 226 | 227 | log.info '登录 step0 验证码检测' 228 | auth.check_qq_verify qq , (result) -> 229 | # log.debug "验证帐号:", result 230 | [need_verify,verify_code,bits,verifySession] = result 231 | if int need_verify 232 | log.info "登录 step0.5 获取验证码" 233 | auth.get_verify_code qq, opt.host, opt.port, verify_code, (error) -> 234 | # open image folder 235 | require('child_process').exec 'open tmp' if process.platform is 'darwin' 236 | 237 | log.notice "打开该地址->", "http://#{opt.host}:#{opt.port}" 238 | auth.prompt "输入验证码:" , (code) -> 239 | auth.finish_verify_code() 240 | verify_code = code 241 | log.notice '验证码:' , verify_code 242 | pass_encrypted = auth.encode_password(pass, verify_code , bits) 243 | login_next( qq , pass_encrypted , verify_code , verifySession , callback) 244 | else 245 | log.info "- 无需验证码" 246 | pass_encrypted= auth.encode_password(pass, verify_code , bits) 247 | login_next( qq , pass_encrypted , verify_code , verifySession , callback) 248 | 249 | # login 函数的步骤2 250 | # TODO:各种回调的 error 处理 251 | login_next = (account , pass_encrypted , verify_code , verifySession , callback)-> 252 | auth = exports 253 | log.info "登录 step1 密码校验" 254 | auth.login_step1 account, pass_encrypted , verify_code , verifySession , (ret)-> 255 | 256 | if not ret[2].match /^http/ 257 | log.error "登录 step1 failed", ret 258 | return 259 | 260 | log.info "登录 step2 cookie获取" 261 | auth.login_step2 ret[2] , (ret) -> 262 | log.info "登录 step3 token 获取" 263 | auth.login_token null,null,(ret,client_id,ptwebqq) -> 264 | if ret.retcode == 0 265 | log.info '登录成功',account 266 | auth_options = 267 | psessionid: ret.result.psessionid 268 | clientid : client_id 269 | ptwebqq : ptwebqq 270 | uin : ret.result.uin 271 | vfwebqq : ret.result.vfwebqq 272 | callback( all_cookies, auth_options) 273 | else 274 | log.info "登录失败" 275 | log.error ret 276 | 277 | 278 | # prompt user to input something 279 | # and also listen for process event data 280 | # @params title : prompt title 281 | # @callback(content) 282 | exports.prompt = (title, callback) -> 283 | process.stdin.resume() 284 | process.stdout.write(title) 285 | process.on "data" , (data) -> 286 | if data 287 | callback data 288 | process.stdin.pause() 289 | process.stdin.on "data", (data) -> 290 | data = data.toString().trim() 291 | # 过滤无效内容 292 | if data 293 | callback data 294 | process.stdin.pause() 295 | # control + d to end 296 | process.stdin.on 'end', -> 297 | process.stdout.write('end') 298 | callback() 299 | -------------------------------------------------------------------------------- /src/qqauth_qrcode.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | (function() { 3 | var Log, Path, Url, all_cookies, create_img_server, crypto, encryptPass, http, https, img_server, int, log, login_next, md5, querystring, stop_img_server; 4 | 5 | https = require("https"); 6 | 7 | http = require('http'); 8 | 9 | crypto = require('crypto'); 10 | 11 | querystring = require('querystring'); 12 | 13 | Url = require('url'); 14 | 15 | all_cookies = []; 16 | 17 | int = function(v) { 18 | return parseInt(v); 19 | }; 20 | 21 | Path = require('path'); 22 | 23 | Log = require('log'); 24 | 25 | log = new Log('debug'); 26 | 27 | encryptPass = require('./encrypt'); 28 | 29 | md5 = function(str) { 30 | var md5sum; 31 | md5sum = crypto.createHash('md5'); 32 | return md5sum.update(str.toString()).digest('hex'); 33 | }; 34 | 35 | var all_cookies_str = function() { 36 | var str = ""; 37 | for(var i=0; i", "http://" + opt.host + ":" + opt.port); 249 | } 250 | 251 | return auth.prompt("手机QQ扫描二维码后, 回车继续: ", function(code) { 252 | log.info("登录 step1 等待二维码校验结果"); 253 | return auth.check_qq_verify(qq, function(ret) { 254 | if( int(ret[0]) == 0 && ret[2].match(/^http/)) { 255 | console.log( ret[5] + ret[4] ); 256 | 257 | log.info("登录 step2 cookie获取"); 258 | return auth.check_sig(ret[2], function(ret){ 259 | log.info("登录 step3 token 获取"); 260 | return auth.login_token(null, null, function(ret, client_id, ptwebqq) { 261 | var auth_options; 262 | if (ret.retcode === 0) { 263 | log.info('登录 token 获取成功'); 264 | auth_options = { 265 | psessionid: ret.result.psessionid, 266 | clientid: client_id, 267 | ptwebqq: ptwebqq, 268 | uin: ret.result.uin, 269 | vfwebqq: ret.result.vfwebqq 270 | }; 271 | console.log( auth_options ); 272 | return callback(all_cookies, auth_options); 273 | } else { 274 | log.info("登录失败"); 275 | return log.error(ret); 276 | } 277 | }); 278 | }); 279 | } else { 280 | log.error("登录 step1 failed", ret); 281 | return; 282 | } 283 | }); 284 | }); 285 | }); 286 | } else { 287 | console.log(result); 288 | } 289 | }); 290 | }); 291 | }; 292 | 293 | exports.prompt = function(title, callback) { 294 | process.stdin.resume(); 295 | process.stdout.write(title); 296 | process.on("data", function(data) { 297 | callback(data); 298 | return process.stdin.pause(); 299 | }); 300 | process.stdin.on("data", function(data) { 301 | data = data.toString().trim(); 302 | callback(data); 303 | return process.stdin.pause(); 304 | }); 305 | return process.stdin.on('end', function() { 306 | process.stdout.write('end'); 307 | return callback(); 308 | }); 309 | }; 310 | 311 | }).call(this); 312 | -------------------------------------------------------------------------------- /src/qqbot.coffee: -------------------------------------------------------------------------------- 1 | auth = require './qqauth' 2 | api = require './qqapi' 3 | Log = require 'log' 4 | Request = require 'request' 5 | Dispatcher = require './dispatcher' 6 | 7 | log = new Log('debug') 8 | jsons = JSON.stringify 9 | 10 | MsgType = 11 | Default:'message' 12 | Sess:'sess_message' 13 | Group:'group_message' 14 | Discuss:'discu_message' 15 | 16 | ### 17 | cookie , auth 登录需要参数 18 | config: 配置信息,将 config.yaml 19 | plugins: 插件 20 | ### 21 | class QQBot 22 | constructor: (@cookies, @auth, @config) -> 23 | # PROTOCOL `用户分组信息格式` 24 | @buddy_info = {} 25 | # PROTOCOL `群分组信息格式` 26 | @group_info = {} 27 | # PROTOCOL `群用户信息` 28 | @groupmember_info = {} 29 | 30 | # discuss group 31 | @dgroup_info = {} 32 | @dgroupmember_info = {} 33 | 34 | # 账户表 35 | @user_account_table = {} 36 | @group_account_table = {} 37 | 38 | api.cookies @cookies 39 | @api = api 40 | @dispatcher = new Dispatcher(@config.plugins,@) 41 | @started = true 42 | @request = Request 43 | 44 | # @format PROTOCOL `群用户信息` 45 | save_group_member: (group,info)-> 46 | @groupmember_info[group.gid] = info 47 | 48 | # 获取用户信息 49 | # @return {nick,uin,flag,face} 50 | get_user: (uin) -> 51 | users = @buddy_info.info.filter (item)-> item.uin == uin 52 | users.pop() 53 | 54 | # 获取群用户信息 55 | get_user_ingroup: (uin, gid)-> 56 | info = @groupmember_info[gid] 57 | users = info.minfo.filter (item)-> item.uin == uin 58 | users.pop() 59 | 60 | # 获取群信息,只支持群 ,支持多关键词搜索 61 | # @options {key:value} 62 | # @return {gid,code,name,flag} 63 | get_group: (options)-> 64 | groups = @group_info.gnamelist.filter (item)-> 65 | for key ,value of options 66 | return item[key] == value 67 | groups.pop() 68 | 69 | # @options {key:value} 70 | # @return {name, did} 71 | get_dgroup: (options)-> 72 | try 73 | groups = @dgroup_info.dnamelist.filter (item)-> 74 | for key,value of options 75 | return item[key] == value 76 | groups.pop() 77 | 78 | 79 | get_user_in_dgroup: (uin, did)-> 80 | try 81 | info = @dgroupmember_info[did] 82 | users = (user for user in info.mem_info when user.uin == uin) 83 | users.pop() 84 | 85 | 86 | # 获取群列表 87 | # @callback {ret:bool,error} 88 | update_group_list: (callback)-> 89 | @api.get_group_list @auth, (ret , e)=> 90 | log.error e if e 91 | @group_info = ret.result if ret.retcode == 0 92 | callback( ret.retcode == 0, e || 'retcode isnot 0' ) if callback 93 | 94 | # 获取好友列表 95 | # @callback (ret:bool, error) 96 | update_buddy_list: (callback)-> 97 | @api.get_buddy_list @auth , (ret,e)=> 98 | @buddy_info = ret.result if ret.retcode == 0 99 | callback( ret.retcode == 0, e || 'retcode isnot 0' ) if callback 100 | 101 | # 更新群成员 102 | # @options {key:value} or group obj 103 | # @callback (ret:bool , error) 104 | update_group_member: (options, callback)-> 105 | group = if options.code then options else @get_group(options) 106 | @api.get_group_member group.code , @auth , (ret,e)=> 107 | if ret.retcode == 0 108 | @save_group_member(group,ret.result) 109 | callback(ret.retcode == 0 , e) if callback 110 | 111 | update_dgroup_list: (callback)-> 112 | log.info "update discuss group list" 113 | @api.get_discuss_list @auth, (ret,e)=> 114 | log.error e if e 115 | @dgroup_info = ret.result if ret.retcode == 0 116 | # log.info jsons @dgroup_info 117 | callback(ret.retcode == 0, e||'retcode isnot 0') if callback 118 | 119 | update_dgroup_member: (dgroup,callback)-> 120 | log.info "update discuss group member #{dgroup.did}" 121 | did = dgroup.did 122 | @api.get_discuss_member did, @auth, (ret,e)=> 123 | if ret.retcode == 0 124 | @dgroupmember_info[did] = ret.result 125 | # log.info jsons ret.result 126 | callback(ret.retcode == 0 , e) if callback 127 | 128 | 129 | # 更新所有群成员 130 | # @callback (success,total_count,success_count) 131 | update_all_group_member: (callback)-> 132 | finished = successed = 0 133 | groups = @group_info.gnamelist || [] 134 | all = groups.length 135 | callback(true,0,0) if all == 0 136 | for group in groups 137 | @update_group_member group , (ret,error)-> 138 | finished += 1; successed += ret 139 | log.debug "groupmember all#{all} fin#{finished} succ#{successed}" 140 | log.debug error if error 141 | callback(successed == all, finished ,successed) if finished == all 142 | 143 | # 更新好友和群成员 144 | # @callback (ret?,actions) 是否全部更新成功 , actions每个状态 145 | update_all_members: (callback)-> 146 | # [0,0] -> [finished,success] 147 | actions= buddy:[0,0],group:[0,0],groupmember:[0,0] 148 | check = -> 149 | finished = successed = 0 ; all = Object.keys(actions).length 150 | stats = (value for key,value of actions) 151 | for item in stats 152 | finished += item[0] ; successed += item[1] 153 | log.debug("updating all: all #{all} finished #{finished} success #{successed}") 154 | callback(successed==all) if finished == all 155 | 156 | # fetching... 157 | log.info 'fetching buddy list...' 158 | @update_buddy_list (ret)-> 159 | actions.buddy = [1, ret] 160 | check() 161 | 162 | log.info 'fetching group list...' 163 | @update_group_list (ret)=> 164 | actions.group = [1, ret] 165 | unless ret 166 | callback(false) 167 | return 168 | 169 | log.info 'fetching all groupmember...' 170 | @update_all_group_member (ret,all,successed)-> 171 | actions.groupmember = [1,ret] 172 | check() 173 | 174 | log.info 'fetching discuss group list' 175 | @update_dgroup_list() 176 | 177 | # 获取号码信息的通用方法 178 | # @param table 179 | # @param uin uin 或 gid 180 | # @param type QQ号码 1, 群号码 4 181 | # @callback (error, account) 182 | get_account_info_general: (table, uin, type, callback)-> 183 | key = "uin" + uin 184 | 185 | if info = table[key] 186 | if acc = info.account 187 | callback null, acc 188 | else 189 | info.callbacks.push callback 190 | 191 | else 192 | callbacks = [callback] 193 | table[key] = callbacks: callbacks 194 | 195 | call_callbacks = (err, account)-> 196 | func err, account for func in callbacks 197 | 198 | log.info "fetching account info: type#{type}, uin#{uin}" 199 | @api.get_friend_uin2 uin, type, @auth, (ret, e)=> 200 | delete table[key] 201 | 202 | unless ret? 203 | call_callbacks {}, null 204 | return 205 | 206 | if ret.retcode == 0 207 | result = table[key] = ret.result 208 | 209 | # 不知道为什么,群号码总要减掉这个数才正确…… 210 | result.account -= 3890000000 if type == 4 211 | 212 | account = result.account 213 | account_key = "acc" + account 214 | 215 | funcs = table[account_key]?.callbacks 216 | table[account_key] = result 217 | 218 | func null, uin for func in funcs if funcs 219 | 220 | call_callbacks null, account 221 | else 222 | call_callbacks ret, null 223 | 224 | # 通过号码反向获取 uin/gid 的通用方法 225 | # @param table 226 | # @param account 227 | # @callback (error, account) 228 | # @return uin 229 | get_uin_general: (table, account, callback)-> 230 | key = "acc" + account 231 | if info = table[key] 232 | if uin = info.uin 233 | callback null, uin if callback 234 | return uin 235 | else 236 | info.callbacks.push callback 237 | return null 238 | else 239 | table[key] = callbacks: [callback] if callback 240 | return null 241 | 242 | # 获取 QQ 号码 243 | # @param uin_or_user 244 | # @callback (error, account) 245 | get_user_account: (uin_or_user, callback)-> 246 | uin = if typeof uin_or_user is 'object' then uin_or_user.uin else uin_or_user 247 | @get_account_info_general @user_account_table, uin, 1, callback 248 | 249 | # 根据 QQ 号码获取 uin,目前必须调用过 get_user_account 才可以获取到对应 uin, 250 | # 如果不使用 callback 参数的话将会直接返回 uin (不可用时为 null), 251 | # 否则将会一直等到可用才会调用 callback 252 | # @param account QQ 号码 253 | # @callback (error, uin) 254 | # @return uin 255 | get_user_uin: (account, callback)-> 256 | return @get_uin_general @user_account_table, account, callback 257 | 258 | # 获取群号码 259 | # @param gid_or_group 260 | # @callback (error, account) 261 | get_group_account: (gid_or_group, callback)-> 262 | uin = if typeof gid_or_group is 'object' then gid_or_group.gid else gid_or_group 263 | @get_account_info_general @group_account_table, uin, 4, callback 264 | 265 | # 根据群号码获取 gid,目前必须调用过 get_group_account 才可以获取到对应 gid, 266 | # 如果不使用 callback 参数的话将会直接返回 gid (不可用时为 null), 267 | # 否则将会一直等到可用才会调用 callback 268 | # @param account 群号码 269 | # @callback (error, gid) 270 | # @return gid 271 | get_group_gid: (account, callback)-> 272 | return @get_uin_general @group_account_table, account, callback 273 | 274 | # die callback 275 | on_die: (callback)-> 276 | @cb_die = callback 277 | 278 | # 长轮询 279 | # @callback 280 | runloop: (callback)-> 281 | @api.long_poll @auth , (ret,e)=> 282 | if @started 283 | @handle_poll_responce ret,e 284 | callback(ret,e) if callback 285 | return @started 286 | 287 | 288 | # 回复消息 289 | # @param message 收到的message 290 | # @param content:string 回复信息 291 | # @callback ret, error 292 | reply_message: (message, content, callback)-> 293 | log.info "发送消息:",content 294 | switch message.type 295 | when MsgType.Group 296 | @api.send_msg_2group message.from_gid , content , @auth, callback 297 | when MsgType.Default 298 | @api.send_msg_2buddy message.from_uin , content , @auth , callback 299 | when MsgType.Discuss 300 | @api.send_msg_2discuss message.from_did, content, @auth, callback 301 | when MsgType.Sess 302 | @api.send_msg_2sess message.from_gid , message.from_uin , content , @auth, callback 303 | 304 | # 发送消息 305 | # @param uin 306 | # @callback (ret,e) 307 | send_message: (uin_or_user, content, callback)-> 308 | uin = if typeof uin_or_user is 'object' then uin_or_user.uin else uin_or_user 309 | log.info "send msg #{content} to user#{uin}" 310 | api.send_msg_2buddy uin, content, @auth, callback 311 | 312 | # 发送群消息 313 | # @param gid_or_group 314 | # @callback (ret,e) 315 | send_message_to_group: (gid_or_group, content, callback)-> 316 | gid = if typeof gid_or_group is 'object' then gid_or_group.gid else gid_or_group 317 | log.info "send msg #{content} to group#{gid}" 318 | api.send_msg_2group gid , content , @auth, callback 319 | 320 | send_message_to_discuss: (did, content, callback)-> 321 | log.info "send msg #{content} to discuss#{did}" 322 | api.send_msg_2discuss did, content, @auth, callback 323 | 324 | # 自杀 325 | die: (message,info)-> 326 | @dispatcher.stop_plugin() 327 | @started = false 328 | 329 | #TODO: 这里 log.error 似乎看不到日志输出,试试console 330 | log.error "QQBot will die! message: #{message}" if message 331 | console.log "QQBot will die! message: #{message}" if message 332 | 333 | log.error "QQBot will die! info #{JSON.stringify info}" if info 334 | console.log "QQBot will die! info #{JSON.stringify info}" if info 335 | 336 | if @cb_die 337 | @cb_die() 338 | else 339 | process.exit(1) 340 | 341 | # 处理poll返回的内容 342 | handle_poll_responce: (resp,e)-> 343 | log.error "poll with error #{e}" if e 344 | code = if resp then resp.retcode else -1 345 | switch code 346 | when -1 then log.error "resp is null, error on parse ret",resp 347 | when 0 then @_handle_poll_event(event) for event in resp.result 348 | when 102 then 'nothing happened, waiting for next loop' 349 | when 116 then @_update_ptwebqq(resp) 350 | when 103,121 then @die("登录异常 #{code}",resp) 351 | else log.debug resp 352 | 353 | ### 354 | 重新登录获取token 355 | @callback success:bool 356 | ### 357 | relogin: (callback)-> 358 | log.info "relogin..." 359 | auth.cookies @cookies 360 | 361 | auth.login_token @auth.clientid, @auth.psessionid, (ret,client_id,ptwebqq) => 362 | if ret.retcode != 0 363 | log.error "relogin failed" 364 | log.info ret 365 | callback(false) if callback 366 | return 367 | 368 | log.debug 'before',@auth 369 | auth_new = 370 | psessionid: ret.result.psessionid 371 | clientid : client_id 372 | ptwebqq : ptwebqq 373 | uin : ret.result.uin 374 | vfwebqq : ret.result.vfwebqq 375 | 376 | @auth = auth_new 377 | log.debug 'after',@auth 378 | callback(true) if callback 379 | 380 | 381 | # 更新token ptwebqq的值,返回值{116 ,p=token} 382 | _update_ptwebqq: (ret)-> 383 | log.debug 'need to update ptwebqq ',ret 384 | @auth['ptwebqq'] = ret.p 385 | 386 | _handle_poll_event : (event) -> 387 | switch event.poll_type 388 | when MsgType.Default, MsgType.Sess, MsgType.Group, MsgType.Discuss 389 | @_on_message(event, event.poll_type) 390 | when 'input_notify' then "" 391 | when 'buddies_status_change' then "" 392 | else log.warning "unimplemented event",event.poll_type , "content: ", jsons event 393 | 394 | _on_message : (event, msg_type)-> 395 | value = event.value 396 | msg = 397 | content : value.content.slice(-1).pop().trim() 398 | time : new Date(value.time * 1000) 399 | from_uin: value.from_uin 400 | type : msg_type 401 | uid : value.msg_id 402 | 403 | if msg_type == MsgType.Group 404 | msg.from_gid = msg.from_uin 405 | msg.group_code = value.group_code 406 | msg.from_uin = value.send_uin # 这才是用户,group消息中 from_uin 是gid 407 | msg.from_group = @get_group( {gid:msg.from_gid} ) 408 | msg.from_user = @get_user_ingroup( msg.from_uin ,msg.from_gid ) 409 | # 更新 410 | @update_group_list unless msg.from_group 411 | @update_group_member {gid:msg.from_gid} unless msg.from_user 412 | 413 | msg.from_group ?= {} 414 | msg.from_user ?= {} 415 | try log.debug "[群组消息]","[#{msg.from_group.name}] #{msg.from_user.nick}:#{msg.content} #{msg.time}" 416 | else if msg_type == MsgType.Discuss 417 | msg.from_did = value.did 418 | msg.from_uin = value.send_uin 419 | msg.from_dgroup = @get_dgroup({did:value.did}) 420 | msg.from_user = @get_user_in_dgroup(msg.from_uin,msg.from_did) 421 | 422 | # 更新 423 | @update_dgroup_list() unless msg.from_dgroup 424 | @update_dgroup_member {did:value.did} unless msg.from_user 425 | msg.from_dgroup ?= {} 426 | msg.from_user ?= {} 427 | 428 | try log.debug "[讨论组消息]","[#{msg.from_dgroup.name}] #{msg.from_user.nick}:#{msg.content} #{msg.time}" 429 | else if msg_type == MsgType.Default 430 | msg.from_user = @get_user( msg.from_uin ) 431 | # 更新 432 | @update_buddy_list unless msg.from_user 433 | try log.debug "[好友消息]","#{msg.from_user.nick}:#{msg.content} #{msg.time}" 434 | else if msg_type == MsgType.Sess 435 | msg.from_gid = value.id 436 | msg.from_uin = value.from_uin 437 | msg.from_group = @get_group( {gid:msg.from_gid} ) 438 | msg.from_user = @get_user_ingroup( msg.from_uin ,msg.from_gid ) 439 | # 更新 440 | @update_group_list unless msg.from_group 441 | @update_group_member {gid:msg.from_gid} unless msg.from_user 442 | 443 | msg.from_group ?= {} 444 | msg.from_user ?= {} 445 | 446 | try log.debug "[临时消息]","#{msg.from_user.nick}:#{msg.content} #{msg.time}" 447 | 448 | 449 | # 消息和插件处理 450 | if @config.offline_msg_keeptime and new Date().getTime() - msg.time.getTime() > @config.offline_msg_keeptime * 1000 451 | return 452 | 453 | replied = false 454 | reply = (content)=> 455 | @reply_message(msg,content) unless replied 456 | replied = true 457 | 458 | @dispatcher.dispatch(msg.content ,reply, @ , msg) 459 | 460 | 461 | 462 | # 监听特定群并返回群对象 463 | # @callback (group对象, error = null) 464 | 465 | listen_group : (name , callback) -> 466 | log.info 'fetching group list' 467 | @update_group_list (ret, e) => 468 | log.info '√ group list fetched' 469 | 470 | log.info "fetching groupmember #{name}" 471 | @update_group_member {name:name} ,(ret,error)=> 472 | log.info '√ group memeber fetched' 473 | 474 | groupinfo = @get_group {name:name} 475 | group = new Group(@, groupinfo.gid) 476 | @dispatcher.add_listener [group,"dispatch"] 477 | callback group 478 | 479 | 480 | ### 481 | 为hubot专门使用,提供两个方法 482 | - send 483 | - on_message (content,send_fun, bot , message_info) -> 484 | ### 485 | class Group 486 | constructor: (@bot,@gid)-> 487 | send: (content , callback)-> 488 | @bot.send_message_to_group @gid , content , (ret,e)-> 489 | callback(ret,e) if callback 490 | 491 | on_message: (@msg_cb)-> 492 | dispatch: (content ,send, robot, message)-> 493 | # log.debug 'dispatch',params[0],@msg_cb 494 | if message.from_gid == @gid and @msg_cb 495 | @msg_cb(content ,send, robot, message) 496 | 497 | 498 | module.exports = QQBot 499 | -------------------------------------------------------------------------------- /tests/api.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | # api相关的测试代码 4 | int = (v) -> parseInt v 5 | log = console.log 6 | auth = require "../src/qqauth" 7 | api = require "../src/qqapi" 8 | 9 | defaults = require '../src/defaults' 10 | config = require '../config' 11 | qq = config.account 12 | pass = config.password 13 | jsons = JSON.stringify 14 | 15 | 16 | # 设置登录信息 17 | api.cookies defaults.data 'cookie' 18 | auth_opts = defaults.data 'auth' 19 | ### 20 | auth_opts ={ 21 | psessionid 22 | clientid 23 | ptwebqq 24 | uin 25 | vfwebqq 26 | } 27 | ### 28 | 29 | test_api = -> 30 | # log "长轮训" 31 | # api.long_poll auth_opts , (ret)-> 32 | # log ret 33 | 34 | 35 | # api.get_buddy_list auth_opts , (ret,e)-> 36 | # log auth_opts 37 | # log 'friend',jsons ret 38 | # log '' 39 | 40 | # api.get_friend_uin2 2967661734, 1, auth_opts, (ret, e) -> 41 | # log 'get_friend_uin', ret 42 | 43 | # api.get_friend_uin2 1093099923, 4, auth_opts, (ret, e) -> 44 | # log 'get group uin: ', ret 45 | 46 | # api.get_group_list auth_opts, (ret , e)-> 47 | # log "get_group_list: #{jsons ret}" 48 | # log '' 49 | 50 | 51 | # api.get_group_member 1783828783, auth_opts , (ret,e)-> 52 | # log 'group_member' , ret 53 | # log '' 54 | 55 | # api.send_msg_2buddy 2967661734, "Hello, world!#{Math.random()}" , auth_opts, (ret,e)-> 56 | # log "buddy send ret:",ret 57 | 58 | api.send_msg_2group 3600594460, "庆贺一郎 #{new Date()}" , auth_opts, (ret,e)-> 59 | log "group send ret:",ret 60 | 61 | # api.get_discuss_list auth_opts, (ret, e) -> 62 | # log "get_discuss_list: #{jsons ret}" 63 | # log '' 64 | 65 | # api.get_discuss_member 979158546, auth_opts, (ret, e) -> 66 | # log "get_discuss_member: #{jsons ret}" 67 | # log "" 68 | 69 | # api.send_msg_2discuss 979158546, "hello", auth_opts, (ret, e) -> 70 | # log "send_msg_2discuss: #{jsons ret}" 71 | # log "" 72 | 73 | 74 | test_api() 75 | -------------------------------------------------------------------------------- /tests/bot.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | # qqbot相关的测试代码 3 | 4 | log = new (require 'log')('debug') 5 | jsons = JSON.stringify 6 | 7 | 8 | api = require "../src/qqapi" 9 | QQBot = require "../src/qqbot" 10 | defaults = require '../src/defaults' 11 | 12 | config = require '../config' 13 | 14 | 15 | # 设置登录信息 16 | cookies = defaults.data 'cookie' 17 | auth_info = defaults.data 'auth' 18 | 19 | 20 | bot = new QQBot(cookies,auth_info,config) 21 | group = null 22 | 23 | 24 | bot.listen_group "qqbot群" , (_group,error)-> 25 | 26 | log.info "enter long poll mode, have fun" 27 | bot.runloop() 28 | 29 | group = _group 30 | group.on_message (content ,send, robot, message)-> 31 | log.info 'received',content 32 | if content.match /^wow/ 33 | send 'mom' 34 | -------------------------------------------------------------------------------- /tests/encrypt.js: -------------------------------------------------------------------------------- 1 | b = function(b, i) { 2 | this.s = b || 0; 3 | this.e = i || 0 4 | } 5 | 6 | P = function(i, a) { 7 | var j = []; 8 | j[0] = i >> 24 & 255; 9 | j[1] = i >> 16 & 255; 10 | j[2] = i >> 8 & 255; 11 | j[3] = i & 255; 12 | for (var s = [], e = 0; e < a.length; ++e) s.push(a.charCodeAt(e)); 13 | e = []; 14 | for (e.push(new b(0, s.length - 1)); e.length > 0;) { 15 | var c = e.pop(); 16 | if (!(c.s >= c.e || c.s < 0 || c.e >= s.length)) 17 | if (c.s + 1 == c.e) { 18 | if (s[c.s] > s[c.e]) { 19 | var J = s[c.s]; 20 | s[c.s] = s[c.e]; 21 | s[c.e] = J 22 | } 23 | } else { 24 | for (var J = c.s, l = c.e, f = s[c.s]; c.s < c.e;) { 25 | for (; c.s < c.e && s[c.e] >= f;) c.e--, j[0] = j[0] + 3 & 255; 26 | c.s < c.e && (s[c.s] = s[c.e], c.s++, j[1] = j[1] * 13 + 43 & 255); 27 | for (; c.s < c.e && s[c.s] <= f;) c.s++, j[2] = j[2] - 3 & 255; 28 | c.s < c.e && (s[c.e] = s[c.s], c.e--, j[3] = (j[0] ^ j[1] ^ j[2] ^ j[3] + 1) & 255) 29 | } 30 | s[c.s] = f; 31 | e.push(new b(J, c.s - 1)); 32 | e.push(new b(c.s + 1, l)) 33 | } 34 | } 35 | s = ["0", "1", "2", "3", "4", 36 | "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" 37 | ]; 38 | e = ""; 39 | for (c = 0; c < j.length; c++) e += s[j[c] >> 4 & 15], e += s[j[c] & 15]; 40 | return e 41 | } 42 | 43 | console.log( P("aaaaasdfdf","bbxxxxxff") ) 44 | -------------------------------------------------------------------------------- /tests/func_args.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | 4 | # 2 ways to call it 5 | # url, params, callback or 6 | # url, callback 7 | # 8 | http_get = (args...) -> 9 | [url,params,callback] = args 10 | [params,callback] = [null,params] unless callback 11 | console.log url 12 | console.log params 13 | console.log callback 14 | 15 | 16 | 17 | http_get(1,2,3) -------------------------------------------------------------------------------- /tests/leak.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | log = new (require 'log')('debug') 4 | 5 | class Group 6 | on: (@callback)-> 7 | dispatch: (msg)-> 8 | log.info 'dispatch',msg 9 | @callback(msg) if @callback 10 | cb: -> 11 | @callback 12 | 13 | class Bot 14 | constructor:(@list = [])-> 15 | setInterval => 16 | o('bbb' ) for o in @list 17 | ,500 18 | 19 | 20 | listen_group : (name , callback) -> 21 | group = new Group() 22 | @list.push group.dispatch 23 | callback group 24 | 25 | 26 | ### DEMO1 27 | bot = new Bot() 28 | # ggg = null 29 | bot.listen_group 'group', (group)-> 30 | group.on (msg)-> 31 | log.info '- received',msg 32 | group.dispatch 'aaa' 33 | 34 | k = group.dispatch 35 | k('single') 36 | # ggg = group 37 | ### 38 | 39 | # ggg.dispatch 'ccc' 40 | # 41 | # setInterval -> 42 | # ggg.dispatch 'ddd' 43 | # ,500 44 | 45 | ###期待 46 | dispatch 47 | - receive 48 | dispatch 49 | - receive 50 | 51 | 实际结果 52 | dispatch only 53 | 54 | 原因猜测 55 | - js中没class概念 传给list的是个函数,破坏了里面的 this.xx 的概念 56 | 57 | ### 58 | 59 | 60 | 61 | # DEMO2 62 | group = new Group() 63 | group.on (msg)-> 64 | log.info '- received',msg 65 | 66 | group.dispatch 'kk' 67 | 68 | # obj = group 69 | # method = group.dispatch 70 | 71 | group['dispatch'] 'str' 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /tests/login.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | # 登录相关的测试代码 4 | 5 | int = (v) -> parseInt v 6 | log = console.log 7 | auth = require "../src/qqauth" 8 | api = require "../src/qqapi" 9 | jsons = JSON.stringify 10 | defaults = require '../src/defaults' 11 | 12 | 13 | config = require '../config' 14 | qq = config.account 15 | pass = config.password 16 | 17 | prompt = (title, callback) -> 18 | process.stdin.resume() 19 | process.stdout.write(title) 20 | process.stdin.on "data", (data) -> 21 | data = data.toString().trim() 22 | # 过滤无效内容 23 | if data 24 | callback data 25 | process.stdin.pause() 26 | # control + d to end 27 | process.stdin.on 'end', -> 28 | process.stdout.write('end') 29 | callback() 30 | 31 | test_check_qq = -> 32 | auth.check_qq_verify '123774072' , (result) -> 33 | log int result[0] 34 | log result[1] 35 | log result[2] 36 | 37 | 38 | test_encode_password = -> 39 | log auth.encode_password(pass,"!PYL",'\\x00\\x00\\x00\\x00\\x07\\x60\\xa4\\x78') 40 | # should equal to 7BB648B45C561A5F986DECCD94644A3E 41 | 42 | # 需要验证码的登录 43 | test_encode_password2 = -> 44 | log auth.encode_password(pass,'zkmm','\\x00\\x00\\x00\\x00\\xa5\\x13\\xed\\x18') 45 | # should equal to F16B5C4EBE52641313403CB93C0FF569 46 | 47 | test_login_full = -> 48 | 49 | log "验证帐号..." 50 | auth.check_qq_verify qq , (result) -> 51 | # log "验证帐号:", result 52 | is_need_verify_code = int result[0] 53 | verify_code = result[1] 54 | bits = result[2] 55 | 56 | #TODO: login_next 异步破坏了代码的结构性,这块如何优化 57 | if is_need_verify_code 58 | log "需要验证码...获取中..." 59 | auth.get_verify_code qq, config.host, config.port, (error) -> 60 | 61 | require('child_process').exec 'open tmp' 62 | log "http://#{config.host}:#{config.port}" 63 | prompt "输入验证码:" , (code) -> 64 | auth.finish_verify_code() 65 | verify_code = code 66 | log '验证码:' , verify_code 67 | new_pass = auth.encode_password(pass,verify_code , bits ) 68 | login_next( qq , new_pass , verify_code) 69 | else 70 | new_pass = auth.encode_password(pass,verify_code , bits ) 71 | login_next( qq , new_pass , verify_code) 72 | 73 | login_next = (qq , encoded_pass , verify_code)-> 74 | log "开始登录1 密码校验" 75 | auth.login_step1 qq, encoded_pass , verify_code , (ret)-> 76 | log '登录结果' 77 | log ret 78 | return unless ret[2].match /^http/ 79 | 80 | log "开始登录2 cookie获取" 81 | auth.login_step2 ret[2] , (ret) -> 82 | log "开始登录3 token 获取" 83 | auth.login_token (ret,client_id,ptwebqq) -> 84 | if ret.retcode == 0 85 | log '登录成功' 86 | # 保存信息 87 | api.cookies( auth.cookies() ) 88 | api.defaults 'psessionid' , ret.result.psessionid 89 | api.defaults 'clientid' , client_id 90 | api.defaults 'ptwebqq' , ptwebqq 91 | api.defaults 'uin' , ret.result.uin 92 | api.defaults 'vfwebqq' , ret.result.vfwebqq 93 | api.defaults_save() 94 | 95 | after_logined() 96 | 97 | else 98 | log "登录失败" 99 | log ret 100 | 101 | 102 | after_logined = -> 103 | 104 | psessionid = api.defaults 'psessionid' 105 | clientid = api.defaults 'clientid' 106 | ptwebqq = api.defaults 'ptwebqq' 107 | uin = api.defaults 'uin' 108 | vfwebqq = api.defaults 'vfwebqq' 109 | 110 | auth_opts ={ 111 | psessionid 112 | clientid 113 | ptwebqq 114 | uin 115 | vfwebqq 116 | } 117 | 118 | log "轮训" 119 | api.long_poll auth_opts , (ret)-> 120 | log jsons ret 121 | 122 | 123 | test_login_token = -> 124 | auth.post (ret) -> log ret 125 | 126 | 127 | test_get_verify_code = -> 128 | auth.get_verify_code qq, config.host, config.port, (error) -> 129 | log 'oh yeah' 130 | 131 | 132 | test_after_login = -> 133 | api.defaults_read() 134 | after_logined() 135 | 136 | 137 | test_login_on_authmodule = -> 138 | auth.login config , (cookies,auth_info)-> 139 | log(auth_info) 140 | defaults.data 'auth' , auth_info 141 | defaults.data 'cookie', cookies 142 | defaults.save() 143 | api.cookies( cookies ) 144 | api.long_poll auth_info , (ret)-> 145 | log jsons ret 146 | 147 | 148 | 149 | # test_check_qq() 150 | # test_encode_password2() 151 | 152 | # test_login_token() 153 | 154 | # test_get_verify_code() 155 | 156 | # test_get_list() 157 | 158 | # test_login_full() 159 | 160 | # test_after_login() 161 | 162 | 163 | test_login_on_authmodule() 164 | -------------------------------------------------------------------------------- /tests/simple.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | log = console.log 3 | prompt = (title, callback) -> 4 | process.stdin.resume() 5 | process.stdout.write(title) 6 | process.stdin.once "data", (data) -> 7 | callback data.toString().trim() 8 | process.stdin.pause() 9 | # control + d to end 10 | process.stdin.on 'end', -> 11 | process.stdout.write('end') 12 | callback() 13 | 14 | 15 | test_prompt = -> 16 | log "hello" 17 | prompt "input something", (content)-> 18 | log content 19 | 20 | log "end" 21 | 22 | 23 | test1 = -> 24 | log 'begin' 25 | k = 26 | a:10 27 | b:20 28 | c:30 29 | 30 | for i,v of k 31 | log i , v 32 | 33 | test2 = -> 34 | # 官方的解释 大概 35 | # for in 用在 array 36 | # for of 用在 obj 37 | 38 | # 测试 for of / for in 39 | # for i,v in [10,2,3] 40 | # log i,v 41 | log v for v in [10,2,3] 42 | 43 | # log "kk" 44 | log k,v for k,v of {a:10,b:20} 45 | # log k,v 46 | 47 | test3 =-> 48 | QQBot = require '../src/qqbot' 49 | bot = new QQBot(100) 50 | bot.save_buddy_info({buddy:100000}) 51 | log bot 52 | 53 | test4 =-> 54 | # 没法在 =-> 方法中调用 @方法 55 | # 在callback 里调用方法需要用 => 56 | class OOO 57 | constructor:(@name)-> 58 | ask:-> 59 | log "ask",@name 60 | test() 61 | test = -> 62 | log "test" 63 | ask2:-> 64 | log "ask2" 65 | setTimeout => 66 | @ask() 67 | ,500 68 | # test2() 69 | test2 = -> 70 | log "test2" 71 | test() 72 | 73 | v = new OOO('名字') 74 | v.ask2() 75 | log v.name 76 | 77 | 78 | test5 =-> 79 | #测试 npm依赖 能否互相 同层级调用 80 | # ln -s ../../hubot-qq . 调用了 yaml 81 | test = require 'hubot-qq' 82 | log test 83 | 84 | 85 | test6 =-> 86 | #测试 export.xx 在包内如何获取 87 | exports.xx =-> log "xxxxx" 88 | exports.xx() 89 | 90 | test7 =-> 91 | # 回调中缩进 似乎可以和父保持一直 92 | log "hello" 93 | setTimeout -> 94 | log "next" 95 | ,300 96 | # test7() 97 | 98 | # setTimeout -> 99 | # log 'test' 100 | # ,500 101 | 102 | 103 | test8 =-> 104 | # 似乎无法在自己执行中传入stdin 105 | # process.stdin.resume() 106 | process.stdin.on "data", (data) -> 107 | data = data.toString().trim() 108 | console.log 'received: ', data 109 | process.stdin.on 'end', -> 110 | console.log 'end' 111 | 112 | Readable = require('stream').Readable 113 | rs = new Readable 114 | rs.push('beep ') 115 | rs.push('boop\n') 116 | rs.push(null) 117 | 118 | # rs.pipe(process.stdin) 119 | process.stdin.resume() 120 | 121 | 122 | test8() 123 | -------------------------------------------------------------------------------- /tmp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | --------------------------------------------------------------------------------