wordpress
WordPressとMisskey.ioとを投稿自動連係
作成日: 2025年6月11日
国産SNSのMisskeyへWordpress投稿を自動投稿するコード。
// Misskey設定
define('MISSKEY_TOKEN', 'YOUR_MISSKEY_TOKEN');
define('MISSKEY_HOST', 'https://misskey.io');
// 投稿公開時にMisskeyへ投稿する関数
function post_to_misskey_ogp($post_id) {
error_log('Misskey OGP: Starting post to Misskey for post ID ' . $post_id);
$post = get_post($post_id);
if (!$post || $post->post_status !== 'publish') {
error_log('Misskey OGP: Invalid post or not published, ID: ' . $post_id);
return;
}
$url = get_permalink($post);
$title = get_the_title($post);
error_log('Misskey OGP: Post URL: ' . $url . ', Title: ' . $title);
// OGP画像を取得
$html = wp_remote_retrieve_body(wp_remote_get($url, ['timeout' => 15]));
if (is_wp_error($html) || empty($html)) {
error_log('Misskey OGP: Failed to fetch post HTML for ID ' . $post_id . ': ' . (is_wp_error($html) ? $html->get_error_message() : 'Empty response'));
return;
}
if (!preg_match('/<meta property=["\']og:image["\'] content=["\']([^"\']+)["\']/i', $html, $m)) {
error_log('Misskey OGP: No og:image found for post ID ' . $post_id);
return;
}
$og_image = $m[1];
error_log('Misskey OGP: Found og:image ' . $og_image . ' for post ID ' . $post_id);
// 画像データを取得
$image_response = wp_remote_get($og_image, ['timeout' => 15]);
if (is_wp_error($image_response)) {
error_log('Misskey OGP: Failed to fetch image data for ' . $og_image . ': ' . $image_response->get_error_message());
return;
}
$image_data = wp_remote_retrieve_body($image_response);
if (empty($image_data)) {
error_log('Misskey OGP: Empty image data for ' . $og_image);
return;
}
error_log('Misskey OGP: Image data fetched for ' . $og_image);
// 画像のMIMEタイプを取得
$temp_file = wp_tempnam(basename($og_image));
file_put_contents($temp_file, $image_data);
$image_info = wp_getimagesize($temp_file);
if ($image_info === false) {
error_log('Misskey OGP: Failed to determine image MIME type for ' . $og_image);
@unlink($temp_file);
return;
}
$mime_type = $image_info['mime'];
error_log('Misskey OGP: Detected MIME type: ' . $mime_type . ' for ' . $og_image);
// サポートするMIMEタイプのチェック
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($mime_type, $allowed_mime_types)) {
error_log('Misskey OGP: Unsupported MIME type: ' . $mime_type . ' for ' . $og_image);
@unlink($temp_file);
return;
}
// Misskey APIに画像をアップロード (multipart/form-data)
$boundary = wp_generate_uuid4();
$body = [
"--$boundary",
'Content-Disposition: form-data; name="i"',
'',
MISSKEY_TOKEN,
"--$boundary",
'Content-Disposition: form-data; name="file"; filename="' . basename($og_image) . '"',
'Content-Type: ' . $mime_type,
'',
$image_data,
"--$boundary--",
];
$body = implode("\r\n", $body);
$upload = wp_remote_post(MISSKEY_HOST . '/api/drive/files/create', [
'headers' => [
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
],
'body' => $body,
'timeout' => 30,
]);
// 一時ファイル削除
@unlink($temp_file);
if (is_wp_error($upload)) {
error_log('Misskey OGP: Failed to upload image for post ID ' . $post_id . ': WP Error - ' . $upload->get_error_message());
return;
}
$upload_body = json_decode(wp_remote_retrieve_body($upload), true);
$status_code = wp_remote_retrieve_response_code($upload);
if (empty($upload_body['id'])) {
error_log('Misskey OGP: Failed to upload image for post ID ' . $post_id . ': Status - ' . $status_code . ', Response - ' . wp_remote_retrieve_body($upload));
return;
}
error_log('Misskey OGP: Image uploaded successfully, file ID: ' . $upload_body['id']);
// ノートを作成
$note = wp_remote_post(MISSKEY_HOST . '/api/notes/create', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode([
'i' => MISSKEY_TOKEN,
'text' => $title . "\n" . $url,
'fileIds' => [$upload_body['id']],
]),
'timeout' => 15,
]);
if (is_wp_error($note)) {
error_log('Misskey OGP: Failed to create note for post ID ' . $post_id . ': WP Error - ' . $note->get_error_message());
return;
}
$note_body = json_decode(wp_remote_retrieve_body($note), true);
$note_status = wp_remote_retrieve_response_code($note);
if (empty($note_body['createdNote']['id'])) {
error_log('Misskey OGP: Failed to create note for post ID ' . $post_id . ': Status - ' . $note_status . ', Response - ' . wp_remote_retrieve_body($note));
return;
}
error_log('Misskey OGP: Successfully posted to Misskey for post ID ' . $post_id . ', note ID: ' . $note_body['createdNote']['id']);
}
// 投稿公開時に実行
add_action('publish_post', 'post_to_misskey_ogp', 10, 1);
// 投稿編集画面にボタン追加
add_action('post_submitbox_misc_actions', function() {
global $post;
if ($post->post_type !== 'post') {
error_log('Misskey OGP: Skipping non-post type for post ID ' . $post->ID);
return;
}
$url = wp_nonce_url(admin_url("admin-post.php?action=misskey_repost&post_id={$post->ID}"), 'misskey_repost_' . $post->ID);
echo '<div class="misc-pub-section misc-pub-misskey-repost">';
echo '<a href="' . esc_url($url) . '" class="button button-primary">Misskey再投稿</a>';
echo '</div>';
});
// 再投稿処理
add_action('admin_post_misskey_repost', function() {
$post_id = intval($_GET['post_id'] ?? 0);
if (!$post_id) {
error_log('Misskey OGP: Missing post ID in repost request');
wp_die('投稿IDが指定されていません。');
}
if (!current_user_can('edit_posts')) {
error_log('Misskey OGP: User lacks permission for post ID ' . $post_id);
wp_die('権限がありません');
}
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'misskey_repost_' . $post_id)) {
error_log('Misskey OGP: Invalid nonce for post ID ' . $post_id);
wp_die('不正な操作です');
}
error_log('Misskey OGP: Initiating repost for post ID ' . $post_id);
post_to_misskey_ogp($post_id);
wp_safe_redirect(admin_url('post.php?post=' . $post_id . '&action=edit&misskey=done'));
exit;
});
コード冒頭の「YOUR_MISSKEY_TOKEN」に取得したトークンを書き込む。
define(‘MISSKEY_HOST’, ‘https://misskey.io’);
ここをMastodnなどと同様サーバーが複数あるので、適宜書き換え。
OGPを読まない仕様らしいので、画像はOGimageを拾ってくる仕様にした。これはここで使ってるテーマではOGIMAGEが[アイキャッチ→投稿の最初の画像→設定で指定したデフォルト画像]だから改めてそのコード入れるのが面倒だったからだが、OGP組んであれば大概その方が楽ではないかな。