Discord4J

Discord4J

高性能反应式 Java Discord 机器人开发框架

Discord4J 是一个基于反应式编程的 Java Discord 机器人开发框架。它支持官方 API,提供模块化架构,适用于不同规模的机器人项目。该框架遵循反应式流协议,具有高效性能和自动速率限制等特性。Discord4J 兼容 Kotlin 协程,并有活跃的社区支持。

Discord4JJavaDiscordAPI反应式编程Github开源项目

Discord4J

<a href="https://discord4j.com"><img align="right" src="https://yellow-cdn.veclightyear.com/ab5030c0/37b0218c-2b14-4f0d-b611-ab4e91cb23f1.svg?sanitize=true" width=27%></a>

支持服务器邀请 Maven Central Javadocs GitHub 工作流状态(分支)

Discord4J 是一个快速、强大、无偏见、响应式的库,可以使用官方 Discord Bot API 在 Java、Kotlin 和其他 JVM 语言中快速轻松地开发 Discord 机器人。

🏃 快速示例

在这个 v3.2 版本的示例中,每当用户发送 !ping 消息时,机器人将立即回复 Pong!

确保在 开发者门户 中启用了机器人的 消息内容 意图。

public class ExampleBot { public static void main(String[] args) { String token = args[0]; DiscordClient client = DiscordClient.create(token); GatewayDiscordClient gateway = client.login().block(); gateway.on(MessageCreateEvent.class).subscribe(event -> { Message message = event.getMessage(); if ("!ping".equals(message.getContent())) { MessageChannel channel = message.getChannel().block(); channel.createMessage("Pong!").block(); } }); gateway.onDisconnect().block(); } }

完整的项目示例,请查看我们的示例项目仓库 这里

🔗 快速链接

💎 优势

  • 🚀 响应式 - Discord4J 遵循 reactive-streams 协议,确保 Discord 机器人无论规模大小都能平稳高效地运行。

  • 📜 官方 - 自动限速、自动重连策略和一致的命名约定是 Discord4J 提供的众多功能之一,确保您的 Discord 机器人符合 Discord 的规范,并在与我们的库交互时提供最少的意外。

  • 🛠️ 模块化 - Discord4J 将自身分解为模块,允许高级用户在较低级别与我们的 API 交互,以构建最小且快速的运行时,甚至添加自己的抽象。

  • ⚔️ 强大 - Discord4J 可用于开发任何规模的机器人。我们提供了许多工具来开发大规模机器人,包括 自定义分布式框架堆外缓存,以及与 Reactor 的交互,允许与 Spring 和 Micronaut 等框架完全集成。

  • 🏫 社区 - 我们以包容性的社区为荣,并愿意在遇到挑战时提供帮助;或者如果您只是想聊天!我们提供从 Discord4J 特定问题、一般编程和 Web 开发帮助,甚至 Reactor 特定问题的帮助。请务必访问我们的 Discord 服务器

📦 安装

Gradle

repositories { mavenCentral() } dependencies { implementation 'com.discord4j:discord4j-core:3.2.5' }

Gradle Kotlin DSL

repositories { mavenCentral() } dependencies { implementation("com.discord4j:discord4j-core:3.2.5") }

Maven

<dependencies> <dependency> <groupId>com.discord4j</groupId> <artifactId>discord4j-core</artifactId> <version>3.2.5</version> </dependency> </dependencies>

SBT

libraryDependencies ++= Seq( "com.discord4j" % "discord4j-core" % "3.2.5" )

🔀 Discord4J 版本

Discord4J 3.2.x 包括更简单且更强大的 API 来构建请求、新的实体缓存和依赖项升级带来的性能改进。查看我们的 迁移指南 了解更多详情。

Discord4J支持状态网关/API意图交互
v3.3.x开发中v9强制,非特权默认完全支持
v3.2.x当前v8强制,非特权默认完全支持
v3.1.x仅维护v6可选,无意图默认仅维护

有关兼容性的更多详情,请参阅 我们的文档

🎉 赞助商

我们要特别感谢所有赞助商为我们提供资金,使我们能够继续开发和托管仓库资源以及推动社区项目。特别要感谢以下这些杰出的个人:

⛰️ 大型机器人

以下是一些使用Discord4J的真实大型机器人示例:

  • Groovy - 曾是Discord上第二大机器人,在2021年8月关闭前为超过400万个服务器提供音乐服务。
  • ZeroTwo - 一个动漫多功能机器人,被100多万个服务器使用。
  • DisCal - 将Google日历无缝且全面地集成到Discord中;服务超过2.1万个服务器。
  • Shadbot - 一个可配置的多功能机器人,具有音乐、赌博小游戏、视频游戏统计等功能;在2021年8月关闭前服务近1.2万个服务器。

你是否拥有使用Discord4J的大型机器人?请在我们的Discord中询问管理员或提交拉取请求,将你的机器人添加到列表中!

⚛️ 响应式

Discord4J使用Project Reactor作为我们异步框架的基础。Reactor提供了一个简单而强大的API,使用户能够减少资源并提高性能。

public class ExampleBot { public static void main(String[] args) { String token = args[0]; DiscordClient client = DiscordClient.create(token); client.login().flatMapMany(gateway -> gateway.on(MessageCreateEvent.class)) .map(MessageCreateEvent::getMessage) .filter(message -> "!ping".equals(message.getContent())) .flatMap(Message::getChannel) .flatMap(channel -> channel.createMessage("Pong!")) .blockLast(); } }

Discord4J还提供了几种方法来帮助更好地组合响应式链,例如GatewayDiscordClient#withGateway和带有错误处理重载的EventDispatcher#on

public class ExampleBot { public static void main(String[] args) { String token = args[0]; DiscordClient client = DiscordClient.create(token); client.withGateway(gateway -> { Publisher<?> pingPong = gateway.on(MessageCreateEvent.class, event -> Mono.just(event.getMessage()) .filter(message -> "!ping".equals(message.getContent())) .flatMap(Message::getChannel) .flatMap(channel -> channel.createMessage("Pong!"))); Publisher<?> onDisconnect = gateway.onDisconnect() .doOnTerminate(() -> System.out.println("已断开连接!")); return Mono.when(pingPong, onDisconnect); }).block(); } }

🧵 Kotlin

通过使用Reactor,Discord4J在与kotlinx-coroutines-reactor库配对时与Kotlin协程有原生集成。

val token = args[0] val client = DiscordClient.create(token) client.withGateway { mono { it.on(MessageCreateEvent::class.java) .asFlow() .collect { val message = it.message if (message.content == "!ping") { val channel = message.channel.awaitSingle() channel.createMessage("Pong!").awaitSingle() } } } } .block()

🐛 常见错误

在未启用消息内容意图的情况下调用Message#getContent

从2022年9月1日起,Discord要求机器人启用"MESSAGE_CONTENT"意图才能访问消息内容。 要启用该意图,请前往Discord开发者门户并选择你的机器人。然后,转到"Bot"选项卡并启用"Message Content"意图。 然后,在创建DiscordClient时将该意图添加到你的机器人中:

GatewayDiscordClient client = DiscordClient.create(token) .gateway() .setEnabledIntents(IntentSet.nonPrivileged().and(IntentSet.of(Intent.MESSAGE_CONTENT))) .login() .block();

📚 示例

📑 消息嵌入

<img align="right" src="https://yellow-cdn.veclightyear.com/ab5030c0/b987c66a-d126-4a7f-8706-90869ddffa6e.png" height=420px>
// IMAGE_URL = https://cdn.betterttv.net/emote/55028cd2135896936880fdd7/3x // ANY_URL = https://www.youtube.com/watch?v=5zwY50-necw MessageChannel channel = ... EmbedCreateSpec.Builder builder = EmbedCreateSpec.builder(); builder.author("setAuthor", ANY_URL, IMAGE_URL); builder.image(IMAGE_URL); builder.title("setTitle/setUrl"); builder.url(ANY_URL); builder.description("setDescription\n" + "大D:是setImage\n" + "小D:是setThumbnail\n" + "<-- setColor"); builder.addField("addField", "inline = true", true); builder.addField("addFIeld", "inline = true", true); builder.addField("addFile", "inline = false", false); builder.thumbnail(IMAGE_URL); builder.footer("setFooter --> setTimestamp", IMAGE_URL); builder.timestamp(Instant.now()); channel.createMessage(builder.build()).block();

🏷️ 通过角色名称查找成员

用户通常更喜欢使用名称而不是ID。这个例子将演示如何搜索所有具有特定名称角色的成员。

Guild guild = ... Set<Member> roleMembers = new HashSet<>(); for (Member member : guild.getMembers().toIterable()) { for (Role role : member.getRoles().toIterable()) { if ("Developers".equalsIgnoreCase(role.getName())) { roleMembers.add(member); break; } } } return roleMembers;

或者,使用Reactor:

Guild guild = ... return guild.getMembers() .filterWhen(member -> member.getRoles() .map(Role::getName) .any("Developers"::equalsIgnoreCase));

🎵 语音和音乐

Discord4J为语音连接提供全面支持,并能够向连接到同一频道的其他用户发送音频。Discord4J可以接受任何Opus音频源,LavaPlayer是从YouTube、SoundCloud和其他提供商下载和编码音频的首选解决方案。

[!警告]
原始的LavaPlayer不再维护。可以在这里找到一个新的维护版本。 如果你需要Java 8支持,可以使用Walkyst的LavaPlayer分支,但它也不再维护!

首先,你需要实例化和配置一个通常是全局的AudioPlayerManager

public static final AudioPlayerManager PLAYER_MANAGER; static { PLAYER_MANAGER = new DefaultAudioPlayerManager(); // 这是Discord4J可以利用的优化策略,以最小化内存分配 PLAYER_MANAGER.getConfiguration().setFrameBufferFactory(NonAllocatingAudioFrameBuffer::new); AudioSourceManagers.registerRemoteSources(PLAYER_MANAGER); AudioSourceManagers.registerLocalSource(PLAYER_MANAGER); } 接下来,我们需要允许Discord4JAudioPlayer读取到AudioProvider public class LavaPlayerAudioProvider extends AudioProvider { private final AudioPlayer player; private final MutableAudioFrame frame; public LavaPlayerAudioProvider(AudioPlayer player) { // 为Discord4J的AudioProvider分配一个ByteBuffer来存储Discord的音频数据 super(ByteBuffer.allocate(StandardAudioDataFormats.DISCORD_OPUS.maximumChunkSize())); // 设置LavaPlayer的AudioFrame使用与Discord4J相同的缓冲区 frame = new MutableAudioFrame(); frame.setBuffer(getBuffer()); this.player = player; } @Override public boolean provide() { // AudioPlayer将音频数据写入AudioFrame boolean didProvide = player.provide(frame); if (didProvide) { getBuffer().flip(); } return didProvide; } } 通常,音频播放器会有队列或内部播放列表,让用户能够在歌曲播放完毕或要求跳过时自动循环播放歌曲。我们可以通过创建AudioTrackScheduler来外部管理这个队列,并将其传递给代码的其他部分,以允许查看、排队或跳过音轨。 public class AudioTrackScheduler extends AudioEventAdapter { private final List<AudioTrack> queue; private final AudioPlayer player; public AudioTrackScheduler(AudioPlayer player) { // 队列可能被不同的线程修改,所以要保证内存安全 // 但这并不能消除目前存在的几个竞态条件 queue = Collections.synchronizedList(new LinkedList<>()); this.player = player; } public List<AudioTrack> getQueue() { return queue; } public boolean play(AudioTrack track) { return play(track, false); } public boolean play(AudioTrack track, boolean force) { boolean playing = player.startTrack(track, !force); if (!playing) { queue.add(track); } return playing; } public boolean skip() { return !queue.isEmpty() && play(queue.remove(0), true); } @Override public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { // 如果音轨自然完成(FINISHED)或无法播放(LOAD_FAILED),则推进播放器 if (endReason.mayStartNext) { skip(); } } } 目前,Discord每个服务器只允许一个语音连接。在这个限制下,将我们目前处理的三个组件(AudioPlayerLavaPlayerAudioProviderAudioTrackScheduler)与特定的Guild关联起来是合乎逻辑的,它们自然由某个Snowflake唯一标识。从逻辑上讲,将这些对象组合成一个对象是有意义的,这样可以将它们放入Map中,在连接到语音频道或处理命令时更容易检索。 public class GuildAudioManager { private static final Map<Snowflake, GuildAudioManager> MANAGERS = new ConcurrentHashMap<>(); public static GuildAudioManager of(Snowflake id) { return MANAGERS.computeIfAbsent(id, ignored -> new GuildAudioManager()); } private final AudioPlayer player; private final AudioTrackScheduler scheduler; private final LavaPlayerAudioProvider provider; private GuildAudioManager() { player = PLAYER_MANAGER.createPlayer(); scheduler = new AudioTrackScheduler(player); provider = new LavaPlayerAudioProvider(player); player.addListener(scheduler); } // getter方法 } 最后,我们需要连接到语音频道。连接后,你会得到一个VoiceConnection对象,之后可以通过调用VoiceConnection#disconnect来断开与语音频道的连接。 VoiceChannel channel = ... AudioProvider provider = GuildAudioManager.of(channel.getGuildId()).getProvider(); VoiceConnection connection = channel.join(spec -> spec.setProvider(provider)).block(); // 在AudioLoadResultHandler中,将AudioTrack实例添加到AudioTrackScheduler(并向用户发送通知) PLAYER_MANAGER.loadItem("https://www.youtube.com/watch?v=dQw4w9WgXcQ", new AudioLoadResultHandler() { /* 重写方法 */ }) ### ❌ 自动断开语音频道连接 通常,在所有人都离开语音频道后,机器人应该自动断开连接,因为用户经常忘记手动断开机器人。下面的示例展示了如何使用反应式方法而不是命令式方法来优雅地解决这个问题。 VoiceChannel channel = ... Mono<Void> onDisconnect = channel.join(spec -> { /* TODO 初始化 */ }) .flatMap(connection -> { // 机器人本身有一个VoiceState;1个VoiceState表示机器人独自一人 Publisher<Boolean> voiceStateCounter = channel.getVoiceStates() .count() .map(count -> 1L == count); // 10秒后,检查机器人是否独自一人。这在机器人独自加入, // 但连接后没有其他人加入的情况下很有用 Mono<Void> onDelay = Mono.delay(Duration.ofSeconds(10L)) .filterWhen(ignored -> voiceStateCounter) .switchIfEmpty(Mono.never()) .then(); // 当人们加入和离开`channel`时,检查机器人是否独自一人。 // 注意,第一个过滤器并非严格必要,但它可以防止许多不必要的缓存调用 Mono<Void> onEvent = channel.getClient().getEventDispatcher().on(VoiceStateUpdateEvent.class) .filter(event -> event.getOld().flatMap(VoiceState::getChannelId).map(channel.getId()::equals).orElse(false)) .filterWhen(ignored -> voiceStateCounter) .next() .then(); // 如果onDelay或onEvent完成,则断开机器人连接! return Mono.first(onDelay, onEvent).then(connection.disconnect()); });

编辑推荐精选

扣子-AI办公

扣子-AI办公

职场AI,就用扣子

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

堆友

堆友

多风格AI绘画神器

堆友平台由阿里巴巴设计团队创建,作为一款AI驱动的设计工具,专为设计师提供一站式增长服务。功能覆盖海量3D素材、AI绘画、实时渲染以及专业抠图,显著提升设计品质和效率。平台不仅提供工具,还是一个促进创意交流和个人发展的空间,界面友好,适合所有级别的设计师和创意工作者。

图像生成AI工具AI反应堆AI工具箱AI绘画GOAI艺术字堆友相机AI图像热门
码上飞

码上飞

零代码AI应用开发平台

零代码AI应用开发平台,用户只需一句话简单描述需求,AI能自动生成小程序、APP或H5网页应用,无需编写代码。

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

酷表ChatExcel

酷表ChatExcel

大模型驱动的Excel数据处理工具

基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。

AI工具酷表ChatExcelAI智能客服AI营销产品使用教程
TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
AIWritePaper论文写作

AIWritePaper论文写作

AI论文写作指导平台

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

AI辅助写作AI工具AI论文工具论文写作智能生成大纲数据安全AI助手热门
博思AIPPT

博思AIPPT

AI一键生成PPT,就用博思AIPPT!

博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。

AI办公办公工具AI工具博思AIPPTAI生成PPT智能排版海量精品模板AI创作热门
潮际好麦

潮际好麦

AI赋能电商视觉革命,一站式智能商拍平台

潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。

下拉加载更多