source: trunk/FACT++/src/skypeclient.cc@ 13965

Last change on this file since 13965 was 13965, checked in by tbretz, 12 years ago
File size: 23.7 KB
Line 
1#include <iostream>
2
3#include "EventImp.h"
4#include "Configuration.h"
5#include "StateMachineDim.h"
6#include "LocalControl.h"
7
8#include <boost/tokenizer.hpp>
9
10#include <dbus/dbus-glib-lowlevel.h>
11
12using namespace std;
13
14class SkypeClient : public StateMachineDim
15{
16private:
17 static const string fAuthorizationMsg;
18
19 enum {
20 kStateDisconnected = 1,
21 kStateConnected = 2,
22 };
23
24 Time fLastConnect;
25
26 DBusConnection *fBus;
27 GMainLoop *fLoop;
28
29 vector<string> fContacts;
30
31 string fUser;
32
33 bool fAllowRaw;
34
35 string Contact(const string &id) const
36 {
37 if (id.size()==0)
38 return "";
39
40 if (id[0]!='#')
41 return "";
42
43 const size_t p = id.find_first_of('/');
44 if (p==string::npos)
45 return "";
46
47 return id.substr(1, p-1);
48 }
49
50
51
52 static DBusHandlerResult NotifyHandler(DBusConnection *, DBusMessage *dbus_msg, void *user_data)
53 {
54 static_cast<SkypeClient*>(user_data)->HandleDBusMessage(dbus_msg);
55 static_cast<SkypeClient*>(user_data)->Minimize();
56 return DBUS_HANDLER_RESULT_HANDLED;
57 }
58
59 int HandleMsg(const EventImp &evt)
60 {
61 if (evt.GetSize()==0)
62 return GetCurrentState();
63
64 for (auto it=fContacts.begin(); it!=fContacts.end(); it++)
65 SendSkypeMessage(*it, evt.GetString());
66
67 ostringstream msg;
68 msg << evt.GetString() << " [" << fContacts.size() << "]";
69
70 Info(msg);
71
72 return GetCurrentState();
73 }
74
75 int HandleRaw(const EventImp &evt)
76 {
77 if (evt.GetSize()==0 || !fAllowRaw)
78 return GetCurrentState();
79
80 SendDBusMessage(evt.GetString());
81
82 return GetCurrentState();
83 }
84
85 int HandleCall()
86 {
87 int cnt = 0;
88 for (auto it=fContacts.begin(); it!=fContacts.end(); it++)
89 {
90 const string user = Contact(*it);
91 if (user.empty())
92 continue;
93
94 SendDBusMessageNB("CALL "+user);
95
96 cnt++;
97 }
98
99 ostringstream msg;
100 msg << "CALLING [" << cnt << "/" << fContacts.size() << "]";
101
102 Info(msg);
103
104 return GetCurrentState();
105 }
106
107 int HandleSMS(const EventImp &sms)
108 {
109 /*
110 -> CREATE SMS OUTGOING +0123456789
111 <- SMS 821 STATUS COMPOSING
112 <- SMS 821 PRICE 0
113 <- SMS 821 TIMESTAMP 0
114 <- SMS 821 PRICE_PRECISION 3
115 <- SMS 821 PRICE_CURRENCY EUR
116 <- SMS 821 STATUS COMPOSING
117 <- SMS 821 TARGET_NUMBERS +0123456789
118 <- SMS 821 PRICE -1
119 <- SMS 821 TARGET_STATUSES +0123456789=TARGET_ANALYZING
120 <- SMS 821 TARGET_STATUSES +0123456789=TARGET_ACCEPTABLE
121 <- SMS 821 PRICE 78
122
123 //-------------------------------------------------------------------
124 // Now let's add two more target numbers (in addition to original)
125 -> SET SMS 1702 TARGET_NUMBERS +37259877305, +37259877306, +37259877307
126 <- SMS 1702 TARGET_NUMBERS +37259877305, +37259877306, +37259877307
127 <- SMS 1702 TARGET_NUMBERS +37259877305, +37259877306, +37259877307
128 <- SMS 1702 PRICE -1
129 <- SMS 1702 TARGET_STATUSES +37259877305=TARGET_ACCEPTABLE, +37259877306=TARGET_ANALYZING, +37259877307=TARGET_ANALYZING
130 <- SMS 1702 TARGET_STATUSES +37259877305=TARGET_ACCEPTABLE, +37259877306=TARGET_ACCEPTABLE, +37259877307=TARGET_ACCEPTABLE
131 <- SMS 1702 TARGET_STATUSES +37259877305=TARGET_ACCEPTABLE, +37259877306=TARGET_ACCEPTABLE, +37259877307=TARGET_ACCEPTABLE
132 <- SMS 1702 PRICE 234
133
134 TARGET_ANALYZING
135 TARGET_UNDEFINED
136 TARGET_ACCEPTABLE
137 TARGET_NOT_ROUTABLE
138 TARGET_DELIVERY_PENDING
139 TARGET_DELIVERY_SUCCESSFUL
140 TARGET_DELIVERY_FAILED
141 UNKNOWN
142
143 // ----------------------------------------------------------------
144 // This is how to set the message text property
145 // Note that you will get two identical lines in response
146 -> SET SMS 821 BODY "test 123 test 223 test 333"
147 <- SMS 821 BODY "test 123 test 223 test 333"
148 <- SMS 821 BODY "test 123 test 223 test 333"
149
150 // ----------------------------------------------------------------
151 // Now lets try to send the message
152 -> ALTER SMS 821 SEND
153 <- ALTER SMS 821 SEND
154 <- SMS 821 STATUS SENDING_TO_SERVER
155 <- SMS 821 TIMESTAMP 1174058095
156 <- SMS 821 TARGET_STATUSES +0123456789=TARGET_ACCEPTABLE
157 <- SMS 821 TARGET_STATUSES +0123456789=TARGET_DELIVERY_FAILED
158 <- SMS 821 FAILUREREASON INSUFFICIENT_FUNDS
159 <- SMS 821 STATUS FAILED
160 <- SMS 821 IS_FAILED_UNSEEN TRUE
161
162 STATUS
163 RECEIVED the message has been received (but not tagged as read)
164 READ the message has been tagged as read
165 COMPOSING the message has been created but not yet sent
166 SENDING_TO_SERVER the message is in process of being sent to server
167 SENT_TO_SERVER the message has been sent to server
168 DELIVERED server has confirmed that the message is sent out to recepient
169 SOME_TARGETS_FAILED server reports failure to deliver the message to one of the recepients within 24h
170 FAILED the message has failed, possible reason may be found in FAILUREREASON property
171 UNKNOWN message status is unknown
172
173 FAILUREREASON
174 MISC_ERROR indicates failure to supply a meaningful error message
175 SERVER_CONNECT_FAILED unable to connect to SMS server
176 NO_SMS_CAPABILITY recepient is unable to receive SMS messages
177 INSUFFICIENT_FUNDS insufficient Skype Credit to send an SMS message
178 INVALID_CONFIRMATION_CODE set when an erroneous code was submitted in a CONFIRMATION_CODE_SUBMIT message
179 USER_BLOCKED user is blocked from the server
180 IP_BLOCKED user IP is blocked from the server
181 NODE_BLOCKED user p2p network node has been blocked from the server
182 UNKNOWN default failure code
183 NO_SENDERID_CAPABILITY Set when a CONFIRMATION_CODE_REQUEST SMS message is sent with a mobile phone number containing country code of either USA, Taiwan or China. Setting reply-to number from Skype SMS’s to your mobile number is not supported in these countries. Added in Skype version 3.5 (protocol 8).
184
185 // ----------------------------------------------------------------
186 // As sending the message failed (not enough Skype credit),
187 // lets delete the message
188 -> DELETE SMS 821
189 <- DELETE SMS 821
190 */
191
192 return GetCurrentState();
193 }
194
195 string SendDBusMessage(const string &cmd, bool display=true)
196 {
197 DBusMessage *send = GetDBusMessage(cmd);
198 if (!send)
199 return "";
200
201 DBusError error;
202 dbus_error_init(&error);
203
204 // Send the message and wait for reply
205 if (display)
206 Info("TX: "+cmd);
207 DBusMessage *reply=
208 dbus_connection_send_with_reply_and_block(fBus, send,
209 DBUS_TIMEOUT_USE_DEFAULT,
210 &error);
211 /*
212 DBusPendingCall *pending = 0;
213 if (!dbus_connection_send_with_reply (fBus, send,
214 &pending, DBUS_TIMEOUT_USE_DEFAULT))
215 return "";
216
217 if (!pending)
218 return "";
219
220 bool = dbus_pending_call_get_completed(pending);
221 //dbus_pending_call_block(pending);
222 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
223 dbus_pending_call_unref(pending);
224 */
225
226 if (!reply)
227 {
228 Error("dbus_connection_send_with_reply_and_block: "+string(error.message));
229 dbus_error_free(&error);
230 return "";
231 }
232
233 // Get Skype's reply string
234 const char *ack = 0;
235 dbus_message_get_args(reply, 0, DBUS_TYPE_STRING, &ack, DBUS_TYPE_INVALID);
236
237 if (display)
238 Info("RX: "+string(ack));
239
240 const string rc = ack;
241
242 // Show no interest in the previously created messages.
243 // DBus will delete a message if reference count drops to zero.
244 dbus_message_unref(send);
245 dbus_message_unref(reply);
246
247 return rc;
248 }
249
250 DBusMessage *GetDBusMessage(const string &cmd)
251 {
252 // Create a message to be sent to Skype
253
254 // Constructs a new message to invoke a method on a remote object.
255 // Sets the service the message should be sent to "com.Skype.API"
256 // Sets the object path the message should be sent to "/com/Skype"
257 // Sets the interface to invoke method on to "com.Skype.API"
258 // Sets the method to invoke to "Invoke"
259 DBusMessage *send=
260 dbus_message_new_method_call("com.Skype.API", "/com/Skype",
261 "com.Skype.API", "Invoke");
262 if (!send)
263 {
264 Error("dbus_message_new_method_call failed.");
265 return NULL;
266 }
267
268 // Set the argument of the Invoke method
269 // Sets arg to be an argument to be passed to the Invoke method.
270 // It is an input argument. It has a type string.
271 // There are no output arguments.
272 const char *msg = cmd.c_str();
273 dbus_message_append_args(send,
274 DBUS_TYPE_STRING, &msg,
275 DBUS_TYPE_INVALID);
276
277 return send;
278 }
279
280 bool Minimize()
281 {
282 DBusMessage *send = GetDBusMessage("MINIMIZE");
283 if (!send)
284 return false;
285
286 // Send the message and ignore the reply
287 const bool rc = dbus_connection_send(fBus, send, NULL);
288
289 // Show no interest in the previously created messages.
290 // DBus will delete a message if reference count drops to zero.
291 dbus_message_unref(send);
292
293 return rc;
294 }
295
296 bool SendDBusMessageNB(const string &cmd)
297 {
298 DBusMessage *send = GetDBusMessage(cmd);
299 if (!send)
300 return false;
301
302 // Send the message and ignore the reply
303 Info("TX: "+cmd);
304 const bool rc = dbus_connection_send(fBus, send, NULL);
305
306 // Show no interest in the previously created messages.
307 // DBus will delete a message if reference count drops to zero.
308 dbus_message_unref(send);
309
310 return rc;
311 }
312
313 bool SendSkypeMessage(const string &chat, const string &msg)
314 {
315 return SendDBusMessageNB("CHATMESSAGE "+chat+" "+msg);
316/*
317 // SendDBusMessage(bus, "CHAT CREATE "+user);
318
319 const string rc = SendDBusMessage("CHATMESSAGE "+chat+" "+msg);
320
321 const vector<string> vec = Split(rc);
322 if (vec[0]=="ERROR")
323 {
324 auto it = find(fContacts.begin(), fContacts.end(), chat);
325 if (it!=fContacts.end())
326 fContacts.erase(it);
327 return false;
328 }
329
330 return true;
331 */
332 }
333 vector<string> Split(const string &msg)
334 {
335 using namespace boost;
336
337 typedef char_separator<char> separator;
338 const tokenizer<separator> tok(msg, separator(" "));
339
340 vector<string> vec;
341 for (auto it=tok.begin(); it!=tok.end(); it++)
342 vec.push_back((*it)[0]==0?it->substr(1):*it);
343
344 return vec;
345 }
346
347 void HandleDBusMessage(DBusMessage *dbus_msg)
348 {
349 if (GetCurrentState()!=kStateConnected)
350 return;
351
352 // CALL target1, target2, target3
353 // SET CALL <id> STATUS FINISHED
354
355 // Stores the argument passed to the Notify method
356 // into notify_argument.
357
358 char *notify_argument=0;
359 dbus_message_get_args(dbus_msg, 0,
360 DBUS_TYPE_STRING, &notify_argument,
361 DBUS_TYPE_INVALID);
362
363 Info("Notify: "+string(notify_argument));
364
365 const vector<string> vec = Split(notify_argument);
366
367 if (vec[0]=="CURRENTUSERHANDLE")
368 {
369 if (vec[1]!=fUser)
370 {
371 Error("Wrong user '"+vec[1]+"' logged in, '"+fUser+"' expected!");
372 fNewState = kStateDisconnected;
373 return;
374 }
375 }
376
377 if (vec[0]=="CONNSTATUS")
378 {
379 // OFFLINE / CONNECTING / PAUSING / ONLINE
380 if (vec[1]!="ONLINE")
381 {
382 Error("Connection status '"+vec[1]+"'");
383 fNewState = kStateDisconnected;
384 return;
385 }
386 }
387
388 if (vec[0]=="USERSTATUS")
389 {
390 if (vec[1]!="ONLINE")
391 {
392 Info("Skype user not visible... setting online.");
393 SendDBusMessageNB("SET USERSTATUS ONLINE");
394 }
395 }
396
397 // USER rtlprmft RECEIVEDAUTHREQUEST Please allow me to see when you are online
398
399 if (vec[0]=="USER")
400 {
401 if (vec[2]=="ONLINESTATUS")
402 {
403 if (vec[3]=="OFFLINE")
404 {
405 }
406 Info("User '"+vec[1]+"' changed status to '"+vec[3]+"'");
407 }
408
409 // Answer authorization requests
410 if (vec[2]=="RECEIVEDAUTHREQUEST")
411 SendDBusMessageNB("SET USER "+vec[1]+" BUDDYSTATUS 2 "+fAuthorizationMsg);
412
413 //if (vec[2]=="NROF_AUTHED_BUDDIES")
414 // cout << vec[1] << " --> " << vec[3];
415 }
416
417 if (vec[0]=="GROUP")
418 {
419 // 1: gorup id
420 // 2: NROFUSERS
421 // 3: n
422 }
423
424 if (vec[0]=="CHATMESSAGE")
425 {
426 if (vec[2]=="STATUS" && vec[3]=="RECEIVED")
427 {
428 string rc;
429
430 rc=SendDBusMessage("GET CHATMESSAGE "+vec[1]+" CHATNAME");
431
432 const string id = Split(rc)[3];
433
434 rc=SendDBusMessage("GET CHATMESSAGE "+vec[1]+" BODY");
435
436 const size_t p = rc.find(" BODY ");
437 if (p==string::npos)
438 return;
439
440 rc = rc.substr(rc.find(" BODY ")+6);
441
442 if (rc=="start")
443 {
444 auto it = find(fContacts.begin(), fContacts.end(), id);
445 if (it==fContacts.end())
446 {
447 SendSkypeMessage(id, "Successfully subscribed.");
448 fContacts.push_back(id);
449 }
450 else
451 SendSkypeMessage(id, "You are already subscribed.");
452
453 return;
454 }
455 if (rc=="stop")
456 {
457 auto it = find(fContacts.begin(), fContacts.end(), id);
458 if (it!=fContacts.end())
459 {
460 SendSkypeMessage(id, "Successfully un-subscribed.");
461 fContacts.erase(it);
462 }
463 else
464 SendSkypeMessage(id, "You were not subscribed.");
465
466 return;
467 }
468
469 if (rc=="list")
470 {
471 ostringstream out;
472 out << "*\n\nCurrently subscribed:\n";
473
474 int cnt = 0;
475 for (auto it=fContacts.begin(); it!=fContacts.end(); it++)
476 {
477 const string user = Contact(*it);
478 if (user.empty())
479 continue;
480
481 out << " * " << user << '\n';
482 cnt ++;
483 }
484
485 if (cnt==0)
486 out << " <no subscriptions>";
487 else
488 out << cnt << " user(s) subscribed.";
489
490 SendSkypeMessage(id, out.str());
491 return;
492 }
493
494 SendSkypeMessage(id, "*\n\nSyntax Error:\nPlease use either 'start' or 'stop'");
495
496 }
497 }
498
499 if (vec[0]=="CHAT")
500 {
501 const string id = vec[1];
502 if (vec[2]=="ACTIVITY_TIMESTAMP")
503 {
504 //Info(vec[2]);
505 }
506 if (vec[2]=="MYROLE")
507 {
508 }
509 if (vec[2]=="MEMBERS")
510 {
511 }
512 if (vec[2]=="ACTIVEMEMBERS")
513 {
514 }
515 if (vec[2]=="STATUS")
516 {
517 // vec[3]=="DIALOG")
518 }
519 if (vec[2]=="TIMESTAMP")
520 {
521 }
522 if (vec[2]=="DIALOG_PARTNER")
523 {
524 }
525 if (vec[2]=="FRIENDLYNAME")
526 {
527 // Notify: CHAT #maggiyy/$rtlprmft;da26ea52b3e70e65 FRIENDLYNAME Lamouette | noch ne message
528 }
529 }
530
531 if (vec[0]=="CALL")
532 {
533 if (vec[2]=="STATUS" && vec[2]=="INPROGRESS")
534 SendDBusMessageNB("SET CALL "+vec[1]+ "STATUS FINISHED");
535
536 // CALL 1501 STATUS UNPLACED
537 // CALL 1501 STATUS ROUTING
538 // CALL 1501 STATUS RINGING
539 }
540
541 }
542
543 int HandleConnect()
544 {
545 fLastConnect = Time();
546
547 // Enable client connection to Skype
548 if (SendDBusMessage("NAME FACT++")!="OK")
549 return kStateDisconnected;
550
551 // Negotiate protocol version
552 if (SendDBusMessage("PROTOCOL 5")!="PROTOCOL 5")
553 return kStateDisconnected;
554
555 // Now we are connected: Minimize the window...
556 SendDBusMessageNB("MINIMIZE");
557
558 // ... and switch off the away message
559 SendDBusMessageNB("SET AUTOAWAY OFF");
560
561 // Check for unauthorized users and...
562 const string rc = SendDBusMessage("SEARCH USERSWAITINGMYAUTHORIZATION");
563
564 // ...authorize them
565 vector<string> users = Split(rc);
566
567 if (users[0]!="USERS")
568 {
569 Error("Unexpected answer received '"+rc+"'");
570 return kStateDisconnected;
571 }
572
573 for (auto it=users.begin()+1; it!=users.end(); it++)
574 {
575 const size_t p = it->length()-1;
576 if (it->at(p)==',')
577 it->erase(p);
578
579 SendDBusMessageNB("SET USER "+*it+" BUDDYSTATUS 2 "+fAuthorizationMsg);
580 }
581
582 return kStateConnected;
583 }
584
585 Time fLastPing;
586 int fNewState;
587
588 int Execute()
589 {
590 fNewState = -1;
591
592 static GMainContext *context = g_main_loop_get_context(fLoop);
593 g_main_context_iteration(context, FALSE);
594
595 if (fNewState>0)
596 return fNewState;
597
598 const Time now;
599
600 if (GetCurrentState()>kStateDisconnected)
601 {
602 if (now-fLastPing>boost::posix_time::seconds(15))
603 {
604 if (SendDBusMessage("PING", false)!="PONG")
605 return kStateDisconnected;
606
607 fLastPing = now;
608 }
609
610 return GetCurrentState();
611 }
612
613 if (now-fLastConnect>boost::posix_time::minutes(1))
614 return HandleConnect();
615
616 return GetCurrentState();
617 }
618
619public:
620 SkypeClient(ostream &lout) : StateMachineDim(lout, "SKYPE"),
621 fLastConnect(Time()-boost::posix_time::minutes(5)), fLoop(0)
622 {
623 AddStateName(kStateDisconnected, "Disonnected", "");
624 AddStateName(kStateConnected, "Connected", "");
625
626 AddEvent("MSG", "C", kStateConnected)
627 (bind(&SkypeClient::HandleMsg, this, placeholders::_1))
628 ("|msg[string]:message to be distributed");
629
630 AddEvent("RAW", "C")
631 (bind(&SkypeClient::HandleRaw, this, placeholders::_1))
632 ("|msg[string]:send a raw message to the Skype API");
633
634 AddEvent("CALL", "", kStateConnected)
635 (bind(&SkypeClient::HandleCall, this))
636 ("");
637
638 AddEvent("CONNECT", kStateDisconnected)
639 (bind(&SkypeClient::HandleConnect, this))
640 ("");
641
642 fLoop = g_main_loop_new(NULL, FALSE);
643 }
644 ~SkypeClient()
645 {
646 g_main_loop_unref(fLoop);
647 }
648
649 int EvalOptions(Configuration &conf)
650 {
651 fUser = conf.Get<string>("user");
652 fAllowRaw = conf.Get<bool>("allow-raw");
653
654 // Get a connection to the session bus.
655 DBusError error;
656 dbus_error_init(&error);
657
658 fBus = dbus_bus_get(DBUS_BUS_SESSION, &error);
659 if (!fBus)
660 {
661 Error("dbus_bus_get failed: "+string(error.message));
662 dbus_error_free(&error);
663 return 1;
664 }
665
666 // Set up this connection to work in a GLib event loop.
667 dbus_connection_setup_with_g_main(fBus, NULL);
668
669 // Install notify handler to process Skype's notifications.
670 // The Skype-to-client method call.
671 DBusObjectPathVTable vtable;
672 vtable.message_function = SkypeClient::NotifyHandler;
673
674 // We will process messages with the object path "/com/Skype/Client".
675 const dbus_bool_t check =
676 dbus_connection_register_object_path(fBus, "/com/Skype/Client",
677 &vtable, this);
678 if (!check)
679 {
680 Error("dbus_connection_register_object_path failed.");
681 return 2;
682 }
683
684 return -1;
685 }
686
687 int Write(const Time &time, const string &txt, int severity=MessageImp::kMessage)
688 {
689 return MessageImp::Write(time, txt, severity);
690 }
691};
692
693const string SkypeClient::fAuthorizationMsg =
694 "This is an automatic client of the FACT project (www.fact-project.org). "
695 "If you haven't tried to get in contact with this bot, feel free to block it. "
696 "In case of problems or questions please contact system@fact-project.org.";
697
698
699// -------------------------------------------------------------------------------------
700
701void SetupConfiguration(Configuration &conf)
702{
703 const string n = conf.GetName()+".log";
704
705 po::options_description config("Skype client options");
706 config.add_options()
707 ("user", var<string>("www.fact-project.org"), "If a user is given only connection to a skype with this user are accepted.")
708 ("allow-raw", po_bool(false), "This allows sending raw messages to the SKype API (for debugging)")
709 ;
710
711 conf.AddOptions(config);
712}
713
714/*
715 Extract usage clause(s) [if any] for SYNOPSIS.
716 Translators: "Usage" and "or" here are patterns (regular expressions) which
717 are used to match the usage synopsis in program output. An example from cp
718 (GNU coreutils) which contains both strings:
719 Usage: cp [OPTION]... [-T] SOURCE DEST
720 or: cp [OPTION]... SOURCE... DIRECTORY
721 or: cp [OPTION]... -t DIRECTORY SOURCE...
722 */
723void PrintUsage()
724{
725 cout <<
726 "The skypeclient is a Dim to Skype interface.\n"
727 "\n"
728 "The default is that the program is started without user intercation. "
729 "All actions are supposed to arrive as DimCommands. Using the -c "
730 "option, a local shell can be initialized. With h or help a short "
731 "help message about the usuage can be brought to the screen.\n"
732 "\n"
733 "Usage: skypeclient [OPTIONS]\n"
734 " or: skypeclient [OPTIONS]\n";
735 cout << endl;
736}
737
738void PrintHelp()
739{
740 /* Additional help text which is printed after the configuration
741 options goes here */
742}
743
744
745#include "Main.h"
746
747int main(int argc, const char *argv[])
748{
749 Configuration conf(argv[0]);
750 conf.SetPrintUsage(PrintUsage);
751 Main::SetupConfiguration(conf);
752 SetupConfiguration(conf);
753
754 if (!conf.DoParse(argc, argv, PrintHelp))
755 return -1;
756
757 // No console access at all
758 if (!conf.Has("console"))
759 return Main::execute<LocalStream, SkypeClient>(conf);
760
761 if (conf.Get<int>("console")==0)
762 return Main::execute<LocalShell, SkypeClient>(conf);
763 else
764 return Main::execute<LocalConsole, SkypeClient>(conf);
765
766 return 0;
767}
Note: See TracBrowser for help on using the repository browser.