first version of prices command done

This commit is contained in:
2022-11-12 16:48:31 -03:00
parent 3d7666d7a0
commit 2661897767
4 changed files with 222 additions and 13 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
node_modules
yarn.lock
.env
.env

34
app.js
View File

@@ -7,7 +7,6 @@ const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on('ready', () => {
console.log('working');
console.log(client.user);
});
client.login(process.env.BOT_TOKEN);
@@ -29,21 +28,36 @@ for (const file of commandFiles) {
}
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
console.log(interaction);
// if (!interaction.isChatInputCommand()) return;
const command = interaction.client.commands.get(interaction.commandName);
if (interaction.isChatInputCommand()) {
try {
await command.execute(interaction);
}
catch (error) {
console.error(error);
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
}
else if (interaction.isAutocomplete()) {
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
return;
}
try {
await command.autocomplete(interaction);
}
catch (error) {
console.error(error);
}
}
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
return;
}
try {
await command.execute(interaction);
}
catch (error) {
console.error(error);
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
});

193
commands/price.js Normal file
View File

@@ -0,0 +1,193 @@
const { SlashCommandSubcommandBuilder, hyperlink, bold } = require('discord.js');
const puppeteer = require('puppeteer');
const jsdom = require('jsdom');
const countryData = {
'ar': {
name: 'Argentina',
currency: 'ARS',
pages: [
{
name: 'Mercado Libre (Argentina)',
searchUrl: 'https://listado.mercadolibre.com.ar/%S#D%5BA:%S',
productUrl: 'https://articulo.mercadolibre.com.ar%S',
selectors: {
container: 'div.andes-card.ui-search-result',
link: 'a.ui-search-link',
price: 'span.price-tag-fraction',
title: 'h2.ui-search-item__title.shops__item-title',
},
},
],
},
'us': {
name: 'United States',
currency: 'USD',
pages: [
{
name: 'Amazon (United States)',
searchUrl: 'https://www.amazon.com/s?k=%S',
productUrl: 'https://www.amazon.com%S',
selectors: {
container: 'div.s-card-container > div.a-section > div.sg-row',
link: 'h2.a-size-mini a.a-link-normal',
price: 'span.a-price span.a-offscreen',
title: 'h2.a-size-mini span.a-size-medium',
},
},
],
},
'cl': {
name: 'Chile',
currency: 'CLP',
pages: [
{
name: 'Falabella',
searchUrl: 'https://www.falabella.com/falabella-cl/search?Ntt=%S',
productUrl: 'https://www.falabella.com%S',
selectors: {
container: 'div.pod-4_GRID',
link: 'a',
price: 'div.prices span.copy10',
title: 'div.pod-details a.pod-link span > b.pod-subTitle',
},
},
],
},
};
const pages = Object.values(countryData).map((country) => country.pages).flat(1);
const responses = {
'extractedFrom': {
'en-US': 'Prices extracted from:',
'es-ES': 'Precios extraídos de:',
},
'missingPlatform': {
'en-US': 'ERROR: Platform don\'t found!!',
'es-ES': 'ERROR: Plataforma no encontrada!!',
},
};
const ELEMENTS_LIMIT = 3;
module.exports = {
data: new SlashCommandSubcommandBuilder()
.setName('prices')
.setNameLocalizations({
'es-ES': 'precios',
})
.setDescription('Get the prices of a product')
.setDescriptionLocalizations({
'es-ES': 'Consigue los precios de algún producto',
})
.addStringOption(option => option
.setName('product')
.setNameLocalizations({
'es-ES': 'producto',
})
.setDescription('Product that you want to search')
.setDescriptionLocalizations({
'es-ES': 'Producto que quieres buscar',
})
.setRequired(true)
.setMaxLength(200))
.addStringOption(option => option
.setName('country')
.setNameLocalizations({
'es-ES': 'país',
})
.setDescription('Country where search the prices')
.setDescriptionLocalizations({
'es-ES': 'País en donde encontrar los precios',
})
.addChoices(
...Object.entries(countryData).map(([key, value]) => ({
name: value.name,
value: key,
})),
))
.addStringOption(option => option
.setName('platform')
.setNameLocalizations({
'es-ES': 'plataforma',
})
.setDescription('Specify a platform to search')
.setDescriptionLocalizations({
'es-ES': 'Especificar una plataforma para la busqueda',
})
.setMaxLength(200)
.setAutocomplete(true),
),
async autocomplete(interaction) {
const focusedValue = interaction.options.getFocused();
const country = interaction.options.getString('country');
const choices = (country ? countryData[country].pages : pages).map(page => (page.name));
const filtered = choices.filter(choice => choice.startsWith(focusedValue));
await interaction.respond(
filtered.map(choice => ({ name: choice, value: choice })),
);
},
async execute(interaction) {
await interaction.deferReply();
const product = interaction.options.getString('product');
const platform = interaction.options.getString('platform');
if (platform && !pages.some(page => (page.name === platform))) {
await interaction.editReply(responses.missingPlatform[interaction.locale]);
return;
}
const country = interaction.options.getString('country') ||
(platform ?
Object.entries(countryData)
.map(([key, value]) => value.pages.some(page => page.name === platform) ? key : false)
.filter(a => a)[0]
: 'us'
);
const countryPages = countryData[country].pages.filter(countryPage => platform ? countryPage.name === platform : countryPage);
const productPrices = [];
const pagesScraped = [];
try {
for (const countryPage of countryPages) {
const browser = await puppeteer.launch() ;
const page = await browser.newPage();
const response = await page.goto(
countryPage.searchUrl.replace('%S', product), { waitUntil: 'domcontentloaded' },
);
const body = await response.text();
const { window: { document } } = new jsdom.JSDOM(body);
document.querySelectorAll(countryPage.selectors.container)
.forEach((element) => {
if (productPrices.length >= ELEMENTS_LIMIT) {
return;
}
try {
const productRelativePath = element
.querySelector(countryPage.selectors.link)
.getAttribute('href')
.replace(/.*\/\/[^/]*/, '');
const link = hyperlink(
element.querySelector(countryPage.selectors.title).textContent,
countryPage.productUrl.replace('%S', productRelativePath),
);
const priceNumber = element.querySelector(countryPage.selectors.price).textContent.replace('$', '').replace(' ', '');
const price = `${countryData[country].currency} ${bold(priceNumber)}`;
productPrices.push(`${link} - ${price}`);
}
catch (err) {
console.log(`FUCK ${countryPage.name} MAQUETATION`);
console.log(err);
}
});
await browser.close();
pagesScraped.push(countryPage.name);
}
}
catch (err) {
console.error(err);
}
await interaction.editReply(`${responses.extractedFrom[interaction.locale || 'en-US']} ${pagesScraped.join(' ')}\n\n${productPrices.join('\n')}`);
},
};

View File

@@ -1,11 +1,13 @@
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
"dev": "node deploy-commands.js && nodemon app.js"
},
"dependencies": {
"discord.js": "^14.6.0",
"dotenv": "^16.0.3"
"dotenv": "^16.0.3",
"jsdom": "^20.0.2",
"puppeteer": "^19.2.2"
},
"name": "shopping-discord-bot",
"version": "1.0.0",