friendship.ts 8.9 KB
Newer Older
1
/**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
2
 *   Wechaty - https://github.com/chatie/wechaty
3
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
4
 *   @copyright 2016-2018 Huan LI <zixia@zixia.net>
5
 *
6 7 8
 *   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
9
 *
10 11 12 13 14 15 16
 *       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.
L
lijiarui 已提交
17
 *   @ignore
18 19 20
 *
 */

21 22 23
import {
  instanceToClass,
}                   from 'clone-class'
24

25 26
import {
  Accessory,
27
}                   from '../accessory'
28 29
import {
  log,
30
}                   from '../config'
31
import {
32 33
  tryWait,
}                   from '../helper-functions'
34

35
import {
36 37
  FriendshipPayload,
  FriendshipType,
38
}                         from 'wechaty-puppet'
39

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
59
  // tslint:disable-next-line:variable-name
60
  public static Type = FriendshipType
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
61

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

73
  /**
L
lijiarui 已提交
74 75 76
   * @description
   * use {@link Friendship#add} instead
   * @deprecated
77
   */
78
  public static async send (contact: Contact,  hello: string) {
79 80 81
    log.warn('Friendship', 'static send() DEPRECATED, use add() instead.')
    return this.add(contact, hello)
  }
L
lijiarui 已提交
82

83 84
  /**
   * Send a Friend Request to a `contact` with message `hello`.
L
lijiarui 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97
   *
   * 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!')
   * }
98
   */
99
  public static async add (
100 101
    contact : Contact,
    hello   : string,
102
  ): Promise<void> {
103
    log.verbose('Friendship', 'static add(%s, %s)',
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104 105
                                  contact.id,
                                  hello,
106
                )
107 108 109
    await this.puppet.friendshipAdd(contact.id, hello)
  }

110
  public static async del (
111 112 113 114
    contact: Contact,
  ): Promise<void> {
    log.verbose('Friendship', 'static del(%s)', contact.id)
    throw new Error('to be implemented')
115 116
  }

117 118 119
  // public static createConfirm(
  //   contactId: string,
  // ): FriendRequestPayload {
120
  //   log.verbose('Friendship', 'createConfirm(%s)',
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
  //                                         contactId,
  //               )

  //   const payload: FriendRequestPayloadConfirm = {
  //     type : FriendRequestType.Confirm,
  //     contactId,
  //   }

  //   return payload
  // }

  // public static createReceive(
  //   contactId : string,
  //   hello     : string,
  //   ticket    : string,
  // ): FriendRequestPayload {
137
  //   log.verbose('Friendship', 'createReceive(%s, %s, %s)',
138 139 140 141 142 143 144 145 146 147 148 149 150 151
  //                                         contactId,
  //                                         hello,
  //                                         ticket,
  //               )

  //   const payload: FriendRequestPayloadReceive = {
  //     type : FriendRequestType.Receive,
  //     contactId,
  //     hello,
  //     ticket,
  //   }

  //   return payload
  // }
152

153 154 155 156 157
  /**
   *
   * Instance Properties
   *
   */
158

L
lijiarui 已提交
159 160 161
  /**
   * @ignore
   */
162
  protected payload?: FriendshipPayload
163

164
  constructor (
165
    public id: string,
166 167
  ) {
    super()
168
    log.verbose('Friendship', 'constructor(id=%s)', id)
169 170

    // tslint:disable-next-line:variable-name
171
    const MyClass = instanceToClass(this, Friendship)
172

173 174
    if (MyClass === Friendship) {
      throw new Error('Friendship class can not be instanciated directly! See: https://github.com/Chatie/wechaty/issues/1217')
175 176 177
    }

    if (!this.puppet) {
178
      throw new Error('Friendship class can not be instanciated without a puppet!')
179
    }
180 181
  }

182
  public toString () {
183
    if (!this.payload) {
184
      return this.constructor.name
185
    }
186

187
    return [
188 189
      'Friendship#',
      FriendshipType[this.payload.type],
190 191 192 193
      '<',
      this.payload.contactId,
      '>',
    ].join('')
194 195
  }

196
  public isReady (): boolean {
197 198 199 200
    return !!this.payload && (Object.keys(this.payload).length > 0)
  }

  /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
201
   * no `dirty` support because Friendship has no rawPayload(yet)
L
lijiarui 已提交
202
   * @ignore
203
   */
204
  public async ready (): Promise<void> {
205
    if (this.isReady()) {
206 207 208
      return
    }

209
    this.payload = await this.puppet.friendshipPayload(this.id)
210

211 212 213 214 215
    if (!this.payload) {
      throw new Error('no payload')
    }
  }

L
lijiarui 已提交
216 217 218 219 220 221 222 223 224 225 226 227
  /**
   * 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 已提交
228
   *     // 1. New Friend Request
L
lijiarui 已提交
229 230 231 232 233
   *
   *     case Friendship.Type.Receive:
   *       await friendship.accept()
   *       break
   *
L
lijiarui 已提交
234
   *     // 2. Friend Ship Confirmed
L
lijiarui 已提交
235 236 237 238 239 240 241 242 243 244 245
   *
   *     case Friendship.Type.Confirm:
   *       console.log(`friend ship confirmed`)
   *       break
   *     }
   *   } catch (e) {
   *     console.error(e)
   *   }
   * }
   * .start()
   */
246
  public async accept (): Promise<void> {
247
    log.verbose('Friendship', 'accept()')
248

249 250 251 252
    if (!this.payload) {
      throw new Error('no payload')
    }

253
    if (this.payload.type !== Friendship.Type.Receive) {
254
      throw new Error('accept() need type to be FriendshipType.Receive, but it got a ' + Friendship.Type[this.payload.type])
255
    }
256

257
    log.silly('Friendship', 'accept() to %s', this.payload.contactId)
258

259
    await this.puppet.friendshipAccept(this.id)
260

261
    const contact = this.contact()
262

263
    await tryWait(async (retry, attempt) => {
264
      log.silly('Friendship', 'accept() retry() ready() attempt %d', attempt)
265

266
      await contact.ready()
267

268
      if (contact.isReady()) {
269
        log.verbose('Friendship', 'accept() with contact %s ready()', contact.name())
270 271
        return
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
272
      retry(new Error('Friendship.accept() contact.ready() not ready'))
273 274

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
278 279
    // try to fix issue #293
    await contact.sync()
280 281
  }

L
lijiarui 已提交
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
  /**
   * 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()
   */
300
  public hello (): string {
301 302 303 304 305 306
    if (!this.payload) {
      throw new Error('no payload')
    }
    return this.payload.hello || ''
  }

L
lijiarui 已提交
307 308 309 310 311 312 313 314 315 316 317 318 319
  /**
   * 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()
   */
320
  public contact (): Contact {
321 322 323
    if (!this.payload) {
      throw new Error('no payload')
    }
324

325
    const contact = this.wechaty.Contact.load(this.payload.contactId)
326
    return contact
327
  }
328

L
lijiarui 已提交
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  /**
   * 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()
   */
352
  public type (): FriendshipType {
353 354
    return this.payload
            ? this.payload.type
355
            : FriendshipType.Unknown
356
  }
357
}