diff --git a/examples/like-unlike-story.php b/examples/like-unlike-story.php new file mode 100644 index 0000000..be0ab80 --- /dev/null +++ b/examples/like-unlike-story.php @@ -0,0 +1,47 @@ +login($credentials->getLogin(), $credentials->getPassword()); + + // we need instagram user id + $profile = $api->getProfile('starwars'); + sleep(1); + $feedStories = $api->getStories($profile->getId()); + + if (count($feedStories->getStories())) { + /** @var \Instagram\Model\StoryMedia $story */ + foreach ($feedStories->getStories() as $story) { + + /** Like Story */ + $likeStory = $api->likeStory($story->getId()); + echo "Like story {$story->getId()} : {$likeStory} \n"; + + /** Unlike Story * + $unlikeStory = $api->unlikeStory($story->getId()); + echo "Unlike story {$story->getId()} : {$unlikeStory} \n"; + /**/ + + } + } else { + echo 'No stories' . "\n"; + } + +} catch (InstagramException $e) { + print_r($e->getMessage()); +} catch (CacheException $e) { + print_r($e->getMessage()); +} diff --git a/examples/login-with-cookies.php b/examples/login-with-cookies.php index c9ff343..e12f541 100644 --- a/examples/login-with-cookies.php +++ b/examples/login-with-cookies.php @@ -21,8 +21,8 @@ /** 1. Get cookies from file */ $sessionId = $cachePool->getItem(Session::SESSION_KEY . '.' . CacheHelper::sanitizeUsername($credentials->getLogin())) - ->get() - ->getCookieByName('sessionId'); + ->get() + ->getCookieByName('sessionId'); // Generate CookieJar from instagram cookie 'sessionid' $cookieJar = new CookieJar(false, [$sessionId]); @@ -39,25 +39,48 @@ "Discard" => false, "HttpOnly" => true, ]); - // Generate CookieJar from instagram cookie 'sessionid' $cookieJar = new CookieJar(false, [$sessionId]); -*/ + */ try { - $api = new Api(); - + $api = new Api($cachePool); + // Optionals for set user agent and language $api->setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.57 Safari/537.36'); $api->setLanguage('id-ID'); - + $api->loginWithCookies($cookieJar); - + + /** You can use below code to auto save session + $api->loginWithCookies($cookieJar, true, $credentials->getLogin()); + */ + $profile = $api->getProfile('robertdowneyjr'); - + dd($profile); } catch (InstagramAuthException $e) { print_r($e->getMessage()); } catch (InstagramException $e) { print_r($e->getMessage()); } + + +/** Note : + * If you want to save your own cookies manually + * you can use the method below + */ + +/** login with method loginWithCookies */ +// $newCookieJar = $api->loginWithCookies($cookieJar); + +/** Save cookieJar into file */ +// $unixStringForCookieIdentification = $credentials->getLogin(); // can be replaced with any string just for cookie identification +// $sessionData = $cachePool +// ->getItem(Session::SESSION_KEY . '.' . CacheHelper::sanitizeUsername($unixStringForCookieIdentification)); +// ->set($newCookieJar); +// $cachePool->save($sessionData); + +/** use of cookies in subsequent requests after login with cookies */ +// $api->login($unixStringForCookieIdentification, ''); +// dd($api->getProfile('robertdowneyjr')); diff --git a/examples/seen-story.php b/examples/seen-story.php new file mode 100644 index 0000000..03c210e --- /dev/null +++ b/examples/seen-story.php @@ -0,0 +1,44 @@ +login($credentials->getLogin(), $credentials->getPassword()); + + // we need instagram user id + $profile = $api->getProfile('starwars'); + sleep(1); + $feedStories = $api->getStories($profile->getId()); + + if (count($feedStories->getStories())) { + /** @var \Instagram\Model\StoryMedia $story */ + foreach ($feedStories->getStories() as $story) { + $storyId = $story->getId(); + $ownerId = intval($feedStories->getOwner()->id); + $storyTakenAt = strtotime($story->getTakenAtDate()->format("Y-m-d h:i:sa")); + $storySeenAt = time(); + + $seenStory = $api->seenStory($storyId, $ownerId, $storyTakenAt, $storySeenAt); + echo "Seen story {$storyId} : {$seenStory} \n"; + } + } else { + echo 'No stories' . "\n"; + } + +} catch (InstagramException $e) { + print_r($e->getMessage()); +} catch (CacheException $e) { + print_r($e->getMessage()); +} diff --git a/src/Instagram/Api.php b/src/Instagram/Api.php index b3b9bec..29a2e41 100644 --- a/src/Instagram/Api.php +++ b/src/Instagram/Api.php @@ -9,7 +9,8 @@ use GuzzleHttp\Cookie\{SetCookie, CookieJar}; use Instagram\Auth\{Checkpoint\ImapClient, Login, Session}; use Instagram\Exception\{InstagramException, InstagramAuthException}; -use Instagram\Hydrator\{LocationHydrator, +use Instagram\Hydrator\{ + LocationHydrator, MediaHydrator, MediaCommentsHydrator, ProfileAlternativeHydrator, @@ -23,7 +24,8 @@ LiveHydrator, TimelineFeedHydrator }; -use Instagram\Model\{Location, +use Instagram\Model\{ + Location, Media, MediaDetailed, MediaComments, @@ -39,7 +41,8 @@ TaggedMediasFeed, TimelineFeed }; -use Instagram\Transport\{CommentPost, +use Instagram\Transport\{ + CommentPost, JsonMediaDetailedDataFeed, JsonMediasDataFeed, JsonMediaCommentsFeed, @@ -59,7 +62,8 @@ LocationData, LiveData, ReelsDataFeed, - TimelineDataFeed + TimelineDataFeed, + StoryInteraction }; use Psr\Cache\CacheItemPoolInterface; use Instagram\Utils\{InstagramHelper, OptionHelper}; @@ -117,9 +121,12 @@ public function setLanguage(string $language): void /** * @param \GuzzleHttp\Cookie\CookieJar $cookies * + * return \GuzzleHttp\Cookie\CookieJar + * * @throws Exception\InstagramAuthException */ - public function loginWithCookies(CookieJar $cookies): void + + public function loginWithCookies(CookieJar $cookies, bool $saveCookies = false, string $sessionKey = null): void { $login = new Login($this->client, '', '', null, $this->challengeDelay); @@ -134,6 +141,17 @@ public function loginWithCookies(CookieJar $cookies): void // Get New Cookies $cookies = $login->withCookies($session->toArray()); + if ($saveCookies) { + if (!($this->cachePool instanceof CacheItemPoolInterface)) + throw new InstagramAuthException('You must set cachePool to save this session, example: \n$cachePool = new \Symfony\Component\Cache\Adapter\FilesystemAdapter("Instagram", 0, __DIR__ . "/../cache"); \n$api = new \Instagram\Api($cachePool);'); + if (empty($sessionKey)) + throw new InstagramAuthException('You must set sessionKey, Example like your instagram username. \nE.g: (new Instagram\Api())->loginWithCookies($cookies, true, $credentials->getLogin());'); + + $sessionData = $this->cachePool->getItem(Session::SESSION_KEY . '.' . CacheHelper::sanitizeUsername($sessionKey)); + $sessionData->set($cookies); + $this->cachePool->save($sessionData); + } + $this->session = new Session($cookies); } @@ -167,7 +185,6 @@ public function login(string $username, string $password, ?ImapClient $imapClien $this->logout($username); $this->login($username, $password, $imapClient); } - } else { $cookies = $login->process(); $sessionData->set($cookies); @@ -393,6 +410,51 @@ public function getStoriesOfHighlightsFolder(StoryHighlightsFolder $folder): Sto return $hydrator->getFolder(); } + /** + * @param int $storyId + * @param int $ownerId + * @param int $takenAt + * @param int $seenAt + * + * @return string + * + * @throws Exception\InstagramAuthException + * @throws Exception\InstagramFetchException + */ + public function seenStory(int $storyId, int $ownerId, int $takenAt, int $seenAt): string + { + $storyInteraction = new StoryInteraction($this->client, $this->session); + return $storyInteraction->seen($storyId, $ownerId, $takenAt, $seenAt); + } + + /** + * @param int $storyId + * + * @return string + * + * @throws Exception\InstagramAuthException + * @throws Exception\InstagramFetchException + */ + public function likeStory(int $storyId): string + { + $storyInteraction = new StoryInteraction($this->client, $this->session); + return $storyInteraction->like($storyId); + } + + /** + * @param int $storyId + * + * @return string + * + * @throws Exception\InstagramAuthException + * @throws Exception\InstagramFetchException + */ + public function unlikeStory(int $storyId): string + { + $storyInteraction = new StoryInteraction($this->client, $this->session); + return $storyInteraction->unlike($storyId); + } + /** * @param Media $media * diff --git a/src/Instagram/Auth/Login.php b/src/Instagram/Auth/Login.php index eb02ee9..00d5929 100644 --- a/src/Instagram/Auth/Login.php +++ b/src/Instagram/Auth/Login.php @@ -155,6 +155,14 @@ public function withCookies(array $session): CookieJar } } + // get all instagram cookies + $manifestRequest = $this->client->request('GET', InstagramHelper::URL_BASE . "data/manifest.json", [ + 'headers' => [ + 'user-agent' => OptionHelper::$USER_AGENT, + ], + 'cookies' => $cookies + ]); + return $cookies; } diff --git a/src/Instagram/Transport/StoryInteraction.php b/src/Instagram/Transport/StoryInteraction.php new file mode 100644 index 0000000..f144e02 --- /dev/null +++ b/src/Instagram/Transport/StoryInteraction.php @@ -0,0 +1,109 @@ + $storyId, + 'reelMediaOwnerId' => $ownerId, + 'reelId' => $ownerId, + 'reelMediaTakenAt' => $takenAt, + 'viewSeenAt' => $seenAt + ]; + + return $this->postData(Endpoints::SEEN_STORY_URL, $params); + } + + /** + * @param int $storyId + * + * @return string + * + * @throws InstagramFetchException + */ + public function like(int $storyId): string + { + $params['media_id'] = $storyId; + return $this->postData(Endpoints::LIKE_STORY_URL, $params); + } + + /** + * @param int $storyId + * + * @return string + * + * @throws InstagramFetchException + */ + public function unlike(int $storyId): string + { + $params['media_id'] = $storyId; + return $this->postData(Endpoints::UNLIKE_STORY_URL, $params); + } + + /** + * @param string $endpoint + * @param string $formParameters + * + * @return string + * + * @throws InstagramFetchException + */ + public function postData(string $endpoint, array $formParameters = []): string + { + $csrfToken = ''; + + if (!empty($this->session->getCookies()->getCookieByName("csrftoken"))) { + $csrfToken = $this->session->getCookies()->getCookieByName("csrftoken")->getValue(); + } + + $options = [ + 'headers' => [ + 'user-agent' => OptionHelper::$USER_AGENT, + 'accept-language' => OptionHelper::$LOCALE, + 'x-csrftoken' => $csrfToken, + 'x-ig-app-id' => 1217981644879628, + ], + 'cookies' => $this->session->getCookies(), + ]; + + if (count($formParameters) > 0) { + $options = array_merge($options, [ + 'form_params' => $formParameters, + ]); + } + + try { + $res = $this->client->request('POST', $endpoint, $options); + } catch (ClientException $exception) { + throw new InstagramFetchException("StoryInteraction error, {$exception->getMessage()}"); + } + + $data = (string) $res->getBody(); + $data = json_decode($data); + + if ($data === null) { + throw new InstagramAuthException('StoryInteraction error, Unable to get JSON data!'); + } + + return $data->status; + } +} diff --git a/src/Instagram/Utils/Endpoints.php b/src/Instagram/Utils/Endpoints.php index 315aa77..7211c33 100644 --- a/src/Instagram/Utils/Endpoints.php +++ b/src/Instagram/Utils/Endpoints.php @@ -24,6 +24,12 @@ class Endpoints public const TIMELINE_URL = 'https://i.instagram.com/api/v1/feed/timeline/'; + const SEEN_STORY_URL = 'https://i.instagram.com/api/v1/stories/reel/seen/'; + + const LIKE_STORY_URL = 'https://i.instagram.com/api/v1/story_interactions/send_story_like'; + + const UNLIKE_STORY_URL = 'https://i.instagram.com/api/v1/story_interactions/unsend_story_like'; + /** * @param int $accountId * diff --git a/tests/LoginWithCookiesTest.php b/tests/LoginWithCookiesTest.php index d587a49..7590f22 100644 --- a/tests/LoginWithCookiesTest.php +++ b/tests/LoginWithCookiesTest.php @@ -5,7 +5,7 @@ use GuzzleHttp\{Client, Cookie\CookieJar, Cookie\SetCookie, Handler\MockHandler, HandlerStack, Psr7\Response}; use Instagram\Api; -use Instagram\Auth\{ Login, Session }; +use Instagram\Auth\{Login, Session}; use Instagram\Exception\InstagramAuthException; use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\FilesystemAdapter; @@ -26,6 +26,7 @@ public function testSucceededLoginWithCookie() $mock = new MockHandler([ new Response(200, [], file_get_contents(__DIR__ . '/fixtures/login-success.json')), + new Response(200, [], file_get_contents(__DIR__ . '/fixtures/manifest.json')), new Response(200, [], file_get_contents(__DIR__ . '/fixtures/profile.json')), ]); @@ -58,7 +59,7 @@ public function testLoginWithCookiesWithExpiredSession() $api = new Api(); $api->loginWithCookies($cookiesJar); } - + public function testLoginWithCookiesInvalid() { $this->expectException(InstagramAuthException::class); @@ -84,4 +85,4 @@ public function testLoginWithCookiesInvalid() $api = new Api(null, $client); $api->loginWithCookies($cookiesJar); } -} \ No newline at end of file +} diff --git a/tests/fixtures/manifest.json b/tests/fixtures/manifest.json new file mode 100644 index 0000000..b15a3dd --- /dev/null +++ b/tests/fixtures/manifest.json @@ -0,0 +1 @@ +{"name":"Instagram","short_name":"Instagram","description":"Instagram is a simple way to capture and share the world's moments.","start_url":"/?utm_source=pwa_homescreen","orientation":"portrait","display":"standalone","background_color":"#ffffff","theme_color":"#ffffff","prompt_message":"Add a Home screen icon for easy access to Instagram","icons":[{"src":"/static/images/ico/xxhdpi_launcher.png/99cf3909d459.png","sizes":"144x144","type":"image/png"},{"src":"/static/images/ico/xxxhdpi_launcher.png/9fc4bab7565b.png","sizes":"192x192","type":"image/png"}],"gcm_sender_id":"524223308106","gcm_user_visible_only":true,"related_applications":[{"platform":"play","id":"com.instagram.lite"},{"platform":"play","id":"com.instagram.android"}],"status":"ok"} \ No newline at end of file