www/matrix_admin.php
2026-06-27 21:44:44 +00:00

280 lines
10 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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>