star back image
people4
電飾 電飾
moon
men

【wordpress】X(旧Twitter)自動投稿プラグイン「X Auto Post」

BLOG AIWEBログWordPressプラグイン
読了約:50分

WordPressの記事を公開した際に、自動的にX(旧Twitter)に投稿するプラグインを作成しました。必要ありませんか?

なぜ作ったんですか。

なぜ!?それは誰かに、ブログを見てほしいからでしょ。

わたしは気持ちを表すことや文章を書くことが得意ではありません。苦労します。
苦労したからと言って、すごいキラキラしたものに変身したり、アクセスが激増するわけもありません。それに。

記事が更新されたことだって、誰も知らないでしょう。

大人気SNSである「X」でお知らせしたいです。でも恥ずかしい。

恥ずかしいから「自動で投稿」してほしい。
そんなこと、できるのでしょうか?さっぱりわかりません。

しかし今はAIがあります。
重要:このプラグインはFreeプランの仕様で構成されています。

検索しました。

参考:ねんでぶろぐ
Twitter(X)APIの無料プラン(Freeプラン)
https://nendeb.com/1265

なるほど、Twitter(X) APIのツイート機能を使うのか。。

【共有】セットアップ(3ステップ)

ステップ1: X Developer PortalでAPI認証情報を取得

API情報の取得は、ねんでぶろぐさんの記事を参考に進めました。
(このページのスクリーンショット画像は2025年10月現在のものです。)

SNSは仕様変更が多いです。(使えなくなったプラグインも多数)
これからも変わるでしょうから自由の利く、自作という考えです。

AIさんが必要なものを教えてくれます。

1. https://developer.x.com/ にアクセス

2. アプリを作成して以下を取得:
- API Key (Consumer Key)
- API Secret (Consumer Secret)
- Access Token
- Access Token Secret

**重要:**
アプリの権限を「Read and Write」に設定してください。
Developer Platformのトップページ

もちろん、情けないことに無料のFreeプランで進めます( -`ω-)キリッ

Freeの下にある「Get started」ボタンをクリックします。
次の画面の「Sign up for Free Account」をクリックします。

Ready to build on X?
はい。

ここからが少し面倒です。
どういう開発をするのか伝える文章を作成し、送る必要(250文字以上の英文で)があります。

私のホームページに投稿した記事をX APIを利用して、Xのタイムラインにも自動的に表示されるようにしたいです。
投稿記事をXのタイムラインに自動的に表示させるのは1日に1回程度です。
これにより、私のホームページの認知を高めることを目標としています。
また、他の機能のツイート、Xアカウント、またはそのコンテンツを分析しません。
リツイート、いいねの機能は使用しません。
XコンテンツをX以外で表示しません。

↑この文章をGoogle翻訳して利用しました。

3つの利用規約に同意のチェックをし「Submit」します。

するとスグに審査とか無しにアプリのIDを作成してくれています。
形式的な感じなのかも?
何日も待たされずに済み、好感が持てます。

プロダクトIDができてますが、このままでは初期設定状態です。
まだ使えないので「歯車アイコン」をクリックして、ユーザー認証設定へと進みます。

Appの詳細トップになるので「Set up」ボタンを押してセッティングしましょう。

AIが言っていた、アプリの権限を「Read and Write」に設定するという重要なチェック項目があります。

それ以外はよくわからなかったのですが、ねんでぶろぐさんのブログを参考に設定していきます。

Type of App(アプリの種類)
「Web App, Automated App or Bot」

App info(アプリ情報)
「Callback URI / Redirect URL」
「Website URL」

必須の項目、利用するサイトのURLを入れてください。

「Save」ボタンを押して、いよいよAPIキーが!?の前にポップアップが出ます。

「Yes」を押してください。

このキーは使いません。
そのまま「Done」するとポップアップがでます。
「Yes I saved it」ボタンで保存します。

するとAppの詳細トップに戻ってきます。

↓セッティング箇所で緑のマークが点灯しているのがわかります。

そしてタブリンクの「Key and tokens」をクリックします。

ここからようやくAPIキーのお出ましとなります。

必要なのはAPIキーは以下です:
– API Key (Consumer Key)
– API Secret (Consumer Secret)
– Access Token
– Access Token Secret

それは以下の2箇所で取得できます。

ボタンを押すとポップアップが出ます。

「Regenerate(再生成)」ということ。「Yes, regenerates」します。
すると2つ、「API Key」と「API Key Secret」を取得できます。

やりましたね!

「Yes, I saved token」して、2箇所目に行きます。

「Access Token」と「Access Token Secret」を取得できました。
「Yes, I saved token」して閉じます。

イエーィ!

「Copy」してメモできましたでしょうか。

ステップ2: WordPressでプラグインを有効化

プラグイン名は「x-auto-post」です。カッコイイかも!

AIと一緒に作成しました。

要件は以下です。

  • WordPressで記事を公開すると同時にXへ投稿
  • 投稿内容を自由にカスタマイズ
  • 本番投稿前に動作確認可能
  • 一度投稿した記事は再投稿されない
zipダウンロード
https://astrowave.jp/amnesia_record/img/x-auto-post.zip

解凍して、あなたのwordpressのpluginsディレクトリに、フォルダごとinしてください。

/wordpress/wp-content/plugins/x-auto-post/
 ├── x-auto-post.php ← メインプラグイン
 ├── README.md ← 基本説明
 └── SETUP_GUIDE.md ← 詳細セットアップガイド

※パーミッション
drwxr-xr-x x-auto-post/ (755)
-rw-r--r-- *.php, *.md (644)

/wp-content/plugins/ディレクトリにUPすると、プラグインをダウンロードしたのと同じことになります。

プラグインを有効にして設定していきましょう。

設定にはここに行きます。

ステップ3: 設定を行う

メモしたAPIキー、トークンを、バッチバチ登録して下さい。

できたら「変更を保存」ボタンを押して準備万端です。

投稿テンプレートを編集できます

----
新しい記事を公開しました(゚∀゚)アヒャ!

{title}
{url}
#アストロウェーブ #ブログ
-----

とか、どうでしょうか。

{excerpt}は記事の抜粋です。文字制限あたりで「...」になります。

{url}を入れると、Xではページの簡単なプレビューが表示されるようですね。

ブログ記事公開でX投稿をする前に、テスト投稿もできます。

何回でもテスト投稿できます。

こんな機能要りますかね。

あったほうが嬉しくないですか(;゚ Д゚) ·。゚

フラグリセットボタン付き。

最新10件までの「投稿フラグをリセット」ボタン付き。

ちょこっと修正などで、記事の更新をするたびにXへ投稿していたら、まずいですよね?
そういうことで重複投稿を防ぐ仕組みになっています。

しかし、記事にかなりの内容追加をしたりした時。
またXに投稿したくなりませんでしょうか。

そういう時「投稿フラグをリセット」ボタンを押して下さい。
X投稿済みの記事だったとしても、
リセットした後、その記事の更新をするとXへ再投稿可能です。

こんな感じに出てきます。

だめ?Freeなので基本的なことしかできません。多分。

https://x.com/
XはTwitterだった頃、文字数制限がありましたが、今では複数の画像や動画を投稿できるように進化しています。
色々と改造の余地もあると思いますが、今回はシンプルな機能に絞っています。

凝った投稿はXのページで、普通にやったらいいと思います。
課金すれば色々できるのかもしれませんが、
今回はブログ更新のお知らせ機能のみの構築でした。

画像関係はここが詳しいかも
https://gaiax-socialmedialab.jp/post-1111/

よくある質問(FAQ)

Q: 過去の記事も自動投稿されますか?
A: いいえ。プラグイン有効化後に公開する記事のみが対象です。

Q: 記事を編集・更新した場合、再度投稿されますか?
A: いいえ。初回公開時のみ投稿されます。

Q: 画像も一緒に投稿できますか?
A: 現在のバージョンでは、テキストのみの投稿です。

Q: 予約投稿に対応していますか?
A: はい。予約した時刻に記事が公開されると同時に自動投稿されます。

Q: 投稿を削除したい場合は?
A: X上で手動で削除してください。

以下にソースコードを共有したいと思います。

【共有2】プラグインのソースコード

以下のコードはx-auto-postフォルダ内のphpファイルです。
SNS仕様の変更でうまく動かなくなった時、このファイルを改修することになります。

x-auto-post.php

<?php
/**
 * Plugin Name: X Auto Post
 * Plugin URI: https://neo.astrowave.jp/
 * Description: 記事公開時に自動的にX(旧Twitter)に投稿します
 * Version: 1.0.0
 * Author: サイガモ
 * Author URI: https://astrowave.jp/
 * License: GPL v2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 */

if (!defined('ABSPATH')) {
    exit;
}

class X_Auto_Post {
    
    private $api_key;
    private $api_secret;
    private $access_token;
    private $access_token_secret;
    
    public function __construct() {
        // 設定ページの追加
        add_action('admin_menu', array($this, 'add_settings_page'));
        add_action('admin_init', array($this, 'register_settings'));
        
        // 記事公開時のフック
        add_action('publish_post', array($this, 'post_to_x'), 10, 2);
        
        // 設定を読み込み
        $this->api_key = get_option('x_auto_post_api_key');
        $this->api_secret = get_option('x_auto_post_api_secret');
        $this->access_token = get_option('x_auto_post_access_token');
        $this->access_token_secret = get_option('x_auto_post_access_token_secret');
    }
    
    /**
     * 管理画面に設定ページを追加
     */
    public function add_settings_page() {
        add_options_page(
            'X自動投稿設定',
            'X自動投稿',
            'manage_options',
            'x-auto-post-settings',
            array($this, 'settings_page')
        );
    }
    
    /**
     * 設定を登録
     */
    public function register_settings() {
        register_setting('x_auto_post_settings', 'x_auto_post_api_key');
        register_setting('x_auto_post_settings', 'x_auto_post_api_secret');
        register_setting('x_auto_post_settings', 'x_auto_post_access_token');
        register_setting('x_auto_post_settings', 'x_auto_post_access_token_secret');
        register_setting('x_auto_post_settings', 'x_auto_post_template');
        register_setting('x_auto_post_settings', 'x_auto_post_enabled');
    }
    
    /**
     * 設定ページの表示
     */
    public function settings_page() {
        // フラグのリセット処理
        if (isset($_POST['x_auto_post_reset_submit']) && 
            check_admin_referer('x_auto_post_reset', 'x_auto_post_reset_nonce')) {
            $reset_post_id = intval($_POST['reset_post_id']);
            delete_post_meta($reset_post_id, '_x_auto_posted');
            delete_post_meta($reset_post_id, '_x_auto_posted_time');
            echo '<div class="notice notice-success"><p>投稿フラグをリセットしました。この記事を更新すると、再度Xに投稿されます。</p></div>';
        }
        
        ?>
        <div class="wrap">
            <h1>X自動投稿設定</h1>
            <div class="notice notice-info">
                <p><strong>💡 ヒント:</strong> 記事のURLからアイキャッチ画像が自動的にXのカードに表示されます。</p>
            </div>
            <form method="post" action="options.php">
                <?php
                settings_fields('x_auto_post_settings');
                do_settings_sections('x_auto_post_settings');
                ?>
                <table class="form-table">
                    <tr>
                        <th scope="row">自動投稿を有効化</th>
                        <td>
                            <input type="checkbox" name="x_auto_post_enabled" value="1" 
                                <?php checked(1, get_option('x_auto_post_enabled'), true); ?> />
                            <p class="description">チェックを入れると、記事公開時に自動的にXに投稿されます</p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">API Key (Consumer Key)</th>
                        <td>
                            <input type="text" name="x_auto_post_api_key" 
                                value="<?php echo esc_attr(get_option('x_auto_post_api_key')); ?>" 
                                class="regular-text" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">API Secret (Consumer Secret)</th>
                        <td>
                            <input type="text" name="x_auto_post_api_secret" 
                                value="<?php echo esc_attr(get_option('x_auto_post_api_secret')); ?>" 
                                class="regular-text" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Access Token</th>
                        <td>
                            <input type="text" name="x_auto_post_access_token" 
                                value="<?php echo esc_attr(get_option('x_auto_post_access_token')); ?>" 
                                class="regular-text" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Access Token Secret</th>
                        <td>
                            <input type="text" name="x_auto_post_access_token_secret" 
                                value="<?php echo esc_attr(get_option('x_auto_post_access_token_secret')); ?>" 
                                class="regular-text" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">投稿テンプレート</th>
                        <td>
                            <textarea name="x_auto_post_template" rows="5" class="large-text"><?php 
                                echo esc_textarea(get_option('x_auto_post_template', '{title} {url}')); 
                            ?></textarea>
                            <p class="description">
                                使用可能な変数: {title} = 記事タイトル, {url} = 記事URL, {excerpt} = 抜粋
                            </p>
                        </td>
                    </tr>
                </table>
                <?php submit_button(); ?>
            </form>
            
            <hr>
            <h2>テスト投稿</h2>
            <form method="post">
                <?php wp_nonce_field('x_auto_post_test', 'x_auto_post_test_nonce'); ?>
                <p>
                    <input type="text" name="test_message" placeholder="テストメッセージを入力" class="regular-text" value="" />
                    <input type="submit" name="x_auto_post_test_submit" class="button button-primary" value="テスト投稿" />
                </p>
            </form>
            
            <?php
            if (isset($_POST['x_auto_post_test_submit']) && 
                check_admin_referer('x_auto_post_test', 'x_auto_post_test_nonce')) {
                $test_message = sanitize_text_field($_POST['test_message']);
                if (!empty($test_message)) {
                    $result = $this->send_to_x($test_message);
                    if ($result['success']) {
                        echo '<div class="notice notice-success"><p><strong>✅ テスト投稿が成功しました!</strong></p></div>';
                    } else {
                        echo '<div class="notice notice-error"><p><strong>❌ エラー:</strong> ' . esc_html($result['message']) . '</p></div>';
                    }
                }
            }
            ?>
            
            <hr>
            <h2>投稿済み記事</h2>
            <?php
            // 最近投稿された記事を表示
            $recent_posts = get_posts(array(
                'numberposts' => 10,
                'post_status' => 'publish',
                'meta_key' => '_x_auto_posted',
                'meta_value' => '1'
            ));
            
            if (!empty($recent_posts)) {
                echo '<table class="wp-list-table widefat fixed striped">';
                echo '<thead><tr><th>記事タイトル</th><th>投稿日時</th><th>アクション</th></tr></thead>';
                echo '<tbody>';
                foreach ($recent_posts as $post) {
                    $posted_time = get_post_meta($post->ID, '_x_auto_posted_time', true);
                    echo '<tr>';
                    echo '<td><a href="' . get_permalink($post->ID) . '" target="_blank">' . esc_html($post->post_title) . '</a></td>';
                    echo '<td>' . esc_html($posted_time) . '</td>';
                    echo '<td><form method="post" style="display:inline;">';
                    wp_nonce_field('x_auto_post_reset', 'x_auto_post_reset_nonce');
                    echo '<input type="hidden" name="reset_post_id" value="' . $post->ID . '">';
                    echo '<input type="submit" name="x_auto_post_reset_submit" class="button button-small" value="投稿フラグをリセット">';
                    echo '</form></td>';
                    echo '</tr>';
                }
                echo '</tbody></table>';
            } else {
                echo '<p>まだXに投稿された記事はありません。</p>';
            }
            ?>
        </div>
        <?php
    }
    
    /**
     * 記事公開時にXに投稿
     */
    public function post_to_x($post_id, $post) {
        // 自動投稿が無効の場合は何もしない
        if (!get_option('x_auto_post_enabled')) {
            return;
        }
        
        // 自動保存や下書きの場合はスキップ
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        
        // 記事の公開状態を確認
        if ($post->post_status !== 'publish') {
            return;
        }
        
        // すでに投稿済みかチェック
        $already_posted = get_post_meta($post_id, '_x_auto_posted', true);
        if ($already_posted) {
            return;
        }
        
        // 投稿内容を作成
        $template = get_option('x_auto_post_template', '{title} {url}');
        $message = str_replace(
            array('{title}', '{url}', '{excerpt}'),
            array(
                get_the_title($post_id),
                get_permalink($post_id),
                wp_trim_words(get_the_excerpt($post_id), 20)
            ),
            $template
        );
        
        // 280文字制限
        if (mb_strlen($message) > 280) {
            $message = mb_substr($message, 0, 277) . '...';
        }
        
        // Xに投稿
        $result = $this->send_to_x($message);
        
        // 投稿成功時にフラグを保存
        if ($result['success']) {
            update_post_meta($post_id, '_x_auto_posted', true);
            update_post_meta($post_id, '_x_auto_posted_time', current_time('mysql'));
        }
        
        return $result;
    }
    
    /**
     * Xへ投稿を送信(OAuth 1.0a)
     */
    private function send_to_x($message) {
        // API認証情報のチェック
        if (empty($this->api_key) || empty($this->api_secret) || 
            empty($this->access_token) || empty($this->access_token_secret)) {
            return array(
                'success' => false,
                'message' => 'API認証情報が設定されていません'
            );
        }
        
        $url = 'https://api.twitter.com/2/tweets';
        
        // OAuth パラメータ
        $timestamp = time();
        $nonce = md5(microtime() . mt_rand());
        
        $oauth = array(
            'oauth_consumer_key' => $this->api_key,
            'oauth_nonce' => $nonce,
            'oauth_signature_method' => 'HMAC-SHA1',
            'oauth_token' => $this->access_token,
            'oauth_timestamp' => $timestamp,
            'oauth_version' => '1.0'
        );
        
        // ベース文字列を作成
        $base_info = $this->build_base_string($url, 'POST', $oauth);
        
        // 署名を作成
        $composite_key = rawurlencode($this->api_secret) . '&' . rawurlencode($this->access_token_secret);
        $oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
        $oauth['oauth_signature'] = $oauth_signature;
        
        // Authorizationヘッダーを作成
        $header_parts = array();
        foreach ($oauth as $key => $value) {
            $header_parts[] = $key . '="' . rawurlencode($value) . '"';
        }
        $header = 'Authorization: OAuth ' . implode(', ', $header_parts);
        
        // POSTデータ
        $tweet_data = array('text' => $message);
        $post_data = json_encode($tweet_data);
        
        // リクエスト送信
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            $header,
            'Content-Type: application/json'
        ));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        // レスポンスを解析
        if ($http_code === 201) {
            return array(
                'success' => true,
                'message' => '投稿に成功しました'
            );
        } else {
            $error_data = json_decode($response, true);
            $error_message = isset($error_data['detail']) ? $error_data['detail'] : $response;
            
            return array(
                'success' => false,
                'message' => $error_message
            );
        }
    }
    
    /**
     * OAuth署名のベース文字列を構築
     */
    private function build_base_string($base_uri, $method, $params) {
        $return = array();
        ksort($params);
        
        foreach ($params as $key => $value) {
            $return[] = rawurlencode($key) . '=' . rawurlencode($value);
        }
        
        return $method . "&" . rawurlencode($base_uri) . '&' . rawurlencode(implode('&', $return));
    }
}

// プラグインを初期化
new X_Auto_Post();

詳しくはAIに放り込んで聞いてください。

以上です。

【AI】イラストを描いてもらった

今回の記事のキャッチ画像で使わせてもらいます「Google ImageFX」で作成した画像です。誰でもgoogleアカウントでログインして使えます。

この記事にピッタリなイラストのための考えたリクエストは、「巨大な金属質のXの文字の碑が背景にあるサイバー空間で、twitterの青い鳥のロゴが飛んでいます。ピンクの髪のメイドさんが、青い鳥のロゴを集めて抱きしめて笑顔になっています。」です。

AIイラスト1
AIイラスト2

メイドさんが好きなんですね。

星間旅路のメロディ

「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」

「この電波はどこの星からきたのだろうか。」

どこかで聞いたことがあるような。