哈囉大家好,這裡是正在修復JDA升級後出現的BUG的小編過路君子
這次 JDA5 到 JDA6 真的算是很大一次的升級,很多的類別路徑和功能的改了。
算從 2021 年 6 月 21 號小編就開始撰寫 Discord Bot 了,但是那時候還是使用 Pyhon discord bot v1.7.3,看看現在都更新到了 v2.6.4,但是也無所謂了,因為小編早早就投向 JDA 的懷抱了。
不得不說在按鈕響應上面,Python 的寫法確實比較簡單,在 Java 上小編用了一套非常縝密的寫法才實現了相同的功能,簡單來說使用到了 Java 中的 Reflection 功能才好不容易實現類似 Python Cog 的功能。
小編將 JDA 內的指令寫成跟 Python 中的 Cog 一樣,可以用比較簡單的方式靜態加載或是卸載,動態的小編實在懶得實現,就實現靜態就好,當時也是砸了好幾天才實現呢。
這次非常多的類別路徑都改了,JDA6 最大的更新就是 Compose 重製,而非常不幸的 StringSelectMenu 就是這次被重製的其中一員......
現在的 Compose 限制更少且功能更加強大(但仍有部分功能需要手動開啟 Components V2),但小編覺得若非真的需要一次提供多個選單這樣的功能,要不可以維持在 JDA4 或是 JDA5 就可以了。
在開始之前,小編先在這邊丟出版本表,如果以後還有 JDA7,那這篇可能又要更新了:
| 軟體 | 版本 |
| 舊JDA版本 | 5.6.1 |
| 新JDA版本 | 6.2.1 |
| Java | 25.0.1 LTS |
創建選單
接下來小編會同時丟出新的寫法和舊的寫法,要注意別看錯囉!
在 JAD5 (舊版)內我們可以這樣創建一個選單:
import module java.base;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
void sendStringSelectMenu(JDA jda)
{
String[] testData = {"option1", "option2", "option3", "option4", "option5"};
List<SelectOption> options = new LinkedList<SelectOption>();
for(int x=0, length=testData.length; length>x; x++)
options.add(SelectOption.of(testData[x], x));
// VM selected menu.
StringSelectMenu vm = StringSelectMenu.create("menuUUID")
.addOptions(options)
.setDefaultOptions(options.get(0))
.setPlaceholder(options.get(0).getValue())
.build();
jda.getTextChannelById(00000000000000000000L)
.sendMessage("test message")
.setActionRow(vm)
.queue();
}
※ import module 為 Java 25 的新語法,如果編譯報錯,請檢查使用的 Java 版本是否過低或是手動 import 所需的 Java 內建的類別。
小編很推薦大家將 Java 升級到 Java 25,因為 Java 25 最佳化了執行階段,相較於舊版的 Java 擁有更高、更好的執行效率,小編覺得最大的改動就是「 緊湊物件標頭(JEP 519: Compact Object Headers)」,將 Java 物件標頭的大小從傳統的 12 bytes 或 16 bytes 縮減至 8 bytes,小編覺得實屬不錯。
而 JDA6(新版)的寫法則改成以下:
import module java.base;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.components.actionrow.ActionRow;
import net.dv8tion.jda.api.components.selections.SelectOption;
import net.dv8tion.jda.api.components.selections.StringSelectMenu;
void sendStringSelectMenu(JDA jda)
{
String[] testData = {"option1", "option2", "option3", "option4", "option5"};
List<SelectOption> options = new LinkedList<SelectOption>();
for(int x=0, length=testData.length; length>x; x++)
options.add(SelectOption.of(testData[x], x));
// VM selected menu.
StringSelectMenu vm = StringSelectMenu.create("menuUUID")
.addOptions(options)
.setDefaultOptions(options.get(0))
.setPlaceholder(options.get(0).getValue())
.build();
jda.getTextChannelById(00000000000000000000L)
.sendMessage("test message")
.setComponents(ActionRow.of(vm))
.queue();
}
其中兩個比較重大的更新:
一、import 資訊更新,刪除了 interactions 資料夾,所以 JDA6 中的相關包路徑變短。
二、setActionRow() 函數棄用,全面改成使用 setComponents(),在 JDA5 內可以在這兩者中擇一使用,JDA6 則只能使用 setComponents();順帶一提,addActionRow() 也同樣被棄用了,請改用 addComponents。
讀取使用者的選擇
這邊小編所提到的部分不是使用 onStringSelectInteraction(StringSelectInteractionEvent event) 來讀取當前使用者的選擇,因為若使用該方法來讀取,無論在 JDA5 或是 JDA6 都是直接呼叫 event.getValue() 就可以取得使用者的選擇了。
小編這邊介紹的是直接透過 Message 類別來讀取使用者所做的選項,那在什麼狀況下會需要用到這樣的手法?就是我們不希望使用者一對選單做選擇就觸發接下來的事件,而是需要使用者點擊一下按鈕之後才進行下一步的動作,這時候就不會觸發 onStringSelectInteraction 事件,而是會觸發 onButtonInteraction 這個事件,這時候就必須用迂迴的手法取得使用者對選單的選擇了。
那就廢話不多說,直接看 JDA5(舊版)的程式碼吧:
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.interactions.components.ItemComponent;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
// jda.getTextChannelById(00000000000000000000L)
// .sendMessage("test message")
// .setComponents(ActionRow.of(vm))
// .queue();
@Override
void onButtonInteraction(ButtonInteractionEvent event)
{
Message message = event.getMessage();
// Get String select menu.
ItemComponent menu = message.getComponents() // List<LayoutComponent>
.get(0) // LayoutComponent
.getComponents() // List<ItemComponent>
.get(0); // ItemComponent
// Get User select.
String selected = ((StringSelectMenu)menu).getPlaceholder();
System.out.println(selected);
}
其中對於 getComponents().get(0) 的邏輯可以參考下面 JDA6 中小編的解釋,邏輯仍然是一樣的。
而在 JDA6 內雖然呼叫變得稍稍複雜了一點,但是小編卻覺得邏輯變得非常清楚,不會再出現那個奇怪的 ItemComponent 類別,尤其是最後一步,必須將 ItemComponent 類別顯性的強制轉型成 StringSelectMenu 類別,小編就試問,這麼不直覺的寫法到底是在寫程式還是考驗我們的看 Document 的能力......
那現在就拋棄 JDA5,跟著小編來看看 JDA6(新版)的程式碼吧:
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.components.selections.StringSelectMenu;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
// jda.getTextChannelById(00000000000000000000L)
// .sendMessage("test message")
// .setComponents(ActionRow.of(vm))
// .queue();
@Override
void onButtonInteraction(ButtonInteractionEvent event)
{
Message message = event.getMessage();
// Get String select menu.
StringSelectMenu menu = message.getComponents() // List<MessageTopLevelComponentUnion>
.get(0) // MessageTopLevelComponentUnion
.asActionRow() // ActionRow
.getComponents() // List<ActionRowChildComponentUnion>
.get(0) // ActionRowChildComponentUnion
.asStringSelectMenu(); // StringSelectMenu
// Get User select.
String selected = menu.getPlaceholder();
System.out.println(selected);
}
另外,這邊的 message.getComponents() 是有序的,而且順序就是傳送訊息的順序。
例如 Discord Bot 在發送訊息的時候送了一個這樣的訊息:
jda.getTextChannelById(00000000000000000000L)
.sendMessage("test message")
.setComponents(ActionRow.of(button1, button2), ActionRow.of(vm))
.queue();
那就要改成這樣取得選單:
StringSelectMenu menu = message.getComponents() .get(1) .asActionRow() .getComponents() .get(0) .asStringSelectMenu();
以上就是 JDA5 和 JDA6 選單方面的差別啦,雖然在 Components 這方面更新非常巨大,但是撇除這點,其他程式碼幾乎都可以直接沿用。
所以總結來說,小編可以接受這樣的升級,但是如果以後真的出了 JDA7,那......小編可能會慎重考慮,考慮成為 JDA6 釘子戶,除非 JDA6 有重大的安全性問題才會更新上去,或許啦。
不排除小編突然一拍腦袋就更新上去整自己的可能性,就像現在一樣。
後記
除了上文介紹到的 StringSelectMenu 以外,其中比較大的更新還有 TextInput 和 Label......等等,其中又以 Label 更新最大,Label 直接被拆分成了 Label、TextDisplay 和 FileDisplay。
以前只要一個 Label 就可以解決所有文字的顯示問題,現在則是在不同的地方要用不同的方式來顯示文字或檔案,小編也不知道這樣的改動好不好,但可以肯定的是,小編覺得程式碼寫起來的邏輯比以前好太多了。
所以小編對於這次的 JDA 換代給予非常高的評價!