LINUX SB 快照站

【神经病项目】单php论坛 附完整代码

原帖: linux.sb/topic/13787 · 共 12 楼 · 标题快照 2026-08-18 22:59:44

楼主
发帖 2026-08-18 22:21:33 · 快照 2026-08-18 22:59:44

瞎做的,当时就准备让我的几个智能体互相聊天而已。。。

下面是代码。希望不被截断。

text.php

<?php
$USER_FILE = __DIR__ . '/user.json';
$SINGLE_DIR = __DIR__ . '/single';
$FORUM_DIR = __DIR__ . '/forum';
$FORUM_POSTS_META = __DIR__ . '/forum/posts_meta.json';
$FORUM_EVENTS_FILE = __DIR__ . '/forum/events.json';
$FORUM_LIKES_FILE = __DIR__ . '/forum/likes.json';
$FORUM_PAGE_SIZE = 30;

date_default_timezone_set('PRC');

function formatMessageTime(?int $ts): string
{
    if ($ts === null || $ts <= 0) {
        return '未知时间';
    }
    return date('Y-m-d H:i', $ts);
}

/** 返回该私聊中对方最后一次发消息的时间戳,无则 null */
function getLastOtherMessageTime(string $currentUser, string $otherUser): ?int
{
    $messages = loadChat($currentUser, $otherUser);
    for ($i = count($messages) - 1; $i >= 0; $i--) {
        if (isset($messages[$i]['from']) && $messages[$i]['from'] === $otherUser && isset($messages[$i]['time'])) {
            return (int) $messages[$i]['time'];
        }
    }
    return null;
}

function minutesAgoText(?int $ts): string
{
    if ($ts === null || $ts <= 0) {
        return '';
    }
    $mins = max(0, (int) floor((time() - $ts) / 60));
    if ($mins === 0) {
        return '刚刚有对方消息';
    }
    return $mins . '分钟前有对方消息';
}

function loadUsers(): array
{
    global $USER_FILE;
    if (!is_readable($USER_FILE)) {
        return [];
    }
    $raw = file_get_contents($USER_FILE);
    $d = $raw !== false ? json_decode($raw, true) : null;
    return is_array($d) ? $d : [];
}

function saveUsers(array $users): void
{
    global $USER_FILE;
    $dir = dirname($USER_FILE);
    if (!is_dir($dir)) {
        mkdir($dir, 0755, true);
    }
    file_put_contents($USER_FILE, json_encode($users, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

function findUsernameByCredential(string $credential): ?string
{
    $users = loadUsers();
    foreach ($users as $name => $info) {
        if (isset($info['credential']) && (string)$info['credential'] === (string)$credential) {
            return $name;
        }
    }
    return null;
}

function ensureSingleDir(): void
{
    global $SINGLE_DIR;
    if (!is_dir($SINGLE_DIR)) {
        mkdir($SINGLE_DIR, 0755, true);
    }
}

function chatFilename(string $user1, string $user2): string
{
    global $SINGLE_DIR;
    $a = $user1;
    $b = $user2;
    if (strcmp($a, $b) > 0) {
        $a = $user2;
        $b = $user1;
    }
    return $SINGLE_DIR . '/' . $a . '_' . $b . '.json';
}

function loadChat(string $user1, string $user2): array
{
    $path = chatFilename($user1, $user2);
    if (!is_readable($path)) {
        return [];
    }
    $raw = file_get_contents($path);
    $d = $raw !== false ? json_decode($raw, true) : null;
    return is_array($d) ? $d : [];
}

function saveChat(string $user1, string $user2, array $messages): void
{
    ensureSingleDir();
    $path = chatFilename($user1, $user2);
    file_put_contents($path, json_encode($messages, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

function q(string $s): string
{
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

function currentCredential(): string
{
    return isset($_GET['credential']) ? (string)$_GET['credential'] : (isset($_POST['credential']) ? (string)$_POST['credential'] : '');
}

function linkParams(array $extra = []): string
{
    $p = array_merge($_GET, $extra);
    return http_build_query($p);
}

// ---------- 论坛与用户动态 ----------
function ensureForumDir(): void
{
    global $FORUM_DIR;
    if (!is_dir($FORUM_DIR)) {
        mkdir($FORUM_DIR, 0755, true);
    }
}

function loadForumPostsMeta(): array
{
    global $FORUM_POSTS_META;
    if (!is_readable($FORUM_POSTS_META)) {
        return [];
    }
    $raw = file_get_contents($FORUM_POSTS_META);
    $d = $raw !== false ? json_decode($raw, true) : null;
    return is_array($d) ? $d : [];
}

function saveForumPostsMeta(array $meta): void
{
    global $FORUM_POSTS_META;
    ensureForumDir();
    file_put_contents($FORUM_POSTS_META, json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

function loadPost(string $id): ?array
{
    global $FORUM_DIR;
    $path = $FORUM_DIR . '/post_' . preg_replace('/[^a-zA-Z0-9_-]/', '', $id) . '.json';
    if (!is_readable($path)) {
        return null;
    }
    $raw = file_get_contents($path);
    $d = $raw !== false ? json_decode($raw, true) : null;
    return is_array($d) ? $d : null;
}

function savePost(array $post): void
{
    global $FORUM_DIR;
    ensureForumDir();
    $id = $post['id'] ?? '';
    $path = $FORUM_DIR . '/post_' . preg_replace('/[^a-zA-Z0-9_-]/', '', $id) . '.json';
    file_put_contents($path, json_encode($post, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

function loadLikes(): array
{
    global $FORUM_LIKES_FILE;
    if (!is_readable($FORUM_LIKES_FILE)) {
        return [];
    }
    $raw = file_get_contents($FORUM_LIKES_FILE);
    $d = $raw !== false ? json_decode($raw, true) : null;
    return is_array($d) ? $d : [];
}

function saveLikes(array $likes): void
{
    global $FORUM_LIKES_FILE;
    ensureForumDir();
    file_put_contents($FORUM_LIKES_FILE, json_encode($likes, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

function loadEvents(): array
{
    global $FORUM_EVENTS_FILE;
    if (!is_readable($FORUM_EVENTS_FILE)) {
        return [];
    }
    $raw = file_get_contents($FORUM_EVENTS_FILE);
    $d = $raw !== false ? json_decode($raw, true) : null;
    return is_array($d) ? $d : [];
}

function saveEvents(array $events): void
{
    global $FORUM_EVENTS_FILE;
    ensureForumDir();
    file_put_contents($FORUM_EVENTS_FILE, json_encode($events, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

function addEvent(string $username, string $type, string $postId, int $floor, ?string $fromUser, ?string $content = null): void
{
    $events = loadEvents();
    if (!isset($events[$username])) {
        $events[$username] = [];
    }
    $ev = ['type' => $type, 'post_id' => $postId, 'floor' => $floor, 'from_user' => $fromUser, 'time' => time(), 'read' => false];
    if ($content !== null && $content !== '') {
        $ev['content'] = $content;
    }
    $events[$username][] = $ev;
    saveEvents($events);
}

function getUserFeedPath(string $username): string
{
    global $FORUM_DIR;
    return $FORUM_DIR . '/feed_' . preg_replace('/[^a-zA-Z0-9_\x80-\xff-]/', '', $username) . '.json';
}

function loadUserFeed(string $username): array
{
    $path = getUserFeedPath($username);
    if (!is_readable($path)) {
        return [];
    }
    $raw = file_get_contents($path);
    $d = $raw !== false ? json_decode($raw, true) : null;
    return is_array($d) ? $d : [];
}

function appendUserFeed(string $username, string $type, string $text, ?string $link = null): void
{
    ensureForumDir();
    $feed = loadUserFeed($username);
    array_unshift($feed, ['type' => $type, 'text' => $text, 'link' => $link, 'time' => time()]);
    file_put_contents(getUserFeedPath($username), json_encode($feed, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

// ---------- 路由 ----------
if (isset($_POST['api'])) {
    $api = (string)$_POST['api'];
    $cred = isset($_POST['credential']) ? (string)$_POST['credential'] : '';

    switch ($api) {
        case 'register': {
            $username = isset($_POST['username']) ? trim((string)$_POST['username']) : '';
            if ($username === '') {
                ?>用户名不能为空。 <a href="text.php?<?= q(linkParams(['page' => 'register'])) ?>">返回注册</a><?php
                exit;
            }
            if (strpos($username,"_") !== false){
                ?>用户名不能有"_"。 <a href="text.php?<?= q(linkParams(['page' => 'register'])) ?>">返回注册</a><?php
                exit;
            }
            if (strpos($username,"/") !== false){
                ?>用户名不能有"/"。 <a href="text.php?<?= q(linkParams(['page' => 'register'])) ?>">返回注册</a><?php
                exit;
            }
            if (strpos($username,"*") !== false){
                ?>用户名不能有"*"。 <a href="text.php?<?= q(linkParams(['page' => 'register'])) ?>">返回注册</a><?php
                exit;
            }
            $users = loadUsers();
            if (isset($users[$username])) {
                ?>用户名已存在。 <a href="text.php?<?= q(linkParams(['page' => 'register'])) ?>">返回注册</a><?php
                exit;
            }
            $credential = (string)random_int(100000, 999999);
            $users[$username] = ['credential' => $credential, 'signature' => ''];
            saveUsers($users);
            ?>注册成功。你的登录凭证是:<strong><?= q($credential) ?></strong><br>
            请将凭证保存到openclaw持久存储,丢失将无法登录。<br>
            <a href="text.php?page=login">去登录</a> | <a href="text.php?page=chat&credential=<?= q($credential) ?>">直接进入发消息</a><?php
            exit;
        }
        case 'login': {
            if ($cred === '') {
                header('Location: text.php?page=login&err=empty');
                exit;
            }
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Location: text.php?page=login&err=invalid');
                exit;
            }
            header('Location: text.php?page=chat&credential=' . urlencode($cred));
            exit;
        }
        case 'start_chat': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                ?>凭证无效,请重新登录或注册。<?php
                exit;
            }
            $other = isset($_POST['other_username']) ? trim((string)$_POST['other_username']) : '';
            if ($other === '') {
                header('Location: text.php?page=chat&credential=' . urlencode($cred) . '&err=no_user');
                exit;
            }
            $users = loadUsers();
            if (!isset($users[$other])) {
                header('Location: text.php?page=chat&credential=' . urlencode($cred) . '&err=user_not_found');
                exit;
            }
            if ($other === $name) {
                header('Location: text.php?page=chat&credential=' . urlencode($cred) . '&err=self');
                exit;
            }
            $path = chatFilename($name, $other);
            if (!is_file($path)) {
                ensureSingleDir();
                file_put_contents($path, '[]');
            }
            header('Location: text.php?page=chat&credential=' . urlencode($cred) . '&chat=' . urlencode($other));
            exit;
        }
        case 'send_message': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                if (!empty($_POST['ajax'])) {
                    header('Content-Type: application/json');
                    echo json_encode(['error' => '凭证无效']);
                    exit;
                }
                ?>凭证无效。<?php
                exit;
            }
            $other = isset($_POST['chat']) ? trim((string)$_POST['chat']) : '';
            $text = isset($_POST['text']) ? trim((string)$_POST['text']) : '';
            if ($other === '' || $text === '') {
                if (!empty($_POST['ajax'])) {
                    header('Content-Type: application/json');
                    echo json_encode(['error' => '缺少聊天对象或消息内容']);
                    exit;
                }
                ?>缺少聊天对象或消息内容。<?php
                exit;
            }
            $users = loadUsers();
            if (!isset($users[$other])) {
                if (!empty($_POST['ajax'])) {
                    header('Content-Type: application/json');
                    echo json_encode(['error' => '用户不存在']);
                    exit;
                }
                ?>用户不存在。<?php
                exit;
            }
            $messages = loadChat($name, $other);
            $messages[] = ['from' => $name, 'to' => $other, 'text' => $text, 'time' => time()];
            saveChat($name, $other, $messages);
            if (!empty($_POST['ajax'])) {
                header('Content-Type: application/json');
                echo json_encode(['ok' => true]);
                exit;
            }
            $autoscroll = isset($_POST['autoscroll_redirect']) ? '&autoscroll=1' : '';
            header('Location: text.php?page=chat&credential=' . urlencode($cred) . '&chat=' . urlencode($other) . $autoscroll);
            exit;
        }
        case 'set_signature': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                ?>凭证无效。<?php
                exit;
            }
            $sig = isset($_POST['signature']) ? (string)$_POST['signature'] : '';
            $users = loadUsers();
            if (!isset($users[$name])) {
                ?>用户不存在。<?php
                exit;
            }
            $users[$name]['signature'] = $sig;
            saveUsers($users);
            appendUserFeed($name, 'signature', '修改了个性签名:' . "\n" . $sig, null);
            header('Location: text.php?page=profile&credential=' . urlencode($cred));
            exit;
        }
        case 'forum_create_post': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Location: text.php?page=forum&credential=' . urlencode($cred));
                exit;
            }
            $title = isset($_POST['title']) ? trim((string)$_POST['title']) : '';
            $content = isset($_POST['content']) ? trim((string)$_POST['content']) : '';
            if ($title === '' || $content === '') {
                header('Location: text.php?page=forum_post&credential=' . urlencode($cred) . '&err=empty');
                exit;
            }
            $meta = loadForumPostsMeta();
            $id = (string)(time() . '_' . bin2hex(random_bytes(4)));
            $meta[] = ['id' => $id, 'title' => $title, 'author' => $name, 'time' => time()];
            saveForumPostsMeta($meta);
            $post = ['id' => $id, 'title' => $title, 'author' => $name, 'content' => $content, 'time' => time(), 'floors' => []];
            savePost($post);
            appendUserFeed($name, 'post', '发帖:' . $title . "\n" . $content, 'text.php?page=forum_post_view&id=' . urlencode($id));
            $goto = isset($_POST['goto']) ? (string)$_POST['goto'] : 'list';
            if ($goto === 'post') {
                header('Location: text.php?page=forum_post_view&id=' . urlencode($id) . '&credential=' . urlencode($cred));
            } else {
                header('Location: text.php?page=forum&credential=' . urlencode($cred));
            }
            exit;
        }
        case 'forum_add_floor': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                if (!empty($_POST['ajax'])) {
                    header('Content-Type: application/json');
                    echo json_encode(['error' => '凭证无效']);
                    exit;
                }
                header('Location: text.php?page=forum&credential=' . urlencode($cred));
                exit;
            }
            $postId = isset($_POST['post_id']) ? trim((string)$_POST['post_id']) : '';
            $content = isset($_POST['content']) ? trim((string)$_POST['content']) : '';
            $quotedFloor = isset($_POST['quoted_floor']) ? (int)$_POST['quoted_floor'] : 0;
            $quotedUser = isset($_POST['quoted_user']) ? trim((string)$_POST['quoted_user']) : '';
            if ($postId === '' || $content === '') {
                if (!empty($_POST['ajax'])) {
                    header('Content-Type: application/json');
                    echo json_encode(['error' => '内容不能为空']);
                    exit;
                }
                header('Location: text.php?page=forum_post_view&id=' . urlencode($postId) . '&credential=' . urlencode($cred));
                exit;
            }
            $post = loadPost($postId);
            if ($post === null) {
                if (!empty($_POST['ajax'])) {
                    header('Content-Type: application/json');
                    echo json_encode(['error' => '帖子不存在']);
                    exit;
                }
                header('Location: text.php?page=forum&credential=' . urlencode($cred));
                exit;
            }
            $floors = $post['floors'] ?? [];
            $floorNum = count($floors) + 2;
            $floors[] = ['floor' => $floorNum, 'author' => $name, 'content' => $content, 'time' => time(), 'quoted_floor' => $quotedFloor ?: null, 'quoted_user' => $quotedUser !== '' ? $quotedUser : null];
            $post['floors'] = $floors;
            savePost($post);
            $meta = loadForumPostsMeta();
            foreach ($meta as &$m) {
                if (($m['id'] ?? '') === $postId) {
                    $m['time'] = time();
                    break;
                }
            }
            saveForumPostsMeta($meta);
            $postTitle = $post['title'] ?? '';
            addEvent($post['author'], 'reply', $postId, $floorNum, $name, $content);
            if ($quotedUser !== '' && $quotedUser !== $name) {
                addEvent($quotedUser, 'reply', $postId, $quotedFloor, $name, $content);
            }
            $replyFeedText = '回复了《' . $postTitle . '》:' . $content;
            appendUserFeed($name, 'reply', $replyFeedText, 'text.php?page=forum_post_view&id=' . urlencode($postId));
            if (!empty($_POST['ajax'])) {
                header('Content-Type: application/json');
                echo json_encode(['ok' => true, 'floor' => $floorNum]);
                exit;
            }
            header('Location: text.php?page=forum_post_view&id=' . urlencode($postId) . '&credential=' . urlencode($cred));
            exit;
        }
        case 'forum_like': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效']);
                exit;
            }
            $postId = isset($_POST['post_id']) ? trim((string)$_POST['post_id']) : '';
            $floor = (int)($_POST['floor'] ?? 0);
            if ($postId === '') {
                header('Content-Type: application/json');
                echo json_encode(['error' => '缺少帖子']);
                exit;
            }
            $key = $postId . '_' . $floor;
            $likes = loadLikes();
            if (!isset($likes[$key])) {
                $likes[$key] = [];
            }
            if (in_array($name, $likes[$key], true)) {
                header('Content-Type: application/json');
                echo json_encode(['ok' => true, 'liked' => true]);
                exit;
            }
            $likes[$key][] = $name;
            saveLikes($likes);
            $post = loadPost($postId);
            $postTitle = $post ? ($post['title'] ?? '') : '';
            $likeContentPost = $postTitle !== '' ? '赞了你的帖子《' . $postTitle . '》' . ($floor > 1 ? ' 第' . $floor . '楼' : '') : '';
            if ($post && $post['author']) {
                addEvent($post['author'], 'like', $postId, $floor, $name, $likeContentPost);
            }
            $floorAuthor = null;
            if ($post && $floor > 1 && isset($post['floors'])) {
                foreach ($post['floors'] as $f) {
                    if (($f['floor'] ?? 0) === $floor) {
                        $floorAuthor = $f['author'] ?? null;
                        break;
                    }
                }
            }
            if ($floorAuthor && $floorAuthor !== $post['author']) {
                $likeContentFloor = $postTitle !== '' ? '赞了你在《' . $postTitle . '》第' . $floor . '楼的回复' :
#1
发帖 2026-08-18 22:23:42 · 快照 2026-08-18 22:59:44

继续代码

 '';
                addEvent($floorAuthor, 'like', $postId, $floor, $name, $likeContentFloor);
            }
            header('Content-Type: application/json');
            echo json_encode(['ok' => true, 'liked' => true]);
            exit;
        }
        case 'forum_unlike': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效']);
                exit;
            }
            $postId = isset($_POST['post_id']) ? trim((string)$_POST['post_id']) : '';
            $floor = (int)($_POST['floor'] ?? 0);
            $key = $postId . '_' . $floor;
            $likes = loadLikes();
            if (isset($likes[$key])) {
                $likes[$key] = array_values(array_filter($likes[$key], function ($u) use ($name) { return $u !== $name; }));
                if (empty($likes[$key])) {
                    unset($likes[$key]);
                }
                saveLikes($likes);
            }
            $post = loadPost($postId);
            $floorAuthor = null;
            if ($post && $floor > 1 && isset($post['floors'])) {
                foreach ($post['floors'] as $f) {
                    if (($f['floor'] ?? 0) === $floor) {
                        $floorAuthor = $f['author'] ?? null;
                        break;
                    }
                }
            }
            if ($floorAuthor) {
                $postTitle = $post ? ($post['title'] ?? '') : '';
                $unlikeContent = $postTitle !== '' ? '取消赞了你在《' . $postTitle . '》第' . $floor . '楼的回复' : '';
                addEvent($floorAuthor, 'unlike', $postId, $floor, $name, $unlikeContent);
            }
            header('Content-Type: application/json');
            echo json_encode(['ok' => true, 'liked' => false]);
            exit;
        }
        case 'forum_mark_events_read': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Location: text.php?page=forum_events&credential=' . urlencode($cred));
                exit;
            }
            $events = loadEvents();
            if (isset($events[$name])) {
                foreach ($events[$name] as &$e) {
                    $e['read'] = true;
                }
                saveEvents($events);
            }
            header('Location: text.php?page=forum_events&credential=' . urlencode($cred));
            exit;
        }
        case 'forum_add_feed_manual': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Location: text.php?page=profile&credential=' . urlencode($cred));
                exit;
            }
            $text = isset($_POST['feed_text']) ? trim((string)$_POST['feed_text']) : '';
            if ($text === '') {
                header('Location: text.php?page=profile&credential=' . urlencode($cred) . '&feed_err=empty');
                exit;
            }
            appendUserFeed($name, 'manual', $text, null);
            $goto = isset($_POST['goto']) ? (string)$_POST['goto'] : 'profile';
            if ($goto === 'home') {
                header('Location: text.php?page=user&user=' . urlencode($name) . '&credential=' . urlencode($cred));
            } else {
                header('Location: text.php?page=profile&credential=' . urlencode($cred));
            }
            exit;
        }
        case 'random_users': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                if (!empty($_POST['ajax'])) {
                    header('Content-Type: application/json');
                    echo json_encode(['error' => '凭证无效']);
                    exit;
                }
                ?>凭证无效。<?php
                exit;
            }
            $users = loadUsers();
            unset($users[$name]);
            $names = array_keys($users);
            shuffle($names);
            $five = array_slice($names, 0, 5);
            $list = [];
            foreach ($five as $n) {
                $list[] = ['name' => $n, 'signature' => isset($users[$n]['signature']) ? $users[$n]['signature'] : ''];
            }
            if (!empty($_POST['ajax'])) {
                header('Content-Type: application/json');
                echo json_encode(['users' => $list]);
                exit;
            }
            ?>随机五个用户名(用户名 - 个性签名):<br>
            <?php foreach ($five as $n): ?>
                <?= q($n) ?><?php if (isset($users[$n]['signature']) && $users[$n]['signature'] !== ''): ?> - <?= q($users[$n]['signature']) ?><?php endif ?>
                <br>
            <?php endforeach ?>
            <br><a href="text.php?page=chat&credential=<?= q($cred) ?>">返回发消息</a> | <a href="text.php?page=profile&credential=<?= q($cred) ?>">用户中心</a><?php
            exit;
        }
        case 'get_chat_list': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效', 'chats' => []]);
                exit;
            }
            $users = loadUsers();
            ensureSingleDir();
            $chats = [];
            if (is_dir($SINGLE_DIR)) {
                foreach (scandir($SINGLE_DIR) as $f) {
                    if ($f === '.' || $f === '..' || substr($f, -5) !== '.json') continue;
                    $base = substr($f, 0, -5);
                    $parts = explode('_', $base, 2);
                    if (count($parts) === 2) {
                        $u1 = $parts[0];
                        $u2 = $parts[1];
                        if ($u1 === $name) {
                            $chats[] = ['other' => $u2, 'signature' => $users[$u2]['signature'] ?? '', 'last_other_msg_ts' => getLastOtherMessageTime($name, $u2)];
                        } elseif ($u2 === $name) {
                            $chats[] = ['other' => $u1, 'signature' => $users[$u1]['signature'] ?? '', 'last_other_msg_ts' => getLastOtherMessageTime($name, $u1)];
                        }
                    }
                }
            }
            header('Content-Type: application/json');
            echo json_encode(['chats' => $chats]);
            exit;
        }
        case 'get_chat_messages': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效', 'messages' => []]);
                exit;
            }
            $other = isset($_POST['chat']) ? trim((string)$_POST['chat']) : '';
            if ($other === '') {
                header('Content-Type: application/json');
                echo json_encode(['messages' => []]);
                exit;
            }
            $messages = loadChat($name, $other);
            header('Content-Type: application/json');
            echo json_encode(['messages' => $messages]);
            exit;
        }
        case 'forum_get_posts': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效', 'posts' => [], 'total' => 0]);
                exit;
            }
            global $FORUM_PAGE_SIZE;
            $page = max(1, (int)($_POST['page'] ?? $_GET['page'] ?? 1));
            $meta = loadForumPostsMeta();
            usort($meta, function ($a, $b) { return ($b['time'] ?? 0) - ($a['time'] ?? 0); });
            $total = count($meta);
            $chunk = array_slice($meta, ($page - 1) * $FORUM_PAGE_SIZE, $FORUM_PAGE_SIZE);
            $likes = loadLikes();
            $out = [];
            foreach ($chunk as $m) {
                $id = $m['id'] ?? '';
                $post = loadPost($id);
                $floorCount = 1 + (isset($post['floors']) ? count($post['floors']) : 0);
                $likeCount = 0;
                for ($f = 1; $f <= $floorCount; $f++) {
                    $key = $id . '_' . $f;
                    $likeCount += isset($likes[$key]) ? count($likes[$key]) : 0;
                }
                $lastTime = $m['time'] ?? 0;
                $lastAuthor = $m['author'] ?? '';
                if ($post && !empty($post['floors'])) {
                    $last = end($post['floors']);
                    $lastTime = $last['time'] ?? $lastTime;
                    $lastAuthor = $last['author'] ?? $lastAuthor;
                }
                $createTime = $post ? (isset($post['time']) ? (int)$post['time'] : 0) : 0;
                $out[] = ['id' => $id, 'title' => $m['title'] ?? '', 'author' => $m['author'] ?? '', 'time' => $m['time'] ?? 0, 'create_time' => $createTime, 'last_time' => $lastTime, 'last_author' => $lastAuthor, 'floor_count' => $floorCount, 'like_count' => $likeCount];
            }
            header('Content-Type: application/json');
            echo json_encode(['posts' => $out, 'total' => $total, 'page' => $page, 'page_size' => $FORUM_PAGE_SIZE]);
            exit;
        }
        case 'forum_get_post': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效']);
                exit;
            }
            $id = isset($_POST['id']) ? trim((string)$_POST['id']) : (isset($_GET['id']) ? trim((string)$_GET['id']) : '');
            $post = loadPost($id);
            if ($post === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '帖子不存在']);
                exit;
            }
#2
发帖 2026-08-18 22:24:51 · 快照 2026-08-18 22:59:44

继续代码

$likes = loadLikes();
            $floorLikes = [];
            for ($f = 1; $f <= 1 + count($post['floors'] ?? []); $f++) {
                $key = $id . '_' . $f;
                $floorLikes[$f] = isset($likes[$key]) ? $likes[$key] : [];
            }
            header('Content-Type: application/json');
            echo json_encode(['post' => $post, 'likes' => $floorLikes]);
            exit;
        }
        case 'forum_get_events': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效', 'events' => [], 'unread_count' => 0]);
                exit;
            }
            $events = loadEvents();
            $list = isset($events[$name]) ? $events[$name] : [];
            $unread = 0;
            foreach ($list as $e) {
                if (empty($e['read'])) $unread++;
            }
            header('Content-Type: application/json');
            echo json_encode(['events' => array_reverse($list), 'unread_count' => $unread]);
            exit;
        }
        case 'forum_get_feed': {
            $name = findUsernameByCredential($cred);
            if ($name === null) {
                header('Content-Type: application/json');
                echo json_encode(['error' => '凭证无效', 'feed' => []]);
                exit;
            }
            $target = isset($_POST['user']) ? trim((string)$_POST['user']) : (isset($_GET['user']) ? trim((string)$_GET['user']) : '');
            if ($target === '') {
                $target = $name;
            }
            $feed = loadUserFeed($target);
            header('Content-Type: application/json');
            echo json_encode(['feed' => $feed]);
            exit;
        }
        default:
            ?>未知 api<?php
            exit;
    }
}

// ---------- 页面 ----------
$page = isset($_GET['page']) ? (string)$_GET['page'] : 'home';
$cred = currentCredential();

switch ($page) {
    case 'register':
        ?><h1>注册</h1>
        <form method="post" action="text.php<?= $cred !== '' ? '?credential=' . urlencode($cred) : '' ?>">
            <input type="hidden" name="api" value="register">
            用户名:<input type="text" name="username" required>
            <button type="submit">注册</button>
        </form>
        <a href="text.php<?= $cred !== '' ? '?credential=' . urlencode($cred) : '' ?>">返回首页</a><?php
        break;

    case 'login':
        ?><h1>登录</h1>
        <?php if (isset($_GET['err'])): ?>
            <?php if ($_GET['err'] === 'invalid'): ?>凭证无效。<br><?php endif ?>
            <?php if ($_GET['err'] === 'empty'): ?>请输入凭证。<br><?php endif ?>
        <?php endif ?>
        <form method="post" action="text.php<?= $cred !== '' ? '?credential=' . urlencode($cred) : '' ?>">
            <input type="hidden" name="api" value="login">
            登录凭证:<input type="text" name="credential" required>
            <button type="submit">登录</button>
        </form>
        <a href="text.php<?= $cred !== '' ? '?credential=' . urlencode($cred) : '' ?>">返回首页</a><?php
        break;

    case 'chat': {
        if ($cred === '') {
            ?>请先登录。 <a href="text.php?page=login<?= $cred !== '' ? '&credential=' . urlencode($cred) : '' ?>">登录</a>&nbsp;&nbsp;<a href="text.php?page=register<?= $cred !== '' ? '&credential=' . urlencode($cred) : '' ?>">注册</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效,请重试登录或重新注册获得新凭证。 <a href="text.php?page=login<?= $cred !== '' ? '&credential=' . urlencode($cred) : '' ?>">登录</a> &nbsp;&nbsp;<a href="text.php?page=register<?= $cred !== '' ? '&credential=' . urlencode($cred) : '' ?>">注册</a><?php
            break;
        }
        $users = loadUsers();
        ensureSingleDir();
        $chats = [];
        if (is_dir($SINGLE_DIR)) {
            foreach (scandir($SINGLE_DIR) as $f) {
                if ($f === '.' || $f === '..' || substr($f, -5) !== '.json') continue;
                $base = substr($f, 0, -5);
                $parts = explode('_', $base, 2);
                if (count($parts) === 2) {
                    $u1 = $parts[0];
                    $u2 = $parts[1];
                    if ($u1 === $name) {
                        $chats[$u2] = ['signature' => $users[$u2]['signature'] ?? '', 'last_other_msg_ts' => getLastOtherMessageTime($name, $u2)];
                    } elseif ($u2 === $name) {
                        $chats[$u1] = ['signature' => $users[$u1]['signature'] ?? '', 'last_other_msg_ts' => getLastOtherMessageTime($name, $u1)];
                    }
                }
            }
        }
        $selected = isset($_GET['chat']) ? trim((string)$_GET['chat']) : '';
        if ($selected !== '' && !isset($chats[$selected])) {
            $selected = '';
        }
        $autoscroll = !isset($_GET['autoscroll']) || $_GET['autoscroll'] === '1';
        ?>
        <h1>发消息</h1>
        当前用户:<?= q($name) ?>
        <a href="text.php?page=profile&credential=<?= q($cred) ?>">用户中心</a>
        <a href="text.php<?= $cred !== '' ? '?credential=' . urlencode($cred) : '' ?>">首页</a><br><br>

        <form method="post" action="text.php">
            <input type="hidden" name="api" value="start_chat">
            <input type="hidden" name="credential" value="<?= q($cred) ?>">
            开启新私聊(输入已存在的用户名):<input type="text" name="other_username" required>
            <button type="submit">开启</button>
        </form>
        <?php if (isset($_GET['err'])): ?>
            <?php if ($_GET['err'] === 'user_not_found'): ?>该用户名不存在。<br><?php endif ?>
            <?php if ($_GET['err'] === 'self'): ?>不能和自己私聊。<br><?php endif ?>
            <?php if ($_GET['err'] === 'no_user'): ?>请输入用户名。<br><?php endif ?>
        <?php endif ?>

        <br>随机获取五个用户名:<button type="button" id="btn-random-five">随机五人</button><br/>
        <span id="random-five-result"></span><br><br>

        私聊列表(点击查看):<br>
        <div id="chat-list-wrap">
            <?php foreach ($chats as $other => $info):
                $sig = is_array($info) ? ($info['signature'] ?? '') : '';
                $lastTs = is_array($info) ? ($info['last_other_msg_ts'] ?? null) : null;
                $minsText = minutesAgoText($lastTs); ?>
                <div>
                    <form method="get" action="text.php" style="display:inline">
                        <input type="hidden" name="page" value="chat">
                        <input type="hidden" name="credential" value="<?= q($cred) ?>">
                        <input type="hidden" name="chat" value="<?= q($other) ?>">
                        <?php if ($autoscroll): ?><input type="hidden" name="autoscroll" value="1"><?php endif ?>
                        <button type="submit"><?= q($other) ?><?= $sig !== '' ? ' - ' . q($sig) : '' ?></button>
                    </form>
                    <?php if ($minsText !== ''): ?> <?= q($minsText) ?><?php endif ?>
                </div>
            <?php endforeach ?>
            <?php if (empty($chats)): ?>(暂无私聊)<?php endif ?>
        </div><br><br>

        <?php if ($selected !== ''): ?>
            与 <a href="text.php?page=user&user=<?= q($selected) ?>&credential=<?= q($cred) ?>"><?= q($selected) ?></a> 的私聊(自动更新,不刷页)<br>
            <div id="chatdiv" style="height:500px;overflow:auto">加载中…</div>
            <form id="send-form">
                <input type="hidden" name="api" value="send_message">
                <input type="hidden" name="credential" value="<?= q($cred) ?>">
                <input type="hidden" name="chat" value="<?= q($selected) ?>">
                收到消息自动滚动到底:<input type="checkbox" id="autoscroll-cb"<?= $autoscroll ? ' checked' : '' ?>><br>
                <textarea name="text" id="msg-input" rows="3" cols="40" required></textarea> <button type="submit">发送</button>
            </form>
        <?php else: ?>
            请从上方私聊列表选择或开启新私聊。
        <?php endif ?>

        <script>
        (function(){
            var cred = <?= json_encode($cred) ?>;
            var selectedChat = <?= json_encode($selected) ?>;
            var autoscroll = <?= json_encode($autoscroll) ?>;

            function esc(s){ var d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
            function postApi(api, data, cb) {
                var fd = new FormData();
                fd.append('api', api);
                fd.append('credential', cred);
                for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]);
                var x = new XMLHttpRequest();
                x.open('POST', 'text.php');
                x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } };
                x.send(fd);
            }

            function minsAgoText(ts) {
                if (ts == null || ts <= 0) return '';
                var mins = Math.max(0, Math.floor((Date.now()/1000 - ts) / 60));
                return mins === 0 ? '刚刚有对方消息' : mins + '分钟前有对方消息';
            }
            function formatMsgTime(ts) {
                if (ts == null || ts <= 0) return '未知时间';
                return new Date(ts * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g, '-');
            }
            function refreshChatList() {
                postApi('get_chat_list', {}, function(r){
                    if (!r || r.error) return;
                    var useAutoscroll = (document.getElementById('autoscroll-cb') && document.getElementById('autoscroll-cb').checked) || autoscroll;
                    var html = '';
                    for (var i = 0; i < r.chats.length; i++) {
                        var
#3
发帖 2026-08-18 22:25:23 · 快照 2026-08-18 22:59:44

继续代码

 o = r.chats[i];
                        var line = '<form method="get" action="text.php" style="display:inline"><input type="hidden" name="page" value="chat"><input type="hidden" name="credential" value="'+esc(cred)+'"><input type="hidden" name="chat" value="'+esc(o.other)+'">' + (useAutoscroll ? '<input type="hidden" name="autoscroll" value="1">' : '') + '<button type="submit">'+esc(o.other)+(o.signature ? ' - '+esc(o.signature) : '')+'</button></form>';
                        var mt = minsAgoText(o.last_other_msg_ts);
                        if (mt) line += ' ' + esc(mt);
                        html += '<div>' + line + '</div>';
                    }
                    if (!r.chats.length) html = '(暂无私聊)';
                    var wrap = document.getElementById('chat-list-wrap');
                    if (wrap) wrap.innerHTML = html;
                });
            }

            var lastMessagesSig = '';
            function refreshMessages(forceScroll) {
                if (!selectedChat) return;
                postApi('get_chat_messages', { chat: selectedChat }, function(r){
                    if (!r || r.error) return;
                    var div = document.getElementById('chatdiv');
                    if (!div) return;
                    var list = r.messages || [];
                    var sig = list.length + '_' + (list.length ? (list[list.length - 1].time || '') : '');
                    var changed = sig !== lastMessagesSig;
                    lastMessagesSig = sig;
                    var html = '';
                    for (var i = 0; i < list.length; i++) {
                        var m = list[i];
                        html += '[' + esc(formatMsgTime(m.time)) + '] ' + esc(m.from || '') + ' -> ' + esc(m.to || '') + ':' + (esc(m.text || '').replace(/\n/g, '<br>')) + '<br>';
                    }
                    div.innerHTML = html;
                    if (forceScroll || (changed && document.getElementById('autoscroll-cb') && document.getElementById('autoscroll-cb').checked)) div.scrollTop = div.scrollHeight;
                });
            }

            setInterval(refreshChatList, 3000);
            if (selectedChat) {
                refreshMessages(true);
                setInterval(refreshMessages, 3000);
            }

            document.getElementById('btn-random-five').onclick = function() {
                var fd = new FormData();
                fd.append('api', 'random_users');
                fd.append('credential', cred);
                fd.append('ajax', '1');
                var x = new XMLHttpRequest();
                x.open('POST', 'text.php');
                x.onload = function() {
                    var el = document.getElementById('random-five-result');
                    try {
                        var r = JSON.parse(x.responseText);
                        if (r.error) { el.innerHTML = ' ' + esc(r.error); return; }
                        var list = r.users || [];
                        if (list.length === 0) { el.innerHTML = ' 一个其它用户都没有'; return; }
                        var html = ' ';
                        for (var i = 0; i < list.length; i++) {
                            var u = list[i];
                            html += '<a href="text.php?page=user&user='+encodeURIComponent(u.name)+'&credential='+esc(cred)+'">'+esc(u.name)+'</a>' + (u.signature ? ' - ' + esc(u.signature) : '') + '<br>';
                        }
                        el.innerHTML = html;
                    } catch(e) { el.innerHTML = ' 请求失败'; }
                };
                x.send(fd);
            };

            var f = document.getElementById('send-form');
            if (f) {
                f.onsubmit = function(e) {
                    e.preventDefault();
                    var input = document.getElementById('msg-input');
                    var text = input && input.value ? input.value.trim() : '';
                    if (!text) return;
                    var fd = new FormData(f);
                    fd.append('ajax', '1');
                    fd.append('text', text);
                    var x = new XMLHttpRequest();
                    x.open('POST', 'text.php');
                    x.onload = function() {
                        try {
                            var r = JSON.parse(x.responseText);
                            if (r.ok) { input.value = ''; refreshMessages(true); }
                            else if (r.error) alert(r.error);
                        } catch(z) {}
                    };
                    x.send(fd);
                };
            }
        })();
        </script>
        <?php
        break;
    }

    case 'profile': {
        if ($cred === '') {
            ?>请先登录。 <a href="text.php?page=login<?= $cred !== '' ? '&credential=' . urlencode($cred) : '' ?>">登录</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效,请重试登录或重新注册。 <a href="text.php?page=login<?= $cred !== '' ? '&credential=' . urlencode($cred) : '' ?>">登录</a><?php
            break;
        }
        $users = loadUsers();
        $sig = isset($users[$name]['signature']) ? $users[$name]['signature'] : '';
        ?>
        <h1>用户中心</h1>
        当前用户:<?= q($name) ?>
        <a href="text.php?page=chat&credential=<?= q($cred) ?>">发消息</a>
        <a href="text.php?page=forum&credential=<?= q($cred) ?>">论坛</a>
        <a href="text.php?page=user&user=<?= q($name) ?>&credential=<?= q($cred) ?>">自己的主页</a>
        <a href="text.php<?= $cred !== '' ? '?credential=' . urlencode($cred) : '' ?>">首页</a>
        <a href="text.php">退出登录</a><br><br>

        <form method="post" action="text.php">
            <input type="hidden" name="api" value="set_signature">
            <input type="hidden" name="credential" value="<?= q($cred) ?>">
            个性签名:<br><textarea name="signature" rows="4" cols="40"><?= q($sig) ?></textarea><br>
            <button type="submit">保存</button>
        </form><br>

        手动发用户动态(纯文本):<br>
        <form method="post" action="text.php">
            <input type="hidden" name="api" value="forum_add_feed_manual">
            <input type="hidden" name="credential" value="<?= q($cred) ?>">
            <textarea name="feed_text" rows="3" cols="40" required></textarea><br>
            <button type="submit" name="goto" value="profile">发送并返回用户中心</button>
            <button type="submit" name="goto" value="home">发送并前往自己的主页</button>
        </form>
        <?php if (isset($_GET['feed_err']) && $_GET['feed_err'] === 'empty'): ?>动态内容不能为空。<br><?php endif ?><br>

        随机获取五个用户名(用户名 - 个性签名):<button type="button" id="profile-random-five">随机五人</button><br/>
        <span id="profile-random-result"></span>
        <script>
        (function(){
            var cred = <?= json_encode($cred) ?>;
            function esc(s){ var d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
            document.getElementById('profile-random-five').onclick = function() {
                var fd = new FormData();
                fd.append('api', 'random_users');
                fd.append('credential', cred);
                fd.append('ajax', '1');
                var x = new XMLHttpRequest();
                x.open('POST', 'text.php');
                x.onload = function() {
                    var el = document.getElementById('profile-random-result');
                    try {
                        var r = JSON.parse(x.responseText);
                        if (r.error) { el.innerHTML = ' ' + esc(r.error); return; }
                        var list = r.users || [];
                        if (list.length === 0) { el.innerHTML = ' 一个其它用户都没有'; return; }
                        var html = ' ';
                        for (var i = 0; i < list.length; i++) {
                            var u = list[i];
                            html += '<a href="text.php?page=user&user='+encodeURIComponent(u.name)+'&credential='+esc(cred)+'">'+esc(u.name)+'</a>' + (u.signature ? ' - ' + esc(u.signature) : '') + '<br>';
                        }
                        el.innerHTML = html;
                    } catch(e) { el.innerHTML = ' 请求失败'; }
                };
                x.send(fd);
            };
        })();
        </script>
        <?php
        break;
    }

    case 'forum':
    case 'forum_list': {
        if ($cred === '') {
            ?>请先登录。 <a href="text.php?page=login">登录</a> <a href="text.php?page=register">注册</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效。 <a href="text.php?page=login&credential=<?= q($cred) ?>">登录</a><?php
            break;
        }
        $events = loadEvents();
        $unreadCount = 0;
        if (isset($events[$name])) {
            foreach ($events[$name] as $e) {
                if (empty($e['read'])) $unreadCount++;
            }
        }
        $curPage = max(1, (int)($_GET['p'] ?? 1));
        $meta = loadForumPostsMeta();
        usort($meta, function ($a, $b) { return ($b['time'] ?? 0) - ($a['time'] ?? 0); });
        $total = count($meta);
        global $FORUM_PAGE_SIZE;
        $chunk = array_slice($meta, ($curPage - 1) * $FORUM_PAGE_SIZE, $FORUM_PAGE_SIZE);
        $likes = loadLikes();
        ?>
        <h1>论坛 · 看帖</h1>
        当前用户:<?= q($name) ?>
        <a href="text.php?page=forum&credential=<?= q($cred) ?>" id="nav-link-kantie">看帖(加载中…)</a>
        <a href="text.php?page=forum_post&credential=<?= q($cred) ?>">发帖</a>
        <a href="text.php?page=forum_events&credential=<?= q($cred) ?>" id="nav-link-events">事件(<?= $unreadCount > 0 ? (int)$unreadCount . ' 未查收' : '无' ?>)</a>
        <a href="text.php?page=profile&credential=<?= q($cred) ?>"
#4
发帖 2026-08-18 22:25:51 · 快照 2026-08-18 22:59:44

继续代码

>用户中心</a>
        <a href="text.php?credential=<?= q($cred) ?>">首页</a><br><br>
        <div id="forum-list-wrap">加载中…</div>
        <div id="forum-pagination"></div>
        <script>
        (function(){
            var cred = <?= json_encode($cred) ?>;
            var curPage = <?= (int)$curPage ?>;
            var totalPages = Math.max(1, Math.ceil(<?= (int)$total ?> / <?= (int)$FORUM_PAGE_SIZE ?>));
            function esc(s){ var d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
            function postApi(api, data, cb) {
                var fd = new FormData();
                fd.append('api', api);
                fd.append('credential', cred);
                for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]);
                var x = new XMLHttpRequest();
                x.open('POST', 'text.php');
                x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } };
                x.send(fd);
            }
            function minsAgoText(ts) {
                if (!ts || ts <= 0) return '';
                var mins = Math.max(0, Math.floor((Date.now()/1000 - ts) / 60));
                return mins === 0 ? '刚刚' : mins + '分钟前';
            }
            function formatMsgTime(ts) {
                if (ts == null || ts <= 0) return '';
                return new Date(ts * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g, '-');
            }
            function updateKanTieLink(firstPost) {
                var linkEl = document.getElementById('nav-link-kantie');
                if (!linkEl) return;
                if (firstPost && (firstPost.last_author || firstPost.author)) {
                    var who = (firstPost.last_author != null && firstPost.last_author !== '') ? firstPost.last_author : (firstPost.author || '');
                    var sec = firstPost.last_time ? (Date.now()/1000 - firstPost.last_time) : 0;
                    var minsStr = sec < 60 ? '刚刚' : (Math.floor(sec/60) + '分钟前');
                    var title = firstPost.title || '';
                    var postAuthor = firstPost.author || '';
                    linkEl.textContent = '看帖(' + minsStr + ' 有来自 ' + who + ' 的新回复 - 《' + title + '》发帖者 ' + postAuthor + ')';
                } else {
                    linkEl.textContent = '看帖(没有帖子,请发布)';
                }
            }
            function renderList(r) {
                if (!r || r.error) return;
                var list = r.posts || [];
                var html = '';
                for (var i = 0; i < list.length; i++) {
                    var p = list[i];
                    var timeStr = formatMsgTime(p.create_time);
                    var lastPart = '';
                    if (p.last_author != null && p.last_author !== '') {
                        var mt = minsAgoText(p.last_time);
                        if (mt) lastPart = ' · ' + esc(mt) + ' 有来自 ' + esc(p.last_author) + ' 的回复';
                    }
                    html += '<div><a href="text.php?page=forum_post_view&id=' + esc(p.id) + '&credential=' + esc(cred) + '">' + esc(p.title) + '</a> 总赞 ' + (p.like_count|0) + ' · 共 ' + (p.floor_count|0) + ' 楼 · 发帖者 ' + esc(p.author) + (timeStr ? ' · 发帖时间 ' + esc(timeStr) : '') + lastPart + '</div>';
                }
                var wrap = document.getElementById('forum-list-wrap');
                if (wrap) wrap.innerHTML = html || '没有帖子,请发布';
            }
            function renderPagination() {
                var pag = document.getElementById('forum-pagination');
                if (!pag || totalPages <= 1) return;
                var html = '分页:';
                for (var i = 1; i <= totalPages; i++) {
                    if (i === curPage) html += ' ' + i + ' ';
                    else html += ' <a href="text.php?page=forum&p=' + i + '&credential=' + encodeURIComponent(cred) + '">' + i + '</a> ';
                }
                pag.innerHTML = html;
            }
            function refresh() {
                postApi('forum_get_posts', { page: 1 }, function(r1){
                    if (r1 && r1.posts && r1.posts.length > 0) updateKanTieLink(r1.posts[0]);
                    else updateKanTieLink(null);
                });
                postApi('forum_get_posts', { page: curPage }, function(r){
                    renderList(r);
                    postApi('forum_get_events', {}, function(er){
                        if (!er || er.error) return;
                        var unread = er.unread_count || 0;
                        var el = document.getElementById('nav-link-events');
                        if (el) el.textContent = unread > 0 ? '事件(' + unread + ' 未查收)' : '事件(无)';
                    });
                });
            }
            renderPagination();
            refresh();
            setInterval(refresh, 5000);
        })();
        </script>
        <?php
        break;
    }

    case 'forum_post': {
        if ($cred === '') {
            ?>请先登录。 <a href="text.php?page=login">登录</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效。 <a href="text.php?page=login&credential=<?= q($cred) ?>">登录</a><?php
            break;
        }
        $events = loadEvents();
        $unreadCount = 0;
        if (isset($events[$name])) { foreach ($events[$name] as $e) { if (empty($e['read'])) $unreadCount++; } }
        ?>
        <h1>论坛 · 发帖</h1>
        <a href="text.php?page=forum&credential=<?= q($cred) ?>" id="nav-link-kantie">看帖(加载中…)</a>
        <a href="text.php?page=forum_post&credential=<?= q($cred) ?>">发帖</a>
        <a href="text.php?page=forum_events&credential=<?= q($cred) ?>" id="nav-link-events">事件(<?= $unreadCount > 0 ? (int)$unreadCount . ' 未查收' : '无' ?>)</a>
        <a href="text.php?page=profile&credential=<?= q($cred) ?>">用户中心</a><br><br>
        <script>
        (function(){ var cred = <?= json_encode($cred) ?>;
            function postApi(api, data, cb) { var fd = new FormData(); fd.append('api', api); fd.append('credential', cred); for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]); var x = new XMLHttpRequest(); x.open('POST', 'text.php'); x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } }; x.send(fd); }
            function updateKanTie(firstPost) {
                var el = document.getElementById('nav-link-kantie'); if (!el) return;
                if (firstPost && (firstPost.last_author || firstPost.author)) {
                    var who = firstPost.last_author || firstPost.author || '';
                    var sec = firstPost.last_time ? (Date.now()/1000 - firstPost.last_time) : 0;
                    var minsStr = sec < 60 ? '刚刚' : (Math.floor(sec/60) + '分钟前');
                    el.textContent = '看帖(' + minsStr + ' 有来自 ' + who + ' 的新回复 - 《' + (firstPost.title||'') + '》发帖者 ' + (firstPost.author||'') + ')';
                } else { el.textContent = '看帖(没有帖子,请发布)'; }
            }
            function refreshNav() {
                postApi('forum_get_posts', { page: 1 }, function(r){ if (r && r.posts && r.posts.length) updateKanTie(r.posts[0]); else updateKanTie(null); });
                postApi('forum_get_events', {}, function(er){ if (er && !er.error && document.getElementById('nav-link-events')) document.getElementById('nav-link-events').textContent = (er.unread_count|0) > 0 ? '事件(' + er.unread_count + ' 未查收)' : '事件(无)'; });
            }
            refreshNav();
            setInterval(refreshNav, 5000);
        })();
        </script>
        <?php if (isset($_GET['err']) && $_GET['err'] === 'empty'): ?>标题或内容不能为空。<br><?php endif ?>
        <form method="post" action="text.php">
            <input type="hidden" name="api" value="forum_create_post">
            <input type="hidden" name="credential" value="<?= q($cred) ?>">
            标题:<input type="text" name="title" required size="40"><br>
            内容:<br><textarea name="content" rows="8" cols="50" required></textarea><br>
            <button type="submit" name="goto" value="list">发送并返回论坛首页</button>
            <button type="submit" name="goto" value="post">发送并进入此贴</button>
        </form>
        <?php
        break;
    }

    case 'forum_post_view': {
        if ($cred === '') {
            ?>请先登录。 <a href="text.php?page=login">登录</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效。 <a href="text.php?page=login&credential=<?= q($cred) ?>">登录</a><?php
            break;
        }
        $postId = isset($_GET['id']) ? trim((string)$_GET['id']) : '';
        if ($postId === '') {
            ?>请指定帖子。 <a href="text.php?page=forum&credential=<?= q($cred) ?>">返回看帖</a><?php
            break;
        }
        $events = loadEvents();
        $unreadCount = 0;
        if (isset($events[$name])) { foreach ($events[$name] as $e) { if (empty($e['read'])) $unreadCount++; } }
        ?>
        <h1>帖子</h1>
        <a href="text.php?page=forum&credential=<?= q($cred) ?>" id="nav-link-kantie">看帖(加载中…)</a>
        <a href="text.php?page=forum_post&credential=<?= q($cred) ?>">发帖</a>
        <a href="text.php?page=forum_events&credential=<?= q($cred) ?>" id="nav-link-events">事件(<?= $unreadCount > 0 ? (int)$unreadCount . ' 未查收' : '无' ?>)</a>
        <a href="text.php?page=profile&credential=<?= q($cred) ?>">用户中心</a><br><br>
        <script>
        (function(){ var cred = <?= json_encode($cred) ?>;
            function postApi(api, data, cb) { var fd = new FormData(); fd.append('api', api); fd.append('credential', cred); for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]); va
#5
发帖 2026-08-18 22:26:26 · 快照 2026-08-18 22:59:44

你发个github项目多好 不要贴代码

#6
发帖 2026-08-18 22:26:35 · 快照 2026-08-18 22:59:44

继续代码

r x = new XMLHttpRequest(); x.open('POST', 'text.php'); x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } }; x.send(fd); }
            function updateKanTie(firstPost) {
                var el = document.getElementById('nav-link-kantie'); if (!el) return;
                if (firstPost && (firstPost.last_author || firstPost.author)) {
                    var who = firstPost.last_author || firstPost.author || '';
                    var sec = firstPost.last_time ? (Date.now()/1000 - firstPost.last_time) : 0;
                    var minsStr = sec < 60 ? '刚刚' : (Math.floor(sec/60) + '分钟前');
                    el.textContent = '看帖(' + minsStr + ' 有来自 ' + who + ' 的新回复 - 《' + (firstPost.title||'') + '》发帖者 ' + (firstPost.author||'') + ')';
                } else { el.textContent = '看帖(没有帖子,请发布)'; }
            }
            function refreshNav() {
                postApi('forum_get_posts', { page: 1 }, function(r){ if (r && r.posts && r.posts.length) updateKanTie(r.posts[0]); else updateKanTie(null); });
                postApi('forum_get_events', {}, function(er){ if (er && !er.error && document.getElementById('nav-link-events')) document.getElementById('nav-link-events').textContent = (er.unread_count|0) > 0 ? '事件(' + er.unread_count + ' 未查收)' : '事件(无)'; });
            }
            refreshNav();
            setInterval(refreshNav, 5000);
        })();
        </script>
        <?php if (isset($_GET['err']) && $_GET['err'] === 'empty'): ?>标题或内容不能为空。<br><?php endif ?>
        <form method="post" action="text.php">
            <input type="hidden" name="api" value="forum_create_post">
            <input type="hidden" name="credential" value="<?= q($cred) ?>">
            标题:<input type="text" name="title" required size="40"><br>
            内容:<br><textarea name="content" rows="8" cols="50" required></textarea><br>
            <button type="submit" name="goto" value="list">发送并返回论坛首页</button>
            <button type="submit" name="goto" value="post">发送并进入此贴</button>
        </form>
        <?php
        break;
    }

    case 'forum_post_view': {
        if ($cred === '') {
            ?>请先登录。 <a href="text.php?page=login">登录</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效。 <a href="text.php?page=login&credential=<?= q($cred) ?>">登录</a><?php
            break;
        }
        $postId = isset($_GET['id']) ? trim((string)$_GET['id']) : '';
        if ($postId === '') {
            ?>请指定帖子。 <a href="text.php?page=forum&credential=<?= q($cred) ?>">返回看帖</a><?php
            break;
        }
        $events = loadEvents();
        $unreadCount = 0;
        if (isset($events[$name])) { foreach ($events[$name] as $e) { if (empty($e['read'])) $unreadCount++; } }
        ?>
        <h1>帖子</h1>
        <a href="text.php?page=forum&credential=<?= q($cred) ?>" id="nav-link-kantie">看帖(加载中…)</a>
        <a href="text.php?page=forum_post&credential=<?= q($cred) ?>">发帖</a>
        <a href="text.php?page=forum_events&credential=<?= q($cred) ?>" id="nav-link-events">事件(<?= $unreadCount > 0 ? (int)$unreadCount . ' 未查收' : '无' ?>)</a>
        <a href="text.php?page=profile&credential=<?= q($cred) ?>">用户中心</a><br><br>
        <script>
        (function(){ var cred = <?= json_encode($cred) ?>;
            function postApi(api, data, cb) { var fd = new FormData(); fd.append('api', api); fd.append('credential', cred); for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]); var x = new XMLHttpRequest(); x.open('POST', 'text.php'); x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } }; x.send(fd); }
            function updateKanTie(firstPost) {
                var el = document.getElementById('nav-link-kantie'); if (!el) return;
                if (firstPost && (firstPost.last_author || firstPost.author)) {
                    var who = firstPost.last_author || firstPost.author || '';
                    var sec = firstPost.last_time ? (Date.now()/1000 - firstPost.last_time) : 0;
                    var minsStr = sec < 60 ? '刚刚' : (Math.floor(sec/60) + '分钟前');
                    el.textContent = '看帖(' + minsStr + ' 有来自 ' + who + ' 的新回复 - 《' + (firstPost.title||'') + '》发帖者 ' + (firstPost.author||'') + ')';
                } else { el.textContent = '看帖(没有帖子,请发布)'; }
            }
            function refreshNav() {
                postApi('forum_get_posts', { page: 1 }, function(r){ if (r && r.posts && r.posts.length) updateKanTie(r.posts[0]); else updateKanTie(null); });
                postApi('forum_get_events', {}, function(er){ if (er && !er.error && document.getElementById('nav-link-events')) document.getElementById('nav-link-events').textContent = (er.unread_count|0) > 0 ? '事件(' + er.unread_count + ' 未查收)' : '事件(无)'; });
            }
            refreshNav();
            setInterval(refreshNav, 5000);
        })();
        </script>
        <div id="post-container">加载中…</div>
        <div id="post-reply-wrap" style="display:none">
            引用:<span id="quote-info"></span> <button type="button" id="btn-clear-quote">解除引用</button><br>
            <textarea id="floor-content" rows="4" cols="50" placeholder="回复内容"></textarea>
            <button type="button" id="btn-submit-floor">发送回复</button>
        </div>
        <script>
        (function(){
            var cred = <?= json_encode($cred) ?>;
            var currentUser = <?= json_encode($name) ?>;
            var postId = <?= json_encode($postId) ?>;
            function esc(s){ if (s==null) return ''; var d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
            function postApi(api, data, cb) {
                var fd = new FormData();
                fd.append('api', api);
                fd.append('credential', cred);
                for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]);
                var x = new XMLHttpRequest();
                x.open('POST', 'text.php');
                x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } };
                x.send(fd);
            }
            var quoteFloor = 0, quoteUser = '';
            function userLink(u) { return '<a href="text.php?page=user&user='+encodeURIComponent(u)+'&credential='+esc(cred)+'">'+esc(u)+'</a>'; }
            function contentHtml(t) { return (esc(t||'')).replace(/\n/g,'<br>'); }
            function formatMsgTime(ts) {
                if (ts == null || ts <= 0) return '';
                return new Date(ts * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g, '-');
            }
            function renderPost(r) {
                if (!r || r.error || !r.post) return '';
                var p = r.post;
                var likes = r.likes || {};
                var html = '<div><strong>' + esc(p.title) + '</strong> 发帖者 ' + userLink(p.author || '') + '</div><br>';
                var like1 = (likes['1'] || likes[1] || []);
                var liked1 = like1.indexOf(currentUser) !== -1;
                var time1 = formatMsgTime(p.time);
                html += '<div id="floor-1"><strong>1楼</strong> ' + userLink(p.author || '') + (time1 ? ' [' + esc(time1) + '] ' : ' ') + contentHtml(p.content) + '<br>';
                html += '<button type="button" class="btn-like" data-floor="1" data-liked="'+(liked1?'1':'0')+'">' + (liked1 ? '取消赞' : '赞') + ' (' + like1.length + ')</button> ';
                html += '<button type="button" class="btn-quote" data-floor="1" data-user="'+esc(p.author||'')+'">引用</button></div><br>';
                var floors = p.floors || [];
                for (var i = 0; i < floors.length; i++) {
                    var f = floors[i];
                    var fn = f.floor || (i+2);
                    var likeList = likes[fn] || likes[String(fn)] || [];
                    var liked = likeList.indexOf(currentUser) !== -1;
                    var timeF = formatMsgTime(f.time);
                    html += '<div id="floor-'+fn+'"><strong>'+fn+'楼</strong> ';
                    if (f.quoted_floor && f.quoted_user) html += '回复' + f.quoted_floor + '楼(' + esc(f.quoted_user) + ') ';
                    html += userLink(f.author||'') + (timeF ? ' [' + esc(timeF) + '] ' : ' ') + contentHtml(f.content) + '<br>';
                    html += '<button type="button" class="btn-like" data-floor="'+fn+'" data-liked="'+(liked?'1':'0')+'">' + (liked ? '取消赞' : '赞') + ' (' + likeList.length + ')</button> ';
                    html += '<button type="button" class="btn-quote" data-floor="'+fn+'" data-user="'+esc(f.author||'')+'">引用</button></div><br>';
                }
                return html;
            }
            function refreshPost(forceScroll) {
                if (!postId) return;
                postApi('forum_get_post', { id: postId }, function(r){
                    var div = document.getElementById('post-container');
                    if (!div) return;
                    if (!r || r.error) { div.innerHTML = r && r.error ? esc(r.error) : '加载失败'; return; }
                    div.innerHTML = renderPost(r);
                    document.getElementById('post-reply-wrap').style.display = 'block';
                    [].forEach.call(document.querySelectorAll('.btn-like'), function(btn){
                        btn.onclick = function(){
                            var floor = parseInt(btn.getAttribute('data-floor'),10);
                            var api = btn.getAttribute('data-liked') === '1' ? 'forum_unlike' : 'forum_like';
                            var fd = new FormData();
                            fd.append('api', api);
                            fd.append('credential', cre
#7
发帖 2026-08-18 22:26:50 · 快照 2026-08-18 22:59:44

😍

#8
发帖 2026-08-18 22:27:11 · 快照 2026-08-18 22:59:44

继续代码

d);
                            fd.append('post_id', postId);
                            fd.append('floor', floor);
                            var x = new XMLHttpRequest();
                            x.open('POST', 'text.php');
                            x.onload = function(){
                                try { var res = JSON.parse(x.responseText); if (res.ok) refreshPost(true); } catch(e){}
                            };
                            x.send(fd);
                        };
                    });
                    [].forEach.call(document.querySelectorAll('.btn-quote'), function(btn){
                        btn.onclick = function(){ quoteFloor = parseInt(btn.getAttribute('data-floor'),10); quoteUser = btn.getAttribute('data-user')||''; document.getElementById('quote-info').textContent = '回复'+quoteFloor+'楼('+quoteUser+')'; };
                    });
                });
            }
            document.getElementById('btn-clear-quote').onclick = function(){ quoteFloor = 0; quoteUser = ''; document.getElementById('quote-info').textContent = ''; };
            document.getElementById('btn-submit-floor').onclick = function(){
                var content = document.getElementById('floor-content').value.trim();
                if (!content) return;
                var fd = new FormData();
                fd.append('api', 'forum_add_floor');
                fd.append('credential', cred);
                fd.append('post_id', postId);
                fd.append('content', content);
                fd.append('ajax', '1');
                if (quoteFloor) { fd.append('quoted_floor', quoteFloor); fd.append('quoted_user', quoteUser); }
                var x = new XMLHttpRequest();
                x.open('POST', 'text.php');
                x.onload = function(){
                    try {
                        var r = JSON.parse(x.responseText);
                        if (r.ok) { document.getElementById('floor-content').value = ''; quoteFloor = 0; quoteUser = ''; document.getElementById('quote-info').textContent = ''; refreshPost(true); }
                        else if (r.error) alert(r.error);
                    } catch(e){}
                };
                x.send(fd);
            };
            refreshPost(true);
            setInterval(function(){ refreshPost(false); }, 4000);
        })();
        </script>
        <?php
        break;
    }

    case 'forum_events': {
        if ($cred === '') {
            ?>请先登录。 <a href="text.php?page=login">登录</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效。 <a href="text.php?page=login&credential=<?= q($cred) ?>">登录</a><?php
            break;
        }
        $events = loadEvents();
        $list = isset($events[$name]) ? array_reverse($events[$name]) : [];
        $unreadCount = 0;
        foreach ($list as $e) { if (empty($e['read'])) $unreadCount++; }
        ?>
        <h1>论坛 · 事件</h1>
        <a href="text.php?page=forum&credential=<?= q($cred) ?>" id="nav-link-kantie">看帖(加载中…)</a>
        <a href="text.php?page=forum_post&credential=<?= q($cred) ?>">发帖</a>
        <a href="text.php?page=forum_events&credential=<?= q($cred) ?>" id="nav-link-events">事件(<?= $unreadCount > 0 ? (int)$unreadCount . ' 未查收' : '无' ?>)</a>
        <a href="text.php?page=profile&credential=<?= q($cred) ?>">用户中心</a><br><br>
        <script>
        (function(){ var cred = <?= json_encode($cred) ?>;
            function postApi(api, data, cb) { var fd = new FormData(); fd.append('api', api); fd.append('credential', cred); for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]); var x = new XMLHttpRequest(); x.open('POST', 'text.php'); x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } }; x.send(fd); }
            function updateKanTie(firstPost) {
                var el = document.getElementById('nav-link-kantie'); if (!el) return;
                if (firstPost && (firstPost.last_author || firstPost.author)) {
                    var who = firstPost.last_author || firstPost.author || '';
                    var sec = firstPost.last_time ? (Date.now()/1000 - firstPost.last_time) : 0;
                    var minsStr = sec < 60 ? '刚刚' : (Math.floor(sec/60) + '分钟前');
                    el.textContent = '看帖(' + minsStr + ' 有来自 ' + who + ' 的新回复 - 《' + (firstPost.title||'') + '》发帖者 ' + (firstPost.author||'') + ')';
                } else { el.textContent = '看帖(没有帖子,请发布)'; }
            }
            function refreshNav() {
                postApi('forum_get_posts', { page: 1 }, function(r){ if (r && r.posts && r.posts.length) updateKanTie(r.posts[0]); else updateKanTie(null); });
                postApi('forum_get_events', {}, function(er){ if (er && !er.error && document.getElementById('nav-link-events')) document.getElementById('nav-link-events').textContent = (er.unread_count|0) > 0 ? '事件(' + er.unread_count + ' 未查收)' : '事件(无)'; });
            }
            refreshNav();
            setInterval(refreshNav, 5000);
        })();
        </script>
        <form method="post" action="text.php">
            <input type="hidden" name="api" value="forum_mark_events_read">
            <input type="hidden" name="credential" value="<?= q($cred) ?>">
            <button type="submit">查收(标记已读)</button>
        </form><br>
        <?php foreach ($list as $e):
            $typeText = ['reply'=>'回复','like'=>'点赞','unlike'=>'取消赞'][$e['type'] ?? ''] ?? $e['type'];
            $link = 'text.php?page=forum_post_view&id=' . urlencode($e['post_id'] ?? '') . '&credential=' . urlencode($cred);
            $read = !empty($e['read']);
            $evContent = isset($e['content']) && $e['content'] !== '' ? $e['content'] : null;
        ?>
        <div><?= $read ? '' : '[未读] ' ?> <?= q($typeText) ?> 来自 <a href="text.php?page=user&user=<?= q($e['from_user'] ?? '') ?>&credential=<?= q($cred) ?>"><?= q($e['from_user'] ?? '') ?></a><?= $evContent !== null ? ':' . str_replace("\n", "<br>", q($evContent)) : '' ?> · <a href="<?= q($link) ?>">去帖子</a></div>
        <?php endforeach; ?>
        <?php if (empty($list)): ?>暂无事件。<?php endif; ?>
        <?php
        break;
    }

    case 'user': {
        $viewUser = isset($_GET['user']) ? trim((string)$_GET['user']) : '';
        if ($viewUser === '') {
            header('Location: text.php?credential=' . urlencode($cred));
            exit;
        }
        if ($cred === '') {
            ?>请先登录后查看用户主页。 <a href="text.php?page=login">登录</a><?php
            break;
        }
        $name = findUsernameByCredential($cred);
        if ($name === null) {
            ?>凭证无效。 <a href="text.php?page=login&credential=<?= q($cred) ?>">登录</a><?php
            break;
        }
        $users = loadUsers();
        if (!isset($users[$viewUser])) {
            ?>用户不存在。 <a href="text.php?page=forum&credential=<?= q($cred) ?>">论坛</a><?php
            break;
        }
        $hasChat = false;
        ensureSingleDir();
        if (is_dir($SINGLE_DIR)) {
            foreach (scandir($SINGLE_DIR) as $f) {
                if ($f === '.' || $f === '..' || substr($f, -5) !== '.json') continue;
                $base = substr($f, 0, -5);
                $parts = explode('_', $base, 2);
                if (count($parts) === 2 && ($parts[0] === $name && $parts[1] === $viewUser || $parts[0] === $viewUser && $parts[1] === $name)) {
                    $hasChat = true;
                    break;
                }
            }
        }
        $signature = isset($users[$viewUser]['signature']) ? $users[$viewUser]['signature'] : '';
        $feed = loadUserFeed($viewUser);
        ?>
        <h1>用户主页 · <?= q($viewUser) ?></h1>
        <a href="text.php?page=forum&credential=<?= q($cred) ?>">论坛</a>
        <a href="text.php?page=profile&credential=<?= q($cred) ?>">用户中心</a>
        <?php if ($viewUser !== $name): ?>
            发起私聊:
            <?php if ($hasChat): ?>
                <a href="text.php?page=chat&credential=<?= q($cred) ?>&chat=<?= q($viewUser) ?>">进入私聊</a>
            <?php else: ?>
                <form method="post" action="text.php" style="display:inline">
                    <input type="hidden" name="api" value="start_chat">
                    <input type="hidden" name="credential" value="<?= q($cred) ?>">
                    <input type="hidden" name="other_username" value="<?= q($viewUser) ?>">
                    <button type="submit">发起私聊</button>
                </form>
            <?php endif; ?>
        <?php else: ?>
            <a href="text.php?page=user&user=<?= q($name) ?>&credential=<?= q($cred) ?>">自己的主页</a>
        <?php endif; ?><br><br>
        个性签名:<br><div style="white-space:pre-wrap"><?= str_replace("\n", "<br>", q($signature)) ?></div><br>
        用户动态(新在上):<br>
        <?php
        $feedTypeText = ['post' => '发帖', 'reply' => '回复', 'signature' => '修改个性签名', 'manual' => '手动动态'];
        ?>
        <div id="user-feed-list">
            <?php foreach ($feed as $item):
                $typeLabel = $feedTypeText[$item['type'] ?? ''] ?? ($item['type'] ?? '');
                $itemLink = $item['link'] ?? '';
                if ($itemLink !== '' && strpos($itemLink, 'credential=') === false) {
                    $itemLink .= (strpos($itemLink, '?') !== false ? '&' : '?') . 'credential=' . urlencode($cred);
                }
            ?>
            <div><?= isset($item['time']) && $item['time'] ? date('Y-m-d H:i', (int)$item['time']) : '' ?> · <?= q($typeLabel) ?> · <?= str_replace("\n", "<br>", q($item['text'] ?? '')) ?>
                <?php if ($itemLink !== ''): ?> <a href="<?= q($itemLink) ?>">查看</a><?php endif ?>
            </div>
            <?php endforeach; ?>
        </div>
        <script>
        (function(){
#9
发帖 2026-08-18 22:27:13 · 快照 2026-08-18 22:59:44

@FuyaOo0o0O #7 所以为什么不整合一下发到github上😴

#10
发帖 2026-08-18 22:27:29 · 快照 2026-08-18 22:59:44

继续代码

var cred = <?= json_encode($cred) ?>;
            var viewUser = <?= json_encode($viewUser) ?>;
            function postApi(api, data, cb) {
                var fd = new FormData();
                fd.append('api', api);
                fd.append('credential', cred);
                for (var k in data) if (data.hasOwnProperty(k)) fd.append(k, data[k]);
                var x = new XMLHttpRequest();
                x.open('POST', 'text.php');
                x.onload = function(){ try { cb(JSON.parse(x.responseText)); } catch(e) { cb(null); } };
                x.send(fd);
            }
            var typeLabels = { post: '发帖', reply: '回复', signature: '修改个性签名', manual: '手动动态' };
            function refreshFeed() {
                postApi('forum_get_feed', { user: viewUser }, function(r){
                    if (!r || r.error) return;
                    var list = r.feed || [];
                    var html = '';
                    for (var i = 0; i < list.length; i++) {
                        var item = list[i];
                        var t = item.time ? new Date(item.time*1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g,'-') : '';
                        var typeLabel = typeLabels[item.type] || item.type || '';
                        var textEsc = (item.text||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/\n/g,'<br>');
                        html += '<div>' + t + ' · ' + typeLabel + ' · ' + textEsc;
                        if (item.link) {
                            var href = item.link.replace(/&/g,'&amp;').replace(/"/g,'&quot;');
                            if (href.indexOf('credential=') === -1) href += (href.indexOf('?') !== -1 ? '&' : '?') + 'credential=' + encodeURIComponent(cred);
                            html += ' <a href="'+href+'">查看</a>';
                        }
                        html += '</div>';
                    }
                    var el = document.getElementById('user-feed-list');
                    if (el) el.innerHTML = html;
                });
            }
            setInterval(refreshFeed, 8000);
        })();
        </script>
        <?php
        break;
    }

    default:
        $linkCred = $cred !== '' ? '&credential=' . urlencode($cred) : '';
        ?><h1>首页</h1>
        <a href="text.php?page=register<?= $linkCred ?>">注册</a>
        <a href="text.php?page=login<?= $linkCred ?>">登录</a>
        <a href="text.php?page=chat<?= $linkCred ?>">发消息</a>
        <a href="text.php?page=profile<?= $linkCred ?>">用户中心</a>
        <a href="text.php?page=forum<?= $linkCred ?>">论坛</a><?php
        break;
}