Skip to content

Commit 80e34b7

Browse files
Merge pull request #303 from andrewlalis/andrew/vote_listener
Added Abstract Vote Listener to Simplify ShareKnowledge and Job Vote Listeners
2 parents 5ac661f + 41e3a73 commit 80e34b7

File tree

3 files changed

+206
-102
lines changed

3 files changed

+206
-102
lines changed
Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,26 @@
11
package net.javadiscord.javabot.listener;
22

3-
import net.dv8tion.jda.api.entities.MessageReaction;
4-
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
5-
import net.dv8tion.jda.api.hooks.ListenerAdapter;
3+
import net.dv8tion.jda.api.entities.Guild;
4+
import net.dv8tion.jda.api.entities.TextChannel;
65
import net.javadiscord.javabot.Bot;
7-
import org.jetbrains.annotations.NotNull;
86

97
/**
108
* Listens for reactions in #looking-for-programmer.
119
* Automatically deletes messages below a certain score.
1210
*/
13-
public class JobChannelVoteListener extends ListenerAdapter {
11+
public class JobChannelVoteListener extends MessageVoteListener {
1412
@Override
15-
public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
16-
if (event.getUser() == null || event.getUser().isBot() || event.getUser().isSystem()) return;
17-
var config = Bot.config.get(event.getGuild());
18-
if (!event.getReactionEmote().isEmoji() || !event.getReactionEmote().getAsReactionCode().equals(config.getEmote().getJobChannelVoteEmoji())) return;
19-
if (!event.getTextChannel().equals(config.getModeration().getJobChannel())) return;
20-
event.getChannel().retrieveMessageById(event.getMessageId()).queue(message -> {
21-
int votes = message
22-
.getReactions()
23-
.stream()
24-
.filter(reaction -> reaction.getReactionEmote().getName().equals(config.getEmote().getJobChannelVoteEmoji()))
25-
.findFirst()
26-
.map(MessageReaction::getCount)
27-
.orElse(0);
28-
if (votes >= config.getModeration().getJobChannelMessageDeleteThreshold()) {
29-
message.delete().queue();
30-
message.getAuthor().openPrivateChannel()
31-
.queue(
32-
s -> s.sendMessageFormat("Your message in %s has been removed due to community feedback.", config.getModeration().getJobChannel().getAsMention()).queue(),
33-
e -> {}
34-
);
35-
}
36-
});
13+
protected TextChannel getChannel(Guild guild) {
14+
return Bot.config.get(guild).getModeration().getJobChannel();
15+
}
16+
17+
@Override
18+
protected int getMessageDeleteVoteThreshold(Guild guild) {
19+
return Bot.config.get(guild).getModeration().getJobChannelMessageDeleteThreshold();
20+
}
21+
22+
@Override
23+
protected boolean shouldAddInitialEmotes(Guild guild) {
24+
return false;
3725
}
3826
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package net.javadiscord.javabot.listener;
2+
3+
import net.dv8tion.jda.api.entities.*;
4+
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
5+
import net.dv8tion.jda.api.events.message.react.GenericMessageReactionEvent;
6+
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
7+
import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent;
8+
import net.dv8tion.jda.api.hooks.ListenerAdapter;
9+
import net.javadiscord.javabot.Bot;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
/**
13+
* Generic listener that can be extended to add the ability for users to vote
14+
* on whether a message should stay in the channel.
15+
*/
16+
public abstract class MessageVoteListener extends ListenerAdapter {
17+
/**
18+
* Gets the text channel in which this vote listener operates.
19+
*
20+
* @param guild The guild to get the channel for.
21+
* @return The text channel that this vote listener should listen in.
22+
*/
23+
protected abstract TextChannel getChannel(Guild guild);
24+
25+
/**
26+
* Gets the threshold needed to remove a message. If a message has <code>U</code>
27+
* upvotes and <code>D</code> downvotes, we compute the difference as
28+
* <code>D - U</code> to get a number that indicates how many more
29+
* downvotes there are than upvotes. If this value is higher than or equal
30+
* to the threshold value returned by this method, the message is deleted.
31+
* <p>
32+
* Note that usually, you want to return a positive value, to indicate
33+
* that the message should have <em>more</em> downvotes than upvotes.
34+
* </p>
35+
*
36+
* @param guild The guild to get the threshold for.
37+
* @return The delete threshold value.
38+
*/
39+
protected int getMessageDeleteVoteThreshold(Guild guild) {
40+
return 5;
41+
}
42+
43+
/**
44+
* Determines if a given message is eligible for voting. Only eligible
45+
* messages will have voting reactions applied.
46+
*
47+
* @param message The message to check.
48+
* @return True if the message is eligible for voting, or false if not.
49+
*/
50+
protected boolean isMessageEligibleForVoting(Message message) {
51+
return true;
52+
}
53+
54+
/**
55+
* Gets the emote that's used for casting upvotes.
56+
*
57+
* @param guild The guild to get the emote for.
58+
* @return The emote.
59+
*/
60+
protected Emote getUpvoteEmote(Guild guild) {
61+
return Bot.config.get(guild).getEmote().getUpvoteEmote();
62+
}
63+
64+
/**
65+
* Gets the emote that's used for casting downvotes.
66+
*
67+
* @param guild The guild to get the emote for.
68+
* @return The emote.
69+
*/
70+
protected Emote getDownvoteEmote(Guild guild) {
71+
return Bot.config.get(guild).getEmote().getDownvoteEmote();
72+
}
73+
74+
/**
75+
* Whether the bot should add the first upvote and downvote emotes to
76+
* messages that are eligible for voting.
77+
*
78+
* @param guild The guild to get this setting for.
79+
* @return True if the bot should add emotes.
80+
*/
81+
protected boolean shouldAddInitialEmotes(Guild guild) {
82+
return true;
83+
}
84+
85+
@Override
86+
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
87+
if (isMessageReceivedEventValid(event) && shouldAddInitialEmotes(event.getGuild())) {
88+
event.getMessage().addReaction(getUpvoteEmote(event.getGuild())).queue();
89+
event.getMessage().addReaction(getDownvoteEmote(event.getGuild())).queue();
90+
}
91+
}
92+
93+
@Override
94+
public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
95+
Bot.asyncPool.submit(() -> handleReactionEvent(event));
96+
}
97+
98+
@Override
99+
public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) {
100+
Bot.asyncPool.submit(() -> handleReactionEvent(event));
101+
}
102+
103+
/**
104+
* Checks if a message received event is valid for this vote listener.
105+
*
106+
* @param event The event to check.
107+
* @return True if the event is valid, meaning that it is relevant for this
108+
* vote listener to add the voting emotes to it.
109+
*/
110+
private boolean isMessageReceivedEventValid(MessageReceivedEvent event) {
111+
if (event.getAuthor().isBot() || event.getAuthor().isSystem() || event.getMessage().getType() == MessageType.THREAD_CREATED) {
112+
return false;
113+
}
114+
return event.getChannel().getId().equals(getChannel(event.getGuild()).getId()) &&
115+
isMessageEligibleForVoting(event.getMessage());
116+
}
117+
118+
/**
119+
* Checks if a reaction event is valid for this vote listener. Note that
120+
* this method may use blocking calls to check if the user who sent the
121+
* reaction is valid.
122+
*
123+
* @param event The event to check.
124+
* @return True if the event is valid, meaning that this listener should
125+
* proceed to check the votes on the message.
126+
*/
127+
private boolean isReactionEventValid(GenericMessageReactionEvent event) {
128+
if (!event.getChannel().getId().equals(getChannel(event.getGuild()).getId())) return false;
129+
String reactionId = event.getReactionEmote().getId();
130+
if (
131+
!reactionId.equals(getUpvoteEmote(event.getGuild()).getId()) &&
132+
!reactionId.equals(getDownvoteEmote(event.getGuild()).getId())
133+
) {
134+
return false;
135+
}
136+
137+
User user = event.retrieveUser().complete();
138+
return !user.isBot() && !user.isSystem();
139+
}
140+
141+
/**
142+
* Handles voting reaction events, including both the addition and removal
143+
* of votes. Note that this is a blocking method.
144+
*
145+
* @param event The reaction event to handle.
146+
*/
147+
private void handleReactionEvent(GenericMessageReactionEvent event) {
148+
if (isReactionEventValid(event)) {
149+
Message message = event.retrieveMessage().complete();
150+
if (isMessageEligibleForVoting(message)) {
151+
checkVotes(message, event.getGuild());
152+
}
153+
}
154+
}
155+
156+
private void checkVotes(Message msg, Guild guild) {
157+
String upvoteId = getUpvoteEmote(guild).getId();
158+
String downvoteId = getDownvoteEmote(guild).getId();
159+
160+
int upvotes = countReactions(msg, upvoteId);
161+
int downvotes = countReactions(msg, downvoteId);
162+
int downvoteDifference = downvotes - upvotes;
163+
164+
if (downvoteDifference >= getMessageDeleteVoteThreshold(guild)) {
165+
msg.delete().queue();
166+
msg.getAuthor().openPrivateChannel()
167+
.queue(
168+
s -> s.sendMessageFormat(
169+
"Your message in %s has been removed due to community feedback.",
170+
getChannel(guild).getAsMention()
171+
).queue(),
172+
e -> {}
173+
);
174+
}
175+
}
176+
177+
private int countReactions(Message msg, String id) {
178+
MessageReaction reaction = msg.getReactionById(id);
179+
if (reaction == null) return 0;
180+
return (int) reaction.retrieveUsers().stream()
181+
.filter(user -> !user.isBot() && !user.isSystem())
182+
.count();
183+
}
184+
}
Lines changed: 7 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,21 @@
11
package net.javadiscord.javabot.listener;
22

3-
import net.dv8tion.jda.api.entities.ChannelType;
4-
import net.dv8tion.jda.api.entities.MessageReaction;
5-
import net.dv8tion.jda.api.entities.MessageType;
6-
import net.dv8tion.jda.api.events.message.GenericMessageEvent;
7-
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
8-
import net.dv8tion.jda.api.events.message.react.GenericMessageReactionEvent;
9-
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
10-
import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent;
11-
import net.dv8tion.jda.api.hooks.ListenerAdapter;
3+
import net.dv8tion.jda.api.entities.Guild;
4+
import net.dv8tion.jda.api.entities.TextChannel;
125
import net.javadiscord.javabot.Bot;
13-
import org.jetbrains.annotations.NotNull;
146

157
/**
168
* Listens for messages and reactions in #share-knowledge.
179
* Automatically deletes messages below a certain score.
1810
*/
19-
public class ShareKnowledgeVoteListener extends ListenerAdapter {
11+
public class ShareKnowledgeVoteListener extends MessageVoteListener {
2012
@Override
21-
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
22-
if (isInvalidEvent(event)) return;
23-
var config = Bot.config.get(event.getGuild());
24-
25-
// add upvote and downvote option
26-
event.getMessage().addReaction(config.getEmote().getUpvoteEmote()).queue();
27-
event.getMessage().addReaction(config.getEmote().getDownvoteEmote()).queue();
13+
protected TextChannel getChannel(Guild guild) {
14+
return Bot.config.get(guild).getModeration().getShareKnowledgeChannel();
2815
}
2916

3017
@Override
31-
public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
32-
onReactionEvent(event);
33-
}
34-
35-
@Override
36-
public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) {
37-
onReactionEvent(event);
38-
}
39-
40-
private boolean isInvalidEvent(GenericMessageEvent genericEvent) {
41-
if (genericEvent instanceof MessageReceivedEvent event &&
42-
(event.getAuthor().isBot() || event.getAuthor().isSystem() || event.getMessage().getType() == MessageType.THREAD_CREATED)) {
43-
return true;
44-
}
45-
if (genericEvent instanceof MessageReactionAddEvent raEvent && raEvent.getUser() != null && (raEvent.getUser().isBot() || raEvent.getUser().isSystem())) {
46-
return true;
47-
}
48-
if (genericEvent.getChannelType() == ChannelType.PRIVATE) return true;
49-
return !genericEvent.getChannel().equals(Bot.config.get(genericEvent.getGuild()).getModeration().getShareKnowledgeChannel());
50-
}
51-
52-
private void onReactionEvent(GenericMessageReactionEvent event) {
53-
if (event.getUser() == null || event.getUser().isBot() || event.getUser().isSystem()) return;
54-
if (isInvalidEvent(event)) return;
55-
var config = Bot.config.get(event.getGuild());
56-
57-
String reactionId = event.getReaction().getReactionEmote().getId();
58-
String upvoteId = config.getEmote().getUpvoteEmote().getId();
59-
String downvoteId = config.getEmote().getDownvoteEmote().getId();
60-
61-
if (!(reactionId.equals(upvoteId) || reactionId.equals(downvoteId))) return;
62-
String messageId = event.getMessageId();
63-
event.getChannel().retrieveMessageById(messageId).queue(message -> {
64-
int upvotes = message
65-
.getReactions()
66-
.stream()
67-
.filter(reaction -> reaction.getReactionEmote().getId().equals(upvoteId))
68-
.findFirst()
69-
.map(MessageReaction::getCount)
70-
.orElse(0);
71-
int downvotes = message
72-
.getReactions()
73-
.stream()
74-
.filter(reaction -> reaction.getReactionEmote().getId().equals(downvoteId))
75-
.findFirst()
76-
.map(MessageReaction::getCount)
77-
.orElse(0);
78-
int eval = downvotes - upvotes;
79-
if (eval >= config.getModeration().getShareKnowledgeMessageDeleteThreshold()) {
80-
message.delete().queue();
81-
message.getAuthor().openPrivateChannel()
82-
.queue(
83-
s -> s.sendMessageFormat("Your message in %s has been removed due to community feedback.", config.getModeration().getShareKnowledgeChannel().getAsMention()).queue(),
84-
e -> {}
85-
);
86-
}
87-
});
18+
protected int getMessageDeleteVoteThreshold(Guild guild) {
19+
return Bot.config.get(guild).getModeration().getShareKnowledgeMessageDeleteThreshold();
8820
}
8921
}

0 commit comments

Comments
 (0)