diff --git a/bot.js b/bot.js index d5d0309..87b6e12 100644 --- a/bot.js +++ b/bot.js @@ -37,7 +37,6 @@ const sendMessage = async (ctx, text, options = {}) => { } }; -// Função para iniciar o bot const startBot = async () => { try { await bot.launch(); diff --git a/commands/crew.js b/commands/crew.js index f98e03b..48e045c 100644 --- a/commands/crew.js +++ b/commands/crew.js @@ -57,7 +57,7 @@ module.exports = (bot) => { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id }); - }, '', Strings.errorRetrievingStats); // No success message + }, '', Strings.errorRetrievingStats); }); bot.command('setbotname', spamwatchMiddleware, async (ctx) => { diff --git a/commands/gsmarena.js b/commands/gsmarena.js new file mode 100644 index 0000000..08e3149 --- /dev/null +++ b/commands/gsmarena.js @@ -0,0 +1,234 @@ +// Ported and improved from Hitalo's PyKorone bot +// Copyright (c) 2024 Hitalo M. (https://github.com/HitaloM) +// Original code license: BSD-3-Clause +// With some help from GPT (I don't really like AI but whatever) +// If this were a kang, I would not be giving credits to him! + +const { isOnSpamWatch } = require('../plugins/lib-spamwatch/spamwatch.js'); +const spamwatchMiddleware = require('../plugins/lib-spamwatch/Middleware.js')(isOnSpamWatch); + +const axios = require('axios'); +const { parse } = require('node-html-parser'); +const { Markup } = require('telegraf'); + +class PhoneSearchResult { + constructor(name, url) { + this.name = name; + this.url = url; + Object.freeze(this); + } +} + +const HEADERS = { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36" +}; + +function getDataFromSpecs(specsData, category, attributes) { + const details = specsData?.specs?.[category] || {}; + + return attributes + .map(attr => details[attr] || null) + .filter(Boolean) + .join("\n"); +} + +function parseSpecs(specsData) { + const categories = { + "status": ["Launch", ["Status"]], + "network": ["Network", ["Technology"]], + "system": ["Platform", ["OS"]], + "weight": ["Body", ["Weight"]], + "jack": ["Sound", ["3.5mm jack"]], + "usb": ["Comms", ["USB"]], + "sensors": ["Features", ["Sensors"]], + "battery": ["Battery", ["Type"]], + "charging": ["Battery", ["Charging"]], + "display_type": ["Display", ["Type"]], + "display_size": ["Display", ["Size"]], + "display_resolution": ["Display", ["Resolution"]], + "platform_chipset": ["Platform", ["Chipset"]], + "platform_cpu": ["Platform", ["CPU"]], + "platform_gpu": ["Platform", ["GPU"]], + "main_camera_single": ["Main Camera", ["Single"]], + "main_camera_dual": ["Main Camera", ["Dual"]], + "main_camera_features": ["Main Camera", ["Features"]], + "main_camera_video": ["Main Camera", ["Video"]], + "selfie_camera_single": ["Selfie Camera", ["Single"]], + "selfie_camera_dual": ["Selfie Camera", ["Dual"]], + "selfie_camera_features": ["Selfie Camera", ["Features"]], + "selfie_camera_video": ["Selfie Camera", ["Video"]], + "memory": ["Memory", ["Internal"]] + }; + + const parsedData = Object.keys(categories).reduce((acc, key) => { + const [cat, attrs] = categories[key]; + acc[key] = getDataFromSpecs(specsData, cat, attrs) || ""; + return acc; + }, {}); + + parsedData["name"] = specsData.name || ""; + parsedData["url"] = specsData.url || ""; + + return parsedData; +} + +function formatPhone(phone) { + const formattedPhone = parseSpecs(phone); + const attributesDict = { + "Status": "status", + "Launch": "launch_date", + "Network": "network", + "Weight": "weight", + "OS": "system", + "Display Resolution": "display_resolution", + "Display Type": "display_type", + "Display Size": "display_size", + "CPU": "platform_cpu", + "GPU": "platform_gpu", + "Chipset": "platform_chipset", + "Memory": "memory", + "Rear Camera (Single)": "main_camera_single", + "Rear Camera (Dual)": "main_camera_single", + "Rear Camera (Features)": "main_camera_single", + "Rear Camera (Video)": "main_camera_single", + "Front Camera (Single)": "selfie_camera_single", + "Front Camera (Dual)": "selfie_camera_single", + "Front Camera (Features)": "selfie_camera_single", + "Front Camera (Video)": "selfie_camera_single", + "3.5mm jack": "jack", + "USB": "usb", + "Sensors": "sensors", + "Battery": "battery", + "Charging": "charging" + }; + + const attributes = Object.entries(attributesDict) + .filter(([_, key]) => formattedPhone[key]) + .map(([label, key]) => `${label}: ${formattedPhone[key]}`) + .join("\n\n"); + + return `${formattedPhone.name}\n\n${attributes}`; +} + +async function fetchHtml(url) { + try { + const response = await axios.get(`https://cors-bypass.amano.workers.dev/${url}`, { headers: HEADERS }); + return response.data; + } catch (error) { + console.error("Error fetching HTML:", error); + throw error; + } +} + +async function searchPhone(phone) { + try { + const searchUrl = `https://m.gsmarena.com/results.php3?sQuickSearch=yes&sName=${encodeURIComponent(phone)}`; + const htmlContent = await fetchHtml(searchUrl); + const root = parse(htmlContent); + const foundPhones = root.querySelectorAll('.general-menu.material-card ul li'); + + return foundPhones.map((phoneTag) => { + const name = phoneTag.querySelector('img')?.getAttribute('title') || ""; + const url = phoneTag.querySelector('a')?.getAttribute('href') || ""; + return new PhoneSearchResult(name, url); + }); + } catch (error) { + console.error("Error searching for phone:", error); + return []; + } +} + +async function checkPhoneDetails(url) { + try { + const htmlContent = await fetchHtml(`https://www.gsmarena.com/${url}`); + const root = parse(htmlContent); + const specsTables = root.querySelectorAll('table[cellspacing="0"]'); + const specsData = extractSpecs(specsTables); + const metaScripts = root.querySelectorAll('script[language="javascript"]'); + const meta = metaScripts.length ? metaScripts[0].text.split("\n") : []; + const name = extractMetaData(meta, "ITEM_NAME"); + const picture = extractMetaData(meta, "ITEM_IMAGE"); + + return { ...specsData, name, picture, url: `https://www.gsmarena.com/${url}` }; + } catch (error) { + console.error("Error fetching phone details:", error); + return {}; + } +} + +function extractSpecs(specsTables) { + return { + specs: specsTables.reduce((acc, table) => { + const feature = table.querySelector('th')?.text.trim() || ""; + table.querySelectorAll('tr').forEach((tr) => { + const header = tr.querySelector('.ttl')?.text.trim() || "info"; + let detail = tr.querySelector('.nfo')?.text.trim() || ""; + detail = detail.replace(/\s*\n\s*/g, " / ").trim(); + if (!acc[feature]) { + acc[feature] = {}; + } + acc[feature][header] = acc[feature][header] + ? `${acc[feature][header]} / ${detail}` + : detail; + }); + return acc; + }, {}) + }; +} + +function extractMetaData(meta, key) { + const line = meta.find((line) => line.includes(key)); + return line ? line.split('"')[1] : ""; +} + +module.exports = (bot) => { + bot.command(['d', 'device'], spamwatchMiddleware, async (ctx) => { + const userId = ctx.from.id; + const userName = ctx.from.first_name; + + const phone = ctx.message.text.split(" ").slice(1).join(" "); + if (!phone) { + return ctx.reply("Please provide the phone name."); + } + + const results = await searchPhone(phone); + if (results.length === 0) { + return ctx.reply("No phones found."); + } + + const buttons = results.map((result) => { + return Markup.button.callback(result.name, `details:${result.url}:${ctx.from.id}`); + }); + + ctx.reply(`${userName}, Select a device:`, Markup.inlineKeyboard(buttons, { columns: 2 }), { + parse_mode: 'HTML', + disable_web_page_preview: true + }); + }); + + bot.action(/details:(.+):(.+)/, async (ctx) => { + const url = ctx.match[1]; + const userId = parseInt(ctx.match[2]); + const userName = ctx.from.first_name; + + const callbackQueryUserId = ctx.update.callback_query.from.id; + + if (userId !== callbackQueryUserId) { + return ctx.answerCbQuery("You are not allowed to interact with this."); + } + + const phoneDetails = await checkPhoneDetails(url); + + if (phoneDetails.name) { + const message = formatPhone(phoneDetails); + ctx.editMessageText(`Here are the details for the phone you requested, ${userName}:\n\n${message}`, { + parse_mode: "HTML" + }); + } else { + ctx.editMessageText(`Unable to fetch phone details, ${userName}.`, { + parse_mode: "HTML" + }); + } + }); +}; diff --git a/commands/info.js b/commands/info.js index d7a80c8..1d57198 100644 --- a/commands/info.js +++ b/commands/info.js @@ -6,7 +6,7 @@ async function getUserInfo(ctx) { const Strings = getStrings(ctx.from.language_code); userInfo = Strings.userInfo - .replace('{userName}', ctx.from.first_name || Strings.unKnown) + .replace('{userName}', `${ctx.from.first_name} ${ctx.from.last_name}` || Strings.unKnown) .replace('{userId}', ctx.from.id || Strings.unKnown) .replace('{userHandle}', ctx.from.username ? `@${ctx.from.username}` : Strings.varNone) .replace('{userPremium}', ctx.from.is_premium ? Strings.varYes : Strings.varNo) diff --git a/package-lock.json b/package-lock.json index e74d035..05c21d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "BSD-3-Clause", "dependencies": { + "axios": "^1.7.7", + "node-html-parser": "^6.1.13", "nodemon": "^3.1.4", "telegraf": "^4.16.3" } @@ -44,6 +46,23 @@ "node": ">= 8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -62,6 +81,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -130,12 +155,52 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/debug": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", @@ -153,6 +218,82 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -174,6 +315,40 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz", + "integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -209,6 +384,15 @@ "node": ">=4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -257,6 +441,27 @@ "node": ">=0.12.0" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -304,6 +509,16 @@ } } }, + "node_modules/node-html-parser": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, "node_modules/nodemon": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", @@ -340,6 +555,18 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/p-timeout": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", @@ -361,6 +588,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/package.json b/package.json index ed66281..ab67866 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "author": "Lucas Gabriel (lucmsilva)", "license": "BSD-3-Clause", "dependencies": { + "axios": "^1.7.7", + "node-html-parser": "^6.1.13", "nodemon": "^3.1.4", "telegraf": "^4.16.3" }