<commit_before><?php

include_once dirname(__FILE__) . '/snapchat_agent.php';
include_once dirname(__FILE__) . '/snapchat_cache.php';
include_once dirname(__FILE__) . '/func.php';

/**
 * @file
 *   Provides an implementation of the undocumented Snapchat API.
 */
class Snapchat extends SnapchatAgent {

	/**
	 * The media types for snaps from confirmed friends.
	 */
	const MEDIA_IMAGE = 0;
	const MEDIA_VIDEO = 1;
	const MEDIA_VIDEO_NOAUDIO = 2;

	/**
	 * The media type for a friend request (not technically media, but it
	 * shows up in the feed).
	 */
	const MEDIA_FRIEND_REQUEST = 3;

	/**
	 * The media types for snaps from unconfirmed friends.
	 */
	const MEDIA_FRIEND_REQUEST_IMAGE = 4;
	const MEDIA_FRIEND_REQUEST_VIDEO = 5;
	const MEDIA_FRIEND_REQUEST_VIDEO_NOAUDIO = 6;

	/**
	 * Snap statuses.
	 */
	const STATUS_NONE = -1;
	const STATUS_SENT = 0;
	const STATUS_DELIVERED = 1;
	const STATUS_OPENED = 2;
	const STATUS_SCREENSHOT = 3;

	/**
	 * Friend statuses.
	 */
	const FRIEND_CONFIRMED = 0;
	const FRIEND_UNCONFIRMED = 1;
	const FRIEND_BLOCKED = 2;
	const FRIEND_DELETED = 3;

	/**
	 * Privacy settings.
	 */
	const PRIVACY_EVERYONE = 0;
	const PRIVACY_FRIENDS = 1;

	protected $auth_token;
	protected $chat_auth_token;
	protected $username;
	protected $debug;
	protected $gEmail;
	protected $gPasswd;

	/**
	 * Sets up some initial variables. If a username and password are passed in,
	 * we attempt to log in. If a username and auth token are passed in, we'll
	 * bypass the login process and use those values.
	 *
	 * @param string $username
	 *   The username for the Snapchat account.
	 * @param string $password
	 *   The password associated with the username, if logging in.
	 */
	public function __construct($username = NULL, $gEmail, $gPasswd, $debug = FALSE)
	{
		$this->username = $username;
		$this->debug = $debug;
		$this->gEmail = $gEmail;
		$this->gPasswd = $gPasswd;

		if (file_exists(__DIR__ . "/auth-$this->username.dat"))
		{
			$this->auth_token = file_get_contents(__DIR__ . "/auth-$this->username.dat");
		}
	}

	public function getDeviceToken()
	{
		$timestamp = parent::timestamp();

		$result = parent::post(
			'/loq/device_id',
			array(
				'timestamp' => $timestamp,
			),
			array(
				parent::STATIC_TOKEN,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}

	public function device()
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/all_updates',
			array(
				'type' => "android",
				'timestamp' => $timestamp,
				'username' => $this->username,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}
	private function getAttestation(){
        	$binaryRequest = base64_decode("<KEY>");
        	$ch = curl_init("https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=JSON&key=<KEY>");
        	curl_setopt($ch, CURLOPT_HEADER, false);
        	curl_setopt($ch, CURLOPT_POST, true);
        	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        	curl_setopt($ch, CURLOPT_POSTFIELDS, $binaryRequest);
        	curl_setopt($ch, CURLOPT_USERAGENT, "SafetyNet/7329000 (A116 _Quad KOT49H); gzip");
        	curl_setopt($ch, CURLOPT_ENCODING, "gzip");
        	curl_setopt($ch, CURLOPT_HTTPHEADER, array("content-type: application/x-protobuf", "Accept:", "Expect:"));
        	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        	$data = curl_exec($ch);
        	if(curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
            		$d = json_decode($data,true);
            		return $d['signedAttestation'];//the json response, json_decode and grab the signedAttestation value.
        	} else {
            		return curl_error($ch);
        	}
    	}
	public function getAuthToken()
	{
		if(($this->gEmail != null) && ($this->gPasswd != null))
		{
				$ch = curl_init();
				$postfields = array(
					'device_country' => 'us',
					'operatorCountry' => 'us',
					'lang' => 'en_US',
					'sdk_version' => '19',
					'google_play_services_version' => '7097038',
					'accountType' => 'HOSTED_OR_GOOGLE',
					'Email' => $this->gEmail,
					'service' => 'audience:server:client_id:694893979329-l59f3phl42et9clpoo296d8raqoljl6p.apps.<EMAIL>',
					'source' => 'android',
					'androidId' => '378c184c6070c26c',
					'app' => 'com.snapchat.android',
					'client_sig' => '49f6badb81d89a9e38d65de76f09355071bd67e7',
					'callerPkg' => 'com.snapchat.android',
					'callerSig' => '49f6badb81d89a9e38d65de76f09355071bd67e7'
				);

				exec('java -version', $output, $returnCode);
				if ($returnCode === 0)
				{
						exec("java -jar " . __DIR__ . "/encrypter.jar $this->gEmail $this->gPasswd", $result);
						$postfields['EncryptedPasswd'] = array_shift(array_slice($result, 0, 1));
				}
				else
				{
						$postfields['Passwd'] = $this->gPasswd;
				}

			$headers = array(
				'device: 378c184c6070c26c',
				'app: com.snapchat.android',
				'User-Agent: GoogleAuth/1.4 (mako JDQ39)',
				'Accept-Encoding: gzip'
			);

			curl_setopt($ch, CURLOPT_URL, "https://android.clients.google.com/auth");
			curl_setopt($ch, CURLOPT_POST, TRUE);
			curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postfields));
			curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
			curl_setopt($ch, CURLOPT_ENCODING, "gzip");
			curl_setopt($ch, CURLINFO_HEADER_OUT, true);

			$result = curl_exec($ch);

			if($this->debug)
			{
				echo "\nREQUEST TO: https://android.clients.google.com/auth\n";
				echo 'DATA: ' . print_r($postfields) . "\n";
				echo 'RESULT: ' . $result . "\n";
			}

			if(curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200)
			{
				$return['error'] = 1;
				$return['data'] = $result;

				return $return;
			}

			curl_close($ch);

			$return['error'] = 0;
			$exploded = explode("\n", $result);
			$return['auth'] = substr($exploded[1], 5);
		}
		else
		{
			$return['error'] = 1;
			$return['data'] = "Email: $this->gEmail Passwd: $<PASSWORD>";
		}

		return $return;
	}

	private function getGCMToken()
	{
		$ch = curl_init();
		$timestamp = parent::timestamp() / 1000;
		$timestamp = (int) $timestamp;
		$postfields = array(
			'device' => '3847872624728098287',
			'sender' => '191410808405',
			'app_ver' => '564',
			'gcm_ver' => '7097038',
			'app' => 'com.snapchat.android',
			'iat' => $timestamp,
			'cert' => '49f6badb81d89a9e38d65de76f09355071bd67e7'
		);

		$headers = array(
			'app: com.snapchat.android',
			'User-Agent: Android-GCM/1.5 (m7 KOT49H)',
			'Authorization: AidLogin 3847872624728098287:1187196130325105010'
		);

		curl_setopt($ch, CURLOPT_URL, "https://android.clients.google.com/c2dm/register3");
		curl_setopt($ch, CURLOPT_POST, TRUE);
		curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postfields));
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($ch, CURLOPT_ENCODING, "gzip");
		curl_setopt($ch, CURLINFO_HEADER_OUT, true);

		$result = curl_exec($ch);

		if($this->debug)
		{
			echo "\nREQUEST TO: https://android.clients.google.com/c2dm/register3\n";
			echo 'DATA: ' . print_r($postfields) . "\n";
			echo 'RESULT: ' . $result . "\n";
		}

		if(curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200)
		{
			$return['error'] = 1;
			$return['data'] = $result;

			return $return;
		}

		curl_close($ch);

		$return['error'] = 0;
		$return['token'] = substr($result, 6);

		return $return;
	}

	/**
	 * Handles login.
	 *
	 * @param string $password
	 *   The password associated with the username.
	 *
	 * @return mixed
	 *   The data returned by the service or FALSE if the request failed.
	 *   Generally, returns the same result as self::getUpdates().
	 */
	public function login($password, $force = FALSE)
	{
		$do = ($force && file_exists(__DIR__ . "/auth-$this->username.dat")) ? 1 : 0;

		if(($do == 1) || (!(file_exists(__DIR__ . "/auth-$this->username.dat"))))
		{
				$dtoken = $this->getDeviceToken();

				if($dtoken['error'] == 1)
				{
						$return['message'] = "Failed to get new Device token set.";
						return $return;
				}

				$timestamp = parent::timestamp();
				$req_token = parent::hash(parent::STATIC_TOKEN, $timestamp);
				$string = $this->username . "|" . $password . "|" . $timestamp . "|" . $req_token;

				$auth = $this->getAuthToken();

				if($auth['error'] == 1)
				{
						return $auth;
				}
				parent::setGAuth($auth);
        			$attestation = $this->getAttestation();
				$result = parent::post(
					'/loq/login',
					array(
						'username' => $this->username,
						'password' => <PASSWORD>,
						'height' => 1280,
						'width' => 720,
						'max_video_height' => 640,
						'max_video_width' => 480,
						'dsig' => substr(hash_hmac('sha256', $string, $dtoken['data']->dtoken1v), 0, 20),
						'dtoken1i' => $dtoken['data']->dtoken1i,
						'ptoken' => "ie",
						'timestamp' => $timestamp,
						'attestation' => $attestation,
						'application_id' => 'com.snapchat.android',
						'req_token' => $req_token,
					),
					array(
						parent::STATIC_TOKEN,
						$timestamp,
						$auth['auth']
					),
					$multipart = false,
					$debug = $this->debug
				);


				if($result['error'] == 1)
				{
					return $result;
				}

				if(isset($result['data']->updates_response->logged) && $result['data']->updates_response->logged)
				{
					$this->auth_token = $result['data']->updates_response->auth_token;
					$this->device();

					$authFile = fopen(__DIR__ . "/auth-$this->username.dat", "w");
					fwrite($authFile, $this->auth_token);
					fclose($authFile);
				}

				return $result;
		} else {
				$this->openAppEvent();
		}
	}

	/**
	 * Logs out the current user.
	 *
	 * @return bool
	 *   TRUE if successful, FALSE otherwise.
	 */
	public function logout()
	{
		// Make sure we're logged in and have a valid access token.
		if(!$this->auth_token || !$this->username)
		{
			return FALSE;
		}

		$timestamp = parent::timestamp();
		$result = parent::post(
			'/ph/logout',
			array(
				'timestamp' => $timestamp,
				'username' => $this->username,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		// Clear out the cache in case the instance is recycled.
		$this->cache = NULL;

		unlink(__DIR__ . "/auth-$this->username.dat");

		return is_null($result);
	}

	/**
	 * Creates a user account.
	 *
	 * @todo
	 *   Add better validation.
	 *
	 * @param string $username
	 *   The desired username.
	 * @param string $password
	 *   The password to associate with the account.
	 * @param string $email
	 *   The email address to associate with the account.
	 * @param $birthday string
	 *   The user's birthday (yyyy-mm-dd).
	 * @param string $phone_verification
	 *   Whether to use phone verification or not.
	 * @param string $phone_number
	 *   Phone number to use if using phone verification. (country code and phone number. with or without +)
	 *
	 * @return mixed
	 *   The data returned by the service or FALSE if registration failed.
	 *   Generally, returns the same result as calling self::getUpdates().
	 */
	public function register($username, $password, $email, $birthday, $phone_verification = FALSE, $phone_number = NULL)
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/register',
			array(
				'birthday' => $birthday,
				'password' => <PASSWORD>,
				'email' => $email,
				'timestamp' => $timestamp,
			),
			array(
				parent::STATIC_TOKEN,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		if(!isset($result["data"]->auth_token))
		{
			return FALSE;
		}
		$this->auth_token = $result['data']->auth_token;

		$timestamp = parent::timestamp();
		parent::post(
			'/loq/register_username',
			array(
				'username' => $email,
				'selected_username' => $username,
				'timestamp' => $timestamp,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		$result = $result["data"];

		// If registration is successful, set the username and auth_token.
		if(isset($result->logged) && $result->logged)
		{
			$this->auth_token = $result->auth_token;
			$this->username = $username;

			if($phone_verification)
			{
				if(!is_null($phone_number))
				{
					return $this->sendPhoneVerification($phone_number);
				}
				else
				{
					echo "\nYou must provide a phone number to verify with.";
					return FALSE;
				}
			}
			else
			{
				return $this->getCaptcha();
			}
		}
		else
		{
			return FALSE;
		}
	}

	/**
	 * Sends SMS verification.
	 *
	 * @param string $phone_number
	 *   Phone number to use if using phone verification.
	 *
	 */
	public function sendPhoneVerification($phone_number)
	{
		$ch = curl_init();
		curl_setopt_array($ch, array(CURLOPT_RETURNTRANSFER => 1, CURLOPT_HTTPHEADER => array("X-Mashape-Key: <KEY>"),CURLOPT_URL => "https://metropolis-api-phone.p.mashape.com/analysis?telephone={$phone_number}", CURLOPT_CAINFO => dirname(__FILE__) . '/ca_bundle.crt'));
		$result = curl_exec($ch);
		$result = json_decode($result, true);

		if($result["valid"])
		{
			$phone_number = str_replace(" ", "", $result["formatted-number"]);
			$countryCode = $result["iso-code"];
		}
		else
		{
			echo "\nInvalid phone number.";
			return FALSE;
		}

		$timestamp = parent::timestamp();
		$result = parent::post(
			"/bq/phone_verify",
			array(
				"timestamp" => $timestamp,
				"username" => $this->username,
				"phoneNumber" => $phone_number,
				"action" => "updatePhoneNumber",
				"countryCode" => $countryCode,
				"skipConfirmation" => true
			),
			array(
				$this->auth_token,
				$timestamp,
			)
		);

		return $result;
	}

	/**
	 * Verifies phone number.
	 *
	 * @param string $code
	 *   Code sent for verification by Snapchat.
	 *
	 */
	public function verifyPhoneNumber($code)
	{
		$timestamp = parent::timestamp();
		$req_token = parent::hash(parent::STATIC_TOKEN, $timestamp);
		$result = parent::post(
			"/bq/phone_verify",
			array(
				"timestamp" => $timestamp,
				"action" => "verifyPhoneNumber",
				"username" => $this->username,
				"code" => $code
			),
			array(
				$req_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		$result = $result["data"];
		return (isset($result->logged) && $result->logged);
	}

	public function getCaptcha()
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/bq/get_captcha',
			array(
			  'username' => $this->username,
			  'timestamp' => $timestamp,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		return $result;
	}

	public function sendCaptcha($result, $id)
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/bq/solve_captcha',
			array(
				'username' => $this->username,
				'timestamp' => $timestamp,
				'captcha_id' => substr($id, 0, strlen($id) - 4),
				'captcha_solution' => $result
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}

	public function getConversationAuth($to)
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/conversation_auth_token',
			array(
				'username' => $this->username,
				'timestamp' => $timestamp,
				'conversation_id' => implode('~', array($this->username, $to))
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}


	public function getConversationInfo($tos){
	    if(!is_array($tos)) $tos = array($tos);
		$messagesArray = array();
		$authArray = array();
	    foreach($tos as $to){
		    $authInfo = $this->getConversationAuth($to);
		    //if user is even a friend
		    if(!property_exists($authInfo["data"], "messaging_auth")) continue;
		      $authArray[$to] = $authInfo['data'];
			    $payload = $authInfo["data"]->messaging_auth->payload;
			    $mac = $authInfo["data"]->messaging_auth->mac;
			    $genID = md5(uniqid());
			    $id = strtoupper(sprintf('%08s-%04s-%04x-%04x-%12s', substr($genID, 0, 8), substr($genID, 8, 4), substr($genID, 12, 4), substr($genID, 16, 4), substr($genID, 20, 12)));
			    $messagesArray[] = array(
					"presences" => array(
						$this->username => true,
						$to => false
					),
					"receiving_video" => false,
					"supports_here" => true,
					"header" => array(
						"auth" => array(
							"mac" => $mac,
							"payload" => $payload
						),
						"to" => array(
							$to
						),
						"conv_id" => implode('~', array($this->username, $to)),
						"from" => $this->username,
						"conn_sequence_number" => 0
					),
					"retried" => false,
					"id" => $id,
					"type" => "presence"
				);
				$messagesArray[] = array(
					"presences" => array(
						$this->username => true,
						$to => false
					),
					"receiving_video" => false,
					"supports_here" => true,
					"header" => array(
						"auth" => array(
							"mac" => $mac,
							"payload" => $payload
						),
						"to" => array(
							$to
						),
						"conv_id" => implode('~', array($to, $this->username)),
						"from" => $this->username,
						"conn_sequence_number" => 0
					),
					"retried" => false,
					"id" => $id,
					"type" => "presence"
				);
		}
			$messages = json_encode($messagesArray);
			$timestamp = parent::timestamp();
			$result = parent::post(
				'/loq/conversation_post_messages',
				array(
					'auth_token' => $this->auth_token,
					'messages' => $messages,
					'timestamp' => $timestamp,
					'username' => $this->username,
				),
				array(
					$this->auth_token,
					$timestamp,
				),
				$multipart = false,
				$debug = $this->debug
			);
			$resultsf = array();
			foreach($result['data']->conversations as $convo){
				$split = explode("~", $convo->id);
				$un = (strtolower($split[0]) != strtolower($this->username)) ? $split[0] : $split[1];
				$resultsf[$un] = $convo;
			}
		return array($resultsf,$authArray);
	}

	public function sendMessage($tos, $text){
	    if(!is_array($tos)) $tos = array($tos);
	    $convoInfo = $this->getConversationInfo($tos);
	    $messagesArray = array();
	    foreach($tos as $to){
	    	if(!array_key_exists($to, $convoInfo[1])){ //check if user can be sent a message
	    		 echo "\nYou have to add {$to} as a friend first!";
	    		 continue; 
	    		}
		    if(!array_key_exists($to, $convoInfo[0])){ //new convo
				    $payload = $convoInfo[1][$to]->messaging_auth->payload;
				    $mac = $convoInfo[1][$to]->messaging_auth->mac;
				    $seq_num = 0;
				    $conv_id = implode('~', array($to, $this->username));
		    }else{ //conversation already exists
			    $payload = $convoInfo[0][$to]->conversation_messages->messaging_auth->payload;
			    $mac = $convoInfo[0][$to]->conversation_messages->messaging_auth->mac;
			    $name = $this->username;
			    $seq_num = $convoInfo[0][$to]->conversation_state->user_sequences->$name;
			    $conv_id = $convoInfo[0][$to]->id;
		    }
		    $genID = md5(uniqid());
		    $chatID = strtoupper(sprintf('%08s-%04s-%04x-%04x-%12s', substr($genID, 0, 8), substr($genID, 8, 4), substr($genID, 12, 4), substr($genID, 16, 4), substr($genID, 20, 12)));
		    $genID = md5(uniqid());
		    $id = strtoupper(sprintf('%08s-%04s-%04x-%04x-%12s', substr($genID, 0, 8), substr($genID, 8, 4), substr($genID, 12, 4), substr($genID, 16, 4), substr($genID, 20, 12)));
		    $timestamp = parent::timestamp();
		    $messagesArray[] =
			    array(
				    'body' => array(
					    'text' => $text,
					    'type' => 'text'
				    ),
				    'chat_message_id' => $chatID,
				    'seq_num' => $seq_num + 1,
				    'timestamp' => $timestamp,
				    'header' => array(
					    'auth' => array(
						    'mac' => $mac,
						    'payload' => $payload
					    ),
					    'to' => array($to),
					    'conv_id' => $conv_id,
					    'from' => $this->username,
					    'conn_seq_num' => 1
				    ),
				    'retried' => false,
				    'id' => $id,
				    'type' => 'chat_message'
			    );
	    }
		$messages = json_encode($messagesArray);
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/conversation_post_messages',
			array(
				'auth_token' => $this->auth_token,
				'messages' => $messages,
				'timestamp' => $timestamp,
				'username' => $this->username,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		return $result;
	}
<commit_msg>Fixed attempting to send a message if there is no one to send to<commit_after><?php

include_once dirname(__FILE__) . '/snapchat_agent.php';
include_once dirname(__FILE__) . '/snapchat_cache.php';
include_once dirname(__FILE__) . '/func.php';

/**
 * @file
 *   Provides an implementation of the undocumented Snapchat API.
 */
class Snapchat extends SnapchatAgent {

	/**
	 * The media types for snaps from confirmed friends.
	 */
	const MEDIA_IMAGE = 0;
	const MEDIA_VIDEO = 1;
	const MEDIA_VIDEO_NOAUDIO = 2;

	/**
	 * The media type for a friend request (not technically media, but it
	 * shows up in the feed).
	 */
	const MEDIA_FRIEND_REQUEST = 3;

	/**
	 * The media types for snaps from unconfirmed friends.
	 */
	const MEDIA_FRIEND_REQUEST_IMAGE = 4;
	const MEDIA_FRIEND_REQUEST_VIDEO = 5;
	const MEDIA_FRIEND_REQUEST_VIDEO_NOAUDIO = 6;

	/**
	 * Snap statuses.
	 */
	const STATUS_NONE = -1;
	const STATUS_SENT = 0;
	const STATUS_DELIVERED = 1;
	const STATUS_OPENED = 2;
	const STATUS_SCREENSHOT = 3;

	/**
	 * Friend statuses.
	 */
	const FRIEND_CONFIRMED = 0;
	const FRIEND_UNCONFIRMED = 1;
	const FRIEND_BLOCKED = 2;
	const FRIEND_DELETED = 3;

	/**
	 * Privacy settings.
	 */
	const PRIVACY_EVERYONE = 0;
	const PRIVACY_FRIENDS = 1;

	protected $auth_token;
	protected $chat_auth_token;
	protected $username;
	protected $debug;
	protected $gEmail;
	protected $gPasswd;

	/**
	 * Sets up some initial variables. If a username and password are passed in,
	 * we attempt to log in. If a username and auth token are passed in, we'll
	 * bypass the login process and use those values.
	 *
	 * @param string $username
	 *   The username for the Snapchat account.
	 * @param string $password
	 *   The password associated with the username, if logging in.
	 */
	public function __construct($username = NULL, $gEmail, $gPasswd, $debug = FALSE)
	{
		$this->username = $username;
		$this->debug = $debug;
		$this->gEmail = $gEmail;
		$this->gPasswd = $<PASSWORD>;

		if (file_exists(__DIR__ . "/auth-$this->username.dat"))
		{
			$this->auth_token = file_get_contents(__DIR__ . "/auth-$this->username.dat");
		}
	}

	public function getDeviceToken()
	{
		$timestamp = parent::timestamp();

		$result = parent::post(
			'/loq/device_id',
			array(
				'timestamp' => $timestamp,
			),
			array(
				parent::STATIC_TOKEN,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}

	public function device()
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/all_updates',
			array(
				'type' => "android",
				'timestamp' => $timestamp,
				'username' => $this->username,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}
	private function getAttestation(){
        	$binaryRequest = base64_decode("ClMKABIUY29tLnNuYXBjaGF0LmFuZHJvaWQaIC8cqvyh7TDQtOOIY+76vqDoFXEfpM95uCJRmoJZ2VpYIgAojKq/AzIECgASADoECAEQAUD4kP+pBRIA");
        	$ch = curl_init("https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=JSON&key=<KEY>");
        	curl_setopt($ch, CURLOPT_HEADER, false);
        	curl_setopt($ch, CURLOPT_POST, true);
        	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        	curl_setopt($ch, CURLOPT_POSTFIELDS, $binaryRequest);
        	curl_setopt($ch, CURLOPT_USERAGENT, "SafetyNet/7329000 (A116 _Quad KOT49H); gzip");
        	curl_setopt($ch, CURLOPT_ENCODING, "gzip");
        	curl_setopt($ch, CURLOPT_HTTPHEADER, array("content-type: application/x-protobuf", "Accept:", "Expect:"));
        	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        	$data = curl_exec($ch);
        	if(curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
            		$d = json_decode($data,true);
            		return $d['signedAttestation'];//the json response, json_decode and grab the signedAttestation value.
        	} else {
            		return curl_error($ch);
        	}
    	}
	public function getAuthToken()
	{
		if(($this->gEmail != null) && ($this->gPasswd != null))
		{
				$ch = curl_init();
				$postfields = array(
					'device_country' => 'us',
					'operatorCountry' => 'us',
					'lang' => 'en_US',
					'sdk_version' => '19',
					'google_play_services_version' => '7097038',
					'accountType' => 'HOSTED_OR_GOOGLE',
					'Email' => $this->gEmail,
					'service' => 'audience:server:client_id:694893979329-l59f3phl42et9clpoo296d8raqoljl6p.apps.googleusercontent.com',
					'source' => 'android',
					'androidId' => '378c184c6070c26c',
					'app' => 'com.snapchat.android',
					'client_sig' => '49f6badb81d89a9e38d65de76f09355071bd67e7',
					'callerPkg' => 'com.snapchat.android',
					'callerSig' => '49f6badb81d89a9e38d65de76f09355071bd67e7'
				);

				exec('java -version', $output, $returnCode);
				if ($returnCode === 0)
				{
						exec("java -jar " . __DIR__ . "/encrypter.jar $this->gEmail $this->gPasswd", $result);
						$postfields['EncryptedPasswd'] = array_shift(array_slice($result, 0, 1));
				}
				else
				{
						$postfields['Passwd'] = $this->gPasswd;
				}

			$headers = array(
				'device: 378c184c6070c26c',
				'app: com.snapchat.android',
				'User-Agent: GoogleAuth/1.4 (mako JDQ39)',
				'Accept-Encoding: gzip'
			);

			curl_setopt($ch, CURLOPT_URL, "https://android.clients.google.com/auth");
			curl_setopt($ch, CURLOPT_POST, TRUE);
			curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postfields));
			curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
			curl_setopt($ch, CURLOPT_ENCODING, "gzip");
			curl_setopt($ch, CURLINFO_HEADER_OUT, true);

			$result = curl_exec($ch);

			if($this->debug)
			{
				echo "\nREQUEST TO: https://android.clients.google.com/auth\n";
				echo 'DATA: ' . print_r($postfields) . "\n";
				echo 'RESULT: ' . $result . "\n";
			}

			if(curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200)
			{
				$return['error'] = 1;
				$return['data'] = $result;

				return $return;
			}

			curl_close($ch);

			$return['error'] = 0;
			$exploded = explode("\n", $result);
			$return['auth'] = substr($exploded[1], 5);
		}
		else
		{
			$return['error'] = 1;
			$return['data'] = "Email: $this->gEmail Passwd: $<PASSWORD>";
		}

		return $return;
	}

	private function getGCMToken()
	{
		$ch = curl_init();
		$timestamp = parent::timestamp() / 1000;
		$timestamp = (int) $timestamp;
		$postfields = array(
			'device' => '3847872624728098287',
			'sender' => '191410808405',
			'app_ver' => '564',
			'gcm_ver' => '7097038',
			'app' => 'com.snapchat.android',
			'iat' => $timestamp,
			'cert' => '49f6badb81d89a9e38d65de76f09355071bd67e7'
		);

		$headers = array(
			'app: com.snapchat.android',
			'User-Agent: Android-GCM/1.5 (m7 KOT49H)',
			'Authorization: AidLogin 3847872624728098287:1187196130325105010'
		);

		curl_setopt($ch, CURLOPT_URL, "https://android.clients.google.com/c2dm/register3");
		curl_setopt($ch, CURLOPT_POST, TRUE);
		curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postfields));
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($ch, CURLOPT_ENCODING, "gzip");
		curl_setopt($ch, CURLINFO_HEADER_OUT, true);

		$result = curl_exec($ch);

		if($this->debug)
		{
			echo "\nREQUEST TO: https://android.clients.google.com/c2dm/register3\n";
			echo 'DATA: ' . print_r($postfields) . "\n";
			echo 'RESULT: ' . $result . "\n";
		}

		if(curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200)
		{
			$return['error'] = 1;
			$return['data'] = $result;

			return $return;
		}

		curl_close($ch);

		$return['error'] = 0;
		$return['token'] = substr($result, 6);

		return $return;
	}

	/**
	 * Handles login.
	 *
	 * @param string $password
	 *   The password associated with the username.
	 *
	 * @return mixed
	 *   The data returned by the service or FALSE if the request failed.
	 *   Generally, returns the same result as self::getUpdates().
	 */
	public function login($password, $force = FALSE)
	{
		$do = ($force && file_exists(__DIR__ . "/auth-$this->username.dat")) ? 1 : 0;

		if(($do == 1) || (!(file_exists(__DIR__ . "/auth-$this->username.dat"))))
		{
				$dtoken = $this->getDeviceToken();

				if($dtoken['error'] == 1)
				{
						$return['message'] = "Failed to get new Device token set.";
						return $return;
				}

				$timestamp = parent::timestamp();
				$req_token = parent::hash(parent::STATIC_TOKEN, $timestamp);
				$string = $this->username . "|" . $password . "|" . $timestamp . "|" . $req_token;

				$auth = $this->getAuthToken();

				if($auth['error'] == 1)
				{
						return $auth;
				}
				parent::setGAuth($auth);
        			$attestation = $this->getAttestation();
				$result = parent::post(
					'/loq/login',
					array(
						'username' => $this->username,
						'password' => <PASSWORD>,
						'height' => 1280,
						'width' => 720,
						'max_video_height' => 640,
						'max_video_width' => 480,
						'dsig' => substr(hash_hmac('sha256', $string, $dtoken['data']->dtoken1v), 0, 20),
						'dtoken1i' => $dtoken['data']->dtoken1i,
						'ptoken' => "ie",
						'timestamp' => $timestamp,
						'attestation' => $attestation,
						'application_id' => 'com.snapchat.android',
						'req_token' => $req_token,
					),
					array(
						parent::STATIC_TOKEN,
						$timestamp,
						$auth['auth']
					),
					$multipart = false,
					$debug = $this->debug
				);


				if($result['error'] == 1)
				{
					return $result;
				}

				if(isset($result['data']->updates_response->logged) && $result['data']->updates_response->logged)
				{
					$this->auth_token = $result['data']->updates_response->auth_token;
					$this->device();

					$authFile = fopen(__DIR__ . "/auth-$this->username.dat", "w");
					fwrite($authFile, $this->auth_token);
					fclose($authFile);
				}

				return $result;
		} else {
				$this->openAppEvent();
		}
	}

	/**
	 * Logs out the current user.
	 *
	 * @return bool
	 *   TRUE if successful, FALSE otherwise.
	 */
	public function logout()
	{
		// Make sure we're logged in and have a valid access token.
		if(!$this->auth_token || !$this->username)
		{
			return FALSE;
		}

		$timestamp = parent::timestamp();
		$result = parent::post(
			'/ph/logout',
			array(
				'timestamp' => $timestamp,
				'username' => $this->username,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		// Clear out the cache in case the instance is recycled.
		$this->cache = NULL;

		unlink(__DIR__ . "/auth-$this->username.dat");

		return is_null($result);
	}

	/**
	 * Creates a user account.
	 *
	 * @todo
	 *   Add better validation.
	 *
	 * @param string $username
	 *   The desired username.
	 * @param string $password
	 *   The password to associate with the account.
	 * @param string $email
	 *   The email address to associate with the account.
	 * @param $birthday string
	 *   The user's birthday (yyyy-mm-dd).
	 * @param string $phone_verification
	 *   Whether to use phone verification or not.
	 * @param string $phone_number
	 *   Phone number to use if using phone verification. (country code and phone number. with or without +)
	 *
	 * @return mixed
	 *   The data returned by the service or FALSE if registration failed.
	 *   Generally, returns the same result as calling self::getUpdates().
	 */
	public function register($username, $password, $email, $birthday, $phone_verification = FALSE, $phone_number = NULL)
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/register',
			array(
				'birthday' => $birthday,
				'password' => <PASSWORD>,
				'email' => $email,
				'timestamp' => $timestamp,
			),
			array(
				parent::STATIC_TOKEN,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		if(!isset($result["data"]->auth_token))
		{
			return FALSE;
		}
		$this->auth_token = $result['data']->auth_token;

		$timestamp = parent::timestamp();
		parent::post(
			'/loq/register_username',
			array(
				'username' => $email,
				'selected_username' => $username,
				'timestamp' => $timestamp,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		$result = $result["data"];

		// If registration is successful, set the username and auth_token.
		if(isset($result->logged) && $result->logged)
		{
			$this->auth_token = $result->auth_token;
			$this->username = $username;

			if($phone_verification)
			{
				if(!is_null($phone_number))
				{
					return $this->sendPhoneVerification($phone_number);
				}
				else
				{
					echo "\nYou must provide a phone number to verify with.";
					return FALSE;
				}
			}
			else
			{
				return $this->getCaptcha();
			}
		}
		else
		{
			return FALSE;
		}
	}

	/**
	 * Sends SMS verification.
	 *
	 * @param string $phone_number
	 *   Phone number to use if using phone verification.
	 *
	 */
	public function sendPhoneVerification($phone_number)
	{
		$ch = curl_init();
		curl_setopt_array($ch, array(CURLOPT_RETURNTRANSFER => 1, CURLOPT_HTTPHEADER => array("X-Mashape-Key: <KEY>"),CURLOPT_URL => "https://metropolis-api-phone.p.mashape.com/analysis?telephone={$phone_number}", CURLOPT_CAINFO => dirname(__FILE__) . '/ca_bundle.crt'));
		$result = curl_exec($ch);
		$result = json_decode($result, true);

		if($result["valid"])
		{
			$phone_number = str_replace(" ", "", $result["formatted-number"]);
			$countryCode = $result["iso-code"];
		}
		else
		{
			echo "\nInvalid phone number.";
			return FALSE;
		}

		$timestamp = parent::timestamp();
		$result = parent::post(
			"/bq/phone_verify",
			array(
				"timestamp" => $timestamp,
				"username" => $this->username,
				"phoneNumber" => $phone_number,
				"action" => "updatePhoneNumber",
				"countryCode" => $countryCode,
				"skipConfirmation" => true
			),
			array(
				$this->auth_token,
				$timestamp,
			)
		);

		return $result;
	}

	/**
	 * Verifies phone number.
	 *
	 * @param string $code
	 *   Code sent for verification by Snapchat.
	 *
	 */
	public function verifyPhoneNumber($code)
	{
		$timestamp = parent::timestamp();
		$req_token = parent::hash(parent::STATIC_TOKEN, $timestamp);
		$result = parent::post(
			"/bq/phone_verify",
			array(
				"timestamp" => $timestamp,
				"action" => "verifyPhoneNumber",
				"username" => $this->username,
				"code" => $code
			),
			array(
				$req_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		$result = $result["data"];
		return (isset($result->logged) && $result->logged);
	}

	public function getCaptcha()
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/bq/get_captcha',
			array(
			  'username' => $this->username,
			  'timestamp' => $timestamp,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		return $result;
	}

	public function sendCaptcha($result, $id)
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/bq/solve_captcha',
			array(
				'username' => $this->username,
				'timestamp' => $timestamp,
				'captcha_id' => substr($id, 0, strlen($id) - 4),
				'captcha_solution' => $result
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}

	public function getConversationAuth($to)
	{
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/conversation_auth_token',
			array(
				'username' => $this->username,
				'timestamp' => $timestamp,
				'conversation_id' => implode('~', array($this->username, $to))
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);

		return $result;
	}


	public function getConversationInfo($tos){
	    if(!is_array($tos)) $tos = array($tos);
		$messagesArray = array();
		$authArray = array();
	    foreach($tos as $to){
		    $authInfo = $this->getConversationAuth($to);
		    //if user is even a friend
		    if(!property_exists($authInfo["data"], "messaging_auth")) continue;
		      $authArray[$to] = $authInfo['data'];
			    $payload = $authInfo["data"]->messaging_auth->payload;
			    $mac = $authInfo["data"]->messaging_auth->mac;
			    $genID = md5(uniqid());
			    $id = strtoupper(sprintf('%08s-%04s-%04x-%04x-%12s', substr($genID, 0, 8), substr($genID, 8, 4), substr($genID, 12, 4), substr($genID, 16, 4), substr($genID, 20, 12)));
			    $messagesArray[] = array(
					"presences" => array(
						$this->username => true,
						$to => false
					),
					"receiving_video" => false,
					"supports_here" => true,
					"header" => array(
						"auth" => array(
							"mac" => $mac,
							"payload" => $payload
						),
						"to" => array(
							$to
						),
						"conv_id" => implode('~', array($this->username, $to)),
						"from" => $this->username,
						"conn_sequence_number" => 0
					),
					"retried" => false,
					"id" => $id,
					"type" => "presence"
				);
				$messagesArray[] = array(
					"presences" => array(
						$this->username => true,
						$to => false
					),
					"receiving_video" => false,
					"supports_here" => true,
					"header" => array(
						"auth" => array(
							"mac" => $mac,
							"payload" => $payload
						),
						"to" => array(
							$to
						),
						"conv_id" => implode('~', array($to, $this->username)),
						"from" => $this->username,
						"conn_sequence_number" => 0
					),
					"retried" => false,
					"id" => $id,
					"type" => "presence"
				);
		}
			$messages = json_encode($messagesArray);
			$timestamp = parent::timestamp();
			$result = parent::post(
				'/loq/conversation_post_messages',
				array(
					'auth_token' => $this->auth_token,
					'messages' => $messages,
					'timestamp' => $timestamp,
					'username' => $this->username,
				),
				array(
					$this->auth_token,
					$timestamp,
				),
				$multipart = false,
				$debug = $this->debug
			);
			$resultsf = array();
			foreach($result['data']->conversations as $convo){
				$split = explode("~", $convo->id);
				$un = (strtolower($split[0]) != strtolower($this->username)) ? $split[0] : $split[1];
				$resultsf[$un] = $convo;
			}
		return array($resultsf,$authArray);
	}

	public function sendMessage($tos, $text){
	    if(!is_array($tos)) $tos = array($tos);
	    $convoInfo = $this->getConversationInfo($tos);
	    $messagesArray = array();
	    foreach($tos as $to){
	    	if(!array_key_exists($to, $convoInfo[1])){ //check if user can be sent a message
	    		 echo "\nYou have to add {$to} as a friend first!";
	    		 continue; 
	    		}
		    if(!array_key_exists($to, $convoInfo[0])){ //new convo
				    $payload = $convoInfo[1][$to]->messaging_auth->payload;
				    $mac = $convoInfo[1][$to]->messaging_auth->mac;
				    $seq_num = 0;
				    $conv_id = implode('~', array($to, $this->username));
		    }else{ //conversation already exists
			    $payload = $convoInfo[0][$to]->conversation_messages->messaging_auth->payload;
			    $mac = $convoInfo[0][$to]->conversation_messages->messaging_auth->mac;
			    $name = $this->username;
			    $seq_num = $convoInfo[0][$to]->conversation_state->user_sequences->$name;
			    $conv_id = $convoInfo[0][$to]->id;
		    }
		    $genID = md5(uniqid());
		    $chatID = strtoupper(sprintf('%08s-%04s-%04x-%04x-%12s', substr($genID, 0, 8), substr($genID, 8, 4), substr($genID, 12, 4), substr($genID, 16, 4), substr($genID, 20, 12)));
		    $genID = md5(uniqid());
		    $id = strtoupper(sprintf('%08s-%04s-%04x-%04x-%12s', substr($genID, 0, 8), substr($genID, 8, 4), substr($genID, 12, 4), substr($genID, 16, 4), substr($genID, 20, 12)));
		    $timestamp = parent::timestamp();
		    $messagesArray[] =
			    array(
				    'body' => array(
					    'text' => $text,
					    'type' => 'text'
				    ),
				    'chat_message_id' => $chatID,
				    'seq_num' => $seq_num + 1,
				    'timestamp' => $timestamp,
				    'header' => array(
					    'auth' => array(
						    'mac' => $mac,
						    'payload' => $payload
					    ),
					    'to' => array($to),
					    'conv_id' => $conv_id,
					    'from' => $this->username,
					    'conn_seq_num' => 1
				    ),
				    'retried' => false,
				    'id' => $id,
				    'type' => 'chat_message'
			    );
	    }
	    	if(count($messagesArray) <= 0) return null;
		$messages = json_encode($messagesArray);
		$timestamp = parent::timestamp();
		$result = parent::post(
			'/loq/conversation_post_messages',
			array(
				'auth_token' => $this->auth_token,
				'messages' => $messages,
				'timestamp' => $timestamp,
				'username' => $this->username,
			),
			array(
				$this->auth_token,
				$timestamp,
			),
			$multipart = false,
			$debug = $this->debug
		);
		return $result;
	}

{'fraction_non_alphanumeric': 0.17297610707124392, 'fraction_numerical': 0.019713478124320536, 'mean_word_length': 3.081845971797653, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 504, 'https://': 12, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5169702', 'n_tokens_mistral': 18342, 'n_tokens_neox': 14516, 'n_words': 2923}