Skip to content

Commit d3838f8

Browse files
committed
Merge branch 'main' into 4-add-mail-functionality
2 parents 8d60678 + 3534bd7 commit d3838f8

File tree

13 files changed

+342
-85
lines changed

13 files changed

+342
-85
lines changed

.github/workflows/release.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
types: [ published ]
66

77
permissions:
8-
contents: read
8+
contents: write
99
pages: write
1010
id-token: write
1111

@@ -30,6 +30,9 @@ jobs:
3030
with:
3131
java-version: '17'
3232
distribution: 'corretto'
33+
34+
- name: Set project version
35+
run: mvn -B versions:set -DnewVersion=${{ github.event.release.tag_name }} -DgenerateBackupPoms=false
3336

3437
- name: Build and package Maven project
3538
run: mvn clean package
@@ -41,6 +44,15 @@ jobs:
4144
env:
4245
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4346

47+
- name: Upload to Modrinth
48+
uses: cloudnode-pro/[email protected]
49+
with:
50+
token: ${{ secrets.MODRINTH_TOKEN }}
51+
project: 5Ce4fxJB
52+
file: target/CloudnodeMSG-${{ github.event.release.tag_name }}.jar
53+
changelog: ${{ github.event.release.body }}
54+
loaders: paper
55+
4456
- name: Generate Javadoc
4557
run: mvn -B javadoc:javadoc --file pom.xml
4658

README.md

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,188 @@
11
# CloudnodeMSG
2-
Cloudnode's private messaging plugin
2+
3+
A Minecraft Java plugin for private messages.
4+
5+
Supports message channels (so you don't have to use a command for every DM) and integrates with vanish plugins.
6+
7+
[See the default config](https://github.com/cloudnode-pro/CloudnodeMSG/blob/main/src/main/resources/config.yml)
8+
9+
## Commands
10+
11+
The following are all commands from this plugin.
12+
13+
***
14+
15+
### `/msg <player> <message>`
16+
17+
Send a private message to another player.
18+
19+
<dl>
20+
<dt>Aliases:</dt> <dd><code>/message</code>, <code>/tell</code>, <code>/t</code>, <code>/whisper</code>, <code>/dm</code>, <code>/m</code>, <code>/pm</code></dd>
21+
<dt>Permission:</dt> <dd><code>cloudnodemsg.use</code></dd>
22+
</dl>
23+
24+
***
25+
26+
### `/msg <player>`
27+
28+
Create a private message channel to a player;
29+
i.e. all of your chat messages will be sent as private messages to this player.
30+
Run the command again to disable the message channel.
31+
32+
<dl>
33+
<dt>Aliases:</dt> <dd><code>/message</code>, <code>/tell</code>, <code>/t</code>, <code>/whisper</code>, <code>/dm</code>, <code>/m</code>, <code>/pm</code></dd>
34+
<dt>Permission:</dt> <dd><code>cloudnodemsg.use</code></dd>
35+
</dl>
36+
37+
***
38+
39+
### `/reply <message>`
40+
41+
Send a private message to the last player that messaged you.
42+
43+
<dl>
44+
<dt>Aliases:</dt> <dd><code>/r</code>, <code>/re</code></dd>
45+
<dt>Permission:</dt> <dd><code>cloudnodemsg.use</code></dd>
46+
</dl>
47+
48+
***
49+
50+
### `/teammsg <message>`
51+
52+
Send a private message to your [scoreboard team](https://minecraft.fandom.com/wiki/Scoreboard#Teams).
53+
54+
<dl>
55+
<dt>Aliases:</dt> <dd><code>/tm</code>, <code>/tmsg</code>, <code>/teamchat</code></dd>
56+
<dt>Permission:</dt> <dd><code>cloudnodemsg.use.team</code></dd>
57+
</dl>
58+
59+
***
60+
61+
### `/teammsg`
62+
63+
Create a private message channel to your [scoreboard team](https://minecraft.fandom.com/wiki/Scoreboard#Teams);
64+
i.e. all of your chat messages will be sent as private team messages.
65+
Run the command again to disable the message channel.
66+
67+
<dl>
68+
<dt>Aliases:</dt> <dd><code>/tm</code>, <code>/tmsg</code>, <code>/teamchat</code></dd>
69+
<dt>Permission:</dt> <dd><code>cloudnodemsg.use.team</code></dd>
70+
</dl>
71+
72+
***
73+
74+
### `/ignore <player>`
75+
76+
Ignore a player—you will stop seeing all of their messages (including public chat messages).
77+
78+
<dl>
79+
<dt>Aliases:</dt> <dd><code>/block</code></dd>
80+
<dt>Permission:</dt> <dd><code>cloudnodemsg.ignore</code></dd>
81+
</dl>
82+
83+
***
84+
85+
### `/unignore <player>`
86+
87+
You will stop ignoring the player and will be able to see their messages again.
88+
89+
<dl>
90+
<dt>Aliases:</dt> <dd><code>/unblock</code></dd>
91+
<dt>Permission:</dt> <dd><code>cloudnodemsg.ignore</code></dd>
92+
</dl>
93+
94+
***
95+
96+
### `/togglemsg`
97+
98+
Enable or disable receiving private messages.
99+
When your private messages are disabled, nobody can message you, but you can still send messages.
100+
101+
<dl>
102+
<dt>Aliases:</dt> <dd><code>/toggledms</code>, <code>/togglepms</code></dd>
103+
<dt>Permission:</dt> <dd><code>cloudnodemsg.toggle</code></dd>
104+
</dl>
105+
106+
***
107+
108+
### `/togglemsg <player>`
109+
110+
Enable or disable receiving private messages of another player.
111+
112+
<dl>
113+
<dt>Aliases:</dt> <dd><code>/toggledms</code>, <code>/togglepms</code></dd>
114+
<dt>Permission:</dt> <dd><code>cloudnodemsg.toggle.other</code></dd>
115+
</dl>
116+
117+
118+
119+
***
120+
121+
### `/cloudnodemsg reload`
122+
123+
Reload the plugin configuration.
124+
125+
<dl>
126+
<dt>Aliases:</dt> <dd><code>/toggledms</code>, <code>/togglepms</code></dd>
127+
<dt>Permission:</dt> <dd><code>cloudnodemsg.reload</code></dd>
128+
</dl>
129+
130+
## Permissions
131+
132+
Here is a list of the permissions used by this plugin.
133+
134+
| Permission | Description | Recommended Group |
135+
|------------------------------|-------------------------------------------------------------------------|-------------------|
136+
| `cloudnodemsg.use` | Allows using the `/msg` and `/r` commands | default |
137+
| `cloudnodemsg.team` | Allows using the `/teammsg` command | default |
138+
| `cloudnodemsg.send.vanished` | Allows sending messages to vanished players | admin |
139+
| `cloudnodemsg.ignore` | Allows using the `/ignore` and `/unignore` commands | default |
140+
| `cloudnodemsg.ignore.bypass` | Makes your private messages visible, even if the recipient ignored you | admin |
141+
| `cloudnodemsg.toggle` | Allows you to use the `/togglemsg` command | default |
142+
| `cloudnodemsg.toggle.other` | Allows you to toggle msg for another player using `/togglemsg <player>` | admin |
143+
| `cloudnodemsg.toggle.bypass` | Allows you to send messages to players with disabled DMs | admin |
144+
| `cloudnodemsg.spy` | Players with this permission see ALL private messages and team messages | admin |
145+
146+
## Release Cycle
147+
148+
CloudnodeMSG follows a weekly **time-based release schedule**,
149+
with new features or changes typically released every **Tuesday**.
150+
151+
When we merge critical bug fixes, we may publish out-of-band releases on any day of the week.
152+
153+
## Report Issues
154+
155+
Please ensure
156+
that you are using the [latest version](https://modrinth.com/plugin/5Ce4fxJB/version/latest) of CloudnodeMSG.
157+
The newest bug fixes are only available in the most recent version,
158+
and support is provided exclusively for this version.
159+
160+
If you encounter any problems with the plugin,
161+
please first check
162+
the [list of known issues](https://github.com/cloudnode-pro/CloudnodeMSG/issues?q=is%3Aopen+is%3Aissue+label%3Abug) on
163+
our GitHub repository.
164+
If you don’t find a similar fault listed there,
165+
we encourage you to [submit a new issue](https://github.com/cloudnode-pro/CloudnodeMSG/issues/new?labels=bug).
166+
Resolving bugs is the highest priority for this project.
167+
168+
To help us resolve your issue as quickly as possible, please provide as much relevant information as possible,
169+
including error logs, screenshots, and detailed steps to reproduce the problem.
170+
171+
## Feature Requests
172+
173+
To suggest a new feature, please [create a new issue](https://github.com/cloudnode-pro/CloudnodeMSG/issues/new),
174+
providing a detailed description of your idea.
175+
176+
## Contributing
177+
178+
CloudnodeMSG is licensed under the [GPL-3.0 licence](https://github.com/cloudnode-pro/CloudnodeMSG/blob/main/LICENSE).
179+
The source code is available on [GitHub](https://github.com/cloudnode-pro/CloudnodeMSG).
180+
181+
New contributors are most welcome to the project. If you're interested in contributing, follow these steps:
182+
183+
1. [Fork the repository](https://github.com/cloudnode-pro/CloudnodeMSG/fork)
184+
2. Create a new branch for your contributions.
185+
3. Make your changes and ensure they align with the project’s goals.
186+
4. Commit your changes with clear and descriptive messages.
187+
5. Push your changes to your fork.
188+
6. Submit a pull request.

src/main/java/pro/cloudnode/smp/cloudnodemsg/Message.java

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
import org.bukkit.persistence.PersistentDataType;
99
import org.jetbrains.annotations.NotNull;
1010
import org.jetbrains.annotations.Nullable;
11+
import pro.cloudnode.smp.cloudnodemsg.error.ChannelOfflineError;
1112
import pro.cloudnode.smp.cloudnodemsg.error.InvalidPlayerError;
13+
import pro.cloudnode.smp.cloudnodemsg.error.PlayerHasIncomingDisabledError;
14+
import pro.cloudnode.smp.cloudnodemsg.error.PlayerNotFoundError;
1215

1316
import java.util.Arrays;
1417
import java.util.HashSet;
@@ -32,20 +35,38 @@ public Message(@NotNull OfflinePlayer sender, @NotNull OfflinePlayer recipient,
3235
}
3336

3437
public void send() throws InvalidPlayerError {
38+
send(false);
39+
}
40+
41+
public void send(final boolean channel) throws InvalidPlayerError {
3542
final @NotNull Optional<@NotNull Player> senderPlayer = Optional.ofNullable(this.sender.getPlayer());
3643
final @NotNull Optional<@NotNull Player> recipientPlayer = Optional.ofNullable(this.recipient.getPlayer());
3744

45+
if (senderPlayer.isPresent() && (recipientPlayer.isEmpty() || (CloudnodeMSG.isVanished(recipientPlayer.get()) && !senderPlayer
46+
.get().hasPermission(Permission.SEND_VANISHED)))) {
47+
if (!channel) new PlayerNotFoundError(senderPlayer.get().getName()).send(senderPlayer.get());
48+
else {
49+
Message.exitChannel(senderPlayer.get());
50+
new ChannelOfflineError(senderPlayer.get().getName(), Optional.ofNullable(recipient.getName())
51+
.orElse("Unknown Player")).send(senderPlayer.get());
52+
}
53+
return;
54+
}
55+
56+
if (recipientPlayer.isPresent() && senderPlayer.isPresent() && !Message.isIncomingEnabled(recipientPlayer.get()) && !senderPlayer
57+
.get().hasPermission(Permission.TOGGLE_BYPASS)) {
58+
new PlayerHasIncomingDisabledError(recipientPlayer.get().getName()).send(senderPlayer.get());
59+
return;
60+
}
61+
3862
sendMessage(sender, CloudnodeMSG.getInstance().config().outgoing(sender, recipient, message));
3963
if (senderPlayer.isPresent() && !Message.hasChannel(senderPlayer.get(), recipient))
4064
setReplyTo(sender, recipient);
4165

4266
sendSpyMessage(sender, recipient, message);
4367

44-
if (
45-
(recipientPlayer.isPresent() && Message.isIgnored(recipientPlayer.get(), sender))
46-
&&
47-
(senderPlayer.isPresent() && !senderPlayer.get().hasPermission(Permission.IGNORE_BYPASS))
48-
) return;
68+
if ((recipientPlayer.isPresent() && Message.isIgnored(recipientPlayer.get(), sender)) && (senderPlayer.isPresent() && !senderPlayer
69+
.get().hasPermission(Permission.IGNORE_BYPASS))) return;
4970
sendMessage(recipient, CloudnodeMSG.getInstance().config()
5071
.incoming(sender, recipient, message));
5172
if (recipientPlayer.isPresent() && !Message.hasChannel(recipientPlayer.get(), sender))
@@ -93,18 +114,17 @@ public static void sendSpyMessage(final @NotNull OfflinePlayer sender, final @No
93114

94115
public static void setReplyTo(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient) {
95116
if (sender.getUniqueId().equals(console.getUniqueId())) consoleReply = recipient.getUniqueId();
96-
else if (sender.isOnline())
97-
Objects.requireNonNull(sender.getPlayer()).getPersistentDataContainer()
98-
.set(REPLY_TO, PersistentDataType.STRING, recipient.getUniqueId().toString());
117+
else if (sender.isOnline()) Objects.requireNonNull(sender.getPlayer()).getPersistentDataContainer()
118+
.set(REPLY_TO, PersistentDataType.STRING, recipient.getUniqueId().toString());
99119
}
100120

101121
public static @NotNull Optional<@NotNull OfflinePlayer> getReplyTo(final @NotNull OfflinePlayer player) {
102122
if (player.getUniqueId().equals(console.getUniqueId())) return Optional.ofNullable(consoleReply)
103123
.map(uuid -> CloudnodeMSG.getInstance().getServer().getOfflinePlayer(uuid));
104-
if (player.isOnline())
105-
return Optional.ofNullable(Objects.requireNonNull(player.getPlayer()).getPersistentDataContainer()
106-
.get(REPLY_TO, PersistentDataType.STRING))
107-
.map(uuid -> CloudnodeMSG.getInstance().getServer().getOfflinePlayer(UUID.fromString(uuid)));
124+
if (player.isOnline()) return Optional
125+
.ofNullable(Objects.requireNonNull(player.getPlayer()).getPersistentDataContainer()
126+
.get(REPLY_TO, PersistentDataType.STRING))
127+
.map(uuid -> CloudnodeMSG.getInstance().getServer().getOfflinePlayer(UUID.fromString(uuid)));
108128
return Optional.empty();
109129
}
110130

@@ -118,15 +138,16 @@ else if (sender.isOnline())
118138
* @param player The player
119139
*/
120140
public static @NotNull HashSet<@NotNull UUID> getIgnored(final @NotNull Player player) {
121-
final @NotNull Optional<@NotNull String> str = Optional.ofNullable(player.getPersistentDataContainer().get(IGNORED_PLAYERS, PersistentDataType.STRING));
122-
return str.map(s -> new HashSet<>(Arrays.stream(s.split(";")).filter(e -> !e.isEmpty()).map(UUID::fromString).toList()))
123-
.orElseGet(HashSet::new);
141+
final @NotNull Optional<@NotNull String> str = Optional.ofNullable(player.getPersistentDataContainer()
142+
.get(IGNORED_PLAYERS, PersistentDataType.STRING));
143+
return str.map(s -> new HashSet<>(Arrays.stream(s.split(";")).filter(e -> !e.isEmpty()).map(UUID::fromString)
144+
.toList())).orElseGet(HashSet::new);
124145
}
125146

126147
/**
127148
* Check if a player is ignored
128149
*
129-
* @param player The player
150+
* @param player The player
130151
* @param ignored The ignored player
131152
*/
132153
public static boolean isIgnored(final @NotNull Player player, final @NotNull OfflinePlayer ignored) {
@@ -142,19 +163,23 @@ public static boolean isIgnored(final @NotNull Player player, final @NotNull Off
142163
public static void ignore(final @NotNull Player player, final @NotNull OfflinePlayer ignore) {
143164
final @NotNull HashSet<@NotNull UUID> ignoredPlayers = getIgnored(player);
144165
ignoredPlayers.add(ignore.getUniqueId());
145-
player.getPersistentDataContainer().set(IGNORED_PLAYERS, PersistentDataType.STRING, String.join(";", ignoredPlayers.stream().map(UUID::toString).toList()));
166+
player.getPersistentDataContainer()
167+
.set(IGNORED_PLAYERS, PersistentDataType.STRING, String.join(";", ignoredPlayers.stream()
168+
.map(UUID::toString).toList()));
146169
}
147170

148171
/**
149172
* Unignore a player
150173
*
151-
* @param player The player
174+
* @param player The player
152175
* @param ignored The player to unignore
153176
*/
154177
public static void unignore(final @NotNull Player player, final @NotNull OfflinePlayer ignored) {
155178
final @NotNull HashSet<@NotNull UUID> ignoredPlayers = getIgnored(player);
156179
ignoredPlayers.remove(ignored.getUniqueId());
157-
player.getPersistentDataContainer().set(IGNORED_PLAYERS, PersistentDataType.STRING, String.join(";", ignoredPlayers.stream().map(UUID::toString).toList()));
180+
player.getPersistentDataContainer()
181+
.set(IGNORED_PLAYERS, PersistentDataType.STRING, String.join(";", ignoredPlayers.stream()
182+
.map(UUID::toString).toList()));
158183
}
159184

160185
public static final @NotNull NamespacedKey INCOMING_ENABLED = new NamespacedKey(CloudnodeMSG.getInstance(), "incoming_enabled");
@@ -193,7 +218,8 @@ public static boolean isIncomingEnabled(final @NotNull Player player) {
193218
* @param recipient The other end of the channel
194219
*/
195220
public static void createChannel(final @NotNull Player player, final @NotNull OfflinePlayer recipient) {
196-
player.getPersistentDataContainer().set(CHANNEL_RECIPIENT, PersistentDataType.STRING, recipient.getUniqueId().toString());
221+
player.getPersistentDataContainer()
222+
.set(CHANNEL_RECIPIENT, PersistentDataType.STRING, recipient.getUniqueId().toString());
197223
player.getPersistentDataContainer().remove(CHANNEL_TEAM);
198224
}
199225

@@ -212,21 +238,31 @@ public static void exitChannel(final @NotNull Player player) {
212238
* @param player The player
213239
*/
214240
public static @NotNull Optional<@NotNull OfflinePlayer> getChannel(final @NotNull Player player) {
215-
return Optional.ofNullable(player.getPersistentDataContainer().get(CHANNEL_RECIPIENT, PersistentDataType.STRING))
241+
return Optional
242+
.ofNullable(player.getPersistentDataContainer().get(CHANNEL_RECIPIENT, PersistentDataType.STRING))
216243
.map(uuid -> CloudnodeMSG.getInstance().getServer().getOfflinePlayer(UUID.fromString(uuid)));
217244
}
218245

219246
/**
220247
* Check whether player has DM channel with recipient
221248
*
222-
* @param player The player
249+
* @param player The player
223250
* @param recipient The recipient
224251
*/
225252
public static boolean hasChannel(final @NotNull Player player, final @NotNull OfflinePlayer recipient) {
226253
final @NotNull Optional<@NotNull OfflinePlayer> channel = getChannel(player);
227254
return channel.isPresent() && channel.get().getUniqueId().equals(recipient.getUniqueId());
228255
}
229256

257+
/**
258+
* Check whether player has DM channel
259+
*
260+
* @param player The player
261+
*/
262+
public static boolean hasChannel(final @NotNull Player player) {
263+
return getChannel(player).isPresent();
264+
}
265+
230266
/**
231267
* Team message channel
232268
*

0 commit comments

Comments
 (0)