Untitled
Anonymous
plain_text
02/18/2026 3:12 PM
22.2 KB
30
Indexable
package com.example.discord.templates;
import net.dv8tion.jda.api.components.MessageTopLevelComponent;
import net.dv8tion.jda.api.components.utils.ComponentDeserializer;
import net.dv8tion.jda.api.components.utils.ComponentSerializer;
import net.dv8tion.jda.api.utils.FileUpload;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Processa templates de Components V2 do JDA:
* - Desserializa JSON -> componentes JDA (1 vez)
* - Renderiza substituindo placeholders {variavel} em campos textuais
* - Re-serializa quando necessário
*
* Tudo isso usando as classes OFICIAIS do JDA:
* - ComponentDeserializer / ComponentSerializer
* - DataArray / DataObject
*/
public final class JdaMessageTemplateProcessor {
private final PlaceholderEngine placeholderEngine;
private final TextFieldPolicy textFieldPolicy;
public JdaMessageTemplateProcessor(PlaceholderEngine placeholderEngine, TextFieldPolicy textFieldPolicy) {
this.placeholderEngine = Objects.requireNonNull(placeholderEngine, "placeholderEngine");
this.textFieldPolicy = Objects.requireNonNull(textFieldPolicy, "textFieldPolicy");
}
/**
* Desserializa o JSON (array de components top-level) para uma estrutura de template.
* Você faz isso UMA VEZ e guarda o TemplateMessageComponents.
*/
public TemplateMessageComponents deserializeTemplate(
String jsonArrayComponents,
List<FileUpload> implicitFileUploads
) {
Objects.requireNonNull(jsonArrayComponents, "jsonArrayComponents");
Objects.requireNonNull(implicitFileUploads, "implicitFileUploads");
DataArray rawComponentsDataArray = DataArray.fromJson(jsonArrayComponents);
ComponentDeserializer componentDeserializer = new ComponentDeserializer(implicitFileUploads);
List<MessageTopLevelComponent> components = componentDeserializer
.deserializeAs(MessageTopLevelComponent.class, rawComponentsDataArray)
.toList();
// Descobrir placeholders existentes no template (uma vez só)
Set<String> placeholdersFound = placeholderEngine.findPlaceholders(rawComponentsDataArray, textFieldPolicy);
return new TemplateMessageComponents(
List.copyOf(components),
List.copyOf(implicitFileUploads),
placeholdersFound
);
}
/**
* Renderiza o template substituindo placeholders com valores.
* Retorna os componentes prontos + relatório de placeholders (usados, faltantes, etc).
*/
public RenderedMessageComponents renderTemplate(
TemplateMessageComponents template,
Map<String, String> placeholderValues
) {
Objects.requireNonNull(template, "template");
Objects.requireNonNull(placeholderValues, "placeholderValues");
// Serializa os componentes em memória (sem JSON string) para DataArray
ComponentSerializer componentSerializer = new ComponentSerializer();
componentSerializer.addAll(template.components());
DataArray serializedDataArray = componentSerializer.toData();
// Aplica substituição somente em campos textuais permitidos
DataArray substitutedDataArray = placeholderEngine.applySubstitutions(
serializedDataArray,
placeholderValues,
textFieldPolicy
);
// Desserializa de volta para objetos do JDA
ComponentDeserializer componentDeserializer = new ComponentDeserializer(template.implicitFileUploads());
List<MessageTopLevelComponent> renderedComponents = componentDeserializer
.deserializeAs(MessageTopLevelComponent.class, substitutedDataArray)
.toList();
PlaceholderReport report = PlaceholderReport.from(template.placeholdersFoundInTemplate(), placeholderValues);
return new RenderedMessageComponents(List.copyOf(renderedComponents), report);
}
/**
* Serializa componentes prontos para JSON (se você precisar armazenar ou depurar).
*/
public String serializeToJson(List<MessageTopLevelComponent> components) {
Objects.requireNonNull(components, "components");
ComponentSerializer componentSerializer = new ComponentSerializer();
componentSerializer.addAll(components);
return componentSerializer.toData().toJson();
}
// ======================================================================
// DTOs (resultados) — imutáveis
// ======================================================================
public record TemplateMessageComponents(
List<MessageTopLevelComponent> components,
List<FileUpload> implicitFileUploads,
Set<String> placeholdersFoundInTemplate
) {
public TemplateMessageComponents {
Objects.requireNonNull(components, "components");
Objects.requireNonNull(implicitFileUploads, "implicitFileUploads");
Objects.requireNonNull(placeholdersFoundInTemplate, "placeholdersFoundInTemplate");
}
}
public record RenderedMessageComponents(
List<MessageTopLevelComponent> components,
PlaceholderReport placeholderReport
) {
public RenderedMessageComponents {
Objects.requireNonNull(components, "components");
Objects.requireNonNull(placeholderReport, "placeholderReport");
}
}
public record PlaceholderReport(
Set<String> placeholdersFoundInTemplate,
Set<String> placeholdersProvided,
Set<String> placeholdersMissingValues,
Set<String> placeholdersUnusedValues
) {
public static PlaceholderReport from(Set<String> placeholdersFoundInTemplate, Map<String, String> placeholderValues) {
Objects.requireNonNull(placeholdersFoundInTemplate, "placeholdersFoundInTemplate");
Objects.requireNonNull(placeholderValues, "placeholderValues");
Set<String> provided = new LinkedHashSet<>(placeholderValues.keySet());
Set<String> missing = new LinkedHashSet<>(placeholdersFoundInTemplate);
missing.removeAll(provided);
Set<String> unused = new LinkedHashSet<>(provided);
unused.removeAll(placeholdersFoundInTemplate);
return new PlaceholderReport(
Collections.unmodifiableSet(new LinkedHashSet<>(placeholdersFoundInTemplate)),
Collections.unmodifiableSet(provided),
Collections.unmodifiableSet(missing),
Collections.unmodifiableSet(unused)
);
}
}
// ======================================================================
// Abstrações SOLID (DIP/OCP)
// ======================================================================
/**
* Decide quais campos são texto e podem sofrer substituição.
* Evita substituir em campos indevidos e mantém o sistema estável.
*/
public interface TextFieldPolicy {
boolean isTextFieldName(String fieldName);
}
/**
* Engine de placeholders: encontra e substitui {variavel} em campos textuais.
*/
public interface PlaceholderEngine {
Set<String> findPlaceholders(DataArray componentsDataArray, TextFieldPolicy textFieldPolicy);
DataArray applySubstitutions(DataArray componentsDataArray,
Map<String, String> placeholderValues,
TextFieldPolicy textFieldPolicy);
}
// ======================================================================
// Implementações padrão
// ======================================================================
/**
* Política padrão: whitelist de chaves textuais comuns em componentes V2.
*/
public static final class DefaultTextFieldPolicy implements TextFieldPolicy {
private final Set<String> allowedTextFields;
public DefaultTextFieldPolicy() {
this.allowedTextFields = Set.of(
"content", // TextDisplay
"label", // Button, etc.
"placeholder", // selects/inputs (se você usar)
"title", // alguns itens
"description" // MediaGalleryItem etc.
);
}
@Override
public boolean isTextFieldName(String fieldName) {
return allowedTextFields.contains(fieldName);
}
}
/**
* Placeholders no formato {nome}.
* - Não explode se faltar valor: mantém "{nome}" no texto
* - Permite relatório de placeholders faltantes
*/
public static final class CurlyBracesPlaceholderEngine implements PlaceholderEngine {
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{([a-zA-Z0-9_\\-]+)\\}");
@Override
public Set<String> findPlaceholders(DataArray componentsDataArray, TextFieldPolicy textFieldPolicy) {
Objects.requireNonNull(componentsDataArray, "componentsDataArray");
Objects.requireNonNull(textFieldPolicy, "textFieldPolicy");
Set<String> placeholders = new LinkedHashSet<>();
scanNodeForPlaceholders(componentsDataArray, textFieldPolicy, placeholders);
return Collections.unmodifiableSet(placeholders);
}
@Override
public DataArray applySubstitutions(DataArray componentsDataArray,
Map<String, String> placeholderValues,
TextFieldPolicy textFieldPolicy) {
Objects.requireNonNull(componentsDataArray, "componentsDataArray");
Objects.requireNonNull(placeholderValues, "placeholderValues");
Objects.requireNonNull(textFieldPolicy, "textFieldPolicy");
DataArray deepCopy = deepCopyArray(componentsDataArray);
substituteNode(deepCopy, placeholderValues, textFieldPolicy);
return deepCopy;
}
// ------------------------------
// Scan
// ------------------------------
private void scanNodeForPlaceholders(Object node,
TextFieldPolicy textFieldPolicy,
Set<String> placeholders) {
if (node instanceof DataArray array) {
for (int i = 0; i < array.length(); i++) {
scanNodeForPlaceholders(array.get(i), textFieldPolicy, placeholders);
}
return;
}
if (node instanceof DataObject object) {
for (String key : object.keys()) {
Object value = object.get(key);
if (value instanceof String text && textFieldPolicy.isTextFieldName(key)) {
collectPlaceholders(text, placeholders);
} else {
scanNodeForPlaceholders(value, textFieldPolicy, placeholders);
}
}
}
}
private void collectPlaceholders(String text, Set<String> placeholders) {
Matcher matcher = PLACEHOLDER_PATTERN.matcher(text);
while (matcher.find()) {
placeholders.add(matcher.group(1));
}
}
// ------------------------------
// Substitute
// ------------------------------
private void substituteNode(Object node,
Map<String, String> placeholderValues,
TextFieldPolicy textFieldPolicy) {
if (node instanceof DataArray array) {
for (int i = 0; i < array.length(); i++) {
Object element = array.get(i);
substituteNode(element, placeholderValues, textFieldPolicy);
}
return;
}
if (node instanceof DataObject object) {
for (String key : object.keys()) {
Object value = object.get(key);
if (value instanceof String text && textFieldPolicy.isTextFieldName(key)) {
String replaced = replacePlaceholders(text, placeholderValues);
object.put(key, replaced);
continue;
}
substituteNode(value, placeholderValues, textFieldPolicy);
}
}
}
private String replacePlaceholders(String template, Map<String, String> placeholderValues) {
Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);
StringBuilder result = new StringBuilder(template.length());
int lastIndex = 0;
while (matcher.find()) {
result.append(template, lastIndex, matcher.start());
String key = matcher.group(1);
String replacement = placeholderValues.get(key);
// Se não houver valor, mantém o placeholder intacto
result.append(replacement != null ? replacement : matcher.group(0));
lastIndex = matcher.end();
}
result.append(template, lastIndex, template.length());
return result.toString();
}
// ------------------------------
// Deep copy (DataObject/DataArray)
// ------------------------------
private DataArray deepCopyArray(DataArray original) {
DataArray copy = DataArray.empty();
for (int i = 0; i < original.length(); i++) {
copy.add(deepCopyNode(original.get(i)));
}
return copy;
}
private Object deepCopyNode(Object value) {
if (value instanceof DataObject object) {
DataObject copy = DataObject.empty();
for (String key : object.keys()) {
copy.put(key, deepCopyNode(object.get(key)));
}
return copy;
}
if (value instanceof DataArray array) {
return deepCopyArray(array);
}
// String/Number/Boolean/null
return value;
}
}
// ======================================================================
// Exemplo de uso
// ======================================================================
public static void exampleUsage() {
String jsonTemplate = """
[
{
"type": 1,
"components": [
{"type": 2, "style": 1, "custom_id": "button", "label": "Click me, {user}!"}
]
},
{
"type": 9,
"components": [
{"type": 10, "content": "Olá {user}, bem-vindo!"}
],
"accessory": {
"type": 11,
"media": {"url": "attachment://logo.png"},
"spoiler": false
}
}
]
""";
PlaceholderEngine placeholderEngine = new CurlyBracesPlaceholderEngine();
TextFieldPolicy textFieldPolicy = new DefaultTextFieldPolicy();
JdaMessageTemplateProcessor processor = new JdaMessageTemplateProcessor(placeholderEngine, textFieldPolicy);
// Se você for enviar e usar "attachment://logo.png", você precisa anexar um FileUpload com nome "logo.png".
// Se for só “montar classes”, pode deixar vazio.
List<FileUpload> implicitUploads = List.of();
TemplateMessageComponents template = processor.deserializeTemplate(jsonTemplate, implicitUploads);
Map<String, String> values = Map.of("user", "Jhonatas");
RenderedMessageComponents rendered = processor.renderTemplate(template, values);
System.out.println("Placeholders no template: " + rendered.placeholderReport().placeholdersFoundInTemplate());
System.out.println("Missing: " + rendered.placeholderReport().placeholdersMissingValues());
System.out.println("Unused: " + rendered.placeholderReport().placeholdersUnusedValues());
String finalJson = processor.serializeToJson(rendered.components());
System.out.println(finalJson);
}
}
Editor is loading...
Leave a Comment