Steamer Lane Studio技術備忘録WordPress

WordPressとMisskey.ioとを投稿自動連係

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組んであれば大概その方が楽ではないかな。