001/* 002 * PermissionsEx 003 * Copyright (C) zml and PermissionsEx contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package ca.stellardrift.permissionsex.minecraft.command; 018 019import ca.stellardrift.permissionsex.subject.SubjectRef; 020import cloud.commandframework.arguments.CommandArgument; 021import cloud.commandframework.arguments.standard.UUIDArgument; 022import cloud.commandframework.meta.CommandMeta; 023import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; 024import org.checkerframework.checker.nullness.qual.Nullable; 025 026import java.util.Locale; 027import java.util.Map; 028import java.util.UUID; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.concurrent.ConcurrentMap; 031import java.util.function.Consumer; 032 033/** 034 * Handler for command callbacks. 035 */ 036public final class CallbackController { 037 private static final CommandArgument<Commander, UUID> CALLBACK_ID = UUIDArgument.of("id"); 038 private final ConcurrentMap<String, java.util.concurrent.ConcurrentMap<UUID, CallbackInstance>> knownCallbacks = new ConcurrentHashMap<>(); 039 040 /** 041 * Register a callback, returning the command string to send to execute the provided function. 042 * 043 * @return the command to execute for the provided function 044 */ 045 public String registerCallback(final Commander source, final Consumer<Commander> callback) { 046 final UUID id = UUID.randomUUID(); 047 knownCallbacks.computeIfAbsent(mapKey(source), $ -> new ConcurrentHashMap<>()) 048 .put(id, new CallbackInstance(source, callback, false)); 049 return "/pex cb " + id; 050 } 051 052 private String mapKey(final Commander cmd) { 053 final @Nullable SubjectRef<?> ident = cmd.subjectIdentifier(); 054 if (ident != null) { 055 return ident.serializedIdentifier(); 056 } else { 057 return PlainComponentSerializer.plain().serialize(cmd.name()); 058 } 059 } 060 061 public void clearOwnedBy(final String name) { 062 this.knownCallbacks.remove(name); 063 } 064 065 public void clearOwnedBy(final UUID name) { 066 knownCallbacks.remove(name.toString().toLowerCase(Locale.ROOT)); 067 } 068 069 public void registerCommand(final CommandRegistrationContext registration) { 070 registration.register(builder -> builder.argument(CALLBACK_ID) 071 .meta(CommandMeta.DESCRIPTION, "Trigger a registered command callback") 072 .hidden() 073 .handler(ctx -> { 074 final UUID id = ctx.get(CALLBACK_ID); 075 final Map<UUID, CallbackInstance> userCalllbacks = knownCallbacks.get(mapKey(ctx.getSender())); 076 if (userCalllbacks == null) { 077 throw new CommandException(Messages.COMMAND_CALLBACK_ERROR_UNKNOWN_ID.tr(id)); 078 } 079 final CallbackInstance callback = userCalllbacks.get(id); 080 if (callback == null) { 081 throw new CommandException(Messages.COMMAND_CALLBACK_ERROR_UNKNOWN_ID.tr(id)); 082 } 083 084 if (!mapKey(callback.source).equals(mapKey(ctx.getSender()))) { 085 throw new CommandException(Messages.COMMAND_CALLBACK_ERROR_ONLY_OWN_ALLOWED.tr()); 086 } 087 088 try { 089 callback.callback.accept(ctx.getSender()); 090 } finally { 091 if (callback.oneUse) { 092 userCalllbacks.remove(id); 093 } 094 } 095 }), "callback", "cb").toString(); 096 } 097 098 099 static final class CallbackInstance { 100 final Commander source; 101 final Consumer<Commander> callback; 102 final boolean oneUse; 103 104 CallbackInstance(final Commander source, final Consumer<Commander> callback, final boolean oneUse) { 105 this.source = source; 106 this.callback = callback; 107 this.oneUse = oneUse; 108 } 109 } 110}