{"id":13410362,"url":"https://github.com/Discord4J/Discord4J","last_synced_at":"2025-03-14T15:32:27.709Z","repository":{"id":40213054,"uuid":"45755430","full_name":"Discord4J/Discord4J","owner":"Discord4J","description":"Discord4J is a fast, powerful, unopinionated, reactive library to enable quick and easy development of Discord bots for Java, Kotlin, and other JVM languages using the official Discord Bot API.","archived":false,"fork":false,"pushed_at":"2025-03-08T14:01:20.000Z","size":25050,"stargazers_count":1817,"open_issues_count":24,"forks_count":264,"subscribers_count":38,"default_branch":"master","last_synced_at":"2025-03-11T23:05:17.078Z","etag":null,"topics":["api","bot","bot-api","d4j","discord","discord-api","discord-bot","discord-bot-api","discord4j","java","javadoc","reactor","rest","rest-api","websocket"],"latest_commit_sha":null,"homepage":"https://discord4j.com","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Discord4J.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"Discord4J","open_collective":"Discord4J"}},"created_at":"2015-11-07T21:29:08.000Z","updated_at":"2025-03-09T17:25:48.000Z","dependencies_parsed_at":"2023-11-20T10:03:53.588Z","dependency_job_id":"ef09eb58-cea3-4a53-b044-e0c630ff1f65","html_url":"https://github.com/Discord4J/Discord4J","commit_stats":{"total_commits":3534,"total_committers":104,"mean_commits":33.98076923076923,"dds":0.6547821165817771,"last_synced_commit":"fc97b3131d8e2e42835e6dcaee3c593bc694f35e"},"previous_names":[],"tags_count":104,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Discord4J%2FDiscord4J","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Discord4J%2FDiscord4J/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Discord4J%2FDiscord4J/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Discord4J%2FDiscord4J/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Discord4J","download_url":"https://codeload.github.com/Discord4J/Discord4J/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243600764,"owners_count":20317330,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","bot","bot-api","d4j","discord","discord-api","discord-bot","discord-bot-api","discord4j","java","javadoc","reactor","rest","rest-api","websocket"],"created_at":"2024-07-30T20:01:06.448Z","updated_at":"2025-03-14T15:32:27.702Z","avatar_url":"https://github.com/Discord4J.png","language":"Java","readme":"# Discord4J\n\n\u003ca href=\"https://discord4j.com\"\u003e\u003cimg align=\"right\" src=\"https://raw.githubusercontent.com/Discord4J/discord4j-web/master/public/logo.svg?sanitize=true\" width=27%\u003e\u003c/a\u003e\n\n[![Support Server Invite](https://img.shields.io/discord/208023865127862272.svg?color=7289da\u0026label=Discord4J\u0026logo=discord\u0026style=flat-square)](https://discord.gg/d4j)\n[![Maven Central](https://img.shields.io/maven-central/v/com.discord4j/discord4j-core/3.2.svg?style=flat-square)](https://search.maven.org/artifact/com.discord4j/discord4j-core)\n[![Javadocs](https://javadoc.io/badge2/com.discord4j/discord4j-core/3.2.7/javadoc.svg?color=blue\u0026style=flat-square)](https://javadoc.io/doc/com.discord4j/discord4j-core/3.2.7)\n[![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/Discord4J/Discord4J/gradle.yml?branch=3.2.x\u0026logo=github\u0026style=flat-square)](https://github.com/Discord4J/Discord4J/actions)\n\nDiscord4J is a fast, powerful, unopinionated, reactive library to enable quick and easy development of Discord bots for Java, Kotlin, and other JVM languages using the official [Discord Bot API](https://discord.com/developers/docs/intro).\n\n## 🏃 Quick Example\n\nIn this example for v3.2, whenever a user sends a `!ping` message the bot will immediately respond with `Pong!`.\n\nMake sure your bot has the **Message Content** intent enabled in your [developer portal](https://discord.com/developers/applications).\n\n```java\npublic class ExampleBot {\n\n  public static void main(String[] args) {\n    String token = args[0];\n    DiscordClient client = DiscordClient.create(token);\n    GatewayDiscordClient gateway = client.login().block();\n\n    gateway.on(MessageCreateEvent.class).subscribe(event -\u003e {\n      Message message = event.getMessage();\n      if (\"!ping\".equals(message.getContent())) {\n        MessageChannel channel = message.getChannel().block();\n        channel.createMessage(\"Pong!\").block();\n      }\n    });\n\n    gateway.onDisconnect().block();\n  }\n}\n```\n\nFor a full project example, check out our example projects repository [here](https://github.com/Discord4J/example-projects).\n\n## 🔗 Quick Links\n\n* [Javadocs](https://www.javadoc.io/doc/com.discord4j/discord4j-core)\n* [Documentation](https://docs.discord4j.com)\n* [Example Projects](https://github.com/Discord4J/example-projects)\n* [More examples](https://docs.discord4j.com/examples)\n* [Gateway Intents](https://discord.com/developers/docs/topics/gateway#gateway-intents)\n* [Discord](https://discord.gg/d4j)\n\n## 💎 Benefits\n\n* 🚀 **Reactive** - Discord4J follows the [reactive-streams](http://www.reactive-streams.org/) protocol to ensure Discord bots run smoothly and efficiently regardless of size.\n\n* 📜 **Official** - Automatic rate limiting, automatic reconnection strategies, and consistent naming conventions are among the many features Discord4J offers to ensure your Discord bots run up to Discord's specifications and to provide the least amount of surprises when interacting with our library.\n\n* 🛠️ **Modular** - Discord4J breaks itself into modules to allow advanced users to interact with our API at lower levels to build minimal and fast runtimes or even add their own abstractions.\n\n* ⚔️ **Powerful** - Discord4J can be used to develop any bot, big or small. We offer many tools for developing large-scale bots from [custom distribution frameworks](https://github.com/Discord4J/connect), [off-heap caching](https://github.com/Discord4J/Stores/tree/master/redis), and its interaction with Reactor allows complete integration with frameworks such as Spring and Micronaut.\n\n* 🏫 **Community** - We pride ourselves on our inclusive community and are willing to help whenever challenges arise; or if you just want to chat! We offer help ranging from Discord4J specific problems, to general programming and web development help, and even Reactor-specific questions. Be sure to visit us on our [Discord server](https://discord.gg/d4j)!\n\n## 📦 Installation\n\n* [Creating a new Gradle project with IntelliJ](https://www.jetbrains.com/help/idea/getting-started-with-gradle.html) *(recommended)*\n* [Creating a new Maven project with IntelliJ](https://www.jetbrains.com/help/idea/maven-support.html)\n* [Creating a new Gradle project with Eclipse](https://www.vogella.com/tutorials/EclipseGradle/article.html#creating-gradle-projects)\n* [Creating a new Maven project with Eclipse](https://www.vogella.com/tutorials/EclipseMaven/article.html#exercise-create-a-new-maven-enabled-project-via-eclipse)\n\n### Gradle\n```groovy\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation 'com.discord4j:discord4j-core:3.2.7'\n}\n```\n\n### Gradle Kotlin DSL\n```kotlin\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation(\"com.discord4j:discord4j-core:3.2.7\")\n}\n```\n\n### Maven\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003ecom.discord4j\u003c/groupId\u003e\n    \u003cartifactId\u003ediscord4j-core\u003c/artifactId\u003e\n    \u003cversion\u003e3.2.7\u003c/version\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\n### SBT\n```scala\nlibraryDependencies ++= Seq(\n  \"com.discord4j\" % \"discord4j-core\" % \"3.2.7\"\n)\n```\n\n## 🔀 Discord4J Versions\n\nDiscord4J 3.2.x includes simpler and more powerful APIs to build requests, a new entity cache and performance improvements from dependency upgrades. Check our [Migration Guide](https://docs.discord4j.com/migrating-from-v3-1-to-v3-2) for more details.\n\n| Discord4J                                                   | Support          | Gateway/API | Intents                           | Interactions    |\n|-------------------------------------------------------------|------------------|-------------|-----------------------------------|-----------------|\n| [v3.3.x](https://github.com/Discord4J/Discord4J/tree/master)| In development   | v9          | Mandatory, non-privileged default | Fully supported |\n| [v3.2.x](https://github.com/Discord4J/Discord4J/tree/3.2.x) | Current          | v8          | Mandatory, non-privileged default | Fully supported |\n| [v3.1.x](https://github.com/Discord4J/Discord4J/tree/3.1.x) | Maintenance only | v6          | Optional, no intent default       | Maintenance only|\n\nSee [our docs](https://docs.discord4j.com/versions) for more details about compatibility.\n\n## 🎉 Sponsors\n\nWe would like to give a special thanks to all of our sponsors for providing us the funding to continue developing and hosting repository resources as well as driving forward initiatives for community programs. In particular, we would like to give a special shoutout to these wonderful individuals:\n\n* [decyg](https://github.com/d-g-n)\n* [nikammerlaan](https://github.com/nikammerlaan)\n* [ByteAlex](https://github.com/ByteAlex)\n* [Shadorc](https://github.com/Shadorc)\n\n## ⛰️ Large Bots\n\nHere are some real-world examples of large bots using Discord4J:\n\n* [Groovy](https://groovy.bot/) - Was the second-largest bot on Discord, serving music to over 4 million servers before its shutdown in August 2021.\n* [ZeroTwo](https://zerotwo.bot/) - An anime multi-purpose bot used in over 1 million servers.\n* [DisCal](https://www.discalbot.com/) - Implements Google Calendar into Discord as seamlessly and comprehensively as possible; serving over 21k servers.\n* [Shadbot](https://github.com/Shadorc/Shadbot) - A configurable multipurpose bot with music, gambling mini-games, video game stats, and more; serving nearly 12K servers before its shutdown in August 2021.\n* [See-Bot](https://seebot.dev) - A Fortnite focused bot with stat tracking, player lookup, mission alerts, and much more; in over 10k servers.\n\nDo you own a large bot using Discord4J? Ask an admin in our Discord or submit a pull request to add your bot to the list!\n\n## ⚛️ Reactive\n\nDiscord4J uses [Project Reactor](https://projectreactor.io/) as the foundation for our asynchronous framework. Reactor provides a simple yet extremely powerful API that enables users to reduce resources and increase performance.\n\n```java\npublic class ExampleBot {\n\n  public static void main(String[] args) {\n    String token = args[0];\n    DiscordClient client = DiscordClient.create(token);\n\n    client.login().flatMapMany(gateway -\u003e gateway.on(MessageCreateEvent.class))\n      .map(MessageCreateEvent::getMessage)\n      .filter(message -\u003e \"!ping\".equals(message.getContent()))\n      .flatMap(Message::getChannel)\n      .flatMap(channel -\u003e channel.createMessage(\"Pong!\"))\n      .blockLast();\n  }\n}\n```\n\nDiscord4J also provides several methods to aid in better reactive chain compositions, such as `GatewayDiscordClient#withGateway` and `EventDispatcher#on` with an [error handling](https://docs.discord4j.com/error-handling) overload.\n\n```java\npublic class ExampleBot {\n\n    public static void main(String[] args) {\n        String token = args[0];\n        DiscordClient client = DiscordClient.create(token);\n\n        client.withGateway(gateway -\u003e {\n            Publisher\u003c?\u003e pingPong = gateway.on(MessageCreateEvent.class, event -\u003e\n                    Mono.just(event.getMessage())\n                            .filter(message -\u003e \"!ping\".equals(message.getContent()))\n                            .flatMap(Message::getChannel)\n                            .flatMap(channel -\u003e channel.createMessage(\"Pong!\")));\n\n            Publisher\u003c?\u003e onDisconnect = gateway.onDisconnect()\n                    .doOnTerminate(() -\u003e System.out.println(\"Disconnected!\"));\n\n            return Mono.when(pingPong, onDisconnect);\n        }).block();\n    }\n}\n```\n\n## 🧵 Kotlin\n\nBy utilizing Reactor, Discord4J has native integration with [Kotlin coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) when paired with the [kotlinx-coroutines-reactor](https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines-reactor) library.\n\n```kotlin\nval token = args[0]\nval client = DiscordClient.create(token)\n\nclient.withGateway {\n  mono {\n    it.on(MessageCreateEvent::class.java)\n      .asFlow()\n      .collect {\n        val message = it.message\n        if (message.content == \"!ping\") {\n          val channel = message.channel.awaitSingle()\n          channel.createMessage(\"Pong!\").awaitSingle()\n        }\n      }\n  }\n}\n.block()\n```\n\n## 🐛 Common mistakes\n\n### Calling Message#getContent without enabling the Message Content intent\nStarting from September 1, 2022, Discord requires bots to enable the \"MESSAGE_CONTENT\" intent to access the content of messages.\nTo enable the intent, go to the [Discord Developer Portal](https://discord.com/developers/applications) and select your bot. Then, go to the \"Bot\" tab and enable the \"Message Content\" intent.\nThen, add the intent to your bot when creating the DiscordClient:\n```java\nGatewayDiscordClient client = DiscordClient.create(token)\n  .gateway()\n  .setEnabledIntents(IntentSet.nonPrivileged().or(IntentSet.of(Intent.MESSAGE_CONTENT)))\n  .login()\n  .block();\n```\n\n## 📚 Examples\n\n### 📑 Message Embeds\n\n\u003cimg align=\"right\" src=\"https://user-images.githubusercontent.com/6114565/82622174-b44a5900-9ba2-11ea-9bc1-2f558958f4cb.png\" height=420px\u003e\n\n```java\n// IMAGE_URL = https://cdn.betterttv.net/emote/55028cd2135896936880fdd7/3x\n// ANY_URL = https://www.youtube.com/watch?v=5zwY50-necw\nMessageChannel channel = ...\nEmbedCreateSpec.Builder builder = EmbedCreateSpec.builder();\nbuilder.author(\"setAuthor\", ANY_URL, IMAGE_URL);\nbuilder.image(IMAGE_URL);\nbuilder.title(\"setTitle/setUrl\");\nbuilder.url(ANY_URL);\nbuilder.description(\"setDescription\\n\" +\n      \"big D: is setImage\\n\" +\n      \"small D: is setThumbnail\\n\" +\n      \"\u003c-- setColor\");\nbuilder.addField(\"addField\", \"inline = true\", true);\nbuilder.addField(\"addFIeld\", \"inline = true\", true);\nbuilder.addField(\"addFile\", \"inline = false\", false);\nbuilder.thumbnail(IMAGE_URL);\nbuilder.footer(\"setFooter --\u003e setTimestamp\", IMAGE_URL);\nbuilder.timestamp(Instant.now());\nchannel.createMessage(builder.build()).block();\n```\n\n### 🏷️ Find Members by Role Name\n\nUsers typically prefer working with names instead of IDs. This example will demonstrate how to search for all members that have a role with a specific name.\n\n```java\nGuild guild = ...\nSet\u003cMember\u003e roleMembers = new HashSet\u003c\u003e();\n\nfor (Member member : guild.getMembers().toIterable()) {\n  for (Role role : member.getRoles().toIterable()) {\n    if (\"Developers\".equalsIgnoreCase(role.getName())) {\n      roleMembers.add(member);\n      break;\n    }\n  }\n}\n\nreturn roleMembers;\n```\n\nAlternatively, using Reactor:\n```java\nGuild guild = ...\nreturn guild.getMembers()\n  .filterWhen(member -\u003e member.getRoles()\n    .map(Role::getName)\n    .any(\"Developers\"::equalsIgnoreCase));\n```\n\n### 🎵 Voice and Music\n\nDiscord4J provides full support for voice connections and the ability to send audio to other users connected to the same channel. Discord4J can accept any [Opus](https://opus-codec.org/) audio source with LavaPlayer being the preferred solution for downloading and encoding audio from YouTube, SoundCloud, and other providers.\n\n\u003e [!WARNING]  \n\u003e The original LavaPlayer is no longer maintained. A new maintained version can be found [here](https://github.com/lavalink-devs/lavaplayer).\n\u003e If you need Java 8 support, you can use [Walkyst's LavaPlayer fork](https://github.com/Walkyst/lavaplayer-fork), but it is also no longer maintained!\n\nTo get started, you will first need to instantiate and configure an, conventionally global, `AudioPlayerManager`.\n\n```java\npublic static final AudioPlayerManager PLAYER_MANAGER;\n\nstatic {\n  PLAYER_MANAGER = new DefaultAudioPlayerManager();\n  // This is an optimization strategy that Discord4J can utilize to minimize allocations\n  PLAYER_MANAGER.getConfiguration().setFrameBufferFactory(NonAllocatingAudioFrameBuffer::new);\n  AudioSourceManagers.registerRemoteSources(PLAYER_MANAGER);\n  AudioSourceManagers.registerLocalSource(PLAYER_MANAGER);\n}\n```\n\nNext, we need to allow Discord4J to read from an `AudioPlayer` to an `AudioProvider`.\n\n```java\npublic class LavaPlayerAudioProvider extends AudioProvider {\n\n  private final AudioPlayer player;\n  private final MutableAudioFrame frame;\n\n  public LavaPlayerAudioProvider(AudioPlayer player) {\n    // Allocate a ByteBuffer for Discord4J's AudioProvider to hold audio data for Discord\n    super(ByteBuffer.allocate(StandardAudioDataFormats.DISCORD_OPUS.maximumChunkSize()));\n    // Set LavaPlayer's AudioFrame to use the same buffer as Discord4J's\n    frame = new MutableAudioFrame();\n    frame.setBuffer(getBuffer());\n    this.player = player;\n  }\n\n  @Override\n  public boolean provide() {\n    // AudioPlayer writes audio data to the AudioFrame\n    boolean didProvide = player.provide(frame);\n\n    if (didProvide) {\n      getBuffer().flip();\n    }\n\n    return didProvide;\n  }\n}\n```\n\nTypically, audio players will have queues or internal playlists for users to be able to automatically cycle through songs as they are finished or requested to be skipped over. We can manage this queue externally and pass it to other areas of our code to allow tracks to be viewed, queued, or skipped over by creating an `AudioTrackScheduler`.\n\n```java\npublic class AudioTrackScheduler extends AudioEventAdapter {\n\n  private final List\u003cAudioTrack\u003e queue;\n  private final AudioPlayer player;\n\n  public AudioTrackScheduler(AudioPlayer player) {\n    // The queue may be modifed by different threads so guarantee memory safety\n    // This does not, however, remove several race conditions currently present\n    queue = Collections.synchronizedList(new LinkedList\u003c\u003e());\n    this.player = player;\n  }\n\n  public List\u003cAudioTrack\u003e getQueue() {\n    return queue;\n  }\n\n  public boolean play(AudioTrack track) {\n    return play(track, false);\n  }\n\n  public boolean play(AudioTrack track, boolean force) {\n    boolean playing = player.startTrack(track, !force);\n\n    if (!playing) {\n      queue.add(track);\n    }\n\n    return playing;\n  }\n\n  public boolean skip() {\n    return !queue.isEmpty() \u0026\u0026 play(queue.remove(0), true);\n  }\n\n  @Override\n  public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {\n    // Advance the player if the track completed naturally (FINISHED) or if the track cannot play (LOAD_FAILED)\n    if (endReason.mayStartNext) {\n      skip();\n    }\n  }\n}\n```\n\nCurrently, Discord only allows 1 voice connection per server. Working within this limitation, it is logical to think of the 3 components we have worked with thus far (`AudioPlayer`, `LavaPlayerAudioProvider`, and `AudioTrackScheduler`) to be correlated to a specific `Guild`, naturally unique by some `Snowflake`. Logically, it makes sense to combine these objects into one, so that they can be put into a `Map` for easier retrieval when connecting to a voice channel or when working with commands.\n\n```java\npublic class GuildAudioManager {\n\n  private static final Map\u003cSnowflake, GuildAudioManager\u003e MANAGERS = new ConcurrentHashMap\u003c\u003e();\n\n  public static GuildAudioManager of(Snowflake id) {\n    return MANAGERS.computeIfAbsent(id, ignored -\u003e new GuildAudioManager());\n  }\n\n  private final AudioPlayer player;\n  private final AudioTrackScheduler scheduler;\n  private final LavaPlayerAudioProvider provider;\n\n  private GuildAudioManager() {\n    player = PLAYER_MANAGER.createPlayer();\n    scheduler = new AudioTrackScheduler(player);\n    provider = new LavaPlayerAudioProvider(player);\n\n    player.addListener(scheduler);\n  }\n\n  // getters\n}\n```\n\nFinally, we need to connect to the voice channel. After connecting you are given a `VoiceConnection` object where you can utilize it later to disconnect from the voice channel by calling `VoiceConnection#disconnect`.\n\n```java\nVoiceChannel channel = ...\nAudioProvider provider = GuildAudioManager.of(channel.getGuildId()).getProvider();\nVoiceConnection connection = channel.join(spec -\u003e spec.setProvider(provider)).block();\n\n// In the AudioLoadResultHandler, add AudioTrack instances to the AudioTrackScheduler (and send notifications to users)\nPLAYER_MANAGER.loadItem(\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\", new AudioLoadResultHandler() { /* overrides */ })\n```\n\n### ❌ Disconnecting from a Voice Channel Automatically\n\nTypically, after everyone has left a voice channel, the bot should disconnect automatically as users typically forget to disconnect the bot manually. This problem can be solved rather elegantly using a reactive approach over an imperative one as the example below demonstrates.\n\n```java\nVoiceChannel channel = ...\nMono\u003cVoid\u003e onDisconnect = channel.join(spec -\u003e { /* TODO Initialize */ })\n  .flatMap(connection -\u003e {\n    // The bot itself has a VoiceState; 1 VoiceState signals bot is alone\n    Publisher\u003cBoolean\u003e voiceStateCounter = channel.getVoiceStates()\n      .count()\n      .map(count -\u003e 1L == count);\n\n    // After 10 seconds, check if the bot is alone. This is useful if\n    // the bot joined alone, but no one else joined since connecting\n    Mono\u003cVoid\u003e onDelay = Mono.delay(Duration.ofSeconds(10L))\n      .filterWhen(ignored -\u003e voiceStateCounter)\n      .switchIfEmpty(Mono.never())\n      .then();\n\n    // As people join and leave `channel`, check if the bot is alone.\n    // Note the first filter is not strictly necessary, but it does prevent many unnecessary cache calls\n    Mono\u003cVoid\u003e onEvent = channel.getClient().getEventDispatcher().on(VoiceStateUpdateEvent.class)\n      .filter(event -\u003e event.getOld().flatMap(VoiceState::getChannelId).map(channel.getId()::equals).orElse(false))\n      .filterWhen(ignored -\u003e voiceStateCounter)\n      .next()\n      .then();\n\n    // Disconnect the bot if either onDelay or onEvent are completed!\n    return Mono.first(onDelay, onEvent).then(connection.disconnect());\n  });\n```\n","funding_links":["https://github.com/sponsors/Discord4J","https://opencollective.com/Discord4J"],"categories":["API Libraries","Libraries","💻 API Libraries"],"sub_categories":["Java","JVM"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDiscord4J%2FDiscord4J","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDiscord4J%2FDiscord4J","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDiscord4J%2FDiscord4J/lists"}