From 95694e3912bfd9f8401fdff3c702d009ad135132 Mon Sep 17 00:00:00 2001 From: sharpy66 <40325290+sharpy66@users.noreply.github.com> Date: Sun, 20 Aug 2023 02:51:35 -0700 Subject: [PATCH] Detailed Device Info Add select button to grab detailed devices results Restrict button interactions to only whoever sent the initial command Change formatting for some things Additional data can now be easily added to the detailed results embed, if needed. --- src/commands/specs.ts | 178 ++++++++++++++++++++++++++++++------------ 1 file changed, 127 insertions(+), 51 deletions(-) diff --git a/src/commands/specs.ts b/src/commands/specs.ts index 658bb90..97f45de 100644 --- a/src/commands/specs.ts +++ b/src/commands/specs.ts @@ -1,14 +1,13 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, EmbedBuilder, Interaction, Message, MessageActionRow, MessageButton, MessageEmbed, sentMessage } from 'discord.js'; -import { search as fakeapi } from 'gsmarena-api'; +import { search as fakeapi_search, catalog as fakeapi_detail } from 'gsmarena-api'; import logger from '../logging'; // Variables you might want to change: // Allowed roles: export const roles = ["@everyone"]; -// How long before previous messages can no longer be interacted with. default is 200000 (200 seconds) +// How long before previous messages can no longer be interacted with: const mtime = 200000 // default 200000 (200 seconds) - // Button config: const next = new ButtonBuilder() .setCustomId('next') @@ -19,12 +18,77 @@ const previous = new ButtonBuilder() .setCustomId('previous') .setLabel('< Previous Result') .setStyle(ButtonStyle.Secondary); + +const select = new ButtonBuilder() + .setCustomId('select') + .setLabel('Select') + .setStyle(ButtonStyle.Success); + const row = new ActionRowBuilder() - .addComponents(previous, next); + .addComponents(previous, select, next); + +// Create detailed embed with selected device information. +async function createDetailedEmbed(selectedDevice: any, searchQuery: string, results: number, index: number, searchResults: array) { +let device = await fakeapi_detail.getDevice(selectedDevice.id); + + let name = device.name; // very messy and long code for parsing data + let platformChipset = device.detailSpec.find(spec => spec.category === 'Platform')?.specifications.find(spec => spec.name === 'Chipset')?.value || 'Unknown'; + let platformGPU = device.detailSpec.find(spec => spec.category === 'Platform')?.specifications.find(spec => spec.name === 'GPU')?.value || 'Unknown'; + let memoryInternal = device.detailSpec.find(spec => spec.category === 'Memory')?.specifications.find(spec => spec.name === 'Internal')?.value || 'Unknown'; + let display = device.quickSpec.find(spec => spec.name === 'Display size')?.value || 'Unknown'; + let battery = device.quickSpec.find(spec => spec.name === 'Battery size')?.value || 'Unknown'; + let year = device.detailSpec.find(spec => spec.category === 'Launch')?.specifications.find(spec => spec.name === 'Announced')?.value.split(',')[0] || 'Unknown'; + let misc = 'Announced in '+year+' '+display+' display, '+battery+' battery.'; + + // format storage and ram nicely + let storage = [...new Set(memoryInternal.match(/\d+(GB|TB)(?= \d+GB RAM)/g))].join('/') || 'Unknown '; + let ram = "Unknown " + if (memoryInternal) { // prevent an error when attempting to get detailed info on devices with no information on GSMArena + let matches = memoryInternal.match(/\d+GB RAM/g); + if (matches) { + ram = [...new Set(matches.map(x => x.replace(' RAM', '')))].join('/'); + } + } + // Messsy json parsing continues with a touch of formatting the cpu and gpu info nicely + let snapdragon = platformChipset.includes('Snapdragon') ? 'Snapdragon ' + platformChipset.split('Snapdragon')[1].split('(')[0].trim() : undefined; + let exynos = platformChipset.includes('Exynos') ? 'Exynos ' + platformChipset.split('Exynos')[1].split(' ')[1] : undefined; + let dimensity = platformChipset.includes('Dimensity') ? 'Dimensity ' + platformChipset.split('Dimensity')[1].split(' ')[1] : undefined; + let formattedChipset = [snapdragon, exynos, dimensity].filter(word => word !== undefined).join(' / '); + if (formattedChipset === '') { // prevent errors when formattedChipset is null + formattedChipset = device.quickSpec.find(spec => spec.name === 'Chipset')?.value || 'Unknown'; + } + + let adreno = platformGPU.includes('Adreno') ? 'Adreno ' + platformGPU.split('Adreno')[1].split(' ')[1] : undefined; + let xclipse = platformGPU.includes('Xclipse') ? 'Xclipse ' + platformGPU.split('Xclipse')[1].split(' ')[1] : undefined; + let mali = platformGPU.includes('Mali') ? 'Mali-' + platformGPU.split('Mali-')[1].split(' ').slice(0, 2).join(' ') : undefined; + let formattedGPU = [adreno, xclipse, mali].filter(word => word !== undefined).join(' / '); + if (formattedGPU === '') { + formattedGPU = platformGPU + } + + const imageUrl = device.img; + let embed = new EmbedBuilder() // Assemble Embed + .setColor('#0099ff') + .setTitle(`Result for: ${searchQuery}`) + .setDescription('Source: https://www.gsmarena.com') + .addFields( + { name: 'Device:', value: name }, + { name: 'Chipset:', value: formattedChipset }, + { name: 'GPU', value: formattedGPU }, + { name: 'Ram:', value: ram, inline: true }, + { name: 'Storage:', value: storage, inline: true }, + { name: 'Misc:', value: misc }, + ) + .setThumbnail(imageUrl) + .setFooter({ text: searchResults.length > 1 ? "Showing Detailed Result...." : "This is the only result for your query." }); + + return embed; +} // Create embed with current device information. -function createEmbed(device: any, searchQuery: string, results: number, index: number, devices: array) { +function createEmbed(device: any, searchQuery: string, results: number, index: number, searchResults: array) { + let name = device.name; let chipset = device.description.match(/(?:display,)(.*)(?:chipset)/)?.[1].trim() || 'Unknown'; // parsing json data let ram = device.description.match(/(?:\s)([\d]+)\sGB RAM/)?.[1] + 'GB' || 'Unknown'; @@ -43,9 +107,9 @@ function createEmbed(device: any, searchQuery: string, results: number, index: n const imageUrl = device?.img; - const embed = new EmbedBuilder() + let embed = new EmbedBuilder() .setColor('#0099ff') - .setTitle(devices.length > 1 ? `Search results for: ${searchQuery}` : `Result for: ${searchQuery}`) + .setTitle(searchResults.length > 1 ? `Preview results for: ${searchQuery}` : `Result for: ${searchQuery}`) .setDescription('Source: https://www.gsmarena.com') .addFields( { name: 'Device:', value: name }, @@ -55,22 +119,23 @@ function createEmbed(device: any, searchQuery: string, results: number, index: n { name: 'Misc:', value: misc }, ) .setThumbnail(imageUrl) - .setFooter({ text: devices.length > 1 ? results+' total results... '+(index + 1)+'/'+devices.length : "This is the only result for your query (:" }); + .setFooter({ text: searchResults.length > 1 ? results+' total results... '+(index + 1)+'/'+searchResults.length : "This is the only result for your query (:" }); return embed; } // End listener function to disable buttons after message collector times out -function addEndListener(collector, previous, next, row, sentMessage, devices) { +function addEndListener(collector, previous, next, row, sentMessage, searchResults) { let endListenerAdded = false; - + // Function to be called when the collector ends to disable buttons and update message. const endListener = async () => { previous.setDisabled(true); next.setDisabled(true); - + select.setDisabled(true); + // Update footer text on timeout only if there were multiple results. - if (devices.length > 1) { + if (searchResults.length > 1) { const newEmbed = new EmbedBuilder(sentMessage.embeds[0]) .setFooter({ text: "Interaction timeout: Buttons disabled." }); await sentMessage.edit({ components: [row] }).catch(console.error); @@ -78,7 +143,7 @@ function addEndListener(collector, previous, next, row, sentMessage, devices) { } collector.stop(); }; - + // Add the end listener only if it hasn't already been added. if (!endListenerAdded) { collector.on('end', endListener); @@ -93,65 +158,76 @@ export async function command(message: Message) { // Reset index and button state. let index = 0; previous.setDisabled(true); + select.setDisabled(false); next.setDisabled(false); // Split message into array. const words = message.content.split(' '); // Remove the command and pass the rest of the message along to the gsmarena-api. const searchQuery = words.slice(1).join(' '); - const devices = await fakeapi.search(searchQuery); + const searchResults = await fakeapi_search.search(searchQuery); - if (devices.length === 0) { + if (searchResults.length === 0) { message.channel.send('No device(s) found for "' + searchQuery + '"'); return; } // Create the initial embed with the first device information and reset button state. - const embed = createEmbed(devices[index], searchQuery, devices.length, index, devices); + const embed = createEmbed(searchResults[index], searchQuery, searchResults.length, index, searchResults); // Only send buttons if there are multiple results let sentMessage; - if (devices.length > 1) { + if (searchResults.length > 1) { sentMessage = await message.channel.send({ embeds: [embed], components: [row] }); } else { - sentMessage = await message.channel.send({ embeds: [embed] }); + let detailedEmbed = await createDetailedEmbed(searchResults[index], searchQuery, searchResults.length, index, searchResults); + sentMessage = await message.channel.send({ embeds: [detailedEmbed] }); } - // Create the interaction collector -- handles button presses. - const filter = (interaction: Interaction) => interaction.isButton() && (interaction.customId === 'next' || interaction.customId === 'previous'); - const collector = sentMessage.createMessageComponentCollector({ filter, time: mtime }); - addEndListener(collector, previous, next, row, sentMessage, devices); + // Create the interaction collector -- handles button presses. + const filter = (interaction: Interaction) => interaction.isButton() && (interaction.customId === 'next' || interaction.customId === 'previous' || interaction.customId === 'select'); + const collector = sentMessage.createMessageComponentCollector({ filter, time: mtime }); + addEndListener(collector, previous, next, row, sentMessage, searchResults); - - // Logic to update the message and components when cycling through pages. - collector.on('collect', async (interaction: Interaction) => { - try { - if (interaction.customId === 'next') { - index++; - } else if (interaction.customId === 'previous') { - index--; - } - index = Math.max(0, Math.min(index, devices.length - 1)); - - // Update button states based on current index. - previous.setDisabled(index === 0); - next.setDisabled(index === devices.length - 1); - - // Create a new embed with the updated device information. - const newEmbed = createEmbed(devices[index], searchQuery, devices.length, index, devices); - - // Update the message with the new embed and components. - await interaction.deferUpdate(); - await interaction.editReply({ embeds: [newEmbed], components: [row] }); + // Logic to update the message and components when cycling through pages. + collector.on('collect', async (interaction: Interaction) => { + if (interaction.user.id !== message.author.id) { // Only allow the original command user to interact with the buttons. + // Send an ephemeral reply to the user + await interaction.reply({ content: 'Only the original sender of the command can interact with buttons.', ephemeral: true }); + return; + } + let selectButtonPushed = false; + try { + if (interaction.customId === 'next') { + index++; + } else if (interaction.customId === 'previous') { + index--; + } else if (interaction.customId == 'select') { + const detailedEmbed = await createDetailedEmbed(searchResults[index], searchQuery, searchResults.length, index, searchResults); + await interaction.deferUpdate(); + await interaction.editReply({ embeds: [detailedEmbed], components: [] }); + selectButtonPushed = true; + } + index = Math.max(0, Math.min(index, searchResults.length - 1)); + // Update button states based on current index. + previous.setDisabled(index === 0); + next.setDisabled(index === searchResults.length - 1); + + // Create a new embed with the updated device information. + if (!selectButtonPushed) { + let newEmbed = createEmbed(searchResults[index], searchQuery, searchResults.length, index, searchResults); + await interaction.deferUpdate(); + await interaction.editReply({ embeds: [newEmbed], components: [row] }); + } + } catch (error) { + logger.error('Collect Error:', error); + await interaction.followUp('An error occurred while processing the button click.') + } finally { + previous.setDisabled(); + next.setDisabled(); + } + }); - } catch (error) { - logger.error('Collect Error:', error); - await interaction.followUp('An error occurred while processing the button click.') - } finally { - previous.setDisabled(); - next.setDisabled(); - } - }); } catch (error) { logger.error('Command Error:', error); message.channel.send('An error occurred while processing your request. It would appear whatever you just did broke the bot... Congratulations!');