Compare commits

..

2 Commits

Author SHA1 Message Date
Sheldan
fa22009007 [AB-xxx] adding initial support for components v2
fixing issue with buttons which only provide an emoji
adding logging in case updating a starboard post goes wrong
2025-07-13 19:44:40 +02:00
Sheldan
7b7fdf781a [AB-xxx] start of embeds v2 2025-05-30 11:07:44 +02:00
241 changed files with 800 additions and 3264 deletions

2
.env
View File

@@ -1,2 +1,2 @@
REGISTRY_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.6.22
VERSION=1.6.8

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -108,6 +108,6 @@ public class MassPingServiceBean implements MassPingService {
.memberDisplay(MemberDisplay.fromMember(member))
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(MASS_PING_MUTE_NOTIFICATION_TEMPLATE_KEY, model, member.getGuild().getIdLong());
return FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, AntiRaidPostTarget.MASS_PING_LOG, member.getGuild().getIdLong()));
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, AntiRaidPostTarget.MASS_PING_LOG, member.getGuild().getIdLong()));
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -74,7 +74,7 @@ public class CreateAssignableRolePlace extends AbstractConditionableCommand {
@Override
public CommandConfiguration getConfiguration() {
List<ParameterValidator> rolePlaceNameValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT.intValue()));
List<ParameterValidator> rolePlaceNameValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT));
Parameter rolePostName = Parameter
.builder()
.name(ASSIGNABLE_ROLE_PLACE_NAME_PARAMETER)
@@ -95,7 +95,7 @@ public class CreateAssignableRolePlace extends AbstractConditionableCommand {
.templated(true)
.optional(true)
.build();
List<ParameterValidator> rolePlaceDescriptionValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT.intValue()));
List<ParameterValidator> rolePlaceDescriptionValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT));
Parameter text = Parameter
.builder()
.name(ASSIGNABLE_ROLE_PLACE_TEXT_PARAMETER)

View File

@@ -31,6 +31,7 @@ import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.components.buttons.ButtonStyle;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -59,6 +60,9 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Autowired
private ChannelService channelService;
@Autowired
private GuildService guildService;
@Autowired
private EmoteService emoteService;
@@ -127,18 +131,9 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
String buttonId = componentService.generateComponentId();
String emoteMarkdown = emoji != null ? emoji.getFormatted() : null;
if (assignableRolePlace.getMessageId() != null) {
AssignablePostMessage model = prepareAssignablePostMessageModel(assignableRolePlace);
model.getRoles().add(AssignablePostRole
.builder()
.componentId(buttonId)
.emoteMarkDown(emoteMarkdown)
.description(description)
.build());
MessageToSend messageToSend = templateService.renderEmbedTemplate(ASSIGNABLE_ROLES_POST_TEMPLATE_KEY, model, assignableRolePlace.getServer().getId());
log.debug("Assignable role place {} has already message post with ID {} - updating.", assignableRolePlace.getId(), assignableRolePlace.getMessageId());
return messageService.editMessageInChannel(textChannel, messageToSend, assignableRolePlace.getMessageId()).thenAccept(unused -> {
self.persistAssignableRoleAddition(placeId, role, description, emoji, buttonId);
});
return componentService.addButtonToMessage(assignableRolePlace.getMessageId(), textChannel, buttonId, description, emoteMarkdown, ButtonStyle.SECONDARY)
.thenAccept(message -> self.persistAssignableRoleAddition(placeId, role, description, emoji, buttonId));
} else {
log.info("Assignable role place {} is not yet setup - only adding role to the database.", assignableRolePlace.getId());
self.persistAssignableRoleAddition(placeId, role, description, emoji, buttonId);
@@ -179,7 +174,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
return channelService.retrieveMessageInChannel(assignableRolePlace.getServer().getId(), assignableRolePlace.getChannel().getId(), assignableRolePlace.getMessageId())
.thenCompose(message -> {
log.debug("Updating message {} to remove component with ID {}.", message.getIdLong(), componentId);
return componentService.removeComponentById(message, componentId).thenAccept(message1 -> {});
return componentService.removeComponentWithId(message, componentId, true);
}
);
}
@@ -274,8 +269,8 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
log.info("Deactivating assignable role place {} in server {}", place.getId(), place.getServer().getId());
return channelService.retrieveMessageInChannel(place.getServer().getId(), place.getChannel().getId(), place.getMessageId())
.thenCompose(message ->
componentService.disableAllComponents(message)
).thenAccept(message -> {});
componentService.disableAllButtons(message)
);
}
@Override
@@ -288,8 +283,8 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
log.info("Activating assignable role place {} in server {}", place.getId(), place.getServer().getId());
return channelService.retrieveMessageInChannel(place.getServer().getId(), place.getChannel().getId(), place.getMessageId())
.thenCompose(message ->
componentService.enableAllComponents(message)
).thenAccept(message -> {});
componentService.enableAllButtons(message)
);
}
@Override

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assignable-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>dynamic-activity</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>dynamic-activity</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -74,6 +74,7 @@ public class TransferCredits extends AbstractConditionableCommand {
.name(MEMBER_PARAMETER)
.templated(true)
.type(Member.class)
.optional(true)
.build();
Parameter amountParameter = Parameter
@@ -81,6 +82,7 @@ public class TransferCredits extends AbstractConditionableCommand {
.name(AMOUNT_PARAMETER)
.templated(true)
.type(Integer.class)
.optional(true)
.build();
List<Parameter> parameters = Arrays.asList(memberParameter, amountParameter);

View File

@@ -200,8 +200,8 @@ public class EntertainmentServiceBean implements EntertainmentService {
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageEmbedToSendToAChannel(messageToSend, pressF.getPressFChannel()))
.thenCompose(unused -> messageService.loadMessage(serverId, channelId, messageId).thenCompose(message -> {
log.info("Clearing buttons from pressF {} in with message {} in channel {} in server {}.", pressFId, pressFId, channelId, serverId);
return componentService.clearComponents(message);
})).thenAccept(message -> {});
return componentService.clearButtons(message);
}));
} else {
throw new AbstractoRunTimeException(String.format("PressF with id %s not found.", pressFId));
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -97,7 +97,7 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
} else {
leaderBoard = userExperienceService.findLeaderBoardData(server, page);
}
List<CompletableFuture<?>> futures = new ArrayList<>();
List<CompletableFuture> futures = new ArrayList<>();
CompletableFuture<List<LeaderBoardEntryModel>> completableFutures = converter.fromLeaderBoard(leaderBoard, actorUser.getGuild().getIdLong());
futures.add(completableFutures);
log.info("Rendering leaderboard for page {} in server {} for user {}.", page, actorUser.getId(), actorUser.getGuild().getId());
@@ -158,6 +158,7 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
Parameter focusMe = Parameter
.builder()
.name(FOCUS_PARAMETER)
.validators(leaderBoardPageValidators)
.optional(true)
.slashCommandOnly(true)
.templated(true)

View File

@@ -313,107 +313,107 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
Long userInServerId = userInAServer.getUserInServerId();
Optional<AUserExperience> aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServer.getUserInServerId());
AUserExperience aUserExperience = aUserExperienceOptional.orElseGet(() -> userExperienceManagementService.createUserInServer(userInAServer));
if (aUserExperience.getExperienceGainDisabled().equals(Boolean.TRUE)) {
if(Boolean.FALSE.equals(aUserExperience.getExperienceGainDisabled())) {
List<AExperienceLevel> levels = experienceLevelManagementService.getLevelConfig();
levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded));
Long minExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MIN_EXP_KEY, serverId);
Long maxExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MAX_EXP_KEY, serverId);
Double multiplier = configService.getDoubleValueOrConfigDefault(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverId);
Long experienceRange = maxExp - minExp + 1;
Long gainedExperience = (secureRandom.nextInt(experienceRange.intValue()) + minExp);
gainedExperience = (long) Math.floor(gainedExperience * multiplier);
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
log.debug("Handling {}. The user gains {}.", userInServerId, gainedExperience);
Long oldExperience = aUserExperience.getExperience();
Long newExperienceCount = oldExperience + gainedExperience;
aUserExperience.setExperience(newExperienceCount);
AExperienceLevel newLevel = calculateLevel(levels, newExperienceCount);
RoleCalculationResult result = RoleCalculationResult
.builder()
.build();
boolean userChangesLevel = !Objects.equals(newLevel.getLevel(), aUserExperience.getCurrentLevel().getLevel());
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
if(userChangesLevel) {
log.info("User {} in server {} changed level. New {}, Old {}.", member.getIdLong(),
member.getGuild().getIdLong(), newLevel.getLevel(),
oldLevel);
aUserExperience.setCurrentLevel(newLevel);
AExperienceRole calculatedNewRole = experienceRoleService.calculateRole(roles, newLevel.getLevel());
Long oldRoleId = aUserExperience.getCurrentExperienceRole() != null && aUserExperience.getCurrentExperienceRole().getRole() != null ? aUserExperience.getCurrentExperienceRole().getRole().getId() : null;
Long newRoleId = calculatedNewRole != null && calculatedNewRole.getRole() != null ? calculatedNewRole.getRole().getId() : null;
result.setOldRoleId(oldRoleId);
result.setNewRoleId(newRoleId);
if(message != null
&& aUserExperience.getLevelUpNotification()
&& featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, serverId, ExperienceFeatureMode.LEVEL_UP_NOTIFICATION)) {
LevelUpNotificationModel model = LevelUpNotificationModel
.builder()
.memberDisplay(MemberDisplay.fromMember(member))
.oldExperience(oldExperience)
.newExperience(newExperienceCount)
.newLevel(newLevel.getLevel())
.oldLevel(oldLevel)
.newRole(oldRoleId != null ? RoleDisplay.fromRole(oldRoleId) : null)
.newRole(newRoleId != null ? RoleDisplay.fromRole(newRoleId) : null)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("experience_level_up_notification", model, serverId);
FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, message.getChannel())).thenAccept(unused -> {
log.info("Sent level up notification to user {} in server {} in channel {}.", member.getIdLong(), serverId, message.getChannel().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to send level up notification to user {} in server {} in channel {}.", member.getIdLong(), serverId, message.getChannel().getIdLong());
return null;
});
}
aUserExperience.setCurrentExperienceRole(calculatedNewRole);
}
aUserExperience.setMessageCount(aUserExperience.getMessageCount() + 1L);
if(userChangesLevel && featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, server, ExperienceFeatureMode.LEVEL_ACTION)) {
levelActionService.applyLevelActionsToUser(aUserExperience, oldLevel)
.thenAccept(unused -> {
log.info("Executed level actions for user {}.", userInServerId);
})
.exceptionally(throwable -> {
log.warn("Failed to execute level actions for user {}.", userInServerId, throwable);
return null;
});
}
if(aUserExperienceOptional.isEmpty()) {
userExperienceManagementService.saveUser(aUserExperience);
}
if(!Objects.equals(result.getOldRoleId(), result.getNewRoleId())) {
if(result.getOldRoleId() != null && result.getNewRoleId() != null) {
roleService.updateRolesIds(member, Arrays.asList(result.getOldRoleId()), Arrays.asList(result.getNewRoleId())).thenAccept(unused -> {
log.debug("Removed role {} from and added role {} to member {} in server {}.", result.getOldRoleId(), result.getNewRoleId(), member.getIdLong(), member.getGuild().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to remove role {} from and add role {} to member {} in server {}.", result.getOldRoleId(), result.getNewRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable);
return null;
});
} else {
if(result.getOldRoleId() != null) {
roleService.removeRoleFromMemberAsync(member, result.getOldRoleId()).thenAccept(unused -> {
log.debug("Removed role {} from member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to remove role {} from member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable);
return null;
});
}
if(result.getNewRoleId() != null) {
roleService.addRoleToMemberAsync(member, result.getNewRoleId()).thenAccept(unused -> {
log.debug("Added role {} to member {} in server {}.", result.getNewRoleId(), member.getIdLong(), member.getGuild().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to add role {} to member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable);
return null;
});
}
}
}
} else {
log.debug("Experience gain was disabled. User did not gain any experience.");
return;
}
List<AExperienceLevel> levels = experienceLevelManagementService.getLevelConfig();
levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded));
Long minExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MIN_EXP_KEY, serverId);
Long maxExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MAX_EXP_KEY, serverId);
Double multiplier = configService.getDoubleValueOrConfigDefault(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverId);
Long experienceRange = maxExp - minExp + 1;
Long gainedExperience = (secureRandom.nextInt(experienceRange.intValue()) + minExp);
gainedExperience = (long) Math.floor(gainedExperience * multiplier);
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
log.debug("Handling {}. The user gains {}.", userInServerId, gainedExperience);
Long oldExperience = aUserExperience.getExperience();
Long newExperienceCount = oldExperience + gainedExperience;
aUserExperience.setExperience(newExperienceCount);
AExperienceLevel newLevel = calculateLevel(levels, newExperienceCount);
RoleCalculationResult result = RoleCalculationResult
.builder()
.build();
boolean userChangesLevel = !Objects.equals(newLevel.getLevel(), aUserExperience.getCurrentLevel().getLevel());
Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0;
if(userChangesLevel) {
log.info("User {} in server {} changed level. New {}, Old {}.", member.getIdLong(),
member.getGuild().getIdLong(), newLevel.getLevel(),
oldLevel);
aUserExperience.setCurrentLevel(newLevel);
AExperienceRole calculatedNewRole = experienceRoleService.calculateRole(roles, newLevel.getLevel());
Long oldRoleId = aUserExperience.getCurrentExperienceRole() != null && aUserExperience.getCurrentExperienceRole().getRole() != null ? aUserExperience.getCurrentExperienceRole().getRole().getId() : null;
Long newRoleId = calculatedNewRole != null && calculatedNewRole.getRole() != null ? calculatedNewRole.getRole().getId() : null;
result.setOldRoleId(oldRoleId);
result.setNewRoleId(newRoleId);
if(message != null
&& aUserExperience.getLevelUpNotification()
&& featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, serverId, ExperienceFeatureMode.LEVEL_UP_NOTIFICATION)) {
LevelUpNotificationModel model = LevelUpNotificationModel
.builder()
.memberDisplay(MemberDisplay.fromMember(member))
.oldExperience(oldExperience)
.newExperience(newExperienceCount)
.newLevel(newLevel.getLevel())
.oldLevel(oldLevel)
.newRole(oldRoleId != null ? RoleDisplay.fromRole(oldRoleId) : null)
.newRole(newRoleId != null ? RoleDisplay.fromRole(newRoleId) : null)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("experience_level_up_notification", model, serverId);
FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, message.getChannel())).thenAccept(unused -> {
log.info("Sent level up notification to user {} in server {} in channel {}.", member.getIdLong(), serverId, message.getChannel().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to send level up notification to user {} in server {} in channel {}.", member.getIdLong(), serverId, message.getChannel().getIdLong());
return null;
});
}
aUserExperience.setCurrentExperienceRole(calculatedNewRole);
}
aUserExperience.setMessageCount(aUserExperience.getMessageCount() + 1L);
if(userChangesLevel && featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, server, ExperienceFeatureMode.LEVEL_ACTION)) {
levelActionService.applyLevelActionsToUser(aUserExperience, oldLevel)
.thenAccept(unused -> {
log.info("Executed level actions for user {}.", userInServerId);
})
.exceptionally(throwable -> {
log.warn("Failed to execute level actions for user {}.", userInServerId, throwable);
return null;
});
}
if(aUserExperienceOptional.isEmpty()) {
userExperienceManagementService.saveUser(aUserExperience);
}
if(!Objects.equals(result.getOldRoleId(), result.getNewRoleId())) {
if(result.getOldRoleId() != null && result.getNewRoleId() != null) {
roleService.updateRolesIds(member, List.of(result.getOldRoleId()), List.of(result.getNewRoleId())).thenAccept(unused -> {
log.debug("Removed role {} from and added role {} to member {} in server {}.", result.getOldRoleId(), result.getNewRoleId(), member.getIdLong(), member.getGuild().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to remove role {} from and add role {} to member {} in server {}.", result.getOldRoleId(), result.getNewRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable);
return null;
});
} else {
if(result.getOldRoleId() != null) {
roleService.removeRoleFromMemberAsync(member, result.getOldRoleId()).thenAccept(unused -> {
log.debug("Removed role {} from member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to remove role {} from member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable);
return null;
});
}
if(result.getNewRoleId() != null) {
roleService.addRoleToMemberAsync(member, result.getNewRoleId()).thenAccept(unused -> {
log.debug("Added role {} to member {} in server {}.", result.getNewRoleId(), member.getIdLong(), member.getGuild().getIdLong());
}).exceptionally(throwable -> {
log.warn("Failed to add role {} to member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable);
return null;
});
}
}
}
}
@@ -521,7 +521,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
public LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer) {
log.debug("Retrieving rank for {}", userInAServer.getUserReference().getId());
Optional<AUserExperience> aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServer.getUserInServerId());
if(aUserExperienceOptional.isEmpty()) {
if(!aUserExperienceOptional.isPresent()) {
throw new NoExperienceTrackedException();
}
Integer rank = 0;

View File

@@ -52,7 +52,7 @@ public class ExperienceLevelServiceBean implements ExperienceLevelService {
if(level < 0) {
throw new IllegalArgumentException("Level should not be less to 0.");
}
return 5L * ((long) level * level) + 50L * level + 100;
return 5L * (level * level) + 50 * level + 100;
}
@Override

View File

@@ -88,7 +88,7 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
@Override
public CompletableFuture<Void> unsetRoles(List<ARole> rolesToUnset, GuildMessageChannel messageChannel) {
List<AExperienceRole> rolesInServer = experienceRoleManagementService.getRolesInServer(rolesToUnset);
int totalCount = 0;
Integer totalCount = 0;
for (AExperienceRole aExperienceRole : rolesInServer) {
totalCount += aExperienceRole.getUsers().size();
}

View File

@@ -1,8 +1,5 @@
package dev.sheldan.abstracto.experience.service;
import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
import org.springframework.stereotype.Component;
import java.time.Instant;
@@ -20,9 +17,11 @@ import java.util.concurrent.locks.ReentrantLock;
@Component
public class RunTimeExperienceService {
@Getter
private final Map<Long,Map<Long, Instant>> runtimeExperience = new HashMap<>();
private Map<Long,Map<Long, Instant>> runtimeExperience = new HashMap<>();
private static final Lock lock = new ReentrantLock();
public Map<Long, Map<Long, Instant>> getRuntimeExperience() {
return runtimeExperience;
}
/**
* Acquires the lock of the runtime experience data structure. Operations on it should only be done, while holding the lock
@@ -40,7 +39,6 @@ public class RunTimeExperienceService {
public void cleanupRunTimeStorage() {
Instant now = Instant.now();
Set<Long> serverIdsToRemove = new HashSet<>();
runtimeExperience.forEach((serverId, userInstantMap) -> {
List<Long> userIdsToRemove = new ArrayList<>();
userInstantMap.forEach((userId, instant) -> {
@@ -49,10 +47,6 @@ public class RunTimeExperienceService {
}
});
userIdsToRemove.forEach(userInstantMap::remove);
if(userInstantMap.isEmpty()) {
serverIdsToRemove.add(serverId);
}
});
serverIdsToRemove.forEach(runtimeExperience::remove);
}
}

View File

@@ -1,32 +0,0 @@
package dev.sheldan.abstracto.experience.service;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class RuntimeExperienceServiceTest {
@Test
public void shouldCleanExpiredExperience() {
RunTimeExperienceService experienceService = new RunTimeExperienceService();
Map<Long, Instant> mapValue = new HashMap<>(Map.of(2L, Instant.now().minusSeconds(5)));
experienceService.getRuntimeExperience().put(1L, mapValue);
experienceService.cleanupRunTimeStorage();
assertThat(experienceService.getRuntimeExperience()).isEmpty();
}
@Test
public void shouldLeaveExperienceIfNotYetExpired() {
RunTimeExperienceService experienceService = new RunTimeExperienceService();
Map<Long, Instant> mapValue2 = new HashMap<>(Map.of(3L, Instant.now().plusSeconds(5)));
experienceService.getRuntimeExperience().put(2L, mapValue2);
experienceService.cleanupRunTimeStorage();
assertThat(experienceService.getRuntimeExperience().get(2L)).containsKey(3L);
assertThat(experienceService.getRuntimeExperience()).hasSize(1);
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<artifactId>giveaway-impl</artifactId>

View File

@@ -134,7 +134,7 @@ public class GiveawayServiceBean implements GiveawayService {
if(giveawayCreationRequest.getTargetChannel() == null) {
log.info("Sending giveaway to post target in server {}", serverId);
postTargetService.validatePostTarget(GiveawayPostTarget.GIVEAWAYS, serverId);
messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, GiveawayPostTarget.GIVEAWAYS, serverId).get(0);
messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, GiveawayPostTarget.GIVEAWAYS, serverId);
} else {
log.info("Sending giveaway to channel {} in server {}.", giveawayCreationRequest.getTargetChannel().getId(), serverId);
messageFutures = channelService.sendMessageToSendToChannel(messageToSend, giveawayCreationRequest.getTargetChannel());
@@ -155,7 +155,7 @@ public class GiveawayServiceBean implements GiveawayService {
Long giveawayId = giveaway.getGiveawayId().getId();
log.info("Adding giveaway participating of user {} to giveaway {} in server {}.", member.getIdLong(), giveawayId, member.getGuild().getIdLong());
MessageToSend messageToSend = templateService.renderEmbedTemplate(GIVEAWAY_MESSAGE_TEMPLATE_KEY, giveawayMessageModel, member.getGuild().getIdLong());
return channelService.editMessageInAChannelFuture(messageToSend, messageChannel, giveaway.getMessageId())
return channelService.editEmbedMessageInAChannel(messageToSend.getEmbeds().get(0), messageChannel, giveaway.getMessageId())
.thenAccept(message -> {
self.persistAddedParticipant(member, giveawayId);
});

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>giveaway</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<artifactId>giveaway-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<artifactId>giveaway</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<artifactId>image-generation-impl</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>image-generation</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<artifactId>image-generation-int</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<artifactId>image-generation</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -254,8 +254,8 @@ public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
log.info("Sending notification about {} deleted invite links in guild {} from user {} in channel {} in message {}.",
codes.size(), serverId, message.getAuthor().getIdLong(), message.getChannel().getIdLong(), message.getIdLong());
MessageToSend messageToSend = templateService.renderEmbedTemplate(INVITE_LINK_DELETED_NOTIFICATION_EMBED_TEMPLATE_KEY, model, message.getGuild().getIdLong());
List<List<CompletableFuture<Message>>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, InviteFilterPostTarget.INVITE_DELETE_LOG, serverId);
return FutureUtils.toSingleFutureGenericList(messageFutures).thenAccept(unused ->
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, InviteFilterPostTarget.INVITE_DELETE_LOG, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures).thenAccept(unused ->
log.debug("Successfully send notification about deleted invite link in message {}.", message.getIdLong())
);
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,7 +18,7 @@
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -13,13 +13,9 @@ import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
import dev.sheldan.abstracto.linkembed.model.MessageEmbedLink;
import dev.sheldan.abstracto.linkembed.service.MessageEmbedService;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.Event;
@@ -31,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@Component
@Slf4j
@@ -70,51 +67,37 @@ public class MessageEmbedListener implements MessageReceivedListener {
}
String messageRaw = message.getContentRaw();
List<MessageEmbedLink> links = messageEmbedService.getLinksInMessage(messageRaw);
for (MessageEmbedLink messageEmbedLink : links) {
messageRaw = messageRaw.replace(messageEmbedLink.getWholeUrl(), "");
}
boolean deleteMessage = StringUtils.isBlank(messageRaw) && !links.isEmpty() && message.getAttachments().isEmpty();
if(!links.isEmpty()) {
Long messageId = message.getIdLong();
Long channelId = message.getChannelIdLong();
Long serverId = message.getGuildIdLong();
log.debug("We found {} links to embed in message {} in channel {} in guild {}.", links.size(), message.getId(), message.getChannel().getId(), message.getGuild().getId());
Long userEmbeddingUserInServerId = userInServerManagementService.loadOrCreateUser(message.getMember()).getUserInServerId();
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (MessageEmbedLink messageEmbedLink : links) {
// potentially support foreign linked servers?
// potentially support foreign linked servers
if(!messageEmbedLink.getServerId().equals(message.getGuild().getIdLong())) {
log.info("Link for message {} was from a foreign server {}. Do not embed.", messageEmbedLink.getMessageId(), messageEmbedLink.getServerId());
continue;
}
Function<CachedMessage, CompletableFuture<Void>> cachedMessageConsumer = cachedMessage -> self.embedSingleLink(message, userEmbeddingUserInServerId, cachedMessage);
futures.add(messageCache.getMessageFromCache(messageEmbedLink.getServerId(), messageEmbedLink.getChannelId(), messageEmbedLink.getMessageId())
.thenCompose(cachedMessageConsumer));
}
if(!futures.isEmpty()) {
// only delete the message if all futures go through
new CompletableFutureList<>(futures).getMainFuture().thenAccept(unused -> {
if(deleteMessage) {
messageService.deleteMessageInChannelInServer(serverId, channelId, messageId);
}
}).thenAccept(unused -> {
log.info("Deleted embedding message server {} channel {} message {}.", serverId, channelId, messageId);
}).exceptionally(throwable -> {
log.info("Failed to delete embedding message or to embed message server {} channel {} message {}.", serverId, channelId, messageId, throwable);
return null;
});
messageRaw = messageRaw.replace(messageEmbedLink.getWholeUrl(), "");
Consumer<CachedMessage> cachedMessageConsumer = cachedMessage -> self.embedSingleLink(message, userEmbeddingUserInServerId, cachedMessage);
messageCache.getMessageFromCache(messageEmbedLink.getServerId(), messageEmbedLink.getChannelId(), messageEmbedLink.getMessageId())
.thenAccept(cachedMessageConsumer)
.exceptionally(throwable -> {
log.error("Error when embedding link for message {}", message.getId(), throwable);
return null;
});
}
}
if(deleteMessage) {
if(StringUtils.isBlank(messageRaw) && !links.isEmpty() && message.getAttachments().isEmpty()) {
messageService.deleteMessage(message);
return ConsumableListenerResult.DELETED;
} else if(!links.isEmpty()) {
}
if(!links.isEmpty()) {
return ConsumableListenerResult.PROCESSED;
}
return ConsumableListenerResult.IGNORED;
}
@Transactional
public CompletableFuture<Void> embedSingleLink(Message message, Long cause, CachedMessage cachedMessage) {
public void embedSingleLink(Message message, Long cause, CachedMessage cachedMessage) {
GuildMemberMessageChannel context = GuildMemberMessageChannel
.builder()
.guildChannel(message.getGuildChannel())
@@ -124,7 +107,7 @@ public class MessageEmbedListener implements MessageReceivedListener {
.build();
log.info("Embedding link to message {} in channel {} in server {} to channel {} and server {}.",
cachedMessage.getMessageId(), cachedMessage.getChannelId(), cachedMessage.getServerId(), message.getChannel().getId(), message.getGuild().getId());
return messageEmbedService.embedLink(cachedMessage, message.getGuildChannel(), cause , context).thenAccept(unused ->
messageEmbedService.embedLink(cachedMessage, message.getGuildChannel(), cause , context).thenAccept(unused ->
metricService.incrementCounter(MESSAGE_EMBED_CREATED)
).exceptionally(throwable -> {
log.error("Failed to embed link towards message {} in channel {} in sever {} linked from message {} in channel {} in server {}.", cachedMessage.getMessageId(), cachedMessage.getChannelId(), cachedMessage.getServerId(),

View File

@@ -12,7 +12,6 @@ import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
import dev.sheldan.abstracto.linkembed.service.MessageEmbedService;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
@@ -47,27 +46,13 @@ public class MessageEmbedContextCommandListener implements MessageContextCommand
Message targetMessage = event.getInteraction().getTarget();
Member actor = model.getEvent().getMember();
Long messageId = targetMessage.getIdLong();
messageCache.getMessageFromCache(targetMessage)
.thenCompose(cachedMessage -> {
try {
return self.embedMessage(model, actor, cachedMessage);
} catch (Exception ex) {
return CompletableFuture.failedFuture(ex);
}
})
.thenAccept(unused -> {
log.info("Finished embedding message {}.", messageId);
})
.exceptionally(throwable -> {
log.error("Failed to embed message {}.", messageId, throwable);
return null;
});
.thenAccept(cachedMessage -> self.embedMessage(model, actor, cachedMessage));
return DefaultListenerResult.PROCESSED;
}
@Transactional
public CompletableFuture<Void> embedMessage(MessageContextInteractionModel model, Member actor, CachedMessage cachedMessage) {
public void embedMessage(MessageContextInteractionModel model, Member actor, CachedMessage cachedMessage) {
Long userEmbeddingUserInServerId = userInServerManagementService.loadOrCreateUser(actor).getUserInServerId();
GuildMemberMessageChannel context = GuildMemberMessageChannel
.builder()
@@ -76,7 +61,7 @@ public class MessageEmbedContextCommandListener implements MessageContextCommand
.member(actor)
.guildChannel(model.getEvent().getGuildChannel())
.build();
return messageEmbedService.embedLink(cachedMessage, model.getEvent().getGuildChannel(), userEmbeddingUserInServerId, context, model.getEvent().getInteraction());
messageEmbedService.embedLink(cachedMessage, model.getEvent().getGuildChannel(), userEmbeddingUserInServerId, context, model.getEvent().getInteraction());
}
@Override

View File

@@ -11,7 +11,6 @@ import dev.sheldan.abstracto.core.interaction.button.ButtonConfigModel;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureMode;
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbedCleanupReplacementModel;
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbedDeleteButtonPayload;
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbeddedModel;
import dev.sheldan.abstracto.core.service.*;
@@ -19,6 +18,7 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.linkembed.model.MessageEmbedLink;
import dev.sheldan.abstracto.linkembed.model.database.EmbeddedMessage;
@@ -28,7 +28,6 @@ import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.interactions.commands.CommandInteraction;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -50,7 +49,6 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
private final Pattern messageRegex = Pattern.compile("(?<whole>(?:https?://)?(?:\\w+\\.)?discord(?:app)?\\.com/channels/(?<server>\\d+)/(?<channel>\\d+)/(?<message>\\d+)(?:.*?))+", Pattern.CASE_INSENSITIVE);
public static final String MESSAGE_EMBED_TEMPLATE = "message_embed";
private static final String MESSAGE_EMBED_CLEANUP_REPLACEMENT_TEMPLATE = "message_embed_cleanup_replacement";
public static final String REMOVAL_EMOTE = "removeEmbed";
@Autowired
@@ -80,6 +78,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
@Autowired
private ReactionService reactionService;
@Autowired
private MessageService messageService;
@Autowired
private ComponentService componentServiceBean;
@@ -159,21 +160,15 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
return CompletableFuture.completedFuture(null);
}
log.info("Cleaning up {} embedded embeddedMessages", embeddedMessages.size());
List<Pair<ServerChannelMessage, ServerChannelMessage>> embeddingMessages = embeddedMessages.stream()
.map(embeddedMessage -> Pair.of(ServerChannelMessage
.builder()
.serverId(embeddedMessage.getEmbeddingServer().getId())
.channelId(embeddedMessage.getEmbeddingChannel().getId())
.messageId(embeddedMessage.getEmbeddingMessageId())
.build(),
ServerChannelMessage
.builder().
serverId(embeddedMessage.getEmbeddedServer().getId())
.channelId(embeddedMessage.getEmbeddedChannel().getId())
.messageId(embeddedMessage.getEmbeddedMessageId())
.build()))
.toList();
List<ServerChannelMessage> reactionChannelMessages = embeddedMessages.stream()
.filter(embeddedMessage -> embeddedMessage.getDeletionComponentId() == null)
.map(this::convertEmbedMessageToServerChannelMessage)
.collect(Collectors.toList());
List<ServerChannelMessage> buttonChannelMessages = embeddedMessages.stream()
.filter(embeddedMessage -> embeddedMessage.getDeletionComponentId() != null)
.map(this::convertEmbedMessageToServerChannelMessage)
.collect(Collectors.toList());
List<Long> embeddedMessagesHandled = embeddedMessages
.stream()
.map(EmbeddedMessage::getEmbeddingMessageId)
@@ -184,26 +179,67 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
List<CompletableFuture<Message>> editList = embeddingMessages.stream().map(messagePair -> {
ServerChannelMessage embeddingMessage = messagePair.getLeft();
ServerChannelMessage embeddedMessage = messagePair.getRight();
MessageEmbedCleanupReplacementModel model = MessageEmbedCleanupReplacementModel
.builder()
.message(embeddedMessage)
.build();
MessageToSend messageToSend =
templateService.renderEmbedTemplate(MESSAGE_EMBED_CLEANUP_REPLACEMENT_TEMPLATE, model, embeddingMessage.getServerId());
return channelService.editMessageInAChannelFuture(messageToSend, embeddingMessage.getServerId(), embeddingMessage.getChannelId(),
embeddingMessage.getMessageId());
}).toList();
return FutureUtils.toSingleFutureGeneric(editList).whenComplete((unused, throwable) -> {
if(throwable != null) {
log.warn("Failed to cleanup embedded messages..", throwable);
}
self.deleteEmbeddedMessages(embeddedMessagesHandled, componentPayloadsToDelete);
});
List<CompletableFuture<Message>> reactionMessageFutures = messageService.retrieveMessages(reactionChannelMessages);
List<CompletableFuture<Message>> buttonMessageFutures = messageService.retrieveMessages(buttonChannelMessages);
CompletableFutureList<Message> reactionFutureList = new CompletableFutureList<>(reactionMessageFutures);
CompletableFutureList<Message> buttonFutureList = new CompletableFutureList<>(buttonMessageFutures);
return reactionFutureList.getMainFuture()
.handle((unused, throwable) -> {
if(throwable != null) {
log.warn("Embedded messages reaction message loading failed.", throwable);
}
return self.removeReactions(reactionFutureList.getObjects());
})
.thenCompose(Function.identity())
.thenCompose(unused -> buttonFutureList.getMainFuture())
.handle((unused, throwable) -> {
if(throwable != null) {
log.warn("Embedded messages button message loading failed.", throwable);
}
return self.removeButtons(buttonFutureList.getObjects());
})
// deleting the messages from db regardless of exceptions, at most the reaction remains
.thenCompose(Function.identity())
.whenComplete((unused, throwable) -> {
if(throwable != null) {
log.warn("Embedded message button clearing failed.", throwable);
}
self.deleteEmbeddedMessages(embeddedMessagesHandled, componentPayloadsToDelete);
})
.exceptionally(throwable -> {
log.error("Failed to clean up embedded messages.", throwable);
return null;
});
}
public CompletableFuture<Void> removeButtons(List<Message> messages) {
List<CompletableFuture<Void>> removalFutures = new ArrayList<>();
messages.forEach(message -> removalFutures.add(componentServiceBean.clearButtons(message)));
return FutureUtils.toSingleFutureGeneric(removalFutures);
}
private ServerChannelMessage convertEmbedMessageToServerChannelMessage(EmbeddedMessage embeddedMessage) {
return ServerChannelMessage
.builder()
.serverId(embeddedMessage.getEmbeddingServer().getId())
.channelId(embeddedMessage.getEmbeddingChannel().getId())
.messageId(embeddedMessage.getEmbeddingMessageId())
.build();
}
@Transactional
public CompletableFuture<Void> removeReactions(List<Message> allMessages) {
List<CompletableFuture<Void>> removalFutures = new ArrayList<>();
Map<Long, List<Message>> groupedPerServer = allMessages
.stream()
.collect(Collectors.groupingBy(message -> message.getGuild().getIdLong()));
groupedPerServer.forEach((serverId, serverMessages) -> {
// we assume the emote remained the same
CompletableFutureList<Void> removalFuture = reactionService.removeReactionFromMessagesWithFutureWithFutureList(serverMessages, REMOVAL_EMOTE);
removalFutures.add(removalFuture.getMainFuture());
});
return FutureUtils.toSingleFutureGeneric(removalFutures);
}
@Transactional
public void deleteEmbeddedMessages(List<Long> embeddedMessagesToDelete, List<String> componentPayloadsToDelete) {

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,11 +0,0 @@
package dev.sheldan.abstracto.linkembed.model.template;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class MessageEmbedCleanupReplacementModel {
private ServerChannelMessage message;
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -37,7 +37,7 @@ public class JoinLogger implements AsyncJoinListener {
.build();
log.debug("Logging join event for user {} in server {}.", listenerModel.getMember().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_JOIN_TEMPLATE, model, listenerModel.getServerId());
FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.JOIN_LOG, listenerModel.getServerId()))
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.JOIN_LOG, listenerModel.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send member joining log.", throwable);
return null;

View File

@@ -5,7 +5,6 @@ import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncLeaveListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.MemberLeaveModel;
import dev.sheldan.abstracto.core.models.template.display.RoleDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
@@ -14,7 +13,6 @@ import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.logging.config.LoggingFeatureDefinition;
import dev.sheldan.abstracto.logging.config.LoggingPostTarget;
import dev.sheldan.abstracto.logging.model.template.MemberLeaveLogModel;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -43,21 +41,14 @@ public class LeaveLogger implements AsyncLeaveListener {
.userId(listenerModel.getUser().getIdLong())
.serverId(listenerModel.getServerId())
.build();
List<RoleDisplay> roles = listenerModel
.getMember()
.getRoles()
.stream()
.map(RoleDisplay::fromRole)
.toList();
MemberLeaveLogModel model = MemberLeaveLogModel
.builder()
.leavingUser(leavingUser)
.user(UserDisplay.fromUser(listenerModel.getUser()))
.roles(roles)
.build();
log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId());
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_LEAVE_TEMPLATE, model, listenerModel.getServerId());
FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.LEAVE_LOG, listenerModel.getServerId()))
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, LoggingPostTarget.LEAVE_LOG, listenerModel.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send member leaving log.", throwable);
return null;

View File

@@ -69,7 +69,7 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.member(authorMember)
.build();
MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_DELETED_TEMPLATE, logModel, messageFromCache.getServerId());
FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable);
return null;
@@ -86,7 +86,7 @@ public class MessageDeleteLogListener implements AsyncMessageDeletedListener {
.member(authorMember)
.build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_DELETED_ATTACHMENT_TEMPLATE, attachmentLogModel, messageFromCache.getServerId());
FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageFromCache.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message deleted log.", throwable);
return null;

View File

@@ -66,7 +66,7 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.member(messageAfter.getMember())
.build();
MessageToSend message = templateService.renderEmbedTemplate(MESSAGE_EDITED_TEMPLATE, lodModel, model.getServerId());
FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.EDIT_LOG, model.getServerId()))
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, LoggingPostTarget.EDIT_LOG, model.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message edited log.", throwable);
return null;
@@ -90,7 +90,7 @@ public class MessageEditedListener implements AsyncMessageUpdatedListener {
.build();
MessageToSend attachmentEmbed = templateService.renderEmbedTemplate(MESSAGE_EDITED_ATTACHMENT_REMOVED_TEMPLATE,
attachmentModel, messageBefore.getServerId());
FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageBefore.getServerId()))
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(attachmentEmbed, LoggingPostTarget.DELETE_LOG, messageBefore.getServerId()))
.exceptionally(throwable -> {
log.error("Failed to send message edited attachment log.", throwable);
return null;

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,9 +1,7 @@
package dev.sheldan.abstracto.logging.model.template;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.template.display.RoleDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -14,5 +12,4 @@ import lombok.Setter;
public class MemberLeaveLogModel {
private ServerUser leavingUser;
private UserDisplay user;
private List<RoleDisplay> roles;
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -23,6 +23,7 @@ import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -75,15 +76,16 @@ public class Ban extends AbstractConditionableCommand {
duration = null;
}
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
if(member != null) {
if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) {
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
return event.deferReply().submit()
.thenCompose((hook) -> self.banMember(event, member, reason, duration, hook))
.thenApply(commandResult -> CommandResult.fromSuccess());
} else {
User user = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, User.class);
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr);
return event.deferReply().submit()
.thenCompose((hook) -> self.banViaUserId(event, user.getIdLong(), reason, duration, hook))
.thenCompose((hook) -> self.banViaUserId(event, userId, reason, duration, hook))
.thenApply(commandResult -> CommandResult.fromSuccess());
}
}

View File

@@ -1,153 +0,0 @@
package dev.sheldan.abstracto.moderation.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandPrivilegeLevels;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.listener.HoneyPotServiceBean;
import dev.sheldan.abstracto.moderation.model.template.command.HoneyPotBanResponseModel;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class HoneyPotBan extends AbstractConditionableCommand {
private static final String DURATION_PARAMETER = "duration";
private static final String HONEYPOT_BAN_RESPONSE = "honeypotBan_response";
private static final String HONEYPOT_BAN_COMMAND = "honeypotBan";
@Autowired
private HoneyPotBan self;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private HoneyPotServiceBean honeyPotServiceBean;
@Autowired
private InteractionService interactionService;
@Autowired
private ConfigService configService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Duration duration;
if(slashCommandParameterService.hasCommandOption(DURATION_PARAMETER, event)) {
String durationStr = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class);
duration = ParseUtils.parseDuration(durationStr);
} else {
Long ignoredSeconds =
configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_IGNORED_JOIN_DURATION_SECONDS, event.getGuild().getIdLong());
duration = Duration.ofSeconds(ignoredSeconds);
}
return event.deferReply(false).submit()
.thenCompose(hook -> self.banEveryHoneypotMember(hook, event.getGuild(), duration))
.thenCompose(banResponse -> self.sendResponse(banResponse))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Transactional
public CompletableFuture<Void> sendResponse(Pair<InteractionHook, Integer> banResponse) {
HoneyPotBanResponseModel responseModel = HoneyPotBanResponseModel
.builder()
.bannedMemberCount(banResponse.getRight())
.build();
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(HONEYPOT_BAN_RESPONSE, responseModel, banResponse.getLeft()));
}
@Transactional
public CompletableFuture<Pair<InteractionHook, Integer>> banEveryHoneypotMember(InteractionHook hook, Guild guild, Duration duration) {
Instant maxJoinAge;
if(duration != null) {
maxJoinAge = Instant.now().minus(duration);
} else {
maxJoinAge = Instant.now();
}
List<Member> currentMembersWithHoneypotRole = honeyPotServiceBean.getCurrentMembersWithHoneypotRole(guild)
.stream().filter(member -> member.getTimeJoined().toInstant().isBefore(maxJoinAge))
.toList();
Role honeyPotRole = guild.getRoleById(honeyPotServiceBean.getHoneyPotRoleId(guild.getIdLong()));
List<CompletableFuture<Void>> futures = currentMembersWithHoneypotRole.stream().map(member ->
honeyPotServiceBean.banForHoneyPot(member, honeyPotRole)
).toList();
Integer memberCount = currentMembersWithHoneypotRole.size();
CompletableFutureList<Void> futureList = new CompletableFutureList<>(futures);
return futureList.getMainFuture()
.thenApply(unused -> Pair.of(hook, memberCount));
}
@Override
public CommandConfiguration getConfiguration() {
Parameter durationParameter = Parameter
.builder()
.name(DURATION_PARAMETER)
.templated(true)
.type(String.class)
.optional(true)
.build();
List<Parameter> parameters = Arrays.asList(durationParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.hasExample(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModerationSlashCommandNames.MODERATION)
.defaultPrivilege(SlashCommandPrivilegeLevels.INVITER)
.commandName("honeypotban")
.build();
return CommandConfiguration.builder()
.name(HONEYPOT_BAN_COMMAND)
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.slashCommandConfig(slashCommandConfig)
.slashCommandOnly(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.HONEYPOT;
}
}

View File

@@ -33,6 +33,7 @@ import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -85,15 +86,16 @@ public class Infractions extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> showInfractions(InteractionHook hook, SlashCommandInteractionEvent event) {
List<Infraction> infractions;
Guild guild = hook.getInteraction().getGuild();
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
User user = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, User.class);
if(member != null) {
if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) {
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
if(!member.getGuild().equals(guild)) {
throw new EntityGuildMismatchException();
}
infractions = infractionManagementService.getInfractionsForUser(userInServerManagementService.loadOrCreateUser(member));
} else if(user != null){
AUserInAServer userInServer = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), user.getIdLong());
} else if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.STRING)){
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr);
AUserInAServer userInServer = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), userId);
infractions = infractionManagementService.getInfractionsForUser(userInServer);
} else {

View File

@@ -61,7 +61,7 @@ public class BanModerationActionModalListener implements ModalInteractionListene
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getCustomId().equals(payload.getDurationInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
@@ -76,7 +76,7 @@ public class BanModerationActionModalListener implements ModalInteractionListene
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getCustomId().equals(payload.getReasonInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);

View File

@@ -4,44 +4,97 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.sync.jda.RoleAddedListener;
import dev.sheldan.abstracto.core.models.ConditionContextInstance;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.RoleAddedModel;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.RoleDisplay;
import dev.sheldan.abstracto.core.service.ConditionService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.service.SystemCondition;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.listener.HoneyPotReasonModel;
import dev.sheldan.abstracto.moderation.service.BanService;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.ISnowflake;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class HoneyPotRoleAddedListener implements RoleAddedListener {
@Autowired
private RoleService roleService;
private ConfigService configService;
@Autowired
private HoneyPotServiceBean honeyPotServiceBean;
private BanService banService;
@Autowired
private TemplateService templateService;
@Autowired
private ConditionService conditionService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private RoleService roleService;
private static final String HONEYPOT_BAN_REASON_TEMPLATE = "honeypot_ban_reason";
private static final String LEVEL_CONDITION_USER_ID_PARAMETER = "userId";
private static final String LEVEL_CONDITION_LEVEL_PARAMETER = "level";
private static final String LEVEL_CONDITION_SERVER_PARAMETER = "serverId";
private static final String LEVEL_CONDITION_NAME = "HAS_LEVEL";
@Override
public DefaultListenerResult execute(RoleAddedModel model) {
Long honeyPotRoleId = honeyPotServiceBean.getHoneyPotRoleId(model.getServerId());
Long honeyPotRoleId = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_ROLE_ID, model.getServerId());
if(honeyPotRoleId == 0) {
log.info("Server {} has honeypot feature enabled, but still default honeypot role config - Ignoring.", model.getServerId());
return DefaultListenerResult.IGNORED;
}
if(honeyPotRoleId.equals(model.getRoleId())) {
boolean fellIntoHoneyPot = honeyPotServiceBean.fellIntoHoneyPot(model.getServerId(), model.getTargetMember());
if (fellIntoHoneyPot) {
log.info("Banning user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId());
honeyPotServiceBean.banForHoneyPot(model.getTargetMember(), model.getRole());
} else {
log.info("User {} in server {} will not get banned by honeypot. All existing roles besides {} will be removed.",
model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), honeyPotRoleId);
Integer levelToSkipBan = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_IGNORED_LEVEL, model.getServerId()).intValue();
Long amountOfSecondsToIgnore = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_IGNORED_JOIN_DURATION_SECONDS, model.getServerId());
boolean allowed = userHasLevel(model.getTargetMember(), levelToSkipBan) || userJoinedLongerThanSeconds(model.getTargetMember(), amountOfSecondsToIgnore);
if(allowed) {
log.info("User {} in server {} has at least level {} or joined more than {} seconds ago and will not get banned by honeypot. All existing roles besides {} will be removed.",
model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), levelToSkipBan, amountOfSecondsToIgnore, honeyPotRoleId);
cleanupRolesBesidesHoneyPot(model, honeyPotRoleId);
} else {
log.info("Banning user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId());
HoneyPotReasonModel reasonModel = HoneyPotReasonModel
.builder()
.memberDisplay(MemberDisplay.fromMember(model.getTargetMember()))
.roleDisplay(RoleDisplay.fromRole(model.getRole()))
.build();
String banReason = templateService.renderTemplate(HONEYPOT_BAN_REASON_TEMPLATE, reasonModel, model.getServerId());
banService.banUserWithNotification(model.getTargetUser(), banReason, ServerUser.fromMember(model.getTargetMember().getGuild().getSelfMember()),
model.getTargetMember().getGuild(), Duration.ofDays(7)).thenAccept(banResult -> {
log.info("Banned user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId());
}).exceptionally(throwable -> {
log.error("Failed to ban user {} in guild {} due to role {}.", model.getTargetUser().getUserId(), model.getTargetUser().getServerId(), model.getRoleId(), throwable);
return null;
});
}
return DefaultListenerResult.PROCESSED;
} else {
@@ -73,6 +126,28 @@ public class HoneyPotRoleAddedListener implements RoleAddedListener {
}
}
private boolean userHasLevel(Member member, Integer level) {
log.info("Checking if member {} is ignored to click on the honeypot in server {}.", member.getIdLong(),member.getGuild().getIdLong());
Map<String, Object> parameters = new HashMap<>();
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member);
parameters.put(LEVEL_CONDITION_USER_ID_PARAMETER, userInAServer.getUserInServerId());
parameters.put(LEVEL_CONDITION_LEVEL_PARAMETER, level);
parameters.put(LEVEL_CONDITION_SERVER_PARAMETER, member.getGuild().getIdLong());
ConditionContextInstance contextInstance = ConditionContextInstance
.builder()
.conditionName(LEVEL_CONDITION_NAME)
.parameters(parameters)
.build();
SystemCondition.Result result = conditionService.checkConditions(contextInstance);
return SystemCondition.Result.isSuccessful(result);
}
private boolean userJoinedLongerThanSeconds(Member member, Long seconds) {
log.info("Checking if member {} joined the server more than {} seconds ago.", member.getIdLong(), seconds);
// the incorrectness of timejoined should not matter, we chunk anyway
return member.getTimeJoined().toInstant().isBefore(Instant.now().minus(seconds, ChronoUnit.SECONDS));
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.HONEYPOT;

View File

@@ -1,115 +0,0 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.models.ConditionContextInstance;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.RoleDisplay;
import dev.sheldan.abstracto.core.service.ConditionService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.SystemCondition;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.moderation.config.feature.HoneyPotFeatureConfig;
import dev.sheldan.abstracto.moderation.model.listener.HoneyPotReasonModel;
import dev.sheldan.abstracto.moderation.service.BanService;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class HoneyPotServiceBean {
@Autowired
private ConfigService configService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private ConditionService conditionService;
@Autowired
private TemplateService templateService;
@Autowired
private BanService banService;
@Autowired
private MemberService memberService;
private static final String LEVEL_CONDITION_USER_ID_PARAMETER = "userId";
private static final String LEVEL_CONDITION_LEVEL_PARAMETER = "level";
private static final String LEVEL_CONDITION_SERVER_PARAMETER = "serverId";
private static final String LEVEL_CONDITION_NAME = "HAS_LEVEL";
private static final String HONEYPOT_BAN_REASON_TEMPLATE = "honeypot_ban_reason";
public Long getHoneyPotRoleId(Long serverId) {
return configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_ROLE_ID, serverId);
}
public boolean fellIntoHoneyPot(Long serverId, Member member) {
Integer levelToSkipBan = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_IGNORED_LEVEL, serverId).intValue();
Long amountOfSecondsToIgnore = configService.getLongValueOrConfigDefault(HoneyPotFeatureConfig.HONEYPOT_IGNORED_JOIN_DURATION_SECONDS, serverId);
boolean allowed = userHasLevel(member, levelToSkipBan) || userJoinedLongerThanSeconds(member, amountOfSecondsToIgnore);
return !allowed;
}
public List<Member> getCurrentMembersWithHoneypotRole(Guild guild) {
return memberService.getMembersWithRole(guild.getIdLong(), getHoneyPotRoleId(guild.getIdLong()));
}
public CompletableFuture<Void> banForHoneyPot(Member targetMember, Role role) {
HoneyPotReasonModel reasonModel = HoneyPotReasonModel
.builder()
.memberDisplay(MemberDisplay.fromMember(targetMember))
.roleDisplay(RoleDisplay.fromRole(role))
.build();
ServerUser bannedUser = ServerUser.fromMember(targetMember);
String banReason = templateService.renderTemplate(HONEYPOT_BAN_REASON_TEMPLATE, reasonModel, bannedUser.getServerId());
long roleId = role.getIdLong();
return banService.banUserWithNotification(bannedUser, banReason, ServerUser.fromMember(targetMember.getGuild().getSelfMember()),
targetMember.getGuild(), Duration.ofDays(7)).thenAccept(banResult -> {
log.info("Banned user {} in guild {} due to role {}.", bannedUser.getUserId(), bannedUser.getServerId(), roleId);
}).exceptionally(throwable -> {
log.error("Failed to ban user {} in guild {} due to role {}.", bannedUser.getUserId(), bannedUser.getServerId(), roleId, throwable);
return null;
});
}
private boolean userHasLevel(Member member, Integer level) {
log.info("Checking if member {} is ignored to click on the honeypot in server {}.", member.getIdLong(),member.getGuild().getIdLong());
Map<String, Object> parameters = new HashMap<>();
AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member);
parameters.put(LEVEL_CONDITION_USER_ID_PARAMETER, userInAServer.getUserInServerId());
parameters.put(LEVEL_CONDITION_LEVEL_PARAMETER, level);
parameters.put(LEVEL_CONDITION_SERVER_PARAMETER, member.getGuild().getIdLong());
ConditionContextInstance contextInstance = ConditionContextInstance
.builder()
.conditionName(LEVEL_CONDITION_NAME)
.parameters(parameters)
.build();
SystemCondition.Result result = conditionService.checkConditions(contextInstance);
return SystemCondition.Result.isSuccessful(result);
}
private boolean userJoinedLongerThanSeconds(Member member, Long seconds) {
log.info("Checking if member {} joined the server more than {} seconds ago.", member.getIdLong(), seconds);
// the incorrectness of timejoined should not matter, we chunk anyway
return member.getTimeJoined().toInstant().isBefore(Instant.now().minus(seconds, ChronoUnit.SECONDS));
}
}

View File

@@ -51,7 +51,7 @@ public class KickModerationActionModalListener implements ModalInteractionListen
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getCustomId().equals(payload.getReasonInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);

View File

@@ -59,7 +59,7 @@ public class MuteModerationActionModalListener implements ModalInteractionListen
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getCustomId().equals(payload.getReasonInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
@@ -68,7 +68,7 @@ public class MuteModerationActionModalListener implements ModalInteractionListen
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getCustomId().equals(payload.getDurationInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getDurationInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);

View File

@@ -60,7 +60,7 @@ public class WarnModerationActionModalListener implements ModalInteractionListen
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getCustomId().equals(payload.getReasonInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getReasonInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);

View File

@@ -43,7 +43,7 @@ public class ReportContextModalListener implements ModalInteractionListener {
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getCustomId().equals(payload.getTextInputId()))
.filter(modalMapping -> modalMapping.getId().equals(payload.getTextInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);

View File

@@ -168,14 +168,14 @@ public class BanServiceBean implements BanService {
@Transactional
public CompletableFuture<Void> sendUnBanLogMessage(UserUnBannedLogModel model, Long serverId) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_UN_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
}
@Override
@Transactional
public CompletableFuture<Void> sendBanLogMessage(UserBannedLogModel model, Long serverId) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(USER_BANNED_NOTIFICATION_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModerationPostTarget.BAN_LOG, serverId));
}
}

View File

@@ -129,7 +129,7 @@ public class InfractionServiceBean implements InfractionService {
.build();
infractionLevelChangedListenerManager.sendInfractionLevelChangedEvent(newLevel, oldLevel, currentPoints, oldPoints, ServerUser.fromAUserInAServer(aUserInAServer));
MessageToSend messageToSend = templateService.renderEmbedTemplate(INFRACTION_NOTIFICATION_TEMPLATE_KEY, model, serverId);
return FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, InfractionPostTarget.INFRACTION_NOTIFICATION, serverId));
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, InfractionPostTarget.INFRACTION_NOTIFICATION, serverId));
} else {
return CompletableFuture.completedFuture(null);
}

View File

@@ -61,9 +61,7 @@ public class KickServiceBean implements KickService {
public CompletableFuture<Void> kickMember(Member kickedMember, Member kickingMember, String reason) {
Guild guild = kickedMember.getGuild();
log.info("Kicking user {} from guild {}", kickedMember.getUser().getIdLong(), guild.getIdLong());
CompletableFuture<Void> kickFuture = guild.kick(kickedMember)
.reason(reason)
.submit();
CompletableFuture<Void> kickFuture = guild.kick(kickedMember, reason).submit();
CompletableFuture<Message> logFuture = sendKickLog(kickedMember.getUser(), ServerUser.fromMember(kickedMember), kickingMember.getUser(), ServerUser.fromMember(kickingMember), reason, guild.getIdLong());
return CompletableFuture.allOf(kickFuture, logFuture)
.thenAccept(unused -> self.storeInfraction(kickedMember, kickingMember, reason, logFuture.join(), guild.getIdLong()));
@@ -127,7 +125,7 @@ public class KickServiceBean implements KickService {
public CompletableFuture<Message> sendKicklog(KickLogModel kickLogModel, Long serverId) {
MessageToSend warnLogMessage = templateService.renderEmbedTemplate(KICK_LOG_TEMPLATE, kickLogModel, serverId);
log.debug("Sending kick log message in guild {}.", serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, serverId).get(0);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join());
}

View File

@@ -320,7 +320,7 @@ public class MuteServiceBean implements MuteService {
@Override
public CompletableFuture<Void> sendMuteLogMessage(MuteLogModel model, Long serverId) {
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, model, serverId);
return FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, serverId));
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, serverId));
}
@Override

View File

@@ -114,7 +114,7 @@ public class ReactionReportServiceBean implements ReactionReportService {
.reportedMessage(reportedMessage)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(REACTION_REPORT_TEMPLATE_KEY, model, serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, ReactionReportPostTarget.REACTION_REPORTS, serverId).get(0);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, ReactionReportPostTarget.REACTION_REPORTS, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures)
.thenAccept(unused -> reportMessageCreatedListenerManager.sendReportMessageCreatedEvent(reportedMessage, messageFutures.get(0).join(), anonymous ? null : reporter))
.thenAccept(unused -> {

View File

@@ -120,7 +120,7 @@ public class WarnServiceBean implements WarnService {
.reason(reason)
.build();
MessageToSend message = renderMessageModel(warnContext, guild.getIdLong());
List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, guild.getIdLong()).get(0);
List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, guild.getIdLong());
return FutureUtils.toSingleFutureGeneric(futures).thenCompose(unused -> futures.get(0));
}
@@ -363,7 +363,7 @@ public class WarnServiceBean implements WarnService {
.warnings(warnDecayWarnings)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(WARN_DECAY_LOG_TEMPLATE_KEY, warnDecayLogModel, serverId);
return FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, WarnDecayPostTarget.DECAY_LOG, server.getId()));
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, WarnDecayPostTarget.DECAY_LOG, server.getId()));
}
@Override

View File

@@ -1,6 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -1,16 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<property name="moderationModule" value="(SELECT id FROM module WHERE name = 'moderation')"/>
<property name="honeypotFeature" value="(SELECT id FROM feature WHERE key = 'honeypot')"/>
<changeSet author="Sheldan" id="honeypotBan-command">
<insert tableName="command">
<column name="name" value="honeypotBan"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${honeypotFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -1,6 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -13,5 +13,4 @@
<include file="1.4.3/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.10/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.23/collection.xml" relativeToChangelogFile="true"/>
<include file="1.6.21/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,10 +0,0 @@
package dev.sheldan.abstracto.moderation.model.template.command;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class HoneyPotBanResponseModel {
private Integer bannedMemberCount;
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.6.23-SNAPSHOT</version>
<version>1.6.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -44,12 +44,6 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>metrics-int</artifactId>

View File

@@ -1,112 +0,0 @@
package dev.sheldan.abstracto.modmail.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandPrivilegeLevels;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SetThreadPause extends AbstractConditionableCommand {
private static final String SET_THREAD_PAUSE_COMMAND = "setThreadPause";
private static final String SET_THREAD_PAUSE_RESPONSE = "setThreadPause_response";
private static final String PAUSED_PARAMETER = "paused";
@Autowired
private ModMailContextCondition requiresModMailCondition;
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(event.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
Boolean paused = slashCommandParameterService.getCommandOption(PAUSED_PARAMETER, event, Boolean.class);
modMailThreadService.setPauseOfThreadTo(modMailThread, paused);
return interactionService.replyEmbed(SET_THREAD_PAUSE_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
Parameter newStateParameter = Parameter
.builder()
.name(PAUSED_PARAMETER)
.type(Boolean.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(newStateParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.defaultPrivilege(SlashCommandPrivilegeLevels.INVITER)
.commandName(SET_THREAD_PAUSE_COMMAND)
.build();
return CommandConfiguration.builder()
.name(SET_THREAD_PAUSE_COMMAND)
.slashCommandConfig(slashCommandConfig)
.module(ModMailModuleDefinition.MODMAIL)
.help(helpInfo)
.slashCommandOnly(true)
.supportsEmbedException(true)
.templated(true)
.parameters(parameters)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(requiresModMailCondition);
return conditions;
}
}

View File

@@ -1,115 +0,0 @@
package dev.sheldan.abstracto.modmail.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandPrivilegeLevels;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailSlashCommandNames;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadClosedException;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SnoozeThreadReminder extends AbstractConditionableCommand {
private static final String SNOOZE_THREAD_REMINDER_COMMAND = "snoozeThreadReminder";
private static final String SNOOZE_THREAD_REMINDER_RESPONSE = "snoozeThreadReminder_response";
private static final String DURATION_PARAMETER = "duration";
@Autowired
private ModMailContextCondition requiresModMailCondition;
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
ModMailThread modMailThread = modMailThreadManagementService.getByChannelId(event.getChannel().getIdLong());
if(ModMailThreadState.CLOSED.equals(modMailThread.getState()) || ModMailThreadState.CLOSING.equals(modMailThread.getState())) {
throw new ModMailThreadClosedException();
}
String durationString = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class);
Duration duration = ParseUtils.parseDuration(durationString);
modMailThreadService.snoozeThreadReminder(modMailThread, duration);
return interactionService.replyEmbed(SNOOZE_THREAD_REMINDER_RESPONSE, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
Parameter durationParameter = Parameter
.builder()
.name(DURATION_PARAMETER)
.type(Duration.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(durationParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModMailSlashCommandNames.MODMAIL)
.defaultPrivilege(SlashCommandPrivilegeLevels.INVITER)
.commandName(SNOOZE_THREAD_REMINDER_COMMAND)
.build();
return CommandConfiguration.builder()
.name(SNOOZE_THREAD_REMINDER_COMMAND)
.slashCommandConfig(slashCommandConfig)
.module(ModMailModuleDefinition.MODMAIL)
.help(helpInfo)
.slashCommandOnly(true)
.supportsEmbedException(true)
.templated(true)
.parameters(parameters)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
@Override
public List<CommandCondition> getConditions() {
List<CommandCondition> conditions = super.getConditions();
conditions.add(requiresModMailCondition);
return conditions;
}
}

View File

@@ -1,32 +0,0 @@
package dev.sheldan.abstracto.modmail.job;
import dev.sheldan.abstracto.modmail.service.ModMailThreadServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class ModmailThreadActionJob extends QuartzJobBean {
@Autowired
private ModMailThreadServiceBean modMailThreadServiceBean;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
log.info("Check modmail threads to perform action for.");
modMailThreadServiceBean.checkModmailActionsForNeededActions();
} catch (Exception exception) {
log.error("Modmail thread action job failed.", exception);
}
}
}

View File

@@ -1,117 +0,0 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailMode;
import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.model.listener.ModmailThreadActionListenerModel;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ModmailAutoCloseListener implements ModmailThreadActionListener {
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private ConfigService configService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private ModmailReminderListener self;
@Autowired
private GuildService guildService;
@Autowired
private TemplateService templateService;
private static final String AUTO_CLOSE_NOTE_TEMPLATE_KEY = "modmail_auto_closing_note_text";
@Override
public Integer getPriority() {
return ListenerPriority.HIGH;
}
@Override
public ModmailThreadActionListenerResult execute(ModmailThreadActionListenerModel model) {
ModmailThreadActionListenerResult result;
if(!featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, model.getServerId(), ModMailMode.THREAD_AUTO_CLOSE)) {
result = ModmailThreadActionListenerResult.IGNORED;
} else {
String closeDuration = configService.getStringValueOrConfigDefault(ModMailFeatureConfig.MOD_MAIL_AUTO_CLOSE_DURATION, model.getServerId());
Duration duration = ParseUtils.parseDuration(closeDuration);
Instant timeInPastDuration = Instant.now().minus(duration);
ModMailThread thread = modMailThreadManagementService.getById(model.getThreadId());
if(thread.getState() == ModMailThreadState.PAUSED) {
log.info("Thread {} is paused - not closing.", thread.getId());
return ModmailThreadActionListenerResult.IGNORED;
}
Instant timeStampToConsider = getTimeStampToConsider(thread);
boolean mustBeClosed = timeInPastDuration.isAfter(timeStampToConsider);
if (mustBeClosed) {
closeThread(thread)
.thenAccept(unused -> {
self.updateSnoozeTimer(model.getThreadId(), duration);
log.info("Automatically closed thread {}", model.getThreadId());
})
.exceptionally(throwable -> {
log.warn("Failed to automatically close thread {}.", model.getThreadId(), throwable);
return null;
});
result = ModmailThreadActionListenerResult.FINAL;
} else {
result = ModmailThreadActionListenerResult.IGNORED;
}
}
return result;
}
private static Instant getTimeStampToConsider(ModMailThread thread) {
if(thread.getLastUpdated() != null) {
return thread.getLastUpdated();
}
return thread.getCreated();
}
private CompletableFuture<Void> closeThread(ModMailThread modMailThread) {
Guild guild = guildService.getGuildById(modMailThread.getServer().getId());
if(guild != null) {
String closingNote = templateService.renderTemplate(AUTO_CLOSE_NOTE_TEMPLATE_KEY, new Object(), modMailThread.getServer().getId());
ClosingContext closingContext = ClosingContext
.builder()
.notifyUser(true)
.log(true)
.closingMember(guild.getSelfMember())
.note(closingNote)
.build();
return modMailThreadService.closeModMailThreadEvaluateLogging(modMailThread, closingContext, new ArrayList<>());
} else {
return CompletableFuture.completedFuture(null);
}
}
}

View File

@@ -1,144 +0,0 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.RoleDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.config.ModMailMode;
import dev.sheldan.abstracto.modmail.model.database.ModMailRole;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.model.database.ModMailThreadState;
import dev.sheldan.abstracto.modmail.model.listener.ModmailThreadActionListenerModel;
import dev.sheldan.abstracto.modmail.model.template.ModmailThreadReminderModel;
import dev.sheldan.abstracto.modmail.service.management.ModMailRoleManagementService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Slf4j
public class ModmailReminderListener implements ModmailThreadActionListener {
@Autowired
private ChannelService channelService;
@Autowired
private TemplateService templateService;
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@Autowired
private ConfigService configService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ModMailRoleManagementService modMailRoleManagementService;
@Autowired
private ModmailReminderListener self;
private static final String MODMAIL_THREAD_REMINDER_TEMPLATE_KEY = "modmail_thread_reminder_notification";
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public ModmailThreadActionListenerResult execute(ModmailThreadActionListenerModel model) {
ModmailThreadActionListenerResult result;
if(!featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, model.getServerId(), ModMailMode.THREAD_REMINDER)) {
result = ModmailThreadActionListenerResult.IGNORED;
} else {
String reminderDurationString = configService.getStringValueOrConfigDefault(ModMailFeatureConfig.MOD_MAIL_REMINDER_DURATION, model.getServerId());
Duration duration = ParseUtils.parseDuration(reminderDurationString);
Instant timeInPastDuration = Instant.now().minus(duration);
ModMailThread thread = modMailThreadManagementService.getById(model.getThreadId());
if(List.of(ModMailThreadState.CLOSED, ModMailThreadState.CLOSING).contains(thread.getState())) {
log.debug("Thread {} is closed - ignoring.", model.getThreadId());
return ModmailThreadActionListenerResult.IGNORED;
}
Instant timeStampToConsider = getTimestampToUse(thread);
boolean mustBeReminded = timeInPastDuration.isAfter(timeStampToConsider);
if (mustBeReminded) {
sendReminder(thread)
.thenAccept(unused -> {
self.updateSnoozeTimer(model.getThreadId(), duration);
log.info("Sent reminder about thread {}", model.getThreadId());
})
.exceptionally(throwable -> {
log.warn("Failed to send reminder about thread {}.", model.getThreadId(), throwable);
return null;
});
result = ModmailThreadActionListenerResult.PROCESSED;
} else {
result = ModmailThreadActionListenerResult.IGNORED;
}
}
return result;
}
private static Instant getTimestampToUse(ModMailThread thread) {
if (thread.getRemindersSnoozedUntil() != null) {
return thread.getRemindersSnoozedUntil();
}
return getUpdatedOrCrated(thread);
}
private static Instant getUpdatedOrCrated(ModMailThread thread) {
if(thread.getUpdated() != null) {
return thread.getUpdated();
}
return thread.getCreated();
}
private CompletableFuture<Void> sendReminder(ModMailThread modMailThread) {
List<ModMailRole> modmailRolesToPing = modMailRoleManagementService.getRolesForServer(modMailThread.getServer());
List<RoleDisplay> rolesToDisplay = modmailRolesToPing.stream().map(role -> RoleDisplay.fromARole(role.getRole())).toList();
Instant autoCloseInstant;
if(featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, modMailThread.getServer().getId(), ModMailMode.THREAD_AUTO_CLOSE)
&& !modMailThread.getState().equals(ModMailThreadState.PAUSED)) {
String closeDurationString = configService.getStringValueOrConfigDefault(ModMailFeatureConfig.MOD_MAIL_AUTO_CLOSE_DURATION, modMailThread.getServer().getId());
Duration autoCloseDuration = ParseUtils.parseDuration(closeDurationString);
autoCloseInstant = getUpdatedOrCrated(modMailThread).plus(autoCloseDuration);
} else {
autoCloseInstant = null;
}
ModmailThreadReminderModel model = ModmailThreadReminderModel
.builder()
.updated(getUpdatedOrCrated(modMailThread))
.created(modMailThread.getCreated())
.paused(modMailThread.getState().equals(ModMailThreadState.PAUSED))
.autoCloseInstant(autoCloseInstant)
.pingRoles(rolesToDisplay)
.memberDisplay(MemberDisplay.fromAUserInAServer(modMailThread.getUser()))
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(MODMAIL_THREAD_REMINDER_TEMPLATE_KEY, model, modMailThread.getServer().getId());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageEmbedToSendToAChannel(messageToSend, modMailThread.getChannel()));
}
@Transactional
public void updateSnoozeTimer(long modmailThreadId, Duration duration) {
ModMailThread thread = modMailThreadManagementService.getById(modmailThreadId);
thread.setRemindersSnoozedUntil(Instant.now().plus(duration));
}
}

View File

@@ -36,7 +36,6 @@ public interface ModMailThreadRepository extends JpaRepository<ModMailThread, Lo
boolean existsByUserAndStateNot(AUserInAServer userInAServer, ModMailThreadState state);
List<ModMailThread> findByUserAndState(AUserInAServer userInAServer, ModMailThreadState state);
List<ModMailThread> findByStateNot(ModMailThreadState state);
@Override
Optional<ModMailThread> findById(@NonNull Long aLong);

View File

@@ -15,7 +15,6 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.BeanUtils;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.utils.SnowflakeUtils;
@@ -27,11 +26,9 @@ import dev.sheldan.abstracto.modmail.config.ModMailPostTargets;
import dev.sheldan.abstracto.modmail.exception.ModMailCategoryIdException;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadChannelNotFound;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadNotFoundException;
import dev.sheldan.abstracto.modmail.listener.ModmailThreadActionListener;
import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.dto.ServiceChoicesPayload;
import dev.sheldan.abstracto.modmail.model.database.*;
import dev.sheldan.abstracto.modmail.model.listener.ModmailThreadActionListenerModel;
import dev.sheldan.abstracto.modmail.model.listener.ModmailThreadCreatedSendMessageModel;
import dev.sheldan.abstracto.modmail.model.template.*;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
@@ -53,7 +50,6 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
@@ -170,9 +166,6 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private List<ModmailThreadActionListener> threadActionListeners;
public static final String MODMAIL_THREAD_METRIC = "modmail.threads";
public static final String MODMAIL_MESSAGE_METRIC = "modmail.messges";
public static final String ACTION = "action";
@@ -276,45 +269,6 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction(MODMAIL_THREAD_CREATED_TEMPLATE_KEY, model, interactionHook));
}
@Transactional
public void checkModmailActionsForNeededActions() {
List<ModMailThread> allOpenThreads = modMailThreadManagementService.getAllOpenThreads();
allOpenThreads.forEach(thread -> {
ModmailThreadActionListenerModel model = ModmailThreadActionListenerModel
.builder()
.threadId(thread.getId())
.state(thread.getState())
.appeal(thread.getAppeal())
.serverId(thread.getServer().getId())
.serverUser(ServerUser.fromAUserInAServer(thread.getUser()))
.messageCount(thread.getMessages() != null ? thread.getMessages().size() : 0)
.updated(thread.getLastUpdated())
.created(thread.getCreated())
.subscriberCount(thread.getSubscribers() != null ? thread.getSubscribers().size() : 0)
.build();
for (ModmailThreadActionListener modmailThreadActionListener : threadActionListeners) {
try {
log.info("Executing action {} for thread {}.", modmailThreadActionListener.getClass().getSimpleName(), model.getThreadId());
ModmailThreadActionListener.ModmailThreadActionListenerResult result =
self.executeThreadAction(modmailThreadActionListener, model);
if(ModmailThreadActionListener.ModmailThreadActionListenerResult.FINAL == result) {
log.info("Listener {} terminated for thread {}.", modmailThreadActionListener.getClass().getSimpleName(), model.getThreadId());
break;
}
} catch (Exception exception) {
log.error("Action failed to execute for thread {}.", thread.getId(), exception);
}
}
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ModmailThreadActionListener.ModmailThreadActionListenerResult executeThreadAction(
ModmailThreadActionListener modmailThreadActionListener, ModmailThreadActionListenerModel model) {
return modmailThreadActionListener.execute(model);
}
/**
* This method is responsible for creating the instance in the database, sending the header in the newly created text channel and forwarding the initial message
* by the user (if any), after this is complete, this method executes the method to perform the mod mail notification.
@@ -378,7 +332,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
public CompletableFuture<Void> sendModMailNotification(User user, GuildMessageChannel channel, boolean appeal) {
Long serverId = channel.getGuild().getIdLong();
MessageToSend messageToSend = getModmailNotificationMessageToSend(user, channel, serverId, true, appeal);
return FutureUtils.toSingleFutureGenericList(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId));
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId));
}
private MessageToSend getModmailNotificationMessageToSend(User user, GuildMessageChannel channel, Long serverId, boolean pingRole, boolean appeal) {
@@ -854,27 +808,6 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
return isModMailThread(channel);
}
@Override
public void snoozeThreadReminder(ModMailThread thread, Duration snoozeDuration) {
Instant snoozeTargetDate = Instant.now().plus(snoozeDuration);
log.info("Snoozing Thread {} until {}.", thread.getId(), snoozeTargetDate);
thread.setRemindersSnoozedUntil(snoozeTargetDate);
}
@Override
public void setPauseOfThreadTo(ModMailThread thread, boolean paused) {
if(thread.getState().equals(ModMailThreadState.PAUSED) && !paused) {
thread.setState(thread.getPreviousState());
thread.setPreviousState(null);
}
if(!thread.getState().equals(ModMailThreadState.PAUSED) && paused) {
thread.setPreviousState(thread.getState());
thread.setState(ModMailThreadState.PAUSED);
}
log.info("Thread {} has state {} and previous state {}.", thread.getId(), thread.getState(), thread.getPreviousState());
}
/**
* This method takes the actively loaded futures, calls the method responsible for logging the messages, and calls the method
* after the logging has been done.
@@ -1090,7 +1023,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
ModMailThread modMailThread = modMailThreadManagementService.getById(modmailThreadId);
return channelService.sendMessageEmbedToSendToAChannel(messageToSend, modMailThread.getChannel());
} else {
return postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, model.getServerId()).get(0);
return postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, model.getServerId());
}
}
@@ -1131,7 +1064,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
ModMailLoggedMessageModel message = loadedMessages.get(i);
log.debug("Sending message {} of modmail thread {} to modmail log post target.", modMailThreadId, message.getMessage().getId());
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_logged_message", message, updateMessage.getGuild().getIdLong());
List<CompletableFuture<Message>> logFuture = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, updateMessage.getGuild().getIdLong()).get(0);
List<CompletableFuture<Message>> logFuture = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, updateMessage.getGuild().getIdLong());
if(i != 0 && (i % 10) == 0) {
progressModel.setLoggedMessages(i);
messageService.editMessageWithNewTemplate(updateMessage, MODMAIL_CLOSE_PROGRESS_TEMPLATE_KEY, progressModel);
@@ -1194,6 +1127,5 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
metricService.registerCounter(MODMAIL_THREAD_CLOSED_COUNTER, "Mod mail threads closed");
metricService.registerCounter(MDOMAIL_THREAD_MESSAGE_RECEIVED, "Mod mail messages received");
metricService.registerCounter(MDOMAIL_THREAD_MESSAGE_SENT, "Mod mail messages sent");
BeanUtils.sortPrioritizedListeners(threadActionListeners);
}
}

View File

@@ -89,11 +89,6 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
return modMailThreadRepository.findByUser(aUserInAServer);
}
@Override
public List<ModMailThread> getAllOpenThreads() {
return modMailThreadRepository.findByStateNot(ModMailThreadState.CLOSED);
}
@Override
public ModMailThread getLatestModMailThread(AUserInAServer aUserInAServer) {
return modMailThreadRepository.findTopByUserOrderByClosedDesc(aUserInAServer);
@@ -116,7 +111,6 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
.user(userInAServer)
.server(userInAServer.getServerReference())
.state(ModMailThreadState.INITIAL)
.lastUpdated(Instant.now())
.updated(Instant.now())
.appeal(appeal)
.build();
@@ -129,12 +123,7 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
@Override
public void setModMailThreadState(ModMailThread modMailThread, ModMailThreadState newState) {
if(modMailThread.getState().equals(ModMailThreadState.PAUSED)) {
modMailThread.setPreviousState(newState);
} else {
modMailThread.setState(newState);
}
modMailThread.setLastUpdated(Instant.now());
modMailThread.setState(newState);
modMailThread.setUpdated(Instant.now());
modMailThreadRepository.save(modMailThread);
}

View File

@@ -1,7 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -1,21 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<property name="modmailModule" value="(SELECT id FROM module WHERE name = 'modmail')"/>
<property name="modmailFeature" value="(SELECT id FROM feature WHERE key = 'modmail')"/>
<changeSet author="Sheldan" id="modmail_thread_action_commands">
<insert tableName="command">
<column name="name" value="snoozeThreadReminder"/>
<column name="module_id" valueComputed="${modmailModule}"/>
<column name="feature_id" valueComputed="${modmailFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="setThreadPause"/>
<column name="module_id" valueComputed="${modmailModule}"/>
<column name="feature_id" valueComputed="${modmailFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -1,7 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="modmail_action_job.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -1,15 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="modmail_action_job-insert">
<insert tableName="scheduler_job">
<column name="name" value="modmailActionJob"/>
<column name="group_name" value="modmail"/>
<column name="clazz" value="dev.sheldan.abstracto.modmail.job.ModmailThreadActionJob"/>
<column name="active" value="true"/>
<column name="cron_expression" value="0 0 0 * * ?"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -1,15 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="mod_mail_thread-add_columns_for_modmail_thread_actions">
<addColumn tableName="mod_mail_thread">
<column name="reminders_snoozed_until" type="TIMESTAMP WITHOUT TIME ZONE" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="true" />
</column>
<column name="previous_state" type="VARCHAR(255)">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -1,6 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="modmail_thread.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -1,6 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -1,12 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="mod_mail_thread-add_last_updated_column">
<addColumn tableName="mod_mail_thread">
<column name="last_updated" type="TIMESTAMP WITHOUT TIME ZONE" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -1,6 +0,0 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="modmail_thread.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -5,6 +5,4 @@
<include file="1.0-modmail/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.37/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.51/collection.xml" relativeToChangelogFile="true"/>
<include file="1.6.22/collection.xml" relativeToChangelogFile="true"/>
<include file="1.6.23/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -4,12 +4,6 @@ abstracto.systemConfigs.modMailClosingText.stringValue=Thread has been closed.
abstracto.systemConfigs.modmailCategory.name=modmailCategory
abstracto.systemConfigs.modmailCategory.longValue=0
abstracto.systemConfigs.modMailReminderDuration.name=modMailReminderDuration
abstracto.systemConfigs.modMailReminderDuration.stringValue=3d
abstracto.systemConfigs.modMailAutoCloseDuration.name=modMailAutoCloseDuration
abstracto.systemConfigs.modMailAutoCloseDuration.stringValue=14d
abstracto.featureFlags.modmail.featureName=modmail
abstracto.featureFlags.modmail.enabled=false
@@ -34,12 +28,4 @@ abstracto.featureModes.modMailAppeals.mode=modMailAppeals
abstracto.featureModes.modMailAppeals.enabled=false
abstracto.systemConfigs.modMailAppealServer.name=modMailAppealServer
abstracto.systemConfigs.modMailAppealServer.longValue=0
abstracto.featureModes.threadReminder.featureName=modmail
abstracto.featureModes.threadReminder.mode=threadReminder
abstracto.featureModes.threadReminder.enabled=false
abstracto.featureModes.threadAutoClose.featureName=modmail
abstracto.featureModes.threadAutoClose.mode=threadAutoClose
abstracto.featureModes.threadAutoClose.enabled=false
abstracto.systemConfigs.modMailAppealServer.longValue=0

Some files were not shown because too many files have changed in this diff Show More