diff --git a/api_v1_channels.php b/api_v1_channels.php index 9c14b53..62b2dbf 100644 --- a/api_v1_channels.php +++ b/api_v1_channels.php @@ -1,6 +1,7 @@ prepare("SELECT s.owner_id FROM servers s JOIN channels c ON s.id = c.server_id WHERE c.id = ?"); + // Check if user has permission to manage channels + $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); $stmt->execute([$channel_id]); - $server = $stmt->fetch(); - - if ($server && $server['owner_id'] == $user_id) { + $chan = $stmt->fetch(); + + if ($chan && Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) { $name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '-', $name)); - $stmt = db()->prepare("UPDATE channels SET name = ?, allow_file_sharing = ?, theme_color = ?, message_limit = ? WHERE id = ?"); - $stmt->execute([$name, $allow_file_sharing, $theme_color, $message_limit, $channel_id]); + $stmt = db()->prepare("UPDATE channels SET name = ?, type = ?, status = ?, allow_file_sharing = ?, theme_color = ?, message_limit = ? WHERE id = ?"); + $stmt->execute([$name, $type, $status, $allow_file_sharing, $theme_color, $message_limit, $channel_id]); } header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id); exit; @@ -44,16 +47,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($action === 'delete') { $channel_id = $_POST['channel_id'] ?? 0; - // Check if user is owner - $stmt = db()->prepare("SELECT s.owner_id, s.id as server_id FROM servers s JOIN channels c ON s.id = c.server_id WHERE c.id = ?"); + $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); $stmt->execute([$channel_id]); - $server = $stmt->fetch(); + $chan = $stmt->fetch(); - if ($server && $server['owner_id'] == $user_id) { + if ($chan && Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) { $stmt = db()->prepare("DELETE FROM channels WHERE id = ?"); $stmt->execute([$channel_id]); } - header('Location: index.php?server_id=' . ($server['server_id'] ?? '')); + header('Location: index.php?server_id=' . ($chan['server_id'] ?? '')); exit; } @@ -61,11 +63,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $type = $_POST['type'] ?? 'text'; $user_id = $_SESSION['user_id']; - // Check if user is member of the server - $stmt = db()->prepare("SELECT 1 FROM server_members WHERE server_id = ? AND user_id = ?"); - $stmt->execute([$server_id, $user_id]); - - if ($stmt->fetch() && $name) { + // Check if user has permission to manage channels + if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS) && $name) { try { // Basic sanitization for channel name $name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '-', $name)); diff --git a/api_v1_clear_channel.php b/api_v1_clear_channel.php index 2e84faa..a9aec57 100644 --- a/api_v1_clear_channel.php +++ b/api_v1_clear_channel.php @@ -32,8 +32,8 @@ $stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server = $stmt->fetch(); -if ($server["owner_id"] != $_SESSION["user_id"]) { - echo json_encode(["success" => false, "error" => "Only the server owner can clear history"]); +if (!Permissions::hasPermission($_SESSION["user_id"], $server_id, Permissions::MANAGE_CHANNELS)) { + echo json_encode(["success" => false, "error" => "Only moderators or admins can clear history"]); exit; } diff --git a/api_v1_messages.php b/api_v1_messages.php index 33d85bb..b24c250 100644 --- a/api_v1_messages.php +++ b/api_v1_messages.php @@ -38,14 +38,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { if ($pinned) { try { + // Get server_id for the channel + $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); + $stmt->execute([$channel_id]); + $server_id = $stmt->fetchColumn(); + $stmt = db()->prepare(" - SELECT m.*, u.username, u.avatar_url + SELECT m.*, u.username, u.avatar_url, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color FROM messages m JOIN users u ON m.user_id = u.id WHERE m.channel_id = ? AND m.is_pinned = 1 ORDER BY m.created_at DESC "); - $stmt->execute([$channel_id]); + $stmt->execute([$server_id ?: 0, $channel_id]); $msgs = $stmt->fetchAll(); foreach ($msgs as &$m) { @@ -120,15 +126,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { $content = ''; $channel_id = 0; +$thread_id = null; $attachment_url = null; if (strpos($_SERVER['CONTENT_TYPE'] ?? '', 'application/json') !== false) { $data = json_decode(file_get_contents('php://input'), true); $content = $data['content'] ?? ''; $channel_id = $data['channel_id'] ?? 0; + $thread_id = !empty($data['thread_id']) ? (int)$data['thread_id'] : null; } else { $content = $_POST['content'] ?? ''; $channel_id = $_POST['channel_id'] ?? 0; + $thread_id = !empty($_POST['thread_id']) ? (int)$_POST['thread_id'] : null; // Check if file sharing is allowed in this channel $stmt = db()->prepare("SELECT allow_file_sharing FROM channels WHERE id = ?"); @@ -185,8 +194,8 @@ if (!empty($content)) { } try { - $stmt = db()->prepare("INSERT INTO messages (channel_id, user_id, content, attachment_url, metadata) VALUES (?, ?, ?, ?, ?)"); - $stmt->execute([$channel_id, $user_id, $content, $attachment_url, $metadata]); + $stmt = db()->prepare("INSERT INTO messages (channel_id, thread_id, user_id, content, attachment_url, metadata) VALUES (?, ?, ?, ?, ?, ?)"); + $stmt->execute([$channel_id, $thread_id, $user_id, $content, $attachment_url, $metadata]); $last_id = db()->lastInsertId(); // Enforce message limit if set @@ -211,9 +220,20 @@ try { $stmt->execute([$channel_id, $channel_id, $limit]); } - // Fetch message with username for the response - $stmt = db()->prepare("SELECT m.*, u.username, u.avatar_url FROM messages m JOIN users u ON m.user_id = u.id WHERE m.id = ?"); - $stmt->execute([$last_id]); + // Get server_id for the channel + $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); + $stmt->execute([$channel_id]); + $server_id = $stmt->fetchColumn(); + + // Fetch message with username and role color for the response + $stmt = db()->prepare(" + SELECT m.*, u.username, u.avatar_url, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color + FROM messages m + JOIN users u ON m.user_id = u.id + WHERE m.id = ? + "); + $stmt->execute([$server_id ?: 0, $last_id]); $msg = $stmt->fetch(); echo json_encode([ @@ -223,6 +243,7 @@ try { 'user_id' => $msg['user_id'], 'username' => $msg['username'], 'avatar_url' => $msg['avatar_url'], + 'role_color' => $msg['role_color'], 'content' => $msg['content'], 'attachment_url' => $msg['attachment_url'], 'metadata' => $msg['metadata'] ? json_decode($msg['metadata']) : null, diff --git a/api_v1_roles.php b/api_v1_roles.php index 66a209e..20862b9 100644 --- a/api_v1_roles.php +++ b/api_v1_roles.php @@ -1,5 +1,7 @@ false, 'error' => 'Missing server_id']); + exit; + } + + // Verify user is in server + $stmt = db()->prepare("SELECT * FROM server_members WHERE server_id = ? AND user_id = ?"); + $stmt->execute([$server_id, $user_id]); + if (!$stmt->fetch()) { + echo json_encode(['success' => false, 'error' => 'Access denied']); + exit; + } + $stmt = db()->prepare("SELECT * FROM roles WHERE server_id = ? ORDER BY position DESC"); $stmt->execute([$server_id]); - echo json_encode(['success' => true, 'roles' => $stmt->fetchAll()]); + $roles = $stmt->fetchAll(); + + // Fetch members and their roles + $stmt = db()->prepare(" + SELECT u.id, u.username, u.avatar_url, + GROUP_CONCAT(r.id) as role_ids, + GROUP_CONCAT(r.name) as role_names, + (SELECT r2.color FROM roles r2 JOIN user_roles ur2 ON r2.id = ur2.role_id WHERE ur2.user_id = u.id AND r2.server_id = ? ORDER BY r2.position DESC LIMIT 1) as role_color + FROM users u + JOIN server_members sm ON u.id = sm.user_id + LEFT JOIN user_roles ur ON u.id = ur.user_id + LEFT JOIN roles r ON ur.role_id = r.id AND r.server_id = ? + WHERE sm.server_id = ? + GROUP BY u.id + "); + $stmt->execute([$server_id, $server_id, $server_id]); + $members = $stmt->fetchAll(); + + echo json_encode([ + 'success' => true, + 'roles' => $roles, + 'members' => $members, + 'permissions_list' => [ + ['value' => 1, 'name' => 'View Channels'], + ['value' => 2, 'name' => 'Send Messages'], + ['value' => 4, 'name' => 'Manage Messages'], + ['value' => 8, 'name' => 'Manage Channels'], + ['value' => 16, 'name' => 'Manage Server'], + ['value' => 32, 'name' => 'Administrator'] + ] + ]); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $data['action'] ?? ''; $server_id = $data['server_id'] ?? 0; - $action = $data['action'] ?? 'create'; - // Check if user is owner of server + // Permissions check: Owner or MANAGE_SERVER $stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server = $stmt->fetch(); - if (!$server || $server['owner_id'] != $user_id) { + + $is_owner = ($server && $server['owner_id'] == $user_id); + $can_manage = Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_SERVER) || Permissions::hasPermission($user_id, $server_id, Permissions::ADMINISTRATOR); + + if (!$is_owner && !$can_manage) { echo json_encode(['success' => false, 'error' => 'Unauthorized']); exit; } @@ -29,58 +78,47 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($action === 'create') { $name = $data['name'] ?? 'New Role'; $color = $data['color'] ?? '#99aab5'; - $stmt = db()->prepare("INSERT INTO roles (server_id, name, color) VALUES (?, ?, ?)"); - $stmt->execute([$server_id, $name, $color]); + $perms = $data['permissions'] ?? 0; + + $stmt = db()->prepare("INSERT INTO roles (server_id, name, color, permissions) VALUES (?, ?, ?, ?)"); + $stmt->execute([$server_id, $name, $color, $perms]); echo json_encode(['success' => true, 'role_id' => db()->lastInsertId()]); + } elseif ($action === 'update') { + $role_id = $data['id'] ?? 0; + $name = $data['name'] ?? ''; + $color = $data['color'] ?? ''; + $perms = $data['permissions'] ?? 0; + + $stmt = db()->prepare("UPDATE roles SET name = ?, color = ?, permissions = ? WHERE id = ? AND server_id = ?"); + $stmt->execute([$name, $color, $perms, $role_id, $server_id]); + echo json_encode(['success' => true]); + } elseif ($action === 'delete') { + $role_id = $data['id'] ?? 0; + $stmt = db()->prepare("DELETE FROM roles WHERE id = ? AND server_id = ?"); + $stmt->execute([$role_id, $server_id]); + echo json_encode(['success' => true]); } elseif ($action === 'assign') { $target_user_id = $data['user_id'] ?? 0; $role_id = $data['role_id'] ?? 0; + + // Verify role belongs to server + $stmt = db()->prepare("SELECT id FROM roles WHERE id = ? AND server_id = ?"); + $stmt->execute([$role_id, $server_id]); + if (!$stmt->fetch()) { + echo json_encode(['success' => false, 'error' => 'Invalid role']); + exit; + } + $stmt = db()->prepare("INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)"); $stmt->execute([$target_user_id, $role_id]); echo json_encode(['success' => true]); } elseif ($action === 'unassign') { $target_user_id = $data['user_id'] ?? 0; $role_id = $data['role_id'] ?? 0; - $stmt = db()->prepare("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?"); - $stmt->execute([$target_user_id, $role_id]); + + $stmt = db()->prepare("DELETE ur FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.role_id = ? AND r.server_id = ?"); + $stmt->execute([$target_user_id, $role_id, $server_id]); echo json_encode(['success' => true]); } exit; } - -if ($_SERVER['REQUEST_METHOD'] === 'PUT') { - $role_id = $data['id'] ?? 0; - $name = $data['name'] ?? ''; - $color = $data['color'] ?? ''; - $permissions = $data['permissions'] ?? null; - - // Check server ownership via role - $stmt = db()->prepare("SELECT s.owner_id FROM servers s JOIN roles r ON s.id = r.server_id WHERE r.id = ?"); - $stmt->execute([$role_id]); - $server = $stmt->fetch(); - - if ($server && $server['owner_id'] == $user_id) { - $stmt = db()->prepare("UPDATE roles SET name = ?, color = ?, permissions = ? WHERE id = ?"); - $stmt->execute([$name, $color, $permissions, $role_id]); - echo json_encode(['success' => true]); - } else { - echo json_encode(['success' => false, 'error' => 'Unauthorized']); - } - exit; -} - -if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { - $role_id = $data['id'] ?? 0; - $stmt = db()->prepare("SELECT s.owner_id FROM servers s JOIN roles r ON s.id = r.server_id WHERE r.id = ?"); - $stmt->execute([$role_id]); - $server = $stmt->fetch(); - - if ($server && $server['owner_id'] == $user_id) { - $stmt = db()->prepare("DELETE FROM roles WHERE id = ?"); - $stmt->execute([$role_id]); - echo json_encode(['success' => true]); - } else { - echo json_encode(['success' => false, 'error' => 'Unauthorized']); - } - exit; -} diff --git a/api_v1_rss.php b/api_v1_rss.php new file mode 100644 index 0000000..5871032 --- /dev/null +++ b/api_v1_rss.php @@ -0,0 +1,116 @@ +prepare("SELECT server_id FROM channels WHERE id = ?"); + $stmt->execute([$channel_id]); + $chan = $stmt->fetch(); + + if (!$chan || !Permissions::hasPermission($user['id'], $chan['server_id'], Permissions::MANAGE_CHANNELS)) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } + + if ($action === 'add') { + $url = $_POST['url'] ?? ''; + if (!filter_var($url, FILTER_VALIDATE_URL)) { + echo json_encode(['success' => false, 'error' => 'Invalid URL']); + exit; + } + $stmt = db()->prepare("INSERT INTO channel_rss_feeds (channel_id, url) VALUES (?, ?)"); + $stmt->execute([$channel_id, $url]); + echo json_encode(['success' => true]); + exit; + } + + if ($action === 'delete') { + $feed_id = $_POST['feed_id'] ?? 0; + $stmt = db()->prepare("DELETE FROM channel_rss_feeds WHERE id = ? AND channel_id = ?"); + $stmt->execute([$feed_id, $channel_id]); + echo json_encode(['success' => true]); + exit; + } + + if ($action === 'sync') { + // Fetch feeds for this channel + $stmt = db()->prepare("SELECT * FROM channel_rss_feeds WHERE channel_id = ?"); + $stmt->execute([$channel_id]); + $feeds = $stmt->fetchAll(); + + $new_items_count = 0; + foreach ($feeds as $feed) { + $rss_content = @file_get_contents($feed['url']); + if (!$rss_content) continue; + + $xml = @simplexml_load_string($rss_content); + if (!$xml) continue; + + $items = []; + if (isset($xml->channel->item)) { // RSS 2.0 + $items = $xml->channel->item; + } elseif (isset($xml->entry)) { // Atom + $items = $xml->entry; + } + + foreach ($items as $item) { + $guid = (string)($item->guid ?? ($item->id ?? $item->link)); + $title = (string)$item->title; + $link = (string)($item->link['href'] ?? $item->link); + $description = strip_tags((string)($item->description ?? $item->summary)); + + // Check if already exists + $stmt_check = db()->prepare("SELECT id FROM messages WHERE channel_id = ? AND rss_guid = ?"); + $stmt_check->execute([$channel_id, $guid]); + if ($stmt_check->fetch()) continue; + + // Insert as message from a special "RSS Bot" user or system + // Let's find or create an RSS Bot user + $stmt_bot = db()->prepare("SELECT id FROM users WHERE username = 'RSS Bot' AND is_bot = 1"); + $stmt_bot->execute(); + $bot = $stmt_bot->fetch(); + if (!$bot) { + $stmt_create_bot = db()->prepare("INSERT INTO users (username, is_bot, status) VALUES ('RSS Bot', 1, 'online')"); + $stmt_create_bot->execute(); + $bot_id = db()->lastInsertId(); + } else { + $bot_id = $bot['id']; + } + + $content = "**" . $title . "**\n" . $description . "\n" . $link; + + // For announcements, we might want to use metadata for a rich embed + $metadata = json_encode([ + 'title' => $title, + 'description' => mb_substr($description, 0, 200) . (mb_strlen($description) > 200 ? '...' : ''), + 'url' => $link, + 'site_name' => parse_url($feed['url'], PHP_URL_HOST) + ]); + + $stmt_msg = db()->prepare("INSERT INTO messages (channel_id, user_id, content, metadata, rss_guid) VALUES (?, ?, ?, ?, ?)"); + $stmt_msg->execute([$channel_id, $bot_id, $content, $metadata, $guid]); + $new_items_count++; + } + + $stmt_update_feed = db()->prepare("UPDATE channel_rss_feeds SET last_fetched_at = CURRENT_TIMESTAMP WHERE id = ?"); + $stmt_update_feed->execute([$feed['id']]); + } + + echo json_encode(['success' => true, 'new_items' => $new_items_count]); + exit; + } +} else { + // GET: List feeds + $channel_id = $_GET['channel_id'] ?? 0; + $stmt = db()->prepare("SELECT * FROM channel_rss_feeds WHERE channel_id = ? ORDER BY created_at DESC"); + $stmt->execute([$channel_id]); + echo json_encode(['success' => true, 'feeds' => $stmt->fetchAll()]); + exit; +} diff --git a/api_v1_rules.php b/api_v1_rules.php new file mode 100644 index 0000000..9f07dd2 --- /dev/null +++ b/api_v1_rules.php @@ -0,0 +1,103 @@ +prepare("SELECT server_id FROM channels WHERE id = ?"); + $stmt->execute([$channel_id]); + $chan = $stmt->fetch(); + + if (!$chan || !Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } + + try { + // Get max position + $stmt = db()->prepare("SELECT MAX(position) FROM channel_rules WHERE channel_id = ?"); + $stmt->execute([$channel_id]); + $pos = (int)$stmt->fetchColumn() + 1; + + $stmt = db()->prepare("INSERT INTO channel_rules (channel_id, content, position) VALUES (?, ?, ?)"); + $stmt->execute([$channel_id, $content, $pos]); + echo json_encode(['success' => true]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { + $id = $_GET['id'] ?? 0; + + $stmt = db()->prepare("SELECT c.server_id FROM channels c JOIN channel_rules r ON c.id = r.channel_id WHERE r.id = ?"); + $stmt->execute([$id]); + $res = $stmt->fetch(); + + if ($res && Permissions::hasPermission($user_id, $res['server_id'], Permissions::MANAGE_CHANNELS)) { + $stmt = db()->prepare("DELETE FROM channel_rules WHERE id = ?"); + $stmt->execute([$id]); + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + } + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'PATCH') { + $data = json_decode(file_get_contents('php://input'), true); + $id = $data['id'] ?? 0; + $dir = $data['dir'] ?? 'up'; + + $stmt = db()->prepare("SELECT channel_id, position FROM channel_rules WHERE id = ?"); + $stmt->execute([$id]); + $current = $stmt->fetch(); + + if ($current) { + $channel_id = $current['channel_id']; + $pos = $current['position']; + + if ($dir === 'up') { + $stmt = db()->prepare("SELECT id, position FROM channel_rules WHERE channel_id = ? AND position < ? ORDER BY position DESC LIMIT 1"); + } else { + $stmt = db()->prepare("SELECT id, position FROM channel_rules WHERE channel_id = ? AND position > ? ORDER BY position ASC LIMIT 1"); + } + $stmt->execute([$channel_id, $pos]); + $other = $stmt->fetch(); + + if ($other) { + db()->prepare("UPDATE channel_rules SET position = ? WHERE id = ?")->execute([$other['position'], $id]); + db()->prepare("UPDATE channel_rules SET position = ? WHERE id = ?")->execute([$pos, $other['id']]); + } + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Rule not found']); + } + exit; +} +if ($_SERVER['REQUEST_METHOD'] === 'PUT') { + $data = json_decode(file_get_contents('php://input'), true); + $id = $data['id'] ?? 0; + $content = $data['content'] ?? ''; + + $stmt = db()->prepare("SELECT c.server_id FROM channels c JOIN channel_rules r ON c.id = r.channel_id WHERE r.id = ?"); + $stmt->execute([$id]); + $res = $stmt->fetch(); + + if ($res && Permissions::hasPermission($user_id, $res['server_id'], Permissions::MANAGE_CHANNELS)) { + $stmt = db()->prepare("UPDATE channel_rules SET content = ? WHERE id = ?"); + $stmt->execute([$content, $id]); + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + } + exit; +} diff --git a/api_v1_tags.php b/api_v1_tags.php new file mode 100644 index 0000000..19d0026 --- /dev/null +++ b/api_v1_tags.php @@ -0,0 +1,54 @@ + false, 'error' => 'Missing channel_id']); + exit; + } + $stmt = db()->prepare("SELECT * FROM forum_tags WHERE channel_id = ?"); + $stmt->execute([$channel_id]); + echo json_encode(['success' => true, 'tags' => $stmt->fetchAll()]); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $data = json_decode(file_get_contents('php://input'), true); + if (!$data) $data = $_POST; + + $action = $data['action'] ?? 'create'; + $channel_id = $data['channel_id'] ?? 0; + + // Check permissions + $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); + $stmt->execute([$channel_id]); + $chan = $stmt->fetch(); + if (!$chan || !Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } + + if ($action === 'create') { + $name = $data['name'] ?? ''; + $color = $data['color'] ?? '#7289da'; + if (!$name) { + echo json_encode(['success' => false, 'error' => 'Missing name']); + exit; + } + $stmt = db()->prepare("INSERT INTO forum_tags (channel_id, name, color) VALUES (?, ?, ?)"); + $stmt->execute([$channel_id, $name, $color]); + echo json_encode(['success' => true, 'tag_id' => db()->lastInsertId()]); + } elseif ($action === 'delete') { + $tag_id = $data['tag_id'] ?? 0; + $stmt = db()->prepare("DELETE FROM forum_tags WHERE id = ? AND channel_id = ?"); + $stmt->execute([$tag_id, $channel_id]); + echo json_encode(['success' => true]); + } + exit; +} diff --git a/api_v1_threads.php b/api_v1_threads.php new file mode 100644 index 0000000..8ddc361 --- /dev/null +++ b/api_v1_threads.php @@ -0,0 +1,80 @@ + false, 'error' => 'Missing data']); + exit; + } + + $tag_ids = $_POST['tag_ids'] ?? []; + if (is_string($tag_ids)) { + $tag_ids = array_filter(explode(',', $tag_ids)); + } + + try { + db()->beginTransaction(); + $stmt = db()->prepare("INSERT INTO forum_threads (channel_id, user_id, title) VALUES (?, ?, ?)"); + $stmt->execute([$channel_id, $user_id, $title]); + $thread_id = db()->lastInsertId(); + + if (!empty($tag_ids)) { + $stmtTag = db()->prepare("INSERT INTO thread_tags (thread_id, tag_id) VALUES (?, ?)"); + foreach ($tag_ids as $tag_id) { + if ($tag_id) $stmtTag->execute([$thread_id, $tag_id]); + } + } + db()->commit(); + echo json_encode(['success' => true, 'thread_id' => $thread_id]); + } catch (Exception $e) { + db()->rollBack(); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'PATCH' || (isset($_GET['action']) && $_GET['action'] === 'solve')) { + $data = json_decode(file_get_contents('php://input'), true) ?? $_POST; + $thread_id = $data['thread_id'] ?? 0; + $message_id = $data['message_id'] ?? null; // null to unsolve + $user_id = $_SESSION['user_id']; + + if (!$thread_id) { + echo json_encode(['success' => false, 'error' => 'Missing thread_id']); + exit; + } + + // Verify permission (thread owner or admin) + $stmt = db()->prepare("SELECT t.*, c.server_id FROM forum_threads t JOIN channels c ON t.channel_id = c.id WHERE t.id = ?"); + $stmt->execute([$thread_id]); + $thread = $stmt->fetch(); + + if (!$thread) { + echo json_encode(['success' => false, 'error' => 'Thread not found']); + exit; + } + + $stmtServer = db()->prepare("SELECT owner_id FROM servers WHERE id = ?"); + $stmtServer->execute([$thread['server_id']]); + $server = $stmtServer->fetch(); + + if ($thread['user_id'] != $user_id && $server['owner_id'] != $user_id) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } + + try { + $stmt = db()->prepare("UPDATE forum_threads SET solution_message_id = ? WHERE id = ?"); + $stmt->execute([$message_id, $thread_id]); + echo json_encode(['success' => true]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; +} diff --git a/api_v1_user.php b/api_v1_user.php index 87938af..f7f851c 100644 --- a/api_v1_user.php +++ b/api_v1_user.php @@ -12,11 +12,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = $_POST['username'] ?? $user['username']; $avatar_url = $_POST['avatar_url'] ?? $user['avatar_url']; $dnd_mode = isset($_POST['dnd_mode']) ? (int)$_POST['dnd_mode'] : (int)($user['dnd_mode'] ?? 0); + $sound_notifications = isset($_POST['sound_notifications']) ? (int)$_POST['sound_notifications'] : (int)($user['sound_notifications'] ?? 0); $theme = $_POST['theme'] ?? $user['theme'] ?? 'dark'; try { - $stmt = db()->prepare("UPDATE users SET username = ?, avatar_url = ?, dnd_mode = ?, theme = ? WHERE id = ?"); - $stmt->execute([$username, $avatar_url, $dnd_mode, $theme, $user['id']]); + $stmt = db()->prepare("UPDATE users SET username = ?, avatar_url = ?, dnd_mode = ?, sound_notifications = ?, theme = ? WHERE id = ?"); + $stmt->execute([$username, $avatar_url, $dnd_mode, $sound_notifications, $theme, $user['id']]); $_SESSION['username'] = $username; // Update session if stored (though getCurrentUser fetches from DB) diff --git a/assets/css/discord.css b/assets/css/discord.css index 37eb8fa..f3498b6 100644 --- a/assets/css/discord.css +++ b/assets/css/discord.css @@ -785,3 +785,77 @@ body { .action-btn.pin.active { color: var(--blurple); } + +/* Announcement Style */ +.announcement-style { + background-color: rgba(88, 101, 242, 0.05); + border: 1px solid rgba(88, 101, 242, 0.1); + border-radius: 8px; + margin-bottom: 8px; + padding: 12px !important; +} + +.announcement-style .message-text { + font-size: 1.1em; + font-weight: 500; +} + +/* Rules Style */ +.rule-item { + transition: transform 0.2s; + cursor: default; +} + +.rule-item:hover { + transform: translateX(5px); +} + +/* Forum Style */ +.thread-item { + transition: background-color 0.2s; +} + +.thread-item:hover { + background-color: #35373c !important; +} + +/* YouTube Embed */ +.youtube-embed { + overflow: hidden; + position: relative; + max-width: 560px; +} + +.youtube-embed iframe { + width: 100%; + aspect-ratio: 16 / 9; +} + +/* Solution Style */ +.message-item.is-solution { + background-color: rgba(35, 165, 89, 0.05); + border-left: 2px solid #23a559; +} + +.action-btn.mark-solution.active { + color: #23a559; +} + +/* Forum Filters */ +.forum-filters .btn { + border-radius: 20px; + margin-right: 5px; + border: none; + background-color: var(--bg-servers); + color: var(--text-muted); + font-size: 0.8em; + padding: 4px 12px; +} +.forum-filters .btn:hover { + background-color: var(--hover); + color: var(--text-primary); +} +.forum-filters .btn.active { + background-color: var(--active); + color: var(--text-primary); +} diff --git a/assets/js/main.js b/assets/js/main.js index 3a9c459..0b16feb 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -6,12 +6,13 @@ document.addEventListener('DOMContentLoaded', () => { const typingIndicator = document.getElementById('typing-indicator'); // Emoji list for reactions - const EMOJIS = ['👍', '❤️', '😂', '😮', '😢', '🔥', '✅', '🚀']; + const EMOJIS = ['👍', '❤️', '😂', '😮', '😢', '🔥', '✅', '🚀', '❓', '💡', '📌', '💯']; // Scroll to bottom messagesList.scrollTop = messagesList.scrollHeight; const currentChannel = new URLSearchParams(window.location.search).get('channel_id') || 1; + const currentThread = new URLSearchParams(window.location.search).get('thread_id'); let typingTimeout; // Notification Permission @@ -52,7 +53,18 @@ document.addEventListener('DOMContentLoaded', () => { new Notification(`Mention in #${window.currentChannelName}`, { body: `${data.username}: ${data.content}`, icon: data.avatar_url || '' - }); + if (e.target.classList.contains('move-rule-btn')) { + const id = e.target.dataset.id; + const dir = e.target.dataset.dir; + const resp = await fetch('api_v1_rules.php', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id, dir }) + }); + if ((await resp.json()).success) location.reload(); + } + }); + } } } @@ -114,6 +126,9 @@ document.addEventListener('DOMContentLoaded', () => { const formData = new FormData(); formData.append('content', content); formData.append('channel_id', currentChannel); + if (currentThread) { + formData.append('thread_id', currentThread); + } const progressContainer = document.getElementById('upload-progress-container'); const progressBar = document.getElementById('upload-progress-bar'); @@ -383,11 +398,12 @@ document.addEventListener('DOMContentLoaded', () => { const div = document.createElement('div'); div.className = 'message-item p-2 border-bottom border-secondary'; div.style.backgroundColor = 'transparent'; + const authorStyle = msg.role_color ? `color: ${msg.role_color};` : ''; div.innerHTML = `