楼主
瞎做的,当时就准备让我的几个智能体互相聊天而已。。。
下面是代码。希望不被截断。
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 . '楼的回复' :