Post Notes to Mastodon
Author: Kev Quirk |
Hook:
on_post_published
Automatically syndicates posts using a notes layout to your Mastodon account when published. Images in the note are uploaded as media attachments. The status is trimmed to fit Mastodon's 500-character limit, with the permalink appended.
Setup
- Generate an access token in your Mastodon instance under Settings → Development → New Application
- Replace the placeholder values at the top of the snippet
⚠️ If your site's source code is publicly available, make sure your Mastodon token isn't.
define('MASTODON_INSTANCE', 'https://your.mastodon.instance');
define('MASTODON_TOKEN', 'your-access-token');
function mastodon_upload_image(string $filePath): ?string
{
$ch = curl_init(MASTODON_INSTANCE . '/api/v1/media');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . MASTODON_TOKEN],
CURLOPT_POSTFIELDS => ['file' => new CURLFile($filePath)],
]);
$response = curl_exec($ch);
if (!$response) {
return null;
}
$data = json_decode((string) $response, true);
return isset($data['id']) ? (string) $data['id'] : null;
}
function on_post_published(string $slug): void
{
$post = get_post_by_slug($slug);
if (!$post || ($post['layout'] ?? '') !== 'notes') {
return;
}
$config = load_config();
$baseUrl = rtrim($config['base_url'] ?? get_base_url(), '/');
$html = render_markdown($post['content'], ['post_title' => '']);
$mediaIds = [];
$urlPrefix = base_path();
preg_match_all('/<img[^>]+src=["\']([^"\']+)["\']/', $html, $matches);
foreach ($matches[1] as $src) {
$urlPath = $src;
if ($urlPrefix !== '' && str_starts_with($urlPath, $urlPrefix)) {
$urlPath = substr($urlPath, strlen($urlPrefix));
}
$fsPath = PUREBLOG_BASE_PATH . '/' . ltrim($urlPath, '/');
if (is_file($fsPath)) {
$id = mastodon_upload_image($fsPath);
if ($id !== null) {
$mediaIds[] = $id;
}
}
}
$html = preg_replace('/<\/p>\s*/i', "\n\n", $html) ?? $html;
$html = preg_replace('/<br\s*\/?>\s*/i', "\n", $html) ?? $html;
$permalink = $baseUrl . '/' . $post['slug'];
$status = trim(strip_tags($html));
if (strlen($status) + strlen($permalink) + 2 > 500) {
$status = substr($status, 0, 500 - strlen($permalink) - 5) . '…';
}
$status .= "\n\n" . $permalink;
$ch = curl_init(MASTODON_INSTANCE . '/api/v1/statuses');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . MASTODON_TOKEN,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(array_filter([
'status' => $status,
'media_ids' => $mediaIds ?: null,
])),
]);
curl_exec($ch);
}