· 7 years ago · Mar 08, 2018, 06:02 PM
1var fs = require('fs');
2var crypto = require('crypto');
3var console = process.console;
4var config = require('./config.js');
5var getSteamAPIKey = require('steam-web-api-key');
6var redisClient, io, requestify;
7var requestify = require('requestify');
8
9module.exports.init = function(redis, ioSocket, requestifyCore) {
10 io = ioSocket;
11 redisClient = redis.createClient();
12 requestify = requestifyCore;
13}
14
15var hash = require('crypto').createHash('sha1');
16hash.update(Math.random().toString());
17hash = hash.digest('hex');
18var device_id = 'android:' + hash;
19
20function log(log) {console.log('[BOTS] ' + log);}
21
22requestify.post('http://' + config.domain + '/api/getBots')
23.then((response) => {
24 response = JSON.parse(response.body);
25 if(!response.success) {
26 log('Ðе удалоÑÑŒ найти бота!');
27 return;
28 }
29 getBots(response.bots);
30}, (response) => {
31 log(JSON.stringify(response.body));
32});
33
34function getBots(bots) {
35
36for(var i = 0; i < bots.length; i++) startBot(bots[i]);
37
38}
39
40function startBot(data) {
41function botLog(log) {
42 console.tag('ROULETTE', logTime()).log(log);
43 }
44var logOnOptions = {
45 account_name : data.username,
46 password : data.password
47}
48
49var g_Steamid = data.steamid64;
50var g_Device_ID = device_id;
51var g_Indentity_Secret = data.identity;
52var g_Shared_Secret = data.shared;
53
54function getSHA1(bytes) {
55 var shasum = crypto.createHash('sha1');
56 shasum.end(bytes);
57 return shasum.read();
58}
59
60var Steam = require('steam');
61var steam = new Steam.SteamClient();
62var SteamTradeOffers = require('steam-tradeoffers');
63var offers = new SteamTradeOffers();
64var steamUser = new Steam.SteamUser(steam);
65var SteamTotp = require('steam-totp');
66var steamFriends = new Steam.SteamFriends(steam);
67var SteamWebLogOn = require('steam-weblogon');
68var steamWebLogOn = new SteamWebLogOn(steam, steamUser);
69var SteamConf = require('steamcommunity-mobile-confirmations');
70var steamConf;
71var confirmTimer;
72steam.connect();
73
74function GetEscrowCode(){
75 botLog('Код авторизиции : '+SteamTotp.generateAuthCode(data.shared));
76 setTimeout(function(){
77 GetEscrowCode()
78 }, 30000);
79}
80GetEscrowCode();
81logOnOptions['two_factor_code'] = SteamTotp.generateAuthCode(g_Shared_Secret);
82steam.on('connected', function() {
83 steamUser.logOn(logOnOptions);
84 });
85
86steam.on('debug', botLog);
87
88steam.on('logOnResponse', function(result) {
89 if (result.eresult === Steam.EResult.OK) {
90 botLog('ÐвторизовалиÑÑŒ!');
91 reWebLogOn(steam);
92 steamFriends.setPersonaState(Steam.EPersonaState.Online);
93 } else {
94 botLog('Ошибка при авторизиции - ' + result.eresult);
95 }
96});
97
98
99function logTime() {
100
101 var date = new Date();
102 var hour = date.getHours();
103 var min = date.getMinutes();
104 var sec = date.getSeconds();
105
106 var year = date.getFullYear();
107 var month = date.getMonth() + 1;
108 var day = date.getDate();
109
110 hour = (hour < 10 ? "0" : "") + hour;
111 min = (min < 10 ? "0" : "") + min;
112 sec = (sec < 10 ? "0" : "") + sec;
113 month = (month < 10 ? "0" : "") + month;
114 day = (day < 10 ? "0" : "") + day;
115
116 return hour + ":" + min + ":" + sec;
117}
118
119
120var checkingOffers = [],
121 WebSession = false,
122 globalSession;
123
124function reWebLogOn(steam, callback) {
125 steamWebLogOn.webLogOn(function(webSessionID, newCookie){
126 botLog('Подключили cookie! - ' + newCookie);
127 steamConf = new SteamConf(
128 {
129 steamid: g_Steamid,
130 identity_secret: g_Indentity_Secret,
131 device_id: g_Device_ID,
132 webCookie: newCookie,
133 });
134 getSteamAPIKey({
135 sessionID: webSessionID,
136 webCookie: newCookie
137 }, function(err, APIKey) {
138 offers.setup({
139 sessionID: webSessionID,
140 webCookie: newCookie,
141 APIKey: APIKey
142 }, function(err){
143 WebSession = true;
144 globalSession = webSessionID;
145 redisClient.lrange(redisChannels.tradeoffersList, 0, -1, function(err, offers){
146 offers.forEach(function(offer) {
147 checkingOffers.push(offer);
148 });
149 handleOffers();
150 });
151 botLog('ÐаÑтройка Офферов!');
152 });
153 botLog('Создал ÑеÑÑию! - '+ webSessionID);
154 });
155
156 });
157}
158
159function confirmTrade(){
160 steamConf.FetchConfirmations((function (err, confirmations) {
161 if (err) {
162 botLog('Ошибка Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ñ„Ñ„ÐµÑ€Ð°: ' + err);
163 return;
164 }
165 botLog('Трейдов в ожидании:' + confirmations.length);
166 if (!confirmations.length) {
167 return;
168 }
169 steamConf.AcceptConfirmation(confirmations[0], (function (err, result) {
170 if (err) {
171 botLog('Ошибка Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ñ„Ñ„ÐµÑ€Ð°: ' + err);
172 return;
173 }
174 botLog('Result:' + result);
175 }).bind(this));
176 }).bind(this));
177
178
179
180}
181
182
183const redisChannels = {
184 checkItemsList: 'checkItems.list',
185 checkList: 'check.list',
186 checkedList: 'checked.list',
187 betsList: 'bets.list',
188 sendOffersList: 'send.offers.list',
189 tradeoffersList: 'tradeoffers.list',
190 declineList: 'decline.list',
191 usersQueue: 'usersQueue.list'
192}
193
194
195steamUser.on('updateMachineAuth', function(sentry, callback) {
196 fs.writeFileSync('sentry', sentry.bytes);
197 callback({ sha_file: getSHA1(sentry.bytes) });
198});
199
200
201function checkBadGame(){
202var mysql = require('mysql');
203var connection = mysql.createConnection({
204host : 'localhost',
205user : 'root',
206password : config.passmysql,
207database : config.dbmysql
208});
209
210connection.connect();
211
212getBadGame ='SELECT won_items, winner_id, items, won_items, g.id as id, steamid64, accessToken FROM `games` as g JOIN users as u ON winner_id = u.id WHERE `not_sended` = 1 AND status = 3 ORDER BY id DESC LIMIT 0,1';
213connection.query(getBadGame, function(err, rows, fields) {
214rows.forEach(function(item, i, arr) {
215botLog('Игра # '+item.id+' игрок #'+ item.winner_id + ' не отправлено '+ item.items +" предметов, пытаюÑÑŒ отправить ÑейчаÑ.");
216sendoffer = [];
217offer = JSON.parse(item.won_items);
218offer.forEach(function(it, key, array) {
219sendoffer.push(it.classid);
220});
221sendTradeOffer(730, item.steamid64, item.accessToken, sendoffer, '', item.id, '');
222});
223});
224connection.end();
225}
226setInterval(function(){checkBadGame();}, 120000);
227
228
229
230function handleOffers() {
231 offers.getOffers({
232 get_received_offers: 1,
233 active_only: 1
234 }, function(error, body) {
235 if (
236 body
237 && body.response
238 && body.response.trade_offers_received
239 ) {
240 body.response.trade_offers_received.forEach(function(offer) {
241 if (offer.trade_offer_state == 2) {
242 if(is_checkingOfferExists(offer.tradeofferid)) return;
243
244 if(offer.items_to_give != null && config.admins.indexOf(offer.steamid_other) != -1) {
245 botLog('Оффер #' + offer.tradeofferid + ' От: Admin ' + offer.steamid_other);
246 offers.acceptOffer({tradeOfferId: offer.tradeofferid});
247 confirmTrade();
248 return;
249 }
250
251 offers.getTradeHoldDuration({tradeOfferId : offer.tradeofferid}, function(err, response)
252 {
253 botLog(offer.steamid_other);
254 botLog(offer.accessToken);
255
256 if (response && response.their != 0)
257 {
258 botLog('Hold TradeOfferID: '+offer.tradeofferid+' days: '+response.their);
259
260
261 redisClient.multi([
262 ['rpush', redisChannels.declineList, offer.tradeofferid],
263 ['lrem', redisChannels.checkItemsList, 0, offer],
264 ['lrem', redisChannels.checkedList, 0, offer]
265 ])
266 .exec(function (err, replies) {
267 });
268
269
270 requestify.post('http://'+config.domain+'/api/haventescrow', {secretKey: config.secretKey , steamid: offer.steamid_other});
271
272 return;
273 }
274
275 });
276 if(offer.items_to_give != null) {
277 offers.declineOffer({tradeOfferId: offer.tradeofferid});
278 return;
279 }
280 if (offer.items_to_receive != null && offer.items_to_give == null) {
281 checkingOffers.push(offer.tradeofferid);
282 botLog('Оффер #' + offer.tradeofferid + ' От: ' + offer.steamid_other);
283 redisClient.multi([
284 ['rpush', redisChannels.tradeoffersList, offer.tradeofferid],
285 ['rpush', redisChannels.checkItemsList, JSON.stringify(offer)],
286 ['rpush', redisChannels.usersQueue, offer.steamid_other]
287 ]).exec(function(){
288 redisClient.lrange(redisChannels.usersQueue, 0, -1, function(err, queues) {
289 io.sockets.emit('queue', queues);
290 });
291 });
292 return;
293 }
294 }
295 });
296 }
297 });
298}
299
300
301steamUser.on('tradeOffers', function(number) {
302 if (number > 0) {
303 handleOffers();
304 }
305});
306
307
308var parseOffer = function(offer, offerJson){
309 offers.loadPartnerInventory({partnerSteamId: offer.steamid_other, appId: 730, contextId: 2, tradeOfferId: offer.tradeofferid, language: "russian"}, function(err, hitems) {
310 if (err) {
311 redisClient.multi([
312 ['rpush', redisChannels.declineList, offer.tradeofferid],
313 ['lrem', redisChannels.checkItemsList, 0, offerJson],
314 ['lrem', redisChannels.usersQueue, 1, offer.steamid_other]
315 ])
316 .exec(function (err, replies) {
317 parseItemsProcceed = false;
318 return;
319 });
320 return;
321 }
322 var items = offer.items_to_receive;
323 var items_to_check = [], num = 0;
324 for (var i = 0; i < items.length; i++) {
325 for (var j = 0; j < hitems.length; j++) {
326 if (items[i].assetid == hitems[j].id) {
327 items_to_check[num] = {
328 appid:hitems[j].appid,
329 name:hitems[j].market_name,
330 market_hash_name:hitems[j].market_hash_name,
331 classid:hitems[j].classid
332 };
333 var type = hitems[j].type;
334 var rarity = '';
335 var arr = type.split(',');
336 if (arr.length == 2) type = arr[1].trim();
337 if (arr.length == 3) type = arr[2].trim();
338 if (arr.length && arr[0] == 'Ðож') type = '★';
339 switch (type) {
340 case 'ÐрмейÑкое качеÑтво': rarity = 'milspec'; break;
341 case 'Запрещенное': rarity = 'restricted'; break;
342 case 'ЗаÑекреченное': rarity = 'classified'; break;
343 case 'Тайное': rarity = 'covert'; break;
344 case 'Ширпотреб': rarity = 'common'; break;
345 case 'Промышленное качеÑтво': rarity = 'common'; break;
346 case '★': rarity = 'rare'; break;
347 }
348 items_to_check[num].rarity = rarity;
349 num++;
350 break;
351 }
352 }
353 }
354 var value = {
355 offerid: offer.tradeofferid,
356 accountid: offer.steamid_other,
357 items: JSON.stringify(items_to_check)
358 };
359
360 console.tag('ROULETTE','OFFER:' + value.offerid).log(value);
361
362 redisClient.multi([
363 ['rpush', redisChannels.checkList, JSON.stringify(value)],
364 ['lrem', redisChannels.checkItemsList, 0, offerJson]
365 ])
366 .exec(function (err, replies) {
367 parseItemsProcceed = false;
368 });
369
370 });
371}
372
373var checkOfferPrice = function () {
374 requestify.get('http://' + config.domain + '/api/checkOffer', {
375 secretKey: config.secretKey
376 })
377 .then(function (response) {
378 var answer = JSON.parse(response.body);
379
380 if (answer.success) {
381 checkProcceed = false;
382 }
383 }, function (response) {
384 console.tag('SteamBot', logTime()).error('Что-то не так Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¾Ð¹ офферов.');
385 setTimeout(function () {
386 checkOfferPrice()
387 }, 1000);
388 });
389
390}
391
392var checkNewBet = function () {
393 requestify.get('http://' + config.domain + '/api/newBet', {
394 secretKey: config.secretKey
395 })
396 .then(function (response) {
397 var answer = JSON.parse(response.body);
398 if (answer.success) {
399 betsProcceed = false;
400 }
401 }, function (response) {
402 console.tag('SteamBot', logTime()).error('Что-то не так Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸ÐµÐ¼ новой Ñтавки.');
403 setTimeout(function () {
404 checkNewBet()
405 }, 1000);
406 });
407}
408
409var checkArrGlobal = [];
410
411var sendTradeOffer = function(appId, partnerSteamId, accessToken, sendItems, message, game, offerJson){
412 try {
413 offers.loadMyInventory({
414 appId: appId,
415 contextId: 2
416 }, function (err, items) {
417 if(err) {
418 botLog(err);
419 sendProcceed = false;
420 return;
421 }
422 var itemsFromMe = [],
423 checkArr = [],
424 num = 0;
425 var i = 0;
426 for (var i = 0; i < sendItems.length; i++) {
427 for (var j = 0; j < items.length; j++) {
428 if (items[j].tradable && (items[j].classid == sendItems[i])) {
429 if ((checkArr.indexOf(items[j].id) == -1) && (checkArrGlobal.indexOf(items[j].id) == -1)) {
430 checkArr[i] = items[j].id;
431 itemsFromMe[num] = {
432 appid: 730,
433 contextid: 2,
434 amount: items[j].amount,
435 assetid: items[j].id
436 };
437 num++;
438 break;
439 }
440 }
441 }
442 }
443 if (num > 0) {
444 offers.makeOffer({
445 partnerSteamId: partnerSteamId,
446 accessToken: accessToken,
447 itemsFromMe: itemsFromMe,
448 itemsFromThem: [],
449 message: 'ПоздравлÑем Ñ Ð¿Ð¾Ð±ÐµÐ´Ð¾Ð¹ на Ñайте '+config.nameSite+' > Ð’ игре #' + game
450 }, function (err, response) {
451 if (err) {
452 console.tag('SteamBot', 'SendPrize', logTime()).error('Ошибка Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ„Ñ„ÐµÑ€Ð°:' + err.message);
453 getErrorCode(err.message, function(errCode) {
454 if(errCode == 2 || errCode == 3 || errCode == 6 || errCode == 15 || errCode == 16 || errCode == 25 || errCode == 50 || err.message.indexOf('was an error sending your trade offer. Please try again later.')) { // if there is an error in submitting the winning re-send it
455 redisClient.lrem(redisChannels.sendOffersList, 0, offerJson, function (err, data) {
456 setPrizeStatus(game, 2);
457 CheckGameForSend(game, 1);
458 setTimeout(function () {
459 CheckGameForSend(game, 0);
460 setPrizeStatus(game, 1);
461 console.tag('SteamBot', 'SendPrize', logTime()).log('Прошло 10 минут, оÑтанавливем переотправку, и Ñтавим ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ€Ð°ÑƒÐ½Ð´Ð° - 1.');
462 }, 60000);
463 sendProcceed = false;
464 });
465 sendProcceed = false;
466 }
467 sendProcceed = false;
468 });
469 return;
470 }
471 checkArrGlobal = checkArrGlobal.concat(checkArr);
472 botLog(checkArrGlobal);
473 botLog(checkArr);
474 redisClient.lrem(redisChannels.sendOffersList, 0, offerJson, function(err, data){
475 setPrizeStatus(game, 1);
476 CheckGameForSend(game, 0);
477 sendProcceed = false;
478 });
479 botLog('Оффер #' + response.tradeofferid +' отправлен!');
480 confirmTrade();
481 });
482 }else{
483 botLog('Ðету предметов!');
484 redisClient.lrem(redisChannels.sendOffersList, 0, offerJson, function(err, data){
485 setPrizeStatus(game, 1);
486 CheckGameForSend(game, 0);
487 sendProcceed = false;
488 });
489 }
490 });
491
492 }catch(ex){
493 console.tag('SteamBot', logTime()).error('Error to send the bet');
494 setPrizeStatus(game, 2);
495 CheckGameForSend(game, 1);
496 sendProcceed = false;
497 }
498};
499
500
501
502var setPrizeStatus = function(game, status){
503 requestify.post('http://'+config.domain+'/api/setPrizeStatus', {
504 secretKey: config.secretKey,
505 game: game,
506 status: status
507 })
508 .then(function(response) {
509
510 },function(response){
511 botLog('Что-то не так Ñ ÑƒÑтановкой prize status.');
512 setTimeout(function(){setPrizeStatus()}, 1000);
513 });
514}
515
516var CheckGameForSend = function(game, status){
517 requestify.post('http://'+config.domain+'/api/CheckGameForSend', {
518 secretKey: config.secretKey,
519 game: game,
520 status: status
521 })
522 .then(function(response) {
523
524 },function(response){
525 botLog('Что-то не так Ñ ÑƒÑтановкой CheckGameForSend.');
526 setTimeout(function(){CheckGameForSend()}, 1000);
527 });
528}
529
530var is_checkingOfferExists = function(tradeofferid){
531 for(var i = 0, len = checkingOffers.length; i<len; ++i ){
532 var offer = checkingOffers[i];
533 if(offer == tradeofferid){
534 return true;
535 break;
536 }
537 }
538 return false;
539}
540
541var checkedOffersProcceed = function(offerJson){
542 var offer = JSON.parse(offerJson);
543 if (offer.success) {
544 botLog('Принимаем: #' + offer.offerid);
545 offers.acceptOffer({tradeOfferId: offer.offerid}, function (err, body) {
546 if (!err) {
547 redisClient.multi([
548 ["lrem", redisChannels.tradeoffersList, 0, offer.offerid],
549 ["lrem", redisChannels.usersQueue, 1, offer.steamid64],
550 ["rpush", redisChannels.betsList, offerJson],
551 ["lrem", redisChannels.checkedList, 0, offerJson]
552 ])
553 .exec(function (err, replies) {
554 redisClient.lrange(redisChannels.usersQueue, 0, -1, function(err, queues) {
555 io.sockets.emit('queue', queues);
556 botLog("Ðовый депозит принÑÑ‚!");
557 checkedProcceed = false;
558 });
559 });
560
561 } else {
562 botLog('Ошибка. Ðе могу принÑть оффер #' + offer.offerid + ' Log: ' + err)
563
564 offers.getOffer({tradeOfferId: offer.offerid}, function (err, body){
565 if(body && body.response && body.response.offer){
566 var offerCheck = body.response.offer;
567 if(offerCheck.trade_offer_state == 2) {
568 checkedProcceed = false;
569 return;
570 }
571 if(offerCheck.trade_offer_state == 3){
572 redisClient.multi([
573 ["lrem", redisChannels.tradeoffersList, 0, offer.offerid],
574 ["lrem", redisChannels.usersQueue, 1, offer.steamid64],
575 ["rpush", redisChannels.betsList, offerJson],
576 ["lrem", redisChannels.checkedList, 0, offerJson]
577 ])
578 .exec(function (err, replies) {
579 redisClient.lrange(redisChannels.usersQueue, 0, -1, function(err, queues) {
580 io.sockets.emit('queue', queues);
581 checkedProcceed = false;
582 });
583 });
584 }else{
585 redisClient.multi([
586 ["lrem", redisChannels.tradeoffersList, 0, offer.offerid],
587 ["lrem", redisChannels.usersQueue, 1, offer.steamid64],
588 ["lrem", redisChannels.checkedList, 0, offerJson]
589 ])
590 .exec(function (err, replies) {
591 redisClient.lrange(redisChannels.usersQueue, 0, -1, function(err, queues) {
592 io.sockets.emit('queue', queues);
593 checkedProcceed = false;
594 });
595 });
596 }
597 }
598 })
599 }
600 });
601 }
602}
603
604var declineOffersProcceed = function(offerid){
605 botLog('ОтменÑем: #' + offerid);
606 offers.declineOffer({tradeOfferId: offerid}, function (err, body) {
607 if (!err) {
608 botLog('Оффер #' + offerid + ' Отменен!');
609 redisClient.lrem(redisChannels.declineList, 0, offerid);
610 declineProcceed = false;
611 } else {
612 botLog('Ошибка. Ðе могу отменить оффер #' + offer.offerid + ' Log: ' + err)
613 declineProcceed = false;
614 }
615 });
616}
617
618
619var queueProceed = function(){
620 redisClient.llen(redisChannels.checkList, function(err, length) {
621 if (length > 0 && !checkProcceed && WebSession) {
622 console.tag('SteamBot','Queues', logTime()).info('ПроверÑем Офферы:' + length);
623 checkProcceed = true;
624 checkOfferPrice();
625 }
626 });
627 redisClient.llen(redisChannels.declineList, function(err, length) {
628 if(length > 0 && !declineProcceed && WebSession) {
629 console.tag('SteamBot','Queues', logTime()).info('ОтклонÑем Офферы:' + length);
630 declineProcceed = true;
631 redisClient.lindex(redisChannels.declineList, 0,function (err, offer) {
632 declineOffersProcceed(offer);
633 });
634 }
635 });
636 redisClient.llen(redisChannels.checkedList, function(err, length) {
637 if(length > 0 && !checkedProcceed && WebSession) {
638 console.tag('SteamBot','Queues', logTime()).info('Проверенные Офферы:' + length);
639 checkedProcceed = true;
640 redisClient.lindex(redisChannels.checkedList, 0,function (err, offer) {
641 checkedOffersProcceed(offer);
642 });
643 }
644 });
645 redisClient.llen(redisChannels.betsList, function(err, length) {
646 if (length > 0 && !betsProcceed && !delayForNewGame) {
647 console.tag('SteamBot','Queues', logTime()).info('Ставки:' + length);
648 betsProcceed = true;
649 checkNewBet();
650 }
651 });
652 redisClient.llen(redisChannels.sendOffersList, function(err, length) {
653 if (length > 0 && !sendProcceed && WebSession) {
654 console.tag('SteamBot','Queues', logTime()).info('ОтправлÑем победителю оффер:' + length);
655 sendProcceed = true;
656 redisClient.lindex(redisChannels.sendOffersList, 0,function (err, offerJson) {
657 offer = JSON.parse(offerJson);
658 sendTradeOffer(offer.appId, offer.steamid, offer.accessToken, offer.items, '', offer.game, offerJson);
659 });
660 }
661 });
662 redisClient.llen(redisChannels.checkItemsList, function(err, length) {
663 if (length > 0 && !parseItemsProcceed && WebSession) {
664 console.tag('SteamBot','Queues', logTime()).info('ПарÑим предметы:' + length);
665 parseItemsProcceed = true;
666 redisClient.lindex(redisChannels.checkItemsList, 0, function (err, offerJson) {
667 offer = JSON.parse(offerJson);
668 parseOffer(offer, offerJson);
669 });
670 }
671 });
672}
673var parseItemsProcceed = false;
674var checkProcceed = false;
675var checkedProcceed = false;
676var declineProcceed = false;
677var betsProcceed = false;
678var sendProcceed = false;
679var delayForNewGame = false;
680setInterval(queueProceed, 1500);
681
682module.exports.handleOffers = handleOffers;
683module.exports.delayForNewGame = function(value){
684 delayForNewGame = value;
685};
686
687function getErrorCode(err, callback){
688 var errCode = 0;
689 var match = err.match(/\(([^()]*)\)/);
690 if(match != null && match.length == 2) errCode = match[1];
691 callback(errCode);
692}
693
694}