Volver al índice

src/main/java/ar/com/companeros/social/AdminChannel.java

package ar.com.companeros.social;

import ar.com.companeros.identity.*;
import com.mojang.brigadier.arguments.StringArgumentType;
import java.util.*;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.ServerChatEvent;
import static net.minecraft.commands.Commands.*;

/** Canal explícito de administración: un mensaje inline o el siguiente mensaje de chat. */
public final class AdminChannel {
    private static final Map<Administrator, Selection> armed = new java.util.concurrent.ConcurrentHashMap<>();
    private static final UUID CONSOLE = new UUID(0, 0);
    private static boolean lifecycleInstalled;
    private record Administrator(MinecraftServer server, UUID id) { }
    private record Selection(List<UUID> recipients, long expiresAt) {
        private Selection { recipients = List.copyOf(recipients); }
    }
    private AdminChannel() { }

    public static void register(RegisterCommandsEvent event) {
        installLifecycle();
        var root = literal("botmalo").requires(AdminChannel::allowed);
        root.then(literal("cancelar").executes(ctx -> {
            clearSelection(ctx.getSource());
            ctx.getSource().sendSuccess(() -> Component.literal("Canal de bots cancelado."), false); return 1;
        }));
        root.then(argument("destino", StringArgumentType.word())
            .executes(ctx -> {
                var source = ctx.getSource();
                String target = StringArgumentType.getString(ctx, "destino");
                List<AgentIdentity> selected = recipients(source, target);
                if (selected.isEmpty()) return missing(source);
                ServerPlayer admin = source.getPlayerOrException();
                armed.put(new Administrator(source.getServer(), admin.getUUID()),
                    new Selection(selected.stream().map(AgentIdentity::id).toList(), System.currentTimeMillis() + 120_000));
                source.sendSuccess(() -> Component.literal("Tu próximo mensaje de chat será enviado a " + target + "."), false); return 1;
            })
            .then(argument("mensaje", StringArgumentType.greedyString()).executes(ctx ->
                send(ctx.getSource(), StringArgumentType.getString(ctx, "destino"), StringArgumentType.getString(ctx, "mensaje")))));
        event.getDispatcher().register(root);
    }

    // Solo un humano con permisos o la consola puede generar una intervención de administrador.
    private static boolean allowed(CommandSourceStack source) {
        return source != null && source.getServer().isSameThread() && source.hasPermission(2) && (source.getEntity() == null
            || source.getEntity() instanceof ServerPlayer player
                && !(player instanceof net.minecraftforge.common.util.FakePlayer)
                && !ar.com.companeros.body.PlayerSessions.isAgent(source.getServer(), player.getUUID())
                && !AgentRegistry.get(source.getServer()).isAgent(player.getUUID()));
    }

    public static void chat(ServerChatEvent event) {
        ServerPlayer admin = event.getPlayer();
        Selection selection = armed.remove(new Administrator(admin.server, admin.getUUID()));
        if (selection == null) return;
        // Aunque haya vencido o perdido OP, este texto privado no vuelve al chat global.
        event.setCanceled(true);
        if (selection.expiresAt() < System.currentTimeMillis()) {
            admin.server.execute(() -> admin.sendSystemMessage(Component.literal("El canal de bots venció. Volvé a usar /botmalo."))); return;
        }
        String text = event.getRawText();
        admin.server.execute(() -> {
            if (admin.server.getPlayerList().getPlayer(admin.getUUID()) != admin || admin.hasDisconnected()) return;
            CommandSourceStack source = admin.createCommandSourceStack();
            if (!allowed(source)) {
                source.sendFailure(Component.literal("El canal de bots ya no está autorizado.")); return;
            }
            sendSelected(source, selection.recipients(), text);
        });
    }

    public static int send(CommandSourceStack source, String target, String message) {
        if (!allowed(source)) return 0;
        if (target == null || target.isBlank() || target.length() > 48 || message == null) return 0;
        clearSelection(source);
        var agents = recipients(source, target);
        if (agents.isEmpty()) return missing(source);
        return sendSelected(source, agents.stream().map(AgentIdentity::id).toList(), message);
    }
    private static int sendSelected(CommandSourceStack source, List<UUID> selected, String message) {
        if (!allowed(source)) return 0;
        AgentRegistry registry = AgentRegistry.get(source.getServer());
        List<AgentIdentity> agents = selected.stream().distinct().map(registry::find).flatMap(Optional::stream)
            .filter(agent -> agent.state() != AgentState.RETIRED).toList();
        if (agents.isEmpty()) return missing(source);
        UUID adminId = source.getEntity() == null ? CONSOLE : source.getEntity().getUUID();
        int delivered = 0;
        for (AgentIdentity agent : agents) if (AgentInbox.get(source.getServer()).deliver(agent.id(), adminId,
                source.getTextName(), AgentInbox.Kind.ADMIN, message)) delivered++;
        int count = delivered;
        if (count == 0) source.sendFailure(Component.literal("No se pudo entregar: el mensaje está vacío, es demasiado largo o el buzón está lleno."));
        else source.sendSuccess(() -> Component.literal("Mensaje del administrador enviado a " + count + " bot(s)."), false);
        return count;
    }
    private static List<AgentIdentity> recipients(CommandSourceStack source, String target) {
        return AgentRegistry.get(source.getServer()).entries().stream().filter(agent -> agent.state() != AgentState.RETIRED)
            .filter(agent -> target.equalsIgnoreCase("all") || agent.name().equalsIgnoreCase(target)).toList();
    }
    private static void clearSelection(CommandSourceStack source) {
        if (source.getEntity() != null) armed.remove(new Administrator(source.getServer(), source.getEntity().getUUID()));
    }
    private static synchronized void installLifecycle() {
        if (lifecycleInstalled) return;
        lifecycleInstalled = true;
        net.minecraftforge.common.MinecraftForge.EVENT_BUS.addListener(AdminChannel::loggedOut);
        net.minecraftforge.common.MinecraftForge.EVENT_BUS.addListener(AdminChannel::stopping);
    }
    private static void loggedOut(net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent event) {
        if (event.getEntity() instanceof ServerPlayer player) armed.remove(new Administrator(player.server, player.getUUID()));
    }
    private static void stopping(net.minecraftforge.event.server.ServerStoppingEvent event) {
        armed.keySet().removeIf(key -> key.server() == event.getServer());
    }
    private static int missing(CommandSourceStack source) {
        source.sendFailure(Component.literal("No hay bots registrados con ese nombre.")); return 0;
    }
}