- Implemented across bootstrap.php, chat frontend, and dashboard:

Per-tier audio bitrate: Free 64, Plus 96, Pro 128 kbps.
Configurable text/voice reply threads and depth.
Registration/login-only honeypot and internal CAPTCHA.
Optional Google reCAPTCHA v2/v3 with server verification.
Archive/export support for replies and bitrate.
Dashboard controls for all settings.
Verified JavaScript, JSON, SQLite migrations, desktop/mobile UI, and 
browser console. Native PHP lint was unavailable because PHP is not 
installed.
Google implementation follows Siteverify and reCAPTCHA v3.
This commit is contained in:
Ty Clifford
2026-06-09 10:59:58 -04:00
parent 88805057cc
commit eb57cde99f
14 changed files with 844 additions and 110 deletions
+21 -2
View File
@@ -17,6 +17,12 @@ if ($action === 'export') {
'id' => (int)$row['id'],
'username' => $row['username'], 'message' => $row['message'],
'type' => $row['message_type'], 'created_at' => date(DATE_ATOM, (int)$row['created_at']),
'reply_to_id' => !empty($row['reply_to_id']) ? (int)$row['reply_to_id'] : null,
'reply_depth' => (int)($row['reply_depth'] ?? 0),
'reply_username' => $row['reply_username'] ?? null,
'reply_message' => $row['reply_message'] ?? null,
'reply_message_type' => $row['reply_message_type'] ?? null,
'voice_bitrate_kbps' => isset($row['voice_bitrate_kbps']) ? (int)$row['voice_bitrate_kbps'] : null,
];
if ($row['message_type'] === 'voice' && $canVoiceExport && !empty($row['voice_file'])) {
$item['voice_url'] = appBaseUrl() . '/' . voicePublicPath($row['voice_file']);
@@ -28,10 +34,17 @@ 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', 'username', 'message', 'type', 'created_at', 'voice_url']);
fputcsv($out, [
'id', 'username', 'message', 'type', 'created_at', 'reply_to_id',
'reply_depth', 'reply_username', 'reply_message', 'reply_message_type',
'voice_bitrate_kbps', 'voice_url',
]);
foreach ($export as $row) fputcsv($out, [
$row['id'], $row['username'], $row['message'], $row['type'],
$row['created_at'], $row['voice_url'] ?? '',
$row['created_at'], $row['reply_to_id'] ?? '', $row['reply_depth'],
$row['reply_username'] ?? '', $row['reply_message'] ?? '',
$row['reply_message_type'] ?? '', $row['voice_bitrate_kbps'] ?? '',
$row['voice_url'] ?? '',
]);
fclose($out);
exit;
@@ -49,6 +62,12 @@ $payload = array_map(function(array $row): array {
'voice_url' => $row['message_type'] === 'voice' && !empty($row['voice_file']) ? voicePublicPath($row['voice_file']) : null,
'voice_mime' => $row['voice_mime'] ?? null,
'voice_duration' => $row['voice_duration'] !== null ? (float)$row['voice_duration'] : null,
'voice_bitrate_kbps' => isset($row['voice_bitrate_kbps']) ? (int)$row['voice_bitrate_kbps'] : null,
'reply_to_id' => !empty($row['reply_to_id']) ? (int)$row['reply_to_id'] : null,
'reply_depth' => (int)($row['reply_depth'] ?? 0),
'reply_username' => $row['reply_username'] ?? null,
'reply_message' => $row['reply_message'] ?? null,
'reply_message_type' => $row['reply_message_type'] ?? null,
'created_at' => (int)$row['created_at'], 'day_key' => $row['day_key'],
];
}, $rows);