Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af9afaf499 | ||
|
|
63ee7f6c2b | ||
|
|
55896a11bd | ||
|
|
fcda68cf07 | ||
|
|
5dff56179a | ||
|
|
bdc0ccf63d | ||
|
|
099f307a09 | ||
|
|
09fa2a7096 | ||
|
|
c11359b2d2 | ||
|
|
24671bdbc7 | ||
|
|
04cad1c49b | ||
|
|
08664dda0d | ||
|
|
d8c5bbb218 | ||
|
|
920e26ada3 | ||
|
|
95cfa227e9 | ||
|
|
75e3425c41 | ||
|
|
35c2bad3b7 | ||
|
|
afb642ce41 | ||
|
|
eb7cbe5ace | ||
|
|
e678bcf5aa | ||
|
|
f46a1c7e4b | ||
|
|
794f971b73 | ||
|
|
e3e1dc3456 | ||
|
|
5083b2794c | ||
|
|
f55113bf56 | ||
|
|
e387e07cc6 | ||
|
|
c0b4015a24 | ||
|
|
5b5ac99cae | ||
|
|
f26d0b6abc | ||
|
|
f9c70d9be2 | ||
|
|
2bda3a08f3 | ||
|
|
7241b4052b | ||
|
|
171804c16a | ||
|
|
cd2b57b27d | ||
|
|
e6233598d6 | ||
|
|
77269fa65c | ||
|
|
f20e908050 | ||
|
|
41fa76eec3 | ||
|
|
1a0b8da2ba | ||
|
|
7aa3b7d910 | ||
|
|
79d65ef265 | ||
|
|
f41686b17d | ||
|
|
a35fd4aafb | ||
|
|
c987b0caba | ||
|
|
b5ae307f55 | ||
|
|
f604713529 | ||
|
|
a757fa13ed | ||
|
|
c08cfebf52 | ||
|
|
5494f1e4ee | ||
|
|
e3984686cb | ||
|
|
98888d0370 | ||
|
|
652014e524 | ||
|
|
dfd640b430 | ||
|
|
1440c83ccf | ||
|
|
001690b707 | ||
|
|
7a52251131 | ||
|
|
5d6fd46690 | ||
|
|
40f605d106 | ||
|
|
9c07e1ee23 | ||
|
|
1e73419ffb | ||
|
|
0911f86785 | ||
|
|
c49abcc049 | ||
|
|
ef520f4259 | ||
|
|
4883125cda | ||
|
|
2642f97c8b |
139
api/emotes.php
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'list') {
|
||||||
|
try {
|
||||||
|
$stmt = db()->query("SELECT * FROM custom_emotes ORDER BY created_at DESC");
|
||||||
|
echo json_encode(['success' => true, 'emotes' => $stmt->fetchAll()]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'upload' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (!isset($_FILES['emote'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Aucun fichier reçu (emote)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_FILES['emote']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$errorMsg = 'Erreur d\'upload PHP: ' . $_FILES['emote']['error'];
|
||||||
|
if ($_FILES['emote']['error'] === 1) $errorMsg = 'Fichier trop volumineux (limit PHP)';
|
||||||
|
if ($_FILES['emote']['error'] === 4) $errorMsg = 'Aucun fichier sélectionné';
|
||||||
|
echo json_encode(['success' => false, 'error' => $errorMsg]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = preg_replace('/[^a-z0-9_]/', '', strtolower($_POST['name'] ?? 'emote'));
|
||||||
|
if (empty($name)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Nom invalide']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['emote'];
|
||||||
|
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||||
|
if (!in_array(strtolower($ext), ['png', 'jpg', 'jpeg'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Format non supporté (PNG uniquement recommandé)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadDir = __DIR__ . '/../assets/images/custom_emotes/';
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = time() . '_' . $name . '.png';
|
||||||
|
$targetPath = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
// Process image: Resize to 48x48
|
||||||
|
$srcImage = null;
|
||||||
|
if (strtolower($ext) === 'png') $srcImage = imagecreatefrompng($file['tmp_name']);
|
||||||
|
else if (in_array(strtolower($ext), ['jpg', 'jpeg'])) $srcImage = imagecreatefromjpeg($file['tmp_name']);
|
||||||
|
|
||||||
|
if (!$srcImage) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Fichier image corrompu']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$width = imagesx($srcImage);
|
||||||
|
$height = imagesy($srcImage);
|
||||||
|
$newSize = 48;
|
||||||
|
$dstImage = imagecreatetruecolor($newSize, $newSize);
|
||||||
|
|
||||||
|
// Transparency for PNG
|
||||||
|
imagealphablending($dstImage, false);
|
||||||
|
imagesavealpha($dstImage, true);
|
||||||
|
$transparent = imagecolorallocatealpha($dstImage, 255, 255, 255, 127);
|
||||||
|
imagefilledrectangle($dstImage, 0, 0, $newSize, $newSize, $transparent);
|
||||||
|
|
||||||
|
imagecopyresampled($dstImage, $srcImage, 0, 0, 0, 0, $newSize, $newSize, $width, $height);
|
||||||
|
imagepng($dstImage, $targetPath);
|
||||||
|
|
||||||
|
imagedestroy($srcImage);
|
||||||
|
imagedestroy($dstImage);
|
||||||
|
|
||||||
|
$relativePath = 'assets/images/custom_emotes/' . $fileName;
|
||||||
|
$code = ':' . $name . ':';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO custom_emotes (name, path, code) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $relativePath, $code]);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'emote' => [
|
||||||
|
'id' => db()->lastInsertId(),
|
||||||
|
'name' => $name,
|
||||||
|
'path' => $relativePath,
|
||||||
|
'code' => $code
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'rename' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
$newName = preg_replace('/[^a-z0-9_]/', '', strtolower($_POST['name'] ?? ''));
|
||||||
|
if (empty($newName)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Nom invalide']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$code = ':' . $newName . ':';
|
||||||
|
$stmt = db()->prepare("UPDATE custom_emotes SET name = ?, code = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$newName, $code, $id]);
|
||||||
|
echo json_encode(['success' => true, 'name' => $newName, 'code' => $code]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("SELECT path FROM custom_emotes WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$emote = $stmt->fetch();
|
||||||
|
if ($emote) {
|
||||||
|
$filePath = __DIR__ . '/../' . $emote['path'];
|
||||||
|
if (file_exists($filePath)) unlink($filePath);
|
||||||
|
|
||||||
|
$stmt = db()->prepare("DELETE FROM custom_emotes WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Emote non trouvée']);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
26
api/pexels.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__.'/../includes/pexels.php';
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? 'search';
|
||||||
|
|
||||||
|
if ($action === 'search') {
|
||||||
|
$q = $_GET['query'] ?? 'avatar';
|
||||||
|
$url = 'https://api.pexels.com/v1/search?query=' . urlencode($q) . '&per_page=12&page=1';
|
||||||
|
$data = pexels_get($url);
|
||||||
|
if (!$data) {
|
||||||
|
echo json_encode(['error' => 'Failed to fetch images']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
foreach ($data['photos'] as $photo) {
|
||||||
|
$results[] = [
|
||||||
|
'id' => $photo['id'],
|
||||||
|
'url' => $photo['src']['medium'],
|
||||||
|
'photographer' => $photo['photographer']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
echo json_encode($results);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
59
api_v1_accept_rules.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$json = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$channel_id = $json['channel_id'] ?? 0;
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!$channel_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'ID de canal manquant']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch channel details to get rules_role_id
|
||||||
|
$stmt = db()->prepare("SELECT * FROM channels WHERE id = ? AND type = 'rules'");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$channel = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$channel) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Canal de règles introuvable']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($channel['rules_role_id'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Aucun rôle n\'est configuré pour ce canal']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$role_id = $channel['rules_role_id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
db()->beginTransaction();
|
||||||
|
|
||||||
|
// 1. Record acceptance
|
||||||
|
$stmtAcc = db()->prepare("INSERT IGNORE INTO rule_acceptances (user_id, channel_id) VALUES (?, ?)");
|
||||||
|
$stmtAcc->execute([$user_id, $channel_id]);
|
||||||
|
|
||||||
|
// 2. Assign role
|
||||||
|
// Check if user already has this role
|
||||||
|
$stmtRoleCheck = db()->prepare("SELECT 1 FROM user_roles WHERE user_id = ? AND role_id = ?");
|
||||||
|
$stmtRoleCheck->execute([$user_id, $role_id]);
|
||||||
|
|
||||||
|
if (!$stmtRoleCheck->fetch()) {
|
||||||
|
$stmtRole = db()->prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
|
||||||
|
$stmtRole->execute([$user_id, $role_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
db()->commit();
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
db()->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Erreur lors de l\'attribution du rôle : ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Méthode non autorisée']);
|
||||||
102
api_v1_autoroles.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$json = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$action = $_POST['action'] ?? ($json['action'] ?? '');
|
||||||
|
|
||||||
|
if ($action === 'create') {
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
$server_id = $_POST['server_id'] ?? 0;
|
||||||
|
$icon = $_POST['icon'] ?? '';
|
||||||
|
$title = $_POST['title'] ?? '';
|
||||||
|
$role_id = $_POST['role_id'] ?? 0;
|
||||||
|
|
||||||
|
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS)) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO channel_autoroles (channel_id, icon, title, role_id) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$channel_id, $icon, $title, $role_id]);
|
||||||
|
}
|
||||||
|
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'update') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
$server_id = $_POST['server_id'] ?? 0;
|
||||||
|
$icon = $_POST['icon'] ?? '';
|
||||||
|
$title = $_POST['title'] ?? '';
|
||||||
|
$role_id = $_POST['role_id'] ?? 0;
|
||||||
|
|
||||||
|
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS)) {
|
||||||
|
$stmt = db()->prepare("UPDATE channel_autoroles SET icon = ?, title = ?, role_id = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$icon, $title, $role_id, $id]);
|
||||||
|
}
|
||||||
|
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$id = $_POST['id'] ?? 0;
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
$server_id = $_POST['server_id'] ?? 0;
|
||||||
|
|
||||||
|
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS)) {
|
||||||
|
$stmt = db()->prepare("DELETE FROM channel_autoroles WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
}
|
||||||
|
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'toggle') {
|
||||||
|
// This will be called via AJAX
|
||||||
|
$role_id = $json['role_id'] ?? 0;
|
||||||
|
|
||||||
|
if (!$role_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid role']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the server for this role
|
||||||
|
$stmt = db()->prepare("SELECT server_id FROM roles WHERE id = ?");
|
||||||
|
$stmt->execute([$role_id]);
|
||||||
|
$role = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$role) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Role not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is member of server
|
||||||
|
$stmt = db()->prepare("SELECT 1 FROM server_members WHERE server_id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$role['server_id'], $user_id]);
|
||||||
|
if (!$stmt->fetch()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Not a member of this server']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle role
|
||||||
|
$stmt = db()->prepare("SELECT 1 FROM user_roles WHERE user_id = ? AND role_id = ?");
|
||||||
|
$stmt->execute([$user_id, $role_id]);
|
||||||
|
$has_role = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($has_role) {
|
||||||
|
$stmt = db()->prepare("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?");
|
||||||
|
$stmt->execute([$user_id, $role_id]);
|
||||||
|
$added = false;
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$user_id, $role_id]);
|
||||||
|
$added = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'added' => $added]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
131
api_v1_channel_permissions.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
$channel_id = $_GET['channel_id'] ?? 0;
|
||||||
|
|
||||||
|
// Get server_id for this channel
|
||||||
|
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$channel = $stmt->fetch();
|
||||||
|
$server_id = $channel['server_id'] ?? 0;
|
||||||
|
|
||||||
|
// Ensure @everyone role exists for this server
|
||||||
|
$stmt = db()->prepare("SELECT id FROM roles WHERE server_id = ? AND (LOWER(name) = '@everyone' OR LOWER(name) = 'everyone') LIMIT 1");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$everyone = $stmt->fetch();
|
||||||
|
if (!$everyone && $server_id) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO roles (server_id, name, color, permissions, position) VALUES (?, '@everyone', '#99aab5', 0, 0)");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$everyone_role_id = db()->lastInsertId();
|
||||||
|
} else {
|
||||||
|
$everyone_role_id = $everyone['id'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch permissions for this channel
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT cp.*, r.name as role_name, r.color as role_color
|
||||||
|
FROM channel_permissions cp
|
||||||
|
JOIN roles r ON cp.role_id = r.id
|
||||||
|
WHERE cp.channel_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$permissions = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Check if @everyone is in permissions, if not add it manually to show up by default
|
||||||
|
$has_everyone = false;
|
||||||
|
foreach($permissions as $p) {
|
||||||
|
if ($p['role_id'] == $everyone_role_id) {
|
||||||
|
$has_everyone = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$has_everyone && $everyone_role_id > 0) {
|
||||||
|
$stmt = db()->prepare("SELECT name, color FROM roles WHERE id = ?");
|
||||||
|
$stmt->execute([$everyone_role_id]);
|
||||||
|
$r = $stmt->fetch();
|
||||||
|
if ($r) {
|
||||||
|
array_unshift($permissions, [
|
||||||
|
'channel_id' => (int)$channel_id,
|
||||||
|
'role_id' => (int)$everyone_role_id,
|
||||||
|
'allow_permissions' => 0,
|
||||||
|
'deny_permissions' => 0,
|
||||||
|
'role_name' => $r['name'],
|
||||||
|
'role_color' => $r['color']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'permissions' => $permissions]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$channel_id = $data['channel_id'] ?? 0;
|
||||||
|
$role_id = $data['role_id'] ?? 0;
|
||||||
|
$allow = $data['allow'] ?? 0;
|
||||||
|
$deny = $data['deny'] ?? 0;
|
||||||
|
|
||||||
|
// Check permissions: Owner or MANAGE_CHANNELS or ADMINISTRATOR
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$ch = $stmt->fetch();
|
||||||
|
$server_id = $ch['server_id'] ?? 0;
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
|
||||||
|
$is_owner = ($server && $server['owner_id'] == $user_id);
|
||||||
|
$can_manage = Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS) ||
|
||||||
|
Permissions::hasPermission($user_id, $server_id, Permissions::ADMINISTRATOR);
|
||||||
|
|
||||||
|
if ($is_owner || $can_manage) {
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
INSERT INTO channel_permissions (channel_id, role_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]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
|
||||||
|
$channel_id = $data['channel_id'] ?? 0;
|
||||||
|
$role_id = $data['role_id'] ?? 0;
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$ch = $stmt->fetch();
|
||||||
|
$server_id = $ch['server_id'] ?? 0;
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
|
||||||
|
$is_owner = ($server && $server['owner_id'] == $user_id);
|
||||||
|
$can_manage = Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS) ||
|
||||||
|
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]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
131
api_v1_channels.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
$server_id = $_GET['server_id'] ?? 0;
|
||||||
|
if (!$server_id) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$stmt = db()->prepare("SELECT c.*, (SELECT MAX(id) FROM messages WHERE channel_id = c.id) as last_message_id FROM channels c WHERE server_id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
echo json_encode($stmt->fetchAll());
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// Handle JSON input
|
||||||
|
$json = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if ($json) {
|
||||||
|
$action = $json['action'] ?? '';
|
||||||
|
if ($action === 'reorder') {
|
||||||
|
$server_id = $json['server_id'] ?? 0;
|
||||||
|
$orders = $json['orders'] ?? []; // Array of {id, position, category_id}
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
file_put_contents('debug_reorder.log', date('Y-m-d H:i:s') . " - Server: $server_id - Orders: " . json_encode($orders) . "\n", FILE_APPEND);
|
||||||
|
|
||||||
|
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS)) {
|
||||||
|
$stmt = db()->prepare("UPDATE channels SET position = ?, category_id = ? WHERE id = ? AND server_id = ?");
|
||||||
|
foreach ($orders as $o) {
|
||||||
|
$stmt->execute([$o['position'], $o['category_id'] ?: null, $o['id'], $server_id]);
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Permission denied']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $_POST['action'] ?? 'create';
|
||||||
|
$server_id = $_POST['server_id'] ?? 0;
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($action === 'update') {
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
$type = $_POST['type'] ?? 'chat';
|
||||||
|
$status = $_POST['status'] ?? null;
|
||||||
|
$allow_file_sharing = isset($_POST['allow_file_sharing']) ? 1 : 0;
|
||||||
|
$message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null;
|
||||||
|
$icon = $_POST['icon'] ?? null;
|
||||||
|
if ($icon === '') $icon = null;
|
||||||
|
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
|
||||||
|
$rules_role_id = !empty($_POST['rules_role_id']) ? (int)$_POST['rules_role_id'] : null;
|
||||||
|
|
||||||
|
// Check if user has permission to manage channels
|
||||||
|
$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)) {
|
||||||
|
if ($type === 'separator' && !$name) $name = 'separator';
|
||||||
|
// Allow spaces, accents and mixed case
|
||||||
|
$name = trim($name);
|
||||||
|
// Explicitly exclude position from update to prevent jumping to bottom
|
||||||
|
$stmt = db()->prepare("UPDATE channels SET name = ?, type = ?, status = ?, allow_file_sharing = ?, message_limit = ?, icon = ?, category_id = ?, rules_role_id = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$name, $type, $status, $allow_file_sharing, $message_limit, $icon, $category_id, $rules_role_id, $channel_id]);
|
||||||
|
|
||||||
|
if ($message_limit !== null) {
|
||||||
|
require_once 'db/config.php';
|
||||||
|
enforceChannelLimit($channel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
$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)) {
|
||||||
|
$stmt = db()->prepare("DELETE FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
}
|
||||||
|
header('Location: index.php?server_id=' . ($chan['server_id'] ?? ''));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
$type = $_POST['type'] ?? 'text';
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Check if user has permission to manage channels
|
||||||
|
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_CHANNELS) && ($name || $type === 'separator')) {
|
||||||
|
try {
|
||||||
|
if ($type === 'separator' && !$name) $name = 'separator';
|
||||||
|
// Allow spaces, accents and mixed case
|
||||||
|
$name = trim($name);
|
||||||
|
$allow_file_sharing = isset($_POST['allow_file_sharing']) ? 1 : 0;
|
||||||
|
$message_limit = !empty($_POST['message_limit']) ? (int)$_POST['message_limit'] : null;
|
||||||
|
$icon = $_POST['icon'] ?? null;
|
||||||
|
if ($icon === '') $icon = null;
|
||||||
|
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
|
||||||
|
$rules_role_id = !empty($_POST['rules_role_id']) ? (int)$_POST['rules_role_id'] : null;
|
||||||
|
|
||||||
|
// Get next position
|
||||||
|
$stmtPos = db()->prepare("SELECT MAX(position) as max_pos FROM channels WHERE server_id = ?");
|
||||||
|
$stmtPos->execute([$server_id]);
|
||||||
|
$maxPos = $stmtPos->fetch();
|
||||||
|
$nextPos = ($maxPos['max_pos'] ?? -1) + 1;
|
||||||
|
|
||||||
|
$stmt = db()->prepare("INSERT INTO channels (server_id, name, type, allow_file_sharing, message_limit, icon, category_id, position, rules_role_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$server_id, $name, $type, $allow_file_sharing, $message_limit, $icon, $category_id, $nextPos, $rules_role_id]);
|
||||||
|
$channel_id = db()->lastInsertId();
|
||||||
|
|
||||||
|
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die("Error creating channel: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header('Location: index.php');
|
||||||
46
api_v1_clear_channel.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/db/config.php";
|
||||||
|
require_once __DIR__ . "/includes/permissions.php";
|
||||||
|
session_start();
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user_id"])) {
|
||||||
|
echo json_encode(["success" => false, "error" => "Unauthorized"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$channel_id = $_POST["channel_id"] ?? null;
|
||||||
|
if (!$channel_id) {
|
||||||
|
echo json_encode(["success" => false, "error" => "Missing channel ID"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get server_id for this channel
|
||||||
|
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$channel = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$channel) {
|
||||||
|
echo json_encode(["success" => false, "error" => "Channel not found"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server_id = $channel["server_id"];
|
||||||
|
|
||||||
|
// Check if user is owner or admin (minimal check for now)
|
||||||
|
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("DELETE FROM messages WHERE channel_id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
echo json_encode(["success" => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(["success" => false, "error" => $e->getMessage()]);
|
||||||
|
}
|
||||||
67
api_v1_dms.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$current_user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$target_user_id = $_POST['user_id'] ?? 0;
|
||||||
|
|
||||||
|
if ($target_user_id == $current_user_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'You cannot message yourself']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if DM channel already exists between these two users
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT c.id
|
||||||
|
FROM channels c
|
||||||
|
JOIN channel_members cm1 ON c.id = cm1.channel_id
|
||||||
|
JOIN channel_members cm2 ON c.id = cm2.channel_id
|
||||||
|
WHERE c.type = 'dm' AND cm1.user_id = ? AND cm2.user_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$current_user_id, $target_user_id]);
|
||||||
|
$existing = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
echo json_encode(['success' => true, 'channel_id' => $existing['id']]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new DM channel
|
||||||
|
$stmt = db()->prepare("INSERT INTO channels (server_id, name, type) VALUES (NULL, 'dm', 'dm')");
|
||||||
|
$stmt->execute();
|
||||||
|
$channel_id = db()->lastInsertId();
|
||||||
|
|
||||||
|
// Add both users to the channel
|
||||||
|
$stmt = db()->prepare("INSERT INTO channel_members (channel_id, user_id) VALUES (?, ?), (?, ?)");
|
||||||
|
$stmt->execute([$channel_id, $current_user_id, $channel_id, $target_user_id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'channel_id' => $channel_id]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
// Fetch all DM channels for current user
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT c.id, u.display_name as other_user, u.username as login_name, 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]);
|
||||||
|
$dms = $stmt->fetchAll();
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'dms' => $dms]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
271
api_v1_messages.php
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/opengraph.php';
|
||||||
|
require_once 'includes/ai_filtering.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
|
||||||
|
// Check for Bot token in headers
|
||||||
|
$headers = getallheaders();
|
||||||
|
$bot_token = null;
|
||||||
|
if (isset($headers['Authorization']) && preg_match('/Bot\s+(\S+)/', $headers['Authorization'], $matches)) {
|
||||||
|
$bot_token = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = null;
|
||||||
|
if ($bot_token) {
|
||||||
|
$stmt = db()->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
$channel_id = $_GET['channel_id'] ?? 0;
|
||||||
|
$pinned = isset($_GET['pinned']) && $_GET['pinned'] == 1;
|
||||||
|
|
||||||
|
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.display_name as username, u.username as login_name, 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,
|
||||||
|
(SELECT r.icon_url 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_icon
|
||||||
|
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([$server_id ?: 0, $server_id ?: 0, $channel_id]);
|
||||||
|
$msgs = $stmt->fetchAll();
|
||||||
|
|
||||||
|
foreach ($msgs as &$m) {
|
||||||
|
$m['time'] = date('H:i', strtotime($m['created_at']));
|
||||||
|
$m['metadata'] = $m['metadata'] ? json_decode($m['metadata']) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'messages' => $msgs]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['after_id'])) {
|
||||||
|
try {
|
||||||
|
$after_id = (int)$_GET['after_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();
|
||||||
|
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT m.*, u.display_name as username, u.username as login_name, 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,
|
||||||
|
(SELECT r.icon_url 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_icon
|
||||||
|
FROM messages m
|
||||||
|
JOIN users u ON m.user_id = u.id
|
||||||
|
WHERE m.channel_id = ? AND m.id > ?
|
||||||
|
ORDER BY m.id ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([$server_id ?: 0, $server_id ?: 0, $channel_id, $after_id]);
|
||||||
|
$msgs = $stmt->fetchAll();
|
||||||
|
|
||||||
|
foreach ($msgs as &$m) {
|
||||||
|
$m['time'] = date('H:i', strtotime($m['created_at']));
|
||||||
|
$m['metadata'] = $m['metadata'] ? json_decode($m['metadata']) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'messages' => $msgs]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$message_id = $data['id'] ?? 0;
|
||||||
|
$content = $data['content'] ?? '';
|
||||||
|
$action = $data['action'] ?? 'edit';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'pin') {
|
||||||
|
$stmt = db()->prepare("UPDATE messages SET is_pinned = 1 WHERE id = ?");
|
||||||
|
$stmt->execute([$message_id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($action === 'unpin') {
|
||||||
|
$stmt = db()->prepare("UPDATE messages SET is_pinned = 0 WHERE id = ?");
|
||||||
|
$stmt->execute([$message_id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($content)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Content cannot be empty']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$stmt = db()->prepare("UPDATE messages SET content = ? WHERE id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$content, $message_id, $user_id]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Message not found or unauthorized']);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$message_id = $data['id'] ?? 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("DELETE FROM messages WHERE id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$message_id, $user_id]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Message not found or unauthorized']);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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 = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$channel = $stmt->fetch();
|
||||||
|
$can_share_files = $channel ? (bool)$channel['allow_file_sharing'] : true;
|
||||||
|
|
||||||
|
if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
if (!$can_share_files) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'File sharing is disabled in this channel.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$upload_dir = 'assets/uploads/';
|
||||||
|
if (!is_dir($upload_dir)) mkdir($upload_dir, 0775, true);
|
||||||
|
|
||||||
|
$filename = time() . '_' . basename($_FILES['file']['name']);
|
||||||
|
$target_file = $upload_dir . $filename;
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['file']['tmp_name'], $target_file)) {
|
||||||
|
$attachment_url = $target_file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($content) && empty($attachment_url)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Empty content and no attachment']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check granular permissions
|
||||||
|
if (!Permissions::canSendInChannel($user_id, $channel_id)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'You do not have permission to send messages in this channel.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($content)) {
|
||||||
|
$moderation = moderateContent($content);
|
||||||
|
if (!$moderation['is_safe']) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Message flagged as inappropriate: ' . ($moderation['reason'] ?? 'Violation of community standards')]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$metadata = null;
|
||||||
|
if (!empty($content)) {
|
||||||
|
$urls = extractUrls($content);
|
||||||
|
if (!empty($urls)) {
|
||||||
|
// Fetch OG data for the first URL
|
||||||
|
$ogData = fetchOpenGraphData($urls[0]);
|
||||||
|
if ($ogData) {
|
||||||
|
$metadata = json_encode($ogData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$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
|
||||||
|
enforceChannelLimit($channel_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.display_name as username, u.username as login_name, 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,
|
||||||
|
(SELECT r.icon_url 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_icon
|
||||||
|
FROM messages m
|
||||||
|
JOIN users u ON m.user_id = u.id
|
||||||
|
WHERE m.id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$server_id ?: 0, $server_id ?: 0, $last_id]);
|
||||||
|
$msg = $stmt->fetch();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => [
|
||||||
|
'id' => $msg['id'],
|
||||||
|
'user_id' => $msg['user_id'],
|
||||||
|
'username' => $msg['username'], 'login_name' => $msg['login_name'],
|
||||||
|
'avatar_url' => $msg['avatar_url'],
|
||||||
|
'role_color' => $msg['role_color'],
|
||||||
|
'role_icon' => $msg['role_icon'],
|
||||||
|
'content' => $msg['content'],
|
||||||
|
'attachment_url' => $msg['attachment_url'],
|
||||||
|
'metadata' => $msg['metadata'] ? json_decode($msg['metadata']) : null,
|
||||||
|
'time' => date('H:i', strtotime($msg['created_at']))
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
58
api_v1_reactions.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$message_id = $data['message_id'] ?? 0;
|
||||||
|
$emoji = $data['emoji'] ?? '';
|
||||||
|
$action = $data['action'] ?? 'toggle'; // 'toggle', 'add', 'remove'
|
||||||
|
|
||||||
|
if (!$message_id || !$emoji) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing message_id or emoji']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'toggle') {
|
||||||
|
$stmt = db()->prepare("SELECT id FROM message_reactions WHERE message_id = ? AND user_id = ? AND emoji = ?");
|
||||||
|
$stmt->execute([$message_id, $user_id, $emoji]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
$stmt = db()->prepare("DELETE FROM message_reactions WHERE message_id = ? AND user_id = ? AND emoji = ?");
|
||||||
|
$stmt->execute([$message_id, $user_id, $emoji]);
|
||||||
|
$res_action = 'removed';
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("INSERT INTO message_reactions (message_id, user_id, emoji) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$message_id, $user_id, $emoji]);
|
||||||
|
$res_action = 'added';
|
||||||
|
}
|
||||||
|
} elseif ($action === 'add') {
|
||||||
|
$stmt = db()->prepare("INSERT IGNORE INTO message_reactions (message_id, user_id, emoji) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$message_id, $user_id, $emoji]);
|
||||||
|
$res_action = 'added';
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("DELETE FROM message_reactions WHERE message_id = ? AND user_id = ? AND emoji = ?");
|
||||||
|
$stmt->execute([$message_id, $user_id, $emoji]);
|
||||||
|
$res_action = 'removed';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get updated reactions for this message
|
||||||
|
$stmt = db()->prepare("SELECT emoji, COUNT(*) as count, GROUP_CONCAT(user_id) as users FROM message_reactions WHERE message_id = ? GROUP BY emoji");
|
||||||
|
$stmt->execute([$message_id]);
|
||||||
|
$reactions = $stmt->fetchAll();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'action' => $res_action,
|
||||||
|
'message_id' => $message_id,
|
||||||
|
'reactions' => $reactions
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
175
api_v1_roles.php
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
$server_id = $_GET['server_id'] ?? 0;
|
||||||
|
if (!$server_id) {
|
||||||
|
echo json_encode(['success' => 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]);
|
||||||
|
$roles = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Fetch members and their roles
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT u.id, u.display_name as username, u.username as login_name, 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,
|
||||||
|
(SELECT r2.icon_url 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_icon
|
||||||
|
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, $server_id]);
|
||||||
|
$members = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$filtered_members = null;
|
||||||
|
$channel_id = $_GET['channel_id'] ?? 0;
|
||||||
|
if ($channel_id) {
|
||||||
|
$filtered_members = [];
|
||||||
|
foreach ($members as $m) {
|
||||||
|
if (Permissions::canViewChannel($m['id'], $channel_id)) {
|
||||||
|
$filtered_members[] = $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'roles' => $roles,
|
||||||
|
'members' => $members,
|
||||||
|
'filtered_members' => $filtered_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;
|
||||||
|
|
||||||
|
// Permissions check: Owner or MANAGE_SERVER
|
||||||
|
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'create') {
|
||||||
|
$name = $data['name'] ?? 'New Role';
|
||||||
|
$color = $data['color'] ?? '#99aab5';
|
||||||
|
$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'] ?? '';
|
||||||
|
$icon_url = $data['icon_url'] ?? null;
|
||||||
|
$perms = $data['permissions'] ?? 0;
|
||||||
|
|
||||||
|
$stmt = db()->prepare("UPDATE roles SET name = ?, color = ?, icon_url = ?, permissions = ? WHERE id = ? AND server_id = ?");
|
||||||
|
$stmt->execute([$name, $color, $icon_url, $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 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]);
|
||||||
|
} elseif ($action === 'reorder') {
|
||||||
|
$orders = $data['orders'] ?? [];
|
||||||
|
foreach ($orders as $order) {
|
||||||
|
$stmt = db()->prepare("UPDATE roles SET position = ? WHERE id = ? AND server_id = ?");
|
||||||
|
$stmt->execute([$order['position'], $order['id'], $server_id]);
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} elseif ($action === 'set_user_roles') {
|
||||||
|
$target_user_id = $data['user_id'] ?? 0;
|
||||||
|
$role_ids = $data['role_ids'] ?? [];
|
||||||
|
|
||||||
|
// Begin transaction
|
||||||
|
$db = db();
|
||||||
|
$db->beginTransaction();
|
||||||
|
try {
|
||||||
|
// Remove all existing roles for this user in this server
|
||||||
|
$stmt = $db->prepare("DELETE ur FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ? AND r.server_id = ?");
|
||||||
|
$stmt->execute([$target_user_id, $server_id]);
|
||||||
|
|
||||||
|
// Add new roles
|
||||||
|
if (!empty($role_ids)) {
|
||||||
|
$stmt = $db->prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
|
||||||
|
foreach ($role_ids as $rid) {
|
||||||
|
// Verify role belongs to server
|
||||||
|
$check = $db->prepare("SELECT id FROM roles WHERE id = ? AND server_id = ?");
|
||||||
|
$check->execute([$rid, $server_id]);
|
||||||
|
if ($check->fetch()) {
|
||||||
|
$stmt->execute([$target_user_id, $rid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$db->commit();
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
189
api_v1_rss.php
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user = getCurrentUser();
|
||||||
|
$action = $_REQUEST['action'] ?? '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
|
||||||
|
// Permission check: must have manage channels permission
|
||||||
|
$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 === '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') {
|
||||||
|
// Cooldown check: only sync if last sync was > 5 minutes ago
|
||||||
|
// Or if it's a forced sync from the settings UI
|
||||||
|
$is_auto = isset($_POST['auto']) && $_POST['auto'] == 1;
|
||||||
|
|
||||||
|
// Fetch feeds for this channel
|
||||||
|
$stmt = db()->prepare("SELECT * FROM channel_rss_feeds WHERE channel_id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$feeds = $stmt->fetchAll();
|
||||||
|
|
||||||
|
if ($is_auto) {
|
||||||
|
$last_fetch = 0;
|
||||||
|
foreach ($feeds as $f) {
|
||||||
|
if ($f['last_fetched_at']) {
|
||||||
|
$ts = strtotime($f['last_fetched_at']);
|
||||||
|
if ($ts > $last_fetch) $last_fetch = $ts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (time() - $last_fetch < 120) { // 2 minutes to match JS
|
||||||
|
echo json_encode(['success' => true, 'new_items' => 0, 'skipped' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$feed_items = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$feed_items[] = $item;
|
||||||
|
}
|
||||||
|
$feed_items = array_reverse($feed_items);
|
||||||
|
|
||||||
|
foreach ($feed_items as $item) {
|
||||||
|
$guid = (string)($item->guid ?? ($item->id ?? $item->link));
|
||||||
|
if (empty($guid) && isset($item->link['href'])) {
|
||||||
|
$guid = (string)$item->link['href'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = (string)$item->title;
|
||||||
|
$link = (string)($item->link['href'] ?? $item->link);
|
||||||
|
if (empty($link) && isset($item->link)) {
|
||||||
|
foreach($item->link as $l) {
|
||||||
|
if ($l['rel'] == 'alternate' || !$l['rel']) {
|
||||||
|
$link = (string)$l['href'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = (string)($item->description ?? ($item->summary ?? $item->content));
|
||||||
|
$description = strip_tags($description);
|
||||||
|
|
||||||
|
// Extract additional fields
|
||||||
|
$category = (string)($item->category ?? ($item->category['term'] ?? ''));
|
||||||
|
$pubDate = (string)($item->pubDate ?? ($item->published ?? ($item->updated ?? '')));
|
||||||
|
|
||||||
|
// Format date nicely if possible
|
||||||
|
if ($pubDate) {
|
||||||
|
$timestamp = strtotime($pubDate);
|
||||||
|
if ($timestamp) {
|
||||||
|
$pubDate = date('d/m/Y H:i', $timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$author = (string)($item->author->name ?? ($item->author ?? ''));
|
||||||
|
if (!$author) {
|
||||||
|
$dc = $item->children('http://purl.org/dc/elements/1.1/');
|
||||||
|
if (isset($dc->creator)) {
|
||||||
|
$author = (string)$dc->creator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already exists in processed items (prevents re-posting if deleted by retention policy)
|
||||||
|
$stmt_check = db()->prepare("SELECT id FROM rss_processed_items WHERE channel_id = ? AND rss_guid = ?");
|
||||||
|
$stmt_check->execute([$channel_id, $guid]);
|
||||||
|
if ($stmt_check->fetch()) continue;
|
||||||
|
|
||||||
|
// Insert into processed items first to be safe
|
||||||
|
$stmt_processed = db()->prepare("INSERT IGNORE INTO rss_processed_items (channel_id, rss_guid) VALUES (?, ?)");
|
||||||
|
$stmt_processed->execute([$channel_id, $guid]);
|
||||||
|
|
||||||
|
// Insert as message from a special "RSS Bot" user or system
|
||||||
|
$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, display_name, is_bot, status, avatar_url, email, password_hash) VALUES ('RSS Bot', 'RSS Bot', 1, 'online', 'https://cdn-icons-png.flaticon.com/512/3607/3607436.png', 'rss-bot@system.internal', 'bot-no-password')");
|
||||||
|
$stmt_create_bot->execute();
|
||||||
|
$bot_id = db()->lastInsertId();
|
||||||
|
} else {
|
||||||
|
$bot_id = $bot['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format content for traditional view
|
||||||
|
$content = "[" . $title . "](" . $link . ")\n";
|
||||||
|
if ($category || $pubDate || $author) {
|
||||||
|
$parts = [];
|
||||||
|
if ($category) $parts[] = $category;
|
||||||
|
if ($pubDate) $parts[] = $pubDate;
|
||||||
|
if ($author) $parts[] = $author;
|
||||||
|
$content .= implode(" · ", $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$metadata = json_encode([
|
||||||
|
'title' => $title,
|
||||||
|
'description' => mb_substr($description, 0, 500) . (mb_strlen($description) > 500 ? '...' : ''),
|
||||||
|
'url' => $link,
|
||||||
|
'category' => $category,
|
||||||
|
'date' => $pubDate,
|
||||||
|
'author' => $author,
|
||||||
|
'is_rss' => true,
|
||||||
|
'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]);
|
||||||
|
enforceChannelLimit($channel_id);
|
||||||
|
$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, 'channel_id' => $channel_id]);
|
||||||
|
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;
|
||||||
|
}
|
||||||
125
api_v1_rules.php
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
$content = $_POST['content'] ?? '';
|
||||||
|
|
||||||
|
// Check if user has permission to manage channels
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (isset($data['order'])) {
|
||||||
|
// Bulk reorder
|
||||||
|
foreach ($data['order'] as $index => $id) {
|
||||||
|
// Basic permission check (optional but recommended: verify all rules belong to same server user can manage)
|
||||||
|
if ($index === 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)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stmt = db()->prepare("UPDATE channel_rules SET position = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$index + 1, $id]);
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $data['id'] ?? 0;
|
||||||
|
$dir = $data['dir'] ?? 'up';
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
$stmt = db()->prepare("SELECT c.server_id, r.channel_id, r.position FROM channels c JOIN channel_rules r ON c.id = r.channel_id WHERE r.id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$current = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($current && Permissions::hasPermission($user_id, $current['server_id'], Permissions::MANAGE_CHANNELS)) {
|
||||||
|
$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;
|
||||||
|
}
|
||||||
57
api_v1_search.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$query = $_GET['q'] ?? '';
|
||||||
|
$type = $_GET['type'] ?? 'messages'; // messages or users
|
||||||
|
$channel_id = $_GET['channel_id'] ?? 0;
|
||||||
|
|
||||||
|
if (empty($query)) {
|
||||||
|
echo json_encode(['success' => true, 'results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($type === 'users') {
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT id, display_name as username, username as login_name, avatar_url, status
|
||||||
|
FROM users
|
||||||
|
WHERE username LIKE ? OR display_name LIKE ?
|
||||||
|
LIMIT 20
|
||||||
|
");
|
||||||
|
$stmt->execute(["%" . $query . "%", "%" . $query . "%"]);
|
||||||
|
$results = $stmt->fetchAll();
|
||||||
|
} else {
|
||||||
|
$sql = "SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url
|
||||||
|
FROM messages m
|
||||||
|
JOIN users u ON m.user_id = u.id
|
||||||
|
WHERE m.content LIKE ? ";
|
||||||
|
$params = ["%" . $query . "%"];
|
||||||
|
|
||||||
|
if ($channel_id > 0) {
|
||||||
|
$sql .= " AND m.channel_id = ?";
|
||||||
|
$params[] = $channel_id;
|
||||||
|
} else {
|
||||||
|
// Search in all channels user has access to
|
||||||
|
$sql .= " AND m.channel_id IN (
|
||||||
|
SELECT c.id FROM channels c
|
||||||
|
LEFT JOIN server_members sm ON c.server_id = sm.server_id
|
||||||
|
LEFT JOIN channel_members cm ON c.id = cm.channel_id
|
||||||
|
WHERE sm.user_id = ? OR cm.user_id = ?
|
||||||
|
)";
|
||||||
|
$params[] = $user_id;
|
||||||
|
$params[] = $user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY m.created_at DESC LIMIT 50";
|
||||||
|
$stmt = db()->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$results = $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'results' => $results]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
85
api_v1_servers.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? 'create';
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($action === 'join') {
|
||||||
|
$invite_code = $_POST['invite_code'] ?? '';
|
||||||
|
$stmt = db()->prepare("SELECT id FROM servers WHERE invite_code = ?");
|
||||||
|
$stmt->execute([$invite_code]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($server) {
|
||||||
|
$stmt = db()->prepare("INSERT IGNORE INTO server_members (server_id, user_id) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$server['id'], $user_id]);
|
||||||
|
header('Location: index.php?server_id=' . $server['id']);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
die("Invalid invite code.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'update') {
|
||||||
|
$server_id = $_POST['server_id'] ?? 0;
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
$icon_url = $_POST['icon_url'] ?? '';
|
||||||
|
$theme_color = $_POST['theme_color'] ?? null;
|
||||||
|
if ($theme_color === '') $theme_color = null;
|
||||||
|
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_SERVER)) {
|
||||||
|
$stmt = db()->prepare("UPDATE servers SET name = ?, icon_url = ?, theme_color = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$name, $icon_url, $theme_color, $server_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: index.php?server_id=' . $server_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$server_id = $_POST['server_id'] ?? 0;
|
||||||
|
$stmt = db()->prepare("DELETE FROM servers WHERE id = ? AND owner_id = ?");
|
||||||
|
$stmt->execute([$server_id, $user_id]);
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
$icon_url = $_POST['icon_url'] ?? '';
|
||||||
|
|
||||||
|
if ($name) {
|
||||||
|
try {
|
||||||
|
$db = db();
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
// Create server
|
||||||
|
$invite_code = substr(strtoupper(md5(uniqid())), 0, 8);
|
||||||
|
$stmt = $db->prepare("INSERT INTO servers (name, owner_id, invite_code, icon_url) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $user_id, $invite_code, $icon_url]);
|
||||||
|
$server_id = $db->lastInsertId();
|
||||||
|
|
||||||
|
// Add owner as member
|
||||||
|
$stmt = $db->prepare("INSERT INTO server_members (server_id, user_id) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$server_id, $user_id]);
|
||||||
|
|
||||||
|
// Create default channel
|
||||||
|
$stmt = $db->prepare("INSERT INTO channels (server_id, name, type) VALUES (?, 'general', 'text')");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
|
||||||
|
// Create default @everyone role
|
||||||
|
$stmt = $db->prepare("INSERT INTO roles (server_id, name, color, permissions, position) VALUES (?, '@everyone', '#99aab5', 0, 0)");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
header('Location: index.php?server_id=' . $server_id);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
die("Error creating server: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header('Location: index.php');
|
||||||
74
api_v1_stats.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
|
||||||
|
$server_id = $_GET['server_id'] ?? 0;
|
||||||
|
if (!$server_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Server ID required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Total members
|
||||||
|
$stmt = db()->prepare("SELECT COUNT(*) as count FROM server_members WHERE server_id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$total_members = $stmt->fetch()['count'];
|
||||||
|
|
||||||
|
// Total messages in all channels of this server
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM messages m
|
||||||
|
JOIN channels c ON m.channel_id = c.id
|
||||||
|
WHERE c.server_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$total_messages = $stmt->fetch()['count'];
|
||||||
|
|
||||||
|
// Messages per day (last 7 days)
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT DATE(m.created_at) as date, COUNT(*) as count
|
||||||
|
FROM messages m
|
||||||
|
JOIN channels c ON m.channel_id = c.id
|
||||||
|
WHERE c.server_id = ? AND m.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||||
|
GROUP BY DATE(m.created_at)
|
||||||
|
ORDER BY date ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$history = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Top active users
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT u.display_name as username, u.username as login_name, COUNT(*) as message_count
|
||||||
|
FROM messages m
|
||||||
|
JOIN channels c ON m.channel_id = c.id
|
||||||
|
JOIN users u ON m.user_id = u.id
|
||||||
|
WHERE c.server_id = ?
|
||||||
|
GROUP BY m.user_id
|
||||||
|
ORDER BY message_count DESC
|
||||||
|
LIMIT 5
|
||||||
|
");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$top_users = $stmt->fetchAll();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'stats' => [
|
||||||
|
'total_members' => $total_members,
|
||||||
|
'total_messages' => $total_messages,
|
||||||
|
'history' => $history,
|
||||||
|
'top_users' => $top_users
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
54
api_v1_tags.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
$channel_id = $_GET['channel_id'] ?? 0;
|
||||||
|
if (!$channel_id) {
|
||||||
|
echo json_encode(['success' => 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;
|
||||||
|
}
|
||||||
86
api_v1_threads.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$channel_id = $_POST['channel_id'] ?? 0;
|
||||||
|
$title = $_POST['title'] ?? '';
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!$channel_id || !$title) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing data']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
if (!Permissions::canSendInChannel($user_id, $channel_id)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'You do not have permission to create threads in this channel.']);
|
||||||
|
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;
|
||||||
|
}
|
||||||
48
api_v1_user.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Detailed log
|
||||||
|
$log = [
|
||||||
|
'date' => date('Y-m-d H:i:s'),
|
||||||
|
'method' => $_SERVER['REQUEST_METHOD'],
|
||||||
|
'post' => $_POST,
|
||||||
|
'session' => $_SESSION
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$user = getCurrentUser();
|
||||||
|
if (!$user) {
|
||||||
|
$log['error'] = 'Unauthorized';
|
||||||
|
file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$log['user_id'] = $user['id'];
|
||||||
|
|
||||||
|
$display_name = !empty($_POST['display_name']) ? $_POST['display_name'] : $user['display_name'];
|
||||||
|
$avatar_url = isset($_POST['avatar_url']) ? $_POST['avatar_url'] : $user['avatar_url'];
|
||||||
|
$dnd_mode = isset($_POST['dnd_mode']) ? (int)$_POST['dnd_mode'] : 0;
|
||||||
|
$sound_notifications = isset($_POST['sound_notifications']) ? (int)$_POST['sound_notifications'] : 0;
|
||||||
|
$theme = !empty($_POST['theme']) ? $_POST['theme'] : $user['theme'];
|
||||||
|
$voice_mode = !empty($_POST['voice_mode']) ? $_POST['voice_mode'] : ($user['voice_mode'] ?? 'vox');
|
||||||
|
$voice_ptt_key = !empty($_POST['voice_ptt_key']) ? $_POST['voice_ptt_key'] : ($user['voice_ptt_key'] ?? 'v');
|
||||||
|
$voice_vox_threshold = isset($_POST['voice_vox_threshold']) ? (float)$_POST['voice_vox_threshold'] : ($user['voice_vox_threshold'] ?? 0.1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("UPDATE users SET display_name = ?, avatar_url = ?, dnd_mode = ?, sound_notifications = ?, theme = ?, voice_mode = ?, voice_ptt_key = ?, voice_vox_threshold = ? WHERE id = ?");
|
||||||
|
$success = $stmt->execute([$display_name, $avatar_url, $dnd_mode, $sound_notifications, $theme, $voice_mode, $voice_ptt_key, $voice_vox_threshold, $user['id']]);
|
||||||
|
|
||||||
|
$log['db_success'] = $success;
|
||||||
|
file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$log['db_error'] = $e->getMessage();
|
||||||
|
file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid request']);
|
||||||
239
api_v1_voice.php
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
<?php // Vocal secours — WebRTC P2P + signalisation PHP + participants + PTT + VOX (gating via GainNode)
|
||||||
|
// Mutualisé OVH : nécessite ./data écrivable.
|
||||||
|
declare(strict_types=1);
|
||||||
|
require_once "auth/session.php";
|
||||||
|
header("X-Content-Type-Options: nosniff");
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type");
|
||||||
|
|
||||||
|
$DATA_DIR = __DIR__ . "/data";
|
||||||
|
if (!is_dir($DATA_DIR)) @mkdir($DATA_DIR, 0775, true);
|
||||||
|
|
||||||
|
$user = getCurrentUser();
|
||||||
|
$current_user_id = $user ? (int)$user["id"] : 0;
|
||||||
|
|
||||||
|
function room_id(string $s): string {
|
||||||
|
$s = preg_replace("~[^a-zA-Z0-9_\-]~", "", $s);
|
||||||
|
return $s !== "" ? $s : "secours";
|
||||||
|
}
|
||||||
|
|
||||||
|
function peer_id(): string {
|
||||||
|
return bin2hex(random_bytes(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
function room_log_file(string $room): string {
|
||||||
|
return __DIR__ . "/data/" . $room . ".log";
|
||||||
|
}
|
||||||
|
|
||||||
|
function room_participants_file(string $room): string {
|
||||||
|
return __DIR__ . "/data/" . $room . ".participants.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
function chat_log_file_for_today(): string {
|
||||||
|
// Un fichier par jour : YYYY-MM-DD.chat.log
|
||||||
|
$d = date("Y-m-d");
|
||||||
|
return __DIR__ . "/data/" . $d . ".chat.log";
|
||||||
|
}
|
||||||
|
|
||||||
|
function now_ms(): int {
|
||||||
|
return (int) floor(microtime(true) * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function json_out($data, int $code = 200): void {
|
||||||
|
http_response_code($code);
|
||||||
|
header("Content-Type: application/json; charset=utf-8");
|
||||||
|
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function read_json_file(string $path): array {
|
||||||
|
if (!file_exists($path)) return [];
|
||||||
|
$raw = @file_get_contents($path);
|
||||||
|
if ($raw === false || $raw === "") return [];
|
||||||
|
$j = json_decode($raw, true);
|
||||||
|
return is_array($j) ? $j : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_json_file(string $path, array $data): void {
|
||||||
|
file_put_contents($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tail_lines(string $path, int $maxLines = 120): array {
|
||||||
|
if (!is_file($path)) return [];
|
||||||
|
$fp = fopen($path, "rb");
|
||||||
|
if (!$fp) return [];
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
fseek($fp, 0, SEEK_END);
|
||||||
|
$pos = ftell($fp);
|
||||||
|
$buffer = "";
|
||||||
|
|
||||||
|
while ($pos > 0 && count($lines) < $maxLines) {
|
||||||
|
$readSize = min($pos, 4096);
|
||||||
|
$pos -= $readSize;
|
||||||
|
fseek($fp, $pos);
|
||||||
|
$chunk = fread($fp, $readSize);
|
||||||
|
$buffer = $chunk . $buffer;
|
||||||
|
|
||||||
|
$chunkLines = explode("\n", $buffer);
|
||||||
|
$buffer = array_shift($chunkLines);
|
||||||
|
|
||||||
|
while (!empty($chunkLines)) {
|
||||||
|
$line = array_pop($chunkLines);
|
||||||
|
if (trim($line) !== "") {
|
||||||
|
array_unshift($lines, trim($line));
|
||||||
|
if (count($lines) >= $maxLines) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($fp);
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic for signaling
|
||||||
|
$action = $_REQUEST["action"] ?? "";
|
||||||
|
$room = room_id($_REQUEST["room"] ?? "secours");
|
||||||
|
$my_id = $_REQUEST["peer_id"] ?? "";
|
||||||
|
|
||||||
|
if ($action === "join") {
|
||||||
|
$name = $_REQUEST["name"] ?? "User";
|
||||||
|
$p_file = room_participants_file($room);
|
||||||
|
$ps = read_json_file($p_file);
|
||||||
|
|
||||||
|
// Cleanup old participants (> 10s)
|
||||||
|
$stale_time = now_ms() - 10000;
|
||||||
|
foreach ($ps as $id => $p) {
|
||||||
|
if (($p["last_seen"] ?? 0) < $stale_time) unset($ps[$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_id = peer_id();
|
||||||
|
$ps[$new_id] = [
|
||||||
|
"id" => $new_id,
|
||||||
|
"user_id" => $current_user_id,
|
||||||
|
"name" => $name,
|
||||||
|
"avatar_url" => $user["avatar_url"] ?? "",
|
||||||
|
"last_seen" => now_ms()
|
||||||
|
];
|
||||||
|
write_json_file($p_file, $ps);
|
||||||
|
|
||||||
|
// DB Integration for sidebar
|
||||||
|
if ($current_user_id > 0) {
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO voice_sessions (user_id, channel_id, last_seen) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE channel_id = ?, last_seen = ?");
|
||||||
|
$stmt->execute([$current_user_id, $room, now_ms(), $room, now_ms()]);
|
||||||
|
} catch (Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_out(["success" => true, "peer_id" => $new_id, "participants" => $ps]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === "poll") {
|
||||||
|
if (!$my_id) json_out(["error" => "Missing peer_id"], 400);
|
||||||
|
$p_file = room_participants_file($room);
|
||||||
|
$ps = read_json_file($p_file);
|
||||||
|
|
||||||
|
if (isset($ps[$my_id])) {
|
||||||
|
$ps[$my_id]["last_seen"] = now_ms();
|
||||||
|
}
|
||||||
|
|
||||||
|
$stale_time = now_ms() - 10000;
|
||||||
|
foreach ($ps as $id => $p) {
|
||||||
|
if (($p["last_seen"] ?? 0) < $stale_time) unset($ps[$id]);
|
||||||
|
}
|
||||||
|
write_json_file($p_file, $ps);
|
||||||
|
|
||||||
|
// Update DB last_seen
|
||||||
|
if ($current_user_id > 0) {
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("UPDATE voice_sessions SET last_seen = ? WHERE user_id = ?");
|
||||||
|
$stmt->execute([now_ms(), $current_user_id]);
|
||||||
|
} catch (Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read signals
|
||||||
|
$log_file = room_log_file($room);
|
||||||
|
$signals = [];
|
||||||
|
if (file_exists($log_file)) {
|
||||||
|
$lines = file($log_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
|
$remaining = [];
|
||||||
|
$now = now_ms();
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$sig = json_decode($line, true);
|
||||||
|
if ($sig && isset($sig["to"]) && $sig["to"] === $my_id) {
|
||||||
|
$signals[] = $sig;
|
||||||
|
} elseif ($sig && ($now - ($sig["time"] ?? 0) < 30000)) {
|
||||||
|
$remaining[] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_put_contents($log_file, implode("\n", $remaining) . (empty($remaining) ? "" : "\n"), LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_out(["success" => true, "participants" => $ps, "signals" => $signals]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === "signal") {
|
||||||
|
if (!$my_id) json_out(["error" => "Missing peer_id"], 400);
|
||||||
|
$to = $_REQUEST["to"] ?? "";
|
||||||
|
$data = $_REQUEST["data"] ?? "";
|
||||||
|
if (!$to || !$data) json_out(["error" => "Missing to/data"], 400);
|
||||||
|
|
||||||
|
$sig = [
|
||||||
|
"from" => $my_id,
|
||||||
|
"to" => $to,
|
||||||
|
"data" => json_decode($data, true),
|
||||||
|
"time" => now_ms()
|
||||||
|
];
|
||||||
|
|
||||||
|
file_put_contents(room_log_file($room), json_encode($sig) . "\n", FILE_APPEND | LOCK_EX);
|
||||||
|
json_out(["success" => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === "list_all") {
|
||||||
|
// Periodic cleanup of the DB table (stale sessions > 15s)
|
||||||
|
if (rand(1, 10) === 1) {
|
||||||
|
try {
|
||||||
|
$stale_db_time = now_ms() - 15000;
|
||||||
|
$stmt = db()->prepare("DELETE FROM voice_sessions WHERE last_seen < ?");
|
||||||
|
$stmt->execute([$stale_db_time]);
|
||||||
|
} catch (Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT vs.channel_id, vs.user_id, u.username, u.display_name, u.avatar_url
|
||||||
|
FROM voice_sessions vs
|
||||||
|
JOIN users u ON vs.user_id = u.id
|
||||||
|
WHERE vs.last_seen > ?
|
||||||
|
");
|
||||||
|
$stale_db_time = now_ms() - 15000;
|
||||||
|
$stmt->execute([$stale_db_time]);
|
||||||
|
$sessions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$by_channel = [];
|
||||||
|
foreach ($sessions as $s) {
|
||||||
|
$by_channel[$s['channel_id']][] = $s;
|
||||||
|
}
|
||||||
|
json_out(["success" => true, "channels" => $by_channel]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
json_out(["error" => $e->getMessage()], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === "leave") {
|
||||||
|
if ($my_id) {
|
||||||
|
$p_file = room_participants_file($room);
|
||||||
|
$ps = read_json_file($p_file);
|
||||||
|
unset($ps[$my_id]);
|
||||||
|
write_json_file($p_file, $ps);
|
||||||
|
}
|
||||||
|
if ($current_user_id > 0) {
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("DELETE FROM voice_sessions WHERE user_id = ?");
|
||||||
|
$stmt->execute([$current_user_id]);
|
||||||
|
} catch (Exception $e) {}
|
||||||
|
}
|
||||||
|
json_out(["success" => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_out(["error" => "Unknown action"], 404);
|
||||||
85
api_v1_webhook.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
|
||||||
|
// Check for execution (no session needed, just token)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['token'])) {
|
||||||
|
require_once 'db/config.php';
|
||||||
|
$token = $_GET['token'] ?? '';
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$content = $data['content'] ?? '';
|
||||||
|
|
||||||
|
$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 {
|
||||||
|
$stmt = db()->prepare("INSERT INTO messages (channel_id, user_id, content) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$webhook['channel_id'], 1, $content]); // 1 is system/bot user
|
||||||
|
enforceChannelLimit($webhook['channel_id']);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage webhooks (session needed)
|
||||||
|
requireLogin();
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
$server_id = $_GET['server_id'] ?? 0;
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT w.*, c.name as channel_name
|
||||||
|
FROM webhooks w
|
||||||
|
JOIN channels c ON w.channel_id = c.id
|
||||||
|
WHERE c.server_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$webhooks = $stmt->fetchAll();
|
||||||
|
echo json_encode(['success' => true, 'webhooks' => $webhooks]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$channel_id = $data['channel_id'] ?? 0;
|
||||||
|
$name = $data['name'] ?? 'New Webhook';
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO webhooks (channel_id, name, token) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$channel_id, $name, $token]);
|
||||||
|
echo json_encode(['success' => true, 'webhook' => ['id' => db()->lastInsertId(), 'token' => $token]]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$id = $data['id'] ?? 0;
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("DELETE FROM webhooks WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
50
api_v1_withdraw_rules.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$json = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$channel_id = $json['channel_id'] ?? 0;
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if (!$channel_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'ID de canal manquant']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch channel details to get rules_role_id
|
||||||
|
$stmt = db()->prepare("SELECT * FROM channels WHERE id = ? AND type = 'rules'");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$channel = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$channel) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Canal de règles introuvable']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$role_id = $channel['rules_role_id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
db()->beginTransaction();
|
||||||
|
|
||||||
|
// 1. Remove acceptance record
|
||||||
|
$stmtAcc = db()->prepare("DELETE FROM rule_acceptances WHERE user_id = ? AND channel_id = ?");
|
||||||
|
$stmtAcc->execute([$user_id, $channel_id]);
|
||||||
|
|
||||||
|
// 2. Remove role if it was configured
|
||||||
|
if ($role_id) {
|
||||||
|
$stmtRole = db()->prepare("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?");
|
||||||
|
$stmtRole->execute([$user_id, $role_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
db()->commit();
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
db()->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Erreur lors du retrait : ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Méthode non autorisée']);
|
||||||
1433
assets/css/discord.css
Normal file
BIN
assets/images/custom_emotes/1771261756_shield.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
2741
assets/js/main.js
Normal file
670
assets/js/voice.js
Normal file
@ -0,0 +1,670 @@
|
|||||||
|
console.log('voice.js loaded');
|
||||||
|
|
||||||
|
class VoiceChannel {
|
||||||
|
constructor(ws, settings) {
|
||||||
|
this.ws = ws;
|
||||||
|
this.settings = settings || { mode: 'vox', pttKey: 'v', voxThreshold: 0.1 };
|
||||||
|
console.log('VoiceChannel constructor called with settings:', this.settings);
|
||||||
|
this.localStream = null;
|
||||||
|
this.analysisStream = null;
|
||||||
|
this.peers = {}; // userId -> RTCPeerConnection
|
||||||
|
this.participants = {}; // userId -> {name}
|
||||||
|
this.currentChannelId = null;
|
||||||
|
this.myPeerId = null;
|
||||||
|
this.pollInterval = null;
|
||||||
|
this.remoteAudios = {}; // userId -> Audio element
|
||||||
|
this.pendingCandidates = {}; // userId -> array of candidates
|
||||||
|
|
||||||
|
this.audioContext = null;
|
||||||
|
this.analyser = null;
|
||||||
|
this.microphone = null;
|
||||||
|
this.scriptProcessor = null;
|
||||||
|
this.delayNode = null;
|
||||||
|
this.voxDestination = null;
|
||||||
|
|
||||||
|
this.isTalking = false;
|
||||||
|
this.pttPressed = false;
|
||||||
|
this.voxActive = false;
|
||||||
|
this.lastVoiceTime = 0;
|
||||||
|
this.voxHoldTime = 500;
|
||||||
|
|
||||||
|
// Track who is speaking to persist across UI refreshes
|
||||||
|
this.speakingUsers = new Set();
|
||||||
|
|
||||||
|
this.setupPTTListeners();
|
||||||
|
window.addEventListener('beforeunload', () => this.leave());
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPTTListeners() {
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
// Ignore if in input field
|
||||||
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||||
|
|
||||||
|
if (this.settings.mode !== 'ptt') return;
|
||||||
|
|
||||||
|
const isMatch = e.key.toLowerCase() === this.settings.pttKey.toLowerCase() ||
|
||||||
|
(e.code && e.code.toLowerCase() === this.settings.pttKey.toLowerCase()) ||
|
||||||
|
(this.settings.pttKey === '0' && e.code === 'Numpad0');
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
if (!this.pttPressed) {
|
||||||
|
console.log('PTT Key Pressed:', e.key, e.code, 'Expected:', this.settings.pttKey);
|
||||||
|
this.pttPressed = true;
|
||||||
|
this.updateMuteState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keyup', (e) => {
|
||||||
|
if (this.settings.mode !== 'ptt') return;
|
||||||
|
|
||||||
|
const isMatch = e.key.toLowerCase() === this.settings.pttKey.toLowerCase() ||
|
||||||
|
(e.code && e.code.toLowerCase() === this.settings.pttKey.toLowerCase()) ||
|
||||||
|
(this.settings.pttKey === '0' && e.code === 'Numpad0');
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
console.log('PTT Key Released:', e.key, e.code, 'Expected:', this.settings.pttKey);
|
||||||
|
this.pttPressed = false;
|
||||||
|
this.updateMuteState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async join(channelId) {
|
||||||
|
console.log('VoiceChannel.join process started for channel:', channelId);
|
||||||
|
if (this.currentChannelId === channelId) {
|
||||||
|
console.log('Already in this channel');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.currentChannelId) {
|
||||||
|
console.log('Leaving previous channel:', this.currentChannelId);
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentChannelId = channelId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Requesting microphone access...');
|
||||||
|
this.localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
|
||||||
|
console.log('Microphone access granted');
|
||||||
|
|
||||||
|
// Always setup VOX logic for volume meter and detection
|
||||||
|
this.setupVOX();
|
||||||
|
|
||||||
|
// Initial mute (on the buffered stream if it exists)
|
||||||
|
this.setMute(true);
|
||||||
|
|
||||||
|
// Join via PHP
|
||||||
|
console.log('Calling API join...');
|
||||||
|
const url = `api_v1_voice.php?action=join&room=${channelId}&name=${encodeURIComponent(window.currentUsername || 'Unknown')}`;
|
||||||
|
const resp = await fetch(url);
|
||||||
|
const data = await resp.json();
|
||||||
|
console.log('API join response:', data);
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.myPeerId = data.peer_id;
|
||||||
|
console.log('Joined room with peer_id:', this.myPeerId);
|
||||||
|
|
||||||
|
// Start polling
|
||||||
|
this.startPolling();
|
||||||
|
this.updateVoiceUI();
|
||||||
|
} else {
|
||||||
|
console.error('API join failed:', data.error);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to join voice:', e);
|
||||||
|
alert('Microphone access required for voice channels. Error: ' + e.message);
|
||||||
|
this.currentChannelId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startPolling() {
|
||||||
|
if (this.pollInterval) clearInterval(this.pollInterval);
|
||||||
|
this.pollInterval = setInterval(() => this.poll(), 1000);
|
||||||
|
this.poll(); // Initial poll
|
||||||
|
}
|
||||||
|
|
||||||
|
async poll() {
|
||||||
|
if (!this.myPeerId || !this.currentChannelId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`api_v1_voice.php?action=poll&room=${this.currentChannelId}&peer_id=${this.myPeerId}`);
|
||||||
|
const data = await resp.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// Update participants
|
||||||
|
const oldPs = Object.keys(this.participants);
|
||||||
|
this.participants = data.participants;
|
||||||
|
const newPs = Object.keys(this.participants);
|
||||||
|
|
||||||
|
// If new people joined, initiate offer if I'm the "older" one (prevent glare by comparing IDs)
|
||||||
|
newPs.forEach(pid => {
|
||||||
|
if (pid !== this.myPeerId && !this.peers[pid]) {
|
||||||
|
// Only initiate if my ID is greater than theirs (lexicographical comparison)
|
||||||
|
if (this.myPeerId > pid) {
|
||||||
|
console.log('Initiating offer to new peer:', pid);
|
||||||
|
this.createPeerConnection(pid, true);
|
||||||
|
} else {
|
||||||
|
console.log('Waiting for offer from peer:', pid);
|
||||||
|
// Just create the PC without offering, it will handle the incoming offer
|
||||||
|
this.createPeerConnection(pid, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup left peers
|
||||||
|
oldPs.forEach(pid => {
|
||||||
|
if (!this.participants[pid] && this.peers[pid]) {
|
||||||
|
console.log('Peer left:', pid);
|
||||||
|
try {
|
||||||
|
this.peers[pid].close();
|
||||||
|
} catch(e) {}
|
||||||
|
delete this.peers[pid];
|
||||||
|
|
||||||
|
if (this.remoteAudios[pid]) {
|
||||||
|
try {
|
||||||
|
this.remoteAudios[pid].pause();
|
||||||
|
this.remoteAudios[pid].srcObject = null;
|
||||||
|
this.remoteAudios[pid].remove();
|
||||||
|
} catch(e) {}
|
||||||
|
delete this.remoteAudios[pid];
|
||||||
|
}
|
||||||
|
// Also cleanup by class just in case
|
||||||
|
document.querySelectorAll(`.voice-remote-audio[data-peer-id="${pid}"]`).forEach(el => el.remove());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle incoming signals
|
||||||
|
if (data.signals && data.signals.length > 0) {
|
||||||
|
for (const sig of data.signals) {
|
||||||
|
await this.handleSignaling(sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateVoiceUI();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Polling error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendSignal(to, data) {
|
||||||
|
if (!this.myPeerId || !this.currentChannelId) return;
|
||||||
|
await fetch(`api_v1_voice.php?action=signal&room=${this.currentChannelId}&peer_id=${this.myPeerId}&to=${to}&data=${encodeURIComponent(JSON.stringify(data))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleSignaling(sig) {
|
||||||
|
const { from, data } = sig;
|
||||||
|
console.log('Handling signaling from:', from, data.type);
|
||||||
|
switch (data.type) {
|
||||||
|
case 'offer':
|
||||||
|
await this.handleOffer(from, data.offer);
|
||||||
|
break;
|
||||||
|
case 'answer':
|
||||||
|
await this.handleAnswer(from, data.answer);
|
||||||
|
break;
|
||||||
|
case 'ice_candidate':
|
||||||
|
await this.handleCandidate(from, data.candidate);
|
||||||
|
break;
|
||||||
|
case 'voice_speaking':
|
||||||
|
this.updateSpeakingUI(data.user_id, data.speaking);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createPeerConnection(userId, isOfferor) {
|
||||||
|
if (this.peers[userId]) return this.peers[userId];
|
||||||
|
|
||||||
|
console.log('Creating PeerConnection for:', userId, 'as offeror:', isOfferor);
|
||||||
|
const pc = new RTCPeerConnection({
|
||||||
|
iceServers: [
|
||||||
|
{ urls: 'stun:stun.l.google.com:19302' },
|
||||||
|
{ urls: 'stun:stun1.l.google.com:19302' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.peers[userId] = pc;
|
||||||
|
this.pendingCandidates[userId] = [];
|
||||||
|
|
||||||
|
pc.oniceconnectionstatechange = () => {
|
||||||
|
console.log(`ICE Connection State with ${userId}: ${pc.iceConnectionState}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.localStream) {
|
||||||
|
const streamToShare = (this.voxDestination && this.voxDestination.stream) ? this.voxDestination.stream : this.localStream;
|
||||||
|
streamToShare.getTracks().forEach(track => {
|
||||||
|
console.log(`Adding track ${track.kind} to peer ${userId}`);
|
||||||
|
pc.addTrack(track, streamToShare);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.onicecandidate = (event) => {
|
||||||
|
if (event.candidate) {
|
||||||
|
this.sendSignal(userId, { type: 'ice_candidate', candidate: event.candidate });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pc.ontrack = (event) => {
|
||||||
|
console.log('Received remote track from:', userId, event);
|
||||||
|
const stream = event.streams[0] || new MediaStream([event.track]);
|
||||||
|
|
||||||
|
if (this.remoteAudios[userId]) {
|
||||||
|
console.log('Updating existing remote audio for:', userId);
|
||||||
|
this.remoteAudios[userId].srcObject = stream;
|
||||||
|
} else {
|
||||||
|
console.log('Creating new remote audio for:', userId);
|
||||||
|
const remoteAudio = new Audio();
|
||||||
|
remoteAudio.classList.add('voice-remote-audio');
|
||||||
|
remoteAudio.dataset.peerId = userId;
|
||||||
|
remoteAudio.autoplay = true;
|
||||||
|
remoteAudio.playsInline = true;
|
||||||
|
remoteAudio.srcObject = stream;
|
||||||
|
document.body.appendChild(remoteAudio);
|
||||||
|
this.remoteAudios[userId] = remoteAudio;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.remoteAudios[userId].play().then(() => {
|
||||||
|
console.log('Audio playing for peer:', userId);
|
||||||
|
}).catch(e => {
|
||||||
|
console.warn('Autoplay prevented or failed for:', userId, e);
|
||||||
|
// Try again on any click
|
||||||
|
const retry = () => {
|
||||||
|
this.remoteAudios[userId].play();
|
||||||
|
window.removeEventListener('click', retry);
|
||||||
|
};
|
||||||
|
window.addEventListener('click', retry);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isOfferor) {
|
||||||
|
pc.createOffer().then(offer => {
|
||||||
|
return pc.setLocalDescription(offer);
|
||||||
|
}).then(() => {
|
||||||
|
this.sendSignal(userId, { type: 'offer', offer: pc.localDescription });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleOffer(from, offer) {
|
||||||
|
console.log('Received offer from:', from);
|
||||||
|
const pc = this.createPeerConnection(from, false);
|
||||||
|
try {
|
||||||
|
await pc.setRemoteDescription(new RTCSessionDescription(offer));
|
||||||
|
console.log('Remote description set for offer from:', from);
|
||||||
|
const answer = await pc.createAnswer();
|
||||||
|
await pc.setLocalDescription(answer);
|
||||||
|
console.log('Local description (answer) set for:', from);
|
||||||
|
this.sendSignal(from, { type: 'answer', answer: pc.localDescription });
|
||||||
|
await this.processPendingCandidates(from);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error handling offer from', from, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAnswer(from, answer) {
|
||||||
|
console.log('Received answer from:', from);
|
||||||
|
const pc = this.peers[from];
|
||||||
|
if (pc) {
|
||||||
|
try {
|
||||||
|
await pc.setRemoteDescription(new RTCSessionDescription(answer));
|
||||||
|
console.log('Remote description (answer) set for:', from);
|
||||||
|
await this.processPendingCandidates(from);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error handling answer from', from, e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('Received answer but no peer connection for:', from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleCandidate(from, candidate) {
|
||||||
|
console.log('Received ICE candidate from:', from);
|
||||||
|
const pc = this.peers[from];
|
||||||
|
if (pc && pc.remoteDescription) {
|
||||||
|
try {
|
||||||
|
await pc.addIceCandidate(new RTCIceCandidate(candidate));
|
||||||
|
console.log('Added ICE candidate for:', from);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error adding ice candidate from', from, e);
|
||||||
|
}
|
||||||
|
} else if (pc) {
|
||||||
|
console.log('Queuing ICE candidate for:', from);
|
||||||
|
this.pendingCandidates[from].push(candidate);
|
||||||
|
} else {
|
||||||
|
console.warn('Received ICE candidate but no peer connection for:', from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processPendingCandidates(userId) {
|
||||||
|
const pc = this.peers[userId];
|
||||||
|
const candidates = this.pendingCandidates[userId];
|
||||||
|
if (pc && pc.remoteDescription && candidates && candidates.length > 0) {
|
||||||
|
console.log(`Processing ${candidates.length} pending candidates for ${userId}`);
|
||||||
|
while (candidates.length > 0) {
|
||||||
|
const cand = candidates.shift();
|
||||||
|
try {
|
||||||
|
await pc.addIceCandidate(new RTCIceCandidate(cand));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error processing pending candidate', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupVOX() {
|
||||||
|
if (!this.localStream) {
|
||||||
|
console.warn('Cannot setup VOX: no localStream');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Setting up VOX logic...');
|
||||||
|
try {
|
||||||
|
if (!this.audioContext) {
|
||||||
|
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-ensure context is running
|
||||||
|
if (this.audioContext.state === 'suspended') {
|
||||||
|
this.audioContext.resume().then(() => console.log('AudioContext resumed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup old nodes
|
||||||
|
if (this.scriptProcessor) {
|
||||||
|
this.scriptProcessor.onaudioprocess = null;
|
||||||
|
try { this.scriptProcessor.disconnect(); } catch(e) {}
|
||||||
|
}
|
||||||
|
if (this.microphone) {
|
||||||
|
try { this.microphone.disconnect(); } catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.analyser = this.audioContext.createAnalyser();
|
||||||
|
this.analyser.fftSize = 512;
|
||||||
|
|
||||||
|
// Use a cloned stream for analysis so VOX works even when localStream is muted/disabled
|
||||||
|
if (this.analysisStream) {
|
||||||
|
this.analysisStream.getTracks().forEach(t => t.stop());
|
||||||
|
}
|
||||||
|
this.analysisStream = this.localStream.clone();
|
||||||
|
this.analysisStream.getAudioTracks().forEach(t => t.enabled = true); // Ensure analysis stream is NOT muted
|
||||||
|
|
||||||
|
this.microphone = this.audioContext.createMediaStreamSource(this.analysisStream);
|
||||||
|
this.scriptProcessor = this.audioContext.createScriptProcessor(2048, 1, 1);
|
||||||
|
|
||||||
|
// Setup Delay Buffer for VOX
|
||||||
|
this.delayNode = this.audioContext.createDelay(1.0);
|
||||||
|
this.delayNode.delayTime.value = 0.3; // 300ms buffer
|
||||||
|
this.voxDestination = this.audioContext.createMediaStreamDestination();
|
||||||
|
|
||||||
|
this.microphone.connect(this.delayNode);
|
||||||
|
this.delayNode.connect(this.voxDestination);
|
||||||
|
|
||||||
|
this.microphone.connect(this.analyser);
|
||||||
|
this.analyser.connect(this.scriptProcessor);
|
||||||
|
|
||||||
|
// Avoid feedback: connect to a gain node with 0 volume then to destination
|
||||||
|
const silence = this.audioContext.createGain();
|
||||||
|
silence.gain.value = 0;
|
||||||
|
this.scriptProcessor.connect(silence);
|
||||||
|
silence.connect(this.audioContext.destination);
|
||||||
|
|
||||||
|
this.voxActive = false;
|
||||||
|
this.currentVolume = 0;
|
||||||
|
|
||||||
|
this.scriptProcessor.onaudioprocess = () => {
|
||||||
|
const array = new Uint8Array(this.analyser.frequencyBinCount);
|
||||||
|
this.analyser.getByteFrequencyData(array);
|
||||||
|
let values = 0;
|
||||||
|
for (let i = 0; i < array.length; i++) values += array[i];
|
||||||
|
const average = values / array.length;
|
||||||
|
this.currentVolume = average / 255;
|
||||||
|
|
||||||
|
if (this.settings.mode !== 'vox') {
|
||||||
|
this.voxActive = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentVolume > this.settings.voxThreshold) {
|
||||||
|
this.lastVoiceTime = Date.now();
|
||||||
|
if (!this.voxActive) {
|
||||||
|
this.voxActive = true;
|
||||||
|
this.updateMuteState();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.voxActive && Date.now() - this.lastVoiceTime > this.voxHoldTime) {
|
||||||
|
this.voxActive = false;
|
||||||
|
this.updateMuteState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
console.log('VOX logic setup complete');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to setup VOX:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getVolume() {
|
||||||
|
return this.currentVolume || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMuteState() {
|
||||||
|
if (!this.currentChannelId || !this.localStream) return;
|
||||||
|
let shouldTalk = (this.settings.mode === 'ptt') ? this.pttPressed : this.voxActive;
|
||||||
|
console.log('updateMuteState: shouldTalk =', shouldTalk, 'mode =', this.settings.mode);
|
||||||
|
if (this.isTalking !== shouldTalk) {
|
||||||
|
this.isTalking = shouldTalk;
|
||||||
|
this.setMute(!shouldTalk);
|
||||||
|
this.updateSpeakingUI(window.currentUserId, shouldTalk);
|
||||||
|
|
||||||
|
// Notify others
|
||||||
|
const msg = { type: 'voice_speaking', channel_id: this.currentChannelId, user_id: window.currentUserId, speaking: shouldTalk };
|
||||||
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||||
|
this.ws.send(JSON.stringify(msg));
|
||||||
|
} else {
|
||||||
|
Object.keys(this.peers).forEach(pid => {
|
||||||
|
this.sendSignal(pid, msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMute(mute) {
|
||||||
|
// We mute the destination stream (delayed) instead of the localStream source
|
||||||
|
// to ensure the delay buffer keeps filling with live audio.
|
||||||
|
const streamToMute = (this.voxDestination && this.voxDestination.stream) ? this.voxDestination.stream : this.localStream;
|
||||||
|
if (streamToMute) {
|
||||||
|
console.log('Setting mute to:', mute, 'on stream:', streamToMute.id);
|
||||||
|
streamToMute.getAudioTracks().forEach(track => { track.enabled = !mute; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leave() {
|
||||||
|
if (!this.currentChannelId) return;
|
||||||
|
const roomToLeave = this.currentChannelId;
|
||||||
|
const myIdToLeave = this.myPeerId;
|
||||||
|
console.log('Leaving voice channel:', roomToLeave);
|
||||||
|
|
||||||
|
// 1. Clear interval and set state to null immediately to stop any ongoing poll/logic
|
||||||
|
if (this.pollInterval) clearInterval(this.pollInterval);
|
||||||
|
this.pollInterval = null;
|
||||||
|
this.currentChannelId = null;
|
||||||
|
this.myPeerId = null;
|
||||||
|
|
||||||
|
// 2. Notify server
|
||||||
|
const leaveUrl = `api_v1_voice.php?action=leave&room=${roomToLeave}&peer_id=${myIdToLeave}`;
|
||||||
|
if (navigator.sendBeacon) {
|
||||||
|
navigator.sendBeacon(leaveUrl);
|
||||||
|
} else {
|
||||||
|
fetch(leaveUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Stop all local audio tracks
|
||||||
|
if (this.localStream) {
|
||||||
|
console.log('Stopping localStream tracks');
|
||||||
|
this.localStream.getTracks().forEach(track => {
|
||||||
|
track.enabled = false;
|
||||||
|
track.stop();
|
||||||
|
});
|
||||||
|
this.localStream = null;
|
||||||
|
}
|
||||||
|
if (this.analysisStream) {
|
||||||
|
this.analysisStream.getTracks().forEach(track => track.stop());
|
||||||
|
this.analysisStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Cleanup VOX/AudioContext nodes
|
||||||
|
if (this.scriptProcessor) {
|
||||||
|
try {
|
||||||
|
this.scriptProcessor.onaudioprocess = null;
|
||||||
|
this.scriptProcessor.disconnect();
|
||||||
|
} catch(e) {}
|
||||||
|
this.scriptProcessor = null;
|
||||||
|
}
|
||||||
|
if (this.microphone) {
|
||||||
|
try { this.microphone.disconnect(); } catch(e) {}
|
||||||
|
this.microphone = null;
|
||||||
|
}
|
||||||
|
if (this.delayNode) {
|
||||||
|
try { this.delayNode.disconnect(); } catch(e) {}
|
||||||
|
this.delayNode = null;
|
||||||
|
}
|
||||||
|
this.voxDestination = null;
|
||||||
|
|
||||||
|
if (this.audioContext && this.audioContext.state !== 'closed') {
|
||||||
|
try { this.audioContext.suspend(); } catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Close all peer connections
|
||||||
|
console.log('Closing all peer connections:', Object.keys(this.peers));
|
||||||
|
Object.keys(this.peers).forEach(pid => {
|
||||||
|
try {
|
||||||
|
this.peers[pid].onicecandidate = null;
|
||||||
|
this.peers[pid].ontrack = null;
|
||||||
|
this.peers[pid].oniceconnectionstatechange = null;
|
||||||
|
this.peers[pid].close();
|
||||||
|
} catch(e) { console.error('Error closing peer:', pid, e); }
|
||||||
|
delete this.peers[pid];
|
||||||
|
});
|
||||||
|
this.peers = {};
|
||||||
|
|
||||||
|
// 6. Remove all remote audio elements
|
||||||
|
document.querySelectorAll('.voice-remote-audio').forEach(audio => {
|
||||||
|
try {
|
||||||
|
audio.pause();
|
||||||
|
audio.srcObject = null;
|
||||||
|
audio.remove();
|
||||||
|
} catch(e) {}
|
||||||
|
});
|
||||||
|
this.remoteAudios = {};
|
||||||
|
this.pendingCandidates = {};
|
||||||
|
|
||||||
|
this.participants = {};
|
||||||
|
this.speakingUsers.clear();
|
||||||
|
this.isTalking = false;
|
||||||
|
this.voxActive = false;
|
||||||
|
|
||||||
|
// Final notify speaking false
|
||||||
|
if (roomToLeave) {
|
||||||
|
const msg = { type: 'voice_speaking', channel_id: roomToLeave, user_id: window.currentUserId, speaking: false };
|
||||||
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||||
|
try { this.ws.send(JSON.stringify(msg)); } catch(e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Update UI
|
||||||
|
this.updateVoiceUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateVoiceUI() {
|
||||||
|
// We now use a global update mechanism for all channels
|
||||||
|
VoiceChannel.refreshAllVoiceUsers();
|
||||||
|
|
||||||
|
if (this.currentChannelId) {
|
||||||
|
if (!document.querySelector('.voice-controls')) {
|
||||||
|
const controls = document.createElement('div');
|
||||||
|
controls.className = 'voice-controls p-2 d-flex justify-content-between align-items-center border-top bg-dark';
|
||||||
|
controls.style.backgroundColor = '#232428';
|
||||||
|
controls.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="voice-status-icon text-success me-2" style="font-size: 8px;">●</div>
|
||||||
|
<div class="small fw-bold" style="font-size: 11px; color: #248046;">Voice (${this.settings.mode.toUpperCase()})</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-sm text-muted" id="btn-voice-leave" title="Disconnect">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91"></path><line x1="23" y1="1" x2="1" y2="23"></line></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const sidebar = document.querySelector('.channels-sidebar');
|
||||||
|
if (sidebar) sidebar.appendChild(controls);
|
||||||
|
const btnLeave = document.getElementById('btn-voice-leave');
|
||||||
|
if (btnLeave) btnLeave.onclick = () => this.leave();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const controls = document.querySelector('.voice-controls');
|
||||||
|
if (controls) controls.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSpeakingUI(userId, isSpeaking) {
|
||||||
|
if (isSpeaking) {
|
||||||
|
this.speakingUsers.add(userId);
|
||||||
|
} else {
|
||||||
|
this.speakingUsers.delete(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userEls = document.querySelectorAll(`.voice-user[data-user-id="${userId}"]`);
|
||||||
|
userEls.forEach(el => {
|
||||||
|
const avatar = el.querySelector('.message-avatar');
|
||||||
|
if (avatar) {
|
||||||
|
avatar.style.boxShadow = isSpeaking ? '0 0 0 2px #23a559' : 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async refreshAllVoiceUsers() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('api_v1_voice.php?action=list_all');
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.success) {
|
||||||
|
// Clear all lists first
|
||||||
|
document.querySelectorAll('.voice-users-list').forEach(el => el.innerHTML = '');
|
||||||
|
|
||||||
|
// Populate based on data
|
||||||
|
Object.keys(data.channels).forEach(channelId => {
|
||||||
|
// Fix: The voice-users-list is a sibling of the container of the voice-item
|
||||||
|
const voiceItem = document.querySelector(`.voice-item[data-channel-id="${channelId}"]`);
|
||||||
|
if (voiceItem) {
|
||||||
|
const container = voiceItem.closest('.channel-item-container');
|
||||||
|
if (container) {
|
||||||
|
const listEl = container.querySelector('.voice-users-list');
|
||||||
|
if (listEl) {
|
||||||
|
data.channels[channelId].forEach(p => {
|
||||||
|
const isSpeaking = window.voiceHandler && window.voiceHandler.speakingUsers.has(p.user_id);
|
||||||
|
VoiceChannel.renderUserToUI(listEl, p.user_id, p.display_name || p.username, p.avatar_url, isSpeaking);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to refresh voice users:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static renderUserToUI(container, userId, username, avatarUrl, isSpeaking = false) {
|
||||||
|
const userEl = document.createElement('div');
|
||||||
|
userEl.className = 'voice-user small text-muted d-flex align-items-center mb-1';
|
||||||
|
userEl.dataset.userId = userId;
|
||||||
|
userEl.style.paddingLeft = '8px';
|
||||||
|
const avatarStyle = avatarUrl ? `background-image: url('${avatarUrl}'); background-size: cover;` : "background-color: #555;";
|
||||||
|
const boxShadow = isSpeaking ? 'box-shadow: 0 0 0 2px #23a559;' : '';
|
||||||
|
userEl.innerHTML = `
|
||||||
|
<div class="message-avatar me-2" style="width: 16px; height: 16px; border-radius: 50%; transition: box-shadow 0.2s; ${avatarStyle} ${boxShadow}"></div>
|
||||||
|
<span style="font-size: 13px;">${username}</span>
|
||||||
|
`;
|
||||||
|
container.appendChild(userEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
assets/pasted-20260215-151928-c94822be.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
assets/pasted-20260215-153522-763a8478.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
assets/pasted-20260215-162151-f0d79b58.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
assets/pasted-20260215-164921-daccb69a.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
assets/pasted-20260215-214239-79c3300e.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
assets/pasted-20260216-132213-80b79cbe.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
assets/pasted-20260216-162915-c3590120.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
assets/pasted-20260216-163622-0013f90f.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
assets/pasted-20260216-170004-77ff069d.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/pasted-20260216-225623-7f182d79.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
assets/pasted-20260217-121543-09802912.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/pasted-20260217-141526-2008a77e.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/pasted-20260217-143739-c7f88b4b.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
assets/pasted-20260217-232157-017555a6.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/pasted-20260217-232953-088c1965.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
69
auth/login.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/session.php';
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$email = $_POST['email'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if ($email && $password) {
|
||||||
|
$stmt = db()->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.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Login | Discord Clone</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="../assets/css/discord.css">
|
||||||
|
<style>
|
||||||
|
body { background-color: #313338; display: flex; align-items: center; justify-content: center; height: 100vh; }
|
||||||
|
.auth-card { background-color: #2b2d31; padding: 32px; border-radius: 8px; width: 100%; max-width: 480px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); }
|
||||||
|
.form-label { color: #b5bac1; font-size: 12px; font-weight: bold; text-transform: uppercase; }
|
||||||
|
.form-control { background-color: #1e1f22; border: none; color: #dbdee1; padding: 10px; }
|
||||||
|
.form-control:focus { background-color: #1e1f22; color: #dbdee1; box-shadow: none; }
|
||||||
|
.btn-blurple { background-color: #5865f2; color: white; width: 100%; font-weight: bold; margin-top: 20px; }
|
||||||
|
.btn-blurple:hover { background-color: #4752c4; color: white; }
|
||||||
|
.auth-footer { color: #949ba4; font-size: 14px; margin-top: 10px; }
|
||||||
|
.auth-footer a { color: #00a8fc; text-decoration: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="auth-card">
|
||||||
|
<h3 class="text-center mb-1">Welcome back!</h3>
|
||||||
|
<p class="text-center mb-4" style="color: #b5bac1;">We're so excited to see you again!</p>
|
||||||
|
<?php if($error): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email or Phone Number</label>
|
||||||
|
<input type="email" name="email" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<a href="#" style="color: #00a8fc; font-size: 14px; text-decoration: none;">Forgot your password?</a>
|
||||||
|
<button type="submit" class="btn btn-blurple">Log In</button>
|
||||||
|
<div class="auth-footer">
|
||||||
|
Need an account? <a href="register.php">Register</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
auth/logout.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_destroy();
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
120
auth/register.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
header("Cache-Control: no-cache, no-store, must-revalidate");
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
header("Expires: 0");
|
||||||
|
require_once __DIR__ . '/session.php';
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = $_POST['username'] ?? '';
|
||||||
|
$email = $_POST['email'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
$inviteCode = $_POST['invite_code'] ?? '';
|
||||||
|
|
||||||
|
if ($username && $email && $password) {
|
||||||
|
// Strict invite code validation (Private by default)
|
||||||
|
$requireInvite = true;
|
||||||
|
if (defined('REQUIRE_INVITE_CODE')) {
|
||||||
|
$requireInvite = REQUIRE_INVITE_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($requireInvite) {
|
||||||
|
if (empty($inviteCode)) {
|
||||||
|
$error = "An invitation code is required to register.";
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("SELECT id FROM servers WHERE invite_code = ?");
|
||||||
|
$stmt->execute([$inviteCode]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
if (!$server) {
|
||||||
|
$error = "Invalid invitation code.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$error) {
|
||||||
|
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("INSERT INTO users (username, display_name, email, password_hash) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$username, $username, $email, $hash]);
|
||||||
|
$userId = db()->lastInsertId();
|
||||||
|
|
||||||
|
// Add to default server or the one from invite code
|
||||||
|
$serverId = 1; // Default
|
||||||
|
if (isset($server) && $server) {
|
||||||
|
$serverId = $server['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = db()->prepare("INSERT IGNORE INTO server_members (server_id, user_id) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$serverId, $userId]);
|
||||||
|
|
||||||
|
$_SESSION['user_id'] = $userId;
|
||||||
|
header('Location: ../index.php');
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Registration failed: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "Please fill all fields.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Register | Discord Clone</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="../assets/css/discord.css">
|
||||||
|
<style>
|
||||||
|
body { background-color: #313338; display: flex; align-items: center; justify-content: center; height: 100vh; }
|
||||||
|
.auth-card { background-color: #2b2d31; padding: 32px; border-radius: 8px; width: 100%; max-width: 480px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); border: 3px solid #f04747; }
|
||||||
|
.form-label { color: #b5bac1; font-size: 12px; font-weight: bold; text-transform: uppercase; }
|
||||||
|
.form-control { background-color: #1e1f22; border: none; color: #dbdee1; padding: 10px; }
|
||||||
|
.form-control:focus { background-color: #1e1f22; color: #dbdee1; box-shadow: none; }
|
||||||
|
.btn-blurple { background-color: #5865f2; color: white; width: 100%; font-weight: bold; margin-top: 20px; }
|
||||||
|
.btn-blurple:hover { background-color: #4752c4; color: white; }
|
||||||
|
.auth-footer { color: #949ba4; font-size: 14px; margin-top: 10px; }
|
||||||
|
.auth-footer a { color: #00a8fc; text-decoration: none; }
|
||||||
|
@keyframes blink { 0% { border-color: #f04747; } 50% { border-color: transparent; } 100% { border-color: #f04747; } }
|
||||||
|
.invite-box { border: 4px solid #f04747; padding: 15px; border-radius: 8px; margin-bottom: 25px; background: rgba(240, 71, 71, 0.1); animation: blink 1s infinite; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="auth-card">
|
||||||
|
<h3 class="text-center mb-4">Create an account</h3>
|
||||||
|
<?php if($error): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<form method="POST">
|
||||||
|
<!-- INVITE CODE FIELD - CRITICAL -->
|
||||||
|
<div class="invite-box">
|
||||||
|
<label class="form-label" style="color: #f04747 !important; font-size: 16px;">REQUIRED: INVITE CODE</label>
|
||||||
|
<div class="small mb-1" style="color: #fff !important; font-weight: bold;">Enter the code from Server Settings.</div>
|
||||||
|
<input type="text" name="invite_code" class="form-control" placeholder="INVITE CODE HERE" required style="border: 2px solid #f04747; font-size: 20px; text-align: center; font-weight: bold;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Username</label>
|
||||||
|
<input type="text" name="username" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email</label>
|
||||||
|
<input type="email" name="email" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-blurple">Continue</button>
|
||||||
|
<div class="text-center mt-2" style="color: #4e5058; font-size: 10px;">Version: <?php echo time(); ?></div>
|
||||||
|
<div class="auth-footer">
|
||||||
|
Already have an account? <a href="login.php">Login</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
auth/session.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
function getCurrentUser() {
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$_SESSION['user_id']]);
|
||||||
|
return $stmt->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireLogin() {
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: auth/login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
auth/test_check.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?php echo "FILE SYSTEM SYNC TEST - SUCCESS - TIME: " . time(); ?>
|
||||||
0
data/22.log
Normal file
1
data/22.participants.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"de65a0b0b1a29c9a":{"id":"de65a0b0b1a29c9a","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771343410040}}
|
||||||
0
data/3.log
Normal file
1
data/3.participants.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"920464469dc771ed":{"id":"920464469dc771ed","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771343410598}}
|
||||||
10
data/6.log
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"offer","offer":{"type":"offer","sdp":"v=0\r\no=mozilla...THIS_IS_SDPARTA-99.0 1705174900585877835 0 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=sendrecv\r\na=fingerprint:sha-256 AA:37:B9:FA:14:15:DC:34:29:97:DC:55:77:28:8E:74:C0:94:15:08:DF:5B:E9:CC:36:81:E5:D9:5C:49:FB:46\r\na=group:BUNDLE 0\r\na=ice-options:trickle\r\na=msid-semantic:WMS *\r\nm=audio 9 UDP\/TLS\/RTP\/SAVPF 109 9 0 8 101\r\nc=IN IP4 0.0.0.0\r\na=sendrecv\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2\/recvonly urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap-allow-mixed\r\na=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1\r\na=fmtp:101 0-15\r\na=ice-pwd:075627a25c9f07d3908305504803057d\r\na=ice-ufrag:03d0add1\r\na=mid:0\r\na=msid:{7a2c5afc-d053-43fd-9196-3caf4510feb3} {8439b857-ead8-4cb1-8111-cb3400348461}\r\na=rtcp-mux\r\na=rtpmap:109 opus\/48000\/2\r\na=rtpmap:9 G722\/8000\/1\r\na=rtpmap:0 PCMU\/8000\r\na=rtpmap:8 PCMA\/8000\r\na=rtpmap:101 telephone-event\/8000\r\na=setup:actpass\r\na=ssrc:1663488241 cname:{7dcb8038-2b50-4e42-827c-c4617a225c01}\r\n"}},"time":1771336456644}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:0 1 UDP 2122252543 192.168.26.26 62572 typ host","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456645}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:2 1 TCP 2105524479 192.168.26.26 9 typ host tcptype active","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456647}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:2 2 TCP 2105524478 192.168.26.26 9 typ host tcptype active","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456652}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:0 2 UDP 2122252542 192.168.26.26 62573 typ host","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456654}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:1 1 UDP 1686052351 78.246.210.10 31184 typ srflx raddr 192.168.26.26 rport 62572","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456720}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:1 2 UDP 1686052350 78.246.210.10 31185 typ srflx raddr 192.168.26.26 rport 62573","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456757}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:1 1 UDP 1686052863 78.246.210.10 31184 typ srflx raddr 192.168.26.26 rport 62572","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456776}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"candidate:1 2 UDP 1686052862 78.246.210.10 31185 typ srflx raddr 192.168.26.26 rport 62573","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456802}
|
||||||
|
{"from":"75b3938d276e81e1","to":"0cce3619c0f299fa","data":{"type":"ice_candidate","candidate":{"candidate":"","sdpMLineIndex":0,"sdpMid":"0","usernameFragment":"03d0add1"}},"time":1771336456805}
|
||||||
1
data/6.participants.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"0cce3619c0f299fa":{"id":"0cce3619c0f299fa","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771336452806},"75b3938d276e81e1":{"id":"75b3938d276e81e1","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771336462293}}
|
||||||
1
data/test.participants.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"0fbf720bc2f110c0":{"id":"0fbf720bc2f110c0","name":"AI","last_seen":1771336229774}}
|
||||||
1
data/test.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
hello
|
||||||
1
data/test_www.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
hello
|
||||||
94
database/schema.sql
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
-- Initial schema for Discord-like app
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
email VARCHAR(100) NOT NULL UNIQUE,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
avatar_url VARCHAR(255),
|
||||||
|
status VARCHAR(20) DEFAULT 'offline',
|
||||||
|
is_bot BOOLEAN DEFAULT FALSE,
|
||||||
|
bot_token VARCHAR(64) UNIQUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS servers (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
owner_id INT NOT NULL,
|
||||||
|
icon_url VARCHAR(255),
|
||||||
|
invite_code VARCHAR(10) UNIQUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (owner_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS channels (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
type ENUM('text', 'voice') DEFAULT 'text',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS webhooks (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
token VARCHAR(64) NOT NULL UNIQUE,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS roles (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
color VARCHAR(7) DEFAULT '#99aab5',
|
||||||
|
permissions INT DEFAULT 0,
|
||||||
|
position INT DEFAULT 0,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_roles (
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
role_id INT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, role_id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS server_members (
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (server_id, user_id),
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed initial data
|
||||||
|
INSERT IGNORE INTO users (id, username, email, password_hash, status) VALUES
|
||||||
|
(1, 'System', 'system@local', '$2y$10$xyz', 'online');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO servers (id, name, owner_id, invite_code) VALUES
|
||||||
|
(1, 'General Community', 1, 'GEN-123'),
|
||||||
|
(2, 'Flatlogic Devs', 1, 'DEV-456');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO server_members (server_id, user_id) VALUES (1, 1), (2, 1);
|
||||||
|
|
||||||
|
INSERT IGNORE INTO channels (id, server_id, name, type) VALUES
|
||||||
|
(1, 1, 'general', 'text'),
|
||||||
|
(2, 1, 'random', 'text'),
|
||||||
|
(3, 1, 'Voice General', 'voice'),
|
||||||
|
(4, 2, 'announcements', 'text'),
|
||||||
|
(5, 2, 'coding-help', 'text');
|
||||||
@ -1,9 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||||
define('DB_HOST', '127.0.0.1');
|
define('DB_HOST', '127.0.0.1');
|
||||||
define('DB_NAME', 'app_38443');
|
define('DB_NAME', 'app_38527');
|
||||||
define('DB_USER', 'app_38443');
|
define('DB_USER', 'app_38527');
|
||||||
define('DB_PASS', '888f6481-a87b-421a-a4bd-c80fa3c5a57b');
|
define('DB_PASS', 'ded9b4d6-94d1-4a22-b636-1d581ddcb411');
|
||||||
|
|
||||||
|
// Registration settings
|
||||||
|
define('REQUIRE_INVITE_CODE', true); // Set to true to make registration private, false for public
|
||||||
|
|
||||||
function db() {
|
function db() {
|
||||||
static $pdo;
|
static $pdo;
|
||||||
@ -15,3 +18,26 @@ function db() {
|
|||||||
}
|
}
|
||||||
return $pdo;
|
return $pdo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enforceChannelLimit($channel_id) {
|
||||||
|
$stmt = db()->prepare("SELECT message_limit FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$channel = $stmt->fetch();
|
||||||
|
if ($channel && !empty($channel['message_limit'])) {
|
||||||
|
$limit = (int)$channel['message_limit'];
|
||||||
|
// Delete oldest messages that exceed the limit
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
DELETE FROM messages
|
||||||
|
WHERE channel_id = ?
|
||||||
|
AND id NOT IN (
|
||||||
|
SELECT id FROM (
|
||||||
|
SELECT id FROM messages
|
||||||
|
WHERE channel_id = ?
|
||||||
|
ORDER BY created_at DESC, id DESC
|
||||||
|
LIMIT " . $limit . "
|
||||||
|
) as tmp
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$stmt->execute([$channel_id, $channel_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
7
db/migrations/001_create_custom_emotes.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS custom_emotes (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
path VARCHAR(255) NOT NULL,
|
||||||
|
code VARCHAR(60) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
13
db/migrations/20260215_add_attachments_and_reactions.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
-- Migration to add attachments and reactions
|
||||||
|
ALTER TABLE messages ADD COLUMN attachment_url VARCHAR(255) AFTER content;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS message_reactions (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
message_id INT NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
emoji VARCHAR(50) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY (message_id, user_id, emoji),
|
||||||
|
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
2
db/migrations/20260215_channel_status.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- Migration: Add status to channels for voice channels
|
||||||
|
ALTER TABLE channels ADD COLUMN status VARCHAR(255) DEFAULT NULL;
|
||||||
26
db/migrations/20260215_dms_and_edits.sql
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
-- Migration to support DMs and Message Editing
|
||||||
|
ALTER TABLE messages ADD COLUMN updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- Support for DMs in channels table
|
||||||
|
ALTER TABLE channels MODIFY COLUMN server_id INT NULL;
|
||||||
|
ALTER TABLE channels MODIFY COLUMN type ENUM('text', 'voice', 'dm') DEFAULT 'text';
|
||||||
|
|
||||||
|
-- Track members in channels (especially for DMs)
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_members (
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (channel_id, user_id),
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Notifications: Track last read message per channel per user
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_last_read (
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
last_read_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (channel_id, user_id),
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
3
db/migrations/20260215_forum_solution.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Add solution support to forum threads
|
||||||
|
ALTER TABLE forum_threads ADD COLUMN solution_message_id INT NULL DEFAULT NULL;
|
||||||
|
ALTER TABLE forum_threads ADD FOREIGN KEY (solution_message_id) REFERENCES messages(id) ON DELETE SET NULL;
|
||||||
16
db/migrations/20260215_forum_tags.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-- Add forum tags support
|
||||||
|
CREATE TABLE IF NOT EXISTS forum_tags (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
color VARCHAR(20) DEFAULT '#7289da',
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS thread_tags (
|
||||||
|
thread_id INT NOT NULL,
|
||||||
|
tag_id INT NOT NULL,
|
||||||
|
PRIMARY KEY (thread_id, tag_id),
|
||||||
|
FOREIGN KEY (thread_id) REFERENCES forum_threads(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (tag_id) REFERENCES forum_tags(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
12
db/migrations/20260215_granular_roles_and_themes.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- Migration: Add channel permissions and user theme preference
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_permissions (
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
role_id INT NOT NULL,
|
||||||
|
allow_permissions INT DEFAULT 0,
|
||||||
|
deny_permissions INT DEFAULT 0,
|
||||||
|
PRIMARY KEY (channel_id, role_id),
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS theme VARCHAR(20) DEFAULT 'dark';
|
||||||
12
db/migrations/20260215_rss_feeds.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- Migration: Add RSS support for announcement channels
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_rss_feeds (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
url VARCHAR(255) NOT NULL,
|
||||||
|
last_fetched_at TIMESTAMP NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS rss_guid VARCHAR(255) NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rss_guid ON messages(rss_guid);
|
||||||
11
db/migrations/20260216_autorole_channels.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- Add autorole channels support
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_autoroles (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
icon VARCHAR(50) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
role_id INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
12
db/migrations/20260216_rules_acceptance.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- Add rules_role_id to channels and create rule_acceptances table
|
||||||
|
ALTER TABLE channels ADD COLUMN rules_role_id INT NULL;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS rule_acceptances (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
accepted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY user_channel (user_id, channel_id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
9
db/migrations/20260217_rss_processed_items.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
-- Migration: Track processed RSS items to prevent re-posting after they are deleted by retention policy
|
||||||
|
CREATE TABLE IF NOT EXISTS rss_processed_items (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
rss_guid VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY idx_channel_guid (channel_id, rss_guid),
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
10
db/migrations/20260217_unread_indicators.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- Unread messages tracking
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_reads (
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
last_read_message_id INT NOT NULL,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (user_id, channel_id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
12
db/migrations/20260217_voice_system.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- Add voice settings to users and create voice sessions table
|
||||||
|
ALTER TABLE users ADD COLUMN voice_mode ENUM('vox', 'ptt') DEFAULT 'vox';
|
||||||
|
ALTER TABLE users ADD COLUMN voice_ptt_key VARCHAR(20) DEFAULT 'v';
|
||||||
|
ALTER TABLE users ADD COLUMN voice_vox_threshold FLOAT DEFAULT 0.1;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS voice_sessions (
|
||||||
|
user_id INT PRIMARY KEY,
|
||||||
|
channel_id INT NOT NULL,
|
||||||
|
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
2
db/migrations/add_display_name.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN display_name VARCHAR(50) AFTER username;
|
||||||
|
UPDATE users SET display_name = username WHERE display_name IS NULL OR display_name = "";
|
||||||
10
db_init.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = file_get_contents('database/schema.sql');
|
||||||
|
db()->exec($sql);
|
||||||
|
echo "Database initialized successfully.\n";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error initializing database: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
19
debug_reorder.log
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
2026-02-15 23:32:05 - Server: 1 - Orders: [{"id":"1","position":0,"category_id":null},{"id":"6","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"2","position":3,"category_id":null},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
|
||||||
|
2026-02-15 23:35:48 - Server: 1 - Orders: [{"id":"10","position":0,"category_id":null},{"id":"1","position":1,"category_id":null},{"id":"6","position":2,"category_id":null},{"id":"2","position":3,"category_id":null},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
|
||||||
|
2026-02-15 23:36:25 - Server: 1 - Orders: [{"id":"10","position":0,"category_id":null},{"id":"1","position":1,"category_id":"10"},{"id":"6","position":2,"category_id":"10"},{"id":"2","position":3,"category_id":null},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
|
||||||
|
2026-02-15 23:36:28 - Server: 1 - Orders: [{"id":"10","position":0,"category_id":null},{"id":"1","position":1,"category_id":"10"},{"id":"6","position":2,"category_id":"10"},{"id":"2","position":3,"category_id":"10"},{"id":"9","position":4,"category_id":null},{"id":"3","position":5,"category_id":null}]
|
||||||
|
2026-02-15 23:39:25 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"10","position":1,"category_id":null},{"id":"1","position":2,"category_id":"10"},{"id":"6","position":3,"category_id":"10"},{"id":"2","position":4,"category_id":"10"},{"id":"9","position":5,"category_id":null},{"id":"3","position":6,"category_id":null}]
|
||||||
|
2026-02-15 23:39:45 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"12","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"1","position":3,"category_id":"10"},{"id":"6","position":4,"category_id":"10"},{"id":"2","position":5,"category_id":"10"},{"id":"9","position":6,"category_id":null},{"id":"3","position":7,"category_id":null}]
|
||||||
|
2026-02-15 23:40:11 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"12","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"1","position":3,"category_id":"10"},{"id":"6","position":4,"category_id":"10"},{"id":"2","position":5,"category_id":"10"},{"id":"13","position":6,"category_id":null},{"id":"9","position":7,"category_id":null},{"id":"3","position":8,"category_id":null}]
|
||||||
|
2026-02-15 23:40:20 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"12","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"1","position":3,"category_id":"10"},{"id":"6","position":4,"category_id":"10"},{"id":"2","position":5,"category_id":"10"},{"id":"14","position":6,"category_id":null},{"id":"13","position":7,"category_id":null},{"id":"9","position":8,"category_id":null},{"id":"3","position":9,"category_id":null}]
|
||||||
|
2026-02-16 00:15:14 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"12","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"1","position":3,"category_id":"10"},{"id":"6","position":4,"category_id":"10"},{"id":"15","position":5,"category_id":"10"},{"id":"2","position":6,"category_id":"10"},{"id":"14","position":7,"category_id":null},{"id":"13","position":8,"category_id":null},{"id":"9","position":9,"category_id":null},{"id":"3","position":10,"category_id":null}]
|
||||||
|
2026-02-16 00:17:23 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"12","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"1","position":3,"category_id":"10"},{"id":"6","position":4,"category_id":"10"},{"id":"15","position":5,"category_id":"10"},{"id":"2","position":6,"category_id":"10"},{"id":"14","position":7,"category_id":null},{"id":"13","position":8,"category_id":null},{"id":"9","position":9,"category_id":null},{"id":"3","position":10,"category_id":null}]
|
||||||
|
2026-02-16 00:17:31 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"12","position":1,"category_id":null},{"id":"10","position":2,"category_id":null},{"id":"1","position":3,"category_id":"10"},{"id":"6","position":4,"category_id":"10"},{"id":"15","position":5,"category_id":"10"},{"id":"2","position":6,"category_id":"10"},{"id":"14","position":7,"category_id":null},{"id":"13","position":8,"category_id":null},{"id":"9","position":9,"category_id":null},{"id":"3","position":10,"category_id":null}]
|
||||||
|
2026-02-16 03:07:52 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"16","position":1,"category_id":null},{"id":"12","position":2,"category_id":null},{"id":"10","position":3,"category_id":null},{"id":"1","position":4,"category_id":"10"},{"id":"6","position":5,"category_id":"10"},{"id":"15","position":6,"category_id":"10"},{"id":"2","position":7,"category_id":"10"},{"id":"14","position":8,"category_id":null},{"id":"13","position":9,"category_id":null},{"id":"9","position":10,"category_id":null},{"id":"3","position":11,"category_id":null}]
|
||||||
|
2026-02-16 03:08:33 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"17","position":1,"category_id":null},{"id":"12","position":2,"category_id":null},{"id":"10","position":3,"category_id":null},{"id":"1","position":4,"category_id":"10"},{"id":"6","position":5,"category_id":"10"},{"id":"15","position":6,"category_id":"10"},{"id":"2","position":7,"category_id":"10"},{"id":"14","position":8,"category_id":null},{"id":"13","position":9,"category_id":null},{"id":"9","position":10,"category_id":null},{"id":"3","position":11,"category_id":null}]
|
||||||
|
2026-02-16 03:09:18 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"17","position":1,"category_id":null},{"id":"12","position":2,"category_id":null},{"id":"10","position":3,"category_id":null},{"id":"1","position":4,"category_id":"10"},{"id":"6","position":5,"category_id":"10"},{"id":"15","position":6,"category_id":"10"},{"id":"2","position":7,"category_id":"10"},{"id":"18","position":8,"category_id":"10"},{"id":"14","position":9,"category_id":null},{"id":"13","position":10,"category_id":null},{"id":"9","position":11,"category_id":null},{"id":"3","position":12,"category_id":null}]
|
||||||
|
2026-02-16 18:43:44 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"17","position":1,"category_id":null},{"id":"12","position":2,"category_id":null},{"id":"19","position":3,"category_id":null},{"id":"10","position":4,"category_id":null},{"id":"1","position":5,"category_id":"10"},{"id":"6","position":6,"category_id":"10"},{"id":"15","position":7,"category_id":"10"},{"id":"2","position":8,"category_id":"10"},{"id":"18","position":9,"category_id":"10"},{"id":"14","position":10,"category_id":null},{"id":"13","position":11,"category_id":null},{"id":"9","position":12,"category_id":null},{"id":"3","position":13,"category_id":null}]
|
||||||
|
2026-02-16 23:37:00 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"12","position":1,"category_id":null},{"id":"17","position":2,"category_id":null},{"id":"19","position":3,"category_id":null},{"id":"10","position":4,"category_id":null},{"id":"1","position":5,"category_id":"10"},{"id":"6","position":6,"category_id":"10"},{"id":"15","position":7,"category_id":"10"},{"id":"2","position":8,"category_id":"10"},{"id":"18","position":9,"category_id":"10"},{"id":"14","position":10,"category_id":null},{"id":"13","position":11,"category_id":null},{"id":"9","position":12,"category_id":null},{"id":"3","position":13,"category_id":null}]
|
||||||
|
2026-02-16 23:37:02 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"17","position":1,"category_id":null},{"id":"12","position":2,"category_id":null},{"id":"19","position":3,"category_id":null},{"id":"10","position":4,"category_id":null},{"id":"1","position":5,"category_id":"10"},{"id":"6","position":6,"category_id":"10"},{"id":"15","position":7,"category_id":"10"},{"id":"2","position":8,"category_id":"10"},{"id":"18","position":9,"category_id":"10"},{"id":"14","position":10,"category_id":null},{"id":"13","position":11,"category_id":null},{"id":"9","position":12,"category_id":null},{"id":"3","position":13,"category_id":null}]
|
||||||
|
2026-02-17 00:31:00 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"17","position":1,"category_id":null},{"id":"12","position":2,"category_id":null},{"id":"20","position":3,"category_id":null},{"id":"19","position":4,"category_id":null},{"id":"10","position":5,"category_id":null},{"id":"1","position":6,"category_id":"10"},{"id":"6","position":7,"category_id":"10"},{"id":"15","position":8,"category_id":"10"},{"id":"2","position":9,"category_id":"10"},{"id":"18","position":10,"category_id":"10"},{"id":"14","position":11,"category_id":null},{"id":"13","position":12,"category_id":null},{"id":"9","position":13,"category_id":null},{"id":"3","position":14,"category_id":null}]
|
||||||
|
2026-02-17 08:19:05 - Server: 1 - Orders: [{"id":"11","position":0,"category_id":null},{"id":"17","position":1,"category_id":null},{"id":"20","position":2,"category_id":null},{"id":"21","position":3,"category_id":null},{"id":"19","position":4,"category_id":null},{"id":"10","position":5,"category_id":null},{"id":"1","position":6,"category_id":"10"},{"id":"6","position":7,"category_id":"10"},{"id":"15","position":8,"category_id":"10"},{"id":"2","position":9,"category_id":"10"},{"id":"18","position":10,"category_id":"10"},{"id":"14","position":11,"category_id":null},{"id":"13","position":12,"category_id":null},{"id":"9","position":13,"category_id":null},{"id":"3","position":14,"category_id":null}]
|
||||||
29
includes/ai_filtering.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
||||||
|
|
||||||
|
function moderateContent($content) {
|
||||||
|
if (empty(trim($content))) return ['is_safe' => true];
|
||||||
|
|
||||||
|
// Bypass moderation for video platforms as they are handled by their own safety measures
|
||||||
|
// and often trigger false positives in AI moderation due to "lack of context".
|
||||||
|
if (preg_match('/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be|dailymotion\.com|dai\.ly|vimeo\.com)\//i', $content)) {
|
||||||
|
return ['is_safe' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
$resp = LocalAIApi::createResponse([
|
||||||
|
'input' => [
|
||||||
|
['role' => 'system', 'content' => 'You are a content moderator. Analyze the message and return a JSON object with "is_safe" (boolean) and "reason" (string, optional). Safe means no hate speech, extreme violence, or explicit sexual content. Do not flag URLs as unsafe simply because you cannot see the content behind them.'],
|
||||||
|
['role' => 'user', 'content' => $content],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($resp['success'])) {
|
||||||
|
$result = LocalAIApi::decodeJsonFromResponse($resp);
|
||||||
|
if ($result && isset($result['is_safe'])) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to safe if AI fails, to avoid blocking users
|
||||||
|
return ['is_safe' => true];
|
||||||
|
}
|
||||||
62
includes/opengraph.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
function fetchOpenGraphData($url) {
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||||
|
'Accept-Language: en-US,en;q=0.9',
|
||||||
|
'Cache-Control: no-cache',
|
||||||
|
'Pragma: no-cache',
|
||||||
|
'Upgrade-Insecure-Requests: 1'
|
||||||
|
]);
|
||||||
|
$html = curl_exec($ch);
|
||||||
|
$info = curl_getinfo($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if (!$html || $info['http_code'] !== 200) return null;
|
||||||
|
if (!class_exists('DOMDocument')) return null;
|
||||||
|
|
||||||
|
$doc = new DOMDocument();
|
||||||
|
@$doc->loadHTML($html);
|
||||||
|
$metas = $doc->getElementsByTagName('meta');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'title' => '',
|
||||||
|
'description' => '',
|
||||||
|
'image' => '',
|
||||||
|
'url' => $url,
|
||||||
|
'site_name' => ''
|
||||||
|
];
|
||||||
|
|
||||||
|
// Try title tag if og:title is missing
|
||||||
|
$titles = $doc->getElementsByTagName('title');
|
||||||
|
if ($titles->length > 0) {
|
||||||
|
$data['title'] = $titles->item(0)->nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($metas as $meta) {
|
||||||
|
$property = $meta->getAttribute('property');
|
||||||
|
$name = $meta->getAttribute('name');
|
||||||
|
$content = $meta->getAttribute('content');
|
||||||
|
|
||||||
|
if ($property === 'og:title' || $name === 'twitter:title') $data['title'] = $content;
|
||||||
|
if ($property === 'og:description' || $name === 'description' || $name === 'twitter:description') $data['description'] = $content;
|
||||||
|
if ($property === 'og:image' || $name === 'twitter:image') $data['image'] = $content;
|
||||||
|
if ($property === 'og:site_name') $data['site_name'] = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out empty results
|
||||||
|
if (empty($data['title']) && empty($data['description'])) return null;
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractUrls($text) {
|
||||||
|
$pattern = '/https?:\/\/[^\s<]+/';
|
||||||
|
preg_match_all($pattern, $text, $matches);
|
||||||
|
return $matches[0];
|
||||||
|
}
|
||||||
131
includes/permissions.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Permissions {
|
||||||
|
const VIEW_CHANNEL = 1;
|
||||||
|
const SEND_MESSAGES = 2;
|
||||||
|
const MANAGE_MESSAGES = 4;
|
||||||
|
const MANAGE_CHANNELS = 8;
|
||||||
|
const MANAGE_SERVER = 16;
|
||||||
|
const ADMINISTRATOR = 32;
|
||||||
|
|
||||||
|
public static function hasPermission($user_id, $server_id, $permission) {
|
||||||
|
$stmt = db()->prepare("SELECT is_admin FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
if ($user && $user['is_admin']) return true;
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
if ($server && $server['owner_id'] == $user_id) return true;
|
||||||
|
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT SUM(r.permissions) as total_perms
|
||||||
|
FROM roles r
|
||||||
|
JOIN user_roles ur ON r.id = ur.role_id
|
||||||
|
WHERE ur.user_id = ? AND r.server_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $server_id]);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
$perms = (int)($row['total_perms'] ?? 0);
|
||||||
|
|
||||||
|
if ($perms & self::ADMINISTRATOR) return true;
|
||||||
|
return ($perms & $permission) === $permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canViewChannel($user_id, $channel_id) {
|
||||||
|
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$c = $stmt->fetch();
|
||||||
|
if (!$c) return false;
|
||||||
|
$server_id = $c['server_id'];
|
||||||
|
|
||||||
|
// Check if owner or admin
|
||||||
|
if (self::hasPermission($user_id, $server_id, self::ADMINISTRATOR)) return true;
|
||||||
|
|
||||||
|
// Fetch overrides for all roles the user has in this server
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT cp.allow_permissions, cp.deny_permissions
|
||||||
|
FROM channel_permissions cp
|
||||||
|
JOIN user_roles ur ON cp.role_id = ur.role_id
|
||||||
|
WHERE ur.user_id = ? AND cp.channel_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $channel_id]);
|
||||||
|
$overrides = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Check @everyone override specifically (even if user has no roles assigned)
|
||||||
|
$stmt = db()->prepare("SELECT id FROM roles WHERE server_id = ? AND (name = '@everyone' OR name = 'Everyone') LIMIT 1");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$everyone_role = $stmt->fetch();
|
||||||
|
if ($everyone_role) {
|
||||||
|
$stmt = db()->prepare("SELECT allow_permissions, deny_permissions FROM channel_permissions WHERE channel_id = ? AND role_id = ?");
|
||||||
|
$stmt->execute([$channel_id, $everyone_role['id']]);
|
||||||
|
$eo = $stmt->fetch();
|
||||||
|
if ($eo) {
|
||||||
|
$overrides[] = $eo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($overrides)) {
|
||||||
|
return true; // Default to yes
|
||||||
|
}
|
||||||
|
|
||||||
|
$allow = false;
|
||||||
|
$deny = false;
|
||||||
|
foreach($overrides as $o) {
|
||||||
|
if ($o['allow_permissions'] & self::VIEW_CHANNEL) $allow = true;
|
||||||
|
if ($o['deny_permissions'] & self::VIEW_CHANNEL) $deny = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($allow) return true;
|
||||||
|
if ($deny) return false;
|
||||||
|
|
||||||
|
return true; // Default to yes
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canSendInChannel($user_id, $channel_id) {
|
||||||
|
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
|
||||||
|
$stmt->execute([$channel_id]);
|
||||||
|
$c = $stmt->fetch();
|
||||||
|
if (!$c) return false;
|
||||||
|
$server_id = $c['server_id'];
|
||||||
|
|
||||||
|
// Check if owner or admin
|
||||||
|
if (self::hasPermission($user_id, $server_id, self::ADMINISTRATOR)) return true;
|
||||||
|
|
||||||
|
// Check overrides
|
||||||
|
$stmt = db()->prepare("
|
||||||
|
SELECT cp.allow_permissions, cp.deny_permissions
|
||||||
|
FROM channel_permissions cp
|
||||||
|
JOIN user_roles ur ON cp.role_id = ur.role_id
|
||||||
|
WHERE ur.user_id = ? AND cp.channel_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $channel_id]);
|
||||||
|
$overrides = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Check @everyone override
|
||||||
|
$stmt = db()->prepare("SELECT id FROM roles WHERE server_id = ? AND (name = '@everyone' OR name = 'Everyone') LIMIT 1");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$everyone_role = $stmt->fetch();
|
||||||
|
if ($everyone_role) {
|
||||||
|
$stmt = db()->prepare("SELECT allow_permissions, deny_permissions FROM channel_permissions WHERE channel_id = ? AND role_id = ?");
|
||||||
|
$stmt->execute([$channel_id, $everyone_role['id']]);
|
||||||
|
$eo = $stmt->fetch();
|
||||||
|
if ($eo) {
|
||||||
|
$overrides[] = $eo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allow = false;
|
||||||
|
$deny = false;
|
||||||
|
foreach($overrides as $o) {
|
||||||
|
if ($o['allow_permissions'] & self::SEND_MESSAGES) $allow = true;
|
||||||
|
if ($o['deny_permissions'] & self::SEND_MESSAGES) $deny = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($allow) return true;
|
||||||
|
if ($deny) return false;
|
||||||
|
|
||||||
|
return self::hasPermission($user_id, $server_id, self::SEND_MESSAGES);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
includes/pexels.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
// includes/pexels.php
|
||||||
|
function pexels_key() {
|
||||||
|
$k = getenv('PEXELS_KEY');
|
||||||
|
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
|
||||||
|
}
|
||||||
|
|
||||||
|
function pexels_get($url) {
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
|
||||||
|
CURLOPT_TIMEOUT => 15,
|
||||||
|
]);
|
||||||
|
$resp = curl_exec($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function download_to($srcUrl, $destPath) {
|
||||||
|
$data = file_get_contents($srcUrl);
|
||||||
|
if ($data === false) return false;
|
||||||
|
if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
|
||||||
|
return file_put_contents($destPath, $data) !== false;
|
||||||
|
}
|
||||||
696
requests.log
Normal file
@ -0,0 +1,696 @@
|
|||||||
|
2026-02-15 23:22:04 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-15 23:27:59 - GET / - POST: []
|
||||||
|
2026-02-15 23:28:03 - HEAD / - POST: []
|
||||||
|
2026-02-15 23:28:16 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-15 23:31:59 - GET /index.php?server_id=1&channel_id=10 - POST: []
|
||||||
|
2026-02-15 23:32:07 - GET /index.php?server_id=1&channel_id=10 - POST: []
|
||||||
|
2026-02-15 23:32:12 - GET /index.php?server_id=1&channel_id=10 - POST: []
|
||||||
|
2026-02-15 23:36:00 - GET /index.php?server_id=1&channel_id=10 - POST: []
|
||||||
|
2026-02-15 23:36:17 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-15 23:36:35 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:38:20 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:38:22 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-15 23:39:19 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:39:43 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-15 23:39:49 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-15 23:40:05 - GET /index.php?server_id=1&channel_id=13 - POST: []
|
||||||
|
2026-02-15 23:40:17 - GET /index.php?server_id=1&channel_id=14 - POST: []
|
||||||
|
2026-02-15 23:40:21 - GET /index.php?server_id=1&channel_id=13 - POST: []
|
||||||
|
2026-02-15 23:40:49 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:42:44 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-15 23:42:55 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:43:28 - GET /index.php - POST: []
|
||||||
|
2026-02-15 23:44:37 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:44:49 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-15 23:44:52 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:44:58 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-15 23:45:04 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:45:05 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:45:11 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:45:12 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:45:13 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-15 23:45:17 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:45:41 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:46:38 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-15 23:46:40 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:46:42 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:46:50 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:47:06 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:47:16 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:47:23 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:47:45 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:48:03 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:49:16 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:52:12 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-15 23:52:32 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:52:37 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-15 23:52:45 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:52:53 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:52:56 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:53:22 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:54:12 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:55:22 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-15 23:55:42 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:55:45 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:56:14 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:56:41 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:57:05 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:57:30 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:57:46 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-15 23:57:49 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-15 23:59:43 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 00:02:15 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:02:20 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:06:51 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 00:11:43 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 00:13:38 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:13:47 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:13:53 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:14:45 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:15:11 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:15:42 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:15:56 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:16:00 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:16:00 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:17:19 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:17:27 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:17:35 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:17:46 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:17:58 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:18:04 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:18:12 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:18:15 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 00:18:17 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:18:19 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:18:25 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 00:18:27 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:19:28 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 00:23:03 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:23:08 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:23:14 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:23:19 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:24:37 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 00:24:58 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 00:25:16 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:25:18 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:25:27 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:28:38 - GET / - POST: []
|
||||||
|
2026-02-16 00:28:56 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 00:29:17 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:29:44 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:29:47 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:30:05 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 00:30:37 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:30:39 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:30:55 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:30:57 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:30:59 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:31:02 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:31:04 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 00:31:06 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:31:09 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:31:11 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:31:13 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:31:15 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 00:31:16 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:31:59 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:32:08 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:32:15 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:32:19 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:32:21 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:32:45 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:34:01 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:34:31 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 00:34:34 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:34:36 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 00:34:39 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 00:36:39 - GET /index.php?server_id=dms - POST: []
|
||||||
|
2026-02-16 00:36:43 - GET /index.php?server_id=dms&channel_id=7 - POST: []
|
||||||
|
2026-02-16 00:36:46 - GET /index.php?server_id=dms&channel_id=7 - POST: []
|
||||||
|
2026-02-16 00:36:48 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 00:36:56 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 00:37:04 - GET /index.php?server_id=dms - POST: []
|
||||||
|
2026-02-16 00:37:05 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 00:38:00 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 00:39:58 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 02:38:56 - GET / - POST: []
|
||||||
|
2026-02-16 02:54:58 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 02:56:39 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 02:57:05 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 02:57:17 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 02:58:22 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 02:59:03 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 03:05:28 - GET / - POST: []
|
||||||
|
2026-02-16 03:06:27 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 03:07:23 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 03:07:43 - GET /index.php?server_id=1&channel_id=16 - POST: []
|
||||||
|
2026-02-16 03:08:00 - GET /index.php?server_id=1&channel_id=16 - POST: []
|
||||||
|
2026-02-16 03:08:21 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 03:08:30 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:09:15 - GET /index.php?server_id=1&channel_id=18 - POST: []
|
||||||
|
2026-02-16 03:09:47 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 03:09:51 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:09:54 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:10:22 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:10:27 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:10:35 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:10:58 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:15:47 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 03:16:11 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:16:19 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:16:26 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:17:11 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:20:43 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 03:21:26 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:21:33 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:21:35 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:21:38 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:26:28 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 03:26:35 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:28:36 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:30:31 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 03:30:36 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 03:30:41 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:32:55 - GET / - POST: []
|
||||||
|
2026-02-16 03:32:59 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:33:09 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:33:11 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 03:33:12 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:33:14 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:33:34 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:33:37 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 03:35:52 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 08:35:40 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 08:37:05 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 08:54:29 - GET / - POST: []
|
||||||
|
2026-02-16 08:54:39 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 12:42:27 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 12:47:00 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 12:47:29 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 12:47:34 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 12:48:16 - GET / - POST: []
|
||||||
|
2026-02-16 12:48:18 - HEAD / - POST: []
|
||||||
|
2026-02-16 12:48:24 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 12:48:45 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 12:51:34 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 12:53:59 - GET / - POST: []
|
||||||
|
2026-02-16 12:54:11 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 12:54:33 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 12:54:52 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 12:56:37 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 12:56:38 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 13:01:21 - GET / - POST: []
|
||||||
|
2026-02-16 13:01:28 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 13:03:56 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 13:06:06 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 13:06:15 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 13:06:20 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 13:06:25 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 13:06:41 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 13:06:43 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 13:06:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 13:11:31 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 13:13:37 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 13:18:51 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 13:19:22 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 13:25:42 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 13:38:52 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 13:44:04 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 14:10:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 14:10:52 - GET /index.php - POST: []
|
||||||
|
2026-02-16 14:10:57 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 14:11:06 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 14:13:29 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 16:20:04 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:20:11 - GET /index.php - POST: []
|
||||||
|
2026-02-16 16:20:31 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:20:47 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:23:59 - GET / - POST: []
|
||||||
|
2026-02-16 16:24:07 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 16:24:48 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:24:51 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:25:36 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:32:41 - GET / - POST: []
|
||||||
|
2026-02-16 16:32:49 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 16:32:51 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:33:28 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:38:10 - GET / - POST: []
|
||||||
|
2026-02-16 16:38:17 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 16:58:08 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 16:58:15 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:01:50 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 17:03:56 - GET / - POST: []
|
||||||
|
2026-02-16 17:04:02 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 17:08:30 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:09:29 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:09:33 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:09:34 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:09:38 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:09:50 - GET /index.php - POST: []
|
||||||
|
2026-02-16 17:09:55 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:09:57 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:09:59 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 17:41:16 - GET / - POST: []
|
||||||
|
2026-02-16 17:41:28 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 18:00:22 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 18:03:21 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 18:23:09 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 18:29:00 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 18:39:35 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 18:39:37 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 18:39:39 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 18:39:40 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 18:40:37 - GET /index.php?server_id=1&channel_id=18 - POST: []
|
||||||
|
2026-02-16 18:40:39 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 18:41:42 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 18:41:48 - GET /index.php?server_id=1&channel_id=2 - POST: []
|
||||||
|
2026-02-16 18:41:51 - GET /index.php?server_id=1&channel_id=18 - POST: []
|
||||||
|
2026-02-16 18:42:47 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-16 18:42:50 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 18:43:03 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 18:43:06 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 18:43:30 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 18:43:38 - GET /index.php?server_id=1&channel_id=19 - POST: []
|
||||||
|
2026-02-16 18:47:12 - GET / - POST: []
|
||||||
|
2026-02-16 18:47:41 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 18:48:13 - GET /index.php?server_id=1&channel_id=19 - POST: []
|
||||||
|
2026-02-16 18:48:31 - GET /index.php?server_id=1&channel_id=19 - POST: []
|
||||||
|
2026-02-16 18:48:34 - GET /index.php?server_id=1&channel_id=19 - POST: []
|
||||||
|
2026-02-16 18:48:39 - GET /index.php?server_id=1&channel_id=19 - POST: []
|
||||||
|
2026-02-16 18:50:31 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 18:50:41 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 18:51:08 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 18:54:57 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 18:54:58 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 18:55:39 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 18:56:06 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 18:56:52 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:05:49 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:06:59 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:09:05 - GET / - POST: []
|
||||||
|
2026-02-16 19:09:11 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 19:09:19 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:09:23 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:11:40 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 19:15:47 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:15:54 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:16:02 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 19:19:15 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 20:15:59 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 20:16:11 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 20:18:56 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 20:29:33 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 20:29:40 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 20:35:04 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 20:49:01 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:20:57 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 21:30:31 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:30:38 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:31:19 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 21:33:59 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 21:35:15 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 21:46:25 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:46:33 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:48:54 - api_v1_user.php - - POST: {"username":"testuser","theme":"light"}
|
||||||
|
2026-02-16 21:48:58 - api_v1_user.php - POST - POST: {"username":"testuser","theme":"light"}
|
||||||
|
2026-02-16 21:53:22 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 21:56:55 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:57:11 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:57:15 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 21:57:23 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
{"date":"2026-02-16 22:01:01","method":"POST","post":{"username":"DebugUser","theme":"light"},"session":{"user_id":1},"user_id":1,"db_success":true}
|
||||||
|
2026-02-16 22:01:28 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 22:01:51 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 22:01:56 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 22:02:03 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
{"date":"2026-02-16 22:02:07","method":"POST","post":{"avatar_url":"","username":"swefpifh","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2,"username":"swefpifh"},"user_id":2,"db_success":true}
|
||||||
|
2026-02-16 22:02:08 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 22:02:09 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 22:02:28 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 22:02:30 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:02:33 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-16 22:02:52 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:03:17 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:03:19 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:08:26 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 22:08:27 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
{"date":"2026-02-16 22:08:44","method":"POST","post":{"avatar_url":"","username":"swefpifh","dnd_mode":"1","sound_notifications":"1","theme":"light"},"session":{"user_id":2,"username":"swefpifh"},"user_id":2,"db_success":true}
|
||||||
|
2026-02-16 22:08:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:08:46 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
{"date":"2026-02-16 22:08:52","method":"POST","post":{"avatar_url":"","username":"swefpifh","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2,"username":"swefpifh"},"user_id":2,"db_success":true}
|
||||||
|
2026-02-16 22:08:52 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:09:17 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:53:00 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 22:53:02 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 22:53:49 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:54:14 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:57:26 - GET / - POST: []
|
||||||
|
2026-02-16 22:57:44 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 22:58:55 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 22:59:59 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:00:03 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:00:08 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:00:09 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:00:18 - GET /index.php - POST: []
|
||||||
|
2026-02-16 23:00:20 - GET /index.php - POST: []
|
||||||
|
2026-02-16 23:00:24 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:00:26 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:00:28 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
{"date":"2026-02-16 23:00:56","method":"POST","post":{"avatar_url":"","username":"swef","theme":"light","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
|
||||||
|
2026-02-16 23:00:57 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:00:59 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:01:02 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:01:08 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 23:01:09 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 23:01:23 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 23:04:26 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 23:05:06 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:05:13 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-16 23:05:15 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 23:05:29 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-16 23:13:30 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:18:05 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 23:18:47 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:26:02 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:26:50 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:27:48 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:28:42 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:28:44 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:30:14 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 23:30:27 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:31:15 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:31:17 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:31:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:31:46 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:32:05 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:32:07 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:32:49 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 23:32:53 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:33:23 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:33:24 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:33:50 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:34:25 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:34:50 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:34:54 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:35:43 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:35:58 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:36:07 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:36:08 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:36:54 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:37:08 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:37:26 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:39:35 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 23:39:49 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:39:52 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-16 23:39:53 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:40:18 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:40:19 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:44:06 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 23:45:42 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-16 23:45:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-16 23:45:46 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:46:19 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:46:41 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:47:02 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-16 23:48:40 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-17 00:21:52 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 00:22:06 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-17 00:22:15 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-17 00:22:19 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-17 00:30:57 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 00:32:51 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-17 00:33:04 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-17 00:34:28 - GET /index.php?server_id=1&channel_id=12 - POST: []
|
||||||
|
2026-02-17 00:34:40 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-17 00:34:45 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 00:35:23 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 00:43:23 - - POST: []
|
||||||
|
2026-02-17 00:44:01 - GET /index.php - POST: []
|
||||||
|
2026-02-17 00:44:15 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 00:45:12 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 00:45:14 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 00:45:21 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-17 00:45:23 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 00:48:04 - GET / - POST: []
|
||||||
|
2026-02-17 00:48:39 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 00:48:50 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 00:57:46 - GET / - POST: []
|
||||||
|
2026-02-17 00:58:08 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 00:58:11 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 00:58:14 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 01:02:05 - GET / - POST: []
|
||||||
|
2026-02-17 01:02:20 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 01:02:36 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 08:06:32 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 08:06:36 - GET /index.php - POST: []
|
||||||
|
2026-02-17 08:06:39 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 08:10:08 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 08:15:51 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 08:17:13 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 08:18:43 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 08:19:02 - GET /index.php?server_id=1&channel_id=21 - POST: []
|
||||||
|
2026-02-17 08:19:06 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||||
|
2026-02-17 08:19:16 - GET /index.php?server_id=1 - POST: []
|
||||||
|
2026-02-17 08:19:29 - GET /index.php?server_id=1&channel_id=21 - POST: []
|
||||||
|
2026-02-17 08:20:44 - GET /index.php?server_id=1&channel_id=17 - POST: []
|
||||||
|
2026-02-17 08:20:46 - GET /index.php?server_id=1&channel_id=11 - POST: []
|
||||||
|
2026-02-17 08:20:48 - GET /index.php?server_id=1&channel_id=21 - POST: []
|
||||||
|
2026-02-17 08:27:06 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 08:31:06 - GET / - POST: []
|
||||||
|
2026-02-17 08:31:24 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 08:46:59 - GET /index.php?server_id=1&channel_id=21 - POST: []
|
||||||
|
{"date":"2026-02-17 08:47:14","method":"POST","post":{"avatar_url":"","display_name":"swefpifh\u00b2","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 08:47:14 - GET /index.php?server_id=1&channel_id=21 - POST: []
|
||||||
|
2026-02-17 08:47:19 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 09:19:30 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 09:19:32 - GET /index.php - POST: []
|
||||||
|
{"date":"2026-02-17 09:20:03","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
|
||||||
|
2026-02-17 09:20:03 - GET /index.php - POST: []
|
||||||
|
{"date":"2026-02-17 09:20:46","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 09:20:47 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
{"date":"2026-02-17 09:21:25","method":"POST","post":{"avatar_url":"","display_name":"\u1d47\u02b0\u1da0\u02b3 swefpifh","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 09:21:25 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
{"date":"2026-02-17 09:22:02","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 09:22:02 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 09:22:13 - GET /index.php - POST: []
|
||||||
|
2026-02-17 09:22:18 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 09:22:20 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 09:22:25 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-17 09:22:29 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 09:36:55 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 09:48:03 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 09:56:28 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 09:57:00 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 11:37:20 - GET /index.php?server_id=1&channel_id=21 - POST: []
|
||||||
|
2026-02-17 11:43:18 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 11:43:46 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 11:46:49 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 11:46:58 - GET /index.php?server_id=1&channel_id=3 - POST: []
|
||||||
|
2026-02-17 11:49:14 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 11:56:37 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 11:56:49 - GET /index.php?server_id=1&channel_id=3 - POST: []
|
||||||
|
2026-02-17 11:56:58 - GET /index.php?server_id=1&channel_id=3 - POST: []
|
||||||
|
2026-02-17 11:57:08 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 11:57:14 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 12:01:03 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:08:31 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:08:36 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
{"date":"2026-02-17 12:09:05","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"0","voice_vox_threshold":"0.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 12:09:05 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 12:14:02 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:14:47 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 12:15:05 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 12:18:43 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:22:00 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:23:42 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 12:24:23 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:25:06 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 12:25:50 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:27:17 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:28:56 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:32:39 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:34:47 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:38:46 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:44:53 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:45:22 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 12:50:07 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 12:51:03 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:29:32 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:34:34 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 13:35:23 - - POST: []
|
||||||
|
2026-02-17 13:35:28 - GET / - POST: []
|
||||||
|
2026-02-17 13:44:18 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:44:19 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 13:44:44 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:46:48 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:47:13 - GET /index.php - POST: []
|
||||||
|
2026-02-17 13:47:48 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:47:48 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:49:26 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:52:42 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:53:30 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:53:31 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:53:33 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:53:34 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:53:44 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:53:50 - GET /index.php - POST: []
|
||||||
|
2026-02-17 13:53:52 - GET /index.php - POST: []
|
||||||
|
2026-02-17 13:54:13 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||||
|
2026-02-17 13:54:23 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 13:54:34 - GET /index.php?server_id=1&channel_id=3 - POST: []
|
||||||
|
2026-02-17 13:54:40 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 13:54:53","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"0","voice_vox_threshold":"0.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 13:54:54 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 13:55:35 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
{"date":"2026-02-17 13:56:01","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.1","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
|
||||||
|
2026-02-17 13:56:02 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||||
|
2026-02-17 13:56:22 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 13:56:32 - GET /index.php - POST: []
|
||||||
|
2026-02-17 14:03:44 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 14:03:45 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 14:04:57 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:05:10 - GET /index.php - POST: []
|
||||||
|
2026-02-17 14:05:34 - GET /index.php - POST: []
|
||||||
|
2026-02-17 14:05:36 - GET /index.php - POST: []
|
||||||
|
2026-02-17 14:12:18 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:12: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.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:12:42 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:12:48 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:12:58","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"0","voice_vox_threshold":"0.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:12:59 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:23:18 - GET / - POST: []
|
||||||
|
2026-02-17 14:23:53 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 14:23:54 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 14:34:38 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:34:58 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:35:14 - GET /index.php - POST: []
|
||||||
|
{"date":"2026-02-17 14:36:59","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.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:37:00 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:42:23 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 14:42:24 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 14:43:06 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:43:18","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.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:43:19 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:43:51 - GET /index.php - POST: []
|
||||||
|
{"date":"2026-02-17 14:44:07","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"ptt","voice_ptt_key":"0","voice_vox_threshold":"0.1","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
|
||||||
|
2026-02-17 14:44:07 - GET /index.php - POST: []
|
||||||
|
{"date":"2026-02-17 14:44:43","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.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:44:43 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:45:25","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.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:45:26 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:46:17","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.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:46:17 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:46:39","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.1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:46:40 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:48:43","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","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:48:44 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
{"date":"2026-02-17 14:48:59","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"1","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 14:48:59 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 14:59:20 - GET / - POST: []
|
||||||
|
2026-02-17 14:59:50 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 14:59:50 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:02:24 - GET /index.php?server_id=1&channel_id=22 - POST: []
|
||||||
|
2026-02-17 15:02:30 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
{"date":"2026-02-17 15:02:45","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","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 15:02:57 - GET /index.php - POST: []
|
||||||
|
2026-02-17 15:03:02 - GET /index.php - POST: []
|
||||||
|
{"date":"2026-02-17 15:03:19","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
|
||||||
|
2026-02-17 15:03:33 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
{"date":"2026-02-17 15:03: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.01","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
{"date":"2026-02-17 15:03:44","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.01","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
{"date":"2026-02-17 15:04:13","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.38","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
|
||||||
|
2026-02-17 15:04:30 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
{"date":"2026-02-17 15:04:48","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-17 15:04:52 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
{"date":"2026-02-17 15:05:02","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}
|
||||||
|
{"date":"2026-02-17 15:05:57","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-17 15:06:05 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 15:06:09 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
{"date":"2026-02-17 15:07:56","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:08:11","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"0","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 15:14:44 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:14:44 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:18:05 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:18:05 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:19:12 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
{"date":"2026-02-17 15:19:29","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"0","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:19:34","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"0","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:19:53","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"0","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
|
||||||
|
2026-02-17 15:19:56 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 15:20:01 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 15:23:33 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:23:33 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:32:11 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 15:32:29 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 15:33:22 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 15:35:42 - GET / - POST: []
|
||||||
|
2026-02-17 15:36:35 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:36:35 - GET /?fl_project=38443 - POST: []
|
||||||
|
2026-02-17 15:46:33 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||||
|
2026-02-17 15:46:36 - GET /index.php - POST: []
|
||||||
|
{"date":"2026-02-17 15:47: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: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-17 16:55:24 - GET / - POST: []
|
||||||
|
2026-02-17 17:03:39 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 17:03:47 - GET / - POST: []
|
||||||
|
2026-02-17 17:09:03 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 18:06:34 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 18:36:12 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 18:56:15 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 18:58:00 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 19:13:31 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 19:14:39 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 19:15:28 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 19:16:15 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 19:20:01 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 20:01:09 - GET / - POST: []
|
||||||
|
2026-02-17 20:15:23 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 20:53:22 - GET / - POST: []
|
||||||
|
2026-02-17 23:02:47 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:06:00 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:09:01 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:13:51 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:16:33 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:21:30 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:23:30 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:26:01 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:27:10 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:30:39 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:34:19 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:43:17 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:44:16 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:44:50 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:46:12 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:47:06 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:48:35 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:52:07 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:52:31 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:53:31 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:54:48 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:56:45 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:57:33 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:57:44 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-17 23:59:06 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:01:35 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:03:11 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:03:37 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:04:31 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:06:20 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:15:34 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:15:59 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:16:09 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:18:47 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:21:02 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:21:20 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:24:47 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:25:20 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:26:40 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:26:58 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:27:08 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:37:29 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 00:44:21 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 01:00:09 - GET /?fl_project=38527 - POST: []
|
||||||
|
2026-02-18 01:02:09 - GET / - POST: []
|
||||||
|
2026-02-18 01:07:48 - GET /?fl_project=38527 - POST: []
|
||||||
9
run_migration.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
try {
|
||||||
|
$sql = file_get_contents('db/migrations/20260217_unread_indicators.sql');
|
||||||
|
db()->exec($sql);
|
||||||
|
echo "Migration successful\n";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Migration failed: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
17
test_reorder.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
require 'db/config.php';
|
||||||
|
$orders = [
|
||||||
|
['id' => 1, 'position' => 1, 'category_id' => null],
|
||||||
|
['id' => 10, 'position' => 0, 'category_id' => null],
|
||||||
|
['id' => 6, 'position' => 2, 'category_id' => null],
|
||||||
|
['id' => 2, 'position' => 3, 'category_id' => null],
|
||||||
|
['id' => 9, 'position' => 4, 'category_id' => null],
|
||||||
|
['id' => 3, 'position' => 5, 'category_id' => null]
|
||||||
|
];
|
||||||
|
$server_id = 1;
|
||||||
|
$stmt = db()->prepare("UPDATE channels SET position = ?, category_id = ? WHERE id = ? AND server_id = ?");
|
||||||
|
foreach ($orders as $o) {
|
||||||
|
$stmt->execute([$o['position'], $o['category_id'], $o['id'], $server_id]);
|
||||||
|
echo "Updated ID {$o['id']} to position {$o['position']}\n";
|
||||||
|
}
|
||||||
|
?>
|
||||||
7
test_save.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||||
|
$_SESSION['user_id'] = 1; // Assuming user 1 exists
|
||||||
|
$_POST['username'] = 'testuser';
|
||||||
|
$_POST['theme'] = 'light';
|
||||||
|
require_once 'api_v1_user.php';
|
||||||
3
ws/server.log
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Server started on 0.0.0.0:8080
|
||||||
|
New client connected
|
||||||
|
Client disconnected
|
||||||
116
ws/server.php
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
// Very basic WebSocket server in pure PHP
|
||||||
|
$host = '0.0.0.0';
|
||||||
|
$port = 8080;
|
||||||
|
|
||||||
|
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||||
|
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
|
||||||
|
socket_bind($socket, $host, $port);
|
||||||
|
socket_listen($socket);
|
||||||
|
|
||||||
|
$clients = [$socket];
|
||||||
|
|
||||||
|
echo "Server started on $host:$port\n";
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$read = $clients;
|
||||||
|
$write = $except = null;
|
||||||
|
socket_select($read, $write, $except, null);
|
||||||
|
|
||||||
|
if (in_array($socket, $read)) {
|
||||||
|
$new_socket = socket_accept($socket);
|
||||||
|
$clients[] = $new_socket;
|
||||||
|
$header = socket_read($new_socket, 1024);
|
||||||
|
perform_handshake($header, $new_socket, $host, $port);
|
||||||
|
echo "New client connected\n";
|
||||||
|
$key = array_search($socket, $read);
|
||||||
|
unset($read[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($read as $client_socket) {
|
||||||
|
$data = socket_read($client_socket, 65536);
|
||||||
|
if ($data === false || strlen($data) === 0) {
|
||||||
|
$key = array_search($client_socket, $clients);
|
||||||
|
unset($clients[$key]);
|
||||||
|
socket_close($client_socket);
|
||||||
|
echo "Client disconnected\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded_data = unmask($data);
|
||||||
|
if ($decoded_data) {
|
||||||
|
$payload = json_decode($decoded_data, true);
|
||||||
|
if ($payload) {
|
||||||
|
// If it's already a structured message, just broadcast it
|
||||||
|
$response = mask($decoded_data);
|
||||||
|
} else {
|
||||||
|
// Fallback for raw text
|
||||||
|
$response = mask(json_encode(['type' => 'message', 'data' => $decoded_data]));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($clients as $client) {
|
||||||
|
if ($client != $socket && $client != $client_socket) {
|
||||||
|
@socket_write($client, $response, strlen($response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function perform_handshake($receved_header, $client_conn, $host, $port) {
|
||||||
|
$headers = array();
|
||||||
|
$lines = preg_split("/\r\n/", $receved_header);
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line = chop($line);
|
||||||
|
if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) {
|
||||||
|
$headers[$matches[1]] = $matches[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($headers['Sec-WebSocket-Key'])) return;
|
||||||
|
|
||||||
|
$secKey = $headers['Sec-WebSocket-Key'];
|
||||||
|
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
|
||||||
|
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
|
||||||
|
"Upgrade: websocket\r\n" .
|
||||||
|
"Connection: Upgrade\r\n" .
|
||||||
|
"Sec-WebSocket-Accept: $secAccept\r\n\r\n";
|
||||||
|
socket_write($client_conn, $upgrade, strlen($upgrade));
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmask($text) {
|
||||||
|
if (strlen($text) < 2) return null;
|
||||||
|
$length = ord($text[1]) & 127;
|
||||||
|
|
||||||
|
if ($length == 126) {
|
||||||
|
if (strlen($text) < 8) return null;
|
||||||
|
$masks = substr($text, 4, 4);
|
||||||
|
$data = substr($text, 8);
|
||||||
|
} elseif ($length == 127) {
|
||||||
|
if (strlen($text) < 14) return null;
|
||||||
|
$masks = substr($text, 10, 4);
|
||||||
|
$data = substr($text, 14);
|
||||||
|
} else {
|
||||||
|
if (strlen($text) < 6) return null;
|
||||||
|
$masks = substr($text, 2, 4);
|
||||||
|
$data = substr($text, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = "";
|
||||||
|
for ($i = 0; $i < strlen($data); ++$i) {
|
||||||
|
$decoded .= $data[$i] ^ $masks[$i % 4];
|
||||||
|
}
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mask($text) {
|
||||||
|
$b1 = 0x81; // FIN + Opcode 1 (text)
|
||||||
|
$length = strlen($text);
|
||||||
|
if ($length <= 125)
|
||||||
|
$header = pack('CC', $b1, $length);
|
||||||
|
elseif ($length > 125 && $length < 65536)
|
||||||
|
$header = pack('CCn', $b1, 126, $length);
|
||||||
|
elseif ($length >= 65536)
|
||||||
|
$header = pack('CCNN', $b1, 127, $length);
|
||||||
|
return $header . $text;
|
||||||
|
}
|
||||||
1
ws_output.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
Server started on 0.0.0.0:8080
|
||||||