mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-04-14 03:45:57 +00:00
added closesilently command for modmail
largely refactored modmail future handling and error handling in case: no permission, unable to contact user fixed help command template
This commit is contained in:
@@ -43,7 +43,7 @@ public class Close extends AbstractConditionableCommand {
|
|||||||
List<Object> parameters = commandContext.getParameters().getParameters();
|
List<Object> parameters = commandContext.getParameters().getParameters();
|
||||||
String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate("modmail_close_default_note", new Object());
|
String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate("modmail_close_default_note", new Object());
|
||||||
ModMailThread thread = modMailThreadManagementService.getByChannel(commandContext.getUserInitiatedContext().getChannel());
|
ModMailThread thread = modMailThreadManagementService.getByChannel(commandContext.getUserInitiatedContext().getChannel());
|
||||||
modMailThreadService.closeModMailThread(thread, commandContext.getChannel(), note);
|
modMailThreadService.closeModMailThread(thread, commandContext.getChannel(), note, true);
|
||||||
return CommandResult.fromSuccess();
|
return CommandResult.fromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,21 @@ package dev.sheldan.abstracto.modmail.commands;
|
|||||||
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
|
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
|
||||||
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
|
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
|
||||||
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
|
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
|
||||||
|
import dev.sheldan.abstracto.core.command.config.HelpInfo;
|
||||||
|
import dev.sheldan.abstracto.core.command.config.Parameter;
|
||||||
import dev.sheldan.abstracto.core.command.execution.CommandContext;
|
import dev.sheldan.abstracto.core.command.execution.CommandContext;
|
||||||
import dev.sheldan.abstracto.core.command.execution.CommandResult;
|
import dev.sheldan.abstracto.core.command.execution.CommandResult;
|
||||||
import dev.sheldan.abstracto.core.config.FeatureEnum;
|
import dev.sheldan.abstracto.core.config.FeatureEnum;
|
||||||
import dev.sheldan.abstracto.modmail.commands.condition.RequiresModMailCondition;
|
import dev.sheldan.abstracto.modmail.commands.condition.RequiresModMailCondition;
|
||||||
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
|
import dev.sheldan.abstracto.modmail.config.ModMailFeatures;
|
||||||
|
import dev.sheldan.abstracto.modmail.models.database.ModMailThread;
|
||||||
|
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
|
||||||
|
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
|
||||||
|
import dev.sheldan.abstracto.templating.service.TemplateService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -19,14 +26,36 @@ public class CloseSilently extends AbstractConditionableCommand {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RequiresModMailCondition requiresModMailCondition;
|
private RequiresModMailCondition requiresModMailCondition;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ModMailThreadManagementService modMailThreadManagementService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ModMailThreadService modMailThreadService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TemplateService templateService;
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandContext commandContext) {
|
public CommandResult execute(CommandContext commandContext) {
|
||||||
return null;
|
List<Object> parameters = commandContext.getParameters().getParameters();
|
||||||
|
String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate("modmail_close_default_note", new Object());
|
||||||
|
ModMailThread thread = modMailThreadManagementService.getByChannel(commandContext.getUserInitiatedContext().getChannel());
|
||||||
|
modMailThreadService.closeModMailThread(thread, commandContext.getChannel(), note, false);
|
||||||
|
return CommandResult.fromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandConfiguration getConfiguration() {
|
public CommandConfiguration getConfiguration() {
|
||||||
return null;
|
Parameter note = Parameter.builder().name("note").type(String.class).remainder(true).optional(true).build();
|
||||||
|
List<Parameter> parameters = Arrays.asList(note);
|
||||||
|
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
|
||||||
|
return CommandConfiguration.builder()
|
||||||
|
.name("closeSilently")
|
||||||
|
.module(ModMailModuleInterface.MODMAIL)
|
||||||
|
.parameters(parameters)
|
||||||
|
.help(helpInfo)
|
||||||
|
.templated(true)
|
||||||
|
.causesReaction(true)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import dev.sheldan.abstracto.templating.model.MessageToSend;
|
|||||||
import dev.sheldan.abstracto.templating.service.TemplateService;
|
import dev.sheldan.abstracto.templating.service.TemplateService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.dv8tion.jda.api.entities.*;
|
import net.dv8tion.jda.api.entities.*;
|
||||||
|
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -250,7 +251,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendModMailFailure(String template, AUserInAServer aUserInAServer, ModMailThread modMailThread, MessageChannel channel, Throwable throwable) {
|
@Transactional
|
||||||
|
public void sendModMailFailure(String template, AUserInAServer aUserInAServer, Long modMailTreadId, MessageChannel channel, Throwable throwable) {
|
||||||
|
ModMailThread modMailThread = modMailThreadManagementService.getById(modMailTreadId);
|
||||||
try {
|
try {
|
||||||
FullUser fullUser = FullUser
|
FullUser fullUser = FullUser
|
||||||
.builder()
|
.builder()
|
||||||
@@ -270,38 +273,81 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeModMailThread(ModMailThread modMailThread, MessageChannel feedBack, String note) {
|
public void closeModMailThread(ModMailThread modMailThread, MessageChannel feedBack, String note, Boolean notifyUser) {
|
||||||
List<ModMailMessage> modMailMessages = modMailThread.getMessages();
|
List<ModMailMessage> modMailMessages = modMailThread.getMessages();
|
||||||
|
|
||||||
List<CompletableFuture<Message>> messages = modMailMessageService.loadModMailMessages(modMailMessages);
|
List<CompletableFuture<Message>> messages = modMailMessageService.loadModMailMessages(modMailMessages);
|
||||||
|
Long modMailThreadId = modMailThread.getId();
|
||||||
for (int i = 0; i < messages.size(); i++) {
|
for (int i = 0; i < messages.size(); i++) {
|
||||||
CompletableFuture<Message> messageCompletableFuture = messages.get(i);
|
CompletableFuture<Message> messageCompletableFuture = messages.get(i);
|
||||||
Long messageId = modMailMessages.get(i).getMessageId();
|
Long messageId = modMailMessages.get(i).getMessageId();
|
||||||
messageCompletableFuture.exceptionally(throwable -> {
|
messageCompletableFuture.exceptionally(throwable -> {
|
||||||
log.warn("Failed to load message {} in mod mail thread {}", messageId, modMailThread.getId());
|
log.warn("Failed to load message {} in mod mail thread {}", messageId, modMailThreadId);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CompletableFuture.allOf(messages.toArray(new CompletableFuture[0])).thenAccept(aVoid -> {
|
CompletableFuture.allOf(messages.toArray(new CompletableFuture[0])).whenComplete((avoid, throwable) -> {
|
||||||
self.logModMailThread(modMailThread, messages, note, modMailMessages);
|
if(throwable != null) {
|
||||||
}).exceptionally(throwable -> {
|
log.warn("Failed to load some mod mail messages for mod mail thread {}. Still trying to post the ones we got.", modMailThreadId, throwable);
|
||||||
log.warn("Failed to load some mod mail messages for mod mail thread {}. Still trying to post the ones we got.", modMailThread.getId(), throwable);
|
}
|
||||||
self.logModMailThread(modMailThread, messages, note, modMailMessages);
|
self.logModMailThread(modMailThreadId, messages, note).thenRun(() -> {
|
||||||
return null;
|
self.afterSuccessfulLog(modMailThreadId, feedBack, notifyUser);
|
||||||
|
}).exceptionally(innerThrowable -> {
|
||||||
|
sendModMailFailure("modmail_exception_generic", modMailThread.getUser(), modMailThreadId, feedBack, innerThrowable);
|
||||||
|
log.error("Failed to log messages for mod mail thread {}.", modMailThreadId, innerThrowable);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void logModMailThread(ModMailThread modMailThread, List<CompletableFuture<Message>> messages, String note, List<ModMailMessage> modMailMessages) {
|
public void afterSuccessfulLog(Long modMailThreadId, MessageChannel feedBack, Boolean notifyUser) {
|
||||||
|
ModMailThread modMailThread = modMailThreadManagementService.getById(modMailThreadId);
|
||||||
|
User user = botService.getMemberInServer(modMailThread.getUser()).getUser();
|
||||||
|
user.openPrivateChannel().queue(privateChannel -> {
|
||||||
|
try {
|
||||||
|
List<CompletableFuture<Message>> messageFutures = new ArrayList<>();
|
||||||
|
if(notifyUser){
|
||||||
|
messageFutures.addAll(channelService.sendTemplateInChannel("modmail_closing_user_message", new Object(), privateChannel));
|
||||||
|
} else {
|
||||||
|
messageFutures.add(CompletableFuture.completedFuture(null));
|
||||||
|
}
|
||||||
|
CompletableFuture.allOf(messageFutures.toArray(new CompletableFuture[0])).whenComplete((result, throwable) -> {
|
||||||
|
if(throwable != null) {
|
||||||
|
log.warn("Failed to send closing message to user {} after closing mod mail thread {}", user.getIdLong(), modMailThread.getId(), throwable);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
channelService.deleteTextChannel(modMailThread.getChannel()).thenRun(() -> {
|
||||||
|
self.closeModMailThreadInDb(modMailThreadId);
|
||||||
|
}).exceptionally(throwable2 -> {
|
||||||
|
log.error("Failed to delete text channel containing mod mail thread {}", modMailThread.getId(), throwable2);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (InsufficientPermissionException ex){
|
||||||
|
log.error("Failed to delete text channel containing mod mail thread {}", modMailThread.getId(), ex);
|
||||||
|
sendModMailFailure("modmail_exception_cannot_delete_channel", modMailThread.getUser(), modMailThreadId, feedBack, ex);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Failed to delete text channel containing mod mail thread {}", modMailThread.getId(), ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to render closing user message", e);
|
||||||
|
sendModMailFailure("modmail_exception_generic", modMailThread.getUser(), modMailThreadId, feedBack, e);
|
||||||
|
}
|
||||||
|
}, throwable -> log.error("Failed to load private channel with user {}", user.getIdLong(), throwable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public CompletableFuture<Void> logModMailThread(Long modMailThreadId, List<CompletableFuture<Message>> messages, String note) {
|
||||||
|
ModMailThread modMailThread = modMailThreadManagementService.getById(modMailThreadId);
|
||||||
List<ModMailLoggedMessage> loggedMessages = new ArrayList<>();
|
List<ModMailLoggedMessage> loggedMessages = new ArrayList<>();
|
||||||
messages.forEach(future -> {
|
messages.forEach(future -> {
|
||||||
try {
|
try {
|
||||||
if(!future.isCompletedExceptionally()) {
|
if(!future.isCompletedExceptionally()) {
|
||||||
Message loadedMessage = future.get();
|
Message loadedMessage = future.get();
|
||||||
if(loadedMessage != null) {
|
if(loadedMessage != null) {
|
||||||
ModMailMessage modmailMessage = modMailMessages
|
ModMailMessage modmailMessage = modMailThread.getMessages()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(modMailMessage -> modMailMessage.getMessageId().equals(loadedMessage.getIdLong()))
|
.filter(modMailMessage -> modMailMessage.getMessageId().equals(loadedMessage.getIdLong()))
|
||||||
.findFirst().get();
|
.findFirst().get();
|
||||||
@@ -330,19 +376,16 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
|
|||||||
List<CompletableFuture<Message>> closeHeaderFutures = postTargetService.sendEmbedInPostTarget(messageToSend, MODMAIL_LOG_POSTTARGET, modMailThread.getServer().getId());
|
List<CompletableFuture<Message>> closeHeaderFutures = postTargetService.sendEmbedInPostTarget(messageToSend, MODMAIL_LOG_POSTTARGET, modMailThread.getServer().getId());
|
||||||
completableFutures.addAll(closeHeaderFutures);
|
completableFutures.addAll(closeHeaderFutures);
|
||||||
completableFutures.addAll(self.sendMessagesToPostTarget(modMailThread, loggedMessages));
|
completableFutures.addAll(self.sendMessagesToPostTarget(modMailThread, loggedMessages));
|
||||||
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).thenAccept(aVoid -> {
|
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]));
|
||||||
channelService.deleteTextChannel(modMailThread.getChannel()).thenAccept(o -> {
|
|
||||||
self.closeModMailThreadInDb(modMailThread);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void closeModMailThreadInDb(ModMailThread modMailThread) {
|
public void closeModMailThreadInDb(Long modMailThreadId) {
|
||||||
|
ModMailThread modMailThread = modMailThreadManagementService.getById(modMailThreadId);
|
||||||
|
log.info("Setting thread {} to closed.", modMailThread.getId());
|
||||||
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.CLOSED);
|
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public List<CompletableFuture<Message>> sendMessagesToPostTarget(ModMailThread modMailThread, List<ModMailLoggedMessage> loadedMessages) {
|
public List<CompletableFuture<Message>> sendMessagesToPostTarget(ModMailThread modMailThread, List<ModMailLoggedMessage> loadedMessages) {
|
||||||
List<CompletableFuture<Message>> messageFutures = new ArrayList<>();
|
List<CompletableFuture<Message>> messageFutures = new ArrayList<>();
|
||||||
loadedMessages.forEach(message -> {
|
loadedMessages.forEach(message -> {
|
||||||
@@ -391,7 +434,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
|
|||||||
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.MOD_REPLIED);
|
modMailThreadManagementService.setModMailThreadState(modMailThread, ModMailThreadState.MOD_REPLIED);
|
||||||
}).exceptionally(throwable -> {
|
}).exceptionally(throwable -> {
|
||||||
log.error("Failed to send message to user {}", modMailThread.getUser().getUserReference().getId());
|
log.error("Failed to send message to user {}", modMailThread.getUser().getUserReference().getId());
|
||||||
sendModMailFailure("modmail_exception_cannot_message_user", modMailThread.getUser(), modMailThread, feedBack, throwable);
|
sendModMailFailure("modmail_exception_cannot_message_user", modMailThread.getUser(), modMailThread.getId(), feedBack, throwable);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
|
|||||||
return getByChannel(channel);
|
return getByChannel(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModMailThread getById(Long modMailThreadId) {
|
||||||
|
return modMailThreadRepository.getOne(modMailThreadId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ModMailThread getByChannel(AChannel channel) {
|
public ModMailThread getByChannel(AChannel channel) {
|
||||||
return modMailThreadRepository.findByChannel(channel);
|
return modMailThreadRepository.findByChannel(channel);
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"author": {
|
||||||
|
"name": "${user.member.effectiveName}",
|
||||||
|
"avatar": "${user.member.user.effectiveAvatarUrl}"
|
||||||
|
},
|
||||||
|
"color" : {
|
||||||
|
"r": 200,
|
||||||
|
"g": 0,
|
||||||
|
"b": 255
|
||||||
|
},
|
||||||
|
"description": "<#include "close_failed_to_delete_text_channel">"
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"author": {
|
||||||
|
"name": "${user.member.effectiveName}",
|
||||||
|
"avatar": "${user.member.user.effectiveAvatarUrl}"
|
||||||
|
},
|
||||||
|
"color" : {
|
||||||
|
"r": 200,
|
||||||
|
"g": 0,
|
||||||
|
"b": 255
|
||||||
|
},
|
||||||
|
"description": "<#include "modmail_generic_error">"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"r": 200,
|
||||||
|
"g": 0,
|
||||||
|
"b": 255
|
||||||
|
},
|
||||||
|
"description": "<#include "modmail_closing_user_message_description">"
|
||||||
|
}
|
||||||
@@ -17,5 +17,5 @@ public interface ModMailThreadService {
|
|||||||
void createModMailPrompt(AUser user, MessageChannel messageChannel);
|
void createModMailPrompt(AUser user, MessageChannel messageChannel);
|
||||||
void relayMessageToModMailThread(ModMailThread modMailThread, Message message);
|
void relayMessageToModMailThread(ModMailThread modMailThread, Message message);
|
||||||
void relayMessageToDm(ModMailThread modMailThread, String text, Message message, Boolean anonymous, MessageChannel feedBack);
|
void relayMessageToDm(ModMailThread modMailThread, String text, Message message, Boolean anonymous, MessageChannel feedBack);
|
||||||
void closeModMailThread(ModMailThread modMailThread, MessageChannel feedBack, String note);
|
void closeModMailThread(ModMailThread modMailThread, MessageChannel feedBack, String note, Boolean notifyUser);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface ModMailThreadManagementService {
|
public interface ModMailThreadManagementService {
|
||||||
ModMailThread getByChannelId(Long channelId);
|
ModMailThread getByChannelId(Long channelId);
|
||||||
|
ModMailThread getById(Long modMailThreadId);
|
||||||
ModMailThread getByChannel(AChannel channel);
|
ModMailThread getByChannel(AChannel channel);
|
||||||
List<ModMailThread> getThreadByUserAndState(AUserInAServer userInAServer, ModMailThreadState state);
|
List<ModMailThread> getThreadByUserAndState(AUserInAServer userInAServer, ModMailThreadState state);
|
||||||
ModMailThread getOpenModmailThreadForUser(AUserInAServer userInAServer);
|
ModMailThread getOpenModmailThreadForUser(AUserInAServer userInAServer);
|
||||||
|
|||||||
@@ -21,9 +21,8 @@
|
|||||||
<#if command.aliases?? && command.aliases?size gt 0>
|
<#if command.aliases?? && command.aliases?size gt 0>
|
||||||
<#include "help_command_embed_command_aliases">: `${command.aliases?join("`, `")}`
|
<#include "help_command_embed_command_aliases">: `${command.aliases?join("`, `")}`
|
||||||
</#if>
|
</#if>
|
||||||
<#include "help_command_embed_command_description">:
|
|
||||||
<#if restricted?? && restricted>
|
<#if restricted?? && restricted>
|
||||||
<#include "help_command_embed_command_description"><#if allowedRoles??> <#list allowedRoles as allowedRole> ${allowedRole.asMention}<#sep><#include "help_command_embed_or"><#else><#include "help_command_embed_command_executable_by_nobody"></#list> </#if>
|
<#include "help_command_embed_command_executable_by">:<#if allowedRoles??> <#list allowedRoles as allowedRole> ${allowedRole.asMention}<#sep><#include "help_command_embed_or"><#else><#include "help_command_embed_command_executable_by_nobody"></#list> </#if>
|
||||||
<#if immuneRoles?? ><#include "help_command_embed_command_immune">: <#list immuneRoles as immuneRole> ${immuneRole.asMention}<#sep><#include "help_command_embed_or"><#else>None</#list> </#if>
|
<#if immuneRoles?? ><#include "help_command_embed_command_immune">: <#list immuneRoles as immuneRole> ${immuneRole.asMention}<#sep><#include "help_command_embed_or"><#else>None</#list> </#if>
|
||||||
<#else>
|
<#else>
|
||||||
<#include "help_command_embed_command_not_restricted">
|
<#include "help_command_embed_command_not_restricted">
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Failed to delete text channel containing mod mail thread. The bot does not have permissions to do so.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Mod mail thread has been closed. If you have any questions please do not hesitate to contact us again.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Generic mod mail failure. Check the logs.
|
||||||
Reference in New Issue
Block a user