388 lines
14 KiB
PHP
388 lines
14 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
|
|
require __DIR__ . '/im_web_bootstrap.php';
|
|
|
|
function detailPlaceholders(array $values): string
|
|
{
|
|
return implode(',', array_fill(0, count($values), '?'));
|
|
}
|
|
|
|
function detailTimestampMs(?string $datetime, string $messageId = '0'): int
|
|
{
|
|
$id = (int)$messageId;
|
|
if ($id > 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() ?: '消息详情加载失败');
|
|
}
|