friendship.ts 7.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)',
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
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
129
    public readonly id: string,
130 131
  ) {
    super()
132
    log.verbose('Friendship', 'constructor(id=%s)', id)
133 134

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

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

    if (!this.puppet) {
142
      throw new Error('Friendship class can not be instanciated without a puppet!')
143
    }
144 145
  }

146
  public toString () {
147
    if (!this.payload) {
148
      return this.constructor.name
149
    }
150

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

160
  public isReady (): boolean {
161 162 163 164
    return !!this.payload && (Object.keys(this.payload).length > 0)
  }

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

173
    this.payload = await this.puppet.friendshipPayload(this.id)
174

175 176 177 178 179
    if (!this.payload) {
      throw new Error('no payload')
    }
  }

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

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

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

221
    log.silly('Friendship', 'accept() to %s', this.payload.contactId)
222

223
    await this.puppet.friendshipAccept(this.id)
224

225
    const contact = this.contact()
226

227
    await tryWait(async (retry, attempt) => {
228
      log.silly('Friendship', 'accept() retry() ready() attempt %d', attempt)
229

230
      await contact.ready()
231

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

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

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

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

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

289
    const contact = this.wechaty.Contact.load(this.payload.contactId)
290
    return contact
291
  }
292

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

322
}