Skip to content

Commit

Permalink
Added a method to set user agent, language and login with cookies (#304)
Browse files Browse the repository at this point in the history
* add method to set user agent and language

* add login method with cookies

* fix update session after login with cookies

* Add a method to access response of each request
  • Loading branch information
nsmle authored Aug 2, 2022
1 parent 42e016d commit 106b39e
Show file tree
Hide file tree
Showing 16 changed files with 379 additions and 47 deletions.
37 changes: 37 additions & 0 deletions examples/login-with-cookies.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

use Instagram\Api;
use Instagram\Auth\Session;
use Instagram\Utils\CacheHelper;
use Instagram\Exception\{InstagramException, InstagramAuthException};
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

require realpath(dirname(__FILE__)) . '/../vendor/autoload.php';
$credentials = include_once realpath(dirname(__FILE__)) . '/credentials.php';

$cachePool = new FilesystemAdapter('Instagram', 0, __DIR__ . '/../cache');

// Make sure you are logged in with the login() method before using this example
// See examples in https://github.com/pgrimaud/instagram-user-feed/blob/5b2358f9918b84c11b7d193f7f3205df87b35793/examples/profile.php#L17-L18
$sessionData = $cachePool->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());
}
49 changes: 46 additions & 3 deletions src/Instagram/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -62,7 +62,7 @@
TimelineDataFeed
};
use Psr\Cache\CacheItemPoolInterface;
use Instagram\Utils\InstagramHelper;
use Instagram\Utils\{InstagramHelper, OptionHelper};

class Api
{
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand Down
11 changes: 7 additions & 4 deletions src/Instagram/Auth/Checkpoint/Challenge.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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
];
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
52 changes: 45 additions & 7 deletions src/Instagram/Auth/Login.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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();

Expand All @@ -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') {
Expand All @@ -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) {
Expand All @@ -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('/<script type="text\/javascript">window\._sharedData\s?=(.+);<\/script>/', $html, $matches);

if (isset($matches[1])) {
throw new InstagramAuthException('Please login with instagram credentials.');
}

return $cookies;
}

/**
* @param CookieJar $cookieJar
* @param \StdClass $data
Expand Down
16 changes: 11 additions & 5 deletions src/Instagram/Transport/AbstractDataFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use GuzzleHttp\ClientInterface;
use Instagram\Auth\Session;
use Instagram\Exception\{InstagramAuthException, InstagramFetchException};
use Instagram\Utils\{UserAgentHelper, InstagramHelper};
use Instagram\Utils\{OptionHelper, InstagramHelper, CacheResponse};

abstract class AbstractDataFeed
{
Expand Down Expand Up @@ -43,7 +43,8 @@ protected function fetchJsonDataFeed(string $endpoint, array $headers = []): \St
{
$headers = [
'headers' => array_merge([
'user-agent' => UserAgentHelper::AGENT_DEFAULT,
'user-agent' => OptionHelper::$USER_AGENT,
'accept-language' => OptionHelper::$LOCALE,
'x-requested-with' => 'XMLHttpRequest',
], $headers),
];
Expand All @@ -53,6 +54,7 @@ protected function fetchJsonDataFeed(string $endpoint, array $headers = []): \St
}

$res = $this->client->request('GET', $endpoint, $headers);
CacheResponse::setResponse($res);

$data = (string)$res->getBody();
$data = json_decode($data);
Expand All @@ -73,10 +75,11 @@ protected function postJsonDataFeed(string $endpoint, array $formParameters = []
{
$options = [
'headers' => [
'user-agent' => UserAgentHelper::AGENT_DEFAULT,
'user-agent' => OptionHelper::$USER_AGENT,
'accept-language' => OptionHelper::$LOCALE,
'x-requested-with' => 'XMLHttpRequest',
'x-instagram-ajax' => $this->getRolloutHash(),
'x-csrftoken' => $this->session->getCookies()->getCookieByName('csrftoken')->getValue(),
'x-csrftoken' => $this->session->getCookies()->getCookieByName('csrftoken')->getValue(),
],
'cookies' => $this->session->getCookies(),
];
Expand All @@ -88,6 +91,7 @@ protected function postJsonDataFeed(string $endpoint, array $formParameters = []
}

$res = $this->client->request('POST', $endpoint, $options);
CacheResponse::setResponse($res);

$data = (string)$res->getBody();
$data = json_decode($data);
Expand All @@ -110,9 +114,11 @@ private function getRolloutHash(): string
try {
$baseRequest = $this->client->request('GET', InstagramHelper::URL_BASE, [
'headers' => [
'user-agent' => UserAgentHelper::AGENT_DEFAULT,
'user-agent' => OptionHelper::$USER_AGENT,
'accept-language' => OptionHelper::$LOCALE,
],
]);
CacheResponse::setResponse($baseRequest);

$html = (string)$baseRequest->getBody();

Expand Down
9 changes: 7 additions & 2 deletions src/Instagram/Transport/HtmlProfileDataFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use GuzzleHttp\Exception\ClientException;
use Instagram\Exception\InstagramFetchException;
use Instagram\Utils\{InstagramHelper, UserAgentHelper};
use Instagram\Utils\{InstagramHelper, OptionHelper, CacheResponse};

class HtmlProfileDataFeed extends AbstractDataFeed
{
Expand All @@ -22,7 +22,8 @@ public function fetchData(string $userName): \StdClass

$headers = [
'headers' => [
'user-agent' => UserAgentHelper::AGENT_DEFAULT,
'user-agent' => OptionHelper::$USER_AGENT,
'accept-language' => OptionHelper::$LOCALE,
],
];

Expand All @@ -33,13 +34,17 @@ public function fetchData(string $userName): \StdClass
try {
$res = $this->client->request('GET', $endpoint, $headers);
} catch (ClientException $exception) {
CacheResponse::setResponse($exception->getResponse());

if ($exception->getCode() === 404) {
throw new InstagramFetchException('User ' . $userName . ' not found');
} else {
throw new InstagramFetchException('Internal error: ' . $exception->getMessage());
}
}

CacheResponse::setResponse($res);

$html = (string)$res->getBody();

preg_match('/<script type="text\/javascript">window\._sharedData\s?=(.+);<\/script>/', $html, $matches);
Expand Down
13 changes: 8 additions & 5 deletions src/Instagram/Transport/JsonProfileAlternativeDataFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
use GuzzleHttp\Cookie\SetCookie;
use GuzzleHttp\Exception\ClientException;
use Instagram\Exception\InstagramFetchException;
use Instagram\Utils\Endpoints;
use Instagram\Utils\UserAgentHelper;
use Instagram\Utils\{Endpoints, OptionHelper, CacheResponse};

class JsonProfileAlternativeDataFeed extends AbstractDataFeed
{
Expand Down Expand Up @@ -37,19 +36,23 @@ public function fetchData(int $userId): \StdClass

$options = [
'headers' => [
'user-agent' => UserAgentHelper::AGENT_DEFAULT,
'x-csrftoken' => $csrfToken,
'x-ig-app-id' => self::IG_APP_ID,
'user-agent' => OptionHelper::$USER_AGENT,
'accept-language' => OptionHelper::$LOCALE,
'x-csrftoken' => $csrfToken,
'x-ig-app-id' => self::IG_APP_ID,
],
'cookies' => $this->session->getCookies(),
];

try {
$res = $this->client->request('GET', Endpoints::getProfileUrl($userId), $options);
} catch (ClientException $exception) {
CacheResponse::setResponse($exception->getResponse());
throw new InstagramFetchException('Reels fetch error');
}

CacheResponse::setResponse($res);

$data = (string) $res->getBody();
$data = json_decode($data);

Expand Down
Loading

0 comments on commit 106b39e

Please sign in to comment.