From 0143f158435191fc7dddcd7e733bc31882a025d2 Mon Sep 17 00:00:00 2001 From: Ryan David Ward Date: Sun, 10 Mar 2024 13:53:39 -0500 Subject: [PATCH] revert to fix formatting --- commands/bank/find.js | 7 +- commands/bank/find.ts | 197 +++++++++++++++------------- commands/bank/item_functions.js | 3 - commands/bank/item_functions.ts | 219 +++++++++++++++++++------------- 4 files changed, 249 insertions(+), 177 deletions(-) diff --git a/commands/bank/find.js b/commands/bank/find.js index f5aac85..34319cd 100644 --- a/commands/bank/find.js +++ b/commands/bank/find.js @@ -3,7 +3,7 @@ import { AttachmentBuilder, EmbedBuilder, SlashCommandBuilder, } from 'discord.j import _ from 'lodash'; import { AppDataSource } from '../../app_data.js'; import { Bank } from '../../entities/Bank.js'; -import { formatField, getImageUrl, getItemStatsText, getSpellDescription, getSpellLevels, } from './item_functions.js'; +import { getImageUrl, getItemStatsText, getSpellDescription, getSpellLevels, } from './item_functions.js'; export const data = new SlashCommandBuilder() .setName('find') .setDescription('Find an item in the guild bank.') @@ -12,6 +12,9 @@ export const data = new SlashCommandBuilder() .setDescription('Item name to search for') .setRequired(true) .setAutocomplete(true)); +export function formatField(field) { + return '```\n' + field.join('\n') + '\n```'; +} export async function autocomplete(interaction) { try { const focusedOption = interaction.options.getFocused(true); @@ -68,9 +71,9 @@ export async function execute(interaction) { itemText = itemText?.replace(/\[\[[^\]]*\|([^\]]+)\]\]/g, '$1'); itemText = itemText?.replace(/(.{1,45})(\s|$)/g, '$1\n'); const lineHeight = 25; - const padding = 50; const lines = itemText.split('\n'); const textHeight = lines.length * lineHeight; + const padding = 50; const canvasHeight = _.max([textHeight + padding, 200]); const canvas = Canvas.createCanvas(700, canvasHeight); const context = canvas.getContext('2d'); diff --git a/commands/bank/find.ts b/commands/bank/find.ts index 786fb6b..38cde62 100644 --- a/commands/bank/find.ts +++ b/commands/bank/find.ts @@ -6,10 +6,10 @@ import { EmbedBuilder, SlashCommandBuilder, } from 'discord.js'; +import _ from 'lodash'; import { AppDataSource } from '../../app_data.js'; import { Bank } from '../../entities/Bank.js'; import { - formatField, getImageUrl, getItemStatsText, getSpellDescription, @@ -22,15 +22,21 @@ export const data = new SlashCommandBuilder() .addStringOption(option => option .setName('item') - .setDescription('The name of the item to search for') + .setDescription('Item name to search for') .setRequired(true) .setAutocomplete(true), ); +export function formatField(field: string[]): string { + return '```\n' + field.join('\n') + '\n```'; +} + export async function autocomplete(interaction: AutocompleteInteraction) { try { const focusedOption = interaction.options.getFocused(true); - if (focusedOption && focusedOption.name === 'item') { + if (!focusedOption) return; + + if (focusedOption.name === 'item') { const searchTerm = `%${focusedOption.value}%`; const items = await AppDataSource.manager .createQueryBuilder(Bank, 'item') @@ -41,7 +47,7 @@ export async function autocomplete(interaction: AutocompleteInteraction) { .limit(10) .getRawMany(); - await interaction.respond( + return await interaction.respond( items.map(item => ({ name: `(${item.Count}x) ${item.Name}`, value: item.Name, @@ -50,107 +56,126 @@ export async function autocomplete(interaction: AutocompleteInteraction) { } } catch (error) { - console.error('Error in autocomplete:', error); + if (error instanceof Error) { + console.error('Error in autocomplete:', error); + } } } export async function execute(interaction: CommandInteraction) { try { - const itemName = interaction.options.get('item', true).value as string; - const itemData = await AppDataSource.manager.find(Bank, { - where: { Name: itemName }, - }); - if (itemData.length === 0) { - await interaction.reply('Item not found.'); - return; - } + const { options } = interaction; - let itemText = - itemName.startsWith('Spell: ') || itemName.startsWith('Song: ') - ? `${await getSpellLevels(itemName)}\n\n${await getSpellDescription(itemName)}` - : await getItemStatsText(itemName); - - if (!itemText) { - await interaction.reply('Item not found.'); - return; + if (!options.get('item')) { + throw new Error('You must provide an item to find.'); } + else { + const itemName = options.get('item')?.value as string; - itemText = itemText.replace(/\[\[[^\]]*\|([^\]]+)\]\]/g, '$1'); - itemText = itemText.replace(/(.{1,45})(\s|$)/g, '$1\n'); - - const lineHeight = 25; - const padding = 50; - const lines = itemText.split('\n'); - const textHeight = lines.length * lineHeight; - const canvasHeight = Math.max(textHeight + padding, 200); - const canvas = Canvas.createCanvas(700, canvasHeight); - const context = canvas.getContext('2d'); - - const background = await Canvas.loadImage('./images/stars.png'); - context.drawImage(background, 0, 0, canvas.width, canvas.height); - - const imageUrl = await getImageUrl(itemName); - if (imageUrl) { - const icon = await Canvas.loadImage(imageUrl); - context.drawImage(icon, canvas.width - 100, 25, 75, 75); - context.font = `${lineHeight}px sans-serif`; - context.fillStyle = '#ffffff'; - lines.forEach((line, i) => { - context.fillText(line, 25, 25 + i * lineHeight); + const itemData = await AppDataSource.manager.find(Bank, { + where: { Name: itemName }, }); - } - const attachment = new AttachmentBuilder(await canvas.encode('png'), { - name: 'item-stats-image.png', - }); + // check if the itemName is a spell or a song + let itemText: string | null | undefined = ''; - const isStack = itemData.some(item => item.Quantity > 1); - const bankers = itemData.map(item => item.Banker); - - const embed = new EmbedBuilder() - .setTitle(':bank: Bank Record') - .setDescription(`**${itemName}**\n`) - .setColor('Green') - .setImage('attachment://item-stats-image.png'); - - const embedBuilder = [...new Set(bankers)].reduce((currentEmbed, banker) => { - const itemsOnBankers = itemData.filter(item => item.Banker === banker); - const sortedItems = itemsOnBankers.sort((a, b) => a.Location.localeCompare(b.Location)); - const sortedItemLocations = formatField( - sortedItems.map(item => item.Location.replace('-', ' ')), - ); + if (itemName.startsWith('Spell: ') || itemName.startsWith('Song: ')) { + const spellLevels = await getSpellLevels(itemName); + const spellDescription = await getSpellDescription(itemName); + itemText = `${spellLevels}\n\n${spellDescription}`; + // wrap newlines every 50 characters, but don't break words + } + else { + itemText = await getItemStatsText(itemName); + } - if (sortedItems.length === 0) { - return currentEmbed; + if (!itemText) { + await interaction.reply('Item not found.'); + return; } - currentEmbed.addFields( - { - name: `:bust_in_silhouette: ${banker}`, - value: `${sortedItems.length} matching item(s)`, - inline: false, - }, - { name: ':mag: Location', value: sortedItemLocations, inline: true }, - ); - if (isStack) { - currentEmbed.addFields({ - name: ':money_bag: Stack', - value: formatField(sortedItems.map(item => item.Quantity.toString())), - inline: true, - }); + itemText = itemText?.replace(/\[\[[^\]]*\|([^\]]+)\]\]/g, '$1'); + itemText = itemText?.replace(/(.{1,45})(\s|$)/g, '$1\n'); + const lineHeight = 25; + const lines = itemText.split('\n'); + const textHeight = lines.length * lineHeight; + const padding = 50; + const canvasHeight = _.max([textHeight + padding, 200]) as number; + const canvas = Canvas.createCanvas(700, canvasHeight); + const context = canvas.getContext('2d'); + + const background = await Canvas.loadImage('./images/stars.png'); + context.drawImage(background, 0, 0, canvas.width, canvas.height); + + const imageUrl = await getImageUrl(itemName); + + if (imageUrl) { + try { + const icon = await Canvas.loadImage(imageUrl); + + context.drawImage(icon, canvas.width - 100, 25, 75, 75); + if (itemText) { + context.font = `${lineHeight}px sans-serif`; + context.fillStyle = '#ffffff'; + lines.forEach((line, i) => { + context.fillText(line, 25, 25 + i * lineHeight + 25); + }); + } + } + catch (error) { + console.error('Failed to load image:', error); + return; + } } - return currentEmbed; - }, embed); - await interaction.reply({ embeds: [embedBuilder], files: [attachment] }); + const attachment = new AttachmentBuilder(await canvas.encode('png'), { + name: 'item-stats-image.png', + }); + + const isStack = itemData.some(item => item.Quantity > 1); + const bankers = itemData.map(item => item.Banker); + + const embed = new EmbedBuilder() + .setTitle(':bank: Bank Record') + .setDescription(`**${itemName}**\n`) + .setColor('Green') + .setImage('attachment://item-stats-image.png'); + + const embedBuilder = [...new Set(bankers)].reduce((currentEmbed: EmbedBuilder, banker) => { + const itemsOnBankers = itemData.filter(item => item.Banker === banker); + const sortedItems = itemsOnBankers.sort((a, b) => a.Location.localeCompare(b.Location)); + const sortedItemLocations = formatField( + sortedItems.map(item => _.replace(item.Location, '-', ' ')), + ); + + if (sortedItems.length === 0) { + return currentEmbed; + } + + currentEmbed.addFields( + { + name: `:bust_in_silhouette: ${banker}`, + value: `${sortedItems.length} matching item(s).`, + inline: false, + }, + { name: ':mag: Location', value: sortedItemLocations, inline: true }, + ); + if (isStack) { + currentEmbed.addFields({ + name: ':money_bag: Stack', + value: formatField(sortedItems.map(item => item.Quantity.toString())), + inline: true, + }); + } + return currentEmbed; + }, embed); + + await interaction.reply({ embeds: [embedBuilder], files: [attachment] }); + } } - catch (error: unknown) { - console.error('Error in execute:', error); + catch (error) { if (error instanceof Error) { await interaction.reply(`Error in execute: ${error.message}`); } - else { - await interaction.reply('An unknown error occurred.'); - } } } diff --git a/commands/bank/item_functions.js b/commands/bank/item_functions.js index 58d524c..6fc92dd 100644 --- a/commands/bank/item_functions.js +++ b/commands/bank/item_functions.js @@ -1,8 +1,5 @@ import axios from 'axios'; import puppeteer from 'puppeteer'; -export function formatField(field) { - return '```\n' + field.join('\n') + '\n```'; -} export async function getImageUrl(itemName) { // Standardize the item name to ensure cache consistency const standardizedItemName = itemName.replace('Song: ', '').replace('Spell: ', ''); diff --git a/commands/bank/item_functions.ts b/commands/bank/item_functions.ts index 6f53c98..440b251 100644 --- a/commands/bank/item_functions.ts +++ b/commands/bank/item_functions.ts @@ -13,25 +13,12 @@ interface MediaWikiResponse { }; } -export function formatField(field: string[]): string { - return '```\n' + field.join('\n') + '\n```'; -} - -const baseUrl = 'http://localhost/mediawiki/api.php'; - -async function fetchMediaWikiResponse(params: URLSearchParams): Promise { - try { - const response = await axios.get(baseUrl, { params }); - return response.data; - } - catch (error) { - console.error('Error fetching from MediaWiki:', error); - return null; - } -} - export async function getImageUrl(itemName: string): Promise { - const standardizedItemName = itemName.replace(/(Song: |Spell: )/g, ''); + // Standardize the item name to ensure cache consistency + const standardizedItemName = itemName.replace('Song: ', '').replace('Spell: ', ''); + + // If not in cache, proceed to fetch the image URL + const baseUrl = 'http://localhost/mediawiki/api.php'; const searchParams = new URLSearchParams({ action: 'query', prop: 'revisions', @@ -40,50 +27,68 @@ export async function getImageUrl(itemName: string): Promise { format: 'json', }); - const searchResponse = await fetchMediaWikiResponse(searchParams); - if (!searchResponse) return null; - - const pageId = Object.keys(searchResponse.query.pages)[0]; - const pageData = searchResponse.query.pages[pageId]; - if (!pageData.revisions) return null; - - const content = pageData.revisions[0]['*']; - const lucyImgIdMatch = content.match(/lucy_img_ID\s*=\s*(\d+)/); - const spelliconMatch = content.match(/spellicon\s*=\s*(\w+)/); - - let filename; - if (lucyImgIdMatch) { - filename = `item_${lucyImgIdMatch[1]}.png`; - } - else if (spelliconMatch) { - filename = `Spellicon_${spelliconMatch[1]}.png`; + try { + const searchResponse = await axios.get(baseUrl, { params: searchParams }); + const pageId = Object.keys(searchResponse.data.query.pages)[0]; + const pageData = searchResponse.data.query.pages[pageId]; + + if (!pageData.revisions) { + console.log(searchResponse.data); + return null; + } + + const lucyImgIdMatch = pageData.revisions[0]['*'].match(/lucy_img_ID\s*=\s*(\d+)/); + const spelliconMatch = pageData.revisions[0]['*'].match(/spellicon\s*=\s*(\w+)/); + + let filename; + if (lucyImgIdMatch) { + const imageId = lucyImgIdMatch[1]; + filename = `item_${imageId}.png`; + } + else if (spelliconMatch) { + const imageId = spelliconMatch[1]; + filename = `Spellicon_${imageId}.png`; + } + else { + return null; + } + + const imageInfoParams = new URLSearchParams({ + action: 'query', + prop: 'imageinfo', + titles: `File:${filename}`, + iiprop: 'url', + format: 'json', + }); + + const imageInfoResponse = await axios.get(baseUrl, { + params: imageInfoParams, + }); + const imagePageId = Object.keys(imageInfoResponse.data.query.pages)[0]; + const imagePageData = imageInfoResponse.data.query.pages[imagePageId]; + + if (imagePageData.imageinfo) { + let imageUrl = imagePageData.imageinfo[0].url; + imageUrl = imageUrl.replace('http://localhost:80/mediawiki/images', '/var/lib/mediawiki'); + + // Cache the image URL for future use + + return imageUrl; + } } - else { + catch (error) { + console.error('Error fetching image URL:', error); return null; } - const imageInfoParams = new URLSearchParams({ - action: 'query', - prop: 'imageinfo', - titles: `File:${filename}`, - iiprop: 'url', - format: 'json', - }); - - const imageInfoResponse = await fetchMediaWikiResponse(imageInfoParams); - if (!imageInfoResponse) return null; - - const imagePageId = Object.keys(imageInfoResponse.query.pages)[0]; - const imagePageData = imageInfoResponse.query.pages[imagePageId]; - if (!imagePageData.imageinfo) return null; - - let imageUrl = imagePageData.imageinfo[0].url; - imageUrl = imageUrl.replace('http://localhost:80/mediawiki/images', '/var/lib/mediawiki'); - return imageUrl; + return null; } export async function getItemUrl(itemName: string): Promise { - const standardizedItemName = itemName.replace(/(Song: |Spell: )/g, ''); + // Standardize the item name to ensure cache consistency + const standardizedItemName = itemName.replace('Song: ', '').replace('Spell: ', ''); + + const baseUrl = 'http://localhost/mediawiki/api.php'; const searchParams = new URLSearchParams({ action: 'query', prop: 'info', @@ -92,49 +97,78 @@ export async function getItemUrl(itemName: string): Promise { format: 'json', }); - const searchResponse = await fetchMediaWikiResponse(searchParams); - if (!searchResponse) return null; + const searchResponse = await axios.get(baseUrl, { params: searchParams }); - return searchResponse.query.pages[Object.keys(searchResponse.query.pages)[0]].fullurl || null; + // Return the 'fullurl' property of the page + return ( + searchResponse.data.query.pages[Object.keys(searchResponse.data.query.pages)[0]].fullurl || null + ); } - -async function fetchPageContent(url: string): Promise { +export async function getItemStatsText(itemName: string): Promise { const browser = await puppeteer.launch(); const page = await browser.newPage(); - await page.goto(url); - return page.content(); -} - -export async function getItemStatsText(itemName: string): Promise { const itemUrl = await getItemUrl(itemName); - if (!itemUrl) { + if (itemUrl) { + await page.goto(itemUrl); + } + else { console.error('Item URL is null'); + await browser.close(); return null; } - const content = await fetchPageContent(itemUrl); - if (!content) return 'No stats found'; + // Primary stats element + const statsElement = await page.$( + '#mw-content-text > div.mw-parser-output > div.itembg > div > p', + ); - const statsTextMatch = content.match(/
.*?

(.*?)<\/p>/s); - return statsTextMatch ? statsTextMatch[1].trim() : 'No stats found'; + if (!statsElement) { + console.log('No stats element found with the given selector. Attempting fallback...'); + return 'No stats found'; + } + else { + const statsText = await page.evaluate(element => element.textContent || '', statsElement); + await browser.close(); + return statsText.trim(); + } } export async function getSpellLevels(spellName: string): Promise { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); const itemUrl = await getItemUrl(spellName); - if (!itemUrl) { + if (itemUrl) { + await page.goto(itemUrl); + } + else { console.error('Item URL is null'); + await browser.close(); return null; } + // Find the first two h2 elements + const h2Elements = await page.$$('h2'); + let statsText = ''; + + // Ensure to only process the first two h2 elements + for (let i = 0; i < Math.min(h2Elements.length, 1); i++) { + // Get the next sibling element of each h2, which contains the desired content + const nextElement = await h2Elements[i].evaluateHandle(el => el.nextElementSibling); + + // Extract text content if the element exists + const nextElementText = await (await nextElement.getProperty('textContent')).jsonValue(); + statsText += nextElementText + '\n'; + } - const content = await fetchPageContent(itemUrl); - if (!content) return null; - - const spellLevelsMatch = content.match(/

.*?<\/h2>(.*?)

/s); - return spellLevelsMatch ? spellLevelsMatch[1].trim() : null; + await browser.close(); + return statsText.trim(); } export async function getSpellDescription(spellName: string): Promise { - const standardizedItemName = spellName.replace(/(Song: |Spell: )/g, ''); + // Standardize the item name to ensure cache consistency + const standardizedItemName = spellName.replace('Song: ', '').replace('Spell: ', ''); + + // If not in cache, proceed to fetch the image URL + const baseUrl = 'http://localhost/mediawiki/api.php'; const searchParams = new URLSearchParams({ action: 'query', prop: 'revisions', @@ -143,13 +177,26 @@ export async function getSpellDescription(spellName: string): Promise(baseUrl, { params: searchParams }); + const pageId = Object.keys(searchResponse.data.query.pages)[0]; + const pageData = searchResponse.data.query.pages[pageId]; + + if (!pageData.revisions) { + console.log(searchResponse.data); + return null; + } + + const spellDescription = pageData.revisions[0]['*'].match(/description\s*=\s*(.*)/); + if (spellDescription) { + return spellDescription[1]; + } + else { + return null; + } + } + catch (error) { + console.error('Error fetching spell description:', error); + return null; + } }