mirror of
https://github.com/FranP-code/shopping-discord-bot.git
synced 2025-10-13 00:22:44 +00:00
failed attempt
This commit is contained in:
66
dist/app.js
vendored
Normal file
66
dist/app.js
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Collection, Client, GatewayIntentBits, Events } = require('discord.js');
|
||||
const { getRandomElementFromArray } = require('./constants');
|
||||
require('dotenv').config();
|
||||
const enviroment = process.env.NODE_ENV;
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
client.on('ready', () => {
|
||||
console.log('working');
|
||||
});
|
||||
client.login(enviroment === 'production' ? process.env.PROD_BOT_TOKEN : process.env.DEV_BOT_TOKEN);
|
||||
client.commands = new Collection();
|
||||
const commandsPath = path.join(__dirname, 'commands');
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
}
|
||||
else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
client.on(Events.InteractionCreate, (interaction) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
if (enviroment !== 'production' && interaction.user.id !== process.env.MY_DISCORD_USER_ID) {
|
||||
interaction.reply(`The dev instance of this bot is only for ${getRandomElementFromArray([`<@${process.env.MY_DISCORD_USER_ID}>`, 'the king'])}`);
|
||||
return;
|
||||
}
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
if (interaction.isChatInputCommand()) {
|
||||
try {
|
||||
yield command.execute(interaction);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
yield 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 {
|
||||
yield command.autocomplete(interaction);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
}));
|
||||
213
dist/commands/price.js
vendored
Normal file
213
dist/commands/price.js
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const { SlashCommandSubcommandBuilder, hyperlink, bold, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
||||
const puppeteer = require('puppeteer');
|
||||
const jsdom = require('jsdom');
|
||||
const { responses } = require('../../constants');
|
||||
function truncateText(text, max) {
|
||||
return text.substr(0, max - 1).trim() + (text.length > max ? '...' : '');
|
||||
}
|
||||
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 ELEMENTS_LIMIT = 3;
|
||||
const DISCORD_MESSAGE_LENGTH_LIMIT = 2000;
|
||||
exports.default = {
|
||||
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)),
|
||||
autocomplete(interaction) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
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));
|
||||
yield interaction.respond(filtered.map(choice => ({ name: choice, value: choice })));
|
||||
});
|
||||
},
|
||||
execute(interaction) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const userLanguage = interaction.locale || 'en-US';
|
||||
yield interaction.deferReply();
|
||||
const product = interaction.options.getString('product');
|
||||
const platform = interaction.options.getString('platform');
|
||||
if (platform && !pages.some(page => (page.name === platform))) {
|
||||
yield interaction.editReply(responses(userLanguage).missingPlatform);
|
||||
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 = [];
|
||||
const pagesWithErrorScrapping = [];
|
||||
for (const countryPage of countryPages) {
|
||||
try {
|
||||
const browser = yield puppeteer.launch({
|
||||
args: ['--no-sandbox'],
|
||||
});
|
||||
const page = yield browser.newPage();
|
||||
const searchUrl = countryPage.searchUrl.replace('%S', product);
|
||||
const response = yield page.goto(searchUrl, { waitUntil: 'domcontentloaded' });
|
||||
const body = yield response.text();
|
||||
const { window: { document } } = new jsdom.JSDOM(body);
|
||||
const products = document.querySelectorAll(countryPage.selectors.container);
|
||||
if (!products.length) {
|
||||
throw Error();
|
||||
}
|
||||
products
|
||||
.forEach((element) => {
|
||||
if (productPrices.length >= ELEMENTS_LIMIT) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const productRelativePath = element
|
||||
.querySelector(countryPage.selectors.link)
|
||||
.getAttribute('href')
|
||||
.replace(/.*\/\/[^/]*/, '');
|
||||
const productName = element.querySelector(countryPage.selectors.title).textContent;
|
||||
const link = hyperlink(truncateText(productName, 100), countryPage.productUrl.replace('%S', encodeURI(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.error(err);
|
||||
}
|
||||
});
|
||||
yield browser.close();
|
||||
pagesScraped.push({ name: countryPage.name, searchUrl: encodeURI(searchUrl) });
|
||||
}
|
||||
catch (err) {
|
||||
pagesWithErrorScrapping.push(countryPage.name);
|
||||
console.log(`FUCK ${countryPage.name}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
const buttons = pagesScraped.map(page => (new ActionRowBuilder()
|
||||
.addComponents(new ButtonBuilder()
|
||||
.setLabel(responses(userLanguage).platformInBrowser.replace('%P', page.name))
|
||||
.setURL(page.searchUrl)
|
||||
.setStyle(ButtonStyle.Link))));
|
||||
const replyTexts = [
|
||||
pagesScraped.length &&
|
||||
`${responses(userLanguage).extractedFrom} ${pagesScraped.map(({ name }) => name).join(' ')}`,
|
||||
`${productPrices.join('\n')}`,
|
||||
pagesWithErrorScrapping.length &&
|
||||
`${responses(userLanguage).errorScrapping} ${pagesWithErrorScrapping.map((name) => name).join(' ')}`,
|
||||
].filter(a => a);
|
||||
const response = replyTexts.join('\n\n');
|
||||
let content;
|
||||
if (response.length >= DISCORD_MESSAGE_LENGTH_LIMIT) {
|
||||
content = responses(userLanguage).discordMessageLengthLimit;
|
||||
}
|
||||
yield interaction.editReply({ content, components: [...buttons] });
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user