Skip to content

Commit 2235426

Browse files
authored
Added optional duration for bans to enable temporary bans (#53)
2 parents 5c9a464 + 147c73a commit 2235426

File tree

6 files changed

+123
-26
lines changed

6 files changed

+123
-26
lines changed

src/main/java/pro/cloudnode/smp/smpcore/Configuration.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public int joinRequestExpireMinutes() {
4242
return config.getInt("join.request-expire-minutes");
4343
}
4444

45-
public @NotNull Component relativeTime(final int t, final @NotNull ChronoUnit unit) {
45+
public @NotNull Component relativeTime(final Number t, final @NotNull ChronoUnit unit) {
4646
final @NotNull String formatString = Objects.requireNonNull(config.getString("relative-time." + switch (unit) {
4747
case SECONDS -> "seconds";
4848
case MINUTES -> "minutes";
@@ -54,8 +54,10 @@ public int joinRequestExpireMinutes() {
5454
throw new IllegalStateException("No relative time format for ChronoUnit " + unit);
5555
}
5656
}));
57-
return MiniMessage.miniMessage()
58-
.deserialize(formatString, Formatter.number("t", t), Formatter.choice("format", Math.abs(t)));
57+
return MiniMessage.miniMessage().deserialize(formatString,
58+
Formatter.number("t", t),
59+
Formatter.choice("format", Math.abs(t.doubleValue()))
60+
);
5961
}
6062

6163
public @NotNull Component relativeTimeFuture(final @NotNull Component relativeTime) {
@@ -67,4 +69,18 @@ public int joinRequestExpireMinutes() {
6769
return MiniMessage.miniMessage()
6870
.deserialize(Objects.requireNonNull(config.getString("relative-time.past")), Placeholder.component("t", relativeTime));
6971
}
72+
73+
public @NotNull Component relativeTimeDuration(final @NotNull Component duration) {
74+
return MiniMessage.miniMessage()
75+
.deserialize(
76+
Objects.requireNonNull(config.getString("relative-time.duration")),
77+
Placeholder.component("t", duration)
78+
);
79+
}
80+
81+
public @NotNull Component relativeTimeDurationIndefinite() {
82+
return MiniMessage.miniMessage().deserialize(
83+
Objects.requireNonNull(config.getString("relative-time.duration-indefinite"))
84+
);
85+
}
7086
}

src/main/java/pro/cloudnode/smp/smpcore/Messages.java

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pro.cloudnode.smp.smpcore;
22

33
import net.kyori.adventure.text.Component;
4+
import net.kyori.adventure.text.JoinConfiguration;
45
import net.kyori.adventure.text.TextComponent;
56
import net.kyori.adventure.text.minimessage.MiniMessage;
67
import net.kyori.adventure.text.minimessage.tag.resolver.Formatter;
@@ -10,7 +11,9 @@
1011
import org.jetbrains.annotations.NotNull;
1112
import org.jetbrains.annotations.Nullable;
1213

14+
import java.time.Duration;
1315
import java.time.ZoneOffset;
16+
import java.time.temporal.ChronoUnit;
1417
import java.util.ArrayList;
1518
import java.util.Arrays;
1619
import java.util.Calendar;
@@ -22,7 +25,7 @@
2225
import java.util.TimeZone;
2326
import java.util.stream.Collectors;
2427

25-
public class Messages extends BaseConfig {
28+
public final class Messages extends BaseConfig {
2629

2730
public Messages() {
2831
super("messages.yml");
@@ -37,26 +40,70 @@ public Messages() {
3740
.deserialize(Objects.requireNonNull(config.getString("usage")), Placeholder.unparsed("label", label), Placeholder.unparsed("args", args));
3841
}
3942

40-
public @NotNull Component bannedPlayer(final @NotNull OfflinePlayer player) {
43+
private @NotNull Component formatDuration(@Nullable Duration duration) {
44+
if (duration == null) return SMPCore.config().relativeTimeDurationIndefinite();
45+
46+
final long seconds = Math.abs(duration.getSeconds());
47+
final long days = seconds / 86_400;
48+
final long hours = (seconds % 86_400) / 3_600;
49+
final long minutes = (seconds % 3_600) / 60;
50+
final long secs = seconds % 60;
51+
52+
final ArrayList<Component> components = new ArrayList<>();
53+
54+
if (days > 0) components.add(SMPCore.config().relativeTime(days, ChronoUnit.DAYS));
55+
if (hours > 0) components.add(SMPCore.config().relativeTime(hours, ChronoUnit.HOURS));
56+
if (minutes > 0) components.add(SMPCore.config().relativeTime(minutes, ChronoUnit.MINUTES));
57+
if (secs > 0) components.add(SMPCore.config().relativeTime(secs, ChronoUnit.SECONDS));
58+
59+
final Component joined = Component.join(JoinConfiguration.spaces(), components);
60+
61+
return SMPCore.config().relativeTimeDuration(joined);
62+
}
63+
64+
public @NotNull Component bannedPlayer(final @NotNull OfflinePlayer player, final @Nullable Duration duration) {
4165
return MiniMessage.miniMessage()
42-
.deserialize(Objects.requireNonNull(config.getString("banned-player")), Placeholder.unparsed("player", Optional
43-
.ofNullable(player.getName()).orElse(player.getUniqueId().toString())));
66+
.deserialize(
67+
Objects.requireNonNull(config.getString("banned-player")),
68+
Placeholder.unparsed("player",
69+
Optional.ofNullable(player.getName())
70+
.orElse(player.getUniqueId().toString())
71+
),
72+
Placeholder.component("duration", formatDuration(duration))
73+
);
4474
}
4575

46-
public @NotNull Component bannedMember(final @NotNull Member member) {
76+
public @NotNull Component bannedMember(final @NotNull Member member, final @Nullable Duration duration) {
4777
return MiniMessage.miniMessage()
48-
.deserialize(Objects.requireNonNull(config.getString("banned-member")), Placeholder.unparsed("player", Optional
49-
.ofNullable(member.player().getName()).orElse(member.player().getUniqueId().toString())));
78+
.deserialize(
79+
Objects.requireNonNull(config.getString("banned-member")),
80+
Placeholder.unparsed("player",
81+
Optional.ofNullable(member.player().getName())
82+
.orElse(member.player().getUniqueId().toString())
83+
),
84+
Placeholder.component("duration", formatDuration(duration))
85+
);
5086
}
5187

52-
public @NotNull Component bannedMemberChain(final @NotNull Member member, final @NotNull List<@NotNull Member> alts) {
88+
public @NotNull Component bannedMemberChain(
89+
final @NotNull Member member,
90+
final @NotNull List<@NotNull Member> alts,
91+
final @Nullable Duration duration
92+
) {
5393
final @NotNull String altsString = alts.stream()
5494
.map(m -> Optional.ofNullable(m.player().getName()).orElse(m.player().getUniqueId().toString()))
5595
.collect(Collectors.joining(", "));
5696
return MiniMessage.miniMessage()
57-
.deserialize(Objects.requireNonNull(config.getString("banned-member-chain")), Placeholder.unparsed("player", Optional
58-
.ofNullable(member.player().getName()).orElse(member.player().getUniqueId()
59-
.toString())), Placeholder.unparsed("n-alt", String.valueOf(alts.size())), Placeholder.unparsed("alts", altsString));
97+
.deserialize(
98+
Objects.requireNonNull(config.getString("banned-member-chain")),
99+
Placeholder.unparsed("player",
100+
Optional.ofNullable(member.player().getName())
101+
.orElse(member.player().getUniqueId().toString())
102+
),
103+
Placeholder.unparsed("n-alt", String.valueOf(alts.size())),
104+
Placeholder.unparsed("alts", altsString),
105+
Placeholder.component("duration", formatDuration(duration))
106+
);
60107
}
61108

62109
public @NotNull Component unbannedPlayer(final @NotNull OfflinePlayer player) {
@@ -568,6 +615,17 @@ public Messages() {
568615
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("error.demote-citizen")));
569616
}
570617

618+
public @NotNull Component errorDurationZeroOrLess() {
619+
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("error.duration-zero-or-less")));
620+
}
621+
622+
public @NotNull Component invalidDuration(final @NotNull String duration) {
623+
return MiniMessage.miniMessage().deserialize(
624+
Objects.requireNonNull(config.getString("error.invalid-duration")),
625+
Placeholder.unparsed("duration", duration)
626+
);
627+
}
628+
571629
public record SubCommandArgument(@NotNull String name, boolean required) {
572630
public @NotNull Component component() {
573631
return required ? SMPCore.messages().subCommandArgumentRequired(name) : SMPCore.messages()

src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ public static boolean ifDisallowedCharacters(final @NotNull String source, final
188188
final double years = Math.floor(months / 12.0);
189189

190190
final @NotNull Component t;
191-
if (years > 0) t = SMPCore.config().relativeTime((int) years, ChronoUnit.YEARS);
191+
if (years > 0) t = SMPCore.config().relativeTime(years, ChronoUnit.YEARS);
192192
else if (months > 0) t = SMPCore.config().relativeTime((int) months, ChronoUnit.MONTHS);
193193
else if (days > 0) t = SMPCore.config().relativeTime((int) days, ChronoUnit.DAYS);
194194
else if (hours > 0) t = SMPCore.config().relativeTime((int) hours, ChronoUnit.HOURS);

src/main/java/pro/cloudnode/smp/smpcore/command/BanCommand.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import pro.cloudnode.smp.smpcore.Permission;
1111
import pro.cloudnode.smp.smpcore.SMPCore;
1212

13+
import java.time.Duration;
14+
import java.time.Instant;
15+
import java.time.format.DateTimeParseException;
1316
import java.util.Arrays;
1417
import java.util.Date;
1518
import java.util.HashSet;
@@ -19,38 +22,54 @@
1922

2023
public final class BanCommand extends Command {
2124
/**
22-
* Usage: {@code /<command> <username> [reason]}
25+
* Usage: {@code /<command> <username> [duration] [reason]}
2326
*/
2427
@Override
2528
public boolean run(@NotNull CommandSender sender, @NotNull String label, @NotNull String @NotNull [] args) {
2629
if (!sender.hasPermission(Permission.BAN))
2730
return sendMessage(sender, SMPCore.messages().errorNoPermission());
28-
if (args.length < 1) return sendMessage(sender, SMPCore.messages().usage(label, "<username> [reason]"));
29-
final @NotNull OfflinePlayer target = SMPCore.getInstance().getServer().getOfflinePlayer(args[0]);
31+
if (args.length < 1)
32+
return sendMessage(sender, SMPCore.messages().usage(label, "<username> [duration] [reason]"));
33+
34+
final @Nullable String durationArg = args.length > 1 ? args[1] : null;
35+
@Nullable Duration duration = null;
36+
if (durationArg != null && durationArg.matches("(?i)^PT\\d.*")) try {
37+
duration = Duration.parse(durationArg);
38+
}
39+
catch (DateTimeParseException ignored) {
40+
return sendMessage(sender, SMPCore.messages().invalidDuration(durationArg));
41+
}
3042

31-
final @Nullable String reason = args.length > 1 ? String.join(" ", Arrays.copyOfRange(args, 1, args.length)) : null;
32-
final @Nullable Date banExpiry = null;
43+
if (duration != null && (duration.isNegative() || duration.isZero()))
44+
return sendMessage(sender, SMPCore.messages().errorDurationZeroOrLess());
45+
46+
final @Nullable Date banExpiry = duration == null ? null : Date.from(Instant.now().plus(duration));
47+
48+
final @Nullable String reason = args.length > 1
49+
? String.join(" ", Arrays.copyOfRange(args, duration == null ? 1 : 2, args.length))
50+
: null;
3351
final @NotNull NamespacedKey banSource;
3452
if (sender instanceof final @NotNull Player player)
3553
banSource = new NamespacedKey(SMPCore.getInstance(), "player/" + player.getUniqueId());
3654
else banSource = new NamespacedKey(SMPCore.getInstance(), "console");
3755

56+
final @NotNull OfflinePlayer target = SMPCore.getInstance().getServer().getOfflinePlayer(args[0]);
3857
final @NotNull Optional<@NotNull Member> targetMember = Member.get(target);
3958
if (targetMember.isEmpty()) {
4059
SMPCore.runMain(() -> target.ban(reason, banExpiry, banSource.asString()));
41-
return sendMessage(sender, SMPCore.messages().bannedPlayer(target));
60+
return sendMessage(sender, SMPCore.messages().bannedPlayer(target, duration));
4261
}
4362
final @NotNull Member main = targetMember.get().altOwner().orElse(targetMember.get());
4463
final @NotNull HashSet<@NotNull Member> alts = main.getAlts();
4564

4665
SMPCore.runMain(() -> main.player().ban(reason, banExpiry, banSource.asString()));
47-
if (alts.isEmpty()) return sendMessage(sender, SMPCore.messages().bannedMember(main));
66+
if (alts.isEmpty()) return sendMessage(sender, SMPCore.messages().bannedMember(main, duration));
4867
else {
4968
SMPCore.runMain(() -> {
5069
for (final @NotNull Member alt : alts)
5170
alt.player().ban(reason, banExpiry, banSource.asString());
5271
});
53-
return sendMessage(sender, SMPCore.messages().bannedMemberChain(main, alts.stream().toList()));
72+
return sendMessage(sender, SMPCore.messages().bannedMemberChain(main, alts.stream().toList(), duration));
5473
}
5574
}
5675

src/main/resources/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ relative-time:
2323
years: <t> <format:'0#years|1#year|1<years'>
2424
future: in <t>
2525
past: <t> ago
26+
duration: for <t>
27+
duration-indefinite: <u><b>forever</b></u>

src/main/resources/messages.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
reloaded: <green>(!) Plugin successfully reloaded.</green>
22
usage: "<yellow>(!) Usage: <white>/<label> <args></white></yellow>"
33

4-
banned-player: <yellow>(!) Banned singular non-member player <gray><player></gray>.</yellow>
5-
banned-member: <green>(!) Banned member <gray><player></gray>. No alts found.</green>
6-
banned-member-chain: "<green>(!) Banned member <gray><player></gray> and <gray><n-alt></gray> alts: <gray><alts></gray>.</green>"
4+
banned-player: <yellow>(!) Banned singular non-member player <gray><player></gray> <duration>.</yellow>
5+
banned-member: <green>(!) Banned member <gray><player></gray> <duration>. No alts found.</green>
6+
banned-member-chain: "<green>(!) Banned member <gray><player></gray> and <gray><n-alt></gray> alts <duration>: <gray><alts></gray>.</green>"
77

88
unbanned-player: <yellow>(!) Unbanned singular non-member player <gray><player></gray>.</yellow>
99
unbanned-member: <green>(!) Unbanned member <gray><player></gray> and <gray><n-alts></gray> alts.</green>
@@ -116,3 +116,5 @@ error:
116116
already-vice: <red>(!) The vice-leader is already <gray><player></gray>.</red>
117117
demote-leader: <red>(!) You cannot demote the nation leader.</red>
118118
demote-citizen: <red>(!) Don't make this <i>citizen</i> unworthy of the nation! (To remove from nation, use <click:suggest_command:/nation citizens kick ><gray>/nation citizens kick</gray></click>.)</red>
119+
duration-zero-or-less: <red>(!) Duration must be greater than zero.</red>
120+
invalid-duration: <red>(!) Invalid duration format <gray>‘<duration></gray>’.</red>

0 commit comments

Comments
 (0)