Rich Embeds

Embeds let you send formatted messages with titles, descriptions, colors, fields, images, and more. The EmbedBuilder provides a fluent API with built-in validation.

EmbedBuilder

Build embeds using a chainable fluent API:

import fluxer4j.embed.EmbedBuilder;
import fluxer4j.data.models.Embed;

Embed embed = new EmbedBuilder()
    .withTitle("Server Status")
    .withDescription("All systems are **operational**.")
    .withColor(0x3fb950)                       // Green
    .withUrl("https://status.example.com")
    .withCurrentTimestamp()
    .build();

Available Methods

MethodDescription
withTitle(String)Set the embed title (max 256 chars)
withDescription(String)Set the description (max 4096 chars)
withUrl(String)Make the title a hyperlink
withColor(int)Set the color bar (hex RGB, e.g. 0x5865F2)
withColor(int r, int g, int b)Set color from RGB components
withTimestamp(Instant)Set a specific timestamp
withCurrentTimestamp()Use the current time
withThumbnailUrl(String)Set a small image (top-right)
withImageUrl(String)Set a large image
withAuthor(name, iconUrl, url)Set the author section
withFooter(text, iconUrl)Set the footer section
addField(name, value, inline)Add a field (max 25 fields)
build()Build and validate the embed

Fields

Fields can be inline (displayed side by side) or full-width:

Embed embed = new EmbedBuilder()
    .withTitle("User Info")
    .addField("Username", "Alice", true)      // Inline
    .addField("Status", "Online", true)        // Inline (same row)
    .addField("Bio", "Hello, I'm Alice!", false) // Full width
    .build();

Images

Embed embed = new EmbedBuilder()
    .withTitle("Gallery")
    .withImageUrl("https://example.com/photo.png")        // Large image
    .withThumbnailUrl("https://example.com/thumb.png")   // Small image (top-right)
    .build();
💡
Image URLs must start with http://, https://, or attachment://.

Embed Limits

The builder validates these limits and throws IllegalArgumentException / IllegalStateException if exceeded:

PropertyMax Length
Title256 characters
Description4,096 characters
Fields25 fields
Total embed length6,000 characters (sum of title + description + author name + footer text + all field names/values)

Sending Embeds

// Via ApiClient
Message msg = new Message();
msg.setContent("Check this out:");
msg.setEmbeds(List.of(embed));
api.sendMessage(channelId, msg).join();

// Inside a command (ModuleBase)
@CommandAttribute(name = "embed")
public CompletableFuture<Message> embedCommand() {
    Embed embed = new EmbedBuilder()
        .withTitle("Hello!")
        .withColor(0x5865F2)
        .build();

    Message msg = new Message();
    msg.setEmbeds(List.of(embed));
    return getContext().getClient().sendMessage(getContext().getChannelId(), msg);
}

Voice Support

Fluxer4J supports joining voice channels and streaming audio via UDP or LiveKit WebRTC. The VoiceManager handles connections per guild.

Voice support requires the webrtc-java dependency (included by default). LiveKit connections use WebRTC; UDP connections use direct Opus streaming.

VoiceManager

The VoiceManager is thread-safe and manages one voice connection per guild.

MethodDescription
joinAsync(VoiceChannel)Join a voice channel (returns CompletableFuture)
leave(long guildId)Leave the voice channel in a guild
leaveAsync(long guildId)Leave asynchronously
isConnected(long guildId)Check if connected in a guild
getVoiceConnection(long guildId)Get the UDP voice connection
getLiveKitConnection(long guildId)Get the LiveKit voice connection
close()Disconnect all voice connections

Joining & Leaving

// Get the voice manager (available after READY event)
VoiceManager voice = bot.getVoiceManager();

// Create a voice channel reference
VoiceChannel channel = new VoiceChannel(channelId, guildId);

// Join
voice.joinAsync(channel).thenAccept(conn -> {
    System.out.println("Connected to voice!");
});

// Leave
voice.leave(guildId);

Audio Playback (UDP)

For UDP voice connections, stream length-prefixed Opus packets:

VoiceConnection vc = voice.getVoiceConnection(guildId);
if (vc != null) {
    // Load audio from a URL (Opus format)
    URL audioUrl = URI.create("https://example.com/audio.opus").toURL();
    InputStream stream = audioUrl.openStream();

    // Create an AudioSource and play
    AudioSource source = AudioSource.fromLengthPrefixedOpusStream(stream);
    vc.play(source);

    // Stop playback
    vc.stop();

    // Check status
    boolean speaking = vc.isSpeaking();
}

LiveKit (WebRTC)

For LiveKit voice connections, stream raw PCM audio:

LiveKitVoiceConnection lk = voice.getLiveKitConnection(guildId);
if (lk != null) {
    // Get PCM audio stream (e.g. from ffmpeg/yt-dlp)
    InputStream pcmStream = PcmFromUrl.openPcmStream(url);
    lk.playPcm(pcmStream);

    // Stop
    lk.stop();
}
💡
The SDK automatically detects whether a voice server uses UDP or LiveKit and creates the appropriate connection type.

Voice Events

Listen for voice connection events with VoiceEventListener:

vc.addEventListener(new VoiceEventListener() {
    @Override
    public void onDisconnect(String reason) {
        System.out.println("Disconnected: " + reason);
    }

    @Override
    public void onPlaybackFinished() {
        System.out.println("Playback finished!");
    }
});

Full Voice Command Example

public class MusicCommands extends ModuleBase {

    private VoiceManager getVoice() {
        return getService(Fluxer4JBot.class).getVoiceManager();
    }

    @CommandAttribute(name = "join")
    @SummaryAttribute("Join a voice channel by ID")
    public CompletableFuture<Message> joinCommand(long channelId) {
        Long guildId = getContext().getGuildId();
        if (guildId == null) return replyAsync("Guild only!");

        VoiceChannel channel = new VoiceChannel(channelId, guildId);
        getVoice().joinAsync(channel);
        return replyAsync("Joining voice channel...");
    }

    @CommandAttribute(name = "leave")
    public CompletableFuture<Message> leaveCommand() {
        Long guildId = getContext().getGuildId();
        if (guildId == null) return replyAsync("Guild only!");

        getVoice().leave(guildId);
        return replyAsync("Left voice channel.");
    }

    @CommandAttribute(name = "stop")
    public CompletableFuture<Message> stopCommand() {
        Long guildId = getContext().getGuildId();
        VoiceConnection vc = getVoice().getVoiceConnection(guildId);
        if (vc != null) vc.stop();
        return replyAsync("Playback stopped.");
    }
}