| <?php | |
| require_once 'protocol.class.php'; | |
| require_once 'BinTreeNodeReader.php'; | |
| require_once 'BinTreeNodeWriter.php'; | |
| require_once 'Constants.php'; | |
| require_once 'func.php'; | |
| require_once 'token.php'; | |
| require_once 'rc4.php'; | |
| require_once 'mediauploader.php'; | |
| require_once 'keystream.class.php'; | |
| require_once 'tokenmap.class.php'; | |
| require_once 'events/WhatsApiEventsManager.php'; | |
| require_once 'SqliteMessageStore.php'; | |
| class SyncResult | |
| { | |
| public $index; | |
| public $syncId; | |
| /** @var array $existing */ | |
| public $existing; | |
| /** @var array $nonExisting */ | |
| public $nonExisting; | |
| public function __construct($index, $syncId, $existing, $nonExisting) | |
| { | |
| $this->index = $index; | |
| $this->syncId = $syncId; | |
| $this->existing = $existing; | |
| $this->nonExisting = $nonExisting; | |
| } | |
| } | |
| class WhatsProt | |
| { | |
| /** | |
| * Property declarations. | |
| */ | |
| protected $accountInfo; // The AccountInfo object. | |
| protected $challengeFilename; // Path to nextChallenge.dat. | |
| protected $challengeData; // | |
| protected $debug; // Determines whether debug mode is on or off. | |
| protected $event; // An instance of the WhatsApiEvent Manager. | |
| protected $groupList = array(); // An array with all the groups a user belongs in. | |
| protected $identity; // The Device Identity token. Obtained during registration with this API or using Missvenom to sniff from your phone. | |
| protected $inputKey; // Instances of the KeyStream class. | |
| protected $outputKey; // Instances of the KeyStream class. | |
| protected $groupId = false; // Id of the group created. | |
| protected $lastId = false; // Id to the last message sent. | |
| protected $loginStatus; // Holds the login status. | |
| protected $mediaFileInfo = array(); // Media File Information | |
| protected $mediaQueue = array(); // Queue for media message nodes | |
| protected $messageCounter = 1; // Message counter for auto-id. | |
| protected $messageQueue = array(); // Queue for received messages. | |
| protected $name; // The user name. | |
| protected $newMsgBind = false; // | |
| protected $outQueue = array(); // Queue for outgoing messages. | |
| protected $password; // The user password. | |
| protected $phoneNumber; // The user phone number including the country code without '+' or '00'. | |
| protected $serverReceivedId; // Confirm that the *server* has received your command. | |
| protected $socket; // A socket to connect to the WhatsApp network. | |
| protected $writer; // An instance of the BinaryTreeNodeWriter class. | |
| protected $messageStore; | |
| protected $nodeId = array(); | |
| protected $loginTime; | |
| public $reader; // An instance of the BinaryTreeNodeReader class. | |
| /** | |
| * Default class constructor. | |
| * | |
| * @param string $number | |
| * The user phone number including the country code without '+' or '00'. | |
| * @param string $identity | |
| * The Device Identity token. Obtained during registration with this API | |
| * or using Missvenom to sniff from your phone. | |
| * @param string $nickname | |
| * The user name. | |
| * @param $debug | |
| * Debug on or off, false by default. | |
| */ | |
| public function __construct($number, $nickname, $debug = false) | |
| { | |
| $this->writer = new BinTreeNodeWriter(); | |
| $this->reader = new BinTreeNodeReader(); | |
| $this->debug = $debug; | |
| $this->phoneNumber = $number; | |
| //e.g. ./cache/nextChallenge.12125557788.dat | |
| $this->challengeFilename = sprintf('%s%s%snextChallenge.%s.dat', | |
| __DIR__, | |
| DIRECTORY_SEPARATOR, | |
| Constants::DATA_FOLDER . DIRECTORY_SEPARATOR, | |
| $number); | |
| $this->identity = $this->buildIdentity(); | |
| $this->name = $nickname; | |
| $this->loginStatus = Constants::DISCONNECTED_STATUS; | |
| $this->eventManager = new WhatsApiEventsManager(); | |
| } | |
| /** | |
| * If you need use different challenge fileName you can use this | |
| * | |
| * @param string $filename | |
| */ | |
| public function setChallengeName($filename) | |
| { | |
| $this->challengeFilename = $filename; | |
| } | |
| /** | |
| * Add message to the outgoing queue. | |
| * | |
| * @param $node | |
| */ | |
| public function addMsgOutQueue($node) | |
| { | |
| $this->outQueue[] = $node; | |
| } | |
| /** | |
| * Check if account credentials are valid. | |
| * | |
| * WARNING: WhatsApp now changes your password everytime you use this. | |
| * Make sure you update your config file if the output informs about | |
| * a password change. | |
| * | |
| * @return object | |
| * An object with server response. | |
| * - status: Account status. | |
| * - login: Phone number with country code. | |
| * - pw: Account password. | |
| * - type: Type of account. | |
| * - expiration: Expiration date in UNIX TimeStamp. | |
| * - kind: Kind of account. | |
| * - price: Formatted price of account. | |
| * - cost: Decimal amount of account. | |
| * - currency: Currency price of account. | |
| * - price_expiration: Price expiration in UNIX TimeStamp. | |
| * | |
| * @throws Exception | |
| */ | |
| public function checkCredentials() | |
| { | |
| if (!$phone = $this->dissectPhone()) { | |
| throw new Exception('The provided phone number is not valid.'); | |
| } | |
| $countryCode = ($phone['ISO3166'] != '') ? $phone['ISO3166'] : 'US'; | |
| $langCode = ($phone['ISO639'] != '') ? $phone['ISO639'] : 'en'; | |
| if ($phone['cc'] == '77' || $phone['cc'] == '79') { | |
| $phone['cc'] = '7'; | |
| } | |
| // Build the url. | |
| $host = 'https://' . Constants::WHATSAPP_CHECK_HOST; | |
| $query = array( | |
| 'cc' => $phone['cc'], | |
| 'in' => $phone['phone'], | |
| 'id' => $this->identity, | |
| 'lg' => $langCode, | |
| 'lc' => $countryCode, | |
| // 'network_radio_type' => "1" | |
| ); | |
| $response = $this->getResponse($host, $query); | |
| if ($response->status != 'ok') { | |
| $this->eventManager()->fire("onCredentialsBad", | |
| array( | |
| $this->phoneNumber, | |
| $response->status, | |
| $response->reason | |
| )); | |
| $this->debugPrint($query); | |
| $this->debugPrint($response); | |
| throw new Exception('There was a problem trying to request the code.'); | |
| } else { | |
| $this->eventManager()->fire("onCredentialsGood", | |
| array( | |
| $this->phoneNumber, | |
| $response->login, | |
| $response->pw, | |
| $response->type, | |
| $response->expiration, | |
| $response->kind, | |
| $response->price, | |
| $response->cost, | |
| $response->currency, | |
| $response->price_expiration | |
| )); | |
| } | |
| return $response; | |
| } | |
| /** | |
| * Register account on WhatsApp using the provided code. | |
| * | |
| * @param integer $code | |
| * Numeric code value provided on requestCode(). | |
| * | |
| * @return object | |
| * An object with server response. | |
| * - status: Account status. | |
| * - login: Phone number with country code. | |
| * - pw: Account password. | |
| * - type: Type of account. | |
| * - expiration: Expiration date in UNIX TimeStamp. | |
| * - kind: Kind of account. | |
| * - price: Formatted price of account. | |
| * - cost: Decimal amount of account. | |
| * - currency: Currency price of account. | |
| * - price_expiration: Price expiration in UNIX TimeStamp. | |
| * | |
| * @throws Exception | |
| */ | |
| public function codeRegister($code) | |
| { | |
| if (!$phone = $this->dissectPhone()) { | |
| throw new Exception('The provided phone number is not valid.'); | |
| } | |
| //$countryCode = ($phone['ISO3166'] != '') ? $phone['ISO3166'] : 'US'; | |
| //$langCode = ($phone['ISO639'] != '') ? $phone['ISO639'] : 'en'; | |
| // Build the url. | |
| $host = 'https://' . Constants::WHATSAPP_REGISTER_HOST; | |
| $query = array( | |
| 'cc' => $phone['cc'], | |
| 'in' => $phone['phone'], | |
| 'id' => $this->identity, | |
| 'code' => $code, | |
| //'lg' => $langCode, | |
| //'lc' => $countryCode, | |
| //'network_radio_type' => "1" | |
| ); | |
| $response = $this->getResponse($host, $query); | |
| if ($response->status != 'ok') { | |
| $this->eventManager()->fire("onCodeRegisterFailed", | |
| array( | |
| $this->phoneNumber, | |
| $response->status, | |
| $response->reason, | |
| $response->retry_after | |
| )); | |
| $this->debugPrint($query); | |
| $this->debugPrint($response); | |
| throw new Exception('An error occurred registering the registration code from WhatsApp.'); | |
| } else { | |
| $this->eventManager()->fire("onCodeRegister", | |
| array( | |
| $this->phoneNumber, | |
| $response->login, | |
| $response->pw, | |
| $response->type, | |
| $response->expiration, | |
| $response->kind, | |
| $response->price, | |
| $response->cost, | |
| $response->currency, | |
| $response->price_expiration | |
| )); | |
| } | |
| return $response; | |
| } | |
| /** | |
| * Request a registration code from WhatsApp. | |
| * | |
| * @param string $method Accepts only 'sms' or 'voice' as a value. | |
| * @param string $carrier | |
| * | |
| * @return object | |
| * An object with server response. | |
| * - status: Status of the request (sent/fail). | |
| * - length: Registration code lenght. | |
| * - method: Used method. | |
| * - reason: Reason of the status (e.g. too_recent/missing_param/bad_param). | |
| * - param: The missing_param/bad_param. | |
| * - retry_after: Waiting time before requesting a new code. | |
| * | |
| * @throws Exception | |
| */ | |
| public function codeRequest($method = 'sms', $carrier = "T-Mobile5") | |
| { | |
| if (!$phone = $this->dissectPhone()) { | |
| throw new Exception('The provided phone number is not valid.'); | |
| } | |
| $countryCode = ($phone['ISO3166'] != '') ? $phone['ISO3166'] : 'US'; | |
| $langCode = ($phone['ISO639'] != '') ? $phone['ISO639'] : 'en'; | |
| if ($carrier != null) { | |
| $mnc = $this->detectMnc(strtolower($countryCode), $carrier); | |
| } else { | |
| $mnc = $phone['mnc']; | |
| } | |
| // Build the token. | |
| $token = generateRequestToken($phone['country'], $phone['phone']); | |
| // Build the url. | |
| $host = 'https://' . Constants::WHATSAPP_REQUEST_HOST; | |
| $query = array( | |
| 'in' => $phone['phone'], | |
| 'cc' => $phone['cc'], | |
| 'id' => $this->identity, | |
| 'lg' => $langCode, | |
| 'lc' => $countryCode, | |
| //'mcc' => '000', | |
| //'mnc' => '000', | |
| 'sim_mcc' => $phone['mcc'], | |
| 'sim_mnc' => $mnc, | |
| 'method' => $method, | |
| //'reason' => "self-send-jailbroken", | |
| 'token' => $token, | |
| //'network_radio_type' => "1" | |
| ); | |
| $this->debugPrint($query); | |
| $response = $this->getResponse($host, $query); | |
| $this->debugPrint($response); | |
| if ($response->status == 'ok') { | |
| $this->eventManager()->fire("onCodeRegister", | |
| array( | |
| $this->phoneNumber, | |
| $response->login, | |
| $response->pw, | |
| $response->type, | |
| $response->expiration, | |
| $response->kind, | |
| $response->price, | |
| $response->cost, | |
| $response->currency, | |
| $response->price_expiration | |
| )); | |
| } else if ($response->status != 'sent') { | |
| if (isset($response->reason) && $response->reason == "too_recent") { | |
| $this->eventManager()->fire("onCodeRequestFailedTooRecent", | |
| array( | |
| $this->phoneNumber, | |
| $method, | |
| $response->reason, | |
| $response->retry_after | |
| )); | |
| $minutes = round($response->retry_after / 60); | |
| throw new Exception("Code already sent. Retry after $minutes minutes."); | |
| } else if (isset($response->reason) && $response->reason == "too_many_guesses") { | |
| $this->eventManager()->fire("onCodeRequestFailedTooManyGuesses", | |
| array( | |
| $this->phoneNumber, | |
| $method, | |
| $response->reason, | |
| $response->retry_after | |
| )); | |
| $minutes = round($response->retry_after / 60); | |
| throw new Exception("Too many guesses. Retry after $minutes minutes."); | |
| } else { | |
| $this->eventManager()->fire("onCodeRequestFailed", | |
| array( | |
| $this->phoneNumber, | |
| $method, | |
| $response->reason, | |
| isset($response->param) ? $response->param : NULL | |
| )); | |
| throw new Exception('There was a problem trying to request the code.'); | |
| } | |
| } else { | |
| $this->eventManager()->fire("onCodeRequest", | |
| array( | |
| $this->phoneNumber, | |
| $method, | |
| $response->length | |
| )); | |
| } | |
| return $response; | |
| } | |
| /** | |
| * Connect (create a socket) to the WhatsApp network. | |
| * | |
| * @return bool | |
| */ | |
| public function connect() | |
| { | |
| if ($this->isConnected()) { | |
| return true; | |
| } | |
| //$WAData = json_decode(file_get_contents(Constants::WHATSAPP_VER_CHECKER), true); | |
| // if(Constants::WHATSAPP_VER != $WAver) | |
| // { | |
| // updateData('token.php', $WAData->e, $WAData->h); | |
| // updateData('whatsprot.class.php', $WAData->e); | |
| // } | |
| /* Create a TCP/IP socket. */ | |
| $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); | |
| if ($socket !== false) { | |
| $result = socket_connect($socket, "e" . rand(1, 16) . ".whatsapp.net", Constants::PORT); | |
| if ($result === false) { | |
| $socket = false; | |
| } | |
| } | |
| if ($socket !== false) { | |
| socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => Constants::TIMEOUT_SEC, 'usec' => Constants::TIMEOUT_USEC)); | |
| socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => Constants::TIMEOUT_SEC, 'usec' => Constants::TIMEOUT_USEC)); | |
| $this->socket = $socket; | |
| $this->eventManager()->fire("onConnect", | |
| array( | |
| $this->phoneNumber, | |
| $this->socket | |
| ) | |
| ); | |
| return true; | |
| } else { | |
| $this->eventManager()->fire("onConnectError", | |
| array( | |
| $this->phoneNumber, | |
| $this->socket | |
| ) | |
| ); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Do we have an active socket connection to WhatsApp? | |
| * | |
| * @return bool | |
| */ | |
| public function isConnected() | |
| { | |
| return ($this->socket !== null); | |
| } | |
| /** | |
| * Disconnect from the WhatsApp network. | |
| */ | |
| public function disconnect() | |
| { | |
| if (is_resource($this->socket)) { | |
| @socket_shutdown($this->socket, 2); | |
| @socket_close($this->socket); | |
| $this->socket = null; | |
| $this->loginStatus = Constants::DISCONNECTED_STATUS; | |
| $this->eventManager()->fire("onDisconnect", | |
| array( | |
| $this->phoneNumber, | |
| $this->socket | |
| ) | |
| ); | |
| } | |
| } | |
| /** | |
| * @return WhatsApiEventsManager | |
| */ | |
| public function eventManager() | |
| { | |
| return $this->eventManager; | |
| } | |
| /** | |
| * Drain the message queue for application processing. | |
| * | |
| * @return ProtocolNode[] | |
| * Return the message queue list. | |
| */ | |
| public function getMessages() | |
| { | |
| $ret = $this->messageQueue; | |
| $this->messageQueue = array(); | |
| return $ret; | |
| } | |
| /** | |
| * Log into the WhatsApp server. | |
| * | |
| * ###Warning### using this method will generate a new password | |
| * from the WhatsApp servers each time. | |
| * | |
| * If you know your password and wish to use it without generating | |
| * a new password - use the loginWithPassword() method instead. | |
| */ | |
| public function login() | |
| { | |
| $this->accountInfo = (array) $this->checkCredentials(); | |
| if ($this->accountInfo['status'] == 'ok') { | |
| $this->debugPrint("New password received: " . $this->accountInfo['pw'] . "\n"); | |
| $this->password = $this->accountInfo['pw']; | |
| } | |
| $this->doLogin(); | |
| } | |
| /** | |
| * Login to the WhatsApp server with your password | |
| * | |
| * If you already know your password you can log into the Whatsapp server | |
| * using this method. | |
| * | |
| * @param string $password Your whatsapp password. You must already know this! | |
| */ | |
| public function loginWithPassword($password) | |
| { | |
| $this->password = $password; | |
| if (is_readable($this->challengeFilename)) { | |
| $challengeData = file_get_contents($this->challengeFilename); | |
| if ($challengeData) { | |
| $this->challengeData = $challengeData; | |
| } | |
| } | |
| $this->doLogin(); | |
| } | |
| /** | |
| * Fetch a single message node | |
| * @param bool $autoReceipt | |
| * @param string $type | |
| * @return bool | |
| * | |
| * @throws Exception | |
| */ | |
| public function pollMessage($autoReceipt = true, $type = "read") | |
| { | |
| if (!$this->isConnected()) { | |
| throw new ConnectionException('Connection Closed!'); | |
| } | |
| $r = array($this->socket); | |
| $w = array(); | |
| $e = array(); | |
| if (socket_select($r, $w, $e, Constants::TIMEOUT_SEC, Constants::TIMEOUT_USEC)) { | |
| // Something to read | |
| if ($stanza = $this->readStanza()) { | |
| $this->processInboundData($stanza, $autoReceipt, $type); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Send the active status. User will show up as "Online" (as long as socket is connected). | |
| */ | |
| public function sendActiveStatus() | |
| { | |
| $messageNode = new ProtocolNode("presence", array("type" => "active"), null, ""); | |
| $this->sendNode($messageNode); | |
| } | |
| /** | |
| * Send a request to get cipher keys from an user | |
| * | |
| * @param $number | |
| * Phone number of the user you want to get the cipher keys. | |
| */ | |
| public function sendGetCipherKeysFromUser($number) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $userNode = new ProtocolNode("user", | |
| array( | |
| "jid" => $this->getJID($number) | |
| ), null, null); | |
| $keyNode = new ProtocolNode("key", null, array($userNode), null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "encrypt", | |
| "type" => "get", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($keyNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send a Broadcast Message with audio. | |
| * | |
| * The recipients MUST have your number (synced) and in their contact list | |
| * otherwise the message will not deliver to that person. | |
| * | |
| * Approx 20 (unverified) is the maximum number of targets | |
| * | |
| * @param array $targets An array of numbers to send to. | |
| * @param string $path URL or local path to the audio file to send | |
| * @param bool $storeURLmedia Keep a copy of the audio file on your server | |
| * @param int $fsize | |
| * @param string $fhash | |
| * @return string|null Message ID if successfully, null if not. | |
| */ | |
| public function sendBroadcastAudio($targets, $path, $storeURLmedia = false, $fsize = 0, $fhash = "") | |
| { | |
| if (!is_array($targets)) { | |
| $targets = array($targets); | |
| } | |
| // Return message ID. Make pull request for this. | |
| return $this->sendMessageAudio($targets, $path, $storeURLmedia, $fsize, $fhash); | |
| } | |
| /** | |
| * Send a Broadcast Message with an image. | |
| * | |
| * The recipients MUST have your number (synced) and in their contact list | |
| * otherwise the message will not deliver to that person. | |
| * | |
| * Approx 20 (unverified) is the maximum number of targets | |
| * | |
| * @param array $targets An array of numbers to send to. | |
| * @param string $path URL or local path to the image file to send | |
| * @param bool $storeURLmedia Keep a copy of the audio file on your server | |
| * @param int $fsize | |
| * @param string $fhash | |
| * @param string $caption | |
| * @return string|null Message ID if successfully, null if not. | |
| */ | |
| public function sendBroadcastImage($targets, $path, $storeURLmedia = false, $fsize = 0, $fhash = "", $caption = "") | |
| { | |
| if (!is_array($targets)) { | |
| $targets = array($targets); | |
| } | |
| // Return message ID. Make pull request for this. | |
| return $this->sendMessageImage($targets, $path, $storeURLmedia, $fsize, $fhash, $caption); | |
| } | |
| /** | |
| * Send a Broadcast Message with location data. | |
| * | |
| * The recipients MUST have your number (synced) and in their contact list | |
| * otherwise the message will not deliver to that person. | |
| * | |
| * If no name is supplied , receiver will see large sized google map | |
| * thumbnail of entered Lat/Long but NO name/url for location. | |
| * | |
| * With name supplied, a combined map thumbnail/name box is displayed | |
| * Approx 20 (unverified) is the maximum number of targets | |
| * | |
| * @param array $targets An array of numbers to send to. | |
| * @param float $long The longitude of the location eg 54.31652 | |
| * @param float $lat The latitude if the location eg -6.833496 | |
| * @param string $name (Optional) A name to describe the location | |
| * @param string $url (Optional) A URL to link location to web resource | |
| * @return string Message ID | |
| */ | |
| public function sendBroadcastLocation($targets, $long, $lat, $name = null, $url = null) | |
| { | |
| if (!is_array($targets)) { | |
| $targets = array($targets); | |
| } | |
| // Return message ID. Make pull request for this. | |
| return $this->sendMessageLocation($targets, $long, $lat, $name, $url); | |
| } | |
| /** | |
| * Send a Broadcast Message | |
| * | |
| * The recipients MUST have your number (synced) and in their contact list | |
| * otherwise the message will not deliver to that person. | |
| * | |
| * Approx 20 (unverified) is the maximum number of targets | |
| * | |
| * @param array $targets An array of numbers to send to. | |
| * @param string $message Your message | |
| * @return string Message ID | |
| */ | |
| public function sendBroadcastMessage($targets, $message) | |
| { | |
| $message = $this->parseMessageForEmojis($message); | |
| $bodyNode = new ProtocolNode("body", null, null, $message); | |
| // Return message ID. Make pull request for this. | |
| return $this->sendBroadcast($targets, $bodyNode, "text"); | |
| } | |
| /** | |
| * Send a Broadcast Message with a video. | |
| * | |
| * The recipients MUST have your number (synced) and in their contact list | |
| * otherwise the message will not deliver to that person. | |
| * | |
| * Approx 20 (unverified) is the maximum number of targets | |
| * | |
| * @param array $targets An array of numbers to send to. | |
| * @param string $path URL or local path to the video file to send | |
| * @param bool $storeURLmedia Keep a copy of the audio file on your server | |
| * @param int $fsize | |
| * @param string $fhash | |
| * @param string $caption | |
| * @return string|null Message ID if successfully, null if not. | |
| */ | |
| public function sendBroadcastVideo($targets, $path, $storeURLmedia = false, $fsize = 0, $fhash = "", $caption = "") | |
| { | |
| if (!is_array($targets)) { | |
| $targets = array($targets); | |
| } | |
| // Return message ID. Make pull request for this. | |
| return $this->sendMessageVideo($targets, $path, $storeURLmedia, $fsize, $fhash, $caption); | |
| } | |
| /** | |
| * Delete Broadcast lists | |
| * | |
| * @param string array $lists | |
| * Contains the broadcast-id list | |
| */ | |
| public function sendDeleteBroadcastLists($lists) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $listNode = array(); | |
| if ($lists != null && count($lists) > 0) { | |
| for ($i = 0; $i < count($lists); $i++) { | |
| $listNode[$i] = new ProtocolNode("list", array("id" => $lists[$i]), null, null); | |
| } | |
| } else { | |
| $listNode = null; | |
| } | |
| $deleteNode = new ProtocolNode("delete", null, $listNode, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:b", | |
| "type" => "set", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($deleteNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Clears the "dirty" status on your account | |
| * | |
| * @param array $categories | |
| */ | |
| protected function sendClearDirty($categories) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $catnodes = array(); | |
| foreach ($categories as $category) { | |
| $catnode = new ProtocolNode("clean", array("type" => $category), null, null); | |
| $catnodes[] = $catnode; | |
| } | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "type" => "set", | |
| "to" => Constants::WHATSAPP_SERVER, | |
| "xmlns" => "urn:xmpp:whatsapp:dirty" | |
| ), $catnodes, null); | |
| $this->sendNode($node); | |
| } | |
| public function sendClientConfig() | |
| { | |
| $attr = array(); | |
| $attr["platform"] = Constants::WHATSAPP_DEVICE; | |
| $attr["version"] = Constants::WHATSAPP_VER; | |
| $child = new ProtocolNode("config", $attr, null, ""); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $this->createMsgId(), | |
| "type" => "set", | |
| "xmlns" => "urn:xmpp:whatsapp:push", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($child), null); | |
| $this->sendNode($node); | |
| } | |
| public function sendGetClientConfig() | |
| { | |
| $msgId = $this->createMsgId(); | |
| $child = new ProtocolNode("config", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "urn:xmpp:whatsapp:push", | |
| "type" => "get", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($child), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Transfer your number to new one | |
| * | |
| * @param string $number | |
| * @param string $identity | |
| */ | |
| public function sendChangeNumber($number, $identity) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $usernameNode = new ProtocolNode("username", null, null, $number); | |
| $passwordNode = new ProtocolNode("password", null, null, urldecode($identity)); | |
| $modifyNode = new ProtocolNode("modify", null, array($usernameNode, $passwordNode), null); | |
| $iqNode = new ProtocolNode("iq", | |
| array( | |
| "xmlns" => "urn:xmpp:whatsapp:account", | |
| "id" => $msgId, | |
| "type" => "get", | |
| "to" => "c.us" | |
| ), array($modifyNode), null); | |
| $this->sendNode($iqNode); | |
| } | |
| /** | |
| * Send a request to return a list of groups user is currently participating in. | |
| * | |
| * To capture this list you will need to bind the "onGetGroups" event. | |
| */ | |
| public function sendGetGroups() | |
| { | |
| $this->sendGetGroupsFiltered("participating"); | |
| } | |
| /** | |
| * Send a request to get new Groups V2 info. | |
| * | |
| * @param $groupID | |
| * The group JID | |
| */ | |
| public function sendGetGroupV2Info($groupID) | |
| { | |
| $msgId = $this->nodeId['get_groupv2_info'] = $this->createMsgId(); | |
| $queryNode = new ProtocolNode("query", | |
| array( | |
| "request" => "interactive" | |
| ), null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:g2", | |
| "type" => "get", | |
| "to" => $this->getJID($groupID) | |
| ), array($queryNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send a request to get a list of people you have currently blocked. | |
| */ | |
| public function sendGetPrivacyBlockedList() | |
| { | |
| $msgId = $this->nodeId['privacy'] = $this->createMsgId(); | |
| $child = new ProtocolNode("list", | |
| array( | |
| "name" => "default" | |
| ), null, null); | |
| $child2 = new ProtocolNode("query", array(), array($child), null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "jabber:iq:privacy", | |
| "type" => "get" | |
| ), array($child2), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Send a request to get privacy settings. | |
| */ | |
| public function sendGetPrivacySettings() | |
| { | |
| $msgId = $this->createMsgId(); | |
| $privacyNode = new ProtocolNode("privacy", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "to" => Constants::WHATSAPP_SERVER, | |
| "id" => $msgId, | |
| "xmlns" => "privacy", | |
| "type" => "get" | |
| ), array($privacyNode), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Set privacy of 'last seen', status or profile picture to all, contacts or none. | |
| * | |
| * @param string $category | |
| * Options: 'last', 'status' or 'profile' | |
| * @param string $value | |
| * Options: 'all', 'contacts' or 'none' | |
| */ | |
| public function sendSetPrivacySettings($category, $value) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $categoryNode = new ProtocolNode("category", | |
| array( | |
| "name" => $category, | |
| "value" => $value | |
| ), null, null); | |
| $privacyNode = new ProtocolNode("privacy", null, array($categoryNode), null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "to" => Constants::WHATSAPP_SERVER, | |
| "type" => "set", | |
| "id" => $msgId, | |
| "xmlns" => "privacy" | |
| ), array($privacyNode), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Get profile picture of specified user. | |
| * | |
| * @param string $number | |
| * Number or JID of user | |
| * @param bool $large | |
| * Request large picture | |
| */ | |
| public function sendGetProfilePicture($number, $large = false) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $hash = array(); | |
| $hash["type"] = "image"; | |
| if (!$large) { | |
| $hash["type"] = "preview"; | |
| } | |
| $picture = new ProtocolNode("picture", $hash, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "type" => "get", | |
| "xmlns" => "w:profile:picture", | |
| "to" => $this->getJID($number) | |
| ), array($picture), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * @param mixed $numbers Numbers to get profile profile photos of. | |
| * @return bool | |
| */ | |
| public function sendGetProfilePhotoIds($numbers) | |
| { | |
| if (!is_array($numbers)) { | |
| $numbers = array($numbers); | |
| } | |
| $msgId = $this->createMsgId(); | |
| $userNode = array(); | |
| for ($i=0; $i < count($numbers); $i++) { | |
| $userNode[$i] = new ProtocolNode("user", | |
| array( | |
| "jid" => $this->getJID($numbers[$i]) | |
| ), null, null); | |
| } | |
| if (!sizeof($userNode)) { | |
| return false; | |
| } | |
| $listNode = new ProtocolNode("list", null, $userNode, null); | |
| $iqNode = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:profile:picture", | |
| "type" => "get" | |
| ), array($listNode), null); | |
| $this->sendNode($iqNode); | |
| return true; | |
| } | |
| /** | |
| * Request to retrieve the last online time of specific user. | |
| * | |
| * @param string $to Number or JID of user | |
| */ | |
| public function sendGetRequestLastSeen($to) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $queryNode = new ProtocolNode("query", null, null, null); | |
| $messageNode = new ProtocolNode("iq", | |
| array( | |
| "to" => $this->getJID($to), | |
| "type" => "get", | |
| "id" => $msgId, | |
| "xmlns" => "jabber:iq:last" | |
| ), array($queryNode), ""); | |
| $this->sendNode($messageNode); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Send a request to get the current server properties. | |
| */ | |
| public function sendGetServerProperties() | |
| { | |
| $id = $this->createMsgId(); | |
| $child = new ProtocolNode("props", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $id, | |
| "type" => "get", | |
| "xmlns" => "w", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($child), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send a request to get the current service pricing. | |
| * | |
| * @param string $lg | |
| * Language | |
| * @param string $lc | |
| * Country | |
| */ | |
| public function sendGetServicePricing($lg, $lc) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $pricingNode = new ProtocolNode("pricing", | |
| array( | |
| "lg" => $lg, | |
| "lc" => $lc | |
| ), null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "urn:xmpp:whatsapp:account", | |
| "type" => "get", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($pricingNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send a request to extend the account. | |
| */ | |
| public function sendExtendAccount() | |
| { | |
| $msgId = $this->createMsgId(); | |
| $extendingNode = new ProtocolNode("extend", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "urn:xmpp:whatsapp:account", | |
| "type" => "set", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($extendingNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Gets all the broadcast lists for an account. | |
| */ | |
| public function sendGetBroadcastLists() | |
| { | |
| $msgId = $this->nodeId['get_lists'] = $this->createMsgId(); | |
| $listsNode = new ProtocolNode("lists", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:b", | |
| "type" => "get", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($listsNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send a request to get the normalized mobile number representing the JID. | |
| * | |
| * @param string $countryCode Country Code | |
| * @param string $number Mobile Number | |
| */ | |
| public function sendGetNormalizedJid($countryCode, $number) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $ccNode = new ProtocolNode("cc", null, null, $countryCode); | |
| $inNode = new ProtocolNode("in", null, null, $number); | |
| $normalizeNode = new ProtocolNode("normalize", null, array($ccNode, $inNode), null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "urn:xmpp:whatsapp:account", | |
| "type" => "get", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($normalizeNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Removes an account from WhatsApp. | |
| * | |
| * @param string $lg Language | |
| * @param string $lc Country | |
| * @param string $feedback User Feedback | |
| */ | |
| public function sendRemoveAccount($lg = null, $lc = null, $feedback = null) | |
| { | |
| $msgId = $this->createMsgId(); | |
| if ($feedback != null && strlen($feedback) > 0) | |
| { | |
| if ($lg == null) { | |
| $lg = ""; | |
| } | |
| if ($lc == null) { | |
| $lc = ""; | |
| } | |
| $child = new ProtocolNode("body", | |
| array( | |
| "lg" => $lg, | |
| "lc" => $lc | |
| ), null, $feedback); | |
| $childNode = array($child); | |
| } else { | |
| $childNode = null; | |
| } | |
| $removeNode = new ProtocolNode("remove", null, $childNode, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "to" => Constants::WHATSAPP_SERVER, | |
| "xmlns" => "urn:xmpp:whatsapp:account", | |
| "type" => "get", | |
| "id" => $msgId | |
| ), array($removeNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send a ping to the server. | |
| */ | |
| public function sendPing() | |
| { | |
| $msgId = $this->createMsgId(); | |
| $pingNode = new ProtocolNode("ping", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:p", | |
| "type" => "get", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($pingNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Get VOIP information of a number or numbers. | |
| * | |
| * @param mixed $jids | |
| */ | |
| public function sendGetHasVoipEnabled($jids) | |
| { | |
| $msgId = $this->createMsgId(); | |
| if (!is_array($jids)) | |
| { | |
| $jids = array($jids); | |
| } | |
| $userNode = array(); | |
| foreach ($jids as $jid) | |
| { | |
| $userNode[] = new ProtocolNode("user", array('jid' => $this->getJID($jid)), null, null); | |
| } | |
| $eligibleNode = new ProtocolNode("eligible", null, $userNode, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "voip", | |
| "type" => "get", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($eligibleNode), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Get the current status message of a specific user. | |
| * | |
| * @param mixed $jids The users' JIDs | |
| */ | |
| public function sendGetStatuses($jids) | |
| { | |
| if (!is_array($jids)) { | |
| $jids = array($jids); | |
| } | |
| $children = array(); | |
| foreach ($jids as $jid) { | |
| $children[] = new ProtocolNode("user", array("jid" => $this->getJID($jid)), null, null); | |
| } | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "to" => Constants::WHATSAPP_SERVER, | |
| "type" => "get", | |
| "xmlns" => "status", | |
| "id" => $this->createMsgId() | |
| ), array( | |
| new ProtocolNode("status", null, $children, null) | |
| ), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Create a group chat. | |
| * | |
| * @param string $subject | |
| * The group Subject | |
| * @param array $participants | |
| * An array with the participants numbers. | |
| * | |
| * @return string | |
| * The group ID. | |
| */ | |
| public function sendGroupsChatCreate($subject, $participants) | |
| { | |
| if (!is_array($participants)) { | |
| $participants = array($participants); | |
| } | |
| $participantNode = array(); | |
| foreach ($participants as $participant) { | |
| $participantNode[] = new ProtocolNode("participant", array( | |
| "jid" => $this->getJID($participant) | |
| ), null, null); | |
| } | |
| $id = $this->nodeId['groupcreate'] = $this->createMsgId(); | |
| $createNode = new ProtocolNode("create", | |
| array( | |
| "subject" => $subject | |
| ), $participantNode, null); | |
| $iqNode = new ProtocolNode("iq", | |
| array( | |
| "xmlns" => "w:g2", | |
| "id" => $id, | |
| "type" => "set", | |
| "to" => Constants::WHATSAPP_GROUP_SERVER | |
| ), array($createNode), null); | |
| $this->sendNode($iqNode); | |
| $this->waitForServer($id); | |
| $groupId = $this->groupId; | |
| $this->eventManager()->fire("onGroupCreate", | |
| array( | |
| $this->phoneNumber, | |
| $groupId | |
| )); | |
| return $groupId; | |
| } | |
| /** | |
| * Change group's subject. | |
| * | |
| * @param string $gjid The group id | |
| * @param string $subject The subject | |
| */ | |
| public function sendSetGroupSubject($gjid, $subject) | |
| { | |
| $child = new ProtocolNode("subject", null, null, $subject); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $this->createMsgId(), | |
| "type" => "set", | |
| "to" => $this->getJID($gjid), | |
| "xmlns" => "w:g2" | |
| ), array($child), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Leave a group chat. | |
| * | |
| * @param mixed $gjids Group or group's ID(s) | |
| */ | |
| public function sendGroupsLeave($gjids) | |
| { | |
| $msgId = $this->nodeId['leavegroup'] = $this->createMsgId(); | |
| if (!is_array($gjids)) { | |
| $gjids = array($this->getJID($gjids)); | |
| } | |
| $nodes = array(); | |
| foreach ($gjids as $gjid) { | |
| $nodes[] = new ProtocolNode("group", | |
| array( | |
| "id" => $this->getJID($gjid) | |
| ), null, null); | |
| } | |
| $leave = new ProtocolNode("leave", | |
| array( | |
| 'action'=>'delete' | |
| ), $nodes, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "to" => Constants::WHATSAPP_GROUP_SERVER, | |
| "type" => "set", | |
| "xmlns" => "w:g2" | |
| ), array($leave), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Add participant(s) to a group. | |
| * | |
| * @param string $groupId The group ID. | |
| * @param mixed $participants An array with the participants numbers to add | |
| */ | |
| public function sendGroupsParticipantsAdd($groupId, $participants) | |
| { | |
| $msgId = $this->createMsgId(); | |
| if (!is_array($participants)) { | |
| $participants = array($participants); | |
| } | |
| $this->sendGroupsChangeParticipants($groupId, $participants, 'add', $msgId); | |
| } | |
| /** | |
| * Remove participant(s) from a group. | |
| * | |
| * @param string $groupId The group ID. | |
| * @param mixed $participants An array with the participants numbers to remove | |
| */ | |
| public function sendGroupsParticipantsRemove($groupId, $participants) | |
| { | |
| $msgId = $this->createMsgId(); | |
| if (!is_array($participants)) { | |
| $participants = array($participants); | |
| } | |
| $this->sendGroupsChangeParticipants($groupId, $participants, 'remove', $msgId); | |
| } | |
| /** | |
| * Promote participant(s) of a group; Make a participant an admin of a group. | |
| * | |
| * @param string $gId The group ID. | |
| * @param mixed $participants An array with the participants numbers to promote | |
| */ | |
| public function sendPromoteParticipants($gId, $participants) | |
| { | |
| $msgId = $this->createMsgId(); | |
| if (!is_array($participants)) { | |
| $participants = array($participants); | |
| } | |
| $this->sendGroupsChangeParticipants($gId, $participants, "promote", $msgId); | |
| } | |
| /** | |
| * Demote participant(s) of a group; remove participant of being admin of a group. | |
| * | |
| * @param string $gId The group ID. | |
| * @param array $participants An array with the participants numbers to demote | |
| */ | |
| public function sendDemoteParticipants($gId, $participants) | |
| { | |
| $msgId = $this->createMsgId(); | |
| if (!is_array($participants)) { | |
| $participants = array($participants); | |
| } | |
| $this->sendGroupsChangeParticipants($gId, $participants, "demote", $msgId); | |
| } | |
| /** | |
| * Lock group: participants cant change group subject or profile picture except admin. | |
| * | |
| * @param string $gId The group ID. | |
| */ | |
| public function sendLockGroup($gId) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $lockedNode = new ProtocolNode("locked", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:g2", | |
| "type" => "set", | |
| "to" => $this->getJID($gId) | |
| ), array($lockedNode), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Unlock group: Any participant can change group subject or profile picture. | |
| * | |
| * | |
| * @param string $gId The group ID. | |
| */ | |
| public function sendUnlockGroup($gId) | |
| { | |
| $msgId = $this->createMsgId(); | |
| $unlockedNode = new ProtocolNode("unlocked", null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:g2", | |
| "type" => "set", | |
| "to" => $this->getJID($gId) | |
| ), array($unlockedNode), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgId); | |
| } | |
| /** | |
| * Send a text message to the user/group. | |
| * | |
| * @param string $to The recipient. | |
| * @param string $txt The text message. | |
| * @param $id | |
| * | |
| * @return string Message ID. | |
| */ | |
| public function sendMessage($to, $txt, $id = null) | |
| { | |
| $txt = $this->parseMessageForEmojis($txt); | |
| $bodyNode = new ProtocolNode("body", null, null, $txt); | |
| $id = $this->sendMessageNode($to, $bodyNode, $id); | |
| $this->waitForServer($id); | |
| if ($this->messageStore !== null) { | |
| $this->messageStore->saveMessage($this->phoneNumber, $to, $txt, $id, time()); | |
| } | |
| return $id; | |
| } | |
| /** | |
| * Send a read receipt to a message. | |
| * | |
| * @param $to The recipient. | |
| * @param $id | |
| */ | |
| public function sendMessageRead($to, $id) | |
| { | |
| $messageNode = new ProtocolNode("receipt", | |
| array( | |
| "type" => "read", | |
| "to" => $to, | |
| "id" => $id, | |
| "t" => time() | |
| ), null, null); | |
| $this->sendNode($messageNode); | |
| } | |
| /** | |
| * Send audio to the user/group. | |
| * | |
| * @param string $to The recipient. | |
| * @param string $filepath The url/uri to the audio file. | |
| * @param bool $storeURLmedia Keep copy of file | |
| * @param int $fsize | |
| * @param string $fhash * | |
| * @return string|null Message ID if successfully, null if not. | |
| */ | |
| public function sendMessageAudio($to, $filepath, $storeURLmedia = false, $fsize = 0, $fhash = "") | |
| { | |
| if ($fsize == 0 || $fhash == "") { | |
| $allowedExtensions = array('3gp', 'caf', 'wav', 'mp3', 'wma', 'ogg', 'aif', 'aac', 'm4a'); | |
| $size = 10 * 1024 * 1024; // Easy way to set maximum file size for this media type. | |
| // Return message ID. Make pull request for this. | |
| return $this->sendCheckAndSendMedia($filepath, $size, $to, 'audio', $allowedExtensions, $storeURLmedia); | |
| } else { | |
| // Return message ID. Make pull request for this. | |
| return $this->sendRequestFileUpload($fhash, 'audio', $fsize, $filepath, $to); | |
| } | |
| } | |
| /** | |
| * Send the composing message status. When typing a message. | |
| * | |
| * @param string $to The recipient to send status to. | |
| */ | |
| public function sendMessageComposing($to) | |
| { | |
| $this->sendChatState($to, "composing"); | |
| } | |
| /** | |
| * Send an image file to group/user. | |
| * | |
| * @param string $to Recipient number | |
| * @param string $filepath The url/uri to the image file. | |
| * @param bool $storeURLmedia Keep copy of file | |
| * @param int $fsize size of the media file | |
| * @param string $fhash base64 hash of the media file | |
| * @param string $caption | |
| * @return string|null Message ID if successfully, null if not. | |
| */ | |
| public function sendMessageImage($to, $filepath, $storeURLmedia = false, $fsize = 0, $fhash = "", $caption = "") | |
| { | |
| $caption = $this->parseMessageForEmojis($caption); | |
| if ($fsize == 0 || $fhash == "") { | |
| $allowedExtensions = array('jpg', 'jpeg', 'gif', 'png'); | |
| $size = 5 * 1024 * 1024; // Easy way to set maximum file size for this media type. | |
| // Return message ID. Make pull request for this. | |
| return $this->sendCheckAndSendMedia($filepath, $size, $to, 'image', $allowedExtensions, $storeURLmedia, $caption); | |
| } else { | |
| // Return message ID. Make pull request for this. | |
| return $this->sendRequestFileUpload($fhash, 'image', $fsize, $filepath, $to, $caption); | |
| } | |
| } | |
| /** | |
| * Send a location to the user/group. | |
| * | |
| * If no name is supplied, the receiver will see a large google maps thumbnail of the lat/long, | |
| * but NO name or url of the location. | |
| * | |
| * When a name supplied, a combined map thumbnail/name box is displayed. | |
| * | |
| * @param mixed $to The recipient(s) to send the location to. | |
| * @param float $long The longitude of the location, e.g. 54.31652. | |
| * @param float $lat The latitude of the location, e.g. -6.833496. | |
| * @param string $name (Optional) A custom name for the specified location. | |
| * @param string $url (Optional) A URL to attach to the specified location. | |
| * @return string Message ID | |
| */ | |
| public function sendMessageLocation($to, $long, $lat, $name = null, $url = null) | |
| { | |
| $mediaNode = new ProtocolNode("media", | |
| array( | |
| "type" => "location", | |
| "encoding" => "raw", | |
| "latitude" => $lat, | |
| "longitude" => $long, | |
| "name" => $name, | |
| "url" => $url | |
| ), null, null); | |
| $id = (is_array($to)) ? $this->sendBroadcast($to, $mediaNode, "media") : $this->sendMessageNode($to, $mediaNode); | |
| $this->waitForServer($id); | |
| // Return message ID. Make pull request for this. | |
| return $id; | |
| } | |
| /** | |
| * Send the 'paused composing message' status. | |
| * | |
| * @param string $to The recipient number or ID. | |
| */ | |
| public function sendMessagePaused($to) | |
| { | |
| $this->sendChatState($to, "paused"); | |
| } | |
| protected function sendChatState($to, $state) | |
| { | |
| $node = new ProtocolNode("chatstate", | |
| array( | |
| "to" => $this->getJID($to) | |
| ), array(new ProtocolNode($state, null, null, null)), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send a video to the user/group. | |
| * | |
| * @param string $to The recipient to send. | |
| * @param string $filepath A URL/URI to the MP4/MOV video. | |
| * @param bool $storeURLmedia Keep a copy of media file. | |
| * @param int $fsize Size of the media file | |
| * @param string $fhash base64 hash of the media file | |
| * @param string $caption * | |
| * @return string|null Message ID if successfully, null if not. | |
| */ | |
| public function sendMessageVideo($to, $filepath, $storeURLmedia = false, $fsize = 0, $fhash = "", $caption = "") | |
| { | |
| $caption = $this->parseMessageForEmojis($caption); | |
| if ($fsize == 0 || $fhash == "") { | |
| $allowedExtensions = array('3gp', 'mp4', 'mov', 'avi'); | |
| $size = 20 * 1024 * 1024; // Easy way to set maximum file size for this media type. | |
| // Return message ID. Make pull request for this. | |
| return $this->sendCheckAndSendMedia($filepath, $size, $to, 'video', $allowedExtensions, $storeURLmedia, $caption); | |
| } else { | |
| // Return message ID. Make pull request for this. | |
| return $this->sendRequestFileUpload($fhash, 'video', $fsize, $filepath, $to, $caption); | |
| } | |
| } | |
| /** | |
| * Send the next message. | |
| */ | |
| public function sendNextMessage() | |
| { | |
| if (count($this->outQueue) > 0) { | |
| $msgnode = array_shift($this->outQueue); | |
| $msgnode->refreshTimes(); | |
| $this->lastId = $msgnode->getAttribute('id'); | |
| $this->sendNode($msgnode); | |
| } else { | |
| $this->lastId = false; | |
| } | |
| } | |
| /** | |
| * Send the offline status. User will show up as "Offline". | |
| */ | |
| public function sendOfflineStatus() | |
| { | |
| $messageNode = new ProtocolNode("presence", array("type" => "inactive"), null, ""); | |
| $this->sendNode($messageNode); | |
| } | |
| /** | |
| * Send a pong to the WhatsApp server. I'm alive! | |
| * | |
| * @param string $msgid The id of the message. | |
| */ | |
| public function sendPong($msgid) | |
| { | |
| $messageNode = new ProtocolNode("iq", | |
| array( | |
| "to" => Constants::WHATSAPP_SERVER, | |
| "id" => $msgid, | |
| "type" => "result" | |
| ), null, ""); | |
| $this->sendNode($messageNode); | |
| $this->eventManager()->fire("onSendPong", | |
| array( | |
| $this->phoneNumber, | |
| $msgid | |
| )); | |
| } | |
| public function sendAvailableForChat($nickname = null) | |
| { | |
| $presence = array(); | |
| if ($nickname) { | |
| //update nickname | |
| $this->name = $nickname; | |
| } | |
| $presence['name'] = $this->name; | |
| $node = new ProtocolNode("presence", $presence, null, ""); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Send presence status. | |
| * | |
| * @param string $type The presence status. | |
| */ | |
| public function sendPresence($type = "active") | |
| { | |
| $node = new ProtocolNode("presence", | |
| array( | |
| "type" => $type | |
| ), null, ""); | |
| $this->sendNode($node); | |
| $this->eventManager()->fire("onSendPresence", | |
| array( | |
| $this->phoneNumber, | |
| $type, | |
| $this->name | |
| )); | |
| } | |
| /** | |
| * Send presence subscription, automatically receive presence updates as long as the socket is open. | |
| * | |
| * @param string $to Phone number. | |
| */ | |
| public function sendPresenceSubscription($to) | |
| { | |
| $node = new ProtocolNode("presence", array("type" => "subscribe", "to" => $this->getJID($to)), null, ""); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Unsubscribe, will stop subscription. | |
| * | |
| * @param string $to Phone number. | |
| */ | |
| public function sendPresenceUnsubscription($to) | |
| { | |
| $node = new ProtocolNode("presence", array("type" => "unsubscribe", "to" => $this->getJID($to)), null, ""); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Set the picture for the group. | |
| * | |
| * @param string $gjid The groupID | |
| * @param string $path The URL/URI of the image to use | |
| */ | |
| public function sendSetGroupPicture($gjid, $path) | |
| { | |
| $this->sendSetPicture($gjid, $path); | |
| } | |
| /** | |
| * Set the list of numbers you wish to block receiving from. | |
| * | |
| * @param mixed $blockedJids One or more numbers to block messages from. | |
| */ | |
| public function sendSetPrivacyBlockedList($blockedJids = array()) | |
| { | |
| if (!is_array($blockedJids)) { | |
| $blockedJids = array($blockedJids); | |
| } | |
| $items = array(); | |
| foreach ($blockedJids as $index => $jid) { | |
| $item = new ProtocolNode("item", | |
| array( | |
| "type" => "jid", | |
| "value" => $this->getJID($jid), | |
| "action" => "deny", | |
| "order" => $index + 1//WhatsApp stream crashes on zero index | |
| ), null, null); | |
| $items[] = $item; | |
| } | |
| $child = new ProtocolNode("list", | |
| array( | |
| "name" => "default" | |
| ), $items, null); | |
| $child2 = new ProtocolNode("query", null, array($child), null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $this->createMsgId(), | |
| "xmlns" => "jabber:iq:privacy", | |
| "type" => "set" | |
| ), array($child2), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Set your profile picture. | |
| * | |
| * @param string $path URL/URI of image | |
| */ | |
| public function sendSetProfilePicture($path) | |
| { | |
| $this->sendSetPicture($this->phoneNumber, $path); | |
| } | |
| /* | |
| * Removes the profile photo. | |
| */ | |
| public function sendRemoveProfilePicture() | |
| { | |
| $msgId = $this->createMsgId(); | |
| $picture = new ProtocolNode("picture", null, null, null); | |
| $thumb = new ProtocolNode("picture", | |
| array( | |
| "type" => "preview" | |
| ), null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "to" => $this->getJID($this->phoneNumber), | |
| "type" => "set", | |
| "xmlns" => "w:profile:picture" | |
| ), array($picture, $thumb), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Set the recovery token for your account to allow you to retrieve your password at a later stage. | |
| * | |
| * @param string $token A user generated token. | |
| */ | |
| public function sendSetRecoveryToken($token) | |
| { | |
| $child = new ProtocolNode("pin", | |
| array( | |
| "xmlns" => "w:ch:p" | |
| ), null, $token); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $this->createMsgId(), | |
| "type" => "set", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($child), null); | |
| $this->sendNode($node); | |
| } | |
| /** | |
| * Update the user status. | |
| * | |
| * @param string $txt The text of the message status to send. | |
| */ | |
| public function sendStatusUpdate($txt) | |
| { | |
| $child = new ProtocolNode("status", null, null, $txt); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "to" => Constants::WHATSAPP_SERVER, | |
| "type" => "set", | |
| "id" => $this->createMsgId(), | |
| "xmlns" => "status" | |
| ), array($child), null); | |
| $this->sendNode($node); | |
| $this->eventManager()->fire("onSendStatusUpdate", | |
| array( | |
| $this->phoneNumber, | |
| $txt | |
| )); | |
| } | |
| /** | |
| * Send a vCard to the user/group. | |
| * | |
| * @param string $to The recipient to send. | |
| * @param string $name The contact name. | |
| * @param object $vCard The contact vCard to send. | |
| * @return string Message ID | |
| */ | |
| public function sendVcard($to, $name, $vCard) | |
| { | |
| $vCardNode = new ProtocolNode("vcard", | |
| array( | |
| "name" => $name | |
| ), null, $vCard); | |
| $mediaNode = new ProtocolNode("media", | |
| array( | |
| "type" => "vcard" | |
| ), array($vCardNode), ""); | |
| // Return message ID. Make pull request for this. | |
| return $this->sendMessageNode($to, $mediaNode); | |
| } | |
| /** | |
| * Send a vCard to the user/group as Broadcast. | |
| * | |
| * @param array $targets An array of recipients to send to. | |
| * @param string $name The vCard contact name. | |
| * @param object $vCard The contact vCard to send. | |
| * @return string Message ID | |
| */ | |
| public function sendBroadcastVcard($targets, $name, $vCard) | |
| { | |
| $vCardNode = new ProtocolNode("vcard", | |
| array( | |
| "name" => $name | |
| ), null, $vCard); | |
| $mediaNode = new ProtocolNode("media", | |
| array( | |
| "type" => "vcard" | |
| ), array($vCardNode), ""); | |
| // Return message ID. Make pull request for this. | |
| return $this->sendBroadcast($targets, $mediaNode, "media"); | |
| } | |
| /** | |
| * Sets the bind of the new message. | |
| * | |
| * @param $bind | |
| */ | |
| public function setNewMessageBind($bind) | |
| { | |
| $this->newMsgBind = $bind; | |
| } | |
| /** | |
| * Wait for WhatsApp server to acknowledge *it* has received message. | |
| * @param string $id The id of the node sent that we are awaiting acknowledgement of. | |
| * @param int $timeout | |
| */ | |
| public function waitForServer($id, $timeout = 5) | |
| { | |
| $time = time(); | |
| $this->serverReceivedId = false; | |
| do { | |
| $this->pollMessage(); | |
| } while ($this->serverReceivedId !== $id && time() - $time < $timeout); | |
| } | |
| /** | |
| * Authenticate with the WhatsApp Server. | |
| * | |
| * @return string Returns binary string | |
| */ | |
| protected function authenticate() | |
| { | |
| $keys = KeyStream::GenerateKeys(base64_decode($this->password), $this->challengeData); | |
| $this->inputKey = new KeyStream($keys[2], $keys[3]); | |
| $this->outputKey = new KeyStream($keys[0], $keys[1]); | |
| $array = "\0\0\0\0" . $this->phoneNumber . $this->challengeData;// . time() . Constants::WHATSAPP_USER_AGENT . " MccMnc/" . str_pad($phone["mcc"], 3, "0", STR_PAD_LEFT) . "001"; | |
| $response = $this->outputKey->EncodeMessage($array, 0, 4, strlen($array) - 4); | |
| return $response; | |
| } | |
| /** | |
| * Add the authentication nodes. | |
| * | |
| * @return ProtocolNode Returns an authentication node. | |
| */ | |
| protected function createAuthNode() | |
| { | |
| $data = $this->createAuthBlob(); | |
| $node = new ProtocolNode("auth", array( | |
| 'mechanism' => 'WAUTH-2', | |
| 'user' => $this->phoneNumber | |
| ), null, $data); | |
| return $node; | |
| } | |
| protected function createAuthBlob() | |
| { | |
| if ($this->challengeData) { | |
| $key = wa_pbkdf2('sha1', base64_decode($this->password), $this->challengeData, 16, 20, true); | |
| $this->inputKey = new KeyStream($key[2], $key[3]); | |
| $this->outputKey = new KeyStream($key[0], $key[1]); | |
| $this->reader->setKey($this->inputKey); | |
| //$this->writer->setKey($this->outputKey); | |
| $phone = $this->dissectPhone(); | |
| $array = "\0\0\0\0" . $this->phoneNumber . $this->challengeData . time(); | |
| $this->challengeData = null; | |
| return $this->outputKey->EncodeMessage($array, 0, strlen($array), false); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Add the auth response to protocoltreenode. | |
| * | |
| * @return ProtocolNode Returns a response node. | |
| */ | |
| protected function createAuthResponseNode() | |
| { | |
| return new ProtocolNode("response", null, null, $this->authenticate()); | |
| } | |
| /** | |
| * Add stream features. | |
| * | |
| * @return ProtocolNode Return itself. | |
| */ | |
| protected function createFeaturesNode() | |
| { | |
| $readreceipts = new ProtocolNode("readreceipts", null, null, null); | |
| $groupsv2 = new ProtocolNode("groups_v2", null, null, null); | |
| $privacy = new ProtocolNode("privacy", null, null, null); | |
| $presencev2 = new ProtocolNode("presence", null, null, null); | |
| $parent = new ProtocolNode("stream:features", null, array($readreceipts, $groupsv2, $privacy, $presencev2), null); | |
| return $parent; | |
| } | |
| /** | |
| * Create a unique msg id. | |
| * | |
| * @return string | |
| * A message id string. | |
| */ | |
| protected function createMsgId() | |
| { | |
| $msgid = $this->messageCounter; | |
| $this->messageCounter++; | |
| return $this->loginTime . "-" . $msgid; | |
| } | |
| /** | |
| * Print a message to the debug console. | |
| * | |
| * @param mixed $debugMsg The debug message. | |
| * @return bool | |
| */ | |
| protected function debugPrint($debugMsg) | |
| { | |
| if ($this->debug) { | |
| if (is_array($debugMsg) || is_object($debugMsg)) { | |
| print_r($debugMsg); | |
| } | |
| else { | |
| echo $debugMsg; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Dissect country code from phone number. | |
| * | |
| * @return array | |
| * An associative array with country code and phone number. | |
| * - country: The detected country name. | |
| * - cc: The detected country code (phone prefix). | |
| * - phone: The phone number. | |
| * - ISO3166: 2-Letter country code | |
| * - ISO639: 2-Letter language code | |
| * Return false if country code is not found. | |
| */ | |
| protected function dissectPhone() | |
| { | |
| if (($handle = fopen(dirname(__FILE__).'/countries.csv', 'rb')) !== false) { | |
| while (($data = fgetcsv($handle, 1000)) !== false) { | |
| if (strpos($this->phoneNumber, $data[1]) === 0) { | |
| // Return the first appearance. | |
| fclose($handle); | |
| $mcc = explode("|", $data[2]); | |
| $mcc = $mcc[0]; | |
| //hook: | |
| //fix country code for North America | |
| if ($data[1][0] == "1") { | |
| $data[1] = "1"; | |
| } | |
| $phone = array( | |
| 'country' => $data[0], | |
| 'cc' => $data[1], | |
| 'phone' => substr($this->phoneNumber, strlen($data[1]), strlen($this->phoneNumber)), | |
| 'mcc' => $mcc, | |
| 'ISO3166' => @$data[3], | |
| 'ISO639' => @$data[4], | |
| 'mnc' => $data[5] | |
| ); | |
| $this->eventManager()->fire("onDissectPhone", | |
| array( | |
| $this->phoneNumber, | |
| $phone['country'], | |
| $phone['cc'], | |
| $phone['phone'], | |
| $phone['mcc'], | |
| $phone['ISO3166'], | |
| $phone['ISO639'], | |
| $phone['mnc'] | |
| ) | |
| ); | |
| return $phone; | |
| } | |
| } | |
| fclose($handle); | |
| } | |
| $this->eventManager()->fire("onDissectPhoneFailed", | |
| array( | |
| $this->phoneNumber | |
| )); | |
| return false; | |
| } | |
| /** | |
| * Detects mnc from specified carrier. | |
| * | |
| * @param string $lc LangCode | |
| * @param string $carrierName Name of the carrier | |
| * @return string | |
| * | |
| * Returns mnc value | |
| */ | |
| protected function detectMnc($lc, $carrierName) | |
| { | |
| $fp = fopen(__DIR__ . DIRECTORY_SEPARATOR . 'networkinfo.csv', 'r'); | |
| $mnc = null; | |
| while ($data = fgetcsv($fp, 0, ',')) { | |
| if ($data[4] === $lc && $data[7] === $carrierName) { | |
| $mnc = $data[2]; | |
| break; | |
| } | |
| } | |
| if ($mnc == null) { | |
| $mnc = '000'; | |
| } | |
| fclose($fp); | |
| return $mnc; | |
| } | |
| /** | |
| * Send the nodes to the WhatsApp server to log in. | |
| * | |
| * @throws Exception | |
| */ | |
| protected function doLogin() | |
| { | |
| if ($this->isLoggedIn()) { | |
| return true; | |
| } | |
| $this->writer->resetKey(); | |
| $this->reader->resetKey(); | |
| $resource = Constants::WHATSAPP_DEVICE . '-' . Constants::WHATSAPP_VER . '-' . Constants::PORT; | |
| $data = $this->writer->StartStream(Constants::WHATSAPP_SERVER, $resource); | |
| $feat = $this->createFeaturesNode(); | |
| $auth = $this->createAuthNode(); | |
| $this->sendData($data); | |
| $this->sendNode($feat); | |
| $this->sendNode($auth); | |
| $this->pollMessage(); | |
| $this->pollMessage(); | |
| $this->pollMessage(); | |
| if ($this->challengeData != null) { | |
| $data = $this->createAuthResponseNode(); | |
| $this->sendNode($data); | |
| $this->reader->setKey($this->inputKey); | |
| $this->writer->setKey($this->outputKey); | |
| $this->pollMessage(); | |
| } | |
| if ($this->loginStatus === Constants::DISCONNECTED_STATUS) { | |
| throw new LoginFailureException(); | |
| } | |
| $this->eventManager()->fire("onLogin", | |
| array( | |
| $this->phoneNumber | |
| )); | |
| $this->sendAvailableForChat(); | |
| $this->loginTime = time(); | |
| return true; | |
| } | |
| /** | |
| * Have we an active connection with WhatsAPP AND a valid login already? | |
| * | |
| * @return bool | |
| */ | |
| protected function isLoggedIn(){ | |
| //If you aren't connected you can't be logged in! ($this->isConnected()) | |
| //We are connected - but are we logged in? (the rest) | |
| return ($this->isConnected() && !empty($this->loginStatus) && $this->loginStatus === Constants::CONNECTED_STATUS); | |
| } | |
| /** | |
| * Create an identity string | |
| * | |
| * @param string $identity Identity. | |
| * @return string Correctly formatted identity | |
| * | |
| * @throws Exception Error when cannot write identity data to file. | |
| */ | |
| protected function buildIdentity() | |
| { | |
| $identity_file = sprintf('%s%s%sid.%s.dat', __DIR__, DIRECTORY_SEPARATOR, Constants::DATA_FOLDER . DIRECTORY_SEPARATOR, $this->phoneNumber); | |
| if (is_readable($identity_file)) { | |
| $data = urldecode(file_get_contents($identity_file)); | |
| $length = strlen($data); | |
| if ($length == 20 || $length == 16) { | |
| return $data; | |
| } | |
| } | |
| $bytes = strtolower(openssl_random_pseudo_bytes(20)); | |
| if (file_put_contents($identity_file, urlencode($bytes)) === false) { | |
| throw new Exception('Unable to write identity file to ' . $identity_file); | |
| } | |
| return $bytes; | |
| } | |
| public function sendSync(array $numbers, array $deletedNumbers = null, $syncType = 4, $index = 0, $last = true) | |
| { | |
| $users = array(); | |
| for ($i=0; $i<count($numbers); $i++) { // number must start with '+' if international contact | |
| $users[$i] = new ProtocolNode("user", null, null, (substr($numbers[$i], 0, 1) != '+')?('+' . $numbers[$i]):($numbers[$i])); | |
| } | |
| if ($deletedNumbers != null || count($deletedNumbers)) { | |
| for ($j=0; $j<count($deletedNumbers); $j++, $i++) { | |
| $users[$i] = new ProtocolNode("user", array("jid" => $this->getJID($deletedNumbers[$j]), "type" => "delete"), null, null); | |
| } | |
| } | |
| switch($syncType) | |
| { | |
| case 0: | |
| $mode = "full"; | |
| $context = "registration"; | |
| break; | |
| case 1: | |
| $mode = "full"; | |
| $context = "interactive"; | |
| break; | |
| case 2: | |
| $mode = "full"; | |
| $context = "background"; | |
| break; | |
| case 3: | |
| $mode = "delta"; | |
| $context = "interactive"; | |
| break; | |
| case 4: | |
| $mode = "delta"; | |
| $context = "background"; | |
| break; | |
| case 5: | |
| $mode = "query"; | |
| $context = "interactive"; | |
| break; | |
| case 6: | |
| $mode = "chunked"; | |
| $context = "registration"; | |
| break; | |
| case 7: | |
| $mode = "chunked"; | |
| $context = "interactive"; | |
| break; | |
| case 8: | |
| $mode = "chunked"; | |
| $context = "background"; | |
| break; | |
| default: | |
| $mode = "delta"; | |
| $context = "background"; | |
| } | |
| $id = $this->createMsgId(); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $id, | |
| "xmlns" => "urn:xmpp:whatsapp:sync", | |
| "type" => "get" | |
| ), array( | |
| new ProtocolNode("sync", | |
| array( | |
| "mode" => $mode, | |
| "context" => $context, | |
| "sid" => "".((time() + 11644477200) * 10000000), | |
| "index" => "".$index, | |
| "last" => $last ? "true" : "false" | |
| ), $users, null) | |
| ), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($id); | |
| return $id; | |
| } | |
| public function setMessageStore(MessageStoreInterface $messageStore) | |
| { | |
| $this->messageStore = $messageStore; | |
| } | |
| /** | |
| * Process number/jid and turn it into a JID if necessary | |
| * | |
| * @param string $number | |
| * Number to process | |
| * @return string | |
| */ | |
| protected function getJID($number) | |
| { | |
| if (!stristr($number, '@')) { | |
| //check if group message | |
| if (stristr($number, '-')) { | |
| //to group | |
| $number .= "@" . Constants::WHATSAPP_GROUP_SERVER; | |
| } else { | |
| //to normal user | |
| $number .= "@" . Constants::WHATSAPP_SERVER; | |
| } | |
| } | |
| return $number; | |
| } | |
| /** | |
| * Retrieves media file and info from either a URL or localpath | |
| * | |
| * @param string $filepath The URL or path to the mediafile you wish to send | |
| * @param integer $maxsizebytes The maximum size in bytes the media file can be. Default 1MB | |
| * | |
| * @return bool false if file information can not be obtained. | |
| */ | |
| protected function getMediaFile($filepath, $maxsizebytes = 1048576) | |
| { | |
| if (filter_var($filepath, FILTER_VALIDATE_URL) !== false) { | |
| $this->mediaFileInfo = array(); | |
| $this->mediaFileInfo['url'] = $filepath; | |
| //File is a URL. Create a curl connection but DON'T download the body content | |
| //because we want to see if file is too big. | |
| $curl = curl_init(); | |
| curl_setopt($curl, CURLOPT_URL, "$filepath"); | |
| curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11"); | |
| curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); | |
| curl_setopt($curl, CURLOPT_HEADER, false); | |
| curl_setopt($curl, CURLOPT_NOBODY, true); | |
| if (curl_exec($curl) === false) { | |
| return false; | |
| } | |
| //While we're here, get mime type and filesize and extension | |
| $info = curl_getinfo($curl); | |
| $this->mediaFileInfo['filesize'] = $info['download_content_length']; | |
| $this->mediaFileInfo['filemimetype'] = $info['content_type']; | |
| $this->mediaFileInfo['fileextension'] = pathinfo(parse_url($this->mediaFileInfo['url'], PHP_URL_PATH), PATHINFO_EXTENSION); | |
| //Only download file if it's not too big | |
| //TODO check what max file size whatsapp server accepts. | |
| if ($this->mediaFileInfo['filesize'] < $maxsizebytes) { | |
| //Create temp file in media folder. Media folder must be writable! | |
| $this->mediaFileInfo['filepath'] = tempnam(__DIR__ . DIRECTORY_SEPARATOR . Constants::DATA_FOLDER . DIRECTORY_SEPARATOR . Constants::MEDIA_FOLDER, 'WHA'); | |
| $fp = fopen($this->mediaFileInfo['filepath'], 'w'); | |
| if ($fp) { | |
| curl_setopt($curl, CURLOPT_NOBODY, false); | |
| curl_setopt($curl, CURLOPT_BUFFERSIZE, 1024); | |
| curl_setopt($curl, CURLOPT_FILE, $fp); | |
| curl_exec($curl); | |
| fclose($fp); | |
| } else { | |
| unlink($this->mediaFileInfo['filepath']); | |
| curl_close($curl); | |
| return false; | |
| } | |
| //Success | |
| curl_close($curl); | |
| return true; | |
| } else { | |
| //File too big. Don't Download. | |
| curl_close($curl); | |
| return false; | |
| } | |
| } else if (file_exists($filepath)) { | |
| //Local file | |
| $this->mediaFileInfo['filesize'] = filesize($filepath); | |
| if ($this->mediaFileInfo['filesize'] < $maxsizebytes) { | |
| $this->mediaFileInfo['filepath'] = $filepath; | |
| $this->mediaFileInfo['fileextension'] = pathinfo($filepath, PATHINFO_EXTENSION); | |
| $this->mediaFileInfo['filemimetype'] = get_mime($filepath); | |
| return true; | |
| } else { | |
| //File too big | |
| return false; | |
| } | |
| } | |
| //Couldn't tell what file was, local or URL. | |
| return false; | |
| } | |
| /** | |
| * Get a decoded JSON response from Whatsapp server | |
| * | |
| * @param string $host The host URL | |
| * @param array $query A associative array of keys and values to send to server. | |
| * | |
| * @return null|object NULL if the json cannot be decoded or if the encoded data is deeper than the recursion limit | |
| */ | |
| protected function getResponse($host, $query) | |
| { | |
| // Build the url. | |
| $url = $host . '?' . http_build_query($query); | |
| // Open connection. | |
| $ch = curl_init(); | |
| // Configure the connection. | |
| curl_setopt($ch, CURLOPT_URL, $url); | |
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
| curl_setopt($ch, CURLOPT_HEADER, 0); | |
| curl_setopt($ch, CURLOPT_USERAGENT, Constants::WHATSAPP_USER_AGENT); | |
| curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: text/json')); | |
| // This makes CURL accept any peer! | |
| curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
| // Get the response. | |
| $response = curl_exec($ch); | |
| // Close the connection. | |
| curl_close($ch); | |
| return json_decode($response); | |
| } | |
| /** | |
| * Process the challenge. | |
| * | |
| * @param ProtocolNode $node The node that contains the challenge. | |
| */ | |
| protected function processChallenge($node) | |
| { | |
| $this->challengeData = $node->getData(); | |
| } | |
| /** | |
| * Process inbound data. | |
| * | |
| * @param $data | |
| * @param bool $autoReceipt | |
| * @param $type | |
| * | |
| * @throws Exception | |
| */ | |
| protected function processInboundData($data, $autoReceipt = true, $type = "read") | |
| { | |
| $node = $this->reader->nextTree($data); | |
| if ($node != null) { | |
| $this->processInboundDataNode($node, $autoReceipt, $type); | |
| } | |
| } | |
| /** | |
| * Will process the data from the server after it's been decrypted and parsed. | |
| * | |
| * This also provides a convenient method to use to unit test the event framework. | |
| * @param ProtocolNode $node | |
| * @param bool $autoReceipt | |
| * @param $type | |
| * | |
| * @throws Exception | |
| */ | |
| protected function processInboundDataNode(ProtocolNode $node, $autoReceipt = true, $type = "read") { | |
| $this->debugPrint($node->nodeString("rx ") . "\n"); | |
| $this->serverReceivedId = $node->getAttribute('id'); | |
| if ($node->getTag() == "challenge") { | |
| $this->processChallenge($node); | |
| } elseif ($node->getTag() == "failure") { | |
| $this->loginStatus = Constants::DISCONNECTED_STATUS; | |
| $this->eventManager()->fire("onLoginFailed", | |
| array( | |
| $this->phoneNumber, | |
| $node->getChild(0)->getTag() | |
| )); | |
| } elseif ($node->getTag() == "success") { | |
| if ($node->getAttribute("status") == "active") { | |
| $this->loginStatus = Constants::CONNECTED_STATUS; | |
| $challengeData = $node->getData(); | |
| file_put_contents($this->challengeFilename, $challengeData); | |
| $this->writer->setKey($this->outputKey); | |
| $this->eventManager()->fire("onLoginSuccess", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute("kind"), | |
| $node->getAttribute("status"), | |
| $node->getAttribute("creation"), | |
| $node->getAttribute("expiration") | |
| )); | |
| } elseif ($node->getAttribute("status") == "expired") { | |
| $this->eventManager()->fire("onAccountExpired", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute("kind"), | |
| $node->getAttribute("status"), | |
| $node->getAttribute("creation"), | |
| $node->getAttribute("expiration") | |
| )); | |
| } | |
| } elseif ($node->getTag() == 'ack' && $node->getAttribute("class") == "message") { | |
| $this->eventManager()->fire("onMessageReceivedServer", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('class'), | |
| $node->getAttribute('t') | |
| )); | |
| } elseif ($node->getTag() == 'receipt') { | |
| if ($node->hasChild("list")) { | |
| foreach ($node->getChild("list")->getChildren() as $child) { | |
| $this->eventManager()->fire("onMessageReceivedClient", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $child->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('participant') | |
| )); | |
| } | |
| } | |
| $this->eventManager()->fire("onMessageReceivedClient", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('participant') | |
| )); | |
| $ackNode = new ProtocolNode("ack", | |
| array( | |
| "to" => $node->getAttribute('from'), | |
| "id" => $node->getAttribute('id'), | |
| "type" => $type, | |
| "t" => time() | |
| ), null, null); | |
| $this->sendNode($ackNode); | |
| } | |
| if ($node->getTag() == "message") { | |
| array_push($this->messageQueue, $node); | |
| if ($node->hasChild('x') && $this->lastId == $node->getAttribute('id')) { | |
| $this->sendNextMessage(); | |
| } | |
| if ($this->newMsgBind && ($node->getChild('body') || $node->getChild('media'))) { | |
| $this->newMsgBind->process($node); | |
| } | |
| if ($node->getAttribute("type") == "text" && $node->getChild('body') != null) { | |
| $author = $node->getAttribute("participant"); | |
| if ($author == "") { | |
| //private chat message | |
| $this->eventManager()->fire("onGetMessage", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute("notify"), | |
| $node->getChild("body")->getData() | |
| )); | |
| if ($this->messageStore !== null) { | |
| $this->messageStore->saveMessage($node->getAttribute('from'), $this->phoneNumber, $node->getChild("body")->getData(), $node->getAttribute('id'), $node->getAttribute('t')); | |
| } | |
| } else { | |
| //group chat message | |
| $this->eventManager()->fire("onGetGroupMessage", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $author, | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute("notify"), | |
| $node->getChild("body")->getData() | |
| )); | |
| } | |
| if ($autoReceipt) { | |
| $this->sendMessageReceived($node, $type, $author); | |
| } | |
| } | |
| if ($node->getAttribute("type") == "text" && $node->getChild(0)->getTag() == 'enc') { | |
| // TODO | |
| if ($autoReceipt) { | |
| $this->sendMessageReceived($node, $type); | |
| } | |
| } | |
| if ($node->getAttribute("type") == "media" && $node->getChild('media') != null) { | |
| if ($node->getChild("media")->getAttribute('type') == 'image') { | |
| if ($node->getAttribute("participant") == null) { | |
| $this->eventManager()->fire("onGetImage", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('notify'), | |
| $node->getChild("media")->getAttribute('size'), | |
| $node->getChild("media")->getAttribute('url'), | |
| $node->getChild("media")->getAttribute('file'), | |
| $node->getChild("media")->getAttribute('mimetype'), | |
| $node->getChild("media")->getAttribute('filehash'), | |
| $node->getChild("media")->getAttribute('width'), | |
| $node->getChild("media")->getAttribute('height'), | |
| $node->getChild("media")->getData(), | |
| $node->getChild("media")->getAttribute('caption') | |
| )); | |
| } else { | |
| $this->eventManager()->fire("onGetGroupImage", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('participant'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('notify'), | |
| $node->getChild("media")->getAttribute('size'), | |
| $node->getChild("media")->getAttribute('url'), | |
| $node->getChild("media")->getAttribute('file'), | |
| $node->getChild("media")->getAttribute('mimetype'), | |
| $node->getChild("media")->getAttribute('filehash'), | |
| $node->getChild("media")->getAttribute('width'), | |
| $node->getChild("media")->getAttribute('height'), | |
| $node->getChild("media")->getData(), | |
| $node->getChild("media")->getAttribute('caption') | |
| )); | |
| } | |
| } elseif ($node->getChild("media")->getAttribute('type') == 'video') { | |
| if ($node->getAttribute("participant") == null) { | |
| $this->eventManager()->fire("onGetVideo", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('notify'), | |
| $node->getChild("media")->getAttribute('url'), | |
| $node->getChild("media")->getAttribute('file'), | |
| $node->getChild("media")->getAttribute('size'), | |
| $node->getChild("media")->getAttribute('mimetype'), | |
| $node->getChild("media")->getAttribute('filehash'), | |
| $node->getChild("media")->getAttribute('duration'), | |
| $node->getChild("media")->getAttribute('vcodec'), | |
| $node->getChild("media")->getAttribute('acodec'), | |
| $node->getChild("media")->getData(), | |
| $node->getChild("media")->getAttribute('caption') | |
| )); | |
| } else { | |
| $this->eventManager()->fire("onGetGroupVideo", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('participant'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('notify'), | |
| $node->getChild("media")->getAttribute('url'), | |
| $node->getChild("media")->getAttribute('file'), | |
| $node->getChild("media")->getAttribute('size'), | |
| $node->getChild("media")->getAttribute('mimetype'), | |
| $node->getChild("media")->getAttribute('filehash'), | |
| $node->getChild("media")->getAttribute('duration'), | |
| $node->getChild("media")->getAttribute('vcodec'), | |
| $node->getChild("media")->getAttribute('acodec'), | |
| $node->getChild("media")->getData(), | |
| $node->getChild("media")->getAttribute('caption') | |
| )); | |
| } | |
| } elseif ($node->getChild("media")->getAttribute('type') == 'audio') { | |
| $author = $node->getAttribute("participant"); | |
| $this->eventManager()->fire("onGetAudio", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('notify'), | |
| $node->getChild("media")->getAttribute('size'), | |
| $node->getChild("media")->getAttribute('url'), | |
| $node->getChild("media")->getAttribute('file'), | |
| $node->getChild("media")->getAttribute('mimetype'), | |
| $node->getChild("media")->getAttribute('filehash'), | |
| $node->getChild("media")->getAttribute('seconds'), | |
| $node->getChild("media")->getAttribute('acodec'), | |
| $author, | |
| )); | |
| } elseif ($node->getChild("media")->getAttribute('type') == 'vcard') { | |
| if ($node->getChild("media")->hasChild('vcard')) { | |
| $name = $node->getChild("media")->getChild("vcard")->getAttribute('name'); | |
| $data = $node->getChild("media")->getChild("vcard")->getData(); | |
| } else { | |
| $name = "NO_NAME"; | |
| $data = $node->getChild("media")->getData(); | |
| } | |
| $author = $node->getAttribute("participant"); | |
| $this->eventManager()->fire("onGetvCard", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('notify'), | |
| $name, | |
| $data, | |
| $author | |
| )); | |
| } elseif ($node->getChild("media")->getAttribute('type') == 'location') { | |
| $url = $node->getChild("media")->getAttribute('url'); | |
| $name = $node->getChild("media")->getAttribute('name'); | |
| $author = $node->getAttribute("participant"); | |
| $this->eventManager()->fire("onGetLocation", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('notify'), | |
| $name, | |
| $node->getChild("media")->getAttribute('longitude'), | |
| $node->getChild("media")->getAttribute('latitude'), | |
| $url, | |
| $node->getChild("media")->getData(), | |
| $author | |
| )); | |
| } | |
| if ($autoReceipt) { | |
| $this->sendMessageReceived($node, $type); | |
| } | |
| } | |
| if ($node->getChild('received') != null) { | |
| $this->eventManager()->fire("onMessageReceivedClient", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('type'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('participant') | |
| )); | |
| } | |
| } | |
| if ($node->getTag() == "presence" && $node->getAttribute("status") == "dirty") { | |
| //clear dirty | |
| $categories = array(); | |
| if (count($node->getChildren()) > 0) { | |
| foreach ($node->getChildren() as $child) { | |
| if ($child->getTag() == "category") { | |
| $categories[] = $child->getAttribute("name"); | |
| } | |
| } | |
| } | |
| $this->sendClearDirty($categories); | |
| } | |
| if (strcmp($node->getTag(), "presence") == 0 | |
| && strncmp($node->getAttribute('from'), $this->phoneNumber, strlen($this->phoneNumber)) != 0 | |
| && strpos($node->getAttribute('from'), "-") === false) { | |
| $presence = array(); | |
| if ($node->getAttribute('type') == null) { | |
| $this->eventManager()->fire("onPresenceAvailable", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| )); | |
| } else { | |
| $this->eventManager()->fire("onPresenceUnavailable", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('last') | |
| )); | |
| } | |
| } | |
| if ($node->getTag() == "presence" | |
| && strncmp($node->getAttribute('from'), $this->phoneNumber, strlen($this->phoneNumber)) != 0 | |
| && strpos($node->getAttribute('from'), "-") !== false | |
| && $node->getAttribute('type') != null) { | |
| $groupId = Constants::parseJID($node->getAttribute('from')); | |
| if ($node->getAttribute('add') != null) { | |
| $this->eventManager()->fire("onGroupsParticipantsAdd", | |
| array( | |
| $this->phoneNumber, | |
| $groupId, | |
| Constants::parseJID($node->getAttribute('add')) | |
| )); | |
| } elseif ($node->getAttribute('remove') != null) { | |
| $this->eventManager()->fire("onGroupsParticipantsRemove", | |
| array( | |
| $this->phoneNumber, | |
| $groupId, | |
| Constants::parseJID($node->getAttribute('remove')) | |
| )); | |
| } | |
| } | |
| if (strcmp($node->getTag(), "chatstate") == 0 | |
| && strncmp($node->getAttribute('from'), $this->phoneNumber, strlen($this->phoneNumber)) != 0 | |
| && strpos($node->getAttribute('from'), "-") === false) { | |
| if($node->getChild(0)->getTag() == "composing"){ | |
| $this->eventManager()->fire("onMessageComposing", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| "composing", | |
| $node->getAttribute('t') | |
| )); | |
| } else { | |
| $this->eventManager()->fire("onMessagePaused", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| "paused", | |
| $node->getAttribute('t') | |
| )); | |
| } | |
| } | |
| if ($node->getTag() == "iq" | |
| && $node->getAttribute('type') == "get" | |
| && $node->getAttribute('xmlns') == "urn:xmpp:ping") { | |
| $this->eventManager()->fire("onPing", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('id') | |
| )); | |
| $this->sendPong($node->getAttribute('id')); | |
| } | |
| if ($node->getTag() == "iq" | |
| && $node->getChild("sync") != null) { | |
| //sync result | |
| $sync = $node->getChild('sync'); | |
| $existing = $sync->getChild("in"); | |
| $nonexisting = $sync->getChild("out"); | |
| //process existing first | |
| $existingUsers = array(); | |
| if (!empty($existing)) { | |
| foreach ($existing->getChildren() as $child) { | |
| $existingUsers[$child->getData()] = $child->getAttribute("jid"); | |
| } | |
| } | |
| //now process failed numbers | |
| $failedNumbers = array(); | |
| if (!empty($nonexisting)) { | |
| foreach ($nonexisting->getChildren() as $child) { | |
| $failedNumbers[] = str_replace('+', '', $child->getData()); | |
| } | |
| } | |
| $index = $sync->getAttribute("index"); | |
| $result = new SyncResult($index, $sync->getAttribute("sid"), $existingUsers, $failedNumbers); | |
| $this->eventManager()->fire("onGetSyncResult", | |
| array( | |
| $result | |
| )); | |
| } | |
| if ($node->getTag() == "receipt") { | |
| $this->eventManager()->fire("onGetReceipt", | |
| array( | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('offline'), | |
| $node->getAttribute('retry') | |
| )); | |
| } | |
| if ($node->getTag() == "iq" | |
| && $node->getAttribute('type') == "result") { | |
| if ($node->getChild("query") != null) { | |
| if (isset($this->nodeId['privacy']) && ($this->nodeId['privacy'] == $node->getAttribute('id'))) { | |
| $listChild = $node->getChild(0)->getChild(0); | |
| foreach ($listChild->getChildren() as $child) { | |
| $blockedJids[] = $child->getAttribute('value'); | |
| } | |
| $this->eventManager()->fire("onGetPrivacyBlockedList", | |
| array( | |
| $this->phoneNumber, | |
| $blockedJids | |
| )); | |
| } | |
| $this->eventManager()->fire("onGetRequestLastSeen", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getChild(0)->getAttribute('seconds') | |
| )); | |
| array_push($this->messageQueue, $node); | |
| } | |
| if ($node->getChild("props") != null) { | |
| //server properties | |
| $props = array(); | |
| foreach($node->getChild(0)->getChildren() as $child) { | |
| $props[$child->getAttribute("name")] = $child->getAttribute("value"); | |
| } | |
| $this->eventManager()->fire("onGetServerProperties", | |
| array( | |
| $this->phoneNumber, | |
| $node->getChild(0)->getAttribute("version"), | |
| $props | |
| )); | |
| } | |
| if ($node->getChild("picture") != null) { | |
| $this->eventManager()->fire("onGetProfilePicture", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute("from"), | |
| $node->getChild("picture")->getAttribute("type"), | |
| $node->getChild("picture")->getData() | |
| )); | |
| } | |
| if ($node->getChild("media") != null || $node->getChild("duplicate") != null) { | |
| $this->processUploadResponse($node); | |
| } | |
| if (strpos($node->getAttribute("from"), Constants::WHATSAPP_GROUP_SERVER) !== false) { | |
| //There are multiple types of Group reponses. Also a valid group response can have NO children. | |
| //Events fired depend on text in the ID field. | |
| $groupList = array(); | |
| $groupNodes = array(); | |
| if ($node->getChild(0) != null && $node->getChild(0)->getChildren() != null) { | |
| foreach ($node->getChild(0)->getChildren() as $child) { | |
| $groupList[] = $child->getAttributes(); | |
| $groupNodes[] = $child; | |
| } | |
| } | |
| if ($this->nodeId['groupcreate'] == $node->getAttribute('id')) { | |
| $this->groupId = $node->getChild(0)->getAttribute('id'); | |
| $this->eventManager()->fire("onGroupsChatCreate", | |
| array( | |
| $this->phoneNumber, | |
| $this->groupId | |
| )); | |
| } | |
| if ($this->nodeId['leavegroup'] == $node->getAttribute('id')) { | |
| $this->groupId = $node->getChild(0)->getChild(0)->getAttribute('id'); | |
| $this->eventManager()->fire("onGroupsChatEnd", | |
| array( | |
| $this->phoneNumber, | |
| $this->groupId | |
| )); | |
| } | |
| if ($this->nodeId['getgroups'] == $node->getAttribute('id')) { | |
| $this->eventManager()->fire("onGetGroups", | |
| array( | |
| $this->phoneNumber, | |
| $groupList | |
| )); | |
| //getGroups returns a array of nodes which are exactly the same as from getGroupV2Info | |
| //so lets call this event, we have all data at hand, no need to call getGroupV2Info for every | |
| //group we are interested | |
| foreach ($groupNodes AS $groupNode) { | |
| $this->handleGroupV2InfoResponse($groupNode, true); | |
| } | |
| } | |
| if ($this->nodeId['get_groupv2_info'] == $node->getAttribute('id')) { | |
| $groupChild = $node->getChild(0); | |
| if ($groupChild != null) { | |
| $this->handleGroupV2InfoResponse($groupChild); | |
| } | |
| } | |
| } | |
| if (isset($this->nodeId['get_lists']) && ($this->nodeId['get_lists'] == $node->getAttribute('id'))) { | |
| $broadcastLists = array(); | |
| if ($node->getChild(0) != null) { | |
| $childArray = $node->getChildren(); | |
| foreach ($childArray as $list) { | |
| if ($list->getChildren() != null) { | |
| foreach ( $list->getChildren() as $sublist) { | |
| $id = $sublist->getAttribute("id"); | |
| $name = $sublist->getAttribute("name"); | |
| $broadcastLists[$id]['name'] = $name; | |
| $recipients = array(); | |
| foreach ($sublist->getChildren() as $recipient) { | |
| array_push($recipients, $recipient->getAttribute('jid')); | |
| } | |
| $broadcastLists[$id]['recipients'] = $recipients; | |
| } | |
| } | |
| } | |
| } | |
| $this->eventManager()->fire("onGetBroadcastLists", | |
| array( | |
| $this->phoneNumber, | |
| $broadcastLists | |
| )); | |
| } | |
| if ($node->getChild("pricing") != null) { | |
| $this->eventManager()->fire("onGetServicePricing", | |
| array( | |
| $this->phoneNumber, | |
| $node->getChild(0)->getAttribute("price"), | |
| $node->getChild(0)->getAttribute("cost"), | |
| $node->getChild(0)->getAttribute("currency"), | |
| $node->getChild(0)->getAttribute("expiration") | |
| )); | |
| } | |
| if ($node->getChild("extend") != null) { | |
| $this->eventManager()->fire("onGetExtendAccount", | |
| array( | |
| $this->phoneNumber, | |
| $node->getChild("account")->getAttribute("kind"), | |
| $node->getChild("account")->getAttribute("status"), | |
| $node->getChild("account")->getAttribute("creation"), | |
| $node->getChild("account")->getAttribute("expiration") | |
| )); | |
| } | |
| if ($node->getChild("normalize") != null) { | |
| $this->eventManager()->fire("onGetNormalizedJid", | |
| array( | |
| $this->phoneNumber, | |
| $node->getChild(0)->getAttribute("result") | |
| )); | |
| } | |
| if ($node->getChild("status") != null) { | |
| $child = $node->getChild("status"); | |
| foreach($child->getChildren() as $status) | |
| { | |
| $this->eventManager()->fire("onGetStatus", | |
| array( | |
| $this->phoneNumber, | |
| $status->getAttribute("jid"), | |
| "requested", | |
| $node->getAttribute("id"), | |
| $status->getAttribute("t"), | |
| $status->getData() | |
| )); | |
| } | |
| } | |
| } | |
| if ($node->getTag() == "iq" && $node->getAttribute('type') == "error") { | |
| $this->eventManager()->fire("onGetError", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getChild(0) | |
| )); | |
| } | |
| if ($node->getTag() == "message" && $node->getAttribute('type') == "media" && $node->getChild(0)->getAttribute('type') == "image" ) { | |
| $msgId = $this->createMsgId(); | |
| $ackNode = new ProtocolNode("ack", | |
| array( | |
| "url" => $node->getChild(0)->getAttribute('url') | |
| ), null, null); | |
| $iqNode = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgId, | |
| "xmlns" => "w:m", | |
| "type" => "set", | |
| "to" => Constants::WHATSAPP_SERVER | |
| ), array($ackNode), null); | |
| $this->sendNode($iqNode); | |
| } | |
| $children = $node->getChild(0); | |
| if ($node->getTag() == "stream:error" && !empty($children) && $node->getChild(0)->getTag() == "system-shutdown") | |
| { | |
| $this->eventManager()->fire("onStreamError", | |
| array( | |
| $node->getChild(0)->getTag() | |
| )); | |
| } | |
| if ($node->getTag() == "stream:error") { | |
| $this->eventManager()->fire("onStreamError", | |
| array( | |
| $node->getChild(0)->getTag() | |
| )); | |
| } | |
| if ($node->getTag() == "notification") { | |
| $name = $node->getAttribute("notify"); | |
| $type = $node->getAttribute("type"); | |
| switch($type) | |
| { | |
| case "status": | |
| $this->eventManager()->fire("onGetStatus", | |
| array( | |
| $this->phoneNumber, //my number | |
| $node->getAttribute("from"), | |
| $node->getChild(0)->getTag(), | |
| $node->getAttribute("id"), | |
| $node->getAttribute("t"), | |
| $node->getChild(0)->getData() | |
| )); | |
| break; | |
| case "picture": | |
| if ($node->hasChild('set')) { | |
| $this->eventManager()->fire("onProfilePictureChanged", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('t') | |
| )); | |
| } else if ($node->hasChild('delete')) { | |
| $this->eventManager()->fire("onProfilePictureDeleted", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('id'), | |
| $node->getAttribute('t') | |
| )); | |
| } | |
| //TODO | |
| break; | |
| case "contacts": | |
| //TODO | |
| break; | |
| case "encrypt": | |
| $value = $node->getChild(0)->getAttribute('value'); | |
| if (is_numeric($value)) { | |
| $this->eventManager()->fire("onGetKeysLeft", | |
| array( | |
| $this->phoneNumber, | |
| $node->getChild(0)->getAttribute('value') | |
| )); | |
| } | |
| else { | |
| echo "Corrupt Stream: value " . $value . "is not numeric"; | |
| } | |
| break; | |
| case "w:gp2": | |
| if ($node->hasChild('remove')) { | |
| if ($node->getChild(0)->hasChild('participant')) | |
| $this->eventManager()->fire("onGroupsParticipantsRemove", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getChild(0)->getChild(0)->getAttribute('jid') | |
| )); | |
| } else if ($node->hasChild('add')) { | |
| $this->eventManager()->fire("onGroupsParticipantsAdd", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getChild(0)->getChild(0)->getAttribute('jid') | |
| )); | |
| } | |
| else if ($node->hasChild('create')) { | |
| $groupMembers = array(); | |
| foreach ($node->getChild(0)->getChild(0)->getChildren() AS $cn) { | |
| $groupMembers[] = $cn->getAttribute('jid'); | |
| } | |
| $this->eventManager()->fire("onGroupisCreated", | |
| array( | |
| $this->phoneNumber, | |
| $node->getChild(0)->getChild(0)->getAttribute('creator'), | |
| $node->getChild(0)->getChild(0)->getAttribute('id'), | |
| $node->getChild(0)->getChild(0)->getAttribute('subject'), | |
| $node->getAttribute('participant'), | |
| $node->getChild(0)->getChild(0)->getAttribute('creation'), | |
| $groupMembers | |
| )); | |
| } | |
| else if ($node->hasChild('subject')) { | |
| $this->eventManager()->fire("onGetGroupsSubject", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getAttribute('t'), | |
| $node->getAttribute('participant'), | |
| $node->getAttribute('notify'), | |
| $node->getChild(0)->getAttribute('subject') | |
| )); | |
| } | |
| else if ($node->hasChild('promote')) { | |
| $promotedJIDs = array(); | |
| foreach ($node->getChild(0)->getChildren() AS $cn) { | |
| $promotedJIDs[] = $cn->getAttribute('jid'); | |
| } | |
| $this->eventManager()->fire("onGroupsParticipantsPromote", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), //Group-JID | |
| $node->getAttribute('t'), //Time | |
| $node->getAttribute('participant'), //Issuer-JID | |
| $node->getAttribute('notify'), //Issuer-Name | |
| $promotedJIDs, | |
| ) | |
| ); | |
| } | |
| break; | |
| case "account": | |
| if (($node->getChild(0)->getAttribute('author')) == "") | |
| $author = "Paypal"; | |
| else | |
| $author = $node->getChild(0)->getAttribute('author'); | |
| $this->eventManager()->fire("onPaidAccount", | |
| array( | |
| $this->phoneNumber, | |
| $author, | |
| $node->getChild(0)->getChild(0)->getAttribute('kind'), | |
| $node->getChild(0)->getChild(0)->getAttribute('status'), | |
| $node->getChild(0)->getChild(0)->getAttribute('creation'), | |
| $node->getChild(0)->getChild(0)->getAttribute('expiration') | |
| )); | |
| break; | |
| case "features": | |
| if ($node->getChild(0)->getChild(0) == "encrypt") { | |
| $this->eventManager()->fire("onGetFeature", | |
| array( | |
| $this->phoneNumber, | |
| $node->getAttribute('from'), | |
| $node->getChild(0)->getChild(0)->getAttribute('value'), | |
| )); | |
| } | |
| break; | |
| default: | |
| throw new Exception("Method $type not implemented"); | |
| } | |
| $this->sendNotificationAck($node); | |
| } | |
| if ($node->getTag() == "ib") | |
| { | |
| foreach($node->getChildren() as $child) | |
| { | |
| switch($child->getTag()) | |
| { | |
| case "dirty": | |
| $this->sendClearDirty(array($child->getAttribute("type"))); | |
| break; | |
| case "account": | |
| $this->eventManager()->fire("onPaymentRecieved", | |
| array( | |
| $this->phoneNumber, | |
| $child->getAttribute("kind"), | |
| $child->getAttribute("status"), | |
| $child->getAttribute("creation"), | |
| $child->getAttribute("expiration") | |
| )); | |
| break; | |
| case "offline": | |
| break; | |
| default: | |
| throw new Exception("ib handler for " . $child->getTag() . " not implemented"); | |
| } | |
| } | |
| } | |
| // Disconnect socket on stream error. | |
| if ($node->getTag() == "stream:error") | |
| { | |
| $this->disconnect(); | |
| } | |
| } | |
| /** | |
| * @param $node ProtocolNode | |
| */ | |
| protected function sendNotificationAck($node) | |
| { | |
| $from = $node->getAttribute("from"); | |
| $to = $node->getAttribute("to"); | |
| $participant = $node->getAttribute("participant"); | |
| $id = $node->getAttribute("id"); | |
| $type = $node->getAttribute("type"); | |
| $attributes = array(); | |
| if ($to) | |
| $attributes["from"] = $to; | |
| if ($participant) | |
| $attributes["participant"] = $participant; | |
| $attributes["to"] = $from; | |
| $attributes["class"] = "notification"; | |
| $attributes["id"] = $id; | |
| $attributes["type"] = $type; | |
| $ack = new ProtocolNode("ack", $attributes, null, null); | |
| $this->sendNode($ack); | |
| } | |
| /** | |
| * Process and save media image. | |
| * | |
| * @param ProtocolNode $node ProtocolNode containing media | |
| */ | |
| protected function processMediaImage($node) | |
| { | |
| $media = $node->getChild("media"); | |
| if ($media != null) { | |
| $filename = $media->getAttribute("file"); | |
| $url = $media->getAttribute("url"); | |
| //save thumbnail | |
| file_put_contents(__DIR__ . DIRECTORY_SEPARATOR . Constants::DATA_FOLDER . DIRECTORY_SEPARATOR . Constants::MEDIA_FOLDER . DIRECTORY_SEPARATOR . 'thumb_' . $filename, $media->getData()); | |
| //download and save original | |
| file_put_contents(__DIR__ . DIRECTORY_SEPARATOR . Constants::DATA_FOLDER . DIRECTORY_SEPARATOR . Constants::MEDIA_FOLDER . DIRECTORY_SEPARATOR . $filename, file_get_contents($url)); | |
| } | |
| } | |
| /** | |
| * Processes received picture node. | |
| * | |
| * @param ProtocolNode $node ProtocolNode containing the picture | |
| */ | |
| protected function processProfilePicture($node) | |
| { | |
| $pictureNode = $node->getChild("picture"); | |
| if ($pictureNode != null) { | |
| if ($pictureNode->getAttribute("type") == "preview") { | |
| $filename = __DIR__ . DIRECTORY_SEPARATOR . Constants::DATA_FOLDER . DIRECTORY_SEPARATOR . Constants::PICTURES_FOLDER . DIRECTORY_SEPARATOR . 'preview_' . $node->getAttribute('from') . 'jpg'; | |
| } else { | |
| $filename = __DIR__ . DIRECTORY_SEPARATOR . Constants::DATA_FOLDER . DIRECTORY_SEPARATOR . Constants::PICTURES_FOLDER . DIRECTORY_SEPARATOR . $node->getAttribute('from') . '.jpg'; | |
| } | |
| file_put_contents($filename, $pictureNode->getData()); | |
| } | |
| } | |
| /** | |
| * If the media file was originally from a URL, this function either deletes it | |
| * or renames it depending on the user option. | |
| * | |
| * @param bool $storeURLmedia Save or delete the media file from local server | |
| */ | |
| protected function processTempMediaFile($storeURLmedia) | |
| { | |
| if (isset($this->mediaFileInfo['url'])) { | |
| if ($storeURLmedia) { | |
| if (is_file($this->mediaFileInfo['filepath'])) { | |
| rename($this->mediaFileInfo['filepath'], $this->mediaFileInfo['filepath'] . $this->mediaFileInfo['fileextension']); | |
| } | |
| } else { | |
| if (is_file($this->mediaFileInfo['filepath'])) { | |
| unlink($this->mediaFileInfo['filepath']); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Process media upload response | |
| * | |
| * @param ProtocolNode $node Message node | |
| * @return bool | |
| */ | |
| protected function processUploadResponse($node) | |
| { | |
| $id = $node->getAttribute("id"); | |
| $messageNode = @$this->mediaQueue[$id]; | |
| if ($messageNode == null) { | |
| //message not found, can't send! | |
| $this->eventManager()->fire("onMediaUploadFailed", | |
| array( | |
| $this->phoneNumber, | |
| $id, | |
| $node, | |
| $messageNode, | |
| "Message node not found in queue" | |
| )); | |
| return false; | |
| } | |
| $duplicate = $node->getChild("duplicate"); | |
| if ($duplicate != null) { | |
| //file already on whatsapp servers | |
| $url = $duplicate->getAttribute("url"); | |
| $filesize = $duplicate->getAttribute("size"); | |
| // $mimetype = $duplicate->getAttribute("mimetype"); | |
| $filehash = $duplicate->getAttribute("filehash"); | |
| $filetype = $duplicate->getAttribute("type"); | |
| // $width = $duplicate->getAttribute("width"); | |
| // $height = $duplicate->getAttribute("height"); | |
| $exploded = explode("/", $url); | |
| $filename = array_pop($exploded); | |
| } else { | |
| //upload new file | |
| $json = WhatsMediaUploader::pushFile($node, $messageNode, $this->mediaFileInfo, $this->phoneNumber); | |
| if (!$json) { | |
| //failed upload | |
| $this->eventManager()->fire("onMediaUploadFailed", | |
| array( | |
| $this->phoneNumber, | |
| $id, | |
| $node, | |
| $messageNode, | |
| "Failed to push file to server" | |
| )); | |
| return false; | |
| } | |
| $url = $json->url; | |
| $filesize = $json->size; | |
| // $mimetype = $json->mimetype; | |
| $filehash = $json->filehash; | |
| $filetype = $json->type; | |
| // $width = $json->width; | |
| // $height = $json->height; | |
| $filename = $json->name; | |
| } | |
| $mediaAttribs = array(); | |
| $mediaAttribs["type"] = $filetype; | |
| $mediaAttribs["url"] = $url; | |
| $mediaAttribs["encoding"] = "raw"; | |
| $mediaAttribs["file"] = $filename; | |
| $mediaAttribs["size"] = $filesize; | |
| if ($this->mediaQueue[$id]['caption'] != '') { | |
| $mediaAttribs["caption"] = $this->mediaQueue[$id]['caption']; | |
| } | |
| $filepath = $this->mediaQueue[$id]['filePath']; | |
| $to = $this->mediaQueue[$id]['to']; | |
| $icon = ""; | |
| switch ($filetype) { | |
| case "image": | |
| $caption = $this->mediaQueue[$id]['caption']; | |
| $icon = createIcon($filepath); | |
| break; | |
| case "video": | |
| $caption = $this->mediaQueue[$id]['caption']; | |
| $icon = createVideoIcon($filepath); | |
| break; | |
| default: | |
| $caption = ''; | |
| $icon = ''; | |
| break; | |
| } | |
| //Retrieve Message ID | |
| $message_id = $messageNode['message_id']; | |
| $mediaNode = new ProtocolNode("media", $mediaAttribs, null, $icon); | |
| if (is_array($to)) { | |
| $this->sendBroadcast($to, $mediaNode, "media"); | |
| } else { | |
| $this->sendMessageNode($to, $mediaNode, $message_id); | |
| } | |
| $this->eventManager()->fire("onMediaMessageSent", | |
| array( | |
| $this->phoneNumber, | |
| $to, | |
| $id, | |
| $filetype, | |
| $url, | |
| $filename, | |
| $filesize, | |
| $filehash, | |
| $caption, | |
| $icon | |
| )); | |
| return true; | |
| } | |
| /** | |
| * Read 1024 bytes from the whatsapp server. | |
| * | |
| * @throws Exception | |
| */ | |
| public function readStanza() | |
| { | |
| $buff = ''; | |
| if ($this->socket != null) { | |
| $header = @socket_read($this->socket, 3);//read stanza header | |
| if ($header === false) { | |
| $error = "socket EOF, closing socket..."; | |
| socket_close($this->socket); | |
| $this->socket = null; | |
| $this->eventManager()->fire("onClose", | |
| array( | |
| $this->phoneNumber, | |
| $error | |
| ) | |
| ); | |
| } | |
| if (strlen($header) == 0) { | |
| //no data received | |
| return; | |
| } | |
| if (strlen($header) != 3) { | |
| throw new ConnectionException("Failed to read stanza header"); | |
| } | |
| $treeLength = (ord($header[0]) & 0x0F) << 16; | |
| $treeLength |= ord($header[1]) << 8; | |
| $treeLength |= ord($header[2]) << 0; | |
| //read full length | |
| $buff = socket_read($this->socket, $treeLength); | |
| //$trlen = $treeLength; | |
| $len = strlen($buff); | |
| //$prev = 0; | |
| while (strlen($buff) < $treeLength) { | |
| $toRead = $treeLength - strlen($buff); | |
| $buff .= socket_read($this->socket, $toRead); | |
| if ($len == strlen($buff)) { | |
| //no new data read, fuck it | |
| break; | |
| } | |
| $len = strlen($buff); | |
| } | |
| if (strlen($buff) != $treeLength) { | |
| throw new ConnectionException("Tree length did not match received length (buff = " . strlen($buff) . " & treeLength = $treeLength)"); | |
| } | |
| $buff = $header . $buff; | |
| } else { | |
| $this->eventManager()->fire("onDisconnect", | |
| array( | |
| $this->phoneNumber, | |
| $this->socket | |
| )); | |
| } | |
| return $buff; | |
| } | |
| /** | |
| * Checks that the media file to send is of allowable filetype and within size limits. | |
| * | |
| * @param string $filepath The URL/URI to the media file | |
| * @param int $maxSize Maximum filesize allowed for media type | |
| * @param string $to Recipient ID/number | |
| * @param string $type media filetype. 'audio', 'video', 'image' | |
| * @param array $allowedExtensions An array of allowable file types for the media file | |
| * @param bool $storeURLmedia Keep a copy of the media file | |
| * @param string $caption * | |
| * @return string|null Message ID if successfully, null if not. | |
| */ | |
| protected function sendCheckAndSendMedia($filepath, $maxSize, $to, $type, $allowedExtensions, $storeURLmedia, $caption = "") | |
| { | |
| if ($this->getMediaFile($filepath, $maxSize) == true) { | |
| if (in_array($this->mediaFileInfo['fileextension'], $allowedExtensions)) { | |
| $b64hash = base64_encode(hash_file("sha256", $this->mediaFileInfo['filepath'], true)); | |
| //request upload and get Message ID | |
| $id =$this->sendRequestFileUpload($b64hash, $type, $this->mediaFileInfo['filesize'], $this->mediaFileInfo['filepath'], $to, $caption); | |
| $this->processTempMediaFile($storeURLmedia); | |
| // Return message ID. Make pull request for this. | |
| return $id; | |
| } else { | |
| //Not allowed file type. | |
| $this->processTempMediaFile($storeURLmedia); | |
| return null; | |
| } | |
| } else { | |
| //Didn't get media file details. | |
| return null; | |
| } | |
| } | |
| /** | |
| * Send a broadcast | |
| * @param array $targets Array of numbers to send to | |
| * @param object $node | |
| * @param $type | |
| * @return string | |
| */ | |
| protected function sendBroadcast($targets, $node, $type) | |
| { | |
| if (!is_array($targets)) { | |
| $targets = array($targets); | |
| } | |
| $toNodes = array(); | |
| foreach ($targets as $target) { | |
| $jid = $this->getJID($target); | |
| $hash = array("jid" => $jid); | |
| $toNode = new ProtocolNode("to", $hash, null, null); | |
| $toNodes[] = $toNode; | |
| } | |
| $broadcastNode = new ProtocolNode("broadcast", null, $toNodes, null); | |
| $msgId = $this->createMsgId(); | |
| $messageNode = new ProtocolNode("message", | |
| array( | |
| "to" => time()."@broadcast", | |
| "type" => $type, | |
| "id" => $msgId | |
| ), array($node, $broadcastNode), null); | |
| $this->sendNode($messageNode); | |
| $this->waitForServer($msgId); | |
| //listen for response | |
| $this->eventManager()->fire("onSendMessage", | |
| array( | |
| $this->phoneNumber, | |
| $targets, | |
| $msgId, | |
| $node | |
| )); | |
| return $msgId; | |
| } | |
| /** | |
| * Send data to the WhatsApp server. | |
| * @param string $data | |
| * | |
| * @throws Exception | |
| */ | |
| protected function sendData($data) | |
| { | |
| if ($this->socket != null) { | |
| if (socket_write($this->socket, $data, strlen($data)) === false) { | |
| $this->disconnect(); | |
| throw new ConnectionException('Connection Closed!'); | |
| } | |
| } | |
| } | |
| /** | |
| * Send the getGroupList request to WhatsApp | |
| * @param string $type Type of list of groups to retrieve. "owning" or "participating" | |
| */ | |
| protected function sendGetGroupsFiltered($type) | |
| { | |
| $msgID = $this->nodeId['getgroups'] = $this->createMsgId(); | |
| $child = new ProtocolNode($type, null, null, null); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $msgID, | |
| "type" => "get", | |
| "xmlns" => "w:g2", | |
| "to" => Constants::WHATSAPP_GROUP_SERVER | |
| ), array($child), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($msgID); | |
| } | |
| /** | |
| * Change participants of a group. | |
| * | |
| * @param string $groupId The group ID. | |
| * @param array $participants An array with the participants. | |
| * @param string $tag The tag action. 'add' or 'remove' | |
| * @param $id | |
| */ | |
| protected function sendGroupsChangeParticipants($groupId, $participants, $tag, $id) | |
| { | |
| $_participants = array(); | |
| foreach ($participants as $participant) { | |
| $_participants[] = new ProtocolNode("participant", array("jid" => $this->getJID($participant)), null, ""); | |
| } | |
| $childHash = array(); | |
| $child = new ProtocolNode($tag, $childHash, $_participants, ""); | |
| $node = new ProtocolNode("iq", | |
| array( | |
| "id" => $id, | |
| "type" => "set", | |
| "xmlns" => "w:g2", | |
| "to" => $this->getJID($groupId) | |
| ), array($child), ""); | |
| $this->sendNode($node); | |
| $this->waitForServer($id); | |
| } | |
| /** | |
| * Send node to the servers. | |
| * | |
| * @param $to | |
| * @param ProtocolNode $node | |
| * @param null $id | |
| * | |
| * @return string Message ID. | |
| */ | |
| protected function sendMessageNode($to, $node, $id = null) | |
| { | |
| $msgId = ($id == null) ? $this->createMsgId() : $id; | |
| $to = $this->getJID($to); | |
| $messageNode = new ProtocolNode("message", array( | |
| 'to' => $to, | |
| 'type' => ($node->getTag() == "body") ? 'text' : 'media', | |
| 'id' => $msgId, | |
| 't' => time() | |
| ), array($node), ""); | |
| $this->sendNode($messageNode); | |
| $this->eventManager()->fire("onSendMessage", | |
| array( | |
| $this->phoneNumber, | |
| $to, | |
| $msgId, | |
| $node | |
| )); | |
| $this->waitForServer($msgId); | |
| return $msgId; | |
| } | |
| /** | |
| * Tell the server we received the message. | |
| * | |
| * @param ProtocolNode $msg The ProtocolTreeNode that contains the message. | |
| * @param string $type | |
| * @param string $participant | |
| */ | |
| protected function sendMessageReceived($msg, $type = "read", $participant = null) | |
| { | |
| $messageHash = array(); | |
| if ($type == "read") { | |
| $messageHash["type"] = $type; | |
| } | |
| if ($participant != null) { | |
| $messageHash["participant"] = $participant; | |
| } | |
| $messageHash["to"] = $msg->getAttribute("from"); | |
| $messageHash["id"] = $msg->getAttribute("id"); | |
| $messageHash["t"] = time(); | |
| $messageNode = new ProtocolNode("receipt", $messageHash, null, null); | |
| $this->sendNode($messageNode); | |
| $this->eventManager()->fire("onSendMessageReceived", | |
| array( | |
| $this->phoneNumber, | |
| $msg->getAttribute("id"), | |
| $msg->getAttribute("from"), | |
| $type | |
| )); | |
| } | |
| /** | |
| * Send node to the WhatsApp server. | |
| * @param ProtocolNode $node | |
| * @param bool $encrypt | |
| */ | |
| protected function sendNode($node, $encrypt = true) | |
| { | |
| $this->debugPrint($node->nodeString("tx ") . "\n"); | |
| $this->sendData($this->writer->write($node, $encrypt)); | |
| } | |
| /** | |
| * Send request to upload file | |
| * | |
| * @param string $b64hash A base64 hash of file | |
| * @param string $type File type | |
| * @param string $size File size | |
| * @param string $filepath Path to image file | |
| * @param mixed $to Recipient(s) | |
| * @param string $caption | |
| * @return string Message ID | |
| */ | |
| protected function sendRequestFileUpload($b64hash, $type, $size, $filepath, $to, $caption = "") | |
| { | |
| $id = $this->createMsgId(); | |
| if (!is_array($to)) { | |
| $to = $this->getJID($to); | |
| } | |
| $mediaNode = new ProtocolNode("media", array( | |
| 'hash' => $b64hash, | |
| 'type' => $type, | |
| 'size' => $size | |
| ), null, null); | |
| $node = new ProtocolNode("iq", array( | |
| 'id' => $id, | |
| 'to' => Constants::WHATSAPP_SERVER, | |
| 'type' => 'set', | |
| 'xmlns' => 'w:m' | |
| ), array($mediaNode), null); | |
| //add to queue | |
| $messageId = $this->createMsgId(); | |
| $this->mediaQueue[$id] = array( | |
| "messageNode" => $node, | |
| "filePath" => $filepath, | |
| "to" => $to, | |
| "message_id" => $messageId, | |
| "caption" => $caption | |
| ); | |
| $this->sendNode($node); | |
| $this->waitForServer($id); | |
| // Return message ID. Make pull request for this. | |
| return $messageId; | |
| } | |
| /** | |
| * Set your profile picture | |
| * | |
| * @param string $jid | |
| * @param string $filepath URL or localpath to image file | |
| */ | |
| protected function sendSetPicture($jid, $filepath) | |
| { | |
| $nodeID = $this->createMsgId(); | |
| $data = preprocessProfilePicture($filepath); | |
| $preview = createIconGD($filepath, 96, true); | |
| $picture = new ProtocolNode("picture", array("type" => "image"), null, $data); | |
| $preview = new ProtocolNode("picture", array("type" => "preview"), null, $preview); | |
| $node = new ProtocolNode("iq", array( | |
| 'id' => $nodeID, | |
| 'to' => $this->getJID($jid), | |
| 'type' => 'set', | |
| 'xmlns' => 'w:profile:picture' | |
| ), array($picture, $preview), null); | |
| $this->sendNode($node); | |
| $this->waitForServer($nodeID); | |
| } | |
| /** | |
| * Parse the message text for emojis | |
| * | |
| * This will look for special strings in the message text | |
| * that need to be replaced with a unicode character to show | |
| * the corresponding emoji. | |
| * | |
| * Emojis should be entered in the message text either as the | |
| * correct unicode character directly, or if this isn't possible, | |
| * by putting a placeholder of ##unicodeNumber## in the message text. | |
| * Include the surrounding ## | |
| * eg: | |
| * ##1f604## this will show the smiling face | |
| * ##1f1ec_1f1e7## this will show the UK flag. | |
| * | |
| * Notice that if 2 unicode characters are required they should be joined | |
| * with an underscore. | |
| * | |
| * | |
| * @param string $txt The message to be parsed for emoji code. | |
| * | |
| * @return string | |
| */ | |
| private function parseMessageForEmojis($txt) | |
| { | |
| $matches = null; | |
| preg_match_all('/##(.*?)##/', $txt, $matches, PREG_SET_ORDER); | |
| if (is_array($matches)) { | |
| foreach ($matches as $emoji) { | |
| $txt = str_ireplace($emoji[0], $this->unichr((string) $emoji[1]), $txt); | |
| } | |
| } | |
| return $txt; | |
| } | |
| /** | |
| * Creates the correct unicode character from the unicode code point | |
| * | |
| * @param integer $int | |
| * | |
| * @return string | |
| */ | |
| private function unichr($int) | |
| { | |
| $string = null; | |
| $multiChars = explode('_', $int); | |
| foreach ($multiChars as $char) { | |
| $string .= mb_convert_encoding('&#' . intval($char, 16) . ';', 'UTF-8', 'HTML-ENTITIES'); | |
| } | |
| return $string; | |
| } | |
| /** | |
| * @param string $jid | |
| * | |
| * @return string | |
| */ | |
| public static function parseJID($jid) | |
| { | |
| $parts = explode('@', $jid); | |
| $parts = reset($parts); | |
| return $parts; | |
| } | |
| /** | |
| * @param ProtocolNode $groupNode | |
| */ | |
| protected function handleGroupV2InfoResponse(ProtocolNode $groupNode, $fromGetGroups = false) | |
| { | |
| $creator = $groupNode->getAttribute('creator'); | |
| $creation = $groupNode->getAttribute('creation'); | |
| $subject = $groupNode->getAttribute('subject'); | |
| $groupID = $groupNode->getAttribute('id'); | |
| $participants = array(); | |
| $admins = array(); | |
| if ($groupNode->getChild(0) != null) { | |
| foreach ($groupNode->getChildren() as $child) { | |
| $participants[] = $child->getAttribute('jid'); | |
| if ($child->getAttribute('type') == "admin") | |
| $admins[] = $child->getAttribute('jid'); | |
| } | |
| } | |
| $this->eventManager()->fire("onGetGroupV2Info", | |
| array( | |
| $this->phoneNumber, | |
| $groupID, | |
| $creator, | |
| $creation, | |
| $subject, | |
| $participants, | |
| $admins, | |
| $fromGetGroups | |
| ) | |
| ); | |
| } | |
| } |