Compare commits
9 Commits
33f355e6ea
...
dev
Author | SHA1 | Date | |
---|---|---|---|
a3c5eb62ac | |||
996f854ff7
|
|||
d2eec8b07c
|
|||
caa2ee7089
|
|||
2e5979e6e4 | |||
ab1eb74e85
|
|||
a5737b9eaa
|
|||
faca21724c
|
|||
4c5e28b679
|
@@ -3,7 +3,7 @@
|
||||
|
||||
[versions]
|
||||
junit-jupiter = "5.10.2"
|
||||
log4j = "2.24.1"
|
||||
slf4j = "2.0.17"
|
||||
javacord = "3.8.0"
|
||||
discord4j = "3.2.7"
|
||||
geantyref = "2.0.0"
|
||||
@@ -11,7 +11,7 @@ mockito = "5.15.2"
|
||||
|
||||
[libraries]
|
||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
|
||||
log4j = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j"}
|
||||
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j"}
|
||||
javacord = { module = "org.javacord:javacord", version.ref = "javacord"}
|
||||
discord4j = { module = "com.discord4j:discord4j-core", version.ref = "discord4j"}
|
||||
geantyref = { module = "io.leangen.geantyref:geantyref", version.ref = "geantyref"}
|
||||
|
@@ -20,7 +20,7 @@ dependencies {
|
||||
testImplementation(libs.junit.jupiter)
|
||||
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
implementation(libs.log4j)
|
||||
implementation(libs.slf4j)
|
||||
implementation(libs.geantyref)
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.tomatentum.marinara;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
|
||||
import net.tomatentum.marinara.registry.InteractionRegistry;
|
||||
|
@@ -5,7 +5,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.util.LoggerUtil;
|
||||
import net.tomatentum.marinara.util.ReflectionUtil;
|
||||
@@ -27,7 +27,7 @@ public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) {
|
||||
logger.debug("Pre Check {} {} with context {}", check.getClass().getName(), result ? "succeeded" : "failed", context.toString());
|
||||
return result;
|
||||
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
|
||||
logger.fatal(e);
|
||||
logger.error("Failed executing pre-check", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) {
|
||||
logger.debug("Executing post check {} with context {}", check.getClass().getName(), context.toString());
|
||||
method.invoke(check, context, annotation);
|
||||
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
|
||||
logger.fatal(e);
|
||||
logger.error("Failed executing post-check", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,8 +4,9 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.CommandChoices;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice;
|
||||
@@ -18,10 +19,11 @@ import net.tomatentum.marinara.util.LoggerUtil;
|
||||
public class SlashCommandDefinition {
|
||||
|
||||
public static SlashCommandOptionChoice[] getActualChoices(SlashCommandOption option) {
|
||||
SlashCommandOptionChoice[] choices = option.choices();
|
||||
if (choices.length <= 0 && !option.choiceEnum().equals(PlaceHolderEnum.class))
|
||||
choices = EnumChoices.of(option.choiceEnum()).choices();
|
||||
return choices;
|
||||
CommandChoices choices = option.choices();
|
||||
SlashCommandOptionChoice[] actualChoices = choices.value();
|
||||
if (choices.value().length <= 0 && !choices.cenum().equals(PlaceHolderEnum.class))
|
||||
actualChoices = EnumChoices.of(choices.cenum()).choices();
|
||||
return actualChoices;
|
||||
}
|
||||
|
||||
private Set<InteractionIdentifier> entries;
|
||||
|
@@ -0,0 +1,8 @@
|
||||
package net.tomatentum.marinara.interaction.commands.annotation;
|
||||
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum;
|
||||
|
||||
public @interface CommandChoices {
|
||||
public SlashCommandOptionChoice[] value() default {};
|
||||
public Class<? extends Enum<?>> cenum() default PlaceHolderEnum.class;
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package net.tomatentum.marinara.interaction.commands.annotation;
|
||||
|
||||
public @interface Range {
|
||||
public double min() default Double.MIN_VALUE;
|
||||
public double max() default Double.MAX_VALUE;
|
||||
}
|
@@ -15,8 +15,8 @@ public @interface SlashCommandOption {
|
||||
public SlashCommandOptionType type() default SlashCommandOptionType.STRING;
|
||||
public boolean required() default false;
|
||||
public boolean autocomplete() default false;
|
||||
public SlashCommandOptionChoice[] choices() default {};
|
||||
public Class<? extends Enum<?>> choiceEnum() default PlaceHolderEnum.class;
|
||||
public Range range() default @Range;
|
||||
public CommandChoices choices() default @CommandChoices;
|
||||
|
||||
public static enum PlaceHolderEnum {
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.Marinara;
|
||||
import net.tomatentum.marinara.checks.AppliedCheck;
|
||||
@@ -69,7 +69,7 @@ public abstract class InteractionMethod {
|
||||
try {
|
||||
method.invoke(handler, getParameters(context));
|
||||
}catch (IllegalAccessException | InvocationTargetException ex) {
|
||||
logger.fatal(ex);
|
||||
logger.error("InteractionMethod failed to run", ex);
|
||||
}
|
||||
|
||||
this.appliedChecks.forEach(x -> x.post(context));
|
||||
|
@@ -3,7 +3,7 @@ package net.tomatentum.marinara.parser;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.interaction.annotation.Button;
|
||||
import net.tomatentum.marinara.util.LoggerUtil;
|
||||
|
@@ -6,7 +6,7 @@ import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.checks.AppliedCheck;
|
||||
import net.tomatentum.marinara.checks.InteractionCheck;
|
||||
|
@@ -3,7 +3,7 @@ package net.tomatentum.marinara.parser;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.interaction.InteractionType;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
|
||||
@@ -59,7 +59,7 @@ public class SlashCommandParser implements AnnotationParser {
|
||||
.build(isAutoComplete);
|
||||
}
|
||||
|
||||
logger.trace("Parsed using SlashCommandParser for method {} with the result:\n{}", ReflectionUtil.getFullMethodName(method), lastIdentifier.toString());
|
||||
logger.trace("Parsed using SlashCommandParser for method {} with the result: {}", ReflectionUtil.getFullMethodName(method), lastIdentifier.toString());
|
||||
consumer.accept((SlashCommandIdentifier) lastIdentifier);
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import io.leangen.geantyref.GenericTypeReflector;
|
||||
import net.tomatentum.marinara.checks.InteractionCheck;
|
||||
|
@@ -3,7 +3,7 @@ package net.tomatentum.marinara.registry;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.interaction.InteractionType;
|
||||
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
|
||||
|
@@ -7,7 +7,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.Marinara;
|
||||
import net.tomatentum.marinara.interaction.InteractionHandler;
|
||||
@@ -66,7 +66,6 @@ public class InteractionRegistry {
|
||||
.toArray(SlashCommandDefinition[]::new);
|
||||
|
||||
marinara.getWrapper().getRegisterer().register(defs);
|
||||
logger.info("Registered all SlashCommands");
|
||||
}
|
||||
|
||||
public void handle(Object context) {
|
||||
|
@@ -1,21 +1,14 @@
|
||||
package net.tomatentum.marinara.util;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.simple.SimpleLogger;
|
||||
import org.apache.logging.log4j.util.PropertiesUtil;
|
||||
import org.apache.logging.log4j.util.ProviderUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.helpers.NOPLoggerFactory;
|
||||
|
||||
public class LoggerUtil {
|
||||
public static Logger getLogger(String name) {
|
||||
if (ProviderUtil.hasProviders()) {
|
||||
return LogManager.getLogger(name);
|
||||
}else
|
||||
return new SimpleLogger(name, Level.DEBUG, true, false, true, true, "yyyy-MM-dd HH:mm:ss.SSSZ", null,
|
||||
new PropertiesUtil(new Properties()), System.out);
|
||||
if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory)
|
||||
return new SimpleLogger(name);
|
||||
return LoggerFactory.getLogger(name);
|
||||
}
|
||||
|
||||
public static Logger getLogger(Class<?> clazz) {
|
||||
|
@@ -102,6 +102,6 @@ public final class ReflectionUtil {
|
||||
}
|
||||
|
||||
public static String getFullMethodName(Method method) {
|
||||
return method.getClass().getName() + "." + method.getName();
|
||||
return method.getDeclaringClass().getName() + "." + method.getName();
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,55 @@
|
||||
package net.tomatentum.marinara.util;
|
||||
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.event.Level;
|
||||
import org.slf4j.helpers.LegacyAbstractLogger;
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
|
||||
public class SimpleLogger extends LegacyAbstractLogger {
|
||||
|
||||
private String name;
|
||||
|
||||
public SimpleLogger(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTraceEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInfoEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWarnEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isErrorEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFullyQualifiedCallerName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments,
|
||||
Throwable throwable) {
|
||||
String formatted = MessageFormatter.basicArrayFormat(messagePattern, arguments);
|
||||
System.out.println("[%s] %s => %s".formatted(level, this.name, formatted));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -4,12 +4,15 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice;
|
||||
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
|
||||
import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier;
|
||||
import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier;
|
||||
import net.tomatentum.marinara.util.LoggerUtil;
|
||||
|
||||
public class CommandConverter<A extends Object, O extends Object, C extends Object> {
|
||||
|
||||
@@ -17,6 +20,8 @@ public class CommandConverter<A extends Object, O extends Object, C extends Obje
|
||||
return new CommandConverter<>(spec);
|
||||
}
|
||||
|
||||
private Logger logger = LoggerUtil.getLogger(getClass());
|
||||
|
||||
private Spec<A, O, C> spec;
|
||||
|
||||
CommandConverter(Spec<A, O, C> spec) {
|
||||
@@ -24,6 +29,7 @@ public class CommandConverter<A extends Object, O extends Object, C extends Obje
|
||||
}
|
||||
|
||||
public A convert(SlashCommandDefinition def) {
|
||||
logger.debug("Converting command {}", def);
|
||||
List<O> options = new ArrayList<>();
|
||||
if (!def.isRootCommand()) {
|
||||
Arrays.stream(def.getSubCommands()).map(this::convertSubCommand).forEach(options::add);
|
||||
@@ -35,17 +41,20 @@ public class CommandConverter<A extends Object, O extends Object, C extends Obje
|
||||
}
|
||||
|
||||
private O convertSubCommandGroup(SlashCommandDefinition def, InteractionIdentifier identifier) {
|
||||
logger.debug("Converting subCommandGroup {} of {}", identifier, def);
|
||||
SlashCommandIdentifier[] subCommands = def.getSubCommands(identifier.name());
|
||||
List<O> convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommand).toList();
|
||||
return spec.convertSubCommandGroup(identifier, convertedSubCommands);
|
||||
}
|
||||
|
||||
private O convertSubCommand(SlashCommandIdentifier identifier) {
|
||||
logger.debug("Converting subCommand {}", identifier);
|
||||
List<O> options = Arrays.stream(identifier.options()).map(this::convertOption).toList();
|
||||
return spec.convertSubCommand(identifier, options);
|
||||
}
|
||||
|
||||
private O convertOption(SlashCommandOption option) {
|
||||
logger.debug("Converting option {}", option);
|
||||
List<C> choices = Arrays.stream(SlashCommandDefinition.getActualChoices(option)).map(spec::convertChoice).toList();
|
||||
return spec.convertOption(option, choices);
|
||||
}
|
||||
|
@@ -4,7 +4,10 @@ import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
|
||||
import net.tomatentum.marinara.util.LoggerUtil;
|
||||
import net.tomatentum.marinara.util.ObjectAggregator;
|
||||
|
||||
public class CommandRegisterer<A extends Object> {
|
||||
@@ -13,6 +16,8 @@ public class CommandRegisterer<A extends Object> {
|
||||
return new CommandRegisterer<A>(strategy, converter);
|
||||
}
|
||||
|
||||
private Logger logger = LoggerUtil.getLogger(getClass());
|
||||
|
||||
private Strategy<A> strategy;
|
||||
private CommandConverter<A, ?, ?> converter;
|
||||
|
||||
@@ -36,6 +41,7 @@ public class CommandRegisterer<A extends Object> {
|
||||
|
||||
serverCommands.forEach(strategy::registerServer);
|
||||
strategy.registerGlobal(globalCommands);
|
||||
logger.info("Registered all SlashCommands");
|
||||
}
|
||||
|
||||
public interface Strategy<A extends Object> {
|
||||
|
@@ -7,7 +7,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import io.leangen.geantyref.GenericTypeReflector;
|
||||
import net.tomatentum.marinara.interaction.ident.InteractionIdentifier;
|
||||
|
@@ -19,11 +19,12 @@ dependencies {
|
||||
// Use JUnit Jupiter for testing.
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(libs.mockito)
|
||||
testImplementation(libs.discord4j)
|
||||
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
implementation(libs.log4j)
|
||||
implementation(libs.slf4j)
|
||||
implementation(libs.discord4j) {
|
||||
// exclude(module="discord4j-voice")
|
||||
exclude(module="discord4j-voice")
|
||||
}
|
||||
implementation(libs.geantyref)
|
||||
implementation(project(":lib"))
|
||||
|
@@ -56,6 +56,10 @@ public class Discord4JConverterSpec implements CommandConverter.Spec<Application
|
||||
.description(option.description())
|
||||
.required(option.required())
|
||||
.autocomplete(option.autocomplete())
|
||||
.minLength(Double.valueOf(option.range().min()).intValue())
|
||||
.minValue(option.range().min())
|
||||
.maxLength(Double.valueOf(option.range().max()).intValue())
|
||||
.maxValue(option.range().max())
|
||||
.choices(choices)
|
||||
.build();
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ package net.tomatentum.marinara.wrapper.discord4j;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import discord4j.core.GatewayDiscordClient;
|
||||
import discord4j.core.event.domain.interaction.InteractionCreateEvent;
|
||||
|
@@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
|
||||
import net.tomatentum.marinara.interaction.InteractionHandler;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.CommandChoices;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
|
||||
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType;
|
||||
@@ -20,7 +21,7 @@ public class TestCommand implements InteractionHandler {
|
||||
name = "foo",
|
||||
description = "foo bar is very fooby",
|
||||
type = SlashCommandOptionType.STRING,
|
||||
choiceEnum = TestChoiceEnum.class
|
||||
choices = @CommandChoices(cenum = TestChoiceEnum.class)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
@@ -21,7 +21,7 @@ dependencies {
|
||||
testImplementation(libs.mockito)
|
||||
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
implementation(libs.log4j)
|
||||
implementation(libs.slf4j)
|
||||
implementation(libs.javacord)
|
||||
implementation(libs.geantyref)
|
||||
implementation(project(":lib"))
|
||||
|
@@ -51,6 +51,10 @@ public class JavacordConverterSpec implements CommandConverter.Spec<SlashCommand
|
||||
.setDescription(option.description())
|
||||
.setRequired(option.required())
|
||||
.setAutocompletable(option.autocomplete())
|
||||
.setMinLength(Double.valueOf(option.range().min()).longValue())
|
||||
.setDecimalMinValue(option.range().min())
|
||||
.setMaxLength(Double.valueOf(option.range().max()).longValue())
|
||||
.setDecimalMaxValue(option.range().max())
|
||||
.setChoices(choices)
|
||||
.build();
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package net.tomatentum.marinara.wrapper.javacord;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.javacord.api.DiscordApi;
|
||||
import org.javacord.api.interaction.SlashCommandBuilder;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import net.tomatentum.marinara.wrapper.CommandConverter;
|
||||
import net.tomatentum.marinara.wrapper.CommandRegisterer;
|
||||
|
@@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import org.javacord.api.interaction.SlashCommandInteraction;
|
||||
|
||||
import net.tomatentum.marinara.interaction.InteractionHandler;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.CommandChoices;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
|
||||
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
|
||||
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType;
|
||||
@@ -21,7 +22,7 @@ public class TestCommand implements InteractionHandler {
|
||||
name = "foo",
|
||||
description = "foo bar is very fooby",
|
||||
type = SlashCommandOptionType.STRING,
|
||||
choiceEnum = TestChoiceEnum.class
|
||||
choices = @CommandChoices(cenum = TestChoiceEnum.class)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
Reference in New Issue
Block a user