|
@@ -0,0 +1,319 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Services\Smm;
|
|
|
+use App\Services\Contracts\SmmPlatformInterface;
|
|
|
+use Carbon\Carbon;
|
|
|
+use Facebook\Exceptions\FacebookResponseException;
|
|
|
+use Facebook\Exceptions\FacebookSDKException;
|
|
|
+use Facebook\Facebook;
|
|
|
+use Illuminate\Http\Request;
|
|
|
+use Illuminate\Support\Facades\Http;
|
|
|
+
|
|
|
+
|
|
|
+class InstagramService implements SmmPlatformInterface
|
|
|
+{
|
|
|
+ protected $clientId;
|
|
|
+ protected $clientSecret;
|
|
|
+ protected $redirectUri;
|
|
|
+ // 构造函数,传入配置信息
|
|
|
+ public $configData = [];
|
|
|
+
|
|
|
+ public function __construct($configData) {
|
|
|
+ $this->configData = $configData;
|
|
|
+ $this->clientId = env('SSM_INSTAGRAM_APP_ID');
|
|
|
+ $this->clientSecret = env('SSM_INSTAGRAM_APP_SECRET');
|
|
|
+ $this->redirectUri = env('DIST_SITE_URL').'/dist/callback/instagram';
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * OAuth 2.0 授权登录
|
|
|
+ * 返回授权地址:https://example.com/fb-callback.php
|
|
|
+ */
|
|
|
+ public function login()
|
|
|
+ {
|
|
|
+ $loginUrl = 'https://www.instagram.com/oauth/authorize' . '?' . http_build_query([
|
|
|
+ 'enable_fb_login'=>0,
|
|
|
+ 'force_authentication'=>1,
|
|
|
+ 'client_id' => $this->clientId,
|
|
|
+ 'redirect_uri' => $this->redirectUri,
|
|
|
+ 'scope' => 'instagram_business_basic,instagram_business_manage_messages,instagram_business_manage_comments,instagram_business_content_publish,instagram_business_manage_insights',
|
|
|
+ 'response_type' => 'code',
|
|
|
+ ]);
|
|
|
+ return ['status'=>true, 'data' => ['url'=>$loginUrl]];
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * OAuth 2.0 授权回调
|
|
|
+ * 授权成功后,得到access_token,refresh_token等信息, 保存到数据库中
|
|
|
+ * 授权成功后,返回回调需要的数据
|
|
|
+ */
|
|
|
+
|
|
|
+
|
|
|
+ public function loginCallback(Request $request)
|
|
|
+ {
|
|
|
+ if (!$request->has('code')) {
|
|
|
+ return ['status' => false, 'data' => '未收到授权代码'];
|
|
|
+ }
|
|
|
+ $code = $request->input('code');
|
|
|
+ // 1. 交换授权代码以获取短期访问令牌
|
|
|
+ $tokenResponse = Http::asForm()->post('https://api.instagram.com/oauth/access_token', [
|
|
|
+ 'client_id' => $this->clientId,
|
|
|
+ 'client_secret' => $this->clientSecret,
|
|
|
+ 'grant_type' => 'authorization_code',
|
|
|
+ 'redirect_uri' => $this->redirectUri,
|
|
|
+ 'code' => $code,
|
|
|
+ ]);
|
|
|
+ $tokenData = $tokenResponse->json();
|
|
|
+ if (!isset($tokenData['access_token'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法获取短期访问令牌 - ' ];
|
|
|
+ }
|
|
|
+ $shortLivedToken = $tokenData['access_token'];
|
|
|
+ // 2. 使用短期令牌交换长期令牌
|
|
|
+ $longLivedTokenResponse = Http::get('https://graph.instagram.com/access_token', [
|
|
|
+ 'grant_type' => 'ig_exchange_token',
|
|
|
+ 'client_secret' => $this->clientSecret,
|
|
|
+ 'access_token' => $shortLivedToken,
|
|
|
+ ]);
|
|
|
+ $longLivedTokenData = $longLivedTokenResponse->json();
|
|
|
+ if (!isset($longLivedTokenData['access_token'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法获取长期访问令牌 - ' ];
|
|
|
+ }
|
|
|
+ $accessToken = $longLivedTokenData['access_token'];
|
|
|
+ // 3. 获取用户信息
|
|
|
+ $userResponse = Http::get('https://graph.instagram.com/me', [
|
|
|
+ 'fields' => 'id,username',
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ]);
|
|
|
+ $userInfo = $userResponse->json();
|
|
|
+ if (!isset($userInfo['id']) || !isset($userInfo['username'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法获取用户信息 - '];
|
|
|
+ }
|
|
|
+ $expiresInSeconds = $longLivedTokenData['expires_in'];
|
|
|
+ // 当前时间
|
|
|
+ $now = Carbon::now();
|
|
|
+ $expiresAt = $now->copy()->addSeconds($expiresInSeconds);
|
|
|
+ // 返回用户信息和长期访问令牌
|
|
|
+ return ['status' => true, 'data' => [
|
|
|
+ 'accessToken' => $accessToken,
|
|
|
+ 'accessToken_expiresAt'=>$expiresAt,
|
|
|
+ 'userName'=>$userInfo['username'],'userId'=>$userInfo['id']
|
|
|
+ ]];
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 发布图片,可以发多个图片
|
|
|
+ * $imagePaths = ['/path/to/image1.jpg','/path/to/image2.jpg'];
|
|
|
+ */
|
|
|
+ public function postImage($message, $imagePaths, $accessToken)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ if (empty($imagePaths)) {
|
|
|
+ return ['status' => false, 'data' => '错误:未提供图片路径'];
|
|
|
+ }
|
|
|
+
|
|
|
+ $igUserId = $this->configData['accountInfo']['account_id'];
|
|
|
+
|
|
|
+ // If single image
|
|
|
+ if (count($imagePaths) === 1) {
|
|
|
+ $imagePath = $imagePaths[0];
|
|
|
+ // Create media container for single image
|
|
|
+ $postData = [
|
|
|
+ 'image_url' => $imagePath, // Assuming images are publicly accessible
|
|
|
+ 'caption' => $message,
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ];
|
|
|
+ $containerResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData);
|
|
|
+
|
|
|
+ $containerData = $containerResponse->json();
|
|
|
+
|
|
|
+
|
|
|
+ if (!isset($containerData['id'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法创建媒体容器 - ' . json_encode($containerData)];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Publish the container
|
|
|
+ $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", [
|
|
|
+ 'creation_id' => $containerData['id'],
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $publishData = $publishResponse->json();
|
|
|
+ if (!isset($publishData['id'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法发布图片 - ' . json_encode($publishData)];
|
|
|
+ }
|
|
|
+ $postIds = [$publishData['id']];
|
|
|
+ $requestContent = [$postData];
|
|
|
+ $responseContent = [$publishData];
|
|
|
+
|
|
|
+ return ['status' => true,
|
|
|
+ 'data' => [
|
|
|
+ 'responseIds' => $postIds,
|
|
|
+ 'requestContent' => $requestContent,
|
|
|
+ 'responseContent' => $responseContent,
|
|
|
+ ]];
|
|
|
+ }
|
|
|
+
|
|
|
+ // For multiple images (carousel post)
|
|
|
+ $children = [];
|
|
|
+ $requestContent = [];
|
|
|
+ $responseContent = [];
|
|
|
+ foreach ($imagePaths as $imagePath) {
|
|
|
+ $postData = [
|
|
|
+ 'image_url' => asset($imagePath),
|
|
|
+ 'is_carousel_item' => true,
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ];
|
|
|
+ $requestContent[] = [
|
|
|
+ 'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
|
|
|
+ 'postData' => $postData,
|
|
|
+ ];
|
|
|
+ $childResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media", $postData);
|
|
|
+
|
|
|
+ $childData = $childResponse->json();
|
|
|
+ $responseContent[] = $childData;
|
|
|
+ if (!isset($childData['id'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法创建子媒体容器 - ' . json_encode($childData)];
|
|
|
+ }
|
|
|
+ $children[] = $childData['id'];
|
|
|
+ }
|
|
|
+ // Create carousel container
|
|
|
+ $postData = [
|
|
|
+ 'media_type' => 'CAROUSEL',
|
|
|
+ 'children' => implode(',', $children),
|
|
|
+ 'caption' => $message,
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ];
|
|
|
+ $requestContent[] = [
|
|
|
+ 'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
|
|
|
+ 'postData' => $postData,
|
|
|
+ ];
|
|
|
+ $carouselResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData );
|
|
|
+
|
|
|
+ $carouselData = $carouselResponse->json();
|
|
|
+ $responseContent[] = $carouselData;
|
|
|
+ if (!isset($carouselData['id'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法创建轮播容器 - ' . json_encode($carouselData)];
|
|
|
+ }
|
|
|
+ // Publish carousel
|
|
|
+ $postData = [
|
|
|
+ 'creation_id' => $carouselData['id'],
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ];
|
|
|
+ $requestContent[] = [
|
|
|
+ 'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media_publish",
|
|
|
+ 'postData' => $postData,
|
|
|
+ ];
|
|
|
+ $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", $postData);
|
|
|
+
|
|
|
+ $publishData = $publishResponse->json();
|
|
|
+ $responseContent[] = $publishData;
|
|
|
+ if (!isset($publishData['id'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法发布轮播 - ' . json_encode($publishData)];
|
|
|
+ }
|
|
|
+ $postIds = [$publishData['id']];
|
|
|
+ return ['status' => true,
|
|
|
+ 'data' => [
|
|
|
+ 'responseIds' => $postIds,
|
|
|
+ 'requestContent' => $requestContent,
|
|
|
+ 'responseContent' => $responseContent,
|
|
|
+ ]];
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ //Log::error('Instagram postImage error: ' . $e->getMessage());
|
|
|
+ return ['status' => false, 'data' => '错误:' . $e->getMessage()];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 发布视频,只能发一个视频
|
|
|
+ */
|
|
|
+ public function postVideo($message, $videoPath, $accessToken)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ if (empty($videoPath)) {
|
|
|
+ return ['status' => false, 'data' => '错误:未提供视频路径'];
|
|
|
+ }
|
|
|
+ $igUserId = $this->configData['accountInfo']['account_id'];
|
|
|
+
|
|
|
+ $requestContent = [];
|
|
|
+ $responseContent = [];
|
|
|
+
|
|
|
+ // Create media container for video
|
|
|
+ $postData = [
|
|
|
+ 'media_type' => 'REELS',
|
|
|
+ 'video_url' => $videoPath, // Assuming video is publicly accessible
|
|
|
+ 'caption' => $message,
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ];
|
|
|
+ $requestContent[] = ['url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media", 'postData' => $postData];
|
|
|
+ $containerResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media", $postData);
|
|
|
+ $containerData = $containerResponse->json();
|
|
|
+ if (!isset($containerData['id'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法创建视频容器 - ' . json_encode($containerData)];
|
|
|
+ }
|
|
|
+ $responseContent[] = $containerData;
|
|
|
+ // Check container status (video processing may take time)
|
|
|
+ $status = 'IN_PROGRESS';
|
|
|
+ $maxAttempts = 10;
|
|
|
+ $attempt = 0;
|
|
|
+ while ($status === 'IN_PROGRESS' && $attempt < $maxAttempts) {
|
|
|
+ sleep(5); // Wait 5 seconds between checks
|
|
|
+ $postData = [
|
|
|
+ 'fields' => 'status',
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ];
|
|
|
+ $requestContent[] = ['url'=>"https://graph.instagram.com/{$containerData['id']}", 'postData' => $postData];
|
|
|
+ $statusResponse = Http::get("https://graph.instagram.com/{$containerData['id']}", $postData);
|
|
|
+
|
|
|
+ $statusData = $statusResponse->json();
|
|
|
+ $status = $statusData['status'] ?? 'ERROR';
|
|
|
+ $attempt++;
|
|
|
+
|
|
|
+ if ($status === 'ERROR') {
|
|
|
+ return ['status' => false, 'data' => '错误:视频处理失败'];
|
|
|
+ }
|
|
|
+ $responseContent[] = $statusData;
|
|
|
+ }
|
|
|
+ if ($status !== 'FINISHED') {
|
|
|
+ return ['status' => false, 'data' => '错误:视频处理超时'];
|
|
|
+ }
|
|
|
+ // Publish the video
|
|
|
+ $postData = [
|
|
|
+ 'creation_id' => $containerData['id'],
|
|
|
+ 'access_token' => $accessToken,
|
|
|
+ ];
|
|
|
+ $requestContent[] = ['url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media_publish", 'postData' => $postData];
|
|
|
+ $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish",$postData);
|
|
|
+
|
|
|
+ $publishData = $publishResponse->json();
|
|
|
+ if (!isset($publishData['id'])) {
|
|
|
+ return ['status' => false, 'data' => '错误:无法发布视频 - ' . json_encode($publishData)];
|
|
|
+ }
|
|
|
+ $responseContent[] = $publishData;
|
|
|
+ $postIds = [$publishData['id']];
|
|
|
+ return ['status' => true,
|
|
|
+ 'data' => [
|
|
|
+ 'responseIds' => $postIds,
|
|
|
+ 'requestContent' => $requestContent,
|
|
|
+ 'responseContent' => $responseContent,
|
|
|
+ ]];
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ return ['status' => false, 'data' => '错误:' . $e->getMessage()];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public function getComments($postId) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public function replyToComment($commentId) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public function deleteComment($commentId) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|