Browse Source

社媒对接

moshaorui 2 days ago
parent
commit
924bddad37

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

@@ -10,6 +10,7 @@ use App\Services\SmmService;
 use Carbon\Carbon;
 use Illuminate\Console\Command;
 use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 use Symfony\Component\DomCrawler\Crawler;
 
 
@@ -37,19 +38,23 @@ class TimerSsmPost extends Command
     public function handle()
     {
         //创建日志
+        Log::info('开始生成发送帖子日志');
         $this->createLog();
 
         //刷新access_token
+        Log::info('开始刷新access_token');
         $this->refreshAccessToken();
+
         exit;
 
         //发送社媒帖子开始
+        Log::info('开始发送社媒帖子');
         $sendLog = SmmPostLog::getSendLog(5);
         $logIds = [];
         foreach ($sendLog as $log) {
             if ($log->media_name == 'twitter' && SmmPostLog::twitterCanSend() == false) {
                 //15分钟内不重复发送
-                    echo 'twitter可发送时间未到,暂时无法发送,ID'.$log->post_id;
+                    Log::info('twitter可发送时间未到,暂时无法发送,ID'.$log->post_id);
                     continue;
             }
             $logIds[] = $log->id;
@@ -71,9 +76,11 @@ class TimerSsmPost extends Command
                     $imageVideoUrl[$key] = CommonHelper::ossUrl($url);
                 }
                 $response = $ssmService->postImage($message, $imageVideoUrl,$accessToken);
+                Log::info('图片帖子返回'.json_encode($response));
             } else {
                 $imageVideoUrl = CommonHelper::ossUrl($imageVideoUrl);
                 $response = $ssmService->postVideo($message, $imageVideoUrl,$accessToken);
+                Log::info('视频帖子返回'.json_encode($response));
             }
             //更新post_logs表
             if ($response['status'] == true) {
@@ -91,7 +98,8 @@ class TimerSsmPost extends Command
             }
         }
 
-        $logIds = print_r($logIds);
+        $logIds = json_encode($logIds);
+        Log::info('发送社媒帖子完成'.$logIds);
         dd('发送社媒帖子完成'.$logIds);
     }
 
@@ -110,23 +118,27 @@ class TimerSsmPost extends Command
         foreach ($accounts as $account) {
             try {
                 $expiresAt = Carbon::parse($account->expires_at);
-                if ($expiresAt->diffInSeconds(Carbon::now()) <= 3000) {
+                //少于10分钟就开始刷新access_token
+                if ($expiresAt->diffInSeconds(Carbon::now()) <= 6000) {
+                    Log::info('开始刷新access_token:'. $account->name);
                     $mediaName = $account->getParent->name;
                     $configData = ['accountInfo' => $account->toArray()];
 
                     $ssmService = new SmmService($mediaName, $configData);
                     $result = $ssmService->refreshAccessToken($account->refresh_token);
 
-                    $account->update([
-                        'access_token' => $result['data']['access_token'],
-                        'expires_at' => Carbon::parse($result['data']['expires_at']->format('Y-m-d H:i:s')),
-                        'refresh_token' => $result['data']['refresh_token'],
-                    ]);
-
-                    echo 'access_token 刷新成功:'. $account->name. "\n";
+                    if ($result['status'])  {
+                        $account->access_token = $result['data']['access_token'];
+                        $account->expires_at = $result['data']['expires_at'];
+                        $account->refresh_token = $result['data']['refresh_token'];
+                        $account->save();
+                        Log::info('access_token 刷新成功:'. $account->name);
+                    } else {
+                        Log::info('access_token 刷新失败:'. $result['data']);
+                    }
                 }
             } catch (\Exception $e) {
-                continue;
+                Log::info('access_token 刷新失败:'. $account->name.$e->getMessage());
             }
         }
     }
@@ -163,6 +175,7 @@ class TimerSsmPost extends Command
                     'send_time' => $send_time,
                 ];
                 SmmPostLog::createLog($data);
+                Log::info('生成发送帖子日志:'. $post->id. ','. $account->name. ','. $send_time);
             }
             $post->status = 1;
             $post->save();

+ 74 - 3
app/Distributor/Controllers/SmmPostController.php

@@ -19,6 +19,7 @@ use Dcat\Admin\FormStep\Form as StepForm;
 use Dcat\Admin\Traits\HasUploadedFile;
 use Dcat\Admin\Widgets\Alert;
 use App\Console\Commands\TimerSsmPost;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
 
 class SmmPostController extends AdminDistController
 {
@@ -109,6 +110,9 @@ class SmmPostController extends AdminDistController
             return $this->upload();
         }
         if (isset($post['ALL_STEPS']) && $post['ALL_STEPS'] == '1') {
+            if ($post['account_ids'] == [] || count($post['account_ids']) == 0 || $post['account_ids'] == '' || empty($post['account_ids'][0])) {
+                return Admin::json()->error('请选择社媒帐号');
+            }
             $post_type = $post['post_type'];
             if ($post_type == 0) {
                 $image_video_url = $post['image_url'];
@@ -118,13 +122,16 @@ class SmmPostController extends AdminDistController
                 $post['message'] = $post['video_message'];
             }
             if ($this->checkStoragePath($image_video_url) === false) {
-                return '发送失败,请检查上传文件是否存在';
+                return Admin::json()->error('请检查上传文件是否存在');
             }
             if ($post['send_type'] == 0) {
                 $send_time = Carbon::now();
             } else {
-                //转换时间格式
-                $send_time = Carbon::createFromFormat('Y-m-d H:i:s', $post['send_time']);
+                // 從北京時間字符串創建 Carbon 對象
+                $sendTime = Carbon::createFromFormat('Y-m-d H:i:s', $post['send_time'], 'Asia/Shanghai');
+                // 轉換為 UTC 時間
+                $send_time = $sendTime->setTimezone('UTC');
+               // $send_time = Carbon::createFromFormat('Y-m-d H:i:s', $post['send_time'])->setTimezone('UTC');
             }
             //保存数据
             SmmPost::create($post,$send_time,$image_video_url);
@@ -183,6 +190,66 @@ class SmmPostController extends AdminDistController
         //JS 验证参数不能为空
         if ($index == 0) {
 $step->leaving(<<<JS
+
+// 全局缓存对象,存储被移除的YouTube选项 {selectName: jQueryObject}
+function toggleYouTube(flag) {
+    var \$this = \$(this);
+
+    // 定义选择器
+    var youtubeSelector = 'option:contains("(YouTube)")';
+    var \$helper1 = \$('select[name="account_ids[]_helper1"]');
+    var \$helper2 = \$('select[name="account_ids[]_helper2"]');
+    var \$mainSelect = \$('select[name="account_ids[]"]');
+
+    // 初始化数据存储
+    if (!\$this.data('youtubeOptions')) {
+        \$this.data('youtubeOptions', {
+            helper1: \$helper1.find(youtubeSelector).clone(true),
+            helper2: \$helper2.find(youtubeSelector).clone(true),
+            main: \$mainSelect.find(youtubeSelector).clone(true),
+            positions: {  // 记录原始位置
+                helper1: \$helper1.find(youtubeSelector).index(),
+                helper2: \$helper2.find(youtubeSelector).index(),
+                main: \$mainSelect.find(youtubeSelector).index()
+            }
+        });
+    }
+
+    // 通用移除函数
+    function removeYouTubeOptions(\$container) {
+        \$container.find(youtubeSelector).each(function() {
+            \$(this).detach(); // 使用 detach 保留事件和数据的引用
+        });
+    }
+
+    // 移除所有 YouTube 选项
+    removeYouTubeOptions(\$helper1);
+    removeYouTubeOptions(\$helper2);
+    removeYouTubeOptions(\$mainSelect);
+
+    // 还原逻辑
+    if (flag === false) {
+        var saved = \$this.data('youtubeOptions');
+
+        // 精确还原到原始位置
+        saved.helper1.insertAfter(
+            \$helper1.find('option').eq(saved.positions.helper1 - 1)
+        );
+
+        saved.helper2.insertAfter(
+            \$helper2.find('option').eq(saved.positions.helper2 - 1)
+        );
+
+        saved.main.insertAfter(
+            \$mainSelect.find('option').eq(saved.positions.main - 1)
+        );
+    }
+    //把选择账号全部向左移
+    $(".removeall ").click();
+    //隐藏youtube 分类
+    $('#select_hide_youtube_category').toggle(false);
+}
+
 function validateForm(formArray) {
     lang = '{$lang}';
     // 仅获取radio类型的post_type值
@@ -206,6 +273,8 @@ function validateForm(formArray) {
                 return '帖子留言和图片不能为空';
             }
         }
+        //隐藏youtube的帐号
+        toggleYouTube(true);
     } else if (postType === '1') {
         const videoMessage = formArray.find(item => item.name === 'video_message')?.value;
         const videoUrl = formArray.find(item => item.name === 'video_url')?.value;
@@ -216,6 +285,7 @@ function validateForm(formArray) {
                 return '帖子留言和视频不能为空';
             }
         }
+        toggleYouTube(false);
     }
 
     // 验证send_type规则(仅处理radio类型的send_type)
@@ -253,6 +323,7 @@ if (!accountIds || accountIds.length === 0) {
     }
     return false;
 }
+
 JS);
         }
 

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

@@ -89,6 +89,7 @@ class SmmUserAccount extends EloquentRepository
         $model = new Model();
         $youTube = $model->where('name', 'YouTube')->first();
         $accounts = $model->where('parent_id','=',$youTube->id)->where('expires_at','>', Carbon::now())->orderBy('id', 'asc')->get();
+        //$accounts = $model->where('parent_id','=',$youTube->id)->orderBy('id', 'asc')->get();
         return $accounts;
     }
 

+ 13 - 2
app/Services/Smm/FacebookService.php

@@ -108,6 +108,7 @@ class FacebookService implements SmmPlatformInterface
     public function postImage($message, $imagePaths, $accessToken)
     {
         try {
+            Log::info('开始拿page access token');
             $pagesInfo = $this->getAccountsInfo($accessToken);
             if (!$pagesInfo['status']) {
                 return $pagesInfo;
@@ -117,6 +118,7 @@ class FacebookService implements SmmPlatformInterface
                 $mediaIds = [];
                 foreach ($imagePaths as $imagePath) {
                     // 上传图片
+                    Log::info('开始上传图片');
                     $uploadResponse = Http::withToken($page['pageAccessToken'])
                         ->attach('source', file_get_contents($imagePath), basename($imagePath))
                         ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/photos", [
@@ -125,16 +127,20 @@ class FacebookService implements SmmPlatformInterface
                     if ($uploadResponse->failed()) {
                         return ['status' => false, 'data' => $uploadResponse->json('error.message')];
                     }
+                    Log::info('上传图片完成');
                     $mediaIds[] = ['media_fbid' => $uploadResponse->json('id')];
                 }
 
                 // 发布帖子
+                Log::info('开始发布帖子');
                 $postResponse = Http::withToken($page['pageAccessToken'])
                     ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/feed", [
                         'message'         => $message,
                         'attached_media'  => json_encode($mediaIds),
                     ]);
 
+                Log::info('发布帖子完成');
+
                 if ($postResponse->failed()) {
                     return ['status' => false, 'data' => $postResponse->json('error.message')];
                 }
@@ -155,18 +161,21 @@ class FacebookService implements SmmPlatformInterface
     public function postVideo($message, $videoPath, $accessToken)
     {
         try {
+            Log::info('开始拿page access token');
             $pagesInfo = $this->getAccountsInfo($accessToken);
             if (!$pagesInfo['status']) {
                 return $pagesInfo;
             }
             $results = [];
+
             foreach ($pagesInfo['data'] as $page) {
+                Log::info('开始发布视频');
                 $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,
                     ]);
-
+                Log::info('发布视频完成:');
                 if ($response->failed()) {
                     throw new \Exception($response->json('error.message'));
                 }
@@ -176,7 +185,6 @@ class FacebookService implements SmmPlatformInterface
                 'data' => [
                     'responseIds' => $results,
                 ]];
-
         } catch (\Exception $e) {
             return ['status' => false, 'data' => $e->getMessage()];
         }
@@ -210,6 +218,7 @@ class FacebookService implements SmmPlatformInterface
             $response = Http::withToken($accessToken)
                 ->get("https://graph.facebook.com/{$this->graphVersion}/me/accounts");
 
+
             if ($response->failed()) {
                 throw new \Exception($response->json('error.message'));
             }
@@ -223,12 +232,14 @@ class FacebookService implements SmmPlatformInterface
             })->toArray();
 
             if (empty($pages)) {
+                Log::info('No pages found');
                 throw new \Exception('No pages found');
             }
 
             return ['status' => true, 'data' => $pages];
 
         } catch (\Exception $e) {
+            Log::info('获取页面信息失败:getAccountsInfo:'.$e->getMessage());
             return ['status' => false, 'data' => $e->getMessage()];
         }
     }

+ 12 - 1
app/Services/Smm/InstagramService.php

@@ -8,6 +8,7 @@ use Facebook\Exceptions\FacebookSDKException;
 use Facebook\Facebook;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
 
 
 class InstagramService implements SmmPlatformInterface
@@ -115,8 +116,10 @@ class InstagramService implements SmmPlatformInterface
 
             // If single image
             if (count($imagePaths) === 1) {
+                Log::info('单图发帖');
                 $imagePath = $imagePaths[0];
                 // Create media container for single image
+                Log::info('开始创建媒体容器');
                 $postData = [
                     'image_url' => $imagePath, // Assuming images are publicly accessible
                     'caption' => $message,
@@ -131,6 +134,7 @@ class InstagramService implements SmmPlatformInterface
                     return ['status' => false, 'data' => '错误:无法创建媒体容器 - ' . json_encode($containerData)];
                 }
 
+                Log::info('开始发布媒体容器');
                 // Publish the container
                 $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", [
                     'creation_id' => $containerData['id'],
@@ -153,6 +157,7 @@ class InstagramService implements SmmPlatformInterface
             $requestContent = [];
             $responseContent = [];
             foreach ($imagePaths as $imagePath) {
+                Log::info('多图发帖');
                 $postData = [
                     'image_url' => asset($imagePath),
                     'is_carousel_item' => true,
@@ -162,6 +167,7 @@ class InstagramService implements SmmPlatformInterface
                     'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
                     'postData' => $postData,
                 ];
+                Log::info('上传子媒体容器');
                 $childResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media", $postData);
 
                 $childData = $childResponse->json();
@@ -182,6 +188,7 @@ class InstagramService implements SmmPlatformInterface
                 'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
                 'postData' => $postData,
             ];
+            Log::info('Create carousel container');
             $carouselResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData );
 
             $carouselData = $carouselResponse->json();
@@ -197,6 +204,7 @@ class InstagramService implements SmmPlatformInterface
                 'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media_publish",
                 'postData' => $postData,
             ];
+            Log::info('Publish carousel');
             $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", $postData);
 
             $publishData = $publishResponse->json();
@@ -223,6 +231,7 @@ class InstagramService implements SmmPlatformInterface
             if (empty($videoPath)) {
                 return ['status' => false, 'data' => '错误:未提供视频路径'];
             }
+            Log::info("开始上传视频");
             $igUserId = $this->configData['accountInfo']['account_id'];
 
             // Create media container for video
@@ -237,6 +246,7 @@ class InstagramService implements SmmPlatformInterface
             if (!isset($containerData['id'])) {
                 return ['status' => false, 'data' => '错误:无法创建视频容器 - ' . json_encode($containerData)];
             }
+            Log::info("创建视频的媒体容器完成");
             // Check container status (video processing may take time)
             $status = 'IN_PROGRESS';
             $maxAttempts = 10;
@@ -257,6 +267,7 @@ class InstagramService implements SmmPlatformInterface
                     return ['status' => false, 'data' => '错误:视频处理失败'];
                 }
             }
+            Log::info("检查容器状态完成");
             if ($status !== 'FINISHED') {
                 return ['status' => false, 'data' => '错误:视频处理超时'];
             }
@@ -266,7 +277,7 @@ class InstagramService implements SmmPlatformInterface
                 'access_token' => $accessToken,
             ];
             $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish",$postData);
-
+            Log::info("发布视频完成");
             $publishData = $publishResponse->json();
             if (!isset($publishData['id'])) {
                 return ['status' => false, 'data' => '错误:无法发布视频 - ' . json_encode($publishData)];

+ 20 - 11
app/Services/Smm/TwitterService.php

@@ -7,6 +7,7 @@ use Abraham\TwitterOAuth\TwitterOAuth;
 use Carbon\Carbon;
 use CURLFile;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
 
 class TwitterService implements SmmPlatformInterface
 {
@@ -92,6 +93,7 @@ class TwitterService implements SmmPlatformInterface
 
     public function postImage($message, $imagePaths, $accessToken = null)
     {
+        Log::info('开始发布图片帖子');
         try {
             $this->twitterOAuth->setApiVersion('1.1'); // 强制使用 v1.1 API
             $this->twitterOAuth->setTimeouts(10, 30);
@@ -105,6 +107,7 @@ class TwitterService implements SmmPlatformInterface
                 $uploadedMedia = $this->twitterOAuth->upload('media/upload', [
                     'media' => $imagePath
                 ]);
+                Log::info('1.1版本上传媒体文件完成');
                 if (isset($uploadedMedia->error)) {
                     throw new \Exception('媒体上传失败: ' . json_encode($uploadedMedia));
                 }
@@ -116,6 +119,7 @@ class TwitterService implements SmmPlatformInterface
                 'text' => $message,
                 'media' => !empty($mediaIds) ? ['media_ids' => $mediaIds] : null
             ]);
+            Log::info('2.0版本发布推文完成');
             if (isset($tweet->errors)) {
                 throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
             }
@@ -146,6 +150,8 @@ class TwitterService implements SmmPlatformInterface
                 'media_category' => 'tweet_video'
             ],['chunkedUpload'=>true]);
 
+            Log::info('1.1版本,分块上传视频');
+
             $code = $this->twitterOAuth->getLastHttpCode();
 
             if (isset($uploadedMedia->error) || $code != 200) {
@@ -153,18 +159,18 @@ class TwitterService implements SmmPlatformInterface
             }
 
             $mediaId = $uploadedMedia->media_id_string;
-            print_r($mediaId);
+
 
             $limit = 0;
             do {
                 $status = $this->twitterOAuth->mediaStatus($mediaId);
-                print_r($status);
+                Log::info('检测视频上传状态'.print_r($status,true));
                 sleep(5); // 等待 5 秒
                 $limit++;
             } while ($status->processing_info->state !== 'succeeded' && $limit <= 3); // 最多重试 3 次
 
             if ($status->processing_info->state === 'succeeded') {
-                print_r($status->processing_info->state);
+                //print_r($status->processing_info->state);
                 // 2.0版本发布推文
                 $this->twitterOAuth->setApiVersion('2');
                 $tweet = $this->twitterOAuth->post('tweets', [
@@ -172,17 +178,20 @@ class TwitterService implements SmmPlatformInterface
                     'media' => ['media_ids' => [$mediaId]]
                 ]);
 
-                if ($this->twitterOAuth->getLastHttpCode() == 200) {
-                    return ['status' => true,
-                        'data' => [
-                            'responseIds' => [$tweet->data->id],
-                            ]
-                    ];
-                } else {
+                Log::info('2.0版本发布推文');
+
+                if (isset($tweet->errors)) {
                     throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
                 }
+
+                return ['status' => true,
+                    'data' => [
+                        'responseIds' => [$tweet->data->id],
+                    ]
+                ];
+
             } else {
-                return ['status' => false, 'data' => '视频上传失败或处理超时。状态:' . print_r($status, true) ];
+                return ['status' => false, 'data' => '视频上传失败或处理超时。'  ];
             }
         } catch (\Exception $e) {
             return ['status' => false, 'data' => $e->getMessage()];

+ 11 - 3
app/Services/Smm/YoutubeService.php

@@ -85,9 +85,14 @@ class YoutubeService implements SmmPlatformInterface
 
     public function postVideo($message, $videoPath, $accessToken)
     {
+        Log::info('YouTube 开始发布视频帖子');
+        /*
         $ossPath = ltrim(parse_url($videoPath, PHP_URL_PATH), '/');
         $fileSize = $this->getOssFileSize($ossPath);
-        dd($fileSize);
+        if ($fileSize['status'] === false) {
+            return ['status' => false, 'data' => $fileSize['data']];
+        }*/
+        $videoPath = toStoragePath($videoPath);
         // 处理 OAuth 回调
         $this->client->setAccessToken($accessToken);
         // 初始化 YouTube 服务
@@ -115,6 +120,7 @@ class YoutubeService implements SmmPlatformInterface
             // 上传视频
             $this->client->setDefer(true);
             $insertRequest = $youtube->videos->insert('snippet,status', $video);
+            Log::info('YouTube 分块上传视频');
             $media = new Google_Http_MediaFileUpload(
                 $this->client,
                 $insertRequest,
@@ -123,7 +129,7 @@ class YoutubeService implements SmmPlatformInterface
                 true,
                 2 * 1048576 // 分块大小 2MB
             );
-            $media->setFileSize($fileSize); // 替换为视频文件路径
+            $media->setFileSize(filesize($videoPath)); // 替换为视频文件路径
             $status = false;
             $handle = fopen($videoPath, 'rb');
             while (!$status && !feof($handle)) {
@@ -133,6 +139,7 @@ class YoutubeService implements SmmPlatformInterface
             fclose($handle);
             $this->client->setDefer(false);
             //echo "Video uploaded: " . $status['id'];
+            Log::info('视频上传成功:  '.$status['id']);
             return ['status' => true,
                 'data' => [
                     'responseIds' => [$status['id']],
@@ -259,7 +266,6 @@ class YoutubeService implements SmmPlatformInterface
     {
         try {
             $newToken = $this->client->fetchAccessTokenWithRefreshToken($refreshToken);
-            dd($newToken);
             return [
                 'status' => true,
                 'data' => [
@@ -277,6 +283,7 @@ class YoutubeService implements SmmPlatformInterface
      * 返回oss文件大小 (字节)
      * $pathName : 'path/to/your/file.txt'
      */
+    /*
     public function getOssFileSize($pathName)
     {
         // 配置信息
@@ -298,4 +305,5 @@ class YoutubeService implements SmmPlatformInterface
             return ['status' => false, 'data' => $e->getMessage()];
         }
     }
+    */
 }