Skip to content

Commit

Permalink
Merge pull request #34 from olarotseyi/master
Browse files Browse the repository at this point in the history
Too Many Requests Error from Google (a production environment issue) solved with rates caching for currency pairs
  • Loading branch information
paul-shuvo committed Nov 4, 2022
2 parents f93c674 + 65d0fff commit 997c427
Show file tree
Hide file tree
Showing 5 changed files with 946 additions and 556 deletions.
296 changes: 184 additions & 112 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const cheerio = require("cheerio")
const got = require("got")
const request = require("request")

class CurrencyConverter {
currencies = {
Expand Down Expand Up @@ -155,137 +155,209 @@ class CurrencyConverter {
}
currencyCode = ["AFN", "ALL", "DZD", "AOA", "ARS", "AMD", "AWG", "AUD", "AZN", "BSD", "BHD", "BBD", "BDT", "BYN", "BZD", "BMD", "BTN", "XBT", "BOB", "BAM", "BWP", "BRL", "BND", "BGN", "BIF", "XPF", "KHR", "CAD", "CVE", "KYD", "FCFA", "CLP", "CLF", "CNY", "CNY", "COP", "CF", "CDF", "CRC", "HRK", "CUC", "CZK", "DKK", "DJF", "DOP", "XCD", "EGP", "ETB", "FJD", "GMD", "GBP", "GEL", "GHS", "GTQ", "GNF", "GYD", "HTG", "HNL", "HKD", "HUF", "ISK", "INR", "IDR", "IRR", "IQD", "ILS", "JMD", "JPY", "JOD", "KZT", "KES", "KWD", "KGS", "LAK", "LBP", "LSL", "LRD", "LYD", "MOP", "MKD", "MGA" , "MWK", "MYR", "MVR", "MRO", "MUR", "MXN", "MDL", "MAD", "MZN", "MMK", "NAD", "NPR", "ANG", "NZD", "NIO", "NGN", "NOK", "OMR", "PKR", "PAB", "PGK", "PYG", "PHP", "PLN", "QAR", "RON", "RUB", "RWF", "SVC", "SAR", "RSD", "SCR", "SLL", "SGD", "SBD", "SOS", "ZAR", "KRW", "VES", "LKR", "SDG", "SRD", "SZL", "SEK", "CHF", "TJS", "TZS", "THB", "TOP", "TTD", "TND", "TRY" , "TMT", "UGX", "UAH", "AED", "USD", "UYU", "UZS", "VND", "XOF", "YER", "ZMW", "ETH", "EUR", "LTC", "TWD", "PEN"]

constructor(params) {
this.currencyFrom = ""
this.currencyTo = ""
this.currencyAmount = 1
this.convertedValue = 0
this.isDecimalComma = false

if(params != undefined){
if(params["from"] !== undefined)
this.from(params["from"])

if(params["to"] !== undefined)
this.to(params["to"])

if(params["amount"] !== undefined)
this.amount(params["amount"])

if(params["isDecimalComma"] !== undefined)
this.setDecimalComma(params["isDecimalComma"])
}
}
from (currencyFrom) {
if(typeof currencyFrom !== "string")
throw new TypeError("currency code should be a string")

if(!this.currencyCode.includes(currencyFrom.toUpperCase()))
throw new Error(`${currencyFrom} is not a valid currency code`)

this.currencyFrom = currencyFrom.toUpperCase()
return this
}
to (currencyTo) {
if(typeof currencyTo !== "string")
throw new TypeError("currency code should be a string")
constructor(params) {
this.currencyFrom = ""
this.currencyTo = ""
this.currencyAmount = 1
this.convertedValue = 0
this.isDecimalComma = false;
this.isRatesCaching = false;
this.ratesCacheDuration = 0;
this.ratesCache = {};

if(!this.currencyCode.includes(currencyTo.toUpperCase()))
throw new Error(`${currencyTo} is not a valid currency code`)
if (params != undefined) {
if (params["from"] !== undefined)
this.from(params["from"])

this.currencyTo = currencyTo
return this
}
amount (currencyAmount){
if(typeof currencyAmount !== "number")
throw new TypeError("amount should be a number")

if(currencyAmount <= 0)
throw new Error("amount should be a positive number")

this.currencyAmount = currencyAmount
return this
if (params["to"] !== undefined)
this.to(params["to"])

if (params["amount"] !== undefined)
this.amount(params["amount"])

if (params["isDecimalComma"] !== undefined)
this.setDecimalComma(params["isDecimalComma"])
}
}
from(currencyFrom) {
if (typeof currencyFrom !== "string")
throw new TypeError("currency code should be a string")

if (!this.currencyCode.includes(currencyFrom.toUpperCase()))
throw new Error(`${currencyFrom} is not a valid currency code`)

this.currencyFrom = currencyFrom.toUpperCase()
return this
}
to(currencyTo) {
if (typeof currencyTo !== "string")
throw new TypeError("currency code should be a string")

if (!this.currencyCode.includes(currencyTo.toUpperCase()))
throw new Error(`${currencyTo} is not a valid currency code`)

this.currencyTo = currencyTo
return this
}
amount(currencyAmount) {
if (typeof currencyAmount !== "number")
throw new TypeError("amount should be a number")

if (currencyAmount <= 0)
throw new Error("amount should be a positive number")

this.currencyAmount = currencyAmount
return this
}

setDecimalComma(isDecimalComma) {
if (typeof isDecimalComma !== "boolean")
throw new TypeError("isDecimalComma should be a boolean")

this.isDecimalComma = isDecimalComma
return this
}

setDecimalComma (isDecimalComma){
if(typeof isDecimalComma !== "boolean")
throw new TypeError("isDecimalComma should be a boolean")

this.isDecimalComma = isDecimalComma
return this
replaceAll(text, queryString, replaceString) {
let text_ = ""
for (let i = 0; i < text.length; i++) {
if (text[i] === queryString) {
text_ += replaceString
} else {
text_ += text[i]
}
}
return text_
}

setupRatesCache(ratesCacheOptions) {
if (typeof ratesCacheOptions != "object")
throw new TypeError("ratesCacheOptions should be an object")

if (ratesCacheOptions.isRatesCaching === undefined)
throw new Error(`ratesCacheOptions should have a property called isRatesCaching`)

if (typeof ratesCacheOptions.isRatesCaching != "boolean")
throw new TypeError("ratesCacheOptions.isRatesCaching should be a boolean")

if (typeof ratesCacheOptions.ratesCacheDuration != "number")
throw new TypeError("ratesCacheOptions.ratesCacheDuration should be a number")

if (ratesCacheOptions.ratesCacheDuration <= 0)
throw new Error("ratesCacheOptions.ratesCacheDuration should be a positive number of seconds")

this.isRatesCaching = ratesCacheOptions.isRatesCaching;

if (ratesCacheOptions.ratesCacheDuration === undefined)
this.ratesCacheDuration = 3600; // Defaults to 3600 seconds (1 hour)
else
this.ratesCacheDuration = ratesCacheOptions.ratesCacheDuration;

replaceAll(text, queryString, replaceString) {
let text_ = ""
for (let i = 0; i < text.length; i++) {
if (text[i] === queryString){
text_ += replaceString
return this
}

rates() {
if (this.currencyFrom === this.currencyTo) {
return new Promise((resolve, _) => {
resolve(1)
})
} else {
let currencyPair = this.currencyFrom.toUpperCase() + this.currencyTo.toUpperCase();
let now = new Date();
if (currencyPair in this.ratesCache && now < this.ratesCache[currencyPair].expiryDate) {
return new Promise((resolve, _) => {
resolve(this.ratesCache[currencyPair].rate);
});
} else {
return new Promise((resolve, reject) => {
request(`https://www.google.com/search?q=${this.currencyFrom}+to+${this.currencyTo}+&hl=en`, function(error, response, body) {
if (error) {
return reject(error);
} else {
resolve(body);
}
});
}).then((body) => {
return cheerio.load(body)
})
.then(($) => {
return $(".iBp4i").text().split(" ")[0]
})
.then((rates) => {
if (this.isDecimalComma) {
if (rates.includes("."))
rates = this.replaceAll(rates, ".", "")
if (rates.includes(","))
rates = this.replaceAll(rates, ",", ".")
} else {
if (rates.includes(","))
rates = this.replaceAll(rates, ",", "")
}
else{
text_ += text[i]
if (this.isRatesCaching) {
this.addRateToRatesCache(currencyPair, parseFloat(rates));
}
}
return text_
return parseFloat(rates)
})
}
}
}

rates(){
if(this.currencyFrom === this.currencyTo)
return new Promise((resolve, _) => {resolve(this.currencyAmount) })
else
return got(`https://www.google.co.in/search?q=${this.currencyAmount}+${this.currencyFrom}+to+${this.currencyTo}+&hl=en`)
.then((html) => {
return cheerio.load(html.body)})
.then(($) => {return $(".iBp4i").text().split(" ")[0]})
.then((rates) => {
if(this.isDecimalComma){
if(rates.includes("."))
rates = this.replaceAll(rates, ".", "")
if(rates.includes(","))
rates = this.replaceAll(rates, ",", ".")
}
else{
if(rates.includes(","))
rates = this.replaceAll(rates, ",", "")
}

return parseFloat(rates)
})
convert(currencyAmount) {
if (currencyAmount !== undefined) {
this.amount(currencyAmount)
}

convert(currencyAmount){
if(currencyAmount !== undefined){
this.amount(currencyAmount)
}
if (this.currencyFrom == "")
throw new Error("currency code cannot be an empty string")

if (this.currencyTo == "")
throw new Error("currency code cannot be an empty string")

if(this.currencyFrom == "")
throw new Error("currency code cannot be an empty string")
if (this.currencyAmount == 0)
throw new Error("currency amount should be a positive value")

if(this.currencyTo == "")
throw new Error("currency code cannot be an empty string")
return this.rates().then((rates) => {
this.convertedValue = rates * this.currencyAmount
return this.convertedValue
})
}

if(this.currencyAmount == 0)
throw new Error("currency amount should be a positive value")
currencyName(currencyCode_) {
if (typeof currencyCode_ != "string")
throw new TypeError("currency code should be a string")

return this.rates().then((rates) =>{
// this.convertedValue = rates * this.currencyAmount
if (!this.currencyCode.includes(currencyCode_.toUpperCase()))
throw new Error(`${currencyCode_} is not a valid currency code`)

// as the google result now sends the exact converted
// currency, multiplying the rates with currencyAmount
// makes it redundant.
this.convertedValue = rates * 1
return this.convertedValue
})
}
return this.currencies[currencyCode_]
}

addRateToRatesCache(currencyPair, rate_) {
if (typeof currencyPair != "string")
throw new TypeError("currency pair should be a string")

currencyName(currencyCode_){
if(typeof currencyCode_ != "string")
throw new TypeError("currency code should be a string")

if(!this.currencyCode.includes(currencyCode_.toUpperCase()))
throw new Error(`${currencyCode_} is not a valid currency code`)
if (typeof rate_ != "number")
throw new TypeError("rate should be a number")

return this.currencies[currencyCode_]
let now = new Date();
if (currencyPair in this.ratesCache) {
if (now > this.ratesCache[currencyPair].expiryDate) {
let newExpiry = new Date();
newExpiry.setSeconds(newExpiry.getSeconds() + this.ratesCacheDuration);
this.ratesCache[currencyPair] = {
rate: rate_,
expiryDate: newExpiry
};
}
} else {
let newExpiry = new Date();
newExpiry.setSeconds(newExpiry.getSeconds() + this.ratesCacheDuration);
this.ratesCache[currencyPair] = {
rate: rate_,
expiryDate: newExpiry
};
}
}
}

module.exports = CurrencyConverter
Loading

0 comments on commit 997c427

Please sign in to comment.