source: skype/skyped.py @ afeb517

Last change on this file since afeb517 was 239b036, checked in by Miklos Vajna <vmiklos@…>, at 2008-02-29T00:42:46Z

skyped: don't exit when bitlbee disconnects

  • 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                time.sleep(2)
73        return True
74
75def server(host, port):
76        global options
77
78        ctx = SSL.Context(SSL.TLSv1_METHOD)
79        ctx.use_privatekey_file(options.config.sslkey)
80        ctx.use_certificate_file(options.config.sslcert)
81        sock = SSL.Connection(ctx, socket.socket())
82        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
83        sock.bind((host, port))
84        sock.listen(1)
85        gobject.io_add_watch(sock, gobject.IO_IN, listener)
86
87def listener(sock, *args):
88        global options
89        options.conn, addr = sock.accept()
90        ret = 0
91        line = options.conn.recv(1024)
92        if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username:
93                ret += 1
94        line = options.conn.recv(1024)
95        if line.startswith("PASSWORD") and sha.sha(line.split(' ')[1].strip()).hexdigest() == options.config.password:
96                ret += 1
97        if ret == 2:
98                dprint("Username and password OK.")
99                options.conn.send("PASSWORD OK\n")
100                gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler)
101                return True
102        else:
103                dprint("Username and/or password WRONG.")
104                options.conn.send("PASSWORD KO\n")
105                return False
106
107def dprint(msg):
108        global options
109
110        if options.debug:
111                print msg
112
113class SkypeApi():
114        def __init__(self):
115                self.skype = Skype4Py.Skype()
116                self.skype.OnNotify = self.recv
117                self.skype.Client.Start()
118
119        def recv(self, msg_text):
120                global options
121                if msg_text == "PONG":
122                        return
123                if "\n" in msg_text:
124                        # crappy skype prefixes only the first line for
125                        # multiline messages so we need to do so for the other
126                        # lines, too. this is something like:
127                        # 'CHATMESSAGE id BODY first line\nsecond line' ->
128                        # 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line'
129                        prefix = " ".join(msg_text.split(" ")[:3])
130                        msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")]
131                else:
132                        msg_text = [msg_text]
133                for i in msg_text:
134                        # use utf-8 here to solve the following problem:
135                        # people use env vars like LC_ALL=en_US (latin1) then
136                        # they complain about why can't they receive latin2
137                        # messages.. so here it is: always use utf-8 then
138                        # everybody will be happy
139                        e = i.encode('UTF-8')
140                        dprint('<< ' + e)
141                        if options.conn:
142                                try:
143                                        options.conn.send(e + "\n")
144                                except IOError, s:
145                                        dprint("Warning, sending '%s' failed (%s)." % (e, s))
146
147        def send(self, msg_text):
148                if not len(msg_text):
149                        return
150                e = msg_text.decode(locale.getdefaultlocale()[1])
151                dprint('>> ' + e)
152                try:
153                        c = self.skype.Command(e, Block=True)
154                        self.skype.SendCommand(c)
155                        self.recv(c.Reply)
156                except Skype4Py.SkypeError:
157                        pass
158                except Skype4Py.SkypeAPIError, s:
159                        dprint("Warning, sending '%s' failed (%s)." % (e, s))
160
161class Options:
162        def __init__(self):
163                self.cfgpath = "/usr/local/etc/skyped/skyped.conf"
164                self.daemon = True
165                self.debug = False
166                self.help = False
167                self.host = "0.0.0.0"
168                self.port = 2727
169                self.version = False
170                # well, this is a bit hackish. we store the socket of the last connected client
171                # here and notify it. maybe later notify all connected clients?
172                self.conn = None
173                # this will be read first by the input handler
174                self.buf = None
175
176
177        def usage(self, ret):
178                print """Usage: skyped [OPTION]...
179
180skyped is a daemon that acts as a tcp server on top of a Skype instance.
181
182Options:
183        -c      --config        path to configuration file (default: %s)
184        -d      --debug         enable debug messages
185        -h      --help          this help
186        -H      --host          set the tcp host (default: %s)
187        -n      --nofork        don't run as daemon in the background
188        -p      --port          set the tcp port (default: %d)
189        -v      --version       display version information""" % (self.cfgpath, self.host, self.port)
190                sys.exit(ret)
191
192if __name__=='__main__':
193        options = Options()
194        try:
195                opts, args = getopt.getopt(sys.argv[1:], "c:dhH:np:v", ["config=", "daemon", "help", "host=", "nofork", "port=", "version"])
196        except getopt.GetoptError:
197                options.usage(1)
198        for opt, arg in opts:
199                if opt in ("-c", "--config"):
200                        options.cfgpath = arg
201                elif opt in ("-d", "--debug"):
202                        options.debug = True
203                elif opt in ("-h", "--help"):
204                        options.help = True
205                elif opt in ("-H", "--host"):
206                        options.host = arg
207                elif opt in ("-n", "--nofork"):
208                        options.daemon = False
209                elif opt in ("-p", "--port"):
210                        options.port = arg
211                elif opt in ("-v", "--version"):
212                        options.version = True
213        if options.help:
214                options.usage(0)
215        elif options.version:
216                print "skyped %s" % __version__
217                sys.exit(0)
218        # parse our config
219        if not os.path.exists(options.cfgpath):
220                print "Can't find configuration file at '%s'." % options.cfgpath
221                print "Use the -c option to specify an alternate one."
222                sys.exit(1)
223        options.config = ConfigParser()
224        options.config.read(options.cfgpath)
225        options.config.username = options.config.get('skyped', 'username').split('#')[0]
226        options.config.password = options.config.get('skyped', 'password').split('#')[0]
227        options.config.sslkey = options.config.get('skyped', 'key').split('#')[0]
228        options.config.sslcert = options.config.get('skyped', 'cert').split('#')[0]
229        dprint("Parsing config file '%s' done, username is '%s'." % (options.cfgpath, options.config.username))
230        if options.daemon:
231                pid = os.fork()
232                if pid == 0:
233                        nullin = file('/dev/null', 'r')
234                        nullout = file('/dev/null', 'w')
235                        os.dup2(nullin.fileno(), sys.stdin.fileno())
236                        os.dup2(nullout.fileno(), sys.stdout.fileno())
237                        os.dup2(nullout.fileno(), sys.stderr.fileno())
238                else:
239                        print 'skyped is started on port %s, pid: %d' % (options.port, pid)
240                        sys.exit(0)
241        server(options.host, options.port)
242        try:
243                skype = SkypeApi()
244        except Skype4Py.SkypeAPIError, s:
245                sys.exit("%s. Are you sure you have started Skype?" % s)
246        gobject.idle_add(idle_handler, skype)
247        gobject.MainLoop().run()
Note: See TracBrowser for help on using the repository browser.