Files
IM-web-new/dist/api/conversation_actions.php
T

991 lines
36 KiB
PHP
Raw Normal View History

2026-10-09 22:49:53 +07:00
<?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 webActionList(mixed $value): array
{
if (is_array($value)) {
$items = $value;
} else {
$items = preg_split('/[\s,,;;]+/u', (string)$value) ?: [];
}
return array_values(array_unique(array_filter(array_map(static function ($item): string {
return trim((string)$item);
}, $items))));
}
function appPlaceholders(array $values): string
{
return implode(',', array_fill(0, count($values), '?'));
}
function resolveWebUserIds(PDO $db, array $values): array
{
$values = webActionList($values);
if (!$values) {
return [];
}
$resolved = [];
foreach ($values as $value) {
$statement = $db->prepare(
'select `_uid` from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ? limit 1'
);
$statement->execute([$value, $value, $value]);
$uid = $statement->fetchColumn();
if (is_string($uid) && $uid !== '') {
$resolved[] = $uid;
}
}
return array_values(array_unique($resolved));
}
function webGroupConversation(array $group, string $role = 'member'): array
{
$name = (string)($group['name'] ?? $group['_name'] ?? $group['group_id'] ?? $group['_gid'] ?? '');
$groupId = (string)($group['group_id'] ?? $group['_gid'] ?? '');
$category = (string)($group['category'] ?? 'group');
$title = $name !== '' ? $name : $groupId;
$updatedAt = (int)($group['updated_at_ms'] ?? $group['_dt'] ?? webImNowMs());
return [
'id' => 'group-' . $groupId,
'type' => $category === 'channel' ? 'channel' : 'group',
'target' => $groupId,
'title' => $title,
'subtitle' => $category === 'channel' ? '公告群' : '群组',
'unread' => 0,
'avatarText' => mb_strtoupper(mb_substr($title, 0, 1, 'UTF-8'), 'UTF-8'),
'updatedAt' => $updatedAt,
'role' => $role,
'raw' => [
'type' => 'group',
'target' => $groupId,
'line' => 0,
'category' => $category,
'role' => $role,
],
];
}
function fetchWebGroup(PDO $db, string $groupKey): ?array
{
$statement = $db->prepare(
'select * from `t_web_groups`'
. ' where `deleted` = 0 and (`group_id` = ? or `name` = ?)'
. ' order by (`group_id` = ?) desc limit 1'
);
$statement->execute([$groupKey, $groupKey, $groupKey]);
$group = $statement->fetch();
return is_array($group) ? $group : null;
}
function fetchWebGroupRole(PDO $db, string $groupId, string $userId): string
{
$statement = $db->prepare(
'select `role` from `t_web_group_members`'
. ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1'
);
$statement->execute([$groupId, $userId]);
$role = $statement->fetchColumn();
return is_string($role) && $role !== '' ? $role : 'member';
}
function fetchWebGroupMember(PDO $db, string $groupId, string $userId): array
{
$statement = $db->prepare(
'select * from `t_web_group_members`'
. ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1'
);
$statement->execute([$groupId, $userId]);
$member = $statement->fetch();
return is_array($member) ? $member : [];
}
function requireWebGroupMember(PDO $db, string $groupId, string $userId): array
{
$member = fetchWebGroupMember($db, $groupId, $userId);
if (!$member) {
respond(403, '你不在该群组中');
}
return $member;
}
function requireWebGroupRole(PDO $db, string $groupId, string $userId, array $roles): array
{
$member = requireWebGroupMember($db, $groupId, $userId);
if (!in_array((string)$member['role'], $roles, true)) {
respond(403, '没有群组管理权限');
}
return $member;
}
function upsertWebGroupMember(PDO $db, string $groupId, string $userId, string $role): void
{
$now = webImNowMs();
$statement = $db->prepare(
'insert into `t_web_group_members`'
. ' (`group_id`, `user_id`, `role`, `joined_at_ms`, `updated_at_ms`, `status`)'
. ' values (?, ?, ?, ?, ?, 0)'
. ' on duplicate key update'
. ' `role` = if(`role` = "owner", `role`, values(`role`)),'
. ' `updated_at_ms` = values(`updated_at_ms`),'
. ' `status` = 0'
);
$statement->execute([$groupId, $userId, $role, $now, $now]);
}
function createNativeGroup(string $operator, string $groupId, string $name, string $category, array $members): array
{
$nativeMembers = [];
foreach ($members as $memberId) {
$nativeMembers[] = [
'member_id' => $memberId,
'type' => $memberId === $operator ? 2 : 0,
];
}
return requireImSuccess(imAdminPost('/admin/group/create', [
'operator' => $operator,
'group' => [
'group_info' => [
'target_id' => $groupId,
'name' => $name,
'owner' => $operator,
'type' => 0,
'mute' => $category === 'channel' ? 1 : 0,
'join_type' => 0,
'searchable' => 1,
'history_message' => 1,
'extra' => json_encode(['webCategory' => $category], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
],
'members' => $nativeMembers,
],
'to_lines' => [0],
]), '原生群组创建失败');
}
function addNativeGroupMember(string $operator, string $groupId, string $userId): void
{
requireImSuccess(imAdminPost('/admin/group/member/add', [
'operator' => $operator,
'group_id' => $groupId,
'members' => [
['member_id' => $userId, 'type' => 0],
],
'to_lines' => [0],
]), '加入原生群组失败');
}
function removeNativeGroupMember(string $operator, string $groupId, array $userIds): void
{
if (!$userIds) {
return;
}
requireImSuccess(imAdminPost('/admin/group/member/del', [
'operator' => $operator,
'group_id' => $groupId,
'members' => array_values($userIds),
'to_lines' => [0],
]), '移除原生成员失败');
}
function dismissNativeGroup(string $operator, string $groupId): void
{
requireImSuccess(imAdminPost('/admin/group/del', [
'operator' => $operator,
'group_id' => $groupId,
'to_lines' => [0],
]), '解散原生群组失败');
}
function transferNativeGroup(string $operator, string $groupId, string $newOwner): void
{
requireImSuccess(imAdminPost('/admin/group/transfer', [
'operator' => $operator,
'group_id' => $groupId,
'new_owner' => $newOwner,
'to_lines' => [0],
]), '转让原生群组失败');
}
function sendWebGroupWelcomeMessage(PDO $appDb, string $groupId, string $senderId, string $welcomeText): void
{
$text = trim($welcomeText);
if ($text === '') {
return;
}
$now = webImNowMs();
$messageUid = webImMessageUid();
$statement = $appDb->prepare(
'insert into `t_web_group_messages`'
. ' (`message_uid`, `group_id`, `sender_uid`, `text`, `created_at_ms`, `edited_at_ms`, `recalled`)'
. ' values (?, ?, ?, ?, ?, 0, 0)'
);
$statement->execute([$messageUid, $groupId, $senderId, $text, $now]);
$update = $appDb->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?');
$update->execute([$now, $groupId]);
try {
requireImSuccess(imAdminPost('/admin/message/send', [
'sender' => $senderId,
'conv' => [
'type' => 1,
'target' => $groupId,
'line' => 0,
],
'payload' => [
'type' => 1,
'searchableContent' => $text,
'pushContent' => $text,
'content' => $text,
'persistFlag' => 3,
],
'userMessage' => true,
]), '欢迎词发送失败');
} catch (Throwable) {
}
}
function createWebGroupAction(PDO $imDb, PDO $appDb, string $userId, array $payload, string $category): array
{
$name = trim((string)($payload['name'] ?? ''));
$announcement = trim((string)($payload['announcement'] ?? ''));
$welcomeText = trim((string)($payload['welcomeText'] ?? ''));
if ($name === '') {
respond(400, $category === 'channel' ? '请输入公告群名称' : '请输入群组名称');
}
$memberIds = resolveWebUserIds($imDb, webActionList($payload['memberIds'] ?? []));
$memberIds[] = $userId;
$memberIds = array_values(array_unique(array_filter($memberIds)));
if ($category === 'group' && count($memberIds) < 2) {
respond(400, '请选择至少一位联系人');
}
$groupId = 'webg_' . bin2hex(random_bytes(8));
$now = webImNowMs();
$nativeStatus = 'pending';
$nativeGroupId = '';
$nativeError = '';
try {
$nativeResult = createNativeGroup($userId, $groupId, $name, $category, $memberIds);
$nativeGroupId = (string)($nativeResult['group_id'] ?? $nativeResult['groupId'] ?? $groupId);
$nativeStatus = 'ok';
} catch (Throwable $error) {
$nativeStatus = 'fallback';
$nativeError = mb_substr($error->getMessage() ?: '原生群组创建失败', 0, 255, 'UTF-8');
}
$statement = $appDb->prepare(
'insert into `t_web_groups`'
. ' (`group_id`, `category`, `name`, `portrait`, `owner_uid`, `join_mode`, `announcement`,'
. ' `welcome_text`, `native_group_id`, `native_status`, `native_error`, `created_by`, `created_at_ms`, `updated_at_ms`, `deleted`)'
. ' values (?, ?, ?, "", ?, "open", ?, ?, ?, ?, ?, ?, ?, ?, 0)'
);
$statement->execute([
$groupId,
$category,
$name,
$userId,
$announcement,
$welcomeText,
$nativeGroupId,
$nativeStatus,
$nativeError,
$userId,
$now,
$now,
]);
foreach ($memberIds as $memberId) {
upsertWebGroupMember($appDb, $groupId, $memberId, $memberId === $userId ? 'owner' : 'member');
}
sendWebGroupWelcomeMessage($appDb, $groupId, $userId, $welcomeText);
$group = fetchWebGroup($appDb, $groupId);
return [
'group' => $group,
'conversation' => webGroupConversation($group ?: [
'group_id' => $groupId,
'category' => $category,
'name' => $name,
'updated_at_ms' => $now,
], 'owner'),
'nativeStatus' => $nativeStatus,
'nativeError' => $nativeError,
];
}
function fetchWebUserMap(PDO $imDb, array $userIds, string $ownerId = ''): array
{
$userIds = webActionList($userIds);
if (!$userIds) {
return [];
}
$statement = $imDb->prepare(
'select `_uid`, `_name`, `_display_name`, `_mobile`, `_portrait` from `t_user`'
. ' where `_uid` in (' . appPlaceholders($userIds) . ')'
);
$statement->execute($userIds);
$users = [];
foreach ($statement->fetchAll() as $row) {
$uid = (string)$row['_uid'];
$displayName = trim((string)($row['_display_name'] ?? ''))
?: trim((string)($row['_name'] ?? ''))
?: trim((string)($row['_mobile'] ?? ''))
?: $uid;
$users[$uid] = [
'uid' => $uid,
'name' => (string)($row['_name'] ?? ''),
'displayName' => $displayName,
'mobile' => (string)($row['_mobile'] ?? ''),
'portrait' => (string)($row['_portrait'] ?? ''),
'contactAlias' => '',
'friendAlias' => '',
'remarkAlias' => '',
'isContact' => false,
];
}
if ($ownerId !== '') {
$aliasStatement = $imDb->prepare(
'select `_friend_uid`, `_alias` from `t_friend` where `_uid` = ?'
. ' and `_friend_uid` in (' . appPlaceholders($userIds) . ')'
);
$aliasStatement->execute(array_merge([$ownerId], $userIds));
foreach ($aliasStatement->fetchAll() as $row) {
$friendUid = (string)$row['_friend_uid'];
if (!isset($users[$friendUid])) {
continue;
}
$users[$friendUid]['isContact'] = true;
$users[$friendUid]['friendAlias'] = trim((string)($row['_alias'] ?? ''));
}
$remarkMap = fetchWebUserRemarkMap(imAppDb(), $ownerId, $userIds);
foreach ($remarkMap as $targetUid => $alias) {
if (!isset($users[$targetUid])) {
continue;
}
$users[$targetUid]['remarkAlias'] = $alias;
}
foreach ($users as $uid => $user) {
$friendAlias = trim((string)($user['friendAlias'] ?? ''));
$remarkAlias = trim((string)($user['remarkAlias'] ?? ''));
$users[$uid]['contactAlias'] = $friendAlias !== '' ? $friendAlias : $remarkAlias;
}
}
return $users;
}
function currentMuteSetting(PDO $appDb, string $groupId): array
{
$statement = $appDb->prepare('select * from `t_web_group_mute_rules` where `group_id` = ? limit 1');
$statement->execute([$groupId]);
$row = $statement->fetch();
if (!is_array($row)) {
return [
'mode' => 'off',
'scheduleType' => 'once',
'startsAtMs' => 0,
'endsAtMs' => 0,
'dailyStart' => '',
'dailyEnd' => '',
'enabled' => false,
];
}
return [
'mode' => (string)$row['mode'],
'scheduleType' => (string)$row['schedule_type'],
'startsAtMs' => (int)$row['starts_at_ms'],
'endsAtMs' => (int)$row['ends_at_ms'],
'dailyStart' => (string)$row['daily_start'],
'dailyEnd' => (string)$row['daily_end'],
'enabled' => (bool)$row['enabled'],
];
}
function groupDetailAction(PDO $imDb, PDO $appDb, string $userId, string $groupId): array
{
$group = fetchWebGroup($appDb, $groupId);
if (!$group) {
respond(404, '没有找到该群组');
}
$member = fetchWebGroupMember($appDb, $groupId, $userId);
if (!$member) {
respond(403, '你不在该群组中');
}
$ownerId = (string)$group['owner_uid'];
$users = fetchWebUserMap($imDb, [$ownerId, $userId], $userId);
$owner = $users[$ownerId] ?? ['uid' => $ownerId, 'displayName' => $ownerId, 'contactAlias' => ''];
$me = $users[$userId] ?? ['uid' => $userId, 'displayName' => $userId];
$myAlias = (string)($member['alias'] ?? '');
return [
'group' => [
'groupId' => (string)$group['group_id'],
'name' => (string)$group['name'],
'category' => (string)$group['category'],
'announcement' => (string)($group['announcement'] ?? ''),
'welcomeText' => (string)($group['welcome_text'] ?? ''),
'ownerUid' => $ownerId,
'ownerName' => (string)($owner['contactAlias'] ?? '') !== '' ? (string)$owner['contactAlias'] : (string)$owner['displayName'],
'myRole' => (string)$member['role'],
'myAlias' => $myAlias,
'myDisplayName' => $myAlias !== '' ? $myAlias : (string)$me['displayName'],
'muteSetting' => currentMuteSetting($appDb, (string)$group['group_id']),
],
];
}
function groupMembersAction(PDO $imDb, PDO $appDb, string $userId, string $groupId): array
{
if (!fetchWebGroupMember($appDb, $groupId, $userId)) {
respond(403, '你不在该群组中');
}
$statement = $appDb->prepare(
'select `user_id`, `role`, `alias`, `joined_at_ms` from `t_web_group_members`'
. ' where `group_id` = ? and `status` = 0 order by (`role` = "owner") desc, `joined_at_ms` asc'
);
$statement->execute([$groupId]);
$members = $statement->fetchAll();
$users = fetchWebUserMap($imDb, array_column($members, 'user_id'), $userId);
return [
'members' => array_map(static function (array $member) use ($users): array {
$uid = (string)$member['user_id'];
$user = $users[$uid] ?? [
'uid' => $uid,
'displayName' => $uid,
'mobile' => '',
'portrait' => '',
'contactAlias' => '',
'friendAlias' => '',
'remarkAlias' => '',
'isContact' => false,
];
$alias = trim((string)($member['alias'] ?? ''));
$contactAlias = trim((string)($user['contactAlias'] ?? ''));
return [
'uid' => $uid,
'role' => (string)$member['role'],
'alias' => $alias,
'contactAlias' => $contactAlias,
'friendAlias' => trim((string)($user['friendAlias'] ?? '')),
'remarkAlias' => trim((string)($user['remarkAlias'] ?? '')),
'isContact' => (bool)($user['isContact'] ?? false),
'displayName' => $contactAlias !== '' ? $contactAlias : ($alias !== '' ? $alias : (string)$user['displayName']),
'rawDisplayName' => (string)$user['displayName'],
'groupDisplayName' => $alias,
'mobile' => (string)($user['mobile'] ?? ''),
'portrait' => (string)($user['portrait'] ?? ''),
];
}, $members),
];
}
function updateGroupAliasAction(PDO $appDb, string $userId, string $groupId, string $alias): array
{
if (mb_strlen($alias, 'UTF-8') > 64) {
respond(400, '本群昵称过长');
}
if (!fetchWebGroupMember($appDb, $groupId, $userId)) {
respond(403, '你不在该群组中');
}
$now = webImNowMs();
$statement = $appDb->prepare(
'update `t_web_group_members` set `alias` = ?, `updated_at_ms` = ?'
. ' where `group_id` = ? and `user_id` = ? and `status` = 0'
);
$statement->execute([$alias, $now, $groupId, $userId]);
return ['alias' => $alias];
}
function leaveGroupAction(PDO $appDb, string $userId, string $groupId): array
{
$group = fetchWebGroup($appDb, $groupId);
if (!$group) {
respond(404, '没有找到该群组');
}
$member = fetchWebGroupMember($appDb, $groupId, $userId);
if (!$member) {
respond(403, '你不在该群组中');
}
$now = webImNowMs();
$statement = $appDb->prepare(
'update `t_web_group_members` set `status` = 1, `updated_at_ms` = ?'
. ' where `group_id` = ? and `user_id` = ?'
);
$statement->execute([$now, $groupId, $userId]);
$groupUpdate = $appDb->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?');
$groupUpdate->execute([$now, $groupId]);
try {
requireImSuccess(imAdminPost('/admin/group/member/quit', [
'operator' => $userId,
'group_id' => $groupId,
'to_lines' => [0],
]), '退出原生群组失败');
} catch (Throwable) {
}
return ['groupId' => $groupId];
}
function dismissGroupAction(PDO $appDb, string $userId, string $groupId): array
{
$group = fetchWebGroup($appDb, $groupId);
if (!$group) {
respond(404, '没有找到该群组');
}
requireWebGroupRole($appDb, $groupId, $userId, ['owner']);
$now = webImNowMs();
$update = $appDb->prepare('update `t_web_groups` set `deleted` = 1, `updated_at_ms` = ? where `group_id` = ?');
$update->execute([$now, $groupId]);
$memberUpdate = $appDb->prepare('update `t_web_group_members` set `status` = 1, `updated_at_ms` = ? where `group_id` = ?');
$memberUpdate->execute([$now, $groupId]);
try {
dismissNativeGroup($userId, $groupId);
} catch (Throwable) {
}
return ['groupId' => $groupId];
}
function transferGroupAction(PDO $appDb, string $userId, string $groupId, string $targetUserId): array
{
if ($targetUserId === '' || $targetUserId === $userId) {
respond(400, '请选择新的群主');
}
$group = fetchWebGroup($appDb, $groupId);
if (!$group) {
respond(404, '没有找到该群组');
}
requireWebGroupRole($appDb, $groupId, $userId, ['owner']);
$target = requireWebGroupMember($appDb, $groupId, $targetUserId);
if ((string)$target['role'] === 'owner') {
respond(400, '该成员已经是群主');
}
$now = webImNowMs();
$appDb->beginTransaction();
try {
$groupUpdate = $appDb->prepare('update `t_web_groups` set `owner_uid` = ?, `updated_at_ms` = ? where `group_id` = ?');
$groupUpdate->execute([$targetUserId, $now, $groupId]);
$oldOwnerUpdate = $appDb->prepare(
'update `t_web_group_members` set `role` = "member", `updated_at_ms` = ?'
. ' where `group_id` = ? and `user_id` = ?'
);
$oldOwnerUpdate->execute([$now, $groupId, $userId]);
$newOwnerUpdate = $appDb->prepare(
'update `t_web_group_members` set `role` = "owner", `updated_at_ms` = ?'
. ' where `group_id` = ? and `user_id` = ?'
);
$newOwnerUpdate->execute([$now, $groupId, $targetUserId]);
$appDb->commit();
} catch (Throwable $error) {
$appDb->rollBack();
throw $error;
}
try {
transferNativeGroup($userId, $groupId, $targetUserId);
} catch (Throwable) {
}
return ['groupId' => $groupId, 'newOwner' => $targetUserId];
}
function setGroupAdminsAction(PDO $appDb, string $userId, string $groupId, array $adminIds): array
{
requireWebGroupRole($appDb, $groupId, $userId, ['owner']);
$adminIds = array_values(array_unique(array_filter(array_map('strval', $adminIds), static fn(string $uid): bool => $uid !== $userId)));
$now = webImNowMs();
$reset = $appDb->prepare(
'update `t_web_group_members` set `role` = "member", `updated_at_ms` = ?'
. ' where `group_id` = ? and `role` = "admin" and `status` = 0'
);
$reset->execute([$now, $groupId]);
if ($adminIds) {
$update = $appDb->prepare(
'update `t_web_group_members` set `role` = "admin", `updated_at_ms` = ?'
. ' where `group_id` = ? and `user_id` in (' . appPlaceholders($adminIds) . ')'
. ' and `role` <> "owner" and `status` = 0'
);
$update->execute(array_merge([$now, $groupId], $adminIds));
}
return ['adminIds' => $adminIds];
}
function updateGroupProfileAction(PDO $appDb, string $userId, string $groupId, array $payload): array
{
requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']);
$name = trim((string)($payload['name'] ?? ''));
$announcement = trim((string)($payload['announcement'] ?? ''));
$welcomeText = trim((string)($payload['welcomeText'] ?? ''));
if ($name === '') {
respond(400, '请输入群名称');
}
if (mb_strlen($name, 'UTF-8') > 128) {
respond(400, '群名称过长');
}
$now = webImNowMs();
$statement = $appDb->prepare(
'update `t_web_groups` set `name` = ?, `announcement` = ?, `welcome_text` = ?, `updated_at_ms` = ?'
. ' where `group_id` = ? and `deleted` = 0'
);
$statement->execute([$name, $announcement, $welcomeText, $now, $groupId]);
return [
'groupId' => $groupId,
'name' => $name,
'announcement' => $announcement,
'welcomeText' => $welcomeText,
'updatedAt' => $now,
];
}
function kickGroupMembersAction(PDO $appDb, string $userId, string $groupId, array $targetIds): array
{
$operator = requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']);
$operatorRole = (string)$operator['role'];
$targetIds = array_values(array_unique(array_filter(array_map('strval', $targetIds), static fn(string $uid): bool => $uid !== '')));
$targetIds = array_values(array_filter($targetIds, static fn(string $uid): bool => $uid !== $userId));
if (!$targetIds) {
respond(400, '请选择要移除的群员');
}
$statement = $appDb->prepare(
'select `user_id`, `role` from `t_web_group_members`'
. ' where `group_id` = ? and `user_id` in (' . appPlaceholders($targetIds) . ') and `status` = 0'
);
$statement->execute(array_merge([$groupId], $targetIds));
$targets = $statement->fetchAll();
if (!$targets) {
respond(400, '没有可移除的群员');
}
$allowedIds = [];
foreach ($targets as $target) {
$role = (string)$target['role'];
if ($role === 'owner') {
continue;
}
if ($operatorRole === 'admin' && $role !== 'member') {
continue;
}
$allowedIds[] = (string)$target['user_id'];
}
if (!$allowedIds) {
respond(403, '没有可移除的群员');
}
$now = webImNowMs();
$update = $appDb->prepare(
'update `t_web_group_members` set `status` = 2, `updated_at_ms` = ?'
. ' where `group_id` = ? and `user_id` in (' . appPlaceholders($allowedIds) . ')'
);
$update->execute(array_merge([$now, $groupId], $allowedIds));
try {
removeNativeGroupMember($userId, $groupId, $allowedIds);
} catch (Throwable) {
}
return ['removedIds' => $allowedIds];
}
function updateMuteSettingAction(PDO $appDb, string $userId, string $groupId, array $payload): array
{
requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']);
$mode = trim((string)($payload['mode'] ?? 'off'));
$allowedModes = ['off', 'all_include_admins', 'all_except_admins'];
if (!in_array($mode, $allowedModes, true)) {
respond(400, '禁言模式不正确');
}
$scheduleType = trim((string)($payload['scheduleType'] ?? 'once'));
if (!in_array($scheduleType, ['manual', 'once', 'daily'], true)) {
respond(400, '禁言时段类型不正确');
}
$startsAtMs = max(0, (int)($payload['startsAtMs'] ?? 0));
$endsAtMs = max(0, (int)($payload['endsAtMs'] ?? 0));
$dailyStart = trim((string)($payload['dailyStart'] ?? ''));
$dailyEnd = trim((string)($payload['dailyEnd'] ?? ''));
$enabled = $mode !== 'off';
if ($enabled && $scheduleType === 'once' && ($startsAtMs <= 0 || $endsAtMs <= $startsAtMs)) {
respond(400, '请设置有效的单次禁言时段');
}
if ($enabled && $scheduleType === 'daily' && (!preg_match('/^\d{2}:\d{2}$/', $dailyStart) || !preg_match('/^\d{2}:\d{2}$/', $dailyEnd))) {
respond(400, '请设置有效的每日禁言时段');
}
$now = webImNowMs();
$statement = $appDb->prepare(
'insert into `t_web_group_mute_rules`'
. ' (`group_id`, `mode`, `schedule_type`, `starts_at_ms`, `ends_at_ms`, `daily_start`, `daily_end`, `enabled`, `created_by`, `updated_at_ms`)'
. ' values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
. ' on duplicate key update'
. ' `mode` = values(`mode`),'
. ' `schedule_type` = values(`schedule_type`),'
. ' `starts_at_ms` = values(`starts_at_ms`),'
. ' `ends_at_ms` = values(`ends_at_ms`),'
. ' `daily_start` = values(`daily_start`),'
. ' `daily_end` = values(`daily_end`),'
. ' `enabled` = values(`enabled`),'
. ' `created_by` = values(`created_by`),'
. ' `updated_at_ms` = values(`updated_at_ms`)'
);
$statement->execute([$groupId, $mode, $scheduleType, $startsAtMs, $endsAtMs, $dailyStart, $dailyEnd, $enabled ? 1 : 0, $userId, $now]);
return ['muteSetting' => currentMuteSetting($appDb, $groupId)];
}
function joinWebGroupAction(PDO $appDb, string $userId, string $groupKey): array
{
if ($groupKey === '') {
respond(400, '请输入群组ID或群名称');
}
$group = fetchWebGroup($appDb, $groupKey);
if (!$group) {
try {
$native = requireImSuccess(imAdminPost('/admin/group/get_info', ['groupId' => $groupKey]), '群组不存在');
$now = webImNowMs();
$statement = $appDb->prepare(
'insert into `t_web_groups`'
. ' (`group_id`, `category`, `name`, `portrait`, `owner_uid`, `join_mode`, `announcement`,'
. ' `native_group_id`, `native_status`, `created_by`, `created_at_ms`, `updated_at_ms`, `deleted`)'
. ' values (?, "group", ?, ?, ?, "open", "", ?, "ok", ?, ?, ?, 0)'
. ' on duplicate key update `updated_at_ms` = values(`updated_at_ms`)'
);
$statement->execute([
$groupKey,
(string)($native['name'] ?? $groupKey),
(string)($native['portrait'] ?? ''),
(string)($native['owner'] ?? ''),
$groupKey,
$userId,
$now,
$now,
]);
$group = fetchWebGroup($appDb, $groupKey);
} catch (Throwable) {
respond(404, '没有找到该群组');
}
}
$groupId = (string)$group['group_id'];
upsertWebGroupMember($appDb, $groupId, $userId, 'member');
try {
addNativeGroupMember((string)($group['owner_uid'] ?: $userId), $groupId, $userId);
} catch (Throwable $error) {
// Web membership is authoritative for this web build; native group sync is best-effort.
}
$role = fetchWebGroupRole($appDb, $groupId, $userId);
return [
'group' => fetchWebGroup($appDb, $groupId),
'conversation' => webGroupConversation($group, $role),
];
}
function sendBroadcastAction(PDO $imDb, PDO $appDb, string $userId, array $payload): array
{
$text = trim((string)($payload['text'] ?? ''));
if ($text === '') {
respond(400, '请输入群发内容');
}
$targetIds = resolveWebUserIds($imDb, webActionList($payload['targetIds'] ?? []));
$targetIds = array_values(array_filter(array_unique($targetIds), static fn(string $uid): bool => $uid !== $userId));
if (!$targetIds) {
respond(400, '请选择群发对象');
}
$results = [];
foreach ($targetIds as $targetId) {
try {
$result = requireImSuccess(imAdminPost('/admin/message/send', [
'sender' => $userId,
'conv' => [
'type' => 0,
'target' => $targetId,
'line' => 0,
],
'payload' => [
'type' => 1,
'searchableContent' => $text,
'pushContent' => $text,
'content' => $text,
'persistFlag' => 3,
],
'userMessage' => true,
]), '发送失败');
$results[] = [
'targetId' => $targetId,
'ok' => true,
'messageUid' => isset($result['messageUid']) ? (string)$result['messageUid'] : '',
];
} catch (Throwable $error) {
$results[] = [
'targetId' => $targetId,
'ok' => false,
'error' => $error->getMessage() ?: '发送失败',
];
}
}
$successCount = count(array_filter($results, static fn(array $item): bool => !empty($item['ok'])));
$failedCount = count($results) - $successCount;
$now = webImNowMs();
$statement = $appDb->prepare(
'insert into `t_web_broadcast_logs`'
. ' (`sender_uid`, `target_count`, `success_count`, `failed_count`, `text`, `target_json`, `result_json`, `created_at_ms`)'
. ' values (?, ?, ?, ?, ?, ?, ?, ?)'
);
$statement->execute([
$userId,
count($targetIds),
$successCount,
$failedCount,
$text,
json_encode($targetIds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
$now,
]);
return [
'targetCount' => count($targetIds),
'successCount' => $successCount,
'failedCount' => $failedCount,
'results' => $results,
];
}
$payload = readJsonPayload();
$action = trim((string)($payload['action'] ?? ''));
$userId = trim((string)($payload['userId'] ?? ''));
if ($userId === '') {
respond(400, '缺少当前用户ID');
}
try {
ensureWebImAppTables();
$imDb = imDb();
$appDb = imAppDb();
if ($action === 'create_group') {
respond(0, 'success', createWebGroupAction($imDb, $appDb, $userId, $payload, 'group'));
}
if ($action === 'create_channel') {
respond(0, 'success', createWebGroupAction($imDb, $appDb, $userId, $payload, 'channel'));
}
if ($action === 'join_group') {
respond(0, 'success', joinWebGroupAction($appDb, $userId, trim((string)($payload['groupKey'] ?? ''))));
}
if ($action === 'group_detail') {
respond(0, 'success', groupDetailAction($imDb, $appDb, $userId, trim((string)($payload['groupId'] ?? ''))));
}
if ($action === 'group_members') {
respond(0, 'success', groupMembersAction($imDb, $appDb, $userId, trim((string)($payload['groupId'] ?? ''))));
}
if ($action === 'update_group_alias') {
respond(0, 'success', updateGroupAliasAction(
$appDb,
$userId,
trim((string)($payload['groupId'] ?? '')),
trim((string)($payload['alias'] ?? ''))
));
}
if ($action === 'leave_group') {
respond(0, 'success', leaveGroupAction($appDb, $userId, trim((string)($payload['groupId'] ?? ''))));
}
if ($action === 'dismiss_group') {
respond(0, 'success', dismissGroupAction($appDb, $userId, trim((string)($payload['groupId'] ?? ''))));
}
if ($action === 'transfer_group') {
respond(0, 'success', transferGroupAction(
$appDb,
$userId,
trim((string)($payload['groupId'] ?? '')),
trim((string)($payload['targetUserId'] ?? ''))
));
}
if ($action === 'set_group_admins') {
respond(0, 'success', setGroupAdminsAction(
$appDb,
$userId,
trim((string)($payload['groupId'] ?? '')),
is_array($payload['adminIds'] ?? null) ? $payload['adminIds'] : []
));
}
if ($action === 'update_group_profile') {
respond(0, 'success', updateGroupProfileAction(
$appDb,
$userId,
trim((string)($payload['groupId'] ?? '')),
$payload
));
}
if ($action === 'kick_group_members') {
respond(0, 'success', kickGroupMembersAction(
$appDb,
$userId,
trim((string)($payload['groupId'] ?? '')),
is_array($payload['targetIds'] ?? null) ? $payload['targetIds'] : []
));
}
if ($action === 'update_mute_setting') {
respond(0, 'success', updateMuteSettingAction(
$appDb,
$userId,
trim((string)($payload['groupId'] ?? '')),
$payload
));
}
if ($action === 'broadcast') {
$result = sendBroadcastAction($imDb, $appDb, $userId, $payload);
if ((int)$result['successCount'] <= 0) {
respond(500, '群发失败', $result);
}
respond(0, 'success', $result);
}
respond(400, '未知操作');
} catch (Throwable $error) {
respond(500, $error->getMessage() ?: '会话操作失败');
}