Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
6 contributors

Users who have contributed to this file

@valga @mgp25 @JRoy @sh2ezo @Pezhvak @aitte2
1551 lines (1466 sloc) 63.3 KB
<?php
namespace InstagramAPI\Request;
use InstagramAPI\Constants;
use InstagramAPI\Exception\InstagramException;
use InstagramAPI\Exception\ThrottledException;
use InstagramAPI\Exception\UploadFailedException;
use InstagramAPI\Media\Constraints\ConstraintsFactory;
use InstagramAPI\Media\Photo\PhotoDetails;
use InstagramAPI\Request\Metadata\Internal as InternalMetadata;
use InstagramAPI\Response;
use InstagramAPI\Signatures;
use InstagramAPI\Utils;
/**
* Instagram Direct messaging functions.
*
* Be aware that many of the functions can take either a list of users or a
* thread ID as their "recipient". If a thread already exists with those
* user(s), you MUST use the "thread" recipient method (otherwise Instagram
* rejects your bad API call). If no thread exists yet, you MUST use the
* "users" recipient method a SINGLE time to create the thread first!
*/
class Direct extends RequestCollection
{
/**
* Get direct inbox messages for your account.
*
* @param string|null $cursorId Next "cursor ID", used for pagination.
* @param int $limit Number of threads. From 0 to 20.
* @param int|null $threadMessageLimit (optional) Number of messages per thread
* @param bool $prefetch (optional) Indicates if the request is called from prefetch.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectInboxResponse
*/
public function getInbox(
$cursorId = null,
$limit = 0,
$threadMessageLimit = null,
$prefetch = false)
{
if ($limit < 0 || $limit > 20) {
throw new \InvalidArgumentException('Invalid value provided to limit.');
}
$request = $this->ig->request('direct_v2/inbox/')
->addParam('persistentBadging', 'true')
->addParam('visual_message_return_type', 'unseen')
->addParam('limit', $limit);
if ($cursorId !== null) {
$request->addParam('cursor', $cursorId);
}
if ($prefetch) {
$request->addHeader('X-IG-Prefetch-Request', 'foreground');
}
if ($threadMessageLimit !== null) {
$request->addParam('thread_message_limit', $threadMessageLimit);
}
return $request->getResponse(new Response\DirectInboxResponse());
}
/**
* Get pending inbox data.
*
* @param string|null $cursorId Next "cursor ID", used for pagination.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectPendingInboxResponse
*/
public function getPendingInbox(
$cursorId = null)
{
$request = $this->ig->request('direct_v2/pending_inbox/')
->addParam('persistentBadging', 'true')
->addParam('use_unified_inbox', 'true');
if ($cursorId !== null) {
$request->addParam('cursor', $cursorId);
}
return $request->getResponse(new Response\DirectPendingInboxResponse());
}
/**
* Approve pending threads by given identifiers.
*
* @param array $threads One or more thread identifiers.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function approvePendingThreads(
array $threads)
{
if (!count($threads)) {
throw new \InvalidArgumentException('Please provide at least one thread to approve.');
}
// Validate threads.
foreach ($threads as &$thread) {
if (!is_scalar($thread)) {
throw new \InvalidArgumentException('Thread identifier must be scalar.');
} elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
}
$thread = (string) $thread;
}
unset($thread);
// Choose appropriate endpoint.
if (count($threads) > 1) {
$request = $this->ig->request('direct_v2/threads/approve_multiple/')
->addPost('thread_ids', json_encode($threads));
} else {
/** @var string $thread */
$thread = reset($threads);
$request = $this->ig->request("direct_v2/threads/{$thread}/approve/");
}
return $request
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Decline pending threads by given identifiers.
*
* @param array $threads One or more thread identifiers.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function declinePendingThreads(
array $threads)
{
if (!count($threads)) {
throw new \InvalidArgumentException('Please provide at least one thread to decline.');
}
// Validate threads.
foreach ($threads as &$thread) {
if (!is_scalar($thread)) {
throw new \InvalidArgumentException('Thread identifier must be scalar.');
} elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
}
$thread = (string) $thread;
}
unset($thread);
// Choose appropriate endpoint.
if (count($threads) > 1) {
$request = $this->ig->request('direct_v2/threads/decline_multiple/')
->addPost('thread_ids', json_encode($threads));
} else {
/** @var string $thread */
$thread = reset($threads);
$request = $this->ig->request("direct_v2/threads/{$thread}/decline/");
}
return $request
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Decline all pending threads.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function declineAllPendingThreads()
{
return $this->ig->request('direct_v2/threads/decline_all/')
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Get a list of activity statuses for users who you follow or message.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\PresencesResponse
*/
public function getPresences()
{
return $this->ig->request('direct_v2/get_presence/')
->getResponse(new Response\PresencesResponse());
}
/**
* Get ranked list of recipients.
*
* WARNING: This is a special, very heavily throttled API endpoint.
* Instagram REQUIRES that you wait several minutes between calls to it.
*
* @param string $mode Either "reshare" or "raven".
* @param bool $showThreads Whether to include existing threads into response.
* @param string|null $query (optional) The user to search for.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectRankedRecipientsResponse|null Will be NULL if throttled by Instagram.
*/
public function getRankedRecipients(
$mode,
$showThreads,
$query = null)
{
try {
$request = $this->ig->request('direct_v2/ranked_recipients/')
->addParam('mode', $mode)
->addParam('show_threads', $showThreads ? 'true' : 'false')
->addParam('use_unified_inbox', 'true');
if ($query !== null) {
$request->addParam('query', $query);
}
return $request
->getResponse(new Response\DirectRankedRecipientsResponse());
} catch (ThrottledException $e) {
// Throttling is so common that we'll simply return NULL in that case.
return null;
}
}
/**
* Get a thread by the recipients list.
*
* @param string[]|int[] $users Array of numerical UserPK IDs.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectThreadResponse
*/
public function getThreadByParticipants(
array $users)
{
if (!count($users)) {
throw new \InvalidArgumentException('Please provide at least one participant.');
}
foreach ($users as $user) {
if (!is_scalar($user)) {
throw new \InvalidArgumentException('User identifier must be scalar.');
}
if (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
}
}
$request = $this->ig->request('direct_v2/threads/get_by_participants/')
->addParam('recipient_users', '['.implode(',', $users).']');
return $request->getResponse(new Response\DirectThreadResponse());
}
/**
* Get direct message thread.
*
* @param string $threadId Thread ID.
* @param string|null $cursorId Next "cursor ID", used for pagination.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectThreadResponse
*/
public function getThread(
$threadId,
$cursorId = null)
{
$request = $this->ig->request("direct_v2/threads/$threadId/")
->addParam('use_unified_inbox', 'true');
if ($cursorId !== null) {
$request->addParam('cursor', $cursorId);
}
return $request->getResponse(new Response\DirectThreadResponse());
}
/**
* Get direct visual thread.
*
* `NOTE:` This "visual" endpoint is only used for Direct stories.
*
* @param string $threadId Thread ID.
* @param string|null $cursorId Next "cursor ID", used for pagination.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectVisualThreadResponse
*
* @deprecated Visual inbox has been superseded by the unified inbox.
* @see Direct::getThread()
*/
public function getVisualThread(
$threadId,
$cursorId = null)
{
$request = $this->ig->request("direct_v2/visual_threads/{$threadId}/");
if ($cursorId !== null) {
$request->addParam('cursor', $cursorId);
}
return $request->getResponse(new Response\DirectVisualThreadResponse());
}
/**
* Update thread title.
*
* @param string $threadId Thread ID.
* @param string $title New title.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectThreadResponse
*/
public function updateThreadTitle(
$threadId,
$title)
{
return $this->ig->request("direct_v2/threads/{$threadId}/update_title/")
->addPost('_uuid', $this->ig->uuid)
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('title', trim($title))
->setSignedPost(false)
->getResponse(new Response\DirectThreadResponse());
}
/**
* Mute direct thread.
*
* @param string $threadId Thread ID.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function muteThread(
$threadId)
{
return $this->ig->request("direct_v2/threads/{$threadId}/mute/")
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Unmute direct thread.
*
* @param string $threadId Thread ID.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function unmuteThread(
$threadId)
{
return $this->ig->request("direct_v2/threads/{$threadId}/unmute/")
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Create a private story sharing group.
*
* NOTE: In the official app, when you create a story, you can choose to
* send it privately. And from there you can create a new group thread. So
* this group creation endpoint is only meant to be used for "direct
* stories" at the moment.
*
* @param string[]|int[] $userIds Array of numerical UserPK IDs.
* @param string $threadTitle Name of the group thread.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectCreateGroupThreadResponse
*/
public function createGroupThread(
array $userIds,
$threadTitle)
{
if (count($userIds) < 2) {
throw new \InvalidArgumentException('You must invite at least 2 users to create a group.');
}
foreach ($userIds as &$user) {
if (!is_scalar($user)) {
throw new \InvalidArgumentException('User identifier must be scalar.');
} elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
}
$user = (string) $user;
}
$request = $this->ig->request('direct_v2/create_group_thread/')
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->addPost('recipient_users', json_encode($userIds))
->addPost('_uid', $this->ig->account_id)
->addPost('thread_title', $threadTitle);
return $request->getResponse(new Response\DirectCreateGroupThreadResponse());
}
/**
* Add users to thread.
*
* @param string $threadId Thread ID.
* @param string[]|int[] $users Array of numerical UserPK IDs.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectThreadResponse
*/
public function addUsersToThread(
$threadId,
array $users)
{
if (!count($users)) {
throw new \InvalidArgumentException('Please provide at least one user.');
}
foreach ($users as &$user) {
if (!is_scalar($user)) {
throw new \InvalidArgumentException('User identifier must be scalar.');
} elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
}
$user = (string) $user;
}
return $this->ig->request("direct_v2/threads/{$threadId}/add_user/")
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('user_ids', json_encode($users))
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\DirectThreadResponse());
}
/**
* Leave direct thread.
*
* @param string $threadId Thread ID.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function leaveThread(
$threadId)
{
return $this->ig->request("direct_v2/threads/{$threadId}/leave/")
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Hide direct thread.
*
* @param string $threadId Thread ID.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function hideThread(
$threadId)
{
return $this->ig->request("direct_v2/threads/{$threadId}/hide/")
->addPost('use_unified_inbox', 'true')
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Send a direct text message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $text Text message.
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function sendText(
array $recipients,
$text,
array $options = [])
{
if (!strlen($text)) {
throw new \InvalidArgumentException('Text can not be empty.');
}
$urls = Utils::extractURLs($text);
if (count($urls)) {
/** @var Response\DirectSendItemResponse $result */
$result = $this->_sendDirectItem('links', $recipients, array_merge($options, [
'link_urls' => json_encode(array_map(function (array $url) {
return $url['fullUrl'];
}, $urls)),
'link_text' => $text,
]));
} else {
/** @var Response\DirectSendItemResponse $result */
$result = $this->_sendDirectItem('message', $recipients, array_merge($options, [
'text' => $text,
]));
}
return $result;
}
/**
* Share an existing media post via direct message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
* @param array $options An associative array of additional parameters, including:
* "media_type" (required) - either "photo" or "video";
* "client_context" (optional) - predefined UUID used to prevent double-posting;
* "text" (optional) - text message.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemsResponse
*
* @see https://help.instagram.com/1209246439090858 For more information.
*/
public function sendPost(
array $recipients,
$mediaId,
array $options = [])
{
if (!preg_match('#^\d+_\d+$#D', $mediaId)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid media ID.', $mediaId));
}
if (!isset($options['media_type'])) {
throw new \InvalidArgumentException('Please provide media_type in options.');
}
if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
}
return $this->_sendDirectItems('media_share', $recipients, array_merge($options, [
'media_id' => $mediaId,
]));
}
/**
* Send a photo (upload) via direct message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $photoFilename The photo filename.
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function sendPhoto(
array $recipients,
$photoFilename,
array $options = [])
{
if (!is_file($photoFilename) || !is_readable($photoFilename)) {
throw new \InvalidArgumentException(sprintf('File "%s" is not available for reading.', $photoFilename));
}
// validate width, height and aspect ratio of photo
$photoDetails = new PhotoDetails($photoFilename);
$photoDetails->validate(ConstraintsFactory::createFor(Constants::FEED_DIRECT));
// uplaod it
return $this->_sendDirectItem('photo', $recipients, array_merge($options, [
'filepath' => $photoFilename,
]));
}
/**
* Send a disappearing photo (upload) via direct message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $photoFilename The photo filename.
* @param array $externalMetadata (optional) User-provided metadata key-value pairs.
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\ConfigureResponse
*
* @see Internal::configureSinglePhoto() for available metadata fields.
*/
public function sendDisappearingPhoto(
array $recipients,
$photoFilename,
array $externalMetadata = [])
{
$internalMetadata = new InternalMetadata();
$internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
$internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
}
/**
* Send a replayable photo (upload) via direct message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $photoFilename The photo filename.
* @param array $externalMetadata (optional) User-provided metadata key-value pairs.
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\ConfigureResponse
*
* @see Internal::configureSinglePhoto() for available metadata fields.
*/
public function sendReplayablePhoto(
array $recipients,
$photoFilename,
array $externalMetadata = [])
{
$internalMetadata = new InternalMetadata();
$internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
$internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
}
/**
* Send a video (upload) via direct message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $videoFilename The video filename.
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \InstagramAPI\Exception\InstagramException
* @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function sendVideo(
array $recipients,
$videoFilename,
array $options = [])
{
// Direct videos use different upload IDs.
$internalMetadata = new InternalMetadata(Utils::generateUploadId(true));
// Attempt to upload the video data.
$internalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_DIRECT, $videoFilename, $internalMetadata);
// We must use the same client_context for all attempts to prevent double-posting.
if (!isset($options['client_context'])) {
$options['client_context'] = Signatures::generateUUID(true);
}
// Send the uploaded video to recipients.
try {
/** @var \InstagramAPI\Response\DirectSendItemResponse $result */
$result = $this->ig->internal->configureWithRetries(
function () use ($internalMetadata, $recipients, $options) {
$videoUploadResponse = $internalMetadata->getVideoUploadResponse();
// Attempt to configure video parameters (which sends it to the thread).
return $this->_sendDirectItem('video', $recipients, array_merge($options, [
'upload_id' => $internalMetadata->getUploadId(),
'video_result' => $videoUploadResponse !== null ? $videoUploadResponse->getResult() : '',
]));
}
);
} catch (InstagramException $e) {
// Pass Instagram's error as is.
throw $e;
} catch (\Exception $e) {
// Wrap runtime errors.
throw new UploadFailedException(
sprintf(
'Upload of "%s" failed: %s',
$internalMetadata->getPhotoDetails()->getBasename(),
$e->getMessage()
),
$e->getCode(),
$e
);
}
return $result;
}
/**
* Send a disappearing video (upload) via direct message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $videoFilename The video filename.
* @param array $externalMetadata (optional) User-provided metadata key-value pairs.
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \InstagramAPI\Exception\InstagramException
* @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
*
* @return \InstagramAPI\Response\ConfigureResponse
*
* @see Internal::configureSingleVideo() for available metadata fields.
*/
public function sendDisappearingVideo(
array $recipients,
$videoFilename,
array $externalMetadata = [])
{
$internalMetadata = new InternalMetadata();
$internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
$internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
}
/**
* Send a replayable video (upload) via direct message to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $videoFilename The video filename.
* @param array $externalMetadata (optional) User-provided metadata key-value pairs.
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \InstagramAPI\Exception\InstagramException
* @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
*
* @return \InstagramAPI\Response\ConfigureResponse
*
* @see Internal::configureSingleVideo() for available metadata fields.
*/
public function sendReplayableVideo(
array $recipients,
$videoFilename,
array $externalMetadata = [])
{
$internalMetadata = new InternalMetadata();
$internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
$internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
}
/**
* Send a like to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function sendLike(
array $recipients,
array $options = [])
{
return $this->_sendDirectItem('like', $recipients, $options);
}
/**
* Send a hashtag to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $hashtag Hashtag to share.
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting;
* "text" - text message.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function sendHashtag(
array $recipients,
$hashtag,
array $options = [])
{
if (!strlen($hashtag)) {
throw new \InvalidArgumentException('Hashtag can not be empty.');
}
return $this->_sendDirectItem('hashtag', $recipients, array_merge($options, [
'hashtag' => $hashtag,
]));
}
/**
* Send a location to a user's inbox.
*
* You must provide a valid Instagram location ID, which you get via other
* functions such as Location::search().
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $locationId Instagram's internal ID for the location.
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting;
* "text" - text message.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*
* @see Location::search()
*/
public function sendLocation(
array $recipients,
$locationId,
array $options = [])
{
if (!ctype_digit($locationId) && (!is_int($locationId) || $locationId < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid location ID.', $locationId));
}
return $this->_sendDirectItem('location', $recipients, array_merge($options, [
'venue_id' => $locationId,
]));
}
/**
* Send a profile to a user's inbox.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $userId Numerical UserPK ID.
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting;
* "text" - text message.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function sendProfile(
array $recipients,
$userId,
array $options = [])
{
if (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid numerical UserPK ID.', $userId));
}
return $this->_sendDirectItem('profile', $recipients, array_merge($options, [
'profile_user_id' => $userId,
]));
}
/**
* Send a reaction to an existing thread item.
*
* @param string $threadId Thread identifier.
* @param string $threadItemId ThreadItemIdentifier.
* @param string $reactionType One of: "like".
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function sendReaction(
$threadId,
$threadItemId,
$reactionType,
array $options = [])
{
return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'created', $options);
}
/**
* Share an existing story post via direct message to a user's inbox.
*
* You are able to share your own stories, as well as public stories from
* other people.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $storyId The story ID in Instagram's internal format (ie "3482384834_43294").
* @param string $reelId The reel ID in Instagram's internal format (ie "highlight:12970012453081168")
* @param array $options An associative array of additional parameters, including:
* "media_type" (required) - either "photo" or "video";
* "client_context" - predefined UUID used to prevent double-posting;
* "text" - text message.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemsResponse
*
* @see https://help.instagram.com/188382041703187 For more information.
*/
public function sendStory(
array $recipients,
$storyId,
$reelId = null,
array $options = [])
{
if (!preg_match('#^\d+_\d+$#D', $storyId)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid story ID.', $storyId));
}
if ($reelId !== null) {
if (!preg_match('#^highlight:\d+$#D', $reelId)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid reel ID.', $reelId));
}
$options = array_merge($options,
[
'reel_id' => $reelId,
]);
}
if (!isset($options['media_type'])) {
throw new \InvalidArgumentException('Please provide media_type in options.');
}
if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
}
return $this->_sendDirectItems('story_share', $recipients, array_merge($options, [
'story_media_id' => $storyId,
]));
}
/**
* Share an occurring or archived live stream via direct message to a user's inbox.
*
* You are able to share your own broadcasts, as well as broadcasts from
* other people.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return Response\DirectSendItemResponse
*/
public function sendLive(
array $recipients,
$broadcastId,
array $options = [])
{
return $this->_sendDirectItem('live', $recipients, array_merge($options, [
'broadcast_id' => $broadcastId,
]));
}
/**
* Delete a reaction to an existing thread item.
*
* @param string $threadId Thread identifier.
* @param string $threadItemId ThreadItemIdentifier.
* @param string $reactionType One of: "like".
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
public function deleteReaction(
$threadId,
$threadItemId,
$reactionType,
array $options = [])
{
return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'deleted', $options);
}
/**
* Delete an item from given thread.
*
* @param string $threadId Thread ID.
* @param string $threadItemId Thread item ID.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function deleteItem(
$threadId,
$threadItemId)
{
return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/delete/")
->addPost('_uuid', $this->ig->uuid)
->addPost('_csrftoken', $this->ig->client->getToken())
->setSignedPost(false)
->getResponse(new Response\GenericResponse());
}
/**
* Marks an item from given thread as seen.
*
* @param string $threadId Thread ID.
* @param string $threadItemId Thread item ID.
*
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSeenItemResponse
*/
public function markItemSeen(
$threadId,
$threadItemId)
{
return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/seen/")
->addPost('use_unified_inbox', 'true')
->addPost('action', 'mark_seen')
->addPost('thread_id', $threadId)
->addPost('item_id', $threadItemId)
->addPost('_uuid', $this->ig->uuid)
->addPost('_csrftoken', $this->ig->client->getToken())
->setSignedPost(false)
->getResponse(new Response\DirectSeenItemResponse());
}
/**
* Marks visual items from given thread as seen.
*
* `NOTE:` This "visual" endpoint is only used for Direct stories.
*
* @param string $threadId Thread ID.
* @param string|string[] $threadItemIds One or more thread item IDs.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function markVisualItemsSeen(
$threadId,
$threadItemIds)
{
if (!is_array($threadItemIds)) {
$threadItemIds = [$threadItemIds];
} elseif (!count($threadItemIds)) {
throw new \InvalidArgumentException('Please provide at least one thread item ID.');
}
return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_seen/")
->addPost('item_ids', '['.implode(',', $threadItemIds).']')
->addPost('_uuid', $this->ig->uuid)
->addPost('_uid', $this->ig->account_id)
->addPost('_csrftoken', $this->ig->client->getToken())
->getResponse(new Response\GenericResponse());
}
/**
* Marks visual items from given thread as replayed.
*
* `NOTE:` This "visual" endpoint is only used for Direct stories.
*
* @param string $threadId Thread ID.
* @param string|string[] $threadItemIds One or more thread item IDs.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\GenericResponse
*/
public function markVisualItemsReplayed(
$threadId,
$threadItemIds)
{
if (!is_array($threadItemIds)) {
$threadItemIds = [$threadItemIds];
} elseif (!count($threadItemIds)) {
throw new \InvalidArgumentException('Please provide at least one thread item ID.');
}
return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_replayed/")
->addPost('item_ids', '['.implode(',', $threadItemIds).']')
->addPost('_uuid', $this->ig->uuid)
->addPost('_uid', $this->ig->account_id)
->addPost('_csrftoken', $this->ig->client->getToken())
->getResponse(new Response\GenericResponse());
}
/**
* Validate and prepare recipients for direct messaging.
*
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param bool $useQuotes Whether to put IDs into quotes.
*
* @throws \InvalidArgumentException
*
* @return array
*/
protected function _prepareRecipients(
array $recipients,
$useQuotes)
{
$result = [];
// users
if (isset($recipients['users'])) {
if (!is_array($recipients['users'])) {
throw new \InvalidArgumentException('"users" must be an array.');
}
foreach ($recipients['users'] as $userId) {
if (!is_scalar($userId)) {
throw new \InvalidArgumentException('User identifier must be scalar.');
} elseif (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $userId));
}
}
// Although this is an array of groups, you will get "Only one group is supported." error
// if you will try to use more than one group here.
// We can't use json_encode() here, because each user id must be a number.
$result['users'] = '[['.implode(',', $recipients['users']).']]';
}
// thread
if (isset($recipients['thread'])) {
if (!is_scalar($recipients['thread'])) {
throw new \InvalidArgumentException('Thread identifier must be scalar.');
} elseif (!ctype_digit($recipients['thread']) && (!is_int($recipients['thread']) || $recipients['thread'] < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $recipients['thread']));
}
// Although this is an array, you will get "Need to specify thread ID or recipient users." error
// if you will try to use more than one thread identifier here.
if (!$useQuotes) {
// We can't use json_encode() here, because thread id must be a number.
$result['thread'] = '['.$recipients['thread'].']';
} else {
// We can't use json_encode() here, because thread id must be a string.
$result['thread'] = '["'.$recipients['thread'].'"]';
}
}
if (!count($result)) {
throw new \InvalidArgumentException('Please provide at least one recipient.');
} elseif (isset($result['thread']) && isset($result['users'])) {
throw new \InvalidArgumentException('You can not mix "users" with "thread".');
}
return $result;
}
/**
* Send a direct message to specific users or thread.
*
* @param string $type One of: "message", "like", "hashtag", "location", "profile", "photo",
* "video", "links", "live".
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param array $options Depends on $type:
* "message" uses "client_context" and "text";
* "like" uses "client_context";
* "hashtag" uses "client_context", "hashtag" and "text";
* "location" uses "client_context", "venue_id" and "text";
* "profile" uses "client_context", "profile_user_id" and "text";
* "photo" uses "client_context" and "filepath";
* "video" uses "client_context", "upload_id" and "video_result";
* "links" uses "client_context", "link_text" and "link_urls".
* "live" uses "client_context" and "text".
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
protected function _sendDirectItem(
$type,
array $recipients,
array $options = [])
{
// Most requests are unsigned, but some use signing by overriding this.
$signedPost = false;
// Handle the request...
switch ($type) {
case 'message':
$request = $this->ig->request('direct_v2/threads/broadcast/text/');
// Check and set text.
if (!isset($options['text'])) {
throw new \InvalidArgumentException('No text message provided.');
}
$request->addPost('text', $options['text']);
break;
case 'like':
$request = $this->ig->request('direct_v2/threads/broadcast/like/');
break;
case 'hashtag':
$request = $this->ig->request('direct_v2/threads/broadcast/hashtag/');
// Check and set hashtag.
if (!isset($options['hashtag'])) {
throw new \InvalidArgumentException('No hashtag provided.');
}
$request->addPost('hashtag', $options['hashtag']);
// Set text if provided.
if (isset($options['text']) && strlen($options['text'])) {
$request->addPost('text', $options['text']);
}
break;
case 'location':
$request = $this->ig->request('direct_v2/threads/broadcast/location/');
// Check and set venue_id.
if (!isset($options['venue_id'])) {
throw new \InvalidArgumentException('No venue_id provided.');
}
$request->addPost('venue_id', $options['venue_id']);
// Set text if provided.
if (isset($options['text']) && strlen($options['text'])) {
$request->addPost('text', $options['text']);
}
break;
case 'profile':
$request = $this->ig->request('direct_v2/threads/broadcast/profile/');
// Check and set profile_user_id.
if (!isset($options['profile_user_id'])) {
throw new \InvalidArgumentException('No profile_user_id provided.');
}
$request->addPost('profile_user_id', $options['profile_user_id']);
// Set text if provided.
if (isset($options['text']) && strlen($options['text'])) {
$request->addPost('text', $options['text']);
}
break;
case 'photo':
$request = $this->ig->request('direct_v2/threads/broadcast/upload_photo/');
// Check and set filepath.
if (!isset($options['filepath'])) {
throw new \InvalidArgumentException('No filepath provided.');
}
$request->addFile('photo', $options['filepath'], 'direct_temp_photo_'.Utils::generateUploadId().'.jpg');
break;
case 'video':
$request = $this->ig->request('direct_v2/threads/broadcast/configure_video/');
// Check and set upload_id.
if (!isset($options['upload_id'])) {
throw new \InvalidArgumentException('No upload_id provided.');
}
$request->addPost('upload_id', $options['upload_id']);
// Set video_result if provided.
if (isset($options['video_result'])) {
$request->addPost('video_result', $options['video_result']);
}
break;
case 'links':
$request = $this->ig->request('direct_v2/threads/broadcast/link/');
// Check and set link_urls.
if (!isset($options['link_urls'])) {
throw new \InvalidArgumentException('No link_urls provided.');
}
$request->addPost('link_urls', $options['link_urls']);
// Check and set link_text.
if (!isset($options['link_text'])) {
throw new \InvalidArgumentException('No link_text provided.');
}
$request->addPost('link_text', $options['link_text']);
break;
case 'reaction':
$request = $this->ig->request('direct_v2/threads/broadcast/reaction/');
// Check and set reaction_type.
if (!isset($options['reaction_type'])) {
throw new \InvalidArgumentException('No reaction_type provided.');
}
$request->addPost('reaction_type', $options['reaction_type']);
// Check and set reaction_status.
if (!isset($options['reaction_status'])) {
throw new \InvalidArgumentException('No reaction_status provided.');
}
$request->addPost('reaction_status', $options['reaction_status']);
// Check and set item_id.
if (!isset($options['item_id'])) {
throw new \InvalidArgumentException('No item_id provided.');
}
$request->addPost('item_id', $options['item_id']);
// Check and set node_type.
if (!isset($options['node_type'])) {
throw new \InvalidArgumentException('No node_type provided.');
}
$request->addPost('node_type', $options['node_type']);
break;
case 'live':
$request = $this->ig->request('direct_v2/threads/broadcast/live_viewer_invite/');
// Check and set broadcast id.
if (!isset($options['broadcast_id'])) {
throw new \InvalidArgumentException('No broadcast_id provided.');
}
$request->addPost('broadcast_id', $options['broadcast_id']);
// Set text if provided.
if (isset($options['text']) && strlen($options['text'])) {
$request->addPost('text', $options['text']);
}
break;
default:
throw new \InvalidArgumentException('Unsupported _sendDirectItem() type.');
}
// Add recipients.
$recipients = $this->_prepareRecipients($recipients, false);
if (isset($recipients['users'])) {
$request->addPost('recipient_users', $recipients['users']);
} elseif (isset($recipients['thread'])) {
$request->addPost('thread_ids', $recipients['thread']);
} else {
throw new \InvalidArgumentException('Please provide at least one recipient.');
}
// Handle client_context.
if (!isset($options['client_context'])) {
// WARNING: Must be random every time otherwise we can only
// make a single post per direct-discussion thread.
$options['client_context'] = Signatures::generateUUID(true);
} elseif (!Signatures::isValidUUID($options['client_context'])) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
}
// Add some additional data if signed post.
if ($signedPost) {
$request->addPost('_uid', $this->ig->account_id);
}
// Execute the request with all data used by both signed and unsigned.
return $request->setSignedPost($signedPost)
->addPost('action', 'send_item')
->addPost('client_context', $options['client_context'])
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->getResponse(new Response\DirectSendItemResponse());
}
/**
* Send a direct messages to specific users or thread.
*
* @param string $type One of: "media_share", "story_share".
* @param array $recipients An array with "users" or "thread" keys.
* To start a new thread, provide "users" as an array
* of numerical UserPK IDs. To use an existing thread
* instead, provide "thread" with the thread ID.
* @param array $options Depends on $type:
* "media_share" uses "client_context", "media_id", "media_type" and "text";
* "story_share" uses "client_context", "story_media_id", "media_type" and "text".
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemsResponse
*/
protected function _sendDirectItems(
$type,
array $recipients,
array $options = [])
{
// Most requests are unsigned, but some use signing by overriding this.
$signedPost = false;
// Handle the request...
switch ($type) {
case 'media_share':
$request = $this->ig->request('direct_v2/threads/broadcast/media_share/');
// Check and set media_id.
if (!isset($options['media_id'])) {
throw new \InvalidArgumentException('No media_id provided.');
}
$request->addPost('media_id', $options['media_id']);
// Set text if provided.
if (isset($options['text']) && strlen($options['text'])) {
$request->addPost('text', $options['text']);
}
// Check and set media_type.
if (isset($options['media_type']) && $options['media_type'] === 'video') {
$request->addParam('media_type', 'video');
} else {
$request->addParam('media_type', 'photo');
}
break;
case 'story_share':
$signedPost = true; // This must be a signed post!
$request = $this->ig->request('direct_v2/threads/broadcast/story_share/');
// Check and set story_media_id.
if (!isset($options['story_media_id'])) {
throw new \InvalidArgumentException('No story_media_id provided.');
}
$request->addPost('story_media_id', $options['story_media_id']);
// Set text if provided.
if (isset($options['reel_id'])) {
$request->addPost('reel_id', $options['reel_id']);
}
// Set text if provided.
if (isset($options['text']) && strlen($options['text'])) {
$request->addPost('text', $options['text']);
}
// Check and set media_type.
if (isset($options['media_type']) && $options['media_type'] === 'video') {
$request->addParam('media_type', 'video');
} else {
$request->addParam('media_type', 'photo');
}
break;
default:
throw new \InvalidArgumentException('Unsupported _sendDirectItems() type.');
}
// Add recipients.
$recipients = $this->_prepareRecipients($recipients, false);
if (isset($recipients['users'])) {
$request->addPost('recipient_users', $recipients['users']);
} elseif (isset($recipients['thread'])) {
$request->addPost('thread_ids', $recipients['thread']);
} else {
throw new \InvalidArgumentException('Please provide at least one recipient.');
}
// Handle client_context.
if (!isset($options['client_context'])) {
// WARNING: Must be random every time otherwise we can only
// make a single post per direct-discussion thread.
$options['client_context'] = Signatures::generateUUID(true);
} elseif (!Signatures::isValidUUID($options['client_context'])) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
}
// Add some additional data if signed post.
if ($signedPost) {
$request->addPost('_uid', $this->ig->account_id);
}
// Execute the request with all data used by both signed and unsigned.
return $request->setSignedPost($signedPost)
->addPost('action', 'send_item')
->addPost('unified_broadcast_format', '1')
->addPost('client_context', $options['client_context'])
->addPost('_csrftoken', $this->ig->client->getToken())
->addPost('_uuid', $this->ig->uuid)
->getResponse(new Response\DirectSendItemsResponse());
}
/**
* Handle a reaction to an existing thread item.
*
* @param string $threadId Thread identifier.
* @param string $threadItemId ThreadItemIdentifier.
* @param string $reactionType One of: "like".
* @param string $reactionStatus One of: "created", "deleted".
* @param array $options An associative array of optional parameters, including:
* "client_context" - predefined UUID used to prevent double-posting.
*
* @throws \InvalidArgumentException
* @throws \InstagramAPI\Exception\InstagramException
*
* @return \InstagramAPI\Response\DirectSendItemResponse
*/
protected function _handleReaction(
$threadId,
$threadItemId,
$reactionType,
$reactionStatus,
array $options = [])
{
if (!ctype_digit($threadId) && (!is_int($threadId) || $threadId < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread ID.', $threadId));
}
if (!ctype_digit($threadItemId) && (!is_int($threadItemId) || $threadItemId < 0)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread item ID.', $threadItemId));
}
if (!in_array($reactionType, ['like'], true)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction type.', $reactionType));
}
return $this->_sendDirectItem('reaction', ['thread' => $threadId], array_merge($options, [
'reaction_type' => $reactionType,
'reaction_status' => $reactionStatus,
'item_id' => $threadItemId,
'node_type' => 'item',
]));
}
}
You can’t perform that action at this time.