【Discord bot 6.2.1】[JDA6](StringSelectMenu)如何從訊息中提取選單並取得使用者選擇的選項

URL Link //n.sfs.tw/16569

2026-01-05 00:07:26 By 過路君子

哈囉大家好,這裡是正在修復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 換代給予非常高的評價!