- Implemented.
Replaced polling with a single queued cursor worker, timeout recovery, backoff, deduplication, chronological insertion, and capped DOM history in cyberchat-v4.js. Removed Groups UI, API, configuration, tier features, admin controls, and archive exposure. Legacy group records remain stored but inaccessible. Added configurable polling limits in config.json.
This commit is contained in:
+4
-4
@@ -14,7 +14,7 @@ if ($action === 'export') {
|
||||
$format = strtolower((string)($_GET['format'] ?? 'json'));
|
||||
$export = array_map(function(array $row) use ($canVoiceExport): array {
|
||||
$item = [
|
||||
'id' => (int)$row['id'], 'group_id' => $row['group_id'] ? (int)$row['group_id'] : null,
|
||||
'id' => (int)$row['id'],
|
||||
'username' => $row['username'], 'message' => $row['message'],
|
||||
'type' => $row['message_type'], 'created_at' => date(DATE_ATOM, (int)$row['created_at']),
|
||||
];
|
||||
@@ -28,9 +28,9 @@ if ($action === 'export') {
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
|
||||
$out = fopen('php://output', 'w');
|
||||
fputcsv($out, ['id', 'group_id', 'username', 'message', 'type', 'created_at', 'voice_url']);
|
||||
fputcsv($out, ['id', 'username', 'message', 'type', 'created_at', 'voice_url']);
|
||||
foreach ($export as $row) fputcsv($out, [
|
||||
$row['id'], $row['group_id'], $row['username'], $row['message'], $row['type'],
|
||||
$row['id'], $row['username'], $row['message'], $row['type'],
|
||||
$row['created_at'], $row['voice_url'] ?? '',
|
||||
]);
|
||||
fclose($out);
|
||||
@@ -44,7 +44,7 @@ if ($action === 'export') {
|
||||
|
||||
$payload = array_map(function(array $row): array {
|
||||
return [
|
||||
'id' => (int)$row['id'], 'group_id' => $row['group_id'] ? (int)$row['group_id'] : null,
|
||||
'id' => (int)$row['id'],
|
||||
'message' => $row['message'], 'message_type' => $row['message_type'],
|
||||
'voice_url' => $row['message_type'] === 'voice' && !empty($row['voice_file']) ? voicePublicPath($row['voice_file']) : null,
|
||||
'voice_duration' => $row['voice_duration'] !== null ? (float)$row['voice_duration'] : null,
|
||||
|
||||
+2
-1
@@ -10,6 +10,8 @@ jsonResponse([
|
||||
'max_username_length' => $config['chat']['max_username_length'] ?? 12,
|
||||
'poll_interval_ms' => $config['chat']['poll_interval_ms'] ?? 2000,
|
||||
'poll_timeout_ms' => $config['chat']['poll_timeout_ms'] ?? 12000,
|
||||
'poll_max_backoff_ms' => $config['chat']['poll_max_backoff_ms'] ?? 15000,
|
||||
'max_rendered_messages' => $config['chat']['max_rendered_messages'] ?? 500,
|
||||
],
|
||||
'ui' => $config['ui'] ?? [],
|
||||
'security' => ['min_password_length' => $config['security']['min_password_length'] ?? 4],
|
||||
@@ -20,6 +22,5 @@ jsonResponse([
|
||||
'max_upload_bytes' => $config['voice']['max_upload_bytes'] ?? 12582912,
|
||||
'auto_play_default' => $config['voice']['auto_play_default'] ?? true,
|
||||
],
|
||||
'groups' => ['enabled' => $config['groups']['enabled'] ?? true],
|
||||
'subscriptions' => ['enabled' => $config['subscriptions']['enabled'] ?? true],
|
||||
]);
|
||||
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
<?php
|
||||
define('CYBERCHAT_API', true);
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
sendCorsHeaders();
|
||||
|
||||
$user = authRequired();
|
||||
$action = $_POST['action'] ?? $_GET['action'] ?? 'list';
|
||||
|
||||
$planGroupsEnabled = (bool)featureValue($user, 'groups_available', true);
|
||||
if (!groupsAvailableForUser($user)) {
|
||||
$error = groupsEnabled()
|
||||
? 'Private groups are not available on your tier'
|
||||
: 'Private groups are currently disabled';
|
||||
if ($action === 'list') {
|
||||
jsonResponse([
|
||||
'groups' => [],
|
||||
'member_limit' => 0,
|
||||
'enabled' => false,
|
||||
'platform_enabled' => groupsEnabled(),
|
||||
'plan_enabled' => $planGroupsEnabled,
|
||||
]);
|
||||
}
|
||||
jsonResponse([
|
||||
'error' => $error,
|
||||
'groups_enabled' => false,
|
||||
'groups_platform_enabled' => groupsEnabled(),
|
||||
], 403);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'list':
|
||||
jsonResponse([
|
||||
'groups' => userGroups((int)$user['id']),
|
||||
'member_limit' => (int)featureValue($user, 'group_member_limit', 2),
|
||||
'enabled' => true,
|
||||
'platform_enabled' => true,
|
||||
'plan_enabled' => true,
|
||||
]);
|
||||
|
||||
case 'create':
|
||||
$name = trim((string)($_POST['name'] ?? ''));
|
||||
$names = array_values(array_unique(array_filter(array_map('trim', explode(',', (string)($_POST['members'] ?? ''))))));
|
||||
if ($name === '' || strlen($name) > 40) jsonResponse(['error' => 'Group name must be 1-40 characters'], 400);
|
||||
$limit = max(2, (int)featureValue($user, 'group_member_limit', 2));
|
||||
if (count($names) + 1 > $limit) jsonResponse(['error' => "Your plan allows $limit group members including you"], 403);
|
||||
$memberIds = [];
|
||||
$lookup = getDB()->prepare("SELECT id FROM users WHERE username=? COLLATE NOCASE");
|
||||
foreach ($names as $username) {
|
||||
if (strcasecmp($username, $user['username']) === 0) continue;
|
||||
$lookup->execute([$username]);
|
||||
$id = (int)$lookup->fetchColumn();
|
||||
if (!$id) jsonResponse(['error' => "User '$username' was not found"], 404);
|
||||
$memberIds[$id] = $id;
|
||||
}
|
||||
if (!$memberIds) jsonResponse(['error' => 'Add at least one other user to create a group'], 400);
|
||||
if (count($memberIds) + 1 > $limit) jsonResponse(['error' => "Your plan allows $limit group members including you"], 403);
|
||||
$db = getDB();
|
||||
$db->beginTransaction();
|
||||
$db->prepare("INSERT INTO chat_groups (owner_id,name,created_at,updated_at) VALUES (?,?,?,?)")
|
||||
->execute([$user['id'], $name, time(), time()]);
|
||||
$groupId = (int)$db->lastInsertId();
|
||||
$insert = $db->prepare("INSERT INTO group_members (group_id,user_id,role,joined_at) VALUES (?,?,?,?)");
|
||||
$insert->execute([$groupId, $user['id'], 'owner', time()]);
|
||||
foreach ($memberIds as $id) $insert->execute([$groupId, $id, 'member', time()]);
|
||||
$db->commit();
|
||||
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
|
||||
|
||||
case 'add_member':
|
||||
$groupId = normalizeGroupId($_POST['group_id'] ?? null);
|
||||
$username = trim((string)($_POST['username'] ?? ''));
|
||||
$groupStmt = getDB()->prepare("SELECT * FROM chat_groups WHERE id=? AND owner_id=?");
|
||||
$groupStmt->execute([$groupId, $user['id']]);
|
||||
$group = $groupStmt->fetch();
|
||||
if (!$group) jsonResponse(['error' => 'Only the group owner can add members'], 403);
|
||||
$limit = max(2, (int)featureValue($user, 'group_member_limit', 2));
|
||||
$countStmt = getDB()->prepare("SELECT COUNT(*) FROM group_members WHERE group_id=?");
|
||||
$countStmt->execute([$groupId]);
|
||||
if ((int)$countStmt->fetchColumn() >= $limit) jsonResponse(['error' => "This group has reached your $limit-member limit"], 403);
|
||||
$lookup = getDB()->prepare("SELECT id FROM users WHERE username=? COLLATE NOCASE");
|
||||
$lookup->execute([$username]);
|
||||
$memberId = (int)$lookup->fetchColumn();
|
||||
if (!$memberId) jsonResponse(['error' => 'User not found'], 404);
|
||||
getDB()->prepare("INSERT OR IGNORE INTO group_members (group_id,user_id,role,joined_at) VALUES (?,?,'member',?)")
|
||||
->execute([$groupId, $memberId, time()]);
|
||||
getDB()->prepare("UPDATE chat_groups SET updated_at=? WHERE id=?")->execute([time(), $groupId]);
|
||||
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
|
||||
|
||||
case 'remove_member':
|
||||
$groupId = normalizeGroupId($_POST['group_id'] ?? null);
|
||||
$memberId = (int)($_POST['user_id'] ?? 0);
|
||||
$stmt = getDB()->prepare("SELECT owner_id FROM chat_groups WHERE id=?");
|
||||
$stmt->execute([$groupId]);
|
||||
$ownerId = (int)$stmt->fetchColumn();
|
||||
if (!$ownerId) jsonResponse(['error' => 'Group not found'], 404);
|
||||
if ($ownerId !== (int)$user['id'] && $memberId !== (int)$user['id']) jsonResponse(['error' => 'Not allowed'], 403);
|
||||
if ($memberId === $ownerId) jsonResponse(['error' => 'The owner must delete the group instead'], 400);
|
||||
getDB()->prepare("DELETE FROM group_members WHERE group_id=? AND user_id=?")->execute([$groupId, $memberId]);
|
||||
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
|
||||
|
||||
case 'delete':
|
||||
$groupId = normalizeGroupId($_POST['group_id'] ?? null);
|
||||
$db = getDB();
|
||||
$owner = $db->prepare("SELECT 1 FROM chat_groups WHERE id=? AND owner_id=?");
|
||||
$owner->execute([$groupId, $user['id']]);
|
||||
if (!$owner->fetchColumn()) jsonResponse(['error' => 'Only the group owner can delete this group'], 403);
|
||||
$voice = $db->prepare("SELECT voice_file FROM messages WHERE group_id=? AND voice_file IS NOT NULL");
|
||||
$voice->execute([$groupId]);
|
||||
$voiceFiles = $voice->fetchAll();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$db->prepare("DELETE FROM recording_status WHERE group_key=?")->execute([groupKey($groupId)]);
|
||||
$db->prepare("DELETE FROM messages WHERE group_id=?")->execute([$groupId]);
|
||||
$db->prepare("DELETE FROM group_members WHERE group_id=?")->execute([$groupId]);
|
||||
$db->prepare("DELETE FROM chat_groups WHERE id=?")->execute([$groupId]);
|
||||
$db->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction()) $db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
foreach ($voiceFiles as $row) deleteVoiceFile($row['voice_file'] ?? null);
|
||||
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
|
||||
|
||||
default:
|
||||
jsonResponse(['error' => 'Unknown action'], 400);
|
||||
}
|
||||
+84
-74
@@ -6,19 +6,18 @@ sendCorsHeaders();
|
||||
try { archiveYesterdayIfNeeded(); } catch (Throwable $e) { error_log($e->getMessage()); }
|
||||
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'send': sendTextMessage();
|
||||
case 'send_voice': sendVoiceMessage();
|
||||
case 'recording': setRecordingStatus();
|
||||
case 'poll': pollMessages();
|
||||
case 'history': archiveDays();
|
||||
default: jsonResponse(['error' => 'Unknown action'], 400);
|
||||
}
|
||||
match ($action) {
|
||||
'send' => sendTextMessage(),
|
||||
'send_voice' => sendVoiceMessage(),
|
||||
'recording' => setRecordingStatus(),
|
||||
'poll' => pollMessages(),
|
||||
'history' => archiveDays(),
|
||||
default => jsonResponse(['error' => 'Unknown action'], 400),
|
||||
};
|
||||
|
||||
function messagePayload(array $row): array {
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'group_id' => isset($row['group_id']) && $row['group_id'] !== null ? (int)$row['group_id'] : null,
|
||||
'username' => $row['username'],
|
||||
'color' => $row['color'],
|
||||
'message' => $row['message'],
|
||||
@@ -31,98 +30,105 @@ function messagePayload(array $row): array {
|
||||
];
|
||||
}
|
||||
|
||||
function requestedGroup(array $user): ?int {
|
||||
$groupId = normalizeGroupId($_POST['group_id'] ?? $_GET['group_id'] ?? null);
|
||||
if ($groupId !== null && !groupsAvailableForUser($user)) {
|
||||
$error = groupsEnabled()
|
||||
? 'Private groups are not available on your tier'
|
||||
: 'Private groups are currently disabled';
|
||||
jsonResponse([
|
||||
'error' => $error,
|
||||
'groups_enabled' => false,
|
||||
'groups_platform_enabled' => groupsEnabled(),
|
||||
], 403);
|
||||
}
|
||||
requireGroupAccess($user, $groupId);
|
||||
return $groupId;
|
||||
function publicMessageCondition(PDO $db): string {
|
||||
return isset(tableColumns($db, 'messages')['group_id']) ? ' AND group_id IS NULL' : '';
|
||||
}
|
||||
|
||||
function sendTextMessage(): never {
|
||||
$user = authRequired();
|
||||
$groupId = requestedGroup($user);
|
||||
$message = trim((string)($_POST['message'] ?? ''));
|
||||
$max = (int)getConfigVal('chat.max_message_length', 500);
|
||||
if ($message === '') jsonResponse(['error' => 'Empty message'], 400);
|
||||
if (strlen($message) > $max) jsonResponse(['error' => "Message too long (max $max chars)"], 400);
|
||||
|
||||
$created = time();
|
||||
$db = getDB();
|
||||
$db->prepare("INSERT INTO messages (user_id,group_id,username,color,message,created_at,day_key)
|
||||
VALUES (?,?,?,?,?,?,?)")->execute([
|
||||
$user['id'], $groupId, $user['username'], $user['color'], $message, $created, dayKey(),
|
||||
$db->prepare("INSERT INTO messages (user_id,username,color,message,created_at,day_key)
|
||||
VALUES (?,?,?,?,?,?)")->execute([
|
||||
$user['id'], $user['username'], $user['color'], $message, $created, dayKey(),
|
||||
]);
|
||||
if ($groupId !== null) {
|
||||
$db->prepare("UPDATE chat_groups SET updated_at=? WHERE id=?")->execute([$created, $groupId]);
|
||||
}
|
||||
trackEvent('message_text', '/api/messages.php', (int)$user['id']);
|
||||
jsonResponse(['success' => true, 'message' => messagePayload([
|
||||
'id' => $db->lastInsertId(), 'group_id' => $groupId, 'username' => $user['username'],
|
||||
'color' => $user['color'], 'message' => $message, 'message_type' => 'text',
|
||||
'created_at' => $created, 'day_key' => dayKey(),
|
||||
'id' => $db->lastInsertId(),
|
||||
'username' => $user['username'],
|
||||
'color' => $user['color'],
|
||||
'message' => $message,
|
||||
'message_type' => 'text',
|
||||
'created_at' => $created,
|
||||
'day_key' => dayKey(),
|
||||
])]);
|
||||
}
|
||||
|
||||
function sendVoiceMessage(): never {
|
||||
$user = authRequired();
|
||||
$groupId = requestedGroup($user);
|
||||
if (!getConfigVal('voice.enabled', true) || !featureValue($user, 'voice_messages', true)) {
|
||||
jsonResponse(['error' => 'Your plan does not include voice messages'], 403);
|
||||
}
|
||||
if (empty($_FILES['voice']) || !is_array($_FILES['voice'])) jsonResponse(['error' => 'No voice clip uploaded'], 400);
|
||||
if (empty($_FILES['voice']) || !is_array($_FILES['voice'])) {
|
||||
jsonResponse(['error' => 'No voice clip uploaded'], 400);
|
||||
}
|
||||
$file = $_FILES['voice'];
|
||||
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) jsonResponse(['error' => 'Voice upload failed'], 400);
|
||||
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||
jsonResponse(['error' => 'Voice upload failed'], 400);
|
||||
}
|
||||
$maxBytes = (int)getConfigVal('voice.max_upload_bytes', 12582912);
|
||||
if (($file['size'] ?? 0) < 1 || $file['size'] > $maxBytes) jsonResponse(['error' => 'Voice clip exceeds the upload limit'], 400);
|
||||
if (($file['size'] ?? 0) < 1 || $file['size'] > $maxBytes) {
|
||||
jsonResponse(['error' => 'Voice clip exceeds the upload limit'], 400);
|
||||
}
|
||||
$duration = (float)($_POST['duration'] ?? 0);
|
||||
$maxSeconds = max(1, (int)getConfigVal('voice.max_duration_seconds', 60));
|
||||
if ($duration <= 0 || $duration > $maxSeconds + 1) jsonResponse(['error' => "Voice clip must be $maxSeconds seconds or less"], 400);
|
||||
if ($duration <= 0 || $duration > $maxSeconds + 1) {
|
||||
jsonResponse(['error' => "Voice clip must be $maxSeconds seconds or less"], 400);
|
||||
}
|
||||
|
||||
$mime = class_exists('finfo') ? (string)(new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']) : '';
|
||||
$header = (string)file_get_contents($file['tmp_name'], false, null, 0, 12);
|
||||
if (str_starts_with($header, "\x1A\x45\xDF\xA3")) $mime = 'audio/webm';
|
||||
elseif (str_starts_with($header, 'RIFF') && substr($header, 8, 4) === 'WAVE') $mime = 'audio/wav';
|
||||
$allowed = ['audio/webm' => 'webm', 'video/webm' => 'webm', 'audio/wav' => 'wav', 'audio/x-wav' => 'wav', 'audio/wave' => 'wav'];
|
||||
$allowed = [
|
||||
'audio/webm' => 'webm',
|
||||
'video/webm' => 'webm',
|
||||
'audio/wav' => 'wav',
|
||||
'audio/x-wav' => 'wav',
|
||||
'audio/wave' => 'wav',
|
||||
];
|
||||
if (!isset($allowed[$mime])) jsonResponse(['error' => 'Voice clips must be WAV or WebM'], 400);
|
||||
|
||||
$dir = voiceUploadDir();
|
||||
ensureWritableDirectory($dir, 'Voice storage');
|
||||
$filename = bin2hex(random_bytes(20)) . '.' . $allowed[$mime];
|
||||
if (!move_uploaded_file($file['tmp_name'], $dir . '/' . $filename)) jsonResponse(['error' => 'Could not store voice clip'], 500);
|
||||
if (!move_uploaded_file($file['tmp_name'], $dir . '/' . $filename)) {
|
||||
jsonResponse(['error' => 'Could not store voice clip'], 500);
|
||||
}
|
||||
|
||||
$created = time();
|
||||
$storedMime = $mime === 'video/webm' ? 'audio/webm' : $mime;
|
||||
$db = getDB();
|
||||
try {
|
||||
$db->prepare("INSERT INTO messages
|
||||
(user_id,group_id,username,color,message,message_type,voice_file,voice_mime,voice_duration,created_at,day_key)
|
||||
VALUES (?,?,?,?,?,'voice',?,?,?,?,?)")->execute([
|
||||
$user['id'], $groupId, $user['username'], $user['color'], '[Voice clip]',
|
||||
(user_id,username,color,message,message_type,voice_file,voice_mime,voice_duration,created_at,day_key)
|
||||
VALUES (?,?,?,?,'voice',?,?,?,?,?)")->execute([
|
||||
$user['id'], $user['username'], $user['color'], '[Voice clip]',
|
||||
$filename, $storedMime, $duration, $created, dayKey(),
|
||||
]);
|
||||
if ($groupId !== null) {
|
||||
$db->prepare("UPDATE chat_groups SET updated_at=? WHERE id=?")->execute([$created, $groupId]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
deleteVoiceFile($filename);
|
||||
throw $e;
|
||||
}
|
||||
getDB()->prepare("DELETE FROM recording_status WHERE group_key=? AND user_id=?")
|
||||
->execute([groupKey($groupId), $user['id']]);
|
||||
$db->prepare("DELETE FROM recording_status WHERE channel_key='public' AND user_id=?")
|
||||
->execute([$user['id']]);
|
||||
trackEvent('message_voice', '/api/messages.php', (int)$user['id']);
|
||||
jsonResponse(['success' => true, 'message' => messagePayload([
|
||||
'id' => $db->lastInsertId(), 'group_id' => $groupId, 'username' => $user['username'],
|
||||
'color' => $user['color'], 'message' => '[Voice clip]', 'message_type' => 'voice',
|
||||
'voice_file' => $filename, 'voice_mime' => $storedMime, 'voice_duration' => $duration,
|
||||
'created_at' => $created, 'day_key' => dayKey(),
|
||||
'id' => $db->lastInsertId(),
|
||||
'username' => $user['username'],
|
||||
'color' => $user['color'],
|
||||
'message' => '[Voice clip]',
|
||||
'message_type' => 'voice',
|
||||
'voice_file' => $filename,
|
||||
'voice_mime' => $storedMime,
|
||||
'voice_duration' => $duration,
|
||||
'created_at' => $created,
|
||||
'day_key' => dayKey(),
|
||||
])]);
|
||||
}
|
||||
|
||||
@@ -131,60 +137,64 @@ function setRecordingStatus(): never {
|
||||
if (!featureValue($user, 'recording_status', false)) {
|
||||
jsonResponse(['error' => 'Your plan does not include recording indicators'], 403);
|
||||
}
|
||||
$groupId = requestedGroup($user);
|
||||
$active = filter_var($_POST['active'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$db = getDB();
|
||||
if ($active) {
|
||||
$db->prepare("INSERT INTO recording_status (group_key,user_id,expires_at) VALUES (?,?,?)
|
||||
ON CONFLICT(group_key,user_id) DO UPDATE SET expires_at=excluded.expires_at")
|
||||
->execute([groupKey($groupId), $user['id'], time() + 10]);
|
||||
$db->prepare("INSERT INTO recording_status (channel_key,user_id,expires_at) VALUES ('public',?,?)
|
||||
ON CONFLICT(channel_key,user_id) DO UPDATE SET expires_at=excluded.expires_at")
|
||||
->execute([$user['id'], time() + 10]);
|
||||
} else {
|
||||
$db->prepare("DELETE FROM recording_status WHERE group_key=? AND user_id=?")
|
||||
->execute([groupKey($groupId), $user['id']]);
|
||||
$db->prepare("DELETE FROM recording_status WHERE channel_key='public' AND user_id=?")
|
||||
->execute([$user['id']]);
|
||||
}
|
||||
jsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
function pollMessages(): never {
|
||||
$user = authRequired();
|
||||
$groupId = requestedGroup($user);
|
||||
$since = max(0, (int)($_GET['since'] ?? 0));
|
||||
$limit = max(1, min(250, (int)getConfigVal('chat.messages_per_page', 100)));
|
||||
$db = getDB();
|
||||
$whereGroup = $groupId === null ? 'group_id IS NULL' : 'group_id = ?';
|
||||
$params = $groupId === null ? [dayKey()] : [dayKey(), $groupId];
|
||||
$publicOnly = publicMessageCondition($db);
|
||||
|
||||
if ($since === 0) {
|
||||
$stmt = $db->prepare("SELECT * FROM messages WHERE day_key=? AND $whereGroup ORDER BY id DESC LIMIT ?");
|
||||
$params[] = $limit;
|
||||
$stmt->execute($params);
|
||||
$stmt = $db->prepare("SELECT * FROM messages
|
||||
WHERE day_key=?$publicOnly ORDER BY id DESC LIMIT ?");
|
||||
$stmt->execute([dayKey(), $limit]);
|
||||
$rows = array_reverse($stmt->fetchAll());
|
||||
$hasMore = false;
|
||||
} else {
|
||||
$stmt = $db->prepare("SELECT * FROM messages WHERE day_key=? AND $whereGroup AND id>? ORDER BY id LIMIT ?");
|
||||
$params[] = $since;
|
||||
$params[] = $limit;
|
||||
$stmt->execute($params);
|
||||
$stmt = $db->prepare("SELECT * FROM messages
|
||||
WHERE day_key=?$publicOnly AND id>? ORDER BY id LIMIT ?");
|
||||
$stmt->execute([dayKey(), $since, $limit + 1]);
|
||||
$rows = $stmt->fetchAll();
|
||||
$hasMore = count($rows) > $limit;
|
||||
if ($hasMore) $rows = array_slice($rows, 0, $limit);
|
||||
}
|
||||
|
||||
$cursor = $since;
|
||||
foreach ($rows as $row) $cursor = max($cursor, (int)$row['id']);
|
||||
|
||||
$cutoff = time() - 300;
|
||||
$onlineStmt = $db->prepare("SELECT COUNT(*) FROM sessions WHERE last_active>?");
|
||||
$onlineStmt->execute([$cutoff]);
|
||||
|
||||
$recording = [];
|
||||
$db->prepare("DELETE FROM recording_status WHERE expires_at<=?")->execute([time()]);
|
||||
if (featureValue($user, 'recording_status', false)) {
|
||||
$recordStmt = $db->prepare("SELECT u.id,u.username,u.color FROM recording_status r
|
||||
JOIN users u ON u.id=r.user_id WHERE r.group_key=? AND r.expires_at>? AND r.user_id!=?");
|
||||
$recordStmt->execute([groupKey($groupId), time(), $user['id']]);
|
||||
JOIN users u ON u.id=r.user_id
|
||||
WHERE r.channel_key='public' AND r.expires_at>? AND r.user_id!=?");
|
||||
$recordStmt->execute([time(), $user['id']]);
|
||||
$recording = $recordStmt->fetchAll();
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'messages' => array_map('messagePayload', $rows),
|
||||
'cursor' => $cursor,
|
||||
'has_more' => $hasMore,
|
||||
'online' => (int)$onlineStmt->fetchColumn(),
|
||||
'recording' => $recording,
|
||||
'groups' => groupsAvailableForUser($user) ? userGroups((int)$user['id']) : [],
|
||||
'groups_enabled' => groupsAvailableForUser($user),
|
||||
'groups_platform_enabled' => groupsEnabled(),
|
||||
'group_id' => $groupId,
|
||||
'server_time' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user