fix: file handle leaks, return types, Facebook reel/story binary upload, null checks, cleanup
This commit is contained in:
parent
e90a24fc31
commit
45dd3f4bde
11 changed files with 136 additions and 103 deletions
|
|
@ -312,7 +312,7 @@ public function refreshToken(SocialAccount $account): void
|
|||
throw new TokenExpiredException('Bluesky session expired');
|
||||
}
|
||||
|
||||
private function handleApiError(Response $response): void
|
||||
private function handleApiError(Response $response): never
|
||||
{
|
||||
throw BlueskyPublishException::fromApiResponse($response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,17 +219,23 @@ private function publishReel(string $pageId, string $accessToken, ?string $conte
|
|||
$videoId = data_get($data, 'video_id');
|
||||
|
||||
// Upload the video file
|
||||
$uploadResponse = Http::post("{$this->baseUrl}/{$videoId}", [
|
||||
'upload_phase' => 'transfer',
|
||||
'video_file_chunk' => $media->url,
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'fb_reel_');
|
||||
try {
|
||||
Http::withOptions(['sink' => $tempFile])->timeout(600)->get($media->url);
|
||||
|
||||
if ($uploadResponse->failed()) {
|
||||
Log::error('Facebook reel upload transfer failed', [
|
||||
'body' => $uploadResponse->body(),
|
||||
]);
|
||||
$this->handleApiError($uploadResponse);
|
||||
$uploadResponse = Http::withToken($accessToken)
|
||||
->attach('video_file_chunk', fopen($tempFile, 'r'), 'video.mp4')
|
||||
->post("{$this->baseUrl}/{$videoId}", [
|
||||
'upload_phase' => 'transfer',
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
|
||||
if ($uploadResponse->failed()) {
|
||||
Log::error('Facebook reel upload transfer failed', ['body' => $uploadResponse->body()]);
|
||||
$this->handleApiError($uploadResponse);
|
||||
}
|
||||
} finally {
|
||||
@unlink($tempFile);
|
||||
}
|
||||
|
||||
// Finish and publish the reel
|
||||
|
|
@ -275,11 +281,24 @@ private function publishStory(string $pageId, string $accessToken, $media): arra
|
|||
$videoId = $response->json()['video_id'];
|
||||
|
||||
// Transfer the video
|
||||
Http::post("{$this->baseUrl}/{$videoId}", [
|
||||
'upload_phase' => 'transfer',
|
||||
'video_file_chunk' => $media->url,
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'fb_story_');
|
||||
try {
|
||||
Http::withOptions(['sink' => $tempFile])->timeout(600)->get($media->url);
|
||||
|
||||
$transferResponse = Http::withToken($accessToken)
|
||||
->attach('video_file_chunk', fopen($tempFile, 'r'), 'video.mp4')
|
||||
->post("{$this->baseUrl}/{$videoId}", [
|
||||
'upload_phase' => 'transfer',
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
|
||||
if ($transferResponse->failed()) {
|
||||
Log::error('Facebook video story transfer failed', ['body' => $transferResponse->body()]);
|
||||
$this->handleApiError($transferResponse);
|
||||
}
|
||||
} finally {
|
||||
@unlink($tempFile);
|
||||
}
|
||||
|
||||
// Finish the story
|
||||
$finishResponse = Http::post("{$this->baseUrl}/{$pageId}/video_stories", [
|
||||
|
|
|
|||
|
|
@ -191,7 +191,13 @@ private function publishCarousel(string $instagramId, string $accessToken, ?stri
|
|||
continue;
|
||||
}
|
||||
|
||||
$childId = $containerResponse->json()['id'];
|
||||
$childId = $containerResponse->json()['id'] ?? null;
|
||||
|
||||
if (! $childId) {
|
||||
Log::error('Instagram carousel item creation returned no ID', ['body' => $containerResponse->body()]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait for video processing if needed
|
||||
if ($isVideo) {
|
||||
|
|
|
|||
|
|
@ -344,40 +344,42 @@ private function doUploadVideo(string $tempFile, $mediaItem, string $ownerUrn):
|
|||
$uploadedPartIds = [];
|
||||
$handle = fopen($tempFile, 'r');
|
||||
|
||||
foreach ($uploadInstructions as $index => $instruction) {
|
||||
$uploadUrl = data_get($instruction, 'uploadUrl');
|
||||
$firstByte = data_get($instruction, 'firstByte');
|
||||
$lastByte = data_get($instruction, 'lastByte');
|
||||
try {
|
||||
foreach ($uploadInstructions as $index => $instruction) {
|
||||
$uploadUrl = data_get($instruction, 'uploadUrl');
|
||||
$firstByte = data_get($instruction, 'firstByte');
|
||||
$lastByte = data_get($instruction, 'lastByte');
|
||||
|
||||
$chunkLength = $lastByte - $firstByte + 1;
|
||||
fseek($handle, $firstByte);
|
||||
$chunkData = fread($handle, $chunkLength);
|
||||
$chunkLength = $lastByte - $firstByte + 1;
|
||||
fseek($handle, $firstByte);
|
||||
$chunkData = fread($handle, $chunkLength);
|
||||
|
||||
$chunkResponse = Http::withToken($this->accessToken)
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
])
|
||||
->timeout(600)
|
||||
->withBody($chunkData, 'application/octet-stream')
|
||||
->put($uploadUrl);
|
||||
$chunkResponse = Http::withToken($this->accessToken)
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
])
|
||||
->timeout(600)
|
||||
->withBody($chunkData, 'application/octet-stream')
|
||||
->put($uploadUrl);
|
||||
|
||||
if ($chunkResponse->failed()) {
|
||||
Log::error('LinkedIn Page video chunk upload failed', [
|
||||
'index' => $index,
|
||||
'body' => $chunkResponse->body(),
|
||||
]);
|
||||
$this->handleApiError($chunkResponse);
|
||||
}
|
||||
|
||||
$etag = $chunkResponse->header('etag');
|
||||
if ($etag) {
|
||||
$uploadedPartIds[] = $etag;
|
||||
}
|
||||
|
||||
if ($chunkResponse->failed()) {
|
||||
Log::error('LinkedIn Page video chunk upload failed', [
|
||||
'index' => $index,
|
||||
'body' => $chunkResponse->body(),
|
||||
]);
|
||||
$this->handleApiError($chunkResponse);
|
||||
}
|
||||
|
||||
$etag = $chunkResponse->header('etag');
|
||||
if ($etag) {
|
||||
$uploadedPartIds[] = $etag;
|
||||
}
|
||||
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
// Step 3: Finalize upload
|
||||
$finalizeResponse = $this->getHttpClient()
|
||||
->post("{$this->baseUrl}/rest/videos?action=finalizeUpload", [
|
||||
|
|
|
|||
|
|
@ -325,40 +325,42 @@ private function doUploadVideo(string $tempFile, $mediaItem, string $ownerUrn):
|
|||
$uploadedPartIds = [];
|
||||
$handle = fopen($tempFile, 'r');
|
||||
|
||||
foreach ($uploadInstructions as $index => $instruction) {
|
||||
$uploadUrl = data_get($instruction, 'uploadUrl');
|
||||
$firstByte = data_get($instruction, 'firstByte');
|
||||
$lastByte = data_get($instruction, 'lastByte');
|
||||
try {
|
||||
foreach ($uploadInstructions as $index => $instruction) {
|
||||
$uploadUrl = data_get($instruction, 'uploadUrl');
|
||||
$firstByte = data_get($instruction, 'firstByte');
|
||||
$lastByte = data_get($instruction, 'lastByte');
|
||||
|
||||
$chunkLength = $lastByte - $firstByte + 1;
|
||||
fseek($handle, $firstByte);
|
||||
$chunkData = fread($handle, $chunkLength);
|
||||
$chunkLength = $lastByte - $firstByte + 1;
|
||||
fseek($handle, $firstByte);
|
||||
$chunkData = fread($handle, $chunkLength);
|
||||
|
||||
$chunkResponse = Http::withToken($this->accessToken)
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
])
|
||||
->timeout(600)
|
||||
->withBody($chunkData, 'application/octet-stream')
|
||||
->put($uploadUrl);
|
||||
$chunkResponse = Http::withToken($this->accessToken)
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
])
|
||||
->timeout(600)
|
||||
->withBody($chunkData, 'application/octet-stream')
|
||||
->put($uploadUrl);
|
||||
|
||||
if ($chunkResponse->failed()) {
|
||||
Log::error('LinkedIn video chunk upload failed', [
|
||||
'index' => $index,
|
||||
'body' => $chunkResponse->body(),
|
||||
]);
|
||||
$this->handleApiError($chunkResponse);
|
||||
}
|
||||
|
||||
$etag = $chunkResponse->header('etag');
|
||||
if ($etag) {
|
||||
$uploadedPartIds[] = $etag;
|
||||
}
|
||||
|
||||
if ($chunkResponse->failed()) {
|
||||
Log::error('LinkedIn video chunk upload failed', [
|
||||
'index' => $index,
|
||||
'body' => $chunkResponse->body(),
|
||||
]);
|
||||
$this->handleApiError($chunkResponse);
|
||||
}
|
||||
|
||||
$etag = $chunkResponse->header('etag');
|
||||
if ($etag) {
|
||||
$uploadedPartIds[] = $etag;
|
||||
}
|
||||
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
// Step 3: Finalize upload
|
||||
$finalizeResponse = $this->getHttpClient()
|
||||
->post("{$this->baseUrl}/rest/videos?action=finalizeUpload", [
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ private function uploadMedia(SocialAccount $account, string $instance, string $u
|
|||
}
|
||||
}
|
||||
|
||||
private function handleApiError(Response $response): void
|
||||
private function handleApiError(Response $response): never
|
||||
{
|
||||
throw MastodonPublishException::fromApiResponse($response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ private function waitForMediaProcessing(SocialAccount $account, string $mediaId,
|
|||
throw new \Exception("Pinterest media processing timeout after {$maxAttempts} attempts");
|
||||
}
|
||||
|
||||
public function refreshToken(SocialAccount $account): void
|
||||
private function refreshToken(SocialAccount $account): void
|
||||
{
|
||||
$response = Http::asForm()
|
||||
->withBasicAuth(
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ private function publishVideo(PostPlatform $postPlatform, $media): array
|
|||
}
|
||||
|
||||
// Wait for processing and get final status
|
||||
$finalStatus = $this->waitForPublishStatus($publishId);
|
||||
$this->waitForPublishStatus($publishId);
|
||||
|
||||
return [
|
||||
'id' => $publishId,
|
||||
|
|
@ -182,7 +182,7 @@ private function publishPhotos(PostPlatform $postPlatform, $mediaCollection): ar
|
|||
}
|
||||
|
||||
// Wait for processing and get final status
|
||||
$finalStatus = $this->waitForPublishStatus($publishId);
|
||||
$this->waitForPublishStatus($publishId);
|
||||
|
||||
return [
|
||||
'id' => $publishId,
|
||||
|
|
|
|||
|
|
@ -195,34 +195,36 @@ private function chunkedUpload(string $tempFile, int $totalBytes, string $mimeTy
|
|||
$handle = fopen($tempFile, 'r');
|
||||
$index = 0;
|
||||
|
||||
while (! feof($handle)) {
|
||||
$chunk = fread($handle, $chunkSize);
|
||||
try {
|
||||
while (! feof($handle)) {
|
||||
$chunk = fread($handle, $chunkSize);
|
||||
|
||||
if ($chunk === '' || $chunk === false) {
|
||||
break;
|
||||
if ($chunk === '' || $chunk === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
$appendResponse = Http::withToken($this->accessToken)
|
||||
->timeout(300)
|
||||
->attach('media', $chunk, 'chunk'.$index)
|
||||
->post("{$this->baseUrl}/2/media/upload/{$mediaId}/append", [
|
||||
'segment_index' => $index,
|
||||
]);
|
||||
|
||||
if ($appendResponse->failed()) {
|
||||
Log::error('X chunked upload APPEND error', [
|
||||
'status' => $appendResponse->status(),
|
||||
'body' => $appendResponse->body(),
|
||||
'segment' => $index,
|
||||
]);
|
||||
$this->handleApiError($appendResponse);
|
||||
}
|
||||
|
||||
$index++;
|
||||
}
|
||||
|
||||
$appendResponse = Http::withToken($this->accessToken)
|
||||
->timeout(300)
|
||||
->attach('media', $chunk, 'chunk'.$index)
|
||||
->post("{$this->baseUrl}/2/media/upload/{$mediaId}/append", [
|
||||
'segment_index' => $index,
|
||||
]);
|
||||
|
||||
if ($appendResponse->failed()) {
|
||||
Log::error('X chunked upload APPEND error', [
|
||||
'status' => $appendResponse->status(),
|
||||
'body' => $appendResponse->body(),
|
||||
'segment' => $index,
|
||||
]);
|
||||
$this->handleApiError($appendResponse);
|
||||
}
|
||||
|
||||
$index++;
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
// FINALIZE - Use the new v2 endpoint
|
||||
$finalizeResponse = Http::withToken($this->accessToken)
|
||||
->timeout(60)
|
||||
|
|
|
|||
|
|
@ -183,8 +183,8 @@ private function buildTitle(string $content): string
|
|||
$shortsTag = ' #Shorts';
|
||||
$availableLength = $maxLength - strlen($shortsTag);
|
||||
|
||||
$title = strtok($content, "\n");
|
||||
$title = strtok($title, '.');
|
||||
$firstLine = explode("\n", $content)[0];
|
||||
$title = explode('.', $firstLine)[0];
|
||||
|
||||
if (strlen($title) > $availableLength) {
|
||||
$title = substr($title, 0, $availableLength - 3).'...';
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@
|
|||
->push(['video_id' => 'reel_video_123'], 200)
|
||||
->push(['id' => 'reel_123', 'success' => true], 200),
|
||||
'*/reel_video_123' => Http::response(['success' => true], 200),
|
||||
'*' => Http::response('', 200),
|
||||
]);
|
||||
|
||||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
|
@ -230,6 +231,7 @@
|
|||
->push(['video_id' => 'story_video_123'], 200)
|
||||
->push(['post_id' => 'video_story_post_123'], 200),
|
||||
'*/story_video_123' => Http::response(['success' => true], 200),
|
||||
'*' => Http::response('', 200),
|
||||
]);
|
||||
|
||||
$result = $this->publisher->publish($this->postPlatform);
|
||||
|
|
|
|||
Loading…
Reference in a new issue