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 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
@@ -0,0 +1,3 @@
1
+ import NodeCache from "node-cache";
2
+
3
+ export const cache = new NodeCache({ stdTTL: 86400 }); // 24 hours
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
@@ -0,0 +1,9 @@
1
+ export function normalizeRates(rawRates) {
2
+ return {
3
+ source: "NBC",
4
+ country: "Rwanda",
5
+ base: "RWF",
6
+ date: new Date().toISOString().split("T")[0],
7
+ rates: rawRates
8
+ };
9
+ }
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
+ }