From a5b998256e035212dc8a382f104377409d0f08e3 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Mon, 16 Apr 2018 19:32:18 +0200 Subject: [PATCH 01/13] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1feaa78..ccdb860 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ composer require pgrimaud/instagram-user-feed 2. The first part of the access token is your User Id -``` +```php $api = new Api(); $api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); From 59bf96259001e6b641dc2e092ccea4df54ceed3f Mon Sep 17 00:00:00 2001 From: Tim Bond Date: Mon, 16 Apr 2018 16:15:58 -0700 Subject: [PATCH 02/13] Use screen scraping to get media --- .gitignore | 1 + README.md | 45 ++----- composer.json | 4 + src/Instagram/Api.php | 77 ++---------- src/Instagram/Hydrator.php | 85 ++++++------- src/Instagram/Hydrator/Feed.php | 54 +------- src/Instagram/Transport/HTMLPage.php | 51 ++++++++ src/Instagram/Transport/JsonFeed.php | 88 ------------- tests/ApiTest.php | 139 +++++---------------- tests/fixtures/medias_feed.json | 2 - tests/fixtures/pgrimaud.html | 180 +++++++++++++++++++++++++++ tests/fixtures/user_feed.json | 1 - 12 files changed, 326 insertions(+), 401 deletions(-) create mode 100644 src/Instagram/Transport/HTMLPage.php delete mode 100644 src/Instagram/Transport/JsonFeed.php delete mode 100644 tests/fixtures/medias_feed.json create mode 100644 tests/fixtures/pgrimaud.html delete mode 100644 tests/fixtures/user_feed.json diff --git a/.gitignore b/.gitignore index be4b633..0670882 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea vendor build .php_cs.cache diff --git a/README.md b/README.md index 1feaa78..23e4e61 100644 --- a/README.md +++ b/README.md @@ -12,22 +12,11 @@ composer require pgrimaud/instagram-user-feed ``` -1. Visit [http://instagram.pixelunion.net/](http://instagram.pixelunion.net/) and create an access token - -2. The first part of the access token is your User Id - -``` -$api = new Api(); - -$api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); -$api->setUserId(1234578); -``` - -Seems like you can only access your own media until 2020 other user's media December 11, 2018. Hope to find a solution for long term. - ## Warning -**2018-04-16 : Now fetching data with access token, only for your account (thanks [@jannejava](https://github.com/jannejava)), please upgrade to version ^4.0** +**2018-04-17 : Now fetching data with screen scraping (thanks [@cookieguru](https://github.com/cookieguru)), please upgrade to version ^5.0** + +~~2018-04-16 : Now fetching data with access token, only for your account (thanks [@jannejava](https://github.com/jannejava)), please upgrade to version ^4.0~~ ~~2018-04-08 : Due to changes of the Instagram API (again...), you must upgrade to version ^3.0~~ @@ -40,10 +29,7 @@ Seems like you can only access your own media until 2020 other user's media Dece ```php $api = new Api(); -$api->setUserId(184263228); -$api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); - -$feed = $api->getFeed(); +$feed = $api->getFeed('pgrimaud'); print_r($feed); @@ -90,28 +76,15 @@ Instagram\Hydrator\Feed Object ) ``` -### Paginate -If you want to use paginate, retrieve `maxId` from previous call and add it to your next call. +### Setting a custom User Agent +Since this method is using screen scraping, it is recommended that you spoof a user agent: ```php -// First call : - -$api = new Api(); -$api->setUserId(184263228); -$api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); +$client = new GuzzleHttp\Client(['User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0']); -$feed = $api->getFeed(); - -$endCursor = $feed->getMaxId(); - -// Second call : - -$api = new Api(); -$api->setUserId(184263228); -$api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); -$api->setMaxId('1230468487398454311_184263228'); +$api = new Api($client); -$feed = $api->getFeed(); +$feed = $api->getFeed('pgrimaud'); // And etc... ``` diff --git a/composer.json b/composer.json index 2fac643..aad93d7 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,10 @@ { "name": "Charles Salvan", "email": "charles.salvan@hotmail.fr" + }, + { + "name": "Tim Bond", + "email": "cookieguru@gmail.com" } ], "autoload": { diff --git a/src/Instagram/Api.php b/src/Instagram/Api.php index 39e6229..4902c21 100644 --- a/src/Instagram/Api.php +++ b/src/Instagram/Api.php @@ -4,94 +4,41 @@ use GuzzleHttp\Client; use Instagram\Exception\InstagramException; -use Instagram\Transport\JsonFeed; +use Instagram\Transport\HTMLPage; class Api { /** * @var Client */ - private $clientUser = null; - - /** - * @var Client - */ - private $clientMedia = null; - - /** - * @var integer - */ - private $userId = null; - - /** - * @var string - */ - private $accessToken = null; - - /** - * @var string - */ - private $maxId = null; + private $client = null; /** * Api constructor. - * @param Client|null $clientUser - * @param Client|null $clientMedia - */ - public function __construct(Client $clientUser = null, Client $clientMedia = null) - { - $this->clientUser = $clientUser ?: new Client(); - $this->clientMedia = $clientMedia ?: new Client(); - } - - /** - * @param int $userId - */ - public function setUserId($userId) - { - $this->userId = $userId; - } - - /** - * @param $token + * @param Client|null $client */ - public function setAccessToken($token) + public function __construct(Client $client = null) { - $this->accessToken = $token; + $this->client = $client ?: new Client(); } /** + * @param string $username * @return Hydrator\Feed * @throws InstagramException * @throws \GuzzleHttp\Exception\GuzzleException */ - public function getFeed() + public function getFeed($username) { - if (!$this->userId) { - throw new InstagramException('Missing userId'); - } - - if (!$this->accessToken) { - throw new InstagramException('Missing access token'); + if(empty($username)) { + throw new InstagramException('username cannot be empty'); } - - $feed = new JsonFeed($this->clientUser, $this->clientMedia, $this->accessToken); + $feed = new HTMLPage($this->client); $hydrator = new Hydrator(); - $userDataFetched = $feed->fetchUserData($this->userId); - $hydrator->setUserData($userDataFetched); - - $mediaDataFetched = $feed->fetchMediaData($this->userId, $this->maxId); - $hydrator->setMediaData($mediaDataFetched); + $dataFetched = $feed->fetchData($username); + $hydrator->setData($dataFetched); return $hydrator->getHydratedData(); } - - /** - * @param string $maxId - */ - public function setMaxId($maxId) - { - $this->maxId = $maxId; - } } diff --git a/src/Instagram/Hydrator.php b/src/Instagram/Hydrator.php index 93f106b..f989893 100644 --- a/src/Instagram/Hydrator.php +++ b/src/Instagram/Hydrator.php @@ -4,33 +4,21 @@ use Instagram\Hydrator\Feed; use Instagram\Hydrator\Media; +use Instagram\Transport\HTMLPage; class Hydrator { /** - * @var array + * @var \stdClass */ - private $userData; + private $data; /** - * @var array + * @param array $data */ - private $mediaData; - - /** - * @param array $userData - */ - public function setUserData($userData) - { - $this->userData = $userData; - } - - /** - * @param array $mediaData - */ - public function setMediaData($mediaData) + public function setData($data) { - $this->mediaData = $mediaData; + $this->data = $data; } /** @@ -40,36 +28,35 @@ public function getHydratedData() { $feed = $this->generateFeed(); - if (isset($this->mediaData['data'][0])) { - foreach ($this->mediaData['data'] as $node) { - $media = new Media(); - - $media->setId($node['id']); - $media->setTypeName($node['type']); + foreach ($this->data->edge_owner_to_timeline_media->edges as $edge) { + $node = $edge->node; + /** @var \stdClass $node */ + $media = new Media(); - $media->setCaption($node['caption']['text']); + $media->setId($node->id); + $media->setTypeName($node->__typename); - $media->setHeight($node['images']['standard_resolution']['height']); - $media->setWidth($node['images']['standard_resolution']['width']); + if($node->edge_media_to_caption->edges) { + $media->setCaption($node->edge_media_to_caption->edges[0]->node->text); + } - $media->setThumbnailSrc($node['images']['thumbnail']['url']); - $media->setDisplaySrc($node['images']['standard_resolution']['url']); + $media->setHeight($node->dimensions->height); + $media->setWidth($node->dimensions->width); - $media->setLink($node['link']); + $media->setThumbnailSrc($node->thumbnail_src); + $media->setDisplaySrc($node->display_url); - $date = new \DateTime(); - $date->setTimestamp($node['created_time']); + $date = new \DateTime(); + $date->setTimestamp($node->taken_at_timestamp); - $media->setDate($date); + $media->setDate($date); - $media->setComments($node['comments']['count']); - $media->setLikes($node['likes']['count']); + $media->setComments($node->edge_media_to_comment->count); + $media->setLikes($node->edge_liked_by->count); - $feed->addMedia($media); - } + $media->setLink(HTMLPage::INSTAGRAM_ENDPOINT . "p/{$node->shortcode}/"); - $feed->setHasNextPage(isset($this->mediaData['pagination']['next_max_id'])); - $feed->setMaxId(isset($this->mediaData['pagination']['next_max_id']) ? $this->mediaData['pagination']['next_max_id'] : null); + $feed->addMedia($media); } return $feed; @@ -82,17 +69,15 @@ private function generateFeed() { $feed = new Feed(); - if ($this->userData) { - $feed->setId($this->userData['id']); - $feed->setUserName($this->userData['username']); - $feed->setBiography($this->userData['bio']); - $feed->setFullName($this->userData['full_name']); - $feed->setProfilePicture($this->userData['profile_picture']); - $feed->setMediaCount($this->userData['counts']['media']); - $feed->setFollowers($this->userData['counts']['followed_by']); - $feed->setFollowing($this->userData['counts']['follows']); - $feed->setExternalUrl($this->userData['website']); - } + $feed->setId($this->data->id); + $feed->setUserName($this->data->username); + $feed->setBiography($this->data->biography); + $feed->setFullName($this->data->full_name); + $feed->setProfilePicture($this->data->profile_pic_url_hd); + $feed->setMediaCount($this->data->edge_owner_to_timeline_media->count); + $feed->setFollowers($this->data->edge_followed_by->count); + $feed->setFollowing($this->data->edge_follow->count); + $feed->setExternalUrl($this->data->external_url); return $feed; } diff --git a/src/Instagram/Hydrator/Feed.php b/src/Instagram/Hydrator/Feed.php index 9d70130..04015d3 100644 --- a/src/Instagram/Hydrator/Feed.php +++ b/src/Instagram/Hydrator/Feed.php @@ -26,12 +26,12 @@ class Feed /** * @var integer */ - public $followers = 0; + public $followers; /** * @var integer */ - public $following = 0; + public $following; /** * @var string @@ -49,17 +49,7 @@ class Feed public $mediaCount = 0; /** - * @var boolean - */ - public $hasNextPage = false; - - /** - * @var string - */ - public $maxId; - - /** - * @var array + * @var Media[] */ public $medias = []; @@ -208,23 +198,7 @@ public function setMediaCount($mediaCount) } /** - * @return bool - */ - public function getHasNextPage() - { - return $this->hasNextPage; - } - - /** - * @param bool $hasNextPage - */ - public function setHasNextPage($hasNextPage) - { - $this->hasNextPage = $hasNextPage; - } - - /** - * @return array + * @return Media[] */ public function getMedias() { @@ -232,26 +206,10 @@ public function getMedias() } /** - * @param $media + * @param Media $media */ - public function addMedia($media) + public function addMedia(Media $media) { $this->medias[] = $media; } - - /** - * @return string - */ - public function getMaxId() - { - return $this->maxId; - } - - /** - * @param string $maxId - */ - public function setMaxId($maxId) - { - $this->maxId = $maxId; - } } diff --git a/src/Instagram/Transport/HTMLPage.php b/src/Instagram/Transport/HTMLPage.php new file mode 100644 index 0000000..6526322 --- /dev/null +++ b/src/Instagram/Transport/HTMLPage.php @@ -0,0 +1,51 @@ +client = $client; + } + + /** + * @param string $userName + * @return mixed + * @throws InstagramException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function fetchData($userName) + { + $endpoint = self::INSTAGRAM_ENDPOINT . "$userName/"; + + $res = $this->client->request('GET', $endpoint); + + $html = (string)$res->getBody(); + preg_match('/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/fixtures/user_feed.json b/tests/fixtures/user_feed.json deleted file mode 100644 index d793ca8..0000000 --- a/tests/fixtures/user_feed.json +++ /dev/null @@ -1 +0,0 @@ -{"data": {"id": "184263228", "username": "pgrimaud", "profile_picture": "https://scontent.cdninstagram.com/vp/f49bc1ac9af43314d3354b4c4a987c6d/5B5BB12E/t51.2885-19/10483606_1498368640396196_604136733_a.jpg", "full_name": "Pierre G", "bio": "Gladiator retired - ESGI 14'", "website": "https://p.ier.re/", "is_business": false, "counts": {"media": 33, "follows": 114, "followed_by": 342}}, "meta": {"code": 200}} \ No newline at end of file From cf52a6c5c72dcf6911127172c788a9ec08b67d41 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Wed, 18 Apr 2018 19:06:54 +0200 Subject: [PATCH 03/13] Manage jsonfeed for paginate --- .gitignore | 2 + cache/.gitkeep | 0 examples/getMedias.php | 20 ++++ src/Instagram/Api.php | 47 ++++++++-- src/Instagram/Exception/CacheException.php | 7 ++ .../Exception/InstagramException.php | 1 + src/Instagram/Hydrator.php | 7 +- src/Instagram/Hydrator/Feed.php | 22 +++++ src/Instagram/Hydrator/Media.php | 1 + src/Instagram/Storage/Cache.php | 69 ++++++++++++++ src/Instagram/Storage/CacheManager.php | 70 ++++++++++++++ src/Instagram/Transport/HTMLPage.php | 39 +++++--- src/Instagram/Transport/JsonFeed.php | 94 +++++++++++++++++++ src/Instagram/Transport/Transport.php | 27 ++++++ 14 files changed, 383 insertions(+), 23 deletions(-) create mode 100644 cache/.gitkeep create mode 100644 examples/getMedias.php create mode 100644 src/Instagram/Exception/CacheException.php create mode 100644 src/Instagram/Storage/Cache.php create mode 100644 src/Instagram/Storage/CacheManager.php create mode 100644 src/Instagram/Transport/JsonFeed.php create mode 100644 src/Instagram/Transport/Transport.php diff --git a/.gitignore b/.gitignore index 0670882..c5359c8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ vendor build .php_cs.cache +cache +!cache/.gitkeep diff --git a/cache/.gitkeep b/cache/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/getMedias.php b/examples/getMedias.php new file mode 100644 index 0000000..b33f61b --- /dev/null +++ b/examples/getMedias.php @@ -0,0 +1,20 @@ +setUserName('pgrimaud'); + +/** @var \Instagram\Hydrator\Feed $feed */ +$feed = $api->getFeed(); + +foreach ($feed->getMedias() as $media) { + echo $media->getCaption() . "\n"; +} + +$api->setEndCursor($feed->getEndCursor()); +$feed = $api->getFeed(); + +foreach ($feed->getMedias() as $media) { + echo $media->getCaption() . "\n"; +} \ No newline at end of file diff --git a/src/Instagram/Api.php b/src/Instagram/Api.php index 4902c21..a53a234 100644 --- a/src/Instagram/Api.php +++ b/src/Instagram/Api.php @@ -5,6 +5,7 @@ use GuzzleHttp\Client; use Instagram\Exception\InstagramException; use Instagram\Transport\HTMLPage; +use Instagram\Transport\JsonFeed; class Api { @@ -13,6 +14,16 @@ class Api */ private $client = null; + /** + * @var string + */ + private $userName; + + /** + * @var string + */ + private $endCursor = null; + /** * Api constructor. * @param Client|null $client @@ -23,22 +34,42 @@ public function __construct(Client $client = null) } /** - * @param string $username * @return Hydrator\Feed * @throws InstagramException - * @throws \GuzzleHttp\Exception\GuzzleException */ - public function getFeed($username) + public function getFeed() { - if(empty($username)) { - throw new InstagramException('username cannot be empty'); + if (empty($this->userName)) { + throw new InstagramException('Username cannot be empty'); + } + + if ($this->endCursor) { + $feed = new JsonFeed($this->client, $this->endCursor); + } else { + $feed = new HTMLPage($this->client); } - $feed = new HTMLPage($this->client); - $hydrator = new Hydrator(); - $dataFetched = $feed->fetchData($username); + $dataFetched = $feed->fetchData($this->userName); + + $hydrator = new Hydrator(); $hydrator->setData($dataFetched); return $hydrator->getHydratedData(); } + + /** + * @param string $userName + */ + public function setUserName($userName) + { + $this->userName = $userName; + } + + /** + * @param string $endCursor + */ + public function setEndCursor($endCursor) + { + $this->endCursor = $endCursor; + } } diff --git a/src/Instagram/Exception/CacheException.php b/src/Instagram/Exception/CacheException.php new file mode 100644 index 0000000..78d2c6c --- /dev/null +++ b/src/Instagram/Exception/CacheException.php @@ -0,0 +1,7 @@ +generateFeed(); foreach ($this->data->edge_owner_to_timeline_media->edges as $edge) { - $node = $edge->node; + /** @var \stdClass $node */ + $node = $edge->node; + $media = new Media(); $media->setId($node->id); $media->setTypeName($node->__typename); - if($node->edge_media_to_caption->edges) { + if ($node->edge_media_to_caption->edges) { $media->setCaption($node->edge_media_to_caption->edges[0]->node->text); } @@ -78,6 +80,7 @@ private function generateFeed() $feed->setFollowers($this->data->edge_followed_by->count); $feed->setFollowing($this->data->edge_follow->count); $feed->setExternalUrl($this->data->external_url); + $feed->setEndCursor($this->data->edge_owner_to_timeline_media->page_info->end_cursor); return $feed; } diff --git a/src/Instagram/Hydrator/Feed.php b/src/Instagram/Hydrator/Feed.php index 04015d3..746481a 100644 --- a/src/Instagram/Hydrator/Feed.php +++ b/src/Instagram/Hydrator/Feed.php @@ -1,4 +1,5 @@ medias[] = $media; } + + /** + * @return string + */ + public function getEndCursor() + { + return $this->endCursor; + } + + /** + * @param string $endCursor + */ + public function setEndCursor($endCursor) + { + $this->endCursor = $endCursor; + } } diff --git a/src/Instagram/Hydrator/Media.php b/src/Instagram/Hydrator/Media.php index 14ddf38..16fe7af 100644 --- a/src/Instagram/Hydrator/Media.php +++ b/src/Instagram/Hydrator/Media.php @@ -1,4 +1,5 @@ rhxGis; + } + + /** + * @param string $rhxGis + */ + public function setRhxGis($rhxGis) + { + $this->rhxGis = $rhxGis; + } + + /** + * @return array + */ + public function getCookie() + { + return $this->cookie; + } + + /** + * @param array $cookie + */ + public function setCookie($cookie) + { + $this->cookie = $cookie; + } + + /** + * @return int + */ + public function getUserId() + { + return $this->userId; + } + + /** + * @param int $userId + */ + public function setUserId($userId) + { + $this->userId = $userId; + } +} diff --git a/src/Instagram/Storage/CacheManager.php b/src/Instagram/Storage/CacheManager.php new file mode 100644 index 0000000..9964481 --- /dev/null +++ b/src/Instagram/Storage/CacheManager.php @@ -0,0 +1,70 @@ +cacheDir = $cacheDir ?: $this->cacheDir; + } + + /** + * @param $userId + * @return string + */ + private function getCacheFile($userId) + { + return ($this->cacheDir ? $this->cacheDir : __DIR__ . '/../../../cache/') . $userId . '.cache'; + } + + /** + * @param $userId + * @return Cache|mixed + */ + public function getCache($userId) + { + if (is_file($this->getCacheFile($userId))) { + $handle = fopen($this->getCacheFile($userId), 'r'); + $data = fread($handle, filesize($this->getCacheFile($userId))); + $cache = unserialize($data); + + fclose($handle); + + if ($cache instanceof Cache) { + return $cache; + } + } + + return new Cache(); + } + + /** + * @param Cache $cache + * @param $userName + * @throws CacheException + */ + public function set(Cache $cache, $userName) + { + if (!is_writable(dirname($this->getCacheFile($userName)))) { + throw new CacheException('Cache folder is not writable'); + } + + $data = serialize($cache); + $handle = fopen($this->getCacheFile($userName), 'w+'); + + fwrite($handle, $data); + fclose($handle); + } +} diff --git a/src/Instagram/Transport/HTMLPage.php b/src/Instagram/Transport/HTMLPage.php index 6526322..4bfd67e 100644 --- a/src/Instagram/Transport/HTMLPage.php +++ b/src/Instagram/Transport/HTMLPage.php @@ -4,48 +4,61 @@ use GuzzleHttp\Client; use Instagram\Exception\InstagramException; +use Instagram\Storage\Cache; +use Instagram\Storage\CacheManager; -class HTMLPage +class HTMLPage extends Transport { - const INSTAGRAM_ENDPOINT = 'https://www.instagram.com/'; - - /** - * @var Client - */ - private $client; - /** * HTMLPage constructor. * @param Client $client */ public function __construct(Client $client) { - $this->client = $client; + parent::__construct($client); } /** - * @param string $userName + * @param $userName * @return mixed * @throws InstagramException * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Instagram\Exception\CacheException */ public function fetchData($userName) { - $endpoint = self::INSTAGRAM_ENDPOINT . "$userName/"; + $endpoint = self::INSTAGRAM_ENDPOINT . $userName . '/'; + + $headers = [ + 'headers' => [ + 'user-agent' => self::USER_AGENT + ] + ]; - $res = $this->client->request('GET', $endpoint); + $res = $this->client->request('GET', $endpoint, $headers); $html = (string)$res->getBody(); + preg_match('/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Pierre G (@pgrimaud) • Instagram photos and videos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/fixtures/pgrimaud.json b/tests/fixtures/pgrimaud.json new file mode 100644 index 0000000..d46afa0 --- /dev/null +++ b/tests/fixtures/pgrimaud.json @@ -0,0 +1 @@ +{"data":{"user":{"edge_owner_to_timeline_media":{"count":33,"page_info":{"has_next_page":true,"end_cursor":"AQBneXlAj9B006L14JauaEbLP0gg4rAQh_fndtQrodO6VWy7IxdnjtvU0nIw0gac3fiBGGiC4YnwS8_8WSmLKSZJPfNNGJP_hpLJCq5Fd-3zVw"},"edges":[{"node":{"id":"1414562998810695114","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Sunny afternoon @ work #lametric #electronics #technology #tech #electronic #device #gadget #gadgets #instatech #instagood #geek #techie #nerd #techy #photooftheday #computers #laptops #hack #screen"}}]},"shortcode":"BOhiY2AhdXK","edge_media_to_comment":{"count":5},"comments_disabled":false,"taken_at_timestamp":1482849071,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/85814a91e0fa9f09fb1d1e992096d534/5B73AB66/t51.2885-15/e35/15538537_1282340145157860_1132859970065268736_n.jpg","edge_media_preview_like":{"count":69},"gating_info":null,"media_preview":"ACoqr8jv+lMZ8dT+hprOfRhj2qMye/8An86AJPNX1/Sk8xfb/vmo959aN59f0oAk3r6/+O0mV9T+VNDn1/Sjd7igDYu7dRHnOcMM4GBgnueM9fXNZE8axthG3j1/ya6G6t5JxjcNp7Gs1tKk7EH8aAM4KpQnkMCPTBB9O+RT5I1VdyncCcDIwfx56+3oRVk6ZKO2ajNhKP4TQAtjF5jnuAOeM8E4zjnp/Ktr7PF6x/8Ajv8AhVXTIzCWJyCcD8K3PwoAipcUtFACYpaWigAxTqSigD//2Q==","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/7da2a4a05ea425a21639fdd79cef4485/5B76754C/t51.2885-15/s640x640/sh0.08/e35/15538537_1282340145157860_1132859970065268736_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/eac1496d92890296d7bf0c4c60e3bfa8/5B5BAD31/t51.2885-15/s150x150/e35/15538537_1282340145157860_1132859970065268736_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/ab04e83a9e3ec221ef7ab82dd25f3287/5B6651A5/t51.2885-15/s240x240/e35/15538537_1282340145157860_1132859970065268736_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/5b7d854802639c25702d5733c6e61142/5B50DA01/t51.2885-15/s320x320/e35/15538537_1282340145157860_1132859970065268736_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/f08e6de4f03410247e2fcd4f3c7df129/5B63BA59/t51.2885-15/s480x480/e35/15538537_1282340145157860_1132859970065268736_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/7da2a4a05ea425a21639fdd79cef4485/5B76754C/t51.2885-15/s640x640/sh0.08/e35/15538537_1282340145157860_1132859970065268736_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1371891436148238220","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Happy #caturday Freya \ud83d\ude3b\ud83d\ude3a\ud83d\ude3c\ud83d\udc08 #cat #cats #catoftheday"}}]},"shortcode":"BMJ7_4aBCeM","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1477762225,"dimensions":{"height":685,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/645131ebb6517df0d0bd116526c819b3/5B5FE3AB/t51.2885-15/e35/14727494_361799304165571_8946499070357143552_n.jpg","edge_media_preview_like":{"count":18},"gating_info":null,"media_preview":"ACoak1O5eKXaM4IBHP8AnuKyzdOfX861tSi810PsRke3PTvUA02NjnfhT0zgHselZR5bXsa3a0M43L/5JqMzMav3GneUu5GDcZx3I9R6+/pWZirVhXY7ex71cDECqI4NT+b7UNXKi7bmxdXDxpujOCD1wDweO9YzSM3LEk1MDuictyR0J5xyKqdqmCshSepIZnwADjaSV9icZx+VQ5pKcnWtCAU//WqyFqPHyj8KtVDZrFH/2Q==","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/f2e054e813b1bc5ba028929e63d85c06/5B68E993/t51.2885-15/s640x640/sh0.08/e35/c197.0.685.685/14727494_361799304165571_8946499070357143552_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/8d2dffffdedcb6a02755cdecc3226553/5B6476D0/t51.2885-15/s150x150/e35/c197.0.685.685/14727494_361799304165571_8946499070357143552_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/74c3921cc6b77ec366c571083c8479c4/5B5C3046/t51.2885-15/s240x240/e35/c197.0.685.685/14727494_361799304165571_8946499070357143552_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/adfe9f4691817bda1c986a17170e52f2/5B7037AE/t51.2885-15/s320x320/e35/c197.0.685.685/14727494_361799304165571_8946499070357143552_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/51afd6896cf8bb6d3e0cfccf96225cd5/5B6C4D87/t51.2885-15/s480x480/e35/c197.0.685.685/14727494_361799304165571_8946499070357143552_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/f2e054e813b1bc5ba028929e63d85c06/5B68E993/t51.2885-15/s640x640/sh0.08/e35/c197.0.685.685/14727494_361799304165571_8946499070357143552_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1341979751174598260","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Chilling in Deauville #sunshine #weekend #sea #deauville"}}]},"shortcode":"BKfq3k8h250","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1474196474,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/d0933757f092851f51452a139e0b5eed/5B6E8F18/t51.2885-15/e35/14269056_1380327651996786_1795139317_n.jpg","edge_media_preview_like":{"count":17},"gating_info":null,"media_preview":"ACoqiS3zVpberyRj0xUmzFXzmXKUPIqNretMqKYVo5hcpjSQYqv5dbTpVTZVKQ7ErSsqEgknjH51YLEt1OPrzmqSvgcAn6Hn8jxUqzA9iPqMVyanVZAkpZnBYjacD8v8ab82cbm9c59+mO3HXFNjj8tmYHO71HvUnJ64p3YNLoV/MLM4ycA469sDI/nzVdi+Tjpn+9U7IEzjjJyfc1VNVcVrD45cVaWUVkr1qwh4/Gk0M0PN9KiaTioCeajeiwCSSZ61W8w+35UyQ8/jUeauxJ//2Q==","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/3afeb69611e006f433b70c37b7266e2f/5B52DBD5/t51.2885-15/s640x640/sh0.08/e35/14269056_1380327651996786_1795139317_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/9d984dd3d831ab3a7ad7cfe59b3b819a/5B5E49F8/t51.2885-15/s150x150/e35/14269056_1380327651996786_1795139317_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/e04ee3a1ee0c28698afeecdc4bf37d9a/5B5CE622/t51.2885-15/s240x240/e35/14269056_1380327651996786_1795139317_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/224e705de89677d9340967117edff5f9/5B59854A/t51.2885-15/s320x320/e35/14269056_1380327651996786_1795139317_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/016ca55b9c91f7b5d4a7d08859a8e617/5B5D626C/t51.2885-15/s480x480/e35/14269056_1380327651996786_1795139317_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/3afeb69611e006f433b70c37b7266e2f/5B52DBD5/t51.2885-15/s640x640/sh0.08/e35/14269056_1380327651996786_1795139317_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1336517393446714995","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Tropicana Field #baseball #tampa #rays #mlb"}}]},"shortcode":"BKMQ3z4hb5z","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1473545310,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/e87ded4bd105b4c463523aeb2896027e/5B6D8FF2/t51.2885-15/e35/14280500_1312438175462902_1434164095_n.jpg","edge_media_preview_like":{"count":9},"gating_info":null,"media_preview":"ACoqLWxYxsrgZTkevrgfiOPrV+xtVQbWGWPzE/7X/wBbpVmO5RYg57joOtZtzqch4jwgPfv/AC/l+dO/RhY1ZYVCMBgbgf5VBBaCFwT/AAxhfxzk1y8krMcEljn3Off/AOv+FIlzLH91mAH1xUlad/wOzZEkGCMj0rNYRgkYHB9KpJfnA+bkjOD/AI//AF6qtcnJ4PX1oETRu8yKWwoAxx3PX8Ae+Ov0qJbaSQkkhd3pzwOwJ6D8KW3BlG3v+gH+eKvJhSVHAUY/XND/ABFczfITfsLMT0yOg9jjGP5U6S0SMHJIAxk5PerjhnYFSAP8Pbv+dBwWIJ6r9O9FmBUjVfL2r86g4/H0rPKjP/16m+0MjlB90nHAHUdD60xlUkn196Yy7aSbAcjHOeO/p+A/nS7mGcEDd1qkpOKkiOd1K4WJ0BXnOTnP4+tKwDHLc/XNVcmkJNAy9uQDO0Z9e9ZjYJP1qQk1AadxH//Z","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/73ab4a9a6683249be65398b281b7d63a/5B6B8D3F/t51.2885-15/s640x640/sh0.08/e35/14280500_1312438175462902_1434164095_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/be13d5c57342450a277f09afc3d07053/5B708512/t51.2885-15/s150x150/e35/14280500_1312438175462902_1434164095_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/1440e4341c9267aea8ffb8591d0856d3/5B6931C8/t51.2885-15/s240x240/e35/14280500_1312438175462902_1434164095_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/3fba86a842412ac1d51ed5676265cd59/5B55D3A0/t51.2885-15/s320x320/e35/14280500_1312438175462902_1434164095_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/80f6d25aa4f293b248078b58fc1e27cd/5B503C86/t51.2885-15/s480x480/e35/14280500_1312438175462902_1434164095_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/73ab4a9a6683249be65398b281b7d63a/5B6B8D3F/t51.2885-15/s640x640/sh0.08/e35/14280500_1312438175462902_1434164095_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1316927826267789203","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Chicago White Sox vs Miami Marlins #baseball #mlb #marlins #whitesox"}}]},"shortcode":"BJGquFvhNeT","edge_media_to_comment":{"count":3},"comments_disabled":false,"taken_at_timestamp":1471210052,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/1c2f359f670d66ac5fbbfe49ffcbbab9/5B61F622/t51.2885-15/e35/13658684_582314511948929_910849640_n.jpg","edge_media_preview_like":{"count":21},"gating_info":null,"media_preview":"ACoqriwDD5JEOfXI/nSf2dKfu7W+jA1XGD7Y4qQcdD+tSMa1nMvVG/LP8qqOhU4IIPuMVoGdgBtYjHXn3qGQtOeTkjPJI6fiaAKOKK1U05XGQ/1GBkZ/Ht69DTvsCDjcePYf40WFcYsUx7ufzqUW0p7v+dOS6PB45PQ5/l6VZW4B/wA9O3T/AOvWbculjSxX+yS+rfnTPsL9cnP1q/5oJwCCR6Gl31k5yXQdl5lEWLgZySfrUotW7s2fwqz5hFL5hqfaS8iDmcOvqCPz/P0pm4/n3pImJB5PSkYliS3J46/hXYInS5dOB0ByPY+vFL9rfIOWAAwcHqB6VTB5qaE5dQeQCKANQ+YIzIWO7rjA7cYPuB/k1TF8/qf0rSb7p+n9K5+oST3Q3of/2Q==","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/437b56feb04359748238543dab265a4a/5B51AB94/t51.2885-15/s640x640/sh0.08/e35/13658684_582314511948929_910849640_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/949eb78351245e9b4f11ea5a6ecce17f/5B5AA3BE/t51.2885-15/s150x150/e35/13658684_582314511948929_910849640_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/157cc8e76f31ee6d372386805255fbf2/5B73A33B/t51.2885-15/s240x240/e35/13658684_582314511948929_910849640_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/0aacc422e23c112ae30520377cc777b0/5B58295C/t51.2885-15/s320x320/e35/13658684_582314511948929_910849640_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/3877eee4283097a6a53b72465e5dbdbd/5B5BB9F0/t51.2885-15/s480x480/e35/13658684_582314511948929_910849640_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/437b56feb04359748238543dab265a4a/5B51AB94/t51.2885-15/s640x640/sh0.08/e35/13658684_582314511948929_910849640_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1258028269420312388","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Gasquet \ud83c\uddeb\ud83c\uddf7 - Fratangelo \ud83c\uddfa\ud83c\uddf8 #RG16 #rolandgarros #tennis"}}]},"shortcode":"BF1agu0MndE","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1464188677,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/4336078cd6eaf2f5972035fda4e87579/5B526A87/t51.2885-15/e35/13187980_1068417143251106_1580500519_n.jpg","edge_media_preview_like":{"count":25},"gating_info":null,"media_preview":"ACoq0l1iBum4/Rc1L/aUHckfhXMgHueP88Uu3A45P/16QHS/2nb/AN79D/hSjUbc/wAX6H/CuZx+J/yTRt556Dp70AdC2rQKM/Mf+An/APVUB12D+6/5D/GsYggfL170nNADFDen0H+f196kBYc4Ptx/Opxg9xUgI7Gsud9jf2a7lMO3cZ9OlLufGMH8R/Ue9WS6hsZ5p28Y4PFHO+wezXcpbnznn8v8/wBaiLyZ6fpWg31BpKOfyD2fmUJl8sKVcOMkgjII9fw9DUYnPUduAP1/H8cmp7ZFa4VWAI54IyOnpVq8hRYcqqg4PIAHetUYFSBiULcEhhwR2P09e+eMe+KfBNF5uJM+Wc5xxzjG7Hp7fpSWP3G+v9DWdQSndtdjRhMbOUbGOdpb5eQPlzjpnv74qfyY/wC//wCOGqloA0ozzznmt7cfU1SjzdbEylY//9k=","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/fc6a451984171c321bf3b1b664a0a751/5B57364A/t51.2885-15/s640x640/sh0.08/e35/13187980_1068417143251106_1580500519_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/5e23f6f66bb4ed1e5f1f0130f0725855/5B5A9867/t51.2885-15/s150x150/e35/13187980_1068417143251106_1580500519_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/939bb91c3d20cd1d8d9bb15847c33fc3/5B5B0DBD/t51.2885-15/s240x240/e35/13187980_1068417143251106_1580500519_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/b0ba95cc864aac10a8a5be56fa203809/5B6257D5/t51.2885-15/s320x320/e35/13187980_1068417143251106_1580500519_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/858c96933b3cde9b82998f4a430e2b31/5B54EEF3/t51.2885-15/s480x480/e35/13187980_1068417143251106_1580500519_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/fc6a451984171c321bf3b1b664a0a751/5B57364A/t51.2885-15/s640x640/sh0.08/e35/13187980_1068417143251106_1580500519_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1245164809820992525","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Ma\u00eetre zen"}}]},"shortcode":"BFHtswgMnQN","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1462655234,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/ad0e39078a6c442b5599551fa4270d07/5B635250/t51.2885-15/e35/13117877_208626432856629_450230271_n.jpg","edge_media_preview_like":{"count":17},"gating_info":null,"media_preview":"ACoqxooTNkAgYGee/wBKkFk/95R9Sf8ACoYCAWznpxj1yK0TEEwRjHHGfXH+NICobJzyCv5n/Cq8kZjJQ9R6VpKoY7TjGT0JqhcriUgdj/SgCAA06rzafKsP2jjZjPXnk46YqjQMFcocipGupG6kn+mOmPyqHFLtzQA4TOO55755phYk5PJpwQYOTz29/r6frSYoAXzXA27jt9MnH5UzNT/Z2wGAyCCeOeB1z6Y96hxRcLC5wfWkJpV60jUAGaM02loEPDkdOO3FNptFAz//2Q==","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/cf459165f7f71c183022cc7779c5973c/5B56B3E6/t51.2885-15/s640x640/sh0.08/e35/13117877_208626432856629_450230271_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/8a41f0da285dc7f5b6933cb0245d4a76/5B55FBCC/t51.2885-15/s150x150/e35/13117877_208626432856629_450230271_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/9b65f5f9f3a356997bbaed404dc1c308/5B5DD949/t51.2885-15/s240x240/e35/13117877_208626432856629_450230271_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/6ef44ed18826ccbd27737b61d16ed55d/5B700B2E/t51.2885-15/s320x320/e35/13117877_208626432856629_450230271_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/6b0e8e022020ef871e5a96078488a727/5B5B8C82/t51.2885-15/s480x480/e35/13117877_208626432856629_450230271_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/cf459165f7f71c183022cc7779c5973c/5B56B3E6/t51.2885-15/s640x640/sh0.08/e35/13117877_208626432856629_450230271_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1230468487398454311","__typename":"GraphVideo","edge_media_to_caption":{"edges":[{"node":{"text":"France vs Danemark #hockey #ensemblepour2017"}}]},"shortcode":"BETgJHqsnQn","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1460903295,"dimensions":{"height":640,"width":640},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/fe320ce806db005b989e0e3d862a331a/5ADAA980/t51.2885-15/e15/12424766_232582647101839_1892955482_n.jpg","edge_media_preview_like":{"count":11},"gating_info":null,"media_preview":null,"owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/fe320ce806db005b989e0e3d862a331a/5ADAA980/t51.2885-15/e15/12424766_232582647101839_1892955482_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/92ef691a8da34e8b25509ea9b95614f4/5ADAC75E/t51.2885-15/s150x150/e15/12424766_232582647101839_1892955482_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/584e6ca54bd18929cedd8c8ae039e97a/5ADAB338/t51.2885-15/s240x240/e15/12424766_232582647101839_1892955482_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/cf9f57d0872951c4ccbd9c6b7a9bfb98/5ADB2BCD/t51.2885-15/s320x320/e15/12424766_232582647101839_1892955482_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/ace995653ef5dc63f54ae6e71910a61c/5ADAC619/t51.2885-15/s480x480/e15/12424766_232582647101839_1892955482_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/fe320ce806db005b989e0e3d862a331a/5ADAA980/t51.2885-15/e15/12424766_232582647101839_1892955482_n.jpg","config_width":640,"config_height":640}],"is_video":true,"video_view_count":36}},{"node":{"id":"1204655681973679611","__typename":"GraphVideo","edge_media_to_caption":{"edges":[{"node":{"text":"En toute cordialit\u00e9 \"Suce ma bite pour la Saint Valentin\" #casseursflowters #yoyo #orelsan #gringe #rap #latergram"}}]},"shortcode":"BC3y_XQsnX7","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1457826169,"dimensions":{"height":640,"width":640},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/e28fc66ad229ab355eba7e0cd661b8d0/5ADB329B/t51.2885-15/e15/12751393_1325549187471828_126322692_n.jpg","edge_media_preview_like":{"count":24},"gating_info":null,"media_preview":null,"owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/e28fc66ad229ab355eba7e0cd661b8d0/5ADB329B/t51.2885-15/e15/12751393_1325549187471828_126322692_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/0beedade2a3d2a8eeb5aaf05859ffb08/5ADAF0C5/t51.2885-15/s150x150/e15/12751393_1325549187471828_126322692_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/1f99934f046036127fa107db51399bde/5ADB3923/t51.2885-15/s240x240/e15/12751393_1325549187471828_126322692_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/86502b6ee51e29e67ac259213f9f8618/5ADAE116/t51.2885-15/s320x320/e15/12751393_1325549187471828_126322692_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/530fd451997d2d1e76936de3f40b7f30/5ADBC882/t51.2885-15/s480x480/e15/12751393_1325549187471828_126322692_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/e28fc66ad229ab355eba7e0cd661b8d0/5ADB329B/t51.2885-15/e15/12751393_1325549187471828_126322692_n.jpg","config_width":640,"config_height":640}],"is_video":true,"video_view_count":59}},{"node":{"id":"1204653224178644409","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Ouais ouais #casseursflowters #yoyo #orelsan #gringe #rap #latergram"}}]},"shortcode":"BC3ybmQsnW5","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1457825876,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/79bf5605c376dd0ffa2d0b2650da8535/5B619638/t51.2885-15/e35/12677518_223951494622994_1057324647_n.jpg","edge_media_preview_like":{"count":14},"gating_info":null,"media_preview":"ACoq50UZpVYjPuMGrMY3Ad8dO9ItRv1K2aKsNCQMelMCcc0w5WRUlSuMdv8AP/16ixQS1YVVLdK0reMqATVNVMTbW4PQirqzBQccgDnHY/4ds1Db6HXSjC15PXsaGd3t68VBMm5cDrn0/Oo/tSgblxk9QfYDpz1Pv3pkl2d25BxjnI9T7H+tRy63HKS2jsU5oyo5qvVt5BcEDJBzgemPfnPX8AKh2Y43D9f8K1OZtPb8y+LOQ42j7ik9Rn/PY8/lxUEMhh3I2ODgqcEHHb178EUXbsAuCeY/X1Y5/PAzVRDlgTz0p+ZG2hZZgfkB4A3EnqTg8fh044PWpPOCRGOMbuBvPrznj6D9c+1aNuivC7MAzeWeSMnp6msBOtNq2gk7k/mK+Vb5c9D/AFz/ADqZZ4QACpJA65xmorUBlkzztTjPb5h09KqHrUjP/9k=","owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/a41e1d524f30268f27dc9bab90601d7e/5B74A282/t51.2885-15/s640x640/sh0.08/e35/12677518_223951494622994_1057324647_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/e3355f00e470b1f758590256bce1716f/5B664826/t51.2885-15/s150x150/e35/12677518_223951494622994_1057324647_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/8314295a957d584253655f0f3591164e/5B55A640/t51.2885-15/s240x240/e35/12677518_223951494622994_1057324647_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/c6635f84807e91354581199e1eb26c4c/5B5105B5/t51.2885-15/s320x320/e35/12677518_223951494622994_1057324647_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/21143d98afaeab89a76af9c9b498893c/5B6BD421/t51.2885-15/s480x480/e35/12677518_223951494622994_1057324647_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/a41e1d524f30268f27dc9bab90601d7e/5B74A282/t51.2885-15/s640x640/sh0.08/e35/12677518_223951494622994_1057324647_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1050315827333723352","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Sports nautiques sur un oc\u00e9an de larmes #impots #france #kristimalakoff"}}]},"shortcode":"6TeK4eMnTY","edge_media_to_comment":{"count":1},"comments_disabled":false,"taken_at_timestamp":1439427425,"dimensions":{"height":1080,"width":1080},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/7c25b9b387169e3c7eab24cdee5e02a7/5B5F7668/t51.2885-15/e35/11328302_931248243584133_661682480_n.jpg","edge_media_preview_like":{"count":49},"gating_info":null,"media_preview":null,"owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/1007ccaabba5af4e372163f14beb1976/5B55D6DE/t51.2885-15/s640x640/sh0.08/e35/11328302_931248243584133_661682480_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/8cc055e9b00533534da691654efedc25/5B5873F4/t51.2885-15/s150x150/e35/11328302_931248243584133_661682480_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/945e63c1dd3ee2ad13e2e85b05804ed1/5B5A3A71/t51.2885-15/s240x240/e35/11328302_931248243584133_661682480_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/4931913ca49867ae697dad53ce18424a/5B5C8216/t51.2885-15/s320x320/e35/11328302_931248243584133_661682480_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/57078b2d1ed074adfb48dc218bff13ed/5B555DBA/t51.2885-15/s480x480/e35/11328302_931248243584133_661682480_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/1007ccaabba5af4e372163f14beb1976/5B55D6DE/t51.2885-15/s640x640/sh0.08/e35/11328302_931248243584133_661682480_n.jpg","config_width":640,"config_height":640}],"is_video":false}},{"node":{"id":"1018030119604876806","__typename":"GraphImage","edge_media_to_caption":{"edges":[{"node":{"text":"Top of the world 4 #bratislava #slovaquia"}}]},"shortcode":"4gxQABsnYG","edge_media_to_comment":{"count":0},"comments_disabled":false,"taken_at_timestamp":1435578669,"dimensions":{"height":640,"width":640},"display_url":"https://scontent-cdg2-1.cdninstagram.com/vp/99cb6e673f4d981531dc0c04bfbec270/5B70E792/t51.2885-15/e15/11375118_522383647909799_322613513_n.jpg","edge_media_preview_like":{"count":34},"gating_info":null,"media_preview":null,"owner":{"id":"184263228"},"thumbnail_src":"https://scontent-cdg2-1.cdninstagram.com/vp/99cb6e673f4d981531dc0c04bfbec270/5B70E792/t51.2885-15/e15/11375118_522383647909799_322613513_n.jpg","thumbnail_resources":[{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/5a61eb87a0532f6679841bf9c543fa19/5B6B5B0E/t51.2885-15/s150x150/e15/11375118_522383647909799_322613513_n.jpg","config_width":150,"config_height":150},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/1de38d199b13a3f96e063fd7ef01d7bf/5B57658B/t51.2885-15/s240x240/e15/11375118_522383647909799_322613513_n.jpg","config_width":240,"config_height":240},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/601bdaf004dd5d26626366521f0edb2b/5B619DEC/t51.2885-15/s320x320/e15/11375118_522383647909799_322613513_n.jpg","config_width":320,"config_height":320},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/620b70493f6238e7f868b0e9cf579c5c/5B631640/t51.2885-15/s480x480/e15/11375118_522383647909799_322613513_n.jpg","config_width":480,"config_height":480},{"src":"https://scontent-cdg2-1.cdninstagram.com/vp/99cb6e673f4d981531dc0c04bfbec270/5B70E792/t51.2885-15/e15/11375118_522383647909799_322613513_n.jpg","config_width":640,"config_height":640}],"is_video":false}}]}}},"status":"ok"} \ No newline at end of file From 815b399e8fd21e6b7441e13d37e7ae6335ad20f9 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 14:40:10 +0200 Subject: [PATCH 05/13] Complete unit tests --- .gitignore | 10 +- src/Instagram/Transport/HtmlTransportFeed.php | 4 + tests/ApiTest.php | 173 +++++++++++++++-- tests/fixtures/invalid_pgrimaud.html | 180 ++++++++++++++++++ 4 files changed, 345 insertions(+), 22 deletions(-) create mode 100644 tests/fixtures/invalid_pgrimaud.html diff --git a/.gitignore b/.gitignore index c5359c8..cf86cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,11 @@ vendor build .php_cs.cache -cache -!cache/.gitkeep +/cache +!/cache/.gitkeep +/tests/cache/ +!/tests/cache/empty/.gitkeep +!/tests/cache/invalid/.gitkeep +!/tests/cache/invalid/demo.cache +!/tests/cache/valid/.gitkeep +!/tests/cache/valid/pgrimaud.cache diff --git a/src/Instagram/Transport/HtmlTransportFeed.php b/src/Instagram/Transport/HtmlTransportFeed.php index aaec6c0..cb88cbf 100644 --- a/src/Instagram/Transport/HtmlTransportFeed.php +++ b/src/Instagram/Transport/HtmlTransportFeed.php @@ -48,6 +48,10 @@ public function fetchData($userName) $data = json_decode($matches[1]); + if ($data === null) { + throw new InstagramException(json_last_error_msg()); + } + $newCache = new Cache(); $newCache->setRhxGis($data->rhx_gis); $newCache->setCookie($res->getHeaders()['Set-Cookie']); diff --git a/tests/ApiTest.php b/tests/ApiTest.php index 4edbc6c..373d0d3 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -17,42 +17,150 @@ class ApiTest extends \PHPUnit_Framework_TestCase /** * @var CacheManager */ - private $cacheManager; + private $validCacheManager; + + /** + * @var CacheManager + */ + private $invalidCacheManager; + + /** + * @var CacheManager + */ + private $emptyCacheManager; + + /** + * @var CacheManager + */ + private $unwritableCacheManager; + + /** + * @var Client + */ + private $validHtmlClient; + + /** + * @var Client + */ + private $invalidJsonHtmlClient; /** * @var Client */ - private $validClient; + private $invalidHtmlClient; /** * @var Client */ - private $invalidClient; + private $validJsonClient; + + /** + * @var Client + */ + private $invalidJsonClient; /** * @return void */ public function setUp() { - $validFixtures = file_get_contents(__DIR__ . '/fixtures/pgrimaud.html'); - $invalidFixtures = ''; + if (is_file(__DIR__ . '/cache/empty/pgrimaud.cache')) { + unlink(__DIR__ . '/cache/empty/pgrimaud.cache'); + } + + copy(__DIR__ . '/cache/invalid/demo.cache', __DIR__ . '/cache/invalid/pgrimaud.cache'); + + $validHtmlFixtures = file_get_contents(__DIR__ . '/fixtures/pgrimaud.html'); + $invalidHtmlJsonFixtures = file_get_contents(__DIR__ . '/fixtures/invalid_pgrimaud.html'); + $invalidHtmlFixtures = ''; + + $validJsonFixtures = file_get_contents(__DIR__ . '/fixtures/pgrimaud.json'); + $invalidJsonFixtures = ''; $headers = [ 'Set-Cookie' => 'cookie' ]; - $response = new Response(200, $headers, $validFixtures); - $mock = new MockHandler([$response]); - $handler = HandlerStack::create($mock); - $this->validClient = new Client(['handler' => $handler]); + $response = new Response(200, $headers, $validHtmlFixtures); + $mock = new MockHandler([$response]); + $handler = HandlerStack::create($mock); + $this->validHtmlClient = new Client(['handler' => $handler]); + + $response = new Response(200, [], $invalidHtmlFixtures); + $mock = new MockHandler([$response]); + $handler = HandlerStack::create($mock); + $this->invalidHtmlClient = new Client(['handler' => $handler]); + + $response = new Response(200, $headers, $invalidHtmlJsonFixtures); + $mock = new MockHandler([$response]); + $handler = HandlerStack::create($mock); + $this->invalidJsonHtmlClient = new Client(['handler' => $handler]); + + $response = new Response(200, $headers, $validJsonFixtures); + $mock = new MockHandler([$response]); + $handler = HandlerStack::create($mock); + $this->validJsonClient = new Client(['handler' => $handler]); + + $response = new Response(200, [], $invalidJsonFixtures); + $mock = new MockHandler([$response]); + $handler = HandlerStack::create($mock); + $this->invalidJsonClient = new Client(['handler' => $handler]); + + if (is_dir(__DIR__ . '/cache/unwritable')) { + rmdir(__DIR__ . '/cache/unwritable'); + } + mkdir(__DIR__ . '/cache/unwritable', 0555); + + $this->validCacheManager = new CacheManager(__DIR__ . '/cache/valid/'); + $this->invalidCacheManager = new CacheManager(__DIR__ . '/cache/invalid/'); + $this->emptyCacheManager = new CacheManager(__DIR__ . '/cache/empty/'); + $this->unwritableCacheManager = new CacheManager(__DIR__ . '/cache/unwritable/'); + } + + /** + * @throws InstagramException + */ + public function testEmptyCacheValueOnJsonFeed() + { + $api = new Api($this->emptyCacheManager, $this->validJsonClient); + $api->setUserName('pgrimaud'); + $api->setEndCursor('endCursor'); + $api->getFeed(); + } + + /** + * @throws InstagramException + */ + public function testInvalidCacheValueOnJsonFeed() + { + $api = new Api($this->invalidCacheManager, $this->validJsonClient); + $api->setUserName('pgrimaud'); + $api->setEndCursor('endCursor'); + $api->getFeed(); + } - $response = new Response(200, [], $invalidFixtures); - $mock = new MockHandler([$response]); - $handler = HandlerStack::create($mock); - $this->invalidClient = new Client(['handler' => $handler]); + /** + * @throws InstagramException + */ + public function testValidCacheValueOnJsonFeed() + { + $api = new Api($this->validCacheManager, $this->validJsonClient); + $api->setUserName('pgrimaud'); + $api->setEndCursor('endCursor'); + $api->getFeed(); + } - $this->cacheManager = new CacheManager(); + /** + * @throws InstagramException + */ + public function testInvalidJsonFeedReturn() + { + $this->expectException(InstagramException::class); + $api = new Api($this->validCacheManager, $this->invalidJsonClient); + $api->setUserName('pgrimaud'); + $api->setEndCursor('endCursor'); + $api->getFeed(); } /** @@ -62,7 +170,7 @@ public function testEmptyUserName() { $this->expectException(InstagramException::class); - $api = new Api($this->cacheManager, $this->validClient); + $api = new Api($this->validCacheManager, $this->validHtmlClient); $api->getFeed(); } @@ -71,7 +179,7 @@ public function testEmptyUserName() */ public function testValidFeedReturn() { - $api = new Api($this->cacheManager, $this->validClient); + $api = new Api($this->validCacheManager, $this->validHtmlClient); $api->setUserName('pgrimaud'); $feed = $api->getFeed(); @@ -82,12 +190,37 @@ public function testValidFeedReturn() /** * @throws InstagramException */ - public function testEmptyUserFeedReturn() + public function testInvalidHtmlFeedReturn() + { + $this->expectException(InstagramException::class); + + $api = new Api($this->validCacheManager, $this->invalidHtmlClient); + $api->setUserName('pgrimaud'); + $api->getFeed(); + } + + /** + * @throws InstagramException + */ + public function testValidHtmlFeedAndInvalidJsonValue() + { + $this->expectException(InstagramException::class); + + $api = new Api($this->validCacheManager, $this->invalidJsonHtmlClient); + $api->setUserName('pgrimaud'); + $api->getFeed(); + } + + /** + * @throws InstagramException + */ + public function testUnwritableCacheManager() { $this->expectException(InstagramException::class); - $api = new Api($this->cacheManager, $this->invalidClient); + $api = new Api($this->unwritableCacheManager, $this->validJsonClient); $api->setUserName('pgrimaud'); + $api->setEndCursor('endCursor'); $api->getFeed(); } @@ -96,7 +229,7 @@ public function testEmptyUserFeedReturn() */ public function testFeedContent() { - $api = new Api($this->cacheManager, $this->validClient); + $api = new Api($this->validCacheManager, $this->validHtmlClient); $api->setUserName('pgrimaud'); $feed = $api->getFeed(); @@ -125,7 +258,7 @@ public function testFeedContent() */ public function testMediaContent() { - $api = new Api($this->cacheManager, $this->validClient); + $api = new Api($this->validCacheManager, $this->validHtmlClient); $api->setUserName('pgrimaud'); /** @var Feed $feed */ diff --git a/tests/fixtures/invalid_pgrimaud.html b/tests/fixtures/invalid_pgrimaud.html new file mode 100644 index 0000000..25980f4 --- /dev/null +++ b/tests/fixtures/invalid_pgrimaud.html @@ -0,0 +1,180 @@ + + + + + + + Pierre G (@pgrimaud) • Instagram photos and videos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 6163bb1adc5ae4b7b47a3f815d92b19622d0c7ec Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 15:00:38 +0200 Subject: [PATCH 06/13] Add cache folder in tests --- .gitignore | 12 ++++++------ tests/cache/empty/.gitkeep | 0 tests/cache/invalid/.gitkeep | 0 tests/cache/invalid/demo.cache | 1 + tests/cache/valid/.gitkeep | 0 tests/cache/valid/pgrimaud.cache | 1 + 6 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 tests/cache/empty/.gitkeep create mode 100644 tests/cache/invalid/.gitkeep create mode 100644 tests/cache/invalid/demo.cache create mode 100644 tests/cache/valid/.gitkeep create mode 100644 tests/cache/valid/pgrimaud.cache diff --git a/.gitignore b/.gitignore index cf86cc2..d692344 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,9 @@ build .php_cs.cache /cache !/cache/.gitkeep -/tests/cache/ -!/tests/cache/empty/.gitkeep -!/tests/cache/invalid/.gitkeep -!/tests/cache/invalid/demo.cache -!/tests/cache/valid/.gitkeep -!/tests/cache/valid/pgrimaud.cache +#/tests/cache/ +#!/tests/cache/empty/.gitkeep +#!/tests/cache/invalid/.gitkeep +#!/tests/cache/invalid/demo.cache +#!/tests/cache/valid/.gitkeep +#!/tests/cache/valid/pgrimaud.cache \ No newline at end of file diff --git a/tests/cache/empty/.gitkeep b/tests/cache/empty/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/cache/invalid/.gitkeep b/tests/cache/invalid/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/cache/invalid/demo.cache b/tests/cache/invalid/demo.cache new file mode 100644 index 0000000..a64735d --- /dev/null +++ b/tests/cache/invalid/demo.cache @@ -0,0 +1 @@ +O:23:"Instagram\Storage\Error":3:{s:6:"rhxGis";N;s:6:"userId";N;s:6:"cookie";a:1:{i:0;s:6:"cookie";}} \ No newline at end of file diff --git a/tests/cache/valid/.gitkeep b/tests/cache/valid/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/cache/valid/pgrimaud.cache b/tests/cache/valid/pgrimaud.cache new file mode 100644 index 0000000..d61e63c --- /dev/null +++ b/tests/cache/valid/pgrimaud.cache @@ -0,0 +1 @@ +O:23:"Instagram\Storage\Cache":3:{s:6:"rhxGis";s:32:"e90a40fc67267150e65a6f2b067b5714";s:6:"userId";s:9:"184263228";s:6:"cookie";a:1:{i:0;s:6:"cookie";}} \ No newline at end of file From b8fea6305913d2410ade152e30dbd9a11866f0e0 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 15:00:46 +0200 Subject: [PATCH 07/13] Add cache folder in tests --- .gitignore | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index d692344..c51f2c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,9 @@ build .php_cs.cache /cache !/cache/.gitkeep -#/tests/cache/ -#!/tests/cache/empty/.gitkeep -#!/tests/cache/invalid/.gitkeep -#!/tests/cache/invalid/demo.cache -#!/tests/cache/valid/.gitkeep -#!/tests/cache/valid/pgrimaud.cache \ No newline at end of file +/tests/cache/ +!/tests/cache/empty/.gitkeep +!/tests/cache/invalid/.gitkeep +!/tests/cache/invalid/demo.cache +!/tests/cache/valid/.gitkeep +!/tests/cache/valid/pgrimaud.cache \ No newline at end of file From c038e37f4f70da7cca5a9c28018f7185faab551a Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 15:02:34 +0200 Subject: [PATCH 08/13] Fix tests with unwritable cache --- tests/ApiTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ApiTest.php b/tests/ApiTest.php index 373d0d3..39aed2b 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -7,6 +7,7 @@ use GuzzleHttp\Psr7\Response; use Instagram\Api; +use Instagram\Exception\CacheException; use Instagram\Exception\InstagramException; use Instagram\Hydrator\Component\Feed; use Instagram\Hydrator\Component\Media; @@ -216,7 +217,7 @@ public function testValidHtmlFeedAndInvalidJsonValue() */ public function testUnwritableCacheManager() { - $this->expectException(InstagramException::class); + $this->expectException(CacheException::class); $api = new Api($this->unwritableCacheManager, $this->validJsonClient); $api->setUserName('pgrimaud'); From c67ffaaf98a260477e1a1af9840592b2792582a6 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 15:32:51 +0200 Subject: [PATCH 09/13] Update readme.md --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0fdd7b0..591119e 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,28 @@ [![Test Coverage](https://codeclimate.com/github/pgrimaud/instagram-user-feed/badges/coverage.svg)](https://codeclimate.com/github/pgrimaud/instagram-user-feed/coverage) [![Issue Count](https://codeclimate.com/github/pgrimaud/instagram-user-feed/badges/issue_count.svg)](https://codeclimate.com/github/pgrimaud/instagram-user-feed) -## Installation +## Information +This library offers 2 packages to retrieve your or any Instagram feed without oAuth for PHP. -``` -composer require pgrimaud/instagram-user-feed -``` +## Version ^4.0 +This version can retrieve **YOUR** Instagram feed using an access token. + +- [Installation](#installation-of-version-^4.0) +- [Usage](#usage-of-version-^4.0) +- [Paginate](#paginate-for-version-^4.0) + +## Version ^5.0 +This version can retrieve **ANY** Instagram feed using web scrapping. + +- [Installation](#installation-of-version-^5.0) +- [Usage](#usage-of-version-^5.0) +- [Paginate](#paginate-for-version-^5.0) -## Warning +## Changelog -**2018-04-17 : Now fetching data with screen scraping (thanks [@cookieguru](https://github.com/cookieguru)), please upgrade to version ^5.0** +2018-04-20 : Release of version ^5.0 in parallel of version ^4.0 which still working. (Kudos for [@jannejava](https://github.com/jannejava) and [@cookieguru](https://github.com/cookieguru) + +~~2018-04-17 : Now fetching data with screen scraping (thanks [@cookieguru](https://github.com/cookieguru)), please upgrade to version ^5.0~~ ~~2018-04-16 : Now fetching data with access token, only for your account (thanks [@jannejava](https://github.com/jannejava)), please upgrade to version ^4.0~~ @@ -22,7 +35,15 @@ composer require pgrimaud/instagram-user-feed ~~2018-03-16 : Due to changes of the Instagram API, you must upgrade to version ^2.1~~ -## Usage + + +## Installation of version ^4.0 + +``` +composer require pgrimaud/instagram-user-feed "^4.0" +``` + +## Usage of version ^4.0 ### Retrieve data @@ -76,16 +97,28 @@ Instagram\Hydrator\Feed Object ) ``` -### Setting a custom User Agent -Since this method is using screen scraping, it is recommended that you spoof a user agent: +### Paginate +If you want to use paginate, retrieve `maxId` from previous call and add it to your next call. ```php -$client = new GuzzleHttp\Client(['User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0']); +// First call : -$api = new Api($client); +$api = new Api(); +$api->setUserId(184263228); +$api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); -$feed = $api->getFeed('pgrimaud'); +$feed = $api->getFeed(); + +$endCursor = $feed->getMaxId(); + +// Second call : + +$api = new Api(); +$api->setUserId(184263228); +$api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); +$api->setMaxId('1230468487398454311_184263228'); + +$feed = $api->getFeed(); // And etc... ``` - From 3dbe7725216bec6c00b3433316ba48c1865776b6 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 15:38:39 +0200 Subject: [PATCH 10/13] Update readme.md --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 591119e..a9966c2 100644 --- a/README.md +++ b/README.md @@ -35,20 +35,29 @@ This version can retrieve **ANY** Instagram feed using web scrapping. ~~2018-03-16 : Due to changes of the Instagram API, you must upgrade to version ^2.1~~ - - ## Installation of version ^4.0 ``` composer require pgrimaud/instagram-user-feed "^4.0" ``` -## Usage of version ^4.0 +1. Visit [http://instagram.pixelunion.net/](http://instagram.pixelunion.net/) and create an access token + +2. The first part of the access token is your User Id + +``` +$api = new \Instagram\Api(); -### Retrieve data +$api->setAccessToken('1234578.abcabc.abcabcabcabcabcabcabcabcabcabc'); +$api->setUserId(1234578); +``` + +**Seems like you can only access your own media until 2020.** + +## Usage of version ^4.0 ```php -$api = new Api(); +$api = new \Instagram\Api(); $feed = $api->getFeed('pgrimaud'); @@ -97,7 +106,7 @@ Instagram\Hydrator\Feed Object ) ``` -### Paginate +## Usage for version ^4.0 If you want to use paginate, retrieve `maxId` from previous call and add it to your next call. ```php From a900d68d3e49dcc60ca1340783bc08999cb5598b Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 15:48:11 +0200 Subject: [PATCH 11/13] Update readme and example --- README.md | 108 +++++++++++++++++++++++++++++++++++++---- examples/getMedias.php | 21 +++----- 2 files changed, 105 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a9966c2..de139f4 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,18 @@ This library offers 2 packages to retrieve your or any Instagram feed without oAuth for PHP. ## Version ^4.0 -This version can retrieve **YOUR** Instagram feed using an access token. +This version can retrieve **YOUR** Instagram feed using an **access token**. -- [Installation](#installation-of-version-^4.0) -- [Usage](#usage-of-version-^4.0) -- [Paginate](#paginate-for-version-^4.0) +- [Installation](#installation-of-version-40) +- [Usage](#usage-of-version-40) +- [Paginate](#paginate-for-version-40) ## Version ^5.0 -This version can retrieve **ANY** Instagram feed using web scrapping. +This version can retrieve **ANY** Instagram feed using **web scrapping**. -- [Installation](#installation-of-version-^5.0) -- [Usage](#usage-of-version-^5.0) -- [Paginate](#paginate-for-version-^5.0) +- [Installation](#installation-of-version-50) +- [Usage](#usage-of-version-50) +- [Paginate](#paginate-for-version-50) ## Changelog @@ -106,7 +106,7 @@ Instagram\Hydrator\Feed Object ) ``` -## Usage for version ^4.0 +## Paginate for version ^4.0 If you want to use paginate, retrieve `maxId` from previous call and add it to your next call. ```php @@ -131,3 +131,93 @@ $feed = $api->getFeed(); // And etc... ``` + +## Installation of version ^5.0 + +``` +composer require pgrimaud/instagram-user-feed "^5.0" +``` + +## Usage of version ^5.0 + +```php + +$cache = new Instagram\Storage\CacheManager(); + +$api = new Instagram\Api($cache); +$api->setUserName('pgrimaud'); + +$feed = $api->getFeed(); + +print_r($feed): + +``` + +```php +Instagram\Hydrator\Component\Feed Object +( + [id] => 184263228 + [userName] => pgrimaud + [fullName] => Pierre G + [biography] => Gladiator retired - ESGI 14' + [followers] => 342 + [following] => 114 + [profilePicture] => https://scontent-cdg2-1.cdninstagram.com/vp/f49bc1ac9af43314d3354b4c4a987c6d/5B5BB12E/t51.2885-19/10483606_1498368640396196_604136733_a.jpg + [externalUrl] => https://p.ier.re/ + [mediaCount] => 33 + [medias] => Array + ( + [0] => Instagram\Hydrator\Component\Media Object + ( + [id] => 1758133053345287778 + [typeName] => GraphImage + [height] => 1080 + [width] => 1080 + [thumbnailSrc] => https://scontent-cdg2-1.cdninstagram.com/vp/dd39e08d3c740e764c61bc694d36f5a7/5B643B2F/t51.2885-15/s640x640/sh0.08/e35/30604700_183885172242354_7971196573931536384_n.jpg + [link] => https://www.instagram.com/p/BhmJLJwhM5i/ + [date] => DateTime Object + ( + [date] => 2018-04-15 17:23:33.000000 + [timezone_type] => 3 + [timezone] => Europe/Paris + ) + + [displaySrc] => https://scontent-cdg2-1.cdninstagram.com/vp/51a54157b8868d715b8dd51a5ecbc46d/5B632D4E/t51.2885-15/e35/30604700_183885172242354_7971196573931536384_n.jpg + [caption] => + [comments] => 2 + [likes] => 14 + ) + + ) + + ... + + [endCursor] => AQBkklLNRIkvdOUFDHvLEZrssIcYn2TauR6cpvDgxiGJZq8mHb8ZFWNVwql1W78We0aOgfJZyQDF32yoP_h2zRKZ2iRY6zVJdDaLaGfUU23iXA +) + +``` + +## Paginate for version ^5.0 +If you want to use paginate, retrieve `endCursor` from previous call and add it to your next call. + +```php +// Initialization + +$cache = new Instagram\Storage\CacheManager(); + +$api = new Instagram\Api($cache); +$api->setUserName('pgrimaud'); + +// First call : + +$feed = $api->getFeed(); + +$endCursor = $feed->getEndCursor(); + +// Second call : + +$api->setEndCursor($endCursor); +$feed = $api->getFeed(); + +// And etc... +``` diff --git a/examples/getMedias.php b/examples/getMedias.php index 9b5462e..2a50f21 100644 --- a/examples/getMedias.php +++ b/examples/getMedias.php @@ -1,24 +1,15 @@ setUserName('pgrimaud'); -/** @var \Instagram\Hydrator\Feed $feed */ -$feed = $api->getFeed(); - -foreach ($feed->getMedias() as $media) { - echo $media->getCaption() . "\n"; +try { + /** @var \Instagram\Hydrator\Component\Feed $feed */ + $feed = $api->getFeed(); +} Catch (\Instagram\Exception\InstagramException $exception) { + print_r($exception->getMessage()); } - -$api->setEndCursor($feed->getEndCursor()); -$feed = $api->getFeed(); - -foreach ($feed->getMedias() as $media) { - echo $media->getCaption() . "\n"; -} \ No newline at end of file From 01cadb4d3c8e3d186679c1ad6f9ce359363e3caf Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 15:52:46 +0200 Subject: [PATCH 12/13] Fix typos in readme --- README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index de139f4..556f17f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This version can retrieve **ANY** Instagram feed using **web scrapping**. ## Changelog -2018-04-20 : Release of version ^5.0 in parallel of version ^4.0 which still working. (Kudos for [@jannejava](https://github.com/jannejava) and [@cookieguru](https://github.com/cookieguru) +**2018-04-20 : Release of version ^5.0 in parallel of version ^4.0 which still working. (Kudos for [@jannejava](https://github.com/jannejava) and [@cookieguru](https://github.com/cookieguru)** ~~2018-04-17 : Now fetching data with screen scraping (thanks [@cookieguru](https://github.com/cookieguru)), please upgrade to version ^5.0~~ @@ -35,7 +35,7 @@ This version can retrieve **ANY** Instagram feed using **web scrapping**. ~~2018-03-16 : Due to changes of the Instagram API, you must upgrade to version ^2.1~~ -## Installation of version ^4.0 +# Installation of version ^4.0 ``` composer require pgrimaud/instagram-user-feed "^4.0" @@ -131,8 +131,9 @@ $feed = $api->getFeed(); // And etc... ``` +___ -## Installation of version ^5.0 +# Installation of version ^5.0 ``` composer require pgrimaud/instagram-user-feed "^5.0" @@ -141,10 +142,8 @@ composer require pgrimaud/instagram-user-feed "^5.0" ## Usage of version ^5.0 ```php - $cache = new Instagram\Storage\CacheManager(); - -$api = new Instagram\Api($cache); +$api = new Instagram\Api($cache); $api->setUserName('pgrimaud'); $feed = $api->getFeed(); @@ -204,18 +203,16 @@ If you want to use paginate, retrieve `endCursor` from previous call and add it // Initialization $cache = new Instagram\Storage\CacheManager(); - -$api = new Instagram\Api($cache); +$api = new Instagram\Api($cache); $api->setUserName('pgrimaud'); // First call : $feed = $api->getFeed(); -$endCursor = $feed->getEndCursor(); - // Second call : +$endCursor = $feed->getEndCursor(); $api->setEndCursor($endCursor); $feed = $api->getFeed(); From bc595c1c5fef51a4eb4f9c7d4b230f9dfb107361 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Fri, 20 Apr 2018 16:01:36 +0200 Subject: [PATCH 13/13] Improve example --- examples/getMedias.php | 15 --------- examples/medias.php | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 15 deletions(-) delete mode 100644 examples/getMedias.php create mode 100644 examples/medias.php diff --git a/examples/getMedias.php b/examples/getMedias.php deleted file mode 100644 index 2a50f21..0000000 --- a/examples/getMedias.php +++ /dev/null @@ -1,15 +0,0 @@ -setUserName('pgrimaud'); - -try { - /** @var \Instagram\Hydrator\Component\Feed $feed */ - $feed = $api->getFeed(); -} Catch (\Instagram\Exception\InstagramException $exception) { - print_r($exception->getMessage()); -} diff --git a/examples/medias.php b/examples/medias.php new file mode 100644 index 0000000..be9647e --- /dev/null +++ b/examples/medias.php @@ -0,0 +1,69 @@ +setUserName('pgrimaud'); + +try { + // First page + + /** @var \Instagram\Hydrator\Component\Feed $feed */ + $feed = $api->getFeed(); + + echo '============================' . "\n"; + echo 'User Informations : ' . "\n"; + echo '============================' . "\n\n"; + + echo 'ID : ' . $feed->getId() . "\n"; + echo 'Full Name : ' . $feed->getFullName() . "\n"; + echo 'UserName : ' . $feed->getUserName() . "\n"; + echo 'Following : ' . $feed->getFollowing() . "\n"; + echo 'Followers : ' . $feed->getFollowers() . "\n\n"; + + echo '============================' . "\n"; + echo 'Medias first page : ' . "\n"; + echo '============================' . "\n\n"; + + /** @var \Instagram\Hydrator\Component\Media $media */ + foreach ($feed->getMedias() as $media) { + echo 'ID : ' . $media->getId() . "\n"; + echo 'Caption : ' . $media->getCaption() . "\n"; + echo 'Link : ' . $media->getLink() . "\n"; + echo 'Likes : ' . $media->getLikes() . "\n"; + echo 'Date : ' . $media->getDate()->format('Y-m-d h:i:s') . "\n"; + echo '============================' . "\n"; + } + + // Second Page + + $api->setEndCursor($feed->getEndCursor()); + + sleep(1); // avoir 429 Rate limit from Instagram + + $feed = $api->getFeed(); + + echo "\n\n"; + echo '============================' . "\n"; + echo 'Medias second page : ' . "\n"; + echo '============================' . "\n\n"; + + /** @var \Instagram\Hydrator\Component\Media $media */ + foreach ($feed->getMedias() as $media) { + echo 'ID : ' . $media->getId() . "\n"; + echo 'Caption : ' . $media->getCaption() . "\n"; + echo 'Link : ' . $media->getLink() . "\n"; + echo 'Likes : ' . $media->getLikes() . "\n"; + echo 'Date : ' . $media->getDate()->format('Y-m-d h:i:s') . "\n"; + echo '============================' . "\n"; + } + + // And etc... + +} Catch (\Instagram\Exception\InstagramException $exception) { + print_r($exception->getMessage()); +} + +// Second page \ No newline at end of file