漫畫搜尋器
這篇雖然是以 Discord 機器人作為演示,但還是著重於 shentai 這個套件本身。
套件說明
根據作者描述適合用於 REST API, Bots, Sites,所以這次我以 Discord 機器人來使用這個套件。
套件的功能大致包含:
- 獲取當前熱門漫畫
- 獲取當前最新漫畫
- 隨機產生漫畫
- 獲取指定 id 的漫畫
- 獲取指定作者的漫畫
- 獲取指定關鍵字的漫畫
其他用法可參考 shentai
不過獲取熱門 / 最新漫畫,目前測試下來除了獲取的資料數量外,並沒有太大的差距,所以以下功能並不包含獲取最新漫畫這個功能。
製作過程
1. 安裝套件
安裝套件至 Discord 機器人的專案當中。
npm i shentai@1.2.3 |
2. 建立命令
使用 @discordjs/builders 內置的 SlashCommandBuilder 方法建立命令。
data: new SlashCommandBuilder() .setName('comic') .setDescription('漫畫搜尋器') |
3. 建立交互方式
使用 discord.js 內置的 MessageSelectMenu 方法建立下拉式選單作為交互方式。
const menu = new MessageActionRow() .addComponents( new MessageSelectMenu() .setCustomId('comicMenu') .setPlaceholder('選擇模式') .addOptions([ { label: 'popular', description: '顯示當前熱度最高的漫畫', value: 'popular', emoji: '<a:blobreach:984878619497230346>' }, { label: 'random', description: '隨機推薦漫畫', value: 'random', emoji: '<a:blobreach:984878612954107944>' }, { label: 'id', description: '根據編號查詢漫畫', value: 'id', emoji: '<a:blobreach:984878611293171713>' }, { label: 'keyword', description: '根據關鍵字查詢漫畫', value: 'keyword', emoji: '<a:blobreach:984878618025021450>' }, { label: 'author', description: '根據作者查詢漫畫', value: 'author', emoji: '<a:blobreach:984878614669566022>' } ] )); |
4. 獲取下拉式選單的交互結果
當使用者選取下拉式選單後,根據選取值執行相對應的動作。
client.on('interactionCreate', async (interaction) => { if (interaction.isSelectMenu()) { if (interaction.customId === 'comicMenu') { const menu = require('./src/comic/menu'); try { await menu.execute(interaction); } catch (error) { console.error(error); interaction.reply({ content: '執行此命令時出錯!', ephemeral: true }); } } } } |
5. 建立嵌入訊息
使用 discord.js 內置的 MessageEmbed 方法建立嵌入訊息,因為 api 會回傳 2 種資料,一種為漫畫的物件,另一種為搜尋結果,所以先建立兩種嵌入訊息,作為將來使用。
單個漫畫物件:
const setComicEmbed = (comic) => new MessageEmbed() .setTitle(comic.titles.english) .setURL(`https://nhentai.net/g/${comic.id}/`) .setDescription(comic.titles.original) .setColor('#f23857') .addField('編號', `\`${comic.id}\``, true) .addField('頁數', `\`${comic.pages.length}\``, true) .setImage(comic.cover); |
多個漫畫物件:
const setMoreComicEmbed = (comic) => comic.map((item) => new MessageEmbed() .setTitle(item.titles.english) .setURL(`https://nhentai.net/g/${item.id}/`) .setDescription(`#${item.id}`) .setColor('#f23857') .setThumbnail(item.cover)); |
6. 判斷選擇值
使用者選擇完下拉式選單後,選擇結果會儲存至 interaction.values[0] 當中,判斷並執行對應的功能。
當前熱門漫畫
使用 sHentai 套件內的 getPopular() 方法取得搜尋結果。
comic = await sHentai.getPopular(); await interaction.update({ embeds: setMoreComicEmbed(comic), components: [] }); |
隨機產生漫畫
使用 sHentai 套件內的 getRandom() 方法取得漫畫物件。
comic = await sHentai.getRandom(); await interaction.update({ embeds: [setComicEmbed(comic)], components: [] }); |
根據 id / 關鍵字 / 作者搜尋漫畫
這三個功能皆須要使用者再輸入對應的查詢條件,才能進行使用,所以當選擇這三個功能時,使用 discord.js 內置的 Modal 及 TextInputComponent 方法建立一個互動視窗,供使用者進行輸入,而透過 id 進行搜尋則限制為最大只能輸入 6 位數字,確保使用者能搜尋到漫畫。
原先打算在互動式窗內進行功能選擇,但目前 DiscordJS 沒有這個做法,甚至 Discord 本身也沒有。
let customId, info, valueActionRow; let components = new TextInputComponent() .setCustomId('value') .setLabel("請輸入查詢條件") .setStyle('SHORT') .setRequired(true); if (menuValue === 'id') { customId = 'comicIdModel'; info = '編號查詢'; components = new TextInputComponent() .setCustomId('value') .setLabel("請輸入查詢條件") .setStyle('SHORT') .setMaxLength(6) .setRequired(true); } else if (menuValue === 'keyword') { customId = 'comicKeywordModel'; info = '關鍵字查詢'; } else if (menuValue === 'author') { customId = 'comicAuthorModel'; info = '作者查詢'; } valueActionRow = new MessageActionRow().addComponents(components); const modal = new Modal() .setCustomId(customId) .setTitle(info); modal.addComponents(valueActionRow); await interaction.showModal(modal); |
7. 獲取互動視窗交互結果
當使用者點擊送出後,根據輸入值執行相對應的動作。
client.on('interactionCreate', async (interaction) => { if (interaction.isModalSubmit()) { let modal; const customId = [ 'comicIdModel', 'comicKeywordModel', 'comicAuthorModel' ]; if (customId.includes(interaction.customId)) { modal = require('./src/comic/modal'); } try { await modal.execute(interaction); } catch (error) { console.error(error); interaction.reply({ content: '執行此命令時出錯!', ephemeral: true }); } } } |
8. 判斷使用者使用的互動視窗
使用 interaction.customId,獲取並判斷使用者所使用的互動視窗,並用 interaction.fields.getTextInputValue('value') 取得輸入值後,執行對應的功能。
id 搜尋漫畫
先使用 正則表達式 判斷是否為正整數,接著使用 sHentai 套件內的 getDoujin(value) 方法取得搜尋結果。
if (/^[0-9]*$/.test(value)) { comic = await sHentai.getDoujin(value); const embed = comic.status ? errorEmbed('看起來你要找的東西不在這裡!') : setComicEmbed(comic); await interaction.update({ embeds: [embed], components: [] }); } else { await interaction.update({ embeds: [errorEmbed('請輸入純數字!')], components: [] }); } |
關鍵搜尋漫畫
使用 sHentai 套件內的 byAuthor(value) 方法取得搜尋結果。
comic = await sHentai.byAuthor(value); const authorEmbed = comic.status ? errorEmbed('看起來你要找的東西不在這裡!') : setMoreComicEmbed(comic.results); await interaction.update({ embeds: [authorEmbed], components: [] }); |
作者搜尋漫畫
使用 sHentai 套件內的 search(value) 方法取得搜尋結果。
comic = await sHentai.search(value); const keyWordEmbed = comic.status ? errorEmbed('看起來你要找的東西不在這裡!') : setMoreComicEmbed(comic.results); await interaction.update({ embeds: [keyWordEmbed], components: [] }); |
回傳值介紹
漫畫物件
{ id: String, // id author: String, // 作者 titles: { english: String, // 英文標題 original: String, // 原始標題 }, pages: Array<String>, // 內容連結 (字元陣列) tags: Array<String>, // 標籤 (字元陣列) cover: String // 封面 } |
搜尋類
{ results: Object[], // 結果 (物件) allDoujinsCount: Number, // 結果總數 lastPage: Number, // 總頁數 currentPage: Number, // 當前頁數 next: Promise<SearchResult>, // 下一頁 prev: Promise<SearchResult>, // 前一頁 first: Promise<SearchResult>, // 第一頁 last: Promise<SearchResult>, // 最後一頁 getPage: Promise<SearchResult>, // 指定頁 } |
搜尋結果
[ { id: String, // id titles: Object, // 標題 cover: String // 封面 }, ] |