280 lines
10 KiB
PHP
280 lines
10 KiB
PHP
<?php declare(strict_types=1);
|
||
error_reporting(E_ALL);
|
||
ini_set('display_errors', '1');
|
||
|
||
// ==============================
|
||
// ⚙️ CONFIGURAÇÃO
|
||
// ==============================
|
||
$MATRIX_BASE = "https://matrix.xupas.mywire.org";
|
||
$SERVER_NAME = "matrix.xupas.mywire.org";
|
||
$TOKEN_FILE = "/var/www/secure/matrix_token.txt";
|
||
|
||
$ADMIN_TOKEN = is_readable($TOKEN_FILE) ? trim(file_get_contents($TOKEN_FILE)) : '';
|
||
|
||
$DEBUG_LOG = "/tmp/matrix_debug.log";
|
||
|
||
// ==============================
|
||
// 📝 DEBUG HELPER
|
||
// ==============================
|
||
function dbg($txt) {
|
||
global $DEBUG_LOG;
|
||
file_put_contents($DEBUG_LOG, "[".date('Y-m-d H:i:s')."] $txt\n", FILE_APPEND);
|
||
}
|
||
|
||
// ==============================
|
||
// 🔧 API CALL
|
||
// ==============================
|
||
function call_api(string $method, string $url, ?array $data = null, ?string $token = null): array {
|
||
dbg("API CALL: $method $url DATA=" . json_encode($data));
|
||
$ch = curl_init($url);
|
||
$headers = ['Content-Type: application/json'];
|
||
if ($token) $headers[] = "Authorization: Bearer $token";
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_CUSTOMREQUEST => $method,
|
||
CURLOPT_HTTPHEADER => $headers,
|
||
CURLOPT_TIMEOUT => 60,
|
||
]);
|
||
if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||
$resp = curl_exec($ch);
|
||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
dbg("API RESP: HTTP=$code BODY=$resp");
|
||
curl_close($ch);
|
||
return [$code, $resp];
|
||
}
|
||
|
||
// ==============================
|
||
// 🧼 USERNAME CLEANER FIX
|
||
// ==============================
|
||
function full_user(string $name, string $server): string {
|
||
dbg("RAW POST USER=[".$name."] HEX=[".bin2hex($name)."]");
|
||
$clean = trim($name);
|
||
$clean = preg_replace('/[\x{00A0}\x{200B}\x{200C}\x{200D}\x{FEFF}]/u', '', $clean);
|
||
dbg("CLEAN USER=[".$clean."] HEX=[".bin2hex($clean)."]");
|
||
if (preg_match('/^@[a-z0-9=_\-\.\/\+]+:[a-z0-9\.\-]+$/i', $clean)) return $clean;
|
||
if ($clean === '') return '';
|
||
if ($clean[0] !== '@') $clean = '@' . $clean;
|
||
if (!str_contains($clean, ':')) $clean .= ':' . $server;
|
||
dbg("FINAL USER=[".$clean."] HEX=[".bin2hex($clean)."]");
|
||
return $clean;
|
||
}
|
||
|
||
// ==============================
|
||
// 🚦 PROCESSAMENTO DE AÇÕES
|
||
// ==============================
|
||
$action = $_POST['action'] ?? null;
|
||
$msg = '';
|
||
|
||
if ($action && $ADMIN_TOKEN) {
|
||
|
||
dbg("=== ACTION: $action ===");
|
||
dbg("POST DATA: " . json_encode($_POST));
|
||
|
||
// criar utilizador
|
||
if ($action === 'add_user') {
|
||
$user = full_user($_POST['user'], $SERVER_NAME);
|
||
$pass = trim($_POST['pass']);
|
||
[$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [
|
||
"password" => $pass,
|
||
"deactivated" => false,
|
||
"admin" => false
|
||
], $ADMIN_TOKEN);
|
||
$msg = ($code == 200 || $code == 201) ? "✅ Criado: $user" : "❌ Erro [$code]: $resp";
|
||
}
|
||
|
||
// alterar password
|
||
if ($action === 'change_pass') {
|
||
$user = full_user($_POST['user'], $SERVER_NAME);
|
||
$pass = trim($_POST['pass']);
|
||
[$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/reset_password/" . urlencode($user), [
|
||
"new_password" => $pass
|
||
], $ADMIN_TOKEN);
|
||
$msg = ($code == 200) ? "🔑 Senha alterada para $user" : "❌ Erro [$code]: $resp";
|
||
}
|
||
|
||
// admin / unadmin
|
||
if ($action === 'make_admin' || $action === 'revoke_admin') {
|
||
$user = full_user($_POST['user'], $SERVER_NAME);
|
||
$is_admin = $action === 'make_admin';
|
||
[$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [
|
||
"admin" => $is_admin
|
||
], $ADMIN_TOKEN);
|
||
$msg = ($code == 200)
|
||
? ($is_admin ? "⬆️ $user agora é admin" : "⬇️ $user deixou de ser admin")
|
||
: "❌ Erro [$code]: $resp";
|
||
}
|
||
|
||
// ativar / desativar
|
||
if ($action === 'deactivate_user' || $action === 'reactivate_user') {
|
||
$user = full_user($_POST['user'], $SERVER_NAME);
|
||
$active = $action === 'reactivate_user';
|
||
[$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [
|
||
"deactivated" => !$active
|
||
], $ADMIN_TOKEN);
|
||
$msg = ($code == 200)
|
||
? ($active ? "♻️ $user reativado" : "🚫 $user desativado")
|
||
: "❌ Erro [$code]: $resp";
|
||
}
|
||
|
||
// apagar user
|
||
if ($action === 'delete_user') {
|
||
$user = full_user($_POST['user'], $SERVER_NAME);
|
||
[$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/deactivate/" . urlencode($user), [
|
||
"erase" => true
|
||
], $ADMIN_TOKEN);
|
||
$msg = ($code == 200 || $code == 204)
|
||
? "🗑️ $user removido permanentemente"
|
||
: "❌ Erro [$code]: $resp";
|
||
}
|
||
|
||
// ✅ NOVA: apagar mensagens do user (mantendo o user)
|
||
if ($action === 'erase_messages') {
|
||
$user = full_user($_POST['user'], $SERVER_NAME);
|
||
[$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/user/$user/rooms", null, $ADMIN_TOKEN);
|
||
$rooms = json_decode($resp, true)['rooms'] ?? [];
|
||
$errors = [];
|
||
foreach ($rooms as $room) {
|
||
[$c, $r] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/purge_history/" . urlencode($room['room_id']), [
|
||
"delete_local_events" => true,
|
||
"reason" => "Apagadas via painel"
|
||
], $ADMIN_TOKEN);
|
||
if ($c != 200) $errors[] = "$room[room_id]: $r";
|
||
}
|
||
$msg = empty($errors) ? "🧹 Mensagens de $user apagadas" : "❌ Erros: " . implode("; ", $errors);
|
||
}
|
||
|
||
// ✅ NOVA: limpar metadados gerais
|
||
if ($action === 'clear_metadata') {
|
||
@unlink($DEBUG_LOG);
|
||
// aqui podes adicionar mais caminhos de logs/metadados
|
||
$msg = "⚡ Metadados limpos!";
|
||
}
|
||
}
|
||
|
||
// ==============================
|
||
// 📋 LISTAGEM USERS
|
||
// ==============================
|
||
$users = [];
|
||
if ($ADMIN_TOKEN) {
|
||
$next = null;
|
||
do {
|
||
$url = "$MATRIX_BASE/_synapse/admin/v2/users?limit=100";
|
||
if ($next) $url .= "&from=" . urlencode($next);
|
||
[$code, $resp] = call_api("GET", $url, null, $ADMIN_TOKEN);
|
||
if ($code != 200) { dbg("ERRO AO LER USERS: HTTP=$code BODY=$resp"); break; }
|
||
$data = json_decode($resp, true);
|
||
$chunk = $data['users'] ?? $data['results'] ?? [];
|
||
$users = array_merge($users, $chunk);
|
||
$next = $data['next_token'] ?? null;
|
||
} while ($next);
|
||
}
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="pt">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Matrix Admin - Xupas</title>
|
||
<style>
|
||
body {background:#0e0e10;color:#eee;font-family:Segoe UI,Arial;margin:20px;}
|
||
.box{background:#1a1a1d;border:1px solid #333;border-radius:10px;padding:12px;margin-top:10px;}
|
||
h2{color:#3fa9f5;}
|
||
input,select,button{background:#202124;color:#eee;border:1px solid #333;border-radius:6px;padding:6px 8px;margin:2px;}
|
||
button:hover{background:#3fa9f5;color:#000;}
|
||
table{border-collapse:collapse;width:100%;margin-top:10px;}
|
||
th,td{border:1px solid #333;padding:6px;}
|
||
tr:nth-child(even){background:#171717;}
|
||
.msg{padding:8px;margin:8px 0;border-radius:6px;}
|
||
.ok{background:#12391f;color:#4caf50;}
|
||
.err{background:#3a1515;color:#ffb3b3;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h2>💻 Matrix Admin - Xupas</h2>
|
||
|
||
<?php if ($msg): ?>
|
||
<div class="msg <?=str_starts_with($msg,'❌')?'err':'ok'?>"><?=htmlspecialchars($msg)?></div>
|
||
<?php endif; ?>
|
||
|
||
<div class="box">
|
||
<h3>➕ Criar Utilizador</h3>
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="add_user">
|
||
<input type="text" name="user" placeholder="nome" required>
|
||
<input type="password" name="pass" placeholder="senha" required>
|
||
<button>Criar</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="box">
|
||
<h3>👥 Utilizadores</h3>
|
||
<table>
|
||
<tr>
|
||
<th>User</th>
|
||
<th>Admin</th>
|
||
<th>Ativo</th>
|
||
<th>Último Login</th>
|
||
<th>Ações</th>
|
||
</tr>
|
||
<?php foreach($users as $u):
|
||
$n = $u['name'] ?? $u['user_id'] ?? '—';
|
||
$seen = isset($u['last_seen_ts']) ? date('Y-m-d H:i:s', (int)($u['last_seen_ts'] / 1000)) : '—';
|
||
?>
|
||
<tr>
|
||
<td><?=$n?></td>
|
||
<td><?=!empty($u['admin'])?'🟢':'⚫'?></td>
|
||
<td><?=!empty($u['deactivated'])?'❌':'✅'?></td>
|
||
<td><?=$seen?></td>
|
||
<td>
|
||
<!-- Alterar senha -->
|
||
<form method="post" style="display:inline">
|
||
<input type="hidden" name="action" value="change_pass">
|
||
<input type="hidden" name="user" value="<?=$n?>">
|
||
<input type="password" name="pass" placeholder="nova" style="width:90px">
|
||
<button>🔑</button>
|
||
</form>
|
||
|
||
<!-- Tornar admin / remover admin -->
|
||
<form method="post" style="display:inline">
|
||
<input type="hidden" name="action" value="<?=!empty($u['admin'])?'revoke_admin':'make_admin'?>">
|
||
<input type="hidden" name="user" value="<?=$n?>">
|
||
<button><?=!empty($u['admin'])?'⬇️':'⬆️'?></button>
|
||
</form>
|
||
|
||
<!-- Ativar / desativar -->
|
||
<form method="post" style="display:inline">
|
||
<input type="hidden" name="action" value="<?=!empty($u['deactivated'])?'reactivate_user':'deactivate_user'?>">
|
||
<input type="hidden" name="user" value="<?=$n?>">
|
||
<button><?=!empty($u['deactivated'])?'♻️':'🚫'?></button>
|
||
</form>
|
||
|
||
<!-- Apagar user -->
|
||
<form method="post" style="display:inline" onsubmit="return confirm('Apagar <?=$n?>?');">
|
||
<input type="hidden" name="action" value="delete_user">
|
||
<input type="hidden" name="user" value="<?=$n?>">
|
||
<button>🗑️</button>
|
||
</form>
|
||
|
||
<!-- NOVO: apagar mensagens do user -->
|
||
<form method="post" style="display:inline" onsubmit="return confirm('Apagar TODAS mensagens de <?=$n?>?');">
|
||
<input type="hidden" name="action" value="erase_messages">
|
||
<input type="hidden" name="user" value="<?=$n?>">
|
||
<button>🧹</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- NOVO: limpar metadados gerais -->
|
||
<div class="box">
|
||
<h3>⚡ Limpar metadados gerais</h3>
|
||
<form method="post" onsubmit="return confirm('⚠️ Isto apagará logs e metadados, mas não afetará utilizadores ou mensagens. Continuar?');">
|
||
<input type="hidden" name="action" value="clear_metadata">
|
||
<button>🗑️ Limpar tudo</button>
|
||
</form>
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|