Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

Commit 65b133a

Browse files
committed
Added automated unmuting.
1 parent 69d6302 commit 65b133a

File tree

3 files changed

+106
-15
lines changed

3 files changed

+106
-15
lines changed

src/main/java/net/javadiscord/javabot2/Bot.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ private static MongoDatabase initMongoDatabase() {
109109
}
110110

111111
private static void initScheduledTasks(DiscordApi api) {
112+
// Regularly check for and unmute users whose mutes have expired.
112113
asyncPool.scheduleAtFixedRate(() -> {
113114
for (var server : api.getServers()) {
114-
new ModerationService(api, config.get(server).getModeration());
115-
// TODO: Figure out how to periodically unmute people. Consider tracking each muted person.
115+
new ModerationService(api, config.get(server).getModeration()).unmuteExpired();
116116
}
117-
}, 3L, 3L, TimeUnit.MINUTES);
117+
}, 1L, 1L, TimeUnit.MINUTES);
118118
}
119119
}

src/main/java/net/javadiscord/javabot2/systems/moderation/ModerationService.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.javadiscord.javabot2.systems.moderation;
22

33

4+
import lombok.extern.slf4j.Slf4j;
45
import net.javadiscord.javabot2.config.guild.ModerationConfig;
56
import net.javadiscord.javabot2.db.DbHelper;
67
import net.javadiscord.javabot2.systems.moderation.dao.MuteRepository;
@@ -12,6 +13,7 @@
1213
import org.javacord.api.DiscordApi;
1314
import org.javacord.api.entity.channel.ServerTextChannel;
1415
import org.javacord.api.entity.message.embed.EmbedBuilder;
16+
import org.javacord.api.entity.server.ServerUpdater;
1517
import org.javacord.api.entity.user.User;
1618

1719
import java.awt.*;
@@ -25,6 +27,7 @@
2527
* This service provides methods for performing moderation actions, like banning
2628
* or warning users.
2729
*/
30+
@Slf4j
2831
public class ModerationService {
2932
private static final int BAN_DELETE_DAYS = 7;
3033

@@ -60,7 +63,7 @@ public CompletableFuture<Void> warn(User user, WarnSeverity severity, String rea
6063
var warnEmbed = buildWarnEmbed(user, severity, reason, warnedBy, warn.getCreatedAt().toInstant(ZoneOffset.UTC), totalWeight);
6164
user.openPrivateChannel().thenAcceptAsync(pc -> pc.sendMessage(warnEmbed));
6265
config.getLogChannel().sendMessage(warnEmbed);
63-
if (!quiet) {
66+
if (!quiet && channel.getId() != config.getLogChannelId()) {
6467
channel.sendMessage(warnEmbed);
6568
}
6669
if (totalWeight > config.getMaxWarnSeverity()) {
@@ -144,7 +147,7 @@ public CompletableFuture<Void> mute(User user, String reason, User mutedBy, Dura
144147
con.commit();
145148
user.openPrivateChannel().thenAcceptAsync(pc -> pc.sendMessage(embed));
146149
config.getLogChannel().sendMessage(embed);
147-
if (!quiet) {
150+
if (!quiet && channel.getId() != config.getLogChannelId()) {
148151
channel.sendMessage(embed);
149152
}
150153
});
@@ -167,6 +170,34 @@ public CompletableFuture<Void> unmute(User user, User unmutedBy) {
167170
});
168171
}
169172

173+
/**
174+
* Unmutes all users whose mutes have expired, and discards those mutes.
175+
* @return A future that completes when all expired mutes have been processed.
176+
*/
177+
public CompletableFuture<Void> unmuteExpired() {
178+
return DbHelper.doDbAction(con -> {
179+
con.setAutoCommit(false);
180+
var repo = new MuteRepository(con);
181+
ServerUpdater updater = new ServerUpdater(config.getGuild());
182+
for (var mute : repo.getExpiredMutes()) {
183+
// Check that for this expired mute, that there are no other active mutes which still apply to the user.
184+
if (!repo.hasActiveMutes(mute.getUserId())) {
185+
var user = api.getUserById(mute.getUserId()).join();
186+
if (user.getRoles(config.getGuild()).contains(config.getMuteRole())) {
187+
log.info("Unmuting user {} because their mute has expired.", user.getDiscriminatedName());
188+
updater.removeRoleFromUser(user, config.getMuteRole());
189+
var embed = buildUnmuteEmbed(user, api.getYourself());
190+
user.openPrivateChannel().thenAcceptAsync(pc -> pc.sendMessage(embed));
191+
config.getLogChannel().sendMessage(embed);
192+
}
193+
repo.discard(mute);
194+
}
195+
}
196+
updater.update();
197+
con.commit();
198+
});
199+
}
200+
170201
private EmbedBuilder buildWarnEmbed(User user, WarnSeverity severity, String reason, User warnedBy, Instant timestamp, int totalSeverity) {
171202
return new EmbedBuilder()
172203
.setColor(Color.ORANGE)
@@ -197,13 +228,10 @@ private EmbedBuilder buildMuteEmbed(User user, String reason, Duration duration,
197228
}
198229

199230
private EmbedBuilder buildUnmuteEmbed(User user, User unmutedBy) {
200-
var e = new EmbedBuilder()
231+
return new EmbedBuilder()
201232
.setColor(Color.DARK_GRAY)
202233
.setTitle(String.format("%s | Unmute", user.getDiscriminatedName()))
203-
.setTimestampToNow();
204-
if (unmutedBy != null) {
205-
e.setFooter(unmutedBy.getDiscriminatedName(), unmutedBy.getAvatar());
206-
}
207-
return e;
234+
.setTimestampToNow()
235+
.setFooter(unmutedBy.getDiscriminatedName(), unmutedBy.getAvatar());
208236
}
209237
}

src/main/java/net/javadiscord/javabot2/systems/moderation/dao/MuteRepository.java

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@
88
import java.util.List;
99
import java.util.Optional;
1010

11+
/**
12+
* DAO for interacting with the collection of stored {@link Mute} objects.
13+
*/
1114
@RequiredArgsConstructor
1215
public class MuteRepository {
1316
private final Connection con;
1417

18+
/**
19+
* Inserts a new mute into the database. Note that this ignores the mute's
20+
* {@link Mute#isDiscarded()}, {@link Mute#getId()}, and {@link Mute#getCreatedAt()}.
21+
* @param mute The mute to save.
22+
* @return The mute that was saved.
23+
* @throws SQLException If an error occurs.
24+
*/
1525
public Mute insert(Mute mute) throws SQLException {
1626
try (var s = con.prepareStatement(
1727
"INSERT INTO mute (user_id, muted_by, reason, ends_at) VALUES (?, ?, ?, ?)",
@@ -29,8 +39,17 @@ public Mute insert(Mute mute) throws SQLException {
2939
}
3040
}
3141

42+
/**
43+
* Gets the list of active mutes for a user, or those which are not
44+
* discarded, and whose ending date is some time in the future.
45+
* @param userId The id of the user to get active mutes for.
46+
* @return A list of mutes.
47+
* @throws SQLException If an error occurs.
48+
*/
3249
public List<Mute> getActiveMutes(long userId) throws SQLException {
33-
try (var s = con.prepareStatement("SELECT * FROM mute WHERE user_id = ? AND discarded = FALSE AND ends_at > CURRENT_TIMESTAMP(0)")) {
50+
try (var s = con.prepareStatement("""
51+
SELECT * FROM mute
52+
WHERE user_id = ? AND discarded = FALSE AND ends_at > CURRENT_TIMESTAMP(0)""")) {
3453
s.setLong(1, userId);
3554
var rs = s.executeQuery();
3655
List<Mute> mutes = new ArrayList<>();
@@ -41,8 +60,33 @@ public List<Mute> getActiveMutes(long userId) throws SQLException {
4160
}
4261
}
4362

44-
public List<Mute> getActiveMutes() throws SQLException {
45-
try (var s = con.prepareStatement("SELECT * FROM mute WHERE discarded = FALSE AND ends_at > CURRENT_TIMESTAMP(0)")) {
63+
/**
64+
* Determines if a user has at least one active mute.
65+
* @param userId The user to check.
66+
* @return True if there is at least one active mute for the user.
67+
* @throws SQLException If an error occurs.
68+
*/
69+
public boolean hasActiveMutes(long userId) throws SQLException {
70+
try (var s = con.prepareStatement("""
71+
SELECT COUNT(id)
72+
FROM mute
73+
WHERE user_id = ? AND discarded = FALSE AND ends_at > CURRENT_TIMESTAMP(0)""")) {
74+
s.setLong(1, userId);
75+
var rs = s.executeQuery();
76+
return rs.next() && rs.getLong(1) > 0;
77+
}
78+
}
79+
80+
/**
81+
* Gets a list of expired mutes, which are those that are not yet discarded,
82+
* but whose ending time is in the past.
83+
* @return The list of mutes.
84+
* @throws SQLException If an error occurs.
85+
*/
86+
public List<Mute> getExpiredMutes() throws SQLException {
87+
try (var s = con.prepareStatement("""
88+
SELECT * FROM mute
89+
WHERE discarded = FALSE AND ends_at < CURRENT_TIMESTAMP(0)""")) {
4690
var rs = s.executeQuery();
4791
List<Mute> mutes = new ArrayList<>();
4892
while (rs.next()) {
@@ -52,6 +96,12 @@ public List<Mute> getActiveMutes() throws SQLException {
5296
}
5397
}
5498

99+
/**
100+
* Finds a mute by its id.
101+
* @param id The id of the mute to fetch.
102+
* @return An optional that contains the mute, if it was found.
103+
* @throws SQLException If an error occurs.
104+
*/
55105
public Optional<Mute> findById(long id) throws SQLException {
56106
try (var s = con.prepareStatement("SELECT * FROM mute WHERE id = ?")) {
57107
s.setLong(1, id);
@@ -61,15 +111,28 @@ public Optional<Mute> findById(long id) throws SQLException {
61111
return Optional.empty();
62112
}
63113

114+
/**
115+
* Discards a mute.
116+
* @param mute The mute to discard.
117+
* @throws SQLException If an error occurs.
118+
*/
64119
public void discard(Mute mute) throws SQLException {
65120
try (var s = con.prepareStatement("UPDATE mute SET discarded = TRUE WHERE id = ?")) {
66121
s.setLong(1, mute.getId());
67122
s.executeUpdate();
68123
}
69124
}
70125

126+
/**
127+
* Discards all currently active mutes for a given user.
128+
* @param userId The id of the user whose active mutes to discard.
129+
* @throws SQLException If an error occurs.
130+
*/
71131
public void discardAllActive(long userId) throws SQLException {
72-
try (var s = con.prepareStatement("UPDATE mute SET discarded = TRUE WHERE user_id = ? AND discarded = FALSE AND ends_at > CURRENT_TIMESTAMP(0)")) {
132+
try (var s = con.prepareStatement("""
133+
UPDATE mute
134+
SET discarded = TRUE
135+
WHERE user_id = ? AND discarded = FALSE AND ends_at > CURRENT_TIMESTAMP(0)""")) {
73136
s.setLong(1, userId);
74137
s.executeUpdate();
75138
}

0 commit comments

Comments
 (0)