0) { return ($id >> 21) + 1514736000000; } if ($datetime !== null && $datetime !== '') { $date = DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $datetime, new DateTimeZone(IM_DB_TIMEZONE) ); if ($date instanceof DateTimeImmutable) { return $date->getTimestamp() * 1000; } } return 0; } function detailResolveUserId(PDO $db, string $value): string { if ($value === '') { return ''; } $statement = $db->prepare( 'select `_uid` from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ? limit 1' ); $statement->execute([$value, $value, $value]); $uid = $statement->fetchColumn(); return is_string($uid) && $uid !== '' ? $uid : $value; } function detailConversationType(mixed $value): string { $type = strtolower(trim((string)$value)); return in_array($type, ['1', 'group', 'channel'], true) ? 'group' : 'single'; } function detailDeliveryMap(PDO $db, array $userIds): array { $userIds = array_values(array_unique(array_filter(array_map('strval', $userIds)))); if (!$userIds) { return []; } $statement = $db->prepare( 'select `_uid`, max(`_dt`) as `_dt` from `t_delivery_report`' . ' where `_uid` in (' . detailPlaceholders($userIds) . ') group by `_uid`' ); $statement->execute($userIds); $items = []; foreach ($statement->fetchAll() as $row) { $items[(string)$row['_uid']] = (int)$row['_dt']; } return $items; } function detailReadAt(PDO $db, string $readerId, array $targets): int { $targets = array_values(array_unique(array_filter(array_map('strval', $targets)))); if ($readerId === '' || !$targets) { return 0; } $statement = $db->prepare( 'select max(`_dt`) from `t_read_report`' . ' where `_uid` = ? and `_type` = 0 and `_line` = 0' . ' and `_target` in (' . detailPlaceholders($targets) . ')' ); $statement->execute(array_merge([$readerId], $targets)); $value = $statement->fetchColumn(); return is_numeric($value) ? (int)$value : 0; } function detailSingleMessageVisible(PDO $db, string $userId, string $targetId, string $messageUid): bool { $table = imUserMessagesTable($userId); $conversationKeys = [$userId . '|' . $targetId, $targetId . '|' . $userId]; $statement = $db->prepare( 'select 1 from `' . $table . '`' . ' where `_uid` = ? and `_mid` = ? and `_type` = 0 and `_line` = 0' . ' and `_target` in (' . detailPlaceholders($conversationKeys) . ') limit 1' ); $statement->execute(array_merge([$userId, $messageUid], $conversationKeys)); return (bool)$statement->fetchColumn(); } function detailUserMap(PDO $imDb, PDO $appDb, array $userIds, string $ownerId, string $groupId = ''): array { $userIds = array_values(array_unique(array_filter(array_map('strval', $userIds)))); if (!$userIds) { return []; } $statement = $imDb->prepare( 'select `_uid`, `_name`, `_display_name`, `_mobile`, `_portrait` from `t_user`' . ' where `_uid` in (' . detailPlaceholders($userIds) . ')' ); $statement->execute($userIds); $users = []; foreach ($statement->fetchAll() as $row) { $uid = (string)$row['_uid']; $rawName = trim((string)($row['_display_name'] ?? '')) ?: trim((string)($row['_name'] ?? '')) ?: trim((string)($row['_mobile'] ?? '')) ?: $uid; $users[$uid] = [ 'uid' => $uid, 'rawName' => $rawName, 'mobile' => (string)($row['_mobile'] ?? ''), 'portrait' => (string)($row['_portrait'] ?? ''), 'contactAlias' => '', 'remarkAlias' => '', 'groupAlias' => '', 'role' => '', 'displayName' => $rawName, ]; } if ($ownerId !== '') { $aliasStatement = $imDb->prepare( 'select `_friend_uid`, `_alias` from `t_friend` where `_uid` = ?' . ' and `_friend_uid` in (' . detailPlaceholders($userIds) . ')' ); $aliasStatement->execute(array_merge([$ownerId], $userIds)); foreach ($aliasStatement->fetchAll() as $row) { $uid = (string)$row['_friend_uid']; if (isset($users[$uid])) { $users[$uid]['contactAlias'] = trim((string)($row['_alias'] ?? '')); } } try { $remarkMap = fetchWebUserRemarkMap($appDb, $ownerId, $userIds); } catch (Throwable) { $remarkMap = []; } foreach ($remarkMap as $uid => $alias) { if (isset($users[$uid])) { $users[$uid]['remarkAlias'] = trim((string)$alias); } } } if ($groupId !== '') { $memberStatement = $appDb->prepare( 'select `user_id`, `alias`, `role` from `t_web_group_members`' . ' where `group_id` = ? and `user_id` in (' . detailPlaceholders($userIds) . ')' ); $memberStatement->execute(array_merge([$groupId], $userIds)); foreach ($memberStatement->fetchAll() as $row) { $uid = (string)$row['user_id']; if (isset($users[$uid])) { $users[$uid]['groupAlias'] = trim((string)($row['alias'] ?? '')); $users[$uid]['role'] = (string)($row['role'] ?? ''); } } } foreach ($users as $uid => $user) { $alias = (string)$user['contactAlias'] !== '' ? (string)$user['contactAlias'] : ((string)$user['remarkAlias'] !== '' ? (string)$user['remarkAlias'] : (string)$user['groupAlias']); $users[$uid]['displayName'] = $alias !== '' ? $alias : (string)$user['rawName']; } foreach ($userIds as $uid) { if (!isset($users[$uid])) { $users[$uid] = [ 'uid' => $uid, 'rawName' => $uid, 'mobile' => '', 'portrait' => '', 'contactAlias' => '', 'remarkAlias' => '', 'groupAlias' => '', 'role' => '', 'displayName' => $uid, ]; } } return $users; } function detailMember(array $user, int $time = 0): array { return [ 'uid' => (string)$user['uid'], 'name' => (string)$user['displayName'], 'rawName' => (string)$user['rawName'], 'mobile' => (string)($user['mobile'] ?? ''), 'portrait' => (string)($user['portrait'] ?? ''), 'role' => (string)($user['role'] ?? ''), 'time' => $time, ]; } function singleDetail(PDO $imDb, string $userId, string $targetId, string $messageUid): array { if (!preg_match('/^\d+$/', $messageUid)) { respond(400, '缺少可查询的消息ID'); } $table = imMessageTable($messageUid); $statement = $imDb->prepare( 'select `_mid`, `_from`, `_target`, `_dt`, `_content_type`, `_searchable_key`, `_to`' . ' from `' . $table . '` where `_mid` = ? limit 1' ); $statement->execute([$messageUid]); $message = $statement->fetch(); if (!is_array($message)) { respond(404, '没有找到该消息'); } $senderId = (string)$message['_from']; if (!detailSingleMessageVisible($imDb, $userId, $targetId, $messageUid)) { respond(403, '没有权限查看该消息'); } $receiverId = $senderId === $userId ? $targetId : $userId; if ($senderId !== $userId && $senderId !== $targetId) { respond(403, '没有权限查看该消息'); } $sentAt = detailTimestampMs((string)($message['_dt'] ?? ''), $messageUid); $targets = [$senderId, $senderId . '|' . $receiverId, $receiverId . '|' . $senderId]; $deliveredRaw = detailDeliveryMap($imDb, [$receiverId])[$receiverId] ?? 0; $readRaw = detailReadAt($imDb, $receiverId, $targets); $users = detailUserMap($imDb, imAppDb(), [$senderId, $receiverId], $userId); return [ 'messageUid' => $messageUid, 'conversationType' => 'single', 'sentAt' => $sentAt, 'sender' => detailMember($users[$senderId]), 'receiver' => detailMember($users[$receiverId]), 'deliveredAt' => $deliveredRaw >= $sentAt ? $deliveredRaw : 0, 'readAt' => $readRaw >= $sentAt ? $readRaw : 0, ]; } function groupDetail(PDO $imDb, PDO $appDb, string $userId, string $groupId, string $messageUid): array { ensureWebImAppTables(); if (!preg_match('/^\d+$/', $messageUid)) { respond(400, '缺少可查询的消息ID'); } $myMember = $appDb->prepare( 'select `role` from `t_web_group_members` where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' ); $myMember->execute([$groupId, $userId]); if (!$myMember->fetchColumn()) { respond(403, '你不在该群组中'); } $messageStatement = $appDb->prepare( 'select `message_uid`, `group_id`, `sender_uid`, `created_at_ms`, `edited_at_ms`, `recalled`' . ' from `t_web_group_messages` where `group_id` = ? and `message_uid` = ? limit 1' ); $messageStatement->execute([$groupId, $messageUid]); $message = $messageStatement->fetch(); if (!is_array($message)) { respond(404, '没有找到该群消息'); } $sentAt = (int)$message['created_at_ms']; $editedAt = (int)($message['edited_at_ms'] ?? 0); $activityAt = max($sentAt, $editedAt); $senderId = (string)$message['sender_uid']; $memberStatement = $appDb->prepare( 'select `user_id`, `role`, `alias`, `joined_at_ms` from `t_web_group_members`' . ' where `group_id` = ? and `status` = 0 and `joined_at_ms` <= ?' . ' order by (`role` = "owner") desc, (`role` = "admin") desc, `joined_at_ms` asc' ); $memberStatement->execute([$groupId, $sentAt]); $members = array_values(array_filter($memberStatement->fetchAll(), static function (array $member) use ($senderId): bool { return (string)$member['user_id'] !== $senderId; })); $memberIds = array_values(array_unique(array_map(static fn(array $member): string => (string)$member['user_id'], $members))); $users = detailUserMap($imDb, $appDb, array_merge([$senderId], $memberIds), $userId, $groupId); $deliveryMap = detailDeliveryMap($imDb, $memberIds); $readMap = []; if ($memberIds) { $readStatement = $appDb->prepare( 'select `user_id`, `read_at_ms` from `t_web_group_reads`' . ' where `group_id` = ? and `user_id` in (' . detailPlaceholders($memberIds) . ')' ); $readStatement->execute(array_merge([$groupId], $memberIds)); foreach ($readStatement->fetchAll() as $row) { $readMap[(string)$row['user_id']] = (int)$row['read_at_ms']; } } $read = []; $unread = []; $undelivered = []; foreach ($memberIds as $uid) { $readAt = (int)($readMap[$uid] ?? 0); $deliveredAt = (int)($deliveryMap[$uid] ?? 0); $member = detailMember($users[$uid], $readAt >= $activityAt ? $readAt : ($deliveredAt >= $sentAt ? $deliveredAt : 0)); if ($readAt >= $activityAt) { $read[] = $member; } elseif ($deliveredAt >= $sentAt) { $unread[] = $member; } else { $undelivered[] = $member; } } return [ 'messageUid' => $messageUid, 'conversationType' => 'group', 'groupId' => $groupId, 'sentAt' => $sentAt, 'editedAt' => $editedAt, 'recalled' => (bool)$message['recalled'], 'sender' => detailMember($users[$senderId] ?? ['uid' => $senderId, 'displayName' => $senderId, 'rawName' => $senderId]), 'group' => [ 'read' => $read, 'unread' => $unread, 'undelivered' => $undelivered, 'counts' => [ 'read' => count($read), 'unread' => count($unread), 'undelivered' => count($undelivered), 'total' => count($memberIds), ], ], ]; } $payload = readJsonPayload(); $userId = trim((string)($payload['userId'] ?? '')); $target = trim((string)($payload['target'] ?? '')); $messageUid = trim((string)($payload['messageUid'] ?? $payload['messageId'] ?? '')); $conversationType = detailConversationType($payload['conversationType'] ?? $payload['type'] ?? 'single'); if ($userId === '') { respond(400, '缺少用户ID'); } if ($target === '') { respond(400, '缺少会话对象'); } if ($messageUid === '') { respond(400, '缺少消息ID'); } try { $imDb = imDb(); $appDb = imAppDb(); $userId = detailResolveUserId($imDb, $userId); if ($conversationType === 'group') { respond(0, 'success', ['detail' => groupDetail($imDb, $appDb, $userId, $target, $messageUid)]); } $targetId = detailResolveUserId($imDb, $target); respond(0, 'success', ['detail' => singleDetail($imDb, $userId, $targetId, $messageUid)]); } catch (Throwable $error) { respond(500, $error->getMessage() ?: '消息详情加载失败'); }