├── README.markdown └── class.sosumi.php /README.markdown: -------------------------------------------------------------------------------- 1 | Sosumi 2 | ========= 3 | 4 | Sosumi is a PHP client for Apple's Find My iPhone service. This allows you to programmatically retrieve your devices's current location and push messages (and an optional alarm) to the remote device. 5 | 6 | The previous version of Sosumi (June 2009 - June 20, 2010) scraped MobileMe's website to determine your location information. However, with Apple's new (as of June 2010) [Find My iPhone app](http://itunes.apple.com/us/app/find-my-iphone/id376101648?mt=8), we can piggy-back on their "official" JSON web service and pull your information much faster and more reliably as it's not prone to breaking whenever there's a website update. I highly recommend upgrading to the new version. 7 | 8 | Much love to the MobileMe/iCloud team for a wonderful service :-) 9 | 10 | **Looking for the Mac desktop version of Sosumi?** [Click here](https://github.com/tylerhall/MacSosumi). 11 | 12 | FEATURES 13 | -------- 14 | 15 | * Retrieve your device's current location and margin of error. 16 | * Push a custom text message to the device and an optional audible alarm. 17 | 18 | INSTALL 19 | ------- 20 | 21 | This script requires PHP 5.2 and the JSON extension, which should be included by default. PHP's CURL extension (with SSL support) is also required. 22 | 23 | UPDATES 24 | ------- 25 | 26 | Code is hosted at GitHub: [http://github.com/tylerhall/sosumi](http://github.com/tylerhall/sosumi) 27 | 28 | OTHER LANGUAGES / RELATED PROJECTS 29 | ---------------------------------- 30 | 31 | * Python port - [https://github.com/comfuture/recordmylatitude](https://github.com/comfuture/recordmylatitude) 32 | * Node.js port - [http://github.com/drudge/node-sosumi](http://github.com/drudge/node-sosumi) 33 | * Ruby port - [http://github.com/hpop/rosumi](http://github.com/hpop/rosumi) 34 | * PlayNice - Automatically update your Google Latitude location using Sosumi [http://github.com/ablyler/playnice](http://github.com/ablyler/playnice) 35 | 36 | LICENSE 37 | ------- 38 | 39 | The MIT License 40 | 41 | Copyright (c) 2011 Tyler Hall 42 | 43 | Permission is hereby granted, free of charge, to any person obtaining a copy 44 | of this software and associated documentation files (the "Software"), to deal 45 | in the Software without restriction, including without limitation the rights 46 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 47 | copies of the Software, and to permit persons to whom the Software is 48 | furnished to do so, subject to the following conditions: 49 | 50 | The above copyright notice and this permission notice shall be included in 51 | all copies or substantial portions of the Software. 52 | 53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 54 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 55 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 56 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 57 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 58 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 59 | THE SOFTWARE. -------------------------------------------------------------------------------- /class.sosumi.php: -------------------------------------------------------------------------------- 1 | 9 | // http://github.com/tylerhall/sosumi/tree/master 10 | // 11 | // Usage: 12 | // $ssm = new Sosumi('username', 'password'); 13 | // $location_info = $ssm->locate(); 14 | // $ssm->sendMessage('Your Message', true, , 'Important Message'); 15 | 16 | class Sosumi 17 | { 18 | public $devices; 19 | public $debug; 20 | private $username; 21 | private $password; 22 | private $partition; 23 | private $prsId; 24 | 25 | public function __construct($mobile_me_username, $mobile_me_password, $debug = false) 26 | { 27 | $this->devices = array(); 28 | $this->debug = $debug; 29 | $this->username = $mobile_me_username; 30 | $this->password = $mobile_me_password; 31 | $this->getPartition(); 32 | $this->initClient(); 33 | $this->refreshClient(); 34 | } 35 | 36 | private function getPartition() 37 | { 38 | $this->iflog('Getting partition...'); 39 | $post = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'; 40 | $response = $this->curlPost("/fmipservice/device/{$this->username}/initClient", $post, array(), true); 41 | preg_match('/MMe-Host:(.*?)$/msi', $response, $matches); 42 | if(isset($matches[1])) $this->partition = trim($matches[1]); 43 | } 44 | 45 | public function refreshClient() 46 | { 47 | $this->iflog('refreshClient ' . $this->prsId); 48 | $post = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'; 49 | $response = $this->curlPost("/fmipservice/device/{$this->prsId}/refreshClient", $post, array(), true); 50 | } 51 | 52 | public function locate($device_id, $max_wait = 300) 53 | { 54 | $start = time(); 55 | 56 | // Loop until the device has been located... 57 | while(!$this->devices[$device_id]->latitude || !$this->devices[$device_id]->longitude) 58 | { 59 | $this->iflog('Waiting for location...'); 60 | if((time() - $start) > $max_wait) 61 | { 62 | throw new Exception("Unable to find location within '$max_wait' seconds\n"); 63 | } 64 | 65 | sleep(5); 66 | $this->initClient(); 67 | } 68 | 69 | $loc = array( 70 | "latitude" => $this->devices[$device_id]->latitude, 71 | "longitude" => $this->devices[$device_id]->longitude, 72 | "accuracy" => $this->devices[$device_id]->horizontalAccuracy, 73 | "timestamp" => $this->devices[$device_id]->locationTimestamp, 74 | ); 75 | 76 | return $loc; 77 | } 78 | 79 | public function sendMessage($device_id, $msg, $alarm = false, $subject = 'Important Message') 80 | { 81 | $post = sprintf('{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true},"sound":%s,"subject":"%s","text":"%s","userText":true}', 82 | $device_id, $device_id, 83 | $alarm ? 'true' : 'false', $subject, $msg); 84 | 85 | $this->iflog('Sending message...'); 86 | $this->curlPost("/fmipservice/device/{$this->username}/sendMessage", $post); 87 | $this->iflog('Message sent'); 88 | } 89 | 90 | public function remoteLock($device_id, $passcode) 91 | { 92 | $post = sprintf('{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","oldPasscode":"","passcode":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true}}', 93 | $device_id, $device_id, $passcode); 94 | 95 | $this->iflog('Sending remote lock...'); 96 | $this->curlPost("/fmipservice/device/{$this->username}/remoteLock", $post); 97 | $this->iflog('Remote lock sent'); 98 | } 99 | 100 | // This hasn't been tested (for obvious reasons). Please let me know if it does/doesn't work... 101 | public function remoteWipe($device_id, $passcode) 102 | { 103 | $post = sprintf('{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","oldPasscode":"","passcode":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true}}', 104 | $device_id, $device_id, $passcode); 105 | 106 | $this->iflog('Sending remote wipe...'); 107 | $this->curlPost("/fmipservice/device/{$this->username}/remoteWipe", $post); 108 | $this->iflog('Remote wipe sent'); 109 | } 110 | 111 | private function initClient() 112 | { 113 | $this->iflog('initClient...'); 114 | $post = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'; 115 | $json_str = $this->curlPost("/fmipservice/device/{$this->username}/initClient", $post); 116 | $this->iflog('initClient Returned: ' . $json_str); 117 | $json = json_decode($json_str); 118 | 119 | if(is_null($json)) 120 | throw new Exception("Error parsing json string"); 121 | 122 | if(isset($json->error)) 123 | throw new Exception("Error from web service: '$json->error'"); 124 | 125 | $this->devices = array(); 126 | if(isset($json) && isset($json->content) && (is_array($json->content) || is_object($json->content))){ 127 | 128 | $this->prsId = $json->serverContext->prsId; 129 | 130 | $this->iflog('Parsing ' . count($json->content) . ' devices...'); 131 | foreach($json->content as $json_device) 132 | { 133 | $device = new SosumiDevice(); 134 | if(isset($json_device->location) && is_object($json_device->location)) 135 | { 136 | $device->locationTimestamp = date('Y-m-d H:i:s', $json_device->location->timeStamp / 1000); 137 | $device->locationType = $json_device->location->positionType; 138 | $device->horizontalAccuracy = $json_device->location->horizontalAccuracy; 139 | $device->locationFinished = $json_device->location->locationFinished; 140 | $device->longitude = $json_device->location->longitude; 141 | $device->latitude = $json_device->location->latitude; 142 | } 143 | $device->isLocating = $json_device->isLocating; 144 | $device->deviceModel = $json_device->deviceModel; 145 | $device->deviceStatus = $json_device->deviceStatus; 146 | $device->id = $json_device->id; 147 | $device->name = $json_device->name; 148 | $device->deviceClass = $json_device->deviceClass; 149 | $device->chargingStatus = $json_device->batteryStatus; 150 | $device->batteryLevel = $json_device->batteryLevel; 151 | $this->devices[$device->id] = $device; 152 | } 153 | } 154 | } 155 | 156 | private function curlPost($url, $post_vars = '', $headers = array(), $return_headers = false) 157 | { 158 | if(isset($this->partition)) 159 | $url = 'https://' . $this->partition . $url; 160 | else 161 | $url = 'https://fmipmobile.icloud.com' . $url; 162 | 163 | $this->iflog("URL: $url"); 164 | $this->iflog("POST DATA: $post_vars"); 165 | 166 | $headers[] = 'Content-Type: application/json; charset=utf-8'; 167 | $headers[] = 'X-Apple-Find-Api-Ver: 2.0'; 168 | $headers[] = 'X-Apple-Authscheme: UserIdGuest'; 169 | $headers[] = 'X-Apple-Realm-Support: 1.0'; 170 | $headers[] = 'User-agent: Find iPhone/1.4 MeKit (iPad: iPhone OS/4.2.1)'; 171 | $headers[] = 'X-Client-Name: iPad'; 172 | $headers[] = 'X-Client-UUID: 0cf3dc501ff812adb0b202baed4f37274b210853'; 173 | $headers[] = 'Accept-Language: en-us'; 174 | $headers[] = "Connection: keep-alive"; 175 | 176 | $ch = curl_init($url); 177 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 178 | curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password); 179 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 180 | curl_setopt($ch, CURLOPT_AUTOREFERER, true); 181 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 182 | curl_setopt($ch, CURLOPT_POST, true); 183 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_vars); 184 | if(!is_null($headers)) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 185 | 186 | // curl_setopt($ch, CURLOPT_VERBOSE, true); 187 | 188 | if($return_headers) 189 | curl_setopt($ch, CURLOPT_HEADER, true); 190 | 191 | return curl_exec($ch); 192 | } 193 | 194 | private function iflog($str) 195 | { 196 | if($this->debug === true) 197 | echo $str . "\n"; 198 | } 199 | } 200 | 201 | class SosumiDevice 202 | { 203 | public $isLocating; 204 | public $locationTimestamp; 205 | public $locationType; 206 | public $horizontalAccuracy; 207 | public $locationFinished; 208 | public $longitude; 209 | public $latitude; 210 | public $deviceModel; 211 | public $deviceStatus; 212 | public $id; 213 | public $name; 214 | public $deviceClass; 215 | 216 | // These values only recently appeared in Apple's JSON response. 217 | // Their final names will probably change to something other than 218 | // 'a' and 'b'. 219 | public $chargingStatus; // location->a 220 | public $batteryLevel; // location->b 221 | } 222 | 223 | 224 | --------------------------------------------------------------------------------