diff --git a/examples/login-with-cookies.php b/examples/login-with-cookies.php new file mode 100644 index 0000000..6328664 --- /dev/null +++ b/examples/login-with-cookies.php @@ -0,0 +1,37 @@ +getItem(Session::SESSION_KEY . '.' . CacheHelper::sanitizeUsername($credentials->getLogin())); +$cookies = $sessionData->get(); + +try { + $api = new Api(); + + // 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($cookies); + + $profile = $api->getProfile('robertdowneyjr'); + + dd($profile); +} catch (InstagramAuthException $e) { + print_r($e->getMessage()); +} catch (InstagramException $e) { + print_r($e->getMessage()); +} diff --git a/src/Instagram/Api.php b/src/Instagram/Api.php index c9a01f7..497cf1d 100644 --- a/src/Instagram/Api.php +++ b/src/Instagram/Api.php @@ -8,7 +8,7 @@ use Instagram\Utils\CacheHelper; use GuzzleHttp\Cookie\{SetCookie, CookieJar}; use Instagram\Auth\{Checkpoint\ImapClient, Login, Session}; -use Instagram\Exception\InstagramException; +use Instagram\Exception\{InstagramException, InstagramAuthException}; use Instagram\Hydrator\{LocationHydrator, MediaHydrator, MediaCommentsHydrator, @@ -62,7 +62,7 @@ TimelineDataFeed }; use Psr\Cache\CacheItemPoolInterface; -use Instagram\Utils\InstagramHelper; +use Instagram\Utils\{InstagramHelper, OptionHelper}; class Api { @@ -91,13 +91,52 @@ class Api * @param ClientInterface|null $client * @param int|null $challengeDelay */ - public function __construct(CacheItemPoolInterface $cachePool, ClientInterface $client = null, ?int $challengeDelay = 3) + public function __construct(CacheItemPoolInterface $cachePool = null, ClientInterface $client = null, ?int $challengeDelay = 3) { $this->cachePool = $cachePool; $this->client = $client ?: new Client(); $this->challengeDelay = $challengeDelay; } + /** + * @param string $userAgent + */ + public function setUserAgent(string $userAgent): void + { + OptionHelper::$USER_AGENT = $userAgent; + } + + /** + * @param string $language + */ + public function setLanguage(string $language): void + { + OptionHelper::$LOCALE = $language; + } + + /** + * @param \GuzzleHttp\Cookie\CookieJar $cookies + * + * @throws Exception\InstagramAuthException + */ + public function loginWithCookies(CookieJar $cookies): void + { + $login = new Login($this->client, '', '', null, $this->challengeDelay); + + /** @var SetCookie */ + $session = $cookies->getCookieByName('sessionId'); + + // Session expired (should never happened, Instagram TTL is ~ 1 year) + if ($session->getExpires() < time()) { + throw new InstagramAuthException('Session expired, Please login with instagram credentials.'); + } + + // Get New Cookies + $cookies = $login->withCookies($session->toArray()); + + $this->session = new Session($cookies); + } + /** * @param string $username * @param string $password @@ -111,6 +150,10 @@ public function login(string $username, string $password, ?ImapClient $imapClien { $login = new Login($this->client, $username, $password, $imapClient, $this->challengeDelay); + if ( !($this->cachePool instanceof CacheItemPoolInterface) ) { + throw new InstagramAuthException('You must set cachePool / login with cookies, example: \n$cachePool = new \Symfony\Component\Cache\Adapter\FilesystemAdapter("Instagram", 0, __DIR__ . "/../cache"); \n$api = new \Instagram\Api($cachePool);'); + } + // fetch previous session and re-use it $sessionData = $this->cachePool->getItem(Session::SESSION_KEY . '.' . CacheHelper::sanitizeUsername($username)); $cookies = $sessionData->get(); diff --git a/src/Instagram/Auth/Checkpoint/Challenge.php b/src/Instagram/Auth/Checkpoint/Challenge.php index cc57539..84a2506 100644 --- a/src/Instagram/Auth/Checkpoint/Challenge.php +++ b/src/Instagram/Auth/Checkpoint/Challenge.php @@ -7,7 +7,7 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\Cookie\CookieJar; use Instagram\Exception\InstagramAuthException; -use Instagram\Utils\{InstagramHelper, UserAgentHelper}; +use Instagram\Utils\{InstagramHelper, OptionHelper}; class Challenge { @@ -59,7 +59,8 @@ public function fetchChallengeContent(): \StdClass { $headers = [ 'headers' => [ - 'user-agent' => UserAgentHelper::AGENT_DEFAULT, + 'user-agent' => OptionHelper::$USER_AGENT, + 'accept-language' => OptionHelper::$LOCALE, ], 'cookies' => $this->cookieJar ]; @@ -110,7 +111,8 @@ public function sendSecurityCode(\StdClass $challengeContent, string $url = '') 'x-instagram-ajax' => $challengeContent->rollout_hash, 'content-type' => 'application/x-www-form-urlencoded', 'accept' => '*/*', - 'user-agent' => UserAgentHelper::AGENT_DEFAULT, + 'user-agent' => OptionHelper::$USER_AGENT, + 'accept-language' => OptionHelper::$LOCALE, 'x-requested-with' => 'XMLHttpRequest', 'x-csrftoken' => $challengeContent->config->csrf_token, 'x-ig-app-id' => 123456889, @@ -175,7 +177,8 @@ public function submitSecurityCode(\StdClass $challengeContent, string $code): C 'x-instagram-ajax' => $challengeContent->rollout_hash, 'content-type' => 'application/x-www-form-urlencoded', 'accept' => '*/*', - 'user-agent' => UserAgentHelper::AGENT_DEFAULT, + 'user-agent' => OptionHelper::$USER_AGENT, + 'accept-language' => OptionHelper::$LOCALE, 'x-requested-with' => 'XMLHttpRequest', 'x-csrftoken' => $challengeContent->config->csrf_token, 'x-ig-app-id' => 123456889, diff --git a/src/Instagram/Auth/Login.php b/src/Instagram/Auth/Login.php index 18d547c..7f3a370 100644 --- a/src/Instagram/Auth/Login.php +++ b/src/Instagram/Auth/Login.php @@ -4,11 +4,11 @@ namespace Instagram\Auth; -use GuzzleHttp\{ClientInterface, Cookie\CookieJar}; +use GuzzleHttp\{ClientInterface, Cookie\SetCookie, Cookie\CookieJar}; use GuzzleHttp\Exception\ClientException; use Instagram\Auth\Checkpoint\{Challenge, ImapClient}; use Instagram\Exception\InstagramAuthException; -use Instagram\Utils\{InstagramHelper, UserAgentHelper}; +use Instagram\Utils\{InstagramHelper, OptionHelper, CacheResponse}; class Login { @@ -63,9 +63,10 @@ public function process(): CookieJar { $baseRequest = $this->client->request('GET', InstagramHelper::URL_BASE, [ 'headers' => [ - 'user-agent' => UserAgentHelper::AGENT_DEFAULT, + 'user-agent' => OptionHelper::$USER_AGENT, ], ]); + CacheResponse::setResponse($baseRequest); $html = (string) $baseRequest->getBody(); @@ -86,14 +87,17 @@ public function process(): CookieJar 'enc_password' => '#PWD_INSTAGRAM_BROWSER:0:' . time() . ':' . $this->password, ], 'headers' => [ - 'cookie' => 'ig_cb=1; csrftoken=' . $data->config->csrf_token, - 'referer' => InstagramHelper::URL_BASE, - 'x-csrftoken' => $data->config->csrf_token, - 'user-agent' => UserAgentHelper::AGENT_DEFAULT, + 'cookie' => 'ig_cb=1; csrftoken=' . $data->config->csrf_token, + 'referer' => InstagramHelper::URL_BASE, + 'x-csrftoken' => $data->config->csrf_token, + 'user-agent' => OptionHelper::$USER_AGENT, + 'accept-language' => OptionHelper::$LOCALE, ], 'cookies' => $cookieJar, ]); } catch (ClientException $exception) { + CacheResponse::setResponse($exception->getResponse()); + $data = json_decode((string) $exception->getResponse()->getBody()); if ($data && $data->message === 'checkpoint_required') { @@ -105,6 +109,8 @@ public function process(): CookieJar } } + CacheResponse::setResponse($query); + $response = json_decode((string) $query->getBody()); if (property_exists($response, 'authenticated') && $response->authenticated == true) { @@ -116,6 +122,38 @@ public function process(): CookieJar } } + /** + * @param \array $session + * + * @return CookieJar + * + * @throws InstagramAuthException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function withCookies(array $session): CookieJar + { + $cookies = new CookieJar(true, [$session]); + + $baseRequest = $this->client->request('GET', InstagramHelper::URL_BASE, [ + 'headers' => [ + 'user-agent' => OptionHelper::$USER_AGENT, + ], + 'cookies' => $cookies + ]); + + CacheResponse::setResponse($baseRequest); + + $html = (string) $baseRequest->getBody(); + + preg_match('/