| | |
| | | /** |
| | | * Instagram Integration |
| | | * File: /inc/integrations/Instagram.php |
| | | * |
| | | * Limits: |
| | | * - 50 uploads (including carousels) per user per 24-hour period |
| | | */ |
| | | |
| | | namespace JVBase\integrations\services; |
| | | use JVBase\integrations\Admin; |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\integrations\OAuth; |
| | | use JVBase\integrations\OAuthCredentials; |
| | | use JVBase\integrations\OAuthURLs; |
| | | use JVBase\integrations\Requests; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | |
| | | class Instagram extends Integrations |
| | | { |
| | | protected array $allowedContent = [ |
| | | 'post' |
| | | ]; |
| | | private string $ig_user_id = ''; |
| | | private string $page_id = ''; |
| | | private string $page_access_token = ''; |
| | | private array $business_account = []; |
| | | private bool $auto_post = false; |
| | | private array $post_settings = []; |
| | | |
| | | // Instagram API endpoints |
| | | private const API_VERSION = 'v18.0'; |
| | | private const GRAPH_API_BASE = 'https://graph.facebook.com/'; |
| | | private const INSTAGRAM_BASE = 'https://graph.instagram.com/'; |
| | | |
| | | protected static array $instances = []; |
| | | public static function getInstance(?int $userID = null):self |
| | | { |
| | | $key = is_null($userID) ? 'base' : $userID; |
| | | if (!array_key_exists($key, self::$instances)) { |
| | | self::$instances[$key] = new self($userID); |
| | | } |
| | | |
| | | return self::$instances[$key]; |
| | | } |
| | | |
| | | protected function __construct(?int $userID = null) |
| | | use Admin, Instance, OAuth, Requests; |
| | | protected function __construct() |
| | | { |
| | | $this->service_name = 'instagram'; |
| | | $this->title = 'Instagram'; |
| | | $this->icon = 'instagram-logo'; |
| | | $this->apiBase = [ |
| | | 'graph' => self::GRAPH_API_BASE . self::API_VERSION, |
| | | 'instagram' => self::INSTAGRAM_BASE . self::API_VERSION |
| | | ]; |
| | | $this->apiEndpoints = [ |
| | | 'me/accounts', |
| | | 'media', |
| | | 'media_publish', |
| | | 'content_publishing_limit', |
| | | 'insights' |
| | | ]; |
| | | |
| | | $this->isOAuthService = true; |
| | | // OAuth configuration for Facebook/Instagram |
| | | $this->oauth = [ |
| | | 'authorize' => 'https://www.facebook.com/' . self::API_VERSION . '/dialog/oauth', |
| | | 'token' => self::GRAPH_API_BASE . self::API_VERSION . '/oauth/access_token', |
| | | 'redirect_uri' => rest_url('jvb/v1/oauth/instagram'), |
| | | 'scopes' => [ |
| | | 'instagram_basic', |
| | | 'instagram_content_publish', |
| | | 'instagram_manage_insights', |
| | | 'pages_show_list', |
| | | 'pages_read_engagement', |
| | | 'business_management' |
| | | ] |
| | | ]; |
| | | $this->apiVersion = 'v25.0'; |
| | | $this->supportsWebp = false; |
| | | |
| | | // Define sync capabilities |
| | | $this->canSync = [ |
| | | 'create' => true, |
| | | 'update' => false, // Instagram doesn't allow editing posts |
| | | 'delete' => true |
| | | ]; |
| | | // $this->defineFields(); |
| | | $this->defineOAuth(); |
| | | // $this->defineActions(); |
| | | |
| | | $this->fields = [ |
| | | |
| | | ]; |
| | | |
| | | $this->advanced = [ |
| | | |
| | | ]; |
| | | |
| | | $this->instructions = [ |
| | | 'Setup Facebook in order to use Instagram.' |
| | | ]; |
| | | |
| | | $this->defaults = [ |
| | | |
| | | ]; |
| | | $this->hasWebhooks = true; |
| | | parent::__construct($userID); |
| | | |
| | | $this->actions = array_merge( |
| | | $this->actions, |
| | | [ |
| | | 'switch_account' => 'switchInstagramAccount' |
| | | ] |
| | | ); |
| | | $this->baseUrl = 'https://graph.instagram.com/'.$this->apiVersion; |
| | | parent::__construct(); |
| | | } |
| | | |
| | | public function defineOAuth():void |
| | | { |
| | | $this->oauth = new OAuthURLs( |
| | | 'https://www.facebook.com/'.$this->apiVersion.'/dialog/oauth', |
| | | 'https://graph.facebook.com/'.$this->apiVersion.'/oauth/access_token', |
| | | 'https://graph.facebook.com/'.$this->apiVersion.'/me/permissions' |
| | | ); |
| | | $this->oathScopes = [ |
| | | 'instagram_basic', |
| | | 'instagram_content_publish', |
| | | ]; |
| | | } |
| | | protected function initialize(): void |
| | | { |
| | | $this->page_access_token = $this->credentials['page_access_token'] ?? ''; |
| | | $this->ig_user_id = $this->credentials['ig_user_id'] ?? ''; |
| | | $this->page_id = $this->credentials['page_id'] ?? ''; |
| | | $this->business_account = $this->credentials['business_account'] ?? []; |
| | | $this->auto_post = $this->credentials['auto_post'] ?? false; |
| | | $this->post_settings = $this->credentials['post_settings'] ?? [ |
| | | 'include_caption' => true, |
| | | 'include_hashtags' => true, |
| | | 'include_location' => false, |
| | | 'default_hashtags' => '' |
| | | ]; |
| | | |
| | | // Set rate limits |
| | | $this->rate_limits = [ |
| | | 'min_interval' => 30, |
| | | 'daily_limit' => 25, // Instagram content publishing limit |
| | | 'hourly_limit' => 5 |
| | | ]; |
| | | // TODO: Implement initialize() method. |
| | | } |
| | | |
| | | /** |
| | | * Get request headers for Instagram API |
| | | */ |
| | | protected function refreshAccessToken(OAuthCredentials $credentials): array |
| | | { |
| | | // TODO: Implement refreshAccessToken() method. |
| | | return []; |
| | | } |
| | | |
| | | protected function getRequestHeaders(): array |
| | | { |
| | | return [ |
| | | 'Content-Type' => 'application/json', |
| | | 'Accept' => 'application/json' |
| | | ]; |
| | | // TODO: Implement getRequestHeaders() method. |
| | | return []; |
| | | } |
| | | |
| | | /** |
| | | * Build Instagram API URL |
| | | */ |
| | | protected function getApiUrl(string $endpoint, ?string $baseKey = null): string|false |
| | | protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array |
| | | { |
| | | $base = ($baseKey === 'instagram') ? $this->apiBase['instagram'] : $this->apiBase['graph']; |
| | | |
| | | // Handle Instagram-specific endpoints |
| | | if (in_array($endpoint, ['media', 'media_publish'])) { |
| | | return "{$base}/{$this->ig_user_id}/{$endpoint}"; |
| | | } |
| | | |
| | | return "{$base}/{$endpoint}"; |
| | | } |
| | | |
| | | /** |
| | | * Add OAuth parameters specific to Facebook/Instagram |
| | | */ |
| | | protected function addOAuthParams(array $params): array |
| | | { |
| | | $params['display'] = 'popup'; |
| | | $params['auth_type'] = 'rerequest'; |
| | | return $params; |
| | | } |
| | | |
| | | public function isSetUp():bool { |
| | | $fb = JVB()->connect('facebook'); |
| | | return $fb->isSetUp(); |
| | | } |
| | | |
| | | protected function getOAuthCredentials(): array |
| | | { |
| | | // Get Facebook integration |
| | | $facebook = JVB()->connect('facebook'); |
| | | |
| | | if (!$facebook || !$facebook->isSetUp()) { |
| | | return []; |
| | | } |
| | | |
| | | $fbCredentials = $facebook->getCredentials(); |
| | | |
| | | return [ |
| | | 'client_id' => $fbCredentials['app_id'] ?? $fbCredentials['client_id'] ?? '', |
| | | 'client_secret' => $fbCredentials['app_secret'] ?? $fbCredentials['client_secret'] ?? '', |
| | | 'access_token' => $fbCredentials['access_token'] ?? '', |
| | | ]; |
| | | } |
| | | |
| | | public function getCredentials():array { |
| | | $credentials = parent::getCredentials(); |
| | | if (empty($credentials)) { |
| | | $facebook = JVB()->connect('facebook'); |
| | | |
| | | if (!$facebook || !$facebook->isSetUp()) { |
| | | return []; |
| | | } |
| | | |
| | | $fbCredentials = $facebook->getCredentials(); |
| | | |
| | | return [ |
| | | 'client_id' => $fbCredentials['client_id'], |
| | | 'client_secret' => $fbCredentials['client_secret'], |
| | | ]; |
| | | } |
| | | return $credentials; |
| | | } |
| | | |
| | | private function switchInstagramAccount(array $data): WP_Error|array |
| | | { |
| | | $page_id = $data['page_id'] ?? ''; |
| | | |
| | | if (empty($page_id)) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'Page ID is required' |
| | | ]; |
| | | } |
| | | |
| | | // Find the selected page in available pages |
| | | $selected_page = null; |
| | | foreach ($this->credentials['available_pages'] ?? [] as $page) { |
| | | if ($page['id'] === $page_id) { |
| | | $selected_page = $page; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (!$selected_page || empty($selected_page['instagram_business_account'])) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'Invalid page or no Instagram account' |
| | | ]; |
| | | } |
| | | |
| | | // Update credentials with new page |
| | | $this->credentials['page_id'] = $selected_page['id']; |
| | | $this->credentials['page_access_token'] = $selected_page['access_token']; |
| | | $this->credentials['ig_user_id'] = $selected_page['instagram_business_account']['id']; |
| | | $this->credentials['business_account'] = $this->getBusinessProfile( |
| | | $selected_page['instagram_business_account']['id'], |
| | | $selected_page['access_token'] |
| | | ); |
| | | |
| | | $this->saveCredentials($this->credentials); |
| | | |
| | | // Reinitialize with new credentials |
| | | $this->initialize(); |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'message' => 'Switched to @' . ($this->credentials['business_account']['username'] ?? 'Instagram account'), |
| | | 'account' => $this->credentials['business_account'] |
| | | ]; |
| | | } |
| | | /** |
| | | * Handle post save - queue Instagram post creation |
| | | */ |
| | | public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void |
| | | { |
| | | |
| | | if (!$this->hasOAuthCredentials()) { |
| | | error_log('OAuth Not set up for '.$this->service_name); |
| | | return; |
| | | } |
| | | if (!has_post_thumbnail($postID)) { |
| | | return; |
| | | } |
| | | |
| | | $this->queueOperation(self::$syncTo, [ |
| | | 'items' => [$postID], |
| | | 'user' => user_can($post->post_author, 'manage_options') ? null : $post->post_author |
| | | ], [ |
| | | 'delay' => 60 |
| | | ]); |
| | | Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued'); |
| | | } |
| | | |
| | | /** |
| | | * Process queued operations |
| | | */ |
| | | public function processOperation(\WP_Error|array $result, object $operation, array $data): \WP_Error|array |
| | | { |
| | | $base = strtolower($this->service_name) . '_'; |
| | | $insta = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this; |
| | | switch ($operation->type) { |
| | | case $base . 'create_post': |
| | | return $insta->processCreatePost($data); |
| | | |
| | | case $base . 'create_story': |
| | | return $insta->processCreateStory($data); |
| | | |
| | | case $base . 'delete_post': |
| | | return $insta->processDeletePost($data); |
| | | |
| | | default: |
| | | return $result; |
| | | } |
| | | } |
| | | |
| | | private function processDeletePost(array $data): array |
| | | { |
| | | return [ |
| | | 'success' => true, |
| | | 'message' => 'Instagram sync data removed (post remains on Instagram)', |
| | | 'note' => 'Instagram API does not support deleting posts programmatically' |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | | * Process post creation for Instagram |
| | | */ |
| | | private function processCreatePost(array $data): array |
| | | { |
| | | $post_id = $data['post_id'] ?? 0; |
| | | $post = get_post($post_id); |
| | | |
| | | if (!$post) { |
| | | return ['success' => false, 'message' => 'Post not found']; |
| | | } |
| | | |
| | | try { |
| | | // Get image URL |
| | | $image_url = get_the_post_thumbnail_url($post_id, 'full'); |
| | | if (!$image_url) { |
| | | throw new Exception('No featured image found'); |
| | | } |
| | | |
| | | // Build caption |
| | | $caption = $this->buildCaption($post); |
| | | |
| | | // Create media container |
| | | $container = $this->createMediaContainer($image_url, $caption); |
| | | |
| | | if (!$container || empty($container['id'])) { |
| | | throw new Exception('Failed to create media container'); |
| | | } |
| | | |
| | | // Wait for processing (Instagram needs time to process the image) |
| | | sleep(10); |
| | | |
| | | // Publish the media |
| | | $published = $this->publishMedia($container['id']); |
| | | |
| | | if ($published && !empty($published['id'])) { |
| | | // Store Instagram media ID |
| | | update_post_meta($post_id, BASE . '_instagram_media_id', $published['id']); |
| | | update_post_meta($post_id, BASE . '_instagram_sync_status', 'synced'); |
| | | update_post_meta($post_id, BASE . '_instagram_sync_date', current_time('mysql')); |
| | | |
| | | // Get permalink if available |
| | | if (!empty($published['permalink'])) { |
| | | update_post_meta($post_id, BASE . '_instagram_permalink', $published['permalink']); |
| | | } |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'message' => 'Posted to Instagram successfully', |
| | | 'instagram_id' => $published['id'] |
| | | ]; |
| | | } |
| | | |
| | | throw new Exception('Failed to publish media'); |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('Instagram post creation failed', [ |
| | | 'post_id' => $post_id, |
| | | 'error' => $e->getMessage() |
| | | ]); |
| | | |
| | | update_post_meta($post_id, BASE . '_instagram_sync_status', 'failed'); |
| | | update_post_meta($post_id, BASE . '_instagram_sync_error', $e->getMessage()); |
| | | |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => $e->getMessage() |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Build caption for Instagram post |
| | | */ |
| | | private function buildCaption(\WP_Post $post): string |
| | | { |
| | | $caption_parts = []; |
| | | |
| | | // Add title and description if enabled |
| | | if ($this->post_settings['include_caption'] ?? true) { |
| | | $caption_parts[] = $post->post_title; |
| | | |
| | | if (!empty($post->post_excerpt)) { |
| | | $caption_parts[] = "\n\n" . $post->post_excerpt; |
| | | } |
| | | } |
| | | |
| | | // Add hashtags |
| | | if ($this->post_settings['include_hashtags'] ?? true) { |
| | | $hashtags = []; |
| | | |
| | | // Get tags from post |
| | | $tags = wp_get_post_tags($post->ID, ['fields' => 'names']); |
| | | foreach ($tags as $tag) { |
| | | $hashtags[] = '#' . str_replace(' ', '', $tag); |
| | | } |
| | | |
| | | // Add default hashtags |
| | | if (!empty($this->post_settings['default_hashtags'])) { |
| | | $default = preg_split('/[\s,]+/', $this->post_settings['default_hashtags']); |
| | | foreach ($default as $tag) { |
| | | $tag = trim($tag); |
| | | if (strpos($tag, '#') !== 0) { |
| | | $tag = '#' . $tag; |
| | | } |
| | | $hashtags[] = $tag; |
| | | } |
| | | } |
| | | |
| | | if (!empty($hashtags)) { |
| | | $caption_parts[] = "\n\n" . implode(' ', array_unique($hashtags)); |
| | | } |
| | | } |
| | | |
| | | // Add website link |
| | | $caption_parts[] = "\n\nš " . get_permalink($post); |
| | | |
| | | return implode('', $caption_parts); |
| | | } |
| | | |
| | | /** |
| | | * Webhook validation |
| | | */ |
| | | protected function validateWebhook(array $payload): bool |
| | | { |
| | | // Facebook webhook verification |
| | | if (isset($payload['hub_mode']) && $payload['hub_mode'] === 'subscribe') { |
| | | $verify_token = $this->credentials['webhook_verify_token'] ?? 'jvb_instagram_webhook'; |
| | | |
| | | if ($payload['hub_verify_token'] === $verify_token) { |
| | | // Echo the challenge for verification |
| | | echo $payload['hub_challenge']; |
| | | exit; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | // Validate webhook signature for actual events |
| | | $signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? ''; |
| | | if (!$signature) { |
| | | return false; |
| | | } |
| | | |
| | | $app_secret = $this->credentials['app_secret'] ?? ''; |
| | | if (!$app_secret) { |
| | | return true; // Allow if no secret configured |
| | | } |
| | | |
| | | $expected = 'sha256=' . hash_hmac('sha256', file_get_contents('php://input'), $app_secret); |
| | | return hash_equals($expected, $signature); |
| | | } |
| | | |
| | | /** |
| | | * Process webhook |
| | | */ |
| | | protected function processWebhook(array $payload): bool |
| | | { |
| | | $entry = $payload['entry'][0] ?? []; |
| | | $changes = $entry['changes'] ?? []; |
| | | |
| | | foreach ($changes as $change) { |
| | | $field = $change['field'] ?? ''; |
| | | $value = $change['value'] ?? []; |
| | | |
| | | switch ($field) { |
| | | case 'comments': |
| | | $this->handleCommentWebhook($value); |
| | | break; |
| | | |
| | | case 'mentions': |
| | | $this->handleMentionWebhook($value); |
| | | break; |
| | | |
| | | default: |
| | | $this->logDebug('Unhandled webhook field', ['field' => $field]); |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * API Helper Methods |
| | | */ |
| | | |
| | | private function getFacebookPages(string $access_token): array |
| | | { |
| | | $response = wp_remote_get( |
| | | $this->apiBase['graph'] . '/me/accounts?' . http_build_query([ |
| | | 'access_token' => $access_token, |
| | | 'fields' => 'id,name,access_token,instagram_business_account' |
| | | ]) |
| | | ); |
| | | |
| | | if (is_wp_error($response)) { |
| | | $this->logError('Failed to get Facebook pages', ['error' => $response->get_error_message()]); |
| | | return []; |
| | | } |
| | | |
| | | $data = json_decode(wp_remote_retrieve_body($response), true); |
| | | return $data['data'] ?? []; |
| | | } |
| | | |
| | | private function getBusinessProfile(string $ig_user_id, string $access_token): array |
| | | { |
| | | $response = wp_remote_get( |
| | | $this->apiBase['graph'] . '/' . $ig_user_id . '?' . http_build_query([ |
| | | 'access_token' => $access_token, |
| | | 'fields' => 'id,username,name,profile_picture_url,followers_count,media_count,biography,website' |
| | | ]) |
| | | ); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return []; |
| | | } |
| | | |
| | | return json_decode(wp_remote_retrieve_body($response), true) ?: []; |
| | | } |
| | | |
| | | private function createMediaContainer(string $image_url, string $caption): ?array |
| | | { |
| | | $response = wp_remote_post( |
| | | $this->getApiUrl('media', 'instagram'), |
| | | [ |
| | | 'body' => [ |
| | | 'image_url' => $image_url, |
| | | 'caption' => $caption, |
| | | 'access_token' => $this->page_access_token |
| | | ] |
| | | ] |
| | | ); |
| | | |
| | | if (is_wp_error($response)) { |
| | | $this->logError('Failed to create media container', ['error' => $response->get_error_message()]); |
| | | return null; |
| | | } |
| | | |
| | | return json_decode(wp_remote_retrieve_body($response), true); |
| | | } |
| | | |
| | | private function publishMedia(string $creation_id): ?array |
| | | { |
| | | $response = wp_remote_post( |
| | | $this->getApiUrl('media_publish', 'instagram'), |
| | | [ |
| | | 'body' => [ |
| | | 'creation_id' => $creation_id, |
| | | 'access_token' => $this->page_access_token |
| | | ] |
| | | ] |
| | | ); |
| | | |
| | | if (is_wp_error($response)) { |
| | | $this->logError('Failed to publish media', ['error' => $response->get_error_message()]); |
| | | return null; |
| | | } |
| | | |
| | | return json_decode(wp_remote_retrieve_body($response), true); |
| | | } |
| | | |
| | | private function getPublishingLimit(): array |
| | | { |
| | | $response = wp_remote_get( |
| | | $this->getApiUrl('content_publishing_limit', 'instagram') . '?' . http_build_query([ |
| | | 'access_token' => $this->page_access_token |
| | | ]) |
| | | ); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return new WP_Error('api_error', $response->get_error_message()); |
| | | } |
| | | |
| | | $data = json_decode(wp_remote_retrieve_body($response), true); |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'data' => [ |
| | | 'quota_usage' => $data['data'][0]['quota_usage'] ?? 0, |
| | | 'quota_total' => $data['data'][0]['config']['quota_total'] ?? 25, |
| | | 'quota_duration' => $data['data'][0]['config']['quota_duration'] ?? 86400 |
| | | ] |
| | | ]; |
| | | } |
| | | |
| | | protected function performConnectionTest(): bool |
| | | { |
| | | if (empty($this->ig_user_id) || empty($this->page_access_token)) { |
| | | return false; |
| | | } |
| | | |
| | | // Test by getting account info |
| | | $profile = $this->getBusinessProfile($this->ig_user_id, $this->page_access_token); |
| | | |
| | | if (!empty($profile['id'])) { |
| | | return true; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | private function createStory(array $data): WP_Error|array |
| | | { |
| | | $image_url = $data['image_url'] ?? ''; |
| | | |
| | | if (empty($image_url)) { |
| | | return new WP_Error('missing_image', 'Image URL required for story'); |
| | | } |
| | | |
| | | // Queue story creation |
| | | $this->queueOperation('create_story', [ |
| | | 'image_url' => $image_url, |
| | | 'sticker_url' => $data['sticker_url'] ?? '' |
| | | ], [ |
| | | 'priority' => 'high' |
| | | ]); |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'message' => 'Story creation queued' |
| | | ]; |
| | | } |
| | | |
| | | private function processCreateStory(array $data): array |
| | | { |
| | | $params = [ |
| | | 'image_url' => $data['image_url'], |
| | | 'media_type' => 'STORIES', |
| | | 'access_token' => $this->page_access_token |
| | | ]; |
| | | |
| | | if (!empty($data['sticker_url'])) { |
| | | $params['sticker_url'] = $data['sticker_url']; |
| | | } |
| | | |
| | | // Create story container |
| | | $response = wp_remote_post( |
| | | $this->getApiUrl('media', 'instagram'), |
| | | ['body' => $params] |
| | | ); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => $response->get_error_message() |
| | | ]; |
| | | } |
| | | |
| | | $container = json_decode(wp_remote_retrieve_body($response), true); |
| | | |
| | | if (empty($container['id'])) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'Failed to create story container' |
| | | ]; |
| | | } |
| | | |
| | | // Wait for processing |
| | | sleep(5); |
| | | |
| | | // Publish story |
| | | $published = $this->publishMedia($container['id']); |
| | | |
| | | return [ |
| | | 'success' => !empty($published['id']), |
| | | 'message' => !empty($published['id']) ? 'Story published' : 'Failed to publish story', |
| | | 'story_id' => $published['id'] ?? null |
| | | ]; |
| | | } |
| | | |
| | | private function handleCommentWebhook(array $value): void |
| | | { |
| | | // Log new comments for moderation |
| | | $this->logDebug('New Instagram comment', $value); |
| | | |
| | | // Could trigger notifications or store for moderation |
| | | do_action(BASE . 'instagram_comment_received', $value); |
| | | } |
| | | |
| | | private function handleMentionWebhook(array $value): void |
| | | { |
| | | // Log mentions |
| | | $this->logDebug('New Instagram mention', $value); |
| | | |
| | | // Could trigger notifications |
| | | do_action(BASE . 'instagram_mention_received', $value); |
| | | } |
| | | |
| | | /** |
| | | * Get service description |
| | | */ |
| | | public function getServiceDescription(): string |
| | | { |
| | | return "Connect your Instagram Business account to automatically share your content and manage your Instagram presence."; |
| | | // TODO: Implement handleResponse() method. |
| | | return []; |
| | | } |
| | | } |