4 # Python front-end to a wget script to use grandcentral.com to place outbound VOIP calls.
5 # (C) 2008 Mark Bergman
23 histfile=os.path.expanduser("~")
24 histfile=os.path.join(histfile,".gcdialerhist") # Use the native OS file separator
25 liststore = gtk.ListStore(str)
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,}$")
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"
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.'
40 def validate(self,number):
41 return GCDialer._validateRe.match(number) != None
43 def dial(self,number):
45 if self.validate(number) == False:
46 self._msg = "Invalid number format %s" % (number)
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()
60 wgetresults = open(GCDialer._wgetoutput, 'r' )
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.'
65 data = wgetresults.read()
68 if GCDialer._wgetOKstrRe.search(data) != None:
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.'
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
79 # clear out existing entries
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
90 if re.match(search4num,num):
91 # the number is already in the drop-down menu list...set the
95 if newnumber == True :
96 dialhist.append(number) # append the number to the history list
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
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])
108 # print "The history file " + histfile + " does not exist"
110 def makeugly(prettynumber):
111 # function to take a phone number and strip out all non-numeric
113 uglynumber=re.sub('\D','',prettynumber)
116 def makepretty(phonenumber):
117 # Function to take a phone number and return the pretty version
119 # if phonenumber begins with 0:
122 # if phonenumber is 13 digits:
124 # else if phonenumber is 10 digits:
126 if len(phonenumber) < 3 :
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]
137 prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")-" + phonenumber[6:9] + "-" + phonenumber[9:]
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:]
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"
155 self.gcd = GCDialer()
156 if self.gcd._msg != "":
157 self.ErrPopUp(self.gcd._msg)
160 self.wTree = gtk.glade.XML(self.gladefile)
161 self.window = self.wTree.get_widget("Dialpad")
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")
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)
177 self.about_dialog = None
178 self.error_dialog = None
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,
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)
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
199 error_dialog.connect("response", close, self)
200 # error_dialog.connect("delete-event", delete_event, self)
201 self.error_dialog = error_dialog
204 def on_About_clicked(self, menuitem, data=None):
205 if self.about_dialog:
206 self.about_dialog.present()
209 authors = [ "Mark Bergman <bergman@merctech.com>",
210 "Eric Warnke <ericew@gmail.com>" ]
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)
222 # callbacks for destroying the dialog
223 def close(dialog, response, editor):
224 editor.about_dialog = None
227 def delete_event(dialog, event, editor):
228 editor.about_dialog = None
231 about_dialog.connect("response", close, self)
232 about_dialog.connect("delete-event", delete_event, self)
233 self.about_dialog = about_dialog
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 )
246 timestamp=time.asctime(time.localtime())
248 if self.gcd.dial(Dialpad.phonenumber) == True :
249 histFH = open(histfile,"a")
250 histFH.write("%s dialed at %s\n" % ( Dialpad.phonenumber, timestamp ) )
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)
258 self.ErrPopUp(self.gcd._msg)
260 def on_Cancel_clicked(self, widget):
263 def Backspace(self, widget):
264 Dialpad.phonenumber = Dialpad.phonenumber[:-1]
265 prettynumber = makepretty(Dialpad.phonenumber)
266 self.numberdisplay.set_text(prettynumber)
268 def on_Clear_clicked(self, widget):
269 Dialpad.phonenumber = ""
270 self.numberdisplay.set_text(Dialpad.phonenumber)
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)
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)
287 if __name__ == "__main__":