feat: Deco proteste
Build and publish / build (push) Successful in 1m42s

This commit is contained in:
Lino Silva
2026-05-12 22:59:12 +01:00
parent 1ce14628de
commit 405a959691
3 changed files with 96 additions and 121 deletions
+95 -42
View File
@@ -10,10 +10,17 @@ const symbols = {
BPIDEST2040: {
url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2040",
name: "BPI Destino PPR 2040",
type: "bpi",
},
BPIDEST2050: {
url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2050",
name: "BPI Destino PPR 2050",
type: "bpi",
},
IE00BK5BQT80: {
url: "https://www.deco.proteste.pt/investe/investimentos/fundos/vanguard-ftse-all-world-ucits-etf-usd-acc",
name: "Vanguard FTSE All-World UCITS ETF USD Acc",
type: "deco",
},
};
@@ -38,53 +45,99 @@ async function getLatestPrice(symbol) {
// Visit the page and wait until network connections are completed
await page.goto(config.url, { waitUntil: "networkidle2" });
// Interact with the DOM to retrieve the price and date
const [price, date] = await page.evaluate(() => {
const spanTags = document.getElementsByTagName("span");
const priceSearchText = "ÚLTIMA COTAÇÃO:";
const dateSearchText = "DATA COTAÇÃO:";
let priceElementFound;
let dateElementFound;
for (let i = 0; i < spanTags.length; i++) {
if (spanTags[i].textContent.trim() == priceSearchText) {
priceElementFound = spanTags[i];
if (priceElementFound && dateElementFound) break;
}
if (spanTags[i].textContent.trim() == dateSearchText) {
dateElementFound = spanTags[i];
if (priceElementFound && dateElementFound) break;
}
}
if (!priceElementFound || !dateElementFound) {
throw new Error("Could not find price or date elements");
}
return [
priceElementFound.nextSibling.innerHTML,
dateElementFound.nextSibling.innerHTML,
];
});
const marketPrice = parseFloat(
price.replace("€", "").trim().replace(",", "."),
);
return {
symbol,
name: config.name,
currency: "EUR",
price: marketPrice,
date: date.trim(),
timestamp: new Date().toISOString(),
};
switch (config.type) {
case "bpi":
return await scrapeBPI(page, symbol, config);
case "deco":
return await scrapeDeco(page, symbol, config);
default:
throw new Error(`Unsupported type: ${config.type}`);
}
} finally {
// Always close the browser
await browser.close();
}
}
async function scrapeDeco(page, symbol, config) {
// Interact with the DOM to retrieve the price and date
const [price, date] = await page.evaluate(() => {
const priceElement = document.querySelector(
".product-points-data__current-value",
);
const dateElement = document.querySelector(
".product-points-data__current-value + span",
);
if (!priceElement || !dateElement) {
throw new Error("Could not find price or date elements");
}
const price = priceElement.textContent
.replace("€", "")
.replace("EUR", "")
.replace(",", ".")
.trim();
const date = dateElement.textContent.trim();
return [price, date];
});
const marketPrice = parseFloat(price);
return {
symbol,
name: config.name,
currency: "EUR",
price: marketPrice,
date,
timestamp: new Date().toISOString(),
};
}
async function scrapeBPI(page, symbol, config) {
// Interact with the DOM to retrieve the price and date
const [price, date] = await page.evaluate(() => {
const spanTags = document.getElementsByTagName("span");
const priceSearchText = "ÚLTIMA COTAÇÃO:";
const dateSearchText = "DATA COTAÇÃO:";
let priceElementFound;
let dateElementFound;
for (let i = 0; i < spanTags.length; i++) {
if (spanTags[i].textContent.trim() == priceSearchText) {
priceElementFound = spanTags[i];
if (priceElementFound && dateElementFound) break;
}
if (spanTags[i].textContent.trim() == dateSearchText) {
dateElementFound = spanTags[i];
if (priceElementFound && dateElementFound) break;
}
}
if (!priceElementFound || !dateElementFound) {
throw new Error("Could not find price or date elements");
}
return [
priceElementFound.nextSibling.innerHTML,
dateElementFound.nextSibling.innerHTML,
];
});
const marketPrice = parseFloat(
price.replace("€", "").trim().replace(",", "."),
);
return {
symbol,
name: config.name,
currency: "EUR",
price: marketPrice,
date: date.trim(),
timestamp: new Date().toISOString(),
};
}
// API endpoint to get latest price for a symbol
app.get("/price/:symbol", async (req, res) => {
const { symbol } = req.params;
@@ -92,7 +145,7 @@ app.get("/price/:symbol", async (req, res) => {
try {
console.log(`Fetching price for symbol: ${symbol}`);
const priceData = await getLatestPrice(symbol);
console.log(`Success: ${symbol} - ${priceData.price}`);
console.log(`Success: ${symbol} - ${priceData.price}`);
res.json(priceData);
} catch (error) {
console.error(`Error fetching price for ${symbol}:`, error.message);