├── README.md ├── node-im ├── README ├── TimRestApiGear.js ├── TimSendGroupMsgPressTest.js ├── config │ ├── config.js │ └── sig_config.js ├── express-im.js ├── image │ ├── 01.png │ ├── 02.png │ ├── 03.jpg │ ├── 04.png │ └── 05.png ├── lib │ ├── TimGenerateSig.js │ ├── TimRestApi.js │ └── path │ │ ├── private_key │ │ └── public_key └── package-lock.json └── wx-tencent-im ├── app.js ├── app.json ├── app.wxss ├── pages ├── chat │ ├── chat.js │ ├── chat.json │ ├── chat.wxml │ └── chat.wxss └── index │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── project.config.json └── utils ├── base64.js ├── encrypt.js ├── im_handler.js ├── rsa.js ├── tea.js ├── tls.js ├── util.js └── webim_wx.js /README.md: -------------------------------------------------------------------------------- 1 | ## 微信小程序接入腾讯云 IM 即时通讯样例代码介绍 2 | 3 | 由于项目需求需要在小程序中接入腾讯云 IM 即时通讯,主要是需要实现一对一单聊的功能,我主要做的是 Java 开发,这个样例是替我们前端去爬坑写的 demo,所以代码较为简洁,适合拷贝过去修改后直接使用,样例实现了两个页面,一个是最近会话列表展示,另一个是好友会话页面展示,具体截图如下: 4 | 5 | ### 最近会话列表 6 | 7 | ![](https://raw.githubusercontent.com/SQDYY/wx-tencent-im/master/node-im/image/01.png) 8 | 9 | ### 好友会话页面 10 | 11 | ![](https://raw.githubusercontent.com/SQDYY/wx-tencent-im/master/node-im/image/02.png) 12 | 13 | --- 14 | 15 | ## 如何使用 16 | 17 | 样例代码包含两个项目,`node-im` 和 `wx-tencent-im`。下面会逐一介绍。 18 | 19 | ### node-im 20 | 21 | 使用腾讯云 IM 时需要通过腾讯云通讯提供的一系列 REST API 来管理你的应用,所以后端需要实现一些接口用于测试和使用,完整接口文档参考 [服务端集成指引](https://cloud.tencent.com/document/product/269/4029)。 22 | 23 | 为了快速将样例跑起来,我直接使用了 node.js 来调用腾讯云通讯提供的 REST API,要启动 `node-im` 之前,你需要 install 两个 npm 包: 24 | 25 | ``` node 26 | cnpm install expres 27 | cnpm install tls-sig-api 28 | ``` 29 | 30 | 实际对接时,后端根据自己的开发语言去进行集成,有 3 个接口是必须实现的: 31 | 32 | 1. 通过 identifier 获取 sig ([通过私钥和公钥生成,保存 180 天](https://cloud.tencent.com/document/product/269/1510)), 用于前端登录 im 鉴权。 33 | 2. 将用户导入到你的腾讯云 im 应用中([独立模式帐号导入](https://cloud.tencent.com/document/product/269/1608))。 34 | 3. 双向绑定好友关系,绑定后才能一对一通讯([添加好友](https://cloud.tencent.com/document/product/269/1643))。 35 | 36 | 我在 `node-im` 中提供了这 3 个接口,另外还实现了一个消息发送的接口,方便用于测试,提供接口的 url 都可以在 `express-im.js` 文件中看到,启动服务端之前,需要修改 `config.js` 和 `sig_config.js` 的配置信息,以及公钥和私钥的文件路径,这些配置信息和文件在你创建腾讯云通讯 app 应用在应用配置里面得到。 37 | 38 | 配置信息修改完毕后,执行以下代码即可启动服务端: 39 | 40 | ``` node 41 | node express-im.js 42 | ``` 43 | 44 | ### wx-tencent-im 启动 45 | 46 | 这里放置的是小程序的源码,首先介绍如何将程序跑起来: 47 | 48 | 根据官方文档的提示,首先我们需要让用户登录腾讯云 IM 应用,在登录之前,我们需要初始化一些必要参数,这些参数都在 `app.js` 中可以看到: 49 | 50 | ``` 51 | App({ 52 | data: { 53 | im: { 54 | sdkAppID: 1400150342, // 用户标识接入 SDK 的应用 ID,必填 55 | accountType: 36862, // 帐号体系集成中的 accountType,必填 56 | accountMode: 0, //帐号模式,0 - 独立模式 1 - 托管模式 57 | imId: null, // 用户的 id 58 | imName: null, // 用户的 im 名称 59 | imAvatarUrl: null, // 用户的 im 头像 url 60 | userSig: null // 用户通过 imId 向后台申请的签名值 sig 61 | } 62 | } 63 | }) 64 | ``` 65 | 66 | 这里的 imId,imName,imAvatarUrl 可以取 userInfo 的参数。然后将 imId 发送给服务端获取 userSig 的值,为了解决小程序异步问题,我将获取参数的方法封装在了 initImParams 方法中。 67 | 68 | 修改配置信息完毕后,重新编译一下,程序应该会报 [70013错误码](https://cloud.tencent.com/document/product/269/1671),这是因为你还未将用户导入到你的腾讯云 im 应用中,可以通过 Postman 先制造假数据: 69 | 70 | ![](https://raw.githubusercontent.com/SQDYY/wx-tencent-im/master/node-im/image/03.jpg) 71 | 72 | 之后重新编译即可,为了能够看到数据,你需要使用 Postman 多创建几个用户,然后与当前账号绑定好友关系,然后调用聊天接口发送消息: 73 | 74 | ![](https://raw.githubusercontent.com/SQDYY/wx-tencent-im/master/node-im/image/04.png) 75 | 76 | ![](https://raw.githubusercontent.com/SQDYY/wx-tencent-im/master/node-im/image/05.png) 77 | -------------------------------------------------------------------------------- /node-im/README: -------------------------------------------------------------------------------- 1 | 工具目录结构: 2 | |-- README 3 | |-- TimRestApiGear.js 4 | |-- TimSendGroupMsgPressTest.js 5 | |-- config 6 | | |-- config.js 7 | `-- lib 8 | |-- TimGenerateSig.js 9 | |-- TimRestApi.js 10 | 11 | config.js 为该工具所需APP基础配置信息。 12 | 13 | TimRestApiGear.js 为使用接口示例工具。 14 | 15 | TimRestApi.js 为接口具体实现; 16 | TimGenerateSig.js 用来生成usersig; 17 | TimSendGroupMsgPressTest.js 为群内发送消息压力测试脚本。 18 | 19 | 20 | 1.集成说明 21 | 支持独立模式 22 | 配置config.js文件,其中: 23 | identifier 为APP管理者账户; 24 | privateKey 为本地私钥位置; 25 | expireAfter 公钥有效时常,不填默认一个月 26 | 执行node TimRestApiGear.js 可看到接口示例工具访问命令(用法)。 27 | 28 | 2.压力测试脚本使用说明 29 | 该工具主要测试在群内发送消息时,客户端接收消息能力; 30 | 执行node TimSendGroupMsgPressTest.js 可看到压测脚本工具访问命令(用法); 31 | 压测前需要确保群组存在,并且群内人数尽可能少,减少其他因素影响。 32 | 33 | 34 | 35 | that.setData({ 36 | contactList: [ 37 | { 38 | "To_Account": "chenchen123", 39 | "C2cNick": "chenchen123", 40 | "C2cImage": app.data.imageUrl + "/2018/10/16/c591c60f65d0b0ff!400x400_big.jpg", 41 | "MsgTimeStamp": "一小时前", 42 | "MsgShow": "在吗1", 43 | "UnreadMsgCount": 3 44 | }, 45 | { 46 | "To_Account": "test03", 47 | "C2cNick": "test03", 48 | "C2cImage": app.data.imageUrl + "/2018/10/16/5bf8c76d57e24b3c!400x400_big.jpg", 49 | "MsgTimeStamp": "一小时前", 50 | "MsgShow": "在吗1", 51 | "UnreadMsgCount": 4 52 | }, 53 | { 54 | "To_Account": "test05", 55 | "C2cNick": "test05", 56 | "C2cImage": app.data.imageUrl + "/2018/10/16/f4c9decfecb3453799954bdac201c809!400x400.jpeg", 57 | "MsgTimeStamp": "一小时前", 58 | "MsgShow": "在吗1", 59 | "UnreadMsgCount": 5 60 | }, 61 | ] 62 | }) 63 | 64 | // index.js 65 | var util = require('../../utils/util.js'); // 转换时间插件 66 | var im = require('../../utils/webim_wx.js'); // 腾讯云 im 包 67 | var imhandler = require('../../utils/im_handler.js'); // 这个是所有 im 事件的 js 68 | 69 | // 获取应用实例 70 | const app = getApp() 71 | 72 | Page({ 73 | data: { 74 | isNoData: false, // isNoData 用于判断是否无数据列表,然后页面做出无数据列表的反应 TODO 效果未实现 75 | /** 76 | * 会话列表(结构定义如下): 77 | * friendId 78 | * friendName 79 | * friendAvatarUrl 80 | * msgTime 81 | * msg 82 | * unreadMsgCount 83 | */ 84 | contactList: [] 85 | }, 86 | onShow: function () { 87 | var that = this; 88 | wx.showLoading() 89 | // 会话列表所需参数初始化 需将当前会话好友数据清空 90 | imhandler.init({ 91 | accountMode: app.data.im.accountMode, 92 | accountType: app.data.im.accountType, 93 | sdkAppID: app.data.im.sdkAppID, 94 | selType: im.SESSION_TYPE.C2C, 95 | imId: app.data.im.imId, 96 | imName: app.data.im.imName, 97 | imAvatarUrl: app.data.im.imAvatarUrl, 98 | friendId: null, 99 | friendName: null, 100 | friendAvatarUrl: null, 101 | contactListThat: that, 102 | chatThat: null 103 | }) 104 | app.initImParams(function cbOk() { 105 | // 检查是否登录返回 true 和 false,不登录则重新登录 106 | if (im.checkLogin()) { 107 | that.initRecentContactList(); 108 | // 初始化最近会话的消息未读数(监听新消息事件) 109 | im.syncMsgs(imhandler.onMsgNotify()); 110 | } else { 111 | imhandler.login(that, app, function () { 112 | that.initRecentContactList(); 113 | // 初始化最近会话的消息未读数(监听新消息事件) 114 | im.syncMsgs(imhandler.onMsgNotify()); 115 | }); 116 | } 117 | wx.hideLoading() 118 | }) 119 | }, 120 | /** 121 | * 拉取最近联系人列表 122 | */ 123 | initRecentContactList: function () { 124 | console.log("[index.js]:[function initRecentContactList]: 开始拉取最近联系人列表"); 125 | var that = this; 126 | // 真正获取会话列表的方法 count: 最近的会话数 ,最大可设置为 100 只获取有价值数据 127 | im.getRecentContactList({ 'Count': 10 }, function (resp) { 128 | if (resp.SessionItem && resp.SessionItem.length > 0) { 129 | var contactList = resp.SessionItem.map((item, index) => { 130 | return { 131 | "friendId": item.To_Account, 132 | "friendName": item.C2cNick, 133 | "friendAvatarUrl": item.C2cImage, 134 | "msgTime": util.getDateDiff(item.MsgTimeStamp * 1000), 135 | "msg": item.MsgShow, 136 | "unreadMsgCount": item.UnreadMsgCount 137 | } 138 | }) 139 | // 设置联系人列表 140 | that.setData({ 141 | contactList: contactList, 142 | isNoData: true 143 | }) 144 | that.updateUnread() 145 | } else { 146 | that.setData({ 147 | isNoData: false, 148 | }) 149 | } 150 | }) 151 | console.log("[index.js]:[function initRecentContactList]: 成功拉取最近联系人列表"); 152 | }, 153 | /** 154 | * 更新未读消息数 155 | */ 156 | updateUnread: function () { 157 | var that = this 158 | // 还需要获取未读消息数 159 | var sessionMap = im.MsgStore.sessMap(); 160 | var contactList = that.data.contactList 161 | for (var i in sessionMap) { 162 | var session = sessionMap[i] 163 | if (session.unread() > 0) { 164 | contactList = contactList.map((item, index) => { 165 | if (item.friendId === session.id()) { 166 | item.unreadMsgCount = session.unread() 167 | } 168 | return item; 169 | }) 170 | } 171 | } 172 | // 设置联系人列表 173 | that.setData({ 174 | contactList: contactList 175 | }) 176 | }, 177 | /** 178 | * go chat.wxml 179 | */ 180 | linkChat: function(e) { 181 | wx.navigateTo({ 182 | url: '/pages/chat/chat?friendId=' + e.currentTarget.dataset.id 183 | + '&friendName=' + e.currentTarget.dataset.name 184 | + '&friendAvatarUrl=' + e.currentTarget.dataset.image, 185 | }) 186 | } 187 | }) 188 | -------------------------------------------------------------------------------- /node-im/TimRestApiGear.js: -------------------------------------------------------------------------------- 1 | var config = require('./config/config.js'); 2 | var TimRestAPI = require('./lib/TimRestApi.js'); 3 | var send_group_msg = function(api, serviceName, commandName, dataArray) { 4 | var i = 0; 5 | var msgFrom = dataArray[i++]; 6 | var groupId = dataArray[i++]; 7 | var msgText = dataArray[i++]; 8 | var reqBody = { 9 | GroupId: groupId, 10 | MsgBody: [{ 11 | MsgType: "TIMTextElem", 12 | From_Account: msgFrom, 13 | MsgContent: { 14 | Text: msgText 15 | } 16 | }] 17 | }; 18 | api.request(serviceName, commandName, reqBody, 19 | function(err, data) { 20 | if (err) { 21 | console.log(err); 22 | return; 23 | } 24 | console.log(data); 25 | }); 26 | } 27 | 28 | var send_msg = function(api, serviceName, commandName, dataArray) { 29 | var i = 0; 30 | var fromId = dataArray[i++]; 31 | var toId = dataArray[i++]; 32 | var msgText = dataArray[i++]; 33 | 34 | var reqBody = { 35 | "To_Account": toId, 36 | //消息接收者 37 | "From_Account": fromId, 38 | //选填字段 39 | "MsgRandom": 123, 40 | //消息随机数 41 | "MsgBody": [{ 42 | "MsgType": "TIMTextElem", 43 | //文本消息类型 44 | "MsgContent": { 45 | "Text": msgText //具体文本消息 46 | } 47 | }] 48 | } 49 | api.request(serviceName, commandName, reqBody, 50 | function(err, data) { 51 | if (err) { 52 | console.log(err); 53 | return; 54 | } 55 | console.log(data); 56 | }); 57 | } 58 | 59 | var get_group_info = function(api, serviceName, commandName, dataArray) { 60 | var groupId = dataArray[0]; 61 | var reqBody = { 62 | "GroupIdList": [groupId] 63 | } 64 | api.request(serviceName, commandName, reqBody, 65 | function(err, data) { 66 | if (err) { 67 | console.log(err); 68 | return; 69 | } 70 | console.log(data); 71 | }); 72 | } 73 | 74 | function begin_process() { 75 | if (process.argv.length < 4) { 76 | console.log("usage:"); 77 | console.log("node " + process.argv[1] + " msg_interface.js (server_name) (command) args...eg:"); 78 | console.log("node " + process.argv[1] + " msg_interface.js openim sendmsg (account_id) (receiver) (text_content) 单发消息"); 79 | console.log("node " + process.argv[1] + " msg_interface.js group_open_http_svc send_group_msg (account_id) (group_id) (text_content) 群组中发送普通消息"); 80 | console.log("node " + process.argv[1] + " msg_interface.js group_open_http_svc get_group_info (group_id) 获取群组信息"); 81 | console.log("注:"); 82 | console.log("默认从配置文件config/config.js读取配置信息,其中:"); 83 | console.log("identifier 为APP管理员账户"); 84 | console.log('private_pem_path 为独立模式下私钥本地路径'); 85 | return - 1; 86 | } 87 | var serviceName = process.argv[2]; 88 | var commandName = process.argv[3]; 89 | var commandKey = serviceName + "." + commandName; 90 | 91 | var commandName; 92 | var commadKey; 93 | 94 | // command dictionary 95 | var commandArray = { 96 | "openim.sendmsg": send_msg, 97 | "group_open_http_svc.send_group_msg": send_group_msg, 98 | "group_open_http_svc.get_group_info": get_group_info 99 | }; 100 | 101 | if (!commandArray.hasOwnProperty(commandKey)) { 102 | console.log(commandKey); 103 | console.log("service_name or command_name error"); 104 | return; 105 | } 106 | 107 | var dataArray = new Array(); 108 | for (var i = 4; i < process.argv.length; i++) { 109 | dataArray[i - 4] = process.argv[i]; 110 | } 111 | 112 | var api = new TimRestAPI(config); 113 | api.init(function(err, data) { 114 | if (err) { 115 | // deal error 116 | console.log(err); 117 | return; 118 | } 119 | commandValue = commandArray[commandKey]; 120 | commandValue(api, serviceName, commandName, dataArray); 121 | }); 122 | } 123 | 124 | begin_process(); 125 | 126 | -------------------------------------------------------------------------------- /node-im/TimSendGroupMsgPressTest.js: -------------------------------------------------------------------------------- 1 | var config = require('./config/config.js'); 2 | var TimRestAPI = require('./lib/TimRestApi.js'); 3 | var util = require('util'); 4 | 5 | function begin_press_test(api, groupId, fromId, sendInterval, totalCount) { 6 | var sendedCount = 0; //count of package already send 7 | var cycleNum = 1; //loop manage num 8 | var increaseSeq = 1; //increase num, use for print local seq 9 | var beginTime = (new Date).getTime(); 10 | var failNum = 0; //rsp return fail num 11 | var errNum = 0; // exception error 12 | var reqBody = { 13 | "GroupIdList": [ //groupid list 14 | groupId] 15 | } 16 | api.request("group_open_http_svc", "get_group_info", reqBody, 17 | function(err, data) // get seq before send_group_msg 18 | { 19 | if (err) { 20 | console.log(err); 21 | return; 22 | } 23 | if (data["ActionStatus"] != "OK") { 24 | console.log("fail at get_group_info before group_msg request\n"); 25 | var strData = JSON.stringify(data); 26 | console.log(strData); 27 | return; 28 | } 29 | var beforeSeq = data["GroupInfo"][0].NextMsgSeq; 30 | var localSeq = increaseSeq; 31 | var timmer = setInterval(doRequest); 32 | function doRequest() { 33 | if (sendedCount++>=totalCount) { 34 | clearInterval(timmer); 35 | cycleNum--; 36 | return; 37 | } 38 | cycleNum++; 39 | var reqBody = { 40 | GroupId: groupId, 41 | MsgBody: [{ 42 | MsgType: "TIMTextElem", 43 | From_Account: fromId, 44 | MsgContent: { 45 | Text: util.format("hello, local seq = %d", localSeq) 46 | } 47 | }] 48 | }; 49 | var startTime = (new Date).getTime(); 50 | api.request("group_open_http_svc", "send_group_msg", reqBody, 51 | function(err, data) { 52 | if (err) { 53 | console.log(err); 54 | errNum++; 55 | return; 56 | } 57 | 58 | if (data["ActionStatus"] != "OK") { 59 | failNum++; 60 | } 61 | cycleNum--; 62 | localSeq = increaseSeq++; 63 | if (!err) { 64 | var strData = JSON.stringify(data); 65 | console.log('local seq = %d, timecost = %d, response body: %s', localSeq, ((new Date).getTime() - startTime), strData); 66 | } 67 | if (cycleNum == 0) { 68 | console.log('total cost time: %d ms', (new Date).getTime() - beginTime); 69 | 70 | var reqBody = { 71 | "GroupIdList": [groupId] 72 | } 73 | api.request("group_open_http_svc", "get_group_info", reqBody, 74 | function(err, data) { 75 | if (err) { 76 | console.log(err); 77 | return; 78 | } 79 | if (data["ActionStatus"] != "OK") { 80 | console.log("fail at get_group_info after group_msg request\n"); 81 | var strData = JSON.stringify(data); 82 | console.log(strData); 83 | return; 84 | } 85 | endSeq = data["GroupInfo"][0].NextMsgSeq; 86 | console.log('msg seq before: %d, after: %d', beforeSeq, endSeq); 87 | console.log('successNum is %s, totalNum is %s, errNum is %s, failNum is %s', totalCount - errNum - failNum, totalCount, errNum, failNum); 88 | }); 89 | }; 90 | }); 91 | } 92 | }); 93 | } 94 | 95 | function begin_process() { 96 | if (process.argv.length < 6) { 97 | console.log("usage:"); 98 | console.log("node " + process.argv[1] + " (groupid) (from_id) (speed) (totalCount)"); 99 | console.log(" groupid 群组ID,脚本在该群内发消息进行压测"); 100 | console.log(" from_id 消息发送者的id"); 101 | console.log(" speed 在群内每秒发送消息的条数"); 102 | console.log(" totalCount 脚本将在群内发消息总条数"); 103 | console.log("注: "); 104 | console.log("默认从配置文件config/config.js读取配置信息,其中:"); 105 | console.log("identifier 为APP管理员账户"); 106 | console.log("private_pem_path 为独立模式下私钥本地路径"); 107 | return - 1; 108 | } 109 | var groupId = process.argv[2]; 110 | var fromId = process.argv[3]; 111 | var speed = parseInt(process.argv[4]); 112 | if (speed < 0 || speed > 150) { 113 | console.error('invalid speed (0 < speed < 150)'); 114 | process.exit(0); 115 | } 116 | var sendInterval = 1000 / speed; 117 | 118 | var totalCount = parseInt(process.argv[5]); 119 | var api = new TimRestAPI(config); 120 | api.init(function(err, data) { 121 | if (err) { 122 | //deal error 123 | console.log(err); 124 | return; 125 | } 126 | begin_press_test(api, groupId, fromId, sendInterval, totalCount); 127 | }); 128 | } 129 | 130 | begin_process(); 131 | -------------------------------------------------------------------------------- /node-im/config/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sdkAppid: '1400150342', 3 | version: '201512300000', 4 | identifier: 'admin', 5 | accountType: '36862', 6 | privateKey: '/path/private_key', 7 | expireAfter: 30 * 24 * 3600, 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /node-im/config/sig_config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "sdk_appid": 1400150342, 3 | "expire_after": 180 * 24 * 3600, 4 | "private_key": "../../lib/path/private_key", 5 | "public_key": "../../lib/path/public_key" 6 | } -------------------------------------------------------------------------------- /node-im/express-im.js: -------------------------------------------------------------------------------- 1 | // express 框架 2 | // 后端服务接口参考腾讯 IM REST API 接口列表:https://cloud.tencent.com/document/product/269/1520 3 | var express = require('express'); 4 | var sig = require('tls-sig-api'); 5 | var app = express(); 6 | 7 | // body-parser 模块,用于获取 post 提交的参数(默认支持 x-www-form-urlencoded) 8 | var bodyParser = require("body-parser"); 9 | app.use(bodyParser.urlencoded({ extended: false })); 10 | 11 | // tencent im rest api 12 | var TimRestAPI = require('./lib/TimRestApi.js'); 13 | var config = require('./config/config.js'); 14 | var api = new TimRestAPI(config); 15 | 16 | var sigConfig = require('./config/sig_config.js'); 17 | 18 | /** 19 | * 通过 identifier 获取 sig (通过私钥生成), 用于登录鉴权 20 | * parmas: 21 | * identifier - 用户 id 22 | */ 23 | app.post('/generatedSig', function (req, res) { 24 | var s = new sig.Sig(sigConfig); 25 | res.end(s.genSig(req.body.identifier)); 26 | }) 27 | 28 | /** 29 | * 用户注册(独立模式帐号导入) 30 | * parmas: 31 | * identifier - 用户 id 32 | * nick - 用户昵称 33 | * faceurl - 用户头像 rul 34 | */ 35 | app.post('/importUser', function (req, res) { 36 | api.init(function(err, data) { 37 | if (err) { 38 | console.log(err); 39 | res.end(err); 40 | return; 41 | } 42 | var reqBody = { 43 | "Identifier": req.body.identifier, // 用户名,长度不超过 32 字节 44 | "Nick": req.body.nick, // 用户昵称 45 | "FaceUrl": req.body.faceurl, // 用户头像 URL 46 | "type": 0 // 导入的均为普通账号 47 | } 48 | api.request("im_open_login_svc", "account_import", reqBody, function(err, data) { 49 | if (err) { 50 | res.end(JSON.stringify(err)); 51 | return; 52 | } 53 | res.end(JSON.stringify(data)); 54 | }) 55 | }) 56 | }) 57 | 58 | /** 59 | * 添加好友 60 | * parmas: 61 | * fromAccount - 需要为该 Identifier 添加好友 62 | * toAccount - 好友的 Identifier 63 | * AddSource - 好友来源(前期可填 weixin) 64 | */ 65 | app.post('/addFriend', function (req, res) { 66 | api.init(function(err, data) { 67 | if (err) { 68 | console.log(err); 69 | res.end(err); 70 | return; 71 | } 72 | var reqBody = { 73 | "From_Account": req.body.fromAccount, 74 | "AddFriendItem": [ 75 | { 76 | "To_Account": req.body.toAccount, 77 | "AddSource":"AddSource_Type_" + req.body.sourceType 78 | } 79 | ], 80 | "AddType":"Add_Type_Both", // 默认双向绑定关系 81 | "ForceAddFlags":1 // 强制加好友 82 | } 83 | api.request("sns", "friend_add", reqBody, function(err, data) { 84 | if(err) { 85 | res.end(JSON.stringify(err)); 86 | return; 87 | } 88 | res.end(JSON.stringify(data)); 89 | }) 90 | }) 91 | }) 92 | 93 | /** 94 | * 单发单聊消息 95 | * parmas: 96 | * SyncOtherMachine - 1 消息同步至发送方 2 不将消息同步至发送方 97 | * From_Account - 好友的 Identifier 98 | * AddSource - 好友来源(前期可填 weixin) 99 | */ 100 | app.post('/sendmsg', function (req, res) { 101 | api.init(function(err, data) { 102 | if (err) { 103 | console.log(err); 104 | res.end(err); 105 | return; 106 | } 107 | var reqBody = { 108 | "SyncOtherMachine": 1, //消息同步至发送方 109 | "From_Account": req.body.fromAccount, 110 | "To_Account": req.body.toAccount, 111 | "MsgRandom": 1287657, 112 | "MsgBody": [ 113 | { 114 | "MsgType": req.body.msgType, 115 | "MsgContent": { 116 | "Text": req.body.msgText 117 | } 118 | } 119 | ] 120 | } 121 | api.request("openim", "sendmsg", reqBody, function(err, data) { 122 | if(err) { 123 | res.end(JSON.stringify(err)); 124 | return; 125 | } 126 | res.end(JSON.stringify(data)); 127 | }) 128 | }) 129 | }) 130 | 131 | 132 | 133 | var server = app.listen(8080, function () { 134 | console.log("服务器已启动, 地址是:http://localhost:8080"); 135 | }) 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /node-im/image/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ch-magic-duck/redhole-tencent-im/9a623418a39cda36bed1362ffa45eb58803f52d4/node-im/image/01.png -------------------------------------------------------------------------------- /node-im/image/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ch-magic-duck/redhole-tencent-im/9a623418a39cda36bed1362ffa45eb58803f52d4/node-im/image/02.png -------------------------------------------------------------------------------- /node-im/image/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ch-magic-duck/redhole-tencent-im/9a623418a39cda36bed1362ffa45eb58803f52d4/node-im/image/03.jpg -------------------------------------------------------------------------------- /node-im/image/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ch-magic-duck/redhole-tencent-im/9a623418a39cda36bed1362ffa45eb58803f52d4/node-im/image/04.png -------------------------------------------------------------------------------- /node-im/image/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ch-magic-duck/redhole-tencent-im/9a623418a39cda36bed1362ffa45eb58803f52d4/node-im/image/05.png -------------------------------------------------------------------------------- /node-im/lib/TimGenerateSig.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var zlib = require('zlib'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var base64url = {}; 7 | 8 | base64url.unescape = function(str) { 9 | return (str + Array(5 - str.length % 4)).replace(/_/g, '=').replace(/\-/g, '/').replace(/\*/g, '+'); 10 | }; 11 | 12 | base64url.escape = function(str) { 13 | return str.replace(/\+/g, '*').replace(/\//g, '-').replace(/=/g, '_'); 14 | }; 15 | 16 | base64url.encode = function(str) { 17 | return this.escape(new Buffer(str).toString('base64')); 18 | }; 19 | 20 | base64url.decode = function(str) { 21 | return new Buffer(this.unescape(str), 'base64').toString(); 22 | }; 23 | 24 | var Sig = function(config) { 25 | this.sdkAppid = config.sdkAppid; 26 | this.accountType = config.accountType; 27 | this.identifier = config.identifier; 28 | this.appidAt3rd = config.sdkAppid; 29 | this.expireAfter = (config.expireAfter || 30 * 24 * 3600).toString(); 30 | this.expireUntil = parseInt(Date.now() / 1000) + parseInt(this.expireAfter); 31 | this.privateKey = fs.readFileSync(path.join(__dirname, config.privateKey)).toString(); 32 | }; 33 | 34 | Sig.prototype._genSignContent = function(obj) { 35 | var ret = ''; 36 | for (var i in obj) { 37 | ret += i + ':' + obj[i] + '\n'; 38 | } 39 | return ret; 40 | }; 41 | 42 | Sig.prototype.genSig = function(evalSig, callback) { 43 | var obj = { 44 | 'TLS.appid_at_3rd': this.appidAt3rd, 45 | 'TLS.account_type': this.accountType, 46 | 'TLS.identifier': this.identifier, 47 | 'TLS.sdk_appid': this.sdkAppid, 48 | 'TLS.time': (Math.floor(Date.now() / 1000)).toString(), 49 | 'TLS.expire_after': this.expireAfter 50 | }; 51 | var content = this._genSignContent(obj); 52 | try { 53 | var signer = crypto.createSign('sha256'); 54 | signer.update(content, 'utf8'); 55 | var usrsig = signer.sign(this.privateKey, 'base64'); 56 | } catch(err) { 57 | callback(err); 58 | return; 59 | } 60 | obj['TLS.sig'] = usrsig; 61 | var text = JSON.stringify(obj); 62 | var compressed = zlib.deflateSync(new Buffer(text)).toString('base64'); 63 | evalSig(base64url.escape(compressed), this.expireUntil); 64 | if (callback) { 65 | callback(); 66 | } 67 | }; 68 | 69 | module.exports = Sig; -------------------------------------------------------------------------------- /node-im/lib/TimRestApi.js: -------------------------------------------------------------------------------- 1 | var Sig = require('./TimGenerateSig.js'); 2 | var https = require('https'); 3 | var util = require('util'); 4 | var maxSock = 10000; 5 | var keepAliveAgent = new https.Agent({ 6 | keepAlive: true, 7 | maxSockets: maxSock 8 | }); 9 | 10 | var TimRestAPI = function(config) { 11 | this.sdkAppid = config.sdkAppid; 12 | this.identifier = config.identifier; 13 | this.config = config; 14 | } 15 | 16 | TimRestAPI.prototype.init = function(callback) { 17 | var self = this; 18 | // get usersig 19 | var sig = new Sig(this.config); 20 | sig.genSig(function(usersig, expireUntil) { 21 | self.usersig = usersig; 22 | self.expireUntil = expireUntil; 23 | }, callback); 24 | } 25 | 26 | TimRestAPI.prototype.request = function(serviceName, cmdName, reqBody, callback) { 27 | var self = this; 28 | if (this.expireUntil < (Date.now() / 1000)) { 29 | var sig = new Sig(this.config); 30 | sig.genSig(function(usersig, expireUntil) { 31 | self.usersig = usersig; 32 | self.expireUntil = expireUntil; 33 | }); 34 | } 35 | var urlPath = util.format("/v4/%s/%s?usersig=%s&identifier=%s&sdkappid=%s&contenttype=json", serviceName, cmdName, this.usersig, this.identifier, this.sdkAppid); 36 | var requestArg = { 37 | agent: keepAliveAgent, 38 | host: 'console.tim.qq.com', 39 | method: 'post', 40 | path: urlPath 41 | } 42 | var chunkList = []; 43 | var req = https.request(requestArg, 44 | function(rsp) { 45 | rsp.setEncoding('utf8'); 46 | rsp.on('data', 47 | function(chunk) { 48 | chunkList.push(chunk); 49 | }); 50 | rsp.on('error', 51 | function(err) { 52 | if (callback) { 53 | callback(err); 54 | } 55 | }); 56 | rsp.on('end', 57 | function() { 58 | rspBody = chunkList.join(''); 59 | try { 60 | var rspJsonBody = JSON.parse(rspBody); 61 | } catch(err) { 62 | if (callback) { 63 | callback(err); 64 | } 65 | } 66 | if (callback) { 67 | callback(null, rspJsonBody); 68 | } 69 | }); 70 | }); 71 | req.write(JSON.stringify(reqBody)); 72 | req.end(); 73 | } 74 | 75 | module.exports = TimRestAPI; 76 | -------------------------------------------------------------------------------- /node-im/lib/path/private_key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ch-magic-duck/redhole-tencent-im/9a623418a39cda36bed1362ffa45eb58803f52d4/node-im/lib/path/private_key -------------------------------------------------------------------------------- /node-im/lib/path/public_key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ch-magic-duck/redhole-tencent-im/9a623418a39cda36bed1362ffa45eb58803f52d4/node-im/lib/path/public_key -------------------------------------------------------------------------------- /node-im/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "accepts": { 6 | "version": "1.3.5", 7 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 8 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 9 | "requires": { 10 | "mime-types": "2.1.20", 11 | "negotiator": "0.6.1" 12 | } 13 | }, 14 | "append-field": { 15 | "version": "1.0.0", 16 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", 17 | "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" 18 | }, 19 | "array-flatten": { 20 | "version": "1.1.1", 21 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 22 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 23 | }, 24 | "body-parser": { 25 | "version": "1.18.3", 26 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 27 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 28 | "requires": { 29 | "bytes": "3.0.0", 30 | "content-type": "1.0.4", 31 | "debug": "2.6.9", 32 | "depd": "1.1.2", 33 | "http-errors": "1.6.3", 34 | "iconv-lite": "0.4.23", 35 | "on-finished": "2.3.0", 36 | "qs": "6.5.2", 37 | "raw-body": "2.3.3", 38 | "type-is": "1.6.16" 39 | } 40 | }, 41 | "buffer-from": { 42 | "version": "1.1.1", 43 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 44 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 45 | }, 46 | "busboy": { 47 | "version": "0.2.14", 48 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", 49 | "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", 50 | "requires": { 51 | "dicer": "0.2.5", 52 | "readable-stream": "1.1.14" 53 | } 54 | }, 55 | "bytes": { 56 | "version": "3.0.0", 57 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 58 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 59 | }, 60 | "concat-stream": { 61 | "version": "1.6.2", 62 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 63 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 64 | "requires": { 65 | "buffer-from": "1.1.1", 66 | "inherits": "2.0.3", 67 | "readable-stream": "2.3.6", 68 | "typedarray": "0.0.6" 69 | }, 70 | "dependencies": { 71 | "isarray": { 72 | "version": "1.0.0", 73 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 74 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 75 | }, 76 | "readable-stream": { 77 | "version": "2.3.6", 78 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 79 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 80 | "requires": { 81 | "core-util-is": "1.0.2", 82 | "inherits": "2.0.3", 83 | "isarray": "1.0.0", 84 | "process-nextick-args": "2.0.0", 85 | "safe-buffer": "5.1.2", 86 | "string_decoder": "1.1.1", 87 | "util-deprecate": "1.0.2" 88 | } 89 | }, 90 | "string_decoder": { 91 | "version": "1.1.1", 92 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 93 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 94 | "requires": { 95 | "safe-buffer": "5.1.2" 96 | } 97 | } 98 | } 99 | }, 100 | "content-disposition": { 101 | "version": "0.5.2", 102 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 103 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 104 | }, 105 | "content-type": { 106 | "version": "1.0.4", 107 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 108 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 109 | }, 110 | "cookie": { 111 | "version": "0.3.1", 112 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 113 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 114 | }, 115 | "cookie-parser": { 116 | "version": "1.4.3", 117 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", 118 | "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", 119 | "requires": { 120 | "cookie": "0.3.1", 121 | "cookie-signature": "1.0.6" 122 | } 123 | }, 124 | "cookie-signature": { 125 | "version": "1.0.6", 126 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 127 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 128 | }, 129 | "core-util-is": { 130 | "version": "1.0.2", 131 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 132 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 133 | }, 134 | "debug": { 135 | "version": "2.6.9", 136 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 137 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 138 | "requires": { 139 | "ms": "2.0.0" 140 | } 141 | }, 142 | "depd": { 143 | "version": "1.1.2", 144 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 145 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 146 | }, 147 | "destroy": { 148 | "version": "1.0.4", 149 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 150 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 151 | }, 152 | "dicer": { 153 | "version": "0.2.5", 154 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", 155 | "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", 156 | "requires": { 157 | "readable-stream": "1.1.14", 158 | "streamsearch": "0.1.2" 159 | } 160 | }, 161 | "ee-first": { 162 | "version": "1.1.1", 163 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 164 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 165 | }, 166 | "encodeurl": { 167 | "version": "1.0.2", 168 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 169 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 170 | }, 171 | "escape-html": { 172 | "version": "1.0.3", 173 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 174 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 175 | }, 176 | "etag": { 177 | "version": "1.8.1", 178 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 179 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 180 | }, 181 | "express": { 182 | "version": "4.16.4", 183 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", 184 | "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", 185 | "requires": { 186 | "accepts": "1.3.5", 187 | "array-flatten": "1.1.1", 188 | "body-parser": "1.18.3", 189 | "content-disposition": "0.5.2", 190 | "content-type": "1.0.4", 191 | "cookie": "0.3.1", 192 | "cookie-signature": "1.0.6", 193 | "debug": "2.6.9", 194 | "depd": "1.1.2", 195 | "encodeurl": "1.0.2", 196 | "escape-html": "1.0.3", 197 | "etag": "1.8.1", 198 | "finalhandler": "1.1.1", 199 | "fresh": "0.5.2", 200 | "merge-descriptors": "1.0.1", 201 | "methods": "1.1.2", 202 | "on-finished": "2.3.0", 203 | "parseurl": "1.3.2", 204 | "path-to-regexp": "0.1.7", 205 | "proxy-addr": "2.0.4", 206 | "qs": "6.5.2", 207 | "range-parser": "1.2.0", 208 | "safe-buffer": "5.1.2", 209 | "send": "0.16.2", 210 | "serve-static": "1.13.2", 211 | "setprototypeof": "1.1.0", 212 | "statuses": "1.4.0", 213 | "type-is": "1.6.16", 214 | "utils-merge": "1.0.1", 215 | "vary": "1.1.2" 216 | } 217 | }, 218 | "finalhandler": { 219 | "version": "1.1.1", 220 | "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 221 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 222 | "requires": { 223 | "debug": "2.6.9", 224 | "encodeurl": "1.0.2", 225 | "escape-html": "1.0.3", 226 | "on-finished": "2.3.0", 227 | "parseurl": "1.3.2", 228 | "statuses": "1.4.0", 229 | "unpipe": "1.0.0" 230 | } 231 | }, 232 | "forwarded": { 233 | "version": "0.1.2", 234 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 235 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 236 | }, 237 | "fresh": { 238 | "version": "0.5.2", 239 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 240 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 241 | }, 242 | "http-errors": { 243 | "version": "1.6.3", 244 | "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 245 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 246 | "requires": { 247 | "depd": "1.1.2", 248 | "inherits": "2.0.3", 249 | "setprototypeof": "1.1.0", 250 | "statuses": "1.4.0" 251 | } 252 | }, 253 | "iconv-lite": { 254 | "version": "0.4.23", 255 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 256 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 257 | "requires": { 258 | "safer-buffer": "2.1.2" 259 | } 260 | }, 261 | "inherits": { 262 | "version": "2.0.3", 263 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 264 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 265 | }, 266 | "ipaddr.js": { 267 | "version": "1.8.0", 268 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 269 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" 270 | }, 271 | "isarray": { 272 | "version": "0.0.1", 273 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 274 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 275 | }, 276 | "media-typer": { 277 | "version": "0.3.0", 278 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 279 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 280 | }, 281 | "merge-descriptors": { 282 | "version": "1.0.1", 283 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 284 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 285 | }, 286 | "methods": { 287 | "version": "1.1.2", 288 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 289 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 290 | }, 291 | "mime": { 292 | "version": "1.4.1", 293 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 294 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 295 | }, 296 | "mime-db": { 297 | "version": "1.36.0", 298 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", 299 | "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" 300 | }, 301 | "mime-types": { 302 | "version": "2.1.20", 303 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", 304 | "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", 305 | "requires": { 306 | "mime-db": "1.36.0" 307 | } 308 | }, 309 | "minimist": { 310 | "version": "0.0.8", 311 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 312 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 313 | }, 314 | "mkdirp": { 315 | "version": "0.5.1", 316 | "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 317 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 318 | "requires": { 319 | "minimist": "0.0.8" 320 | } 321 | }, 322 | "ms": { 323 | "version": "2.0.0", 324 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 325 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 326 | }, 327 | "multer": { 328 | "version": "1.4.1", 329 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.1.tgz", 330 | "integrity": "sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw==", 331 | "requires": { 332 | "append-field": "1.0.0", 333 | "busboy": "0.2.14", 334 | "concat-stream": "1.6.2", 335 | "mkdirp": "0.5.1", 336 | "object-assign": "4.1.1", 337 | "on-finished": "2.3.0", 338 | "type-is": "1.6.16", 339 | "xtend": "4.0.1" 340 | } 341 | }, 342 | "negotiator": { 343 | "version": "0.6.1", 344 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 345 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 346 | }, 347 | "object-assign": { 348 | "version": "4.1.1", 349 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 350 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 351 | }, 352 | "on-finished": { 353 | "version": "2.3.0", 354 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 355 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 356 | "requires": { 357 | "ee-first": "1.1.1" 358 | } 359 | }, 360 | "parseurl": { 361 | "version": "1.3.2", 362 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 363 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 364 | }, 365 | "path-to-regexp": { 366 | "version": "0.1.7", 367 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 368 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 369 | }, 370 | "process-nextick-args": { 371 | "version": "2.0.0", 372 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 373 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 374 | }, 375 | "proxy-addr": { 376 | "version": "2.0.4", 377 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 378 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 379 | "requires": { 380 | "forwarded": "0.1.2", 381 | "ipaddr.js": "1.8.0" 382 | } 383 | }, 384 | "qs": { 385 | "version": "6.5.2", 386 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 387 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 388 | }, 389 | "range-parser": { 390 | "version": "1.2.0", 391 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 392 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 393 | }, 394 | "raw-body": { 395 | "version": "2.3.3", 396 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 397 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 398 | "requires": { 399 | "bytes": "3.0.0", 400 | "http-errors": "1.6.3", 401 | "iconv-lite": "0.4.23", 402 | "unpipe": "1.0.0" 403 | } 404 | }, 405 | "readable-stream": { 406 | "version": "1.1.14", 407 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 408 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 409 | "requires": { 410 | "core-util-is": "1.0.2", 411 | "inherits": "2.0.3", 412 | "isarray": "0.0.1", 413 | "string_decoder": "0.10.31" 414 | } 415 | }, 416 | "safe-buffer": { 417 | "version": "5.1.2", 418 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 419 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 420 | }, 421 | "safer-buffer": { 422 | "version": "2.1.2", 423 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 424 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 425 | }, 426 | "send": { 427 | "version": "0.16.2", 428 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 429 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 430 | "requires": { 431 | "debug": "2.6.9", 432 | "depd": "1.1.2", 433 | "destroy": "1.0.4", 434 | "encodeurl": "1.0.2", 435 | "escape-html": "1.0.3", 436 | "etag": "1.8.1", 437 | "fresh": "0.5.2", 438 | "http-errors": "1.6.3", 439 | "mime": "1.4.1", 440 | "ms": "2.0.0", 441 | "on-finished": "2.3.0", 442 | "range-parser": "1.2.0", 443 | "statuses": "1.4.0" 444 | } 445 | }, 446 | "serve-static": { 447 | "version": "1.13.2", 448 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 449 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 450 | "requires": { 451 | "encodeurl": "1.0.2", 452 | "escape-html": "1.0.3", 453 | "parseurl": "1.3.2", 454 | "send": "0.16.2" 455 | } 456 | }, 457 | "setprototypeof": { 458 | "version": "1.1.0", 459 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 460 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 461 | }, 462 | "statuses": { 463 | "version": "1.4.0", 464 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 465 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 466 | }, 467 | "streamsearch": { 468 | "version": "0.1.2", 469 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 470 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" 471 | }, 472 | "string_decoder": { 473 | "version": "0.10.31", 474 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 475 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 476 | }, 477 | "type-is": { 478 | "version": "1.6.16", 479 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 480 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 481 | "requires": { 482 | "media-typer": "0.3.0", 483 | "mime-types": "2.1.20" 484 | } 485 | }, 486 | "typedarray": { 487 | "version": "0.0.6", 488 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 489 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 490 | }, 491 | "unpipe": { 492 | "version": "1.0.0", 493 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 494 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 495 | }, 496 | "util-deprecate": { 497 | "version": "1.0.2", 498 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 499 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 500 | }, 501 | "utils-merge": { 502 | "version": "1.0.1", 503 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 504 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 505 | }, 506 | "vary": { 507 | "version": "1.1.2", 508 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 509 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 510 | }, 511 | "xtend": { 512 | "version": "4.0.1", 513 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 514 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /wx-tencent-im/app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | data: { 4 | im: { 5 | sdkAppID: 1400150342, // 用户标识接入 SDK 的应用 ID,必填 6 | accountType: 36862, // 帐号体系集成中的 accountType,必填 7 | accountMode: 0, //帐号模式,0 - 独立模式 1 - 托管模式 8 | imId: null, // 用户的 id 9 | imName: null, // 用户的 im 名称 10 | imAvatarUrl: null, // 用户的 im 头像 url 11 | userSig: null // 用户通过 imId 向后台申请的签名值 sig 12 | } 13 | }, 14 | onLaunch: function () { 15 | var that = this 16 | // 获取用户信息 17 | wx.getSetting({ 18 | success: res => { 19 | if (res.authSetting['scope.userInfo']) { 20 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 21 | wx.getUserInfo({ 22 | success: res => { 23 | // 可以将 res 发送给后台解码出 unionId 24 | this.globalData.userInfo = res.userInfo 25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 26 | // 所以此处加入 callback 以防止这种情况 27 | if (this.userInfoReadyCallback) { 28 | this.userInfoReadyCallback(res) 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | }) 35 | }, 36 | globalData: { 37 | userInfo: null 38 | }, 39 | /** 40 | * 初始化 im 参数,返回成功回调 41 | */ 42 | initImParams: function (cbOk){ 43 | var that = this 44 | // 登录 初始化 im 参数 45 | // 注意:如果首次使用,后台需要创建【腾讯 im】账号 46 | wx.login({ 47 | success: res => { 48 | var appid = 'wx17bd33e979aa3dba' 49 | var secret = 'd37ab36b1e592674213d8a1afd2195df' 50 | var uri = '?appid=' + appid + '&secret=' + secret + '&js_code=' + res.code + '&grant_type=authorization_code' 51 | var url = 'https://api.weixin.qq.com/sns/jscode2session' + uri 52 | wx.request({ 53 | url: url, method: 'GET', success: res => { 54 | // 通过 openid 获取【腾讯 im】签名值 55 | var generatedSigUrl = 'http://localhost:8080/generatedSig' 56 | var header = { "Content-Type": "application/x-www-form-urlencoded" }; 57 | var data = { "identifier": res.data.openid} 58 | that.data.im.imId = res.data.openid // ocGnM4nO9kZf6WANSo7H5GGBZVk4 59 | wx.request({ 60 | url: generatedSigUrl, header: header, method: "POST", data: data, success: res => { 61 | // 初始化 im 数据 初始化完毕再返回回调 62 | that.data.im.userSig = res.data 63 | // 初始化 im 数据 64 | that.data.im.imName = '鸭鸭' 65 | that.data.im.imAvatarUrl = 'https://wx.qlogo.cn/mmopen/vi_32/vcFFe9Kg2Q0YfwQuaib7sHlSI65nKraL7ibuQvq1icbkrumuWDlbSM51PShPVoialzlpkiaKudLtLia0JLeWUmFprMjg/132' 66 | 67 | cbOk() 68 | } 69 | }) 70 | } 71 | }) 72 | } 73 | }) 74 | 75 | } 76 | }) -------------------------------------------------------------------------------- /wx-tencent-im/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index" 4 | ], 5 | "window":{ 6 | "backgroundTextStyle":"light", 7 | "navigationBarBackgroundColor": "#fff", 8 | "navigationBarTitleText": "WeChat", 9 | "navigationBarTextStyle":"black", 10 | "enablePullDownRefresh": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /wx-tencent-im/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | -------------------------------------------------------------------------------- /wx-tencent-im/pages/chat/chat.js: -------------------------------------------------------------------------------- 1 | var util = require('../../utils/util.js'); // 转换时间插件 2 | var im = require('../../utils/webim_wx.js'); // 腾讯云 im 包 3 | var imhandler = require('../../utils/im_handler.js'); // 这个是所有 im 事件的 js 4 | const app = getApp() 5 | 6 | Page({ 7 | data: { 8 | friendId: '', 9 | friendName: '', 10 | friendAvatarUrl: '', 11 | /** 12 | * 消息集合(结构如下): 13 | * msgTime 消息时间 14 | * myself 消息发送人 1 - 自己发的 0 - 好友发的 15 | * avatarUrl 头像 16 | * msgText 消息内容 17 | */ 18 | messages: [],// 消息集合 19 | complete: 0, // 是否还有历史消息可以拉取,1 - 表示没有,0 - 表示有 20 | content: '', // 输入框的文本值 21 | lock: false, // 发送消息锁 true - 加锁状态 false - 解锁状态 22 | scroll_height: wx.getSystemInfoSync().windowHeight - 54, 23 | }, 24 | onLoad: function (options) { 25 | var that = this 26 | if (options) { // 设置会话列表传参过来的好友id 27 | that.setData({ 28 | friendId: options.friendId, 29 | friendName: options.friendName, 30 | friendAvatarUrl: options.friendAvatarUrl 31 | }) 32 | wx.setNavigationBarTitle({ 33 | title: options.friendName 34 | }) 35 | } 36 | that.data.messages = [] // 清空历史消息 37 | }, 38 | onShow: function () { 39 | var that = this 40 | // 私聊参数初始化 41 | imhandler.init({ 42 | accountMode: app.data.im.accountMode, 43 | accountType: app.data.im.accountType, 44 | sdkAppID: app.data.im.sdkappid, 45 | selType: im.SESSION_TYPE.C2C, //私聊 46 | imId: app.data.im.identifier, 47 | imName: app.data.im.imName, 48 | imAvatarUrl: app.data.im.imAvatarUrl, 49 | friendId: that.data.friendId, 50 | friendName: that.data.friendName, 51 | friendAvatarUrl: that.data.friendAvatarUrl, 52 | contactListThat: null, 53 | chatThat: that 54 | }) 55 | if (im.checkLogin()) { 56 | //获取聊天历史记录 57 | imhandler.getC2CHistoryMsgs(function cbOk(result) { 58 | handlerHistoryMsgs(result, that) 59 | }) 60 | } else { 61 | imhandler.sdkLogin(that, app, this.data.selToID, () => { 62 | //获取聊天历史记录 63 | imhandler.getC2CHistoryMsgs(function cbOk(result) { 64 | handlerHistoryMsgs(result, that) 65 | }); 66 | }); 67 | } 68 | }, 69 | /** 70 | * 获取文本的消息 71 | */ 72 | getContent: function (e) { 73 | var that = this; 74 | that.setData({ 75 | content: e.detail.value 76 | }) 77 | }, 78 | /** 79 | * 发送消息 80 | */ 81 | sendMsg: function (e) { 82 | debugger 83 | var that = this 84 | // 消息锁 锁定中 85 | if (that.data.lock) { 86 | wx.showToast({ 87 | title: '发消息太急了,慢一点' 88 | }); 89 | return 90 | } 91 | // 开始加锁 92 | that.setData({ lock: true }) 93 | if (that.data.content == '' || !that.data.content.replace(/^\s*|\s*$/g, '')) { 94 | wx.showToast({ 95 | title: '总得填点内容吧' 96 | }); 97 | this.setData({ lock: false }) 98 | return; 99 | } 100 | var content = that.data.content 101 | // 调用腾讯IM发送消息 102 | imhandler.onSendMsg(content, function cbOk() { 103 | that.addMessage(content, true, that) 104 | }, function cbErr(err) { 105 | im.Log.error("消息发送失败", err) 106 | }) 107 | // 解锁 108 | this.setData({ lock: false}) 109 | }, 110 | /** 111 | * 发送消息 112 | */ 113 | addMessage: function(msg, isSend, that) { 114 | var messages = that.data.messages; 115 | var message = { 116 | 'myself': isSend ? 1 : 0, 117 | 'avatarUrl': isSend ? app.data.im.imAvatarUrl : that.data.friendAvatarUrl, 118 | 'msgText': msg, 119 | 'msgTime': util.getDateDiff(Date.parse(new Date())) 120 | } 121 | messages.push(message); 122 | that.setData({ 123 | messages: messages, 124 | content: '' // 清空输入框文本 125 | }) 126 | that.scrollToBottom(); 127 | }, 128 | scrollToBottom: function () { 129 | this.setData({ 130 | toView: 'row_' + (this.data.messages.length - 1) 131 | }); 132 | } 133 | }) 134 | /** 135 | * 处理历史消息 136 | */ 137 | function handlerHistoryMsgs(result, that) { 138 | var historyMsgs = []; 139 | for (var i = 0; i < result.MsgList.length; i++) { 140 | var msg = result.MsgList[i] 141 | var message = { 142 | 'myself': msg.isSend ? 1 : 0, 143 | 'avatarUrl': msg.isSend ? app.data.im.imAvatarUrl : that.data.friendAvatarUrl, 144 | 'msgText': msg.elems[0].content.text, 145 | 'msgTime': util.getDateDiff(msg.time * 1000) 146 | } 147 | historyMsgs.push(message) 148 | } 149 | // 拉取消息后,可以先将下一次拉取信息所需要的数据存储起来 150 | wx.setStorageSync('lastMsgTime', result.LastMsgTime); 151 | wx.setStorageSync('msgKey', result.MsgKey); 152 | that.setData({ 153 | messages: historyMsgs, 154 | complete: result.Complete 155 | }) 156 | } -------------------------------------------------------------------------------- /wx-tencent-im/pages/chat/chat.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /wx-tencent-im/pages/chat/chat.wxml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | {{item.msgTime}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {{item.msgText}} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 发送 25 | -------------------------------------------------------------------------------- /wx-tencent-im/pages/chat/chat.wxss: -------------------------------------------------------------------------------- 1 | /** 聊天窗口样式 2 | * 54px为回复框高度,js同 3 | */ 4 | 5 | /*聊天记录*/ 6 | .message-list { 7 | /*margin-bottom: 54px;*/ 8 | background: rgb(235, 235, 235); 9 | } 10 | 11 | /*单元行*/ 12 | .row { 13 | display: flex; 14 | flex-direction: column; 15 | margin: 0 30rpx; 16 | } 17 | 18 | /*日期*/ 19 | .datetime { 20 | font-size: 10px; 21 | padding: 10px 0; 22 | color: #999; 23 | text-align: center; 24 | } 25 | 26 | .send { 27 | font-size: 15px; 28 | padding-right: 10px; 29 | color: #999; 30 | text-align: center; 31 | } 32 | 33 | /*主体*/ 34 | .body { 35 | display: flex; 36 | flex-direction: row; 37 | align-items: flex-start; 38 | justify-content: flex-start; 39 | width: 100%; 40 | margin-top: 10px; 41 | } 42 | 43 | 44 | /*头像容器*/ 45 | .body.avatar-container { 46 | width: 20%; 47 | } 48 | 49 | /*头像*/ 50 | .body .avatar { 51 | width: 80rpx; 52 | height: 80rpx; 53 | border-radius: 50%; 54 | margin: 0 20rpx; 55 | } 56 | 57 | /*文本消息*/ 58 | .body .content { 59 | font-size: 16px; 60 | background: #fff; 61 | border-radius: 5px; 62 | padding: 10px; 63 | line-height: 22px; 64 | margin-bottom: 10px; 65 | } 66 | 67 | /* 三角箭头 */ 68 | .body .triangle { 69 | background: white; 70 | width: 20rpx; 71 | height: 20rpx; 72 | margin-top: 26rpx; 73 | transform: rotate(45deg); 74 | position: absolute; 75 | } 76 | 77 | /*图片消息*/ 78 | .picture { 79 | width: 160px; 80 | } 81 | 82 | /*回复框*/ 83 | .reply { 84 | display: flex; 85 | flex-direction: row; 86 | justify-content: flex-start; 87 | align-items: center; 88 | position: fixed; 89 | bottom: 0; 90 | width: 100%; 91 | height: 54px; 92 | border-top: 1px solid rgb(215, 215, 215); 93 | background: rgb(245, 245, 245); 94 | } 95 | 96 | .reply .voice-image { 97 | width: 25px; 98 | height: 25px; 99 | margin-left: 3%; 100 | } 101 | 102 | /*文本输入或语音录入*/ 103 | .reply .opration-area { 104 | flex: 1; 105 | padding: 8px; 106 | } 107 | 108 | /*回复文本框*/ 109 | .reply input { 110 | background: rgb(252, 252, 252); 111 | height: 36px; 112 | border: 1px solid rgb(221, 221, 221); 113 | border-radius: 6px; 114 | padding-left: 3px; 115 | } 116 | 117 | /*选取图片*/ 118 | .reply .choose-image { 119 | width: 25px; 120 | height: 25px; 121 | margin-right: 3%; 122 | } 123 | 124 | /*按住说话button*/ 125 | .voice-button { 126 | height: 36px; 127 | color: #818181; 128 | font-size: 14px; 129 | line-height: 36px; 130 | } 131 | 132 | /*悬浮提示框*/ 133 | .hud-container { 134 | position: fixed; 135 | width: 150px; 136 | height: 150px; 137 | left: 50%; 138 | top: 50%; 139 | margin-left: -75px; 140 | margin-top: -75px; 141 | } 142 | 143 | /*背景层*/ 144 | .hud-background { 145 | position: absolute; 146 | width: 100%; 147 | height: 100%; 148 | background: #999; 149 | opacity: .8; 150 | z-index: 11; 151 | border-radius: 10px; 152 | } 153 | 154 | /*悬浮框主体*/ 155 | .hud-body { 156 | position: relative; 157 | width: 100%; 158 | height: 100%; 159 | z-index: 19; 160 | display: flex; 161 | flex-direction: column; 162 | justify-content: space-between; 163 | align-items: center; 164 | } 165 | 166 | /*图标*/ 167 | .hud-body image { 168 | margin-top: 20px; 169 | width: 80px; 170 | height: 80px; 171 | } 172 | 173 | /*文字*/ 174 | .hud-body .tip { 175 | color: #fff; 176 | text-align: center; 177 | width: 90%; 178 | line-height: 34px; 179 | margin: 0 auto; 180 | margin-bottom: 10px; 181 | width: 90%; 182 | } 183 | 184 | .hud-body .warning { 185 | background: #cc3333; 186 | border-radius: 5px; 187 | } 188 | -------------------------------------------------------------------------------- /wx-tencent-im/pages/index/index.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | var util = require('../../utils/util.js'); // 转换时间插件 3 | var im = require('../../utils/webim_wx.js'); // 腾讯云 im 包 4 | var imhandler = require('../../utils/im_handler.js'); // 这个是所有 im 事件的 js 5 | 6 | // 获取应用实例 7 | const app = getApp() 8 | 9 | Page({ 10 | data: { 11 | isNoData: false, // isNoData 用于判断是否无数据列表,然后页面做出无数据列表的反应 TODO 效果未实现 12 | /** 13 | * 会话列表(结构定义如下): 14 | * friendId 15 | * friendName 16 | * friendAvatarUrl 17 | * msgTime 18 | * msg 19 | * unreadMsgCount 20 | */ 21 | contactList: [] 22 | }, 23 | onShow: function () { 24 | var that = this; 25 | wx.showLoading() 26 | // 会话列表所需参数初始化 需将当前会话好友数据清空 27 | imhandler.init({ 28 | accountMode: app.data.im.accountMode, 29 | accountType: app.data.im.accountType, 30 | sdkAppID: app.data.im.sdkAppID, 31 | selType: im.SESSION_TYPE.C2C, 32 | imId: app.data.im.imId, 33 | imName: app.data.im.imName, 34 | imAvatarUrl: app.data.im.imAvatarUrl, 35 | friendId: null, 36 | friendName: null, 37 | friendAvatarUrl: null, 38 | contactListThat: that, 39 | chatThat: null 40 | }) 41 | app.initImParams(function cbOk() { 42 | // 检查是否登录返回 true 和 false,不登录则重新登录 43 | if (im.checkLogin()) { 44 | that.initRecentContactList(); 45 | // 初始化最近会话的消息未读数(监听新消息事件) 46 | im.syncMsgs(imhandler.onMsgNotify()); 47 | } else { 48 | imhandler.login(that, app, function () { 49 | that.initRecentContactList(); 50 | // 初始化最近会话的消息未读数(监听新消息事件) 51 | im.syncMsgs(imhandler.onMsgNotify()); 52 | }); 53 | } 54 | wx.hideLoading() 55 | }) 56 | }, 57 | /** 58 | * 拉取最近联系人列表 59 | */ 60 | initRecentContactList: function () { 61 | im.Log.warn("开始拉取最近联系人列表"); 62 | var that = this; 63 | // 真正获取会话列表的方法 count: 最近的会话数 ,最大可设置为 100 只获取有价值数据 64 | im.getRecentContactList({ 'Count': 10 }, function (resp) { 65 | if (resp.SessionItem && resp.SessionItem.length > 0) { 66 | var contactList = resp.SessionItem.map((item, index) => { 67 | return { 68 | "friendId": item.To_Account, 69 | "friendName": item.C2cNick, 70 | "friendAvatarUrl": item.C2cImage, 71 | "msgTime": util.getDateDiff(item.MsgTimeStamp * 1000), 72 | "msg": item.MsgShow, 73 | "unreadMsgCount": item.UnreadMsgCount 74 | } 75 | }) 76 | // 设置联系人列表 77 | that.setData({ 78 | contactList: contactList, 79 | isNoData: true 80 | }) 81 | that.updateUnread() 82 | } else { 83 | that.setData({ 84 | isNoData: false, 85 | }) 86 | } 87 | }) 88 | im.Log.warn("成功拉取最近联系人列表"); 89 | }, 90 | /** 91 | * 更新未读消息数 92 | */ 93 | updateUnread: function () { 94 | var that = this 95 | // 还需要获取未读消息数 96 | var sessionMap = im.MsgStore.sessMap(); 97 | var contactList = that.data.contactList 98 | for (var i in sessionMap) { 99 | var session = sessionMap[i] 100 | if (session.unread() > 0) { 101 | contactList = contactList.map((item, index) => { 102 | if (item.friendId === session.id()) { 103 | item.unreadMsgCount = session.unread() 104 | } 105 | return item; 106 | }) 107 | } 108 | } 109 | // 设置联系人列表 110 | that.setData({ 111 | contactList: contactList 112 | }) 113 | }, 114 | /** 115 | * go chat.wxml 116 | */ 117 | linkChat: function(e) { 118 | wx.navigateTo({ 119 | url: '/pages/chat/chat?friendId=' + e.currentTarget.dataset.id 120 | + '&friendName=' + e.currentTarget.dataset.name 121 | + '&friendAvatarUrl=' + e.currentTarget.dataset.image, 122 | }) 123 | } 124 | }) 125 | -------------------------------------------------------------------------------- /wx-tencent-im/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /wx-tencent-im/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | {{item.friendName}} 7 | 8 | 9 | {{item.msgTime}} 10 | 11 | {{item.msg}} 12 | [{{item.unreadMsgCount}}条] {{item.msg}} 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /wx-tencent-im/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /**index.wxss**/ 2 | .message-sec { 3 | width: 96%; 4 | height: auto; 5 | padding-left: 4%; 6 | display: block; 7 | background: white; 8 | border-bottom: 1rpx solid #ededed; 9 | } 10 | .message-sec .child { 11 | width: 100%; 12 | height: 90rpx; 13 | padding: 25rpx 0rpx; 14 | } 15 | .message-sec .child image { 16 | width: 90rpx; 17 | height: 90rpx; 18 | display: block; 19 | left: 0rpx; 20 | } 21 | 22 | .message-sec .child em { 23 | top:25rpx; 24 | left:105rpx; 25 | right: inherit; 26 | } 27 | 28 | .message-sec .child .content { 29 | height: 90rpx; 30 | margin-left: 110rpx; 31 | padding: 0rpx 4% 15rpx 0rpx; 32 | border-bottom: 1rpx solid #ededed; 33 | } 34 | 35 | .message-sec .child .content .flex { 36 | margin: 10rpx 0rpx 5rpx 0rpx; 37 | } 38 | 39 | .message-sec .child .content .flex .flex100-5 { 40 | font-size: 28rpx; 41 | color: #303030; 42 | } 43 | 44 | .message-sec .child .content .flex .tr { 45 | color: #6a6a6a; 46 | } 47 | 48 | .message-sec .child .content .text { 49 | font-size: 24rpx; 50 | color: #989898; 51 | } 52 | 53 | .flex100-5 { 54 | flex: 0 0 50%; 55 | box-sizing: border-box; 56 | } 57 | 58 | .ellipsis { 59 | overflow: hidden; 60 | text-overflow: ellipsis; 61 | white-space: nowrap; 62 | } 63 | .flex, { 64 | display: flex; 65 | align-items: center; 66 | justify-content: space-between; 67 | } 68 | .tr { 69 | text-align: right; 70 | } 71 | 72 | .fix { 73 | position: fixed; 74 | } 75 | 76 | .abs { 77 | position: absolute; 78 | } 79 | 80 | .rel { 81 | position: relative; 82 | } 83 | 84 | .br-3 { 85 | border-radius: 3rpx; 86 | } 87 | 88 | .br-5 { 89 | border-radius: 5rpx; 90 | } 91 | 92 | .br-10 { 93 | border-radius: 10rpx; 94 | } 95 | 96 | .br-13 { 97 | border-radius: 13rpx; 98 | } 99 | 100 | .br-15 { 101 | border-radius: 15rpx; 102 | } 103 | 104 | page { 105 | background: #f4f4f8; 106 | } 107 | 108 | view,button { 109 | display: block; 110 | overflow: initial; 111 | } -------------------------------------------------------------------------------- /wx-tencent-im/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": false, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true 12 | }, 13 | "compileType": "miniprogram", 14 | "libVersion": "2.3.0", 15 | "appid": "wx17bd33e979aa3dba", 16 | "projectname": "wx-tencent-im", 17 | "debugOptions": { 18 | "hidedInDevtools": [] 19 | }, 20 | "isGameTourist": false, 21 | "condition": { 22 | "search": { 23 | "current": -1, 24 | "list": [] 25 | }, 26 | "conversation": { 27 | "current": -1, 28 | "list": [] 29 | }, 30 | "game": { 31 | "currentL": -1, 32 | "list": [] 33 | }, 34 | "miniprogram": { 35 | "current": -1, 36 | "list": [] 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /wx-tencent-im/utils/base64.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/base64 v0.1.0 by @mathias | MIT license */ 2 | ;(function(root) { 3 | 4 | // Detect free variables `exports`. 5 | var freeExports = typeof exports == 'object' && exports; 6 | 7 | // Detect free variable `module`. 8 | var freeModule = typeof module == 'object' && module && 9 | module.exports == freeExports && module; 10 | 11 | // Detect free variable `global`, from Node.js or Browserified code, and use 12 | // it as `root`. 13 | var freeGlobal = typeof global == 'object' && global; 14 | if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { 15 | root = freeGlobal; 16 | } 17 | 18 | /*--------------------------------------------------------------------------*/ 19 | 20 | var InvalidCharacterError = function(message) { 21 | this.message = message; 22 | }; 23 | InvalidCharacterError.prototype = new Error; 24 | InvalidCharacterError.prototype.name = 'InvalidCharacterError'; 25 | 26 | var error = function(message) { 27 | // Note: the error messages used throughout this file match those used by 28 | // the native `atob`/`btoa` implementation in Chromium. 29 | throw new InvalidCharacterError(message); 30 | }; 31 | 32 | var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 33 | // http://whatwg.org/html/common-microsyntaxes.html#space-character 34 | var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g; 35 | 36 | // `decode` is designed to be fully compatible with `atob` as described in the 37 | // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob 38 | // The optimized base64-decoding algorithm used is based on @atk’s excellent 39 | // implementation. https://gist.github.com/atk/1020396 40 | var decode = function(input) { 41 | input = String(input) 42 | .replace(REGEX_SPACE_CHARACTERS, ''); 43 | var length = input.length; 44 | if (length % 4 == 0) { 45 | input = input.replace(/==?$/, ''); 46 | length = input.length; 47 | } 48 | if ( 49 | length % 4 == 1 || 50 | // http://whatwg.org/C#alphanumeric-ascii-characters 51 | /[^+a-zA-Z0-9/]/.test(input) 52 | ) { 53 | error( 54 | 'Invalid character: the string to be decoded is not correctly encoded.' 55 | ); 56 | } 57 | var bitCounter = 0; 58 | var bitStorage; 59 | var buffer; 60 | var output = ''; 61 | var position = -1; 62 | while (++position < length) { 63 | buffer = TABLE.indexOf(input.charAt(position)); 64 | bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer; 65 | // Unless this is the first of a group of 4 characters… 66 | if (bitCounter++ % 4) { 67 | // …convert the first 8 bits to a single ASCII character. 68 | output += String.fromCharCode( 69 | 0xFF & bitStorage >> (-2 * bitCounter & 6) 70 | ); 71 | } 72 | } 73 | return output; 74 | }; 75 | 76 | // `encode` is designed to be fully compatible with `btoa` as described in the 77 | // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa 78 | var encode = function(input) { 79 | input = String(input); 80 | if (/[^\0-\xFF]/.test(input)) { 81 | // Note: no need to special-case astral symbols here, as surrogates are 82 | // matched, and the input is supposed to only contain ASCII anyway. 83 | error( 84 | 'The string to be encoded contains characters outside of the ' + 85 | 'Latin1 range.' 86 | ); 87 | } 88 | var padding = input.length % 3; 89 | var output = ''; 90 | var position = -1; 91 | var a; 92 | var b; 93 | var c; 94 | var d; 95 | var buffer; 96 | // Make sure any padding is handled outside of the loop. 97 | var length = input.length - padding; 98 | 99 | while (++position < length) { 100 | // Read three bytes, i.e. 24 bits. 101 | a = input.charCodeAt(position) << 16; 102 | b = input.charCodeAt(++position) << 8; 103 | c = input.charCodeAt(++position); 104 | buffer = a + b + c; 105 | // Turn the 24 bits into four chunks of 6 bits each, and append the 106 | // matching character for each of them to the output. 107 | output += ( 108 | TABLE.charAt(buffer >> 18 & 0x3F) + 109 | TABLE.charAt(buffer >> 12 & 0x3F) + 110 | TABLE.charAt(buffer >> 6 & 0x3F) + 111 | TABLE.charAt(buffer & 0x3F) 112 | ); 113 | } 114 | 115 | if (padding == 2) { 116 | a = input.charCodeAt(position) << 8; 117 | b = input.charCodeAt(++position); 118 | buffer = a + b; 119 | output += ( 120 | TABLE.charAt(buffer >> 10) + 121 | TABLE.charAt((buffer >> 4) & 0x3F) + 122 | TABLE.charAt((buffer << 2) & 0x3F) + 123 | '=' 124 | ); 125 | } else if (padding == 1) { 126 | buffer = input.charCodeAt(position); 127 | output += ( 128 | TABLE.charAt(buffer >> 2) + 129 | TABLE.charAt((buffer << 4) & 0x3F) + 130 | '==' 131 | ); 132 | } 133 | 134 | return output; 135 | }; 136 | 137 | var base64 = { 138 | 'encode': encode, 139 | 'decode': decode, 140 | 'version': '0.1.0' 141 | }; 142 | 143 | // Some AMD build optimizers, like r.js, check for specific condition patterns 144 | // like the following: 145 | if ( 146 | typeof define == 'function' && 147 | typeof define.amd == 'object' && 148 | define.amd 149 | ) { 150 | define(function() { 151 | return base64; 152 | }); 153 | } else if (freeExports && !freeExports.nodeType) { 154 | if (freeModule) { // in Node.js or RingoJS v0.8.0+ 155 | freeModule.exports = base64; 156 | } else { // in Narwhal or RingoJS v0.7.0- 157 | for (var key in base64) { 158 | base64.hasOwnProperty(key) && (freeExports[key] = base64[key]); 159 | } 160 | } 161 | } else { // in Rhino or a web browser 162 | root.base64 = base64; 163 | } 164 | 165 | }(this)); 166 | -------------------------------------------------------------------------------- /wx-tencent-im/utils/encrypt.js: -------------------------------------------------------------------------------- 1 | module.exports = function(){ 2 | /******************************************** 3 | * 4 | * 加密及算法相关 5 | * 6 | *******************************************/ 7 | var hexcase = 1; 8 | var b64pad = ""; 9 | var chrsz = 8; 10 | var mode = 32; 11 | var TEA = require("tea.js"); 12 | var RSA = require("rsa.js"); 13 | var base64 = require("base64.js"); 14 | function md5(s){ 15 | return hex_md5(s); 16 | } 17 | function hex_md5(s){ 18 | return binl2hex(core_md5(str2binl(s), s.length * chrsz)); 19 | } 20 | 21 | function str_md5(s){ 22 | return binl2str(core_md5(str2binl(s), s.length * chrsz)); 23 | } 24 | function hex_hmac_md5(key, data){ 25 | return binl2hex(core_hmac_md5(key, data)); 26 | } 27 | function b64_hmac_md5(key, data){ 28 | return binl2b64(core_hmac_md5(key, data)); 29 | } 30 | function str_hmac_md5(key, data){ 31 | return binl2str(core_hmac_md5(key, data)); 32 | } 33 | function core_md5(x, len){ 34 | x[len >> 5] |= 0x80 << ((len) % 32); 35 | x[(((len + 64) >>> 9) << 4) + 14] = len; 36 | 37 | var a = 1732584193; 38 | var b = - 271733879; 39 | var c = - 1732584194; 40 | var d = 271733878; 41 | 42 | for (var i = 0; i < x.length; i += 16) { 43 | var olda = a; 44 | var oldb = b; 45 | var oldc = c; 46 | var oldd = d; 47 | 48 | a = md5_ff(a, b, c, d, x[i + 0], 7, - 680876936); 49 | d = md5_ff(d, a, b, c, x[i + 1], 12, - 389564586); 50 | c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819); 51 | b = md5_ff(b, c, d, a, x[i + 3], 22, - 1044525330); 52 | a = md5_ff(a, b, c, d, x[i + 4], 7, - 176418897); 53 | d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); 54 | c = md5_ff(c, d, a, b, x[i + 6], 17, - 1473231341); 55 | b = md5_ff(b, c, d, a, x[i + 7], 22, - 45705983); 56 | a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); 57 | d = md5_ff(d, a, b, c, x[i + 9], 12, - 1958414417); 58 | c = md5_ff(c, d, a, b, x[i + 10], 17, - 42063); 59 | b = md5_ff(b, c, d, a, x[i + 11], 22, - 1990404162); 60 | a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); 61 | d = md5_ff(d, a, b, c, x[i + 13], 12, - 40341101); 62 | c = md5_ff(c, d, a, b, x[i + 14], 17, - 1502002290); 63 | b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); 64 | 65 | a = md5_gg(a, b, c, d, x[i + 1], 5, - 165796510); 66 | d = md5_gg(d, a, b, c, x[i + 6], 9, - 1069501632); 67 | c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713); 68 | b = md5_gg(b, c, d, a, x[i + 0], 20, - 373897302); 69 | a = md5_gg(a, b, c, d, x[i + 5], 5, - 701558691); 70 | d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083); 71 | c = md5_gg(c, d, a, b, x[i + 15], 14, - 660478335); 72 | b = md5_gg(b, c, d, a, x[i + 4], 20, - 405537848); 73 | a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438); 74 | d = md5_gg(d, a, b, c, x[i + 14], 9, - 1019803690); 75 | c = md5_gg(c, d, a, b, x[i + 3], 14, - 187363961); 76 | b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); 77 | a = md5_gg(a, b, c, d, x[i + 13], 5, - 1444681467); 78 | d = md5_gg(d, a, b, c, x[i + 2], 9, - 51403784); 79 | c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); 80 | b = md5_gg(b, c, d, a, x[i + 12], 20, - 1926607734); 81 | 82 | a = md5_hh(a, b, c, d, x[i + 5], 4, - 378558); 83 | d = md5_hh(d, a, b, c, x[i + 8], 11, - 2022574463); 84 | c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); 85 | b = md5_hh(b, c, d, a, x[i + 14], 23, - 35309556); 86 | a = md5_hh(a, b, c, d, x[i + 1], 4, - 1530992060); 87 | d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); 88 | c = md5_hh(c, d, a, b, x[i + 7], 16, - 155497632); 89 | b = md5_hh(b, c, d, a, x[i + 10], 23, - 1094730640); 90 | a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174); 91 | d = md5_hh(d, a, b, c, x[i + 0], 11, - 358537222); 92 | c = md5_hh(c, d, a, b, x[i + 3], 16, - 722521979); 93 | b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189); 94 | a = md5_hh(a, b, c, d, x[i + 9], 4, - 640364487); 95 | d = md5_hh(d, a, b, c, x[i + 12], 11, - 421815835); 96 | c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520); 97 | b = md5_hh(b, c, d, a, x[i + 2], 23, - 995338651); 98 | 99 | a = md5_ii(a, b, c, d, x[i + 0], 6, - 198630844); 100 | d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); 101 | c = md5_ii(c, d, a, b, x[i + 14], 15, - 1416354905); 102 | b = md5_ii(b, c, d, a, x[i + 5], 21, - 57434055); 103 | a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); 104 | d = md5_ii(d, a, b, c, x[i + 3], 10, - 1894986606); 105 | c = md5_ii(c, d, a, b, x[i + 10], 15, - 1051523); 106 | b = md5_ii(b, c, d, a, x[i + 1], 21, - 2054922799); 107 | a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); 108 | d = md5_ii(d, a, b, c, x[i + 15], 10, - 30611744); 109 | c = md5_ii(c, d, a, b, x[i + 6], 15, - 1560198380); 110 | b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); 111 | a = md5_ii(a, b, c, d, x[i + 4], 6, - 145523070); 112 | d = md5_ii(d, a, b, c, x[i + 11], 10, - 1120210379); 113 | c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259); 114 | b = md5_ii(b, c, d, a, x[i + 9], 21, - 343485551); 115 | 116 | a = safe_add(a, olda); 117 | b = safe_add(b, oldb); 118 | c = safe_add(c, oldc); 119 | d = safe_add(d, oldd); 120 | } 121 | if (mode == 16) { 122 | return Array(b, c); 123 | }else{ 124 | return Array(a, b, c, d); 125 | } 126 | } 127 | function md5_cmn(q, a, b, x, s, t){ 128 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b); 129 | } 130 | function md5_ff(a, b, c, d, x, s, t){ 131 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 132 | } 133 | function md5_gg(a, b, c, d, x, s, t){ 134 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 135 | } 136 | function md5_hh(a, b, c, d, x, s, t){ 137 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 138 | } 139 | function md5_ii(a, b, c, d, x, s, t){ 140 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 141 | } 142 | function core_hmac_md5(key, data){ 143 | var bkey = str2binl(key); 144 | if (bkey.length > 16) 145 | bkey = core_md5(bkey, key.length * chrsz); 146 | 147 | var ipad = Array(16), opad = Array(16); 148 | for (var i = 0; i < 16; i++){ 149 | ipad[i] = bkey[i] ^ 0x36363636; 150 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 151 | } 152 | 153 | var hash = core_md5(ipad.concat(str2binl(data)), 512+data.length * chrsz); 154 | return core_md5(opad.concat(hash), 512+128); 155 | } 156 | function safe_add(x, y){ 157 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 158 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 159 | return (msw << 16) | (lsw & 0xFFFF); 160 | } 161 | function bit_rol(num, cnt){ 162 | return (num << cnt) | (num >>> (32-cnt)); 163 | } 164 | function str2binl(str){ 165 | var bin = Array(); 166 | var mask = (1 << chrsz) - 1; 167 | for (var i = 0; i < str.length * chrsz; i += chrsz) 168 | bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32); 169 | return bin; 170 | } 171 | function binl2str(bin){ 172 | var str = ""; 173 | var mask = (1 << chrsz) - 1; 174 | for (var i = 0; i < bin.length * 32; i += chrsz) 175 | str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask); 176 | return str; 177 | } 178 | function binl2hex(binarray){ 179 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 180 | var str = ""; 181 | 182 | for (var i = 0; i < binarray.length * 4; i++){ 183 | str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8+4)) & 0xF) + 184 | hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF); 185 | } 186 | return str; 187 | } 188 | function binl2b64(binarray){ 189 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 190 | var str = ""; 191 | for (var i = 0; i < binarray.length * 4; i += 3){ 192 | var triplet = (((binarray[i >> 2] >> 8 * (i % 4)) & 0xFF) << 16) | (( 193 | (binarray[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 0xFF) << 8) | ((binarray[i 194 | + 2 >> 2] >> 8 * ((i + 2) % 4)) & 0xFF); 195 | for (var j = 0; j < 4; j++){ 196 | if (i * 8+j * 6 > binarray.length * 32) 197 | str += b64pad; 198 | else 199 | str += tab.charAt((triplet >> 6 * (3-j)) & 0x3F); 200 | } 201 | } 202 | return str; 203 | } 204 | 205 | function hexchar2bin(str){ 206 | var arr = []; 207 | for(var i=0;i "1234" 246 | // 输出为十六进制字符串展示的二进制内容 247 | var saltPwd = TEA.encrypt(h1 + idLen + hexId + hexAppid + hexAcctype + vcodeLen + hexVcode); 248 | TEA.initkey(""); // reset key 249 | var pubKey = "00988f6fe99e3d7c72b8b8a1cc9563e9750f5815316de064b531a0bfaa4dd5c2a5ea1f0e9b6e87bbcd19f445a13afada991a8ef60b812c628019741e4337933fb68438d93b62a538da25884627d3d46e6c62a5a41d30a7167a3a1ce5f6ecc3353db98b14a04ce2f777f335223134a900caa74fa79d9ab2c20ce19aaac9c24a82c847fa2eed0704553f75e030d93aa721186576cf5c344015ddc384b6b37add7139531af060548be8060a4bb075cc842bb190343c7f5e0e0b03fe1ca46c29b0df0bec7345888028df47f71fe44a0bd9cb8aed6282c095a75c57b6a604600886744b2965138730b27cf7d173381f0e53523aa1ced6864c09f7cc4135d45c5d4cfcbd"; 250 | return urlBase64(RSA.encrypt(hexchar2bin(saltPwd), pubKey, "10001")); 251 | } 252 | 253 | function getRSAH1(pwd) { 254 | pwd = pwd || randpwd(); 255 | var h1 = md5(pwd); 256 | var pubKey = "00ccaa91239f0a10fae03522fe6fdc6194007809732b07cb89e04dee9b4fdb9186787659fdf308be6efbc8aa147ffd8b5e4d61aba8a7e40e08af759751e1acc207a3988ce381cca6dfac4c75af1acda8bb3c09dce7a3d43fc23c95eecf56ca0c0c7a7eaeb019c912877757fe23ab28ac7060ee5409da3f0b5f079901475b11ac7d6c5cea1e7bd26a324674878cc31094b62eb407247f3e7f2070bb76a919883eaa114b0a40ea1341bf99dfd131d77343fd113f3a294fc0e19d9cc06989b98a0c14677e589ac41dd414283a3cf7685089d92770e7fde43c6aa443f2822c52fdbba309ea819bea8e4c2f1fac03930081ffd5189de9f025e15c4a1c466b761ba8e7f3"; 257 | return urlBase64(RSA.encrypt(hexchar2bin(h1), pubKey, "10001")); 258 | } 259 | 260 | function urlBase64(src) { 261 | src = base64.encode(hexchar2bin(src)); 262 | return src.replace(/[\/\+=]/g, function(a) {return {'/':'-', '+':'*', '=':'_'}[a];}); 263 | } 264 | 265 | function randpwd() { 266 | var base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 267 | var pwd = ""; 268 | for (var i=0; i<16; i++) { 269 | pwd += base[Math.round(Math.random() * 1000) % base.length] 270 | } 271 | 272 | return pwd; 273 | } 274 | return { 275 | getEncPwd: getEncPwd, 276 | getRSAH1: getRSAH1, 277 | md5: md5 278 | }; 279 | }(); 280 | -------------------------------------------------------------------------------- /wx-tencent-im/utils/im_handler.js: -------------------------------------------------------------------------------- 1 | var util = require('util.js'); //转换时间插件 2 | var im = require('webim_wx.js'); 3 | 4 | // 需要用到的参数 5 | var accountMode, // 帐号模式,0 - 独立模式 1 - 托管模式 6 | accountType, // 帐号体系集成中的 accountType,必填 7 | sdkAppID, // 用户标识接入 SDK 的应用 ID,必填 8 | selType, // 会话类型 webim.MSG_MAX_LENGTH.C2C - 私聊 webim.SESSION_TYPE.GROUP - 群聊 9 | imId, // 用户的 id 10 | imName, // 用户的 im 名称 11 | imAvatarUrl, // 用户的 im 头像 url 12 | friendId, // 好友 id 13 | friendName, // 好友昵称 14 | friendAvatarUrl, // 好友头像 15 | currentMsgsArray, // 当前消息数组 用于增删改查 16 | contactListThat, // 当前会话列表页面对象 17 | chatThat, // 当前聊天好友页面对象 18 | selSess 19 | 20 | /** 21 | * 登录 im 22 | */ 23 | function login(that, app, callback) { 24 | im.Log.warn('开始登录 im') 25 | if (!callback) callback = () => {} 26 | if (!app.data.im.imId || !app.data.im.userSig) { 27 | im.Log.error("登录 im 失败[im 数据未初始化完毕]") 28 | return 29 | } 30 | // 获取当前用户身份 31 | var loginInfo = { 32 | 'sdkAppID': app.data.im.sdkAppID, // 用户标识接入 SDK 的应用 ID,必填 33 | 'appIDAt3rd': app.data.im.sdkAppID, // App 用户使用 OAuth 授权体系分配的 Appid,必填 34 | 'accountType': app.data.im.accountType, // 帐号体系集成中的 accountType,必填 35 | 'identifier': app.data.im.imId, // 当前用户帐号,必填 36 | 'identifierNick': app.data.im.imName, // 当前用户昵称,选填 37 | 'userSig': app.data.im.userSig, // 鉴权 Token,必填 38 | } 39 | // 指定监听事件 40 | var listeners = { 41 | "onConnNotify": onConnNotify, // 监听连接状态回调变化事件,必填 42 | "onMsgNotify": onMsgNotify // 监听新消息回调变化事件,必填 43 | } 44 | //其他对象,选填 45 | var options = { 46 | 'isAccessFormalEnv': true, // 是否访问正式环境,默认访问正式,选填 47 | 'isLogOn': true // 是否开启控制台打印日志,默认开启,选填 48 | } 49 | // sdk 登录(独立模式) 50 | im.login(loginInfo, listeners, options, function (resp) { 51 | im.Log.warn('登录 im 成功') 52 | callback() 53 | }, function (err) { 54 | im.Log.error("登录 im 失败", err.ErrorInfo) 55 | }) 56 | } 57 | 58 | /** 59 | * 监听连接状态回调变化事件 60 | */ 61 | function onConnNotify(resp) { 62 | switch (resp.ErrorCode) { 63 | case im.CONNECTION_STATUS.ON: 64 | im.Log.warn('连接状态正常...') 65 | break 66 | case im.CONNECTION_STATUS.OFF: 67 | im.Log.warn('连接已断开,无法收到新消息,请检查下你的网络是否正常') 68 | break 69 | default: 70 | im.Log.error('未知连接状态,status=' + resp.ErrorCode) 71 | break 72 | } 73 | } 74 | 75 | /** 76 | * 监听新消息(初始化时会获取所有会话数组,随后只获取新会话数组) 77 | * newMsgList - 新消息数组 78 | */ 79 | function onMsgNotify(newMsgList) { 80 | var newMsg, session; 81 | // 如果有新消息,并且处在聊天界面上 82 | if(newMsgList && chatThat) { 83 | for (var j in newMsgList) { 84 | newMsg = newMsgList[j]; 85 | if (chatThat && newMsg.getSession().id() == friendId) { 86 | selSess = newMsg.getSession() 87 | chatThat.addMessage(newMsg.elems[0].content.text, false, chatThat) 88 | } 89 | } 90 | } 91 | // 如果有新消息,并且处在会话列表界面上 92 | if (newMsgList && contactListThat) { 93 | contactListThat.initRecentContactList() 94 | } 95 | } 96 | 97 | /** 98 | * 获取聊天历史记录 99 | */ 100 | function getC2CHistoryMsgs(cbOk) { 101 | im.Log.warn('开始获取聊天历史记录') 102 | var that = this 103 | currentMsgsArray = [] 104 | var reqMsgCount = 10 // 拉取消息条数 105 | var lastMsgTime = wx.setStorageSync('lastMsgTime') || 0 // 最后一次拉取历史消息的时间 106 | var msgKey = wx.getStorageSync('msgKey') || '' 107 | var options = { 108 | 'Peer_Account': friendId, // 好友帐号 109 | 'MaxCnt': reqMsgCount, // 拉取消息条数 110 | 'LastMsgTime': lastMsgTime, // 最近的消息时间,即从这个时间点向前拉取历史消息 111 | 'MsgKey': msgKey 112 | } 113 | // 真正获取历史消息的方法 交由实际调用者处理数据 114 | im.getC2CHistoryMsgs(options, function (resp) { 115 | cbOk(resp) 116 | }) 117 | //消息已读上报,以及设置会话自动已读标记 118 | var sessMap = im.MsgStore.sessMap() 119 | for(var i in sessMap) { 120 | var sess = sessMap[i]; 121 | if (friendId == sess.id()) { 122 | im.setAutoRead(sess, true, true) 123 | } 124 | } 125 | im.Log.warn('获取聊天历史纪录完毕') 126 | } 127 | 128 | /** 129 | * 发送消息(普通消息) 130 | */ 131 | function onSendMsg(msg, cbOk, cbErr) { 132 | //获取消息内容 133 | var msgtosend = msg; 134 | // 创建会话对象 135 | if (!selSess) { 136 | selSess = new im.Session(selType, friendId, friendName, friendAvatarUrl, Math.round(new Date().getTime() / 1000)); 137 | } 138 | var isSend = true;// 是否为自己发送 139 | var seq = -1; // 消息序列,-1 表示 sdk 自动生成,用于去重 140 | var random = Math.round(Math.random() * 4294967296); // 消息随机数,用于去重 141 | var msgTime = Date.parse(new Date()) / 1000; // 消息时间戳 142 | var subType = im.C2C_MSG_SUB_TYPE.COMMON; // 消息子类型 c2c 消息时,参考 c2c 消息子类型对象:im.C2C_MSG_SUB_TYPE 143 | // loginInfo.identifier 消息发送者账号,loginInfo.identifierNick 消息发送者昵称 144 | var msg = new im.Msg(selSess, isSend, seq, random, msgTime, imId, subType, imName); 145 | var textObj = new im.Msg.Elem.Text(msgtosend); 146 | msg.addText(textObj); 147 | im.sendMsg(msg, function (resp) { 148 | cbOk() 149 | }, function (err) { 150 | cbErr(err) 151 | }) 152 | } 153 | 154 | /** 155 | * 初始化 im 156 | */ 157 | function init(opts) { 158 | accountMode = opts.accountMode 159 | accountType = opts.accountType 160 | sdkAppID = opts.sdkAppID 161 | selType = opts.selType 162 | imId = opts.imId 163 | imName = opts.imName 164 | imAvatarUrl = opts.imAvatarUrl 165 | friendId = opts.friendId 166 | friendName = opts.friendName 167 | friendAvatarUrl = opts.friendAvatarUrl 168 | contactListThat = opts.contactListThat 169 | chatThat = opts.chatThat 170 | } 171 | 172 | module.exports = { 173 | init: init, 174 | login: login, 175 | onMsgNotify: onMsgNotify, 176 | getC2CHistoryMsgs: getC2CHistoryMsgs, 177 | onSendMsg: onSendMsg 178 | } -------------------------------------------------------------------------------- /wx-tencent-im/utils/rsa.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [Encryption rsa算法封装] 3 | */ 4 | module.exports = function() { 5 | // Depends on jsbn.js and rng.js 6 | 7 | // Version 1.1: support utf-8 encoding in pkcs1pad2 8 | 9 | // convert a (hex) string to a bignum object 10 | 11 | function parseBigInt(str, r) { 12 | return new BigInteger(str, r); 13 | } 14 | 15 | function linebrk(s, n) { 16 | var ret = ""; 17 | var i = 0; 18 | while (i + n < s.length) { 19 | ret += s.substring(i, i + n) + "\n"; 20 | i += n; 21 | } 22 | return ret + s.substring(i, s.length); 23 | } 24 | 25 | function byte2Hex(b) { 26 | if (b < 0x10) 27 | return "0" + b.toString(16); 28 | else 29 | return b.toString(16); 30 | } 31 | 32 | // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint 33 | function pkcs1pad2(s, n) { 34 | if (n < s.length + 11) { // TODO: fix for utf-8 35 | uv_alert("Message too long for RSA"); 36 | return null; 37 | } 38 | var ba = new Array(); 39 | var i = s.length - 1; 40 | while (i >= 0 && n > 0) { 41 | var c = s.charCodeAt(i--); 42 | ba[--n] = c; 43 | /* if(c < 128) { // encode using utf-8 44 | ba[--n] = c; 45 | } 46 | else if((c > 127) && (c < 2048)) { 47 | ba[--n] = (c & 63) | 128; 48 | ba[--n] = (c >> 6) | 192; 49 | } 50 | else { 51 | ba[--n] = (c & 63) | 128; 52 | ba[--n] = ((c >> 6) & 63) | 128; 53 | ba[--n] = (c >> 12) | 224; 54 | }*/ 55 | } 56 | ba[--n] = 0; 57 | var rng = new SecureRandom(); 58 | var x = new Array(); 59 | while (n > 2) { // random non-zero pad 60 | x[0] = 0; 61 | while (x[0] == 0) rng.nextBytes(x); 62 | ba[--n] = x[0]; 63 | } 64 | ba[--n] = 2; 65 | ba[--n] = 0; 66 | return new BigInteger(ba); 67 | } 68 | 69 | // "empty" RSA key constructor 70 | function RSAKey() { 71 | this.n = null; 72 | this.e = 0; 73 | this.d = null; 74 | this.p = null; 75 | this.q = null; 76 | this.dmp1 = null; 77 | this.dmq1 = null; 78 | this.coeff = null; 79 | } 80 | 81 | // Set the public key fields N and e from hex strings 82 | function RSASetPublic(N, E) { 83 | if (N != null && E != null && N.length > 0 && E.length > 0) { 84 | this.n = parseBigInt(N, 16); 85 | this.e = parseInt(E, 16); 86 | } else 87 | uv_alert("Invalid RSA public key"); 88 | } 89 | 90 | // Perform raw public operation on "x": return x^e (mod n) 91 | function RSADoPublic(x) { 92 | return x.modPowInt(this.e, this.n); 93 | } 94 | 95 | // Return the PKCS#1 RSA encryption of "text" as an even-length hex string 96 | function RSAEncrypt(text) { 97 | var m = pkcs1pad2(text, (this.n.bitLength() + 7) >> 3); 98 | if (m == null) return null; 99 | var c = this.doPublic(m); 100 | if (c == null) return null; 101 | var h = c.toString(16); 102 | if ((h.length & 1) == 0) return h; 103 | else return "0" + h; 104 | } 105 | 106 | // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string 107 | //function RSAEncryptB64(text) { 108 | // var h = this.encrypt(text); 109 | // if(h) return hex2b64(h); else return null; 110 | //} 111 | 112 | // protected 113 | RSAKey.prototype.doPublic = RSADoPublic; 114 | 115 | // public 116 | RSAKey.prototype.setPublic = RSASetPublic; 117 | RSAKey.prototype.encrypt = RSAEncrypt; 118 | //RSAKey.prototype.encrypt_b64 = RSAEncryptB64; 119 | 120 | 121 | //==================================================jsbn.js======================================================================// 122 | 123 | // Copyright (c) 2005 Tom Wu 124 | // All Rights Reserved. 125 | // See "LICENSE" for details. 126 | 127 | // Basic JavaScript BN library - subset useful for RSA encryption. 128 | 129 | // Bits per digit 130 | var dbits; 131 | 132 | // JavaScript engine analysis 133 | var canary = 0xdeadbeefcafe; 134 | var j_lm = ((canary & 0xffffff) == 0xefcafe); 135 | 136 | // (public) Constructor 137 | function BigInteger(a, b, c) { 138 | if (a != null) 139 | if ("number" == typeof a) this.fromNumber(a, b, c); 140 | else if (b == null && "string" != typeof a) this.fromString(a, 256); 141 | else this.fromString(a, b); 142 | } 143 | 144 | // return new, unset BigInteger 145 | function nbi() { 146 | return new BigInteger(null); 147 | } 148 | 149 | // am: Compute w_j += (x*this_i), propagate carries, 150 | // c is initial carry, returns final carry. 151 | // c < 3*dvalue, x < 2*dvalue, this_i < dvalue 152 | // We need to select the fastest one that works in this environment. 153 | 154 | // am1: use a single mult and divide to get the high bits, 155 | // max digit bits should be 26 because 156 | // max internal value = 2*dvalue^2-2*dvalue (< 2^53) 157 | function am1(i, x, w, j, c, n) { 158 | while (--n >= 0) { 159 | var v = x * this[i++] + w[j] + c; 160 | c = Math.floor(v / 0x4000000); 161 | w[j++] = v & 0x3ffffff; 162 | } 163 | return c; 164 | } 165 | // am2 avoids a big mult-and-extract completely. 166 | // Max digit bits should be <= 30 because we do bitwise ops 167 | // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) 168 | function am2(i, x, w, j, c, n) { 169 | var xl = x & 0x7fff, 170 | xh = x >> 15; 171 | while (--n >= 0) { 172 | var l = this[i] & 0x7fff; 173 | var h = this[i++] >> 15; 174 | var m = xh * l + h * xl; 175 | l = xl * l + ((m & 0x7fff) << 15) + w[j] + (c & 0x3fffffff); 176 | c = (l >>> 30) + (m >>> 15) + xh * h + (c >>> 30); 177 | w[j++] = l & 0x3fffffff; 178 | } 179 | return c; 180 | } 181 | // Alternately, set max digit bits to 28 since some 182 | // browsers slow down when dealing with 32-bit numbers. 183 | function am3(i, x, w, j, c, n) { 184 | var xl = x & 0x3fff, 185 | xh = x >> 14; 186 | while (--n >= 0) { 187 | var l = this[i] & 0x3fff; 188 | var h = this[i++] >> 14; 189 | var m = xh * l + h * xl; 190 | l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; 191 | c = (l >> 28) + (m >> 14) + xh * h; 192 | w[j++] = l & 0xfffffff; 193 | } 194 | return c; 195 | } 196 | // if (j_lm && (navigator.appName == "Microsoft Internet Explorer")) { 197 | // BigInteger.prototype.am = am2; 198 | // dbits = 30; 199 | // } else if (j_lm && (navigator.appName != "Netscape")) { 200 | // BigInteger.prototype.am = am1; 201 | // dbits = 26; 202 | // } else { // Mozilla/Netscape seems to prefer am3 203 | // BigInteger.prototype.am = am3; 204 | // dbits = 28; 205 | // } 206 | BigInteger.prototype.am = am3; 207 | dbits = 28; 208 | BigInteger.prototype.DB = dbits; 209 | BigInteger.prototype.DM = ((1 << dbits) - 1); 210 | BigInteger.prototype.DV = (1 << dbits); 211 | 212 | var BI_FP = 52; 213 | BigInteger.prototype.FV = Math.pow(2, BI_FP); 214 | BigInteger.prototype.F1 = BI_FP - dbits; 215 | BigInteger.prototype.F2 = 2 * dbits - BI_FP; 216 | 217 | // Digit conversions 218 | var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; 219 | var BI_RC = new Array(); 220 | var rr, vv; 221 | rr = "0".charCodeAt(0); 222 | for (vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; 223 | rr = "a".charCodeAt(0); 224 | for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; 225 | rr = "A".charCodeAt(0); 226 | for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; 227 | 228 | function int2char(n) { 229 | return BI_RM.charAt(n); 230 | } 231 | 232 | function intAt(s, i) { 233 | var c = BI_RC[s.charCodeAt(i)]; 234 | return (c == null) ? -1 : c; 235 | } 236 | 237 | // (protected) copy this to r 238 | function bnpCopyTo(r) { 239 | for (var i = this.t - 1; i >= 0; --i) r[i] = this[i]; 240 | r.t = this.t; 241 | r.s = this.s; 242 | } 243 | 244 | // (protected) set from integer value x, -DV <= x < DV 245 | function bnpFromInt(x) { 246 | this.t = 1; 247 | this.s = (x < 0) ? -1 : 0; 248 | if (x > 0) this[0] = x; 249 | else if (x < -1) this[0] = x + DV; 250 | else this.t = 0; 251 | } 252 | 253 | // return bigint initialized to value 254 | function nbv(i) { 255 | var r = nbi(); 256 | r.fromInt(i); 257 | return r; 258 | } 259 | 260 | // (protected) set from string and radix 261 | function bnpFromString(s, b) { 262 | var k; 263 | if (b == 16) k = 4; 264 | else if (b == 8) k = 3; 265 | else if (b == 256) k = 8; // byte array 266 | else if (b == 2) k = 1; 267 | else if (b == 32) k = 5; 268 | else if (b == 4) k = 2; 269 | else { 270 | this.fromRadix(s, b); 271 | return; 272 | } 273 | this.t = 0; 274 | this.s = 0; 275 | var i = s.length, 276 | mi = false, 277 | sh = 0; 278 | while (--i >= 0) { 279 | var x = (k == 8) ? s[i] & 0xff : intAt(s, i); 280 | if (x < 0) { 281 | if (s.charAt(i) == "-") mi = true; 282 | continue; 283 | } 284 | mi = false; 285 | if (sh == 0) 286 | this[this.t++] = x; 287 | else if (sh + k > this.DB) { 288 | this[this.t - 1] |= (x & ((1 << (this.DB - sh)) - 1)) << sh; 289 | this[this.t++] = (x >> (this.DB - sh)); 290 | } else 291 | this[this.t - 1] |= x << sh; 292 | sh += k; 293 | if (sh >= this.DB) sh -= this.DB; 294 | } 295 | if (k == 8 && (s[0] & 0x80) != 0) { 296 | this.s = -1; 297 | if (sh > 0) this[this.t - 1] |= ((1 << (this.DB - sh)) - 1) << sh; 298 | } 299 | this.clamp(); 300 | if (mi) BigInteger.ZERO.subTo(this, this); 301 | } 302 | 303 | // (protected) clamp off excess high words 304 | function bnpClamp() { 305 | var c = this.s & this.DM; 306 | while (this.t > 0 && this[this.t - 1] == c) --this.t; 307 | } 308 | 309 | // (public) return string representation in given radix 310 | function bnToString(b) { 311 | if (this.s < 0) return "-" + this.negate().toString(b); 312 | var k; 313 | if (b == 16) k = 4; 314 | else if (b == 8) k = 3; 315 | else if (b == 2) k = 1; 316 | else if (b == 32) k = 5; 317 | else if (b == 4) k = 2; 318 | else return this.toRadix(b); 319 | var km = (1 << k) - 1, 320 | d, m = false, 321 | r = "", 322 | i = this.t; 323 | var p = this.DB - (i * this.DB) % k; 324 | if (i-- > 0) { 325 | if (p < this.DB && (d = this[i] >> p) > 0) { 326 | m = true; 327 | r = int2char(d); 328 | } 329 | while (i >= 0) { 330 | if (p < k) { 331 | d = (this[i] & ((1 << p) - 1)) << (k - p); 332 | d |= this[--i] >> (p += this.DB - k); 333 | } else { 334 | d = (this[i] >> (p -= k)) & km; 335 | if (p <= 0) { 336 | p += this.DB; 337 | --i; 338 | } 339 | } 340 | if (d > 0) m = true; 341 | if (m) r += int2char(d); 342 | } 343 | } 344 | return m ? r : "0"; 345 | } 346 | 347 | // (public) -this 348 | function bnNegate() { 349 | var r = nbi(); 350 | BigInteger.ZERO.subTo(this, r); 351 | return r; 352 | } 353 | 354 | // (public) |this| 355 | function bnAbs() { 356 | return (this.s < 0) ? this.negate() : this; 357 | } 358 | 359 | // (public) return + if this > a, - if this < a, 0 if equal 360 | function bnCompareTo(a) { 361 | var r = this.s - a.s; 362 | if (r != 0) return r; 363 | var i = this.t; 364 | r = i - a.t; 365 | if (r != 0) return r; 366 | while (--i >= 0) 367 | if ((r = this[i] - a[i]) != 0) return r; 368 | return 0; 369 | } 370 | 371 | // returns bit length of the integer x 372 | function nbits(x) { 373 | var r = 1, 374 | t; 375 | if ((t = x >>> 16) != 0) { 376 | x = t; 377 | r += 16; 378 | } 379 | if ((t = x >> 8) != 0) { 380 | x = t; 381 | r += 8; 382 | } 383 | if ((t = x >> 4) != 0) { 384 | x = t; 385 | r += 4; 386 | } 387 | if ((t = x >> 2) != 0) { 388 | x = t; 389 | r += 2; 390 | } 391 | if ((t = x >> 1) != 0) { 392 | x = t; 393 | r += 1; 394 | } 395 | return r; 396 | } 397 | 398 | // (public) return the number of bits in "this" 399 | function bnBitLength() { 400 | if (this.t <= 0) return 0; 401 | return this.DB * (this.t - 1) + nbits(this[this.t - 1] ^ (this.s & this.DM)); 402 | } 403 | 404 | // (protected) r = this << n*DB 405 | function bnpDLShiftTo(n, r) { 406 | var i; 407 | for (i = this.t - 1; i >= 0; --i) r[i + n] = this[i]; 408 | for (i = n - 1; i >= 0; --i) r[i] = 0; 409 | r.t = this.t + n; 410 | r.s = this.s; 411 | } 412 | 413 | // (protected) r = this >> n*DB 414 | function bnpDRShiftTo(n, r) { 415 | for (var i = n; i < this.t; ++i) r[i - n] = this[i]; 416 | r.t = Math.max(this.t - n, 0); 417 | r.s = this.s; 418 | } 419 | 420 | // (protected) r = this << n 421 | function bnpLShiftTo(n, r) { 422 | var bs = n % this.DB; 423 | var cbs = this.DB - bs; 424 | var bm = (1 << cbs) - 1; 425 | var ds = Math.floor(n / this.DB), 426 | c = (this.s << bs) & this.DM, 427 | i; 428 | for (i = this.t - 1; i >= 0; --i) { 429 | r[i + ds + 1] = (this[i] >> cbs) | c; 430 | c = (this[i] & bm) << bs; 431 | } 432 | for (i = ds - 1; i >= 0; --i) r[i] = 0; 433 | r[ds] = c; 434 | r.t = this.t + ds + 1; 435 | r.s = this.s; 436 | r.clamp(); 437 | } 438 | 439 | // (protected) r = this >> n 440 | function bnpRShiftTo(n, r) { 441 | r.s = this.s; 442 | var ds = Math.floor(n / this.DB); 443 | if (ds >= this.t) { 444 | r.t = 0; 445 | return; 446 | } 447 | var bs = n % this.DB; 448 | var cbs = this.DB - bs; 449 | var bm = (1 << bs) - 1; 450 | r[0] = this[ds] >> bs; 451 | for (var i = ds + 1; i < this.t; ++i) { 452 | r[i - ds - 1] |= (this[i] & bm) << cbs; 453 | r[i - ds] = this[i] >> bs; 454 | } 455 | if (bs > 0) r[this.t - ds - 1] |= (this.s & bm) << cbs; 456 | r.t = this.t - ds; 457 | r.clamp(); 458 | } 459 | 460 | // (protected) r = this - a 461 | function bnpSubTo(a, r) { 462 | var i = 0, 463 | c = 0, 464 | m = Math.min(a.t, this.t); 465 | while (i < m) { 466 | c += this[i] - a[i]; 467 | r[i++] = c & this.DM; 468 | c >>= this.DB; 469 | } 470 | if (a.t < this.t) { 471 | c -= a.s; 472 | while (i < this.t) { 473 | c += this[i]; 474 | r[i++] = c & this.DM; 475 | c >>= this.DB; 476 | } 477 | c += this.s; 478 | } else { 479 | c += this.s; 480 | while (i < a.t) { 481 | c -= a[i]; 482 | r[i++] = c & this.DM; 483 | c >>= this.DB; 484 | } 485 | c -= a.s; 486 | } 487 | r.s = (c < 0) ? -1 : 0; 488 | if (c < -1) r[i++] = this.DV + c; 489 | else if (c > 0) r[i++] = c; 490 | r.t = i; 491 | r.clamp(); 492 | } 493 | 494 | // (protected) r = this * a, r != this,a (HAC 14.12) 495 | // "this" should be the larger one if appropriate. 496 | function bnpMultiplyTo(a, r) { 497 | var x = this.abs(), 498 | y = a.abs(); 499 | var i = x.t; 500 | r.t = i + y.t; 501 | while (--i >= 0) r[i] = 0; 502 | for (i = 0; i < y.t; ++i) r[i + x.t] = x.am(0, y[i], r, i, 0, x.t); 503 | r.s = 0; 504 | r.clamp(); 505 | if (this.s != a.s) BigInteger.ZERO.subTo(r, r); 506 | } 507 | 508 | // (protected) r = this^2, r != this (HAC 14.16) 509 | function bnpSquareTo(r) { 510 | var x = this.abs(); 511 | var i = r.t = 2 * x.t; 512 | while (--i >= 0) r[i] = 0; 513 | for (i = 0; i < x.t - 1; ++i) { 514 | var c = x.am(i, x[i], r, 2 * i, 0, 1); 515 | if ((r[i + x.t] += x.am(i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { 516 | r[i + x.t] -= x.DV; 517 | r[i + x.t + 1] = 1; 518 | } 519 | } 520 | if (r.t > 0) r[r.t - 1] += x.am(i, x[i], r, 2 * i, 0, 1); 521 | r.s = 0; 522 | r.clamp(); 523 | } 524 | 525 | // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) 526 | // r != q, this != m. q or r may be null. 527 | function bnpDivRemTo(m, q, r) { 528 | var pm = m.abs(); 529 | if (pm.t <= 0) return; 530 | var pt = this.abs(); 531 | if (pt.t < pm.t) { 532 | if (q != null) q.fromInt(0); 533 | if (r != null) this.copyTo(r); 534 | return; 535 | } 536 | if (r == null) r = nbi(); 537 | var y = nbi(), 538 | ts = this.s, 539 | ms = m.s; 540 | var nsh = this.DB - nbits(pm[pm.t - 1]); // normalize modulus 541 | if (nsh > 0) { 542 | pm.lShiftTo(nsh, y); 543 | pt.lShiftTo(nsh, r); 544 | } else { 545 | pm.copyTo(y); 546 | pt.copyTo(r); 547 | } 548 | var ys = y.t; 549 | var y0 = y[ys - 1]; 550 | if (y0 == 0) return; 551 | var yt = y0 * (1 << this.F1) + ((ys > 1) ? y[ys - 2] >> this.F2 : 0); 552 | var d1 = this.FV / yt, 553 | d2 = (1 << this.F1) / yt, 554 | e = 1 << this.F2; 555 | var i = r.t, 556 | j = i - ys, 557 | t = (q == null) ? nbi() : q; 558 | y.dlShiftTo(j, t); 559 | if (r.compareTo(t) >= 0) { 560 | r[r.t++] = 1; 561 | r.subTo(t, r); 562 | } 563 | BigInteger.ONE.dlShiftTo(ys, t); 564 | t.subTo(y, y); // "negative" y so we can replace sub with am later 565 | while (y.t < ys) y[y.t++] = 0; 566 | while (--j >= 0) { 567 | // Estimate quotient digit 568 | var qd = (r[--i] == y0) ? this.DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); 569 | if ((r[i] += y.am(0, qd, r, j, 0, ys)) < qd) { // Try it out 570 | y.dlShiftTo(j, t); 571 | r.subTo(t, r); 572 | while (r[i] < --qd) r.subTo(t, r); 573 | } 574 | } 575 | if (q != null) { 576 | r.drShiftTo(ys, q); 577 | if (ts != ms) BigInteger.ZERO.subTo(q, q); 578 | } 579 | r.t = ys; 580 | r.clamp(); 581 | if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder 582 | if (ts < 0) BigInteger.ZERO.subTo(r, r); 583 | } 584 | 585 | // (public) this mod a 586 | function bnMod(a) { 587 | var r = nbi(); 588 | this.abs().divRemTo(a, null, r); 589 | if (this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r, r); 590 | return r; 591 | } 592 | 593 | // Modular reduction using "classic" algorithm 594 | function Classic(m) { 595 | this.m = m; 596 | } 597 | 598 | function cConvert(x) { 599 | if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); 600 | else return x; 601 | } 602 | 603 | function cRevert(x) { 604 | return x; 605 | } 606 | 607 | function cReduce(x) { 608 | x.divRemTo(this.m, null, x); 609 | } 610 | 611 | function cMulTo(x, y, r) { 612 | x.multiplyTo(y, r); 613 | this.reduce(r); 614 | } 615 | 616 | function cSqrTo(x, r) { 617 | x.squareTo(r); 618 | this.reduce(r); 619 | } 620 | 621 | Classic.prototype.convert = cConvert; 622 | Classic.prototype.revert = cRevert; 623 | Classic.prototype.reduce = cReduce; 624 | Classic.prototype.mulTo = cMulTo; 625 | Classic.prototype.sqrTo = cSqrTo; 626 | 627 | // (protected) return "-1/this % 2^DB"; useful for Mont. reduction 628 | // justification: 629 | // xy == 1 (mod m) 630 | // xy = 1+km 631 | // xy(2-xy) = (1+km)(1-km) 632 | // x[y(2-xy)] = 1-k^2m^2 633 | // x[y(2-xy)] == 1 (mod m^2) 634 | // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 635 | // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. 636 | // JS multiply "overflows" differently from C/C++, so care is needed here. 637 | function bnpInvDigit() { 638 | if (this.t < 1) return 0; 639 | var x = this[0]; 640 | if ((x & 1) == 0) return 0; 641 | var y = x & 3; // y == 1/x mod 2^2 642 | y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 643 | y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 644 | y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 645 | // last step - calculate inverse mod DV directly; 646 | // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints 647 | y = (y * (2 - x * y % this.DV)) % this.DV; // y == 1/x mod 2^dbits 648 | // we really want the negative inverse, and -DV < y < DV 649 | return (y > 0) ? this.DV - y : -y; 650 | } 651 | 652 | // Montgomery reduction 653 | function Montgomery(m) { 654 | this.m = m; 655 | this.mp = m.invDigit(); 656 | this.mpl = this.mp & 0x7fff; 657 | this.mph = this.mp >> 15; 658 | this.um = (1 << (m.DB - 15)) - 1; 659 | this.mt2 = 2 * m.t; 660 | } 661 | 662 | // xR mod m 663 | function montConvert(x) { 664 | var r = nbi(); 665 | x.abs().dlShiftTo(this.m.t, r); 666 | r.divRemTo(this.m, null, r); 667 | if (x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r, r); 668 | return r; 669 | } 670 | 671 | // x/R mod m 672 | function montRevert(x) { 673 | var r = nbi(); 674 | x.copyTo(r); 675 | this.reduce(r); 676 | return r; 677 | } 678 | 679 | // x = x/R mod m (HAC 14.32) 680 | function montReduce(x) { 681 | while (x.t <= this.mt2) // pad x so am has enough room later 682 | x[x.t++] = 0; 683 | for (var i = 0; i < this.m.t; ++i) { 684 | // faster way of calculating u0 = x[i]*mp mod DV 685 | var j = x[i] & 0x7fff; 686 | var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & x.DM; 687 | // use am to combine the multiply-shift-add into one call 688 | j = i + this.m.t; 689 | x[j] += this.m.am(0, u0, x, i, 0, this.m.t); 690 | // propagate carry 691 | while (x[j] >= x.DV) { 692 | x[j] -= x.DV; 693 | x[++j]++; 694 | } 695 | } 696 | x.clamp(); 697 | x.drShiftTo(this.m.t, x); 698 | if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); 699 | } 700 | 701 | // r = "x^2/R mod m"; x != r 702 | function montSqrTo(x, r) { 703 | x.squareTo(r); 704 | this.reduce(r); 705 | } 706 | 707 | // r = "xy/R mod m"; x,y != r 708 | function montMulTo(x, y, r) { 709 | x.multiplyTo(y, r); 710 | this.reduce(r); 711 | } 712 | 713 | Montgomery.prototype.convert = montConvert; 714 | Montgomery.prototype.revert = montRevert; 715 | Montgomery.prototype.reduce = montReduce; 716 | Montgomery.prototype.mulTo = montMulTo; 717 | Montgomery.prototype.sqrTo = montSqrTo; 718 | 719 | // (protected) true iff this is even 720 | function bnpIsEven() { 721 | return ((this.t > 0) ? (this[0] & 1) : this.s) == 0; 722 | } 723 | 724 | // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) 725 | function bnpExp(e, z) { 726 | if (e > 0xffffffff || e < 1) return BigInteger.ONE; 727 | var r = nbi(), 728 | r2 = nbi(), 729 | g = z.convert(this), 730 | i = nbits(e) - 1; 731 | g.copyTo(r); 732 | while (--i >= 0) { 733 | z.sqrTo(r, r2); 734 | if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); 735 | else { 736 | var t = r; 737 | r = r2; 738 | r2 = t; 739 | } 740 | } 741 | return z.revert(r); 742 | } 743 | 744 | // (public) this^e % m, 0 <= e < 2^32 745 | function bnModPowInt(e, m) { 746 | var z; 747 | if (e < 256 || m.isEven()) z = new Classic(m); 748 | else z = new Montgomery(m); 749 | return this.exp(e, z); 750 | } 751 | 752 | // protected 753 | BigInteger.prototype.copyTo = bnpCopyTo; 754 | BigInteger.prototype.fromInt = bnpFromInt; 755 | BigInteger.prototype.fromString = bnpFromString; 756 | BigInteger.prototype.clamp = bnpClamp; 757 | BigInteger.prototype.dlShiftTo = bnpDLShiftTo; 758 | BigInteger.prototype.drShiftTo = bnpDRShiftTo; 759 | BigInteger.prototype.lShiftTo = bnpLShiftTo; 760 | BigInteger.prototype.rShiftTo = bnpRShiftTo; 761 | BigInteger.prototype.subTo = bnpSubTo; 762 | BigInteger.prototype.multiplyTo = bnpMultiplyTo; 763 | BigInteger.prototype.squareTo = bnpSquareTo; 764 | BigInteger.prototype.divRemTo = bnpDivRemTo; 765 | BigInteger.prototype.invDigit = bnpInvDigit; 766 | BigInteger.prototype.isEven = bnpIsEven; 767 | BigInteger.prototype.exp = bnpExp; 768 | 769 | // public 770 | BigInteger.prototype.toString = bnToString; 771 | BigInteger.prototype.negate = bnNegate; 772 | BigInteger.prototype.abs = bnAbs; 773 | BigInteger.prototype.compareTo = bnCompareTo; 774 | BigInteger.prototype.bitLength = bnBitLength; 775 | BigInteger.prototype.mod = bnMod; 776 | BigInteger.prototype.modPowInt = bnModPowInt; 777 | 778 | // "constants" 779 | BigInteger.ZERO = nbv(0); 780 | BigInteger.ONE = nbv(1); 781 | 782 | //====================================================rng.js===================================================================// 783 | // Random number generator - requires a PRNG backend, e.g. prng4.js 784 | 785 | // For best results, put code like 786 | // 787 | // in your main HTML document. 788 | 789 | var rng_state; 790 | var rng_pool; 791 | var rng_pptr; 792 | 793 | // Mix in a 32-bit integer into the pool 794 | function rng_seed_int(x) { 795 | rng_pool[rng_pptr++] ^= x & 255; 796 | rng_pool[rng_pptr++] ^= (x >> 8) & 255; 797 | rng_pool[rng_pptr++] ^= (x >> 16) & 255; 798 | rng_pool[rng_pptr++] ^= (x >> 24) & 255; 799 | if (rng_pptr >= rng_psize) rng_pptr -= rng_psize; 800 | } 801 | 802 | // Mix in the current time (w/milliseconds) into the pool 803 | function rng_seed_time() { 804 | rng_seed_int(new Date().getTime()); 805 | } 806 | 807 | // Initialize the pool with junk if needed. 808 | if (rng_pool == null) { 809 | rng_pool = new Array(); 810 | rng_pptr = 0; 811 | var t; 812 | // if (navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto && window.crypto.random) { 813 | // // Extract entropy (256 bits) from NS4 RNG if available 814 | // var z = window.crypto.random(32); 815 | // for (t = 0; t < z.length; ++t) 816 | // rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; 817 | // } 818 | while (rng_pptr < rng_psize) { // extract some randomness from Math.random() 819 | t = Math.floor(65536 * Math.random()); 820 | rng_pool[rng_pptr++] = t >>> 8; 821 | rng_pool[rng_pptr++] = t & 255; 822 | } 823 | rng_pptr = 0; 824 | rng_seed_time(); 825 | //rng_seed_int(window.screenX); 826 | //rng_seed_int(window.screenY); 827 | } 828 | 829 | function rng_get_byte() { 830 | if (rng_state == null) { 831 | rng_seed_time(); 832 | rng_state = prng_newstate(); 833 | rng_state.init(rng_pool); 834 | for (rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) 835 | rng_pool[rng_pptr] = 0; 836 | rng_pptr = 0; 837 | //rng_pool = null; 838 | } 839 | // TODO: allow reseeding after first request 840 | return rng_state.next(); 841 | } 842 | 843 | function rng_get_bytes(ba) { 844 | var i; 845 | for (i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); 846 | } 847 | 848 | function SecureRandom() {} 849 | 850 | SecureRandom.prototype.nextBytes = rng_get_bytes; 851 | 852 | 853 | //===============================================prng4==========================================================================// 854 | // prng4.js - uses Arcfour as a PRNG 855 | 856 | function Arcfour() { 857 | this.i = 0; 858 | this.j = 0; 859 | this.S = new Array(); 860 | } 861 | 862 | // Initialize arcfour context from key, an array of ints, each from [0..255] 863 | function ARC4init(key) { 864 | var i, j, t; 865 | for (i = 0; i < 256; ++i) 866 | this.S[i] = i; 867 | j = 0; 868 | for (i = 0; i < 256; ++i) { 869 | j = (j + this.S[i] + key[i % key.length]) & 255; 870 | t = this.S[i]; 871 | this.S[i] = this.S[j]; 872 | this.S[j] = t; 873 | } 874 | this.i = 0; 875 | this.j = 0; 876 | } 877 | 878 | function ARC4next() { 879 | var t; 880 | this.i = (this.i + 1) & 255; 881 | this.j = (this.j + this.S[this.i]) & 255; 882 | t = this.S[this.i]; 883 | this.S[this.i] = this.S[this.j]; 884 | this.S[this.j] = t; 885 | return this.S[(t + this.S[this.i]) & 255]; 886 | } 887 | 888 | Arcfour.prototype.init = ARC4init; 889 | Arcfour.prototype.next = ARC4next; 890 | 891 | // Plug in your RNG constructor here 892 | function prng_newstate() { 893 | return new Arcfour(); 894 | } 895 | 896 | // Pool size must be a multiple of 4 and greater than 32. 897 | // An array of bytes the size of the pool will be passed to init() 898 | var rng_psize = 256; 899 | 900 | //rsa加密 901 | function rsa_encrypt(rawValue, key, mod) { 902 | //公钥 903 | key = key || "F20CE00BAE5361F8FA3AE9CEFA495362FF7DA1BA628F64A347F0A8C012BF0B254A30CD92ABFFE7A6EE0DC424CB6166F8819EFA5BCCB20EDFB4AD02E412CCF579B1CA711D55B8B0B3AEB60153D5E0693A2A86F3167D7847A0CB8B00004716A9095D9BADC977CBB804DBDCBA6029A9710869A453F27DFDDF83C016D928B3CBF4C7"; 904 | mod = mod || "3"; 905 | var _RSA = new RSAKey(); //生成rsa加密对象 906 | _RSA.setPublic(key, mod); //设置公钥和mod,PublicKey是1(2)中打印的hex值 907 | return _RSA.encrypt(rawValue); 908 | } 909 | return { 910 | encrypt: rsa_encrypt 911 | } 912 | }(); 913 | -------------------------------------------------------------------------------- /wx-tencent-im/utils/tea.js: -------------------------------------------------------------------------------- 1 | var base64 = require("base64.js"); 2 | var __key = '', 3 | __pos = 0, 4 | __plain = [], 5 | __prePlain = [], 6 | __cryptPos = 0, // 当前密文块位置 7 | __preCryptPos = 0, // 上一个密文块位置 8 | __out = [], // 保存加密/解密的输出 9 | __cipher = [], // 输出的密文 10 | /*用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的, 11 | 但是最开始的8个字节没有反馈可用,所有需要标明这种情况*/ 12 | __header = true; 13 | function __rand() { 14 | return Math.round(Math.random()*0xffffffff); 15 | } 16 | /** 17 | * 将数据转化为无符号整形 18 | */ 19 | function __getUInt(data, offset, len) { 20 | if (!len || len > 4) 21 | len = 4; 22 | var ret = 0; 23 | for (var i=offset; i>> 0; // 无符号化 28 | } 29 | /** 30 | 把整形数据填充到数组里,要注意端序 31 | */ 32 | function __intToBytes(data, offset, value) { 33 | data[offset+3] = (value >> 0) & 0xff; 34 | data[offset+2] = (value >> 8) & 0xff; 35 | data[offset+1] = (value >> 16) & 0xff; 36 | data[offset+0] = (value >> 24) & 0xff; 37 | } 38 | function __bytesInStr(data) { 39 | if (!data) return ""; 40 | var outInHex = ""; 41 | for (var i=0; i 0x0 && code <= 0x7f){ 71 | //单字节 72 | //UTF-16 0000 - 007F 73 | //UTF-8 0xxxxxxx 74 | ret.push(s.charAt(i)); 75 | }else if(code >= 0x80 && code <= 0x7ff){ 76 | //双字节 77 | //UTF-16 0080 - 07FF 78 | //UTF-8 110xxxxx 10xxxxxx 79 | ret.push( 80 | //110xxxxx 81 | String.fromCharCode(0xc0 | ((code >> 6) & 0x1f)), 82 | //10xxxxxx 83 | String.fromCharCode(0x80 | (code & 0x3f)) 84 | ); 85 | }else if(code >= 0x800 && code <= 0xffff){ 86 | //三字节 87 | //UTF-16 0800 - FFFF 88 | //UTF-8 1110xxxx 10xxxxxx 10xxxxxx 89 | ret.push( 90 | //1110xxxx 91 | String.fromCharCode(0xe0 | ((code >> 12) & 0xf)), 92 | //10xxxxxx 93 | String.fromCharCode(0x80 | ((code >> 6) & 0x3f)), 94 | //10xxxxxx 95 | String.fromCharCode(0x80 | (code & 0x3f)) 96 | ); 97 | } 98 | } 99 | 100 | return ret.join(''); 101 | } 102 | 103 | function __encrypt(data) { 104 | __plain = new Array(8); 105 | __prePlain = new Array(8); 106 | __cryptPos = __preCryptPos = 0; 107 | __header = true; 108 | __pos = 0; 109 | var len = data.length; 110 | var padding = 0; 111 | 112 | __pos = (len + 0x0A) % 8; 113 | if (__pos != 0) 114 | __pos = 8 - __pos; 115 | __out = new Array(len + __pos + 10); 116 | __plain[0] = ((__rand() & 0xF8) | __pos ) & 0xFF; 117 | 118 | for (var i=1; i<=__pos; i++) 119 | __plain[i] = __rand() & 0xFF; 120 | __pos++; 121 | 122 | for (var i=0; i<8; i++) 123 | __prePlain[i] = 0; 124 | 125 | padding = 1; 126 | while (padding <= 2) { 127 | if (__pos < 8) { 128 | __plain[__pos++] = __rand() & 0xFF; 129 | padding++; 130 | } 131 | if (__pos == 8) 132 | __encrypt8bytes(); 133 | } 134 | 135 | var i = 0; 136 | while (len > 0) { 137 | if (__pos < 8) { 138 | __plain[__pos++] = data[i++]; 139 | len--; 140 | } 141 | if (__pos == 8) 142 | __encrypt8bytes(); 143 | } 144 | 145 | padding = 1; 146 | while (padding <= 7) { 147 | if (__pos < 8) { 148 | __plain[__pos++] = 0; 149 | padding++; 150 | } 151 | if (__pos == 8) 152 | __encrypt8bytes(); 153 | } 154 | 155 | return __out; 156 | } 157 | function __decrypt(data) { 158 | var count = 0; 159 | var m = new Array(8); 160 | var len = data.length; 161 | __cipher = data; 162 | 163 | if (len % 8 != 0 || len < 16) 164 | return null; 165 | /* 第一个8字节,加密的时候因为prePlain是全0,所以可以直接解密,得到消息的头部, 166 | 关键是可以得到真正明文开始的位置 167 | */ 168 | __prePlain = __decipher(data); 169 | __pos = __prePlain[0] & 0x7; 170 | count = len - __pos - 10; // 真正的明文长度 171 | if (count < 0) 172 | return null; 173 | 174 | // 临时的preCrypt, 与加密时对应,全0的prePlain 对应 全0的preCrypt 175 | for (var i=0; i>> 0; 264 | 265 | while (loop-- > 0) { 266 | sum += delta; 267 | sum = (sum & 0xFFFFFFFF) >>> 0; 268 | y += ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b); 269 | y = (y & 0xFFFFFFFF) >>> 0; 270 | z += ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d); 271 | z = (z & 0xFFFFFFFF) >>> 0; 272 | } 273 | var bytes = new Array(8); 274 | __intToBytes(bytes, 0, y); 275 | __intToBytes(bytes, 4, z); 276 | return bytes; 277 | } 278 | function __decipher(data) { 279 | var loop = 16; 280 | var y = __getUInt(data, 0, 4); 281 | var z = __getUInt(data, 4, 4); 282 | var a = __getUInt(__key, 0, 4); 283 | var b = __getUInt(__key, 4, 4); 284 | var c = __getUInt(__key, 8, 4); 285 | var d = __getUInt(__key, 12, 4); 286 | var sum = 0xE3779B90 >>> 0; 287 | var delta = 0x9E3779B9 >>> 0; 288 | 289 | while (loop-- > 0) { 290 | z -= ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d); 291 | z = (z & 0xFFFFFFFF) >>> 0; 292 | y -= ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b); 293 | y = (y & 0xFFFFFFFF) >>> 0; 294 | sum -= delta; 295 | sum = (sum & 0xFFFFFFFF) >>> 0; 296 | } 297 | 298 | var bytes = new Array(8); 299 | __intToBytes(bytes, 0, y); 300 | __intToBytes(bytes, 4, z); 301 | return bytes; 302 | } 303 | function __decrypt8Bytes() { 304 | var len = __cipher.length; 305 | for (var i=0; i<8; i++) { 306 | __prePlain[i] ^= __cipher[__cryptPos + i]; 307 | } 308 | 309 | __prePlain = __decipher(__prePlain); 310 | 311 | __cryptPos += 8; 312 | __pos = 0; 313 | return true; 314 | } 315 | /** 316 | * 把输入字符串转换为javascript array 317 | */ 318 | function __dataFromStr(str, isASCII) { 319 | var data = []; 320 | if (isASCII) { 321 | for (var i=0; i> 16, (b10 >> 8) & 0xff, b10 & 0xff)); 408 | } 409 | 410 | switch (pads) { 411 | case 1: 412 | b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6) 413 | x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff)); 414 | break; 415 | case 2: 416 | b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12); 417 | x.push(String.fromCharCode(b10 >> 16)); 418 | break; 419 | } 420 | return x.join(''); 421 | } 422 | */ 423 | 424 | base64.getbyte = function(s,i) { 425 | var x = s.charCodeAt(i); 426 | if (x > 255) { 427 | throw "INVALID_CHARACTER_ERR: DOM Exception 5"; 428 | } 429 | return x; 430 | } 431 | 432 | base64.encode = function(s) { 433 | if (arguments.length != 1) { 434 | throw "SyntaxError: Not enough arguments"; 435 | } 436 | var padchar = base64.PADCHAR; 437 | var alpha = base64.ALPHA; 438 | var getbyte = base64.getbyte; 439 | 440 | var i, b10; 441 | var x = []; 442 | 443 | // convert to string 444 | s = "" + s; 445 | 446 | var imax = s.length - s.length % 3; 447 | 448 | if (s.length == 0) { 449 | return s; 450 | } 451 | for (i = 0; i < imax; i += 3) { 452 | b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2); 453 | x.push(alpha.charAt(b10 >> 18)); 454 | x.push(alpha.charAt((b10 >> 12) & 0x3F)); 455 | x.push(alpha.charAt((b10 >> 6) & 0x3f)); 456 | x.push(alpha.charAt(b10 & 0x3f)); 457 | } 458 | switch (s.length - imax) { 459 | case 1: 460 | b10 = getbyte(s,i) << 16; 461 | x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + 462 | padchar + padchar); 463 | break; 464 | case 2: 465 | b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8); 466 | x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + 467 | alpha.charAt((b10 >> 6) & 0x3f) + padchar); 468 | break; 469 | } 470 | return x.join(''); 471 | } 472 | 473 | module.exports = TEA; 474 | -------------------------------------------------------------------------------- /wx-tencent-im/utils/tls.js: -------------------------------------------------------------------------------- 1 | var encrypt = require('encrypt.js'); 2 | 3 | var sdkappid = 10001; 4 | 5 | 6 | function login(opts){ 7 | var user = "user" + parseInt(Math.random(0, 1) * 1000000) ; 8 | wx.request({ 9 | url: 'https://sxb.qcloud.com/sxb_dev/?svc=doll&cmd=fetchsig', //仅为示例,并非真实的接口地址 10 | data: { 11 | "id": user, 12 | "appid": sdkappid 13 | }, 14 | method: 'post', 15 | header: { 16 | 'content-type': 'application/json' 17 | }, 18 | success: function(res) { 19 | opts.success && opts.success({ 20 | Identifier: user, 21 | UserSig: res.data.data.userSig 22 | }); 23 | }, 24 | fail : function(errMsg){ 25 | opts.error && opts.error(errMsg); 26 | } 27 | }); 28 | } 29 | 30 | module.exports = { 31 | init : function(opts){ 32 | sdkappid = opts.sdkappid; 33 | }, 34 | login : login 35 | }; -------------------------------------------------------------------------------- /wx-tencent-im/utils/util.js: -------------------------------------------------------------------------------- 1 | function formatTime(date) { 2 | var year = date.getFullYear() 3 | var month = date.getMonth() + 1 4 | var day = date.getDate() 5 | 6 | var hour = date.getHours() 7 | var minute = date.getMinutes() 8 | var second = date.getSeconds() 9 | 10 | 11 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 12 | } 13 | 14 | function formatNumber(n) { 15 | n = n.toString() 16 | return n[1] ? n : '0' + n 17 | } 18 | 19 | function getDateDiff(dateTimeStamp) { 20 | var result; 21 | var minute = 1000 * 60; 22 | var hour = minute * 60; 23 | var day = hour * 24; 24 | var halfamonth = day * 15; 25 | var month = day * 30; 26 | var now = new Date().getTime(); 27 | var diffValue = now - dateTimeStamp; 28 | if (diffValue < 0) { 29 | return; 30 | } 31 | var monthC = diffValue / month; 32 | var weekC = diffValue / (7 * day); 33 | var dayC = diffValue / day; 34 | var hourC = diffValue / hour; 35 | var minC = diffValue / minute; 36 | if (monthC >= 1) { 37 | if (monthC <= 12) 38 | result = "" + parseInt(monthC) + "月前"; 39 | else { 40 | result = "" + parseInt(monthC / 12) + "年前"; 41 | } 42 | } 43 | else if (weekC >= 1) { 44 | result = "" + parseInt(weekC) + "周前"; 45 | } 46 | else if (dayC >= 1) { 47 | result = "" + parseInt(dayC) + "天前"; 48 | } 49 | else if (hourC >= 1) { 50 | result = "" + parseInt(hourC) + "小时前"; 51 | } 52 | else if (minC >= 1) { 53 | result = "" + parseInt(minC) + "分钟前"; 54 | } else { 55 | result = "刚刚"; 56 | } 57 | return result; 58 | }; 59 | 60 | 61 | module.exports = { 62 | formatTime: formatTime, 63 | getDateDiff: getDateDiff 64 | } 65 | 66 | 67 | --------------------------------------------------------------------------------