介绍

本文是为没有经验的 DiscordBot 创建者编写的。如有任何疑问,请参阅附录(尚未编写)。我很想在推特上收到你的来信。
对于那些有创建机器人经验的人,我们总结了 DiscordBot 可以完成的基本处理,所以请使用它。

目录
设置创建机器人的环境
让你的机器人上线
创建命令
在 Discord 服务器上获取成员
授予机器人权限
让你的机器人离线

__文字频道相关____
命令实现
发信息
获取和删除消息(删除所有消息)
发送文件
嵌入消息

__语音频道相关__
授予语音通道相关权限
联系
发送音频
连续音频播放

参考文献/文章
附录

设置创建机器人的环境

将你的机器人连接到 Discord 服务器

这个Discord 开发者门户我将制作一个机器人。
点击右上角NewApplication->自由设置bot名称->点击“Crete”。
单击左侧菜单中的机器人 -> 添加机器人。
当屏幕看起来像图像时,使用“重置令牌”->“复制”复制此机器人的令牌。
我稍后会使用它。
如果这个令牌泄露了,就会被劫持,所以请不要告诉别人。如果有的话,请使用“重置令牌”重新生成令牌。
KotlinでDiscordBot手引き

向下滚动并更改复选框,如下所示:
KotlinでDiscordBot手引き
PUBLIC BOT 是是否将其设为公共机器人(本次禁用)。
Require OAuth2 Code Grant 是是否指定可以添加的服务器(这次禁用)。
Privileged Gateway Intents 是是否扩展bot的功能(本次全部启用)。允许您向服务器发送消息并获取成员。

接下来,将机器人连接到 Discord 服务器。
OAuth2 ->
☑[范围]机器人
☑ [BOT 权限] 管理员
向下滚动,将生成 URL,复制并搜索。选择要添加机器人的服务器,进行身份验证,机器人将被添加到所选服务器!
KotlinでDiscordBot手引き
KotlinでDiscordBot手引き

java的介绍

接下来,我们将编写程序来操作机器人。首先,我们将准备编程环境。本文:下载并安装 OpenJDK我觉得可以参考一下。

介绍 IntelliJ 和 Kotlin

这个网站下载社区(免费版)从
按照以下步骤安装 IntelliJ。

对于 Windows

当您启动它时,会显示“欢迎〜”并出现屏幕,因此单击“下一步>”按钮继续。
然后,将出现“选择安装位置”屏幕,如果没有特殊原因,请单击“下一步>”按钮继续。
在显示“安装选项”的下一个屏幕上,如果没有特殊原因,请关闭所有内容并继续按“下一步>”按钮。
最后,在显示“选择开始菜单文件夹”的屏幕上,如果没有特别的原因,保持原样。按“安装”按钮开始安装。
当出现“Completing~”安装完成画面时,勾选“Run~”复选框并按{Finish. IntelliJ 将在退出后启动。

对于 macOS

本文:IntelliJ IDEA 安装说明<macOS>请参阅“安装 IntelliJ IDEA”。我认为最好不要“日本化”。更新后,应用程序可能无法启动(可用的解决方法)。

项目创建

首次启动 IntelJ 时,会出现“导入 IntelJ IDE 设置”窗口。由于是交接用,如果没有什么特别的,请选择“不导入设置”,然后确定。
此外,如果您看到诸如“插件更新可用”之类的链接,请单击它进行更新。

我们将创建一个新项目。
新项目-> Gradle
☑Java
☑Kotlin/JVM
☑ Kotlin DSL buld 脚本
KotlinでDiscordBot手引き

在按“下一步”后的屏幕上,自由设置此项目名称。 (例如:MyTestBot)
确定项目名称后,按完成开始!

更新项目

我们将更新版本。
制作文件项目-> gradle(红色)-> 6.8 7.0.2。
将Project -> gradle(black) -> wrapper -> gradle-wrapper.properties 中的distributionUrl 6.8 改写为7.0.2 并按右上角显示的大象标记进行更新。更新完成后删除 6.8 文件。
KotlinでDiscordBot手引き
KotlinでDiscordBot手引き

现在您已准备好构建您的机器人!

实现功能

让你的机器人上线

Discord 服务器上的 bot 还处于离线状态,所以我要上线了!
现在,机器人生产正式开始!

  1. 更新构建工具
  2. 编辑源代码
    更新构建工具

    首先,我们将重写 Gradle,这是用于构建和运行项目的工具。
    将以下内容添加到项目文件 -> src -> build.gradle.kts 文件中,然后按大象标记进行更新。

    repositories {
        ...
        maven("https://m2.dv8tion.net/releases")
    }
    
    dependencies {    
        ...
        implementation("net.dv8tion:JDA:5.0.0-alpha.18")
    }
    

    然后添加 Application{} 并再次更新。

    application {
        mainClass.set("${group}.${rootProject.name}.MainKt")
    }
    
    编辑源代码

    接下来,我们将创建一个地方来编写程序。
    点击项目名称 -> src -> main -> kotlin -> new -> Kotlin Class/File。
    将文件的名称创建为 BotClient。
    KotlinでDiscordBot手引き
    将以下源代码复制并粘贴到创建的文件中。解释附在这里!

    import net.dv8tion.jda.api.JDA
    import net.dv8tion.jda.api.JDABuilder
    import net.dv8tion.jda.api.OnlineStatus
    import net.dv8tion.jda.api.entities.Activity
    import net.dv8tion.jda.api.entities.Guild
    import net.dv8tion.jda.api.hooks.ListenerAdapter
    import net.dv8tion.jda.api.requests.GatewayIntent
    
    const val TOKEN  = "CopyしたToken"
    const val GUILD_ID = "DiscordサーバーのID"
    
    fun main(){
        BotClient().main()
    }
    
    
    class BotClient : ListenerAdapter() {
    
        private lateinit var jda: JDA
        private lateinit var guild: Guild
    
        fun main() {
    
            val intents = listOf(GatewayIntent.GUILD_MESSAGES)
    
            jda = JDABuilder.createLight(TOKEN, intents)
                .setRawEventsEnabled(true)
                .addEventListeners(this)
                .setStatus(OnlineStatus.ONLINE)
                .setActivity(Activity.playing("@セカイ"))
                .build()
    
            jda.awaitReady()// ログイン完了まで待機
            guild = jda.getGuildById(GUILD_ID)!!// ぬるぽ -> 開発者ポータルからBotをサーバーに入れる
        }
    }
    

    按 fun main(){} 右侧的绿色 ▶ 运行此应用程序。
    每次重写程序时,按此 ▶ 应用更新。
    如果您的日志看起来像这样,那么您就可以开始了!
    检查您的 Discord 服务器,看看它是否在线!
    要退出应用程序,请按右上角的“红色方块”标记。
    KotlinでDiscordBot手引き

    渠道相关

    总结了该频道的主要操作。

    创建命令

    当您想到机器人时,您会想到命令。编写处理命令的代码。

    斜线命令

    斜杠命令是在发送 Discord 消息时在开头添加“/”的命令。
    这一次,如果您发送“/hallo”,它将返回“Hello World!”。
    添加在 fun main() 中注册命令的代码和在外部处理命令的代码。
    解释附在这里!

    fun main(){
        BotClient().main()
    }
    
    class BotClient : ListenerAdapter() {
        ...
        fun main() {
            ...
            // コマンドをリセット
            guild.updateCommands().queue()
    
            // スラッシュコマンドを作成
            val slash: SlashCommandData = Commands.slash("hallo", "「Hallo World!」を送信します")
            val cmdAry = arrayListOf<CommandData>(slash)
    
            // スラッシュコマンドを指定したサーバーに登録
            guild.updateCommands().addCommands(cmdAry).queue()
        }
    
        override fun onSlashCommandInteraction(event: SlashCommandInteractionEvent) {
            if (event.name == "hallo") {  //「Hallo World!」を送信
                event.reply("Hallo World!").setEphemeral(false).queue()
            }
        }
    }
    

    消息命令

    可以通过“右键单击 Discord 中的消息正文 -> 单击应用程序”来使用消息命令。
    更改 BotClient() 的 main() 中的创建斜线命令。

    // スラッシュコマンドを作成
        val slash: SlashCommandData = Commands.slash("hallo", "「Hallo World!」を送信します")
        val message = Commands.message("neko")
        val cmdAry = arrayListOf<CommandData>(slash, message)
    

    在 BotClient 中添加它。

    override fun onMessageContextInteraction(event: MessageContextInteractionEvent) {
        if (event.name == "neko") {  //「にゃん(⋈◍>◡<◍)。✧♡」を送信
            event.reply("にゃん(⋈◍>◡<◍)。✧♡").setEphemeral(false).queue()
        }
    }
    

    用户命令

    用户命令可以通过“右键单击消息屏幕上的用户图标->单击应用程序”来使用。
    更改 BotClient() 的 main() 中的创建斜杠命令。

    // スラッシュコマンドを作成
        val slash: SlashCommandData = Commands.slash("hallo", "「Hallo World!」を送信します")
        val message = Commands.message("neko")
        val user = Commands.user("dog")
        val cmdAry = arrayListOf<CommandData>(slash, message, user)
    

    在 BotClient 中添加它。

    override fun onUserContextInteraction(event: UserContextInteractionEvent) {
        if (event.name == "dog") {  //「わん★わん」を送信
            event.reply("わん★わん").setEphemeral(false).queue()
        }
    }
    

    在 Discord 服务器上获取成员

    让我们获取频道中的成员。
    为此,需要从机器人端和程序端传递访问权限(特权)。
    机器人端的权限没有问题,因为所有 Privileged Gateway Intents 都在 Discord 的开发者门户上启用。我们将编写代码从程序端获取访问会员信息的权限。
    在 BotClient 中重写 main 的意图并添加其他的。

    fun main() {
    
        val intents = listOf(
            GatewayIntent.GUILD_MESSAGES,
            GatewayIntent.GUILD_MEMBERS,
            GatewayIntent.GUILD_PRESENCES,
            GatewayIntent.MESSAGE_CONTENT)
    
        val cacheFlags = listOf(CacheFlag.MEMBER_OVERRIDES)
    
        jda = JDABuilder.createLight(TOKEN, intents)
            .setRawEventsEnabled(true)
            .addEventListeners(this)
            .setStatus(OnlineStatus.ONLINE)
            .setActivity(Activity.playing("@セカイ"))
            .enableCache(cacheFlags)    //追記
            .setMemberCachePolicy(MemberCachePolicy.ALL)    //追記
            .build()
    
        ...
    
        //メンバー情報を取得
        guild.members.forEach { member -> println("MemberName: " + member.effectiveName) }
    }
    

    授予机器人权限

    与之前获取成员的情况一样,可能需要获取权限。
    以下是如何获得它。

    ・通过 Discord 开发者门户端的许可
    OAuth2 ->
    ☑[范围]机器人
    ☑ [BOT 权限] 管理员
    ・通过程序方面的许可
    启用意图
    启用缓存标志
    启用缓存策略

    让你的机器人离线

    这个过程的实现在本文的文本通道相关命令的实现中有所涉及。

    jda.shutdownNow()
    

    文字频道相关

    命令实现

    使用您自己的前缀实施命令,以随时使您的机器人离线。
    前缀用于识别定向到机器人的消息。
    这次,我们将设置“!shutdown”(*带半角空格)以“!”为前缀,并描述使机器人下线的过程。

    override fun onMessageReceived(event: MessageReceivedEvent) {
    
        //prefix(接頭詞)メッセージの処理
        val msg = event.message.contentRaw
            val msgList = msg.split(" ") //""の間は半角スペース
    
        if (msgList[0] == "!"){ //接頭詞「!」で始まっているかどうか
            when(msgList[1]) {
                "shutdown" -> {
                    Thread.sleep(3000)  //処理を3秒間一時停止
                    jda.shutdownNow()   //オフラインにする
                }
            }
        }
    }
    

    发信息

    发送消息可以获取发送消息的通道并发送。

    channel.sendMessage("またね~♪").queue()
    

    让我们将它添加到命令实现中编写的代码中。

    "shutdown" -> {
        event.channel.sendMessage("またね~♪").queue()
        Thread.sleep(3000)  //処理を3秒間一時停止
        jda.shutdownNow()   //オフラインにする
    }
    

    要获取要发送的通道,您可以从事件中获取它,该事件包含有关发送“!shutdown”消息的通道的信息。
    “再见~♪”应该已经发送到 Discord 并且机器人应该已经离线了。

    获取和删除消息

    我们将实施消息删除。如果你做一个机器人,Discord 的消息字段会变得粗糙,所以我们也会编写一个程序来删除所有消息。
    我们想在发送“!delMsg”时清除它。
    消息删除过程如下。

        when(msgList[1]) {
                ...
            "delMsg" -> {
                val textChannel = event.channel
                val delMsg = textChannel.history.retrievePast(1).complete()[0]  //Discordnの最新メッセージから1番目までを取得
                delMsg.delete().queue()
                println("$delMsg を削除したよ♪")
            }
        }
    }
    

    要删除所有消息:
    我想在发送“!allDelMsg”时清除所有内容。

    when(msgList[1]) {
       ...
        "allDelMsg" -> {  //全メッセージを削除
            val textChannel = event.channel
            var delMsg = textChannel.history.retrievePast(1).complete()
            var nextDelMsg = delMsg
    
            event.channel.sendMessage("いいライブだったね♪").queue()
    
            Thread {
                while (delMsg != null) {
    
                    try {
                        textChannel.deleteMessageById(delMsg[0].id).queue()
                    } catch (e: IndexOutOfBoundsException) {
                        println("削除が完了したよ♪")
                        return@Thread
                    }
    
                    println("$delMsg を削除したよ♪")
    
                    while (delMsg == nextDelMsg) {   //削除完了まで待機
                        Thread.sleep(500)
                        nextDelMsg = textChannel.history.retrievePast(1).complete()
                    }
    
                    delMsg = nextDelMsg
                }
            }.start()
        }
    }
    

    发送文件

    发送文件与发送消息同时完成。我似乎无法发送文件。

    channel.sendMessage("またね~♪").addFiles(file).queue()
    

    我还将描述一个实现示例。
    首先,复制本地文件路径(指向位置)。
    KotlinでDiscordBot手引き
    更改“!shutdown”的处理。该文件将在机器人脱机后立即发送。
    *如果将路径复制并粘贴到“C:\Users\***\Pictures\Screenshots\***”,请将“\”更改为“\\”和“C:\\Users\\***” \\图片\\截图\\***”。
    * 如果显示“日元标记”,则为错误。它实际上是一个半角正斜杠。

    "shutdown" -> {
        val file = FileUpload.fromData(File("コピーしたパス"))
        event.channel.sendMessage("またね~♪").addFiles(file).queue()
        Thread.sleep(3000)  //処理を3秒間一時停止
        jda.shutdownNow()   //オフラインにする
    }
    

    KotlinでDiscordBot手引き

    嵌入消息

    嵌入消息 (Embed) 是只能由机器人发送的类似图像的消息。
    KotlinでDiscordBot手引き

    这个嵌入的消息部分引用了这篇文章:使用 Kotlin 3 启动 Discord Bot ~Embed implementation~。

    首先创建一个新文件。
    “项目 -> src -> kotlin(右键单击)-> 新建 -> Kotlin 文件/类”并将其命名为 Embed。
    即使不创建,也可以在 BotClient 的 { } 之外编写如下代码。
    将创建的文件命名为 Embed。
    在创建的文件中编写代码。

    import net.dv8tion.jda.api.EmbedBuilder
    import net.dv8tion.jda.api.entities.MessageEmbed
    import net.dv8tion.jda.api.events.message.MessageReceivedEvent
    
    class Embed(private val event: MessageReceivedEvent) {
    
        fun buildEmbed(): MessageEmbed {
    
            val embed = EmbedBuilder()
                .setTitle("Tutorial Embed","https://example.com")   //タイトル文字列。第2引数にURLを入れるとタイトルを指定URLへのリンクにできます
                .setAuthor("tutorial bot","https://example.com", event.member?.avatarUrl)   //Botの情報。タイトルと同じくリンクを指定できる他、第3引数にアイコン画像を指定できます。
                .appendDescription("Embed made with Kotlin JDA!!") //Embedの説明文
                .setColor(0x00ff00) //Embed左端の色を設定します.
    
                .addField("フィールド1","値1",false) //以下3つフィールドをセット
                .addField("フィールド2","値2",true)
                .addField("フィールド3","値3",true)
                .setThumbnail("https://example.com") //サムネイル(小さい画像)
                .setImage("https://example.com") //イメージ(大きい画像)
                .setFooter("made by Yosuga","https://example.com")//フッターには開発者情報を入れるといいでしょう。
                .build()
    
            return embed
        }
    }
    

    包括它,以便它可以由命令处理。

    override fun onMessageReceived(event: MessageReceivedEvent) {
        ...
        if (msgList[0] == "!"){ //接頭詞「!」で始まっているかどうか
            when(msgList[1]) {
               ...
                "Embed" -> {
                    event.channel.sendMessageEmbeds(Embed(event).buildEmbed()).queue()  //送信
                }
            }
        }
    }
    

    语音通道相关

    我们将描述与语音通道相关的处理。
    首先,授予与语音通道相关的机器人权限。

    class BotClient : ListenerAdapter() {
        ...
        fun main() {
    
            val intents = listOf(
                GatewayIntent.GUILD_MESSAGES,
                GatewayIntent.GUILD_MEMBERS,
                GatewayIntent.GUILD_PRESENCES,
                GatewayIntent.GUILD_VOICE_STATES,   //追記
                GatewayIntent.MESSAGE_CONTENT)
    
            val cacheFlags = listOf(
                CacheFlag.MEMBER_OVERRIDES,
                CacheFlag.VOICE_STATE)  //追記
            ...
            guild.members.forEach { member -> println("VoiceState @${member.effectiveName}: ${member.voiceState?.inAudioChannel()}") 
        }
    }
    

    现在您可以获取成员是否连接到语音通道。
    请删除一次添加的部分,并检查您这次是否无法获取。
    在这种情况下,不要忘记再次添加它。

    联系

    连接语音通道。就是这样:

    guild.audioManager.openAudioConnection(voiceChannel)
    

    允许这由命令处理。

    override fun onMessageReceived(event: MessageReceivedEvent) {
        ...
        if (msgList[0] == "!"){ //接頭詞「!」で始まっているかどうか
                ...
                "play" -> {
    
                    //コマンドが送信されたテキストチャンネルを取得
                    val textChannel = event.channel.asTextChannel()
                    //コマンドを送信したメンバーが接続しているボイスチャンネルを取得(していない場合はnull)
                    val voiceChannel = guild.getMemberById(event.member!!.id)!!.voiceState!!.channel
    
                    if(!guild.audioManager.isConnected){
                        if (voiceChannel == null){  //ボイスチャンネルに接続してるメンバーがいるかどうか
                            textChannel.sendMessage("ボイスチャンネルがみつかりません").queue()
                            return
                        }
                        //ボイスチャンネルに接続
                        guild.audioManager.openAudioConnection(voiceChannel)
                    }
                }
            }
        }
    }
    

    发送音频

    我们会让机器人说话。这很难。

    对于音频传输,视频在这里我引用的内容播放的声音是音效实验室我从

    1. 准备音频文件
    2. 编辑 Gradle
    3. 创建处理程序
    4. 创建监听器
    5. 创建音乐播放器
    6. 创建音乐播放器管理器
    7. 执行命令
      准备音频文件

      音频文件可以自己准备。
      这次音效实验室我下载了“战斗->用剑砍”和“语音素材->哟,非常感谢”。

      编辑 Gradle

      现在让我们谈谈API。
      API 是允许应用程序在外部运行的东西。因此,可以通过从外部操作名为 Discord 的应用程序来开发 Bot 应用程序。特别是,JDA(Java Discord API)是允许您使用 Java 操作 Discord 的一种。
      实际上,这个 JDA 包被设置为可以在本文的“让 Bot 上线 -> 更新构建工具”中使用。

      这次还有一个JDA推荐的“播放音频文件”的包,所以我们会使用它。
      我们将编辑文件 build.gradle.kts。
      将以下内容添加到依赖项中。

      //音声ファイルの再生ツール
      implementation("com.sedmelluq:lavaplayer:1.3.76")
      

      KotlinでDiscordBot手引き
      在这里,不要忘记按右上角出现的大象标记进行更新。
      您现在已完成添加包。

      创建处理程序

      让我们谈谈处理程序和侦听器。
      用户执行的操作称为“事件”,响应该事件执行的过程称为“事件处理程序”,检测是否发生事件的处理称为“侦听器”。
      例如,
      “用户触摸屏幕!”(事件)
      “让我们密切关注它!”(听众)
      “因为我被感动了,所以我会做〇〇!”(处理程序)

      从这里开始,我们将编写代码。
      创建处理程序。创建一个新文件。
      转到“项目 -> src -> kotlin(右键单击)-> 新建 -> Kotlin 文件/类”并将其命名为 MyAudioHandler。即使不创建,也可以在 BotClient 的 { } 之外编写如下代码。
      在新创建的文件或 BotClient { } 之外编写以下代码。

      class MyAudioHandler : AudioSendHandler, AudioReceiveHandler {
      
          private val queue: Queue<ByteArray> = ConcurrentLinkedQueue()
          private val audioPlayer: AudioPlayer
          private var lastFrame: AudioFrame? = null
      
          constructor(audioPlayer: AudioPlayer) {
              this.audioPlayer = audioPlayer
          }
      
          /** 送信処理    */
          override fun canProvide(): Boolean {
              lastFrame = audioPlayer.provide()
              return lastFrame != null
          }
      
          override fun provide20MsAudio(): ByteBuffer? {
              return ByteBuffer.wrap(lastFrame!!.data)
          }
      
          override fun isOpus(): Boolean {
              return true
          }
      }
      

      这里,我们已经描述了响应事件要执行的处理。
      其实我也不是很了解这个过程。很高兴了解这是典型的。

      创建监听器

      创建一个监听器。创建一个新文件。
      转到“项目 -> src -> kotlin(右键单击)-> 新建 -> Kotlin 文件/类”并将其命名为 MyAudioListener。即使不创建,也可以在 BotClient 的 { } 之外编写如下代码。
      在新创建的文件或 BotClient { } 之外编写以下代码。

      import com.sedmelluq.discord.lavaplayer.player.AudioPlayer
      import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter
      import com.sedmelluq.discord.lavaplayer.track.AudioTrack
      import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason
      import java.util.concurrent.BlockingQueue
      import java.util.concurrent.LinkedBlockingQueue
      
      
      class MyAudioListener : AudioEventAdapter {
      
          private val player: MyMusicPlayer
          private val tracks: BlockingQueue<AudioTrack> = LinkedBlockingQueue()
      
          constructor(player: MyMusicPlayer) : super() {
              this.player = player
          }
      
          fun getTracks(): BlockingQueue<AudioTrack> {
              return tracks
          }
      
          fun getTrackSize(): Int {
              return tracks.size
          }
      
          fun nextTrack(){
              if(tracks.isEmpty()){
                      // 音楽のトラックが全て再生された時の処理
              }
              player.getAudioPlayer().startTrack(tracks.poll(), false)
          }
      
          //トラック終わった事を検知
          override fun onTrackEnd(player: AudioPlayer?, track: AudioTrack?, endReason: AudioTrackEndReason?) {
      
              //トラックが終わった時の処理
              if (endReason != null) {
                  if (endReason.mayStartNext) nextTrack()
                  println("endReason: " + endReason.name + "\n")
                  
              }
          }
      
          //トラック始まった事を検知
          override fun onTrackStart(player: AudioPlayer?, track: AudioTrack?) {
              if (track != null) {
      
                  //トラックが始まった時の処理(トラックの情報を出力)
                  val info = track.info
                  println("[track]@onTrackStart\n title: ${info.title},\n author: ${info.author},\n length: ${info.length},\n identifier: ${info.identifier},\n" +
                          " isStream: ${info.isStream},\n uri: ${info.uri}\nPLAY")
              }
          }
      
        
          fun queue(track: AudioTrack) {
              if (!player.getAudioPlayer().startTrack(track, true)) tracks.offer(track)
          }
      }
      

      由于这是一个侦听器,因此定义了方法来检测轨道开始和轨道结束事件。
      红色字母有错误,但这是因为我们还没有创建音乐播放器。您可以放心地忽略它。

      创建音乐播放器

      创建一个音乐播放器。创建一个新文件。
      转到“项目 -> src -> kotlin(右键单击)-> 新建 -> Kotlin 文件/类”并将其命名为 MyMusicPlayer。即使不创建,也可以在 BotClient 的 { } 之外编写如下代码。
      在新创建的文件或 BotClient { } 之外编写以下代码。

      import com.sedmelluq.discord.lavaplayer.player.AudioPlayer
      import com.sedmelluq.discord.lavaplayer.track.AudioTrack
      import net.dv8tion.jda.api.entities.Guild
      
      class MyMusicPlayer {
      
          private val audioPlayer: AudioPlayer
          private var listener: MyAudioListener
          private val guild: Guild
      
          constructor(audioPlayer: AudioPlayer, guild: Guild) {
              this.audioPlayer = audioPlayer
              this.guild = guild
              listener = MyAudioListener(this)
              audioPlayer.addListener(listener)    //リスナーを追加
          }
      
          fun musicPlayer(){
              listener = MyAudioListener(this)
              audioPlayer.addListener(listener)
          }
      
          fun getAudioPlayer(): AudioPlayer {
              return audioPlayer
          }
      
          fun getGuild(): Guild {
              return guild
          }
      
          fun getListener(): MyAudioListener {
              return listener
          }
      
          fun getAudioHandler(): MyAudioHandler {
              return MyAudioHandler(audioPlayer)
          }
      
          @Synchronized
          fun playTrack(track: AudioTrack){
              listener.queue(track)
          }
      
          @Synchronized
          fun skipTrack() {
              listener.nextTrack()
          }
      }
      

      重要的部分是添加听众。之后,我添加了任何可能需要的处理。

      创建音乐播放器管理器

      创建音乐播放器管理器。创建一个新文件。
      转到“项目 -> src -> kotlin(右键单击)-> 新建 -> Kotlin 文件/类”并将其命名为 MyMusicPlayerManager。即使不创建,也可以在 BotClient 的 { } 之外编写如下代码。
      在新创建的文件或 BotClient { } 之外编写以下代码。

      import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler
      import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager
      import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers
      import com.sedmelluq.discord.lavaplayer.tools.FriendlyException
      import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
      import com.sedmelluq.discord.lavaplayer.track.AudioTrack
      import net.dv8tion.jda.api.entities.Guild
      import net.dv8tion.jda.api.entities.TextChannel
      import java.util.HashMap
      
      //プレイヤーマネージャー
      class MyMusicManager {
          private val manager = DefaultAudioPlayerManager()
          private val players: MutableMap<String, MyMusicPlayer> = HashMap()
      
          constructor(){
              AudioSourceManagers.registerRemoteSources(manager)
              AudioSourceManagers.registerLocalSource(manager)
          }
      
          @Synchronized
          fun getPlayer(guild: Guild): MyMusicPlayer? {
              //  プレイヤーマネージャーの作成
              if (!players.containsKey(guild.id)) players[guild.id] = MyMusicPlayer(manager.createPlayer(), guild)
              return players[guild.id]
          }
      
          fun loadTrack(channel: TextChannel, source: String){
      
              val player = getPlayer(channel.guild)
              if (player != null) {
                  channel.guild.audioManager.sendingHandler = player.getAudioHandler()
              }
      
              manager.loadItemOrdered(player, source, object : AudioLoadResultHandler {
      
                  /** トラックの再生   */
                  override fun trackLoaded(track: AudioTrack?) {
                      
                      if (track != null && player != null) {
                          player.playTrack(track) //再生
                      }
                  }
      
                  override fun playlistLoaded(playlist: AudioPlaylist?) {
                      val builder = StringBuilder()
                      builder.append("プレイリストの追加 **").append(playlist?.name).append("**\n")
                      
                      for (i in 0..4){
                          if (playlist != null){
                              if (playlist.tracks.size >= i) break
      
                              val track = playlist.tracks[i]
                              builder.append("\n ** -> ** ").append(track.info.title)
                              player?.playTrack(track)
      
                          } else println("playList@MyMusicManager null")
                      }
                  }
      
                  override fun noMatches() {
                      channel.sendMessage("トラックが見つかりません").queue()
                      println("トラックが見つかりません")
                  }
      
                  override fun loadFailed(exception: FriendlyException?) {
                      val em = exception?.message
                      channel.sendMessage("[トラック]$source の再生が出来ません。\n理由:$em")
                  }
              })
          }
      }
      

      在此处获取曲目信息。如果信息处理得当,就可以回放。
      您可以使用命令执行此操作。

      实施指挥

      如果您发送“!播放”,机器人将连接到语音通道并播放音频文件。
      首先,复制您要播放的音频文件的路径。
      并将您之前制作的“播放”内容更新如下。

      override fun onMessageReceived(event: MessageReceivedEvent) {
          ...
          if (msgList[0] == "!"){ //接頭詞「!」で始まっているかどうか
              when(msgList[1]) {
                  ...
                  "play" -> {
      
                      //コマンドが送信されたテキストチャンネルを取得
                      val textChannel = event.channel.asTextChannel()
                      //コマンドを送信したメンバーが接続しているボイスチャンネルを取得(していない場合はnull)
                      val voiceChannel = guild.getMemberById(event.member!!.id)!!.voiceState!!.channel
      
                      if (!guild.audioManager.isConnected) {
                          if (voiceChannel == null) {  //ボイスチャンネルに接続してるメンバーがいるかどうか
                              textChannel.sendMessage("ボイスチャンネルがみつかりません").queue()
                              return
                          }
                          //ボイスチャンネルに接続
                          guild.audioManager.openAudioConnection(voiceChannel)
                      }
      
                      //コピーしたパス
                      val track = "C:\\Users\\***\\Music\\Playlists\\「よ、よろしくお願いします」.mp3"
                      MyMusicManager().loadTrack(textChannel, track)   //再生
                  }
              }
          }
      }
      

      如果音频文件播放成功。

      连续音频播放

      用我之前做的过程,如果路径是播放列表,似乎可以连续播放。
      但是,就我而言,它并不顺利,所以我将实现自己的流程来连续播放。

      创建一个新文件。
      “项目 -> src -> kotlin(右键单击)-> 新建 -> Kotlin 文件/类”并将其命名为 ContinuousPlay。即使不创建,也可以在 BotClient 的 { } 之外编写如下代码。
      在新创建的文件或 BotClient { } 之外编写以下代码。

      import net.dv8tion.jda.api.entities.TextChannel
      import kotlin.properties.Delegates
      
      var myPlayList = listOf<String>()
      lateinit var myPlayTextChannel : TextChannel
      
      //連続再生処理(observePropertyが変更される度に処理される)
      var observeProperty: Int by Delegates.observable(-1) { property, oldValue, newValue ->
          println("[${property.name}] $oldValue -> $newValue (${if (newValue!=-1) newValue+1 else myPlayList.size}文字目を再生)")
      
          ContinuousPlay().inObserve(newValue)
      }
      
      class ContinuousPlay{
      
          fun start(){
              //myPlaylistに何もないなら再生する(再生中の場合はトラックに追加される)
              if(observeProperty == -1) observeProperty ++
          }
      
          /** MyAudioListenerのTrackEndEvent内にこの関数を追記   */
          fun inTrackEndEvent(){
              //連続再生処理。最後のtrackの場合はobservePropertyに-1を代入する
              val endPoint = myPlayList.size - 2
              when {
                  observeProperty == -1 -> endProcess()
                  observeProperty < endPoint -> observeProperty ++
                  observeProperty == endPoint -> observeProperty = -1
              }
          }
      
          /** observe内にこの関数を追記   */
          fun inObserve(newValue:Int){
              var number = newValue
              val isLastTrack: Boolean = newValue == -1
      
              if (isLastTrack) number = myPlayList.lastIndex  ////最後のtrackの時は、myPlayList.lastIndexを参照
              MyMusicManager().loadTrack(myPlayTextChannel, myPlayList[number])   //再生
              if (isLastTrack)  myPlayList = listOf() //最後のtrack終了後に、myPlayListをリセット
          }
      
          //myPlayListの終了処理
          private fun endProcess(){}
      }
      

      接下来,在 MyAudioListener 的 TrackEndEvent 中添加以下内容。

      /** 連続再生処理を追記   */
      override fun onTrackEnd(player: AudioPlayer?, track: AudioTrack?, endReason: AudioTrackEndReason?) {
          if (endReason != null) {
              if (endReason.mayStartNext) nextTrack()
      
              println("endReason: " + endReason.name + "\n")
      
              ContinuousPlay().inTrackEndEvent()  //追記
          }
      }
      

      最后,我们将其实现为命令!
      如果发送“!play ai”,则“a”和“i”对应的曲目将连续播放。
      请更改播放的处理如下。

      "play" -> {
      
          //コマンドが送信されたテキストチャンネルを取得
          val textChannel = event.channel.asTextChannel()
          //コマンドを送信したメンバーが接続しているボイスチャンネルを取得(していない場合はnull)
          val voiceChannel = guild.getMemberById(event.member!!.id)!!.voiceState!!.channel
      
          if (!guild.audioManager.isConnected) {
              if (voiceChannel == null) {  //ボイスチャンネルに接続してるメンバーがいるかどうか
                  textChannel.sendMessage("ボイスチャンネルがみつかりません").queue()
                  return
              }
              //ボイスチャンネルに接続
              guild.audioManager.openAudioConnection(voiceChannel)
          }
      
          /* 連続再生    */
          val messageData = msgList[2].trim()  //文字列の前後の空白を削除(途中の空白)は削除されない
          val myPlayMap = mapOf(
              "あ" to "C:\\Users\\***\\Music\\Playlists\\剣で斬る1.mp3",
              "い" to "C:\\Users\\***\\Music\\Playlists\\「よ、よろしくお願いします」.mp3")
      
          //myPlayListの作成(文字でリストを作成)
          for (i in messageData.indices){
              val moji = messageData[i].toString() //i文字目を取得
              println("\n[track追加] ${if (observeProperty==-1) i+1 else myPlayList.size+1}文字目:$moji\n")
      
              val myTrack: String? = myPlayMap[moji]
              //文字に紐づけられた音声ファイルでPlaylistを作成(再生中の場合はトラックに追加される)
              if (myTrack != null) myPlayList = myPlayList + myTrack
          }
          if (myPlayList.isEmpty()) return //myPlayListが空なら処理を中断
      
          //連続再生開始
          myPlayTextChannel = textChannel
          ContinuousPlay().start()
      }
      

      如果你发送了“!play ai”,那么“a”和“i”对应的曲目应该已经连续播放了!
      此外,如果您在连续播放期间键入命令,则会添加与角色对应的曲目。

      参考

      下载并安装 OpenJDK
      IntelliJ IDEA 安装过程<macOS>
      Kotlin 3 中的 Discord Bot 入门~嵌入实现~
      [JDA] Créer un bot discord : LES BOTS MUSICAUX avec LavaPlayer
      音效实验室
      Kotlin 动手实践 Tsuyano Shoda [作者]
      Android应用开发教材Shinzo Saito [作者]
      音频播放包https://github.com/sedmelluq/lavaplayer
      JDA 包
      https://github.com/DV8FromTheWorld/JDA/releases
      https://javadoc.io/doc/net.dv8tion/JDA/latest/index.html

      附录

      截至2022/09/29,附录正在编写中。请稍等。
      如果您有任何问题,请随时在 Twitter 上问我。


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308628062.html

相关文章: