SMS listener and improves handler sintax
authorkibergus <kibergus@gmail.com>
Fri, 12 Mar 2010 23:24:33 +0000 (23:24 +0000)
committerkibergus <kibergus@gmail.com>
Fri, 12 Mar 2010 23:24:33 +0000 (23:24 +0000)
git-svn-id: file:///svnroot/ussd-widget/trunk@25 d197f4d6-dc93-42ad-8354-0da1f58e353f

ussd-widget/build_ussd-widget.py
ussd-widget/src/usr/lib/hildon-desktop/ussd-widget.py

index 5a95e81..5b6ce3d 100644 (file)
@@ -23,7 +23,7 @@ if __name__ == "__main__":
     p.description="Widget, that executes USSD query and displays response text\nThe main purpose is viewing your balance. In Russia all operators provide balace information via USSD queries and most part of contracts are prepaid. Ability to see your balance on desktop can be useful in such case.\nAnyway, you can configure widget to any other USSD query."
     p.author="Alexey Guseynov"
     p.mail="kibergus@gmail.com"
-    p.depends = "python2.5, ussd-common (>=0.0.7), python-hildondesktop (>=0.1.0-1maemo2), hildon-desktop-python-loader (>=0.1.0-1maemo2), python-gtk2, python-gobject, python-hildon, python-cairo"
+    p.depends = "python2.5, ussd-common (>=0.0.8), python-hildondesktop (>=0.1.0-1maemo2), hildon-desktop-python-loader (>=0.1.0-1maemo2), python-gtk2, python-gobject, python-hildon, python-cairo"
     p.section="user/desktop"
     p.icon = "./ussd-widget.png"
     p.arch="all"                #should be all for python, any for all arch
@@ -34,9 +34,9 @@ if __name__ == "__main__":
 #    p.postinstall="""#!/bin/sh
 #""" #Set here your post install script
 
-    version = "0.1.3"
+    version = "0.1.6"
     build = "0" 
-    changeloginformation = "Multiline regexps, language selected moved to the bottom and a warning added." 
+    changeloginformation = "SMS listener. Changed parser sintax." 
    
     dir_name = "src"   
 
index 2b1ab82..19a489e 100755 (executable)
@@ -11,7 +11,10 @@ import time
 import re
 import gettext
 import fcntl
-from subprocess import *
+import dbus
+import subprocess
+import gsmdecode
+from dbus.mainloop.glib import DBusGMainLoop
 
 try :
        t = gettext.translation('ussd-widget', '/usr/share/locale')
@@ -32,12 +35,14 @@ ussd_languages_localized = [_("German"), _("English"), _("Italian"), _("French")
 class USSD_Controller:
        def __init__( self, widget ) :
                self.widget = widget
-               # number, parser, chain, interval, regexp, width, execute_at_start, retry pattern, font, name, language, show_message_box, message_box_parser, additional arguments, regexp group 
-               self.default_config = ["", "", "", 0, "", 0, True, [], pango.FontDescription("Nokia Sans 18"), _("Click to update"), 15, False, "", "", 1]
+               # number, parser, chain, interval, regexp, width, execute_at_start, retry pattern, font, name, language, show_message_box, message_box_parser, additional arguments, regexp group, use SMS listener, SMS number, SMS regexp, SMS timeout
+               self.default_config = ["", "", "", 0, "", 0, True, [], pango.FontDescription("Nokia Sans 18"), _("Click to update"), 15, False, "", "", 1, False, "", "", 60]
                self.config = self.default_config
                self.timeout_version = 0
                self.retry_version = 0
                self.retry_state = 0
+               self.sms_counter = 0
+               self.sms_reply = ""
 
        def save_config( self ) :
                configname = os.getenv("HOME")+"/.ussdWidget.conf"
@@ -87,7 +92,11 @@ class USSD_Controller:
                fconfig.writelines(["#Show banner\n", "show_box="+str(self.config[11]), "\n"])
                fconfig.writelines(["#USSD reply language\n", "language="+str(self.config[10]), "\n"])
                fconfig.writelines(["#Additional ussdquery.py arguments\n", "args="+self.config[13], "\n"])
-               fconfig.writelines(["#Regexp matching group\n", "reggroup="+self.config[14], "\n"])
+               fconfig.writelines(["#Regexp matching group\n", "reggroup="+str(self.config[14]), "\n"])
+               fconfig.writelines(["#Use SMS listener\n", "listen_sms="+str(self.config[15]), "\n"])
+               fconfig.writelines(["#Number,from which SMS should come\n", "sms_number="+self.config[16], "\n"])
+               fconfig.writelines(["#SMS RegExp pattern\n", "sms_regexp="+self.config[17], "\n"])
+               fconfig.writelines(["#SMS timeout\n", "sms_timeout="+str(self.config[18]), "\n"])
                fconfig.close()
 
                fcntl.flock(lockf,fcntl.LOCK_UN)
@@ -212,6 +221,22 @@ class USSD_Controller:
                                                error = True
                                                print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
                                                continue
+                               elif line[0] == "listen_sms" :
+                                       if line[1].strip() == "True" :
+                                               self.config[15] = True
+                                       else :
+                                               self.config[15] = False
+                               elif line[0] == "sms_number" :
+                                       self.config[16] = line[1].strip()
+                               elif line[0] == "sms_regexp" :
+                                       self.config[17] = line[1].strip()
+                               elif line[0] == "sms_timeout" :
+                                       try:
+                                               self.config[18] = int(line[1].strip())
+                                       except:
+                                               error = True
+                                               print _("Error reading config on line %(line)d. Integer expected.")%{"line":i}
+                                               continue
                                else :
                                        error = True
                                        print _("Error reading config on line %(line)d. Unexpected variable: ")%{"line":i}+line[0]
@@ -248,6 +273,10 @@ class USSD_Controller:
                        if not check_number(dialog.ussdNumber.get_text()):
                                dialog.on_error_ussd_number()
                                continue
+               
+                       if not check_number(dialog.sms_number.get_text()):
+                               dialog.on_error_sms_number()
+                               continue
 
                        # Parse retry pattern
                        retry = dialog.retryEdit.get_text().strip()
@@ -284,7 +313,11 @@ class USSD_Controller:
                        dialog.show_box.get_active(),
                        dialog.b_parser.get_text(),
                        dialog.args.get_text(),
-                       dialog.reggroup.get_value()
+                       dialog.reggroup.get_value(),
+                       dialog.sms_listener.get_active(),
+                       dialog.sms_number.get_text(),
+                       dialog.sms_regexp.get_text(),
+                       dialog.sms_timeout.get_value() 
                ]
 
                widget.set_bg_color(dialog.bg_color)
@@ -303,6 +336,21 @@ class USSD_Controller:
                        self.widget.set_text(_("Click to update"))
                return
 
+       def handle_sms(self, pdumsg, msgcenter, message, sendernumber):
+               # Timeout was recieved first
+               if self.sms_ready:
+                       return
+
+               if self.config[16] == "" or self.config[16] == sendernumber:
+                       pdu = gsmdecode.decode_pdu (pdumsg)
+                       if pdu != None :
+                               self.sms_reply += pdu['user_data']
+                               if not pdu['part']:
+                                       if self.config[17] == "" or re.search( self.config[17], message, re.MULTILINE | re.UNICODE ):
+                                               self.sms_ready = True
+                                               self.sms_signal.remove()
+                                               self.process_reply()
+
        def callback_ussd_data( self, source, condition ):
                if condition == gobject.IO_IN or condition == gobject.IO_PRI :
                        data = source.read( )
@@ -315,6 +363,7 @@ class USSD_Controller:
 
                elif condition == gobject.IO_HUP or condition == gobject.IO_ERR :
                        self.cb_ready = 1
+                       self.ussd_ready = True
                        self.process_reply()
                        return False
 
@@ -329,7 +378,7 @@ class USSD_Controller:
        def call_external_script( self, ussd_code, language ):
                self.cb_ready = 0
                self.cb_reply = "";
-               p = Popen(['/usr/bin/ussdquery.py', ussd_code, "-l", ussd_languages[language]] + smart_split_string(self.config[13],"%"), stdout=PIPE)
+               p = subprocess.Popen(['/usr/bin/ussdquery.py', ussd_code, "-l", ussd_languages[language]] + smart_split_string(self.config[13],"%","&"), stdout=subprocess.PIPE)
                gobject.io_add_watch( p.stdout, gobject.IO_IN | gobject.IO_PRI | gobject.IO_HUP | gobject.IO_ERR , self.callback_ussd_data )
 
        def ussd_renew(self, widget, event):
@@ -337,6 +386,19 @@ class USSD_Controller:
                        if self.config :
                                widget.processing = 1
                                widget.set_text(_("Processing"), 0)
+
+                               self.ussd_ready = False
+                               self.sms_ready = False
+                               self.sms_reply = ""
+
+                               if self.config[15]:
+                                       self.sms_counter += 1
+                                       self.retry_timer = gobject.timeout_add (1000*self.config[18], self.sms_timeout, self.sms_counter)
+                                       
+                                       DBusGMainLoop(set_as_default=True)
+                                       self.bus = dbus.SystemBus()
+                                       self.sms_signal = self.bus.add_signal_receiver(self.handle_sms, path='/com/nokia/phone/SMS',   dbus_interface='Phone.SMS', signal_name='IncomingSegment')
+
                                self.call_external_script( self.config[0], self.config[10] )
                        else :
                                widget.processing = 0
@@ -344,9 +406,13 @@ class USSD_Controller:
                                widget.set_text(_("No config"), 0)
 
        def process_reply( self ):
+               if not self.ussd_ready or not self.sms_ready and self.config[15]:
+                       return
+
                reply = self.cb_reply.strip()
+               sms_reply = self.sms_reply.strip()
                
-               if reply == "" :
+               if reply == "" or self.config[15] and sms_reply == "" :
                        self.widget.error = 1
                        self.widget.set_text (_("Error"), 5000)
                        if self.retry_state == len(self.config[7]):
@@ -358,34 +424,48 @@ class USSD_Controller:
                else :
                        self.widget.error = 0
                        # Apply regexp
+                       reresult1 = reresult2 = None
                        if self.config[4] != "":
-                               try :
-                                       reply = re.search( self.config[4], reply, re.MULTILINE | re.UNICODE ).group( self.config[14] )
-                               except Exception, e:
-                                       self.widget.error = 1
-                                       reply = _("Regexp Error: ") + str( e ) + "\n" + reply
+                               reresult1 = re.search( self.config[4], reply, re.MULTILINE | re.UNICODE )
+                       if self.config[17] != "":
+                               reresult2 = re.search( self.config[17], sms_reply, re.MULTILINE | re.UNICODE )
                        w_reply = b_reply = reply
                        if self.widget.error == 0:
                                # Pass to box parser
                                if self.config[12] != "" and self.config[11]:
                                        try:
-                                               p = Popen(smart_split_string(self.config[12], reply), stdout=PIPE)
+                                               p = subprocess.Popen(smart_split_string(self.config[12], reply, sms_reply, reresult1, reresult2), stdout=subprocess.PIPE)
                                                b_reply = p.communicate()[0].strip()
                                        except Exception, e:
                                                print _("Couldn't exec banner parser:")+str(e)
                                                self.widget.error = 1
+                               else:
+                                       if self.config[4] != "":
+                                               try :
+                                                       b_reply = reresult1.group( self.config[14] )
+                                               except Exception, e:
+                                                       self.widget.error = 1
+                                                       b_reply = _("Group not found: \n") + reply
+                                        
                                # Pass to widget parser
                                if self.config[1] != "":
                                        try:
-                                               p = Popen(smart_split_string(self.config[1], reply), stdout=PIPE)
+                                               p = subprocess.Popen(smart_split_string(self.config[1], reply, sms_reply, reresult1, reresult2), stdout=subprocess.PIPE)
                                                w_reply = p.communicate()[0].strip()
                                        except Exception, e:
                                                print _("Couldn't exec widget parser:")+str(e)
                                                self.widget.error = 1
+                               else:
+                                       if self.config[4] != "":
+                                               try :
+                                                       w_reply = reresult1.group( self.config[14] )
+                                               except Exception, e:
+                                                       self.widget.error = 1
+                                                       w_reply = _("Group not found: \n") + reply
                                # Pass to chain
                                if self.config[2] != "":
                                        try:
-                                               p = Popen(smart_split_string(self.config[2], reply))
+                                               p = subprocess.Popen(smart_split_string(self.config[2], reply, sms_reply, reresult1, reresult2))
                                        except Exception, e:
                                                print _("Couldn't exec chain:")+str(e)
                                                self.widget.error = 1
@@ -396,6 +476,14 @@ class USSD_Controller:
                        self.widget.set_text(w_reply)
                self.widget.processing = 0
 
+       def sms_timeout(self, version):
+               if version == self.sms_counter :
+                       self.sms_reply = ""
+                       self.sms_ready = True
+                       self.sms_signal.remove()
+                       self.process_reply()
+               return False
+
        def timed_renew(self, version):
                if version < self.timeout_version :
                        return False
@@ -455,7 +543,7 @@ class UssdConfigDialog(gtk.Dialog):
                self.args.set_text(config[13])
                self.reggroup = hildon.NumberEditor(0, 255)
                self.reggroup.set_value(config[14])
-
+               
                selector = hildon.TouchSelector(text=True)
                for i in ussd_languages_localized:
                        selector.append_text(i)
@@ -468,6 +556,7 @@ class UssdConfigDialog(gtk.Dialog):
                self.wname = hildon.Entry(gtk.HILDON_SIZE_AUTO)
                self.wname.set_text(config[9])
                self.show_box = gtk.CheckButton(_("Enable banner. Parser:"))
+               self.show_box.connect("toggled", self.show_box_changed)
                self.show_box.set_active(config[11])
 
                text = ""
@@ -550,7 +639,7 @@ class UssdConfigDialog(gtk.Dialog):
                b_parserBox.add(bphelp)
                vbox.add(b_parserBox)
                vbox.add(self.b_parser)
-
+               
                chainBox = gtk.HBox()
                chainLabel = gtk.Label(_("Chain"))
                chainLabel.set_alignment(0,0.6)
@@ -623,11 +712,54 @@ class UssdConfigDialog(gtk.Dialog):
                viewBox.add(self.textColorButton)
                viewBox.add(self.colorButton)
                vbox.add(viewBox)
+       
+               self.sms_box = gtk.VBox()       
+               self.sms_listener = gtk.CheckButton(_("Enable SMS listener."))
+               self.sms_listener.connect("toggled", self.sms_box_changed)
+               self.sms_listener.set_active(config[15])
+               vbox.add (self.sms_listener)
+
+               self.sms_number = hildon.Entry(gtk.HILDON_SIZE_AUTO)
+               self.sms_number.set_text(config[16])
+               smsNumberBox = gtk.HBox()
+               smsNumberLabel = gtk.Label(_("SMS number"))
+               smsNumberLabel.set_alignment(0,0.6)
+               smsNumberLabel.set_size_request(100, -1)
+               self.sms_number.set_size_request(200, -1)
+               smsNumberBox.add(smsNumberLabel)
+               smsNumberBox.add(self.sms_number)
+               self.sms_box.add(smsNumberBox)
                
+               smsRegexpLabel = gtk.Label(_("Regular expression"))
+               smsRegexpLabel.set_alignment(0,0.6)
+               self.sms_box.add(smsRegexpLabel)
+               
+               self.sms_regexp = hildon.Entry(gtk.HILDON_SIZE_AUTO)
+               self.sms_regexp.set_text(config[17])
+               self.sms_box.add(self.sms_regexp)
+
+               self.sms_timeout = hildon.NumberEditor(0, 9999)
+               self.sms_timeout.set_value(config[18])
+               sms_timeout_box = gtk.HBox()
+               timeoutLabel = gtk.Label(_("Timeout"))
+               timeoutLabel.set_alignment(0,0.6)
+               secondsLabel = gtk.Label(_("seconds"))
+               timeoutLabel.set_size_request(140, -1)
+               self.sms_timeout.set_size_request(50, -1)
+               secondsLabel.set_size_request(40, -1)
+               sms_timeout_box.add(timeoutLabel)
+               sms_timeout_box.add(self.sms_timeout)
+               sms_timeout_box.add(secondsLabel)
+               self.sms_box.add(sms_timeout_box)
+               
+               vbox.add(self.sms_box)
+
                vbox.add(gtk.Label(_("DO NOT CHANGE. Unspecified is what you want.")))
                vbox.add(self.language)
 
                self.show_all()
+               self.show_box_changed(None)
+               self.sms_box_changed(None)
                self.parent
 
        #============ Dialog helper functions =============
@@ -701,36 +833,85 @@ class UssdConfigDialog(gtk.Dialog):
                self.font = pango.FontDescription (fontDialog.get_font_name())
                fontDialog.destroy()
 
-def smart_split_string (str, query) :
+       def show_box_changed (self, event):
+               if self.show_box.get_active():
+                       self.b_parser.show()
+               else:
+                       self.b_parser.hide()
+
+       def sms_box_changed (self, event):
+               if self.sms_listener.get_active():
+                       self.sms_box.show()
+               else:
+                       self.sms_box.hide()
+
+def smart_split_string (str, reply1, reply2, reres1 = None, reres2 = None) :
        word = ""
        result = []
        # Is simbol backslashed?
        bs = 0
        # Quotes: 1 - ", 2 - ', 0 - no quotes
        qs = 0
+       # Read out number
+       num = -1
+       # Current substitution simbol
+       subst = ''
+
        for i in range(len(str)) :
-         if bs == 0 and (str[i] == '"' and qs == 1 or str[i] == "'" and qs == 2) :
-                 qs = 0
-         elif bs == 0 and qs == 0 and (str[i] == '"' or str[i] == "'") :
-                 if str[i] == '"':
-                         qs = 1
-                 else :
-                         qs = 2
-         elif bs == 0 and str[i] == '\\' :
-                 bs = 1
-         elif bs == 0 and str[i] == '%' :
-                 word += query
-                 ws = 0
-         else :
-                 if bs == 1 and str[i] != '\\' and str[i] != '"' and str[i] != "'" :
-                         word += "\\"
-                 if qs == 0 and (str[i] == " " or str[i] == "\t") :
-                         if word != "" :
-                                 result.append(word)
-                                 word = ""
-                 else :
-                         word += str[i]
-                 bs = 0 
+               if num>= 0:
+                       if str[i] in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] :
+                               num *= 10
+                               num += int(str[i])
+                               continue
+                       else:
+                               if subst == '&':
+                                       if reres2 != None and num != 0:
+                                               word += reres2.group(num)
+                                       else:
+                                               word += reply2
+                               else:
+                                       if reres1 != None and num != 0:
+                                               word += reres1.group(num)
+                                       else:
+                                               word += reply1
+                               ws = 0
+                               num = -1
+                               # Delete backslash if it delimites usual numbers from % or &
+                               if str[i] == '\\' and i < len(str)-1 and str[i+1] in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] :
+                                       continue
+               if bs == 0 and (str[i] == '"' and qs == 1 or str[i] == "'" and qs == 2) :
+                       qs = 0
+               elif bs == 0 and qs == 0 and (str[i] == '"' or str[i] == "'") :
+                       if str[i] == '"':
+                               qs = 1
+                       else :
+                               qs = 2
+               elif bs == 0 and str[i] == '\\' :
+                       bs = 1
+               elif bs == 0 and (str[i] == '%' or str[i] == '&') :
+                       subst = str[i]
+                       num = 0
+               else :
+                       if bs == 1 and str[i] != '\\' and str[i] != '"' and str[i] != "'" :
+                               word += "\\"
+                       if qs == 0 and (str[i] == " " or str[i] == "\t") :
+                               if word != "" :
+                                       result.append(word)
+                                       word = ""
+                       else :
+                               word += str[i]
+                       bs = 0 
+       
+       if subst == '&':
+               if reres2 != None and num != 0 and num != -1:
+                       word += reres2.group(num)
+               else:
+                       word += reply2
+       elif subst == '%':
+               if reres1 != None and num != 0 and num != -1:
+                       word += reres1.group(num)
+               else:
+                       word += reply1
        if word != "" :
                result.append(word)
        return result