diff --git a/README.md b/README.md
index d6768c1..5383bc0 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,8 @@ A Telegram bot that can track price of Amazon & flipkart products (more coming s
## Deploy
+**NOTE** - We're now using https proxy for sending requests. So add your proxy url in env as `PROXY`
+
[](https://heroku.com/deploy?template=https://github.com/siddiquiaffan/price-tracker "Deploy with Heroku")
[](https://railway.app/new/template?template=https://github.com/siddiquiaffan/price-tracker&envs=ADMINS,BOT_TOKEN,DB_URL,WORKER_URL,API_KEY,LIMIT&ADMINSDesc=Telegarm+ids+of+admins+separated+by+space&BOT_TOKENDesc=Get+Your+Bot+Token+From+@BotFather.&DB_URLDesc=Create+A+Database+In+Mongodb+And+Get+URL.&WORKER_URLDesc=Paste+worker.js+code+in+Cloudfare+Worker+and+get+url.&API_KEYDesc=Any+secret+key+to+access+API&LIMITDesc=Limit+of+products+to+track+per+user. "Deploy on Railway")
@@ -47,6 +49,14 @@ Deploy locally:
---
+### POST-DEPLOYMENT
+**Setup bot:** Get your deployment url and navigate to {YOUR_DEPLOYMENT_URL}/setup
+```
+Example: https://price-tracker.herokuapp.com/setup
+```
+=> Replace `https://price-tracker.herokuapp.com/` with your deployment url.
+
+---
## Contributing
- Fork this repo 
diff --git a/bot.js b/bot.js
index 99c1a0e..8ff7145 100644
--- a/bot.js
+++ b/bot.js
@@ -23,27 +23,31 @@ const reply_markup = {
const processUrl = async (msg, ctx) => {
try {
- const url = await unshort(msg);
- const productUrl = "http" + url.split("http")[1].split(" ")[0].replace("dl.", "www.")
- if (isUrl(productUrl)) {
- const merchant = productUrl.replace("www.", "").split("//")[1].split(".")[0];
- if (merchant.match(/amazon|flipkart|snapdeal/gi)) {
- const noOfProducts = ( await manageProducts({ userId: ctx.from.id }, "read") )?.result?.length;
+ const url = await unshort(msg);
+ const productUrl = "http" + url.split("http")[1].split(" ")[0].replace("dl.", "www.")
+
+ if (isUrl(productUrl)) {
+ const merchant = productUrl.replace("www.", "").split("//")[1].split(".")[0];
+
+ if (merchant.match(/amazon|flipkart|snapdeal/gi)) {
+ const noOfProducts = (await manageProducts({ userId: ctx.from.id }, "read"))?.result?.length;
+
if (noOfProducts < LIMIT) {
const sentMsg = await ctx.reply(`Tracking ${merchant} product...`, { reply_to_message_id: ctx.message.message_id });
const details = await getProductDetails(productUrl, merchant);
-
+
if (details.ok) {
try {
const tracking_id = getRandomId();
await manageProducts(
{ tracking_id, userId: ctx.from.id, merchant, title: details.title, link: details.link, initPrice: details.price, price: details.price, },
"update"
- );
- await ctx.api.editMessageText(
- ctx.chat.id, sentMsg.message_id,
- ` \nTracking ${details.title}\n\nCurrent Price: ${details.price}\nLink: ${merchant}\n\nTo stop tracking send /stop_${tracking_id}`,
- { parse_mode: "HTML", reply_markup }
+ );
+
+ await ctx.api.editMessageText(
+ ctx.chat.id, sentMsg.message_id,
+ ` \nTracking ${details.title}\n\nCurrent Price: ${details.price}\nLink: ${merchant}\n\nTo stop tracking send /stop_${tracking_id}`,
+ { parse_mode: "HTML", reply_markup }
);
} catch (e) { }
} else {
@@ -51,21 +55,23 @@ const processUrl = async (msg, ctx) => {
ctx.chat.id, sentMsg.message_id,
`Sorry, I couldn't track this product. Make sure you've sent correct product link.`,
{ parse_mode: "Markdown", reply_markup }
- ).catch(e => {});
- }
- } else {
- ctx.reply( "I'm sorry, but you can't add more products as you've already reached the maximum limit.\n\nPlease delete atleast one product. And try again.\n\nTo get list send /list",
- { reply_to_message_id: ctx.message.message_id } ).catch(e => {});
+ ).catch(e => { });
}
+
} else {
- ctx.reply( `Sorry, I can't track this product. Cuz the link you sent is not a amazon or flipkart product link.` ).catch(e => {});
+ ctx.reply("I'm sorry, but you can't add more products as you've already reached the maximum limit.\n\nPlease delete atleast one product. And try again.\n\nTo get list send /list",
+ { reply_to_message_id: ctx.message.message_id }).catch(e => { });
}
+
} else {
- ctx.reply( `Sorry ${ctx.message.chat.first_name}, I can't track this product. Make sure you've sent correct product link.` ).catch(e => {});
+ ctx.reply(`Sorry, I can't track this product. Cuz the link you sent is not a amazon or flipkart product link.`).catch(e => { });
}
- } catch(e){
- console.error(e)
+ } else {
+ ctx.reply(`Sorry ${ctx.message.chat.first_name}, I can't track this product. Make sure you've sent correct product link.`).catch(e => { });
}
+ } catch (e) {
+ console.error(e)
+ }
}
@@ -74,9 +80,13 @@ bot.command("start", (ctx) => {
try {
ctx.reply(
`Hello ${ctx.message.chat.first_name}, I can track price for Amazon & Flipkart products (Soon more).\n\nCheck /help to get started.\n`,
- { reply_to_message_id: ctx.message.message_id, reply_markup, }
- ).catch(() => {})
- manageUsers( { id: ctx.message.from.id, name: ctx.message.from.first_name }, "update" );
+ {
+ reply_to_message_id: ctx.message.message_id,
+ reply_markup,
+ }
+ ).catch(() => { })
+
+ manageUsers({ id: ctx.message.from.id, name: ctx.message.from.first_name }, "update");
} catch (e) {
console.log("Error", e);
}
@@ -91,7 +101,7 @@ bot.command("help", (ctx) => {
reply_to_message_id: ctx.message.message_id,
reply_markup,
}
- ).catch(() => {})
+ ).catch(() => { })
} catch (e) { }
});
@@ -100,16 +110,18 @@ bot.command("track", async (ctx) => {
const message = ctx.message.text.replace("/track ", "");
processUrl(message, ctx);
});
-
+
bot.command("list", async (ctx) => {
try {
const products = await manageProducts({ 'users.userId': ctx.from.id }, "read");
+
const list = products.result
.map(
(product) =>
`${product.title}\nLast Price: ${product.price}\nLink: ${product.merchant}\nTo stop send /stop_${product.users.filter(u => u.userId == ctx.from.id)[0].tracking_id}`
)
.join("\n\n");
+
ctx.reply(`Here is your tracking list:\n\n${list}`, {
reply_to_message_id: ctx.message.message_id,
parse_mode: "HTML",
@@ -124,10 +136,12 @@ bot.command("list", async (ctx) => {
bot.hears(/^\/stop_([a-z0-9])/, async (ctx) => {
const tracking_id = ctx.message.text.replace("/stop_", "");
+
const result = await manageProducts(
{ tracking_id, userId: ctx.from.id },
"delete"
);
+
ctx.reply(
result.ok
? `Stopped tracking product with tracking id ${tracking_id}`
@@ -139,7 +153,9 @@ bot.command("broadcast", async (ctx) => {
if (ADMINS.includes(ctx.from.id)) {
let msg = ctx.message.text.replace("/broadcast ", "");
const inline_keyboard = ctx.message.text.split("inline_keyboard:")[1];
+
msg = msg.replace("inline_keyboard:", "").replace(inline_keyboard, "");
+
const users = await manageUsers({}, "read");
await Promise.all(
users.result.map(async (user) => {
@@ -168,6 +184,7 @@ bot.command("broadcast", async (ctx) => {
bot.command("users", async (ctx) => {
if (ADMINS.includes(ctx.from.id)) {
let users = await manageUsers({}, "read");
+
users =
"List Of Users: \n\n" +
users.result
@@ -176,27 +193,30 @@ bot.command("users", async (ctx) => {
`${user.id} - ${user.name}`
)
.join("\n");
+
ctx.reply(users, { parse_mode: "HTML" });
}
});
bot.command("stats", async (ctx) => {
- try{
- const[users, products] = await Promise.all([manageUsers, manageProducts].map(
+ try {
+ const [users, products] = await Promise.all([manageUsers, manageProducts].map(
async (func) => await func({}, "read")
+
));
let prodCount = 0;
products.result.map(prod => prodCount += prod.users.length);
+
ctx.reply(
`Total Users: ${users.result.length}\nTotal Products: ${prodCount}`
);
- }catch(e){
+ } catch (e) {
console.log(e)
}
});
bot.on('::url', async ctx => {
- if(ctx.chat.type === "private"){
+ if (ctx.chat.type === "private") {
const message = ctx.message.text;
processUrl(message, ctx);
}
@@ -207,10 +227,12 @@ bot.callbackQuery("stopTracking", async (ctx) => {
ctx.update?.callback_query?.message?.reply_markup?.inline_keyboard[1][0]?.text?.split(
" - "
)[1];
+
const result = await manageProducts(
{ tracking_id, userId: ctx.from.id },
"delete"
);
+
ctx.api.editMessageText(
ctx.update?.callback_query?.message?.chat?.id,
ctx.update?.callback_query?.message?.message_id,
@@ -224,16 +246,26 @@ const track = async () => {
try {
const products = await manageProducts({}, "read");
// Process 10 products at a time
- for (let i = 0; i < products.result.length; i = i + 10 ) {
- const temp = products.result.slice(i, i + 10)
-
+ for (let i = 0; i < products.result.length; i = i + 10) {
+ const temp = products.result.slice(i, i + 10)
+
await Promise.all(
temp.map(async (product) => {
const details = await getProductDetails(product.link, product.merchant);
if (details.ok && !isNaN(details.price) && details.price !== product.price) {
try {
- await manageProducts({ tracking_id: product.tracking_id, userId: product.userId, merchant: product.merchant, title: details.title, link: product.link, initPrice: product.price, price: details.price, users: product.users}, "update");
+ await manageProducts({
+ tracking_id: product.tracking_id,
+ userId: product.userId,
+ merchant: product.merchant,
+ title: details.title,
+ link: product.link,
+ initPrice: product.price,
+ price: details.price,
+ users: product.users
+ }, "update");
+
await Promise.all(product.users.map(async user => {
bot.api.sendMessage(
user.userId,
@@ -246,28 +278,28 @@ const track = async () => {
parse_mode: "HTML",
reply_markup: {
inline_keyboard: details?.link ? [
- [{ text: "Buy Now", url: productCommonUrl(details.link, true) }],
- [{ text: "Stop Tracking - " + user.tracking_id, callback_data: `stopTracking`, }]]
- : []
+ [{ text: "Buy Now", url: productCommonUrl(details.link, true) }],
+ [{ text: "Stop Tracking - " + user.tracking_id, callback_data: `stopTracking`, }]]
+ : []
}
}).catch(e => console.log(`🚀 ~ file: bot.js:255 ~ temp.map ~ e:`, e))
}))
// wait for 1 sec
await new Promise(resolve => setTimeout(resolve, 1000))
- } catch (e) {
+ } catch (e) {
console.log(`🚀 ~ file: bot.js:260 ~ temp.map ~ e:`, e)
- bot.start()
+ // bot.start()
// wait for 5 sec
await new Promise(resolve => setTimeout(resolve, 5000))
- }
+ }
}
})
);
}
} catch (e) {
console.log(`🚀 ~ file: bot.js:270 ~ track ~ e:`, e)
- }
+ }
};
bot.command("update", async (ctx) => {
@@ -279,11 +311,14 @@ bot.command("update", async (ctx) => {
bot.catch((err) => {
console.error("err");
+
const ctx = err.ctx;
console.error(`Error while handling update ${ctx.update.update_id}:`);
+
const e = err.error;
console.error("Error: ", e.description);
- bot.start();
+
+ // bot.start();
});
setInterval(track, 3600000); //Track every hr.
diff --git a/config.js b/config.js
index bead029..71503ef 100644
--- a/config.js
+++ b/config.js
@@ -11,11 +11,25 @@ if(!process.env.DB_URL) {
process.exit(1);
}
-const ADMINS = process.env.ADMINS || ''
-const BOT_TOKEN = process.env.BOT_TOKEN || ''
-const DB_URL = process.env.DB_URL || ''
-const WORKER_URL = process.env.WORKER_URL || ''
-const API_KEY = process.env.API_KEY || '' // Generate any API Key and pass it when accessing the API.
+/** List of amdins (tg IDs) separated by space */
+const ADMINS = process.env.ADMINS ?? ''
+
+/** Telegram bot token */
+const BOT_TOKEN = process.env.BOT_TOKEN ?? ''
+
+/** HTTPS Proxy URL */
+const HTTPS_PROXY = process.env.PROXY ?? ''
+
+/** MongoDB URL */
+const DB_URL = process.env.DB_URL ?? ''
+
+/** Cloudflare Worker URL */
+const WORKER_URL = process.env.WORKER_URL ?? ''
+
+/** API Key - A random secure key to access api */
+const API_KEY = process.env.API_KEY ?? '' // Generate any API Key and pass it when accessing the API.
+
+/** Maximum number of products can be added by a user at a time. */
const LIMIT = Number(process.env.LIMIT) // Maximum number of products can be added by a user at a time.
-
-export { ADMINS, BOT_TOKEN, DB_URL, WORKER_URL, API_KEY, LIMIT }
\ No newline at end of file
+
+export { ADMINS, BOT_TOKEN, DB_URL, WORKER_URL, API_KEY, LIMIT, HTTPS_PROXY }
\ No newline at end of file
diff --git a/index.js b/index.js
index 0c129dc..a675612 100644
--- a/index.js
+++ b/index.js
@@ -3,6 +3,7 @@ import { manageProducts, manageUsers } from "./db.js"
import {API_KEY} from './config.js'
import express from 'express'
import bot from './bot.js'
+import { webhookCallback } from "grammy"
//Globals
const port = process.env.PORT || 3000;
@@ -81,5 +82,25 @@ app.get('/info', async(req, res) => {
res.send(JSON.stringify({error: 'Invalid API key'}))
})
+// use bot webhook path
+app.use('/bot', webhookCallback(bot, 'express'));
+
+// set bot webhook, use req.url as webhook path
+app.get('/setup', async (req, res) => {
+ // get host name from req
+ try {
+ const host = req.hostname;
+ bot.api.setWebhook(`https://${host}/bot`);
+ res.send('ok');
+ } catch (e) {
+ console.log(e);
+ res.send('error');
+ };
+})
+
app.listen(port, async () => console.log('listening to port ' + port));
-bot.start().then(() => console.log('Bot launched!'));
\ No newline at end of file
+
+// if NODE_ENV is development, start polling
+if (process.env.NODE_ENV === 'development') {
+ bot.start();
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 2e3bdbc..4342899 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"dotenv": "^10.0.0",
"express": "^4.17.1",
"grammy": "^1.5.4",
+ "https-proxy-agent": "^7.0.2",
"mongodb": "^4.9.0"
}
}
diff --git a/unshort.js b/unshort.js
index ca65765..b6b163c 100644
--- a/unshort.js
+++ b/unshort.js
@@ -1,19 +1,48 @@
import axios from 'axios';
+
+const merchants = [
+ 'amazon',
+ 'flipkart',
+ 'snapdeal',
+ 'ajio',
+ 'myntra',
+ 'jabong',
+ 'bewakoof',
+ 'limeroad',
+ 'shein',
+]
+
+/**
+ * Get the long url from a short url
+ * @param {string} url
+ * @returns
+ */
const unshort = async (url) => {
const extractUrl = req => req?.request?.res?.responseUrl ||
req?.request?._redirectable?._currentUrl ||
req?.request?._currentUrl ||
req?.request?._options?.href ||
'https://' + req?.request?.host + req?.request?.path;
+
+
+ const host = new URL(url).hostname.split(".");
+ const merchant = host[0] === 'www' ? host[1] : host[0];
+
+ if (merchants.includes(merchant))
+ return url;
+
+ let longUrl = url;
+
try {
const req = await axios.get(url);
const result = extractUrl(req);
- var longUrl = result ? result : url;
+ longUrl = result ? result : url;
} catch (err) {
const result = extractUrl(err);
- var longUrl = result ? result : url;
+ longUrl = result ? result : url;
}
return longUrl;
}
+
export default unshort;
\ No newline at end of file
diff --git a/utils.js b/utils.js
index ac3d53e..f377ebd 100644
--- a/utils.js
+++ b/utils.js
@@ -1,33 +1,51 @@
import * as cheerio from 'cheerio'
import axios from 'axios'
-import {WORKER_URL} from './config.js'
+import { WORKER_URL, HTTPS_PROXY } from './config.js'
+import { HttpsProxyAgent } from 'https-proxy-agent';
const urlRegex = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i;
+
+/**
+ * check if string is url or not
+ * @param {string} str
+ * @returns {boolean}
+ */
const isUrl = (str) => { // Return true if string is a valid URL
- return urlRegex.test(str);
+ return urlRegex.test(str);
};
-const getRandomId = () => [...Array(10)].map(i=>(~~(Math.random()*36)).toString(36)).join(''); // Return a random id
+/**
+ * Generate random string id
+ * @returns {string}
+ */
+const getRandomId = () => [...Array(10)].map(i => (~~(Math.random() * 36)).toString(36)).join('');
+
const selectors = {
- amazon: {
- title: '#productTitle',
- price1: 'span.a-price.a-text-price.a-size-medium.apexPriceToPay > span:nth-child(2)', price2: 'span.a-price.aok-align-center.priceToPay > span.a-offscreen',
- image1: '#landingImage'
- },
- flipkart: {
- title: '.B_NuCI',
- price1: '._30jeq3._16Jk6d',
- image1: '#container > div > div._2c7YLP.UtUXW0._6t1WkM._3HqJxg > div._1YokD2._2GoDe3 > div._1YokD2._3Mn1Gg.col-5-12._78xt5Y > div:nth-child(1) > div > div._3li7GG > div._1BweB8 > div._3kidJX > div.CXW8mj._3nMexc > img',
- },
- snapdeal: {
- title: '#productOverview > div.col-xs-14.right-card-zoom.reset-padding > div > div.pdp-fash-topcenter-inner.layout > div.row > div.col-xs-18 > h1',
- price1: '#buyPriceBox > div.row.reset-margin > div.col-xs-14.reset-padding.padL8 > div.disp-table > div.pdp-e-i-PAY-r.disp-table-cell.lfloat > span.pdp-final-price > span',
- image1: '#bx-slider-left-image-panel > li:nth-child(1) > img'
- }
+ amazon: {
+ title: '#productTitle',
+ // price1: 'span.a-price.a-text-price.a-size-medium.apexPriceToPay > span:nth-child(2)', price2: 'span.a-price.aok-align-center.priceToPay > span.a-offscreen',
+ price1: '#tp_price_block_total_price_ww span',
+ image1: '#landingImage'
+ },
+ flipkart: {
+ title: '.B_NuCI',
+ price1: '._30jeq3._16Jk6d',
+ image1: '#container > div > div._2c7YLP.UtUXW0._6t1WkM._3HqJxg > div._1YokD2._2GoDe3 > div._1YokD2._3Mn1Gg.col-5-12._78xt5Y > div:nth-child(1) > div > div._3li7GG > div._1BweB8 > div._3kidJX > div.CXW8mj._3nMexc > img',
+ },
+ snapdeal: {
+ title: '#productOverview > div.col-xs-14.right-card-zoom.reset-padding > div > div.pdp-fash-topcenter-inner.layout > div.row > div.col-xs-18 > h1',
+ price1: '#buyPriceBox > div.row.reset-margin > div.col-xs-14.reset-padding.padL8 > div.disp-table > div.pdp-e-i-PAY-r.disp-table-cell.lfloat > span.pdp-final-price > span',
+ image1: '#bx-slider-left-image-panel > li:nth-child(1) > img'
+ }
}
+/**
+ * Get common url for the product
+ * @param {string} link
+ * @param {string} tag
+*/
const productCommonUrl = (link, tag) => {
const url = new URL(link?.replace("www.", ""));
const merchant = url.hostname.split(".")[0];
@@ -41,7 +59,7 @@ const productCommonUrl = (link, tag) => {
break;
case "flipkart":
id = url.searchParams.get("pid");
- commonUrl = id ? "https://www.flipkart.com/product/p/itme?pid=" + id : link.includes('/p/itm') ?link.split('?')[0] : link;
+ commonUrl = id ? "https://www.flipkart.com/product/p/itme?pid=" + id : link.includes('/p/itm') ? link.split('?')[0] : link;
break;
default:
null;
@@ -50,28 +68,67 @@ const productCommonUrl = (link, tag) => {
return commonUrl;
};
-const getProductDetails = async(url, merchant) => {
- try{
- const commonUrl = productCommonUrl(url);
- const res = await axios.get(`${WORKER_URL}/?url=${encodeURIComponent(commonUrl)}`, {
- headers: {
- "User-Agent":
- "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
- },
- });
- const $ = cheerio.load(res.data);
- const selector = selectors[merchant];
- const price = parseFloat($(selector.price1).text().trim().replace(/^\D+|[^0-9.]/g, '')) || parseFloat($(selector.price2).text().trim().replace(/^\D+|[^0-9.]/g, ''));
- const title = $(selector.title).text().trim();
- const image = $(selector.image1).attr('src');
- if(!title || !price) {
- return {ok: false}
- }
- return {ok: true, title, price, image, link: commonUrl}
- }catch(e){
- console.log(e);
- return {ok: false}
- }
+
+/**
+ * Make request to the url
+ * @param {string} url
+ * @param {object} options
+ */
+const makeRequest = async (url, { method = 'GET', useProxy }) => {
+ try {
+
+ const options = { method, headers: { "User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" } };
+ if (useProxy) options.httpsAgent = new HttpsProxyAgent(HTTPS_PROXY);
+
+ if (HTTPS_PROXY && useProxy)
+ return await axios(url, options);
+
+ // else if (WORKER_URL) {
+ // const url1 = new URL(WORKER_URL)
+ // url1.searchParams.set('url', url)
+
+ // return await axios(url1.toString(), options);
+ // }
+
+ else return await axios(url, options);
+ } catch (err) {
+ throw err;
+ }
+}
+
+/**
+ * Get product details from the url
+ * @param {string} url
+ * @param {string} merchant
+ * @returns
+ */
+const getProductDetails = async (url, merchant) => {
+ try {
+ const commonUrl = productCommonUrl(url);
+ const res = await makeRequest(commonUrl, { useProxy: commonUrl.includes('amazon.') });
+
+ const $ = cheerio.load(res.data);
+ const selector = selectors[merchant];
+
+ const priceEl = $(selector.price1) || $(selector.price2);
+ if (!priceEl || !priceEl.text()?.trim())
+ return { ok: false }
+
+ const price = priceEl.text()?.split('.')[0]?.trim().replace(/^\D+|[^0-9.]/g, '');
+
+ // const price = parseFloat($(selector.price1).text().trim().replace(/^\D+|[^0-9.]/g, '')) || parseFloat($(selector.price2).text().trim().replace(/^\D+|[^0-9.]/g, ''));
+
+ const title = $(selector.title).text().trim();
+ const image = $(selector.image1).attr('src');
+
+ if (!title || !price)
+ return { ok: false }
+
+ return { ok: true, title, price, image, link: commonUrl }
+ } catch (e) {
+ console.log(e);
+ return { ok: false }
+ }
}
export { isUrl, getRandomId, getProductDetails, productCommonUrl };