contact.ts 6.6 KB
Newer Older
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1 2 3 4 5 6 7 8
/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
9 10 11 12
import {
    Config
  , Sayable
}               from './config'
13
import Message  from './message'
14
import UtilLib  from './util-lib'
15
import Wechaty  from './wechaty'
16
import log      from './brolog-env'
17

18 19
type ContactObj = {
  address:    string
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
20 21 22 23 24 25 26 27 28 29 30
  city:       string
  id:         string
  name:       string
  province:   string
  remark:     string
  sex:        string
  signature:  string
  star:       boolean
  stranger:   boolean
  uin:        string
  weixin:     string
31 32
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
33
export type ContactRawObj = {
34 35 36
  Alias:        string
  City:         string
  NickName:     string
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
37 38 39
  Province:     string
  RemarkName:   string
  Sex:          string
40
  Signature:    string
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
41 42 43 44 45
  StarFriend:   string
  Uin:          string
  UserName:     string

  stranger:     string // assign by injectio.js
46 47
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
48
export type ContactQueryFilter = {
49 50 51
  name: string | RegExp
}

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
52
export class Contact implements Sayable {
53 54
  private static pool = new Map<string, Contact>()

55
  private obj: ContactObj | null
56
  private dirtyObj: ContactObj | null
57 58 59
  private rawObj: ContactRawObj

  constructor(public readonly id: string) {
60
    log.silly('Contact', `constructor(${id})`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
61

62 63 64
    if (typeof id !== 'string') {
      throw new Error('id must be string. found: ' + typeof id)
    }
65 66
  }

67 68
  public toString()  { return this.id }
  public toStringEx() { return `Contact(${this.obj && this.obj.name}[${this.id}])` }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
69

70
  private parse(rawObj: ContactRawObj): ContactObj | null {
71 72 73 74
    if (!rawObj) {
      log.warn('Contact', 'parse() got empty rawObj!')
    }

75
    return !rawObj ? null : {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
76
      id:           rawObj.UserName
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
77
      , uin:        rawObj.Uin    // stable id: 4763975 || getCookie("wxuin")
78
      , weixin:     rawObj.Alias  // Wechat ID
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
79 80 81
      , name:       rawObj.NickName
      , remark:     rawObj.RemarkName
      , sex:        rawObj.Sex
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82 83 84
      , province:   rawObj.Province
      , city:       rawObj.City
      , signature:  rawObj.Signature
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
85

86 87
      , address:    rawObj.Alias // XXX: need a stable address for user

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
88 89
      , star:       !!rawObj.StarFriend
      , stranger:   !!rawObj.stranger // assign by injectio.js
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
90 91
    }
  }
92

93
  public weixin()    { return this.obj && this.obj.weixin || '' }
94
  public name()      { return UtilLib.plainText(this.obj && this.obj.name || '') }
95 96 97
  public remark()    { return this.obj && this.obj.remark }
  public stranger()  { return this.obj && this.obj.stranger }
  public star()      { return this.obj && this.obj.star }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
98

99
  public get(prop)   { return this.obj && this.obj[prop] }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
100

101
  public isReady(): boolean {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
102
    return !!(this.obj && this.obj.id)
103 104
  }

105 106 107 108 109 110 111 112
  public async refresh(): Promise<this> {
    if (this.isReady()) {
      this.dirtyObj = this.obj
    }
    this.obj = null
    return this.ready()
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
113
  public async ready(contactGetter?: (id: string) => Promise<ContactRawObj>): Promise<this> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
114
    log.silly('Contact', 'ready(' + (contactGetter ? typeof contactGetter : '') + ')')
115
    if (!this.id) {
116 117
      const e = new Error('ready() call on an un-inited contact')
      throw e
118
    }
119

120
    if (this.isReady()) { // already ready
121 122
      return Promise.resolve(this)
    }
123 124

    if (!contactGetter) {
125 126 127 128
      if (!Config.puppetInstance()) { throw new Error('Config.puppetInstance() is not found by Contact') }

      log.silly('Contact', 'get contact via ' + Config.puppetInstance().constructor.name)
      contactGetter = Config.puppetInstance()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
129 130
                            .getContact.bind(Config.puppetInstance())
    }
131 132 133
    if (!contactGetter) {
      throw new Error('no contatGetter')
    }
134

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
135 136 137 138 139 140
    try {
      const rawObj = await contactGetter(this.id)
      log.silly('Contact', `contactGetter(${this.id}) resolved`)
      this.rawObj = rawObj
      this.obj    = this.parse(rawObj)
      return this
141

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
142 143 144
    } catch (e) {
      log.error('Contact', `contactGetter(${this.id}) exception: %s`, e.message)
      throw e
145 146 147
    }
  }

148
  public dumpRaw() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
149
    console.error('======= dump raw contact =======')
150
    Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
151
  }
152
  public dump()    {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
153
    console.error('======= dump contact =======')
154
    Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj && this.obj[k]}`))
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
155
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
156

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169
  public self(): boolean {
    const userId = Config.puppetInstance()
                          .userId

    const selfId = this.id

    if (!userId || !selfId) {
      throw new Error('no user or no self id')
    }

    return selfId === userId
  }

170 171 172 173
  public static findAll(query?: ContactQueryFilter): Promise<Contact[]> {
    if (!query) {
      query = { name: /.*/ }
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
174
    log.verbose('Cotnact', 'findAll({ name: %s })', query.name)
175 176

    const name = query.name
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
177 178 179 180 181

    if (!name) {
      throw new Error('name not found')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
182 183 184 185 186 187
    /**
     * must be string because we need inject variable value
     * into code as variable name
     */
    let filterFunction: string

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
188 189 190 191 192 193 194 195 196 197 198
    if (name instanceof RegExp) {
      filterFunction = `c => ${name.toString()}.test(c)`
    } else if (typeof name === 'string') {
      filterFunction = `c => c === '${name}'`
    } else {
      throw new Error('unsupport name type')
    }

    return Config.puppetInstance()
                  .contactFind(filterFunction)
                  .catch(e => {
199 200
                    log.error('Contact', 'findAll() rejected: %s', e.message)
                    return [] // fail safe
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
201
                  })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
202
  }
203

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
204
  public static async find(query: ContactQueryFilter): Promise<Contact> {
205
    log.verbose('Contact', 'find(%s)', query.name)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
206

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
207
    const contactList = await Contact.findAll(query)
208
    if (!contactList || !contactList.length) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
209 210 211
      throw new Error('find not found any contact')
    }
    return contactList[0]
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
212 213
  }

214
  public static load(id: string): Contact | null {
215 216 217
    if (!id || typeof id !== 'string') {
      return null
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
218

219 220 221 222
    if (!(id in Contact.pool)) {
      Contact.pool[id] = new Contact(id)
    }
    return Contact.pool[id]
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
223
  }
224

225
  public async say(content: string): Promise<void> {
226 227 228 229 230
    log.verbose('Contact', 'say(%s)', content)

    const wechaty = Wechaty.instance()
    const user = wechaty.user()

231 232 233
    if (!user) {
      throw new Error('no user')
    }
234 235 236 237 238 239 240
    const m = new Message()
    m.from(user)
    m.to(this)
    m.content(content)

    log.silly('Contact', 'say() from: %s to: %s content: %s', user.name(), this.name(), content)

241 242
    await wechaty.send(m)
    return
243 244
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245
}
246

247 248 249 250 251 252 253 254 255 256
// Contact.search = function(options) {
//   if (options.name) {
//     const regex = new RegExp(options.name)
//     return Object.keys(Contact.pool)
//     .filter(k => regex.test(Contact.pool[k].name()))
//     .map(k => Contact.pool[k])
//   }

//   return []
// }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
257

258
export default Contact