1 <?php 2 /** 3 * Live chat API for private messages. 4 * Actions: send, poll, delete, conversations 5 */ 6 require_once __DIR__ . '/../includes/bootstrap.php'; 7 must_login(); 8 if (!csrf_ok()) json_out(['error' => 'CSRF'], 403); 9 10 $action = post('action'); 11 $uid = (int)$USER['id']; 12 13 /* ── Helper: build conversation_id ───────────────────── */ 14 function conv_id(int $a, int $b): string { 15 return min($a,$b) . '-' . max($a,$b); 16 } 17 18 /* ── Send a message ──────────────────────────────────── */ 19 if ($action === 'send') { 20 $toId = (int)post('to_id'); 21 $body = sanitise(post('body')); 22 23 if (!$toId || $toId === $uid) json_out(['error' => 'Invalid recipient'], 400); 24 if (!$body) json_out(['error' => 'Message is empty'], 400); 25 if (mb_strlen($body) > 2000) json_out(['error' => 'Message too long (max 2000)'], 400); 26 27 $other = DB::row('SELECT id,username FROM users WHERE id=? AND suspended=0', [$toId]); 28 if (!$other) json_out(['error' => 'User not found'], 404); 29 30 $convId = conv_id($uid, $toId); 31 32 $msgId = DB::insert( 33 'INSERT INTO messages (sender_id,receiver_id,subject,body,conversation_id) VALUES (?,?,?,?,?)', 34 [$uid, $toId, '', $body, $convId] 35 ); 36 37 // Notify recipient 38 add_notification($toId, 'message', [ 39 'from' => $USER['username'], 40 'from_id' => $uid, 41 'subject' => mb_substr($body, 0, 60) . (mb_strlen($body) > 60 ? '…' : ''), 42 ]); 43 44 // Return the new message row for live append 45 $msg = DB::row( 46 'SELECT m.*, u.username AS sender_name, u.avatar AS sender_av 47 FROM messages m JOIN users u ON u.id=m.sender_id 48 WHERE m.id=?', [$msgId] 49 ); 50 json_out(['ok' => true, 'message' => $msg]); 51 } 52 53 /* ── Poll: fetch new messages since a given id ───────── */ 54 if ($action === 'poll') { 55 $otherId = (int)post('other_id'); 56 $sinceId = (int)post('since_id'); 57 if (!$otherId) json_out(['error' => 'Missing other_id'], 400); 58 59 $convId = conv_id($uid, $otherId); 60 $msgs = DB::rows( 61 'SELECT m.*, u.username AS sender_name, u.avatar AS sender_av 62 FROM messages m JOIN users u ON u.id=m.sender_id 63 WHERE m.conversation_id=? AND m.id>? 64 ORDER BY m.id ASC LIMIT 50', 65 [$convId, $sinceId] 66 ); 67 68 // Mark messages from other user as read 69 if ($msgs) { 70 $now = DB::now(); 71 DB::run( 72 "UPDATE messages SET is_read=1 73 WHERE conversation_id=? AND receiver_id=? AND `is_read`=0", 74 [$convId, $uid] 75 ); 76 } 77 78 json_out(['ok' => true, 'messages' => $msgs]); 79 } 80 81 /* ── Load full conversation ──────────────────────────── */ 82 if ($action === 'load') { 83 $otherId = (int)post('other_id'); 84 $before = (int)post('before_id'); // for pagination 85 if (!$otherId) json_out(['error' => 'Missing other_id'], 400); 86 87 $convId = conv_id($uid, $otherId); 88 $params = [$convId]; 89 $where = ''; 90 if ($before) { 91 $where = ' AND m.id < ?'; 92 $params[] = $before; 93 } 94 95 $msgs = DB::rows( 96 "SELECT m.*, u.username AS sender_name, u.avatar AS sender_av 97 FROM messages m JOIN users u ON u.id=m.sender_id 98 WHERE m.conversation_id=? $where 99 ORDER BY m.id DESC LIMIT 40", 100 $params 101 ); 102 $msgs = array_reverse($msgs); // oldest first 103 104 // Mark as read 105 DB::run( 106 "UPDATE messages SET `is_read`=1 107 WHERE conversation_id=? AND receiver_id=? AND `is_read`=0", 108 [$convId, $uid] 109 ); 110 111 $other = DB::row('SELECT id,username,avatar,role,karma,last_seen FROM users WHERE id=?', [$otherId]); 112 json_out(['ok' => true, 'messages' => $msgs, 'other' => $other]); 113 } 114 115 /* ── List conversations ──────────────────────────────── */ 116 if ($action === 'conversations') { 117 $convs = DB::rows( 118 "SELECT m.*, 119 u.username AS other_name, u.avatar AS other_av, 120 (SELECT COUNT(*) FROM messages m2 121 WHERE m2.conversation_id=m.conversation_id 122 AND m2.receiver_id=? AND m2.is_read=0) AS unread_count 123 FROM messages m 124 JOIN users u ON u.id = CASE WHEN m.sender_id=? THEN m.receiver_id ELSE m.sender_id END 125 WHERE m.conversation_id IN ( 126 SELECT conversation_id FROM messages 127 WHERE sender_id=? OR receiver_id=? 128 ) 129 AND m.id IN ( 130 SELECT MAX(id) FROM messages 131 WHERE sender_id=? OR receiver_id=? 132 GROUP BY conversation_id 133 ) 134 ORDER BY m.created_at DESC", 135 [$uid, $uid, $uid, $uid, $uid, $uid] 136 ); 137 json_out(['ok' => true, 'conversations' => $convs]); 138 } 139 140 /* ── Delete a message ────────────────────────────────── */ 141 if ($action === 'delete') { 142 $msgId = (int)post('msg_id'); 143 $msg = DB::row('SELECT * FROM messages WHERE id=?', [$msgId]); 144 if (!$msg) json_out(['error' => 'Not found'], 404); 145 if ($msg['sender_id'] !== $uid && $msg['receiver_id'] !== $uid) json_out(['error' => 'Forbidden'], 403); 146 147 if ($msg['sender_id'] === $uid) 148 DB::run('UPDATE messages SET deleted_by_sender=1 WHERE id=?', [$msgId]); 149 else 150 DB::run('UPDATE messages SET deleted_by_receiver=1 WHERE id=?', [$msgId]); 151 152 json_out(['ok' => true]); 153 } 154 155 json_out(['error' => 'Unknown action'], 400);