africa-fx-feed 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +23 -0
- package/package.json +44 -0
- package/src/cache.js +3 -0
- package/src/index.js +20 -0
- package/src/parser.js +9 -0
- package/src/rss.js +21 -0
- package/src/scraper.js +37 -0
package/cli.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { getRwandaRates, getRwandaRSSFeed } from "./src/index.js";
|
|
4
|
+
|
|
5
|
+
const arg = process.argv[2];
|
|
6
|
+
|
|
7
|
+
if (arg === "--json") {
|
|
8
|
+
const data = await getRwandaRates();
|
|
9
|
+
console.log(JSON.stringify(data, null, 2));
|
|
10
|
+
}
|
|
11
|
+
else if (arg === "--rss") {
|
|
12
|
+
const xml = await getRwandaRSSFeed();
|
|
13
|
+
console.log(xml);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log(`
|
|
17
|
+
Africa FX Feed — Rwanda (NBC)
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
africa-fx-feed --json Output JSON
|
|
21
|
+
africa-fx-feed --rss Output RSS XML
|
|
22
|
+
`);
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "africa-fx-feed",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Rwanda exchange rates feed from NBC (JSON + RSS)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"africa-fx-feed": "./cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"cli.js",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node cli.js",
|
|
18
|
+
"test": "node src/index.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"rwanda",
|
|
22
|
+
"ncb",
|
|
23
|
+
"exchange",
|
|
24
|
+
"rates",
|
|
25
|
+
"rss",
|
|
26
|
+
"fx"
|
|
27
|
+
],
|
|
28
|
+
"author": "David Iyera",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/IYERADavid/Africa-FX-Feed"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/IYERADavid/Africa-FX-Feed/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/IYERADavid/Africa-FX-Feed#readme",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"cheerio": "^1.0.0",
|
|
40
|
+
"node-cache": "^5.1.2",
|
|
41
|
+
"undici": "^6.0.0",
|
|
42
|
+
"xmlbuilder2": "^3.0.2"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/cache.js
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { scrapeNBCRWRates } from "./scraper.js";
|
|
2
|
+
import { normalizeRates } from "./parser.js";
|
|
3
|
+
import { generateRSS } from "./rss.js";
|
|
4
|
+
import { cache } from "./cache.js";
|
|
5
|
+
|
|
6
|
+
export async function getRwandaRates() {
|
|
7
|
+
const cached = cache.get("rwanda_rates");
|
|
8
|
+
if (cached) return cached;
|
|
9
|
+
|
|
10
|
+
const raw = await scrapeNBCRWRates();
|
|
11
|
+
const data = normalizeRates(raw);
|
|
12
|
+
|
|
13
|
+
cache.set("rwanda_rates", data);
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getRwandaRSSFeed() {
|
|
18
|
+
const data = await getRwandaRates();
|
|
19
|
+
return generateRSS(data);
|
|
20
|
+
}
|
package/src/parser.js
ADDED
package/src/rss.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { create } from "xmlbuilder2";
|
|
2
|
+
|
|
3
|
+
export function generateRSS(data) {
|
|
4
|
+
const root = create({ version: "1.0" })
|
|
5
|
+
.ele("rss", { version: "2.0" })
|
|
6
|
+
.ele("channel");
|
|
7
|
+
|
|
8
|
+
root.ele("title").txt("NBC Rwanda Exchange Rates");
|
|
9
|
+
root.ele("description").txt("Official exchange rates from NBC Group");
|
|
10
|
+
root.ele("link").txt("https://rw.ncbagroup.com/forex-rates/");
|
|
11
|
+
root.ele("pubDate").txt(new Date().toUTCString());
|
|
12
|
+
|
|
13
|
+
data.rates.forEach(rate => {
|
|
14
|
+
const item = root.ele("item");
|
|
15
|
+
item.ele("title").txt(`${rate.currency} / RWF`);
|
|
16
|
+
item.ele("description").txt(`Buy: ${rate.buy} | Sell: ${rate.sell}`);
|
|
17
|
+
item.ele("pubDate").txt(new Date().toUTCString());
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return root.end({ prettyPrint: true });
|
|
21
|
+
}
|
package/src/scraper.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { fetch } from "undici";
|
|
2
|
+
import * as cheerio from "cheerio";
|
|
3
|
+
|
|
4
|
+
const NBC_RW_URL = "https://rw.ncbagroup.com/forex-rates/";
|
|
5
|
+
|
|
6
|
+
export async function scrapeNBCRWRates() {
|
|
7
|
+
const res = await fetch(NBC_RW_URL, {
|
|
8
|
+
headers: {
|
|
9
|
+
"User-Agent": "Mozilla/5.0",
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const html = await res.text();
|
|
14
|
+
const $ = cheerio.load(html);
|
|
15
|
+
|
|
16
|
+
const table = $("#tab-daily-rates table tbody tr");
|
|
17
|
+
|
|
18
|
+
const rates = [];
|
|
19
|
+
|
|
20
|
+
table.each((_, row) => {
|
|
21
|
+
const cols = $(row).find("td");
|
|
22
|
+
|
|
23
|
+
const currency = $(cols[1]).text().trim();
|
|
24
|
+
const buy = parseNumber($(cols[2]).text());
|
|
25
|
+
const sell = parseNumber($(cols[3]).text());
|
|
26
|
+
|
|
27
|
+
if (currency && buy && sell) {
|
|
28
|
+
rates.push({ currency, buy, sell });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return rates;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseNumber(value) {
|
|
36
|
+
return parseFloat(value.replace(/,/g, "").trim());
|
|
37
|
+
}
|