|
@@ -5,6 +5,7 @@ namespace App\Services\Smm;
|
|
|
use App\Services\Contracts\SmmPlatformInterface;
|
|
|
use Abraham\TwitterOAuth\TwitterOAuth;
|
|
|
use Carbon\Carbon;
|
|
|
+use CURLFile;
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
class TwitterService implements SmmPlatformInterface
|
|
@@ -21,17 +22,24 @@ class TwitterService implements SmmPlatformInterface
|
|
|
{
|
|
|
$this->configData = $configData;
|
|
|
|
|
|
- $this->consumerKey = env('TWITTER_CONSUMER_KEY');
|
|
|
- $this->consumerSecret = env('TWITTER_CONSUMER_SECRET');
|
|
|
+ $this->consumerKey = 'ahCsl1rD6ml2Ofma5UKUaMveJ';
|
|
|
+ $this->consumerSecret = 'CsS5AHR1vIVuuhWHrbV8RR9DrF6JvZjjMOIt7oA9YubpxllDWD';
|
|
|
$this->oauthCallbackUrl = env('DIST_SITE_URL') . '/dist/callback/twitter';
|
|
|
|
|
|
- if (!empty($configData['access_token']) && !empty($configData['access_token_secret'])) {
|
|
|
- $this->twitterOAuth = new TwitterOAuth(
|
|
|
- $this->consumerKey,
|
|
|
- $this->consumerSecret,
|
|
|
- $configData['access_token'],
|
|
|
- $configData['access_token_secret']
|
|
|
- );
|
|
|
+ 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
|
|
|
+ );
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -65,94 +73,174 @@ class TwitterService implements SmmPlatformInterface
|
|
|
$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'],
|
|
|
- 'accessTokenSecret' => $accessToken['oauth_token_secret'],
|
|
|
+ '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;
|
|
|
}
|
|
|
-
|
|
|
- $tweet = $this->twitterOAuth->post('statuses/update', [
|
|
|
- 'status' => $message,
|
|
|
- 'media_ids' => implode(',', $mediaIds)
|
|
|
+ // 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,
|
|
|
+ return ['status' => true,
|
|
|
'data' => [
|
|
|
- 'tweetId' => $tweet->id_str,
|
|
|
- 'mediaIds' => $mediaIds
|
|
|
- ]
|
|
|
- ];
|
|
|
+ '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}");
|
|
|
+
|
|
|
}
|
|
|
|
|
|
- $uploadedVideo = $this->twitterOAuth->upload('media/upload', [
|
|
|
- 'media' => $videoPath,
|
|
|
- 'media_type' => 'video/mp4'
|
|
|
- ], true); // 注意: chunked upload
|
|
|
+ // 获取视频 MIME 类型
|
|
|
+ $mimeType = mime_content_type($videoPath);
|
|
|
+ if (strpos($mimeType, 'video/') !== 0) {
|
|
|
+
|
|
|
+ throw new \Exception("文件不是有效的视频文件。MIME 类型: " . $mimeType);
|
|
|
|
|
|
- if (isset($uploadedVideo->error)) {
|
|
|
- throw new \Exception('视频上传失败: ' . json_encode($uploadedVideo));
|
|
|
}
|
|
|
|
|
|
- $tweet = $this->twitterOAuth->post('statuses/update', [
|
|
|
- 'status' => $message,
|
|
|
- 'media_ids' => $uploadedVideo->media_id_string
|
|
|
+ // 获取视频文件大小 (以字节为单位)
|
|
|
+ $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,
|
|
|
+ return ['status' => true,
|
|
|
'data' => [
|
|
|
- 'tweetId' => $tweet->id_str,
|
|
|
- 'mediaId' => $uploadedVideo->media_id_string
|
|
|
+ 'responseIds' => [$tweet->data->id],
|
|
|
]
|
|
|
];
|
|
|
} catch (\Exception $e) {
|
|
@@ -160,6 +248,7 @@ class TwitterService implements SmmPlatformInterface
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// 保持空实现
|
|
|
public function getComments($postId) {}
|
|
|
public function replyToComment($commentId) {}
|