├── .github └── workflows │ ├── test.yml │ ├── codecov.yml │ └── publish.yml ├── package.json ├── LICENSE ├── .gitignore ├── readme.md ├── test └── test.js └── index.js /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [12.x, 13.x, 14.x, 15.x, 16.x, 17.x, 18.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm run build --if-present 26 | - run: npm test -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Codecov 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Set up Node 18 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 18 17 | - name: Install dependencies 18 | run: npm install 19 | - name: Run tests and collect coverage 20 | run: npm run report-coverage 21 | - name: Upload coverage to Codecov 22 | uses: codecov/codecov-action@v3 23 | with: 24 | verbose: true 25 | fail_ci_if_error: true -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "currency-converter-lt", 3 | "version": "2.0.1", 4 | "description": "A nodejs currency converter library that doesn't require subscribing to any API calls.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "cheerio": "latest", 8 | "request": "^2.88.2" 9 | }, 10 | "devDependencies": { 11 | "chai": "latest", 12 | "chai-as-promised": "latest", 13 | "codecov": "latest", 14 | "mocha": "latest", 15 | "nyc": "latest" 16 | }, 17 | "scripts": { 18 | "test": "mocha", 19 | "coverage": "nyc mocha", 20 | "report-coverage": "nyc --reporter=text-lcov > coverage.lcov mocha" 21 | }, 22 | "keywords": [ 23 | "currency", 24 | "converter", 25 | "library" 26 | ], 27 | "author": "Shuvo Kumar Paul", 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "git://github.com/paul-shuvo/nodejs-currency-converter.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/paul-shuvo/nodejs-currency-converter/issues" 35 | }, 36 | "homepage": "https://github.com/paul-shuvo/nodejs-currency-converter" 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Arthur Manuel Bandeira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: "🚀 release" 2 | 3 | on: 4 | push: 5 | branches: 6 | - release 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # Setup .npmrc file to publish to npm 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: '14.x' 16 | registry-url: 'https://registry.npmjs.org' 17 | # scope: '@paul-shuvo' 18 | - run: npm install 19 | - run: npm publish --access public 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | 23 | - uses: actions/checkout@v2 24 | - uses: actions/setup-node@v2 25 | with: 26 | node-version: '14.x' 27 | registry-url: 'https://npm.pkg.github.com' 28 | # Defaults to the user or organization that owns the workflow file 29 | scope: '@paul-shuvo' 30 | - run: | 31 | sudo apt install jq 32 | echo $(jq '.name = "@paul-shuvo/nodejs-currency-converter"' package.json) > package.json 33 | echo $(jq '.publishConfig.registry = "https://npm.pkg.github.com/paul-shuvo"' package.json) > package.json 34 | - run: npm install 35 | - run: npm publish 36 | env: 37 | NODE_AUTH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 38 | - run: | 39 | mkdir test-package 40 | cd test-package 41 | npm init -y 42 | npm install --save currency-converter-lt 43 | echo -e "const CC = require('currency-converter-lt')\nlet currencyConverter = new CC({from:'USD', to:'JPY', amount:1})\ncurrencyConverter.convert().then((response) => {\nconsole.log(response) //or do something else\n})" >> index.js 44 | node index.js 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | coverage.lcov 119 | .nyc_output 120 | .nyc_output/ 121 | 122 | .vscode 123 | t.js 124 | s.js 125 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
A minimal currency converter library for NodeJS that works out of the box.
7 |!!! Note: if you get incorrect conversion described in this [issue](https://github.com/paul-shuvo/nodejs-currency-converter/issues/20) then make sure you pass `isDecimalComma: true` to the constructor as following:
55 | 56 | ```javascript 57 | let currencyConverter = new CC({from:"USD", to:"JPY", amount:100, isDecimalComma:true}) 58 | ``` 59 | 60 | 61 | The convert method will return the conversion based on the last conversion rate and can be used as a promise. 62 | 63 | ```javascript 64 | currencyConverter.convert().then((response) => { 65 | console.log(response) //or do something else 66 | }) 67 | ``` 68 | 69 | `convert` can also take the amount as a parameter. 70 | 71 | ```javascript 72 | currencyConverter.convert(100).then((response) => { 73 | console.log(response) //or do something else 74 | }) 75 | ``` 76 | 77 | To find the rates use the `rates` method. 78 | 79 | ```javascript 80 | currencyConverter.rates().then((response) => { 81 | console.log(response) //or do something else 82 | }) 83 | ``` 84 | 85 | Rates can be cached for currency pairs. To implement rate caching, instantiate an object of CurrencyConverter only once in your project, in a CurrencyConverter file, and setup rates caching then import the instance of CurrencyConverter from the CurrencyConverter file in your project across the rest of your project. Use chaining to convert currencies when caching is implemented. Below is an example of a CurrencyConverter file. 86 | 87 | Note: Rates are not actually deleted after the ratesCacheDuration. The rate remains in the rates cache of the CurrencyConverter object until a request is made for the same currency pair at which point, the old rate is overwritten. 88 | 89 | ```javascript 90 | const CC = require('currency-converter-lt') 91 | 92 | let currencyConverter = new CC() 93 | 94 | let ratesCacheOptions = { 95 | isRatesCaching: true, // Set this boolean to true to implement rate caching 96 | ratesCacheDuration: 3600 // Set this to a positive number to set the number of seconds you want the rates to be cached. Defaults to 3600 seconds (1 hour) 97 | } 98 | 99 | currencyConverter = currencyConverter.setupRatesCache(ratesCacheOptions) 100 | 101 | module.exports = currencyConverter 102 | ``` 103 | 104 | Chaining is also supported. 105 | 106 | ```javascript 107 | currencyConverter.from("USD").to("GBP").amount(125).convert().then((response) => { 108 | console.log(response) //or do something else 109 | }) 110 | ``` 111 | 112 | ## Disclaimer 113 | 114 | This package uses web scraping to provide the api. 115 | 116 | ## Issues 117 | 118 | If any issues are found, they can be reported [here](https://github.com/paul-shuvo/nodejs-currency-converter/issues). 119 | 120 | ## License 121 | 122 | This project is licensed under the [MIT](LICENSE) license. 123 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai") 2 | const assert = chai.assert 3 | const expect = chai.expect 4 | const chaiAsPromised = require("chai-as-promised") 5 | // const chaiJestMock = require('chai-jest-mocks') 6 | 7 | chai.use(chaiAsPromised) 8 | // chai.use(chaiJestMock) 9 | 10 | const CC = require("../index.js") 11 | 12 | let currencyConverter = new CC() 13 | 14 | describe("currencyConverter", () => { 15 | describe("constructor", () => { 16 | it("should instantiate an object without parameters", () => { 17 | let CC_ = new CC() 18 | assert.equal(CC_.currencyFrom, "") 19 | }) 20 | 21 | it("should instantiate an object with json object as a parameter", () => { 22 | let CC_ = new CC({from:"GBP", to:"CAD", amount: 100}) 23 | assert.equal(CC_.currencyFrom, "GBP") 24 | }) 25 | 26 | it("should instantiate an object with json object as a parameter with isDecimalComma", () => { 27 | let CC_ = new CC({from:"GBP", to:"CAD", amount: 100, isDecimalComma: true}) 28 | assert.equal(CC_.currencyFrom, "GBP") 29 | }) 30 | 31 | it("should instantiate an object with json object with partial parameters", () => { 32 | let CC_ = new CC({from:"GBP", amount: 100}) 33 | assert.equal(CC_.currencyFrom, "GBP") 34 | assert.equal(CC_.currencyTo, "") 35 | 36 | CC_ = new CC({to:"GBP", amount: 100}) 37 | assert.equal(CC_.currencyTo, "GBP") 38 | assert.equal(CC_.currencyFrom, "") 39 | 40 | CC_ = new CC({from:"GBP", to: "USD"}) 41 | assert.equal(CC_.currencyFrom, "GBP") 42 | assert.equal(CC_.currencyAmount, 1) 43 | }) 44 | 45 | it("should throw a TypeError", () => { 46 | expect(() => new CC({from:20, amount: 100})).to.throw(TypeError) 47 | }) 48 | }) 49 | 50 | describe("currencyFrom", () => { 51 | it("should equal to USD", () => { 52 | currencyConverter.currencyFrom = "USD" 53 | assert.equal(currencyConverter.currencyFrom, "USD") 54 | }) 55 | 56 | it("should equal to INR", () => { 57 | currencyConverter.from("INR") 58 | assert.equal(currencyConverter.currencyFrom, "INR") 59 | }) 60 | 61 | it("should throw a TypeError", () => { 62 | expect(() => currencyConverter.from(5)).to.throw(TypeError); 63 | }) 64 | 65 | it("should throw an Error", () => { 66 | expect(() => currencyConverter.from("UDD")).to.throw(Error); 67 | }) 68 | 69 | }) 70 | 71 | describe("currencyTo", () => { 72 | it("should equal to CAD", () => { 73 | currencyConverter.currencyTo = "CAD" 74 | assert.equal(currencyConverter.currencyTo, "CAD"); 75 | }) 76 | 77 | it("should equal to JPY", () => { 78 | currencyConverter.to("JPY") 79 | assert.equal(currencyConverter.currencyTo, "JPY"); 80 | }) 81 | 82 | it("should throw a TypeError", () => { 83 | expect(() => currencyConverter.to(5)).to.throw(TypeError); 84 | }) 85 | 86 | it("should throw an Error", () => { 87 | expect(() => currencyConverter.to("UDD")).to.throw(Error); 88 | }) 89 | }) 90 | 91 | describe("currencyAmount", () => { 92 | it("should equal to 10", () => { 93 | currencyConverter.amount(10) 94 | assert.equal(currencyConverter.currencyAmount, 10); 95 | }) 96 | 97 | it("should throw TypeError", () => { 98 | expect(() => currencyConverter.amount("10")).to.throw(TypeError); 99 | }) 100 | 101 | it("should throw an Error", () => { 102 | expect(() => currencyConverter.amount(-1)).to.throw(Error); 103 | }) 104 | }) 105 | 106 | describe("setDecimalComma", () => { 107 | it("should be true", () => { 108 | currencyConverter.setDecimalComma(true) 109 | assert.equal(currencyConverter.isDecimalComma, true) 110 | }) 111 | 112 | it("should throw TypeError", () => { 113 | expect(() => currencyConverter.isDecimalComma("10")).to.throw(TypeError); 114 | }) 115 | }) 116 | 117 | describe("replaceAll", () => { 118 | it("should replace all the , with empty space", () => { 119 | assert.equal(currencyConverter.replaceAll("123,456,789.50", ",", ""), "123456789.50") 120 | }) 121 | 122 | it("should replace all the , with empty .", () => { 123 | assert.equal(currencyConverter.replaceAll("123456789,50", ",", "."), "123456789.50") 124 | }) 125 | 126 | it("should replace all the . with empty space", () => { 127 | assert.equal(currencyConverter.replaceAll("123.456.789", ".", ""), "123456789") 128 | }) 129 | }) 130 | 131 | describe("rates", () => { 132 | it("should not return undefined values", () => { 133 | currencyConverter.from("USD").to("JPY") 134 | return expect(currencyConverter.rates()).to.eventually.not.equal(undefined) 135 | }) 136 | 137 | it("should not return undefined values for CF", () => { 138 | currencyConverter.from("CF").to("JPY") 139 | return expect(currencyConverter.rates()).to.eventually.not.equal(undefined) 140 | }) 141 | 142 | it("should not return undefined values for CHF", () => { 143 | currencyConverter.from("USD").to("CHF") 144 | return expect(currencyConverter.rates()).to.eventually.not.equal(undefined) 145 | }) 146 | 147 | it("should not return undefined values for KMF", () => { 148 | currencyConverter.from("KMF").to("USD") 149 | return expect(currencyConverter.rates()).to.eventually.not.equal(undefined) 150 | }) 151 | 152 | it("should not return undefined values when isDecimalComma is true", () => { 153 | currencyConverter.from("USD").to("JPY").setDecimalComma(true) 154 | return expect(currencyConverter.rates()).to.eventually.not.equal(undefined) 155 | }) 156 | }) 157 | 158 | describe("convert", () => { 159 | it("should set the amount value if a paramter is passed", () => { 160 | currencyConverter.from("USD").to("USD") 161 | return expect(currencyConverter.convert(100)).to.eventually.equal(100) 162 | }) 163 | 164 | it("should throw an Error", () => { 165 | currencyConverter.currencyFrom = "" 166 | currencyConverter.to("CAD").amount(100) 167 | expect(() => currencyConverter.convert()).to.throw(Error) 168 | }) 169 | 170 | it("should throw an Error", () => { 171 | currencyConverter.currencyTo = "" 172 | currencyConverter.from("CAD").amount(100) 173 | expect(() => currencyConverter.convert()).to.throw(Error) 174 | }) 175 | 176 | it("should throw an Error", () => { 177 | currencyConverter.currencyAmount = 0 178 | currencyConverter.from("USD").to("CAD") 179 | expect(() => currencyConverter.convert()).to.throw(Error) 180 | }) 181 | }) 182 | 183 | describe("currencyName", () => { 184 | it("should return name of the currency from the currency code", () => { 185 | assert.equal(currencyConverter.currencyName("CHF"), "Swiss Franc") 186 | }) 187 | 188 | it("should throw a TypeError", () => { 189 | expect(() => currencyConverter.currencyName(5)).to.throw(TypeError); 190 | }) 191 | 192 | it("should throw an Error", () => { 193 | expect(() => currencyConverter.currencyName("DDD")).to.throw(Error); 194 | }) 195 | }) 196 | 197 | describe("setupRatesCache", () => { 198 | it("should throw a TypeError", () => { 199 | expect(() => currencyConverter.setupRatesCache(5)).to.throw(TypeError); 200 | }) 201 | 202 | it("should throw a TypeError", () => { 203 | let ratesCacheOptions = { 204 | isRatesCaching: 3 205 | } 206 | expect(() => currencyConverter.setupRatesCache(ratesCacheOptions)).to.throw(TypeError); 207 | }) 208 | 209 | it("should throw a TypeError", () => { 210 | let ratesCacheOptions = { 211 | isRatesCaching: true, 212 | ratesCacheDuration: "10" 213 | } 214 | expect(() => currencyConverter.setupRatesCache(ratesCacheOptions)).to.throw(TypeError); 215 | }) 216 | 217 | it("should throw an Error", () => { 218 | let ratesCacheOptions = { 219 | ratesCacheDuration: 50 220 | } 221 | expect(() => currencyConverter.setupRatesCache(ratesCacheOptions)).to.throw(Error); 222 | }) 223 | 224 | it("should throw an Error", () => { 225 | let ratesCacheOptions = { 226 | isRatesCaching: true, 227 | ratesCacheDuration: -50 228 | } 229 | expect(() => currencyConverter.setupRatesCache(ratesCacheOptions)).to.throw(Error); 230 | }) 231 | }) 232 | 233 | describe("addRateToRatesCache", () => { 234 | it("should throw an error", () => { 235 | let ratesCacheOptions = { 236 | isRatesCaching: true, 237 | ratesCacheDuration: 3600 238 | } 239 | currencyConverter.setupRatesCache(ratesCacheOptions); 240 | expect(() => currencyConverter.addRateToRatesCache("USDCAD", "0.31")).to.throw(Error); 241 | }) 242 | 243 | it("should throw an error", () => { 244 | let ratesCacheOptions = { 245 | isRatesCaching: true, 246 | ratesCacheDuration: 3600 247 | } 248 | currencyConverter.setupRatesCache(ratesCacheOptions); 249 | expect(() => currencyConverter.addRateToRatesCache(100, "0.31")).to.throw(Error); 250 | }) 251 | }) 252 | }) 253 | 254 | // console.log(cf) 255 | 256 | 257 | // console.log(c.currencies) 258 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const cheerio = require("cheerio") 2 | const request = require("request") 3 | 4 | class CurrencyConverter { 5 | currencies = { 6 | "AFN": "Afghan Afghani", 7 | "ALL": "Albanian Lek", 8 | "DZD": "Algerian Dinar", 9 | "AOA": "Angolan Kwanza", 10 | "ARS": "Argentine Peso", 11 | "AMD": "Armenian Dram", 12 | "AWG": "Aruban Florin", 13 | "AUD": "Australian Dollar", 14 | "AZN": "Azerbaijani M anat", 15 | "BSD": "Bahamian Dollar", 16 | "BHD": "Bahraini Dinar", 17 | "BBD": "Bajan Dollar", 18 | "BDT": "Bangladeshi Taka", 19 | "BYN": "Belarusian Ruble", 20 | "BZD": "Belize Dollar", 21 | "BMD": "Bermudan Dollar", 22 | "BTN": "Bhutan currency", 23 | "BOB": "Bolivian Boliviano", 24 | "BAM": "Bosnia-Herzegovina Convertible Mark", 25 | "BWP": "Botswanan Pula", 26 | "BRL": "Brazilian Real", 27 | "BND": "Brunei Dollar", 28 | "BGN": "Bulgarian Lev", 29 | "BIF": "Burundian Fra nc", 30 | "XPF": "CFP Franc", 31 | "KHR": "Cambodian riel", 32 | "CAD": "Canadian Dollar", 33 | "CVE": "Cape Verdean Escudo", 34 | "KYD": "Cayman Islands Dollar", 35 | "FCFA": "Central African CFA Franc", 36 | "CLP": "Chilean Peso", 37 | "CLF": "Chilean Unit of Account (UF)", 38 | "CNY": "Chinese Yuan", 39 | "CNY": "Chinese Yuan (offshore)", 40 | "COP": "Colombian Peso", 41 | "KMF": "Comorian Franc", 42 | "CDF": "Congolese Franc", 43 | "CRC": "Costa Rican Colón", 44 | "HRK": "Croatian Kuna", 45 | "CUC": "Cuban Peso", 46 | "CZK": "Czech Koruna", 47 | "DKK": "Danish Krone", 48 | "DJF": "Djiboutian Franc", 49 | "DOP": "Dominican Pe so", 50 | "XCD": "East Caribbean Dollar", 51 | "EGP": "Egyptian Pound", 52 | "ETB": "Ethiopian Birr", 53 | "FJD": "Fijian Dollar", 54 | "GMD": "Gambian dalasi", 55 | "GEL": "Georgian Lari", 56 | "GHS": "Ghanaian Cedi", 57 | "GTQ": "Guatemalan Quetzal", 58 | "GNF": "Guinean Franc", 59 | "GYD": "Guyanaese Dollar", 60 | "HTG": "Haitian Gourde", 61 | "HNL": "Honduran Lempira", 62 | "HKD": "Hong Kong Dollar", 63 | "HUF": "Hungarian Forint", 64 | "ISK": "Icelandic Króna", 65 | "INR": "Indian Rupee", 66 | "IDR": "Indonesian Rupiah", 67 | "IRR": "Iranian Rial", 68 | "IQD": "Iraqi Dinar", 69 | "ILS": "Israeli New Shekel", 70 | "JMD": "Jamaican Dollar", 71 | "JPY": "Japanese Yen", 72 | "JOD": "Jordanian Dinar", 73 | "KZT": "Kazakhstani Tenge", 74 | "KES": "Kenyan Shilling", 75 | "KWD": "Kuwaiti Dinar", 76 | "KGS": "Kyrgystani Som", 77 | "LAK": "Laotian Kip", 78 | "LBP": "Lebanese pound", 79 | "LSL": "Lesotho Loti", 80 | "LRD": "Liberian Dollar", 81 | "LYD": "Libyan Dinar", 82 | "MOP": "Macanese Pataca", 83 | "MKD": "Macedonian Denar", 84 | "MGA": "Malagasy Ariary", 85 | "MWK": "Malawian Kwacha", 86 | "MYR": "Malaysian Ringgit", 87 | "MVR": "Maldivian Rufiyaa", 88 | "MRO": "Mauritanian Ouguiya", 89 | "MUR": "Mauritian Rupee", 90 | "MXN": "Mexican Peso", 91 | "MDL": "Moldovan Leu", 92 | "MAD": "Moroccan Dirham", 93 | "MZN": "Mozambican metical", 94 | "MMK": "Myanmar Kyat", 95 | "NAD": "Namibian dol lar", 96 | "NPR": "Nepalese Rupee", 97 | "ANG": "Netherlands Antillean Guilder", 98 | "NZD": "New Zealand Dollar", 99 | "NIO": "Nicaraguan Córdoba", 100 | "NGN": "Nigerian Naira", 101 | "NOK": "Norwegian Krone", 102 | "OMR": "Omani Rial", 103 | "PKR": "Pakistani Rupee", 104 | "PAB": "Panamanian Balboa", 105 | "PGK": "Papua New Guinean Kina", 106 | "PYG": "Paraguayan Guarani", 107 | "PHP": "Philippine peso", 108 | "PLN": "Poland Złoty", 109 | "GBP": "Pound sterling", 110 | "QAR": "Qatari Rial", 111 | "RON": "Romania n Leu", 112 | "RUB": "Russian Ruble", 113 | "RWF": "Rwandan franc", 114 | "SVC": "Salvadoran Colón", 115 | "SAR": "Saudi Riyal", 116 | "RSD": "Serbian Dinar", 117 | "SCR": "Seychellois Rupee", 118 | "SLL": "Sierra Leonean Leone", 119 | "SGD": "Singapore Dollar", 120 | "SBD": "Solomon Islands Dollar", 121 | "SOS": "Somali Shilling", 122 | "ZAR": "South African Rand", 123 | "KRW": "South Korean won", 124 | "VES": "Sovereign Bolivar", 125 | "LKR": "Sri Lankan Rupee", 126 | "SDG": "Sudanese pound", 127 | "SRD": "Surinamese Dollar", 128 | "SZL": "Swazi Lilangeni", 129 | "SEK": "Swedish Krona", 130 | "CF": "Swiss Franc", 131 | "CHF": "Swiss Franc", 132 | "TJS": "Tajikistani Somoni", 133 | "TZS": "Tanzanian Shilling", 134 | "THB": "Thai Baht", 135 | "TOP": "Tongan Pa\"anga", 136 | "TTD": "Trinidad and Tobago Dollar", 137 | "TND": "Tunisian Dinar", 138 | "TRY": "Turkish lira", 139 | "TMT": "Turkmenistan manat", 140 | "UGX": "Ugandan Shilling", 141 | "UAH": "Ukrainian hryvnia", 142 | "AED": "United Arab Emirates Dirham", 143 | "USD": "United States Dollar", 144 | "UYU": "Uruguayan Peso", 145 | "UZS": "Uzbekistani Som", 146 | "VND": "Vietnamese dong", 147 | "XAF": "Central African CFA franc", 148 | "XOF": "West African CFA franc", 149 | "YER": "Yemeni Rial", 150 | "ZMW": "Zambian Kwacha", 151 | "XBT": "Bitcoin", 152 | "ETH": "Ether", 153 | "EUR": "Euro", 154 | "LTC": "Litecoin", 155 | "TWD": "NT$", 156 | "PEN": "Peruvian Sol" 157 | } 158 | 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", "CHF", "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", "KMF", "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", "XAF", "XOF", "YER", "ZMW", "ETH", "EUR", "LTC", "TWD", "PEN"] 159 | 160 | constructor(params) { 161 | this.currencyFrom = "" 162 | this.currencyTo = "" 163 | this.currencyAmount = 1 164 | this.convertedValue = 0 165 | this.isDecimalComma = false; 166 | this.isRatesCaching = false; 167 | this.ratesCacheDuration = 0; 168 | this.ratesCache = {}; 169 | 170 | if (params != undefined) { 171 | if (params["from"] !== undefined) 172 | this.from(params["from"]) 173 | 174 | if (params["to"] !== undefined) 175 | this.to(params["to"]) 176 | 177 | if (params["amount"] !== undefined) 178 | this.amount(params["amount"]) 179 | 180 | if (params["isDecimalComma"] !== undefined) 181 | this.setDecimalComma(params["isDecimalComma"]) 182 | } 183 | } 184 | from(currencyFrom) { 185 | if (typeof currencyFrom !== "string") 186 | throw new TypeError("currency code should be a string") 187 | 188 | if (!this.currencyCode.includes(currencyFrom.toUpperCase())) 189 | throw new Error(`${currencyFrom} is not a valid currency code`) 190 | 191 | this.currencyFrom = currencyFrom.toUpperCase() 192 | return this 193 | } 194 | to(currencyTo) { 195 | if (typeof currencyTo !== "string") 196 | throw new TypeError("currency code should be a string") 197 | 198 | if (!this.currencyCode.includes(currencyTo.toUpperCase())) 199 | throw new Error(`${currencyTo} is not a valid currency code`) 200 | 201 | this.currencyTo = currencyTo 202 | return this 203 | } 204 | amount(currencyAmount) { 205 | if (typeof currencyAmount !== "number") 206 | throw new TypeError("amount should be a number") 207 | 208 | if (currencyAmount <= 0) 209 | throw new Error("amount should be a positive number") 210 | 211 | this.currencyAmount = currencyAmount 212 | return this 213 | } 214 | 215 | setDecimalComma(isDecimalComma) { 216 | if (typeof isDecimalComma !== "boolean") 217 | throw new TypeError("isDecimalComma should be a boolean") 218 | 219 | this.isDecimalComma = isDecimalComma 220 | return this 221 | } 222 | 223 | replaceAll(text, queryString, replaceString) { 224 | let text_ = "" 225 | for (let i = 0; i < text.length; i++) { 226 | if (text[i] === queryString) { 227 | text_ += replaceString 228 | } else { 229 | text_ += text[i] 230 | } 231 | } 232 | return text_ 233 | } 234 | 235 | setupRatesCache(ratesCacheOptions) { 236 | if (typeof ratesCacheOptions != "object") 237 | throw new TypeError("ratesCacheOptions should be an object") 238 | 239 | if (ratesCacheOptions.isRatesCaching === undefined) 240 | throw new Error(`ratesCacheOptions should have a property called isRatesCaching`) 241 | 242 | if (typeof ratesCacheOptions.isRatesCaching != "boolean") 243 | throw new TypeError("ratesCacheOptions.isRatesCaching should be a boolean") 244 | 245 | if (typeof ratesCacheOptions.ratesCacheDuration != "number") 246 | throw new TypeError("ratesCacheOptions.ratesCacheDuration should be a number") 247 | 248 | if (ratesCacheOptions.ratesCacheDuration <= 0) 249 | throw new Error("ratesCacheOptions.ratesCacheDuration should be a positive number of seconds") 250 | 251 | this.isRatesCaching = ratesCacheOptions.isRatesCaching; 252 | 253 | if (ratesCacheOptions.ratesCacheDuration === undefined) 254 | this.ratesCacheDuration = 3600; // Defaults to 3600 seconds (1 hour) 255 | else 256 | this.ratesCacheDuration = ratesCacheOptions.ratesCacheDuration; 257 | 258 | return this 259 | } 260 | 261 | rates() { 262 | if (this.currencyFrom === this.currencyTo) { 263 | return new Promise((resolve, _) => { 264 | resolve(1) 265 | }) 266 | } else { 267 | let currencyPair = this.currencyFrom.toUpperCase() + this.currencyTo.toUpperCase(); 268 | let now = new Date(); 269 | if (currencyPair in this.ratesCache && now < this.ratesCache[currencyPair].expiryDate) { 270 | return new Promise((resolve, _) => { 271 | resolve(this.ratesCache[currencyPair].rate); 272 | }); 273 | } else { 274 | return new Promise((resolve, reject) => { 275 | request(`https://www.google.com/search?q=${this.currencyAmount}+${this.currencyFrom}+to+${this.currencyTo}+&hl=en`, function(error, response, body) { 276 | if (error) { 277 | return reject(error); 278 | } else { 279 | resolve(body); 280 | } 281 | }); 282 | }).then((body) => { 283 | return cheerio.load(body) 284 | }) 285 | .then(($) => { 286 | return $(".iBp4i").text().split(" ")[0] 287 | }) 288 | .then((rates) => { 289 | if (this.isDecimalComma) { 290 | if (rates.includes(".")) 291 | rates = this.replaceAll(rates, ".", "") 292 | if (rates.includes(",")) 293 | rates = this.replaceAll(rates, ",", ".") 294 | } else { 295 | if (rates.includes(",")) 296 | rates = this.replaceAll(rates, ",", "") 297 | } 298 | rates = parseFloat(rates) / this.currencyAmount 299 | if (this.isRatesCaching) { 300 | this.addRateToRatesCache(currencyPair, rates); 301 | } 302 | return rates 303 | }) 304 | } 305 | } 306 | } 307 | 308 | convert(currencyAmount) { 309 | if (currencyAmount !== undefined) { 310 | this.amount(currencyAmount) 311 | } 312 | 313 | if (this.currencyFrom == "") 314 | throw new Error("currency code cannot be an empty string") 315 | 316 | if (this.currencyTo == "") 317 | throw new Error("currency code cannot be an empty string") 318 | 319 | if (this.currencyAmount == 0) 320 | throw new Error("currency amount should be a positive value") 321 | 322 | return this.rates().then((rates) => { 323 | this.convertedValue = rates * this.currencyAmount 324 | return this.convertedValue 325 | }) 326 | } 327 | 328 | currencyName(currencyCode_) { 329 | if (typeof currencyCode_ != "string") 330 | throw new TypeError("currency code should be a string") 331 | 332 | if (!this.currencyCode.includes(currencyCode_.toUpperCase())) 333 | throw new Error(`${currencyCode_} is not a valid currency code`) 334 | 335 | return this.currencies[currencyCode_] 336 | } 337 | 338 | addRateToRatesCache(currencyPair, rate_) { 339 | if (typeof currencyPair != "string") 340 | throw new TypeError("currency pair should be a string") 341 | 342 | if (typeof rate_ != "number") 343 | throw new TypeError("rate should be a number") 344 | 345 | let now = new Date(); 346 | if (currencyPair in this.ratesCache) { 347 | if (now > this.ratesCache[currencyPair].expiryDate) { 348 | let newExpiry = new Date(); 349 | newExpiry.setSeconds(newExpiry.getSeconds() + this.ratesCacheDuration); 350 | this.ratesCache[currencyPair] = { 351 | rate: rate_, 352 | expiryDate: newExpiry 353 | }; 354 | } 355 | } else { 356 | let newExpiry = new Date(); 357 | newExpiry.setSeconds(newExpiry.getSeconds() + this.ratesCacheDuration); 358 | this.ratesCache[currencyPair] = { 359 | rate: rate_, 360 | expiryDate: newExpiry 361 | }; 362 | } 363 | } 364 | } 365 | 366 | module.exports = CurrencyConverter 367 | --------------------------------------------------------------------------------