source: skype/skyped.py @ 0fbeef1

Last change on this file since 0fbeef1 was 0fbeef1, checked in by Miklos Vajna <vmiklos@…>, at 2008-04-20T20:40:32Z

skyped: use gobject.timeout_add() to make it more responsive

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