TwitterService.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. namespace App\Services\Smm;
  3. use App\Services\Contracts\SmmPlatformInterface;
  4. use Abraham\TwitterOAuth\TwitterOAuth;
  5. use Carbon\Carbon;
  6. use CURLFile;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Support\Facades\Log;
  9. class TwitterService implements SmmPlatformInterface
  10. {
  11. protected $configData;
  12. protected $twitterOAuth;
  13. protected $consumerKey;
  14. protected $consumerSecret;
  15. protected $oauthCallbackUrl;
  16. public function __construct($configData)
  17. {
  18. $this->configData = $configData;
  19. $this->consumerKey = env('SSM_X_CONSUMER_KEY');
  20. $this->consumerSecret = env('SSM_X_CONSUMER_SECRET');
  21. $this->oauthCallbackUrl = env('DIST_SITE_URL') . '/dist/callback/twitter';
  22. if (isset($configData['accountInfo'])) {
  23. //初始化1.1版本的配置信息
  24. $access_token = $configData['accountInfo']['access_token'];
  25. $backup_field1 = json_decode($configData['accountInfo']['backup_field1'],true);
  26. $access_token_secret = $backup_field1['accessTokenSecret'];
  27. if (!empty($access_token) && !empty($access_token_secret)) {
  28. $this->twitterOAuth = new TwitterOAuth(
  29. $this->consumerKey,
  30. $this->consumerSecret,
  31. $access_token,
  32. $access_token_secret
  33. );
  34. }
  35. }
  36. }
  37. public function login()
  38. {
  39. $connection = new TwitterOAuth($this->consumerKey, $this->consumerSecret);
  40. $requestToken = $connection->oauth('oauth/request_token', ['oauth_callback' => $this->oauthCallbackUrl]);
  41. session(['twitter_oauth_token' => $requestToken['oauth_token']]);
  42. session(['twitter_oauth_token_secret' => $requestToken['oauth_token_secret']]);
  43. $url = $connection->url('oauth/authorize', ['oauth_token' => $requestToken['oauth_token']]);
  44. return [
  45. 'status' => true,
  46. 'data' => ['url' => $url]
  47. ];
  48. }
  49. public function loginCallback(Request $request)
  50. {
  51. try {
  52. $oauthToken = $request->get('oauth_token');
  53. $oauthVerifier = $request->get('oauth_verifier');
  54. $requestToken = session('twitter_oauth_token');
  55. $requestTokenSecret = session('twitter_oauth_token_secret');
  56. $connection = new TwitterOAuth(
  57. $this->consumerKey,
  58. $this->consumerSecret,
  59. $requestToken,
  60. $requestTokenSecret
  61. );
  62. $accessToken = $connection->oauth('oauth/access_token', [
  63. 'oauth_verifier' => $oauthVerifier
  64. ]);
  65. $expiresAt = Carbon::now()->addDays(90)->format('Y-m-d H:i:s');
  66. return [
  67. 'status' => true,
  68. 'data' => [
  69. 'accessToken' => $accessToken['oauth_token'],
  70. 'backupField1' => json_encode(['accessTokenSecret'=>$accessToken['oauth_token_secret']]),
  71. 'userId' => $accessToken['user_id'],
  72. 'userName' => $accessToken['screen_name'],
  73. 'accessToken_expiresAt' => $expiresAt,
  74. ]
  75. ];
  76. } catch (\Exception $e) {
  77. return ['status' => false, 'data' => $e->getMessage()];
  78. }
  79. }
  80. public function postImage($message, $imagePaths, $accessToken = null)
  81. {
  82. Log::info('开始发布图片帖子');
  83. try {
  84. $this->twitterOAuth->setApiVersion('1.1'); // 强制使用 v1.1 API
  85. $this->twitterOAuth->setTimeouts(15, 60);
  86. $mediaIds = [];
  87. foreach ($imagePaths as $imagePath) {
  88. $imagePath = toStoragePath($imagePath);
  89. if (!file_exists($imagePath)) {
  90. throw new \Exception("图片不存在: {$imagePath}");
  91. }
  92. //1.1版本上传媒体文件
  93. $uploadedMedia = $this->twitterOAuth->upload('media/upload', [
  94. 'media' => $imagePath
  95. ]);
  96. Log::info('1.1版本上传媒体文件完成');
  97. if (isset($uploadedMedia->error)) {
  98. throw new \Exception('媒体上传失败: ' . json_encode($uploadedMedia));
  99. }
  100. $mediaIds[] = $uploadedMedia->media_id_string;
  101. }
  102. // 2.0版本发布推文
  103. $this->twitterOAuth->setApiVersion('2');
  104. $tweet = $this->twitterOAuth->post('tweets', [
  105. 'text' => $message,
  106. 'media' => !empty($mediaIds) ? ['media_ids' => $mediaIds] : null
  107. ]);
  108. Log::info('2.0版本发布推文完成');
  109. if (isset($tweet->errors)) {
  110. throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
  111. }
  112. return ['status' => true,
  113. 'data' => [
  114. 'responseIds' => [$tweet->data->id],
  115. ]];
  116. } catch (\Exception $e) {
  117. return ['status' => false, 'data' => $e->getMessage()];
  118. }
  119. }
  120. public function postVideo($message, $videoPath, $accessToken = null)
  121. {
  122. try {
  123. $this->twitterOAuth->setTimeouts(15, 120);
  124. $this->twitterOAuth->setApiVersion('1.1');
  125. $videoPath = toStoragePath($videoPath);
  126. // Verify file exists and is readable
  127. if (!file_exists($videoPath) || !is_readable($videoPath)) {
  128. throw new Exception("Video file does not exist or is not readable: $videoPath");
  129. }
  130. Log::info('twitter上传视频'.$videoPath);
  131. echo '1';
  132. // Upload video with chunked upload
  133. $uploadedMedia = $this->twitterOAuth->upload('media/upload', [
  134. 'media' => $videoPath,
  135. // 'media_category' => 'tweet_video'
  136. 'media_type' => 'video/mp4'
  137. ],['chunkedUpload'=>true]);
  138. // dd($uploadedMedia);
  139. // dd($uploadedMedia);
  140. Log::info('1.1版本,分块上传视频');
  141. $code = $this->twitterOAuth->getLastHttpCode();
  142. if (isset($uploadedMedia->error) || $code != 200) {
  143. throw new \Exception('媒体上传失败: ' . json_encode($uploadedMedia));
  144. }
  145. $mediaId = $uploadedMedia->media_id_string;
  146. $limit = 0;
  147. do {
  148. $status = $this->twitterOAuth->mediaStatus($mediaId);
  149. Log::info('检测视频上传状态'.print_r($status,true));
  150. sleep(5); // 等待 5 秒
  151. $limit++;
  152. } while ($status->processing_info->state !== 'succeeded' && $limit <= 3); // 最多重试 3 次
  153. if ($status->processing_info->state === 'succeeded') {
  154. //print_r($status->processing_info->state);
  155. // 2.0版本发布推文
  156. $this->twitterOAuth->setApiVersion('2');
  157. $tweet = $this->twitterOAuth->post('tweets', [
  158. 'text' => $message,
  159. 'media' => ['media_ids' => [$mediaId]]
  160. ]);
  161. Log::info('2.0版本发布推文');
  162. if (isset($tweet->errors)) {
  163. throw new \Exception('推文发布失败: ' . json_encode($tweet->errors));
  164. }
  165. return ['status' => true,
  166. 'data' => [
  167. 'responseIds' => [$tweet->data->id],
  168. ]
  169. ];
  170. } else {
  171. return ['status' => false, 'data' => '视频上传失败或处理超时。' ];
  172. }
  173. } catch (\Exception $e) {
  174. return ['status' => false, 'data' => '视频上传异常: '.$e->getMessage()];
  175. }
  176. }
  177. // 保持空实现
  178. public function getComments($postId) {}
  179. public function replyToComment($commentId) {}
  180. public function deleteComment($commentId) {}
  181. }