-
Notifications
You must be signed in to change notification settings - Fork 1
/
logic.js
359 lines (315 loc) · 11.6 KB
/
logic.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
require('./promise-retry.js');
const wxMediator = require('./wechat/mediator.js');
const syncPromiseLoop = require('./promise-syncloop.js');
const logger = require('./log/logger.js');
const events = require('./events/events.js');
const eventsConst = require('./events/events-const.js');
// flag indicate whether we load all contact according to Internet request
var isLoadedContactsCurrentPage = false;
// receive onResourceReceived event
events.on(eventsConst.onResourceReceived, (resource) => {
// if phantom makes request for contact, and we received the response
// then we will mark that we received information
if (/^https:\/\/web\.wechat\.com\/cgi-bin\/mmwebwx-bin\/webwxbatchgetcontact.+/.test(resource.url)) {
logger.log('received response for contact request');
isLoadedContactsCurrentPage = true;
}
logger.log('received response: ' + resource.url);
});
/**
* Wait until contact-loaded flag is set.
* @return {Object} Promise object.
*/
function waitUntilContactLoaded(timeout=10000) {
var countingTime = 0;
return Promise.retryEndlessly(() => {
if (isLoadedContactsCurrentPage) return Promise.resolve();
else return Promise.reject();
}, 100);
}
/**
* Wait until contact-loaded flag is set or until timeout is up.
* @param {Number} timeout (optional) Timeout if reached then return success in ms. Default is 5000 ms.
* @return {Object} Promise object.
*/
function waitUntilContactLoadedOrTimeout(timeout=10000) {
var countingTime = 0;
return Promise.retryEndlessly(() => {
if (isLoadedContactsCurrentPage) return Promise.resolve();
else {
countingTime += 100;
if (countingTime >= timeout) {
return Promise.resolve();
}
else {
return Promise.reject();
}
}
}, 100);
}
var _ = {
/**
* All contacts
* @type {Array}
*/
contacts: null,
/**
* wxMediator object
* @type {Object}
*/
wxMediator: wxMediator,
/**
* Get all contacts.
*
* It will click on contact tab button, then parse DOM to extract needed information before returning it.
* @param {Object} headless Headless object
* @return {Object} Promise object. Success contains array of contact which is { wechat_id: <String>, display_name: <String>, avatar_url: <String> }. Otherwise failure contains null.
*/
getAllContacts: function(headless) {
var that = this;
return new Promise((resolve, reject) => {
// click on contact tab button
wxMediator.clickOnContactTabButton(headless)
.then((result) => {
if (result) {
logger.log('clicked on contact tab button');
// get all properties of scrollableDiv element
wxMediator.getScrollableDivEssentialProperties(headless)
.then((result) => {
if (result == null) {
logger.log('failed to get one or more property value from scrollableDiv element');
reject(null);
}
else {
logger.log('got all property values from scrollableDiv element');
// get all properties
var clientHeight = result.client_height;
var scrollHeight = result.scroll_height;
var scrollTop = result.scroll_top;
logger.log('properties: ', result);
// calculate number of page to navigate
var numPage = Math.ceil(scrollHeight / clientHeight);
logger.log(`need to get all contacts from ${numPage} page(s)`);
// hold all contacts for each page here
let allContacts = [];
var bottomMostContactOfCurrPage = null;
// create worker fn to work in promise-syncloop
let workerFn = function(i, ...args) {
return new Promise((_resolve, _reject) => {
// get all contacts for the current page
wxMediator.getContactsOnCurrentPageScrolledThenScrollToNextPage(headless, bottomMostContactOfCurrPage)
.then((_contacts) => {
if (_contacts != null && typeof _contacts === 'object') {
logger.log('got contacts for page ' + (i+1), _contacts);
// update bottom contact object to navigate page further
bottomMostContactOfCurrPage = _contacts[_contacts.length - 1];
logger.log('bottomMostContact: ', bottomMostContactOfCurrPage);
// save contacts for our accumulated contacts to use later
allContacts = allContacts.concat(_contacts);
// invalidate contact loaded status
isLoadedContactsCurrentPage = false;
// wait as we might need to wait for contact-load status to be set
waitUntilContactLoadedOrTimeout(300).then(() => {
logger.log('contacts are loaded, or timeouted');
_resolve();
});
}
else {
logger.log('failed to get contacts for current page');
_reject();
}
})
.catch((err) => {
logger.log('error to get contacts from current page');
_reject(err);
});
});
};
// only for first time we wait for contact-load flag to be set first
waitUntilContactLoaded().then(() => {
logger.log('contacts are loaded, ready to proceed getting all contacts');
// navigate page from first to last
syncPromiseLoop(numPage, workerFn)
.then(() => {
// all done
logger.log('we got all contacts now!');
// save all contacts to itself for later use
that.contacts = allContacts;
// click on chat tab button to return to normal
// make others convenient to work without a need to click on chat tab button, or check before proceed again
wxMediator.clickOnChatTabButton(headless)
.then((result) => {
if (result) {
logger.log('successfully clicked on chat tab button (for conveient of other components to proceed work)');
resolve(allContacts);
}
else {
logger.log('failed to click on chat tab button (for conveient of other components to proceed work)');
reject(false);
}
})
.catch((err) => {
logger.log('error trying to click on chat tab button (for conveient of other components to proceed work)');
reject(err);
});
})
.catch((err) => {
logger.log(err);
reject(err);
});
});
}
})
.catch((err) => {
logger.log('error to get one or more property value from scrollableDiv element');
reject(err);
});
}
else {
logger.log('failed to click on contact tab item');
reject(false);
}
})
.catch((err) => {
logger.log('error trying to click on contact tab item');
reject(err);
});
});
},
/**
* Process messages.
* @param {Object} headless Headless object
* @param {Function} processorFn (Optional) Message processor function with signature fn(msgObj) in which msgObj is { context: <Object>, message: <String> }. The function needs to return string as a reply back of receiving message.
*/
processMsgs: function(headless, processorFn) {
// check normal new msg
wxMediator.checkNewMsgAndClickOnIt(headless, true)
.then((newMsgs) => {
if (newMsgs != 0 && newMsgs != false) {
logger.log(`found (${newMsgs}) new messages`);
// get latest N messgaes
wxMediator.getLatestNMsg(headless, newMsgs)
.then((msgs) => {
logger.log('got msgs: ', msgs);
// create workerFn (see signature of function in promise-syncloop.js)
// 1st argument is array of messages which is 'msgs' above
let workerFn = function(i, ...args) {
return new Promise((resolve, reject) => {
// get message from input parameter
let msgs = args[0];
// get individual message
let msg = msgs[i];
if (processorFn != null) {
// call custom processor callback to get reply message
var replyMsg = processorFn(msg);
// validate
if (replyMsg == null || typeof replyMsg !== 'string') {
// return immediately, and skip to next message
return resolve();
}
// set reply message
wxMediator.setReplyMsg(headless, replyMsg)
.then((result) => {
if (result) {
logger.log('successfully set reply msg to textarea');
// workaround: switch to file transfer
// in order to let text message update to DOM
wxMediator.clickOnFilehelper(headless)
.then((result) => {
if (result) {
logger.log('[work around] successfully clicked on file helper');
// click on previously marked convo item
wxMediator.clickOnItemMarkedAsPreviousItem(headless)
.then((result) => {
if (result) {
logger.log('successfully clicked on prevoiusly marked item');
// click on send button
wxMediator.clickOnSendButton(headless)
.then((result) => {
if (result) {
logger.log('successfully clicked on send button');
resolve();
}
else {
logger.log('failed to click on send button');
resolve();
}
})
.catch((err) => {
logger.log(err);
// resolve it, and process next message
resolve();
});
}
else {
logger.log('failed to click on previously marked item');
resolve();
}
})
.catch((err) => {
logger.log('failed to click on previously marked item');
resolve();
});
}
else {
logger.log('[work around] failed to click on file helper');
resolve();
}
})
.catch((err) => {
logger.log('[work around] failed to click on file helper');
resolve();
});
}
else {
logger.log('failed to set reply msg to textarea');
// resolve it, and process next message
resolve();
}
}).catch((err) => {
logger.log(err);
// resolve it, and process next message
resolve();
});
}
else {
// processor callback for message isn't set, then ignore it
logger.log('processor callback isn\'t set, skip processing');
// just ignore this message
resolve();
}
});
};
// process messages from oldest to newest
syncPromiseLoop(msgs.length, workerFn, msgs)
.then(() => {
// all done
// click on filehelper to allow us to detect new msgs again
wxMediator.clickOnFilehelper(headless)
.then((foundAndClicked) => {
if (foundAndClicked) {
logger.log('clicked on filehelper');
}
else {
logger.log('failed to click on filehelper');
}
})
.catch((err) => {
logger.log(err);
});
})
.catch((err) => {
logger.log(err);
});
})
.catch((err) => {
logger.log(err);
});
}
})
.catch((err) => {
logger.log(err);
});
}
};
module.exports = _;