star back image
people4
電飾 電飾
moon
men

【WordPress】LLMs.txtプラグインを作る

BLOG WEBログWordPress
読了約:56分

AI検索をフレンドリーにするllms.txtの育成プラグインの記事を書いていました。
しかしファイルが更新されない原因不明の不具合が発生していました。

改善への進展なく放置して。。

忘れてましたね。

以下の話題で思い出しました。
AIにURLを教えつつ、以下のようにお願いしたらllms.txtを作成してくれます。

https://neo.astrowave.jp/ のllms.txtを作ってください。llms.txtの仕様は https://llmstxt.org/ です。
ブログ記事の最新10件ほどを加えて欲しいです。

すると瞬時にブログを検索してくれて、以下のようにテキストを作ってくれます。

サイトを読み取って、内容をある程度まとめてくれます。

llms.txt

# LLMs.txt for astrowave
# Last updated: 2025-05-07

# Site Information
Site: https://neo.astrowave.jp
Owner: saigamo@●●.com
Description: SF好き|WEB制作ワーカー|NISA高配当株初心者|昭和生まれの氷河期世代が「空想SF」「高配当株」をキーワードに、赤裸々に綴ります。

# Content Categories
Categories:
- BLOG
- SF
- 株関係

# Main Topics
Topics:
- 空想SF
- WEB制作
- 高配当株投資
- NISA投資
- 配当金レポート

# Content Guidelines
Guidelines:
- 日本語コンテンツのみ
- 個人の投資経験に基づく情報
- SF作品のレビューと考察
- WEB制作の技術的な内容
- 配当金の受取記録

# Content Structure
Structure:
- 新着記事
- カテゴリー別記事
- タグ別記事
- プロフィール
- お問い合わせ

# Recent Posts
Posts:
- グラフィックイコライザーのあるオーディオプレイヤーを作る_テスト (2025-04-28)
- 【株】2025年4月の暴落の記録と反省点 (2025-04-14)
- オフラインのカレンダーを作る (2025-04-03)
- 【配当金】IS米国連続増配 (2014) (2025-03-28)
- 【探索】NISA貧乏の痕跡 (2025-03-25)
- 【WordPress】AI最適化!Website LLMS.txt プラグイン (2025-03-14)
- 【WordPress】記事の画像をクリックで拡大させたい (2025-03-13)
- 【配当金】PFE / ファイザー(3月) (2025-03-11)
- 【探索/イベント】2025年7月5日に何かが起きる (2025-03-11)
- 【デザイン】AIでアスキーアートを作ろう (2025-03-05)

# Contact Information
Contact:
- お問い合わせフォーム: https://neo.astrowave.jp/contact/

# Copyright
Copyright: Copyright © astrowave. all rights reserved.

# Language
Language: Japanese

# Last Modified
Last-Modified: 2025-05-07

これでいいじゃん!一瞬そう思います。

記事を投稿する度にやるんですか?

ですよね。毎回やるのはめんどくさい。。自動がいいです。

【AI】一緒にプラグインを作ろう

プラグインを作った事ありますか?
AIにプラグインを1度も作ったことがないと伝えました。

すると。。〆(゚ε゚AI). サササ

llms-txt-updater/
├── llms-txt-updater.php
├── includes/
│ ├── class-llms-txt-generator.php
│ └── class-llms-txt-updater.php
├── admin/
│ ├── class-llms-txt-admin.php
│ └── js/
│  └── admin.js
└── README.txt

パーミッションはWordPressのプラグインとして動作するために必要な権限は:

  • ディレクトリ: 755 (drwxr-xr-x)
  • ファイル: 644 (-rw-r–r–)

↓ここに作成ファイルをUPしてくださいとのこと。
wp_パス/wp-content/plugins/llms-txt-updater/

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

以下にAIと一緒に作ったソースコードを共有したいと思います。

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

WordPressがインストールされているフォルダを探してください。
/llms-txt-updater/というフォルダを作成してまとめ入れ、FTPでアップします。

アップ場所:
wp_パス/wp-content/plugins/

要件は以下です。

  • 自動更新機能(新規投稿・投稿更新時に自動更新)
  • 最新の記事10件を入れる
  • バックアップ機能(自動更新前に自動)
  • 管理画面での設定(シンプルな機能で)

llms-txt-updater.php

<?php
/**
 * Plugin Name: LLMs.txt Updater
 * Plugin URI: https://neo.astrowave.jp/
 * Description: WordPressのLLMs.txtを自動更新します
 * Version: 1.0.0
 * Author: サイガモ
 * Author URI: https://neo.astrowave.jp/
 * License: GPL v2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: llms-txt-updater
 */

// プラグインの定数定義
define('LLMS_TXT_VERSION', '1.0.0');
define('LLMS_TXT_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('LLMS_TXT_PLUGIN_URL', plugin_dir_url(__FILE__));

// 必要なファイルを読み込み
require_once LLMS_TXT_PLUGIN_DIR . 'includes/class-llms-txt-generator.php';
require_once LLMS_TXT_PLUGIN_DIR . 'includes/class-llms-txt-updater.php';
require_once LLMS_TXT_PLUGIN_DIR . 'admin/class-llms-txt-admin.php';

// プラグインの初期化
function llms_txt_updater_init() {
    // 管理画面クラスのインスタンス化
    if (is_admin()) {
        new LLMS_TXT_Admin();
    }
    
    // アップデータークラスのインスタンス化
    $updater = new LLMS_TXT_Updater();
    $updater->init();
}
add_action('plugins_loaded', 'llms_txt_updater_init');

// プラグインの有効化時の処理
function llms_txt_updater_activate() {
    // 初期のLLMs.txtを生成
    $generator = new LLMS_TXT_Generator();
    $generator->generate_llms_txt();
}
register_activation_hook(__FILE__, 'llms_txt_updater_activate');

// プラグインの無効化時の処理
function llms_txt_updater_deactivate() {
    // 必要に応じてクリーンアップ処理を追加
}
register_deactivation_hook(__FILE__, 'llms_txt_updater_deactivate');

// AJAXハンドラー
function llms_txt_manual_update_handler() {
    check_ajax_referer('llms_txt_manual_update', 'nonce');
    
    if (!current_user_can('manage_options')) {
        wp_send_json_error('権限がありません。');
    }
    
    $generator = new LLMS_TXT_Generator();
    $generator->generate_llms_txt();
    
    wp_send_json_success('更新が完了しました。');
}
add_action('wp_ajax_llms_txt_manual_update', 'llms_txt_manual_update_handler');

// セキュリティチェック
if (!defined('ABSPATH')) {
    exit;
}

※メモ:AJAXハンドラー(51行目)

  • 手動更新(ボタン)がAJAXで実行される
  • ページのリロードが不要になる
  • 更新中はスピナーが表示される
  • 更新結果がメッセージで表示される
  • 更新履歴が自動的に更新される

includes/class-llms-txt-updater.php

<?php
class LLMS_TXT_Updater {
    public function init() {
        // 設定を取得
        $options = get_option('llms_txt_options', array('auto_update' => 0));
        
        // 自動更新が有効な場合のみフックを追加
        if (!empty($options['auto_update'])) {
            // 投稿が公開されたときに更新
            add_action('publish_post', array($this, 'update_llms_txt'));
            
            // 投稿が更新されたときに更新
            add_action('post_updated', array($this, 'update_llms_txt'));
            
            // カテゴリーが更新されたときに更新
            add_action('edited_category', array($this, 'update_llms_txt'));
            
            // 毎日自動更新
            if (!wp_next_scheduled('llms_txt_daily_update')) {
                wp_schedule_event(time(), 'daily', 'llms_txt_daily_update');
            }
            add_action('llms_txt_daily_update', array($this, 'update_llms_txt'));
        }
    }

    public function update_llms_txt() {
        $generator = new LLMS_TXT_Generator();
        $generator->generate_llms_txt();
    }
}

includes/class-llms-txt-generator.php

<?php
class LLMS_TXT_Generator {
    public function generate_llms_txt() {
        $content = $this->get_header();
        $content .= $this->get_site_info();
        $content .= $this->get_categories();
        $content .= $this->get_topics();
        $content .= $this->get_guidelines();
        $content .= $this->get_structure();
        $content .= $this->get_recent_posts();
        $content .= $this->get_contact_info();
        $content .= $this->get_footer();

        $this->backup_llms_txt(); // バックアップを作成
        $this->save_llms_txt($content);

        // 更新履歴を記録
        $this->update_history();
    }

    private function update_history() {
        $history = get_option('llms_txt_update_history', array());
        $reason = '手動更新';
        if (did_action('publish_post')) {
            $reason = '新規投稿';
        } elseif (did_action('post_updated')) {
            $reason = '投稿更新';
        } elseif (did_action('edited_category')) {
            $reason = 'カテゴリー更新';
        } elseif (did_action('llms_txt_daily_update')) {
            $reason = '定期更新';
        }
        
        array_unshift($history, array(
            'date' => current_time('Y-m-d H:i:s'),
            'reason' => $reason
        ));
        
        // 最新の10件のみ保持
        $history = array_slice($history, 0, 10);
        update_option('llms_txt_update_history', $history);
    }

    private function get_header() {
        return "# LLMs.txt for " . get_bloginfo('name') . "\n" .
               "# Last updated: " . date('Y-m-d') . "\n\n";
    }

    private function get_site_info() {
        return "# Site Information\n" .
               "Site: " . get_site_url() . "\n" .
               "Owner: " . get_bloginfo('admin_email') . "\n" .
               "Description: " . get_bloginfo('description') . "\n\n";
    }

    private function get_categories() {
        $content = "# Content Categories\nCategories:\n";
        $categories = get_categories();
        foreach ($categories as $category) {
            $content .= "- " . $category->name . "\n";
        }
        return $content . "\n";
    }

    private function get_topics() {
        return "# Main Topics\nTopics:\n" .
               "- 空想SF\n" .
               "- WEB制作\n" .
               "- 高配当株投資\n" .
               "- NISA投資\n" .
               "- 配当金レポート\n\n";
    }

    private function get_guidelines() {
        return "# Content Guidelines\nGuidelines:\n" .
               "- 日本語コンテンツのみ\n" .
               "- 個人の投資経験に基づく情報\n" .
               "- SF作品のレビューと考察\n" .
               "- WEB制作の技術的な内容\n" .
               "- 配当金の受取記録\n\n";
    }

    private function get_structure() {
        return "# Content Structure\nStructure:\n" .
               "- 新着記事\n" .
               "- カテゴリー別記事\n" .
               "- タグ別記事\n" .
               "- プロフィール\n" .
               "- お問い合わせ\n\n";
    }

    private function get_recent_posts() {
        $content = "# Recent Posts\nPosts:\n";
        $recent_posts = wp_get_recent_posts(array(
            'numberposts' => 10,
            'post_status' => 'publish'
        ));
        
        foreach ($recent_posts as $post) {
            $content .= "- " . $post['post_title'] . " (" . 
                       date('Y-m-d', strtotime($post['post_date'])) . ")\n";
        }
        return $content . "\n";
    }

    private function get_contact_info() {
        return "# Contact Information\nContact:\n" .
               "- お問い合わせフォーム: " . get_site_url() . "/contact/\n\n";
    }

    private function get_footer() {
        return "# Copyright\nCopyright: Copyright © " . get_bloginfo('name') . ". all rights reserved.\n\n" .
               "# Language\nLanguage: Japanese\n\n" .
               "# Last Modified\nLast-Modified: " . date('Y-m-d') . "\n";
    }

    private function save_llms_txt($content) {
        $file_path = ABSPATH . 'llms.txt';
        if (is_writable(ABSPATH)) {
            file_put_contents($file_path, $content);
        } else {
            // エラーログに記録
            error_log('LLMs.txt: ファイルの書き込み権限がありません');
        }
    }

    private function backup_llms_txt() {
        $file_path = ABSPATH . 'llms.txt';
        if (file_exists($file_path)) {
            $backup_path = ABSPATH . 'llms.txt.bak';
            copy($file_path, $backup_path);
        }
    }
}

admin/class-llms-txt-admin.php

<?php
class LLMS_TXT_Admin {
    public function __construct() {
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('admin_init', array($this, 'register_settings'));
        add_action('admin_notices', array($this, 'admin_notices'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
    }

    public function enqueue_scripts($hook) {
        if ('settings_page_llms-txt-settings' !== $hook) {
            return;
        }
        wp_enqueue_script('llms-txt-admin', LLMS_TXT_PLUGIN_URL . 'admin/js/admin.js', array('jquery'), LLMS_TXT_VERSION, true);
        wp_localize_script('llms-txt-admin', 'llmsTxtAdmin', array(
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('llms_txt_manual_update')
        ));
    }

    public function add_admin_menu() {
        add_options_page(
            'LLMs.txt設定',
            'LLMs.txt設定',
            'manage_options',
            'llms-txt-settings',
            array($this, 'display_settings_page')
        );
    }

    public function register_settings() {
        register_setting('llms_txt_settings', 'llms_txt_options');
    }

    public function display_settings_page() {
        ?>
        <div class="wrap">
            <h1>LLMs.txt設定</h1>
            
            <!-- 手動更新フォーム -->
            <form method="post" action="" id="llms-txt-manual-update-form">
                <?php wp_nonce_field('llms_txt_manual_update'); ?>
                <p>
                    <input type="submit" name="llms_txt_manual_update" class="button button-primary" value="LLMs.txtを手動更新">
                    <span class="spinner" style="float: none; margin: 0 10px;"></span>
                    <span class="update-message"></span>
                </p>
            </form>

            <!-- 設定フォーム -->
            <form method="post" action="options.php">
                <?php
                settings_fields('llms_txt_settings');
                do_settings_sections('llms_txt_settings');
                ?>
                <table class="form-table">
                    <tr>
                        <th>自動更新</th>
                        <td>
                            <label>
                                <input type="checkbox" name="llms_txt_options[auto_update]" value="1" 
                                    <?php checked(1, get_option('llms_txt_options')['auto_update'] ?? 0); ?>>
                                投稿時に自動更新する
                            </label>
                        </td>
                    </tr>
                </table>
                <?php submit_button(); ?>
            </form>

            <!-- 更新履歴 -->
            <h2>更新履歴</h2>
            <div id="llms-txt-history">
                <?php $this->display_update_history(); ?>
            </div>
        </div>
        <?php
    }

    private function display_update_history() {
        $update_history = get_option('llms_txt_update_history', array());
        ?>
        <table class="widefat">
            <thead>
                <tr>
                    <th>更新日時</th>
                    <th>更新理由</th>
                </tr>
            </thead>
            <tbody>
                <?php
                if (!empty($update_history)) {
                    foreach ($update_history as $history) {
                        echo '<tr>';
                        echo '<td>' . esc_html($history['date']) . '</td>';
                        echo '<td>' . esc_html($history['reason']) . '</td>';
                        echo '</tr>';
                    }
                } else {
                    echo '<tr><td colspan="2">更新履歴がありません。</td></tr>';
                }
                ?>
            </tbody>
        </table>
        <?php
    }

    public function admin_notices() {
        if (isset($_GET['llms_txt_updated'])) {
            ?>
            <div class="notice notice-success is-dismissible">
                <p>LLMs.txtを更新しました。</p>
            </div>
            <?php
        }
    }
}

admin/js/admin.js

jQuery(document).ready(function($) {
   $('#llms-txt-manual-update-form').on('submit', function(e) {
       e.preventDefault();
       
       var $form = $(this);
       var $button = $form.find('input[type="submit"]');
       var $spinner = $form.find('.spinner');
       var $message = $form.find('.update-message');
       
       $button.prop('disabled', true);
       $spinner.addClass('is-active');
       $message.html('');
       
       $.ajax({
           url: llmsTxtAdmin.ajaxurl,
           type: 'POST',
           data: {
               action: 'llms_txt_manual_update',
               nonce: llmsTxtAdmin.nonce
           },
           success: function(response) {
               if (response.success) {
                   $message.html('<span style="color: green;">更新が完了しました。</span>');
                   // 更新履歴を更新
                   $('#llms-txt-history').load(window.location.href + ' #llms-txt-history > *');
               } else {
                   $message.html('<span style="color: red;">更新に失敗しました。</span>');
               }
           },
           error: function() {
               $message.html('<span style="color: red;">更新に失敗しました。</span>');
           },
           complete: function() {
               $button.prop('disabled', false);
               $spinner.removeClass('is-active');
           }
       });
   });
});

プラグイン用ファイルは以上になります。

以下はマニュアル

README.txt

=== LLMs.txt Updater ===
Contributors: saigami
Tags: llms.txt, seo, content
Requires at least: 5.0
Tested up to: 6.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

WordPressのLLMs.txtを自動更新するプラグインです。

== 説明 ==

このプラグインは、WordPressサイトのLLMs.txtファイルを自動的に生成・更新します。
以下の機能を提供します:

* 投稿時の自動更新
* カテゴリー更新時の自動更新
* 毎日の自動更新
* 管理画面からの設定
* バックアップの作成

== インストール方法 ==

1. プラグインファイルを`wp-content/plugins/`ディレクトリにアップロード
2. 管理画面の「プラグイン」メニューから「LLMs.txt Updater」を有効化
3. 「設定」→「LLMs.txt設定」から必要な設定を行う

== よくある質問 ==

Q: LLMs.txtとは何ですか?
A: LLMs.txtは、大規模言語モデル(LLM)に対して、ウェブサイトの内容や構造を伝えるためのファイルです。

Q: バックアップはどこに保存されますか?
A: バックアップは`llms.txt.bak`として、LLMs.txtと同じディレクトリに保存されます。

== 更新履歴 ==

= 1.0.0 =
* 初回リリース

== 変更履歴 ==

= 1.0.0 =
* 基本的な機能の実装
* 自動更新機能の追加
* 管理画面の実装
* バックアップ機能の追加

自動出力されたllms.txtファイル
https://neo.astrowave.jp/llms.txt

【補足1】固定の内容はオプションです

includes/class-llms-txt-generator.phpソース内に、固定の内容があります。
65〜90行目ですが、お気づきでしょうか。

    private function get_topics() {
return "# Main Topics\nTopics:\n" .
"- 空想SF\n" .
"- WEB制作\n" .
"- 高配当株投資\n" .
"- NISA投資\n" .
"- 配当金レポート\n\n";
}

private function get_guidelines() {
return "# Content Guidelines\nGuidelines:\n" .
"- 日本語コンテンツのみ\n" .
"- 個人の投資経験に基づく情報\n" .
"- SF作品のレビューと考察\n" .
"- WEB制作の技術的な内容\n" .
"- 配当金の受取記録\n\n";
}

private function get_structure() {
return "# Content Structure\nStructure:\n" .
"- 新着記事\n" .
"- カテゴリー別記事\n" .
"- タグ別記事\n" .
"- プロフィール\n" .
"- お問い合わせ\n\n";
}

↑これは最初、AIにまとめてもらった時の内容が良かったので採用しました。
しかしこれはllms.txtの基本的な必須項目ではありません。

llms.txtの基本的な必須項目は以下です。

1.サイト情報
サイト名
所有者
説明

2.コンテンツカテゴリー
カテゴリー一覧

3.最近の投稿
最新の記事一覧

4.連絡先情報
お問い合わせ方法

5.言語情報
使用言語

6.最終更新日

※参考
https://llmstxt.org/

このサンプル実装では、必須ではない項目(トピックス、ガイドライン、構造)を追加で含めています。これらは、サイトの内容をより詳細に説明するためのオプション項目としての採用です。

いらないなら消していただくか、自分用に変えるもよし。

プラグインの機能をシンプルにしたいのです。

【補足2】固定は嫌だという人には

どうしてもオプションを管理画面で編集したい人にAIからヒント。

class-llms-txt-generator.php

private function get_topics() {
    $topics = get_option('llms_txt_topics', array(
        '空想SF',
        'WEB制作',
        '高配当株投資',
        'NISA投資',
        '配当金レポート'
    ));
    
    $content = "# Main Topics\nTopics:\n";
    foreach ($topics as $topic) {
        $content .= "- " . $topic . "\n";
    }
    return $content . "\n";
}

private function get_guidelines() {
    $guidelines = get_option('llms_txt_guidelines', array(
        '日本語コンテンツのみ',
        '個人の投資経験に基づく情報',
        'SF作品のレビューと考察',
        'WEB制作の技術的な内容',
        '配当金の受取記録'
    ));
    
    $content = "# Content Guidelines\nGuidelines:\n";
    foreach ($guidelines as $guideline) {
        $content .= "- " . $guideline . "\n";
    }
    return $content . "\n";
}

private function get_structure() {
    $structure = get_option('llms_txt_structure', array(
        '新着記事',
        'カテゴリー別記事',
        'タグ別記事',
        'プロフィール',
        'お問い合わせ'
    ));
    
    $content = "# Content Structure\nStructure:\n";
    foreach ($structure as $item) {
        $content .= "- " . $item . "\n";
    }
    return $content . "\n";
}

class-llms-txt-admin.php

public function register_settings() {
    register_setting('llms_txt_settings', 'llms_txt_options');
    register_setting('llms_txt_settings', 'llms_txt_topics');
    register_setting('llms_txt_settings', 'llms_txt_guidelines');
    register_setting('llms_txt_settings', 'llms_txt_structure');
}

public function display_settings_page() {
    // ... 既存のコード ...
    
    <h2>コンテンツ設定</h2>
    <table class="form-table">
        <tr>
            <th>トピックス</th>
            <td>
                <textarea name="llms_txt_topics" rows="5" cols="50"><?php 
                    echo esc_textarea(implode("\n", get_option('llms_txt_topics', array()))); 
                ?></textarea>
                <p class="description">1行に1つのトピックスを入力してください。</p>
            </td>
        </tr>
        <tr>
            <th>ガイドライン</th>
            <td>
                <textarea name="llms_txt_guidelines" rows="5" cols="50"><?php 
                    echo esc_textarea(implode("\n", get_option('llms_txt_guidelines', array()))); 
                ?></textarea>
                <p class="description">1行に1つのガイドラインを入力してください。</p>
            </td>
        </tr>
        <tr>
            <th>構造</th>
            <td>
                <textarea name="llms_txt_structure" rows="5" cols="50"><?php 
                    echo esc_textarea(implode("\n", get_option('llms_txt_structure', array()))); 
                ?></textarea>
                <p class="description">1行に1つの項目を入力してください。</p>
            </td>
        </tr>
    </table>

以上になります。

星間旅路のメロディ

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

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

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

楽しげで元気がでそうかも?

「【WordPress】LLMs.txtプラグインを作る」への1件のフィードバック

コメントは受け付けていません。