Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/main/java/org/pearlbot/PearlBotMessages.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* PearlBot — a ZenithProxy plugin for on-demand stasis chamber pulls.
* Copyright (C) 2026 Leonetic
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.pearlbot;

public class PearlBotMessages {

// --- In-game whispers ---

public String noPearlFound = "No pearl found for you.";

// {remaining} = e.g. "2 pearls" or "1 pearl"
public String pulled = "Pulled. You have {remaining} left.";

// {count} = current pearl count, {max} = configured max
public String maxPearlsExceeded = "You have {count} pearl(s) but the max is {max}! Automatically pulling your oldest pearl.";

// {count} = e.g. "2" or "2/3" when a max is set
public String pearlCount = "You have {count} pearl(s) stasised.";

// {timeout} = seconds configured for pull timeout
public String pullTimedOut = "Positioning timed out after {timeout}s.";

// {timeout} = seconds configured for owner-online timeout
public String ownerTimedOut = "Expired - you did not log on within {timeout}s.";

public String authUsage = "Usage: !auth <code> - get a code by typing !auth in Discord first.";
public String authInvalidCode = "Invalid or expired code.";

// {discordUsername} = the Discord username that was linked
public String authLinked = "Linked to Discord {discordUsername}.";

// --- Discord messages (the @mention is prepended automatically) ---

public String discordNoAccountsLinked = "No MC accounts linked. Type `!auth` to link one.";

// {accounts} = comma-separated account names, {trigger} = e.g. "!warp"
public String discordMultipleAccounts = "Accounts linked: {accounts}. Please type `{trigger} <username>` to pull a specific account.";

// {name} = the username that wasn't found, {accounts} = comma-separated linked account names
public String discordAccountNotFound = "No linked account named `{name}`. Accounts linked: {accounts}.";

// {code} = auth code, {ttl} = minutes until expiry
public String discordAuthCode = "Whisper me `!auth {code}` in-game from each MC account you want to link. Expires in {ttl} minutes.";

// {mcUsername} = the MC account that was linked
public String discordAuthLinked = "Linked MC account `{mcUsername}`.";

// Replaces {key} placeholders — pass alternating key, value pairs
public String format(String template, Object... keyValues) {
String result = template;
for (int i = 0; i + 1 < keyValues.length; i += 2) {
result = result.replace("{" + keyValues[i] + "}", String.valueOf(keyValues[i + 1]));
}
return result;
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/pearlbot/PearlBotPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
public class PearlBotPlugin implements ZenithProxyPlugin {
public static PluginAPI API;
public static PearlBotConfig PLUGIN_CONFIG;
public static PearlBotMessages PLUGIN_MESSAGES;
public static ComponentLogger LOG;

@Override
Expand All @@ -44,6 +45,7 @@ public void onLoad(PluginAPI pluginAPI) {
LOG = pluginAPI.getLogger();
LOG.info("PearlBot loading...");
PLUGIN_CONFIG = API.registerConfig(BuildConstants.PLUGIN_ID, PearlBotConfig.class);
PLUGIN_MESSAGES = API.registerConfig(BuildConstants.PLUGIN_ID + "-messages", PearlBotMessages.class);
API.registerModule(new EnderPearlTrackerModule());
API.registerModule(new AutoPearlModule());
API.registerCommand(new PearlBotCommand());
Expand Down
34 changes: 15 additions & 19 deletions src/main/java/org/pearlbot/module/AutoPearlModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import static com.zenith.Globals.CONFIG;
import static com.zenith.Globals.DISCORD;
import static org.pearlbot.PearlBotPlugin.PLUGIN_CONFIG;
import static org.pearlbot.PearlBotPlugin.PLUGIN_MESSAGES;

public class AutoPearlModule extends Module {
private static final long PULL_RETRY_INTERVAL_MS = 1_000L;
Expand Down Expand Up @@ -138,7 +139,7 @@ private void onWhisper(WhisperChatEvent event) {
.count();
int max = PLUGIN_CONFIG.maxChambersPerPlayer;
String countStr = max > 0 ? count + "/" + max : String.valueOf(count);
sendWhisper(name, "You have " + countStr + " pearl(s) set.");
sendWhisper(name, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.pearlCount, "count", countStr));
return;
}

Expand All @@ -150,7 +151,7 @@ private void onWhisper(WhisperChatEvent event) {

PearlBotConfig.StasisChamber chamber = findChamberFor(uuid);
if (chamber == null) {
sendWhisper(name, "No pearl found for you.");
sendWhisper(name, PLUGIN_MESSAGES.noPearlFound);
return;
}

Expand All @@ -159,19 +160,19 @@ private void onWhisper(WhisperChatEvent event) {

private void handleAuthWhisper(UUID mcUuid, String mcUsername, String code) {
if (code.isBlank()) {
sendWhisper(mcUsername, "Usage: !auth <code> - get a code with !auth in Discord first.");
sendWhisper(mcUsername, PLUGIN_MESSAGES.authUsage);
return;
}
purgeExpiredAuthCodes();
PendingAuth pending = pendingAuthCodes.remove(code.toUpperCase());
if (pending == null) {
sendWhisper(mcUsername, "Invalid or expired code.");
sendWhisper(mcUsername, PLUGIN_MESSAGES.authInvalidCode);
return;
}
PLUGIN_CONFIG.linkedAccounts.put(mcUuid, new PearlBotConfig.LinkedAccount(
pending.discordUserId, pending.discordUsername, mcUsername, System.currentTimeMillis()));
info("Linked MC {} ({}) to Discord {} ({})", mcUsername, mcUuid, pending.discordUsername, pending.discordUserId);
sendWhisper(mcUsername, "Linked to Discord " + pending.discordUsername + ".");
sendWhisper(mcUsername, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.authLinked, "discordUsername", pending.discordUsername));
notifyAuthSuccess(pending.discordUserId, mcUsername);
}

Expand All @@ -184,7 +185,7 @@ private void notifyAuthSuccess(String discordUserId, String mcUsername) {
warn("Cannot send auth-success ping: channel {} not found", channelId);
return;
}
channel.sendMessage("<@" + discordUserId + "> Linked MC account `" + mcUsername + "`.").queue();
channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordAuthLinked, "mcUsername", mcUsername)).queue();
}

private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
Expand All @@ -205,8 +206,7 @@ private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
if (firstWord.equals(DISCORD_AUTH_CMD)) {
String code = newAuthCode(discordUserId, discordUsername);
long ttl = (long) PLUGIN_CONFIG.discordTrigger.authCodeTtlMinutes;
channel.sendMessage("<@" + discordUserId + "> Whisper me `!auth " + code
+ "` in-game from each MC account you want to link. Expires in " + ttl + " minutes.").queue();
channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordAuthCode, "code", code, "ttl", ttl)).queue();
return;
}

Expand All @@ -221,16 +221,15 @@ private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
.map(Map.Entry::getValue)
.toList();
if (linked.isEmpty()) {
channel.sendMessage("<@" + discordUserId + "> No MC accounts linked. Type `!auth` to link one.").queue();
channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.discordNoAccountsLinked).queue();
return;
}

if (linked.size() > 1 && targetName == null) {
String names = linked.stream()
.map(a -> a.mcUsername != null ? a.mcUsername : "?")
.collect(java.util.stream.Collectors.joining(", "));
channel.sendMessage("<@" + discordUserId + "> Accounts linked: " + names
+ ". Please type `" + triggerWordDiscord() + " <username>` to pull a specific account.").queue();
channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordMultipleAccounts, "accounts", names, "trigger", triggerWordDiscord())).queue();
return;
}

Expand Down Expand Up @@ -258,8 +257,7 @@ private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
String names = linked.stream()
.map(a -> a.mcUsername != null ? a.mcUsername : "?")
.collect(java.util.stream.Collectors.joining(", "));
channel.sendMessage("<@" + discordUserId + "> No linked account named `" + targetName
+ "`. Accounts linked: " + names + ".").queue();
channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordAccountNotFound, "name", targetName, "accounts", names)).queue();
return;
}

Expand Down Expand Up @@ -326,8 +324,7 @@ public void checkAndEnforceMaxChambers(UUID ownerUuid) {
PearlBotConfig.StasisChamber chamber = findChamberFor(ownerUuid);
if (chamber == null) return;
if (name != null) {
sendWhisper(name, "You have " + count + " pearl(s) but the max is " + max
+ "! Pulling your oldest pearl.");
sendWhisper(name, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.maxPearlsExceeded, "count", count, "max", max));
}
enqueuePull(ownerUuid, name, chamber);
}
Expand Down Expand Up @@ -387,7 +384,7 @@ private void tickPending() {
if (timeoutMs > 0 && now - activePullStartMs > timeoutMs) {
warn("Positioning for {} timed out after {}s; cancelling",
labelOf(activePull), PLUGIN_CONFIG.pullTimeoutSeconds);
abortActivePull("Positioning timed out after " + PLUGIN_CONFIG.pullTimeoutSeconds + "s.");
abortActivePull(PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.pullTimedOut, "timeout", PLUGIN_CONFIG.pullTimeoutSeconds));
return;
}
} else if (isOwnerOnline(activePull.ownerUuid)) {
Expand All @@ -397,8 +394,7 @@ private void tickPending() {
if (waitMs > 0 && now - readyAtMs > waitMs) {
warn("{} did not come online within {}s; expiring pull",
labelOf(activePull), PLUGIN_CONFIG.waitForOwnerSeconds);
abortActivePull("Expired - you did not log on within "
+ PLUGIN_CONFIG.waitForOwnerSeconds + "s.");
abortActivePull(PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.ownerTimedOut, "timeout", PLUGIN_CONFIG.waitForOwnerSeconds));
return;
}
}
Expand Down Expand Up @@ -540,7 +536,7 @@ private void fireClick() {
int remaining = Math.max(0, remainingPearlsFor(pull.ownerUuid) - 1);
if (pull.ownerName != null) {
String tail = remaining == 1 ? "1 pearl" : remaining + " pearls";
sendWhisper(pull.ownerName, "Pulled. You have " + tail + " left.");
sendWhisper(pull.ownerName, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.pulled, "remaining", tail));
}

if (PLUGIN_CONFIG.idleGoal.enabled) {
Expand Down
Loading