source: skype/skyped.py @ eeeb30e

Last change on this file since eeeb30e was eeeb30e, checked in by Miklos Vajna <vmiklos@…>, at 2008-01-12T21:18:21Z

skyped: catch KeyboardInterrupts everywhere

  • Property mode set to 100644
File size: 7.4 KB
Line 
1#!/usr/bin/env python
2#
3#   skyped.py
4
5#   Copyright (c) 2007, 2008 by Miklos Vajna <vmiklos@frugalware.org>
6#
7#   It uses several code from a very basic python CLI interface, available at:
8#
9#   http://forum.skype.com/index.php?showtopic=42640
10
11#   This program is free software; you can redistribute it and/or modify
12#   it under the terms of the GNU General Public License as published by
13#   the Free Software Foundation; either version 2 of the License, or
14#   (at your option) any later version.
15#
16#   This program is distributed in the hope that it will be useful,
17#   but WITHOUT ANY WARRANTY; without even the implied warranty of
18#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19#   GNU General Public License for more details.
20
21#   You should have received a copy of the GNU General Public License
22#   along with this program; if not, write to the Free Software
23#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
24#   USA.
25#
26
27import sys
28import os
29import signal
30import locale
31import time
32import gobject
33import socket
34import getopt
35import Skype4Py
36import threading
37import sha
38from ConfigParser import ConfigParser
39from OpenSSL import SSL
40from traceback import print_exception
41#from exceptions import KeyboardInterrupt
42
43__version__ = "0.1.1"
44
45SKYPE_SERVICE = 'com.Skype.API'
46CLIENT_NAME = 'SkypeApiPythonShell'
47
48def eh(type, value, tb):
49        if type == KeyboardInterrupt:
50                sys.exit("Exiting.")
51        print_exception(type, value, tb)
52        sys.exit(1)
53
54sys.excepthook = eh
55
56def input_handler(fd, io_condition):
57        global options
58        if options.buf:
59                for i in options.buf:
60                        skype.send(i.strip())
61                options.buf = None
62        else:
63                input = fd.recv(1024)
64                for i in input.split("\n"):
65                        skype.send(i.strip())
66                return True
67
68def idle_handler(skype):
69        try:
70                skype.skype.SendCommand(skype.skype.Command(-1, "PING"))
71        except Skype4Py.SkypeAPIError, s:
72                dprint("Warning, pinging Skype failed (%s)." % (s))
73                time.sleep(2)
74        return True
75
76def server(host, port):
77        global options
78
79        ctx = SSL.Context(SSL.TLSv1_METHOD)
80        ctx.use_privatekey_file(options.config.sslkey)
81        ctx.use_certificate_file(options.config.sslcert)
82        sock = SSL.Connection(ctx, socket.socket())
83        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
84        sock.bind((host, port))
85        sock.listen(1)
86        gobject.io_add_watch(sock, gobject.IO_IN, listener)
87
88def listener(sock, *args):
89        global options
90        options.conn, addr = sock.accept()
91        ret = 0
92        line = options.conn.recv(1024)
93        if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username:
94                ret += 1
95        line = options.conn.recv(1024)
96        if line.startswith("PASSWORD") and sha.sha(line.split(' ')[1].strip()).hexdigest() == options.config.password:
97                ret += 1
98        if ret == 2:
99                dprint("Username and password OK.")
100                options.conn.send("PASSWORD OK\n")
101                gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler)
102                return True
103        else:
104                dprint("Username and/or password WRONG.")
105                options.conn.send("PASSWORD KO\n")
106                return False
107
108def dprint(msg):
109        global options
110
111        if options.debug:
112                print msg
113
114class SkypeApi():
115        def __init__(self):
116                self.skype = Skype4Py.Skype()
117                self.skype.OnNotify = self.recv
118                self.skype.Attach()
119
120        def recv(self, msg_text):
121                global options
122                if msg_text == "PONG":
123                        return
124                if "\n" in msg_text:
125                        # crappy skype prefixes only the first line for
126                        # multiline messages so we need to do so for the other
127                        # lines, too. this is something like:
128                        # 'CHATMESSAGE id BODY first line\nsecond line' ->
129                        # 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line'
130                        prefix = " ".join(msg_text.split(" ")[:3])
131                        msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")]
132                else:
133                        msg_text = [msg_text]
134                for i in msg_text:
135                        # use utf-8 here to solve the following problem:
136                        # people use env vars like LC_ALL=en_US (latin1) then
137                        # they complain about why can't they receive latin2
138                        # messages.. so here it is: always use utf-8 then
139                        # everybody will be happy
140                        e = i.encode('UTF-8')
141                        dprint('<< ' + e)
142                        if options.conn:
143                                try:
144                                        options.conn.send(e + "\n")
145                                except IOError, s:
146                                        dprint("Warning, sending '%s' failed (%s)." % (e, s))
147
148        def send(self, msg_text):
149                if not len(msg_text):
150                        return
151                e = msg_text.decode(locale.getdefaultlocale()[1])
152                dprint('>> ' + e)
153                try:
154                        c = self.skype.Command(e, Block=True)
155                        self.skype.SendCommand(c)
156                        self.recv(c.Reply)
157                except Skype4Py.SkypeError:
158                        pass
159                except Skype4Py.SkypeAPIError, s:
160                        dprint("Warning, sending '%s' failed (%s)." % (e, s))
161
162class Options:
163        def __init__(self):
164                self.cfgpath = "/usr/local/etc/skyped/skyped.conf"
165                self.daemon = True
166                self.debug = False
167                self.help = False
168                self.host = "0.0.0.0"
169                self.port = 2727
170                self.version = False
171                # well, this is a bit hackish. we store the socket of the last connected client
172                # here and notify it. maybe later notify all connected clients?
173                self.conn = None
174                # this will be read first by the input handler
175                self.buf = None
176
177
178        def usage(self, ret):
179                print """Usage: skyped [OPTION]...
180
181skyped is a daemon that acts as a tcp server on top of a Skype instance.
182
183Options:
184        -c      --config        path to configuration file (default: %s)
185        -d      --debug         enable debug messages
186        -h      --help          this help
187        -H      --host          set the tcp host (default: %s)
188        -n      --nofork        don't run as daemon in the background
189        -p      --port          set the tcp port (default: %d)
190        -v      --version       display version information""" % (self.cfgpath, self.host, self.port)
191                sys.exit(ret)
192
193if __name__=='__main__':
194        options = Options()
195        try:
196                opts, args = getopt.getopt(sys.argv[1:], "c:dhH:np:v", ["config=", "daemon", "help", "host=", "nofork", "port=", "version"])
197        except getopt.GetoptError:
198                options.usage(1)
199        for opt, arg in opts:
200                if opt in ("-c", "--config"):
201                        options.cfgpath = arg
202                elif opt in ("-d", "--debug"):
203                        options.debug = True
204                elif opt in ("-h", "--help"):
205                        options.help = True
206                elif opt in ("-H", "--host"):
207                        options.host = arg
208                elif opt in ("-n", "--nofork"):
209                        options.daemon = False
210                elif opt in ("-p", "--port"):
211                        options.port = arg
212                elif opt in ("-v", "--version"):
213                        options.version = True
214        if options.help:
215                options.usage(0)
216        elif options.version:
217                print "skyped %s" % __version__
218                sys.exit(0)
219        # parse our config
220        if not os.path.exists(options.cfgpath):
221                print "Can't find configuration file at '%s'." % options.cfgpath
222                print "Use the -c option to specify an alternate one."
223                sys.exit(1)
224        options.config = ConfigParser()
225        options.config.read(options.cfgpath)
226        options.config.username = options.config.get('skyped', 'username').split('#')[0]
227        options.config.password = options.config.get('skyped', 'password').split('#')[0]
228        options.config.sslkey = options.config.get('skyped', 'key').split('#')[0]
229        options.config.sslcert = options.config.get('skyped', 'cert').split('#')[0]
230        dprint("Parsing config file '%s' done, username is '%s'." % (options.cfgpath, options.config.username))
231        if options.daemon:
232                pid = os.fork()
233                if pid == 0:
234                        nullin = file('/dev/null', 'r')
235                        nullout = file('/dev/null', 'w')
236                        os.dup2(nullin.fileno(), sys.stdin.fileno())
237                        os.dup2(nullout.fileno(), sys.stdout.fileno())
238                        os.dup2(nullout.fileno(), sys.stderr.fileno())
239                else:
240                        print 'skyped is started on port %s, pid: %d' % (options.port, pid)
241                        sys.exit(0)
242        server(options.host, options.port)
243        try:
244                skype = SkypeApi()
245        except Skype4Py.SkypeAPIError, s:
246                sys.exit("%s. Are you sure you have started Skype?" % s)
247        gobject.idle_add(idle_handler, skype)
248        gobject.MainLoop().run()
Note: See TracBrowser for help on using the repository browser.