Cleaning up the directory structure. This is a code breaking change but it is meant...
[gc-dialer] / gc_dialer / gc_dialer.py
1 #!/usr/bin/python 
2
3 # Grandcentral Dialer
4 # Python front-end to a wget script to use grandcentral.com to place outbound VOIP calls.
5 # (C) 2008 Mark Bergman
6 # bergman@merctech.com
7
8 import sys
9 import os
10 import time
11 import re
12 try:
13         import pygtk
14         pygtk.require("2.0")
15 except:
16         pass
17 try:
18         import gtk
19         import gtk.glade
20 except:
21         sys.exit(1)
22
23 histfile=os.path.expanduser("~")
24 histfile=os.path.join(histfile,".gcdialerhist") # Use the native OS file separator
25 liststore = gtk.ListStore(str)
26
27 class GCDialer:
28         _wgetOKstrRe    = re.compile("This may take a few seconds", re.M)       # string from Grandcentral.com on successful dial 
29         _validateRe     = re.compile("^[0-9]{7,}$")
30
31         _wgetoutput     = "/tmp/gc_dialer.output"       # results from wget command
32         _cookiefile     = os.path.join(os.path.expanduser("~"),".mozilla/microb/cookies.txt")   # file with browser cookies
33         _wgetcmd        = "wget -nv -O %s --load-cookie=\"%s\" --referer=http://www.grandcentral.com/mobile/messages http://www.grandcentral.com/mobile/calls/click_to_call?destno=%s"
34
35         def __init__(self):
36                 self._msg = ""
37                 if ( os.path.isfile(GCDialer._cookiefile) == False ) :
38                         self._msg = 'Error: Failed to locate a file with saved browser cookies at \"' + cookiefile + '\".\n\tPlease use the web browser on your tablet to connect to www.grandcentral.com and then re-run Grandcentral Dialer.'
39
40         def validate(self,number):
41                 return GCDialer._validateRe.match(number) != None
42
43         def dial(self,number):
44                 self._msg = ""
45                 if self.validate(number) == False:
46                         self._msg = "Invalid number format %s" % (number)
47                         return False
48
49                 # Remove any existing output file...
50                 if os.path.isfile(GCDialer._wgetoutput) :
51                         os.unlink(GCDialer._wgetoutput)
52                 child_stdout, child_stdin, child_stderr = os.popen3(GCDialer._wgetcmd % (GCDialer._wgetoutput, GCDialer._cookiefile, number))
53                 stderr=child_stderr.read()
54
55                 child_stdout.close()
56                 child_stderr.close()
57                 child_stdin.close()
58
59                 try:
60                         wgetresults = open(GCDialer._wgetoutput, 'r' )
61                 except IOError:
62                         self._msg = 'IOError: No /tmp/gc_dialer.output file...dial attempt failed\n\tThis probably means that there is no active internet connection, or that\nthe site www.grandcentral.com is inacessible.'
63                         return False
64                 
65                 data = wgetresults.read()
66                 wgetresults.close()
67
68                 if GCDialer._wgetOKstrRe.search(data) != None:
69                         return True
70                 else:
71                         self._msg = 'Error: Failed to login to www.grandcentral.com.\n\tThis probably means that there is no saved cookie for that site.\n\tPlease use the web browser on your tablet to connect to www.grandcentral.com and then re-run Grandcentral Dialer.'
72                         return False
73
74
75 def load_history_list(histfile,liststore):
76         # read the history list, load it into the liststore variable for later
77         # assignment to the combobox menu
78
79         # clear out existing entries
80         dialhist = []
81         liststore.clear()
82         if os.path.isfile(histfile) :
83                 histFH = open(histfile,"r")
84                 for line in histFH.readlines() :
85                         fields = line.split()   # split the input lines on whitespace
86                         number=fields[0]                #...save only the first field (the phone number)
87                         search4num=re.compile('^' + number + '$')
88                         newnumber=True  # set a flag that the current phone number is not on the history menu
89                         for num in dialhist :
90                                 if re.match(search4num,num):
91                                         # the number is already in the drop-down menu list...set the
92                                         # flag and bail out
93                                         newnumber = False
94                                         break
95                         if newnumber == True :
96                                 dialhist.append(number) # append the number to the history list
97         
98                 histlen=len(dialhist)
99                 if histlen > 10 :
100                         dialhist=dialhist[histlen - 10:histlen]         # keep only the last 10 entries
101                 dialhist.reverse()      # reverse the list, so that the most recent entry is now first
102         
103                 # Now, load the liststore with the entries, for later assignment to the Gtk.combobox menu
104                 for entry in dialhist :
105                         entry=makepretty(entry)
106                         liststore.append([entry])
107         # else :
108         #        print "The history file " + histfile + " does not exist"
109
110 def makeugly(prettynumber):
111         # function to take a phone number and strip out all non-numeric
112         # characters
113         uglynumber=re.sub('\D','',prettynumber)
114         return uglynumber
115
116 def makepretty(phonenumber):
117         # Function to take a phone number and return the pretty version
118         # pretty numbers:
119         #       if phonenumber begins with 0:
120         #               ...-(...)-...-....
121         #       else
122         #               if phonenumber is 13 digits:
123         #                       (...)-...-....
124         #               else if phonenumber is 10 digits:
125         #                       ...-....
126         if len(phonenumber) < 3 :
127                 return phonenumber
128
129         if  phonenumber[0] == "0" :
130                         if len(phonenumber) <=3:
131                                 prettynumber = "+" + phonenumber[0:3] 
132                         elif len(phonenumber) <=6:
133                                 prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")"
134                         elif len(phonenumber) <=9:
135                                 prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")-" + phonenumber[6:9]
136                         else:
137                                 prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")-" + phonenumber[6:9] + "-" + phonenumber[9:]
138                         return prettynumber
139         elif len(phonenumber) <= 7 :
140                         prettynumber = phonenumber[0:3] + "-" + phonenumber[3:] 
141         elif len(phonenumber) > 7 :
142                         prettynumber = "(" + phonenumber[0:3] + ")-" + phonenumber[3:6] + "-" + phonenumber[6:]
143         return prettynumber
144
145 class Dialpad:
146
147         phonenumber = ""
148
149         def __init__(self):
150                 if os.path.isfile("/usr/local/lib/gc_dialer.glade") :
151                         self.gladefile = "/usr/local/lib/gc_dialer.glade"  
152                 elif os.path.isfile("./gc_dialer.glade") :
153                         self.gladefile = "./gc_dialer.glade"
154
155                 self.gcd = GCDialer()
156                 if self.gcd._msg != "":
157                         self.ErrPopUp(self.gcd._msg)
158                         sys.exit(1)
159
160                 self.wTree = gtk.glade.XML(self.gladefile)
161                 self.window = self.wTree.get_widget("Dialpad")
162                 if (self.window):
163                         self.window.connect("destroy", gtk.main_quit)
164                 #Get the buffer associated with the number display
165                 self.numberdisplay = self.wTree.get_widget("numberdisplay")
166                 self.dialer_history = self.wTree.get_widget("dialer_history")
167
168                 # Load the liststore array with the numbers from the history file
169                 load_history_list(histfile,liststore)
170                 # load the dropdown menu with the numbers from the dial history
171                 self.dialer_history.set_model(liststore)
172                 cell = gtk.CellRendererText()
173                 self.dialer_history.pack_start(cell, True)
174                 self.dialer_history.set_active(-1)
175                 self.dialer_history.set_attributes(cell, text=0)
176
177                 self.about_dialog = None
178                 self.error_dialog = None
179
180                 dic = {
181                         # Routine for processing signal from the combobox (ie., when the
182                         # user selects an entry from the dropdown history
183                         "on_dialer_history_changed" : self.on_dialer_history_changed,
184
185                         # Process signals from buttons
186                         "on_number_clicked"  : self.on_number_clicked,
187                         "on_Clear_clicked"   : self.on_Clear_clicked,
188                         "on_Dial_clicked"    : self.on_Dial_clicked,
189                         "on_Backspace_clicked" : self.Backspace,
190                         "on_Cancel_clicked"  : self.on_Cancel_clicked,
191                         "on_About_clicked"   : self.on_About_clicked}
192                 self.wTree.signal_autoconnect(dic)
193
194         def ErrPopUp(self,msg):
195                 error_dialog = gtk.MessageDialog(None,0,gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,msg)
196                 def close(dialog, response, editor):
197                         editor.about_dialog = None
198                         dialog.destroy()
199                 error_dialog.connect("response", close, self)
200                 # error_dialog.connect("delete-event", delete_event, self)
201                 self.error_dialog = error_dialog
202                 error_dialog.run()
203
204         def on_About_clicked(self, menuitem, data=None):
205                 if self.about_dialog: 
206                         self.about_dialog.present()
207                         return
208
209                 authors = [ "Mark Bergman <bergman@merctech.com>",
210                                 "Eric Warnke <ericew@gmail.com>" ]
211
212                 about_dialog = gtk.AboutDialog()
213                 about_dialog.set_transient_for(None)
214                 about_dialog.set_destroy_with_parent(True)
215                 about_dialog.set_name("Grandcentral Dialer")
216                 about_dialog.set_version("0.5")
217                 about_dialog.set_copyright("Copyright \xc2\xa9 2008 Mark Bergman")
218                 about_dialog.set_comments("GUI front-end to initiate outbound call from Grandcentral.com, typically with Grancentral configured to connect the outbound call to a VOIP number accessible via Gizmo on the Internet Tablet.\n\nRequires an existing browser cookie from a previous login session to http://www.grandcentral.com/mobile/messages and the program 'wget'.")
219                 about_dialog.set_authors            (authors)
220                 about_dialog.set_logo_icon_name     (gtk.STOCK_EDIT)
221
222                 # callbacks for destroying the dialog
223                 def close(dialog, response, editor):
224                         editor.about_dialog = None
225                         dialog.destroy()
226
227                 def delete_event(dialog, event, editor):
228                         editor.about_dialog = None
229                         return True
230
231                 about_dialog.connect("response", close, self)
232                 about_dialog.connect("delete-event", delete_event, self)
233                 self.about_dialog = about_dialog
234                 about_dialog.show()
235
236         def on_Dial_clicked(self, widget):
237                 # Strip the leading "1" before the area code, if present
238                 if len(Dialpad.phonenumber) == 11 and Dialpad.phonenumber[0] == "1" :
239                                 Dialpad.phonenumber = Dialpad.phonenumber[1:]
240                 prettynumber = makepretty(Dialpad.phonenumber)
241                 if len(Dialpad.phonenumber) < 7 :
242                         # It's too short to be a phone number
243                         msg = 'Phone number "%s" is too short' % ( prettynumber )
244                         self.ErrPopUp(msg)
245                 else :
246                         timestamp=time.asctime(time.localtime())
247                         
248                         if self.gcd.dial(Dialpad.phonenumber) == True : 
249                                 histFH = open(histfile,"a")
250                                 histFH.write("%s dialed at %s\n" % ( Dialpad.phonenumber, timestamp ) )
251                                 histFH.close()
252
253                                 # Re-load the updated history of dialed numbers
254                                 load_history_list(histfile,liststore)
255                                 self.dialer_history.set_active(-1)
256                                 self.on_Clear_clicked(widget)
257                         else:
258                                 self.ErrPopUp(self.gcd._msg)
259
260         def on_Cancel_clicked(self, widget):
261                 sys.exit(1)
262
263         def Backspace(self, widget):
264                 Dialpad.phonenumber = Dialpad.phonenumber[:-1]
265                 prettynumber = makepretty(Dialpad.phonenumber)
266                 self.numberdisplay.set_text(prettynumber)
267
268         def on_Clear_clicked(self, widget):
269                 Dialpad.phonenumber = ""
270                 self.numberdisplay.set_text(Dialpad.phonenumber)
271
272         def on_dialer_history_changed(self,widget):
273                 # Set the displayed number to the number chosen from the history list
274                 history_list = self.dialer_history.get_model()
275                 history_index = self.dialer_history.get_active()
276                 prettynumber = history_list[history_index][0]
277                 Dialpad.phonenumber = makeugly(prettynumber)
278                 self.numberdisplay.set_text(prettynumber)
279
280         def on_number_clicked(self, widget):
281                 Dialpad.phonenumber = Dialpad.phonenumber + re.sub('\D','',widget.get_label())
282                 prettynumber = makepretty(Dialpad.phonenumber)
283                 self.numberdisplay.set_text(prettynumber)
284
285
286
287 if __name__ == "__main__":
288         title = 'Dialpad'
289         handle = Dialpad()
290         gtk.main()
291         sys.exit(1)