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 ();
}
}
完全なプロジェクトの例については、こちらのサンプル プロジェクト リポジトリを確認してください。
Reactive - Discord4J は reactive-streams プロトコルに従い、サイズに関係なく Discord ボットがスムーズかつ効率的に実行されるようにします。
公式- 自動レート制限、自動再接続戦略、一貫した命名規則など、Discord4J が提供する多くの機能の 1 つで、Discord ボットが Discord の仕様に準拠して動作することを保証し、ライブラリと対話する際の驚きを最小限に抑えます。
モジュール式- Discord4J は自身をモジュールに分割し、上級ユーザーが下位レベルで API を操作して最小限の高速ランタイムを構築したり、独自の抽象化を追加したりできるようにします。
⚔️強力- Discord4J は、大小を問わずあらゆるボットの開発に使用できます。カスタム配布フレームワーク、オフヒープ キャッシュから大規模なボットを開発するためのツールを多数提供しており、Reactor との相互作用により、Spring や Micronaut などのフレームワークとの完全な統合が可能になります。
?コミュニティ- 私たちは包括的なコミュニティに誇りを持っており、課題が発生したときはいつでも喜んで支援します。または単にチャットしたい場合でも! Discord4J 固有の問題から、一般的なプログラミングや Web 開発のヘルプ、さらには Reactor 固有の質問まで、幅広いサポートを提供します。ぜひDiscordサーバーにアクセスしてください!
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 には、リクエストを構築するためのよりシンプルで強力な API、新しいエンティティ キャッシュ、依存関係のアップグレードによるパフォーマンスの向上が含まれています。詳細については、移行ガイドを確認してください。
Discord4J | サポート | ゲートウェイ/API | インテント | インタラクション |
---|---|---|---|---|
v3.3.x | 開発中 | v9 | 必須の非特権デフォルト | 完全にサポート |
v3.2.x | 現在 | v8 | 必須の非特権デフォルト | 完全にサポート |
v3.1.x | メンテナンスのみ | v6 | オプション、インテントのデフォルトなし | メンテナンスのみ |
互換性の詳細については、ドキュメントを参照してください。
リポジトリ リソースの開発とホスティングを継続し、コミュニティ プログラムの取り組みを推進するための資金を提供してくださったスポンサーの皆様に特別に感謝いたします。特に、次のような素晴らしい方々に特別なエールを送りたいと思います。
Discord4J を使用した大規模なボットの実例をいくつか示します。
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 ( "Disconnected!" ));
return Mono . when ( pingPong , onDisconnect );
}). block ();
}
}
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()
2022 年 9 月 1 日以降、Discord ではボットがメッセージのコンテンツにアクセスするために「MESSAGE_CONTENT」インテントを有効にする必要があります。インテントを有効にするには、Discord 開発者ポータルに移動し、ボットを選択します。次に、[ボット] タブに移動し、[メッセージ コンテンツ] インテントを有効にします。次に、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 ();
ユーザーは通常、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 は、YouTube、SoundCloud、その他のプロバイダーからオーディオをダウンロードしてエンコードするための推奨ソリューションである LavaPlayer を使用して、あらゆる Opus オーディオ ソースを受け入れることができます。
警告
オリジナルの LavaPlayer は保守されなくなりました。新しいメンテナンスバージョンはここにあります。 Java 8 サポートが必要な場合は、Walkyst の LavaPlayer フォークを使用できますが、これもメンテナンスされなくなりました。
開始するには、まず、従来のグローバルであるAudioPlayerManager
インスタンス化して構成する必要があります。
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 );
}
次に、Discord4J がAudioPlayer
から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 ;
}
}
通常、オーディオ プレーヤーにはキューまたは内部プレイリストがあり、ユーザーが曲の終了時またはスキップ要求時に曲を自動的に循環できるようになります。このキューを外部で管理し、コードの他の領域に渡すことで、 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 ();
}
}
}
現在、Discord ではサーバーごとに 1 つの音声接続のみが許可されています。この制限内で作業する場合、これまでに作業してきた 3 つのコンポーネント ( AudioPlayer
、 LavaPlayerAudioProvider
、およびAudioTrackScheduler
) が、当然ながら一部のSnowflake
によって一意である特定のGuild
に関連付けられると考えるのは論理的です。論理的には、これらのオブジェクトを 1 つに結合して、音声チャネルに接続するときやコマンドを使用するときに簡単に取得できるように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 );
}
// getters
}
最後に、音声チャネルに接続する必要があります。接続すると、 VoiceConnection
オブジェクトが与えられ、後で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 */ })
通常、ユーザーは手動でボットを切断することを忘れるため、全員が音声チャネルから退出した後、ボットは自動的に切断される必要があります。この問題は、以下の例が示すように、命令的なアプローチではなく、事後的なアプローチを使用することで、かなりエレガントに解決できます。
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 ());
});