diff --git a/api_v1_messages.php b/api_v1_messages.php index c19c168..418c59b 100644 --- a/api_v1_messages.php +++ b/api_v1_messages.php @@ -1,11 +1,35 @@ prepare("SELECT id FROM users WHERE bot_token = ? AND is_bot = TRUE"); + $stmt->execute([$bot_token]); + $bot = $stmt->fetch(); + if ($bot) { + $user_id = $bot['id']; + } else { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Invalid Bot Token']); + exit; + } +} elseif (isset($_SESSION['user_id'])) { + $user_id = $_SESSION['user_id']; +} else { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; +} $data = json_decode(file_get_contents('php://input'), true); -$content = $data['content'] ?? ''; -$channel_id = (int)($data['channel_id'] ?? 1); -$user_id = 1; // Mock logged in user if (empty($content)) { echo json_encode(['success' => false, 'error' => 'Empty content']); diff --git a/api_v1_webhook.php b/api_v1_webhook.php new file mode 100644 index 0000000..fc87466 --- /dev/null +++ b/api_v1_webhook.php @@ -0,0 +1,44 @@ + false, 'error' => 'Missing token']); + exit; +} + +$stmt = db()->prepare("SELECT * FROM webhooks WHERE token = ?"); +$stmt->execute([$token]); +$webhook = $stmt->fetch(); + +if (!$webhook) { + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Invalid token']); + exit; +} + +if (empty($content)) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Empty content']); + exit; +} + +try { + // We'll use a special System user or a placeholder user_id for webhooks + // Or we could create a bot user for each webhook. + // For now, let's assume we use user_id 1 (System) but override the name if provided. + + $stmt = db()->prepare("INSERT INTO messages (channel_id, user_id, content) VALUES (?, ?, ?)"); + $stmt->execute([$webhook['channel_id'], 1, $content]); + + echo json_encode(['success' => true]); +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/assets/css/discord.css b/assets/css/discord.css index 2e91fd8..ac5dfee 100644 --- a/assets/css/discord.css +++ b/assets/css/discord.css @@ -115,6 +115,50 @@ body { font-weight: 300; } +.voice-item::before { + content: "🔊"; + font-size: 0.9em; +} + +.channel-category { + color: var(--text-muted); + font-size: 0.75em; + text-transform: uppercase; + font-weight: bold; + margin-bottom: 8px; + padding-left: 8px; +} + +/* User Panel */ +.user-panel { + height: 52px; + background-color: #232428; + padding: 0 8px; + display: flex; + align-items: center; + gap: 8px; +} + +.user-info { + flex: 1; + display: flex; + align-items: center; + gap: 8px; + padding: 4px; + border-radius: 4px; + cursor: pointer; + min-width: 0; +} + +.user-info:hover { + background-color: var(--hover); +} + +.user-actions { + display: flex; + gap: 4px; +} + /* Chat Area */ .chat-container { flex: 1; diff --git a/assets/js/main.js b/assets/js/main.js index fba2c42..edc58cf 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -6,6 +6,26 @@ document.addEventListener('DOMContentLoaded', () => { // Scroll to bottom messagesList.scrollTop = messagesList.scrollHeight; + // WebSocket for real-time + let ws; + try { + ws = new WebSocket('ws://' + window.location.hostname + ':8080'); + ws.onmessage = (e) => { + const msg = JSON.parse(e.data); + if (msg.type === 'message') { + const data = JSON.parse(msg.data); + // Simple broadcast, we check if it belongs to current channel + const currentChannel = new URLSearchParams(window.location.search).get('channel_id') || 1; + if (data.channel_id == currentChannel) { + appendMessage(data); + messagesList.scrollTop = messagesList.scrollHeight; + } + } + }; + } catch (e) { + console.warn('WebSocket connection failed, falling back to REST only.'); + } + chatForm.addEventListener('submit', async (e) => { e.preventDefault(); const content = chatInput.value.trim(); @@ -13,20 +33,44 @@ document.addEventListener('DOMContentLoaded', () => { chatInput.value = ''; + const channel_id = new URLSearchParams(window.location.search).get('channel_id') || 1; + try { const response = await fetch('api_v1_messages.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: content, - channel_id: new URLSearchParams(window.location.search).get('channel_id') || 1 + channel_id: channel_id }) - }); + // Voice + const voiceHandler = new VoiceChannel(ws); + document.querySelectorAll('.voice-item').forEach(item => { + item.addEventListener('click', () => { + const cid = item.dataset.channelId; + voiceHandler.join(cid); + + // UI Update + document.querySelectorAll('.voice-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + }); + }); +}); + const result = await response.json(); if (result.success) { - appendMessage(result.message); - messagesList.scrollTop = messagesList.scrollHeight; + // If WS is connected, we might want to let WS handle the UI update + // But for simplicity, we append here and also send to WS + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ + ...result.message, + channel_id: channel_id + })); + } else { + appendMessage(result.message); + messagesList.scrollTop = messagesList.scrollHeight; + } } else { alert('Error: ' + result.error); } diff --git a/assets/js/voice.js b/assets/js/voice.js new file mode 100644 index 0000000..8a0d47f --- /dev/null +++ b/assets/js/voice.js @@ -0,0 +1,30 @@ +// Placeholder for WebRTC Voice Logic +class VoiceChannel { + constructor(ws) { + this.ws = ws; + this.localStream = null; + this.peers = {}; + } + + async join(channelId) { + console.log('Joining voice channel:', channelId); + try { + this.localStream = await navigator.mediaDevices.getUserMedia({ audio: true }); + this.ws.send(JSON.stringify({ + type: 'voice_join', + channel_id: channelId + })); + // Signalization would happen here via WS + } catch (e) { + console.error('Failed to get local stream:', e); + alert('Could not access microphone.'); + } + } + + leave() { + if (this.localStream) { + this.localStream.getTracks().forEach(track => track.stop()); + } + this.ws.send(JSON.stringify({ type: 'voice_leave' })); + } +} diff --git a/auth/login.php b/auth/login.php new file mode 100644 index 0000000..aa6ef5b --- /dev/null +++ b/auth/login.php @@ -0,0 +1,69 @@ +prepare("SELECT * FROM users WHERE email = ?"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password_hash'])) { + $_SESSION['user_id'] = $user['id']; + header('Location: ../index.php'); + exit; + } else { + $error = "Invalid email or password."; + } + } else { + $error = "Please fill all fields."; + } +} +?> + + +
+ +We're so excited to see you again!
+ + + + +