Discord4J adalah pustaka reaktif yang cepat, kuat, tidak beropini, dan reaktif untuk memungkinkan pengembangan bot Discord dengan cepat dan mudah untuk Java, Kotlin, dan bahasa JVM lainnya menggunakan API Bot Discord resmi.
Dalam contoh v3.2 ini, setiap kali pengguna mengirimkan pesan !ping
bot akan segera merespons dengan Pong!
.
Pastikan bot Anda mengaktifkan maksud Konten Pesan di portal pengembang Anda.
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 ();
}
}
Untuk contoh proyek lengkap, lihat repositori contoh proyek kami di sini.
Reaktif - Discord4J mengikuti protokol aliran reaktif untuk memastikan bot Discord berjalan dengan lancar dan efisien berapa pun ukurannya.
Resmi - Pembatasan tarif otomatis, strategi koneksi ulang otomatis, dan konvensi penamaan yang konsisten adalah beberapa di antara banyak fitur yang ditawarkan Discord4J untuk memastikan bot Discord Anda berjalan sesuai spesifikasi Discord dan memberikan kejutan paling sedikit saat berinteraksi dengan perpustakaan kami.
Modular - Discord4J memecah dirinya menjadi beberapa modul untuk memungkinkan pengguna tingkat lanjut berinteraksi dengan API kami di tingkat yang lebih rendah untuk membangun runtime yang minimal dan cepat atau bahkan menambahkan abstraksi mereka sendiri.
⚔️ Kuat - Discord4J dapat digunakan untuk mengembangkan bot apa pun, besar atau kecil. Kami menawarkan banyak alat untuk mengembangkan bot skala besar mulai dari kerangka distribusi khusus, caching off-heap, dan interaksinya dengan Reactor memungkinkan integrasi lengkap dengan kerangka kerja seperti Spring dan Micronaut.
? Komunitas - Kami bangga dengan komunitas inklusif kami dan bersedia membantu kapan pun tantangan muncul; atau jika Anda hanya ingin mengobrol! Kami menawarkan bantuan mulai dari masalah khusus Discord4J, hingga bantuan pemrograman umum dan pengembangan web, dan bahkan pertanyaan khusus Reaktor. Pastikan untuk mengunjungi kami di server Discord kami!
repositories {
mavenCentral()
}
dependencies {
implementation ' com.discord4j:discord4j-core:3.2.7 '
}
repositories {
mavenCentral()
}
dependencies {
implementation( " com.discord4j:discord4j-core:3.2.7 " )
}
< dependencies >
< dependency >
< groupId >com.discord4j</ groupId >
< artifactId >discord4j-core</ artifactId >
< version >3.2.7</ version >
</ dependency >
</ dependencies >
libraryDependencies ++= Seq (
" com.discord4j " % " discord4j-core " % " 3.2.7 "
)
Discord4J 3.2.x menyertakan API yang lebih sederhana dan kuat untuk membuat permintaan, cache entitas baru, dan peningkatan kinerja dari peningkatan ketergantungan. Lihat Panduan Migrasi kami untuk detail lebih lanjut.
Perselisihan4J | Mendukung | Gerbang/API | Maksud | Interaksi |
---|---|---|---|---|
v3.3.x | Dalam pengembangan | v9 | Default wajib dan tidak memiliki hak istimewa | Didukung penuh |
v3.2.x | Saat ini | v8 | Default wajib dan tidak memiliki hak istimewa | Didukung penuh |
v3.1.x | Hanya pemeliharaan | v6 | Opsional, tidak ada maksud default | Hanya pemeliharaan |
Lihat dokumen kami untuk detail lebih lanjut tentang kompatibilitas.
Kami ingin mengucapkan terima kasih khusus kepada semua sponsor kami yang telah menyediakan dana untuk terus mengembangkan dan menampung sumber daya repositori serta mendorong inisiatif untuk program komunitas. Secara khusus, kami ingin memberikan pujian khusus kepada individu-individu luar biasa ini:
Berikut adalah beberapa contoh bot besar di dunia nyata yang menggunakan Discord4J:
Apakah Anda memiliki bot besar menggunakan Discord4J? Tanyakan kepada admin di Discord kami atau kirimkan permintaan tarik untuk menambahkan bot Anda ke daftar!
Discord4J menggunakan Project Reactor sebagai fondasi kerangka kerja asinkron kami. Reactor menyediakan API sederhana namun sangat kuat yang memungkinkan pengguna mengurangi sumber daya dan meningkatkan kinerja.
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 juga menyediakan beberapa metode untuk membantu komposisi rantai reaktif yang lebih baik, seperti GatewayDiscordClient#withGateway
dan EventDispatcher#on
dengan penanganan kesalahan yang berlebihan.
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 ( "Disconnected!" ));
return Mono . when ( pingPong , onDisconnect );
}). block ();
}
}
Dengan memanfaatkan Reactor, Discord4J memiliki integrasi asli dengan coroutine Kotlin ketika dipasangkan dengan pustaka kotlinx-coroutines-reactor.
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()
Mulai 1 September 2022, Discord mengharuskan bot mengaktifkan maksud "MESSAGE_CONTENT" untuk mengakses konten pesan. Untuk mengaktifkan maksud tersebut, buka Portal Pengembang Discord dan pilih bot Anda. Lalu, buka tab "Bot" dan aktifkan maksud "Konten Pesan". Lalu, tambahkan maksud ke bot Anda saat membuat DiscordClient:
GatewayDiscordClient client = DiscordClient . create ( token )
. gateway ()
. setEnabledIntents ( IntentSet . nonPrivileged (). or ( IntentSet . of ( Intent . MESSAGE_CONTENT )))
. login ()
. block ();
// 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 " +
"big D: is setImage n " +
"small D: is 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 ();
Pengguna biasanya lebih suka bekerja dengan nama daripada ID. Contoh ini akan menunjukkan cara mencari semua anggota yang memiliki peran dengan nama tertentu.
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 ;
Alternatifnya, menggunakan Reaktor:
Guild guild = ...
return guild . getMembers ()
. filterWhen ( member -> member . getRoles ()
. map ( Role :: getName )
. any ( "Developers" :: equalsIgnoreCase ));
Discord4J memberikan dukungan penuh untuk koneksi suara dan kemampuan mengirim audio ke pengguna lain yang terhubung ke saluran yang sama. Discord4J dapat menerima sumber audio Opus apa pun dengan LavaPlayer menjadi solusi pilihan untuk mengunduh dan menyandikan audio dari YouTube, SoundCloud, dan penyedia lainnya.
Peringatan
LavaPlayer asli tidak lagi dipertahankan. Versi baru yang dikelola dapat ditemukan di sini. Jika Anda memerlukan dukungan Java 8, Anda dapat menggunakan garpu LavaPlayer Walkyst, tetapi garpu tersebut juga tidak lagi dipertahankan!
Untuk memulai, pertama-tama Anda perlu membuat instance dan mengonfigurasi AudioPlayerManager
, yang biasanya bersifat global.
public static final AudioPlayerManager PLAYER_MANAGER ;
static {
PLAYER_MANAGER = new DefaultAudioPlayerManager ();
// This is an optimization strategy that Discord4J can utilize to minimize allocations
PLAYER_MANAGER . getConfiguration (). setFrameBufferFactory ( NonAllocatingAudioFrameBuffer :: new );
AudioSourceManagers . registerRemoteSources ( PLAYER_MANAGER );
AudioSourceManagers . registerLocalSource ( PLAYER_MANAGER );
}
Selanjutnya, kita perlu mengizinkan Discord4J membaca dari AudioPlayer
ke AudioProvider
.
public class LavaPlayerAudioProvider extends AudioProvider {
private final AudioPlayer player ;
private final MutableAudioFrame frame ;
public LavaPlayerAudioProvider ( AudioPlayer player ) {
// Allocate a ByteBuffer for Discord4J's AudioProvider to hold audio data for Discord
super ( ByteBuffer . allocate ( StandardAudioDataFormats . DISCORD_OPUS . maximumChunkSize ()));
// Set LavaPlayer's AudioFrame to use the same buffer as Discord4J's
frame = new MutableAudioFrame ();
frame . setBuffer ( getBuffer ());
this . player = player ;
}
@ Override
public boolean provide () {
// AudioPlayer writes audio data to the AudioFrame
boolean didProvide = player . provide ( frame );
if ( didProvide ) {
getBuffer (). flip ();
}
return didProvide ;
}
}
Biasanya, pemutar audio memiliki antrean atau daftar putar internal sehingga pengguna dapat memutar lagu secara otomatis setelah selesai atau diminta untuk dilewati. Kita dapat mengelola antrean ini secara eksternal dan meneruskannya ke area lain dalam kode kita agar trek dapat dilihat, diantri, atau dilewati dengan membuat AudioTrackScheduler
.
public class AudioTrackScheduler extends AudioEventAdapter {
private final List < AudioTrack > queue ;
private final AudioPlayer player ;
public AudioTrackScheduler ( AudioPlayer player ) {
// The queue may be modifed by different threads so guarantee memory safety
// This does not, however, remove several race conditions currently present
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 ) {
// Advance the player if the track completed naturally (FINISHED) or if the track cannot play (LOAD_FAILED)
if ( endReason . mayStartNext ) {
skip ();
}
}
}
Saat ini, Discord hanya mengizinkan 1 koneksi suara per server. Bekerja dalam batasan ini, masuk akal untuk memikirkan 3 komponen yang telah kami kerjakan sejauh ini ( AudioPlayer
, LavaPlayerAudioProvider
, dan AudioTrackScheduler
) untuk dikorelasikan ke Guild
tertentu, yang secara alami unik oleh beberapa Snowflake
. Logikanya, masuk akal untuk menggabungkan objek-objek ini menjadi satu, sehingga mereka dapat dimasukkan ke dalam Map
untuk memudahkan pengambilan ketika menghubungkan ke saluran suara atau ketika bekerja dengan perintah.
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 );
}
// getters
}
Terakhir, kita perlu terhubung ke saluran suara. Setelah terhubung, Anda diberikan objek VoiceConnection
yang dapat Anda gunakan nanti untuk memutuskan sambungan dari saluran suara dengan memanggil VoiceConnection#disconnect
.
VoiceChannel channel = ...
AudioProvider provider = GuildAudioManager . of ( channel . getGuildId ()). getProvider ();
VoiceConnection connection = channel . join ( spec -> spec . setProvider ( provider )). block ();
// In the AudioLoadResultHandler, add AudioTrack instances to the AudioTrackScheduler (and send notifications to users)
PLAYER_MANAGER . loadItem ( "https://www.youtube.com/watch?v=dQw4w9WgXcQ" , new AudioLoadResultHandler () { /* overrides */ })
Biasanya, setelah semua orang meninggalkan saluran suara, bot akan terputus secara otomatis karena pengguna biasanya lupa memutuskan sambungan bot secara manual. Masalah ini dapat diselesaikan dengan lebih elegan dengan menggunakan pendekatan reaktif dibandingkan pendekatan imperatif seperti yang ditunjukkan pada contoh di bawah ini.
VoiceChannel channel = ...
Mono < Void > onDisconnect = channel . join ( spec -> { /* TODO Initialize */ })
. flatMap ( connection -> {
// The bot itself has a VoiceState; 1 VoiceState signals bot is alone
Publisher < Boolean > voiceStateCounter = channel . getVoiceStates ()
. count ()
. map ( count -> 1L == count );
// After 10 seconds, check if the bot is alone. This is useful if
// the bot joined alone, but no one else joined since connecting
Mono < Void > onDelay = Mono . delay ( Duration . ofSeconds ( 10L ))
. filterWhen ( ignored -> voiceStateCounter )
. switchIfEmpty ( Mono . never ())
. then ();
// As people join and leave `channel`, check if the bot is alone.
// Note the first filter is not strictly necessary, but it does prevent many unnecessary cache calls
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 ();
// Disconnect the bot if either onDelay or onEvent are completed!
return Mono . first ( onDelay , onEvent ). then ( connection . disconnect ());
});