| <?php |
|
|
| namespace App\Services; |
|
|
| use App\Models\SocialPlatformCredential; |
| use App\Models\SocialPost; |
| use Illuminate\Support\Facades\Http; |
| use RuntimeException; |
|
|
| class SocialPublisherService |
| { |
| public function publish(SocialPost $post, SocialPlatformCredential $credential): array |
| { |
| return match ($post->platform) { |
| 'facebook' => $this->publishFacebook($post, $credential), |
| 'instagram' => $this->publishInstagram($post, $credential), |
| 'x' => $this->publishX($post, $credential), |
| 'tiktok' => $this->publishTikTok($post, $credential), |
| default => throw new RuntimeException('Platform tidak dikenali: '.$post->platform), |
| }; |
| } |
|
|
| private function publishFacebook(SocialPost $post, SocialPlatformCredential $credential): array |
| { |
| $pageId = trim((string) $credential->account_id); |
| $token = trim((string) $credential->access_token); |
|
|
| if ($pageId === '' || $token === '') { |
| throw new RuntimeException('Facebook butuh PAGE_ID dan PAGE_ACCESS_TOKEN.'); |
| } |
|
|
| $payload = [ |
| 'message' => $post->fullCaption(), |
| 'access_token' => $token, |
| ]; |
|
|
| if (! empty($post->image_url)) { |
| $payload['link'] = $post->image_url; |
| } |
|
|
| $response = Http::asForm() |
| ->timeout(45) |
| ->post("https://graph.facebook.com/v22.0/{$pageId}/feed", $payload); |
|
|
| if (! $response->successful()) { |
| $message = data_get($response->json(), 'error.message', $response->body()); |
| throw new RuntimeException('Facebook publish gagal: '.$response->status().' - '.$message); |
| } |
|
|
| return [ |
| 'external_post_id' => (string) data_get($response->json(), 'id', ''), |
| 'payload' => $response->json(), |
| ]; |
| } |
|
|
| private function publishInstagram(SocialPost $post, SocialPlatformCredential $credential): array |
| { |
| $igAccountId = trim((string) $credential->account_id); |
| $token = trim((string) $credential->access_token); |
|
|
| if ($igAccountId === '' || $token === '') { |
| throw new RuntimeException('Instagram butuh IG_BUSINESS_ACCOUNT_ID dan ACCESS_TOKEN.'); |
| } |
|
|
| if (empty($post->image_url)) { |
| throw new RuntimeException('Instagram saat ini butuh image_url publik. Isi URL gambar dulu.'); |
| } |
|
|
| $createContainer = Http::asForm() |
| ->timeout(60) |
| ->post("https://graph.facebook.com/v22.0/{$igAccountId}/media", [ |
| 'image_url' => $post->image_url, |
| 'caption' => $post->fullCaption(), |
| 'access_token' => $token, |
| ]); |
|
|
| if (! $createContainer->successful()) { |
| $message = data_get($createContainer->json(), 'error.message', $createContainer->body()); |
| throw new RuntimeException('Instagram create media gagal: '.$createContainer->status().' - '.$message); |
| } |
|
|
| $creationId = (string) data_get($createContainer->json(), 'id'); |
| if ($creationId === '') { |
| throw new RuntimeException('Instagram creation_id kosong.'); |
| } |
|
|
| $publish = Http::asForm() |
| ->timeout(60) |
| ->post("https://graph.facebook.com/v22.0/{$igAccountId}/media_publish", [ |
| 'creation_id' => $creationId, |
| 'access_token' => $token, |
| ]); |
|
|
| if (! $publish->successful()) { |
| $message = data_get($publish->json(), 'error.message', $publish->body()); |
| throw new RuntimeException('Instagram media_publish gagal: '.$publish->status().' - '.$message); |
| } |
|
|
| return [ |
| 'external_post_id' => (string) data_get($publish->json(), 'id', ''), |
| 'payload' => [ |
| 'creation' => $createContainer->json(), |
| 'publish' => $publish->json(), |
| ], |
| ]; |
| } |
|
|
| private function publishX(SocialPost $post, SocialPlatformCredential $credential): array |
| { |
| $token = trim((string) $credential->access_token); |
|
|
| if ($token === '') { |
| throw new RuntimeException('X/Twitter butuh ACCESS_TOKEN OAuth2 user context (scope tweet.write).'); |
| } |
|
|
| $response = Http::timeout(45) |
| ->withToken($token) |
| ->acceptJson() |
| ->post('https://api.x.com/2/tweets', [ |
| 'text' => $post->fullCaption(), |
| ]); |
|
|
| if (! $response->successful()) { |
| $message = data_get($response->json(), 'detail', $response->body()); |
| throw new RuntimeException('X publish gagal: '.$response->status().' - '.$message); |
| } |
|
|
| return [ |
| 'external_post_id' => (string) data_get($response->json(), 'data.id', ''), |
| 'payload' => $response->json(), |
| ]; |
| } |
|
|
| private function publishTikTok(SocialPost $post, SocialPlatformCredential $credential): array |
| { |
| $token = trim((string) $credential->access_token); |
|
|
| if ($token === '') { |
| throw new RuntimeException('TikTok butuh OAuth access token user (scope video.publish).'); |
| } |
|
|
| if (empty($post->video_url)) { |
| throw new RuntimeException('TikTok butuh video_url publik. Isi URL video pada form post.'); |
| } |
|
|
| $title = trim((string) ($post->title ?: 'Konten terbaru')); |
| if ($title === '') { |
| $title = 'Konten terbaru'; |
| } |
|
|
| $response = Http::timeout(90) |
| ->withToken($token) |
| ->acceptJson() |
| ->post('https://open.tiktokapis.com/v2/post/publish/video/init/', [ |
| 'post_info' => [ |
| 'title' => mb_substr($title, 0, 90), |
| 'privacy_level' => 'SELF_ONLY', |
| 'disable_duet' => false, |
| 'disable_comment' => false, |
| 'disable_stitch' => false, |
| ], |
| 'source_info' => [ |
| 'source' => 'PULL_FROM_URL', |
| 'video_url' => $post->video_url, |
| ], |
| ]); |
|
|
| if (! $response->successful()) { |
| $message = data_get($response->json(), 'error.message', $response->body()); |
| throw new RuntimeException('TikTok init publish gagal: '.$response->status().' - '.$message); |
| } |
|
|
| return [ |
| 'external_post_id' => (string) data_get($response->json(), 'data.publish_id', ''), |
| 'payload' => $response->json(), |
| ]; |
| } |
| } |
|
|