UNPKG

6.9 kBPlain TextView Raw
1/**
2 * Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
3 *
4 * @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
5 * Wechaty Contributors <https://github.com/wechaty>.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 */
20/**
21 * DO NOT use `require('../')` here!
22 * because it will cause a LOOP require ERROR
23 */
24import { StateSwitch } from 'wechaty-puppet'
25
26import {
27 PuppetServer,
28 PuppetServerOptions,
29} from 'wechaty-puppet-service'
30
31import { Message } from './user/mod'
32
33import {
34 log,
35} from './config'
36import { Io } from './io'
37import { Wechaty } from './wechaty'
38
39export interface IoClientOptions {
40 token : string,
41 wechaty : Wechaty,
42 port?: number
43}
44
45const DEFAULT_IO_CLIENT_OPTIONS: Partial<IoClientOptions> = {
46 port: 8788,
47}
48
49export class IoClient {
50
51 /**
52 * Huan(20161026): keep io `null-able` or not?
53 * Huan(202002): make it optional.
54 */
55 private io?: Io
56 private puppetServer?: PuppetServer
57
58 private state: StateSwitch
59
60 protected options: Required<IoClientOptions>
61
62 constructor (
63 options: IoClientOptions,
64 ) {
65 log.verbose('IoClient', 'constructor({%s})',
66 Object.keys(options)
67 .map(key => {
68 return `${key}:${(options as any)[key]}`
69 })
70 .reduce((acc, cur) => `${acc}, ${cur}`)
71 )
72
73 const normalizedOptions = {
74 ...DEFAULT_IO_CLIENT_OPTIONS,
75 ...options,
76 } as Required<IoClientOptions>
77
78 this.options = normalizedOptions
79
80 this.state = new StateSwitch('IoClient', { log })
81 }
82
83 private async startPuppetServer () {
84 log.verbose('IoClient', 'startPuppetServer()')
85
86 if (this.puppetServer) {
87 throw new Error('puppet server exists')
88 }
89
90 const options: PuppetServerOptions = {
91 endpoint : '0.0.0.0:' + this.options.port,
92 puppet : this.options.wechaty.puppet,
93 token : this.options.token,
94 }
95 this.puppetServer = new PuppetServer(options)
96 await this.puppetServer.start()
97 }
98
99 private async stopPuppetServer () {
100 log.verbose('IoClient', 'stopPuppetService()')
101
102 if (!this.puppetServer) {
103 throw new Error('puppet server does not exist')
104 }
105
106 await this.puppetServer.stop()
107 this.puppetServer = undefined
108 }
109
110 public async start (): Promise<void> {
111 log.verbose('IoClient', 'start()')
112
113 if (this.state.on()) {
114 log.warn('IoClient', 'start() with a on state, wait and return')
115 await this.state.ready('on')
116 return
117 }
118
119 this.state.on('pending')
120
121 try {
122 await this.hookWechaty(this.options.wechaty)
123
124 await this.startIo()
125
126 await this.options.wechaty.start()
127
128 await this.startPuppetServer()
129
130 this.state.on(true)
131
132 } catch (e) {
133 log.error('IoClient', 'start() exception: %s', e.message)
134 this.state.off(true)
135 throw e
136 }
137 }
138
139 private async hookWechaty (wechaty: Wechaty): Promise<void> {
140 log.verbose('IoClient', 'hookWechaty()')
141
142 if (this.state.off()) {
143 const e = new Error('state.off() is true, skipped')
144 log.warn('IoClient', 'initWechaty() %s', e.message)
145 throw e
146 }
147
148 wechaty
149 .on('login', user => log.info('IoClient', `${user.name()} logged in`))
150 .on('logout', user => log.info('IoClient', `${user.name()} logged out`))
151 .on('message', msg => this.onMessage(msg))
152 .on('scan', (url, code) => {
153 log.info('IoClient', [
154 `[${code}] ${url}`,
155 `Online QR Code Image: https://wechaty.js.org/qrcode/${encodeURIComponent(url)}`,
156 ].join('\n'))
157 })
158 }
159
160 private async startIo (): Promise<void> {
161 log.verbose('IoClient', 'startIo() with token %s', this.options.token)
162
163 if (this.state.off()) {
164 const e = new Error('startIo() state.off() is true, skipped')
165 log.warn('IoClient', e.message)
166 throw e
167 }
168
169 if (this.io) {
170 throw new Error('io exists')
171 }
172
173 this.io = new Io({
174 servicePort : this.options.port,
175 token : this.options.token,
176 wechaty : this.options.wechaty,
177 })
178
179 try {
180 await this.io.start()
181 } catch (e) {
182 log.verbose('IoClient', 'startIo() init fail: %s', e.message)
183 throw e
184 }
185 }
186
187 private async stopIo () {
188 log.verbose('IoClient', 'stopIo()')
189
190 if (!this.io) {
191 log.warn('IoClient', 'stopIo() io does not exist')
192 return
193 }
194
195 await this.io.stop()
196 this.io = undefined
197 }
198
199 private async onMessage (msg: Message) {
200 log.verbose('IoClient', 'onMessage(%s)', msg)
201
202 // const from = m.from()
203 // const to = m.to()
204 // const content = m.toString()
205 // const room = m.room()
206
207 // log.info('Bot', '%s<%s>:%s'
208 // , (room ? '['+room.topic()+']' : '')
209 // , from.name()
210 // , m.toStringDigest()
211 // )
212
213 // if (/^wechaty|chatie|botie/i.test(m.text()) && !m.self()) {
214 // await m.say('https://www.chatie.io')
215 // .then(_ => log.info('Bot', 'REPLIED to magic word "chatie"'))
216 // }
217 }
218
219 public async stop (): Promise<void> {
220 log.verbose('IoClient', 'stop()')
221
222 this.state.off('pending')
223
224 await this.stopIo()
225 await this.stopPuppetServer()
226 await this.options.wechaty.stop()
227
228 this.state.off(true)
229
230 // XXX 20161026
231 // this.io = null
232 }
233
234 public async restart (): Promise<void> {
235 log.verbose('IoClient', 'restart()')
236
237 try {
238 await this.stop()
239 await this.start()
240 } catch (e) {
241 log.error('IoClient', 'restart() exception %s', e.message)
242 throw e
243 }
244 }
245
246 public async quit (): Promise<void> {
247 log.verbose('IoClient', 'quit()')
248
249 if (this.state.off() === 'pending') {
250 log.warn('IoClient', 'quit() with state.off() = `pending`, skipped')
251 throw new Error('quit() with state.off() = `pending`')
252 }
253
254 this.state.off('pending')
255
256 try {
257 if (this.options.wechaty) {
258 await this.options.wechaty.stop()
259 // this.wechaty = null
260 } else { log.warn('IoClient', 'quit() no this.wechaty') }
261
262 if (this.io) {
263 await this.io.stop()
264 // this.io = null
265 } else { log.warn('IoClient', 'quit() no this.io') }
266
267 } catch (e) {
268 log.error('IoClient', 'exception: %s', e.message)
269 throw e
270 } finally {
271 this.state.off(true)
272 }
273 }
274
275}