This repository was archived by the owner on Nov 20, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
8 changed files
with
1,499 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,5 @@ run/ | |
.DS_Store | ||
*.sw* | ||
*.un~ | ||
config.json | ||
qrcode.jpg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', // 微博-未知 | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) |
Oops, something went wrong.