|
@@ -1,314 +1,309 @@
|
|
<?php
|
|
<?php
|
|
|
|
|
|
namespace App\Services\Smm;
|
|
namespace App\Services\Smm;
|
|
|
|
+
|
|
use App\Services\Contracts\SmmPlatformInterface;
|
|
use App\Services\Contracts\SmmPlatformInterface;
|
|
use Carbon\Carbon;
|
|
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\Http\Request;
|
|
-use Illuminate\Support\Facades\Http;
|
|
|
|
|
|
+use OSS\OssClient;
|
|
|
|
+use OSS\Core\OssException;
|
|
|
|
+use Illuminate\Support\Facades\Log;
|
|
|
|
|
|
|
|
|
|
class YoutubeService implements SmmPlatformInterface
|
|
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 {
|
|
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,
|
|
return ['status' => true,
|
|
'data' => [
|
|
'data' => [
|
|
- 'responseIds' => $postIds,
|
|
|
|
|
|
+ 'responseIds' => [$status['id']],
|
|
'requestContent' => $requestContent,
|
|
'requestContent' => $requestContent,
|
|
'responseContent' => $responseContent,
|
|
'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 {
|
|
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) {
|
|
} 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()];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|