├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── ca_bundle.crt ├── snapchat.php ├── snapchat_agent.php └── snapchat_cache.php └── tests ├── media ├── movie.mov └── picture.jpg └── phpSnapchatTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.5 4 | - 5.4 5 | - 5.3 6 | before_install: 7 | - composer install 8 | script: 9 | - phpunit 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Daniel Stelljes 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snapchat for PHP 2 | 3 | 4 | Originally written by Daniel Stelljes, but his whole account dissapeared without a trace. 5 | Will try to keep it up-to-date with the Snapchat API. 6 | 7 | 8 | This library is built to communicate with the Snapchat API. It is nearly 9 | feature complete but still lacks some functionality available in the latest 10 | versions of the official apps (namely Stories). 11 | 12 | It's similar to the [excellent Snaphax library](http://github.com/tlack/snaphax) 13 | built by Thomas Lackner <[@tlack](http://twitter.com/tlack)>, but the approach 14 | is different enough that I figured it deserved its own repo. 15 | 16 | 17 | ## Class Methods 18 | 19 | 20 | ```php 21 | require 'snapchat.php'; 22 | 23 | $snapchat = new Snapchat('username', 'password'); 24 | $snapchat->logout(); 25 | ``` 26 | 27 | - #### getSnaps() 28 | 29 | ```php 30 | echo $snapchat->getSnaps(); 31 | ``` 32 | 33 | - #### getMedia($snap_id) 34 | 35 | ```php 36 | $data = $snapchat->getMedia('122FAST2FURIOUS334r'); 37 | file_put_contents('/home/dan/snap.jpg', $data); 38 | ``` 39 | 40 | - #### markSnapViewed($snap_id) 41 | 42 | ```php 43 | echo $snapchat->markSnapViewed('122FAST2FURIOUS334r'); 44 | ``` 45 | 46 | - #### uploadImage($image_path) 47 | 48 | ```php 49 | echo $snapchat->uploadImage('../test.jpg'); 50 | ``` 51 | 52 | - #### uploadVideo($video_path) 53 | 54 | ```php 55 | echo $snapchat->uploadVideo('../test.mp4'); 56 | ``` 57 | 58 | - #### send($snap_id, $friends_array, $seconds) 59 | 60 | ```php 61 | $id = $snapchat->uploadImage('../test.jpg'); 62 | echo $snapchat->send($id, array('kartiktalwar'), 8); 63 | ``` 64 | 65 | - #### getFriends() 66 | 67 | ```php 68 | echo $snapchat->getFriends(); 69 | ``` 70 | 71 | - #### addFriends($friends_array) 72 | 73 | ```php 74 | echo $snapchat->addFriends(array('bill', 'bob', 'bart')); 75 | ``` 76 | 77 | - #### getBests($friends_array) 78 | 79 | ```php 80 | echo $snapchat->getBests(array('bill', 'bob')); 81 | ``` 82 | 83 | - #### deleteFriends($friends_array) 84 | 85 | ```php 86 | echo $snapchat->deleteFriends(array('bart')); 87 | ``` 88 | 89 | - #### updatePrivacy($privacy) 90 | 91 | ```php 92 | echo $snapchat->updatePrivacy(Snapchat::PRIVACY_FRIENDS); 93 | ``` 94 | 95 | 96 | ## Usage 97 | 98 | 99 | Include src/snapchat.php via require_once or Composer or whatever, then: 100 | 101 | ```php 102 | getSnaps(); 109 | 110 | // Get your friends' stories: 111 | $snaps = $snapchat->getFriendStories(); 112 | 113 | // Download a specific snap: 114 | $data = $snapchat->getMedia('122FAST2FURIOUS334r'); 115 | file_put_contents('/home/dan/snap.jpg', $data); 116 | 117 | // Download a specific story: 118 | $data = $snapchat->getStory('[story_media_id]', '[story_key]', '[story_iv]'); 119 | 120 | // Download a specific story's thumbnail: 121 | $data = $snapchat->getStoryThumb('[story_media_id]', '[story_key]', '[thumbnail_iv]'); 122 | 123 | // Mark the snap as viewed: 124 | $snapchat->markSnapViewed('122FAST2FURIOUS334r'); 125 | 126 | // Mark the story as viewed: 127 | $snapchat->markStoryViewed('[story_id]'); 128 | 129 | // Screenshot! 130 | $snapchat->markSnapShot('122FAST2FURIOUS334r'); 131 | 132 | // Upload a snap and send it to me for 8 seconds: 133 | $id = $snapchat->upload( 134 | Snapchat::MEDIA_IMAGE, 135 | file_get_contents('/home/dan/whatever.jpg') 136 | ); 137 | $snapchat->send($id, array('stelljes'), 8); 138 | 139 | // Upload a video story: 140 | $id = $snapchat->upload( 141 | Snapchat::MEDIA_VIDEO, 142 | file_get_contents('/home/dan/whatever.mov') 143 | ); 144 | $snapchat->setStory($id, Snapchat::MEDIA_VIDEO); 145 | 146 | // Destroy the evidence: 147 | $snapchat->clearFeed(); 148 | 149 | // Find friends by phone number: 150 | $friends = $snapchat->findFriends(array('18006492568', '7183876962')); 151 | 152 | // Get a list of your friends: 153 | $friends = $snapchat->getFriends(); 154 | 155 | // Add some people as friends: 156 | $snapchat->addFriends(array('bill', 'bob')); 157 | 158 | // Add someone you forgot: 159 | $snapchat->addFriend('bart'); 160 | 161 | // Get a list of the people you've added: 162 | $added = $snapchat->getAddedFriends(); 163 | 164 | // Find out who Bill and Bob snap the most: 165 | $bests = $snapchat->getBests(array('bill', 'bob')); 166 | 167 | // Set Bart's display name: 168 | $snapchat->setDisplayName('bart', 'Barty'); 169 | 170 | // Block Bart: 171 | $snapchat->block('bart'); 172 | 173 | // Unblock Bart: 174 | $snapchat->unblock('bart'); 175 | 176 | // Delete Bart entirely: 177 | $snapchat->deleteFriend('bart'); 178 | 179 | // You only want your friends to be able to snap you: 180 | $snapchat->updatePrivacy(Snapchat::PRIVACY_FRIENDS); 181 | 182 | // You want to change your email: 183 | $snapchat->updateEmail('dan@example.com'); 184 | 185 | // Log out: 186 | $snapchat->logout(); 187 | 188 | ?> 189 | ``` 190 | 191 | 192 | 193 | ## License 194 | 195 | MIT 196 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dstelljes/php-snapchat", 3 | "description": "A PHP library for the Snapchat API", 4 | "keywords": ["snapchat"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Daniel Stelljes", 10 | "email": "iam@danielstelljes.net" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3.0", 15 | "ext-curl": "*", 16 | "ext-json": "*", 17 | "ext-mcrypt": "*" 18 | }, 19 | "autoload": { 20 | "classmap": ["src/"] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | ./tests/ 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/snapchat.php: -------------------------------------------------------------------------------- 1 | auth_token = FALSE; 69 | $this->username = FALSE; 70 | 71 | if (!empty($password)) { 72 | $this->login($username, $password); 73 | } 74 | elseif (!empty($auth_token)) { 75 | $this->auth_token = $auth_token; 76 | $this->cache = new SnapchatCache(); 77 | $this->username = $username; 78 | } 79 | } 80 | 81 | /** 82 | * Handles login. 83 | * 84 | * @param string $username 85 | * The username for the Snapchat account. 86 | * @param string $password 87 | * The password associated with the username. 88 | * 89 | * @return mixed 90 | * The data returned by the service or FALSE if the request failed. 91 | * Generally, returns the same result as self::getUpdates(). 92 | */ 93 | public function login($username, $password) { 94 | $timestamp = parent::timestamp(); 95 | $result = parent::post( 96 | '/login', 97 | array( 98 | 'username' => $username, 99 | 'password' => $password, 100 | 'timestamp' => $timestamp, 101 | ), 102 | array( 103 | parent::STATIC_TOKEN, 104 | $timestamp, 105 | ) 106 | ); 107 | 108 | // If the login is successful, set the username and auth_token. 109 | if (isset($result->logged) && $result->logged) { 110 | $this->auth_token = $result->auth_token; 111 | $this->username = $result->username; 112 | 113 | $this->cache = new SnapchatCache(); 114 | $this->cache->set('updates', $result); 115 | 116 | return $result; 117 | } 118 | else { 119 | return FALSE; 120 | } 121 | } 122 | 123 | /** 124 | * Logs out the current user. 125 | * 126 | * @return bool 127 | * TRUE if successful, FALSE otherwise. 128 | */ 129 | public function logout() { 130 | // Make sure we're logged in and have a valid access token. 131 | if (!$this->auth_token || !$this->username) { 132 | return FALSE; 133 | } 134 | 135 | $timestamp = parent::timestamp(); 136 | $result = parent::post( 137 | '/logout', 138 | array( 139 | 'timestamp' => $timestamp, 140 | 'username' => $this->username, 141 | ), 142 | array( 143 | $this->auth_token, 144 | $timestamp, 145 | ) 146 | ); 147 | 148 | // Clear out the cache in case the instance is recycled. 149 | $this->cache = NULL; 150 | 151 | return is_null($result); 152 | } 153 | 154 | /** 155 | * Creates a user account. 156 | * 157 | * @todo 158 | * Add better validation. 159 | * 160 | * @param string $username 161 | * The desired username. 162 | * @param string $password 163 | * The password to associate with the account. 164 | * @param string $email 165 | * The email address to associate with the account. 166 | * @param $birthday string 167 | * The user's birthday (yyyy-mm-dd). 168 | * 169 | * @return mixed 170 | * The data returned by the service or FALSE if registration failed. 171 | * Generally, returns the same result as calling self::getUpdates(). 172 | */ 173 | public function register($username, $password, $email, $birthday) { 174 | $timestamp = parent::timestamp(); 175 | $result = parent::post( 176 | '/register', 177 | array( 178 | 'birthday' => $birthday, 179 | 'password' => $password, 180 | 'email' => $email, 181 | 'timestamp' => $timestamp, 182 | ), 183 | array( 184 | parent::STATIC_TOKEN, 185 | $timestamp, 186 | ) 187 | ); 188 | 189 | if (!isset($result->token)) { 190 | return FALSE; 191 | } 192 | 193 | $timestamp = parent::timestamp(); 194 | $result = parent::post( 195 | '/registeru', 196 | array( 197 | 'email' => $email, 198 | 'username' => $username, 199 | 'timestamp' => $timestamp, 200 | ), 201 | array( 202 | parent::STATIC_TOKEN, 203 | $timestamp, 204 | ) 205 | ); 206 | 207 | // If registration is successful, set the username and auth_token. 208 | if (isset($result->logged) && $result->logged) { 209 | $this->auth_token = $result->auth_token; 210 | $this->username = $result->username; 211 | 212 | $this->cache = new SnapchatCache(); 213 | $this->cache->set('updates', $result); 214 | 215 | return $result; 216 | } 217 | else { 218 | return FALSE; 219 | } 220 | } 221 | 222 | /** 223 | * Retrieves general user, friend, and snap updates. 224 | * 225 | * @param bool $force 226 | * Forces an update even if there's fresh data in the cache. Defaults 227 | * to FALSE. 228 | * 229 | * @return mixed 230 | * The data returned by the service or FALSE on failure. 231 | */ 232 | public function getUpdates($force = FALSE) { 233 | if (!$force) { 234 | $result = $this->cache->get('updates'); 235 | if ($result) { 236 | return $result; 237 | } 238 | } 239 | 240 | // Make sure we're logged in and have a valid access token. 241 | if (!$this->auth_token || !$this->username) { 242 | return FALSE; 243 | } 244 | 245 | $timestamp = parent::timestamp(); 246 | $result = parent::post( 247 | '/all_updates', 248 | array( 249 | 'timestamp' => $timestamp, 250 | 'username' => $this->username, 251 | ), 252 | array( 253 | $this->auth_token, 254 | $timestamp, 255 | ) 256 | ); 257 | 258 | if (!empty($result->updates_response)) { 259 | $this->auth_token = $result->updates_response->auth_token; 260 | $this->cache->set('updates', $result->updates_response); 261 | return $result->updates_response; 262 | } 263 | 264 | return $result; 265 | } 266 | 267 | /** 268 | * Gets the user's snaps. 269 | * 270 | * @return mixed 271 | * An array of snaps or FALSE on failure. 272 | */ 273 | public function getSnaps() { 274 | $updates = $this->getUpdates(); 275 | 276 | if (empty($updates)) { 277 | return FALSE; 278 | } 279 | 280 | // We'll make these a little more readable. 281 | $snaps = array(); 282 | foreach ($updates->snaps as $snap) { 283 | $snaps[] = (object) array( 284 | 'id' => $snap->id, 285 | 'media_id' => empty($snap->c_id) ? FALSE : $snap->c_id, 286 | 'media_type' => $snap->m, 287 | 'time' => empty($snap->t) ? FALSE : $snap->t, 288 | 'sender' => empty($snap->sn) ? $this->username : $snap->sn, 289 | 'recipient' => empty($snap->rp) ? $this->username : $snap->rp, 290 | 'status' => $snap->st, 291 | 'screenshot_count' => empty($snap->c) ? 0 : $snap->c, 292 | 'sent' => $snap->sts, 293 | 'opened' => $snap->ts, 294 | 'broadcast' => empty($snap->broadcast) ? FALSE : (object) array( 295 | 'url' => $snap->broadcast_url, 296 | 'action_text' => $snap->broadcast_action_text, 297 | 'hide_timer' => $snap->broadcast_hide_timer, 298 | ), 299 | ); 300 | } 301 | 302 | return $snaps; 303 | } 304 | 305 | /** 306 | * Gets friends' stories. 307 | * 308 | * @param bool $force 309 | * Forces an update even if there's fresh data in the cache. Defaults 310 | * to FALSE. 311 | * 312 | * @return mixed 313 | * An array of stories or FALSE on failure. 314 | */ 315 | function getFriendStories($force = FALSE) { 316 | if (!$force) { 317 | $result = $this->cache->get('stories'); 318 | if ($result) { 319 | return $result; 320 | } 321 | } 322 | 323 | // Make sure we're logged in and have a valid access token. 324 | if (!$this->auth_token || !$this->username) { 325 | return FALSE; 326 | } 327 | 328 | $timestamp = parent::timestamp(); 329 | $result = parent::post( 330 | '/all_updates', 331 | array( 332 | 'timestamp' => $timestamp, 333 | 'username' => $this->username, 334 | ), 335 | array( 336 | $this->auth_token, 337 | $timestamp, 338 | ) 339 | ); 340 | 341 | if (!empty($result->stories_response)) { 342 | $this->cache->set('stories', $result->stories_response); 343 | } 344 | else { 345 | return FALSE; 346 | } 347 | 348 | $stories = array(); 349 | foreach ($result->stories_response->friend_stories as $group) { 350 | foreach ($group->stories as $story) { 351 | $stories[] = $story->story; 352 | } 353 | } 354 | 355 | return $stories; 356 | } 357 | 358 | /** 359 | * Queries the friend-finding service. 360 | * 361 | * @todo 362 | * If over 30 numbers are passed in, spread the query across multiple 363 | * requests. The API won't return more than 30 results at once. 364 | * 365 | * @param array $numbers 366 | * An array of phone numbers. 367 | * @param string $country 368 | * The country code. Defaults to US. 369 | * 370 | * @return mixed 371 | * An array of user objects or FALSE on failure. 372 | */ 373 | public function findFriends($numbers, $country = 'US') { 374 | $batches = array_chunk(array_flip($numbers), 30, TRUE); 375 | 376 | // Make sure we're logged in and have a valid access token. 377 | if (!$this->auth_token || !$this->username) { 378 | return FALSE; 379 | } 380 | 381 | $results = array(); 382 | foreach ($batches as $batch) { 383 | $timestamp = parent::timestamp(); 384 | $result = parent::post( 385 | '/find_friends', 386 | array( 387 | 'countryCode' => $country, 388 | 'numbers' => json_encode($batch), 389 | 'timestamp' => $timestamp, 390 | 'username' => $this->username, 391 | ), 392 | array( 393 | $this->auth_token, 394 | $timestamp, 395 | ) 396 | ); 397 | 398 | if (isset($result->results)) { 399 | $results = $results + $result->results; 400 | } 401 | } 402 | 403 | return $results; 404 | } 405 | 406 | /** 407 | * Gets the user's friends. 408 | * 409 | * @return mixed 410 | * An array of friends or FALSE on failure. 411 | */ 412 | public function getFriends() { 413 | $updates = $this->getUpdates(); 414 | 415 | if (empty($updates)) { 416 | return FALSE; 417 | } 418 | 419 | return $updates->friends; 420 | } 421 | 422 | /** 423 | * Gets the user's added friends. 424 | * 425 | * @return mixed 426 | * An array of friends or FALSE on failure. 427 | */ 428 | public function getAddedFriends() { 429 | $updates = $this->getUpdates(); 430 | 431 | if (empty($updates)) { 432 | return FALSE; 433 | } 434 | 435 | return $updates->added_friends; 436 | } 437 | 438 | /** 439 | * Adds a friend. 440 | * 441 | * @param string $username 442 | * The username of the friend to add. 443 | * 444 | * @return bool 445 | * TRUE if successful, FALSE otherwise. 446 | */ 447 | public function addFriend($username) { 448 | // Make sure we're logged in and have a valid access token. 449 | if (!$this->auth_token || !$this->username) { 450 | return FALSE; 451 | } 452 | 453 | $timestamp = parent::timestamp(); 454 | $result = parent::post( 455 | '/friend', 456 | array( 457 | 'action' => 'add', 458 | 'friend' => $username, 459 | 'timestamp' => $timestamp, 460 | 'username' => $this->username, 461 | ), 462 | array( 463 | $this->auth_token, 464 | $timestamp, 465 | ) 466 | ); 467 | 468 | // Sigh... 469 | if (strpos($result->message, 'Sorry! Couldn\'t find') === 0) { 470 | return FALSE; 471 | } 472 | 473 | return !empty($result->message); 474 | } 475 | 476 | /** 477 | * Adds multiple friends. 478 | * 479 | * @param array $usernames 480 | * Usernames of friends to add. 481 | * 482 | * @return bool 483 | * TRUE if successful, FALSE otherwise. 484 | */ 485 | public function addFriends($usernames) { 486 | // Make sure we're logged in and have a valid access token. 487 | if (!$this->auth_token || !$this->username) { 488 | return FALSE; 489 | } 490 | 491 | $friends = array(); 492 | foreach ($usernames as $username) { 493 | $friends[] = (object) array( 494 | 'display' => '', 495 | 'name' => $username, 496 | 'type' => self::FRIEND_UNCONFIRMED, 497 | ); 498 | } 499 | 500 | $timestamp = parent::timestamp(); 501 | $result = parent::post( 502 | '/friend', 503 | array( 504 | 'action' => 'multiadddelete', 505 | 'friend' => json_encode(array( 506 | 'friendsToAdd' => $friends, 507 | 'friendsToDelete' => array(), 508 | )), 509 | 'timestamp' => $timestamp, 510 | 'username' => $this->username, 511 | ), 512 | array( 513 | $this->auth_token, 514 | $timestamp, 515 | ) 516 | ); 517 | 518 | return !empty($result->message); 519 | } 520 | 521 | /** 522 | * Deletes a friend. 523 | * 524 | * @todo 525 | * Investigate deleting multiple friends at once. 526 | * 527 | * @param string $username 528 | * The username of the friend to delete. 529 | * 530 | * @return bool 531 | * TRUE if successful, FALSE otherwise. 532 | */ 533 | public function deleteFriend($username) { 534 | // Make sure we're logged in and have a valid access token. 535 | if (!$this->auth_token || !$this->username) { 536 | return FALSE; 537 | } 538 | 539 | $timestamp = parent::timestamp(); 540 | $result = parent::post( 541 | '/friend', 542 | array( 543 | 'action' => 'delete', 544 | 'friend' => $username, 545 | 'timestamp' => $timestamp, 546 | 'username' => $this->username, 547 | ), 548 | array( 549 | $this->auth_token, 550 | $timestamp, 551 | ) 552 | ); 553 | 554 | return !empty($result->message); 555 | } 556 | 557 | /** 558 | * Sets a friend's display name. 559 | * 560 | * @param string $username 561 | * The username of the user to modify. 562 | * @param string $display 563 | * The new display name. 564 | * 565 | * @return bool 566 | * TRUE if successful, FALSE otherwise. 567 | */ 568 | public function setDisplayName($username, $display) { 569 | // Make sure we're logged in and have a valid access token. 570 | if (!$this->auth_token || !$this->username) { 571 | return FALSE; 572 | } 573 | 574 | $timestamp = parent::timestamp(); 575 | $result = parent::post( 576 | '/friend', 577 | array( 578 | 'action' => 'display', 579 | 'display' => $display, 580 | 'friend' => $username, 581 | 'timestamp' => $timestamp, 582 | 'username' => $this->username, 583 | ), 584 | array( 585 | $this->auth_token, 586 | $timestamp, 587 | ) 588 | ); 589 | 590 | return !empty($result->message); 591 | } 592 | 593 | /** 594 | * Blocks a user. 595 | * 596 | * @param string $username 597 | * The username of the user to be blocked. 598 | * 599 | * @return bool 600 | * TRUE if successful, FALSE otherwise. 601 | */ 602 | public function block($username) { 603 | // Make sure we're logged in and have a valid access token. 604 | if (!$this->auth_token || !$this->username) { 605 | return FALSE; 606 | } 607 | 608 | $timestamp = parent::timestamp(); 609 | $result = parent::post( 610 | '/friend', 611 | array( 612 | 'action' => 'block', 613 | 'friend' => $username, 614 | 'timestamp' => $timestamp, 615 | 'username' => $this->username, 616 | ), 617 | array( 618 | $this->auth_token, 619 | $timestamp, 620 | ) 621 | ); 622 | 623 | return !empty($result->message); 624 | } 625 | 626 | /** 627 | * Unblocks a user. 628 | * 629 | * @param string $username 630 | * The username of the user to unblock. 631 | * 632 | * @return bool 633 | * TRUE if successful, FALSE otherwise. 634 | */ 635 | public function unblock($username) { 636 | // Make sure we're logged in and have a valid access token. 637 | if (!$this->auth_token || !$this->username) { 638 | return FALSE; 639 | } 640 | 641 | $timestamp = parent::timestamp(); 642 | $result = parent::post( 643 | '/friend', 644 | array( 645 | 'action' => 'unblock', 646 | 'friend' => $username, 647 | 'timestamp' => $timestamp, 648 | 'username' => $this->username, 649 | ), 650 | array( 651 | $this->auth_token, 652 | $timestamp, 653 | ) 654 | ); 655 | 656 | return !empty($result->message); 657 | } 658 | 659 | 660 | /** 661 | * Downloads a snap. 662 | * 663 | * @param string $id 664 | * The snap ID. 665 | * 666 | * @return mixed 667 | * The snap data or FALSE on failure. 668 | */ 669 | public function getMedia($id) { 670 | // Make sure we're logged in and have a valid access token. 671 | if (!$this->auth_token || !$this->username) { 672 | return FALSE; 673 | } 674 | 675 | $timestamp = parent::timestamp(); 676 | $result = parent::post( 677 | '/blob', 678 | array( 679 | 'id' => $id, 680 | 'timestamp' => $timestamp, 681 | 'username' => $this->username, 682 | ), 683 | array( 684 | $this->auth_token, 685 | $timestamp, 686 | ) 687 | ); 688 | 689 | if (parent::isMedia(substr($result, 0, 2))) { 690 | return $result; 691 | } 692 | else { 693 | $result = parent::decryptECB($result); 694 | 695 | if (parent::isMedia(substr($result, 0, 2))) { 696 | return $result; 697 | } 698 | } 699 | 700 | return FALSE; 701 | } 702 | 703 | /** 704 | * Sends event information to Snapchat. 705 | * 706 | * @param array $events 707 | * An array of events. This seems to be used only to report usage data. 708 | * @param array $snap_info 709 | * Data to send along in addition to the event array. This is used to 710 | * mark snaps as viewed. Defaults to an empty array. 711 | * 712 | * @return bool 713 | * TRUE if successful, FALSE otherwise. 714 | */ 715 | public function sendEvents($events, $snap_info = array()) { 716 | // Make sure we're logged in and have a valid access token. 717 | if (!$this->auth_token || !$this->username) { 718 | return FALSE; 719 | } 720 | 721 | $timestamp = parent::timestamp(); 722 | $result = parent::post( 723 | '/update_snaps', 724 | array( 725 | 'events' => json_encode($events), 726 | 'json' => json_encode($snap_info), 727 | 'timestamp' => $timestamp, 728 | 'username' => $this->username, 729 | ), 730 | array( 731 | $this->auth_token, 732 | $timestamp, 733 | ) 734 | ); 735 | 736 | return is_null($result); 737 | } 738 | 739 | /** 740 | * Marks a snap as viewed. 741 | * 742 | * Snaps can be downloaded an (apparently) unlimited amount of times before 743 | * they are viewed. Once marked as viewed, they are deleted. 744 | * 745 | * It's worth noting that it seems possible to mark others' snaps as viewed 746 | * as long as you know the ID. This hasn't been tested thoroughly, but it 747 | * could be useful if you send a snap that you immediately regret. 748 | * 749 | * @param string $id 750 | * The snap to mark as viewed. 751 | * @param int $time 752 | * The amount of time (in seconds) the snap was viewed. Defaults to 1. 753 | * 754 | * @return bool 755 | * TRUE if successful, FALSE otherwise. 756 | */ 757 | public function markSnapViewed($id, $time = 1) { 758 | $snap_info = array( 759 | $id => array( 760 | // Here Snapchat saw fit to use time as a float instead of 761 | // straight milliseconds. 762 | 't' => microtime(TRUE), 763 | // We add a small variation here just to make it look more 764 | // realistic. 765 | 'sv' => $time + (mt_rand() / mt_getrandmax() / 10), 766 | ), 767 | ); 768 | 769 | $events = array( 770 | array( 771 | 'eventName' => 'SNAP_VIEW', 772 | 'params' => array( 773 | 'id' => $id, 774 | // There are others, but it wouldn't be worth the effort to 775 | // put them in here since they likely don't matter. 776 | ), 777 | 'ts' => time() - $time, 778 | ), 779 | array( 780 | 'eventName' => 'SNAP_EXPIRED', 781 | 'params' => array( 782 | 'id' => $id, 783 | ), 784 | 'ts' => time() 785 | ), 786 | ); 787 | 788 | return $this->sendEvents($events, $snap_info); 789 | } 790 | 791 | /** 792 | * Sends a screenshot event. 793 | * 794 | * @param string $id 795 | * The snap to mark as shot. 796 | * @param int $time 797 | * The amount of time (in seconds) the snap was viewed. Defaults to 1. 798 | * 799 | * @return bool 800 | * TRUE if successful, FALSE otherwise. 801 | */ 802 | public function markSnapShot($id, $time = 1) { 803 | $snap_info = array( 804 | $id => array( 805 | // We use the same time values as in markSnapViewed, but add in the 806 | // screenshot status. 807 | 't' => microtime(TRUE), 808 | 'sv' => $time + (mt_rand() / mt_getrandmax() / 10), 809 | 'c' => self::STATUS_SCREENSHOT, 810 | ), 811 | ); 812 | 813 | $events = array( 814 | array( 815 | 'eventName' => 'SNAP_SCREENSHOT', 816 | 'params' => array( 817 | 'id' => $id, 818 | ), 819 | 'ts' => time() - $time, 820 | ), 821 | ); 822 | 823 | return $this->sendEvents($events, $snap_info); 824 | } 825 | 826 | /** 827 | * Uploads a snap. 828 | * 829 | * @todo 830 | * Fix media ID generation; it looks like they're GUIDs now. 831 | * 832 | * @param int $type 833 | * The media type, i.e. MEDIA_IMAGE or MEDIA_VIDEO. 834 | * @param data $data 835 | * The file data to upload. 836 | * 837 | * @return mixed 838 | * The ID of the uploaded media or FALSE on failure. 839 | */ 840 | public function upload($type, $data) { 841 | // Make sure we're logged in and have a valid access token. 842 | if (!$this->auth_token || !$this->username) { 843 | return FALSE; 844 | } 845 | 846 | // To make cURL happy, we write the data to a file first. 847 | $temp = tempnam(sys_get_temp_dir(), 'Snap'); 848 | file_put_contents($temp, parent::encryptECB($data)); 849 | 850 | if (version_compare(PHP_VERSION, '5.5.0', '>=')) { 851 | $cfile = curl_file_create($temp, ($type == self::MEDIA_IMAGE ? 'image/jpeg' : 'video/quicktime'), 'snap'); 852 | } 853 | 854 | $media_id = strtoupper($this->username) . '~' . time(); 855 | $timestamp = parent::timestamp(); 856 | $result = parent::post( 857 | '/upload', 858 | array( 859 | 'media_id' => $media_id, 860 | 'type' => $type, 861 | 'data' => (version_compare(PHP_VERSION, '5.5.0', '>=') ? $cfile : '@' . $temp . ';filename=data'), 862 | 'timestamp' => $timestamp, 863 | 'username' => $this->username, 864 | ), 865 | array( 866 | $this->auth_token, 867 | $timestamp, 868 | ), 869 | TRUE 870 | ); 871 | 872 | unlink($temp); 873 | 874 | return is_null($result) ? $media_id : FALSE; 875 | } 876 | 877 | /** 878 | * Sends a snap. 879 | * 880 | * @param string $media_id 881 | * The media ID of the snap to send. 882 | * @param array $recipients 883 | * An array of recipient usernames. 884 | * @param int $time 885 | * The time in seconds the snap should be available (1-10). Defaults to 3. 886 | * 887 | * @return bool 888 | * TRUE if successful, FALSE otherwise. 889 | */ 890 | public function send($media_id, $recipients, $time = 3) { 891 | // Make sure we're logged in and have a valid access token. 892 | if (!$this->auth_token || !$this->username) { 893 | return FALSE; 894 | } 895 | 896 | $timestamp = parent::timestamp(); 897 | $result = parent::post( 898 | '/send', 899 | array( 900 | 'media_id' => $media_id, 901 | 'recipient' => implode(',', $recipients), 902 | 'time' => $time, 903 | 'timestamp' => $timestamp, 904 | 'username' => $this->username, 905 | ), 906 | array( 907 | $this->auth_token, 908 | $timestamp, 909 | ) 910 | ); 911 | 912 | return is_null($result); 913 | } 914 | 915 | /** 916 | * Sets a story. 917 | * 918 | * @param string $media_id 919 | * The media ID of the story to set. 920 | * @param int $media_type 921 | * The media type of the story to set (i.e. MEDIA_IMAGE or MEDIA_VIDEO). 922 | * @param int $time 923 | * The time in seconds the story should be available (1-10). Defaults to 3. 924 | * 925 | * @return bool 926 | * TRUE if successful, FALSE otherwise. 927 | */ 928 | public function setStory($media_id, $media_type, $time = 3) { 929 | // Make sure we're logged in and have a valid access token. 930 | if (!$this->auth_token || !$this->username) { 931 | return FALSE; 932 | } 933 | 934 | $timestamp = parent::timestamp(); 935 | $result = parent::post( 936 | '/post_story', 937 | array( 938 | 'client_id' => $media_id, 939 | 'media_id' => $media_id, 940 | 'time' => $time, 941 | 'timestamp' => $timestamp, 942 | 'type' => $media_type, 943 | 'username' => $this->username, 944 | ), 945 | array( 946 | $this->auth_token, 947 | $timestamp, 948 | ) 949 | ); 950 | 951 | return is_null($result); 952 | } 953 | 954 | /** 955 | * Downloads a story. 956 | * 957 | * @param string $media_id 958 | * The media ID of the story. 959 | * @param string $key 960 | * The base64-encoded key of the story. 961 | * @param string $iv 962 | * The base64-encoded IV of the story. 963 | * 964 | * @return mixed 965 | * The story data or FALSE on failure. 966 | */ 967 | public function getStory($media_id, $key, $iv) { 968 | // Make sure we're logged in and have a valid access token. 969 | if (!$this->auth_token || !$this->username) { 970 | return FALSE; 971 | } 972 | 973 | // Retrieve encrypted story and decrypt. 974 | $blob = parent::get('/story_blob?story_id=' . $media_id); 975 | 976 | if (!empty($blob)) { 977 | return parent::decryptCBC($blob, $key, $iv); 978 | } 979 | 980 | return FALSE; 981 | } 982 | 983 | /** 984 | * Downloads a story's thumbnail. 985 | * 986 | * @param string $media_id 987 | * The media_id of the story. 988 | * @param string $key 989 | * The base64-encoded key of the story. 990 | * @param string $iv 991 | * The base64-encoded IV of the thumbnail. 992 | * 993 | * @return mixed 994 | * The thumbnail data or FALSE on failure. 995 | */ 996 | public function getStoryThumb($media_id, $key, $iv) { 997 | // Make sure we're logged in and have a valid access token. 998 | if (!$this->auth_token || !$this->username) { 999 | return FALSE; 1000 | } 1001 | 1002 | // Retrieve encrypted story and decrypt. 1003 | $blob = parent::get('/story_thumbnail?story_id=' . $media_id); 1004 | 1005 | if (!empty($blob)) { 1006 | return parent::decryptCBC($blob, $key, $iv); 1007 | } 1008 | 1009 | return FALSE; 1010 | } 1011 | 1012 | /** 1013 | * Marks a story as viewed. 1014 | * 1015 | * @param string $id 1016 | * The ID of the story. 1017 | * @param int $screenshot_count 1018 | * Amount of times screenshotted. Defaults to 0. 1019 | * 1020 | * @return bool 1021 | * TRUE if successful, FALSE otherwise. 1022 | */ 1023 | public function markStoryViewed($id, $screenshot_count = 0) { 1024 | // Make sure we're logged in and have a valid access token. 1025 | if (!$this->auth_token || !$this->username) { 1026 | return FALSE; 1027 | } 1028 | 1029 | // Mark story as viewed. 1030 | $timestamp = parent::timestamp(); 1031 | $result = parent::post( 1032 | '/update_stories', 1033 | array( 1034 | 'friend_stories' => json_encode(array( 1035 | array( 1036 | 'id' => $id, 1037 | 'screenshot_count' => $screenshot_count, 1038 | 'timestamp' => $timestamp, 1039 | ), 1040 | )), 1041 | 'timestamp' => $timestamp, 1042 | 'username' => $this->username, 1043 | ), 1044 | array( 1045 | $this->auth_token, 1046 | $timestamp, 1047 | ) 1048 | ); 1049 | 1050 | return is_null($result); 1051 | } 1052 | 1053 | /** 1054 | * Gets the best friends and scores of the specified users. 1055 | * 1056 | * @param array $friends 1057 | * An array of usernames for which to retrieve best friend information. 1058 | * 1059 | * @return mixed 1060 | * An dictionary of friends by username or FALSE on failure. 1061 | */ 1062 | public function getBests($friends) { 1063 | // Make sure we're logged in and have a valid access token. 1064 | if (!$this->auth_token || !$this->username) { 1065 | return FALSE; 1066 | } 1067 | 1068 | $timestamp = parent::timestamp(); 1069 | $result = parent::post( 1070 | '/bests', 1071 | array( 1072 | 'friend_usernames' => json_encode($friends), 1073 | 'timestamp' => $timestamp, 1074 | 'username' => $this->username, 1075 | ), 1076 | array( 1077 | $this->auth_token, 1078 | $timestamp, 1079 | ) 1080 | ); 1081 | 1082 | if (empty($result)) { 1083 | return FALSE; 1084 | } 1085 | 1086 | $friends = array(); 1087 | foreach((array) $result as $friend => $bests) { 1088 | $friends[$friend] = (array) $bests; 1089 | } 1090 | 1091 | return $friends; 1092 | } 1093 | 1094 | /** 1095 | * Clears the current user's feed. 1096 | * 1097 | * @return bool 1098 | * TRUE if successful, FALSE otherwise. 1099 | */ 1100 | public function clearFeed() { 1101 | // Make sure we're logged in and have a valid access token. 1102 | if (!$this->auth_token || !$this->username) { 1103 | return FALSE; 1104 | } 1105 | 1106 | $timestamp = parent::timestamp(); 1107 | $result = parent::post( 1108 | '/clear', 1109 | array( 1110 | 'timestamp' => $timestamp, 1111 | 'username' => $this->username, 1112 | ), 1113 | array( 1114 | $this->auth_token, 1115 | $timestamp, 1116 | ) 1117 | ); 1118 | 1119 | return is_null($result); 1120 | } 1121 | 1122 | /** 1123 | * Updates the current user's privacy setting. 1124 | * 1125 | * @param int $setting 1126 | * The privacy setting, i.e. PRIVACY_EVERYONE or PRIVACY_FRIENDS. 1127 | * 1128 | * @return bool 1129 | * TRUE if successful, FALSE otherwise. 1130 | */ 1131 | public function updatePrivacy($setting) { 1132 | // Make sure we're logged in and have a valid access token. 1133 | if (!$this->auth_token || !$this->username) { 1134 | return FALSE; 1135 | } 1136 | 1137 | $timestamp = parent::timestamp(); 1138 | $result = parent::post( 1139 | '/settings', 1140 | array( 1141 | 'action' => 'updatePrivacy', 1142 | 'privacySetting' => $setting, 1143 | 'timestamp' => $timestamp, 1144 | 'username' => $this->username, 1145 | ), 1146 | array( 1147 | $this->auth_token, 1148 | $timestamp, 1149 | ) 1150 | ); 1151 | 1152 | return isset($result->param) && $result->param == $setting; 1153 | } 1154 | 1155 | /** 1156 | * Updates the current user's email address. 1157 | * 1158 | * @param string $email 1159 | * The new email address. 1160 | * 1161 | * @return bool 1162 | * TRUE if successful, FALSE otherwise. 1163 | */ 1164 | public function updateEmail($email) { 1165 | // Make sure we're logged in and have a valid access token. 1166 | if (!$this->auth_token || !$this->username) { 1167 | return FALSE; 1168 | } 1169 | 1170 | $timestamp = parent::timestamp(); 1171 | $result = parent::post( 1172 | '/settings', 1173 | array( 1174 | 'action' => 'updateEmail', 1175 | 'email' => $email, 1176 | 'timestamp' => $timestamp, 1177 | 'username' => $this->username, 1178 | ), 1179 | array( 1180 | $this->auth_token, 1181 | $timestamp, 1182 | ) 1183 | ); 1184 | 1185 | return isset($result->param) && $result->param == $email; 1186 | } 1187 | 1188 | } 1189 | -------------------------------------------------------------------------------- /src/snapchat_agent.php: -------------------------------------------------------------------------------- 1 | 5, 54 | CURLOPT_RETURNTRANSFER => TRUE, 55 | CURLOPT_TIMEOUT => 10, 56 | CURLOPT_USERAGENT => 'Snapchat/6.1.2 (iPhone; iOS 7.0.4; gzip)', 57 | ); 58 | 59 | /** 60 | * Returns the current timestamp. 61 | * 62 | * @return int 63 | * The current timestamp, expressed in milliseconds since epoch. 64 | */ 65 | public function timestamp() { 66 | return intval(microtime(TRUE) * 1000); 67 | } 68 | 69 | /** 70 | * Pads data using PKCS5. 71 | * 72 | * @param data $data 73 | * The data to be padded. 74 | * @param int $blocksize 75 | * The block size to pad to. Defaults to 16. 76 | * 77 | * @return data 78 | * The padded data. 79 | */ 80 | public function pad($data, $blocksize = 16) { 81 | $pad = $blocksize - (strlen($data) % $blocksize); 82 | return $data . str_repeat(chr($pad), $pad); 83 | } 84 | 85 | /** 86 | * Decrypts blob data for standard images and videos. 87 | * 88 | * @param data $data 89 | * The data to decrypt. 90 | * 91 | * @return data 92 | * The decrypted data. 93 | */ 94 | public function decryptECB($data) { 95 | return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, self::BLOB_ENCRYPTION_KEY, self::pad($data), MCRYPT_MODE_ECB); 96 | } 97 | 98 | /** 99 | * Encrypts blob data for standard images and videos. 100 | * 101 | * @param data $data 102 | * The data to encrypt. 103 | * 104 | * @return data 105 | * The encrypted data. 106 | */ 107 | public function encryptECB($data) { 108 | return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, self::BLOB_ENCRYPTION_KEY, self::pad($data), MCRYPT_MODE_ECB); 109 | } 110 | 111 | /** 112 | * Decrypts blob data for stories. 113 | * 114 | * @param data $data 115 | * The data to decrypt. 116 | * @param string $key 117 | * The base64-encoded key. 118 | * @param string $iv 119 | * $iv The base64-encoded IV. 120 | * 121 | * @return data 122 | * The decrypted data. 123 | */ 124 | public function decryptCBC($data, $key, $iv) { 125 | // Decode the key and IV. 126 | $iv = base64_decode($iv); 127 | $key = base64_decode($key); 128 | 129 | // Decrypt the data. 130 | $data = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); 131 | $padding = ord($data[strlen($data) - 1]); 132 | 133 | return substr($data, 0, -$padding); 134 | } 135 | 136 | /** 137 | * Implementation of Snapchat's hashing algorithm. 138 | * 139 | * @param string $first 140 | * The first value to use in the hash. 141 | * @param string $second 142 | * The second value to use in the hash. 143 | * 144 | * @return string 145 | * The generated hash. 146 | */ 147 | public function hash($first, $second) { 148 | // Append the secret to the values. 149 | $first = self::SECRET . $first; 150 | $second = $second . self::SECRET; 151 | 152 | // Hash the values. 153 | $hash = hash_init('sha256'); 154 | hash_update($hash, $first); 155 | $hash1 = hash_final($hash); 156 | $hash = hash_init('sha256'); 157 | hash_update($hash, $second); 158 | $hash2 = hash_final($hash); 159 | 160 | // Create a new hash with pieces of the two we just made. 161 | $result = ''; 162 | for ($i = 0; $i < strlen(self::HASH_PATTERN); $i++) { 163 | $result .= substr(self::HASH_PATTERN, $i, 1) ? $hash2[$i] : $hash1[$i]; 164 | } 165 | 166 | return $result; 167 | } 168 | 169 | /** 170 | * Checks to see if a blob looks like a media file. 171 | * 172 | * @param data $data 173 | * The blob data (or just the header). 174 | * 175 | * @return bool 176 | * TRUE if the blob looks like a media file, FALSE otherwise. 177 | */ 178 | function isMedia($data) { 179 | // Check for a JPG header. 180 | if ($data[0] == chr(0xFF) && $data[1] == chr(0xD8)) { 181 | return TRUE; 182 | } 183 | 184 | // Check for a PNG header. 185 | if ($data[0] == chr(0x89) && $data[1] == chr(0x50)) { 186 | return TRUE; 187 | } 188 | 189 | // Check for a GIF header. 190 | if ($data[0] == chr(0x47) && $data[1] == chr(0x49)) { 191 | return TRUE; 192 | } 193 | 194 | // Check for a BMP header. 195 | if ($data[0] == chr(0x42) && $data[1] == chr(0x4D)) { 196 | return TRUE; 197 | } 198 | 199 | // Check for a MP4 header. 200 | if ($data[0] == chr(0x00) && $data[1] == chr(0x00)) { 201 | return TRUE; 202 | } 203 | 204 | return FALSE; 205 | } 206 | 207 | /** 208 | * Performs a GET request. Currently only used for story blobs. 209 | * 210 | * @todo 211 | * cURL-ify this and maybe combine with the post function. 212 | * 213 | * @param string $endpoint 214 | * The address of the resource being requested (e.g. '/story_blob' or 215 | * '/story_thumbnail'). 216 | * 217 | * @return data 218 | * The retrieved data. 219 | */ 220 | public function get($endpoint) { 221 | return file_get_contents(self::URL . $endpoint); 222 | } 223 | 224 | /** 225 | * Performs a POST request. Used for pretty much everything. 226 | * 227 | * @todo 228 | * Replace the blob endpoint check with a more robust check for 229 | * application/octet-stream. 230 | * 231 | * @param string $endpoint 232 | * The address of the resource being requested (e.g. '/update_snaps' or 233 | * '/friend'). 234 | * @param array $data 235 | * An dictionary of values to send to the API. A request token is added 236 | * automatically. 237 | * @param array $params 238 | * An array containing the parameters used to generate the request token. 239 | * @param bool $multipart 240 | * If TRUE, sends the request as multipart/form-data. Defaults to FALSE. 241 | * 242 | * @return mixed 243 | * The data returned from the API (decoded if JSON). Returns FALSE if 244 | * the request failed. 245 | */ 246 | public function post($endpoint, $data, $params, $multipart = FALSE) { 247 | $ch = curl_init(); 248 | 249 | $data['req_token'] = self::hash($params[0], $params[1]); 250 | $data['version'] = self::VERSION; 251 | 252 | if (!$multipart) { 253 | $data = http_build_query($data); 254 | } 255 | 256 | $options = self::$CURL_OPTIONS + array( 257 | CURLOPT_POST => TRUE, 258 | CURLOPT_POSTFIELDS => $data, 259 | CURLOPT_URL => self::URL . $endpoint, 260 | ); 261 | curl_setopt_array($ch, $options); 262 | 263 | $result = curl_exec($ch); 264 | 265 | // If cURL doesn't have a bundle of root certificates handy, we provide 266 | // ours (see http://curl.haxx.se/docs/sslcerts.html). 267 | if (curl_errno($ch) == 60) { 268 | curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/ca_bundle.crt'); 269 | $result = curl_exec($ch); 270 | } 271 | 272 | // If the cURL request fails, return FALSE. Also check the status code 273 | // since the API generally won't return friendly errors. 274 | if ($result === FALSE || curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200) { 275 | curl_close($ch); 276 | return FALSE; 277 | } 278 | 279 | curl_close($ch); 280 | 281 | if ($endpoint == '/blob') { 282 | return $result; 283 | } 284 | 285 | // Add support for foreign characters in the JSON response. 286 | $result = iconv('UTF-8', 'UTF-8//IGNORE', utf8_encode($result)); 287 | 288 | $data = json_decode($result); 289 | return json_last_error() == JSON_ERROR_NONE ? $data : FALSE; 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /src/snapchat_cache.php: -------------------------------------------------------------------------------- 1 | _cache[$key])) { 33 | return FALSE; 34 | } 35 | 36 | // Second, check its freshness. 37 | if ($this->_cache[$key]['time'] < time() - self::$_lifespan) { 38 | return FALSE; 39 | } 40 | 41 | return $this->_cache[$key]['data']; 42 | } 43 | 44 | /** 45 | * Adds a result to the cache. 46 | * 47 | * @param string $key 48 | * The key of the result to store. 49 | * @param mixed $data 50 | * The data to store. 51 | */ 52 | public function set($key, $data) { 53 | $this->_cache[$key] = array( 54 | 'time' => time(), 55 | 'data' => $data, 56 | ); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /tests/media/movie.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KartikTalwar/php-snapchat/e85597803efff8228655d6b6e2f435df8ff4e120/tests/media/movie.mov -------------------------------------------------------------------------------- /tests/media/picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KartikTalwar/php-snapchat/e85597803efff8228655d6b6e2f435df8ff4e120/tests/media/picture.jpg -------------------------------------------------------------------------------- /tests/phpSnapchatTest.php: -------------------------------------------------------------------------------- 1 | users = array( 14 | 1 => array( 15 | 'name' => 'u1php5' . PHP_MINOR_VERSION, 16 | 'pass' => '123456789', 17 | ), 18 | 2 => array( 19 | 'name'=> 'u2php5' . PHP_MINOR_VERSION, 20 | 'pass' => '123456789', 21 | ), 22 | 3 => array( 23 | 'name'=> 'u3php5' . PHP_MINOR_VERSION, 24 | 'pass' => '123456789', 25 | ), 26 | 4 => array( 27 | 'name'=> 'u4php5' . PHP_MINOR_VERSION, 28 | 'pass' => '123456789', 29 | ), 30 | 5 => array( 31 | 'name'=> 'u5php5' . PHP_MINOR_VERSION, 32 | 'pass' => '123456789', 33 | ), 34 | ); 35 | } 36 | 37 | /** 38 | * Run after each test is completed. 39 | */ 40 | public function tearDown() { 41 | 42 | } 43 | 44 | public function testWrongLoginUser1() { 45 | $snapchat = new Snapchat($this->users[1]['name'], 'fail'); 46 | $this->assertFalse($snapchat->auth_token); 47 | } 48 | 49 | // public function testLoginUser1() { 50 | // $snapchat = new Snapchat($this->users[1]['name'], $this->users[1]['pass']); 51 | // $this->assertNotEquals($snapchat->auth_token, FALSE); 52 | // } 53 | 54 | public function testWrongLoginUser2() { 55 | $snapchat = new Snapchat($this->users[2]['name'], 'fail'); 56 | $this->assertFalse($snapchat->auth_token); 57 | } 58 | 59 | // public function testLoginUser2() { 60 | // $snapchat = new Snapchat($this->users[2]['name'], $this->users[2]['pass']); 61 | // $this->assertNotEquals($snapchat->auth_token, FALSE); 62 | // } 63 | 64 | public function testSendAndReceivePicture() { 65 | $this->_sendAndReceive(implode(DIRECTORY_SEPARATOR, array(__DIR__, 'media', 'picture.jpg')), Snapchat::MEDIA_IMAGE); 66 | } 67 | 68 | public function testSendAndReceiveMovie() { 69 | $this->_sendAndReceive(implode(DIRECTORY_SEPARATOR, array(__DIR__, 'media', 'movie.mov')), Snapchat::MEDIA_VIDEO); 70 | } 71 | 72 | private function _sendAndReceive($file, $type) { 73 | $snapchat = new Snapchat($this->users[1]['name'], $this->users[1]['pass']); 74 | $this->assertNotEquals($snapchat->auth_token, FALSE, 'Login failed for test user 1.'); 75 | 76 | $id = $snapchat->upload($type, file_get_contents($file)); 77 | $this->assertEquals(is_string($id), TRUE, ($type == Snapchat::MEDIA_IMAGE ? 'Image' : 'Video') . ' upload failed.'); 78 | 79 | $result = $snapchat->send($id, array($this->users[2]['name'])); 80 | $this->assertEquals($result, TRUE, 'Media send failed.'); // TODO 81 | 82 | $this->assertEquals($snapchat->clearFeed(), TRUE, 'Failed to clear the feed of test user 1.'); 83 | $this->assertEquals($snapchat->logout(), TRUE, 'Logout failed for test user 1.'); 84 | 85 | $snapchat = new Snapchat($this->users[2]['name'], $this->users[2]['pass']); 86 | $this->assertNotEquals($snapchat->auth_token, FALSE, 'Login failed for test user 2.'); 87 | 88 | $snaps = $snapchat->getSnaps(); 89 | $this->assertNotEquals($snaps, FALSE, 'Failed to get snap list.'); 90 | 91 | foreach($snaps as $snap) { 92 | if ($snap->status == Snapchat::STATUS_DELIVERED && strcmp($snap->recipient, $snapchat->username) == 0) { 93 | $data = $snapchat->getMedia($snap->id); 94 | $this->assertEquals($snapchat->markSnapViewed($snap->id), TRUE, 'User 2 marked snap viewed.'); 95 | $this->assertEquals($snapchat->markSnapShot($snap->id), TRUE, 'User 2 marked screenshot.'); 96 | $this->assertEquals(is_string($data), TRUE); 97 | } 98 | } 99 | 100 | $this->assertEquals($snapchat->clearFeed(), TRUE, 'Failed to clear the feed of test user 2.'); 101 | $this->assertEquals($snapchat->logout(), TRUE, 'Logout failed for test user 2.'); 102 | } 103 | 104 | public function testWrongMedia() { 105 | $snapchat = new Snapchat($this->users[1]['name'], $this->users[1]['pass']); 106 | $this->assertNotEquals($snapchat->auth_token, FALSE, 'Login failed for test user 1.'); 107 | $data = $snapchat->getMedia('12345'); 108 | $this->assertEquals($data, FALSE); 109 | } 110 | 111 | public function testManageFriends() { 112 | $snapchat = new Snapchat($this->users[1]['name'], $this->users[1]['pass']); 113 | $this->assertNotEquals($snapchat->auth_token, FALSE, 'Login failed for test user 1.'); 114 | 115 | $this->assertEquals($snapchat->addFriend(PHPSnapchatTest::STRANGE_USERNAME), FALSE, 'User 1 added a strange username.'); 116 | $this->assertEquals($snapchat->deleteFriend($this->users[3]['name']), TRUE, 'User 1 deleted an unknown friend.'); 117 | 118 | $this->assertEquals($snapchat->addFriend($this->users[3]['name']), TRUE, 'User 1 added user 3 as friend.'); 119 | $this->assertEquals($snapchat->deleteFriend($this->users[3]['name']), TRUE, 'User 1 removed user 3 from friends.'); 120 | $this->assertEquals($snapchat->addFriends(array($this->users[4]['name'], $this->users[5]['name'])), TRUE, 'User 1 added multiple friends.'); 121 | 122 | $this->assertEquals($snapchat->deleteFriend($this->users[4]['name']), TRUE, 'User 1 removed user 4 from friends.'); 123 | $this->assertEquals($snapchat->deleteFriend($this->users[5]['name']), TRUE, 'User 1 removed user 5 from friends.'); 124 | 125 | $friends = $snapchat->getFriends(); 126 | $this->assertEquals(count($friends) > 0, TRUE); 127 | $friends = $snapchat->getAddedFriends(); 128 | $this->assertEquals(count($friends) > 0, TRUE); 129 | $bestFriends = $snapchat->getBests(array($this->users[2]['name'])); 130 | $this->assertEquals(is_int($bestFriends[$this->users[2]['name']]['score']), TRUE); 131 | } 132 | 133 | public function testManageUserSettings() { 134 | $snapchat = new Snapchat($this->users[1]['name'], $this->users[1]['pass']); 135 | $this->assertNotEquals($snapchat->auth_token, FALSE, 'Login failed for test user 1.'); 136 | 137 | $this->assertEquals($snapchat->block($this->users[5]['name']), TRUE, 'User 1 blocked user 5.'); 138 | $this->assertEquals($snapchat->unblock($this->users[5]['name']), TRUE, 'User 1 unblocked user 5.'); 139 | 140 | $this->assertEquals($snapchat->updatePrivacy(Snapchat::PRIVACY_EVERYONE), TRUE, 'User 1 accepts snaps from everyone.'); 141 | $this->assertEquals($snapchat->updatePrivacy(Snapchat::PRIVACY_FRIENDS), TRUE, 'User 1 accepts snaps only from friends.'); 142 | 143 | $this->assertEquals($snapchat->updateEmail($this->users[1]['name'] . '@php-snapchat.tld'), FALSE, 'User 1 attempted to change his email to an invalid address.'); 144 | $this->assertEquals($snapchat->updateEmail($this->users[1]['name'] . '@php-snapchat.org'), TRUE, 'User 1 changed his email.'); 145 | 146 | $this->assertEquals($snapchat->setDisplayName($this->users[2]['name'], $this->users[2]['name']), TRUE, 'User 1 set user 2\'s display name.'); 147 | } 148 | } 149 | --------------------------------------------------------------------------------