38443-vm/index.php
Flatlogic Bot 1e73419ffb v6
2026-02-15 11:24:55 +00:00

828 lines
55 KiB
PHP

<?php
require_once 'auth/session.php';
requireLogin();
$user = getCurrentUser();
$current_user_id = $user['id'];
// Fetch servers user is member of
$stmt = db()->prepare("
SELECT s.* FROM servers s
JOIN server_members sm ON s.id = sm.server_id
WHERE sm.user_id = ?
LIMIT 20
");
$stmt->execute([$current_user_id]);
$servers = $stmt->fetchAll();
$is_dm_view = (isset($_GET['server_id']) && $_GET['server_id'] == 'dms') || !isset($_GET['server_id']) && empty($servers);
if ($is_dm_view) {
$active_server_id = 'dms';
// Fetch DM channels
$stmt = db()->prepare("
SELECT c.id, u.username as other_user, u.avatar_url, u.status, u.id as other_user_id
FROM channels c
JOIN channel_members cm1 ON c.id = cm1.channel_id
JOIN channel_members cm2 ON c.id = cm2.channel_id
JOIN users u ON cm2.user_id = u.id
WHERE c.type = 'dm' AND cm1.user_id = ? AND cm2.user_id != ?
");
$stmt->execute([$current_user_id, $current_user_id]);
$dm_channels = $stmt->fetchAll();
$active_channel_id = $_GET['channel_id'] ?? ($dm_channels[0]['id'] ?? 0);
$channel_theme = null; // DMs don't have custom themes for now
if ($active_channel_id) {
// Fetch DM messages
$stmt = db()->prepare("
SELECT m.*, u.username, u.avatar_url
FROM messages m
JOIN users u ON m.user_id = u.id
WHERE m.channel_id = ?
ORDER BY m.created_at ASC
LIMIT 50
");
$stmt->execute([$active_channel_id]);
$messages = $stmt->fetchAll();
$current_channel_name = 'Direct Message';
foreach($dm_channels as $dm) {
if ($dm['id'] == $active_channel_id) {
$current_channel_name = $dm['other_user'];
break;
}
}
} else {
$messages = [];
$current_channel_name = 'Direct Messages';
}
$channels = [];
$members = []; // Members list is different for DMs or hidden
} else {
$active_server_id = $_GET['server_id'] ?? ($servers[0]['id'] ?? 1);
// Fetch channels
$stmt = db()->prepare("SELECT * FROM channels WHERE server_id = ?");
$stmt->execute([$active_server_id]);
$channels = $stmt->fetchAll();
$active_channel_id = $_GET['channel_id'] ?? ($channels[0]['id'] ?? 1);
// Fetch active channel details for theme
$active_channel = null;
foreach($channels as $c) {
if($c['id'] == $active_channel_id) {
$active_channel = $c;
break;
}
}
$channel_theme = $active_channel['theme_color'] ?? null;
// Fetch messages
$stmt = db()->prepare("
SELECT m.*, u.username, u.avatar_url
FROM messages m
JOIN users u ON m.user_id = u.id
WHERE m.channel_id = ?
ORDER BY m.created_at ASC
LIMIT 50
");
$stmt->execute([$active_channel_id]);
$messages = $stmt->fetchAll();
$current_channel_name = 'general';
foreach($channels as $c) if($c['id'] == $active_channel_id) $current_channel_name = $c['name'];
// Fetch members
$stmt = db()->prepare("
SELECT u.id, u.username, u.avatar_url, u.status
FROM users u
JOIN server_members sm ON u.id = sm.user_id
WHERE sm.server_id = ?
");
$stmt->execute([$active_server_id]);
$members = $stmt->fetchAll();
}
// SEO & Env tags
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Discord-like messaging app built with PHP';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#<?php echo htmlspecialchars($current_channel_name); ?> | <?php echo htmlspecialchars($projectDescription); ?></title>
<meta name="description" content="<?php echo htmlspecialchars($projectDescription); ?>">
<meta property="og:description" content="<?php echo htmlspecialchars($projectDescription); ?>">
<meta property="twitter:description" content="<?php echo htmlspecialchars($projectDescription); ?>">
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?php echo htmlspecialchars($projectImageUrl); ?>">
<meta property="twitter:image" content="<?php echo htmlspecialchars($projectImageUrl); ?>">
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/discord.css?v=<?php echo time(); ?>">
<script>
window.currentUserId = <?php echo $current_user_id; ?>;
window.currentUsername = "<?php echo addslashes($user['username']); ?>";
window.isServerOwner = <?php echo ($is_owner ?? false) ? 'true' : 'false'; ?>;
window.currentChannelName = "<?php echo addslashes($current_channel_name); ?>";
window.isDndMode = <?php echo ($user['dnd_mode'] ?? 0) ? 'true' : 'false'; ?>;
</script>
<style>
:root {
<?php if ($channel_theme): ?>
--blurple: <?php echo $channel_theme; ?>;
<?php endif; ?>
}
<?php if ($channel_theme): ?>
.mention {
background-color: <?php echo $channel_theme; ?>4D; /* 30% opacity */
}
.mention:hover {
background-color: <?php echo $channel_theme; ?>;
}
<?php endif; ?>
</style>
</head>
<body data-theme="<?php echo htmlspecialchars($user['theme'] ?? 'dark'); ?>">
<div class="discord-app">
<!-- Servers Sidebar -->
<div class="servers-sidebar">
<a href="index.php?server_id=dms" class="server-icon <?php echo $active_server_id == 'dms' ? 'active' : ''; ?>" title="Direct Messages">
<svg width="28" height="20" viewBox="0 0 28 20" fill="currentColor"><path d="M23.0212 1.67671C21.3107 0.883335 19.4805 0.314845 17.5566 0C17.3137 0.434033 17.0423 1.00252 16.8488 1.46581C14.8193 1.16016 12.8016 1.16016 10.8011 1.46581C10.6076 1.00252 10.3255 0.434033 10.0827 0C8.14811 0.314845 6.31792 0.883335 4.60741 1.67671C1.14775 6.84711 0.210418 11.8962 0.67293 16.8681C2.9723 18.5677 5.19143 19.5997 7.37191 20C7.91578 19.2558 8.3897 18.4616 8.79155 17.6166C7.99616 17.3148 7.2343 16.9416 6.51603 16.505C6.70881 16.3639 6.89745 16.2125 7.07923 16.052C11.4116 18.0494 16.1264 18.0494 20.4137 16.052C20.597 16.2125 20.7856 16.3639 20.9784 16.505C20.2586 16.9416 19.4967 17.3148 18.7013 17.6166C19.1031 18.4616 19.577 19.2558 20.1209 20C22.3014 19.5997 24.5205 18.5677 26.8199 16.8681C27.3693 11.127 25.9189 6.13063 23.0212 1.67671ZM9.51636 13.6749C8.21405 13.6749 7.14188 12.4839 7.14188 11.026C7.14188 9.56816 8.19284 8.3771 9.51636 8.3771C10.8399 8.3771 11.912 9.56816 11.8908 11.026C11.8908 12.4839 10.8399 13.6749 9.51636 13.6749ZM18.0051 13.6749C16.7028 13.6749 15.6306 12.4839 15.6306 11.026C15.6306 9.56816 16.6815 8.3771 18.0051 8.3771C19.3286 8.3771 20.4008 9.56816 20.3796 11.026C20.3796 12.4839 19.3286 13.6749 18.0051 13.6749Z"/></svg>
</a>
<hr style="width: 32px; border-color: #35363c; margin: 4px 0;">
<?php foreach($servers as $s): ?>
<a href="?server_id=<?php echo $s['id']; ?>"
class="server-icon <?php echo $s['id'] == $active_server_id ? 'active' : ''; ?>"
title="<?php echo htmlspecialchars($s['name']); ?>"
style="<?php echo !empty($s['icon_url']) ? "background-image: url('{$s['icon_url']}'); background-size: cover;" : ""; ?>">
<?php echo empty($s['icon_url']) ? mb_substr($s['name'], 0, 1) : ''; ?>
</a>
<?php endforeach; ?>
<a href="#" class="server-icon add-btn" title="Add a Server" data-bs-toggle="modal" data-bs-target="#addServerModal">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
</a>
</div>
<!-- Channels Sidebar -->
<div class="channels-sidebar">
<div class="channels-header">
<?php
if ($is_dm_view) {
echo "Direct Messages";
} else {
$active_server_name = 'Server';
$is_owner = false;
foreach($servers as $s) {
if($s['id'] == $active_server_id) {
$active_server_name = $s['name'];
$is_owner = ($s['owner_id'] == $current_user_id);
break;
}
}
echo htmlspecialchars($active_server_name);
if ($is_owner): ?>
<span class="ms-auto" style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#serverSettingsModal">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg>
</span>
<?php endif;
}
?>
</div>
<div class="channels-list">
<?php if ($is_dm_view): ?>
<?php foreach($dm_channels as $dm): ?>
<a href="?server_id=dms&channel_id=<?php echo $dm['id']; ?>"
class="dm-user-item <?php echo $dm['id'] == $active_channel_id ? 'active' : ''; ?>">
<div class="message-avatar" style="width: 32px; height: 32px; <?php echo $dm['avatar_url'] ? "background-image: url('{$dm['avatar_url']}');" : ""; ?>">
<div class="dm-status-indicator dm-status-<?php echo $dm['status']; ?>" style="position: absolute; bottom: 0; right: 0;"></div>
</div>
<span><?php echo htmlspecialchars($dm['other_user']); ?></span>
</a>
<?php endforeach; ?>
<?php else: ?>
<div class="channel-category">
<span>Text Channels</span>
<span class="add-channel-btn" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="text">+</span>
</div>
<?php foreach($channels as $c): if($c['type'] !== 'text') continue; ?>
<div class="channel-item-container d-flex align-items-center">
<a href="?server_id=<?php echo $active_server_id; ?>&channel_id=<?php echo $c['id']; ?>"
class="channel-item flex-grow-1 <?php echo $c['id'] == $active_channel_id ? 'active' : ''; ?>">
<?php echo htmlspecialchars($c['name']); ?>
</a>
<?php if ($is_owner): ?>
<span class="channel-settings-btn ms-1" style="cursor: pointer; color: var(--text-muted);"
data-bs-toggle="modal" data-bs-target="#editChannelModal"
data-id="<?php echo $c['id']; ?>"
data-name="<?php echo htmlspecialchars($c['name']); ?>"
data-files="<?php echo $c['allow_file_sharing']; ?>"
data-theme="<?php echo $c['theme_color']; ?>">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
<div class="channel-category" style="margin-top: 16px;">
<span>Voice Channels</span>
<span class="add-channel-btn" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="voice">+</span>
</div>
<?php foreach($channels as $c): if($c['type'] !== 'voice') continue; ?>
<div class="channel-item-container d-flex align-items-center">
<div class="channel-item voice-item flex-grow-1" data-channel-id="<?php echo $c['id']; ?>">
<?php echo htmlspecialchars($c['name']); ?>
</div>
<?php if ($is_owner): ?>
<span class="channel-settings-btn ms-1" style="cursor: pointer; color: var(--text-muted);"
data-bs-toggle="modal" data-bs-target="#editChannelModal"
data-id="<?php echo $c['id']; ?>"
data-name="<?php echo htmlspecialchars($c['name']); ?>"
data-files="<?php echo $c['allow_file_sharing']; ?>"
data-theme="<?php echo $c['theme_color']; ?>">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="user-panel">
<div class="user-info" data-bs-toggle="modal" data-bs-target="#userSettingsModal">
<div class="message-avatar" style="width: 32px; height: 32px; <?php echo $user['avatar_url'] ? "background-image: url('{$user['avatar_url']}');" : ""; ?>"></div>
<div style="flex: 1; min-width: 0;">
<div style="font-weight: bold; font-size: 0.85em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
<?php echo htmlspecialchars($user['username']); ?>
</div>
<div style="color: var(--text-muted); font-size: 0.75em;">#<?php echo str_pad($user['id'], 4, '0', STR_PAD_LEFT); ?></div>
</div>
</div>
<div class="user-actions">
<a href="#" title="Settings" style="color: var(--text-muted); margin-right: 8px;" data-bs-toggle="modal" data-bs-target="#userSettingsModal">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82 1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</a>
<a href="auth/logout.php" title="Logout" style="color: var(--text-muted);"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg></a>
</div>
</div>
<div style="padding: 10px; font-size: 10px; color: #4e5058; border-top: 1px solid #1e1f22;">
PHP <?php echo PHP_VERSION; ?> | <?php echo date('H:i'); ?>
</div>
</div>
<!-- Chat Area -->
<div class="chat-container">
<div class="chat-header">
<span style="color: var(--text-muted); margin-right: 8px;"><?php echo $is_dm_view ? '@' : '#'; ?></span>
<span class="flex-grow-1"><?php echo htmlspecialchars($current_channel_name); ?></span>
<div class="d-flex align-items-center">
<button id="pinned-messages-btn" class="btn btn-link text-muted p-1 me-2" title="Pinned Messages">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
</button>
<div class="search-container">
<div class="input-group input-group-sm">
<select id="search-type" class="form-select bg-dark text-muted border-0" style="width: auto; max-width: 80px; font-size: 0.7em;">
<option value="messages">Chat</option>
<option value="users">Users</option>
</select>
<input type="text" id="global-search" class="search-input" placeholder="Search..." autocomplete="off">
</div>
<div id="search-results" class="search-results-dropdown"></div>
</div>
</div>
</div>
<div class="messages-list" id="messages-list">
<?php if(empty($messages)): ?>
<div style="text-align: center; color: var(--text-muted); margin-top: 40px;">
<h4>Welcome to #<?php echo htmlspecialchars($current_channel_name); ?>!</h4>
<p>This is the start of the #<?php echo htmlspecialchars($current_channel_name); ?> channel.</p>
</div>
<?php endif; ?>
<?php foreach($messages as $m):
$mention_pattern = '/@' . preg_quote($user['username'], '/') . '\b/';
$is_mentioned = preg_match($mention_pattern, $m['content']);
?>
<div class="message-item <?php echo $is_mentioned ? 'mentioned' : ''; ?> <?php echo $m['is_pinned'] ? 'pinned' : ''; ?>" data-id="<?php echo $m['id']; ?>">
<div class="message-avatar" style="<?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>"></div>
<div class="message-content">
<div class="message-author">
<?php echo htmlspecialchars($m['username']); ?>
<span class="message-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
<?php if ($m['is_pinned']): ?>
<span class="pinned-badge ms-2" title="Pinned Message">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path></svg>
Pinned
</span>
<?php endif; ?>
<?php if ($m['user_id'] == $current_user_id || ($active_server_id != 'dms' && $is_owner)): ?>
<div class="message-actions-menu">
<span class="action-btn pin <?php echo $m['is_pinned'] ? 'active' : ''; ?>" title="<?php echo $m['is_pinned'] ? 'Unpin' : 'Pin'; ?>" data-id="<?php echo $m['id']; ?>" data-pinned="<?php echo $m['is_pinned']; ?>">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
</span>
<?php if ($m['user_id'] == $current_user_id): ?>
<span class="action-btn edit" title="Edit" data-id="<?php echo $m['id']; ?>">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
</span>
<span class="action-btn delete" title="Delete" data-id="<?php echo $m['id']; ?>">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
</span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="message-text">
<?php
$msg_content = htmlspecialchars($m['content']);
$msg_content = preg_replace($mention_pattern, '<span class="mention">@' . htmlspecialchars($user['username']) . '</span>', $msg_content);
echo nl2br($msg_content);
?>
<?php if ($m['attachment_url']): ?>
<div class="message-attachment mt-2">
<?php
$ext = strtolower(pathinfo($m['attachment_url'], PATHINFO_EXTENSION));
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])):
?>
<img src="<?php echo htmlspecialchars($m['attachment_url']); ?>" class="img-fluid rounded message-img-preview" alt="Attachment" style="max-height: 300px; cursor: pointer;" onclick="window.open(this.src)">
<?php else: ?>
<a href="<?php echo htmlspecialchars($m['attachment_url']); ?>" target="_blank" class="attachment-link d-inline-flex align-items-center p-2 rounded bg-dark text-white text-decoration-none">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
<?php echo basename($m['attachment_url']); ?>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($m['metadata'])):
$meta = json_decode($m['metadata'], true);
if ($meta): ?>
<div class="rich-embed mt-2 p-3 rounded" style="background: rgba(0,0,0,0.1); border-left: 4px solid var(--blurple); max-width: 520px;">
<?php if (!empty($meta['site_name'])): ?>
<div class="embed-site-name mb-1" style="font-size: 0.75em; color: var(--text-muted); text-transform: uppercase; font-weight: bold;"><?php echo htmlspecialchars($meta['site_name']); ?></div>
<?php endif; ?>
<?php if (!empty($meta['title'])): ?>
<a href="<?php echo htmlspecialchars($meta['url']); ?>" target="_blank" class="embed-title d-block mb-1 text-decoration-none" style="font-weight: 600; color: #00a8fc;"><?php echo htmlspecialchars($meta['title']); ?></a>
<?php endif; ?>
<?php if (!empty($meta['description'])): ?>
<div class="embed-description mb-2" style="font-size: 0.9em; color: var(--text-normal);"><?php echo htmlspecialchars($meta['description']); ?></div>
<?php endif; ?>
<?php if (!empty($meta['image'])): ?>
<div class="embed-image">
<img src="<?php echo htmlspecialchars($meta['image']); ?>" class="rounded" style="max-width: 100%; max-height: 300px; object-fit: contain;">
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<div class="message-reactions mt-1" data-message-id="<?php echo $m['id']; ?>">
<?php
// Fetch reactions for this message
$stmt_react = db()->prepare("SELECT emoji, COUNT(*) as count, GROUP_CONCAT(user_id) as users FROM message_reactions WHERE message_id = ? GROUP BY emoji");
$stmt_react->execute([$m['id']]);
$reactions = $stmt_react->fetchAll();
foreach ($reactions as $r):
$reacted = in_array($current_user_id, explode(',', $r['users']));
?>
<span class="reaction-badge <?php echo $reacted ? 'active' : ''; ?>" data-emoji="<?php echo htmlspecialchars($r['emoji']); ?>">
<?php echo htmlspecialchars($r['emoji']); ?> <span class="count"><?php echo $r['count']; ?></span>
</span>
<?php endforeach; ?>
<span class="add-reaction-btn" title="Add Reaction">+</span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div id="typing-indicator" class="typing-indicator"></div>
<div class="chat-input-container">
<?php
$allow_files = true;
foreach($channels as $c) {
if($c['id'] == $active_channel_id) {
$allow_files = (bool)$c['allow_file_sharing'];
break;
}
}
?>
<div id="upload-progress-container" class="upload-progress-container" style="display: none;">
<div class="progress" style="height: 4px; background-color: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden;">
<div id="upload-progress-bar" class="progress-bar" role="progressbar" style="width: 0%; background-color: var(--blurple);"></div>
</div>
<div class="d-flex justify-content-between mt-1">
<span id="upload-filename" class="small text-muted" style="font-size: 0.7em;">Uploading...</span>
<span id="upload-percentage" class="small text-muted" style="font-size: 0.7em;">0%</span>
</div>
</div>
<form id="chat-form" enctype="multipart/form-data">
<div class="chat-input-wrapper">
<?php if ($allow_files): ?>
<label for="file-upload" class="upload-btn-label" title="Upload File">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
</label>
<input type="file" id="file-upload" style="display: none;">
<?php else: ?>
<div class="upload-btn-label disabled" title="File sharing disabled" style="opacity: 0.3; cursor: not-allowed;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="8" x2="16" y2="16"></line><line x1="16" y1="8" x2="8" y2="16"></line></svg>
</div>
<?php endif; ?>
<input type="text" id="chat-input" class="chat-input" placeholder="Message #<?php echo htmlspecialchars($current_channel_name); ?>" autocomplete="off">
</div>
</form>
</div>
</div>
<!-- Members Sidebar -->
<div class="members-sidebar">
<div style="color: var(--text-muted); font-size: 0.75em; text-transform: uppercase; font-weight: bold; margin-bottom: 16px;">
Members — <?php echo count($members); ?>
</div>
<?php foreach($members as $m): ?>
<div class="channel-item start-dm-btn" data-user-id="<?php echo $m['id']; ?>" style="color: var(--text-primary); margin-bottom: 8px;">
<div class="message-avatar" style="width: 32px; height: 32px; background-color: <?php echo $m['status'] == 'online' ? '#23a559' : '#80848e'; ?>; position: relative; <?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>">
<?php if($m['status'] == 'online'): ?>
<div style="position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; background-color: #23a559; border-radius: 50%; border: 2px solid var(--bg-members);"></div>
<?php endif; ?>
</div>
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<?php echo htmlspecialchars($m['username']); ?>
</span>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- User Settings Modal -->
<div class="modal fade" id="userSettingsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">User Settings</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="user-settings-form">
<div class="row">
<div class="col-md-4 text-center">
<div class="message-avatar mx-auto mb-3" id="settings-avatar-preview" style="width: 100px; height: 100px; <?php echo $user['avatar_url'] ? "background-image: url('{$user['avatar_url']}');" : ""; ?>"></div>
<input type="hidden" name="avatar_url" id="settings-avatar-url" value="<?php echo htmlspecialchars($user['avatar_url'] ?? ''); ?>">
<p class="small text-muted">Pick an avatar from Pexels or search for one.</p>
</div>
<div class="col-md-8">
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Username</label>
<input type="text" name="username" class="form-control" value="<?php echo htmlspecialchars($user['username']); ?>" required>
</div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Settings</label>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" name="dnd_mode" id="dnd-switch" value="1" <?php echo ($user['dnd_mode'] ?? 0) ? 'checked' : ''; ?>>
<label class="form-check-label text-white" for="dnd-switch">Do Not Disturb</label>
<div class="form-text text-muted" style="font-size: 0.8em;">Mute all desktop notifications.</div>
</div>
<div class="mb-2">
<label class="form-label text-white d-block" style="font-size: 0.9em;">Appearance</label>
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="theme" id="theme-dark" value="dark" <?php echo ($user['theme'] ?? 'dark') == 'dark' ? 'checked' : ''; ?>>
<label class="btn btn-outline-secondary btn-sm" for="theme-dark">Dark</label>
<input type="radio" class="btn-check" name="theme" id="theme-light" value="light" <?php echo ($user['theme'] ?? 'dark') == 'light' ? 'checked' : ''; ?>>
<label class="btn btn-outline-secondary btn-sm" for="theme-light">Light</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Search Avatars</label>
<div class="input-group mb-2">
<input type="text" id="avatar-search-query" class="form-control" placeholder="e.g. cat, abstract, gamer">
<button class="btn btn-outline-secondary" type="button" id="search-avatar-btn">Search</button>
</div>
<div id="avatar-results" class="d-flex flex-wrap gap-2 overflow-auto" style="max-height: 200px;">
<!-- Pexels results here -->
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link text-white text-decoration-none" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="save-settings-btn" class="btn btn-primary" style="background-color: var(--blurple); border: none; padding: 10px 24px;">Save Changes</button>
</div>
</div>
</div>
</div>
<!-- Server Settings Modal -->
<div class="modal fade" id="serverSettingsModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Server Settings</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
<ul class="nav nav-tabs nav-fill" id="serverSettingsTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active text-white border-0 bg-transparent" data-bs-toggle="tab" data-bs-target="#settings-general" type="button">General</button>
</li>
<li class="nav-item">
<button class="nav-link text-white border-0 bg-transparent" id="roles-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-roles" type="button">Roles</button>
</li>
<li class="nav-item">
<button class="nav-link text-white border-0 bg-transparent" id="webhooks-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-webhooks" type="button">Webhooks</button>
</li>
<li class="nav-item">
<button class="nav-link text-white border-0 bg-transparent" id="stats-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-stats" type="button">Stats</button>
</li>
</ul>
<div class="tab-content p-3">
<div class="tab-pane fade show active" id="settings-general">
<form action="api_v1_servers.php" method="POST" id="server-settings-form">
<input type="hidden" name="action" value="update">
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
<div class="mb-3 text-center">
<?php
$active_icon = '';
foreach($servers as $s) if($s['id'] == $active_server_id) $active_icon = $s['icon_url'];
?>
<div class="message-avatar mx-auto mb-2" id="server-icon-preview" style="width: 80px; height: 80px; <?php echo $active_icon ? "background-image: url('{$active_icon}');" : ""; ?>"></div>
<input type="hidden" name="icon_url" id="server-icon-url" value="<?php echo htmlspecialchars($active_icon); ?>">
<button type="button" class="btn btn-sm btn-outline-secondary" id="search-server-icon-btn">Change Icon</button>
</div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Server Name</label>
<input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($active_server_name ?? ''); ?>" required>
</div>
<div id="server-icon-search-results" class="d-flex flex-wrap gap-2 mb-3 overflow-auto" style="max-height: 150px;"></div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Invite Code</label>
<?php
$invite = '';
foreach($servers as $s) if($s['id'] == $active_server_id) $invite = $s['invite_code'];
?>
<div class="input-group">
<input type="text" class="form-control bg-dark text-white border-0" value="<?php echo $invite; ?>" readonly>
<button class="btn btn-secondary" type="button" onclick="navigator.clipboard.writeText('<?php echo $invite; ?>')">Copy</button>
</div>
</div>
<hr class="border-secondary">
<button type="submit" class="btn btn-primary w-100 mb-2">Save Changes</button>
</form>
<form action="api_v1_servers.php" method="POST" onsubmit="return confirm('Are you sure you want to delete this server? This action is irreversible.');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
<button type="submit" class="btn btn-danger w-100">Delete Server</button>
</form>
</div>
<div class="tab-pane fade" id="settings-roles">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Server Roles</h6>
<button class="btn btn-sm btn-primary" id="add-role-btn">+ Add Role</button>
</div>
<div id="roles-list" class="list-group list-group-flush bg-transparent">
<!-- Roles will be loaded here -->
</div>
</div>
<div class="tab-pane fade" id="settings-webhooks">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Webhooks</h6>
<button class="btn btn-sm btn-primary" id="add-webhook-btn">+ Create Webhook</button>
</div>
<div id="webhooks-list" class="list-group list-group-flush bg-transparent">
<!-- Webhooks will be loaded here -->
</div>
</div>
<div class="tab-pane fade" id="settings-stats">
<div id="stats-content">
<div class="row text-center mb-4">
<div class="col-6">
<div class="p-3 rounded bg-dark">
<div class="small text-muted text-uppercase">Members</div>
<div class="h4 mb-0" id="stat-members">-</div>
</div>
</div>
<div class="col-6">
<div class="p-3 rounded bg-dark">
<div class="small text-muted text-uppercase">Messages</div>
<div class="h4 mb-0" id="stat-messages">-</div>
</div>
</div>
</div>
<h6 class="text-uppercase small text-muted mb-2">Top Active Users</h6>
<div id="top-users-list" class="mb-4">
<!-- Top users here -->
</div>
<h6 class="text-uppercase small text-muted mb-2">Activity (Last 7 Days)</h6>
<div id="activity-chart-placeholder" class="small text-muted text-center p-4 border border-secondary rounded">
<!-- Simplistic chart or list -->
Loading activity...
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add Server Modal -->
<div class="modal fade" id="addServerModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create or Join a server</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
<ul class="nav nav-tabs nav-fill" id="serverTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active text-white border-0 bg-transparent" id="create-tab" data-bs-toggle="tab" data-bs-target="#create-pane" type="button">Create</button>
</li>
<li class="nav-item">
<button class="nav-link text-white border-0 bg-transparent" id="join-tab" data-bs-toggle="tab" data-bs-target="#join-pane" type="button">Join</button>
</li>
</ul>
<div class="tab-content p-3">
<div class="tab-pane fade show active" id="create-pane">
<form action="api_v1_servers.php" method="POST">
<p style="color: var(--text-muted); font-size: 0.9em;">Give your new server a personality with a name.</p>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Server Name</label>
<input type="text" name="name" class="form-control" placeholder="My Awesome Server" required>
</div>
<button type="submit" class="btn btn-primary w-100" style="background-color: var(--blurple); border: none;">Create Server</button>
</form>
</div>
<div class="tab-pane fade" id="join-pane">
<form action="api_v1_servers.php" method="POST">
<input type="hidden" name="action" value="join">
<p style="color: var(--text-muted); font-size: 0.9em;">Enter an invite code to join an existing server.</p>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Invite Code</label>
<input type="text" name="invite_code" class="form-control" placeholder="GEN-123" required>
</div>
<button type="submit" class="btn btn-success w-100" style="background-color: #23a559; border: none;">Join Server</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add Channel Modal -->
<div class="modal fade" id="addChannelModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create Channel</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="api_v1_channels.php" method="POST">
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
<input type="hidden" name="type" id="channel-type-input" value="text">
<div class="modal-body">
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Name</label>
<div class="input-group">
<span class="input-group-text bg-dark border-0 text-muted" id="channel-hash-prefix">#</span>
<input type="text" name="name" class="form-control" placeholder="new-channel" required>
</div>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="allow_file_sharing" id="add-channel-files" value="1" checked>
<label class="form-check-label text-white" for="add-channel-files">Allow File Sharing</label>
</div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Theme Color</label>
<input type="color" name="theme_color" class="form-control form-control-color w-100" value="#5865f2" title="Choose channel theme color">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link text-white text-decoration-none" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary" style="background-color: var(--blurple); border: none; padding: 10px 24px;">Create Channel</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Channel Modal -->
<div class="modal fade" id="editChannelModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Channel Settings</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
<ul class="nav nav-tabs nav-fill" id="editChannelTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active text-white border-0 bg-transparent" data-bs-toggle="tab" data-bs-target="#edit-channel-general" type="button">General</button>
</li>
<li class="nav-item">
<button class="nav-link text-white border-0 bg-transparent" id="channel-permissions-tab-btn" data-bs-toggle="tab" data-bs-target="#edit-channel-permissions" type="button">Permissions</button>
</li>
</ul>
<div class="tab-content p-3">
<div class="tab-pane fade show active" id="edit-channel-general">
<form action="api_v1_channels.php" method="POST">
<input type="hidden" name="action" value="update">
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
<input type="hidden" name="channel_id" id="edit-channel-id">
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Name</label>
<input type="text" name="name" id="edit-channel-name" class="form-control" required>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="allow_file_sharing" id="edit-channel-files" value="1">
<label class="form-check-label text-white" for="edit-channel-files">Allow File Sharing</label>
<div class="form-text text-muted" style="font-size: 0.8em;">When disabled, users cannot upload files in this channel.</div>
</div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Theme Color</label>
<input type="color" name="theme_color" id="edit-channel-theme" class="form-control form-control-color w-100" value="#5865f2" title="Choose channel theme color">
</div>
<button type="submit" class="btn btn-primary w-100 mb-2">Save Changes</button>
</form>
<form action="api_v1_channels.php" method="POST" onsubmit="return confirm('Are you sure you want to delete this channel?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
<input type="hidden" name="channel_id" id="delete-channel-id">
<button type="submit" class="btn btn-danger w-100">Delete Channel</button>
</form>
</div>
<div class="tab-pane fade" id="edit-channel-permissions">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Role Overrides</h6>
<div class="dropdown">
<button class="btn btn-sm btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">Add Role</button>
<ul class="dropdown-menu dropdown-menu-dark" id="add-permission-role-list">
<!-- Server roles loaded here -->
</ul>
</div>
</div>
<div id="channel-permissions-list" class="list-group list-group-flush bg-transparent">
<!-- Channel permissions loaded here -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Pinned Messages Modal -->
<div class="modal fade" id="pinnedMessagesModal" tabindex="-1">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Pinned Messages</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0" id="pinned-messages-container" style="max-height: 500px; overflow-y: auto; background-color: var(--bg-chat);">
<div class="p-3 text-center text-muted">No pinned messages yet.</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
window.currentUserId = <?php echo $current_user_id; ?>;
window.currentUsername = "<?php echo addslashes($user['username']); ?>";
window.currentChannelName = "<?php echo addslashes($current_channel_name); ?>";
window.isServerOwner = <?php echo ($is_owner ?? false) ? 'true' : 'false'; ?>;
window.isDndMode = <?php echo ($user['dnd_mode'] ?? 0) ? 'true' : 'false'; ?>;
</script>
<script src="assets/js/voice.js?v=<?php echo time(); ?>"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script>
// Handle channel type in modal
document.querySelectorAll('.add-channel-btn').forEach(btn => {
btn.addEventListener('click', function() {
const type = this.getAttribute('data-type');
document.getElementById('channel-type-input').value = type;
document.getElementById('channel-hash-prefix').textContent = type === 'text' ? '#' : '🔊';
});
});
</script>
</body>
</html>