4 Grandcentral Dialer backend code
5 Eric Warnke <ericew@gmail.com>
14 from browser_emu import MozillaEmulator
17 class GCDialer(object):
19 This class encapsulates all of the knowledge necessary to interace with the grandcentral servers
20 the functions include login, setting up a callback number, and initalting a callback
23 _wgetOKstrRe = re.compile("This may take a few seconds", re.M) # string from Grandcentral.com on successful dial
24 _validateRe = re.compile("^[0-9]{10,}$")
25 _accessTokenRe = re.compile(r"""<input type="hidden" name="a_t" [^>]*value="(.*)"/>""")
26 _isLoginPageRe = re.compile(r"""<form method="post" action="https://www.grandcentral.com/mobile/account/login">""")
27 _callbackRe = re.compile(r"""name="default_number" value="(\d+)" />\s+(.*)\s$""", re.M)
28 _accountNumRe = re.compile(r"""<img src="/images/mobile/inbox_logo.gif" alt="GrandCentral" />\s*(.{14})\s* """, re.M)
29 _inboxRe = re.compile(r"""<td>.*?(voicemail|received|missed|call return).*?</td>\s+<td>\s+<font size="2">\s+(.*?)\s+ \| \s+<a href="/mobile/contacts/.*?">(.*?)\s?</a>\s+<br/>\s+(.*?)\s?<a href=""", re.S)
31 _forwardselectURL = "http://www.grandcentral.com/mobile/settings/forwarding_select"
32 _loginURL = "https://www.grandcentral.com/mobile/account/login"
33 _setforwardURL = "http://www.grandcentral.com/mobile/settings/set_forwarding?from=settings"
34 _clicktocallURL = "http://www.grandcentral.com/mobile/calls/click_to_call?a_t=%s&destno=%s"
35 _inboxallURL = "http://www.grandcentral.com/mobile/messages/inbox?types=all"
37 def __init__(self, cookieFile = None):
38 # Important items in this function are the setup of the browser emulation and cookie file
41 if cookieFile == None:
42 cookieFile = os.path.join(os.path.expanduser("~"), ".gc_dialer_cookies.txt")
43 self._browser = MozillaEmulator(None, 0)
44 self._browser.cookies.filename = cookieFile
45 if os.path.isfile(cookieFile):
46 self._browser.cookies.load()
48 # self._browser.cookies.save()
50 self._accessToken = None
51 self._accountNum = None
53 def grabToken(self, data):
54 # Pull the magic cookie from the datastream
56 atGroup = GCDialer._accessTokenRe.search(data)
58 self._accessToken = atGroup.group(1)
62 anGroup = GCDialer._accountNumRe.search(data)
64 self._accountNum = anGroup.group(1)
68 def getAccountNumber(self):
69 return self._accountNum
73 Attempts to detect a current session and pull the
74 auth token ( a_t ) from the page
77 self._lastData = self._browser.download(GCDialer._forwardselectURL)
78 self._browser.cookies.save()
79 if GCDialer._isLoginPageRe.search(self._lastData) is None:
80 self.grabToken(self._lastData)
84 def login(self, username, password):
86 Attempt to login to grandcentral
92 loginPostData = urllib.urlencode( {'username' : username , 'password' : password } )
93 self._lastData = self._browser.download(GCDialer._loginURL, loginPostData)
94 return self.isAuthed()
96 def setSaneCallback(self):
98 Try to set a sane default callback number on these preferences
99 1) 1747 numbers ( Gizmo )
100 2) anything with gizmo in the name
101 3) anything with computer in the name
105 numbers = self.getCallbackNumbers()
107 for k, v in numbers.iteritems():
108 if not re.compile(r"""1747""").match(k) is None:
109 self.setCallbackNumber(k)
112 for k, v in numbers.iteritems():
113 if not re.compile(r"""gizmo""", re.I).search(v) is None:
114 self.setCallbackNumber(k)
117 for k, v in numbers.iteritems():
118 if not re.compile(r"""computer""", re.I).search(v) is None:
119 self.setCallbackNumber(k)
122 for k, v in numbers.iteritems():
123 self.setCallbackNumber(k)
126 def getCallbackNumbers(self):
129 self._lastData = self._browser.download(GCDialer._forwardselectURL)
130 for match in GCDialer._callbackRe.finditer(self._lastData):
131 retval[match.group(1)] = match.group(2)
135 def setCallbackNumber(self, callbacknumber):
137 set the number that grandcental calls
138 this should be a proper 10 digit number
141 callbackPostData = urllib.urlencode({'a_t' : self._accessToken, 'default_number' : callbacknumber })
142 self._lastData = self._browser.download(GCDialer._setforwardURL, callbackPostData)
143 self._browser.cookies.save()
145 def getCallbackNumber(self):
146 for c in self._browser.cookies:
147 if c.name == "pda_forwarding_number":
152 self._browser.cookies.clear()
153 self._browser.cookies.save()
155 def validate(self, number):
157 Can this number be called ( syntax validation only )
160 return GCDialer._validateRe.match(number) is not None
162 def dial(self, number):
164 This is the main function responsible for initating the callback
167 # If the number is not valid throw exception
168 if self.validate(number) is False:
169 raise ValueError('number is not valid')
171 # No point if we don't have the magic cookie
172 if not self.isAuthed():
175 # Strip leading 1 from 11 digit dialing
176 if len(number) == 11 and number[0] == 1:
179 self._lastData = self._browser.download(
180 GCDialer._clicktocallURL % (self._accessToken, number),
181 None, {'Referer' : 'http://www.grandcentral.com/mobile/messages'} )
183 if GCDialer._wgetOKstrRe.search(self._lastData) != None:
188 def get_recent(self):
190 self._lastData = self._browser.download(GCDialer._inboxallURL)
191 for match in self._inboxRe.finditer(self._lastData):
192 #print "type: %s date: %s person: %s number: %s" % (match.group(1), match.group(2), match.group(3), match.group(4))
193 retval.append([match.group(4), "%s on %s from/to %s - %s" % (match.group(1).capitalize(), match.group(2), match.group(3), match.group(4))])