friendship.ts 8.0 KB
Newer Older
1
/**
2
 *   Wechaty - https://github.com/wechaty/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)
 *
55
 * [Examples/Friend-Bot]{@link https://github.com/wechaty/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)',
104 105 106
      contact.id,
      hello,
    )
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 120 121
  }

  /**
   *
   * Instance Properties
   *
   */
122

L
lijiarui 已提交
123 124 125
  /**
   * @ignore
   */
126
  protected payload?: FriendshipPayload
127

128 129 130
  /*
   * @hideconstructor
   */
131
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
132
    public readonly id: string,
133 134
  ) {
    super()
135
    log.verbose('Friendship', 'constructor(id=%s)', id)
136 137

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

140
    if (MyClass === Friendship) {
141
      throw new Error('Friendship class can not be instanciated directly! See: https://github.com/wechaty/wechaty/issues/1217')
142 143 144
    }

    if (!this.puppet) {
145
      throw new Error('Friendship class can not be instanciated without a puppet!')
146
    }
147 148
  }

149
  public toString () {
150
    if (!this.payload) {
151
      return this.constructor.name
152
    }
153

154
    return [
155 156
      'Friendship#',
      FriendshipType[this.payload.type],
157 158 159 160
      '<',
      this.payload.contactId,
      '>',
    ].join('')
161 162
  }

163
  public isReady (): boolean {
164 165 166 167
    return !!this.payload && (Object.keys(this.payload).length > 0)
  }

  /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
168
   * no `dirty` support because Friendship has no rawPayload(yet)
L
lijiarui 已提交
169
   * @ignore
170
   */
171
  public async ready (): Promise<void> {
172
    if (this.isReady()) {
173 174 175
      return
    }

176
    this.payload = await this.puppet.friendshipPayload(this.id)
177

178 179 180 181 182
    if (!this.payload) {
      throw new Error('no payload')
    }
  }

L
lijiarui 已提交
183 184 185 186 187 188 189 190 191 192 193 194
  /**
   * 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 已提交
195
   *     // 1. New Friend Request
L
lijiarui 已提交
196 197 198 199 200
   *
   *     case Friendship.Type.Receive:
   *       await friendship.accept()
   *       break
   *
L
lijiarui 已提交
201
   *     // 2. Friend Ship Confirmed
L
lijiarui 已提交
202 203 204 205 206 207 208 209 210 211 212
   *
   *     case Friendship.Type.Confirm:
   *       console.log(`friend ship confirmed`)
   *       break
   *     }
   *   } catch (e) {
   *     console.error(e)
   *   }
   * }
   * .start()
   */
213
  public async accept (): Promise<void> {
214
    log.verbose('Friendship', 'accept()')
215

216 217 218 219
    if (!this.payload) {
      throw new Error('no payload')
    }

220
    if (this.payload.type !== Friendship.Type.Receive) {
221
      throw new Error('accept() need type to be FriendshipType.Receive, but it got a ' + Friendship.Type[this.payload.type])
222
    }
223

224
    log.silly('Friendship', 'accept() to %s', this.payload.contactId)
225

226
    await this.puppet.friendshipAccept(this.id)
227

228
    const contact = this.contact()
229

230
    await tryWait(async (retry, attempt) => {
231
      log.silly('Friendship', 'accept() retry() ready() attempt %d', attempt)
232

233
      await contact.ready()
234

235
      if (contact.isReady()) {
236
        log.verbose('Friendship', 'accept() with contact %s ready()', contact.name())
237 238
        return
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
239
      retry(new Error('Friendship.accept() contact.ready() not ready'))
240 241

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245 246
    // try to fix issue #293
    await contact.sync()
247 248
  }

L
lijiarui 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
  /**
   * 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()
   */
267
  public hello (): string {
268 269 270 271 272 273
    if (!this.payload) {
      throw new Error('no payload')
    }
    return this.payload.hello || ''
  }

L
lijiarui 已提交
274 275 276 277 278 279 280 281 282 283 284 285 286
  /**
   * 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()
   */
287
  public contact (): Contact {
288 289 290
    if (!this.payload) {
      throw new Error('no payload')
    }
291

292
    const contact = this.wechaty.Contact.load(this.payload.contactId)
293
    return contact
294
  }
295

L
lijiarui 已提交
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
  /**
   * 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()
   */
319
  public type (): FriendshipType {
320
    return this.payload
321 322
      ? this.payload.type
      : FriendshipType.Unknown
323
  }
324

325
}