Skip to content

Commit 31f36a0

Browse files
authored
feat(mute): add online-only temp mutes with pause/resume on disconnect (#1029)
Adds `-o` flag to `/tempmute` that pauses the mute timer when the player goes offline and resumes when they reconnect. Includes race condition fixes for cross-server sync and expiry handling. fixes #934
1 parent 8c02c6b commit 31f36a0

File tree

26 files changed

+846
-30
lines changed

26 files changed

+846
-30
lines changed

common/src/main/java/me/confuser/banmanager/common/commands/CommandParser.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ public class CommandParser {
2626
@Getter
2727
private boolean soft = false;
2828

29+
@Argument(alias = "o")
30+
@Getter
31+
private boolean onlineOnly = false;
32+
2933
@Getter
3034
private Reason reason;
3135

common/src/main/java/me/confuser/banmanager/common/commands/InfoCommand.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,13 @@ public void playerInfo(CommonSender sender, String name, Integer index, InfoComm
541541

542542
Message message;
543543

544-
if (mute.getExpires() == 0) {
544+
if (mute.isOnlineOnly() && mute.isPaused()) {
545+
message = Message.get("info.mute.temporaryOnlinePaused");
546+
message.set("remaining", DateUtils.formatDifference(mute.getPausedRemaining()));
547+
} else if (mute.isOnlineOnly() && mute.getExpires() > 0) {
548+
message = Message.get("info.mute.temporaryOnline");
549+
message.set("expires", DateUtils.getDifferenceFormat(mute.getExpires()));
550+
} else if (mute.getExpires() == 0) {
545551
message = Message.get("info.mute.permanent");
546552
} else {
547553
message = Message.get("info.mute.temporary");

common/src/main/java/me/confuser/banmanager/common/commands/TempMuteCommand.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) {
3434
return true;
3535
}
3636

37+
final boolean isOnlineOnly = parser.isOnlineOnly();
38+
39+
if (isOnlineOnly && !sender.hasPermission(getPermission() + ".online")) {
40+
sender.sendMessage(Message.getString("sender.error.noPermission"));
41+
return true;
42+
}
43+
3744
if (parser.args.length < 3) {
3845
return false;
3946
}
@@ -158,7 +165,17 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) {
158165
}
159166
}
160167

161-
PlayerMuteData mute = new PlayerMuteData(player, actor, reason.getMessage(), isSilent, isSoft, expires);
168+
PlayerMuteData mute;
169+
long now = System.currentTimeMillis() / 1000L;
170+
long durationSeconds = expires - now;
171+
172+
if (isOnlineOnly && onlinePlayer == null) {
173+
mute = new PlayerMuteData(player, actor, reason.getMessage(), isSilent, isSoft, 0, true);
174+
mute.setPausedRemaining(durationSeconds);
175+
} else {
176+
mute = new PlayerMuteData(player, actor, reason.getMessage(), isSilent, isSoft, expires, isOnlineOnly);
177+
}
178+
162179
boolean created;
163180

164181
try {
@@ -179,14 +196,20 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) {
179196

180197
if (isSoft || onlinePlayer1 == null) return;
181198

182-
Message muteMessage = Message.get("tempmute.player.disallowed")
199+
String messageKey = isOnlineOnly ? "tempmute.player.disallowedOnline" : "tempmute.player.disallowed";
200+
Message muteMessage = Message.get(messageKey)
183201
.set("displayName", onlinePlayer1.getDisplayName())
184202
.set("player", player.getName())
185203
.set("playerId", player.getUUID().toString())
186204
.set("reason", mute.getReason())
187205
.set("actor", actor.getName())
188-
.set("id", mute.getId())
189-
.set("expires", DateUtils.getDifferenceFormat(mute.getExpires()));
206+
.set("id", mute.getId());
207+
208+
if (mute.isPaused()) {
209+
muteMessage.set("expires", DateUtils.formatDifference(mute.getPausedRemaining()));
210+
} else {
211+
muteMessage.set("expires", DateUtils.getDifferenceFormat(mute.getExpires()));
212+
}
190213

191214
onlinePlayer1.sendMessage(muteMessage.toString());
192215

common/src/main/java/me/confuser/banmanager/common/data/PlayerMuteData.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ public class PlayerMuteData {
4141
@Getter
4242
private boolean silent = false;
4343

44+
@DatabaseField
45+
@Getter
46+
private boolean onlineOnly = false;
47+
48+
@DatabaseField(columnDefinition = "BIGINT UNSIGNED NOT NULL DEFAULT 0")
49+
@Getter
50+
private long pausedRemaining = 0;
51+
4452
PlayerMuteData() {
4553

4654
}
@@ -59,6 +67,12 @@ public PlayerMuteData(PlayerData player, PlayerData actor, String reason, boolea
5967
this.expires = expires;
6068
}
6169

70+
public PlayerMuteData(PlayerData player, PlayerData actor, String reason, boolean silent, boolean soft, long expires, boolean onlineOnly) {
71+
this(player, actor, reason, silent, soft, expires);
72+
73+
this.onlineOnly = onlineOnly;
74+
}
75+
6276
// Only use for imports!
6377
public PlayerMuteData(PlayerData player, PlayerData actor, String reason, boolean silent, boolean soft, long expires, long created) {
6478
this(player, actor, reason, silent, soft, expires);
@@ -73,19 +87,45 @@ public PlayerMuteData(int id, PlayerData player, PlayerData actor, String reason
7387
this.updated = updated;
7488
}
7589

90+
public PlayerMuteData(int id, PlayerData player, PlayerData actor, String reason, boolean silent, boolean soft, long expires, long created, long updated, boolean onlineOnly, long pausedRemaining) {
91+
this(id, player, actor, reason, silent, soft, expires, created, updated);
92+
93+
this.onlineOnly = onlineOnly;
94+
this.pausedRemaining = pausedRemaining;
95+
}
96+
7697
public PlayerMuteData(PlayerMuteRecord record) {
7798
this(record.getPlayer(), record.getPastActor(), record.getReason(), record.isSilent(), record.isSoft(), record.getExpired(), record.getPastCreated());
99+
if (record.isOnlineOnly() && record.getRemainingOnlineTime() > 0) {
100+
this.onlineOnly = true;
101+
this.expires = 0;
102+
this.pausedRemaining = record.getRemainingOnlineTime();
103+
}
78104
}
79105

80106
public boolean hasExpired() {
81107
return getExpires() != 0 && getExpires() <= (System.currentTimeMillis() / 1000L);
82108
}
83109

110+
public boolean isPaused() {
111+
return onlineOnly && pausedRemaining > 0;
112+
}
113+
114+
public void setExpires(long expires) {
115+
this.expires = expires;
116+
}
117+
118+
public void setPausedRemaining(long pausedRemaining) {
119+
this.pausedRemaining = pausedRemaining;
120+
}
121+
84122
public boolean equalsMute(PlayerMuteData mute) {
85123
return mute.getReason().equals(this.reason)
86124
&& mute.getExpires() == expires
87125
&& mute.getCreated() == this.created
88126
&& mute.getPlayer().getUUID().equals(this.getPlayer().getUUID())
89-
&& mute.getActor().getUUID().equals(this.actor.getUUID());
127+
&& mute.getActor().getUUID().equals(this.actor.getUUID())
128+
&& mute.isOnlineOnly() == this.onlineOnly
129+
&& mute.getPausedRemaining() == this.pausedRemaining;
90130
}
91131
}

common/src/main/java/me/confuser/banmanager/common/data/PlayerMuteRecord.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ public class PlayerMuteRecord {
5252
@Getter
5353
private boolean silent = false;
5454

55+
@DatabaseField
56+
@Getter
57+
private boolean onlineOnly = false;
58+
59+
@DatabaseField(columnDefinition = "BIGINT UNSIGNED NOT NULL DEFAULT 0")
60+
@Getter
61+
private long remainingOnlineTime = 0;
62+
5563
PlayerMuteRecord() {
5664

5765
}
@@ -64,6 +72,16 @@ public PlayerMuteRecord(PlayerMuteData mute, PlayerData actor, String reason) {
6472
createdReason = reason;
6573
silent = mute.isSilent();
6674
soft = mute.isSoft();
75+
onlineOnly = mute.isOnlineOnly();
76+
77+
if (mute.isOnlineOnly()) {
78+
if (mute.isPaused()) {
79+
remainingOnlineTime = mute.getPausedRemaining();
80+
} else if (mute.getExpires() > 0) {
81+
long now = System.currentTimeMillis() / 1000L;
82+
remainingOnlineTime = Math.max(0, mute.getExpires() - now);
83+
}
84+
}
6785

6886
this.reason = mute.getReason();
6987
this.actor = actor;
@@ -77,6 +95,16 @@ public PlayerMuteRecord(PlayerMuteData mute, PlayerData actor, long created) {
7795
pastCreated = mute.getCreated();
7896
silent = mute.isSilent();
7997
soft = mute.isSoft();
98+
onlineOnly = mute.isOnlineOnly();
99+
100+
if (mute.isOnlineOnly()) {
101+
if (mute.isPaused()) {
102+
remainingOnlineTime = mute.getPausedRemaining();
103+
} else if (mute.getExpires() > 0) {
104+
long now = System.currentTimeMillis() / 1000L;
105+
remainingOnlineTime = Math.max(0, mute.getExpires() - now);
106+
}
107+
}
80108

81109
this.actor = actor;
82110
this.created = created;

common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@ public void onJoin(final CommonPlayer player) {
263263
}
264264

265265
UUID id = player.getUniqueId();
266+
267+
PlayerMuteData mute = plugin.getPlayerMuteStorage().getMute(id);
268+
if (mute != null && mute.isOnlineOnly() && mute.isPaused()) {
269+
try {
270+
plugin.getPlayerMuteStorage().resumeMute(mute);
271+
} catch (SQLException e) {
272+
e.printStackTrace();
273+
}
274+
}
275+
266276
CloseableIterator<PlayerNoteData> notesItr = null;
267277

268278
try {

common/src/main/java/me/confuser/banmanager/common/listeners/CommonLeaveListener.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import me.confuser.banmanager.common.BanManagerPlugin;
44
import me.confuser.banmanager.common.data.PlayerHistoryData;
5+
import me.confuser.banmanager.common.data.PlayerMuteData;
56

67
import java.sql.SQLException;
78
import java.util.UUID;
@@ -33,5 +34,21 @@ public void onLeave(UUID id, String name) {
3334
}
3435
});
3536
}
37+
38+
PlayerMuteData mute = plugin.getPlayerMuteStorage().getMute(id);
39+
if (mute != null && mute.isOnlineOnly() && !mute.isPaused() && mute.getExpires() > 0) {
40+
long now = System.currentTimeMillis() / 1000L;
41+
long remaining = mute.getExpires() - now;
42+
43+
if (remaining > 0) {
44+
plugin.getScheduler().runAsync(() -> {
45+
try {
46+
plugin.getPlayerMuteStorage().pauseMute(mute, remaining);
47+
} catch (SQLException e) {
48+
e.printStackTrace();
49+
}
50+
});
51+
}
52+
}
3653
}
3754
}

common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,17 @@ public void notifyOnMute(PlayerMuteData data, boolean silent) {
2121
String broadcastPermission;
2222
Message message;
2323

24-
if (data.getExpires() == 0) {
24+
if (data.getExpires() == 0 && !data.isOnlineOnly()) {
2525
broadcastPermission = "bm.notify.mute";
2626
message = Message.get("mute.notify");
27+
} else if (data.isOnlineOnly()) {
28+
broadcastPermission = "bm.notify.tempmute";
29+
message = Message.get("tempmute.notifyOnline");
30+
if (data.isPaused()) {
31+
message.set("expires", DateUtils.formatDifference(data.getPausedRemaining()));
32+
} else {
33+
message.set("expires", DateUtils.getDifferenceFormat(data.getExpires()));
34+
}
2735
} else {
2836
broadcastPermission = "bm.notify.tempmute";
2937
message = Message.get("tempmute.notify");

common/src/main/java/me/confuser/banmanager/common/runnables/ExpiresSync.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public void run() {
6565
while (mutes.hasNext()) {
6666
PlayerMuteData mute = mutes.next();
6767

68-
muteStorage.unmute(mute, plugin.getPlayerStorage().getConsole(), "");
68+
muteStorage.unmuteIfExpired(mute, plugin.getPlayerStorage().getConsole());
6969
}
7070
} catch (SQLException e) {
7171
e.printStackTrace();

common/src/main/java/me/confuser/banmanager/common/runnables/GlobalMuteSync.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ private void newUnmutes() {
8686
while (itr.hasNext()) {
8787
GlobalPlayerMuteRecordData record = itr.next();
8888

89-
if (!localMuteStorage.isMuted(record.getUUID())) {
89+
PlayerMuteData localMute = localMuteStorage.getMute(record.getUUID());
90+
if (localMute == null) {
9091
continue;
9192
}
9293

93-
localMuteStorage.unmute(localMuteStorage.getMute(record.getUUID()), record.getActor(plugin));
94+
localMuteStorage.unmute(localMute, record.getActor(plugin));
9495

9596
}
9697
} catch (SQLException e) {

0 commit comments

Comments
 (0)