MOMOちゃんねるは掲示板です。クライアント側の機能は単純で、書き込み、読み込み、Re:と、書き込み数制限などです。サーバー側では個別削除、全削除、削除用パスワード設定、ログインフォームなどです。

パスワード設定

設定順番①


1.初期設定は管理者パスワード設定フォーム
2.設定するとadmin_password.txtのファイルがハッシュ化されて自動生成
3.管理者パスワード設定された後は、管理者パスワード設定フォームのアドレスを入力しても管理者専用メッセージ削除フォームに移動
4.管理者パスワード変更は、管理者専用メッセージ削除フォームでログイン後でなければ、"権限がありません"を表示
5.管理者パスワード変更は現在のパスワード、新パスワードを入力して変更ボタン

6.万が一パスワードを忘れた場合はadmin_password.txtを直接削除後に再設定可能(テキスト自動再生成)
7.1000件を超えた場合に新スレッド表示、旧スレッドリンクボタン ※詳細は最後の方に記述

ご利用について
掲示板コードは仕様の変更・修正・削除・追加・デザイン変更等、全て制限がなくご自由にアレンジしても差し支えありません。また、コード全体をコピー&ペーストして、動作不具合が発生しても作成者は責任を負う事は致しません。
※全体を変更なく使用された場合は、どこか片隅に小さくMOMOPLANと書いて頂くと嬉しいです。
ファイル名・階層
chat/
├── index.html←クライアント側フォーム
├── script.js←フォーム入力処理
├── send.php←書き込み・読み込み処理
├── style.css←デザイン
├── new_script.js←オプション
├── new_send.php←オプション
├── admin/ ← 🔒 管理ページ専用フォルダ(アクセス制限)
 ├── admin_delete.html←管理者削除フォーム
 ├── admin_delete.php←===↓==削除・読み込み処理
 ├── admin_messages.php
 ├── admin_script.js←====↑==
 ├── admin_setup.php←パスワード登録
 ├── admin_auth.php←認証
 ├── admin_change_password.php←パスワード変更
 ├── style.css
 ├── admin_password.txt←パスワード
 ├── messages.txt←書き込み内容
フォルダーはお好きな名前で※各ファイルの相対・絶対pathの変更
【ファイルジャンプ】
index.html script.js send.php style.css admin_delete.html admin_script.js  admin_delete.php admin_messages.php admin_setup.php admin_auth.php style.css  admin_change_password.php new_script.js new_send.php
1000件を超える場合の処理
クライアント側
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>チャットルーム</title>
    <link rel="stylesheet" href=" client_style.css">
</head>
<body>
    <center><h2>MOMOちゃんねる</h2></center>

    <div class="scroller" id="chat-box"></div>
    <h2><div><input type="text" id="username" placeholder="ユーザー名を入力..."><font color="#6633ff"> 【コード公開用のサンプルチャットです・・・】</font></div></h2>
    <textarea class="tarea" input type="text" id="message" placeholder="メッセージを入力..."></textarea>
    <input type="hidden" id="reply_to" value="0"><br>
    <button class="bchat" id="send-button" onclick="sendMessage()" disabled>送信</button>

    <script src="script.js"></script>
</body>
</html>
JS
document.addEventListener("DOMContentLoaded", function () {
    let messageInput = document.getElementById("message");
    let sendButton = document.getElementById("send-button");

    function checkMessage() {
        // メッセージが空欄のときだけ送信ボタンを無効化
        sendButton.disabled = messageInput.value.trim() === "";
    }

    // 入力が変更されたらリアルタイムでチェック
    messageInput.addEventListener("input", checkMessage);

    // 初回チェック(リロード後も適用)
    checkMessage();
});

// メッセージ送信
function sendMessage() {
    let username = document.getElementById("username").value;
    let message = document.getElementById("message").value;
    let reply_to = document.getElementById("reply_to").value;

    fetch("send.php", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: "username=" + encodeURIComponent(username) + 
              "&message=" + encodeURIComponent(message) + 
              "&reply_to=" + encodeURIComponent(reply_to)
    }).then(() => { 
        document.getElementById("message").value = ""; 
        document.getElementById("reply_to").value = "0"; 
    });
}

// 返信ボタン処理
function replyToMessage(id) {
    document.getElementById("reply_to").value = id;
    document.getElementById("message").focus();
}

// メッセージの読み込み
function loadMessages() {
    fetch("messages.txt")
        .then(response => response.text())
        .then(text => {
            let chatBox = document.getElementById("chat-box");
            chatBox.innerHTML = "";

            text.split("\n").forEach(line => {
                if (line.trim()) {
                    let match = line.match(/^(\d+) \| \[(\d{2}-\d{2} \d{2}:\d{2})\] (.+): (.+)$/);
                    if (!match) {
                        console.warn("不正データのためスキップ:", line);
                        return;
                    }

                    let msgId = match[1];
                    let date = match[2];
                    let username = match[3];
                    let message = match[4];
                 
                    chatBox.innerHTML += `<div><strong>${msgId}</strong> | ${date} | ${username}: ${message} 
                        <button onclick="replyToMessage(${msgId})">Re:</button></div>`;
                }
            });
        })
        .catch(error => console.error("メッセージ読み込みエラー:", error));
}
// メッセージの定期更新
setInterval(loadMessages, 1000);
send.php
<?php
session_start();

$file_path = "messages.txt";

// メッセージ送信データ取得
$username = isset($_POST["username"]) && trim($_POST["username"]) !== "" ? htmlspecialchars($_POST["username"]) : "名無しさん";
/*$username = trim($_POST["username"] ?? "");*/
$message = trim($_POST["message"] ?? "");
$reply_to = intval($_POST["reply_to"] ?? 0); // 数値IDで取得

// メッセージファイルの読み込み
$messages = file($file_path, FILE_IGNORE_NEW_LINES);
$new_id = count($messages) + 1;

if ($new_id > 1000) {
    die("エラー: メッセージの数が上限に達しました。新規登録はできません。");
}

$reply_user = "";

// 返信元のユーザー名を取得
foreach ($messages as $line) {
    preg_match("/^$reply_to \| \[\d{2}-\d{2} \d{2}:\d{2}\] (\S+):/", $line, $matches);
    if (!empty($matches[1])) {
        $reply_user = $matches[1]; // ユーザー名を取得
        break;
    }
}

// 新しいIDを設定
$new_id = count($messages) + 1;

// メッセージのフォーマット
$new_message = "$new_id | [" . date("m-d H:i") . "] $username: $message";
if ($reply_to > 0 && !empty($reply_user)) {
    $new_message .= " (Re: $reply_user)"; // ユーザー名を返信参照に追加
}

// メッセージファイルに保存
$result = file_put_contents($file_path, $new_message . "\n", FILE_APPEND);

if ($result === false) {
    die("エラー: ファイルに書き込めませんでした!");
}

/*session_start();
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = isset($_POST["username"]) && trim($_POST["username"]) !== "" ? htmlspecialchars($_POST["username"]) : "名無しさん";
    $message = htmlspecialchars($_POST["message"]);
    $reply_to = isset($_POST["reply_to"]) ? intval($_POST["reply_to"]) : 0;

    // IDを生成(簡易的な連番方式)
    $messages = file("messages.txt");
    $new_id = count($messages) + 1;

    // メッセージのフォーマット
    $log = "$new_id | [" . date("m-d H:i") . "] " . $username . ": " . $message;
    if ($reply_to > 0) {
        $log .= " (Re: $reply_to)";
    }
    $log .= "\n";

    file_put_contents("messages.txt", $log, FILE_APPEND);
}*/
?>
CSS
body {
    background-image: url("chat.png");
    color: #131314; /* 文字色を見やすく */
    font-family: Arial, sans-serif;
    font-weight: bold;
    margin: 2em;
}

.scroller {
    margin: 0 auto;
    width: auto;
    height: 360px;
    margin : 30px ;
    padding : 20px ;
    color: #2e4f97; /* 文字色を見やすく */
    overflow-y: scroll;
    background-color: #fcf3f8;
    border: solid 4px #DE94BF;
    border-radius: 6px;
    scrollbar-color: #FAF8D8 #DE94BF;
    scrollbar-width: thin;
    line-height: 1.8em
}

#message {
    width: 50%;
}

.message-box {
    border: 2px solid #a292fd;
    padding: 10px;
    background-color: #b7a7fc; /* 白背景で読みやすく */
    width: auto;
    border-radius: 12px;
}

button {
    background-color: #ffe48c; /* 緑のボタンで優しい印象に */
    color: rgb(63, 61, 61);
    border: none;
    padding: 2px 8px;
    cursor: pointer;
    border-radius: 4px;
}

button:hover {
    background-color: #caa53d;
}

.bchat{
    background-color: #2e7ff8;
    color: #ebf6ff;
    padding: 12px 24px;
}

.tarea{
    display: inline-block;
    width: 10%;
    padding: 10px;
    border-radius:20px;
    box-sizing: border-box;
    background: #d7d4f5;
    margin: 0.5em 0;
    line-height: 1.5;
    height: 4em;
}

#username{
    display: inline-block;
    width: 20%;
    padding: 4px;
    border-radius:6px;
    box-sizing: border-box;
    background: #e7e6f8;
    color: #4034e4;
    font-weight: bold;
    margin: 0.5em 0;
    line-height: 1;
    height: 2em;
}
サーバー側
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>管理者専用メッセージ削除</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h2>管理者専用メッセージ削除</h2>

    <!-- 管理者ログインフォーム -->
    <form id="login-form">
        <input type="text" name="username" autocomplete="username" style="display:none;">
        <input type="password" id="admin_password" placeholder="管理者パスワード" autocomplete="new-password">
        <button type="button" onclick="adminLogin()">ログイン</button>
    </form>

    <!-- メッセージ一覧と削除ボタン(ログイン後に表示) -->
    <div id="delete-section" style="display: none;">
        <h3>メッセージ一覧</h3>
        <div id="message-list" class="message-box">
            <!-- メッセージ一覧を動的に追加 -->
        </div>
    </div>
    <button onclick="deleteAllMessages()">全削除</button>
    <script src="admin_script.js"></script>
</body>
</html>
JS
function adminLogin() {
    let password = document.getElementById("admin_password").value;

    fetch("admin_auth.php", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: "password=" + encodeURIComponent(password)
    })
    .then(response => response.text())
    .then(result => {
        if (result === "success") {
            document.getElementById("login-form").style.display = "none";
            document.getElementById("delete-section").style.display = "block";
            fetchMessages(); // メッセージ一覧を取得
        } else {
            alert("認証失敗:パスワードが間違っています");
        }
    });
}

function fetchMessages() {
    fetch("admin_messages.php")
        .then(response => response.json())
        .then(messages => {
            let messageList = document.getElementById("message-list");
            messageList.innerHTML = "";

            messages.forEach(msg => {
                let msgElement = document.createElement("div");
                msgElement.innerHTML = `${msg.id} | ${msg.username}: ${msg.message} 
                <button onclick="deleteMessage(${msg.id})">削除</button>`;
                messageList.appendChild(msgElement);
            });
        });
}

function deleteMessage(messageId) {
    fetch("admin_delete.php", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: "delete_id=" + encodeURIComponent(messageId)
    })
    .then(response => response.text())
    .then(result => {
        console.log("削除レスポンス:", result); // 削除結果をコンソールに表示
        fetchMessages(); // メッセージ一覧を更新
    });
}

function deleteAllMessages() {
    if (!confirm("本当にすべてのメッセージを削除しますか?")) {
        return; // ユーザー確認
    }

    fetch("admin_delete.php", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: "delete_all=true"
    })
    .then(() => fetchMessages()); // 削除後に一覧を更新
}
admin_delete.php
<?php
session_start();
if (!isset($_SESSION["admin"]) || $_SESSION["admin"] !== true) {
    die("権限がありません");
}

$file_path = "messages.txt";

// ファイルが存在しない場合は作成
if (!file_exists($file_path)) {
    file_put_contents($file_path, ""); // 空ファイルを作成
}

// ファイルを読み込む
$messages = file($file_path, FILE_IGNORE_NEW_LINES);
if ($messages === false) {
    $messages = [];
}

// IDの取得(文字+数値を許可&空白削除)
$delete_id = $_POST["delete_id"] ?? "";
error_log("削除リクエストID: " . $delete_id);

$filtered_messages = array_filter($messages, function($line) use ($delete_id) {
    return !preg_match("/^$delete_id \| /", $line); // 正確なIDが一致する行のみ削除
});

file_put_contents($file_path, implode("\n", $filtered_messages) . "\n");

echo "メッセージID '" . htmlspecialchars($delete_id) . "' を削除しました";

if (isset($_POST["delete_all"]) && $_POST["delete_all"] === "true") {
    file_put_contents($file_path, ""); // ファイルを空にする
    echo "すべてのメッセージを削除しました";
    exit;
}
?>
admin_messages.php
<?php
session_start();
if (!isset($_SESSION["admin"]) || $_SESSION["admin"] !== true) {
    die(json_encode([])); // 認証されていない場合は空リスト
}

$file_path = "messages.txt";
$messages = file($file_path, FILE_IGNORE_NEW_LINES);
$response = [];

//=============================================================
/*foreach ($messages as &$line) {
    if (preg_match("/\(Re: $delete_id\)/", $line)) {
        $line = preg_replace("/\(Re: $delete_id\)/", "(Re: 管理者の都合で削除されました)", $line);
    }
}
file_put_contents($file_path, implode("\n", $messages) . "\n");*/

foreach ($messages as $line) {
    preg_match("/^(\d+) \| \[\d{2}-\d{2} \d{2}:\d{2}\] (\S+): (.*)/", $line, $matches);
    if (!empty($matches)) {
        $response[] = ["id" => $matches[1], "username" => $matches[2], "message" => $matches[3]];
    }
}

echo json_encode($response);
?>
admin_setup.php
<?php
session_start();
$file_path = "admin_password.txt";

// すでにパスワードが設定されている場合、ログイン画面へリダイレクト
if (file_exists($file_path)) {
    header("Location: admin_delete.html");
    exit();
}

// 初回パスワード設定処理
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $new_password = password_hash($_POST["new_password"], PASSWORD_DEFAULT); // ハッシュ化
    file_put_contents($file_path, $new_password);
    echo "パスワードが設定されました!管理画面へ移動してください。";
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>管理者パスワード設定</title>
</head>
<body>
    <h2>管理者パスワード設定</h2>
    <form method="POST">
        <input type="password" name="new_password" placeholder="管理者パスワードを入力">
        <button type="submit">設定</button>
    </form>
</body>
</html>
admin_auth.php
<?php
session_start();
$file_path = "admin_password.txt";

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    if (!file_exists($file_path)) {
        die("管理者パスワードが設定されていません。");
    }

    $saved_password = file_get_contents($file_path);
    $input_password = $_POST["password"] ?? "";

    if (password_verify($input_password, $saved_password)) {
        $_SESSION["admin"] = true;
        echo "success";
    } else {
        echo "error";
    }
}
?>
CSS
body {
    background-color: #ccc;
    color: #333; /* 文字色を見やすく */
    font-family: Arial, sans-serif;
}

#chat-box {
    height: 300px;
    overflow-y: scroll;
    border: 1px solid #918afa;
    padding: 10px;
    width: auto;
}

#message {
    width: 100%;
}
.message-box {
    border: 2px solid #333;
    padding: 10px;
    margin: 10px 0;
    background-color: #b5dbff;
    width: auto;
    border-radius: 8px;
}

.message-box div {
    padding: 5px;
    margin: 5px 0;
    border-bottom: 1px solid #ccc;
}

button {
    background-color: #4d26db;
    color: white;
    border: none;
    padding: 4px 8px;
    cursor: pointer;
    border-radius: 4px;
}

button:hover {
    background-color: #cc0000;
}
admin_change_password.php
<?php
session_start();
$file_path = "admin_password.txt";

// 認証チェック
if (!isset($_SESSION["admin"]) || $_SESSION["admin"] !== true) {
    die("権限がありません");
}

// 現在のパスワード取得
$saved_password = file_exists($file_path) ? file_get_contents($file_path) : "";

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $current_password = $_POST["current_password"] ?? "";
    $new_password = $_POST["new_password"] ?? "";
    
    // 現在のパスワードが正しいか確認
    if (!password_verify($current_password, $saved_password)) {
        die("エラー: 現在のパスワードが正しくありません");
    }
    
    // 新しいパスワードの設定
    $hashed_new_password = password_hash($new_password, PASSWORD_DEFAULT);
    file_put_contents($file_path, $hashed_new_password);
    echo "パスワードが変更されました!";
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>管理者パスワード変更</title>
</head>
<body>
    <h2>管理者パスワード変更</h2>
    <form method="POST">
        <input type="password" name="current_password" placeholder="現在のパスワードを入力">
        <input type="password" name="new_password" placeholder="新しいパスワードを入力">
        <button type="submit">変更</button>
    </form>
</body>
</html>
1000件を超える場合の処置
現状の仕様は1000件を超えるとif ($new_id > 1000) {die("エラー: メッセージの数が上限に達しました。新規登録はできません。");}で終了しますが、オプションとして1000件を超えると新スレッドに切り替わり、過去ログはリンクボタンが表示されて、クリックすると閲覧できる仕様となります。オプションのコードを下記に書きましたので参考にして下さい。
new_script.js
document.addEventListener("DOMContentLoaded", function () {
    let messageInput = document.getElementById("message");
    let sendButton = document.getElementById("send-button");

    function checkMessage() {
        sendButton.disabled = messageInput.value.trim() === "";
    }

    messageInput.addEventListener("input", checkMessage);
    checkMessage();

    let threadList = document.getElementById("thread-list"); // ✅ スレッド一覧の要素を取得

    let files = ["messages1.txt", "messages2.txt", "messages3.txt"]; // ✅ 過去スレッドリスト

    files.forEach(file => {
        let threadLink = document.createElement("button"); // ✅ ボタンを作成
        threadLink.textContent = `📂 過去ログ: ${file}`;
        threadLink.dataset.file = file;
        threadLink.addEventListener("click", function () {
            loadMessages(file); // ✅ 指定ファイルのメッセージをロード
        });
        threadList.appendChild(threadLink);
        threadList.appendChild(document.createElement("br"));
    });
});

function sendMessage() {
    let username = document.getElementById("username").value;
    let message = document.getElementById("message").value;
    let reply_to = document.getElementById("reply_to").value;

    fetch("send.php", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: "username=" + encodeURIComponent(username) + 
              "&message=" + encodeURIComponent(message) + 
              "&reply_to=" + encodeURIComponent(reply_to)
    }).then(() => { 
        document.getElementById("message").value = ""; 
        document.getElementById("reply_to").value = "0"; 
    });
}

function replyToMessage(id) {
    document.getElementById("reply_to").value = id;
    document.getElementById("message").focus();
}

// ✅ 指定ファイルのメッセージをロードできるよう修正
function loadMessages(filePath = "messages.txt") {
    fetch(filePath)
        .then(response => response.text())
        .then(text => {
            let chatBox = document.getElementById("chat-box");
            chatBox.innerHTML = "";

            text.split("\n").forEach(line => {
                if (line.trim()) {
                    let match = line.match(/^(\d+) \| \[(\d{2}-\d{2} \d{2}:\d{2})\] (.+): (.+)$/);
                    if (!match) return;

                    let msgId = match[1];
                    let date = match[2];
                    let username = match[3];
                    let message = match[4];

                    chatBox.innerHTML += `<div><strong>${msgId}</strong> | ${date} | ${username}: ${message}
                        <button onclick="replyToMessage(${msgId})">Re:</button></div>`;
                }
            });
        })
        .catch(error => console.error(`エラー: ${filePath} の読み込みに失敗`, error));
}

// ✅ 1秒ごとに最新の messages.txt をロード(リアルタイム更新)
setInterval(() => loadMessages("messages.txt"), 1000);
new_send.php
<?php
session_start();

$base_file = "messages"; // 基本ファイル名
$extension = ".txt"; // 拡張子
$file_index = 1;

// **最新のファイルを決定**
while (file_exists($base_file . $file_index . $extension)) {
    $file_index++;
}

// 最新ファイルのパス(直前のファイルを取得)
$file_path = $base_file . ($file_index - 1) . $extension;
if (!file_exists($file_path)) {
    file_put_contents($file_path, ""); // 新規作成
}

// メッセージ送信データ取得
$username = isset($_POST["username"]) && trim($_POST["username"]) !== "" ? htmlspecialchars($_POST["username"]) : "名無しさん";
$message = trim($_POST["message"] ?? "");
$reply_to = intval($_POST["reply_to"] ?? 0); // 数値IDで取得

// **現在のメッセージ数を取得**
$messages = file($file_path, FILE_IGNORE_NEW_LINES);
$new_id = count($messages) + 1;

// **1000件を超えたら新しいファイルへ切り替え**
if ($new_id > 1000) {
    $file_index++; // 新しいファイル番号へ
    $file_path = $base_file . $file_index . $extension;
    file_put_contents($file_path, ""); // 空の新規ファイルを作成
    $new_id = 1; // **新しいファイルではIDをリセット**
}

$reply_user = "";

// **返信元のユーザー名を取得**
foreach ($messages as $line) {
    preg_match("/^$reply_to \| \[\d{2}-\d{2} \d{2}:\d{2}\] (\S+):/", $line, $matches);
    if (!empty($matches[1])) {
        $reply_user = $matches[1]; // ユーザー名を取得
        break;
    }
}

// **メッセージのフォーマット**
$new_message = "$new_id | [" . date("m-d H:i") . "] $username: $message";
if ($reply_to > 0 && !empty($reply_user)) {
    $new_message .= " (Re: $reply_user)"; // **返信元ユーザー名を追加**
}

// **メッセージファイルに保存**
$result = file_put_contents($file_path, $new_message . "\n", FILE_APPEND);

if ($result === false) {
    die("エラー: ファイルに書き込めませんでした!");
}

echo "メッセージが正常に保存されました!(ファイル: $file_path)";
?>

以上です・・・・・・・ 実装サンプル