Skip to content
This repository was archived by the owner on Nov 20, 2019. It is now read-only.

Commit

Permalink
feat: v0.1.0
Browse files Browse the repository at this point in the history
完成基础sdk框架
binsee committed Mar 20, 2018

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 8dad475 commit 613b2ae
Showing 8 changed files with 1,499 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -10,3 +10,5 @@ run/
.DS_Store
*.sw*
*.un~
config.json
qrcode.jpg
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Padchat-sdk

## 说明

Padchat本地服务器的开发包。通过websocket协议与运行在windows平台上的微信ipad协议服务程序通讯,来登陆与操作微信。

## 开发说明

请勿将配套的exe程序泄露给非项目协作者使用,否则将封key并拒绝再参与内部开发。



## 通讯接口

### 1. 服务地址

地址: ws://api.batorange.com/ws

### 2. 通信协议

WebSocket 通信协议

* [websockets 官方github](https://github.com/websockets)
* [NodeJs 参考](https://github.com/websockets/ws)

#### 连接授权

后续更新。

#### API请求操作结果(识别异步请求)

由于websocket自身是异步操作,未原生支持识别请求返回的结果(即向服务端发送一个请求,服务端返回执行结果,客户端却无法确认是自己这个主动请求的结果,或者是另一个请求的返回结果,或者是服务端主动推送)。因此本服务增加了一个字段`cmdId`,用于标识请求,并在返回操作结果时一块返回。

如果希望发送api请求后,能识别服务端执行本次请求后的返回结果,可提供`cmdId`字段,请一定提供随机值,建议使用`uuid`模块随机生成。当收到服务端推送过来的数据中包含`cmdId`字段时,即可确认为之前请求对应的执行结果。
建议结合使用`Promise`+`Event.once(cmdId)`来实现。

#### 数据规则约定

> TODO:需要在sdk内统一字段命名,建议统一转换为`小驼峰`写法。
微信协议原生接口返回的所有数据字段名称下划线写法(如`user_name`)。

API请求的数据结构中,所有字段名称为`小驼峰`写法。

推送回来的数据结构中,第一级字段名称为`小驼峰`写法,`data`字段下所有字段名称为`下划线`写法。

### 3. API请求结构

API请求是以websocket协议发送的json数据,以下为json数据的字段

| **名称** | **类型** | **描述** | **必选** |
| --------- | ------ | ---------------- | ------ |
| type | String | 请求类型:`sys`/`user` ||
| cmd | String | API命令 ||
| cmdId | String | 指令id。用于识别API异步操作结果,操作结果会增加此属性推送回来 ||
| data | Object | 取决于是不同的API要求 ||

#### data字段总述

此部分为请求API指令时,需要附加的data数据。根据使用的API不同,需要提供不同的字段及对应数据。

字段名称 | 说明 | 备注
-----|----|---
**发送消息** | |
toUserName | 目标用户/群id | 群id包含@chatroom部分
content | 文本内容 | 文本消息内容<br>App消息xml结构体<br>名片自定义标题<br>添加好友时,为验证信息
image | 图片base64编码 | 发送图片消息<br>上传头像<br>朋友圈上传图片
atList | 要at的用户`数组` | `["wxid1","wxid2"]` <br>文本消息时有效
**群及好友管理** | |
roomName | 群名称
userIds | 用户id列表数组 | `["wxid1","wxid2"]` <br>创建群
chatroom | 要操作的群id
remark | 备注名称
userId | 要操作的用户id | 主动添加好友<br>好友验证<br>添加/邀请用户进入群
stranger | V1码,相对加密的userId | 接受好友请求(仅限stranger字段)<br>主动添加好友(也可使用`userId`字段)
ticket | V2码,好友请求中的ticket | 添加单向好友<br>接受好友请求
type | 添加好友来源 | `1`搜索QQ号;`2`邮箱搜索;`3`微信号;<br>`4`来自QQ好友;`8`通过群聊; `15`手机号<br>默认为 微信号
**其他** | |
rawMsgData | 原始MsgData数据(即接收到的push的data字段) | 接收红包<br>接收转账<br>获取原始图片(可删除掉json中的data字段减少数据量,即缩略图base64)

40 changes: 40 additions & 0 deletions define.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict'

exports.wsEventType = [
'log',
'cmdRet',
'userEvent',
'sysEvent',
]

exports.userEvents = [
'qrcode', // 登陆二维码
'scan', // 扫码登陆状态
'login', // 登陆完成
'loaded', // 通讯录载入完毕
'logout', // 注销登录(账户退出)
'close', // 任务断线
'warn', // 错误
'sns', // 朋友圈事件(朋友圈小圆点)
// 'push', // 推送消息(系统、好友消息、联系人等)
]

exports.loginType = {
token : 'token', // 断线重连
request : 'request', // 二次登陆
qrcode : 'qrcode', // 扫码登陆
phone : 'phone', // 手机验证码登陆
user : 'user', // 账号密码登陆
}

exports.blacklist = [
'weixin', // 腾讯团队
'newsapp', // 腾讯新闻
'tmessage', //
'fmessage', // 朋友推荐
'qmessage', // qq离线消息
'floatbottle', // 漂流瓶
'medianote', // 语音记事本
'mphelper', // 公众平台安全助手
'weibo', // 微博-未知
]
241 changes: 241 additions & 0 deletions demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
'use strict'

const log4js = require('log4js')
const Padchat = require('./index')
const fs = require('fs')

/**
* 创建日志目录
*/

try {
require('fs').mkdirSync('./logs')
} catch (e) {
if (e.code !== 'EEXIST') {
console.error('Could not set up log directory, error: ', e)
process.exit(1)
}
}

try {
log4js.configure('./log4js.json')
} catch (e) {
console.error('载入log4js日志输出配置错误: ', e)
process.exit(1);
}

const logger = log4js.getLogger('app')

logger.info('demo start!')

const autoData = {
wxData: '',
token: '',
}
const server = 'ws://127.0.0.1:7777'

try {
const tmpBuf = fs.readFileSync('./config.json')
const data = JSON.parse(String(tmpBuf))
autoData.wxData = data.wxData
autoData.token = data.token
logger.info('载入设备参数: %o \n\n自动登陆数据:%o ', autoData)
} catch (e) {
logger.warn('没有在本地发现设备登录参数或解析数据失败!如首次登录请忽略!')
}

const wx = new Padchat(server)
logger.info('当前连接接口服务器为:')

wx
.on('close', () => {
logger.info('与服务器连接断开!')
})
.on('open', async () => {
let ret
logger.info('连接成功!')

// 非首次登录时最好使用以前成功登录时使用的设备参数,
// 否则可能会被tx服务器怀疑账号被盗,导致手机端被登出
ret = await wx.init()
if (!ret.success) {
logger.error('新建任务失败!', ret)
return
}
logger.info('新建任务成功, json: ', ret)

if (autoData.token) {
ret = await wx.login('request', autoData)
if (ret.success) {
logger.info('自动登录成功!', ret)
return
}
logger.warn('自动登录失败!', ret)
}

ret = await wx.login('qrcode')
if (!ret.success) {
logger.error('使用qrcode登录模式失败!', ret)
return
}
logger.info('使用qrcode登录模式!')
})
.on('qrcode', data => {
if (!data.qrCode) {
logger.error('没有在数据中获得登陆二维码!', data)
return
}
fs.writeFileSync('./qrcode.jpg', Buffer.from(data.qrCode || '', 'base64'))
logger.info('登陆二维码已经写入到 ./qrcode.jpg,请打开扫码登陆!')
})
.on('scan', data => {
switch (data.status) {
case 0:
logger.info('等待扫码...', data)
break;
case 1:
logger.info('已扫码,请在手机端确认登陆...', data)
break;
case 2:
switch (data.subStatus) {
case 0:
logger.info('扫码成功!登陆成功!', data)
break;
case 1:
logger.info('扫码成功!登陆失败!', data)
break;
default:
logger.info('扫码成功!未知状态码!', data)
break;
}
break;
case 3:
logger.info('二维码已过期!', data)
break;
case 4:
logger.info('手机端已取消登陆!', data)
break;
default:
break;
}
})
.on('login', async () => {
logger.info('微信账号登陆成功!')
let ret
ret = await wx.getWxData()
if (!ret.success) {
logger.warn('获取设备参数未成功! json:', ret)
return
}
logger.info('获取设备参数成功, json: ', ret)

const tmp = Object.assign({}, ret.data)

ret = await wx.getLoginToken()
if (!ret.success) {
logger.warn('获取自动登陆数据未成功! json:', ret)
return
}
logger.info('获取自动登陆数据成功, json: ', ret)
Object.assign(tmp, { token: ret.data.token })

// NOTE: 这里将设备参数保存到本地,以后再次登录此账号时提供相同参数
fs.writeFileSync('./config.json', JSON.stringify(tmp))
logger.info('设备参数已写入到 ./config.json文件')
})
.on('logout', ({ msg }) => {
logger.info('微信账号已退出!', msg)
})
.on('over', ({ msg }) => {
logger.info('任务实例已关闭!', msg)
})
.on('loaded', async () => {
logger.info('通讯录同步完毕!')

const ret = await wx.sendMsg('filehelper', '你登录了!')
logger.info('发送信息结果:', ret)
})
.on('sns', (data, msg) => {
logger.info('收到朋友圈事件!请查看朋友圈新消息哦!', msg)
})
.on('push', async data => {
// 消息类型 data.msgType
// 1 文字消息
// 2 好友信息推送,包含好友,群,公众号信息
// 3 收到图片消息
// 34 语音消息
// 35 用户头像buf
// 37 收到好友请求消息
// 42 名片消息
// 43 视频消息
// 47 表情消息
// 48 定位消息
// 49 APP消息(文件 或者 链接 H5)
// 50 语音通话
// 51 状态通知(如打开与好友/群的聊天界面)
// 52 语音通话通知
// 53 语音通话邀请
// 62 小视频
// 2000 转账消息
// 2001 收到红包消息
// 3000 群邀请
// 9999 系统通知
// 10000 微信通知信息. 微信群信息变更通知,多为群名修改,进群,离群信息,不包含群内聊天信息
// 10002 撤回消息
// --------------------------------
// 注意,如果是来自微信群的消息,data.content字段中包含发言人的wxid及其发言内容,需要自行提取
// 各类复杂消息,data.content中是xml格式的文本内容,需要自行从中提取各类数据。(如好友请求)
let ret

switch (data.mType) {
case 2:
logger.info('收到推送联系人:', data.nickName)
break

case 1:
if (data.fromUser === 'newsapp') { // 腾讯新闻发的信息太长
break
}
logger.info('收到来自 %s 的文本消息:', data.fromUser, data.description || data.content)
if (/ding/.test(data.content)) {
await wx.sendMsg(data.fromUser, 'dong')
.then(ret => {
logger.info('回复信息给%s 结果:', data.fromUser, ret)
})
.catch(e => {
logger.warn('回复信息异常:', e.message)
})
}
break

case 34:
logger.info('收到来自 %s 的语言消息,现在转发给文件传输助手', data.fromUser)
await wx.sendVoice('filehelper', data.data)
.then(ret => {
logger.info('转发语音信息给%s 结果:', data.fromUser, ret)
})
.catch(e => {
logger.warn('转发语音信息异常:', e.message)
})
break

default:
logger.info('收到推送消息:', data)
break
}
})
.on('error', e => {
logger.error('ws 错误:', e)
})
.on('warn', e => {
logger.error('任务出现错误:', e)
})


process.on('uncaughtException', e => {
logger.error('Main', 'uncaughtException:', e)
})

process.on('unhandledRejection', e => {
logger.error('Main', 'unhandledRejection:', e)
})
Loading

0 comments on commit 613b2ae

Please sign in to comment.