diff --git a/api_v1_channel_permissions.php b/api_v1_channel_permissions.php index a6ce976..b09871c 100644 --- a/api_v1_channel_permissions.php +++ b/api_v1_channel_permissions.php @@ -27,15 +27,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $everyone_role_id = $everyone['id'] ?? 0; } - // Fetch permissions for this channel + // Fetch permissions for this channel (roles and users) $stmt = db()->prepare(" - SELECT cp.*, r.name as role_name, r.color as role_color + SELECT cp.*, r.name as role_name, r.color as role_color, + u.display_name as member_name, u.avatar_url as member_avatar FROM channel_permissions cp - JOIN roles r ON cp.role_id = r.id + LEFT JOIN roles r ON cp.role_id = r.id + LEFT JOIN users u ON cp.user_id = u.id WHERE cp.channel_id = ? "); $stmt->execute([$channel_id]); - $permissions = $stmt->fetchAll(); + $permissions = []; + while($row = $stmt->fetch()) { + if ($row['user_id']) { + $row['display_name'] = $row['member_name'] ?? 'Unknown Member'; + $row['type'] = 'member'; + } else { + $row['display_name'] = $row['role_name'] ?? 'Unknown Role'; + $row['type'] = 'role'; + } + $permissions[] = $row; + } // Check if @everyone is in permissions, if not add it manually to show up by default $has_everyone = false; @@ -54,10 +66,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { array_unshift($permissions, [ 'channel_id' => (int)$channel_id, 'role_id' => (int)$everyone_role_id, + 'user_id' => null, 'allow_permissions' => 0, 'deny_permissions' => 0, 'role_name' => $r['name'], - 'role_color' => $r['color'] + 'role_color' => $r['color'], + 'display_name' => $r['name'], + 'type' => 'role' ]); } } @@ -68,10 +83,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $channel_id = $data['channel_id'] ?? 0; - $role_id = $data['role_id'] ?? 0; + $role_id = $data['role_id'] ?? null; + $target_user_id = $data['user_id'] ?? null; $allow = $data['allow'] ?? 0; $deny = $data['deny'] ?? 0; + if (!$role_id && !$target_user_id) { + echo json_encode(['success' => false, 'error' => 'Missing role_id or user_id']); + exit; + } + // Check permissions: Owner or MANAGE_CHANNELS or ADMINISTRATOR require_once 'includes/permissions.php'; $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); @@ -89,11 +110,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($is_owner || $can_manage) { $stmt = db()->prepare(" - INSERT INTO channel_permissions (channel_id, role_id, allow_permissions, deny_permissions) - VALUES (?, ?, ?, ?) + INSERT INTO channel_permissions (channel_id, role_id, user_id, allow_permissions, deny_permissions) + VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE allow_permissions = VALUES(allow_permissions), deny_permissions = VALUES(deny_permissions) "); - $stmt->execute([$channel_id, $role_id, $allow, $deny]); + $stmt->execute([$channel_id, $role_id, $target_user_id, $allow, $deny]); echo json_encode(['success' => true]); } else { echo json_encode(['success' => false, 'error' => 'Unauthorized']); @@ -103,7 +124,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { $channel_id = $data['channel_id'] ?? 0; - $role_id = $data['role_id'] ?? 0; + $role_id = $data['role_id'] ?? null; + $target_user_id = $data['user_id'] ?? null; // Check permissions require_once 'includes/permissions.php'; @@ -121,8 +143,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { Permissions::hasPermission($user_id, $server_id, Permissions::ADMINISTRATOR); if ($is_owner || $can_manage) { - $stmt = db()->prepare("DELETE FROM channel_permissions WHERE channel_id = ? AND role_id = ?"); - $stmt->execute([$channel_id, $role_id]); + if ($role_id !== null) { + $stmt = db()->prepare("DELETE FROM channel_permissions WHERE channel_id = ? AND role_id = ? AND user_id IS NULL"); + $stmt->execute([$channel_id, $role_id]); + } else if ($target_user_id !== null) { + $stmt = db()->prepare("DELETE FROM channel_permissions WHERE channel_id = ? AND user_id = ? AND role_id IS NULL"); + $stmt->execute([$channel_id, $target_user_id]); + } echo json_encode(['success' => true]); } else { echo json_encode(['success' => false, 'error' => 'Unauthorized']); diff --git a/assets/js/main.js b/assets/js/main.js index e26e253..93261ba 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1069,6 +1069,20 @@ document.addEventListener('DOMContentLoaded', () => { await loadRolesForPermissions(channelId); }); + const searchChannelPerms = document.getElementById('search-channel-perms'); + searchChannelPerms?.addEventListener('input', () => { + const query = searchChannelPerms.value.toLowerCase(); + const items = channelPermissionsRolesList.querySelectorAll('.list-group-item'); + items.forEach(item => { + const name = item.textContent.toLowerCase(); + if (name.includes(query)) { + item.classList.remove('d-none'); + } else { + item.classList.add('d-none'); + } + }); + }); + async function loadChannelPermissions(channelId) { channelPermissionsRolesList.innerHTML = '
Loading...
'; const resp = await fetch(`api_v1_channel_permissions.php?channel_id=${channelId}`); @@ -1081,7 +1095,7 @@ document.addEventListener('DOMContentLoaded', () => { async function loadRolesForPermissions(channelId) { if (!addPermRoleList) return; - addPermRoleList.innerHTML = '
  • Loading roles...
  • '; + addPermRoleList.innerHTML = '
  • Loading...
  • '; try { const resp = await fetch(`api_v1_roles.php?server_id=${activeServerId}`); @@ -1090,57 +1104,73 @@ document.addEventListener('DOMContentLoaded', () => { if (data.success) { addPermRoleList.innerHTML = ''; - // Filter out roles already in overrides - const existingRoleIds = channelPermissionsData.map(p => parseInt(p.role_id)); + // Roles Section + const existingRoleIds = channelPermissionsData.filter(p => p.type === 'role').map(p => parseInt(p.role_id)); const availableRoles = data.roles.filter(role => !existingRoleIds.includes(parseInt(role.id))); - if (availableRoles.length === 0) { - addPermRoleList.innerHTML = '
  • No more roles to add
  • '; - if (window.canManageServer) { - const divider = document.createElement('li'); - divider.innerHTML = ''; - addPermRoleList.appendChild(divider); - const createLink = document.createElement('li'); - createLink.innerHTML = ' Create roles in Server Settings'; - addPermRoleList.appendChild(createLink); - } - return; + if (availableRoles.length > 0) { + const header = document.createElement('li'); + header.innerHTML = ''; + addPermRoleList.appendChild(header); + + availableRoles.forEach(role => { + const li = document.createElement('li'); + li.innerHTML = ` +
    + ${role.name} +
    `; + li.onclick = async (e) => { + e.preventDefault(); + const postResp = await fetch('api_v1_channel_permissions.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ channel_id: channelId, role_id: role.id, allow: 0, deny: 0 }) + }); + const postData = await postResp.json(); + if (postData.success) { + await loadChannelPermissions(channelId); + await loadRolesForPermissions(channelId); + selectOverrideItem(role.id, role.name, 'role'); + } + }; + addPermRoleList.appendChild(li); + }); } - // Add Roles section - const header = document.createElement('li'); - header.innerHTML = ''; - addPermRoleList.appendChild(header); + // Members Section + const existingUserIds = channelPermissionsData.filter(p => p.type === 'member').map(p => parseInt(p.user_id)); + const availableMembers = data.members.filter(m => !existingUserIds.includes(parseInt(m.id))); - availableRoles.forEach(role => { - const li = document.createElement('li'); - li.innerHTML = ` -
    - ${role.name} -
    `; - li.onclick = async (e) => { - e.preventDefault(); - const postResp = await fetch('api_v1_channel_permissions.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ channel_id: channelId, role_id: role.id, allow: 0, deny: 0 }) - }); - const postData = await postResp.json(); - if (postData.success) { - await loadChannelPermissions(channelId); - await loadRolesForPermissions(channelId); - selectOverrideRole(role.id, role.name); - } else { - alert("Error adding permission: " + (postData.error || "Unknown error")); - } - }; - addPermRoleList.appendChild(li); - }); - } else { - addPermRoleList.innerHTML = `
  • Error: ${data.error || 'Failed to load'}
  • `; + if (availableMembers.length > 0) { + const header = document.createElement('li'); + header.innerHTML = ''; + addPermRoleList.appendChild(header); + + availableMembers.forEach(m => { + const li = document.createElement('li'); + li.innerHTML = ` + + ${m.username} + `; + li.onclick = async (e) => { + e.preventDefault(); + const postResp = await fetch('api_v1_channel_permissions.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ channel_id: channelId, user_id: m.id, allow: 0, deny: 0 }) + }); + const postData = await postResp.json(); + if (postData.success) { + await loadChannelPermissions(channelId); + await loadRolesForPermissions(channelId); + selectOverrideItem(m.id, m.username, 'member'); + } + }; + addPermRoleList.appendChild(li); + }); + } } } catch (err) { - addPermRoleList.innerHTML = '
  • Network error
  • '; console.error(err); } } @@ -1148,14 +1178,14 @@ document.addEventListener('DOMContentLoaded', () => { function renderRoleOverridesList(channelId) { channelPermissionsRolesList.innerHTML = ''; if (channelPermissionsData.length === 0) { - channelPermissionsRolesList.innerHTML = '
    No overrides configured for this channel.
    '; + channelPermissionsRolesList.innerHTML = '
    No overrides configured.
    '; return; } - // Sort: @everyone always at top, then by name const sortedData = [...channelPermissionsData].sort((a, b) => { - const nameA = (a.role_name || '').toLowerCase(); - const nameB = (b.role_name || '').toLowerCase(); + if (a.type !== b.type) return a.type === 'role' ? -1 : 1; + const nameA = (a.display_name || '').toLowerCase(); + const nameB = (b.display_name || '').toLowerCase(); const isAEveryone = nameA.includes('everyone'); const isBEveryone = nameB.includes('everyone'); if (isAEveryone && !isBEveryone) return -1; @@ -1165,34 +1195,46 @@ document.addEventListener('DOMContentLoaded', () => { sortedData.forEach(p => { const item = document.createElement('div'); - item.className = `list-group-item list-group-item-action bg-transparent text-white border-0 mb-1 p-2 small d-flex align-items-center ${currentSelectedOverrideRole == p.role_id ? 'active' : ''}`; + const isActive = currentSelectedOverrideRole == (p.type === 'role' ? p.role_id : p.user_id) && currentSelectedOverrideType === p.type; + item.className = `list-group-item list-group-item-action bg-transparent text-white border-0 mb-1 p-2 small d-flex align-items-center ${isActive ? 'active' : ''}`; item.style.cursor = 'pointer'; + + let icon = ''; + if (p.type === 'role') { + icon = `
    `; + } else { + icon = ``; + } + item.innerHTML = ` -
    - ${p.role_name || 'Unknown Role'} + ${icon} + ${p.display_name} `; - item.onclick = () => selectOverrideRole(p.role_id, p.role_name || 'Unknown Role'); + item.onclick = () => selectOverrideItem(p.type === 'role' ? p.role_id : p.user_id, p.display_name, p.type); channelPermissionsRolesList.appendChild(item); }); } - function selectOverrideRole(roleId, roleName) { - currentSelectedOverrideRole = roleId; + let currentSelectedOverrideType = 'role'; + + function selectOverrideItem(id, name, type) { + currentSelectedOverrideRole = id; + currentSelectedOverrideType = type; const channelId = document.getElementById('edit-channel-id').value; - // Update list active state renderRoleOverridesList(channelId); - selectedPermRoleName.textContent = roleName; + selectedPermRoleName.textContent = name; noRoleSelectedView.classList.add('d-none'); channelPermissionsSettings.classList.remove('d-none'); - // Load existing permissions for this role - const p = channelPermissionsData.find(perm => perm.role_id == roleId) || { allow_permissions: 0, deny_permissions: 0 }; + const p = channelPermissionsData.find(perm => { + if (type === 'role') return perm.role_id == id && perm.type === 'role'; + return perm.user_id == id && perm.type === 'member'; + }) || { allow_permissions: 0, deny_permissions: 0 }; - // Update toggles - updateToggleUI(1, p.allow_permissions, p.deny_permissions); // View Channel - updateToggleUI(2, p.allow_permissions, p.deny_permissions); // Send Messages + updateToggleUI(1, p.allow_permissions, p.deny_permissions); + updateToggleUI(2, p.allow_permissions, p.deny_permissions); } function updateToggleUI(bit, allowPerms, denyPerms) { @@ -1212,10 +1254,17 @@ document.addEventListener('DOMContentLoaded', () => { if (!currentSelectedOverrideRole) return; const channelId = document.getElementById('edit-channel-id').value; + const payload = { channel_id: channelId }; + if (currentSelectedOverrideType === 'role') { + payload.role_id = currentSelectedOverrideRole; + } else { + payload.user_id = currentSelectedOverrideRole; + } + await fetch('api_v1_channel_permissions.php', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ channel_id: channelId, role_id: currentSelectedOverrideRole }) + body: JSON.stringify(payload) }); currentSelectedOverrideRole = null; @@ -1230,11 +1279,17 @@ document.addEventListener('DOMContentLoaded', () => { const bit = parseInt(group.dataset.permBit); const val = e.target.value; const channelId = document.getElementById('edit-channel-id').value; - const roleId = currentSelectedOverrideRole; + const id = currentSelectedOverrideRole; + const type = currentSelectedOverrideType; - let p = channelPermissionsData.find(perm => perm.role_id == roleId); + let p = channelPermissionsData.find(perm => { + if (type === 'role') return perm.role_id == id && perm.type === 'role'; + return perm.user_id == id && perm.type === 'member'; + }); + if (!p) { - p = { role_id: roleId, allow_permissions: 0, deny_permissions: 0 }; + p = { channel_id: channelId, allow_permissions: 0, deny_permissions: 0 }; + if (type === 'role') p.role_id = id; else p.user_id = id; } let allow = parseInt(p.allow_permissions); @@ -1247,10 +1302,13 @@ document.addEventListener('DOMContentLoaded', () => { if (val === 'allow') allow |= bit; if (val === 'deny') deny |= bit; + const payload = { channel_id: channelId, allow, deny }; + if (type === 'role') payload.role_id = id; else payload.user_id = id; + await fetch('api_v1_channel_permissions.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ channel_id: channelId, role_id: roleId, allow, deny }) + body: JSON.stringify(payload) }); // Update local data diff --git a/assets/pasted-20260218-160633-6ce717d1.png b/assets/pasted-20260218-160633-6ce717d1.png new file mode 100644 index 0000000..ddd4797 Binary files /dev/null and b/assets/pasted-20260218-160633-6ce717d1.png differ diff --git a/data/22.participants.json b/data/22.participants.json index 3caf8fb..4cd408b 100644 --- a/data/22.participants.json +++ b/data/22.participants.json @@ -1 +1 @@ -{"de65a0b0b1a29c9a":{"id":"de65a0b0b1a29c9a","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771343410040}} \ No newline at end of file +{"b2dca0f015f8373d":{"id":"b2dca0f015f8373d","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771431264239}} \ No newline at end of file diff --git a/data/3.participants.json b/data/3.participants.json index 486a3e1..485c217 100644 --- a/data/3.participants.json +++ b/data/3.participants.json @@ -1 +1 @@ -{"920464469dc771ed":{"id":"920464469dc771ed","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771343410598}} \ No newline at end of file +{"42293ef053eb80b0":{"id":"42293ef053eb80b0","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771431325495}} \ No newline at end of file diff --git a/index.php b/index.php index 3576530..9135172 100644 --- a/index.php +++ b/index.php @@ -1999,14 +1999,20 @@ async function handleSaveUserSettings(btn) {
    Roles / Members - +
    + +
    + +
    +
    +
    diff --git a/requests.log b/requests.log index d8b5db0..1f22491 100644 --- a/requests.log +++ b/requests.log @@ -632,3 +632,10 @@ {"date":"2026-02-17 15:47:36","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true} {"date":"2026-02-17 15:48:00","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true} {"date":"2026-02-17 15:48:41","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true} +2026-02-18 16:03:28 - GET /?fl_project=38443 - POST: [] +2026-02-18 16:03:33 - GET / - POST: [] +2026-02-18 16:03:54 - GET /index.php?server_id=1&channel_id=3 - POST: [] +2026-02-18 16:04:41 - GET /?server_id=1&channel_id=18 - POST: [] +2026-02-18 16:04:48 - GET /?server_id=1&channel_id=18 - POST: [] +2026-02-18 16:12:25 - GET /?fl_project=38443 - POST: [] +2026-02-18 16:14:55 - GET /?server_id=1&channel_id=18 - POST: []