friendship.ts 10.9 KB
Newer Older
1
/**
2
 *   Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
3
 *
4 5
 *   @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
 *                   Wechaty Contributors <https://github.com/wechaty>.
6
 *
7 8 9
 *   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
10
 *
11 12 13 14 15 16 17
 *       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.
18 19
 *
 */
20 21
import { EventEmitter }     from 'events'
import { instanceToClass }  from 'clone-class'
22

23
import {
24 25
  Wechaty,
}                   from '../wechaty'
26 27
import {
  log,
28
}                   from '../config'
29
import {
30
  tryWait,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
31
}                   from '../helper-functions/mod'
32

33
import {
34 35
  FriendshipPayload,
  FriendshipType,
S
SuperChang 已提交
36 37
  FriendshipSearchQueryFilter,
}                                 from 'wechaty-puppet'
38

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
39 40 41 42
import {
  Acceptable,
}                   from '../types'

43 44 45
import {
  Contact,
}                   from './contact'
46

L
lijiarui 已提交
47 48 49 50 51 52 53
/**
 * Send, receive friend request, and friend confirmation events.
 *
 * 1. send request
 * 2. receive request(in friend event)
 * 3. confirmation friendship(friend event)
 *
54
 * [Examples/Friend-Bot]{@link https://github.com/wechaty/wechaty/blob/1523c5e02be46ebe2cc172a744b2fbe53351540e/examples/friend-bot.ts}
L
lijiarui 已提交
55
 */
56 57 58 59
class Friendship extends EventEmitter implements Acceptable {

  static get wechaty  (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
  get wechaty        (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
60

61
  public static Type = FriendshipType
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
62

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
63
  /**
64
   * @ignore
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
65
   */
66
  public static load<T extends typeof Friendship> (
67 68
    this : T,
    id   : string,
69
  ): T['prototype'] {
70 71
    const newFriendship = new (this as any)(id)
    return newFriendship
72 73
  }

S
SuperChang 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
  /**
   * Search a Friend by phone or weixin.
   *
   * The best practice is to search friend request once per minute.
   * Remeber not to do this too frequently, or your account may be blocked.
   *
   * @param {FriendshipSearchCondition} condition - Search friend by phone or weixin.
   * @returns {Promise<Contact>}
   *
   * @example
   * const friend_phone = await bot.Friendship.search({phone: '13112341234'})
   * const friend_weixin = await bot.Friendship.search({weixin: 'weixin_account'})
   *
   * console.log(`This is the new friend info searched by phone : ${friend_phone}`)
   * await bot.Friendship.add(friend_phone, 'hello')
   *
   */
  public static async search (
    queryFiter : FriendshipSearchQueryFilter,
  ): Promise<null | Contact> {
    log.verbose('Friendship', 'static search("%s")',
      JSON.stringify(queryFiter),
    )
97
    const contactId = await this.wechaty.puppet.friendshipSearch(queryFiter)
S
SuperChang 已提交
98 99 100 101 102 103 104 105 106 107

    if (!contactId) {
      return null
    }

    const contact = this.wechaty.Contact.load(contactId)
    await contact.ready()
    return contact
  }

108 109
  /**
   * Send a Friend Request to a `contact` with message `hello`.
L
lijiarui 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122
   *
   * The best practice is to send friend request once per minute.
   * Remeber not to do this too frequently, or your account may be blocked.
   *
   * @param {Contact} contact - Send friend request to contact
   * @param {string} hello    - The friend request content
   * @returns {Promise<void>}
   *
   * @example
   * const memberList = await room.memberList()
   * for (let i = 0; i < memberList.length; i++) {
   *   await bot.Friendship.add(member, 'Nice to meet you! I am wechaty bot!')
   * }
123
   */
124
  public static async add (
125 126
    contact : Contact,
    hello   : string,
127
  ): Promise<void> {
128
    log.verbose('Friendship', 'static add(%s, %s)',
129 130 131
      contact.id,
      hello,
    )
132
    await this.wechaty.puppet.friendshipAdd(contact.id, hello)
133 134
  }

135
  public static async del (
136 137 138 139
    contact: Contact,
  ): Promise<void> {
    log.verbose('Friendship', 'static del(%s)', contact.id)
    throw new Error('to be implemented')
140 141 142 143 144 145 146
  }

  /**
   *
   * Instance Properties
   *
   */
147

L
lijiarui 已提交
148
  /**
149
    * @ignore
L
lijiarui 已提交
150
   */
151
  protected payload?: FriendshipPayload
152

153 154 155
  /*
   * @hideconstructor
   */
156
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
157
    public readonly id: string,
158 159
  ) {
    super()
160
    log.verbose('Friendship', 'constructor(id=%s)', id)
161

162
    const MyClass = instanceToClass(this, Friendship)
163

164
    if (MyClass === Friendship) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
165
      throw new Error('Friendship class can not be instantiated directly! See: https://github.com/wechaty/wechaty/issues/1217')
166 167
    }

168
    if (!this.wechaty.puppet) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
169
      throw new Error('Friendship class can not be instantiated without a puppet!')
170
    }
171 172
  }

173
  public toString () {
174
    if (!this.payload) {
175
      return this.constructor.name
176
    }
177

178
    return [
179 180
      'Friendship#',
      FriendshipType[this.payload.type],
181 182 183 184
      '<',
      this.payload.contactId,
      '>',
    ].join('')
185 186
  }

187
  public isReady (): boolean {
188 189 190 191
    return !!this.payload && (Object.keys(this.payload).length > 0)
  }

  /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
192
   * no `dirty` support because Friendship has no rawPayload(yet)
193
    * @ignore
194
   */
195
  public async ready (): Promise<void> {
196
    if (this.isReady()) {
197 198 199
      return
    }

200
    this.payload = await this.wechaty.puppet.friendshipPayload(this.id)
201

202 203 204
    if (!this.payload) {
      throw new Error('no payload')
    }
S
SuperChang 已提交
205 206

    await this.contact().ready()
207 208
  }

L
lijiarui 已提交
209 210 211 212 213 214 215 216 217 218 219 220
  /**
   * Accept Friend Request
   *
   * @returns {Promise<void>}
   *
   * @example
   * const bot = new Wechaty()
   * bot.on('friendship', async friendship => {
   *   try {
   *     console.log(`received friend event.`)
   *     switch (friendship.type()) {
   *
L
lijiarui 已提交
221
   *     // 1. New Friend Request
L
lijiarui 已提交
222 223 224 225 226
   *
   *     case Friendship.Type.Receive:
   *       await friendship.accept()
   *       break
   *
L
lijiarui 已提交
227
   *     // 2. Friend Ship Confirmed
L
lijiarui 已提交
228 229 230 231 232 233 234 235 236 237 238
   *
   *     case Friendship.Type.Confirm:
   *       console.log(`friend ship confirmed`)
   *       break
   *     }
   *   } catch (e) {
   *     console.error(e)
   *   }
   * }
   * .start()
   */
239
  public async accept (): Promise<void> {
240
    log.verbose('Friendship', 'accept()')
241

242 243 244 245
    if (!this.payload) {
      throw new Error('no payload')
    }

246
    if (this.payload.type !== Friendship.Type.Receive) {
247
      throw new Error('accept() need type to be FriendshipType.Receive, but it got a ' + Friendship.Type[this.payload.type])
248
    }
249

250
    log.silly('Friendship', 'accept() to %s', this.payload.contactId)
251

252
    await this.wechaty.puppet.friendshipAccept(this.id)
253

254
    const contact = this.contact()
255

256
    await tryWait(async (retry, attempt) => {
257
      log.silly('Friendship', 'accept() retry() ready() attempt %d', attempt)
258

259
      await contact.ready()
260

261
      if (contact.isReady()) {
262
        log.verbose('Friendship', 'accept() with contact %s ready()', contact.name())
263 264
        return
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
265
      retry(new Error('Friendship.accept() contact.ready() not ready'))
266 267

    }).catch((e: Error) => {
268
      log.warn('Friendship', 'accept() contact %s not ready because of %s', contact, (e && e.message) || e)
269 270
    })

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
271 272
    // try to fix issue #293
    await contact.sync()
273 274
  }

L
lijiarui 已提交
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
  /**
   * Get verify message from
   *
   * @returns {string}
   * @example <caption>If request content is `ding`, then accept the friendship</caption>
   * const bot = new Wechaty()
   * bot.on('friendship', async friendship => {
   *   try {
   *     console.log(`received friend event from ${friendship.contact().name()}`)
   *     if (friendship.type() === Friendship.Type.Receive && friendship.hello() === 'ding') {
   *       await friendship.accept()
   *     }
   *   } catch (e) {
   *     console.error(e)
   *   }
   * }
   * .start()
   */
293
  public hello (): string {
294 295 296 297 298 299
    if (!this.payload) {
      throw new Error('no payload')
    }
    return this.payload.hello || ''
  }

L
lijiarui 已提交
300 301 302 303 304 305 306 307 308 309 310 311 312
  /**
   * Get the contact from friendship
   *
   * @returns {Contact}
   * @example
   * const bot = new Wechaty()
   * bot.on('friendship', async friendship => {
   *   const contact = friendship.contact()
   *   const name = contact.name()
   *   console.log(`received friend event from ${name}`)
   * }
   * .start()
   */
313
  public contact (): Contact {
314 315 316
    if (!this.payload) {
      throw new Error('no payload')
    }
317

318
    const contact = this.wechaty.Contact.load(this.payload.contactId)
319
    return contact
320
  }
321

L
lijiarui 已提交
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
  /**
   * Return the Friendship Type
   * > Tips: FriendshipType is enum here. </br>
   * - FriendshipType.Unknown  </br>
   * - FriendshipType.Confirm  </br>
   * - FriendshipType.Receive  </br>
   * - FriendshipType.Verify   </br>
   *
   * @returns {FriendshipType}
   *
   * @example <caption>If request content is `ding`, then accept the friendship</caption>
   * const bot = new Wechaty()
   * bot.on('friendship', async friendship => {
   *   try {
   *     if (friendship.type() === Friendship.Type.Receive && friendship.hello() === 'ding') {
   *       await friendship.accept()
   *     }
   *   } catch (e) {
   *     console.error(e)
   *   }
   * }
   * .start()
   */
345
  public type (): FriendshipType {
346
    return this.payload
347 348
      ? this.payload.type
      : FriendshipType.Unknown
349
  }
350

S
SilentQianyi 已提交
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
  /**
   * get friendShipPayload Json
   * @returns {FriendshipPayload}
   *
   * @example
   * const bot = new Wechaty()
   * bot.on('friendship', async friendship => {
   *   try {
   *     // JSON.stringify(friendship) as well.
   *     const payload = await friendship.toJSON()
   *   } catch (e) {
   *     console.error(e)
   *   }
   * }
   * .start()
   */
  public toJSON (): string {
    log.verbose('Friendship', 'toJSON()')

    if (!this.isReady()) {
      throw new Error(`Friendship<${this.id}> needs to be ready. Please call ready() before toJSON()`)
    }
    return JSON.stringify(this.payload)
  }

  /**
   * create friendShip by friendshipJson
   * @example
   * const bot = new Wechaty()
   * bot.start()
   *
   * const payload = '{...}'  // your saved JSON payload
   * const friendship = bot.FriendShip.fromJSON(friendshipFromDisk)
   * await friendship.accept()
   */
  public static async fromJSON (
    payload: string | FriendshipPayload,
  ): Promise<Friendship> {
    log.verbose('Friendship', 'static fromJSON(%s)',
      typeof payload === 'string'
        ? payload
        : JSON.stringify(payload),
    )

    if (typeof payload === 'string') {
      payload = JSON.parse(payload) as FriendshipPayload
    }

    /**
     * Set the payload back to the puppet for future use
     */
402
    await this.wechaty.puppet.friendshipPayload(payload.id, payload)
S
SilentQianyi 已提交
403 404 405 406 407 408 409

    const instance = this.wechaty.Friendship.load(payload.id)
    await instance.ready()

    return instance
  }

410
}
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428

function wechatifyFriendship (wechaty: Wechaty): typeof Friendship {

  class WechatifiedFriendship extends Friendship {

    static get wechaty  () { return wechaty }
    get wechaty        () { return wechaty }

  }

  return WechatifiedFriendship

}

export {
  Friendship,
  wechatifyFriendship,
}