-
-
Notifications
You must be signed in to change notification settings - Fork 89
Feat/purge messages upto 24 hrs in current channel #982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ankitsmt211
wants to merge
35
commits into
Together-Java:develop
Choose a base branch
from
ankitsmt211:feat/delete-last-hour-messages
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
1c12b9d
new table to hold message history
ankitsmt211 d368324
add package for purge command feature
ankitsmt211 79277c9
message listener to store message data in db
ankitsmt211 093aa32
add routine to clear message history and purge command
ankitsmt211 bde724c
adding to features
ankitsmt211 f6757a9
refactor based on suggestions
ankitsmt211 357d41d
refactor variables
ankitsmt211 b25ee1e
fix doc
ankitsmt211 a6627ba
improve doc
ankitsmt211 117c0cc
method to ignore messages from bot or webhooks
ankitsmt211 650388e
refactor stream using try-with-resource
ankitsmt211 ec443d3
updated catch block
ankitsmt211 5feb1e6
add try-with-resource and logger
ankitsmt211 0bdc627
minor improvements
ankitsmt211 672d319
method to handle reasons
ankitsmt211 c3f3532
minor improvements
ankitsmt211 c6e81af
update privacy policy
ankitsmt211 45bda14
improved message for user and delete already pulled records
ankitsmt211 d38d874
adds option for duration, message expiration from db increased
ankitsmt211 b9f5aed
add an atomic counter as safety brake
ankitsmt211 0700661
refactor variable name
ankitsmt211 8e80ba0
refactor counter methods
ankitsmt211 20a2eb3
update record limit from 7 to 7.5k
ankitsmt211 fedb8c6
refactor classes to be final
ankitsmt211 53a5b72
log level from warn to debug on record limit
ankitsmt211 cdc511f
Merge branch 'develop' into feat/delete-last-hour-messages
ankitsmt211 5813ae3
auto trim routine and minor improvements
ankitsmt211 9a2c3da
add choices for duration, better UX
ankitsmt211 10d3664
add duration enum for constants and fix
ankitsmt211 ec27d05
writes should happen below limit
ankitsmt211 6f7f08d
refactoring to a better name for clarity
ankitsmt211 abe1e89
Merge branch 'develop' into feat/delete-last-hour-messages
ankitsmt211 9bd90cd
refactor better names for roles & replacing get(0) with getFirst()
ankitsmt211 671d92a
refactor enum Duration for clarity
ankitsmt211 1f6209e
add doc
ankitsmt211 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
application/src/main/java/org/togetherjava/tjbot/features/moderation/history/Duration.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package org.togetherjava.tjbot.features.moderation.history; | ||
|
||
/** | ||
* Holds duration constants used for both purge history command and message history routine. | ||
*/ | ||
public enum Duration { | ||
// All new/existing options for duration in PurgeHistoryCommand should be within max and min | ||
// duration | ||
PURGE_HISTORY_MAX_DURATION(24), | ||
PURGE_HISTORY_MIN_DURATION(1), | ||
PURGE_HISTORY_THREE_HOURS(3), | ||
PURGE_HISTORY_SIX_HOURS(6), | ||
PURGE_HISTORY_TWELVE_HOURS(12), | ||
PURGE_HISTORY_TWENTY_FOUR_HOURS(24); | ||
ankitsmt211 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private final int hours; | ||
|
||
Duration(int hours) { | ||
this.hours = hours; | ||
} | ||
|
||
public int getHours() { | ||
return hours; | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
...n/java/org/togetherjava/tjbot/features/moderation/history/PurgeExpiredMessageHistory.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.togetherjava.tjbot.features.moderation.history; | ||
|
||
import net.dv8tion.jda.api.JDA; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import org.togetherjava.tjbot.db.Database; | ||
import org.togetherjava.tjbot.db.generated.tables.records.MessageHistoryRecord; | ||
import org.togetherjava.tjbot.features.Routine; | ||
|
||
import java.time.Instant; | ||
import java.time.temporal.ChronoUnit; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.stream.Stream; | ||
|
||
import static org.togetherjava.tjbot.db.generated.Tables.MESSAGE_HISTORY; | ||
|
||
/** | ||
* Routine that deletes records from message_history post expiration hours. | ||
*/ | ||
public final class PurgeExpiredMessageHistory implements Routine { | ||
private static final Logger logger = LoggerFactory.getLogger(PurgeExpiredMessageHistory.class); | ||
private static final int SCHEDULE_INTERVAL_SECONDS = 30; | ||
private static final int EXPIRATION_HOURS = Duration.PURGE_HISTORY_MAX_DURATION.getHours(); | ||
private final Database database; | ||
|
||
/** | ||
* Creates a new instance. | ||
* | ||
* @param database the database that contains records of messages to be purged. | ||
*/ | ||
public PurgeExpiredMessageHistory(Database database) { | ||
this.database = database; | ||
} | ||
|
||
|
||
@Override | ||
public Schedule createSchedule() { | ||
return new Schedule(ScheduleMode.FIXED_RATE, 0, SCHEDULE_INTERVAL_SECONDS, | ||
TimeUnit.SECONDS); | ||
} | ||
|
||
@Override | ||
public void runRoutine(JDA jda) { | ||
Instant now = Instant.now(); | ||
Instant preExpirationHours = now.minus(EXPIRATION_HOURS, ChronoUnit.HOURS); | ||
|
||
Stream<MessageHistoryRecord> messageRecords = | ||
database.writeAndProvide(context -> context.selectFrom(MESSAGE_HISTORY) | ||
.where(MESSAGE_HISTORY.SENT_AT.lessThan(preExpirationHours)) | ||
.stream()); | ||
|
||
try (messageRecords) { | ||
messageRecords.forEach(messageRecord -> { | ||
messageRecord.delete(); | ||
PurgeMessageListener.decrementRecordsCounterByOne(); | ||
}); | ||
} catch (Exception exception) { | ||
logger.error( | ||
"Unknown error happened during delete operation during message history routine", | ||
exception); | ||
} | ||
} | ||
} |
174 changes: 174 additions & 0 deletions
174
...src/main/java/org/togetherjava/tjbot/features/moderation/history/PurgeHistoryCommand.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package org.togetherjava.tjbot.features.moderation.history; | ||
|
||
import net.dv8tion.jda.api.entities.Member; | ||
import net.dv8tion.jda.api.entities.Role; | ||
import net.dv8tion.jda.api.entities.User; | ||
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; | ||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; | ||
import net.dv8tion.jda.api.interactions.InteractionHook; | ||
import net.dv8tion.jda.api.interactions.commands.OptionMapping; | ||
import net.dv8tion.jda.api.interactions.commands.OptionType; | ||
import net.dv8tion.jda.api.interactions.commands.build.OptionData; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import org.togetherjava.tjbot.db.Database; | ||
import org.togetherjava.tjbot.db.DatabaseException; | ||
import org.togetherjava.tjbot.db.generated.tables.records.MessageHistoryRecord; | ||
import org.togetherjava.tjbot.features.CommandVisibility; | ||
import org.togetherjava.tjbot.features.SlashCommandAdapter; | ||
|
||
import java.time.Instant; | ||
import java.time.temporal.ChronoUnit; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.Stream; | ||
|
||
import static org.togetherjava.tjbot.db.generated.Tables.MESSAGE_HISTORY; | ||
|
||
public final class PurgeHistoryCommand extends SlashCommandAdapter { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(PurgeHistoryCommand.class); | ||
ankitsmt211 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static final String USER_OPTION = "user"; | ||
private static final String REASON_OPTION = "reason"; | ||
private static final String COMMAND_NAME = "purge-in-channel"; | ||
private static final int PURGE_MESSAGES_DEFAULT_DURATION = | ||
Duration.PURGE_HISTORY_MIN_DURATION.getHours(); | ||
private static final String DURATION = "duration"; | ||
private final Database database; | ||
|
||
/** | ||
* Creates an instance of the command. | ||
* | ||
* @param database the database to retrieve message records of a target user | ||
*/ | ||
|
||
public PurgeHistoryCommand(Database database) { | ||
super(COMMAND_NAME, "purge message history of user in same channel", | ||
CommandVisibility.GUILD); | ||
|
||
String optionUserDescription = "user who's message history you want to purge within %s hr" | ||
.formatted(PURGE_MESSAGES_DEFAULT_DURATION); | ||
|
||
String optionDurationDescription = | ||
"duration in hours (default: %s)".formatted(PURGE_MESSAGES_DEFAULT_DURATION); | ||
|
||
OptionData durationData = | ||
new OptionData(OptionType.INTEGER, DURATION, optionDurationDescription, false); | ||
durationData.addChoice("last 3 hrs", Duration.PURGE_HISTORY_THREE_HOURS.getHours()) | ||
.addChoice("last 6 hrs", Duration.PURGE_HISTORY_SIX_HOURS.getHours()) | ||
.addChoice("last 12 hrs", Duration.PURGE_HISTORY_TWELVE_HOURS.getHours()) | ||
.addChoice("last 24 hrs", Duration.PURGE_HISTORY_TWENTY_FOUR_HOURS.getHours()); | ||
|
||
getData().addOption(OptionType.USER, USER_OPTION, optionUserDescription, true) | ||
.addOption(OptionType.STRING, REASON_OPTION, | ||
"reason for purging user's message history", true) | ||
.addOptions(durationData); | ||
this.database = database; | ||
} | ||
|
||
@Override | ||
public void onSlashCommand(SlashCommandInteractionEvent event) { | ||
event.deferReply(true).queue(); | ||
|
||
OptionMapping targetOption = | ||
tj-wazei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Objects.requireNonNull(event.getOption(USER_OPTION), "target is null"); | ||
Member author = Objects.requireNonNull(event.getMember(), "author is null"); | ||
String reason = Objects.requireNonNull(event.getOption(REASON_OPTION).getAsString(), | ||
"reason is null"); | ||
|
||
OptionMapping durationMapping = event.getOption(DURATION); | ||
int duration = durationMapping == null ? PURGE_MESSAGES_DEFAULT_DURATION | ||
tj-wazei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
: durationMapping.getAsInt(); | ||
|
||
handleHistory(event, author, reason, targetOption, event.getHook(), duration); | ||
} | ||
|
||
private void handleHistory(SlashCommandInteractionEvent event, Member author, String reason, | ||
OptionMapping targetOption, InteractionHook hook, int duration) { | ||
Instant now = Instant.now(); | ||
Instant purgeMessagesAfter = now.minus(duration, ChronoUnit.HOURS); | ||
|
||
User targetUser = targetOption.getAsUser(); | ||
String sourceChannelId = event.getChannel().getId(); | ||
|
||
if (!validateHierarchy(author, targetOption)) { | ||
hook.sendMessage("Cannot purge history of user with a higher role than you").queue(); | ||
return; | ||
} | ||
|
||
List<String> messageIdsForDeletion = new ArrayList<>(); | ||
|
||
Stream<MessageHistoryRecord> fetchedMessageHistory = | ||
database.writeAndProvide(context -> context.selectFrom(MESSAGE_HISTORY) | ||
.where(MESSAGE_HISTORY.AUTHOR_ID.equal(targetUser.getIdLong()) | ||
.and(MESSAGE_HISTORY.CHANNEL_ID.equal(Long.valueOf(sourceChannelId))) | ||
.and(MESSAGE_HISTORY.SENT_AT.greaterOrEqual(purgeMessagesAfter))) | ||
.stream()); | ||
|
||
try (fetchedMessageHistory) { | ||
fetchedMessageHistory.forEach(messageHistoryRecord -> { | ||
String messageId = String.valueOf(messageHistoryRecord.getMessageId()); | ||
messageIdsForDeletion.add(messageId); | ||
messageHistoryRecord.delete(); | ||
|
||
PurgeMessageListener.decrementRecordsCounterByOne(); | ||
}); | ||
} catch (DatabaseException exception) { | ||
logger.error("unknown error during fetching message history records for {} command", | ||
COMMAND_NAME, exception); | ||
} | ||
|
||
handleDelete(messageIdsForDeletion, event.getChannel(), event.getHook(), targetUser, reason, | ||
author, duration); | ||
} | ||
|
||
private void handleDelete(List<String> messageIdsForDeletion, MessageChannelUnion channel, | ||
InteractionHook hook, User targetUser, String reason, Member author, int duration) { | ||
|
||
if (messageIdsForDeletion.isEmpty()) { | ||
handleEmptyMessageHistory(hook, targetUser, duration); | ||
return; | ||
} | ||
|
||
if (hasSingleElement(messageIdsForDeletion)) { | ||
ankitsmt211 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
String messageId = messageIdsForDeletion.get(0); | ||
String messageForMod = "message purged from user %s in this channel within last %s hr." | ||
.formatted(targetUser.getName(), duration); | ||
channel.deleteMessageById(messageId).queue(); | ||
hook.sendMessage(messageForMod).queue(); | ||
return; | ||
} | ||
|
||
int noOfMessagePurged = messageIdsForDeletion.size(); | ||
channel.purgeMessagesById(messageIdsForDeletion); | ||
|
||
String messageForMod = "%s messages purged from user %s in this channel within last %s hrs." | ||
.formatted(noOfMessagePurged, targetUser.getName(), duration); | ||
hook.sendMessage(messageForMod) | ||
.queue(onSuccess -> logger.info("{} purged messages from {} in {} because: {}", | ||
author.getUser(), targetUser, channel.getName(), reason)); | ||
} | ||
|
||
private boolean hasSingleElement(List<String> messageIdsForDeletion) { | ||
return messageIdsForDeletion.size() == 1; | ||
} | ||
|
||
private void handleEmptyMessageHistory(InteractionHook hook, User targetUser, int duration) { | ||
String messageForMod = "%s has no message history in this channel within last %s hrs." | ||
.formatted(targetUser.getName(), duration); | ||
|
||
hook.sendMessage(messageForMod).queue(); | ||
} | ||
|
||
private boolean validateHierarchy(Member author, OptionMapping target) { | ||
Role targetUserHighestRole = Objects | ||
.requireNonNull(target.getAsMember(), "target user for purge command is not a member") | ||
.getRoles() | ||
.getFirst(); | ||
Role authorHighestRole = author.getRoles().getFirst(); | ||
|
||
return targetUserHighestRole.getPosition() >= authorHighestRole.getPosition(); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.