├── .gitignore ├── bin └── php_xxhash.dll ├── src ├── Util │ ├── Comparable.php │ ├── MicroTime.php │ ├── Serializable.php │ ├── ContextSerializable.php │ ├── Hex.php │ ├── Random.php │ └── Enum.php ├── Map │ ├── Pokestop.php │ ├── Gym.php │ ├── WildPokemon.php │ ├── Fort.php │ └── Location.php ├── Session │ ├── AccountType.php │ ├── Requests │ │ ├── HatchedEggsRequest.php │ │ ├── PlayerRequest.php │ │ ├── ItemTemplatesRequest.php │ │ ├── AvatarRequest.php │ │ ├── ContactSettingsRequest.php │ │ ├── FortDetailsRequest.php │ │ ├── Request.php │ │ ├── CompleteTutorialRequest.php │ │ └── MapObjectsRequest.php │ ├── AuthTicket.php │ ├── Signature.php │ ├── GoogleSession.php │ ├── Session.php │ ├── PTCSession.php │ └── RequestHandler.php └── Player │ ├── Contact.php │ ├── Tutorial.php │ ├── Avatar.php │ └── Profile.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock -------------------------------------------------------------------------------- /bin/php_xxhash.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebabtent/pogoapi-php/HEAD/bin/php_xxhash.dll -------------------------------------------------------------------------------- /src/Util/Comparable.php: -------------------------------------------------------------------------------- 1 | getType() != FortType::CHECKPOINT()) { 16 | throw new InvalidArgumentException("Fort not a pokestop"); 17 | } 18 | 19 | parent::__construct($session, $data); 20 | 21 | // TODO: modifier 22 | // TODO: lure 23 | // TODO: sponsor (maybe) 24 | } 25 | } -------------------------------------------------------------------------------- /src/Session/AccountType.php: -------------------------------------------------------------------------------- 1 | getValue()) { 21 | case 1: 22 | return "google"; 23 | break; 24 | case 2: 25 | return "ptc"; 26 | break; 27 | default: 28 | return "unknown"; 29 | break; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Map/Gym.php: -------------------------------------------------------------------------------- 1 | getType()) && $data->getType() != FortType::GYM()) { 17 | throw new InvalidArgumentException("Fort not a gym"); 18 | } 19 | 20 | parent::__construct($session, $data); 21 | 22 | // TODO: team color 23 | // TODO: highest cp pokemon 24 | // TODO: xp 25 | // TODO: in battle 26 | } 27 | } -------------------------------------------------------------------------------- /src/Session/Requests/HatchedEggsRequest.php: -------------------------------------------------------------------------------- 1 | setAppVersion(Session::APPVERSION); 25 | return $msg; 26 | } 27 | 28 | /** 29 | * @param string $raw 30 | * @return GetPlayerResponse 31 | */ 32 | protected function getResponseHandler($raw) { 33 | return new GetPlayerResponse($raw); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Session/Requests/ItemTemplatesRequest.php: -------------------------------------------------------------------------------- 1 | session->getProfile(); 27 | if (!$profile->hasAvatar()) { 28 | throw new Exception("No avatar set"); 29 | } 30 | $msg->setPlayerAvatar($profile->getAvatar()->toProto()); 31 | return $msg; 32 | } 33 | 34 | /** 35 | * @param string $raw 36 | * @return SetAvatarResponse 37 | */ 38 | protected function getResponseHandler($raw) { 39 | return new SetAvatarResponse($raw); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Session/Requests/ContactSettingsRequest.php: -------------------------------------------------------------------------------- 1 | session->getProfile()->getContact(); 27 | 28 | $contactSettings = new ContactSettings(); 29 | $contactSettings->setSendMarketingEmails($contact->getSendMarketingEmails()); 30 | $contactSettings->setSendPushNotifications($contact->getSendPushNotifications()); 31 | 32 | $msg->setContactSettings($contactSettings); 33 | 34 | return $msg; 35 | } 36 | 37 | /** 38 | * @param string $raw 39 | * @return SetContactSettingsResponse 40 | */ 41 | protected function getResponseHandler($raw) { 42 | return new SetContactSettingsResponse($raw); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Session/Requests/FortDetailsRequest.php: -------------------------------------------------------------------------------- 1 | fort = $fort; 23 | } 24 | 25 | /** 26 | * @return RequestType 27 | */ 28 | public function getType() { 29 | return RequestType::FORT_DETAILS(); 30 | } 31 | 32 | /** 33 | * @return FortDetailsMessage 34 | */ 35 | public function getRequestMessage() { 36 | $msg = new FortDetailsMessage(); 37 | $msg->setFortId($this->fort->getId()); 38 | $location = $this->fort->getLocation(); 39 | $msg->setLatitude($location->getLatitude()); 40 | $msg->setLongitude($location->getLongitude()); 41 | return $msg; 42 | } 43 | 44 | /** 45 | * @param string $raw 46 | * @return FortDetailsResponse 47 | */ 48 | protected function getResponseHandler($raw) { 49 | return new FortDetailsResponse($raw); 50 | } 51 | } -------------------------------------------------------------------------------- /src/Map/WildPokemon.php: -------------------------------------------------------------------------------- 1 | pokemonId = $proto->getPokemonData()->getPokemonId()->value(); 18 | $this->encounterId = $proto->getEncounterId(); 19 | $this->spawnPointId = $proto->getSpawnPointId(); 20 | $this->timeTillHiddenMs = $proto->getTimeTillHiddenMs(); 21 | $this->location = new Location($proto->getLatitude(), $proto->getLongitude()); 22 | } 23 | 24 | /** 25 | * @return int 26 | */ 27 | public function getPokemonId() { 28 | return $this->pokemonId; 29 | } 30 | 31 | /** 32 | * @return int 33 | */ 34 | public function getEncounterId() { 35 | return $this->encounterId; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getSpawnPointId() { 42 | return $this->spawnPointId; 43 | } 44 | 45 | /** 46 | * @return int 47 | */ 48 | public function getTimeTillHiddenMs() { 49 | return $this->timeTillHiddenMs; 50 | } 51 | 52 | /** 53 | * @return Location 54 | */ 55 | public function getLocation() { 56 | return $this->location; 57 | } 58 | } -------------------------------------------------------------------------------- /src/Session/AuthTicket.php: -------------------------------------------------------------------------------- 1 | start = $proto->getStart()->getContents(); 17 | $this->expire = $proto->getExpireTimestampMs(); 18 | $this->end = $proto->getEnd()->getContents(); 19 | } 20 | 21 | /** 22 | * @return bool 23 | */ 24 | public function isValid() { 25 | return $this->getTimeToExpire() > 0; 26 | } 27 | 28 | /** 29 | * @return int 30 | */ 31 | public function getTimeToExpire() { 32 | return round($this->expire/1000) - time() - 3; 33 | } 34 | 35 | /** 36 | * @return ProtoAuthTicket 37 | */ 38 | public function toProto() { 39 | $ticket = new ProtoAuthTicket(); 40 | $ticket->setStart($this->start); 41 | $ticket->setExpireTimestampMs($this->expire); 42 | $ticket->setEnd($this->end); 43 | return $ticket; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function toBinary() { 50 | return $this->toProto()->toStream()->getContents(); 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function serialize() { 57 | return bin2hex($this->toBinary()); 58 | } 59 | 60 | /** 61 | * @param string $raw 62 | * @return AuthTicket 63 | */ 64 | public static function fromBinary($raw) { 65 | return new self(new ProtoAuthTicket($raw)); 66 | } 67 | 68 | /** 69 | * @param string $hex 70 | * @return AuthTicket 71 | */ 72 | public static function unserialize($hex) { 73 | return self::fromBinary(hex2bin($hex)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POGOAPI-PHP 2 | API for Pokemon Go 3 | 4 | ## Progress 5 | * [x] Login with google 6 | * [x] Login with PTC 7 | * [x] Uk6 compatible 8 | * [x] Obtain endpoint 9 | * [x] Obtain profile 10 | * [x] Obtain map objects (pokemons/pokestops/gyms) 11 | 12 | ## Installation 13 | Add the following fields in your project `composer.json`: 14 | ``` json 15 | { 16 | "require": { 17 | "jaspervdm/pogoapi-php": "dev-master" 18 | }, 19 | "minimum-stability": "dev", 20 | "prefer-stable": true 21 | } 22 | ``` 23 | 24 | ## Usage 25 | ``` php 26 | // First set up some logger 27 | $logger = new \Monolog\Logger("POGOAPI"); 28 | 29 | // Set initial location 30 | $location = new \POGOAPI\Map\Location(LATITUDE, LONGITUDE, ALTITUDE); 31 | 32 | // Create a Session instance 33 | $session = new \POGOAPI\Session\GoogleSession($logger, $location, USERNAME, PASSWORD); 34 | $session->authenticate(); 35 | $session->createEndpoint(); 36 | 37 | // At this point one can communicate with the pokemon go servers, for example: 38 | $profile = $session->getProfile(); 39 | echo "My username is ".$profile->getUsername()."\n"; 40 | ``` 41 | See also the `examples/` directory 42 | 43 | ## Contributions 44 | * [jaspervdm](https://github.com/jaspervdm) 45 | * [barryvdh](https://github.com/barryvdh) 46 | 47 | ## Credits 48 | * [AeonLucid](https://github.com/AeonLucid) for the [proto files](https://github.com/AeonLucid/POGOProtos) 49 | * [POGO-PHP](https://github.com/POGO-PHP) for the [pure PHP implementation of encrypt.c](https://github.com/POGO-PHP/POGOEncrypt-PHP) 50 | * [Sjaakmoes](https://github.com/Sjaakmoes) for the [correct implementation of request signing](https://github.com/Sjaakmoes/pokapi/blob/27cb0281500821b7b2c64150aa779a5a997080c3/src/Pokapi/Rpc/Service.php#L243) 51 | * [MatthewKingDev](https://github.com/MatthewKingDev) for the [modified xxhash](https://github.com/MatthewKingDev/php-xxhash) 52 | 53 | -------------------------------------------------------------------------------- /src/Session/Requests/Request.php: -------------------------------------------------------------------------------- 1 | logger = $session->getLogger(); 36 | $this->session = $session; 37 | $this->response = false; 38 | } 39 | 40 | /** 41 | * @return ProtoRequest 42 | */ 43 | public function toProto() { 44 | $proto = new ProtoRequest(); 45 | $proto->setRequestType($this->getType()); 46 | $proto->setRequestMessage($this->getRequestMessage()->toStream()->getContents()); 47 | return $proto; 48 | } 49 | 50 | /** 51 | * @param string $raw 52 | */ 53 | public function setRawResponse($raw) { 54 | $message = $this->getResponseHandler($raw); 55 | $this->setResponse($message); 56 | } 57 | 58 | /** 59 | * @param AbstractMessage $response 60 | */ 61 | public function setResponse(AbstractMessage $response) { 62 | $this->response = $response; 63 | } 64 | 65 | /** 66 | * @return AbstractMessage 67 | */ 68 | public function getResponse() { 69 | return $this->response; 70 | } 71 | 72 | /** 73 | * @param bool $defaults 74 | * @throws Exception 75 | */ 76 | public function execute($defaults = true) { 77 | $this->session->getRequestHandler()->execute([$this], $defaults); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Map/Fort.php: -------------------------------------------------------------------------------- 1 | session = $session; 25 | 26 | $this->id = $data->getId(); 27 | $this->location = new Location($data->getLatitude(), $data->getLongitude()); 28 | } 29 | 30 | public function getDetails() { 31 | if ($this->hasDetails) { 32 | return; 33 | } 34 | 35 | $req = new FortDetailsRequest($this->session, $this); 36 | $req->execute(); 37 | $resp = $req->getResponse(); 38 | 39 | $this->hasDetails = true; 40 | $this->name = utf8_decode($resp->getName()); 41 | $this->imageURL = NULL; 42 | if ($resp->hasImageUrlsList()) { 43 | $urls = $resp->getImageUrlsList(); 44 | foreach ($urls as $url) { 45 | $this->imageURL = (string) $url; 46 | break; 47 | } 48 | } 49 | $this->description = utf8_decode($resp->getDescription()); 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function getId() { 56 | return $this->id; 57 | } 58 | 59 | /** 60 | * @return Location 61 | */ 62 | public function getLocation() { 63 | return $this->location; 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getName() { 70 | $this->getDetails(); 71 | return $this->name; 72 | } 73 | 74 | /** 75 | * @return string[] 76 | */ 77 | public function getImageURL() { 78 | $this->getDetails(); 79 | return $this->imageURL; 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function getDescription() { 86 | $this->getDetails(); 87 | return $this->description; 88 | } 89 | } -------------------------------------------------------------------------------- /src/Util/Enum.php: -------------------------------------------------------------------------------- 1 | name = $name; 20 | $this->value = $value; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getName() { 27 | return $this->name; 28 | } 29 | 30 | /** 31 | * @return string 32 | */ 33 | public function getValue() { 34 | return $this->value; 35 | } 36 | 37 | /** 38 | * @return ReflectionClass 39 | */ 40 | protected static function getReflectionClass() { 41 | if (!is_array(self::$reflection)) { 42 | self::$reflection = []; 43 | } 44 | 45 | $class = get_called_class(); 46 | 47 | if (!isset(self::$reflection[$class])) { 48 | self::$reflection[$class] = new ReflectionClass($class); 49 | } 50 | 51 | return self::$reflection[$class]; 52 | } 53 | 54 | /** 55 | * @return array 56 | */ 57 | public static function getConstants() { 58 | $reflection = static::getReflectionClass(); 59 | return $reflection->getConstants(); 60 | } 61 | 62 | /** 63 | * @param string $value 64 | * @return self|null 65 | */ 66 | public static function byValue($value) { 67 | $constants = static::getConstants(); 68 | foreach ($constants as $name => $constValue) { 69 | if ($value == $constValue) { 70 | return static::$name(); 71 | } 72 | } 73 | return null; 74 | } 75 | 76 | /** 77 | * @param $name 78 | * @param $arguments 79 | * @return static 80 | * @throws Exception 81 | */ 82 | public static function __callStatic($name, $arguments) { 83 | if (!defined("static::".$name)) { 84 | throw new Exception("Unknown enum '".$name."'"); 85 | } 86 | 87 | if (!is_array(static::$instances)) { 88 | static::$instances = []; 89 | } 90 | 91 | if (!isset(static::$instances[$name])) { 92 | static::$instances[$name] = new static($name, constant("static::".$name)); 93 | } 94 | 95 | return static::$instances[$name]; 96 | } 97 | } -------------------------------------------------------------------------------- /src/Session/Requests/CompleteTutorialRequest.php: -------------------------------------------------------------------------------- 1 | completed = []; 22 | } 23 | 24 | /** 25 | * @param TutorialState $tutorial 26 | * @return bool 27 | */ 28 | protected function tutorialCompleted(TutorialState $tutorial) { 29 | return in_array($tutorial, $this->completed); 30 | } 31 | 32 | /** 33 | * @param TutorialState $tutorial 34 | */ 35 | public function completeTutorial(TutorialState $tutorial) { 36 | if (!$this->tutorialCompleted($tutorial)) { 37 | $this->completed[] = $tutorial; 38 | } 39 | } 40 | 41 | /** 42 | * @param array $tutorials 43 | */ 44 | public function completeTutorials($tutorials) { 45 | foreach ($tutorials as $tutorial) { 46 | if ($tutorial instanceof TutorialState) { 47 | $this->completeTutorial($tutorial); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * @return RequestType 54 | */ 55 | public function getType() { 56 | return RequestType::MARK_TUTORIAL_COMPLETE(); 57 | } 58 | 59 | /** 60 | * @return MarkTutorialCompleteMessage 61 | */ 62 | public function getRequestMessage() { 63 | $msg = new MarkTutorialCompleteMessage(); 64 | 65 | $contact = $this->session->getProfile()->getContact(); 66 | 67 | $msg->setSendMarketingEmails($contact->getSendMarketingEmails()); 68 | $msg->setSendPushNotifications($contact->getSendPushNotifications()); 69 | foreach ($this->completed as $state) { 70 | /** @var TutorialState $state */ 71 | $msg->addTutorialsCompleted($state); 72 | } 73 | 74 | return $msg; 75 | } 76 | 77 | /** 78 | * @param string $raw 79 | * @return MarkTutorialCompleteResponse 80 | */ 81 | protected function getResponseHandler($raw) { 82 | return new MarkTutorialCompleteResponse($raw); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Player/Contact.php: -------------------------------------------------------------------------------- 1 | profile = $profile; 19 | $this->sendMarketingEmails = false; 20 | $this->sendPushNotifications = false; 21 | } 22 | 23 | /** 24 | * @return bool 25 | */ 26 | public function getSendMarketingEmails() { 27 | return $this->sendMarketingEmails; 28 | } 29 | 30 | /** 31 | * @param bool|false $sendMarketingEmails 32 | */ 33 | protected function setSendMarketingEmails($sendMarketingEmails = false) { 34 | $this->sendMarketingEmails = $sendMarketingEmails; 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function getSendPushNotifications() { 41 | return $this->sendPushNotifications; 42 | } 43 | 44 | /** 45 | * @param bool|false $sendPushNotifications 46 | */ 47 | protected function setSendPushNotifications($sendPushNotifications = false) { 48 | $this->sendPushNotifications = $sendPushNotifications; 49 | } 50 | 51 | /** 52 | * @param ContactSettings $contactSettings 53 | */ 54 | public function updated(ContactSettings $contactSettings) { 55 | $this->sendMarketingEmails = $contactSettings->getSendMarketingEmails(); 56 | $this->sendPushNotifications = $contactSettings->getSendPushNotifications(); 57 | } 58 | 59 | public function update() { 60 | $req = new ContactSettingsRequest($this->profile->getSession()); 61 | $req->execute(); 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function serialize() { 68 | return ($this->sendMarketingEmails ? "1" : "0").($this->sendPushNotifications ? "1" : "0"); 69 | } 70 | 71 | /** 72 | * @param Profile $profile 73 | * @param string $data 74 | * @return Contact 75 | * @throws InvalidArgumentException 76 | */ 77 | public static function unserialize($profile, $data) { 78 | if (!($profile instanceof Profile)) { 79 | throw new InvalidArgumentException("Expected Profile instance"); 80 | } 81 | 82 | $parts = str_split($data); 83 | if (count($parts) < 2) { 84 | throw new InvalidArgumentException("Invalid contact parts"); 85 | } 86 | 87 | $contact = new self($profile); 88 | $contact->setSendMarketingEmails($parts[0] != 0); 89 | $contact->setSendPushNotifications($parts[1] != 0); 90 | 91 | 92 | return $contact; 93 | } 94 | } -------------------------------------------------------------------------------- /src/Map/Location.php: -------------------------------------------------------------------------------- 1 | latitude = $latitude; 19 | $this->longitude = $longitude; 20 | $this->altitude = $altitude < 0 ? mt_rand(1, 10) : $altitude; 21 | } 22 | 23 | /** 24 | * @return float 25 | */ 26 | public function getLatitude() { 27 | return $this->latitude; 28 | } 29 | 30 | /** 31 | * @return float 32 | */ 33 | public function getLongitude() { 34 | return $this->longitude; 35 | } 36 | 37 | /** 38 | * @return float 39 | */ 40 | public function getAltitude() { 41 | return $this->altitude; 42 | } 43 | 44 | /** 45 | * @param float $angle Angle in degrees (0=north, 90=east) 46 | * @param float $distance Distance in meter 47 | * @return self 48 | */ 49 | public function translate($angle, $distance) { 50 | $realAngle = $angle; 51 | if ($distance < 0) { 52 | $realAngle += 180; 53 | } 54 | $realAngle %= 360; 55 | if ($realAngle < 0) { 56 | $realAngle += 360; 57 | } 58 | $realDistance = abs($distance); 59 | 60 | $oldLat = deg2rad($this->latitude); 61 | $oldLong = deg2rad($this->longitude); 62 | $relativeDistance = $realDistance/6367000.; 63 | $realAngleRad = deg2rad($realAngle); 64 | 65 | $newLat = asin( sin($oldLat)*cos($relativeDistance) + cos($oldLat)*sin($relativeDistance)*cos($realAngleRad) ); 66 | $newLong = $oldLong + atan2(sin($realAngleRad)*sin($relativeDistance)*cos($oldLat), cos($relativeDistance)-sin($oldLat)*sin($newLat)); 67 | 68 | $newLatDeg = rad2deg($newLat); 69 | $newLongDeg = rad2deg($newLong); 70 | 71 | $this->latitude = $newLatDeg; 72 | $this->longitude = $newLongDeg; 73 | return $this; 74 | } 75 | 76 | /** 77 | * @return string 78 | */ 79 | public function toBytes() { 80 | return Hex::d2h($this->latitude).Hex::d2h($this->longitude).Hex::d2h($this->altitude); 81 | } 82 | 83 | /** 84 | * @return array 85 | */ 86 | public function serialize() { 87 | return [ 88 | "latitude" => $this->getLatitude(), 89 | "longitude" => $this->getLongitude(), 90 | "altitude" => $this->getAltitude() 91 | ]; 92 | } 93 | 94 | /** 95 | * @param $data 96 | * @return self 97 | */ 98 | public static function unserialize($data) { 99 | return new self($data['latitude'], $data['longitude'], $data['altitude']); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Session/Signature.php: -------------------------------------------------------------------------------- 1 | hasAuthTicket()) { 27 | return; 28 | } 29 | 30 | $location = $session->getLocation(); 31 | $rawTicket = $session->getAuthTicket()->toBinary(); 32 | 33 | $microTime = MicroTime::get(); 34 | 35 | $protoSignature = new ProtoSignature(); 36 | $protoSignature->setTimestampSinceStart($microTime-$session->getStartMicroTime()); 37 | // TODO: LocationFix 38 | // TODO: AndroidGpsInfo 39 | // TODO: SensorInfo 40 | // TODO: DeviceInfo 41 | // TODO: ActivityStatus 42 | $protoSignature->setLocationHash1(self::generateLocation1($rawTicket, $location)); 43 | $protoSignature->setLocationHash2(self::generateLocation2($location)); 44 | $protoSignature->setSessionHash($session->getSessionHash()); 45 | $protoSignature->setTimestamp($microTime); 46 | 47 | foreach ($reqs as $req) { 48 | $protoSignature->addRequestHash(self::generateRequestHash($rawTicket, $req->toProto()->toStream()->getContents())); 49 | } 50 | 51 | $protoSignature->setUnknown25(0x898654dd2753a481); 52 | 53 | $uk6 = new Unknown6(); 54 | $uk6->setRequestType(6); 55 | 56 | $uk2 = new Unknown2(); 57 | $enc = Encrypt::encrypt($protoSignature->toStream()->getContents(), random_bytes(32)); 58 | $uk2->setEncryptedSignature($enc); 59 | 60 | $uk6->setUnknown2($uk2); 61 | $env->setUnknown6($uk6); 62 | 63 | $session->getLogger()->debug("Signed request: ".strlen($enc)." bytes"); 64 | } 65 | 66 | /** 67 | * @param string $rawTicket 68 | * @param Location $location 69 | * @return int 70 | */ 71 | protected static function generateLocation1($rawTicket, Location $location) { 72 | $seed = (int) hexdec(xxhash32($rawTicket, 0x1B845238)); 73 | return (int) hexdec(xxhash32($location->toBytes(), $seed)); 74 | } 75 | 76 | /** 77 | * @param Location $location 78 | * @return int 79 | */ 80 | protected static function generateLocation2(Location $location) { 81 | return (int) hexdec(xxhash32($location->toBytes(), 0x1B845238)); 82 | } 83 | 84 | /** 85 | * @param string $rawTicket 86 | * @param string $rawRequest 87 | * @return int 88 | */ 89 | public static function generateRequestHash($rawTicket, $rawRequest) { 90 | $seed = unpack("J", pack("H*", xxhash64($rawTicket, 0x1B845238))); 91 | $unpack = unpack("J", pack("H*", xxhash64($rawRequest, $seed[1]))); 92 | return $unpack[1]; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/Player/Tutorial.php: -------------------------------------------------------------------------------- 1 | profile = $profile; 17 | $this->completed = []; 18 | } 19 | 20 | /** 21 | * @param Collection $collection 22 | */ 23 | public function updated(Collection $collection) { 24 | $this->clear(); 25 | foreach ($collection as $state) { 26 | if ($state instanceof TutorialState){ 27 | $this->completed[] = $state; 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * @param array $data 34 | */ 35 | public function updateFromValues($data) { 36 | $this->clear(); 37 | foreach ($data as $stateValue) { 38 | $this->completed(TutorialState::valueOf($stateValue)); 39 | } 40 | } 41 | 42 | public function clear() { 43 | $this->completed = []; 44 | } 45 | 46 | /** 47 | * @param TutorialState $state 48 | */ 49 | protected function completed(TutorialState $state) { 50 | if (!$this->isCompleted($state)) { 51 | $this->completed[] = $state; 52 | } 53 | } 54 | 55 | /** 56 | * @param TutorialState $state 57 | * @return bool 58 | */ 59 | public function isCompleted(TutorialState $state) { 60 | return in_array($state, $this->completed); 61 | } 62 | 63 | /** 64 | * @param TutorialState $state 65 | * @throws Exception 66 | */ 67 | public function complete(TutorialState $state) { 68 | if ($this->isCompleted($state)) { 69 | return; 70 | } 71 | 72 | $req = new CompleteTutorialRequest($this->profile->getSession()); 73 | $req->completeTutorial($state); 74 | $req->execute(); 75 | if (!$req->getResponse()->hasPlayerData()) { 76 | throw new Exception("Invalid response"); 77 | } 78 | 79 | $this->updated($req->getResponse()->getPlayerData()->getTutorialStateList()); 80 | } 81 | 82 | /** 83 | * @return bool 84 | */ 85 | public function isTOSAccepted() { 86 | return $this->isCompleted(TutorialState::LEGAL_SCREEN()); 87 | } 88 | 89 | /** 90 | * @return bool 91 | */ 92 | public function isAvatarSelected() { 93 | return $this->isCompleted(TutorialState::AVATAR_SELECTION()); 94 | } 95 | 96 | /** 97 | * @return bool 98 | */ 99 | public function isAccountCreated() { 100 | return $this->isCompleted(TutorialState::ACCOUNT_CREATION()); 101 | } 102 | 103 | /** 104 | * @throws Exception 105 | */ 106 | public function acceptTOS() { 107 | $this->complete(TutorialState::LEGAL_SCREEN()); 108 | } 109 | 110 | /** 111 | * @throws Exception 112 | */ 113 | public function selectAvatar() { 114 | $this->complete(TutorialState::AVATAR_SELECTION()); 115 | } 116 | 117 | /** 118 | * @return array 119 | */ 120 | public function serialize() { 121 | $data = []; 122 | foreach ($this->completed as $state) { 123 | /* @var TutorialState $state */ 124 | $data[] = $state->value(); 125 | } 126 | return $data; 127 | } 128 | 129 | /** 130 | * @param Profile $profile 131 | * @param $data 132 | * @return Tutorial 133 | * @throws InvalidArgumentException 134 | */ 135 | public static function unserialize($profile, $data) { 136 | if (!($profile instanceof Profile)) { 137 | throw new InvalidArgumentException("Expected Profile instance"); 138 | } 139 | 140 | $tutorial = new self($profile); 141 | $tutorial->updateFromValues($data); 142 | return $tutorial; 143 | } 144 | } -------------------------------------------------------------------------------- /src/Session/GoogleSession.php: -------------------------------------------------------------------------------- 1 | username = $username; 34 | $this->password = $password; 35 | $this->androidId = $androidId; 36 | $this->service = $service; 37 | $this->app = $app; 38 | $this->clientSig = $clientSig; 39 | 40 | $this->authClient = new Client([ 41 | "base_uri" => "https://android.clients.google.com", 42 | "headers" => ["User-Agent" => "POGOAPI/1.0"] 43 | ]); 44 | } 45 | 46 | /** 47 | * @throws Exception 48 | */ 49 | public function authenticate() { 50 | // Master login 51 | $loginRes = $this->authClient->post("auth", ["form_params" => [ 52 | "accountType" => "HOSTED_OR_GOOGLE", 53 | "Email" => $this->username, 54 | "has_permission" => 1, 55 | "add_account" => 1, 56 | "Passwd" => $this->password, 57 | "service" => "ac2dm", 58 | "source" => "android", 59 | "androidId" => $this->androidId, 60 | "device_country" => "us", 61 | "operatorCountry" => "us", 62 | "lang" => "en", 63 | "sdk_version" => 17 64 | ]]); 65 | 66 | $this->logger->debug("Google authentication"); 67 | 68 | $masterLogin = parse_ini_string($loginRes->getBody()); 69 | if (!isset($masterLogin['Token'])) { 70 | throw new Exception("Failed master login"); 71 | } 72 | 73 | $this->logger->debug("Google auth: master login"); 74 | 75 | // OAuth 76 | $oauthRes = $this->authClient->post("auth", ["form_params" => [ 77 | "accountType" => "HOSTED_OR_GOOGLE", 78 | "Email" => $this->username, 79 | "has_permission" => 1, 80 | "EncryptedPasswd" => $masterLogin['Token'], 81 | "service" => $this->service, 82 | "source" => "android", 83 | "androidId" => $this->androidId, 84 | "app" => $this->app, 85 | "client_sig" => $this->clientSig, 86 | "device_country" => "us", 87 | "operatorCountry" => "us", 88 | "lang" => "en", 89 | "sdk_version" => 17 90 | ]]); 91 | 92 | $auth = parse_ini_string($oauthRes->getBody()); 93 | if (!isset($auth['Auth'])) { 94 | throw new Exception("Failed auth"); 95 | } 96 | $this->token = $auth['Auth']; 97 | 98 | $this->logger->debug("Google auth: obtained token"); 99 | } 100 | 101 | /** 102 | * @return bool 103 | */ 104 | public function hasToken() { 105 | return !is_null($this->token); 106 | } 107 | 108 | /** 109 | * @return string 110 | * @throws Exception 111 | */ 112 | public function getToken() { 113 | if (!$this->hasToken()) { 114 | throw new Exception("No token set"); 115 | } 116 | return $this->token; 117 | } 118 | 119 | /** 120 | * @param string $token 121 | */ 122 | public function setToken($token) { 123 | $this->token = $token; 124 | } 125 | 126 | /** 127 | * @return AccountType 128 | */ 129 | public function getType() { 130 | return AccountType::GOOGLE(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Session/Requests/MapObjectsRequest.php: -------------------------------------------------------------------------------- 1 | setLatitude($this->session->getLocation()->getLatitude()); 39 | $msg->setLongitude($this->session->getLocation()->getLongitude()); 40 | $this->addCellIds($msg); 41 | return $msg; 42 | } 43 | 44 | /** 45 | * @param string $raw 46 | * @return GetMapObjectsResponse 47 | */ 48 | protected function getResponseHandler($raw) { 49 | return new GetMapObjectsResponse($raw); 50 | } 51 | 52 | /** 53 | * @param bool $defaults 54 | * @throws Exception 55 | */ 56 | public function execute($defaults = true) { 57 | $this->pokestops = []; 58 | $this->gyms = []; 59 | $this->wildPokemons = []; 60 | 61 | parent::execute($defaults); 62 | 63 | $resp = $this->getResponse(); 64 | if ($resp->getStatus() != MapObjectsStatus::SUCCESS() || !$resp->hasMapCellsList()) { 65 | throw new Exception("Unable to retrieve map objects"); 66 | } 67 | 68 | $mapCells = $resp->getMapCellsList(); 69 | foreach ($mapCells as $mapCell) { 70 | /** @var MapCell $mapCell */ 71 | if ($mapCell->hasFortsList()) { 72 | $forts = $mapCell->getFortsList(); 73 | foreach ($forts as $fort) { 74 | /** @var FortData $fort */ 75 | $fortType = $fort->getType(); 76 | $fortId = $fort->getId(); 77 | if (is_null($fortType) || $fortType == FortType::GYM()) { 78 | if (!isset($this->gyms[$fortId])) { 79 | $this->gyms[$fortId] = new Gym($this->session, $fort); 80 | } 81 | } 82 | elseif ($fortType == FortType::CHECKPOINT()) { 83 | if (!isset($this->pokestops[$fortId])) { 84 | $this->pokestops[$fortId] = new Pokestop($this->session, $fort); 85 | } 86 | } 87 | } 88 | } 89 | 90 | if ($mapCell->hasWildPokemonsList()) { 91 | $wildPokemons = $mapCell->getWildPokemonsList(); 92 | foreach ($wildPokemons as $wildPokemon) { 93 | $this->wildPokemons[] = new WildPokemon($wildPokemon); 94 | } 95 | } 96 | } 97 | } 98 | 99 | /** 100 | * @return Pokestop[] 101 | */ 102 | public function getPokestops() { 103 | return $this->pokestops; 104 | } 105 | 106 | /** 107 | * @return int 108 | */ 109 | public function getCountPokestops() { 110 | return count($this->pokestops); 111 | } 112 | 113 | /** 114 | * @return Gym[] 115 | */ 116 | public function getGyms() { 117 | return $this->gyms; 118 | } 119 | 120 | /** 121 | * @return int 122 | */ 123 | public function getCountGyms() { 124 | return count($this->gyms); 125 | } 126 | 127 | /** 128 | * @return WildPokemon[] 129 | */ 130 | public function getWildPokemons() { 131 | return $this->wildPokemons; 132 | } 133 | 134 | /** 135 | * @return int 136 | */ 137 | public function getCountWildPokemons() { 138 | return count($this->wildPokemons); 139 | } 140 | 141 | /** 142 | * @param GetMapObjectsMessage $msg 143 | * @param int $width 144 | */ 145 | protected function addCellIds(GetMapObjectsMessage $msg, $width = 6) { 146 | $latLng = S2LatLng::fromDegrees($this->session->getLocation()->getLatitude(), $this->session->getLocation()->getLongitude()); 147 | $cellId = S2CellId::fromLatLng($latLng)->parent(15); 148 | $size = 2**(S2CellId::MAX_LEVEL - $cellId->level()); 149 | $iIndex = 0; 150 | $jIndex = 0; 151 | $face = $cellId->toFaceIJOrientation($iIndex, $jIndex); 152 | $halfWidth = (int) floor($width / 2); 153 | for ($x = -$halfWidth; $x <= $halfWidth; $x++) { 154 | for ($y = -$halfWidth; $y <= $halfWidth; $y++) { 155 | $msg->addCellId(S2CellId::fromFaceIJ($face, $iIndex+$x*$size, $jIndex+$y*$size)->parent(15)->id()); 156 | } 157 | } 158 | 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Session/Session.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 32 | $this->location = $location; 33 | $this->handler = new RequestHandler($this); 34 | } 35 | 36 | /** 37 | * @return Logger 38 | */ 39 | public function getLogger() { 40 | return $this->logger; 41 | } 42 | 43 | /** 44 | * @return RequestHandler 45 | */ 46 | public function getRequestHandler() { 47 | return $this->handler; 48 | } 49 | 50 | public function hasProfile() { 51 | return !is_null($this->profile); 52 | } 53 | 54 | /** 55 | * @return Profile 56 | */ 57 | public function getProfile() { 58 | if (!$this->hasProfile()) { 59 | $this->profile = new Profile($this); 60 | } 61 | return $this->profile; 62 | } 63 | 64 | /** 65 | * @param Profile $profile 66 | */ 67 | public function setProfile(Profile $profile) { 68 | $this->profile = $profile; 69 | } 70 | 71 | /** 72 | * @return Location 73 | */ 74 | public function getLocation() { 75 | return $this->location; 76 | } 77 | 78 | /** 79 | * @return bool 80 | */ 81 | public function hasAuthTicket() { 82 | return !is_null($this->authTicket); 83 | } 84 | 85 | /** 86 | * @return AuthTicket 87 | * @throws Exception 88 | */ 89 | public function getAuthTicket() { 90 | if (!$this->hasAuthTicket()) { 91 | throw new Exception("No auth ticket"); 92 | } 93 | return $this->authTicket; 94 | } 95 | 96 | /** 97 | * @param AuthTicket $authTicket 98 | */ 99 | public function setAuthTicket(AuthTicket $authTicket) { 100 | $this->authTicket = $authTicket; 101 | } 102 | 103 | /** 104 | * @return bool 105 | */ 106 | public function hasEndpoint() { 107 | return !is_null($this->endpoint); 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function getEndpoint() { 114 | return $this->endpoint; 115 | } 116 | 117 | /** 118 | * @param string $endpoint 119 | */ 120 | public function setEndpoint($endpoint) { 121 | $this->endpoint = $endpoint; 122 | } 123 | 124 | /** 125 | * @return int 126 | */ 127 | public function getStartMicroTime() { 128 | if (is_null($this->startMicroTime)) { 129 | $this->start(); 130 | } 131 | 132 | return $this->startMicroTime; 133 | } 134 | 135 | /** 136 | * @param int $microTime 137 | */ 138 | public function setStartMicroTime($microTime) { 139 | $this->startMicroTime = $microTime; 140 | } 141 | 142 | /** 143 | * @return string 144 | */ 145 | public function getSessionHash() { 146 | if (is_null($this->sessionHash)) { 147 | $this->start(); 148 | } 149 | 150 | return $this->sessionHash; 151 | } 152 | 153 | /** 154 | * @param string $hash 155 | */ 156 | public function setSessionHash($hash) { 157 | $this->sessionHash = $hash; 158 | } 159 | 160 | public function start() { 161 | $this->startMicroTime = MicroTime::get(); 162 | $this->sessionHash = random_bytes(16); 163 | } 164 | 165 | /** 166 | * @param Location $location 167 | */ 168 | public function setLocation(Location $location) { 169 | $this->location = $location; 170 | } 171 | 172 | /** 173 | * @return MapObjectsRequest 174 | * @throws Exception 175 | */ 176 | public function getMapObjects() { 177 | $req = new MapObjectsRequest($this); 178 | $req->execute(); 179 | return $req; 180 | } 181 | 182 | abstract public function authenticate(); 183 | 184 | /** 185 | * @return AccountType 186 | */ 187 | abstract public function getType(); 188 | 189 | /** 190 | * @return bool 191 | */ 192 | abstract public function hasToken(); 193 | 194 | /** 195 | * @return string 196 | */ 197 | abstract public function getToken(); 198 | 199 | /** 200 | * @param string $token 201 | */ 202 | abstract public function setToken($token); 203 | 204 | /** 205 | * @throws Exception 206 | */ 207 | public function createEndpoint() { 208 | $this->start(); 209 | 210 | $req = new PlayerRequest($this); 211 | $this->handler->execute([$req], false, true); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Session/PTCSession.php: -------------------------------------------------------------------------------- 1 | username = $username; 38 | $this->password = $password; 39 | 40 | $this->authClient = new Client([ 41 | "cookies" => true, 42 | "verifyPeer" => false, 43 | "connect_timeout" => 30 44 | ]); 45 | } 46 | 47 | /** 48 | * @throws Exception 49 | */ 50 | public function authenticate() { 51 | $ticket = null; 52 | $executionToken = $this->fetchExecutionToken(); 53 | try { 54 | $this->authClient->post(self::LOGIN_URL, [ 55 | "headers" => ["User-Agent" => "niantic"], 56 | "allow_redirects" => [ 57 | "on_redirect" => function(Request $request, Response $response) use (&$ticket) { 58 | // Extract ticket from HTTP-Location header. 59 | preg_match("/ticket=(.+)/", $response->getHeaderLine("Location"), $matches); 60 | if (isset($matches[1])) { 61 | $ticket = $matches[1]; 62 | } 63 | } 64 | ], 65 | "form_params" => [ 66 | "lt" => $executionToken->lt, 67 | "execution" => $executionToken->execution, 68 | "_eventId" => "submit", 69 | "username" => $this->username, 70 | "password" => $this->password, 71 | ] 72 | ]); 73 | } 74 | catch(ServerException $exception) { 75 | // This is expected... 76 | } 77 | 78 | if ($ticket === null) { 79 | throw new Exception("No token found"); 80 | } 81 | 82 | // Make oAuth request 83 | try { 84 | $response = $this->authClient->post(self::LOGIN_OAUTH, [ 85 | "headers" => ["User-Agent" => "niantic"], 86 | "form_params" => [ 87 | "client_id" => "mobile-app_pokemon-go", 88 | "redirect_uri" => "https://www.nianticlabs.com/pokemongo/error", 89 | "client_secret" => self::LOGIN_SECRET, 90 | "grant_type" => "refresh_token", 91 | "code" => $ticket 92 | ] 93 | ]); 94 | } catch(ServerException $e) { 95 | throw new Exception($e); 96 | } 97 | 98 | parse_str($response->getBody()->getContents(), $data); 99 | if (!isset($data['access_token'])) { 100 | throw new Exception("No access token"); 101 | } 102 | 103 | $this->token = $data['access_token']; 104 | } 105 | 106 | /** 107 | * @param int $retry 108 | * @return mixed 109 | * @throws Exception 110 | */ 111 | protected function fetchExecutionToken($retry = 0) { 112 | if ($retry >= 3) { 113 | throw new Exception("Unable to fetch execution token"); 114 | } 115 | 116 | try { 117 | $response = $this->authClient->get(self::LOGIN_URL, ["headers" => ["User-Agent" => "niantic"]]); 118 | } 119 | catch(ServerException $e) { 120 | sleep(1); 121 | return $this->fetchExecutionToken($retry+1); 122 | } 123 | 124 | if ($response->getStatusCode() !== 200) { 125 | throw new Exception("Wrong response ".$response->getStatusCode()); 126 | } 127 | $jsonData = json_decode($response->getBody()->getContents()); 128 | if (!is_null($jsonData)) { 129 | return $jsonData; 130 | } 131 | throw new Exception("Unable to fetch execution token (2)"); 132 | } 133 | 134 | /** 135 | * @return bool 136 | */ 137 | public function hasToken() { 138 | return !is_null($this->token); 139 | } 140 | 141 | /** 142 | * @return string 143 | * @throws Exception 144 | */ 145 | public function getToken() { 146 | if (!$this->hasToken()) { 147 | throw new Exception("No token set"); 148 | } 149 | return $this->token; 150 | } 151 | 152 | /** 153 | * @param string $token 154 | */ 155 | public function setToken($token) { 156 | $this->token = $token; 157 | } 158 | 159 | /** 160 | * @return AccountType 161 | */ 162 | public function getType() { 163 | return AccountType::PTC(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Session/RequestHandler.php: -------------------------------------------------------------------------------- 1 | logger = $session->getLogger(); 33 | $this->session = $session; 34 | $this->RPCId = mt_rand(); 35 | 36 | $this->client = new Client([ 37 | "headers" => ["User-Agent" => "Niantic App"] 38 | ]); 39 | } 40 | 41 | /** 42 | * @return int 43 | */ 44 | protected function getRPCId() { 45 | return ++$this->RPCId; 46 | } 47 | 48 | /** 49 | * @return Request[] 50 | */ 51 | protected function getDefaultRequests() { 52 | $reqs = []; 53 | /* $reqs[] = new R\HatchedEggsRequest(); 54 | 55 | $inventory = new ProtoRequest(); 56 | $inventory->setRequestType(RequestType::GET_INVENTORY); 57 | $inventoryMessage = new GetInventoryMessage(); 58 | $inventoryMessage->setLastTimestampMs(0); 59 | $inventory->setRequestMessage($inventoryMessage->toProtobuf()); 60 | $reqs[] = $inventory; 61 | 62 | $badges = new ProtoRequest(); 63 | $badges->setRequestType(RequestType::CHECK_AWARDED_BADGES); 64 | $reqs[] = $badges; 65 | 66 | $settings = new ProtoRequest(); 67 | $settings->setRequestType(RequestType::DOWNLOAD_SETTINGS); 68 | $settingsMessage = new DownloadSettingsMessage(); 69 | $settingsMessage->setHash("4a2e9bc330dae60e7b74fc85b98868ab4700802e"); 70 | $settings->setRequestMessage($settingsMessage->toProtobuf()); 71 | $reqs[] = $settings;*/ 72 | 73 | return $reqs; 74 | } 75 | 76 | /** 77 | * @param Request[] $reqs 78 | * @param bool|false $createEndpoint 79 | * @param bool|false $redo 80 | * @throws Exception 81 | */ 82 | public function execute($reqs, $createEndpoint = false, $redo = true) { 83 | $reqTypes = []; 84 | foreach ($reqs as $req) { 85 | $reqTypes[] = get_class($req); 86 | } 87 | $this->logger->info("Execute ".implode(", ", $reqTypes)); 88 | 89 | $env = new RequestEnvelope(); 90 | $env->setStatusCode(2); 91 | $env->setRequestId($this->getRPCId()); 92 | 93 | $location = $this->session->getLocation(); 94 | 95 | $env->setLatitude($location->getLatitude()); 96 | $env->setLongitude($location->getLongitude()); 97 | $env->setAltitude($location->getAltitude()); 98 | 99 | if ($this->session->hasAuthTicket() && $this->session->getAuthTicket()->isValid()) { 100 | $this->logger->debug("Existing auth ticket"); 101 | $env->setAuthTicket($this->session->getAuthTicket()->toProto()); 102 | } 103 | else { 104 | if (!$this->session->hasToken()) { 105 | $this->session->authenticate(); 106 | } 107 | 108 | $this->session->setEndpoint(null); 109 | 110 | $this->logger->debug("Auth with token"); 111 | $info = new AuthInfo(); 112 | $info->setProvider($this->session->getType()->getProvider()); 113 | 114 | $token = new JWT(); 115 | $token->setContents($this->session->getToken()); 116 | $token->setUnknown2(59); 117 | 118 | $info->setToken($token); 119 | $env->setAuthInfo($info); 120 | } 121 | $env->setUnknown12(989); 122 | 123 | foreach ($reqs as $req) { 124 | $env->addRequests($req->toProto()); 125 | } 126 | 127 | $URL = $this->session->hasEndpoint() ? $this->session->getEndpoint() : self::APIURL; 128 | 129 | Signature::sign($this->session, $env, $reqs); 130 | 131 | $resp = $this->client->post($URL, ["body" => $env->toStream()->getContents()]); 132 | $respEnv = new ResponseEnvelope($resp->getBody()->getContents()); 133 | if ($respEnv->hasAuthTicket()) { 134 | $ticket = new AuthTicket($respEnv->getAuthTicket()); 135 | $this->logger->info("Received auth ticket, expires in ".$ticket->getTimeToExpire()."s"); 136 | $this->session->setAuthTicket($ticket); 137 | } 138 | 139 | if ($respEnv->hasApiUrl()) { 140 | $endpoint = $respEnv->getApiUrl(); 141 | $this->logger->info("Received API endpoint ".$endpoint); 142 | $this->session->setEndpoint("https://".$endpoint."/rpc"); 143 | } 144 | 145 | $statusCode = $respEnv->getStatusCode(); 146 | $this->logger->debug("Status code ".$statusCode); 147 | if ($statusCode == 102) { 148 | // Some auth problem 149 | throw new Exception("Received status code 102: invalid auth"); 150 | } 151 | elseif ($statusCode == 53) { 152 | // Wrong endpoint 153 | if ($redo) { 154 | sleep(3); 155 | $this->execute($reqs, $createEndpoint, false); 156 | return; 157 | } 158 | else { 159 | throw new Exception("Received status code 53: wrong endpoint"); 160 | } 161 | } 162 | elseif ($statusCode == 3) { 163 | // Possible ban 164 | throw new Exception("Received status code 3: account possibly banned"); 165 | } 166 | 167 | if (!$respEnv->hasReturnsList()) { 168 | throw new Exception("No responses given"); 169 | } 170 | 171 | $returns = $respEnv->getReturnsList(); 172 | $countResponses = $returns->count(); 173 | if ($countResponses != count($reqs)) { 174 | throw new Exception("Invalid responses found"); 175 | } 176 | 177 | $responses = []; 178 | foreach ($returns as $return) { 179 | $responses[] = $return; 180 | } 181 | 182 | array_map(function (Request $req, $response) { 183 | $req->setRawResponse($response); 184 | }, $reqs, $responses); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/Player/Avatar.php: -------------------------------------------------------------------------------- 1 | profile = $profile; 30 | } 31 | 32 | /** 33 | * @return int 34 | */ 35 | public function getSkin() { 36 | return $this->skin; 37 | } 38 | 39 | /** 40 | * @param int $skin 41 | */ 42 | protected function setSkin($skin) { 43 | $this->skin = $skin; 44 | } 45 | 46 | /** 47 | * @return int 48 | */ 49 | public function getHair() { 50 | return $this->hair; 51 | } 52 | 53 | /** 54 | * @param int $hair 55 | */ 56 | protected function setHair($hair) { 57 | $this->hair = $hair; 58 | } 59 | 60 | /** 61 | * @return int 62 | */ 63 | public function getShirt() { 64 | return $this->shirt; 65 | } 66 | 67 | /** 68 | * @param int $shirt 69 | */ 70 | protected function setShirt($shirt) { 71 | $this->shirt = $shirt; 72 | } 73 | 74 | /** 75 | * @return int 76 | */ 77 | public function getPants() { 78 | return $this->pants; 79 | } 80 | 81 | /** 82 | * @param int $pants 83 | */ 84 | protected function setPants($pants) { 85 | $this->pants = $pants; 86 | } 87 | 88 | /** 89 | * @return int 90 | */ 91 | public function getHat() { 92 | return $this->hat; 93 | } 94 | 95 | /** 96 | * @param int $hat 97 | */ 98 | protected function setHat($hat) { 99 | $this->hat = $hat; 100 | } 101 | 102 | /** 103 | * @return int 104 | */ 105 | public function getShoes() { 106 | return $this->shoes; 107 | } 108 | 109 | /** 110 | * @param int $shoes 111 | */ 112 | protected function setShoes($shoes) { 113 | $this->shoes = $shoes; 114 | } 115 | 116 | /** 117 | * @return int 118 | */ 119 | public function getGender() { 120 | return $this->gender; 121 | } 122 | 123 | /** 124 | * @param int $gender 125 | */ 126 | protected function setGender($gender) { 127 | $this->gender = $gender; 128 | } 129 | 130 | /** 131 | * @return bool 132 | */ 133 | public function isMale() { 134 | return $this->getGender() == Gender::MALE()->value(); 135 | } 136 | 137 | /** 138 | * @return bool 139 | */ 140 | public function isFemale() { 141 | return !$this->isMale(); 142 | } 143 | 144 | /** 145 | * @return int 146 | */ 147 | public function getEyes() { 148 | return $this->eyes; 149 | } 150 | 151 | /** 152 | * @param int $eyes 153 | */ 154 | protected function setEyes($eyes) { 155 | $this->eyes = $eyes; 156 | } 157 | 158 | /** 159 | * @return int 160 | */ 161 | public function getBackpack() { 162 | return $this->backpack; 163 | } 164 | 165 | /** 166 | * @param int $backpack 167 | */ 168 | protected function setBackpack($backpack) { 169 | $this->backpack = $backpack; 170 | } 171 | 172 | /** 173 | * @throws Exception 174 | */ 175 | public function set() { 176 | $tutorial = $this->profile->getTutorial(); 177 | if ($tutorial->isAvatarSelected()) { 178 | throw new Exception("Avatar already selected"); 179 | } 180 | 181 | $req = new AvatarRequest($this->profile->getSession()); 182 | $req->execute(); 183 | 184 | var_dump($req->getResponse()); 185 | 186 | if ($req->getResponse()->getStatus()->value() != Status::SUCCESS_VALUE) { 187 | throw new Exception("Unable to set avatar"); 188 | } 189 | } 190 | 191 | /** 192 | * @param PlayerAvatar $playerAvatar 193 | */ 194 | public function updated(PlayerAvatar $playerAvatar) { 195 | $this->setSkin($playerAvatar->getSkin()); 196 | $this->setHair($playerAvatar->getHair()); 197 | $this->setShirt($playerAvatar->getShirt()); 198 | $this->setPants($playerAvatar->getPants()); 199 | $this->setHat($playerAvatar->getHat()); 200 | $this->setShoes($playerAvatar->getShoes()); 201 | $this->setGender(!$playerAvatar->hasGender() ? Gender::MALE_VALUE : $playerAvatar->getGender()->value()); 202 | $this->setEyes($playerAvatar->getEyes()); 203 | $this->setBackpack($playerAvatar->getBackpack()); 204 | } 205 | 206 | /** 207 | * @return PlayerAvatar 208 | */ 209 | public function toProto() { 210 | $playerAvatar = new PlayerAvatar(); 211 | $playerAvatar->setSkin($this->getSkin()); 212 | $playerAvatar->setHair($this->getHair()); 213 | $playerAvatar->setShirt($this->getShirt()); 214 | $playerAvatar->setPants($this->getPants()); 215 | $playerAvatar->setHat($this->getHat()); 216 | $playerAvatar->setShoes($this->getShoes()); 217 | $playerAvatar->setGender(Gender::valueOf($this->getGender())); 218 | $playerAvatar->setEyes($this->getEyes()); 219 | $playerAvatar->setBackpack($this->getBackpack()); 220 | return $playerAvatar; 221 | } 222 | 223 | /** 224 | * @return string 225 | */ 226 | public function serialize() { 227 | return (string) $this->getSkin().$this->getHair().$this->getShirt().$this->getPants().$this->getHat().$this->getShoes().$this->getGender().$this->getEyes().$this->getBackpack(); 228 | } 229 | 230 | /** 231 | * @param Profile $profile 232 | * @param string $data 233 | * @return Avatar 234 | */ 235 | public static function unserialize($profile, $data) { 236 | if (!($profile instanceof Profile)) { 237 | throw new InvalidArgumentException("Expected Profile instance"); 238 | } 239 | $parts = str_split($data); 240 | if (count($parts) != 9) { 241 | throw new InvalidArgumentException("Invalid avatar parts"); 242 | } 243 | $avatar = new self($profile); 244 | $avatar->setSkin((int) $parts[0]); 245 | $avatar->setHair((int) $parts[1]); 246 | $avatar->setShirt((int) $parts[2]); 247 | $avatar->setPants((int) $parts[3]); 248 | $avatar->setHat((int) $parts[4]); 249 | $avatar->setShoes((int) $parts[5]); 250 | $avatar->setGender((int) $parts[6]); 251 | $avatar->setEyes((int) $parts[7]); 252 | $avatar->setBackpack((int) $parts[8]); 253 | return $avatar; 254 | } 255 | 256 | /** 257 | * @param Profile $profile 258 | * @return Avatar 259 | */ 260 | public static function generateRandom(Profile $profile) { 261 | $avatar = new self($profile); 262 | $avatar->setSkin(mt_rand(0, 3)); 263 | $avatar->setHair(mt_rand(0, 5)); 264 | $avatar->setShirt(mt_rand(0, 3)); 265 | $avatar->setPants(mt_rand(0, 2)); 266 | $avatar->setHat(mt_rand(0, 4)); 267 | $avatar->setShoes(mt_rand(0, 6)); 268 | $avatar->setGender(mt_rand(0, 1)); 269 | $avatar->setEyes(mt_rand(0, 4)); 270 | $avatar->setBackpack(mt_rand(0, 5)); 271 | return $avatar; 272 | } 273 | } -------------------------------------------------------------------------------- /src/Player/Profile.php: -------------------------------------------------------------------------------- 1 | session = $session; 37 | $this->logger = $this->session->getLogger(); 38 | 39 | $this->updated = -1; 40 | } 41 | 42 | /** 43 | * @return Session 44 | */ 45 | public function getSession() { 46 | return $this->session; 47 | } 48 | 49 | /** 50 | * @return bool 51 | */ 52 | public function hasUpdated() { 53 | return $this->updated > 0; 54 | } 55 | 56 | /** 57 | * @return int 58 | */ 59 | public function getUpdated() { 60 | return $this->updated; 61 | } 62 | 63 | /** 64 | * @param int $time 65 | */ 66 | protected function setUpdated($time = -1) { 67 | $this->updated = $time < 0 ? time() : $time; 68 | } 69 | 70 | /** 71 | * @return int 72 | */ 73 | public function timeSinceUpdated() { 74 | return $this->hasUpdated() ? time()-$this->updated : -1; 75 | } 76 | 77 | /** 78 | * @return int 79 | */ 80 | public function getCreationTimestampMs() { 81 | return $this->creationTimeStampMs; 82 | } 83 | 84 | /** 85 | * @param int $creationTimeStampMs 86 | */ 87 | protected function setCreationTimeStampMs($creationTimeStampMs) { 88 | $this->creationTimeStampMs = $creationTimeStampMs; 89 | } 90 | 91 | /** 92 | * @return string 93 | */ 94 | public function getUsername() { 95 | return $this->username; 96 | } 97 | 98 | /** 99 | * @param string $username 100 | */ 101 | protected function setUsername($username) { 102 | $this->username = $username; 103 | } 104 | 105 | /** 106 | * @return bool 107 | */ 108 | public function hasTutorial() { 109 | return !is_null($this->tutorial); 110 | } 111 | 112 | /** 113 | * @return Tutorial 114 | */ 115 | public function getTutorial() { 116 | if (!$this->hasTutorial()) { 117 | $this->tutorial = new Tutorial($this); 118 | } 119 | return $this->tutorial; 120 | } 121 | 122 | /** 123 | * @param Tutorial $tutorial 124 | */ 125 | protected function setTutorial(Tutorial $tutorial) { 126 | $this->tutorial = $tutorial; 127 | } 128 | 129 | /** 130 | * @return int 131 | */ 132 | public function getMaxPokemonStorage() { 133 | return $this->maxPokemonStorage; 134 | } 135 | 136 | /** 137 | * @param int $maxPokemonStorage 138 | */ 139 | protected function setMaxPokemonStorage($maxPokemonStorage) { 140 | $this->maxPokemonStorage = $maxPokemonStorage; 141 | } 142 | 143 | /** 144 | * @return int 145 | */ 146 | public function getMaxItemStorage() { 147 | return $this->maxItemStorage; 148 | } 149 | 150 | /** 151 | * @param int $maxItemStorage 152 | */ 153 | protected function setMaxItemStorage($maxItemStorage) { 154 | $this->maxItemStorage = $maxItemStorage; 155 | } 156 | 157 | /** 158 | * @return bool 159 | */ 160 | public function hasContact() { 161 | return !is_null($this->contact); 162 | } 163 | 164 | /** 165 | * @return Contact 166 | */ 167 | public function getContact() { 168 | if (!$this->hasContact()) { 169 | $this->contact = new Contact($this); 170 | } 171 | return $this->contact; 172 | } 173 | 174 | /** 175 | * @param Contact $contact 176 | */ 177 | public function setContact(Contact $contact) { 178 | $this->contact = $contact; 179 | } 180 | 181 | /** 182 | * @return int 183 | */ 184 | public function getPokecoins() { 185 | return $this->pokecoins; 186 | } 187 | 188 | /** 189 | * @param int $pokecoins 190 | */ 191 | protected function setPokecoins($pokecoins) { 192 | $this->pokecoins = $pokecoins; 193 | } 194 | 195 | /** 196 | * @return int 197 | */ 198 | public function getStardust() { 199 | return $this->stardust; 200 | } 201 | 202 | /** 203 | * @param int $stardust 204 | */ 205 | protected function setStardust($stardust) { 206 | $this->stardust = $stardust; 207 | } 208 | 209 | /** 210 | * @return bool 211 | */ 212 | public function hasAvatar() { 213 | return !is_null($this->avatar); 214 | } 215 | 216 | /** 217 | * @return Avatar 218 | */ 219 | public function getAvatar() { 220 | return $this->avatar; 221 | } 222 | 223 | /** 224 | * @param Avatar $avatar 225 | */ 226 | public function setAvatar(Avatar $avatar) { 227 | $this->avatar = $avatar; 228 | } 229 | 230 | /** 231 | * @throws Exception 232 | */ 233 | public function update() { 234 | $this->logger->debug("Update profile"); 235 | 236 | $req = new PlayerRequest($this->session); 237 | $req->execute(); 238 | $resp = $req->getResponse(); 239 | if (!$resp->hasPlayerData()) { 240 | throw new Exception("Unable to obtain player data"); 241 | } 242 | $data = $resp->getPlayerData(); 243 | 244 | $this->setUpdated(); 245 | $this->setCreationTimeStampMs($data->getCreationTimestampMs()); 246 | $this->setUsername($data->getUsername()); 247 | if ($data->hasTutorialStateList()) { 248 | $this->getTutorial()->updated($data->getTutorialStateList()); 249 | } 250 | else { 251 | $this->getTutorial()->clear(); 252 | } 253 | $this->setMaxPokemonStorage($data->getMaxPokemonStorage()); 254 | $this->setMaxItemStorage($data->getMaxItemStorage()); 255 | $this->getContact()->updated($data->getContactSettings()); 256 | if ($data->hasAvatar()) { 257 | if (!$this->hasAvatar()) { 258 | $this->setAvatar(Avatar::generateRandom($this)); 259 | } 260 | $this->getAvatar()->updated($data->getAvatar()); 261 | } 262 | 263 | $this->pokecoins = 0; 264 | $this->stardust = 0; 265 | foreach ($data->getCurrenciesList() as $currency) { 266 | /* @var Currency $currency */ 267 | $amount = $currency->getAmount(); 268 | if (is_null($amount)) { 269 | continue; 270 | } 271 | 272 | switch ($currency->getName()) { 273 | case "POKECOIN": 274 | $this->pokecoins = $amount; 275 | break; 276 | case "STARDUST": 277 | $this->stardust = $amount; 278 | break; 279 | default: 280 | break; 281 | } 282 | } 283 | } 284 | 285 | /** 286 | * @return string 287 | */ 288 | public function serialize() { 289 | if (!$this->hasUpdated()) { 290 | return []; 291 | } 292 | 293 | return [ 294 | "updated" => $this->getUpdated(), 295 | "creation_timestamp_ms" => $this->getCreationTimestampMs(), 296 | "username" => $this->getUsername(), 297 | "tutorial" => $this->hasTutorial() ? $this->getTutorial()->serialize() : [], 298 | "max_pokemon_storage" => $this->getMaxPokemonStorage(), 299 | "max_item_storage" => $this->getMaxItemStorage(), 300 | "contact" => $this->hasContact() ? $this->getContact()->serialize() : [], 301 | "pokecoins" => $this->getPokecoins(), 302 | "stardust" => $this->getStardust(), 303 | "avatar" => $this->hasAvatar() ? $this->getAvatar()->serialize() : null 304 | ]; 305 | } 306 | 307 | /** 308 | * @param Session $session 309 | * @param array $data 310 | * @return Profile 311 | * @throws InvalidArgumentException 312 | */ 313 | public static function unserialize($session, $data) { 314 | if (!($session instanceof Session)) { 315 | throw new InvalidArgumentException("Expected Session instance"); 316 | } 317 | 318 | $profile = new Profile($session); 319 | if (isset($data['updated']) && $data['updated'] > 0) { 320 | $profile->setUpdated($data['updated']); 321 | $profile->setCreationTimeStampMs($data['creation_timestamp_ms']); 322 | $profile->setUsername($data['username']); 323 | $profile->setTutorial(Tutorial::unserialize($profile, $data['tutorial'])); 324 | $profile->setMaxPokemonStorage($data['max_pokemon_storage']); 325 | $profile->setMaxItemStorage($data['max_item_storage']); 326 | $profile->setContact(Contact::unserialize($profile, $data['contact'])); 327 | $profile->setPokecoins($data['pokecoins']); 328 | $profile->setStardust($data['stardust']); 329 | if (isset($data['avatar']) && !is_null($data['avatar']) && strlen($data['avatar']) > 1) { 330 | $profile->setAvatar(Avatar::unserialize($profile, $data['avatar'])); 331 | } 332 | } 333 | 334 | return $profile; 335 | } 336 | } 337 | --------------------------------------------------------------------------------