|
@@ -12,83 +12,102 @@ use Illuminate\Support\Facades\Log;
|
|
class TwitterService implements SmmPlatformInterface
|
|
class TwitterService implements SmmPlatformInterface
|
|
{
|
|
{
|
|
protected $configData;
|
|
protected $configData;
|
|
- protected $twitterOAuth;
|
|
|
|
|
|
|
|
- protected $consumerKey;
|
|
|
|
- protected $consumerSecret;
|
|
|
|
|
|
+ private $clientId;
|
|
|
|
+ private $clientSecret;
|
|
|
|
|
|
- protected $oauthCallbackUrl;
|
|
|
|
|
|
+ private $access_token;
|
|
|
|
+ private $media_upload_url = 'https://api.x.com/2/media/upload';
|
|
|
|
+ private $tweet_url = 'https://api.x.com/2/tweets';
|
|
|
|
+ private $chunk_size = 4 * 1024 * 1024; // 分块上传的大小,不能大于5M,否则会报错
|
|
|
|
|
|
public function __construct($configData)
|
|
public function __construct($configData)
|
|
{
|
|
{
|
|
|
|
+ $this->clientId = env('SSM_X_CLIENT_ID');
|
|
|
|
+ $this->clientSecret = env('SSM_X_CLIENT_SECRET');
|
|
$this->configData = $configData;
|
|
$this->configData = $configData;
|
|
-
|
|
|
|
- $this->consumerKey = env('SSM_X_CONSUMER_KEY');
|
|
|
|
- $this->consumerSecret = env('SSM_X_CONSUMER_SECRET');
|
|
|
|
- $this->oauthCallbackUrl = env('DIST_SITE_URL') . '/dist/callback/twitter';
|
|
|
|
-
|
|
|
|
- if (isset($configData['accountInfo'])) {
|
|
|
|
- //初始化1.1版本的配置信息
|
|
|
|
- $access_token = $configData['accountInfo']['access_token'];
|
|
|
|
- $backup_field1 = json_decode($configData['accountInfo']['backup_field1'],true);
|
|
|
|
- $access_token_secret = $backup_field1['accessTokenSecret'];
|
|
|
|
- if (!empty($access_token) && !empty($access_token_secret)) {
|
|
|
|
- $this->twitterOAuth = new TwitterOAuth(
|
|
|
|
- $this->consumerKey,
|
|
|
|
- $this->consumerSecret,
|
|
|
|
- $access_token,
|
|
|
|
- $access_token_secret
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public function login()
|
|
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']]);
|
|
|
|
-
|
|
|
|
|
|
+ $redirectUri = env('DIST_SITE_URL') . '/dist/callback/twitter';
|
|
|
|
+ $scopes = ['tweet.read','tweet.write','dm.write', 'users.read', 'offline.access','media.write']; // 需要的权限范围
|
|
|
|
+ $oauthState = bin2hex(random_bytes(16));
|
|
|
|
+ $authUrl = 'https://twitter.com/i/oauth2/authorize?' . http_build_query([
|
|
|
|
+ 'response_type' => 'code',
|
|
|
|
+ 'client_id' => $this->clientId,
|
|
|
|
+ 'redirect_uri' => $redirectUri,
|
|
|
|
+ 'scope' => implode(' ', $scopes),
|
|
|
|
+ 'state' => $oauthState,
|
|
|
|
+ 'code_challenge' => 'challenge', // 如果使用 PKCE 需要生成
|
|
|
|
+ 'code_challenge_method' => 'plain'
|
|
|
|
+ ]);
|
|
return [
|
|
return [
|
|
'status' => true,
|
|
'status' => true,
|
|
- 'data' => ['url' => $url]
|
|
|
|
|
|
+ 'data' => ['url' => $authUrl]
|
|
];
|
|
];
|
|
}
|
|
}
|
|
|
|
|
|
public function loginCallback(Request $request)
|
|
public function loginCallback(Request $request)
|
|
{
|
|
{
|
|
try {
|
|
try {
|
|
- $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
|
|
|
|
- ]);
|
|
|
|
- $expiresAt = Carbon::now()->addDays(90)->format('Y-m-d H:i:s');
|
|
|
|
- return [
|
|
|
|
- 'status' => true,
|
|
|
|
- 'data' => [
|
|
|
|
- 'accessToken' => $accessToken['oauth_token'],
|
|
|
|
- 'backupField1' => json_encode(['accessTokenSecret'=>$accessToken['oauth_token_secret']]),
|
|
|
|
- 'userId' => $accessToken['user_id'],
|
|
|
|
- 'userName' => $accessToken['screen_name'],
|
|
|
|
- 'accessToken_expiresAt' => $expiresAt,
|
|
|
|
- ]
|
|
|
|
|
|
+ $tokenUrl = 'https://api.twitter.com/2/oauth2/token';
|
|
|
|
+ $redirectUri = env('DIST_SITE_URL') . '/dist/callback/twitter';
|
|
|
|
+ $params = [
|
|
|
|
+ 'code' => $_GET['code'],
|
|
|
|
+ 'grant_type' => 'authorization_code',
|
|
|
|
+ 'client_id' => $this->clientId,
|
|
|
|
+ 'redirect_uri' => $redirectUri,
|
|
|
|
+ 'code_verifier' => 'challenge' // 如果使用 PKCE 需要对应
|
|
];
|
|
];
|
|
|
|
+ $ch = curl_init();
|
|
|
|
+ curl_setopt($ch, CURLOPT_URL, $tokenUrl);
|
|
|
|
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
+ curl_setopt($ch, CURLOPT_POST, true);
|
|
|
|
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
|
|
|
|
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
|
|
+ 'Authorization: Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret),
|
|
|
|
+ 'Content-Type: application/x-www-form-urlencoded'
|
|
|
|
+ ]);
|
|
|
|
+ $auth = curl_exec($ch);
|
|
|
|
+ curl_close($ch);
|
|
|
|
+ $auth = json_decode($auth, true);
|
|
|
|
+ if (isset($auth['access_token']) == false) {
|
|
|
|
+ return ['status' => false, 'data' => '获取token失败'];
|
|
|
|
+ }
|
|
|
|
+ $this->access_token = $auth['access_token'];
|
|
|
|
+ $url = 'https://api.twitter.com/2/users/me?user.fields=id,username,name';
|
|
|
|
+ $result = $this->twitterApiRequest($url, 'GET');
|
|
|
|
+ // var_dump($result);
|
|
|
|
+ // exit;
|
|
|
|
+ if ($result['code'] != 200) {
|
|
|
|
+ return ['status' => false, 'data' => '获取token失败'];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (isset($auth['access_token'])) {
|
|
|
|
+ $userData = $result['response']['data'];
|
|
|
|
+ $userId = $userData['id'];
|
|
|
|
+ $username = $userData['username'];
|
|
|
|
+ $name = $userData['name'];
|
|
|
|
+
|
|
|
|
+ $refresh_token = $auth['refresh_token'] ?? '';
|
|
|
|
+ $expires_in = $auth['expires_in'] ?? 0;
|
|
|
|
+ $expiresAt = Carbon::now()->addSeconds($expires_in)->format('Y-m-d H:i:s');
|
|
|
|
+
|
|
|
|
+ return [
|
|
|
|
+ 'status' => true,
|
|
|
|
+ 'data' => [
|
|
|
|
+ 'accessToken' => $auth['access_token'],
|
|
|
|
+ 'backupField1' => json_encode(['refresh_token'=>$refresh_token]),
|
|
|
|
+ 'userId' => $userId,
|
|
|
|
+ 'userName' => $username,
|
|
|
|
+ 'accessToken_expiresAt' => $expiresAt,
|
|
|
|
+ ]
|
|
|
|
+ ];
|
|
|
|
+ } else {
|
|
|
|
+ return ['status' => false, 'data' => '获取token失败'];
|
|
|
|
+ }
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
return ['status' => false, 'data' => $e->getMessage()];
|
|
return ['status' => false, 'data' => $e->getMessage()];
|
|
}
|
|
}
|
|
@@ -97,40 +116,22 @@ class TwitterService implements SmmPlatformInterface
|
|
|
|
|
|
public function postImage($message, $imagePaths, $accessToken = null)
|
|
public function postImage($message, $imagePaths, $accessToken = null)
|
|
{
|
|
{
|
|
- Log::info('开始发布图片帖子');
|
|
|
|
try {
|
|
try {
|
|
- $this->twitterOAuth->setApiVersion('1.1'); // 强制使用 v1.1 API
|
|
|
|
- $this->twitterOAuth->setTimeouts(15, 60);
|
|
|
|
- $mediaIds = [];
|
|
|
|
|
|
+ $this->access_token = $accessToken;
|
|
|
|
+ $videoPaths = [];
|
|
foreach ($imagePaths as $imagePath) {
|
|
foreach ($imagePaths as $imagePath) {
|
|
- $imagePath = toStoragePath($imagePath);
|
|
|
|
- if (!file_exists($imagePath)) {
|
|
|
|
- throw new \Exception("图片不存在: {$imagePath}");
|
|
|
|
- }
|
|
|
|
- //1.1版本上传媒体文件
|
|
|
|
- $uploadedMedia = $this->twitterOAuth->upload('media/upload', [
|
|
|
|
- 'media' => $imagePath
|
|
|
|
- ]);
|
|
|
|
- Log::info('1.1版本上传媒体文件完成');
|
|
|
|
- if (isset($uploadedMedia->error)) {
|
|
|
|
- throw new \Exception('媒体上传失败: ' . json_encode($uploadedMedia));
|
|
|
|
- }
|
|
|
|
- $mediaIds[] = $uploadedMedia->media_id_string;
|
|
|
|
|
|
+ $videoPaths[] = toStoragePath($imagePath);//用本地路径上传
|
|
}
|
|
}
|
|
- // 2.0版本发布推文
|
|
|
|
- $this->twitterOAuth->setApiVersion('2');
|
|
|
|
- $tweet = $this->twitterOAuth->post('tweets', [
|
|
|
|
- 'text' => $message,
|
|
|
|
- 'media' => !empty($mediaIds) ? ['media_ids' => $mediaIds] : null
|
|
|
|
- ]);
|
|
|
|
- Log::info('2.0版本发布推文完成');
|
|
|
|
- if (isset($tweet->errors)) {
|
|
|
|
- throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
|
|
|
|
|
|
+ $mediaResult = $this->uploadAndPost($videoPaths, $message);
|
|
|
|
+ if (isset($mediaResult['response']['data']['id'])) {
|
|
|
|
+ return ['status' => true,
|
|
|
|
+ 'data' => [
|
|
|
|
+ 'responseIds' => [$mediaResult['response']['data']['id']],
|
|
|
|
+ ]
|
|
|
|
+ ];
|
|
|
|
+ } else {
|
|
|
|
+ return ['status' => false, 'data' => '发布失败'];
|
|
}
|
|
}
|
|
- return ['status' => true,
|
|
|
|
- 'data' => [
|
|
|
|
- 'responseIds' => [$tweet->data->id],
|
|
|
|
- ]];
|
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
return ['status' => false, 'data' => $e->getMessage()];
|
|
return ['status' => false, 'data' => $e->getMessage()];
|
|
}
|
|
}
|
|
@@ -140,80 +141,244 @@ class TwitterService implements SmmPlatformInterface
|
|
public function postVideo($message, $videoPath, $accessToken = null)
|
|
public function postVideo($message, $videoPath, $accessToken = null)
|
|
{
|
|
{
|
|
try {
|
|
try {
|
|
- $this->twitterOAuth->setTimeouts(15, 120);
|
|
|
|
- $this->twitterOAuth->setApiVersion('1.1');
|
|
|
|
- $videoPath = toStoragePath($videoPath);
|
|
|
|
- // Verify file exists and is readable
|
|
|
|
- if (!file_exists($videoPath) || !is_readable($videoPath)) {
|
|
|
|
- throw new Exception("Video file does not exist or is not readable: $videoPath");
|
|
|
|
|
|
+ $this->access_token = $accessToken;
|
|
|
|
+ $videoPath = toStoragePath($videoPath);//用本地路径上传
|
|
|
|
+ $mediaResult = $this->uploadAndPost([$videoPath], $message);
|
|
|
|
+ if (isset($mediaResult['response']['data']['id'])) {
|
|
|
|
+ return ['status' => true,
|
|
|
|
+ 'data' => [
|
|
|
|
+ 'responseIds' => [$mediaResult['response']['data']['id']],
|
|
|
|
+ ]
|
|
|
|
+ ];
|
|
|
|
+ } else {
|
|
|
|
+ return ['status' => false, 'data' => '上传视频失败'];
|
|
|
|
+ }
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
+ return ['status' => false, 'data' => $e->getMessage()];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 保持空实现
|
|
|
|
+ public function getComments($postId) {}
|
|
|
|
+
|
|
|
|
+ public function replyToComment($commentId) {}
|
|
|
|
+
|
|
|
|
+ public function deleteComment($commentId) {}
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 通用Twitter API请求
|
|
|
|
+ */
|
|
|
|
+ private function twitterApiRequest($url, $method = 'POST', $data = [], $headers = []) {
|
|
|
|
+ try {
|
|
|
|
+ Log::info('curl 请求:'.$url);
|
|
|
|
+ $ch = curl_init();
|
|
|
|
+ $headers = array_merge([
|
|
|
|
+ 'Authorization: Bearer ' . $this->access_token
|
|
|
|
+ ], $headers);
|
|
|
|
+
|
|
|
|
+ Log::info('curl $headers:'.json_encode($headers));
|
|
|
|
+
|
|
|
|
+ curl_setopt_array($ch, [
|
|
|
|
+ CURLOPT_URL => $url,
|
|
|
|
+ CURLOPT_RETURNTRANSFER => true,
|
|
|
|
+ CURLOPT_FOLLOWLOCATION => true,
|
|
|
|
+ CURLOPT_HTTPHEADER => $headers,
|
|
|
|
+ CURLOPT_TIMEOUT => 50,
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ if ($method === 'POST') {
|
|
|
|
+ curl_setopt($ch, CURLOPT_POST, true);
|
|
|
|
+
|
|
|
|
+ // 处理JSON数据
|
|
|
|
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
|
}
|
|
}
|
|
|
|
|
|
- Log::info('twitter上传视频'.$videoPath);
|
|
|
|
|
|
+ $response = curl_exec($ch);
|
|
|
|
+ $error = curl_error($ch);
|
|
|
|
+ curl_close($ch);
|
|
|
|
+
|
|
|
|
+ Log::info('curl response:'.$response);
|
|
|
|
|
|
- echo '1';
|
|
|
|
- // Upload video with chunked upload
|
|
|
|
- $uploadedMedia = $this->twitterOAuth->upload('media/upload', [
|
|
|
|
- 'media' => $videoPath,
|
|
|
|
- // 'media_category' => 'tweet_video'
|
|
|
|
- 'media_type' => 'video/mp4'
|
|
|
|
- ],['chunkedUpload'=>true]);
|
|
|
|
|
|
+ if ($error) {
|
|
|
|
+ throw new \Exception("cURL Error: ".$error);
|
|
|
|
+ }
|
|
|
|
|
|
- // dd($uploadedMedia);
|
|
|
|
- // dd($uploadedMedia);
|
|
|
|
|
|
+ if ($response === false) {
|
|
|
|
+ throw new \Exception("cURL Error: false response");
|
|
|
|
+ }
|
|
|
|
|
|
- Log::info('1.1版本,分块上传视频');
|
|
|
|
|
|
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
|
|
- $code = $this->twitterOAuth->getLastHttpCode();
|
|
|
|
|
|
+ return ['code'=>$http_code,'response' => json_decode($response, true)];
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
+ throw new \Exception("catch cURL Error: ".$e->getMessage());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 初始化上传
|
|
|
|
+ */
|
|
|
|
+ public function initUpload($fileSize, $mediaType = 'video/mp4',$media_category = 'tweet_video') {
|
|
|
|
+ return $this->twitterApiRequest($this->media_upload_url, 'POST', [
|
|
|
|
+ 'command' => 'INIT',
|
|
|
|
+ 'media_type' => $mediaType,
|
|
|
|
+ 'total_bytes' => $fileSize,
|
|
|
|
+ 'media_category' => $media_category
|
|
|
|
+ ]);
|
|
|
|
+ }
|
|
|
|
|
|
- if (isset($uploadedMedia->error) || $code != 200) {
|
|
|
|
- throw new \Exception('媒体上传失败: ' . json_encode($uploadedMedia));
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 分块上传
|
|
|
|
+ */
|
|
|
|
+ public function appendUpload($media_id, $filePath) {
|
|
|
|
+ $handle = fopen($filePath, 'rb');
|
|
|
|
+ $segment_index = 0;
|
|
|
|
+
|
|
|
|
+ while (!feof($handle)) {
|
|
|
|
+ $chunk = fread($handle, $this->chunk_size);
|
|
|
|
+ $result = $this->twitterApiRequest($this->media_upload_url, 'POST', [
|
|
|
|
+ 'command' => 'APPEND',
|
|
|
|
+ 'media_id' => $media_id,
|
|
|
|
+ 'segment_index' => $segment_index,
|
|
|
|
+ 'media' => $chunk
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ Log::info('分块上传'.$segment_index.' : '.json_encode($result));
|
|
|
|
+
|
|
|
|
+ if ($result['code'] !== 204) {
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
+ $segment_index++;
|
|
|
|
+ sleep(2); // 避免速率限制
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fclose($handle);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 完成上传
|
|
|
|
+ */
|
|
|
|
+ public function finalizeUpload($media_id) {
|
|
|
|
+ return $this->twitterApiRequest($this->media_upload_url, 'POST', [
|
|
|
|
+ 'command' => 'FINALIZE',
|
|
|
|
+ 'media_id' => $media_id
|
|
|
|
+ ]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 检查媒体状态
|
|
|
|
+ */
|
|
|
|
+ public function checkStatus($media_id) {
|
|
|
|
+ $url = $this->media_upload_url . '?' . http_build_query([
|
|
|
|
+ 'command' => 'STATUS',
|
|
|
|
+ 'media_id' => $media_id
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ return $this->twitterApiRequest($url, 'GET');
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 等待媒体处理完成
|
|
|
|
+ */
|
|
|
|
+ public function waitForProcessing($media_id, $interval = 10, $max_attempts = 6) {
|
|
|
|
+ $attempts = 0;
|
|
|
|
+ do {
|
|
|
|
+ $status = $this->checkStatus($media_id);
|
|
|
|
+ Log::info('等待媒体处理完成:'.$attempts.' : '.json_encode($status));
|
|
|
|
+ if (!isset($status['response']['data'])) {
|
|
|
|
+ throw new \Exception("waitForProcessing failed: status data not found");
|
|
|
|
+ }
|
|
|
|
+ $state = $status['response']['data']['processing_info']['state'] ?? '';
|
|
|
|
|
|
- $mediaId = $uploadedMedia->media_id_string;
|
|
|
|
|
|
+ if ($state === 'succeeded') return true;
|
|
|
|
+ if ($state === 'failed') return false;
|
|
|
|
|
|
|
|
+ sleep($interval);
|
|
|
|
+ $attempts++;
|
|
|
|
+ } while ($attempts < $max_attempts);
|
|
|
|
|
|
- $limit = 0;
|
|
|
|
- do {
|
|
|
|
- $status = $this->twitterOAuth->mediaStatus($mediaId);
|
|
|
|
- Log::info('检测视频上传状态'.print_r($status,true));
|
|
|
|
- sleep(5); // 等待 5 秒
|
|
|
|
- $limit++;
|
|
|
|
- } while ($status->processing_info->state !== 'succeeded' && $limit <= 3); // 最多重试 3 次
|
|
|
|
|
|
+ throw new \Exception("Media processing timeout");
|
|
|
|
+ }
|
|
|
|
|
|
- if ($status->processing_info->state === 'succeeded') {
|
|
|
|
- //print_r($status->processing_info->state);
|
|
|
|
- // 2.0版本发布推文
|
|
|
|
- $this->twitterOAuth->setApiVersion('2');
|
|
|
|
- $tweet = $this->twitterOAuth->post('tweets', [
|
|
|
|
- 'text' => $message,
|
|
|
|
- 'media' => ['media_ids' => [$mediaId]]
|
|
|
|
- ]);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 发布推文
|
|
|
|
+ */
|
|
|
|
+ public function postTweet($text, $media_ids) {
|
|
|
|
+ $data = [
|
|
|
|
+ 'text' => $text,
|
|
|
|
+ 'media' => ['media_ids' => $media_ids]
|
|
|
|
+ ];
|
|
|
|
+ $data = json_encode($data);
|
|
|
|
+ return $this->twitterApiRequest($this->tweet_url, 'POST',$data , ['Content-Type: application/json']);
|
|
|
|
+ }
|
|
|
|
|
|
- Log::info('2.0版本发布推文');
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 完整上传流程
|
|
|
|
+ */
|
|
|
|
+ public function uploadAndPost($filePaths, $tweetText) {
|
|
|
|
+ try {
|
|
|
|
+ $media_ids = [];
|
|
|
|
+ foreach ($filePaths as $filePath) {
|
|
|
|
+ $file_info = pathinfo($filePath);
|
|
|
|
+ $file_extension = $file_info['extension'];
|
|
|
|
+
|
|
|
|
+ if ($file_extension == 'mp4' && filesize($filePath) > 20 * 1024 * 1024) {
|
|
|
|
+ //不能大于20M
|
|
|
|
+ return ['status'=>false,'message' => 'File size too large (max 20M)'];
|
|
|
|
+ } else if (($file_extension == 'jpg' || $file_extension == 'png') && filesize($filePath) > 5 * 1024 * 1024) {
|
|
|
|
+ //不能大于5M
|
|
|
|
+ return ['status'=>false,'message' => 'File size too large (max 5M)'];
|
|
|
|
+ }
|
|
|
|
+ if ($file_extension == 'mp4') {
|
|
|
|
+ $media_category = 'tweet_video';
|
|
|
|
+ $mediaType = 'video/mp4';
|
|
|
|
+ } else if ($file_extension == 'jpg') {
|
|
|
|
+ $media_category = 'tweet_image';
|
|
|
|
+ $mediaType = 'image/jpeg';
|
|
|
|
+ } else {
|
|
|
|
+ $media_category = 'tweet_image';
|
|
|
|
+ $mediaType = 'image/png';
|
|
|
|
+ }
|
|
|
|
+ Log::info('Twitter开始发布帖子,文件格式:'.$file_extension);
|
|
|
|
+ // 1. 初始化上传
|
|
|
|
+ $init = $this->initUpload(filesize($filePath),$mediaType,$media_category);
|
|
|
|
+ $media_id = isset($init['response']['data']['id']) ? $init['response']['data']['id'] : null;
|
|
|
|
+ $media_ids[] = $media_id;
|
|
|
|
+ if (!$media_id) {
|
|
|
|
+ return ['status'=>false,'message' => 'Init upload failed'];
|
|
|
|
+ }
|
|
|
|
+ Log::info('初始化上传成功'.$media_id);
|
|
|
|
|
|
- if (isset($tweet->errors)) {
|
|
|
|
- throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
|
|
|
|
|
|
+ // 2. 分块上传
|
|
|
|
+ $append = $this->appendUpload($media_id, $filePath);
|
|
|
|
+ if (!$append) {
|
|
|
|
+ return ['status'=>false,'message' => 'Append upload failed'];
|
|
}
|
|
}
|
|
|
|
|
|
- return ['status' => true,
|
|
|
|
- 'data' => [
|
|
|
|
- 'responseIds' => [$tweet->data->id],
|
|
|
|
- ]
|
|
|
|
- ];
|
|
|
|
|
|
+ // 3. 完成上传
|
|
|
|
+ $finalize = $this->finalizeUpload($media_id);
|
|
|
|
+ Log::info('完成上传:'.json_encode($finalize));
|
|
|
|
+ if (isset($finalize['response']['data']) == false) {
|
|
|
|
+ return ['status'=>false,'message' => 'Finalize upload failed'];
|
|
|
|
+ }
|
|
|
|
|
|
- } else {
|
|
|
|
- return ['status' => false, 'data' => '视频上传失败或处理超时。' ];
|
|
|
|
|
|
+ // 4. mp4 等待处理完成
|
|
|
|
+ if ($file_extension == 'mp4') {
|
|
|
|
+ Log::info('等待处理完成');
|
|
|
|
+ if (!$this->waitForProcessing($media_id)) {
|
|
|
|
+ throw new \Exception('Media processing failed');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // 5. 发布推文
|
|
|
|
+ Log::info('发布推文');
|
|
|
|
+ $postTweet = $this->postTweet($tweetText, $media_ids);
|
|
|
|
+ Log::info('发布推文结果:'.json_encode($postTweet));
|
|
|
|
+ return $postTweet;
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
- return ['status' => false, 'data' => '视频上传异常: '.$e->getMessage()];
|
|
|
|
|
|
+ return ['status'=>false,'message' => "Upload failed: ".$e->getMessage()];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
-
|
|
|
|
- // 保持空实现
|
|
|
|
- public function getComments($postId) {}
|
|
|
|
- public function replyToComment($commentId) {}
|
|
|
|
- public function deleteComment($commentId) {}
|
|
|
|
}
|
|
}
|