mirror of
https://github.com/FranP-code/shopping-discord-bot.git
synced 2025-10-13 00:22:44 +00:00
refactoring and adding limit command option
This commit is contained in:
2
app.js
2
app.js
@@ -1,7 +1,7 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { Collection, Client, GatewayIntentBits, Events } = require('discord.js');
|
const { Collection, Client, GatewayIntentBits, Events } = require('discord.js');
|
||||||
const { getRandomElementFromArray } = require('./constants');
|
const { getRandomElementFromArray } = require('./utils/constants');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const enviroment = process.env.NODE_ENV;
|
const enviroment = process.env.NODE_ENV;
|
||||||
|
|||||||
@@ -1,71 +1,12 @@
|
|||||||
const { SlashCommandSubcommandBuilder, hyperlink, bold, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
const { SlashCommandSubcommandBuilder, hyperlink, bold, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
||||||
const puppeteer = require('puppeteer');
|
const puppeteer = require('puppeteer');
|
||||||
const jsdom = require('jsdom');
|
const jsdom = require('jsdom');
|
||||||
const { responses } = require('../constants');
|
const { countryData, DISCORD_MESSAGE_LENGTH_LIMIT } = require('../utils/constants');
|
||||||
|
const truncateText = require('../scripts/truncateText');
|
||||||
function truncateText(text, max) {
|
const generateLocalizedResponses = require('../scripts/generateLocalizedResponses');
|
||||||
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 pages = Object.values(countryData).map((country) => country.pages).flat(1);
|
||||||
|
|
||||||
const ELEMENTS_LIMIT = 3;
|
|
||||||
const DISCORD_MESSAGE_LENGTH_LIMIT = 2000;
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
data: new SlashCommandSubcommandBuilder()
|
data: new SlashCommandSubcommandBuilder()
|
||||||
.setName('prices')
|
.setName('prices')
|
||||||
@@ -113,6 +54,18 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
.setMaxLength(200)
|
.setMaxLength(200)
|
||||||
.setAutocomplete(true),
|
.setAutocomplete(true),
|
||||||
|
)
|
||||||
|
.addIntegerOption(option => option
|
||||||
|
.setName('limit')
|
||||||
|
.setNameLocalizations({
|
||||||
|
'es-ES': 'límite',
|
||||||
|
})
|
||||||
|
.setDescription('Define the limit of results of search')
|
||||||
|
.setDescriptionLocalizations({
|
||||||
|
'es-ES': 'Definir el límite de resultados de la busqueda',
|
||||||
|
})
|
||||||
|
.setMaxValue(5)
|
||||||
|
,
|
||||||
),
|
),
|
||||||
async autocomplete(interaction) {
|
async autocomplete(interaction) {
|
||||||
const focusedValue = interaction.options.getFocused();
|
const focusedValue = interaction.options.getFocused();
|
||||||
@@ -128,8 +81,9 @@ module.exports = {
|
|||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
const product = interaction.options.getString('product');
|
const product = interaction.options.getString('product');
|
||||||
const platform = interaction.options.getString('platform');
|
const platform = interaction.options.getString('platform');
|
||||||
|
const ELEMENTS_LIMIT = interaction.options.getInteger('limit') || 3;
|
||||||
if (platform && !pages.some(page => (page.name === platform))) {
|
if (platform && !pages.some(page => (page.name === platform))) {
|
||||||
await interaction.editReply(responses(userLanguage).missingPlatform);
|
await interaction.editReply(generateLocalizedResponses(userLanguage).missingPlatform);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const country = interaction.options.getString('country') ||
|
const country = interaction.options.getString('country') ||
|
||||||
@@ -196,22 +150,25 @@ module.exports = {
|
|||||||
(new ActionRowBuilder()
|
(new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setLabel(responses(userLanguage).platformInBrowser.replace('%P', page.name))
|
.setLabel(generateLocalizedResponses(userLanguage).platformInBrowser.replace('%P', page.name))
|
||||||
.setURL(page.searchUrl)
|
.setURL(page.searchUrl)
|
||||||
.setStyle(ButtonStyle.Link),
|
.setStyle(ButtonStyle.Link),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
const replyTexts = [
|
const replyTexts = [
|
||||||
pagesScraped.length &&
|
pagesScraped.length &&
|
||||||
`${responses(userLanguage).extractedFrom} ${pagesScraped.map(({ name }) => name).join(' ')}`,
|
`${generateLocalizedResponses(userLanguage).extractedFrom} ${pagesScraped.map(({ name }) => name).join(' ')}`,
|
||||||
`${productPrices.join('\n')}`,
|
`${productPrices.join('\n')}`,
|
||||||
pagesWithErrorScrapping.length &&
|
pagesWithErrorScrapping.length &&
|
||||||
`${responses(userLanguage).errorScrapping} ${pagesWithErrorScrapping.map((name) => name).join(' ')}`,
|
`${generateLocalizedResponses(userLanguage).errorScrapping} ${pagesWithErrorScrapping.map((name) => name).join(' ')}`,
|
||||||
].filter(a => a);
|
].filter(a => a);
|
||||||
const response = replyTexts.join('\n\n');
|
const response = replyTexts.join('\n\n');
|
||||||
let content;
|
let content;
|
||||||
if (response.length >= DISCORD_MESSAGE_LENGTH_LIMIT) {
|
if (response.length >= DISCORD_MESSAGE_LENGTH_LIMIT) {
|
||||||
content = responses(userLanguage).discordMessageLengthLimit;
|
content = generateLocalizedResponses(userLanguage).discordMessageLengthLimit;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
content = response;
|
||||||
}
|
}
|
||||||
await interaction.editReply({ content, components: [...buttons] });
|
await interaction.editReply({ content, components: [...buttons] });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { SlashCommandSubcommandBuilder } = require('discord.js');
|
const { SlashCommandSubcommandBuilder } = require('discord.js');
|
||||||
const { urlRegex, responses } = require('../constants');
|
const generateLocalizedResponses = require('../scripts/generateLocalizedResponses');
|
||||||
|
const { urlRegex } = require('../utils/constants');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -26,16 +27,16 @@ module.exports = {
|
|||||||
const userLanguage = interaction.locale || 'en-US';
|
const userLanguage = interaction.locale || 'en-US';
|
||||||
const suggestion = interaction.options.getString('text');
|
const suggestion = interaction.options.getString('text');
|
||||||
if (!suggestion) {
|
if (!suggestion) {
|
||||||
interaction.reply(responses(userLanguage).notSuggest);
|
interaction.reply(generateLocalizedResponses(userLanguage).notSuggest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (urlRegex.test(suggestion)) {
|
if (urlRegex.test(suggestion)) {
|
||||||
interaction.reply(responses(userLanguage).linksNotAllowed);
|
interaction.reply(generateLocalizedResponses(userLanguage).linksNotAllowed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationsChannel = interaction.client.channels.cache.get(process.env.NOTIFICATION_DISCORD_CHANNEL_ID);
|
const notificationsChannel = interaction.client.channels.cache.get(process.env.NOTIFICATION_DISCORD_CHANNEL_ID);
|
||||||
notificationsChannel.send(`SUGGESTION: ${suggestion}`);
|
notificationsChannel.send(`SUGGESTION: ${suggestion}`);
|
||||||
await interaction.reply(responses(userLanguage).suggestionSended);
|
await interaction.reply(generateLocalizedResponses(userLanguage).suggestionSended);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
const { SlashCommandSubcommandBuilder } = require('discord.js');
|
const { SlashCommandSubcommandBuilder } = require('discord.js');
|
||||||
const { urlRegex, responses } = require('../constants');
|
const generateLocalizedResponses = require('../scripts/generateLocalizedResponses');
|
||||||
|
const { urlRegex } = require('../utils/constants');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -37,15 +38,15 @@ module.exports = {
|
|||||||
const platform = interaction.options.getString('platform');
|
const platform = interaction.options.getString('platform');
|
||||||
const suggestion = [country, platform].filter(a => a).join(' - ');
|
const suggestion = [country, platform].filter(a => a).join(' - ');
|
||||||
if (!country && !platform) {
|
if (!country && !platform) {
|
||||||
interaction.reply(responses(userLanguage).notSuggest);
|
interaction.reply(generateLocalizedResponses(userLanguage).notSuggest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (urlRegex.test(suggestion)) {
|
if (urlRegex.test(suggestion)) {
|
||||||
interaction.reply(responses(userLanguage).linksNotAllowed);
|
interaction.reply(generateLocalizedResponses(userLanguage).linksNotAllowed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const notificationsChannel = interaction.client.channels.cache.get(process.env.NOTIFICATION_DISCORD_CHANNEL_ID);
|
const notificationsChannel = interaction.client.channels.cache.get(process.env.NOTIFICATION_DISCORD_CHANNEL_ID);
|
||||||
notificationsChannel.send(`SUGGESTION of country: ${suggestion}`);
|
notificationsChannel.send(`SUGGESTION of country: ${suggestion}`);
|
||||||
await interaction.reply(responses(userLanguage).suggestionSended);
|
await interaction.reply(generateLocalizedResponses(userLanguage).suggestionSended);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
54
constants.js
54
constants.js
@@ -1,54 +0,0 @@
|
|||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
const urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
|
|
||||||
function getRandomElementFromArray(arr) {
|
|
||||||
return arr[Math.floor((Math.random() * arr.length))];
|
|
||||||
}
|
|
||||||
const responsesTexts = {
|
|
||||||
suggestionSended: {
|
|
||||||
'en-US': `Suggestion sended to <@${process.env.MY_DISCORD_USER_ID}>`,
|
|
||||||
'es-ES': `Sugerencia enviada a <@${process.env.MY_DISCORD_USER_ID}>`,
|
|
||||||
},
|
|
||||||
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!!',
|
|
||||||
},
|
|
||||||
platformInBrowser: {
|
|
||||||
'en-US': 'Search in %P on browser',
|
|
||||||
'es-ES': 'Buscar en %P en el buscador',
|
|
||||||
},
|
|
||||||
errorScrapping: {
|
|
||||||
'en-US': 'No products could be found in:',
|
|
||||||
'es-ES': 'No se pudieron encontrar productos en:',
|
|
||||||
},
|
|
||||||
notSuggest: {
|
|
||||||
'en-US': 'Please suggest someting :tired_face:',
|
|
||||||
'es-ES': 'Por favor, sugiere algo :tired_face:',
|
|
||||||
},
|
|
||||||
linksNotAllowed: {
|
|
||||||
'en-US': 'Links aren\'t allowed :/',
|
|
||||||
'es-ES': 'No esta permitido enviar links :/',
|
|
||||||
},
|
|
||||||
discordMessageLengthLimit: {
|
|
||||||
'en-US': 'Sorry, the links of this product exceeds the limit of characters by discord message.\n\nPlease try again with a lower quantity of results.',
|
|
||||||
'es-ES': 'Lo sentimos, los enlaces de este producto exceden el límite de caracteres por mensaje de discord.\n\nPor favor, intente nuevamente con una menor cantidad de resultados.',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
function responses(userLanguage) {
|
|
||||||
const responsesEntries = Object.entries(responsesTexts);
|
|
||||||
const localizatedResponses = {};
|
|
||||||
responsesEntries.forEach(([responseName, responseTexts]) => {
|
|
||||||
localizatedResponses[responseName] = Object.entries(responseTexts).find(responseText => responseText[0] === userLanguage)[1];
|
|
||||||
});
|
|
||||||
return localizatedResponses;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
urlRegex,
|
|
||||||
getRandomElementFromArray,
|
|
||||||
responses,
|
|
||||||
};
|
|
||||||
12
scripts/generateLocalizedResponses.js
Normal file
12
scripts/generateLocalizedResponses.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const { responsesTexts } = require('../utils/constants');
|
||||||
|
|
||||||
|
function generateLocalizedResponses(userLanguage) {
|
||||||
|
const responsesEntries = Object.entries(responsesTexts);
|
||||||
|
const localizatedResponses = {};
|
||||||
|
responsesEntries.forEach(([responseName, responseTexts]) => {
|
||||||
|
localizatedResponses[responseName] = Object.entries(responseTexts).find(responseText => responseText[0] === userLanguage)[1];
|
||||||
|
});
|
||||||
|
return localizatedResponses;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = generateLocalizedResponses;
|
||||||
5
scripts/getRandomElementFromArray.js
Normal file
5
scripts/getRandomElementFromArray.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
function getRandomElementFromArray(arr) {
|
||||||
|
return arr[Math.floor((Math.random() * arr.length))];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getRandomElementFromArray;
|
||||||
5
scripts/truncateText.js
Normal file
5
scripts/truncateText.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
function truncateText(text, max) {
|
||||||
|
return text.substr(0, max - 1).trim() + (text.length > max ? '...' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = truncateText;
|
||||||
102
utils/constants.js
Normal file
102
utils/constants.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
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 DISCORD_MESSAGE_LENGTH_LIMIT = 2000;
|
||||||
|
|
||||||
|
const responsesTexts = {
|
||||||
|
suggestionSended: {
|
||||||
|
'en-US': `Suggestion sended to <@${process.env.MY_DISCORD_USER_ID}>`,
|
||||||
|
'es-ES': `Sugerencia enviada a <@${process.env.MY_DISCORD_USER_ID}>`,
|
||||||
|
},
|
||||||
|
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!!',
|
||||||
|
},
|
||||||
|
platformInBrowser: {
|
||||||
|
'en-US': 'Search in %P on browser',
|
||||||
|
'es-ES': 'Buscar en %P en el buscador',
|
||||||
|
},
|
||||||
|
errorScrapping: {
|
||||||
|
'en-US': 'No products could be found in:',
|
||||||
|
'es-ES': 'No se pudieron encontrar productos en:',
|
||||||
|
},
|
||||||
|
notSuggest: {
|
||||||
|
'en-US': 'Please suggest someting :tired_face:',
|
||||||
|
'es-ES': 'Por favor, sugiere algo :tired_face:',
|
||||||
|
},
|
||||||
|
linksNotAllowed: {
|
||||||
|
'en-US': 'Links aren\'t allowed :/',
|
||||||
|
'es-ES': 'No esta permitido enviar links :/',
|
||||||
|
},
|
||||||
|
discordMessageLengthLimit: {
|
||||||
|
'en-US': 'Sorry, the links of this product exceeds the limit of characters by discord message.\n\nPlease try again with a lower quantity of results.',
|
||||||
|
'es-ES': 'Lo sentimos, los enlaces de este producto exceden el límite de caracteres por mensaje de discord.\n\nPor favor, intente nuevamente con una menor cantidad de resultados.',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
countryData,
|
||||||
|
responsesTexts,
|
||||||
|
DISCORD_MESSAGE_LENGTH_LIMIT,
|
||||||
|
urlRegex,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user