mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-29 11:49:16 +00:00
Add input validation, fix forms with inputs not having labels
This commit is contained in:
@@ -67,6 +67,8 @@ public abstract class Dialog {
|
||||
private final AfterAction afterAction;
|
||||
private final List<String> labels;
|
||||
private final List<DialogInput<?>> inputs = new ArrayList<>();
|
||||
@Getter
|
||||
private final ParsedInputs defaultInputs;
|
||||
|
||||
protected Dialog(GeyserSession session, NbtMap map) {
|
||||
title = MessageTranslator.convertFromNullableNbtTag(session, map.get("title"));
|
||||
@@ -96,6 +98,7 @@ public abstract class Dialog {
|
||||
for (NbtMap input : inputTag) {
|
||||
inputs.add(DialogInput.read(session, input));
|
||||
}
|
||||
defaultInputs = inputs.isEmpty() ? ParsedInputs.EMPTY : new ParsedInputs(inputs);
|
||||
}
|
||||
|
||||
private static Optional<String> readBody(GeyserSession session, NbtMap tag) {
|
||||
@@ -123,6 +126,9 @@ public abstract class Dialog {
|
||||
CustomForm.Builder builder = CustomForm.builder()
|
||||
.translator(MinecraftLocale::getLocaleString, session.locale())
|
||||
.title(title);
|
||||
for (String label : labels) {
|
||||
builder.label(label);
|
||||
}
|
||||
|
||||
restored.ifPresentOrElse(last -> last.restore(builder), () -> inputs.forEach(input -> input.addComponent(builder)));
|
||||
builder.closedOrInvalidResultHandler(response -> holder.closeDialog(onCancel()));
|
||||
@@ -143,8 +149,13 @@ public abstract class Dialog {
|
||||
session.sendDialogForm(createForm(session, Optional.of(inputs), holder).build());
|
||||
}
|
||||
|
||||
protected ParsedInputs parseInput(CustomFormResponse response) {
|
||||
return new ParsedInputs(inputs, response);
|
||||
protected Optional<ParsedInputs> parseInput(GeyserSession session, CustomFormResponse response, DialogHolder holder) {
|
||||
ParsedInputs parsed = new ParsedInputs(inputs, response);
|
||||
if (parsed.hasErrors()) {
|
||||
restoreForm(session, parsed, holder);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(parsed);
|
||||
}
|
||||
|
||||
public static Dialog readDialog(RegistryEntryContext context) {
|
||||
|
||||
@@ -146,7 +146,7 @@ public class DialogHolder {
|
||||
// Don't run close functionality if we're asking for command confirmation
|
||||
if (dialog.canCloseWithEscape()) {
|
||||
shouldClose = true;
|
||||
if (runAction(onCancel, lastInputs == null ? ParsedInputs.EMPTY : lastInputs)) {
|
||||
if (runAction(onCancel, lastInputs == null ? dialog.defaultInputs() : lastInputs)) {
|
||||
manager.close();
|
||||
}
|
||||
return;
|
||||
@@ -216,7 +216,7 @@ public class DialogHolder {
|
||||
.translator(MinecraftLocale::getLocaleString, manager.session().locale())
|
||||
.title("gui.waitingForResponse.title")
|
||||
.content(content)
|
||||
.optionalButton("Back", sendBackButton)
|
||||
.optionalButton("gui.back", sendBackButton)
|
||||
.closedOrInvalidResultHandler(() -> {
|
||||
if (stillValid()) { // If still waiting on a new dialog
|
||||
waitForResponse();
|
||||
@@ -229,7 +229,7 @@ public class DialogHolder {
|
||||
/**
|
||||
* This method runs the given action, if present, with the given inputs.
|
||||
*
|
||||
* <p>These inputs can be {@link ParsedInputs#EMPTY} when the dialog has no inputs, or the dialog was closed without entering anything, but can never be {@code null}.
|
||||
* <p>These inputs can be {@link ParsedInputs#EMPTY} when the dialog has no inputs, but can never be {@code null}.
|
||||
* The method returns {@code true} if the dialog's after action can be executed, and {@code false} if not. The latter is the case when the action opened a new
|
||||
* dialog or screen, in which case the after action will not be handled or be handled by the screen, respectively.</p>
|
||||
*
|
||||
|
||||
@@ -58,13 +58,14 @@ public abstract class DialogWithButtons extends Dialog {
|
||||
builder.dropdown(dropdown);
|
||||
|
||||
builder.validResultHandler(response -> {
|
||||
ParsedInputs inputs = parseInput(response);
|
||||
int selection = response.asDropdown();
|
||||
if (selection == buttons.size()) {
|
||||
holder.runButton(exitAction, inputs);
|
||||
} else {
|
||||
holder.runButton(Optional.of(buttons.get(selection)), inputs);
|
||||
}
|
||||
parseInput(session, response, holder).ifPresent(inputs -> {
|
||||
int selection = response.asDropdown();
|
||||
if (selection == buttons.size()) {
|
||||
holder.runButton(exitAction, inputs);
|
||||
} else {
|
||||
holder.runButton(Optional.of(buttons.get(selection)), inputs);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,7 +80,6 @@ public abstract class DialogWithButtons extends Dialog {
|
||||
if (response.clickedButtonId() == buttons.size()) {
|
||||
holder.runButton(exitAction, ParsedInputs.EMPTY);
|
||||
} else {
|
||||
|
||||
holder.runButton(Optional.of(buttons.get(response.clickedButtonId())), ParsedInputs.EMPTY);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ public class NoticeDialog extends Dialog {
|
||||
|
||||
@Override
|
||||
protected void addCustomComponents(GeyserSession session, CustomForm.Builder builder, DialogHolder holder) {
|
||||
builder.validResultHandler(response -> holder.runButton(button, parseInput(response)));
|
||||
builder.validResultHandler(response -> parseInput(session, response, holder).ifPresent(inputs -> holder.runButton(button, inputs)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -56,7 +56,7 @@ public class BooleanInput extends DialogInput<Boolean> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean read(CustomFormResponse response) {
|
||||
public Boolean read(CustomFormResponse response) throws DialogInputParseException {
|
||||
return response.asToggle();
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public abstract class DialogInput<T> {
|
||||
|
||||
public abstract void addComponent(CustomForm.Builder builder, Optional<T> restored);
|
||||
|
||||
public abstract T read(CustomFormResponse response);
|
||||
public abstract T read(CustomFormResponse response) throws DialogInputParseException;
|
||||
|
||||
public abstract String asSubstitution(T value);
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.session.dialog.input;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class DialogInputParseException extends Exception {
|
||||
|
||||
// Exceptions don't work with generics, so we have to do a bit of unsafe casting and assume the object is of the input type :(
|
||||
@Getter
|
||||
private final Object partial;
|
||||
|
||||
public DialogInputParseException(String message, Object partial) {
|
||||
super(message);
|
||||
this.partial = partial;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class NumberRangeInput extends DialogInput<Float> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float read(CustomFormResponse response) {
|
||||
public Float read(CustomFormResponse response) throws DialogInputParseException {
|
||||
return response.asSlider();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,19 +30,26 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.geysermc.cumulus.form.CustomForm;
|
||||
import org.geysermc.cumulus.response.CustomFormResponse;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ParsedInputs {
|
||||
public static final ParsedInputs EMPTY = new ParsedInputs(List.of(), null);
|
||||
public static final ParsedInputs EMPTY = new ParsedInputs(List.of());
|
||||
|
||||
private final Map<DialogInput<?>, Object> values = new LinkedHashMap<>();
|
||||
private final Map<DialogInput<?>, String> errors = new HashMap<>();
|
||||
|
||||
public ParsedInputs(List<DialogInput<?>> inputs, CustomFormResponse response) {
|
||||
for (DialogInput<?> input : inputs) {
|
||||
values.put(input, input.read(response));
|
||||
try {
|
||||
values.put(input, input.read(response));
|
||||
} catch (DialogInputParseException exception) {
|
||||
values.put(input, exception.getPartial());
|
||||
errors.put(input, exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +61,11 @@ public class ParsedInputs {
|
||||
|
||||
public void restore(CustomForm.Builder builder) {
|
||||
for (Map.Entry<DialogInput<?>, Object> entry : values.entrySet()) {
|
||||
String error = errors.get(entry.getKey());
|
||||
if (error != null) {
|
||||
builder.label("§cError parsing input data: " + error + ".");
|
||||
builder.label("§cPlease adjust!");
|
||||
}
|
||||
// Can't be a Geyser update without eclipse dealing with generics
|
||||
((DialogInput) entry.getKey()).addComponent(builder, Optional.of(entry.getValue()));
|
||||
}
|
||||
@@ -75,4 +87,8 @@ public class ParsedInputs {
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return !errors.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class SingleOptionInput extends DialogInput<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(CustomFormResponse response) {
|
||||
public String read(CustomFormResponse response) throws DialogInputParseException {
|
||||
return entries.get(response.asDropdown()).id();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,11 +56,13 @@ public class TextInput extends DialogInput<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(CustomFormResponse response) {
|
||||
String raw = response.asInput();
|
||||
assert raw != null;
|
||||
// Bedrock doesn't support setting a max length, so we just cut it off to not have the server complain
|
||||
return raw.substring(0, Math.min(raw.length(), maxLength));
|
||||
public String read(CustomFormResponse response) throws DialogInputParseException {
|
||||
String text = response.asInput();
|
||||
assert text != null;
|
||||
if (text.length() > maxLength) {
|
||||
throw new DialogInputParseException("length of text cannot be above " + maxLength, text);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user