Fix image posting for Discord and Mastodon

Discord: payload_json was missing the attachments array, so uploaded
files were orphaned and not shown in the message (Discord API v10 requirement).

Mastodon: media_ids[] sent as URL-encoded form was unreliable — switch to
JSON body. Also poll /api/v1/media/:id when upload returns 202 (async) to
wait until the image is processed before attaching it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Bashy 2026-06-04 19:35:03 +03:00
parent 1a366bef63
commit 4767fe0109

View file

@ -86,7 +86,7 @@ function ensure_li_token(string $base, array &$config): ?string {
return $li['access_token'];
}
// Upload image to Mastodon, return media_id or null
// Upload image to Mastodon, return media_id or null (polls until ready if async)
function mastodon_upload_image(string $api_base, string $token, string $image_path): ?string {
if (!file_exists($image_path)) return null;
$ch = curl_init($api_base . '/api/v2/media');
@ -99,7 +99,25 @@ function mastodon_upload_image(string $api_base, string $token, string $image_pa
$resp = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 200 || $status === 202) return $resp['id'] ?? null;
if ($status === 200) return $resp['id'] ?? null;
// 202 = media accepted but still processing — poll until URL is set (max 10s)
if ($status === 202) {
$media_id = $resp['id'] ?? null;
if (!$media_id) return null;
for ($i = 0; $i < 10; $i++) {
sleep(1);
$ch2 = curl_init($api_base . '/api/v1/media/' . $media_id);
curl_setopt_array($ch2, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token]]);
$poll = json_decode(curl_exec($ch2), true);
curl_close($ch2);
if (!empty($poll['url'])) return $media_id;
}
log_msg(' Mastodon media still processing after 10s — attaching anyway');
return $media_id;
}
log_msg(' Mastodon media upload HTTP ' . $status);
return null;
}
@ -150,13 +168,20 @@ function linkedin_upload_image(string $token, string $author, string $image_path
// Discord multipart post (with optional file attachment)
function discord_post(string $webhook_url, string $text, ?string $image_path): array {
$boundary = '----FormBoundary' . bin2hex(random_bytes(8));
$boundary = '----FormBoundary' . bin2hex(random_bytes(8));
$has_file = $image_path && file_exists($image_path);
$filename = $has_file ? basename($image_path) : null;
// payload_json must declare attachments array so Discord links the uploaded file
$payload = ['content' => $text];
if ($has_file) $payload['attachments'] = [['id' => 0, 'filename' => $filename]];
$body = "--$boundary\r\n";
$body .= "Content-Disposition: form-data; name=\"payload_json\"\r\n\r\n";
$body .= json_encode(['content' => $text]) . "\r\n";
if ($image_path && file_exists($image_path)) {
$body .= json_encode($payload) . "\r\n";
if ($has_file) {
$body .= "--$boundary\r\n";
$body .= 'Content-Disposition: form-data; name="files[0]"; filename="' . basename($image_path) . '"' . "\r\n";
$body .= 'Content-Disposition: form-data; name="files[0]"; filename="' . $filename . '"' . "\r\n";
$body .= 'Content-Type: ' . (mime_content_type($image_path) ?: 'image/jpeg') . "\r\n\r\n";
$body .= file_get_contents($image_path) . "\r\n";
}
@ -229,13 +254,13 @@ foreach ($targets as $t) {
if ($mid) $media_ids[] = $mid;
}
$params = ['status' => $content];
if ($media_ids) $params['media_ids[]'] = $media_ids[0];
if ($media_ids) $params['media_ids'] = $media_ids;
$ch = curl_init($api_base . '/api/v1/statuses');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/x-www-form-urlencoded'],
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'],
]);
$resp = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);