<?php
class GoogleVoice
{
// Our credentials
private $_login;
private $_pass;
private $_rnr_se; // some crazy google thing that we need later
// Our private curl handle
private $_ch;
// The location of our cookies
private $_cookieFile;
// Are we logged in already?
private $_loggedIn = FALSE;
public function __construct($login, $pass)
{
$this->_login = $login;
$this->_pass = $pass;
$this->_cookieFile = '/tmp/gvCookies.txt';
$this->_ch = curl_init();
curl_setopt($this->_ch, CURLOPT_COOKIEJAR, $this->_cookieFile);
curl_setopt($this->_ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($this->_ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)");
}
private function _logIn()
{
global $conf;
if( $this->_loggedIn )
return TRUE;
// fetch the login page
curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/accounts/ServiceLogin?passive=true&service=grandcentral');
$html = curl_exec($this->_ch);
if(preg_match('/name="GALX"\s*value="([^"]+)"/', $html, $match))
$GALX = $match[1];
else
throw new Exception('Could not parse for GALX token');
curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/accounts/ServiceLoginAuth?service=grandcentral');
curl_setopt($this->_ch, CURLOPT_POST, TRUE);
curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array(
'Email' => $this->_login,
'Passwd' => $this->_pass,
'continue' => 'https://www.google.com/voice/account/signin',
'service' => 'grandcentral',
'GALX' => $GALX
));
$html = curl_exec($this->_ch);
if( preg_match('/name="_rnr_se".*?value="(.*?)"/', $html, $match) )
{
$this->_rnr_se = $match[1];
}
else
{
throw new Exception("Could not log in to Google Voice with username: " . $this->_login);
}
}
/**
* Place a call to $number connecting first to $fromNumber
* @param $number The 10-digit phone number to call (formatted with parens and hyphens or none)
* @param $fromNumber The 10-digit number on your account to connect the call (no hyphens or spaces)
* @param $phoneType (mobile, work, home, gizmo) The type of phone the $fromNumber is. The call will not be connected without this value.
*/
public function callNumber($number, $fromNumber, $phoneType='mobile')
{
$types = array(
'mobile' => 2,
'work' => 3,
'home' => 1,
'gizmo' => 7
);
if(!array_key_exists($phoneType, $types))
throw new Exception('Phone type must be mobile, work, home or gizmo');
$this->_logIn();
curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/call/connect/');
curl_setopt($this->_ch, CURLOPT_POST, TRUE);
curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array(
'_rnr_se'=>$this->_rnr_se,
'forwardingNumber'=>'+1' . $fromNumber,
'outgoingNumber'=>$number,
'phoneType'=>$types[$phoneType],
'remember'=>0,
'subscriberNumber'=>'undefined'
));
curl_exec($this->_ch);
}
public function sendSMS($number, $message)
{
$this->_logIn();
curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/sms/send/');
curl_setopt($this->_ch, CURLOPT_POST, TRUE);
curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array(
'_rnr_se'=>$this->_rnr_se,
'phoneNumber'=>'+1' . $number,
'text'=>$message
));
curl_exec($this->_ch);
}
public function getNewSMS()
{
$this->_logIn();
curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/inbox/recent/sms/');
curl_setopt($this->_ch, CURLOPT_POST, FALSE);
curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, TRUE);
$xml = curl_exec($this->_ch);
$dom = new DOMDocument();
// load the "wrapper" xml (contains two elements, json and html)
$dom->loadXML($xml);
$json = $dom->documentElement->getElementsByTagName("json")->item(0)->nodeValue;
$json = json_decode($json);
// now make a dom parser which can parse the contents of the HTML tag
$html = $dom->documentElement->getElementsByTagName("html")->item(0)->nodeValue;
// replace all "&" with "&" so it can be parsed
$html = str_replace("&", "&", $html);
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$results = array();
foreach( $json->messages as $mid=>$convo )
{
$elements = $xpath->query("//div[@id='$mid']//div[@class='gc-message-sms-row']");
if(!is_null($elements))
{
if( in_array('unread', $convo->labels) )
{
foreach($elements as $i=>$element)
{
$XMsgFrom = $xpath->query("span[@class='gc-message-sms-from']", $element);
$msgFrom = '';
foreach($XMsgFrom as $m)
$msgFrom = trim($m->nodeValue);
if( $msgFrom != "Me:" )
{
$XMsgText = $xpath->query("span[@class='gc-message-sms-text']", $element);
$msgText = '';
foreach($XMsgText as $m)
$msgText = trim($m->nodeValue);
$XMsgTime = $xpath->query("span[@class='gc-message-sms-time']", $element);
$msgTime = '';
foreach($XMsgTime as $m)
$msgTime = trim($m->nodeValue);
$results[] = array('msgID'=>$mid, 'phoneNumber'=>$convo->phoneNumber, 'message'=>$msgText, 'date'=>date('Y-m-d H:i:s', strtotime(date('m/d/Y ',intval($convo->startTime/1000)).$msgTime)));
}
}
}
else
{
//echo "This message is not unread\n";
}
}
else
{
//echo "gc-message-sms-row query failed\n";
}
}
return $results;
}
public function markSMSRead($msgID)
{
$this->_logIn();
curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/inbox/mark/');
curl_setopt($this->_ch, CURLOPT_POST, TRUE);
curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array(
'_rnr_se'=>$this->_rnr_se,
'messages'=>$msgID,
'read'=>1
));
curl_exec($this->_ch);
}
public function getNewVoicemail()
{
$this->_logIn();
curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/inbox/recent/voicemail/');
curl_setopt($this->_ch, CURLOPT_POST, FALSE);
curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, TRUE);
$xml = curl_exec($this->_ch);
$dom = new DOMDocument();
// load the "wrapper" xml (contains two elements, json and html)
$dom->loadXML($xml);
$json = $dom->documentElement->getElementsByTagName("json")->item(0)->nodeValue;
$json = json_decode($json);
// now make a dom parser which can parse the contents of the HTML tag
$html = $dom->documentElement->getElementsByTagName("html")->item(0)->nodeValue;
// replace all "&" with "&" so it can be parsed
$html = str_replace("&", "&", $html);
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$results = array();
foreach( $json->messages as $mid=>$convo )
{
$elements = $xpath->query("//div[@id='$mid']");
if(!is_null($elements))
{
if( $convo->isRead == false )
{
$element = $elements->item(0);
$XMsg = $xpath->query("//div[@class='gc-message-message-display']", $element);
$XMsgPart = $xpath->query("span|a", $XMsg->item(0));
$msgText = '';
$msgWords = array();
foreach($XMsgPart as $m)
{
$word = $this->_unhtmlentities(trim($m->nodeValue));
$msgText .= $word . ' ';
if(preg_match('/gc-word-(.+)/', $m->attributes->getNamedItem('class')->textContent, $match))
$confidence = $match[1];
else
$confidence = '';
$msgWords[] = array('word'=>$word, 'confidence'=>$confidence);
}
$results[] = array('msgID'=>$mid, 'phoneNumber'=>$convo->phoneNumber, 'message'=>$msgText, 'date'=>date('Y-m-d H:i:s', intval($convo->startTime/1000)), 'words'=>$msgWords);
}
else
{
echo "This message ($mid) is not unread\n";
}
}
else
{
echo "Could not find HTML version of message: $mid\n";
}
}
return $results;
}
public function dom_dump($obj) {
if ($classname = get_class($obj)) {
$retval = "Instance of $classname, node list: \n";
switch (true) {
case ($obj instanceof DOMDocument):
$retval .= "XPath: {$obj->getNodePath()}\n".$obj->saveXML($obj);
break;
case ($obj instanceof DOMElement):
$retval .= "XPath: {$obj->getNodePath()}\n".$obj->ownerDocument->saveXML($obj);
break;
case ($obj instanceof DOMAttr):
$retval .= "XPath: {$obj->getNodePath()}\n".$obj->ownerDocument->saveXML($obj);
break;
case ($obj instanceof DOMNodeList):
for ($i = 0; $i < $obj->length; $i++) {
$retval .= "Item #$i, XPath: {$obj->item($i)->getNodePath()}\n"."{$obj->item($i)->ownerDocument->saveXML($obj->item($i))}\n";
}
break;
default:
return "Instance of unknown class";
}
} else {
return 'no elements...';
}
return htmlspecialchars($retval);
}
private function _unhtmlentities($string)
{
// replace numeric entities
$string = preg_replace('~&#x([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $string);
$string = preg_replace('~&#([0-9]+);~e', 'chr("\\1")', $string);
// replace literal entities
$string = html_entity_decode($string);
return $string;
}
}
?>
All system for education purposes only. For more tools: Telegram @jackleet