www/matrix_admin.php

280 lines
10 KiB
PHP
Raw Normal View History

2026-05-11 22:31:48 +00:00
<?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>