configData = $configData; $this->consumerKey = 'ahCsl1rD6ml2Ofma5UKUaMveJ'; $this->consumerSecret = 'CsS5AHR1vIVuuhWHrbV8RR9DrF6JvZjjMOIt7oA9YubpxllDWD'; $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() { $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 ]); $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, ] ]; } public function postImage($message, $imagePaths, $accessToken = null) { try { $this->twitterOAuth->setApiVersion('1.1'); // 强制使用 v1.1 API $this->twitterOAuth->setTimeouts(10, 30); $mediaIds = []; 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 ]); if (isset($uploadedMedia->error)) { throw new \Exception('媒体上传失败: ' . json_encode($uploadedMedia)); } $mediaIds[] = $uploadedMedia->media_id_string; } // 2.0版本发布推文 $this->twitterOAuth->setApiVersion('2'); $tweet = $this->twitterOAuth->post('tweets', [ 'text' => $message, 'media' => !empty($mediaIds) ? ['media_ids' => $mediaIds] : null ]); if (isset($tweet->errors)) { throw new \Exception('推文发布失败: ' . json_encode($tweet->errors)); } return ['status' => true, 'data' => [ 'responseIds' => [$tweet->data->id], ]]; } catch (\Exception $e) { return ['status' => false, 'data' => $e->getMessage()]; } } public function postVideo($message, $videoPath, $accessToken = null) { try { $this->twitterOAuth->setTimeouts(10, 30); //转为本地路径 $videoPath = toStoragePath($videoPath); if (!file_exists($videoPath)) { throw new \Exception("视频文件不存在: {$videoPath}"); } // 获取视频 MIME 类型 $mimeType = mime_content_type($videoPath); if (strpos($mimeType, 'video/') !== 0) { throw new \Exception("文件不是有效的视频文件。MIME 类型: " . $mimeType); } // 获取视频文件大小 (以字节为单位) $fileSize = filesize($videoPath); // 1.1版本上传媒体文件 - 初始化上传 $initParams = [ 'command' => 'INIT', 'media_type' => $mimeType, 'media_category' => 'tweet_video', 'total_bytes' => $fileSize, ]; $uploadedInit = $this->twitterOAuth->upload('media/upload', $initParams, true); if (!isset($uploadedInit->media_id_string)) { throw new \Exception('视频上传初始化失败: ' . json_encode($uploadedInit)); } $mediaId = $uploadedInit->media_id_string; // 分段上传视频 $fileHandle = fopen($videoPath, 'r'); $segmentId = 0; while (!feof($fileHandle)) { $chunk = fread($fileHandle, 1024 * 1024); // 每次上传 1MB $segmentParams = [ 'command' => 'APPEND', 'media_id' => $mediaId, 'segment_index' => $segmentId, 'media' => $chunk, ]; $uploadedSegment = $this->twitterOAuth->upload('media/upload', $segmentParams, true); if (!empty($uploadedSegment->error)) { fclose($fileHandle); throw new \Exception('视频分段上传失败: ' . json_encode($uploadedSegment)); } $segmentId++; } fclose($fileHandle); // 1.1版本上传媒体文件 - 完成上传 $finalizeParams = [ 'command' => 'FINALIZE', 'media_id' => $mediaId, ]; $uploadedFinalize = $this->twitterOAuth->upload('media/upload', $finalizeParams, true); if (isset($uploadedFinalize->processing_info)) { $processingInfo = $uploadedFinalize->processing_info; $checkStatusSecs = $processingInfo->check_after_secs; $status = $processingInfo->state; while ($status !== 'succeeded' && $status !== 'failed') { sleep($checkStatusSecs); $statusParams = [ 'command' => 'STATUS', 'media_id' => $mediaId, ]; $uploadedStatus = $this->twitterOAuth->upload('media/upload', $statusParams, true); if (!empty($uploadedStatus->error)) { throw new \Exception('获取视频上传状态失败: ' . json_encode($uploadedStatus)); } $processingInfo = $uploadedStatus->processing_info; $checkStatusSecs = $processingInfo->check_after_secs; $status = $processingInfo->state; if ($status === 'failed') { throw new \Exception('视频上传失败,Twitter 处理失败: ' . json_encode($uploadedStatus)); } } } elseif (isset($uploadedFinalize->error)) { throw new \Exception('视频上传完成步骤失败: ' . json_encode($uploadedFinalize)); } // 2.0版本发布推文 $this->twitterOAuth->setApiVersion('2'); $tweet = $this->twitterOAuth->post('tweets', [ 'text' => $message, 'media' => ['media_ids' => [$mediaId]], ]); if (isset($tweet->errors)) { throw new \Exception('推文发布失败: ' . json_encode($tweet->errors)); } return ['status' => true, 'data' => [ 'responseIds' => [$tweet->data->id], ] ]; } catch (\Exception $e) { return ['status' => false, 'data' => $e->getMessage()]; } } // 保持空实现 public function getComments($postId) {} public function replyToComment($commentId) {} public function deleteComment($commentId) {} }