Browse Source

社媒对接

moshaorui 3 hours ago
parent
commit
3615efe0d2

+ 11 - 2
app/Console/Commands/TimerSsmPost.php

@@ -62,7 +62,9 @@ class TimerSsmPost extends Command
             $post->status = 1;
             $post->save();
         }
+
         //dd('middle');
+
         //发送社媒帖子开始
         $sendLog = SmmPostLog::getSendLog();
         foreach ($sendLog as $log) {
@@ -87,9 +89,16 @@ class TimerSsmPost extends Command
                 $response = $ssmService->postImage($message, $imageVideoUrl,$accessToken);
             } else {
                 $imageVideoUrl = CommonHelper::ossUrl($imageVideoUrl);
-                $response = $ssmService->postVideo($message, $imageVideoUrl,$accessToken);
+               // $response = $ssmService->postVideo($message, $imageVideoUrl,$accessToken);
+
+                $response = $ssmService->postVideo([
+                    'title' => 'Test Video',
+                    'description' => 'This is a test video111',
+                    'categoryId' => '22', // People & Blogs
+                    'privacyStatus' => 'private'
+                ], '/mnt/hgfs/wwwroot/mtb_dcatadmin_plus/1.mp4', $accessToken);
             }
-           // dd($response);
+            dd($response);
             //更新post_logs表
             if ($response['status'] == true) {
                 $log->status = 1;

+ 34 - 2
app/Distributor/Controllers/SmmPostController.php

@@ -5,6 +5,7 @@ namespace App\Distributor\Controllers;
 use App\Admin\Repositories\BaseProductImage;
 use App\Distributor\Repositories\SmmPost;
 use App\Distributor\Repositories\SmmUserAccount;
+use App\Services\SmmService;
 use Carbon\Carbon;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
@@ -26,6 +27,10 @@ class SmmPostController extends AdminDistController
      */
     public function index(Content $content)
     {
+//        $ssmService = new SmmService('youtube');
+//        $videoCategories = $ssmService->getVideoCategories();
+//        var_dump($videoCategories);
+//        exit;
         return $content
             ->header('发报帖子')
             ->body($this->form());
@@ -55,7 +60,7 @@ class SmmPostController extends AdminDistController
                                 ->removable() // 可移除图片
                                 ->autoUpload() // 自动上传
                                 ->uniqueName()
-                                ->limit(5)
+                                ->limit(4)
                                 ->accept(config('admin.upload.oss_image.accept'))
                                 ->maxSize(config('admin.upload.oss_image.max_size'));
                         })
@@ -77,9 +82,12 @@ class SmmPostController extends AdminDistController
                     foreach ($rootAccounts as $account) {
                         $listBoxOptions[$account->id] = $account->name . ' ('.$account->getParent->name.')';
                     }
-                    $step->listbox('account_ids', '<span style="color:#bd4147;">*</span> '.admin_trans_label('accountsSelect'))->options($listBoxOptions);
+                    $step->listbox('account_ids', '<span style="color:#bd4147;">*</span> '.admin_trans_label('accountsSelect'))
+                        ->options($listBoxOptions);
+                    $step->select('youtube_category')->setView('distributor.form_custom.select_hide')->options(SmmUserAccount::getYoutubeCategory())->default(22)->required();
                     $this->stepLeaving($step,1);
                 });
+            $this->addJs();
         });
     }
 
@@ -229,4 +237,28 @@ JS);
         }
 
     }
+
+    public function addJs()
+    {
+        Admin::script(
+<<<JS
+function checkYouTubeOption(select) {
+    // 检查是否存在包含"YouTube"的option
+    var hasYouTube = select.find('option:contains("YouTube")').length > 0;
+    // 切换目标元素的显示状态
+    $('#select_hide_youtube_category').toggle(hasYouTube);
+}
+var select = $('select[name="account_ids\\[\\]_helper2"]');
+var select_lenght = select.children().length;
+let intervalId = setInterval(() => {
+    var select = $('select[name="account_ids\\[\\]_helper2"]');
+    // 处理变化的逻辑
+    if (select_lenght != select.children().length) {
+        checkYouTubeOption(select);
+    }
+}, 300);
+JS
+        );
+
+    }
 }

+ 2 - 1
app/Distributor/Controllers/SmmUserAccountController.php

@@ -115,7 +115,8 @@ class SmmUserAccountController extends AdminDistController
                 $expiresAt = $result['data']['accessToken_expiresAt'];
                 $userName = $result['data']['userName'];
                 $userId = $result['data']['userId'];
-                SmmUserAccount::createAccountIfMediaExists($mediaName, $userId,$userName, $accessToken,$expiresAt);
+                $refreshToken = isset($result['data']['refreshToken']) ? $result['data']['refreshToken'] : '';
+                SmmUserAccount::createAccountIfMediaExists($mediaName, $userId,$userName, $accessToken,$expiresAt,$refreshToken);
                 return response()->json(['code' => 1, 'msg' => 'success']);
             } else {
                 return response()->json(['code' => 0,'msg' => $result['data']]);

+ 3 - 0
app/Distributor/Repositories/SmmPost.php

@@ -29,6 +29,9 @@ class SmmPost extends EloquentRepository
         $model->image_video_url = $imageVideoUrl;
         $model->status = 0;
         $model->dist_id = getDistributorId();
+        $model->backup_field1 = json_encode([
+            'youtube_category' => $post['youtube_category'],
+        ]);
         $model->save();
         return $model->id;
     }

+ 13 - 1
app/Distributor/Repositories/SmmUserAccount.php

@@ -23,7 +23,7 @@ class SmmUserAccount extends EloquentRepository
      * @param string $accessToken 访问令牌
      * @return Model|null         新创建的模型实例或null
      */
-    public static function createAccountIfMediaExists($mediaName, $accountId,$accountName, $accessToken,$expiresAt)
+    public static function createAccountIfMediaExists($mediaName, $accountId,$accountName, $accessToken,$expiresAt,$refreshToken = '',$backupField1 = '')
     {
         $model = new Model();
         // 查找匹配的社媒记录
@@ -37,6 +37,8 @@ class SmmUserAccount extends EloquentRepository
             $userRow->access_token = $accessToken;
             $userRow->expires_at = $expiresAt;
             $userRow->name = $accountName;
+            $userRow->refresh_token = $refreshToken;
+            $userRow->backup_field1 = $backupField1;
             $userRow->save();
         } else {
             // 创建新账号并关联父级
@@ -49,6 +51,8 @@ class SmmUserAccount extends EloquentRepository
                 'dist_id'      => getDistributorId(),
                 'created_at'   => Carbon::now(),  // 自动生成时间戳
                 'updated_at'   => Carbon::now(),
+                'refresh_token' => $refreshToken,
+                'backup_field1' => $backupField1, //备用字段
             ];
             $model->insert($data);
         }
@@ -106,6 +110,14 @@ class SmmUserAccount extends EloquentRepository
 
 
 
+    public static function getYoutubeCategory()
+    {
+        $model = new Model();
+        $categories = $model->where('name', 'YouTube')->first();
+        $categories = json_decode($categories->backup_field1,true);
+        return $categories;
+    }
+
 
 
 

+ 191 - 227
app/Services/Smm/FacebookService.php

@@ -1,43 +1,26 @@
 <?php
 
 namespace App\Services\Smm;
+
 use App\Services\Contracts\SmmPlatformInterface;
 use Carbon\Carbon;
 use Illuminate\Http\Request;
-use \Facebook\Facebook;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Session;
 
-
-use \Facebook\Exceptions\FacebookResponseException;
-use \Facebook\Exceptions\FacebookSDKException;
-
 class FacebookService implements SmmPlatformInterface
 {
-    public $fb = null;
-    public $pageAccessToken = null;
+    protected $configData;
+    protected $graphVersion = 'v19.0';
 
-    // 构造函数,传入配置信息
-    public $configData = [];
-
-    public function __construct($configData) {
+    public function __construct($configData)
+    {
         $this->configData = $configData;
-        $this->fb = new Facebook([
-            'app_id' => env('SSM_FACEBOOK_APP_ID'), // 替换为您的App ID
-            'app_secret' => env('SSM_FACEBOOK_APP_SECRET'), // 替换为您的App Secret
-            'default_graph_version' => 'v19.0', // 使用当前版本
-        ]);
-        //$this->pageAccessToken = $pageAccessToken;
     }
 
-    /*
-     * OAuth 2.0 授权登录
-     * 返回授权地址:https://example.com/fb-callback.php
-     */
     public function login()
     {
-        session_start();
-        // 实现Facebook登录逻辑
-        $helper = $this->fb->getRedirectLoginHelper();
         $permissions = [
             'publish_video',
             'pages_manage_cta',
@@ -51,256 +34,237 @@ class FacebookService implements SmmPlatformInterface
             'pages_manage_engagement',
         ];
 
-        $loginUrl = $helper->getLoginUrl(env('DIST_SITE_URL').'/dist/callback/facebook', $permissions);
+        $query = http_build_query([
+            'client_id'        => env('SSM_FACEBOOK_APP_ID'),
+            'redirect_uri'     => env('DIST_SITE_URL').'/dist/callback/facebook',
+            'response_type'   => 'code',
+            'scope'           => implode(',', $permissions),
+            'state'           => csrf_token(),
+        ]);
 
-        return ['status'=>true, 'data' => ['url'=>$loginUrl]];
+        return ['status' => true, 'data' => ['url' => "https://www.facebook.com/{$this->graphVersion}/dialog/oauth?{$query}"]];
     }
 
-    /*
-     * OAuth 2.0 授权回调
-     * 授权成功后,得到access_token,refresh_token等信息, 保存到数据库中
-     * 授权成功后,返回回调需要的数据
-     */
-
-
     public function loginCallback(Request $request)
     {
-        $helper = $this->fb->getRedirectLoginHelper();
-
-        if (isset($_GET['state'])) {
-            // 由于session的原因,这里需要重新设置state
-            $helper->getPersistentDataHandler()->set('state', $_GET['state']);
+        // 验证 state 防止 CSRF
+        if ($request->state !== csrf_token()) {
+            return ['status' => false, 'data' => 'Invalid state parameter'];
         }
+
         try {
-            $accessToken = $helper->getAccessToken();
-        } catch (Facebook\Exceptions\FacebookResponseException $e) {
-            return ['status' => false, 'data' => 'Graph API错误:' . $e->getMessage()];
-        } catch (Facebook\Exceptions\FacebookSDKException $e) {
-            return ['status' => false, 'data' => 'SDK错误:' . $e->getMessage()];
-        }
-        if (isset($accessToken)) {
-            // 可选:将短期令牌转换为长期令牌(有效期约60天)
-            $oAuth2Client = $this->fb->getOAuth2Client();
-            $longLivedAccessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
-            $useriInfo = $this->getFacebookUser($longLivedAccessToken->getValue());
-            if ($useriInfo['status'] == false) {
-                return ['status' => false, 'data' => $useriInfo['data']];
+            // 获取短期访问令牌
+            $response = Http::asForm()->post("https://graph.facebook.com/{$this->graphVersion}/oauth/access_token", [
+                'client_id'     => env('SSM_FACEBOOK_APP_ID'),
+                'client_secret' => env('SSM_FACEBOOK_APP_SECRET'),
+                'redirect_uri'  => env('DIST_SITE_URL').'/dist/callback/facebook',
+                'code'          => $request->code,
+            ]);
+
+            if ($response->failed()) {
+                throw new \Exception($response->json('error.message'));
             }
-            $expiresAt = $longLivedAccessToken->getExpiresAt();
-            //DateTime转 carbon
-            if ($expiresAt != null) {
-                $expiresAt = $expiresAt->format('Y-m-d H:i:s');
-            } else {
-                $expiresAt = Carbon::now()->addDays(60)->format('Y-m-d H:i:s');
+
+            $shortToken = $response->json('access_token');
+
+            // 转换为长期令牌
+            $longTokenResponse = Http::get("https://graph.facebook.com/{$this->graphVersion}/oauth/access_token", [
+                'grant_type'        => 'fb_exchange_token',
+                'client_id'         => env('SSM_FACEBOOK_APP_ID'),
+                'client_secret'     => env('SSM_FACEBOOK_APP_SECRET'),
+                'fb_exchange_token' => $shortToken,
+            ]);
+
+            if ($longTokenResponse->failed()) {
+                throw new \Exception($longTokenResponse->json('error.message'));
             }
-            //$expiresAt = $expiresAt->format('Y-m-d H:i:s');
-            //保存到数据库中
-            return ['status' => true, 'data' => ['accessToken' => $longLivedAccessToken->getValue(),'accessToken_expiresAt'=>$expiresAt,'userName'=>$useriInfo['data']['name'],'userId'=>$useriInfo['data']['id']]];
-        } else {
-            return ['status' => false, 'data' => '无法获取访问令牌'];
-        }
-    }
 
-    /*
-     * 发布图片,可以发多个图片
-     * $imagePath = ['/path/to/image1.jpg','/path/to/image2.jpg'];
-     */
-    public function postImage($message,$imagePaths,$accessToken) {
-        // 获取pageAccessToken
-        $info = $this->getAccountsInfo($accessToken);
-        if ($info['status'] == false) {
-            return ['status' => false, 'data' => $info['data']];
+            $longToken = $longTokenResponse->json('access_token');
+
+            //当前时间加60天
+            $expiresAt = Carbon::now()->addDays(60)->format('Y-m-d H:i:s');
+
+            // 获取用户信息
+            $userInfo = $this->getFacebookUser($longToken);
+            if (!$userInfo['status']) {
+                return $userInfo;
+            }
+
+            return [
+                'status' => true,
+                'data' => [
+                    'accessToken' => $longToken,
+                    'accessToken_expiresAt' => $expiresAt,
+                    'userName' => $userInfo['data']['name'],
+                    'userId' => $userInfo['data']['id']
+                ]
+            ];
+
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
         }
+    }
 
+    public function postImage($message, $imagePaths, $accessToken)
+    {
         try {
+            $pagesInfo = $this->getAccountsInfo($accessToken);
+            if (!$pagesInfo['status']) {
+                return $pagesInfo;
+            }
+            $results = [];
             $requestContent = [];
             $responseContent = [];
-            $postIds = [];
-            foreach ($info['data'] as $value) {
-                $mediaIds  = [];
-                $pageId = $value['id'];
-                // 第一步:上传每张图片,获取 media_id
-                foreach ($imagePaths as $imagePath){
-                    //dd($imagePath);
-                    $pageAccessToken = $value['pageAccessToken'];
-                    $postData = [
-                        'source' => $this->fb->fileToUpload($imagePath), // 上传图片文件
-                      //  'source' => $imagePath, // 上传图片文件
-                        'published' => false,                    // 添加描述
+            foreach ($pagesInfo['data'] as $page) {
+                $mediaIds = [];
+                foreach ($imagePaths as $imagePath) {
+                    // 上传图片
+                    $uploadResponse = Http::withToken($page['pageAccessToken'])
+                        ->attach('source', file_get_contents($imagePath), basename($imagePath))
+                        ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/photos", [
+                            'published' => false,
+                        ]);
+
+                    if ($uploadResponse->failed()) {
+                        return ['status' => false, 'data' => $uploadResponse->json('error.message')];
+                    }
+                    $mediaIds[] = ['media_fbid' => $uploadResponse->json('id')];
+                    $requestContent[] = [
+                        'url'=> "https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/photos",
+                        'source' => $imagePath,
+                        'published' => false,
                     ];
-                    $postUrl = '/'.$pageId.'/photos';
-
-                    $response = $this->fb->post(
-                        $postUrl,
-                        $postData,
-                        $pageAccessToken
-                    );
-                    $requestContent[] = ['postUrl'=>$postUrl,'data'=>$postData,'pageAccessToken'=>$pageAccessToken];
-                    $responseContent[] = $response->getDecodedBody();
-                    $mediaIds[] = $response->getDecodedBody()['id'];
+                    $responseContent[] = $uploadResponse->json();
                 }
 
-                // 第二步:发布帖子,附加多个 media_id
-                $attachedMedia = array_map(function($id) {
-                    return ['media_fbid' => $id];
-                }, $mediaIds);
+                // 发布帖子
+                $postResponse = Http::withToken($page['pageAccessToken'])
+                    ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/feed", [
+                        'message'         => $message,
+                        'attached_media'  => json_encode($mediaIds),
+                    ]);
 
-                $feedParams = [
-                    'message' => $message,
-                    'attached_media' => json_encode($attachedMedia)
+                if ($postResponse->failed()) {
+                    return ['status' => false, 'data' => $postResponse->json('error.message')];
+                }
+
+                $results[] = $postResponse->json('id');
+                $requestContent[] = [
+                    'url' => "https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/feed",
+                   'message' => $message,
+                    'attached_media' => json_encode($mediaIds),
                 ];
+                $responseContent[] = $postResponse->json();
+            }
 
-                $postUrl = '/'.$pageId.'/feed';
+            return ['status' => true,
+                'data' => [
+                    'responseIds' => $results,
+                    'requestContent' => $requestContent,
+                    'responseContent' => $responseContent,
+                ]];
 
-                $response = $this->fb->post(
-                    $postUrl,
-                    $feedParams, $pageAccessToken
-                );
-                $requestContent[] = ['postUrl'=>$postUrl,'data'=>$feedParams,'pageAccessToken'=>$pageAccessToken];
-                $responseContent[] = $response->getDecodedBody();
-                $postIds[] = $response->getDecodedBody()['id'];
-            }
-            return ['status' => true, 'data' => ['responseIds' => $postIds,'requestContent'=>$requestContent,'responseContent'=>$responseContent]];
-        } catch (Facebook\Exceptions\FacebookResponseException $e) {
-            $errorMsg  = 'Graph API错误:' . $e->getMessage();
-        } catch (Facebook\Exceptions\FacebookSDKException $e) {
-            $errorMsg = 'SDK错误:' . $e->getMessage();
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
         }
-        return ['status'=>false,'data' => $errorMsg];
     }
 
-
-    /*
-     * 发布视频,只能发一个视频
-     */
-    public function postVideo($message,$videoPath,$accessToken) {
-        $info = $this->getAccountsInfo($accessToken);
-        if ($info['status'] == false) {
-            return ['status' => false, 'data' => $info['data']];
-        }
+    public function postVideo($message, $videoPath, $accessToken)
+    {
         try {
-            $postIds = [];
+            $pagesInfo = $this->getAccountsInfo($accessToken);
+            if (!$pagesInfo['status']) {
+                return $pagesInfo;
+            }
+
+            $results = [];
             $requestContent = [];
             $responseContent = [];
-            foreach ($info['data'] as $value) {
-                $pageId = $value['id'];
-                $pageAccessToken = $value['pageAccessToken'];
-                $data = [
-                    'source' => $this->fb->fileToUpload($videoPath), // 上传图片文件
-                    'description' => $message,                     // 添加描述
+            foreach ($pagesInfo['data'] as $page) {
+                $response = Http::withToken($page['pageAccessToken'])
+                    ->attach('source', file_get_contents($videoPath), basename($videoPath))
+                    ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/videos", [
+                        'description' => $message,
+                    ]);
+
+                if ($response->failed()) {
+                    throw new \Exception($response->json('error.message'));
+                }
+
+                $results[] = $response->json('id');
+                $requestContent[] = [
+                    'url' => "https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/videos",
+                    'source' => $videoPath,
+                    'description' => $message,
                 ];
-                $response = $this->fb->post(
-                    '/'.$pageId.'/videos',
-                    $data,
-                    $pageAccessToken
-                );
-                $requestContent[] = ['url'=>'/'.$pageId.'/videos','data'=>$data,'pageAccessToken'=>$pageAccessToken];
-                $responseContent[] = $response->getDecodedBody();
-                $postIds[] = $response->getDecodedBody()['id'];
-                //第二步:发布帖子,附加 media_id
+                $responseContent[] = $response->json();
             }
-            return ['status' => true, 'data' => ['responseIds' => $postIds,'requestContent'=>$requestContent,'responseContent'=>$responseContent]];
-        } catch (Facebook\Exceptions\FacebookResponseException $e) {
-            $errorMsg = 'Graph API错误:' . $e->getMessage();
-        } catch (Facebook\Exceptions\FacebookSDKException $e) {
-            $errorMsg = 'SDK错误:' . $e->getMessage();
-        }
-        return ['status'=>false,'data' => $errorMsg];
-    }
-
-    public function getComments($postId) {
-
-    }
-
-    public function replyToComment($commentId) {
 
-    }
-
-    public function deleteComment($commentId) {
+            return ['status' => true,
+                'data' => [
+                    'responseIds' => $results,
+                    'requestContent' => $requestContent,
+                    'responseContent' => $responseContent,
+                ]];
 
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
+        }
     }
 
-
-    /**
-     * 获取Facebook用户信息
-     * @param string $accessToken
-     * @return array [
-     *     'name' => string,
-     *     'id' => string,
-     *     'error' => string|null
-     * ]
-     */
-    public function getFacebookUser($accessToken)
+    private function getFacebookUser($accessToken)
     {
         try {
-            // 验证并设置访问令牌
-            $this->fb->setDefaultAccessToken($accessToken);
-            // 发送请求获取用户信息
-            $response = $this->fb->get('/me?fields=name,id');
-            $userNode = $response->getGraphUser();
+            $response = Http::withToken($accessToken)
+                ->get("https://graph.facebook.com/{$this->graphVersion}/me", [
+                    'fields' => 'name,id'
+                ]);
+
+            if ($response->failed()) {
+                throw new \Exception($response->json('error.message'));
+            }
+
             return [
                 'status' => true,
-                'data' => [
-                    'name' => $userNode->getName(),
-                    'id' => $userNode->getId(),
-                ]
+                'data' => $response->json()
             ];
-        } catch (FacebookResponseException $e) {
-            // API 响应错误处理
-            $errorMsg = 'Graph API Error: ' . $e->getMessage();
-        } catch (FacebookSDKException $e) {
-            // SDK 错误处理
-            $errorMsg = 'SDK Error: ' . $e->getMessage();
-        } catch (Exception $e) {
-            // 其他错误处理
-            $errorMsg = 'General Error: ' . $e->getMessage();
+
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
         }
-        return [
-            'status' => false,
-            'data' => $errorMsg
-        ];
     }
 
-
-    public function getAccountsInfo($accessToken) {
+    public function getAccountsInfo($accessToken)
+    {
         try {
-            // 验证并设置访问令牌
-            $this->fb->setDefaultAccessToken($accessToken);
-            // 发送请求获取用户信息
-            $response = $this->fb->get('/me/accounts');
-            $userNode = $response->getDecodedBody();
-            if ($userNode['data'] == [] || $userNode['data'] == null || $userNode['data'] == '') {
-                return [
-                    'status' => false,
-                    'data' => '没有获取到任何账号信息'
-                ];
+            $response = Http::withToken($accessToken)
+                ->get("https://graph.facebook.com/{$this->graphVersion}/me/accounts");
+
+            if ($response->failed()) {
+                throw new \Exception($response->json('error.message'));
             }
-            $data = [];
-            foreach ($userNode['data'] as $value) {
-                $data[] = [
-                    'id' => $value['id'],
-                    'name' => $value['name'],
-                    'pageAccessToken' => $value['access_token'],
+
+            $pages = collect($response->json('data'))->map(function ($page) {
+                return [
+                    'id' => $page['id'],
+                    'name' => $page['name'],
+                    'pageAccessToken' => $page['access_token'],
                 ];
+            })->toArray();
+
+            if (empty($pages)) {
+                throw new \Exception('No pages found');
             }
-            return [
-                'status'=>true,
-                'data' => $data
-            ];
-        } catch (FacebookResponseException $e) {
-            // API 响应错误处理
-            $errorMsg = 'Graph API Error: ' . $e->getMessage();
-        } catch (FacebookSDKException $e) {
-            // SDK 错误处理
-            $errorMsg = 'SDK Error: ' . $e->getMessage();
-        } catch (Exception $e) {
-            // 其他错误处理
-            $errorMsg = 'General Error: ' . $e->getMessage();
+
+            return ['status' => true, 'data' => $pages];
+
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
         }
-        return [
-            'status'=>false,
-            'data' => $errorMsg
-        ];
     }
 
+    // 其他接口方法保持空实现
+    public function getComments($postId) {}
+    public function replyToComment($commentId) {}
+    public function deleteComment($commentId) {}
 }

+ 167 - 0
app/Services/Smm/TwitterService.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace App\Services\Smm;
+
+use App\Services\Contracts\SmmPlatformInterface;
+use Abraham\TwitterOAuth\TwitterOAuth;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+
+class TwitterService implements SmmPlatformInterface
+{
+    protected $configData;
+    protected $twitterOAuth;
+
+    protected $consumerKey;
+    protected $consumerSecret;
+
+    protected $oauthCallbackUrl;
+
+    public function __construct($configData)
+    {
+        $this->configData = $configData;
+
+        $this->consumerKey = env('TWITTER_CONSUMER_KEY');
+        $this->consumerSecret = env('TWITTER_CONSUMER_SECRET');
+        $this->oauthCallbackUrl = env('DIST_SITE_URL') . '/dist/callback/twitter';
+
+        if (!empty($configData['access_token']) && !empty($configData['access_token_secret'])) {
+            $this->twitterOAuth = new TwitterOAuth(
+                $this->consumerKey,
+                $this->consumerSecret,
+                $configData['access_token'],
+                $configData['access_token_secret']
+            );
+        }
+    }
+
+    public function login()
+    {
+        $connection = new TwitterOAuth($this->consumerKey, $this->consumerSecret);
+        $requestToken = $connection->oauth('oauth/request_token', ['oauth_callback' => $this->oauthCallbackUrl]);
+
+        session(['twitter_oauth_token' => $requestToken['oauth_token']]);
+        session(['twitter_oauth_token_secret' => $requestToken['oauth_token_secret']]);
+
+        $url = $connection->url('oauth/authorize', ['oauth_token' => $requestToken['oauth_token']]);
+
+        return [
+            'status' => true,
+            'data' => ['url' => $url]
+        ];
+    }
+
+    public function loginCallback(Request $request)
+    {
+        $oauthToken = $request->get('oauth_token');
+        $oauthVerifier = $request->get('oauth_verifier');
+
+        $requestToken = session('twitter_oauth_token');
+        $requestTokenSecret = session('twitter_oauth_token_secret');
+
+        $connection = new TwitterOAuth(
+            $this->consumerKey,
+            $this->consumerSecret,
+            $requestToken,
+            $requestTokenSecret
+        );
+
+        $accessToken = $connection->oauth('oauth/access_token', [
+            'oauth_verifier' => $oauthVerifier
+        ]);
+
+        return [
+            'status' => true,
+            'data' => [
+                'accessToken' => $accessToken['oauth_token'],
+                'accessTokenSecret' => $accessToken['oauth_token_secret'],
+                'userId' => $accessToken['user_id'],
+                'userName' => $accessToken['screen_name'],
+            ]
+        ];
+    }
+
+    public function postImage($message, $imagePaths, $accessToken = null)
+    {
+        try {
+            $mediaIds = [];
+
+            foreach ($imagePaths as $imagePath) {
+                if (!file_exists($imagePath)) {
+                    throw new \Exception("图片不存在: {$imagePath}");
+                }
+
+                $uploadedMedia = $this->twitterOAuth->upload('media/upload', [
+                    'media' => $imagePath
+                ]);
+
+                if (isset($uploadedMedia->error)) {
+                    throw new \Exception('媒体上传失败: ' . json_encode($uploadedMedia));
+                }
+
+                $mediaIds[] = $uploadedMedia->media_id_string;
+            }
+
+            $tweet = $this->twitterOAuth->post('statuses/update', [
+                'status' => $message,
+                'media_ids' => implode(',', $mediaIds)
+            ]);
+
+            if (isset($tweet->errors)) {
+                throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
+            }
+
+            return [
+                'status' => true,
+                'data' => [
+                    'tweetId' => $tweet->id_str,
+                    'mediaIds' => $mediaIds
+                ]
+            ];
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
+        }
+    }
+
+    public function postVideo($message, $videoPath, $accessToken = null)
+    {
+        try {
+            if (!file_exists($videoPath)) {
+                throw new \Exception("视频文件不存在: {$videoPath}");
+            }
+
+            $uploadedVideo = $this->twitterOAuth->upload('media/upload', [
+                'media' => $videoPath,
+                'media_type' => 'video/mp4'
+            ], true); // 注意: chunked upload
+
+            if (isset($uploadedVideo->error)) {
+                throw new \Exception('视频上传失败: ' . json_encode($uploadedVideo));
+            }
+
+            $tweet = $this->twitterOAuth->post('statuses/update', [
+                'status' => $message,
+                'media_ids' => $uploadedVideo->media_id_string
+            ]);
+
+            if (isset($tweet->errors)) {
+                throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
+            }
+
+            return [
+                'status' => true,
+                'data' => [
+                    'tweetId' => $tweet->id_str,
+                    'mediaId' => $uploadedVideo->media_id_string
+                ]
+            ];
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
+        }
+    }
+
+    // 保持空实现
+    public function getComments($postId) {}
+    public function replyToComment($commentId) {}
+    public function deleteComment($commentId) {}
+}

+ 249 - 254
app/Services/Smm/YoutubeService.php

@@ -1,314 +1,309 @@
 <?php
 
 namespace App\Services\Smm;
+
 use App\Services\Contracts\SmmPlatformInterface;
 use Carbon\Carbon;
+use Google_Client;
+use Google_Service_YouTube;
+use Google_Service_YouTube_Video;
+use Google_Service_YouTube_VideoSnippet;
+use Google_Service_YouTube_VideoStatus;
+use Google_Http_MediaFileUpload;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Http;
+use OSS\OssClient;
+use OSS\Core\OssException;
+use Illuminate\Support\Facades\Log;
 
 
 class YoutubeService implements SmmPlatformInterface
 {
-    protected $clientId;
-    protected $clientSecret;
-    // 构造函数,传入配置信息
-    public $configData = [];
+    protected Google_Client $client;
+    protected string $redirectUri;
 
-    public function __construct($configData) {
-        $this->configData = $configData;
-        $this->clientId = env('SSM_INSTAGRAM_APP_ID');
-        $this->clientSecret = env('SSM_INSTAGRAM_APP_SECRET');
-    }
+    protected array $configData;
 
-    /*
-     * OAuth 2.0 授权登录
-     * 返回授权地址:https://example.com/fb-callback.php
-     */
-    public function login()
+    public function __construct(array $configData)
     {
-        $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]];
+        $this->configData = $configData;
+        $this->client = new Google_Client();
+        $this->client->setClientId(env('SSM_YOUTUBE_CLIENT_ID'));
+        $this->client->setClientSecret(env('SSM_YOUTUBE_CLIENT_SECRET'));
+        $this->client->setRedirectUri(env('DIST_SITE_URL').'/dist/callback/youtube');
+        $this->client->setScopes([
+            Google_Service_YouTube::YOUTUBE_UPLOAD,
+            Google_Service_YouTube::YOUTUBE,
+            Google_Service_YouTube::YOUTUBE_FORCE_SSL,
+            Google_Service_YouTube::YOUTUBE_READONLY,
+            Google_Service_YouTube::YOUTUBEPARTNER,
+            Google_Service_YouTube::YOUTUBEPARTNER_CHANNEL_AUDIT
+        ]);
+        $this->client->setAccessType('offline');
+        $this->client->setPrompt('consent');
+        $this->client->setDeveloperKey(env('SSM_YOUTUBE_DEV_KEY'));
     }
 
-    /*
-     * OAuth 2.0 授权回调
-     * 授权成功后,得到access_token,refresh_token等信息, 保存到数据库中
-     * 授权成功后,返回回调需要的数据
-     */
-
-
-    public function loginCallback(Request $request)
+    public function login()
     {
-        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' => '错误:无法获取用户信息 - '];
+        try {
+            return ['status' => true, 'data' => ['url' => $this->client->createAuthUrl()]];
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => 'Failed to generate login URL: '.$e->getMessage()];
         }
-        $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)
+    public function loginCallback(Request $request)
     {
         try {
-            if (empty($imagePaths)) {
-                return ['status' => false, 'data' => '错误:未提供图片路径'];
+            $code = $request->input('code');
+            if (!$code) {
+                return ['status' => false, 'data' => 'Authorization code not found'];
             }
-
-            $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,
-                    ]];
+            $token = $this->client->fetchAccessTokenWithAuthCode($code);
+            if (isset($token['error'])) {
+                return ['status' => false, 'data' => $token['error_description'] ?? 'Failed to exchange authorization code'];
             }
+            $this->client->setAccessToken($token);
+            $youtube = new Google_Service_YouTube($this->client);
+            $channelResponse = $youtube->channels->listChannels('snippet', ['mine' => true]);
 
-            // 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,
+            return [
+                'status' => true,
+                'data' => [
+                    'accessToken' => $token['access_token'],
+                    'refreshToken' => $token['refresh_token'] ?? '',
+                    'accessToken_expiresAt' => Carbon::now()->addSeconds($token['expires_in']),
+                    'userName' => $channelResponse->items[0]->snippet->title ?? '',
+                    'userId' => $channelResponse->items[0]->id ?? '',
+                ]
             ];
-            $carouselResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData );
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
+        }
+    }
 
-            $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)];
+    public function postVideo($message, $videoPath, $accessToken)
+    {
+        $ossPath = ltrim(parse_url($videoPath, PHP_URL_PATH), '/');
+        $fileSize = $this->getOssFileSize($ossPath);
+        dd($fileSize);
+        // 处理 OAuth 回调
+        $this->client->setAccessToken($accessToken);
+        // 初始化 YouTube 服务
+        $youtube = new Google_Service_YouTube($this->client);
+        try {
+            $requestContent = [];
+            // 定义视频元数据
+            $requestContent[] = $snippetData;
+            $snippetData = [
+                'title' => $message,
+                'description' => '',
+                'tags' => [],
+                'categoryId' => '22' // 人物与博客
+            ];
+            $snippet = new Google_Service_YouTube_VideoSnippet();
+            $snippet->setTitle($snippetData['title']);
+            $snippet->setDescription($snippetData['description']);
+            $snippet->setTags($snippetData['tags']);//'test', 'api'
+            $snippet->setCategoryId($snippetData['categoryId']); // 人物与博客
+            // 设置视频状态
+            $status = new Google_Service_YouTube_VideoStatus();
+            $status->setPrivacyStatus('public'); // public, private, unlisted
+            // 创建视频对象
+            $video = new Google_Service_YouTube_Video();
+            $video->setSnippet($snippet);
+            $video->setStatus($status);
+            // 上传视频
+            $this->client->setDefer(true);
+            $insertRequest = $youtube->videos->insert('snippet,status', $video);
+            $media = new Google_Http_MediaFileUpload(
+                $this->client,
+                $insertRequest,
+                'video/*',
+                null,
+                true,
+                2 * 1048576 // 分块大小 2MB
+            );
+            $media->setFileSize($fileSize); // 替换为视频文件路径
+            $status = false;
+            $handle = fopen($videoPath, 'rb');
+            while (!$status && !feof($handle)) {
+                $chunk = fread($handle, 2 * 1048576);
+                $status = $media->nextChunk($chunk);
             }
-            $postIds = [$publishData['id']];
+            fclose($handle);
+            $this->client->setDefer(false);
+            //echo "Video uploaded: " . $status['id'];
+            $responseContent = [$status];
             return ['status' => true,
                 'data' => [
-                    'responseIds' => $postIds,
+                    'responseIds' => [$status['id']],
                     'requestContent' => $requestContent,
                     'responseContent' => $responseContent,
                 ]];
-        } catch (\Exception $e) {
-            //Log::error('Instagram postImage error: ' . $e->getMessage());
-            return ['status' => false, 'data' => '错误:' . $e->getMessage()];
+        } catch (Google_Service_Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
+        } catch (Google_Exception $e) {
+            return ['status' => false, 'data' => $e->getMessage()];
         }
     }
 
-    /*
-     * 发布视频,只能发一个视频
-     */
-    public function postVideo($message, $videoPath, $accessToken)
+
+
+    public function getVideoCategories($regionCode = 'US')
     {
         try {
-            if (empty($videoPath)) {
-                return ['status' => false, 'data' => '错误:未提供视频路径'];
+            $youtube = new Google_Service_YouTube($this->client);
+            $response = $youtube->videoCategories->listVideoCategories('snippet', [
+                'regionCode' => $regionCode
+            ]);
+            $categories = [];
+            foreach ($response->items as $category) {
+                if ($category->snippet->assignable) {
+                    $categories[$category->id] = $category->snippet->title;
+                }
             }
-            $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,
+            return [
+                'status' => true,
+                'data' => $categories
             ];
-            $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);
+        } catch (\Google_Service_Exception $e) {
+            $error = json_decode($e->getMessage(), true)['error']['message'] ?? $e->getMessage();
+            return ['status' => false, 'data' => $error];
+        }
+    }
 
-                $statusData = $statusResponse->json();
-                $status = $statusData['status'] ?? 'ERROR';
-                $attempt++;
+    public function postImage($message, $imagePaths, $accessToken)
+    {
+        return [
+            'status' => false,
+            'error' => 'YouTube API does not support image-only posts'
+        ];
+    }
 
-                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);
+    public function getComments($postId)
+    {
+        try {
+            $youtube = new Google_Service_YouTube($this->client);
+            $response = $youtube->commentThreads->listCommentThreads('snippet', [
+                'videoId' => $postId,
+                'maxResults' => 100
+            ]);
 
-            $publishData = $publishResponse->json();
-            if (!isset($publishData['id'])) {
-                return ['status' => false, 'data' => '错误:无法发布视频 - ' . json_encode($publishData)];
+            $comments = [];
+            foreach ($response->items as $item) {
+                $comment = $item->snippet->topLevelComment->snippet;
+                $comments[] = [
+                    'id' => $item->id,
+                    'text' => $comment->textDisplay,
+                    'author' => $comment->authorDisplayName,
+                    'createdAt' => $comment->publishedAt
+                ];
             }
-            $responseContent[] = $publishData;
-            $postIds = [$publishData['id']];
-            return ['status' => true,
-                'data' => [
-                    'responseIds' => $postIds,
-                    'requestContent' => $requestContent,
-                    'responseContent' => $responseContent,
-                ]];
+
+            return ['status' => true, 'data' => $comments];
         } catch (\Exception $e) {
-            return ['status' => false, 'data' => '错误:' . $e->getMessage()];
+            Log::error('YouTube get comments error: '.$e->getMessage());
+            return ['status' => false, 'error' => $e->getMessage()];
         }
     }
 
+    public function replyToComment($commentId)
+    {
+        $text = '';
+        try {
+            $youtube = new Google_Service_YouTube($this->client);
 
-    public function getComments($postId) {
+            $commentSnippet = new \Google_Service_YouTube_CommentSnippet();
+            $commentSnippet->setTextOriginal($text);
+            $commentSnippet->setParentId($commentId);
 
-    }
+            $comment = new \Google_Service_YouTube_Comment();
+            $comment->setSnippet($commentSnippet);
 
-    public function replyToComment($commentId) {
+            $response = $youtube->comments->insert('snippet', $comment);
 
+            return [
+                'status' => true,
+                'data' => [
+                    'commentId' => $response->getId(),
+                    'text' => $text
+                ]
+            ];
+        } catch (\Exception $e) {
+            Log::error('YouTube reply error: '.$e->getMessage());
+            return ['status' => false, 'error' => $e->getMessage()];
+        }
     }
 
-    public function deleteComment($commentId) {
+    public function deleteComment($commentId)
+    {
+        try {
+            $youtube = new Google_Service_YouTube($this->client);
+            $youtube->comments->delete($commentId);
+            return ['status' => true];
+        } catch (\Exception $e) {
+            Log::error('YouTube delete comment error: '.$e->getMessage());
+            return ['status' => false, 'error' => $e->getMessage()];
+        }
+    }
 
+    public function refreshToken($refreshToken)
+    {
+        try {
+            $this->client->refreshToken($refreshToken);
+            return $this->client->getAccessToken();
+        } catch (\Exception $e) {
+            Log::error('YouTube refresh token error: '.$e->getMessage());
+            return ['error' => $e->getMessage()];
+        }
     }
 
+    public function refreshAccessToken($refreshToken, $expiresAt)
+    {
+        if (Carbon::now() < Carbon::parse($expiresAt)->subMinutes(10)) {
+            return ['status' => false, 'error' => 'Access token is not expired'];
+        }
+
+        try {
+            $newToken = $this->client->fetchAccessTokenWithRefreshToken($refreshToken);
 
+            return [
+                'status' => true,
+                'data' => [
+                    'access_token' => $newToken['access_token'],
+                    'expires_at' => Carbon::now()->addSeconds($newToken['expires_in'])
+                ]
+            ];
+        } catch (\Exception $e) {
+            return ['status' => false, 'error' => 'Failed to refresh access token: '.$e->getMessage()];
+        }
+    }
 
+    /*
+     * 返回oss文件大小 (字节)
+     * $pathName : 'path/to/your/file.txt'
+     */
+    public function getOssFileSize($pathName)
+    {
+        // 配置信息
+        $accessKeyId = env('OSS_ACCESS_KEY_ID');
+        $accessKeySecret = env('OSS_ACCESS_KEY_SECRET');
+        $endpoint = env('OSS_ENDPOINT'); // 替换为你的 Region Endpoint
+        $bucketName = env('OSS_BUCKET');
+        $objectName = $pathName; // OSS 文件路径
+        try {
+            // 创建 OssClient 实例
+            $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
+            // 获取文件元数据
+            $metadata = $ossClient->headObject($bucketName, $objectName);
+            // 从元数据中获取文件大小(字节)
+            $fileSize = $metadata['content-length'];
+            return ['status' => true, 'data' => $fileSize];
+        } catch (OssException $e) {
+            // 错误处理
+            return ['status' => false, 'data' => $e->getMessage()];
+        }
+    }
 }

+ 1 - 1
lang/zh_CN/global.php

@@ -115,7 +115,7 @@ return [
         'title_en'              => '标题',
         'account_id'            => '账号ID',
         'expires_at'            => '过期时间',
-
+        'youtube_category'      => 'Youtube分类',
     ],
     'labels' => [
         'list'         => '列表',

+ 35 - 0
resources/views/distributor/form_custom/select_hide.blade.php

@@ -0,0 +1,35 @@
+<div class="{{$viewClass['form-group']}}" style="display: none;" id="select_hide_{{ $name }}">
+
+    <div  class="{{ $viewClass['label'] }} control-label">
+        <span>{!! $label !!}</span>
+    </div>
+
+    <div class="{{$viewClass['field']}}">
+
+        @include('admin::form.error')
+
+        <input type="hidden" name="{{$name}}"/>
+
+        <select class="form-control {{$class}}" style="width: 100%;" name="{{$name}}" {!! $attributes !!} >
+            <option value=""></option>
+            @if($groups)
+                @foreach($groups as $group)
+                    <optgroup label="{{ $group['label'] }}">
+                        @foreach($group['options'] as $select => $option)
+                            <option value="{{$select}}" {{ $select == $value ?'selected':'' }}>{{$option}}</option>
+                        @endforeach
+                    </optgroup>
+                @endforeach
+             @else
+                @foreach($options as $select => $option)
+                    <option value="{{$select}}" {{ Dcat\Admin\Support\Helper::equal($select, $value) ?'selected':'' }}>{{$option}}</option>
+                @endforeach
+            @endif
+        </select>
+
+        @include('admin::form.help-block')
+
+    </div>
+</div>
+
+@include('admin::form.select-script')