提交 c95f75df 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓)

move FriendRequest implementation to the base class (#1196)

上级 82b1e0cb
......@@ -6,10 +6,9 @@
Wechaty is a Bot Framework for Wechat **Personal** Account which can help you create a bot in 6 lines of javascript, with cross-platform support include [Linux](https://travis-ci.org/chatie/wechaty), [Windows](https://ci.appveyor.com/project/chatie/wechaty), [Darwin(OSX/Mac)](https://travis-ci.org/chatie/wechaty) and [Docker](https://app.shippable.com/github/Chatie/wechaty).
[![node](https://img.shields.io/node/v/wechaty.svg?maxAge=604800)](https://nodejs.org/)
[![NPM Version](https://badge.fury.io/js/wechaty.svg)](https://badge.fury.io/js/wechaty)
[![Docker Pulls](https://img.shields.io/docker/pulls/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-blue.svg)](https://www.typescriptlang.org/)
[![Repo Size](https://reposs.herokuapp.com/?path=Chatie/wechaty)](https://github.com/chatie/wechaty)
[![Donate Wechaty](https://img.shields.io/badge/Donate-Wechaty%20$-green.svg)](https://salt.bountysource.com/checkout/amount?team=chatie)
:octocat: <https://github.com/chatie/wechaty>
:beetle: <https://github.com/chatie/wechaty/issues>
......@@ -54,6 +53,9 @@ You can find more examples from [Wiki](https://github.com/chatie/wechaty/wiki/Ex
## GETTING STARTED
[![node](https://img.shields.io/node/v/wechaty.svg?maxAge=604800)](https://nodejs.org/)
[![Repo Size](https://reposs.herokuapp.com/?path=Chatie/wechaty)](https://github.com/chatie/wechaty)
### A Great Live Coding Tutorial
<div align="center">
......@@ -77,7 +79,9 @@ Notice: The published versions have always passed the CI tests. We highly recomm
#### Docker
[![Docker Pulls](https://img.shields.io/docker/pulls/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/) [![Docker Stars](https://img.shields.io/docker/stars/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/) [![Docker Layers](https://images.microbadger.com/badges/image/zixia/wechaty.svg)](https://microbadger.com/#/images/zixia/wechaty)
[![Docker Pulls](https://img.shields.io/docker/pulls/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/)
[![Docker Stars](https://img.shields.io/docker/stars/zixia/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/zixia/wechaty/)
[![Docker Layers](https://images.microbadger.com/badges/image/zixia/wechaty.svg)](https://microbadger.com/#/images/zixia/wechaty)
The **best practice** to use Wechaty is running with docker, because it's not only the most easy way to get started, but also protects you from the troubles of dependency problems.
......@@ -150,6 +154,8 @@ See: [Official API Reference](https://chatie.github.io/wechaty/)
## POWERED BY WECHATY
[![Donate Wechaty](https://img.shields.io/badge/Donate-Wechaty%20$-green.svg)](https://salt.bountysource.com/checkout/amount?team=chatie)
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/chatie/wechaty)
### Wechaty Badge
......
......@@ -30,6 +30,7 @@ import {
// Contact,
log,
Wechaty,
FriendRequest,
} from '../src/'
const welcome = `
......@@ -75,16 +76,16 @@ bot
* Wechaty Event: `friend`
*
*/
.on('friend', async (contact, request) => {
.on('friend', async request => {
let logMsg
const fileHelper = bot.Contact.load('filehelper')
try {
logMsg = 'received `friend` event from ' + contact.name()
logMsg = 'received `friend` event from ' + request.contact().name()
fileHelper.say(logMsg)
console.log(logMsg)
if (request) {
switch (request.type()) {
/**
*
* 1. New Friend Request
......@@ -92,20 +93,24 @@ bot
* when request is set, we can get verify message from `request.hello`,
* and accept this request by `request.accept()`
*/
if (request.hello() === 'ding') {
logMsg = 'accepted because verify messsage is "ding"'
request.accept()
} else {
logMsg = 'not auto accepted, because verify message is: ' + request.hello
}
} else {
/**
*
* 2. Friend Ship Confirmed
*
*/
logMsg = 'friend ship confirmed with ' + contact.name()
case FriendRequest.Type.RECEIVE:
if (request.hello() === 'ding') {
logMsg = 'accepted because verify messsage is "ding"'
request.accept()
} else {
logMsg = 'not auto accepted, because verify message is: ' + request.hello
}
break
/**
*
* 2. Friend Ship Confirmed
*
*/
case FriendRequest.Type.CONFIRM:
logMsg = 'friend ship confirmed with ' + request.contact().name()
break
}
} catch (e) {
logMsg = e.message
......
......@@ -23,7 +23,6 @@
* when you are runing with Docker or NPM instead of Git Source.
*/
import {
Contact,
FriendRequest,
Wechaty,
// Room,
......@@ -31,14 +30,16 @@ import {
export async function onFriend(
this: Wechaty,
contact: Contact,
request?: FriendRequest,
request: FriendRequest,
): Promise<void> {
try {
if (!request) {
const contact = request.contact()
if (request.type() === FriendRequest.Type.CONFIRM) {
console.log('New friend ' + contact.name() + ' relationship confirmed!')
return
}
/********************************************
*
* 从这里开始修改 vvvvvvvvvvvv
......
......@@ -17,63 +17,8 @@
* @ignore
*/
import {
// config,
log,
} from '../config'
import { FriendRequest } from '../puppet/'
import { FriendRequest, FriendRequestType } from '../puppet/'
import { MockContact } from './mock-contact'
export class MockFriendRequest extends FriendRequest {
public payload: any
private ticket: string
private _contact: MockContact
private _type: FriendRequestType
private _hello: string
constructor() {
log.verbose('MockFriendRequest', 'constructor()')
super()
}
public receive(payload: any): void {
log.verbose('MockFriendRequest', 'receive(%s)', payload)
this.payload = payload
}
public confirm(contact: MockContact): void {
log.verbose('MockFriendRequest', 'confirm(%s)', contact)
}
public async send(contact: MockContact, hello = 'Hi'): Promise<void> {
log.verbose('MockFriendRequest', 'send(%s)', contact)
await this.puppet.friendRequestSend(contact, hello)
}
public async accept(): Promise<void> {
log.verbose('FriendRequest', 'accept() %s', this.contact)
await this.puppet.friendRequestAccept(this.contact(), this.ticket)
}
public contact(): MockContact {
return this._contact
}
public type(): FriendRequestType {
return this._type
}
public async reject(): Promise<void> {
log.verbose('MockFriendRequest', 'reject()')
}
public hello(): string {
return this._hello
}
}
export class MockFriendRequest extends FriendRequest {}
export default MockFriendRequest
......@@ -32,7 +32,7 @@ import { MockRoom } from './mock-room'
import {
WebMsgType,
AppMsgType,
} from '../puppet-puppeteer/schema'
} from '../puppet/schemas/'
export type ParsedPath = Partial<path.ParsedPath>
......
......@@ -41,7 +41,7 @@ import {
WebMessageMediaPayload,
WebMessageRawPayload,
WebContactRawPayload,
} from './schema'
} from '../puppet/schemas/'
import {
PuppeteerRoomRawPayload,
} from './puppet-puppeteer'
......
......@@ -35,7 +35,7 @@ import PuppetPuppeteer from './puppet-puppeteer'
import {
WebMsgType,
WebMessageRawPayload,
} from './schema'
} from '../puppet/schemas/'
/* tslint:disable:variable-name */
export const Event = {
......
......@@ -24,10 +24,16 @@ import {
log,
} from '../config'
import {
WebRecomendInfo,
FriendRequest,
} from '../puppet/'
import PuppetPuppeteer from './puppet-puppeteer'
import PuppeteerContact from './puppeteer-contact'
import PuppeteerFriendRequest from './puppeteer-friend-request'
import {
PuppeteerFriendRequest,
} from './puppeteer-friend-request'
import PuppeteerMessage from './puppeteer-message'
/* tslint:disable:variable-name */
......@@ -105,28 +111,38 @@ const regexConfig = {
async function checkFriendRequest(
this: PuppetPuppeteer,
msg: PuppeteerMessage,
) {
): Promise<void> {
if (!msg.rawObj) {
throw new Error('message empty')
throw new Error('no rawPayload')
} else if (!msg.rawObj.RecommendInfo) {
throw new Error('no RecommendInfo')
}
const info = msg.rawObj.RecommendInfo
log.verbose('PuppetPuppeteerFirer', 'fireFriendRequest(%s)', info)
const rawPayload: WebRecomendInfo = msg.rawObj.RecommendInfo
log.verbose('PuppetPuppeteerFirer', 'fireFriendRequest(%s)', rawPayload)
if (!info) {
throw new Error('no info')
if (!rawPayload) {
throw new Error('no rawPayload')
}
const request = new PuppeteerFriendRequest()
request.puppet = msg.puppet
const contact = PuppeteerContact.load(rawPayload.UserName)
contact.puppet = msg.puppet
request.receive(info)
const hello = rawPayload.Content
const ticket = rawPayload.Ticket
await request.contact().ready()
if (!request.contact().isReady()) {
await contact.ready()
if (!contact.isReady()) {
log.warn('PuppetPuppeteerFirer', 'fireFriendConfirm() contact still not ready after `ready()` call')
}
this.emit('friend', request.contact(), request)
const receivedRequest = FriendRequest.createReceive(
contact,
hello,
ticket,
)
receivedRequest.puppet = msg.puppet
this.emit('friend', receivedRequest)
}
/**
......@@ -157,17 +173,20 @@ async function checkFriendConfirm(
if (!parseFriendConfirm.call(this, content)) {
return
}
const request = new PuppeteerFriendRequest()
request.puppet = m.puppet
const contact = m.from()
request.confirm(contact)
const confirmedRequest = PuppeteerFriendRequest.createConfirm(
contact,
)
confirmedRequest.puppet = m.puppet
await contact.ready()
if (!contact.isReady()) {
log.warn('PuppetPuppeteerFirer', 'fireFriendConfirm() contact still not ready after `ready()` call')
}
this.emit('friend', contact)
this.emit('friend', confirmedRequest)
}
/**
......
......@@ -49,8 +49,7 @@ import Profile from '../profile'
import Misc from '../misc'
import {
WebContactRawPayload,
} from './schema'
} from '../puppet/schemas/'
import {
Bridge,
Cookie,
......@@ -58,11 +57,12 @@ import {
import Event from './event'
import {
WebContactRawPayload,
WebMessageMediaPayload,
WebMessageRawPayload,
WebMediaType,
WebMsgType,
} from './schema'
} from '../puppet/'
import {
PuppeteerContact,
......
......@@ -28,33 +28,50 @@ import Profile from '../profile'
import Wechaty from '../wechaty'
import {
Puppet,
// Puppet,
// FriendRequest,
WebRecomendInfo,
} from '../puppet/'
import {
PuppetMock,
} from '../puppet-mock/'
// import {
// PuppetMock,
// } from '../puppet-mock/'
import PuppeteerContact from './puppeteer-contact'
import PuppeteerMessage from './puppeteer-message'
import PuppeteerFriendRequest from './puppeteer-friend-request'
import { PuppetPuppeteer } from './puppet-puppeteer'
test('PuppetPuppeteerFriendRequest.receive smoke testing', async t => {
// tslint:disable-next-line:variable-name
const MyFriendRequest = cloneClass(PuppeteerFriendRequest)
MyFriendRequest.puppet = {
userId: 'xxx',
} as any as Puppet
// tslint:disable-next-line:variable-name
const MyContact = cloneClass(PuppeteerContact)
const puppet = new PuppetPuppeteer({
profile: new Profile(),
wechaty: new Wechaty(),
})
MyFriendRequest.puppet = MyContact.puppet = puppet
/* tslint:disable:max-line-length */
const rawMessageData = `
{"MsgId":"3225371967511173931","FromUserName":"fmessage","ToUserName":"@f7321198e0349f1b38c9f2ef158f70eb","MsgType":37,"Content":"&lt;msg fromusername=\\"wxid_a8d806dzznm822\\" encryptusername=\\"v1_c1e03a32c60dd9a9e14f1092132808a2de0ad363f79b303693654282954fbe4d3e12481166f4b841f28de3dd58b0bd54@stranger\\" fromnickname=\\"李卓桓.PreAngel\\" content=\\"我是群聊&amp;quot;Wechaty&amp;quot;的李卓桓.PreAngel\\" shortpy=\\"LZHPREANGEL\\" imagestatus=\\"3\\" scene=\\"14\\" country=\\"CN\\" province=\\"Beijing\\" city=\\"Haidian\\" sign=\\"投资人中最会飞的程序员。好友请加 918999 ,因为本号好友已满。\\" percard=\\"1\\" sex=\\"1\\" alias=\\"zixia008\\" weibo=\\"\\" weibonickname=\\"\\" albumflag=\\"0\\" albumstyle=\\"0\\" albumbgimgid=\\"911623988445184_911623988445184\\" snsflag=\\"49\\" snsbgimgid=\\"http://mmsns.qpic.cn/mmsns/zZSYtpeVianSQYekFNbuiajROicLficBzzeGuvQjnWdGDZ4budZovamibQnoKWba7D2LeuQRPffS8aeE/0\\" snsbgobjectid=\\"12183966160653848744\\" mhash=\\"\\" mfullhash=\\"\\" bigheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/0\\" smallheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/132\\" ticket=\\"v2_ba70dfbdb1b10168d61c1ab491be19e219db11ed5c28701f605efb4dccbf132f664d8a4c9ef6e852b2a4e8d8638be81d125c2e641f01903669539c53f1e582b2@stranger\\" opcode=\\"2\\" googlecontact=\\"\\" qrticket=\\"\\" chatroomusername=\\"2332413729@chatroom\\" sourceusername=\\"\\" sourcenickname=\\"\\"&gt;&lt;brandlist count=\\"0\\" ver=\\"670564024\\"&gt;&lt;/brandlist&gt;&lt;/msg&gt;","Status":3,"ImgStatus":1,"CreateTime":1475567560,"VoiceLength":0,"PlayLength":0,"FileName":"","FileSize":"","MediaId":"","Url":"","AppMsgType":0,"StatusNotifyCode":0,"StatusNotifyUserName":"","RecommendInfo":{"UserName":"@04a0fa314d0d8d50dc54e2ec908744ebf46b87404d143fd9a6692182dd90bd49","NickName":"李卓桓.PreAngel","Province":"北京","City":"海淀","Content":"我是群聊\\"Wechaty\\"的李卓桓.PreAngel","Signature":"投资人中最会飞的程序员。好友请加 918999 ,因为本号好友已满。","Alias":"zixia008","Scene":14,"AttrStatus":233251,"Sex":1,"Ticket":"v2_ba70dfbdb1b10168d61c1ab491be19e219db11ed5c28701f605efb4dccbf132f664d8a4c9ef6e852b2a4e8d8638be81d125c2e641f01903669539c53f1e582b2@stranger","OpCode":2,"HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@04a0fa314d0d8d50dc54e2ec908744ebf46b87404d143fd9a6692182dd90bd49&skey=@crypt_f9cec94b_5b073dca472bd5e41771d309bb8c37bd&msgid=3225371967511173931","MMFromVerifyMsg":true},"ForwardFlag":0,"AppInfo":{"AppID":"","Type":0},"HasProductId":0,"Ticket":"","ImgHeight":0,"ImgWidth":0,"SubMsgType":0,"NewMsgId":3225371967511174000,"MMPeerUserName":"fmessage","MMDigest":"李卓桓.PreAngel想要将你加为朋友","MMIsSend":false,"MMIsChatRoom":false,"MMUnread":true,"LocalID":"3225371967511173931","ClientMsgId":"3225371967511173931","MMActualContent":"&lt;msg fromusername=\\"wxid_a8d806dzznm822\\" encryptusername=\\"v1_c1e03a32c60dd9a9e14f1092132808a2de0ad363f79b303693654282954fbe4d3e12481166f4b841f28de3dd58b0bd54@stranger\\" fromnickname=\\"李卓桓.PreAngel\\" content=\\"我是群聊&amp;quot;Wechaty&amp;quot;的李卓桓.PreAngel\\" shortpy=\\"LZHPREANGEL\\" imagestatus=\\"3\\" scene=\\"14\\" country=\\"CN\\" province=\\"Beijing\\" city=\\"Haidian\\" sign=\\"投资人中最会飞的程序员。好友请加 918999 ,因为本号好友已满。\\" percard=\\"1\\" sex=\\"1\\" alias=\\"zixia008\\" weibo=\\"\\" weibonickname=\\"\\" albumflag=\\"0\\" albumstyle=\\"0\\" albumbgimgid=\\"911623988445184_911623988445184\\" snsflag=\\"49\\" snsbgimgid=\\"http://mmsns.qpic.cn/mmsns/zZSYtpeVianSQYekFNbuiajROicLficBzzeGuvQjnWdGDZ4budZovamibQnoKWba7D2LeuQRPffS8aeE/0\\" snsbgobjectid=\\"12183966160653848744\\" mhash=\\"\\" mfullhash=\\"\\" bigheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/0\\" smallheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/132\\" ticket=\\"v2_ba70dfbdb1b10168d61c1ab491be19e219db11ed5c28701f605efb4dccbf132f664d8a4c9ef6e852b2a4e8d8638be81d125c2e641f01903669539c53f1e582b2@stranger\\" opcode=\\"2\\" googlecontact=\\"\\" qrticket=\\"\\" chatroomusername=\\"2332413729@chatroom\\" sourceusername=\\"\\" sourcenickname=\\"\\"&gt;&lt;brandlist count=\\"0\\" ver=\\"670564024\\"&gt;&lt;/brandlist&gt;&lt;/msg&gt;","MMActualSender":"fmessage","MMDigestTime":"15:52","MMDisplayTime":1475567560,"MMTime":"15:52"}
`
const rawObj = JSON.parse(rawMessageData)
const fr = new MyFriendRequest()
fr.receive(rawObj.RecommendInfo)
const info = (rawObj.RecommendInfo as WebRecomendInfo)
const contact = MyContact.load(info.UserName)
const hello = info.Content
const ticket = info.Ticket
const fr = MyFriendRequest.createReceive(
contact,
hello,
ticket,
)
t.true(typeof fr.payload === 'object', 'should has info object')
t.is(fr.hello(), '我是群聊"Wechaty"的李卓桓.PreAngel', 'should has right request message')
t.true(fr.contact() instanceof PuppeteerContact, 'should have a Contact instance')
t.is(fr.type(), MyFriendRequest.Type.RECEIVE, 'should be receive type')
......@@ -68,9 +85,12 @@ test('PuppetPuppeteerFriendRequest.confirm smoke testing', async t => {
// tslint:disable-next-line:variable-name
const MyMessage = cloneClass(PuppeteerMessage)
MyContact.puppet = MyMessage.puppet = MyFriendRequest.puppet = {
userId: 'xxx',
} as any as Puppet
const puppet = new PuppetPuppeteer({
profile: new Profile(),
wechaty: new Wechaty(),
})
MyMessage.puppet = MyFriendRequest.puppet = MyContact.puppet = puppet
/* tslint:disable:max-line-length */
const rawMessageData = `
......@@ -78,16 +98,12 @@ test('PuppetPuppeteerFriendRequest.confirm smoke testing', async t => {
`
const rawObj = JSON.parse(rawMessageData)
const m = new MyMessage(rawObj)
m.puppet = new PuppetMock({
profile: new Profile(),
wechaty: new Wechaty(),
})
m.puppet = puppet
t.true(/^You have added (.+) as your WeChat contact. Start chatting!$/.test(m.text()), 'should match confirm message')
const fr = new MyFriendRequest()
const contact = m.from()
fr.confirm(contact || new MyContact('xx'))
const fr = MyFriendRequest.createConfirm(contact || new MyContact('xx'))
t.true(fr.contact() instanceof PuppeteerContact, 'should have a Contact instance')
t.is(fr.type(), MyFriendRequest.Type.CONFIRM, 'should be confirm type')
......
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* @ignore
*/
import FriendRequest from '../puppet/friend-request'
/**
* request/accept: https://github.com/Chatie/wechaty/issues/33
*
* 1. send request
* 2. receive request(in friend event)
* 3. confirmation friendship(friend event)
* @ignore
*/
/* tslint:disable:no-var-requires */
const retryPromise = require('retry-promise').default
import {
// config,
log,
} from '../config'
import {
FriendRequest,
FriendRequestType,
} from '../puppet/'
import {
WebRecommendPayload,
} from './schema'
import PuppeteerContact from './puppeteer-contact'
/**
* @alias FriendRequest
*/
export class PuppeteerFriendRequest extends FriendRequest {
public payload: WebRecommendPayload
private ticket: string
private _contact: PuppeteerContact
private _hello: string
private _type: FriendRequestType
constructor() {
log.verbose('PuppeteerFriendRequest', 'constructor()')
super()
}
public receive(payload: WebRecommendPayload): void {
log.verbose('PuppeteerFriendRequest', 'receive(%s)', payload)
if (!payload || !payload.UserName) {
throw new Error('not valid RecommendInfo: ' + payload)
}
this.payload = payload
const contact = PuppeteerContact.load(payload.UserName)
contact.puppet = this.puppet
this._contact = contact
this._hello = payload.Content
this.ticket = payload.Ticket
// ??? this.nick = info.NickName
if (!this.ticket) {
throw new Error('ticket not found')
}
this._type = FriendRequestType.RECEIVE
return
}
public confirm(contact: PuppeteerContact): void {
log.verbose('PuppeteerFriendRequest', 'confirm(%s)', contact)
if (!contact) {
throw new Error('contact not found')
}
this._contact = contact
this._type = FriendRequestType.CONFIRM
}
/**
* Send a new friend request
* @param {PuppeteerContact} contact
* @param {string} [hello='Hi']
* @returns {Promise<boolean>} Return a Promise, true for accept successful, false for failure.
* @example
* const from = message.from()
* const request = new FriendRequest()
* request.send(from, 'hello~')
*/
public async send(contact: PuppeteerContact, hello = 'Hi'): Promise<void> {
log.verbose('PuppeteerFriendRequest', 'send(%s)', contact)
if (!contact) {
throw new Error('contact not found')
}
this._contact = contact
this._type = FriendRequestType.SEND
if (hello) {
this._hello = hello
}
await this.puppet.friendRequestSend(contact, hello)
}
/**
* Accept a friend request
*
* @returns {Promise<void>} Return a Promise, true for accept successful, false for failure.
*/
public async accept(): Promise<void> {
log.verbose('FriendRequest', 'accept() %s', this.contact)
if (this._type !== FriendRequestType.RECEIVE) {
throw new Error('request is not a `receive` type. it is a ' + this.type + ' type')
}
await this.puppet.friendRequestAccept(this.contact(), this.ticket)
const max = 20
const backoff = 300
const timeout = max * (backoff * max) / 2
// 20 / 300 => 63,000
// max = (2*totalTime/backoff) ^ (1/2)
// timeout = 11,250 for {max: 15, backoff: 100}
// refresh to wait contact ready
await retryPromise({ max: max, backoff: backoff }, async (attempt: number) => {
log.silly('PuppeteerFriendRequest', 'accept() retryPromise() attempt %d with timeout %d', attempt, timeout)
await this.contact().ready()
if (this.contact().isReady()) {
log.verbose('PuppeteerFriendRequest', 'accept() with contact %s ready()', this.contact().name())
return
}
throw new Error('FriendRequest.accept() content.ready() not ready')
}).catch((e: Error) => {
log.warn('PuppeteerFriendRequest', 'accept() rejected for contact %s because %s', this.contact, e && e.message || e)
})
}
public hello(): string {
return this._hello
}
public contact(): PuppeteerContact {
return this._contact
}
public async reject(): Promise<void> {
log.warn('PuppeteerFriendRequest', 'reject() not necessary, NOP.')
return
}
public type(): FriendRequestType {
return this._type
}
}
export class PuppeteerFriendRequest extends FriendRequest {}
export default PuppeteerFriendRequest
......@@ -42,7 +42,7 @@ import {
WebMsgPayload,
WebMessageRawPayload,
WebMsgType,
} from './schema'
} from '../puppet/schemas/'
// export type TypeName = 'attachment'
// | 'audio'
......
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {
Gender,
} from '../puppet/'
export interface WebContactRawPayload {
Alias: string,
City: string,
NickName: string,
Province: string,
RemarkName: string,
Sex: Gender,
Signature: string,
StarFriend: string,
Uin: string,
UserName: string,
HeadImgUrl: string,
stranger: string, // assign by injectio.js
VerifyFlag: number,
}
export interface WebMessageMediaPayload {
ToUserName: string,
MsgType: number,
MediaId: string,
FileName: string,
FileSize: number,
FileMd5?: string,
FileType?: number,
MMFileExt?: string,
Signature?: string,
}
export interface WebMessageRawPayload {
MsgId: string,
MMActualSender: string, // getUserContact(message.MMActualSender,message.MMPeerUserName).isContact()
MMPeerUserName: string, // message.MsgType == CONF.MSGTYPE_TEXT && message.MMPeerUserName == 'newsapp'
ToUserName: string,
FromUserName: string,
MMActualContent: string, // Content has @id prefix added by wx
Content: string,
MMDigest: string,
MMDisplayTime: number, // Javascript timestamp of milliseconds
/**
* MsgType == MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_URL
* class="cover" mm-src="{{getMsgImg(message.MsgId,'slave')}}"
*/
Url: string,
MMAppMsgDesc: string, // class="desc" ng-bind="message.MMAppMsgDesc"
/**
* Attachment
*
* MsgType == MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_ATTACH
*/
FileName: string, // FileName: '钢甲互联项目BP1108.pdf',
FileSize: number, // FileSize: '2845701',
MediaId: string, // MediaId: '@crypt_b1a45e3f_c21dceb3ac01349...
MMFileExt: string, // doc, docx ... 'undefined'?
Signature: string, // checkUpload return the signature used to upload large files
MMAppMsgFileExt: string, // doc, docx ... 'undefined'?
MMAppMsgFileSize: string, // '2.7MB',
MMAppMsgDownloadUrl: string, // 'https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia?sender=@4f549c2dafd5ad731afa4d857bf03c10&mediaid=@crypt_b1a45e3f
// <a download ng-if="message.MMFileStatus == CONF.MM_SEND_FILE_STATUS_SUCCESS
// && (massage.MMStatus == CONF.MSG_SEND_STATUS_SUCC || massage.MMStatus === undefined)
// " href="{{message.MMAppMsgDownloadUrl}}">下载</a>
MMUploadProgress: number, // < 100
/**
* 模板消息
* MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_READER_TYPE
* item.url
* item.title
* item.pub_time
* item.cover
* item.digest
*/
MMCategory: any[], // item in message.MMCategory
/**
* Type
*
* MsgType == CONF.MSGTYPE_VOICE : ng-style="{'width':40 + 7*message.VoiceLength/1000}
*/
MsgType: number,
AppMsgType: AppMsgType, // message.MsgType == CONF.MSGTYPE_APP && message.AppMsgType == CONF.APPMSGTYPE_URL
// message.MsgType == CONF.MSGTYPE_TEXT && message.SubMsgType != CONF.MSGTYPE_LOCATION
SubMsgType: WebMsgType, // "msgType":"{{message.MsgType}}","subType":{{message.SubMsgType||0}},"msgId":"{{message.MsgId}}"
/**
* Status-es
*/
Status: string,
MMStatus: number, // img ng-show="message.MMStatus == 1" class="ico_loading"
// ng-click="resendMsg(message)" ng-show="message.MMStatus == 5" title="重新发送"
MMFileStatus: number, // <p class="loading" ng-show="message.MMStatus == 1 || message.MMFileStatus == CONF.MM_SEND_FILE_STATUS_FAIL">
// CONF.MM_SEND_FILE_STATUS_QUEUED, MM_SEND_FILE_STATUS_SENDING
/**
* Location
*/
MMLocationUrl: string, // ng-if="message.MsgType == CONF.MSGTYPE_TEXT && message.SubMsgType == CONF.MSGTYPE_LOCATION"
// <a href="{{message.MMLocationUrl}}" target="_blank">
// 'http://apis.map.qq.com/uri/v1/geocoder?coord=40.075041,116.338994'
MMLocationDesc: string, // MMLocationDesc: '北京市昌平区回龙观龙腾苑(五区)内(龙腾街南)',
/**
* MsgType == CONF.MSGTYPE_EMOTICON
*
* getMsgImg(message.MsgId,'big',message)
*/
/**
* Image
*
* getMsgImg(message.MsgId,'slave')
*/
MMImgStyle: string, // ng-style="message.MMImgStyle"
MMPreviewSrc: string, // message.MMPreviewSrc || message.MMThumbSrc || getMsgImg(message.MsgId,'slave')
MMThumbSrc: string,
/**
* Friend Request & ShareCard ?
*
* MsgType == CONF.MSGTYPE_SHARECARD" ng-click="showProfile($event,message.RecommendInfo.UserName)
* MsgType == CONF.MSGTYPE_VERIFYMSG
*/
RecommendInfo?: WebRecommendPayload,
/**
* Transpond Message
*/
MsgIdBeforeTranspond?: string, // oldMsg.MsgIdBeforeTranspond || oldMsg.MsgId,
isTranspond?: boolean,
MMSourceMsgId?: string,
MMSendContent?: string,
MMIsChatRoom?: boolean,
}
export interface WebMsgPayload {
id: string,
type: WebMsgType,
from: string,
to?: string, // if to is not set, then room must be set
room?: string,
content: string,
status: string,
digest: string,
date: number,
url?: string, // for MessageMedia class
}
// export type MessageTypeName = 'TEXT' | 'IMAGE' | 'VOICE' | 'VERIFYMSG' | 'POSSIBLEFRIEND_MSG'
// | 'SHARECARD' | 'VIDEO' | 'EMOTICON' | 'LOCATION' | 'APP' | 'VOIPMSG' | 'STATUSNOTIFY'
// | 'VOIPNOTIFY' | 'VOIPINVITE' | 'MICROVIDEO' | 'SYSNOTICE' | 'SYS' | 'RECALLED'
// export type MessageTypeValue = 1 | 3 | 34 | 37 | 40 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 62 | 9999 | 10000 | 10002
export interface WebMsgTypeDict {
[index: string]: string|number,
// MessageTypeName: MessageTypeValue
// , MessageTypeValue: MessageTypeName
}
/**
*
* Enum for AppMsgType values.
*
* @enum {number}
* @property {number} TEXT - AppMsgType.TEXT (1) for TEXT
* @property {number} IMG - AppMsgType.IMG (2) for IMG
* @property {number} AUDIO - AppMsgType.AUDIO (3) for AUDIO
* @property {number} VIDEO - AppMsgType.VIDEO (4) for VIDEO
* @property {number} URL - AppMsgType.URL (5) for URL
* @property {number} ATTACH - AppMsgType.ATTACH (6) for ATTACH
* @property {number} OPEN - AppMsgType.OPEN (7) for OPEN
* @property {number} EMOJI - AppMsgType.EMOJI (8) for EMOJI
* @property {number} VOICE_REMIND - AppMsgType.VOICE_REMIND (9) for VOICE_REMIND
* @property {number} SCAN_GOOD - AppMsgType.SCAN_GOOD (10) for SCAN_GOOD
* @property {number} GOOD - AppMsgType.GOOD (13) for GOOD
* @property {number} EMOTION - AppMsgType.EMOTION (15) for EMOTION
* @property {number} CARD_TICKET - AppMsgType.CARD_TICKET (16) for CARD_TICKET
* @property {number} REALTIME_SHARE_LOCATION - AppMsgType.REALTIME_SHARE_LOCATION (17) for REALTIME_SHARE_LOCATION
* @property {number} TRANSFERS - AppMsgType.TRANSFERS (2e3) for TRANSFERS
* @property {number} RED_ENVELOPES - AppMsgType.RED_ENVELOPES (2001) for RED_ENVELOPES
* @property {number} READER_TYPE - AppMsgType.READER_TYPE (100001) for READER_TYPE
*/
export enum AppMsgType {
TEXT = 1,
IMG = 2,
AUDIO = 3,
VIDEO = 4,
URL = 5,
ATTACH = 6,
OPEN = 7,
EMOJI = 8,
VOICE_REMIND = 9,
SCAN_GOOD = 10,
GOOD = 13,
EMOTION = 15,
CARD_TICKET = 16,
REALTIME_SHARE_LOCATION = 17,
TRANSFERS = 2e3,
RED_ENVELOPES = 2001,
READER_TYPE = 100001,
}
/**
*
* Enum for MsgType values.
* @enum {number}
* @property {number} TEXT - MsgType.TEXT (1) for TEXT
* @property {number} IMAGE - MsgType.IMAGE (3) for IMAGE
* @property {number} VOICE - MsgType.VOICE (34) for VOICE
* @property {number} VERIFYMSG - MsgType.VERIFYMSG (37) for VERIFYMSG
* @property {number} POSSIBLEFRIEND_MSG - MsgType.POSSIBLEFRIEND_MSG (40) for POSSIBLEFRIEND_MSG
* @property {number} SHARECARD - MsgType.SHARECARD (42) for SHARECARD
* @property {number} VIDEO - MsgType.VIDEO (43) for VIDEO
* @property {number} EMOTICON - MsgType.EMOTICON (47) for EMOTICON
* @property {number} LOCATION - MsgType.LOCATION (48) for LOCATION
* @property {number} APP - MsgType.APP (49) for APP
* @property {number} VOIPMSG - MsgType.VOIPMSG (50) for VOIPMSG
* @property {number} STATUSNOTIFY - MsgType.STATUSNOTIFY (51) for STATUSNOTIFY
* @property {number} VOIPNOTIFY - MsgType.VOIPNOTIFY (52) for VOIPNOTIFY
* @property {number} VOIPINVITE - MsgType.VOIPINVITE (53) for VOIPINVITE
* @property {number} MICROVIDEO - MsgType.MICROVIDEO (62) for MICROVIDEO
* @property {number} SYSNOTICE - MsgType.SYSNOTICE (9999) for SYSNOTICE
* @property {number} SYS - MsgType.SYS (10000) for SYS
* @property {number} RECALLED - MsgType.RECALLED (10002) for RECALLED
*/
export enum WebMsgType {
TEXT = 1,
IMAGE = 3,
VOICE = 34,
VERIFYMSG = 37,
POSSIBLEFRIEND_MSG = 40,
SHARECARD = 42,
VIDEO = 43,
EMOTICON = 47,
LOCATION = 48,
APP = 49,
VOIPMSG = 50,
STATUSNOTIFY = 51,
VOIPNOTIFY = 52,
VOIPINVITE = 53,
MICROVIDEO = 62,
SYSNOTICE = 9999,
SYS = 10000,
RECALLED = 10002,
}
/**
* from Message
*/
export interface WebRecommendPayload {
UserName: string,
NickName: string, // display_name
Content: string, // request message
HeadImgUrl: string, // message.RecommendInfo.HeadImgUrl
Ticket: string, // a pass token
VerifyFlag: number,
}
export const enum WebMediaType {
IMAGE = 1,
VIDEO = 2,
AUDIO = 3,
ATTACHMENT = 4,
}
......@@ -18,16 +18,31 @@
*
*/
/* tslint:disable:no-var-requires */
const retryPromise = require('retry-promise').default
import PuppetAccessory from '../puppet-accessory'
import Contact from './contact'
import {
log,
} from '../config'
export enum FriendRequestType {
UNKNOWN = 0,
SEND,
RECEIVE,
CONFIRM,
}
export interface FriendRequestPayload {
contact? : Contact,
hello? : string,
ticket? : string
type? : FriendRequestType,
}
/**
* Send, receive friend request, and friend confirmation events.
*
......@@ -37,19 +52,162 @@ export enum FriendRequestType {
*
* [Examples/Friend-Bot]{@link https://github.com/Chatie/wechaty/blob/master/examples/friend-bot.ts}
*/
export abstract class FriendRequest extends PuppetAccessory {
export class FriendRequest extends PuppetAccessory {
// tslint:disable-next-line:variable-name
public static Type = FriendRequestType
public abstract send(contact: Contact, hello: string): Promise<void>
public static createSend(
contact : Contact,
hello : string,
): FriendRequest {
log.verbose('PuppeteerFriendRequest', 'createSend(%s, %s)',
contact,
hello,
)
const sentRequest = new this({
type: FriendRequestType.SEND,
contact,
hello,
})
return sentRequest
}
public static createConfirm(
contact: Contact,
): FriendRequest {
log.verbose('PuppeteerFriendRequest', 'createConfirm(%s)',
contact,
)
const confirmedRequest = new this({
type: FriendRequestType.CONFIRM,
contact,
})
return confirmedRequest
}
public static createReceive(
contact : Contact,
hello : string,
ticket : string,
): FriendRequest {
log.verbose('PuppeteerFriendRequest', 'createReceive(%s, %s, %s)',
contact,
hello,
ticket,
)
const receivedRequest = new this({
type: FriendRequestType.RECEIVE,
contact,
hello,
ticket,
})
return receivedRequest
}
/**
*
* Instance Properties
*
*/
constructor(
protected payload?: FriendRequestPayload,
) {
super()
log.verbose('PuppeteerFriendRequest', 'constructor(%s)', payload)
}
public async send(): Promise<void> {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.contact) {
throw new Error('no contact')
} else if (this.payload.type !== FriendRequest.Type.SEND) {
throw new Error('not a send request')
}
log.verbose('PuppeteerFriendRequest', 'send() to %s', this.payload.contact)
await this.puppet.friendRequestSend(
this.payload.contact,
this.payload.hello,
)
}
public async accept(): Promise<void> {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.contact) {
throw new Error('no contact')
} else if (!this.payload.ticket) {
throw new Error('no ticket')
} else if (this.payload.type !== FriendRequest.Type.RECEIVE) {
throw new Error('not a receive request, its a ' + FriendRequest.Type[this.payload.type!])
}
log.verbose('FriendRequest', 'accept() to %s', this.payload.contact)
await this.puppet.friendRequestAccept(this.payload.contact, this.payload.ticket)
const max = 20
const backoff = 300
const timeout = max * (backoff * max) / 2
// 20 / 300 => 63,000
// max = (2*totalTime/backoff) ^ (1/2)
// timeout = 11,250 for {max: 15, backoff: 100}
// refresh to wait contact ready
await retryPromise({ max: max, backoff: backoff }, async (attempt: number) => {
log.silly('PuppeteerFriendRequest', 'accept() retryPromise() attempt %d with timeout %d', attempt, timeout)
await this.contact().ready()
if (this.contact().isReady()) {
log.verbose('PuppeteerFriendRequest', 'accept() with contact %s ready()', this.contact().name())
return
}
throw new Error('FriendRequest.accept() content.ready() not ready')
}).catch((e: Error) => {
log.warn('PuppeteerFriendRequest', 'accept() rejected for contact %s because %s', this.contact, e && e.message || e)
})
}
public hello(): string {
if (!this.payload) {
throw new Error('no payload')
}
return this.payload.hello || ''
}
public contact(): Contact {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.contact) {
throw new Error('no contact')
}
return this.payload.contact
}
public abstract accept(): Promise<void>
public abstract reject(): Promise<void>
public async reject(): Promise<void> {
log.warn('PuppeteerFriendRequest', 'reject() not necessary, NOP.')
return
}
public abstract contact() : Contact
public abstract hello() : string
public abstract type() : FriendRequestType
public type(): FriendRequestType {
if (!this.payload) {
throw new Error('no payload')
} else if (!this.payload.type) {
throw new Error('no type')
}
return this.payload.type
}
}
......
......@@ -7,6 +7,7 @@ export {
} from './contact'
export {
FriendRequest,
FriendRequestPayload,
FriendRequestType,
} from './friend-request'
export {
......@@ -25,3 +26,5 @@ export {
PuppetOptions,
ScanData,
} from './puppet'
export * from './schemas/'
......@@ -23,7 +23,7 @@ import {
import {
WebMsgType,
AppMsgType,
} from '../puppet-puppeteer/schema'
} from '../puppet/schemas/'
import {
log,
......
......@@ -151,7 +151,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
}
public emit(event: 'error', e: Error) : boolean
public emit(event: 'friend', friend: Contact, request?: FriendRequest) : boolean
public emit(event: 'friend', request: FriendRequest) : boolean
public emit(event: 'heartbeat', data: any) : boolean
public emit(event: 'login', user: Contact) : boolean
public emit(event: 'logout', user: Contact | string) : boolean
......@@ -161,7 +161,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public emit(event: 'room-topic', room: Room, topic: string, oldTopic: string, changer: Contact) : boolean
public emit(event: 'scan', url: string, code: number) : boolean
public emit(event: 'watchdog', food: WatchdogFood) : boolean
public emit(event: never, ...args: never[]): never
public emit(event: never, ...args: never[]) : never
public emit(
event: PuppetEventName,
......@@ -171,7 +171,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
}
public on(event: 'error', listener: (e: Error) => void) : this
public on(event: 'friend', listener: (friend: Contact, request?: FriendRequest) => void) : this
public on(event: 'friend', listener: (request: FriendRequest) => void) : this
public on(event: 'heartbeat', listener: (data: any) => void) : this
public on(event: 'login', listener: (user: Contact) => void) : this
public on(event: 'logout', listener: (user: Contact) => void) : this
......
......@@ -198,7 +198,7 @@ export class Wechaty extends PuppetAccessory implements Sayable {
}
public on(event: 'error' , listener: string | ((this: Wechaty, error: Error) => void)) : this
public on(event: 'friend' , listener: string | ((this: Wechaty, friend: Contact, request?: FriendRequest) => void)) : this
public on(event: 'friend' , listener: string | ((this: Wechaty, request: FriendRequest) => void)) : this
public on(event: 'heartbeat' , listener: string | ((this: Wechaty, data: any) => void)) : this
public on(event: 'logout' , listener: string | ((this: Wechaty, user: Contact) => void)) : this
public on(event: 'login' , listener: string | ((this: Wechaty, user: Contact) => void)) : this
......@@ -246,7 +246,7 @@ export class Wechaty extends PuppetAccessory implements Sayable {
* <li>408 waits for scan</li>
* </ul>
* @property {Function} heartbeat -(this: Wechaty, data: any) => void
* @property {Function} friend -(this: Wechaty, friend: Contact, request?: FriendRequest) => void
* @property {Function} friend -(this: Wechaty, request?: FriendRequest) => void
* @property {Function} message -(this: Wechaty, message: Message) => void
* @property {Function} room-join -(this: Wechaty, room: Room, inviteeList: Contact[], inviter: Contact) => void
* @property {Function} room-topic -(this: Wechaty, room: Room, topic: string, oldTopic: string, changer: Contact) => void
......@@ -282,15 +282,16 @@ export class Wechaty extends PuppetAccessory implements Sayable {
* })
*
* @example <caption>Event:friend </caption>
* bot.on('friend', (contact: Contact, request: FriendRequest) => {
* if(request){ // 1. request to be friend from new contact
* bot.on('friend', (request: FriendRequest) => {
* if(request.type === FriendRequest.Type.RECEIVE){ // 1. receive new friend request from new contact
* const contact = request.contact()
* let result = await request.accept()
* if(result){
* console.log(`Request from ${contact.name()} is accept succesfully!`)
* } else{
* console.log(`Request from ${contact.name()} failed to accept!`)
* }
* } else { // 2. confirm friend ship
* } else if (request.type === FriendRequest.Type.CONFIRM) { // 2. confirm friend ship
* console.log(`new friendship confirmed with ${contact.name()}`)
* }
* })
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册