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:
118
.eslintrc.json
118
.eslintrc.json
@@ -1,55 +1,65 @@
|
||||
// {
|
||||
// "extends": "eslint:recommended",
|
||||
// "env": {
|
||||
// "node": true,
|
||||
// "es6": true
|
||||
// },
|
||||
// "parserOptions": {
|
||||
// "ecmaVersion": 2021
|
||||
// },
|
||||
// "rules": {
|
||||
// "arrow-spacing": ["warn", { "before": true, "after": true }],
|
||||
// "brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
||||
// "comma-dangle": ["error", "always-multiline"],
|
||||
// "comma-spacing": "error",
|
||||
// "comma-style": "error",
|
||||
// "curly": ["error", "multi-line", "consistent"],
|
||||
// "dot-location": ["error", "property"],
|
||||
// "handle-callback-err": "off",
|
||||
// "indent": ["error", "tab"],
|
||||
// "keyword-spacing": "error",
|
||||
// "max-nested-callbacks": ["error", { "max": 4 }],
|
||||
// "max-statements-per-line": ["error", { "max": 2 }],
|
||||
// "no-console": "off",
|
||||
// "no-empty-function": "error",
|
||||
// "no-floating-decimal": "error",
|
||||
// "no-inline-comments": "error",
|
||||
// "no-lonely-if": "error",
|
||||
// "no-multi-spaces": "error",
|
||||
// "no-multiple-empty-lines": [
|
||||
// "error",
|
||||
// { "max": 2, "maxEOF": 1, "maxBOF": 0 }
|
||||
// ],
|
||||
// "no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
|
||||
// "no-trailing-spaces": ["error"],
|
||||
// "no-var": "error",
|
||||
// "object-curly-spacing": ["error", "always"],
|
||||
// "prefer-const": "error",
|
||||
// "quotes": ["error", "single"],
|
||||
// "semi": ["error", "always"],
|
||||
// "space-before-blocks": "error",
|
||||
// "space-before-function-paren": [
|
||||
// "error",
|
||||
// {
|
||||
// "anonymous": "never",
|
||||
// "named": "never",
|
||||
// "asyncArrow": "always"
|
||||
// }
|
||||
// ],
|
||||
// "space-in-parens": "error",
|
||||
// "space-infix-ops": "error",
|
||||
// "space-unary-ops": "error",
|
||||
// "spaced-comment": "error",
|
||||
// "yoda": "error"
|
||||
// }
|
||||
// }
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
||||
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
"curly": ["error", "multi-line", "consistent"],
|
||||
"dot-location": ["error", "property"],
|
||||
"handle-callback-err": "off",
|
||||
"indent": ["error", "tab"],
|
||||
"keyword-spacing": "error",
|
||||
"max-nested-callbacks": ["error", { "max": 4 }],
|
||||
"max-statements-per-line": ["error", { "max": 2 }],
|
||||
"no-console": "off",
|
||||
"no-empty-function": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multiple-empty-lines": [
|
||||
"error",
|
||||
{ "max": 2, "maxEOF": 1, "maxBOF": 0 }
|
||||
],
|
||||
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"prefer-const": "error",
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": [
|
||||
"error",
|
||||
{
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}
|
||||
],
|
||||
"space-in-parens": "error",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
"yoda": "error"
|
||||
}
|
||||
}
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"semi": ["error", "never"],
|
||||
"quotes": [2, "single"],
|
||||
"no-unused-vars": "error",
|
||||
"@typescript-eslint/no-unused-vars": "error"
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ RUN apt-get update \
|
||||
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN npm install -g typescript
|
||||
RUN yarn add puppeteer \
|
||||
&& groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
|
||||
&& mkdir -p /home/pptruser/Downloads \
|
||||
|
||||
@@ -4,8 +4,12 @@ require('dotenv').config();
|
||||
|
||||
const enviroment = process.env.NODE_ENV;
|
||||
|
||||
const commandFilesDir = enviroment === "production" ? './dist/commands' : './src/commands'
|
||||
const commandFilesExtension = enviroment === "production" ? '.js' : '.ts'
|
||||
|
||||
const commands = [];
|
||||
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
|
||||
// const commandFiles = fs.readdirSync(commandFilesDir).filter(file => file.endsWith(commandFilesExtension));
|
||||
const commandFiles = fs.readdirSync('./dist/commands').filter(file => file.endsWith('.js'));
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const command = require(`./commands/${file}`);
|
||||
|
||||
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] });
|
||||
});
|
||||
},
|
||||
};
|
||||
5
nodemon.json
Normal file
5
nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts, json",
|
||||
"exec": "ts-node ./src/app.ts"
|
||||
}
|
||||
10
package.json
10
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"scripts": {
|
||||
"start": "node deploy-commands.js && node app.js",
|
||||
"dev": "node deploy-commands.js && nodemon -L app.js"
|
||||
"start": "node deploy-commands.js && npx tsc && node dist/app.js",
|
||||
"dev": "npx tsc && node deploy-commands.js && nodemon -L src/app.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14.6.0",
|
||||
@@ -16,7 +16,11 @@
|
||||
"author": "Francisco Pessano <franpessano1@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"eslint": "^8.27.0",
|
||||
"nodemon": "^2.0.20"
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Collection, Client, GatewayIntentBits, Events } = require('discord.js');
|
||||
const { getRandomElementFromArray } = require('./constants');
|
||||
require('dotenv').config();
|
||||
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 enviroment = process.env.NODE_ENV
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] })
|
||||
|
||||
client.on('ready', () => {
|
||||
console.log('working');
|
||||
});
|
||||
console.log('working')
|
||||
})
|
||||
|
||||
client.login(enviroment === 'production' ? process.env.PROD_BOT_TOKEN : process.env.DEV_BOT_TOKEN);
|
||||
client.login(enviroment === 'production' ? process.env.PROD_BOT_TOKEN : process.env.DEV_BOT_TOKEN)
|
||||
|
||||
client.commands = new Collection();
|
||||
client.commands = new Collection()
|
||||
|
||||
const commandsPath = path.join(__dirname, 'commands');
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
const commandsPath = path.join(__dirname, 'commands')
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file: any) => file.endsWith('.js'))
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
const filePath = path.join(commandsPath, file)
|
||||
const command = require(filePath)
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
client.commands.set(command.data.name, command)
|
||||
}
|
||||
else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`)
|
||||
}
|
||||
}
|
||||
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
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;
|
||||
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);
|
||||
const command = interaction.client.commands.get(interaction.commandName)
|
||||
if (interaction.isChatInputCommand()) {
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
await command.execute(interaction)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
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;
|
||||
console.error(`No command matching ${interaction.commandName} was found.`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await command.autocomplete(interaction);
|
||||
await command.autocomplete(interaction)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
console.error(`No command matching ${interaction.commandName} was found.`)
|
||||
return
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,72 +1,16 @@
|
||||
const { SlashCommandSubcommandBuilder, hyperlink, bold, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
||||
const puppeteer = require('puppeteer');
|
||||
const jsdom = require('jsdom');
|
||||
const { responses } = require('../constants');
|
||||
import { AutocompleteInteraction, CommandOptionChoiceResolvableType } from 'discord.js'
|
||||
import { countryData } from '../utils/constants'
|
||||
|
||||
function truncateText(text, max) {
|
||||
return text.substr(0, max - 1).trim() + (text.length > max ? '...' : '');
|
||||
}
|
||||
const { SlashCommandSubcommandBuilder, hyperlink, bold, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js')
|
||||
const puppeteer = require('puppeteer')
|
||||
const jsdom = require('jsdom')
|
||||
const { responses } = require('../../constants')
|
||||
|
||||
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 ELEMENTS_LIMIT = 3;
|
||||
const DISCORD_MESSAGE_LENGTH_LIMIT = 2000;
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
data: new SlashCommandSubcommandBuilder()
|
||||
.setName('prices')
|
||||
.setNameLocalizations({
|
||||
@@ -76,7 +20,7 @@ module.exports = {
|
||||
.setDescriptionLocalizations({
|
||||
'es-ES': 'Consigue los precios de algún producto',
|
||||
})
|
||||
.addStringOption(option => option
|
||||
.addStringOption((option: any) => option
|
||||
.setName('product')
|
||||
.setNameLocalizations({
|
||||
'es-ES': 'producto',
|
||||
@@ -87,7 +31,7 @@ module.exports = {
|
||||
})
|
||||
.setRequired(true)
|
||||
.setMaxLength(200))
|
||||
.addStringOption(option => option
|
||||
.addStringOption((option: any) => option
|
||||
.setName('country')
|
||||
.setNameLocalizations({
|
||||
'es-ES': 'país',
|
||||
@@ -102,7 +46,7 @@ module.exports = {
|
||||
value: key,
|
||||
})),
|
||||
))
|
||||
.addStringOption(option => option
|
||||
.addStringOption((option: any) => option
|
||||
.setName('platform')
|
||||
.setNameLocalizations({
|
||||
'es-ES': 'plataforma',
|
||||
@@ -114,23 +58,23 @@ module.exports = {
|
||||
.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));
|
||||
async autocomplete(interaction: AutocompleteInteraction) {
|
||||
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: string) => choice.startsWith(focusedValue))
|
||||
await interaction.respond(
|
||||
filtered.map(choice => ({ name: choice, value: choice })),
|
||||
);
|
||||
filtered.map((choice: CommandOptionChoiceResolvableType) => ({ name: choice, value: choice })),
|
||||
)
|
||||
},
|
||||
async execute(interaction) {
|
||||
const userLanguage = interaction.locale || 'en-US';
|
||||
await interaction.deferReply();
|
||||
const product = interaction.options.getString('product');
|
||||
const platform = interaction.options.getString('platform');
|
||||
const userLanguage = interaction.locale || 'en-US'
|
||||
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(userLanguage).missingPlatform);
|
||||
return;
|
||||
await interaction.editReply(responses(userLanguage).missingPlatform)
|
||||
return
|
||||
}
|
||||
const country = interaction.options.getString('country') ||
|
||||
(platform ?
|
||||
@@ -138,58 +82,58 @@ module.exports = {
|
||||
.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 = [];
|
||||
)
|
||||
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 = await puppeteer.launch({
|
||||
args: ['--no-sandbox'],
|
||||
}) ;
|
||||
const page = await browser.newPage();
|
||||
const searchUrl = countryPage.searchUrl.replace('%S', product);
|
||||
const response = await page.goto(searchUrl, { waitUntil: 'domcontentloaded' });
|
||||
const body = await response.text();
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
const searchUrl = countryPage.searchUrl.replace('%S', product)
|
||||
const response = await page.goto(searchUrl, { waitUntil: 'domcontentloaded' })
|
||||
const body = await response.text()
|
||||
|
||||
const { window: { document } } = new jsdom.JSDOM(body);
|
||||
const { window: { document } } = new jsdom.JSDOM(body)
|
||||
|
||||
const products = document.querySelectorAll(countryPage.selectors.container);
|
||||
const products = document.querySelectorAll(countryPage.selectors.container)
|
||||
if (!products.length) {
|
||||
throw Error();
|
||||
throw Error()
|
||||
}
|
||||
products
|
||||
.forEach((element) => {
|
||||
if (productPrices.length >= ELEMENTS_LIMIT) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
try {
|
||||
const productRelativePath = element
|
||||
.querySelector(countryPage.selectors.link)
|
||||
.getAttribute('href')
|
||||
.replace(/.*\/\/[^/]*/, '');
|
||||
const productName = element.querySelector(countryPage.selectors.title).textContent;
|
||||
.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}`);
|
||||
)
|
||||
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);
|
||||
console.log(`FUCK ${countryPage.name} MAQUETATION`)
|
||||
console.error(err)
|
||||
}
|
||||
});
|
||||
await browser.close();
|
||||
pagesScraped.push({ name: countryPage.name, searchUrl: encodeURI(searchUrl) });
|
||||
})
|
||||
await browser.close()
|
||||
pagesScraped.push({ name: countryPage.name, searchUrl: encodeURI(searchUrl) })
|
||||
}
|
||||
catch (err) {
|
||||
pagesWithErrorScrapping.push(countryPage.name);
|
||||
console.log(`FUCK ${countryPage.name}`);
|
||||
console.error(err);
|
||||
pagesWithErrorScrapping.push(countryPage.name)
|
||||
console.log(`FUCK ${countryPage.name}`)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
const buttons = pagesScraped.map(page =>
|
||||
@@ -200,19 +144,19 @@ module.exports = {
|
||||
.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;
|
||||
].filter(a => a)
|
||||
const response = replyTexts.join('\n\n')
|
||||
let content
|
||||
if (response.length >= DISCORD_MESSAGE_LENGTH_LIMIT) {
|
||||
content = responses(userLanguage).discordMessageLengthLimit;
|
||||
content = responses(userLanguage).discordMessageLengthLimit
|
||||
}
|
||||
await interaction.editReply({ content, components: [...buttons] });
|
||||
await interaction.editReply({ content, components: [...buttons] })
|
||||
},
|
||||
};
|
||||
}
|
||||
3
src/scripts/truncateText.ts
Normal file
3
src/scripts/truncateText.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function truncateText(text: string, max: number) {
|
||||
return text.substr(0, max - 1).trim() + (text.length > max ? '...' : '')
|
||||
}
|
||||
55
src/utils/constants.ts
Normal file
55
src/utils/constants.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export 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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const DISCORD_MESSAGE_LENGTH_LIMIT = 2000
|
||||
103
tsconfig.json
Normal file
103
tsconfig.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
1958
yarn-error.log
Normal file
1958
yarn-error.log
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user