diff --git a/GoogleVoice.php b/GoogleVoice.php index e41cc76..7d6c20b 100644 --- a/GoogleVoice.php +++ b/GoogleVoice.php @@ -1,7 +1,10 @@ 2, + 'work' => 3, + 'home' => 1 + ); + + private $_serverPath = array( + 'addNote' => '/inbox/savenote/', + 'archive' => '/inbox/archiveMessages/', + 'call' => '/call/connect/', + 'cancel' => '/call/cancel/', + 'delete' => '/inbox/deleteMessages/', + 'deleteNote' => '/inbox/deletenote/', + 'getMP3' => '/media/send_voicemail/', + 'getSMS' => '/inbox/recent/sms/', + 'voicemail' => '/inbox/recent/voicemail/', + 'inbox' => '/inbox/recent/', + 'mark' => '/inbox/mark/', + 'missed' => '/inbox/recent/missed/', + 'search' => '/inbox/search/', + 'sendSMS' => '/sms/send/', + 'voicemail' => '/inbox/recent/voicemail/' + ); + + public function __construct($login, $pass) { $this->_login = $login; $this->_pass = $pass; $this->_cookieFile = '/tmp/gvCookies.txt'; @@ -28,10 +58,111 @@ public function __construct($login, $pass) { 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/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"); //was "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)" + curl_setopt($this->_ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"); //was "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko + // Added the login call here. login throws an exception, so if we can't login, + // let's find out now, and catch the exception on construction instead of on + // all the individual members + $this->_logIn(); } + + + /** + * Private helper methods. This is where all the magic happens + */ + + /** + * Source from http://www.binarytides.com/php-get-name-and-value-of-all-input-tags-on-a-page-with-domdocument/ + * Generic function to fetch all input tags (name and value) on a page + * Useful when writing automatic login bots/scrapers + */ + private function _domGetInputTags($html) + { + $post_data = array(); + + // a new dom object + $dom = new DomDocument; + + //load the html into the object + @$dom->loadHTML($html); //@suppresses warnings + //discard white space + $dom->preserveWhiteSpace = FALSE; + + //all input tags as a list + $input_tags = $dom->getElementsByTagName('input'); + + //get all rows from the table + for ($i = 0; $i < $input_tags->length; $i++) + { + if( is_object($input_tags->item($i)) ) + { + $name = $value = ''; + $name_o = $input_tags->item($i)->attributes->getNamedItem('name'); + if(is_object($name_o)) + { + $name = $name_o->value; + + $value_o = $input_tags->item($i)->attributes->getNamedItem('value'); + if(is_object($value_o)) + { + $value = $input_tags->item($i)->attributes->getNamedItem('value')->value; + } + + $post_data[$name] = $value; + } + } + } + + return $post_data; + } + + private function _formatNumber($number) + { + $newNumber = ''; + $numChars = strlen($number); + + // Remove all characters except numbers + for ($i = 0; $i < $numChars; $i += 1) { + $theChar = substr($number, $i, 1); + if (stripos('0123456789', $theChar) !== false) { + $newNumber .= $theChar; + } + } + + // Make sure the mumber begins with '+1' + if (substr($newNumber, 0, 1) === '1') { + $newNumber = '+' . $newNumber; + } else { + $newNumber = '+1' . $newNumber; + } + + return $newNumber; + } + + private function _get($path) + { + // Login to the service if not already done. + $this->_logIn(); + + // @TODO we can access beyond page 1 by adding ?page=pX where X is the number + // of the page requested + $this->_curlUrl = self::GV_SERVER_URL . $path; + + // Send HTTP POST request. + curl_setopt($this->_ch, CURLOPT_URL, $this->_curlUrl); + curl_setopt($this->_ch, CURLOPT_POST, false); + curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, true); + + $this->_result = curl_exec($this->_ch); + + return $this->_result; + } + + private function _getAndParse($path, $isRead = null) + { + return $this->_parseGetResults($this->_get($path), $isRead); + } private function _logIn() { global $conf; @@ -40,20 +171,29 @@ private function _logIn() { return TRUE; // Fetch the Google Voice login page input fields - $URL='https://accounts.google.com/ServiceLogin?service=grandcentral&passive=1209600&continue=https://www.google.com/voice&followup=https://www.google.com/voice<mpl=open'; //adding login to GET prefills with username "&Email=$this->_login" + $URL = "https://accounts.google.com/ServiceLogin?" + ."service=grandcentral&" + ."passive=1209600&" + ."continue=".self::GV_LEGACY_SERVER_URL."&" + ."followup=".self::GV_LEGACY_SERVER_URL."#inbox"; curl_setopt($this->_ch, CURLOPT_URL, $URL); $html = curl_exec($this->_ch); // Send HTTP POST service login request using captured input information. - $URL='https://accounts.google.com/signin/challenge/sl/password'; // This is the second page of the two page signin + // This is the second page of the two page signin + $URL='https://accounts.google.com/signin/challenge/sl/password'; curl_setopt($this->_ch, CURLOPT_URL, $URL); - $postarray = $this->dom_get_input_tags($html); // Using DOM keeps the order of the name/value from breaking the code. + // Using DOM keeps the order of the name/value from breaking the code. + $postarray = $this->_domGetInputTags($html); // Parse the returned webpage for the "GALX" token, needed for POST requests. - if(!isset($postarray['GALX']) || $postarray['GALX']==''){ + if((!isset($postarray['GALX']) || $postarray['GALX']=='') && + (!isset($postarray['gxf']) || $postarray['gxf']=='')) { $pi1 = var_export($postarray, TRUE); - error_log("Could not parse for GALX token. Inputs from page:\n" . $pi1 . "\n\nHTML from page:" . $html); - throw new Exception("Could not parse for GALX token. Inputs from page:\n" . $pi1); + // putting html in the error_log clogs it up. + error_log("Could not parse for GALX or gfx tokens. *** TURN ON THE LOGGING OF INPUTS TO DEBUG ***"); +// error_log("Could not parse for GALX or gfx tokens. Inputs from page:\n" . $pi1 . "\n\nHTML from page:" . $html); + throw new Exception("Could not parse for GALX or gfx tokens. Inputs from page:\n" . $pi1); } $postarray['Email'] = $this->_login; //Add login to POST array @@ -63,7 +203,8 @@ private function _logIn() { $html = curl_exec($this->_ch); // Test if the service login was successful. - $postarray = $this->dom_get_input_tags($html); // Using DOM keeps the order of the name/value from breaking the code. + // Using DOM keeps the order of the name/value from breaking the code. + $postarray = $this->_domGetInputTags($html); if(isset($postarray['_rnr_se']) && $postarray['_rnr_se']!='') { $this->_rnr_se = $postarray['_rnr_se']; $this->_loggedIn = TRUE; @@ -73,528 +214,416 @@ private function _logIn() { "\n\nMay need to change scraping. Here are the inputs from the page:\n". $pi2 ); //add POST action information from DOM. May help hunt down single or dual sign on page changes. throw new Exception("Could not log in to Google Voice with username: " . $this->_login . "\nLook at error log for detailed input information.\n"); - } + } } + private function _parseGetResults($xml, $isRead = null) + { + // Load the "wrapper" xml (contains two elements, json and html). + $dom = new \DOMDocument(); + $dom->loadXML($xml); + $json = $dom->documentElement->getElementsByTagName("json")->item(0)->nodeValue; + $json = json_decode($json); + // $json->resultsPerPage shows how many messages are returned per page + // $json->unreadCount an array of messages labels and the number of unread messages + + // Loop through all of the messages. + $results = array(); + foreach ($json->messages as $mid => $convo) { + // This is what I had: + if ($isRead !== null) { + if ($convo->isRead == $isRead) { + $results[] = $convo; + } + } else { + $results[] = $convo; + } + } + + return $results; + } + + /** + * Communicate with Google voice server using post + * @param $path Path appended to the google voice server url + * @param $options Required options for the specified path + */ + private function _post($path, $options) + { + // Login to the service if not already done. + $this->_logIn(); + + $options['_rnr_se'] = $this->_rnr_se; + $this->_curlUrl = self::GV_SERVER_URL . $path; + $this->_curlOptions = $options; + // Send HTTP POST request. + curl_setopt($this->_ch, CURLOPT_URL, $this->_curlUrl); + curl_setopt($this->_ch, CURLOPT_POST, true); + curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $options); + + $this->_result = curl_exec($this->_ch); + + return $this->_result; + } + + private function _verifyPhoneType($type) + { + // Make sure phone type is set properly. + if (!array_key_exists($type, $this->_phoneTypes)) { + throw new \Exception('Phone type must be mobile, work, or home'); + } + } + + + + + + /** + * Public debugging type methods + */ + 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); + } + /** + * + * @return object of some debugging type results + */ + public function getVals() + { + return (object) array( + 'curlUrl' => $this->_curlUrl, + 'curlOptions' => $this->_curlOptions, + 'result' => $this->_result + ); + } + + + + + + /** + * General messaging methods + * These messages can be used to affect all types of messages including + * voicemail, SMS and missed calls. + */ + /** - * 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) The type of phone the $fromNumber is. The call will not be connected without this value. + * Add a note to a message in a Google Voice Account. + * @param $messageId The id of the message to update. + * @param $note The message to send within the SMS. */ - public function callNumber($number, $from_number, $phone_type = 'mobile') { - $types = array( - 'mobile' => 2, - 'work' => 3, - 'home' => 1 - ); - - // Make sure phone type is set properly. - if(!array_key_exists($phone_type, $types)) - throw new Exception('Phone type must be mobile, work, or home'); - - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - 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'.$from_number, - 'outgoingNumber' => $number, - 'phoneType' => $types[$phone_type], - 'remember' => 0, - 'subscriberNumber' => 'undefined' - )); - curl_exec($this->_ch); + public function addNote($messageId, $note) { + return $this->_post( + $this->_serverPath['addNote'], + array( + 'id' => $messageId, + 'note' => $note + ) + ); } - + /** + * Mark a message in a Google Voice Account as archived. + * @param $messageId The id of the message to archive. + * @return array JSON object from Google server with results + */ + public function archive($messageId) + { + return $this->_post( + $this->_serverPath['archive'], + array( + 'messages' => $messageId, + 'archive' => '1', + 'read' => '1' + ) + ); + } /** - * Cancel 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) The type of phone the $fromNumber is. The call will not be connected without this value. + * Delete a message or conversation. + * @param $messageId The ID of the conversation to delete. + * @return array JSON object from Google server with results */ - public function cancelCall($number, $from_number, $phone_type = 'mobile') { - $types = array( - 'mobile' => 2, - 'work' => 3, - 'home' => 1 - ); - - // Make sure phone type is set properly. - if(!array_key_exists($phone_type, $types)) - throw new Exception('Phone type must be mobile, work, or home'); - - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/call/cancel/'); - curl_setopt($this->_ch, CURLOPT_POST, TRUE); - curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array( - '_rnr_se' => $this->_rnr_se, - 'forwardingNumber' => '+1'.$from_number, - 'outgoingNumber' => $number, - 'phoneType' => $types[$phone_type], - 'remember' => 0, - 'subscriberNumber' => 'undefined' - )); - curl_exec($this->_ch); + public function delete($messageId) { + return $this->_post( + $this->_serverPath['delete'], + array( + 'messages' => $messageId, + 'trash' => '1' + ) + ); } - + /** + * Get all messages from the Google Voice inbox including voicemails, SMS + * and missed calls + * @return array Array of message objects + */ + public function getInbox() + { + return $this->_getAndParse($this->_serverPath['inbox'], null); + } /** - * Send an SMS to $number containing $message. - * @param $number The 10-digit phone number to send the message to (formatted with parens and hyphens or none). - * @param $message The message to send within the SMS. + * Mark a message in a Google Voice Account as read. + * @param $messageId The id of the message to mark as read. */ - public function sendSMS($number, $message) { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - 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 markRead($messageId) { + return $this->_post( + $this->_serverPath['mark'], + array( + 'messages' => $messageId, + 'read' => '1' + ) + ); } - - - 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 markSMSDeleted($msgID) - { - $this->_logIn(); - - curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/inbox/deleteMessages/'); - curl_setopt($this->_ch, CURLOPT_POST, TRUE); - curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array( - '_rnr_se'=>$this->_rnr_se, - 'messages'=>$msgID, - 'trash'=>1 - )); - curl_exec($this->_ch); - } - - - /** - * Add a note to a message in a Google Voice Inbox or Voicemail. - * @param $message_id The id of the message to update. - * @param $note The message to send within the SMS. + * Mark a message in a Google Voice Account as unread. + * @param $messageId The id of the message to update. */ - public function addNote($message_id, $note) { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/inbox/savenote/'); - curl_setopt($this->_ch, CURLOPT_POST, TRUE); - curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array( - '_rnr_se' => $this->_rnr_se, - 'id' => $message_id, - 'note' => $note - )); - curl_exec($this->_ch); + public function markUnread($messageId) { + return $this->_post( + $this->_serverPath['mark'], + array( + 'messages' => $messageId, + 'read' => '0' + ) + ); } - - /** - * Removes a note from a message in a Google Voice Inbox or Voicemail. - * @param $message_id The id of the message to update. + * Removes a note from a message in a Google Voice Account. + * @param $messageId The id of the message to update. */ - public function removeNote($message_id, $note) { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/inbox/deletenote/'); - curl_setopt($this->_ch, CURLOPT_POST, TRUE); - curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array( - '_rnr_se' => $this->_rnr_se, - 'id' => $message_id, - )); - curl_exec($this->_ch); + public function removeNote($messageId) { + return $this->_post( + '/inbox/deletenote/', + array( + 'id' => $messageId + ) + ); } - + /** + * Mark a message in a Google Voice Account as unarchived. + * @param $messageId The id of the message to unarchive. + * @return array JSON object from Google server with results + */ + public function unArchive($messageId) + { + return $this->_post( + $this->_serverPath['archive'], + array( + 'messages' => $messageId, + 'archive' => '0' + ) + ); + } + + + + + + /** + * Methods related to Calls + */ /** - * Get all of the unread SMS messages in a Google Voice inbox. + * 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) The type of phone the $fromNumber is. The call will not be connected without this value. */ - public function getUnreadSMS() { - // Login to the service if not already done. - $this->_logIn(); + public function callNumber($number, $from_number, $phone_type = 'mobile') { + $this->_verifyPhoneType($phone_type); // Send HTTP POST request. - 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); - - // Load the "wrapper" xml (contains two elements, json and html). - $dom = new DOMDocument(); - $dom->loadXML($xml); - $json = $dom->documentElement->getElementsByTagName("json")->item(0)->nodeValue; - $json = json_decode($json); - - // Loop through all of the messages. - $results = array(); - foreach($json->messages as $mid=>$convo) { - if($convo->isRead == FALSE) { - $results[] = $convo; - } - } - return $results; + return $this->_post( + $this->_serverPath['call'], + array( + 'forwardingNumber' => $this->_formatNumber($from_number), + 'outgoingNumber' => $this->_formatNumber($number), + 'phoneType' => $this->_phoneTypes[$phone_type], + 'remember' => '0', + 'subscriberNumber' => 'undefined' + ) + ); } - - /** - * Get all of the read SMS messages in a Google Voice inbox. + * Cancel 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) The type of phone the $fromNumber is. The call will not be connected without this value. */ - public function getReadSMS() { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - 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); - - // Load the "wrapper" xml (contains two elements, json and html). - $dom = new DOMDocument(); - $dom->loadXML($xml); - $json = $dom->documentElement->getElementsByTagName("json")->item(0)->nodeValue; - $json = json_decode($json); - - // Loop through all of the messages. - $results = array(); - foreach($json->messages as $mid=>$convo) { - if($convo->isRead == TRUE) { - $results[] = $convo; - } - } - return $results; + public function cancelCall($number, $from_number, $phone_type = 'mobile') { + $this->_verifyPhoneType($phone_type); + + return $this->_post( + $this->_serverPath['cancel'], + array( + 'forwardingNumber' => $this->_formatNumber($from_number), + 'outgoingNumber' => $this->_formatNumber($number), + 'phoneType' => $this->_phoneTypes[$phone_type], + 'remember' => 0, + 'subscriberNumber' => 'undefined' + ) + ); } - - + /** + * Get all of the missed calls in a Google Voice inbox. + * @return array Array of message objects + */ + public function getMissedCalls() + { + return $this->_getAndParse($this->_serverPath['missed'], null); + } + + + + + + /** + * SMS methods + */ + /** - * Get all of the unread SMS messages from a Google Voice Voicemail. + * Get all of the SMS messages in a Google Voice inbox. + * @return array Array of message objects */ - public function getUnreadVoicemail() { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - 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); - - // Load the "wrapper" xml (contains two elements, json and html) - $dom = new DOMDocument(); - $dom->loadXML($xml); - $json = $dom->documentElement->getElementsByTagName("json")->item(0)->nodeValue; - $json = json_decode($json); - - // Loop through all of the messages. - $results = array(); - foreach($json->messages as $mid=>$convo) { - if($convo->isRead == FALSE) { - $results[] = $convo; - } - } - return $results; + public function getAllSMS() + { + return $this->_getAndParse($this->_serverPath['getSMS'], null); } - - /** - * Get all of the unread SMS messages from a Google Voice Voicemail. + * Get all of the unread SMS messages in a Google Voice inbox. + * @return array Array of message objects */ - public function getReadVoicemail() { - // Login to the service if not already done. - $this->_logIn(); + public function getNewSMS() + { + // I don't understand what xenth was doing here, and the function + // did not work for me in testing, so I am replacing this function + // call with getUread() - // Send HTTP POST request. - 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); - - // load the "wrapper" xml (contains two elements, json and html) - $dom = new DOMDocument(); - $dom->loadXML($xml); - $json = $dom->documentElement->getElementsByTagName("json")->item(0)->nodeValue; - $json = json_decode($json); - - // Loop through all of the messages. - $results = array(); - foreach( $json->messages as $mid=>$convo ) { - if( $convo->isRead == TRUE ) { - $results[] = $convo; - } - } - return $results; + return $this->getUnreadSMS(); } - - /** - * Get MP3 of a Google Voice Voicemail. + * Get all of the read SMS messages in a Google Voice inbox. + * @return array Array of message objects */ - public function getVoicemailMP3($message_id) { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - curl_setopt($this->_ch, CURLOPT_URL, "https://www.google.com/voice/media/send_voicemail/$message_id/"); - curl_setopt($this->_ch, CURLOPT_POST, FALSE); - curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, TRUE); - $results = curl_exec($this->_ch); - - return $results; + public function getReadSMS() + { + // isRead = false + return $this->_getAndParse($this->_serverPath['getSMS'], true); } - - - /** - * Mark a message in a Google Voice Inbox or Voicemail as read. - * @param $message_id The id of the message to update. - * @param $note The message to send within the SMS. + /** + * Get all of the unread SMS messages in a Google Voice inbox. + * @return array Array of message objects */ - public function markMessageRead($message_id) { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - 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' => $message_id, - 'read' => '1' - )); - curl_exec($this->_ch); + public function getUnreadSMS() { + // isRead = false + return $this->_getAndParse($this->_serverPath['getSMS'], false); } - - /** - * Mark a message in a Google Voice Inbox or Voicemail as unread. - * @param $message_id The id of the message to update. - * @param $note The message to send within the SMS. + * Send an SMS to $number containing $message. + * @param $number The 10-digit phone number to send the message to (formatted with parens and hyphens or none). + * @param $message The message to send within the SMS. */ - public function markMessageUnread($message_id) { - // Login to the service if not already done. - $this->_logIn(); - - // Send HTTP POST request. - 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' => $message_id, - 'read' => '0' - )); - curl_exec($this->_ch); + public function sendSMS($number, $message) { + return $this->_post( + $this->_serverPath['sendSMS'], + array( + 'phoneNumber' => $this->_formatNumber($number), + 'text' => $message + ) + ); } - - + + + + /** + * Voicemail methods + */ + + + /** + * Get all of the voicemail messages in a Google Voice inbox. + * @return array Array of message objects + */ + public function getAllVoicemail() + { + return $this->_getAndParse($this->_serverPath['voicemail'], null); + } + /** - * Delete a message or conversation. - * @param $message_id The ID of the conversation to delete. + * Get all of the unread voicemail messages from a Google Voice inbox. + * @return array Array of message objects */ - public function deleteMessage($message_id) { - $this->_logIn(); - - curl_setopt($this->_ch, CURLOPT_URL, 'https://www.google.com/voice/inbox/deleteMessages/'); - curl_setopt($this->_ch, CURLOPT_POST, TRUE); - curl_setopt($this->_ch, CURLOPT_POSTFIELDS, array( - '_rnr_se' => $this->_rnr_se, - 'messages' => $message_id, - 'trash' => 1 - )); - - curl_exec($this->_ch); + public function getUnreadVoicemail() + { + return $this->_getAndParse($this->_serverPath['voicemail'], false); } - - - 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); + /** + * Get all of the read voicemail messages from a Google Voice inbox. + * @return array Array of message objects + */ + public function getReadVoicemail() + { + return $this->_getAndParse($this->_serverPath['voicemail'], true); } /** - * Source from http://www.binarytides.com/php-get-name-and-value-of-all-input-tags-on-a-page-with-domdocument/ - * Generic function to fetch all input tags (name and value) on a page - * Useful when writing automatic login bots/scrapers + * Get MP3 of a Google Voice Voicemail. */ - private function dom_get_input_tags($html) - { - $post_data = array(); - - // a new dom object - $dom = new DomDocument; - - //load the html into the object - @$dom->loadHTML($html); //@suppresses warnings - //discard white space - $dom->preserveWhiteSpace = FALSE; - - //all input tags as a list - $input_tags = $dom->getElementsByTagName('input'); - - //get all rows from the table - for ($i = 0; $i < $input_tags->length; $i++) - { - if( is_object($input_tags->item($i)) ) - { - $name = $value = ''; - $name_o = $input_tags->item($i)->attributes->getNamedItem('name'); - if(is_object($name_o)) - { - $name = $name_o->value; - - $value_o = $input_tags->item($i)->attributes->getNamedItem('value'); - if(is_object($value_o)) - { - $value = $input_tags->item($i)->attributes->getNamedItem('value')->value; - } - - $post_data[$name] = $value; - } - } - } - - return $post_data; + public function getVoicemailMP3($messageId) + { + return $this->_get($this->_serverPath['getMP3'] . $messageId . '/'); } -} + public function searchNumber($phoneNumber) + { + return $this->_post( + $this->_serverPath['search'], + array( + 'q' => $this->_formatNumber($phoneNumber) + ) +// 'v' => 82547960 + ); + } -?> +} diff --git a/README.md b/README.md index ea4cb19..828ab9d 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,65 @@ mark a message or voicemail as read or unread, and download transcriptions and/o MP3 files of voicemail. Feel free to implement new functionality and send me your changes so I can incorporate them into this library! -getUnreadSMS, getReadSMS, getUnreadVoicemail, and getReadVoicemail all return -an array of JSON objects. Each object has the following attributes, example -values included: + +Interactive Testing Dashboard +============================= +Use gvTest.php to test all of the implemented methods (DO NOT UPLOAD gvTest.php +TO A PUBLIC WEB SERVER.) It is a great way to familiarize yourself with what +the "message" objects look like and to discover new fields that Google may +introduce. Also, if the script breaks, gvTest makes it easier to track down +a reason. + +General messaging methods +========================= +These methods can be used to affect all types of messages including voicemail, +SMS and missed calls: + + addNote($messageId, $note) + archive($messageId) + delete($messageId) + getInbox() + markRead($messageId) + markUnread($messageId) + removeNote($messageId) + unArchive($messageId) + +"Call" Methods +============== +These methods are related to "calls" only: + + callNumber($number, $from_number, $phone_type = 'mobile') + cancelCall($number, $from_number, $phone_type = 'mobile') + getMissedCalls() + +SMS methods +=========== +Methods that act on SMS messages: + + getAllSMS() + getNewSMS() + getReadSMS() + getUnreadSMS() + sendSMS($number, $message) + +Voicemail methods +================= + getAllVoicemail() + getReadVoicemail() + getUnreadVoicemail() + getVoicemailMP3($messageId) + +Methods that don't work +======================= + searchNumber($phoneNumber) + +Debugging Methods +================= + dom_dump($obj) + getVals() + +The "get" methods above all return an array of JSON "message" objects. Each object +has the following attributes, example values included: $msg->id = c3716aa447a19c7e2e7347f443dd29091401ae13 $msg->phoneNumber = +15555555555 @@ -32,9 +88,6 @@ values included: $msg->type = 11 $msg->children = -Note: Receiving SMSs and voicemails is mostly unnecessary via this API since -Google now allows SMSs to be forwarded to an email address. It is a better -idea to parse those incoming emails with a script. SMS and Voice Integration ========================= @@ -60,4 +113,3 @@ Copyright 2009 by Aaron Parecki [http://aaronparecki.com](http://aaronparecki.com) See LICENSE - diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..9b95bbf --- /dev/null +++ b/autoload.php @@ -0,0 +1,33 @@ +searchNumber($number); + $function = "searchNumber('$number')"; + break; + case 'sendText' : + $number = $_GET['number']; + $message = $_GET['message']; + $results = $gv->sendSMS($number, $message); + $function = "sendSMS('$number', '$message')"; + break; + case 'callNumber' : + $results = $gv->callNumber($_GET['numberToCall'], $_GET['numberFrom']); + $function = "callNumber('{$_GET['numberToCall']}', '{$_GET['numberFrom']}')"; + break; + case 'getMissedCalls' : + $results = $gv->getMissedCalls(); + $function = "getMissedCalls()"; + break; + case 'getInbox' : + $results = $gv->getInbox(); + $function = "getInbox()"; + break; + case 'getSms' : + switch ($_GET['scope']) { + case 'new': + $results = $gv->getNewSMS(); + $function = "getNewSMS()"; + break; + case 'all': + $results = $gv->getAllSMS(); + $function = "getAllSMS()"; + break; + case 'read': + $results = $gv->getReadSMS(); + $function = "getReadSMS()"; + break; + case 'unread': + $results = $gv->getUnreadSMS(); + $function = "getUnreadSMS()"; + break; + } + break; + case 'getVoicemail' : + switch ($_GET['scope']) { + case 'all': + $results = $gv->getAllVoicemail(); + $function = "getAllVoicemail()"; + break; + case 'read': + $results = $gv->getReadVoicemail(); + $function = "getRedVoicemail()"; + break; + case 'unread': + $results = $gv->getUnreadVoicemail(); + $function = "getUnreadVoicemail()"; + break; + } + break; + case 'addNote' : + $results = $gv->addNote($_GET['messageId'], $_GET['note']); + $function = "addNote('{$_GET['messageId']}', '{$_GET['note']}')"; + break; + case 'removeNote' : + $results = $gv->removeNote($_GET['messageId']); + $function = "removeNote('{$_GET['messageId']}')"; + break; + case 'markRead' : + $results = $gv->markRead($_GET['messageId']); + $function = "markRead('{$_GET['messageId']}')"; + break; + case 'markUnread' : + $results = $gv->markUnread($_GET['messageId']); + $function = "markUnread('{$_GET['messageId']}')"; + break; + case 'archive' : + $results = $gv->archive($_GET['messageId']); + $function = "archive('{$_GET['messageId']}')"; + break; + case 'unArchive' : + $results = $gv->unArchive($_GET['messageId']); + $function = "unArchive('{$_GET['messageId']}')"; + break; + case 'delete' : + $results = $gv->delete($_GET['messageId']); + $function = "delete('{$_GET['messageId']}')"; + break; + } + } catch (\Exception $e) { + $results = $e->getMessage(); + } + // +// KLGHWKYHJOLVJZZJQWLVSZNHPVJWVQPWHWOXWTPL +//cancelCall($number, $from_number, $phone_type = 'mobile') +//getVoicemailMP3($messageId) +} +?> +

Google Voice Testing Dashboard

+

Google Account UserName:

+

Google Voice Phone Number:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Get Voicemail

+
+ + All
+ Read
+ Unread
+ +
+
+

Get Missed Calls

+
+ + +
+
+

Get Inbox

+
+ + +
+
+

Search for a number

+

*** Does not work ***

+
+ + + +
+ +
+
+

Send a text

+
+ + + +
+ + +
+ +
+
+

Call Number

+
+ + + +
+ + +
+ +
+
+

Add a note

+
+ + + +
+ + +
+ +
+
+

Remove Note

+
+ + + +
+ +
+
+

Get SMS

+
+ + New (same as unread)
+ All
+ Read
+ Unread
+ +
+
+
+

Mark Message as Read

+
+ + + +
+ +
+
+

Mark Message as Unread

+
+ + + +
+ +
+
+

Delete Message

+
+ + + +
+ +
+
+

Archive Message

+
+ + + +
+ +
+
+

Un-Archive Message

+
+ + + +
+ +
+
+
+

+ These functions are not implemented in the test suite yet: +

+
+        cancelCall($number, $from_number, $phone_type = 'mobile')
+        getVoicemailMP3($messageId)
+      
+
+ +
+

Results from actions

+Function used to obtain results:
GoogleVoice::$function

"; +} + +if ($results === null) { + echo '

No results

'; +} else { + echo '
'.print_r($results, true).'
'; +} +?> +
+
+

Debug Results

+No results'; +} else { + $vals = $gv->getVals(); + echo "
"
+    ."Curl Url: $vals->curlUrl\n"
+    ."Curl Options: ".print_r($vals->curlOptions, true)."\n"
+    ."Raw server result: ".print_r(htmlspecialchars($vals->result), true)
+    ."
"; +// echo 'Results display disabled.'; +} +?> +