diff --git a/examples/addAndDeleteComment.php b/examples/addAndDeleteComment.php index b1b34501..ca1aae97 100644 --- a/examples/addAndDeleteComment.php +++ b/examples/addAndDeleteComment.php @@ -1,9 +1,10 @@ login(); try { @@ -12,7 +13,7 @@ $comment = $instagram->addComment($mediaId, 'Text 1'); // replied to comment $instagram->addComment($mediaId, 'Text 2', $comment); - + $instagram->deleteComment($mediaId, $comment); } catch (InstagramException $ex) { echo $ex->getMessage(); diff --git a/examples/getAccountFollowers.php b/examples/getAccountFollowers.php index 9d880b7b..74e6ce24 100644 --- a/examples/getAccountFollowers.php +++ b/examples/getAccountFollowers.php @@ -1,7 +1,9 @@ login(); sleep(2); // Delay to mimic user @@ -10,4 +12,4 @@ $account = $instagram->getAccount($username); sleep(1); $followers = $instagram->getFollowers($account->getId(), 1000, 100, true); // Get 1000 followers of 'kevin', 100 a time with random delay between requests -echo '
' . json_encode($followers, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . ''; \ No newline at end of file +echo '
' . json_encode($followers, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . ''; diff --git a/examples/getAccountFollowings.php b/examples/getAccountFollowings.php index 80adb6a2..27e29290 100644 --- a/examples/getAccountFollowings.php +++ b/examples/getAccountFollowings.php @@ -1,7 +1,9 @@ login(); sleep(2); // Delay to mimic user @@ -10,4 +12,4 @@ $account = $instagram->getAccount($username); sleep(1); $followers = $instagram->getFollowing($account->getId(), 1000, 100, true); // Get 1000 followings of 'kevin', 100 a time with random delay between requests -echo '
' . json_encode($followers, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . ''; \ No newline at end of file +echo '
' . json_encode($followers, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . ''; diff --git a/examples/getAccountMediasByUsername.php b/examples/getAccountMediasByUsername.php index f7765617..b55681df 100644 --- a/examples/getAccountMediasByUsername.php +++ b/examples/getAccountMediasByUsername.php @@ -1,4 +1,6 @@ login(); $medias = $instagram->getMedias('private_account', 100); diff --git a/examples/getCurrentTopMediasByLocationId.php b/examples/getCurrentTopMediasByLocationId.php index aaf0bb65..5523cd1a 100644 --- a/examples/getCurrentTopMediasByLocationId.php +++ b/examples/getCurrentTopMediasByLocationId.php @@ -1,7 +1,9 @@ login(); $medias = $instagram->getCurrentTopMediasByLocationId('1'); diff --git a/examples/getCurrentTopMediasByTagName.php b/examples/getCurrentTopMediasByTagName.php index 79435caa..1f38eb51 100644 --- a/examples/getCurrentTopMediasByTagName.php +++ b/examples/getCurrentTopMediasByTagName.php @@ -1,7 +1,9 @@ login(); $medias = $instagram->getCurrentTopMediasByTagName('youneverknow'); diff --git a/examples/getHighlights.php b/examples/getHighlights.php index d577326f..79286e0a 100644 --- a/examples/getHighlights.php +++ b/examples/getHighlights.php @@ -1,7 +1,9 @@ login(); $userId = $instagram->getAccount('instagram')->getId(); diff --git a/examples/getLocationById.php b/examples/getLocationById.php index 35fe2b26..35be50b1 100644 --- a/examples/getLocationById.php +++ b/examples/getLocationById.php @@ -1,7 +1,9 @@ login(); // Location id from facebook diff --git a/examples/getMediaByCode.php b/examples/getMediaByCode.php index c6c3295d..617c4112 100644 --- a/examples/getMediaByCode.php +++ b/examples/getMediaByCode.php @@ -1,9 +1,11 @@ login(); $media = $instagram->getMediaByCode('BHaRdodBouH'); diff --git a/examples/getMediaById.php b/examples/getMediaById.php index d245d393..5e47c8d8 100644 --- a/examples/getMediaById.php +++ b/examples/getMediaById.php @@ -1,11 +1,13 @@ login(); $media = $instagram->getMediaById('1270593720437182847'); diff --git a/examples/getMediaByUrl.php b/examples/getMediaByUrl.php index 87b9cff2..b16839f1 100644 --- a/examples/getMediaByUrl.php +++ b/examples/getMediaByUrl.php @@ -1,11 +1,13 @@ login(); $media = $instagram->getMediaByUrl('https://www.instagram.com/p/BHaRdodBouH'); diff --git a/examples/getMediaComments.php b/examples/getMediaComments.php index fb339795..d841a9b1 100644 --- a/examples/getMediaComments.php +++ b/examples/getMediaComments.php @@ -1,7 +1,9 @@ login(); // Get media comments by shortcode diff --git a/examples/getMediasByLocationId.php b/examples/getMediasByLocationId.php index 47bbd5f1..12372207 100644 --- a/examples/getMediasByLocationId.php +++ b/examples/getMediasByLocationId.php @@ -1,7 +1,9 @@ login(); $medias = $instagram->getMediasByLocationId('1', 20); diff --git a/examples/getMediasByTag.php b/examples/getMediasByTag.php index 45ee606c..43e2025c 100644 --- a/examples/getMediasByTag.php +++ b/examples/getMediasByTag.php @@ -1,7 +1,9 @@ login(); $medias = $instagram->getMediasByTag('youneverknow', 20); diff --git a/examples/getPaginateMediasByTag.php b/examples/getPaginateMediasByTag.php index 311d1d62..43fc02ba 100644 --- a/examples/getPaginateMediasByTag.php +++ b/examples/getPaginateMediasByTag.php @@ -1,7 +1,9 @@ login(); $result = $instagram->getPaginateMediasByTag('zara'); @@ -12,4 +14,4 @@ $medias = array_merge($medias, $result['medias']); } -echo json_encode($medias); \ No newline at end of file +echo json_encode($medias); diff --git a/examples/getSidecarMediaByUrl.php b/examples/getSidecarMediaByUrl.php index a3dd77fa..b8e718da 100644 --- a/examples/getSidecarMediaByUrl.php +++ b/examples/getSidecarMediaByUrl.php @@ -1,4 +1,6 @@ login(); $media = $instagram->getMediaByUrl('https://www.instagram.com/p/BQ0lhTeAYo5'); diff --git a/examples/getStories.php b/examples/getStories.php index 13a36919..c602ccf2 100644 --- a/examples/getStories.php +++ b/examples/getStories.php @@ -1,8 +1,10 @@ login(); $stories = $instagram->getStories(); -print_r($stories); \ No newline at end of file +print_r($stories); diff --git a/examples/getThreads.php b/examples/getThreads.php new file mode 100644 index 00000000..84653fa7 --- /dev/null +++ b/examples/getThreads.php @@ -0,0 +1,47 @@ +login(); +$instagram->saveSession(); + +$threads = $instagram->getThreads(10, 10, 20); +$thread = $threads[0]; + +echo "Thread Info:\n"; +echo "Id: {$thread->getId()}\n"; +echo "Title: {$thread->getTitle()}\n"; +echo "Type: {$thread->getType()}\n"; +echo "Read State: {$thread->getReadState()}\n\n"; + +$items = $thread->getItems(); +$item = $items[0]; + +echo "Item Info:\n"; +echo "Id: {$item->getId()}\n"; +echo "Type: {$item->getType()}\n"; +echo "Time: {$item->getTime()}\n"; +echo "User ID: {$item->getUserId()}\n"; +echo "Text: {$item->getText()}\n\n"; + +$reelShare = $item->getReelShare(); + +echo "Reel Share Info:\n"; +echo "Text: {$reelShare->getText()}\n"; +echo "Type: {$reelShare->getType()}\n"; +echo "Owner Id: {$reelShare->getOwnerId()}\n"; +echo "Mentioned Id: {$reelShare->getMentionedId()}\n\n"; + +$reelMedia = $reelShare->getMedia(); + +echo "Reel Media Info:\n"; +echo "Id: {$reelMedia->getId()}\n"; +echo "Caption: {$reelMedia->getCaption()}\n"; +echo "Code: {$reelMedia->getCode()}\n"; +echo "Expiring At: {$reelMedia->getExpiringAt()}\n"; +echo "Image: {$reelMedia->getImage()}\n\n"; + +// $user = $reelMedia->getUser(); // InstagramScraper\Model\Account +// $mentions = $reelMedia->getMentions(); // InstagramScraper\Model\Account[] diff --git a/examples/likeAndUnlikeMedia.php b/examples/likeAndUnlikeMedia.php index 612d3f21..55b3d165 100644 --- a/examples/likeAndUnlikeMedia.php +++ b/examples/likeAndUnlikeMedia.php @@ -1,10 +1,10 @@ login(); try { diff --git a/examples/paginateAccountMediaByUsername.php b/examples/paginateAccountMediaByUsername.php index d9787947..aa862cd7 100644 --- a/examples/paginateAccountMediaByUsername.php +++ b/examples/paginateAccountMediaByUsername.php @@ -1,9 +1,9 @@ login(); $result = $instagram->getPaginateMedias('kevin'); diff --git a/examples/searchAccountsByUsername.php b/examples/searchAccountsByUsername.php index f57002e0..6b25f512 100644 --- a/examples/searchAccountsByUsername.php +++ b/examples/searchAccountsByUsername.php @@ -1,7 +1,9 @@ login(); diff --git a/examples/setCustomCookies.php b/examples/setCustomCookies.php index 94698a37..2ee21186 100644 --- a/examples/setCustomCookies.php +++ b/examples/setCustomCookies.php @@ -1,4 +1,5 @@ "36*****872", ]; -$instagram = \InstagramScraper\Instagram::withCredentials($this->instaUsername, $this->instaPassword,new Psr16Adapter('Files') ); +$instagram = \InstagramScraper\Instagram::withCredentials($this->instaUsername, $this->instaPassword, new Psr16Adapter('Files')); $instagram->setUserAgent('User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0'); $instagram->setCustomCookies($newCookie); $instagram->login(); diff --git a/src/InstagramScraper/Endpoints.php b/src/InstagramScraper/Endpoints.php index 53943dfb..08c14dbd 100644 --- a/src/InstagramScraper/Endpoints.php +++ b/src/InstagramScraper/Endpoints.php @@ -35,6 +35,7 @@ class Endpoints const DELETE_COMMENT_URL = 'https://www.instagram.com/web/comments/{mediaId}/delete/{commentId}/'; const ACCOUNT_MEDIAS2 = 'https://www.instagram.com/graphql/query/?query_id=17880160963012870&id={{accountId}}&first=10&after='; const HIGHLIGHT_URL = 'https://www.instagram.com/graphql/query/?query_hash=c9100bf9110dd6361671f113dd02e7d6&variables={"user_id":"{userId}","include_chaining":false,"include_reel":true,"include_suggested_users":false,"include_logged_out_extras":false,"include_highlight_reels":true,"include_live_status":false}'; + const THREADS_URL = 'https://www.instagram.com/direct_v2/web/inbox/?persistentBadging=true&folder=&limit={limit}&thread_message_limit={messageLimit}&cursor={cursor}'; // Look alike?? const URL_SIMILAR = 'https://www.instagram.com/graphql/query/?query_id=17845312237175864&id=4663052'; @@ -216,4 +217,15 @@ public static function getHighlightUrl($id) { return str_replace('{userId}', urlencode($id), static::HIGHLIGHT_URL); } + + public static function getThreadsUrl($limit, $messageLimit, $cursor) + { + $url = static::THREADS_URL; + + $url = str_replace('{limit}', $limit, $url); + $url = str_replace('{messageLimit}', $messageLimit, $url); + $url = str_replace('{cursor}', $cursor, $url); + + return $url; + } } diff --git a/src/InstagramScraper/Instagram.php b/src/InstagramScraper/Instagram.php index 82ebcb3a..d4fbd731 100644 --- a/src/InstagramScraper/Instagram.php +++ b/src/InstagramScraper/Instagram.php @@ -17,6 +17,7 @@ use InstagramScraper\Model\Media; use InstagramScraper\Model\Story; use InstagramScraper\Model\Tag; +use InstagramScraper\Model\Thread; use InstagramScraper\Model\UserStories; use InstagramScraper\Model\Highlight; use InstagramScraper\TwoStepVerification\ConsoleVerification; @@ -40,6 +41,8 @@ class Instagram const PAGING_DELAY_MINIMUM_MICROSEC = 1000000; // 1 sec min delay to simulate browser const PAGING_DELAY_MAXIMUM_MICROSEC = 3000000; // 3 sec max delay to simulate browser + const X_IG_APP_ID = '936619743392459'; + /** @var CacheInterface $instanceCache */ private static $instanceCache = null; @@ -60,7 +63,7 @@ class Instagram * * @return Instagram */ - public static function withCredentials($username, $password, CacheInterface $cache) + public static function withCredentials($username, $password, $cache) { static::$instanceCache = $cache; $instance = new self(); @@ -1998,4 +2001,77 @@ public function getHighlights($userId) } return $highlights; } + + /** + * @param int $limit + * @param int $messageLimit + * @param string|null $cursor + * + * @return array + * @throws InstagramException + */ + public function getPaginateThreads($limit = 10, $messageLimit = 10, $cursor = null) + { + $response = Request::get( + Endpoints::getThreadsUrl($limit, $messageLimit, $cursor), + array_merge( + ['x-ig-app-id' => self::X_IG_APP_ID], + $this->generateHeaders($this->userSession) + ) + ); + + if ($response->code !== static::HTTP_OK) { + throw new InstagramException('Response code is ' . $response->code . '. Body: ' . static::getErrorBody($response->body) . ' Something went wrong. Please report issue.'); + } + + $jsonResponse = $this->decodeRawBodyToJson($response->raw_body); + + if (!isset($jsonResponse['status']) || $jsonResponse['status'] !== 'ok') { + throw new InstagramException('Response code is not equal 200. Something went wrong. Please report issue.'); + } + + if (!isset($jsonResponse['inbox']['threads']) || empty($jsonResponse['inbox']['threads'])) { + return []; + } + + $threads = []; + + foreach ($jsonResponse['inbox']['threads'] as $jsonThread) { + $threads[] = Thread::create($jsonThread); + } + + return [ + 'hasOlder' => (bool) $jsonResponse['inbox']['has_older'], + 'oldestCursor' => isset($jsonResponse['inbox']['oldest_cursor']) ? $jsonResponse['inbox']['oldest_cursor'] : null, + 'threads' => $threads, + ]; + } + + /** + * @param int $count + * @param int $limit + * @param int $messageLimit + * + * @return Thread[] + * @throws InstagramException + */ + public function getThreads($count = 10, $limit = 10, $messageLimit = 10) + { + $threads = []; + $cursor = null; + + while (count($threads) < $count) { + $result = $this->getPaginateThreads($limit, $messageLimit, $cursor); + + $threads = array_merge($threads, $result['threads']); + + if (!$result['hasOlder'] || !$result['oldestCursor']) { + break; + } + + $cursor = $result['oldestCursor']; + } + + return $threads; + } } diff --git a/src/InstagramScraper/Model/ReelMedia.php b/src/InstagramScraper/Model/ReelMedia.php new file mode 100644 index 00000000..a900f860 --- /dev/null +++ b/src/InstagramScraper/Model/ReelMedia.php @@ -0,0 +1,146 @@ +id; + } + + /** + * @return string + */ + public function getCaption() + { + return $this->caption; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @return string + */ + public function getExpiringAt() + { + return $this->expiringAt; + } + + /** + * @return string + */ + public function getImage() + { + return $this->image; + } + + /** + * @return Account + */ + public function getUser() + { + return $this->user; + } + + /** + * @return Account[] + */ + public function getMentions() + { + return $this->mentions; + } + + /** + * @param $value + * @param $prop + * @param $arr + */ + protected function initPropertiesCustom($value, $prop, $arr) + { + switch ($prop) { + case 'id': + $this->id = $value; + break; + case 'caption': + $this->caption = isset($arr[$prop]['text']) ? $arr[$prop]['text'] : null; + break; + case 'code': + $this->code = $value; + break; + case 'expiring_at': + $this->expiringAt = (int)$value; + break; + case 'reel_mentions': + foreach ($arr[$prop] as $mention) { + if (!$mention['user']) { + continue; + } + + $this->mentions[] = Account::create($mention['user']); + } + break; + case 'user': + $this->user = Account::create($arr[$prop]); + break; + case 'image_versions2': + foreach ($arr[$prop]['candidates'] as $candidate) { + if (!$candidate['url']) { + continue; + } + + $this->image = $candidate['url']; + break; + } + break; + } + } +} diff --git a/src/InstagramScraper/Model/ReelShare.php b/src/InstagramScraper/Model/ReelShare.php new file mode 100644 index 00000000..971505fe --- /dev/null +++ b/src/InstagramScraper/Model/ReelShare.php @@ -0,0 +1,101 @@ +text; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getMentionedId() + { + return $this->mentionedId; + } + + /** + * @return int + */ + public function getOwnerId() + { + return $this->ownerId; + } + + /** + * @return ReelMedia + */ + public function getMedia() + { + return $this->media; + } + + /** + * @param $value + * @param $prop + * @param $arr + */ + protected function initPropertiesCustom($value, $prop, $arr) + { + switch ($prop) { + case 'text': + $this->text = $value; + break; + case 'type': + $this->type = $value; + break; + case 'mentioned_user_id': + $this->mentionedId = (int) $value; + break; + case 'reel_owner_id': + $this->ownerId = (int) $value; + break; + case 'media': + $this->media = ReelMedia::create($arr[$prop]); + break; + } + } +} diff --git a/src/InstagramScraper/Model/Thread.php b/src/InstagramScraper/Model/Thread.php new file mode 100644 index 00000000..b3b2572d --- /dev/null +++ b/src/InstagramScraper/Model/Thread.php @@ -0,0 +1,183 @@ +id; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return boolean + */ + public function isArchived() + { + return $this->archived; + } + + /** + * @return boolean + */ + public function hasNewer() + { + return $this->hasNewer; + } + + /** + * @return boolean + */ + public function hasOlder() + { + return $this->hasOlder; + } + + /** + * @return boolean + */ + public function isGroup() + { + return $this->isGroup; + } + + /** + * @return boolean + */ + public function isPin() + { + return $this->isPin; + } + + /** + * @return boolean + */ + public function getReadState() + { + return $this->readState; + } + + /** + * @return ThreadItem[] + */ + public function getItems() + { + return $this->items; + } + + /** + * @param $value + * @param $prop + * @param $arr + */ + protected function initPropertiesCustom($value, $prop, $arr) + { + switch ($prop) { + case 'thread_id': + $this->id = $value; + break; + case 'thread_title': + $this->title = $value; + break; + case 'thread_type': + $this->type = $value; + break; + case 'archived': + $this->archived = (bool)$value; + break; + case 'has_newer': + $this->hasNewer = (bool)$value; + break; + case 'has_older': + $this->hasOlder = (bool)$value; + break; + case 'is_group': + $this->isGroup = (bool)$value; + break; + case 'is_pin': + $this->isPin = (bool)$value; + break; + case 'read_state': + $this->readState = (bool)$value; + break; + case 'items': + foreach ($arr[$prop] as $item) { + $this->items[] = ThreadItem::create($item); + } + break; + } + } +} diff --git a/src/InstagramScraper/Model/ThreadItem.php b/src/InstagramScraper/Model/ThreadItem.php new file mode 100644 index 00000000..3abdd97e --- /dev/null +++ b/src/InstagramScraper/Model/ThreadItem.php @@ -0,0 +1,121 @@ +id; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @return int + */ + public function getTime() + { + return $this->time; + } + + /** + * @return string + */ + public function getUserId() + { + return $this->userId; + } + + /** + * @return ReelShare + */ + public function getReelShare() + { + return $this->reelShare; + } + + /** + * @param $value + * @param $prop + * @param $arr + */ + protected function initPropertiesCustom($value, $prop, $arr) + { + switch ($prop) { + case 'item_id': + $this->id = $value; + break; + case 'item_type': + $this->type = $value; + break; + case 'text': + if ($arr['item_type'] === 'text') { + $this->text = $value; + } + break; + case 'reel_share': + if ($arr['item_type'] === 'reel_share') { + $this->reelShare = ReelShare::create($arr[$prop]); + } + break; + case 'timestamp': + $this->time = (int) ($value / 1000000); + break; + case 'user_id': + $this->userId = $value; + break; + } + } +}