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
+1 -1
View File
@@ -129,4 +129,4 @@ dist
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
.DS_Store
+58 -5
View File
@@ -10,10 +10,17 @@ const symbols = {
BPIDEST2040: { BPIDEST2040: {
url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2040", url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2040",
name: "BPI Destino PPR 2040", name: "BPI Destino PPR 2040",
type: "bpi",
}, },
BPIDEST2050: { BPIDEST2050: {
url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2050", url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2050",
name: "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,6 +45,56 @@ async function getLatestPrice(symbol) {
// Visit the page and wait until network connections are completed // Visit the page and wait until network connections are completed
await page.goto(config.url, { waitUntil: "networkidle2" }); await page.goto(config.url, { waitUntil: "networkidle2" });
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 // Interact with the DOM to retrieve the price and date
const [price, date] = await page.evaluate(() => { const [price, date] = await page.evaluate(() => {
const spanTags = document.getElementsByTagName("span"); const spanTags = document.getElementsByTagName("span");
@@ -79,10 +136,6 @@ async function getLatestPrice(symbol) {
date: date.trim(), date: date.trim(),
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
} finally {
// Always close the browser
await browser.close();
}
} }
// API endpoint to get latest price for a symbol // API endpoint to get latest price for a symbol
@@ -92,7 +145,7 @@ app.get("/price/:symbol", async (req, res) => {
try { try {
console.log(`Fetching price for symbol: ${symbol}`); console.log(`Fetching price for symbol: ${symbol}`);
const priceData = await getLatestPrice(symbol); const priceData = await getLatestPrice(symbol);
console.log(`Success: ${symbol} - ${priceData.price}`); console.log(`Success: ${symbol} - ${priceData.price}`);
res.json(priceData); res.json(priceData);
} catch (error) { } catch (error) {
console.error(`Error fetching price for ${symbol}:`, error.message); console.error(`Error fetching price for ${symbol}:`, error.message);
-78
View File
@@ -9,7 +9,6 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.7.7",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^5.2.1", "express": "^5.2.1",
"puppeteer": "^23.5.3" "puppeteer": "^23.5.3"
@@ -432,19 +431,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.7.7",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/b4a": { "node_modules/b4a": {
"version": "1.6.7", "version": "1.6.7",
"license": "Apache-2.0" "license": "Apache-2.0"
@@ -676,16 +662,6 @@
"version": "1.1.4", "version": "1.1.4",
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"dev": true, "dev": true,
@@ -809,13 +785,6 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1299,36 +1268,6 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"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.1",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/forwarded": { "node_modules/forwarded": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1808,23 +1747,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/mime-db": {
"version": "1.52.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"dev": true, "dev": true,