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

62
  public static load<T extends typeof Friendship> (
63 64
    this : T,
    id   : string,
65
  ): T['prototype'] {
66 67
    const newFriendship = new (this as any)(id)
    return newFriendship
68 69
  }

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

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

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

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

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

  //   return payload
  // }

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

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

  //   return payload
  // }
149

150 151 152 153 154
  /**
   *
   * Instance Properties
   *
   */
155

L
lijiarui 已提交
156 157 158
  /**
   * @ignore
   */
159
  protected payload?: FriendshipPayload
160

161
  constructor (
162
    public id: string,
163 164
  ) {
    super()
165
    log.verbose('Friendship', 'constructor(id=%s)', id)
166 167

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

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

    if (!this.puppet) {
175
      throw new Error('Friendship class can not be instanciated without a puppet!')
176
    }
177 178
  }

179
  public toString () {
180
    if (!this.payload) {
181
      return this.constructor.name
182
    }
183

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

193
  public isReady (): boolean {
194 195 196 197
    return !!this.payload && (Object.keys(this.payload).length > 0)
  }

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

206
    this.payload = await this.puppet.friendshipPayload(this.id)
207

208 209 210 211 212
    if (!this.payload) {
      throw new Error('no payload')
    }
  }

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

246 247 248 249
    if (!this.payload) {
      throw new Error('no payload')
    }

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

254
    log.silly('Friendship', 'accept() to %s', this.payload.contactId)
255

256
    await this.puppet.friendshipAccept(this.id)
257

258
    const contact = this.contact()
259

260
    await tryWait(async (retry, attempt) => {
261
      log.silly('Friendship', 'accept() retry() ready() attempt %d', attempt)
262

263
      await contact.ready()
264

265
      if (contact.isReady()) {
266
        log.verbose('Friendship', 'accept() with contact %s ready()', contact.name())
267 268
        return
      }
269
      retry(new Error('Friendship.accept() content.ready() not ready'))
270 271

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

  }

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

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

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

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