
07-23-09, 11:16 AM
|
|
Junior Member
|
|
Join Date: Aug 2008
Posts: 14
|
|
|
Google Voice Dialing Script for Asterisk
I was planning on posting this once I got it working, but I have been unable to get fully there. :( So, I figured I would post it now to tap into some the knowledge floating around here. Any help on this is *greatly* appreciated!
I also want everyone to know that the credit for this script goes to its respective author, Paul Marks of http://www.pcmarks.net . I understand the script works fine in a roll-your-own Asterisk 1.6 installation. I am just having trouble implementing it on a build with Freepbx (PIAF, in my case). Last but not least, I was made aware of this script by kmem in the #pbxinaflash channel on freenode. Some of the code I will post is his or came from him and all of the credit of finding this gem goes to him.
Without further ado:
|
Code:
|
#!/usr/bin/env python
# google-voice-dialout.agi
# Paul Marks (www.pmarks.net)
#
# This is an Asterisk 1.6 script to place outgoing calls through Google Voice.
# It will automatically sign into the web interface, and submit a click2call
# request through your registered Gizmo number. Asterisk can then answer
# the incoming call, and Bridge() it into your original outgoing call.
#
# I deduced the click2call sequence by using the "Live HTTP Headers" Firefox
# plugin. If the website changes too much, this script will probably stop
# working, so don't use it for anything too important.
#
# This assumes you've already configured Asterisk to receive Gizmo calls.
#
#
# This rule will redirect outbound calls to this script:
# exten => _1NXXNXXXXXX,1,AGI(google-voice-dialout.agi)
#
# This rule will connect the inbound GV/Gizmo calls:
# exten => s/6502650000,1,Bridge(${DB_DELETE(gv_dialout/channel)}, p)
# ^-- Put your 10-digit Google Voice number here.
#
#
# To test this script from the command line without Asterisk, type the
# following. Be sure to type a few linefeeds at the end:
#
# $ ./google-voice-dialout.agi
# agi_channel:
# agi_dnid: 18004664411
#
# Put your Google login and Gizmo number here:
USERNAME = "bob"
PASSWORD = "hunter2"
GIZMO_NUMBER = "17470000000"
import httplib
import urllib
import re
import sys
import time
class Error(Exception):
pass
def ReadAgiEnvironment():
env = {}
while 1:
line = sys.stdin.readline().strip()
if not line:
break
key, data = line.split(':')
env[key.strip()] = data.strip()
return env
def SendAgi(cmd):
sys.stdout.write("%s\n" % cmd)
sys.stdout.flush()
sys.stdin.readline()
class SimpleCookieJar(object):
cookie_re = re.compile(r"(?i)set-cookie: (\w+)=([^;]+).*")
def __init__(self):
self.cookies = {}
def addCookies(self, response):
for header in response.msg.headers:
m = self.cookie_re.match(header)
if not m:
continue
self.cookies[m.group(1)] = m.group(2)
def get(self):
return "; ".join("%s=%s" % kv for kv in self.cookies.iteritems())
class GVClickToCall(object):
USER_AGENT = "google-voice-dialout.agi/1.1"
def __init__(self, username, password, via, dial):
self.username = username
self.password = password
self.via = via
self.dial = dial
self.cj = SimpleCookieJar()
self.h = httplib.HTTPSConnection("www.google.com")
self.login()
self.placeCall()
self.logout()
def login(self):
print >>sys.stderr, "Logging in."
postdata = urllib.urlencode({ "Email": self.username,
"Passwd": self.password })
self.doRequest(
method="POST", url="/accounts/ServiceLoginAuth",
body=postdata,
headers={ "Content-Type": "application/x-www-form-urlencoded" })
# Start at https://www.google.com/voice, and collect cookies as we
# follow all the redirects.
PREFIX = "https://www.google.com/"
location = "/voice"
for i in xrange(5):
response, html = self.doRequest(
method="GET", url=location,
headers={})
location = response.getheader("location")
if not location:
# No more redirects, yay!
break
# All redirects should fall within the same domain.
if not location.startswith(PREFIX):
raise Error("Unexpected redirect: %s" % location)
location = location[len(PREFIX)-1:]
# Scrape magic _rnr_se value from the HTML.
m = re.search(r'name="_rnr_se" type="hidden" value="([^"]+)"', html)
if not m:
raise Error("Can't find _rnr_se. Not logged in?")
self.magic_rnr_se = m.group(1)
def placeCall(self):
print >>sys.stderr, "Calling %s via %s" % (self.dial, self.via)
postdata = urllib.urlencode({ "outgoingNumber": self.dial,
"forwardingNumber": self.via,
"_rnr_se": self.magic_rnr_se })
response, http = self.doRequest(
method="POST", url="/voice/call/connect",
body=postdata,
headers={ "Content-Type": "application/x-www-form-urlencoded" })
print >>sys.stderr, "Dial response:", http
def logout(self):
self.doRequest(
method="GET", url="/accounts/Logout",
headers={ "Connection": "close" })
print >>sys.stderr, "Logged out."
def doRequest(self, headers, **kw):
headers["User-agent"] = self.USER_AGENT
headers["Cookie"] = self.cj.get()
self.h.request(headers=headers, **kw)
response = self.h.getresponse()
self.cj.addCookies(response)
return response, response.read()
def main():
env = ReadAgiEnvironment()
print >>sys.stderr, env
agi_channel = env["agi_channel"]
agi_dnid = env["agi_dnid"]
# Write the channel ID to Asterisk's database, so it can be accessed
# by the incoming call when it arrives.
SendAgi("database put gv_dialout channel %s" % agi_channel)
SendAgi("answer")
try:
GVClickToCall(username=USERNAME, password=PASSWORD,
dial=agi_dnid, via=GIZMO_NUMBER)
# Asterisk should patch in the incoming call while we're asleep.
time.sleep(10)
finally:
SendAgi("hangup")
if __name__ == '__main__':
main() |
Code is also attached (change filename ending from txt to agi) and available at original source: http://www.pmarks.net/posted_links/g...ce-dialout.agi .
This is where I am at:
I copied the script to "/var/lib/asterisk/agi-bin" and made it executable by issuing "chmod -x google-voice-dialout.agi" . I already have an inbound route setup for my gizmo account, so running the script manually (like instructed in the notes) successfully completes a call. However, when trying to complete an outbound call from an endpoint it fails. I have tracked what I believe to be the problem as the "agi_dnid" not being pulled properly (it is being delivered to the script as "unknown"). Further, I believe this problem is being caused by an improper outbound route on my part. My outbound route setup is as follows:
I have an outbound route pointing to a custom trunk. The custom trunk is:
|
Code:
|
Custom Dial String: Local/$OUTNUM$@gv-doout-custom |
I have the custom context, "gv-doout-custom", added to my "extensions_custom.conf" file:
|
Code:
|
[gv-doout-custom]
exten => _1NXXNXXXXXX,1,AGI(google-voice-dialout.agi) |
I haven't gotten to the perils of ensuring the bridge statement in the inbound route functions. I am making the assumption that if someone points me down the right path with the outbound, the inbound might fall into place.
Thank you again for any help provided and looking forward to getting Google calling through PIAF a reality again!
~derek
Last edited by derekneuberger : 07-24-09 at 02:42 AM.
|
|

07-24-09, 01:17 PM
|
|
Nerd Uno
|
|
Join Date: Oct 2007
Posts: 3,881
|
|
Finally got this working. We'll write up the solution this weekend and publish early Monday on Nerd Vittles at the following link: http://nerdvittles.com/?p=630
|
|

07-24-09, 01:45 PM
|
|
Guru
|
|
Join Date: Feb 2009
Posts: 575
|
|
|
Ugh, I can't help but wonder why someone felt compelled to write this in python, when we already have a ton of stuff written in php - now there are two scripting languages to keep track of (actually 3, if you count perl.)
|
|

07-24-09, 02:53 PM
|
|
Nerd Uno
|
|
Join Date: Oct 2007
Posts: 3,881
|
|
You got that right. If it weren't the most requested article on Nerd Vittles all day every day, we'd have run away from it without ever looking back. 
Last edited by wardmundy : 07-24-09 at 03:43 PM.
|
|

07-24-09, 03:04 PM
|
|
Guru
|
|
Join Date: Feb 2009
Posts: 575
|
|
Biased here, but I must admit I far prefer PHP, since it is very "C" like (a language I am extremely familiar with.) We won't even go into how badly perl sucks 
|
|

07-24-09, 06:25 PM
|
|
Junior Member
|
|
Join Date: Aug 2008
Posts: 14
|
|
Ok, The bridge line does work in 1.6, but obviously is a no go in 1.4. After discussing this a little on the IRC channel, I think the best way to join the calls (the incoming google voice via gizmo with your ext to server) is with the meetme. I am at a loss on how to actually get the implemented in the dialplan. Something like:
|
Code:
|
exten => s,1,MeetMe(|Mde) |
ought to dump into a dynamic conference, no pin needed. I tested this out in my [from-gizmo] context. You would, obviously, need some error handling to only route the appropriate calls from your gizmo trunk in this fashion..
I am also thinking that channelredirect can be used to grab your active ext>server call and dump it in the conference to join the call from gizmo just routed there. http://astbook.asteriskdocs.org/en/2...APP-B-349.html
|
|

07-24-09, 06:30 PM
|
|
Junior Member
|
|
Join Date: Aug 2008
Posts: 14
|
|
I didn't quite complete my thought there. The problem I am running into, is somehow using the dialplan to initiate the channelredirect line to join the calls after the gizmo call comes in.
Also, some ideas on the error handling of the gizmo calls:
|
Code:
|
exten => s,1,GotoIf($[${CALLERID(num)} = GVNUMBER]?reject:allow) ;matches with your gvnumber. if only your gizmo route was using this context, this would work, otherwise, you would also need to patternmatch on the ext.
exten => s,n(allow),Set(CALLERID(all)=${CALLERID(num)} <${CALLERID(num)}>)
exten => s,n,Dial(sip/EXT) ; or whereever your plan B would send it
exten => s,n,Hangup()
exten => s,n(reject),Bridge(${DB_DELETE(gv_dialout/channel)}, p)
exten => s,n,Hangup() |
|
|

07-24-09, 06:59 PM
|
|
Nerd Uno
|
|
Join Date: Oct 2007
Posts: 3,881
|
|
I must be missing something here. For inbound calls, what's wrong with setting up a forwarding number on GV and then using that DID to accept calls on your Asterisk box?? Then you don't need the bridging at all. 
|
|

07-24-09, 07:58 PM
|
|
Junior Member
|
|
Join Date: Aug 2008
Posts: 14
|
|
|
Ward,
The idea of the bridge is so it appears and acts like a regular outbound route. So:
a) I pick up my phone and dial the number
b) #behind the scenes asterisk initiates the click2call, excepts the incoming call from GV and bridges (or for 1.4's case would have to dump both calls into the same conference).
c) I am presented with my called parties phone ringing without ever having to pickup another call
Now, given, unless you were calling in from the outside or from an extensions that wouldn't normally receive inbound calls, most of that side of the script is luxury. However, it would be pretty awesome!
~derek
|
|

07-24-09, 08:06 PM
|
|
Nerd Uno
|
|
Join Date: Oct 2007
Posts: 3,881
|
|
Ah. Got it now. With Call Forward to same extension I dialed from, I have to punch the answer button a second time on a 57i, but I understand the coolness factor.  For many folks, they might actually prefer to redirect the conversation to their cell. I think I'll leave that for another day and cover the basics on Monday.
|
|
| Thread Tools |
|
|
| Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -5. The time now is 09:01 PM.
|
|