Astral Realms Documentation Help

Developer API

AstralChat exposes its services through the main plugin instance and fires a cancellable Bukkit event for every channel message. Other plugins can integrate by depending on com.astralrealms:chat.

Maven coordinates

<dependency> <groupId>com.astralrealms</groupId> <artifactId>chat</artifactId> <version>1.0-SNAPSHOT</version> <scope>provided</scope> </dependency>

Repository: https://maven.astralrealms.fr/repository/maven-public/.

Plugin handle

import com.astralrealms.chat.AstralChat; AstralChat plugin = AstralChat.getPlugin(AstralChat.class);

Services

Every service is exposed via a Lombok-generated getter on the plugin instance.

Getter

Type

Use

plugin.channels()

ChannelService

Broadcast into a channel, pause/resume, network packet helpers.

plugin.chat()

PaperChatService

Render and broadcast components to channel subscribers. Implements the shared ChatService interface.

plugin.privateMessages()

PrivateMessageService

Programmatically send a PM.

plugin.ignores()

IgnoreService (impl: IgnoreServiceImpl)

Read and mutate ignore relationships. Shared via the AstralCore service repository.

plugin.filters()

FilterService

Run filters; register custom filters.

plugin.mentions()

MentionService

Dispatch a mention notification programmatically.

plugin.tags()

TagService

Look up a tag by id; iterate all tags.

plugin.players()

PlayerService

Subscription state, cached PlayerChatData.

plugin.moderation()

ModerationService

The recent-message cache (similarity filter, cooldowns) and the HTTP call to the moderation classifier — analyze(String) returns a CompletableFuture<ModerationResponse>.

plugin.autoModeration()

AutoModerationService

Threshold evaluation, staff notification, and the flagged_messages dataset — submit, recordFilterHit, review, repository().

plugin.messageStorage()

MessageStorageService

save(sender, ChatMessage) — persists to chat_messages and hands the message to auto moderation.

plugin.menus()

MenuContainer

AstralChat's own menus (the ignored-list menu).

plugin.configuration()

MainConfiguration

Loaded config.yml. Call findChannel, defaultChannel, isEnabled.

plugin.groupsConfiguration()

GroupsConfiguration

Loaded groups.yml.

plugin.filtersConfiguration()

FiltersConfiguration

Loaded filters.yml.

plugin.moderationConfiguration()

ModerationConfiguration

Loaded moderation.yml.

plugin.database()/plugin.cache()/plugin.messaging()

AstralCore services

The plugin's own MariaDB, Redis and RabbitMQ handles.

The chat service and ignore service are also registered with AstralCore's service repository so any other plugin can resolve them without a hard dependency on AstralChat:

AstralPaperAPI.getService(ChatService.class) .ifPresent(chat -> chat.broadcastMessage(component, "staff")); AstralPaperAPI.getService(IgnoreService.class) .ifPresent(ignores -> ignores.isIgnoring(viewer, candidate));

The shared ChatService interface is deliberately narrow — sendMessage(uuid, component), broadcastMessage(component[, channel]), actionBar(uuid, component) and isIgnoring(viewer, target). All four are network-aware: a target on another server is reached over RabbitMQ. Rendering a player's chat message (broadcastChatMessage) is not part of it and needs the concrete PaperChatService.

IgnoreService.isIgnoring is the raw relationship check. The AstralChat-specific IgnoreServiceImpl.isIgnored(viewer, sender) is the one to use before delivering something, because it also honours the staff bypass.

Broadcasting

ChatChannel channel = plugin.configuration().findChannel("global").orElseThrow(); Component message = Component.text("Server restart in 5 minutes", NamedTextColor.RED); plugin.channels().broadcast(channel, message);

For a full player chat message — subscription check, ChannelChatEvent, filters, rendering, cross-server fan-out, caching and storage — use the concrete chat service. It takes the unrendered body and returns the filtered body, or null when the message must not be shown:

Component filtered = plugin.chat().broadcastChatMessage(sender, "global", component, rawString); if (filtered == null) return; // blocked, cancelled, or the sender is not subscribed

Pause and cooldown are enforced by ChannelListener on the vanilla chat event, not by this call.

Sending a private message

plugin.privateMessages() .sendPrivateMessage(sender, recipient, "Hello!");

The call is network-aware and returns asynchronously via the RabbitMQ pipeline. Failure reasons surface as status codes which are mapped to message keys (see Private Messages).

Listening to chat events

ChannelChatEvent is a cancellable Bukkit event fired by PaperChatService.broadcastChatMessage, after the subscription check and before the line is rendered. It carries the unrendered message body, so a listener rewriting component rewrites the player's words only — the channel format, prefix and display name are applied afterwards and cannot be touched here. It is marked async whenever it is constructed off the main thread, which is the normal path for player chat.

FiltersListener is itself just a listener on this event, so custom listeners compete with the filters on equal terms — order them with the usual EventPriority.

import com.astralrealms.chat.event.ChannelChatEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import net.kyori.adventure.text.Component; public class ExampleListener implements Listener { @EventHandler public void onChannelChat(ChannelChatEvent event) { Player sender = event.player(); String raw = event.message(); Component component = event.component(); ChatChannel channel = event.channel(); // Rewrite — replace the message body event.component(component.append(Component.text(" *edited*"))); // Or cancel — sender's message never reaches subscribers if (raw.contains("forbidden")) { event.setCancelled(true); } } }

Method

Notes

player()

Sender.

message()

Raw string the player typed (pre-filter).

component()/component(Component)

The message body about to be rendered and broadcast.

channel()

The ChatChannel.

isCancelled()/setCancelled(boolean)

Inherited from AbstractCancellableEvent. Cancelling drops the message: the sender's line never reaches anyone, and nothing is stored.

Always check Bukkit.isPrimaryThread() before touching Bukkit APIs from inside the handler — the event fires async when AstralChat handles the message off the main thread.

Filters

Filters are configuration-driven, not runtime-registrable: FilterService walks filtersConfiguration().filters() and there is no register hook. The three available implementations are fixed by the ChatFilterType enum (REGEX, CAPS, SIMILARITY), each pairing a type name with the configuration record it deserializes into. Adding a fourth means adding an enum constant, a ChatFilter<T> subclass and a rebuild.

public abstract class ChatFilter<T> { public abstract FilterResult matches(AstralChat plugin, Player player, String message, Component component); }

The T is the filter's own @ConfigSerializable configuration record, and enabled plus the shared Actions block (log, notification-channel, cancel, replacement, custom-message, actions) come from the base class.

To run the configured filters against arbitrary text without going through chat:

FilterService.Result result = plugin.filters().filter(player, "global", rawString, component); if (result.blocked()) return;

FilterResult factories, for an implementation:

Factory

Meaning

FilterResult.noMatch()

Pass through.

FilterResult.match()

Match without a reason.

FilterResult.match(String)

Match with a reason string (used by notification-channel templates).

FilterResult.match(String, Component)

Match with a replacement component for the broadcast.

Reading per-player state

PlayerChatData is persisted by AstralSync — pull it through SyncAPI:

import com.astralrealms.chat.snapshot.PlayerChatData; import com.astralrealms.sync.SyncAPI; SyncAPI.findData(player.getUniqueId(), PlayerChatData.class) .ifPresent(data -> { boolean pmOn = data.privateMessagesEnabled(); String tag = data.chatTag(); Set<String> subs = data.subscribedChannels(); });

Mutating these flags directly bypasses the user-facing confirmation messages — use the action types from Actions for normal flows; only touch the snapshot directly for admin tooling.

Member

Meaning

channels()

Subscribed channel names (an immutable copy; mutate with addChannel/removeChannel).

privateMessagesEnabled()

/togglepm.

chatTag()

The equipped tag's id, or null. Re-validated against the tag's permission on every save.

nickname()

The /nick value, or null. Applied to the player's display name on load.

showPaidRoles()

The toggle-paid-roles flag, read by %chat_prefix%.

mentionsEnabled()

The toggle-chat-mentions flag.

lastMessageTimes()

Per-channel timestamp of the player's last message, backing the cooldowns.

Moderation

plugin.moderation() .analyze("some message") .thenAccept(response -> response.first() .ifPresent(result -> result.scores().forEach((category, score) -> …)));

analyze returns null when the classifier is disabled or the content is blank, and answers identical content from a 60-second in-memory cache. To record a decision of your own, build a FlaggedMessage through its builder and insert it via plugin.autoModeration().repository(); to apply a human verdict, call plugin.autoModeration().review(id, ReviewLabel.TOXIC, reviewerId). See Auto Moderation.

Last modified: 03 September 2026