diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb0fd91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist.js +ext.zip diff --git a/README.md b/README.md index 361cf90..a78d0d7 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,5 @@ This sample demonstrates how developers can use content scripts which employ Doc 2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked). 3. Navigate to a Chrome extension or Chrome Webstore documentation page with an article element. [Here](https://developer.chrome.com/docs/webstore/publish) is an example of a webpage with an article element. 4. The extension will provide an estimated reading time for the text in that article element. Here is the [link](https://developer.chrome.com/docs/extensions/get-started/tutorial/scripts-on-every-tab) for the full instructions. + +Cryptocurrency icons created by Freepik - Flaticon diff --git a/btc.ts b/btc.ts deleted file mode 100644 index 59ffeac..0000000 --- a/btc.ts +++ /dev/null @@ -1,122 +0,0 @@ -(function(e,g){"object"===typeof exports&&"undefined"!==typeof module?module.exports=g():"function"===typeof define&&define.amd?define(g):(e=e||self,e.currency=g())})(this,function(){function e(b,a){if(!(this instanceof e))return new e(b,a);a=Object.assign({},m,a);var d=Math.pow(10,a.precision);this.intValue=b=g(b,a);this.value=b/d;a.increment=a.increment||1/d;a.groups=a.useVedic?n:p;this.s=a;this.p=d}function g(b,a){var d=2 token.replace("$", "\\$")).join( - "|" - )})\\s?((\\d+(\\.|,|')?)+\\d)$`, - "i" -); - -async function convertPrices() { - const currencyMap = {}; - var elements = document.querySelectorAll("*"); - - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - const matches = element.textContent.match(REGEX); - if (!matches) continue; - - const currencyCode = Object.entries(CURRENCIES).find( - ([_, e]) => e === matches[1] - )[0]; - - const storageKey = `${currencyCode}-exchangeRate`; - console.log("btc", storageKey); - if (!currencyMap[currencyCode]) { - const storageData = await chrome.storage.local.get(storageKey); - console.log("btc", storageData); - if (!storageData || Object.keys(storageData).length === 0) { - updateExchangeRateToLocalStorage(currencyCode); - } - const { [storageKey]: exchangeRate } = await chrome.storage.local.get( - storageKey - ); - console.log("btc", exchangeRate); - - currencyMap[currencyCode] = exchangeRate; - } - - const exchangeRate = currencyMap[currencyCode]; - - var originalText = element.textContent; - var price = currency(originalText, { symbol: matches[1], precision: 2, decimal: "," }); - console.log("btc", price, exchangeRate); - var bitcoinPrice = price / exchangeRate; - var satoshisPrice = bitcoinPrice * 100000000; - console.log("btc", price, bitcoinPrice, satoshisPrice); - element.textContent = bitcoinPrice.toFixed(8) + " BTC"; - element.setAttribute("data-original", originalText); - element.setAttribute("data-bitcoin", bitcoinPrice.toFixed(8) + " BTC"); - element.setAttribute( - "data-satoshis", - satoshisPrice - .toFixed(0) - .toString() - .replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " Sats" - ); - element.setAttribute("data-toggle", "BTC"); - element.addEventListener("mouseover", function (event) { - event.target.textContent = event.target.getAttribute("data-original"); - }); - element.addEventListener("mouseout", function (event) { - var currentToggle = event.target.getAttribute("data-toggle"); - if (currentToggle == "BTC") { - event.target.textContent = event.target.getAttribute("data-satoshis"); - event.target.setAttribute("data-toggle", "SATS"); - } else { - event.target.textContent = event.target.getAttribute("data-bitcoin"); - event.target.setAttribute("data-toggle", "BTC"); - } - }); - } -} - -async function updateExchangeRateToLocalStorage(currency = "USD") { - let data; - var endpoint = `https://api.coindesk.com/v1/bpi/currentprice/${currency}.json`; - - try { - const response = await fetch(endpoint); - data = await response.json(); - } catch (error) { - console.error(error); - } - - if (!data?.bpi?.[currency]) { - console.error( - `Error: Exchange rate data not found in API response (${currency})` - ); - return; - } - - const storageData = {}; - - console.log("btc", data); - storageData[`${currency}-exchangeRate`] = data.bpi[currency].rate_float; - storageData[`${currency}-exchangeRateTimestamp`] = Date.now(); - - await chrome.storage.local.set(storageData); -} - -convertPrices(); - -// TODO: this needs improvment -// setInterval(updateExchangeRateToLocalStorage, 3600 * 1000); diff --git a/bun.lockb b/bun.lockb index 94528d4..935a368 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/images/icon-128.png b/images/icon-128.png index 2b330ab..db26327 100644 Binary files a/images/icon-128.png and b/images/icon-128.png differ diff --git a/images/icon-16.png b/images/icon-16.png index 0d31fa7..244afcb 100644 Binary files a/images/icon-16.png and b/images/icon-16.png differ diff --git a/images/icon-32.png b/images/icon-32.png index 1509b03..0970799 100644 Binary files a/images/icon-32.png and b/images/icon-32.png differ diff --git a/images/icon-48.png b/images/icon-48.png index aa53868..c733d16 100644 Binary files a/images/icon-48.png and b/images/icon-48.png differ diff --git a/manifest.json b/manifest.json index b38d46e..550e861 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, "name": "Bitcoin Companion", - "version": "1.0", - "description": "Add the reading time to Chrome Extension documentation articles", + "version": "0.1.0", + "description": "Adds a tooltip on hover to all money amount with the current exchange rate in bitcoin bits", "permissions": ["storage"], "icons": { "16": "images/icon-16.png", @@ -13,7 +13,7 @@ "content_scripts": [ { "matches": [""], - "js": ["btc.js"], + "js": ["dist.js"], "run_at": "document_end" } ] diff --git a/node_modules/currency.js/dist/currency.d.ts b/node_modules/currency.js/dist/currency.d.ts deleted file mode 100644 index 770fb92..0000000 --- a/node_modules/currency.js/dist/currency.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -declare module 'currency.js' { - namespace currency { - type Any = number | string | currency; - type Format = (currency?: currency, opts?: Options) => string; - interface Constructor { - (value: currency.Any, opts?: currency.Options): currency, - new(value: currency.Any, opts?: currency.Options): currency - } - interface Options { - symbol?: string, - separator?: string, - decimal?: string, - errorOnInvalid?: boolean, - precision?: number, - increment?: number, - useVedic?: boolean, - pattern?: string, - negativePattern?: string, - format?: currency.Format, - fromCents?: boolean - } - } - - interface currency { - add(number: currency.Any): currency; - subtract(number: currency.Any): currency; - multiply(number: currency.Any): currency; - divide(number: currency.Any): currency; - distribute(count: number): Array; - dollars(): number; - cents(): number; - format(opts?: currency.Options | currency.Format): string; - toString(): string; - toJSON(): number; - readonly intValue: number; - readonly value: number; - } - - const currency: currency.Constructor; - - export = currency; -} diff --git a/node_modules/currency.js/dist/currency.es.js b/node_modules/currency.js/dist/currency.es.js deleted file mode 100644 index 96066e6..0000000 --- a/node_modules/currency.js/dist/currency.es.js +++ /dev/null @@ -1,254 +0,0 @@ -/*! - * currency.js - v2.0.4 - * http://scurker.github.io/currency.js - * - * Copyright (c) 2021 Jason Wilson - * Released under MIT license - */ - -var defaults = { - symbol: '$', - separator: ',', - decimal: '.', - errorOnInvalid: false, - precision: 2, - pattern: '!#', - negativePattern: '-!#', - format: format, - fromCents: false -}; - -var round = function round(v) { - return Math.round(v); -}; - -var pow = function pow(p) { - return Math.pow(10, p); -}; - -var rounding = function rounding(value, increment) { - return round(value / increment) * increment; -}; - -var groupRegex = /(\d)(?=(\d{3})+\b)/g; -var vedicRegex = /(\d)(?=(\d\d)+\d\b)/g; -/** - * Create a new instance of currency.js - * @param {number|string|currency} value - * @param {object} [opts] - */ - -function currency(value, opts) { - var that = this; - - if (!(that instanceof currency)) { - return new currency(value, opts); - } - - var settings = Object.assign({}, defaults, opts), - precision = pow(settings.precision), - v = parse(value, settings); - that.intValue = v; - that.value = v / precision; // Set default incremental value - - settings.increment = settings.increment || 1 / precision; // Support vedic numbering systems - // see: https://en.wikipedia.org/wiki/Indian_numbering_system - - if (settings.useVedic) { - settings.groups = vedicRegex; - } else { - settings.groups = groupRegex; - } // Intended for internal usage only - subject to change - - - this.s = settings; - this.p = precision; -} - -function parse(value, opts) { - var useRounding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - var v = 0, - decimal = opts.decimal, - errorOnInvalid = opts.errorOnInvalid, - decimals = opts.precision, - fromCents = opts.fromCents, - precision = pow(decimals), - isNumber = typeof value === 'number', - isCurrency = value instanceof currency; - - if (isCurrency && fromCents) { - return value.intValue; - } - - if (isNumber || isCurrency) { - v = isCurrency ? value.value : value; - } else if (typeof value === 'string') { - var regex = new RegExp('[^-\\d' + decimal + ']', 'g'), - decimalString = new RegExp('\\' + decimal, 'g'); - v = value.replace(/\((.*)\)/, '-$1') // allow negative e.g. (1.99) - .replace(regex, '') // replace any non numeric values - .replace(decimalString, '.'); // convert any decimal values - - v = v || 0; - } else { - if (errorOnInvalid) { - throw Error('Invalid Input'); - } - - v = 0; - } - - if (!fromCents) { - v *= precision; // scale number to integer value - - v = v.toFixed(4); // Handle additional decimal for proper rounding. - } - - return useRounding ? round(v) : v; -} -/** - * Formats a currency object - * @param currency - * @param {object} [opts] - */ - - -function format(currency, settings) { - var pattern = settings.pattern, - negativePattern = settings.negativePattern, - symbol = settings.symbol, - separator = settings.separator, - decimal = settings.decimal, - groups = settings.groups, - split = ('' + currency).replace(/^-/, '').split('.'), - dollars = split[0], - cents = split[1]; - return (currency.value >= 0 ? pattern : negativePattern).replace('!', symbol).replace('#', dollars.replace(groups, '$1' + separator) + (cents ? decimal + cents : '')); -} - -currency.prototype = { - /** - * Adds values together. - * @param {number} number - * @returns {currency} - */ - add: function add(number) { - var intValue = this.intValue, - _settings = this.s, - _precision = this.p; - return currency((intValue += parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings); - }, - - /** - * Subtracts value. - * @param {number} number - * @returns {currency} - */ - subtract: function subtract(number) { - var intValue = this.intValue, - _settings = this.s, - _precision = this.p; - return currency((intValue -= parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings); - }, - - /** - * Multiplies values. - * @param {number} number - * @returns {currency} - */ - multiply: function multiply(number) { - var intValue = this.intValue, - _settings = this.s; - return currency((intValue *= number) / (_settings.fromCents ? 1 : pow(_settings.precision)), _settings); - }, - - /** - * Divides value. - * @param {number} number - * @returns {currency} - */ - divide: function divide(number) { - var intValue = this.intValue, - _settings = this.s; - return currency(intValue /= parse(number, _settings, false), _settings); - }, - - /** - * Takes the currency amount and distributes the values evenly. Any extra pennies - * left over from the distribution will be stacked onto the first set of entries. - * @param {number} count - * @returns {array} - */ - distribute: function distribute(count) { - var intValue = this.intValue, - _precision = this.p, - _settings = this.s, - distribution = [], - split = Math[intValue >= 0 ? 'floor' : 'ceil'](intValue / count), - pennies = Math.abs(intValue - split * count), - precision = _settings.fromCents ? 1 : _precision; - - for (; count !== 0; count--) { - var item = currency(split / precision, _settings); // Add any left over pennies - - pennies-- > 0 && (item = item[intValue >= 0 ? 'add' : 'subtract'](1 / precision)); - distribution.push(item); - } - - return distribution; - }, - - /** - * Returns the dollar value. - * @returns {number} - */ - dollars: function dollars() { - return ~~this.value; - }, - - /** - * Returns the cent value. - * @returns {number} - */ - cents: function cents() { - var intValue = this.intValue, - _precision = this.p; - return ~~(intValue % _precision); - }, - - /** - * Formats the value as a string according to the formatting settings. - * @param {boolean} useSymbol - format with currency symbol - * @returns {string} - */ - format: function format(options) { - var _settings = this.s; - - if (typeof options === 'function') { - return options(this, _settings); - } - - return _settings.format(this, Object.assign({}, _settings, options)); - }, - - /** - * Formats the value as a string according to the formatting settings. - * @returns {string} - */ - toString: function toString() { - var intValue = this.intValue, - _precision = this.p, - _settings = this.s; - return rounding(intValue / _precision, _settings.increment).toFixed(_settings.precision); - }, - - /** - * Value for JSON serialization. - * @returns {float} - */ - toJSON: function toJSON() { - return this.value; - } -}; - -export default currency; diff --git a/node_modules/currency.js/dist/currency.js b/node_modules/currency.js/dist/currency.js deleted file mode 100644 index cca7854..0000000 --- a/node_modules/currency.js/dist/currency.js +++ /dev/null @@ -1,256 +0,0 @@ -/*! - * currency.js - v2.0.4 - * http://scurker.github.io/currency.js - * - * Copyright (c) 2021 Jason Wilson - * Released under MIT license - */ - -'use strict'; - -var defaults = { - symbol: '$', - separator: ',', - decimal: '.', - errorOnInvalid: false, - precision: 2, - pattern: '!#', - negativePattern: '-!#', - format: format, - fromCents: false -}; - -var round = function round(v) { - return Math.round(v); -}; - -var pow = function pow(p) { - return Math.pow(10, p); -}; - -var rounding = function rounding(value, increment) { - return round(value / increment) * increment; -}; - -var groupRegex = /(\d)(?=(\d{3})+\b)/g; -var vedicRegex = /(\d)(?=(\d\d)+\d\b)/g; -/** - * Create a new instance of currency.js - * @param {number|string|currency} value - * @param {object} [opts] - */ - -function currency(value, opts) { - var that = this; - - if (!(that instanceof currency)) { - return new currency(value, opts); - } - - var settings = Object.assign({}, defaults, opts), - precision = pow(settings.precision), - v = parse(value, settings); - that.intValue = v; - that.value = v / precision; // Set default incremental value - - settings.increment = settings.increment || 1 / precision; // Support vedic numbering systems - // see: https://en.wikipedia.org/wiki/Indian_numbering_system - - if (settings.useVedic) { - settings.groups = vedicRegex; - } else { - settings.groups = groupRegex; - } // Intended for internal usage only - subject to change - - - this.s = settings; - this.p = precision; -} - -function parse(value, opts) { - var useRounding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - var v = 0, - decimal = opts.decimal, - errorOnInvalid = opts.errorOnInvalid, - decimals = opts.precision, - fromCents = opts.fromCents, - precision = pow(decimals), - isNumber = typeof value === 'number', - isCurrency = value instanceof currency; - - if (isCurrency && fromCents) { - return value.intValue; - } - - if (isNumber || isCurrency) { - v = isCurrency ? value.value : value; - } else if (typeof value === 'string') { - var regex = new RegExp('[^-\\d' + decimal + ']', 'g'), - decimalString = new RegExp('\\' + decimal, 'g'); - v = value.replace(/\((.*)\)/, '-$1') // allow negative e.g. (1.99) - .replace(regex, '') // replace any non numeric values - .replace(decimalString, '.'); // convert any decimal values - - v = v || 0; - } else { - if (errorOnInvalid) { - throw Error('Invalid Input'); - } - - v = 0; - } - - if (!fromCents) { - v *= precision; // scale number to integer value - - v = v.toFixed(4); // Handle additional decimal for proper rounding. - } - - return useRounding ? round(v) : v; -} -/** - * Formats a currency object - * @param currency - * @param {object} [opts] - */ - - -function format(currency, settings) { - var pattern = settings.pattern, - negativePattern = settings.negativePattern, - symbol = settings.symbol, - separator = settings.separator, - decimal = settings.decimal, - groups = settings.groups, - split = ('' + currency).replace(/^-/, '').split('.'), - dollars = split[0], - cents = split[1]; - return (currency.value >= 0 ? pattern : negativePattern).replace('!', symbol).replace('#', dollars.replace(groups, '$1' + separator) + (cents ? decimal + cents : '')); -} - -currency.prototype = { - /** - * Adds values together. - * @param {number} number - * @returns {currency} - */ - add: function add(number) { - var intValue = this.intValue, - _settings = this.s, - _precision = this.p; - return currency((intValue += parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings); - }, - - /** - * Subtracts value. - * @param {number} number - * @returns {currency} - */ - subtract: function subtract(number) { - var intValue = this.intValue, - _settings = this.s, - _precision = this.p; - return currency((intValue -= parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings); - }, - - /** - * Multiplies values. - * @param {number} number - * @returns {currency} - */ - multiply: function multiply(number) { - var intValue = this.intValue, - _settings = this.s; - return currency((intValue *= number) / (_settings.fromCents ? 1 : pow(_settings.precision)), _settings); - }, - - /** - * Divides value. - * @param {number} number - * @returns {currency} - */ - divide: function divide(number) { - var intValue = this.intValue, - _settings = this.s; - return currency(intValue /= parse(number, _settings, false), _settings); - }, - - /** - * Takes the currency amount and distributes the values evenly. Any extra pennies - * left over from the distribution will be stacked onto the first set of entries. - * @param {number} count - * @returns {array} - */ - distribute: function distribute(count) { - var intValue = this.intValue, - _precision = this.p, - _settings = this.s, - distribution = [], - split = Math[intValue >= 0 ? 'floor' : 'ceil'](intValue / count), - pennies = Math.abs(intValue - split * count), - precision = _settings.fromCents ? 1 : _precision; - - for (; count !== 0; count--) { - var item = currency(split / precision, _settings); // Add any left over pennies - - pennies-- > 0 && (item = item[intValue >= 0 ? 'add' : 'subtract'](1 / precision)); - distribution.push(item); - } - - return distribution; - }, - - /** - * Returns the dollar value. - * @returns {number} - */ - dollars: function dollars() { - return ~~this.value; - }, - - /** - * Returns the cent value. - * @returns {number} - */ - cents: function cents() { - var intValue = this.intValue, - _precision = this.p; - return ~~(intValue % _precision); - }, - - /** - * Formats the value as a string according to the formatting settings. - * @param {boolean} useSymbol - format with currency symbol - * @returns {string} - */ - format: function format(options) { - var _settings = this.s; - - if (typeof options === 'function') { - return options(this, _settings); - } - - return _settings.format(this, Object.assign({}, _settings, options)); - }, - - /** - * Formats the value as a string according to the formatting settings. - * @returns {string} - */ - toString: function toString() { - var intValue = this.intValue, - _precision = this.p, - _settings = this.s; - return rounding(intValue / _precision, _settings.increment).toFixed(_settings.precision); - }, - - /** - * Value for JSON serialization. - * @returns {float} - */ - toJSON: function toJSON() { - return this.value; - } -}; - -module.exports = currency; diff --git a/node_modules/currency.js/dist/currency.js.flow b/node_modules/currency.js/dist/currency.js.flow deleted file mode 100644 index f3e0edf..0000000 --- a/node_modules/currency.js/dist/currency.js.flow +++ /dev/null @@ -1,37 +0,0 @@ -// @flow -declare type $currency$any = number | string | currency; - -declare type formatFunction = (currency: currency, options: $currency$opts) => string; - -declare type $currency$opts = { - symbol?: string, - separator?: string, - decimal?: string, - errorOnInvalid?: boolean, - precision?: number, - increment?: number, - useVedic?: boolean, - pattern?: string, - negativePattern?: string, - format?: formatFunction, - fromCents?: boolean -} - -declare class currency { - static (value: $currency$any, opts?: $currency$opts): currency, - constructor(value: $currency$any, opts?: $currency$opts): currency, - add(number: $currency$any): currency; - subtract(number: $currency$any): currency; - multiply(number: $currency$any): currency; - divide(number: $currency$any): currency; - distribute(count: number): Array; - dollars(): number; - cents(): number; - format(options?: $currency$opts | formatFunction): string; - toString(): string; - toJSON(): number; - +intValue: number; - +value: number; -} - -declare module.exports: typeof currency; diff --git a/node_modules/currency.js/dist/currency.min.js b/node_modules/currency.js/dist/currency.min.js deleted file mode 100644 index 1da42fd..0000000 --- a/node_modules/currency.js/dist/currency.min.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - currency.js - v2.0.4 - http://scurker.github.io/currency.js - - Copyright (c) 2021 Jason Wilson - Released under MIT license -*/ -(function(e,g){"object"===typeof exports&&"undefined"!==typeof module?module.exports=g():"function"===typeof define&&define.amd?define(g):(e=e||self,e.currency=g())})(this,function(){function e(b,a){if(!(this instanceof e))return new e(b,a);a=Object.assign({},m,a);var d=Math.pow(10,a.precision);this.intValue=b=g(b,a);this.value=b/d;a.increment=a.increment||1/d;a.groups=a.useVedic?n:p;this.s=a;this.p=d}function g(b,a){var d=2 2 && arguments[2] !== undefined ? arguments[2] : true; - var v = 0, - decimal = opts.decimal, - errorOnInvalid = opts.errorOnInvalid, - decimals = opts.precision, - fromCents = opts.fromCents, - precision = pow(decimals), - isNumber = typeof value === 'number', - isCurrency = value instanceof currency; - - if (isCurrency && fromCents) { - return value.intValue; - } - - if (isNumber || isCurrency) { - v = isCurrency ? value.value : value; - } else if (typeof value === 'string') { - var regex = new RegExp('[^-\\d' + decimal + ']', 'g'), - decimalString = new RegExp('\\' + decimal, 'g'); - v = value.replace(/\((.*)\)/, '-$1') // allow negative e.g. (1.99) - .replace(regex, '') // replace any non numeric values - .replace(decimalString, '.'); // convert any decimal values - - v = v || 0; - } else { - if (errorOnInvalid) { - throw Error('Invalid Input'); - } - - v = 0; - } - - if (!fromCents) { - v *= precision; // scale number to integer value - - v = v.toFixed(4); // Handle additional decimal for proper rounding. - } - - return useRounding ? round(v) : v; - } - /** - * Formats a currency object - * @param currency - * @param {object} [opts] - */ - - - function format(currency, settings) { - var pattern = settings.pattern, - negativePattern = settings.negativePattern, - symbol = settings.symbol, - separator = settings.separator, - decimal = settings.decimal, - groups = settings.groups, - split = ('' + currency).replace(/^-/, '').split('.'), - dollars = split[0], - cents = split[1]; - return (currency.value >= 0 ? pattern : negativePattern).replace('!', symbol).replace('#', dollars.replace(groups, '$1' + separator) + (cents ? decimal + cents : '')); - } - - currency.prototype = { - /** - * Adds values together. - * @param {number} number - * @returns {currency} - */ - add: function add(number) { - var intValue = this.intValue, - _settings = this.s, - _precision = this.p; - return currency((intValue += parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings); - }, - - /** - * Subtracts value. - * @param {number} number - * @returns {currency} - */ - subtract: function subtract(number) { - var intValue = this.intValue, - _settings = this.s, - _precision = this.p; - return currency((intValue -= parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings); - }, - - /** - * Multiplies values. - * @param {number} number - * @returns {currency} - */ - multiply: function multiply(number) { - var intValue = this.intValue, - _settings = this.s; - return currency((intValue *= number) / (_settings.fromCents ? 1 : pow(_settings.precision)), _settings); - }, - - /** - * Divides value. - * @param {number} number - * @returns {currency} - */ - divide: function divide(number) { - var intValue = this.intValue, - _settings = this.s; - return currency(intValue /= parse(number, _settings, false), _settings); - }, - - /** - * Takes the currency amount and distributes the values evenly. Any extra pennies - * left over from the distribution will be stacked onto the first set of entries. - * @param {number} count - * @returns {array} - */ - distribute: function distribute(count) { - var intValue = this.intValue, - _precision = this.p, - _settings = this.s, - distribution = [], - split = Math[intValue >= 0 ? 'floor' : 'ceil'](intValue / count), - pennies = Math.abs(intValue - split * count), - precision = _settings.fromCents ? 1 : _precision; - - for (; count !== 0; count--) { - var item = currency(split / precision, _settings); // Add any left over pennies - - pennies-- > 0 && (item = item[intValue >= 0 ? 'add' : 'subtract'](1 / precision)); - distribution.push(item); - } - - return distribution; - }, - - /** - * Returns the dollar value. - * @returns {number} - */ - dollars: function dollars() { - return ~~this.value; - }, - - /** - * Returns the cent value. - * @returns {number} - */ - cents: function cents() { - var intValue = this.intValue, - _precision = this.p; - return ~~(intValue % _precision); - }, - - /** - * Formats the value as a string according to the formatting settings. - * @param {boolean} useSymbol - format with currency symbol - * @returns {string} - */ - format: function format(options) { - var _settings = this.s; - - if (typeof options === 'function') { - return options(this, _settings); - } - - return _settings.format(this, Object.assign({}, _settings, options)); - }, - - /** - * Formats the value as a string according to the formatting settings. - * @returns {string} - */ - toString: function toString() { - var intValue = this.intValue, - _precision = this.p, - _settings = this.s; - return rounding(intValue / _precision, _settings.increment).toFixed(_settings.precision); - }, - - /** - * Value for JSON serialization. - * @returns {float} - */ - toJSON: function toJSON() { - return this.value; - } - }; - - return currency; - -}))); diff --git a/node_modules/currency.js/license b/node_modules/currency.js/license deleted file mode 100644 index 6a5ef5d..0000000 --- a/node_modules/currency.js/license +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Jason Wilson - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/node_modules/currency.js/package.json b/node_modules/currency.js/package.json deleted file mode 100644 index 28a00ee..0000000 --- a/node_modules/currency.js/package.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "name": "currency.js", - "homepage": "http://scurker.github.io/currency.js", - "version": "2.0.4", - "description": "A small, lightweight javascript library for working with currency values.", - "main": "dist/currency.js", - "module": "dist/currency.es.js", - "js:next": "dist/currency.es.js", - "browser": "dist/currency.min.js", - "engines": { - "node": ">=4" - }, - "directories": { - "test": "test" - }, - "scripts": { - "build": "npm-run-all clean transpile copy-typescript-definition copy-flow-definition", - "build:docs": "npm-run-all clean:docs compile:docs minify:docs", - "clean": "rm -rf dist/*", - "clean:docs": "rm -rf docs/build", - "compile:docs": "npx babel-node --presets=@babel/preset-env ./docs/src/build-docs.js", - "coverage": "BABEL_ENV=test nyc ava ./test/test.js", - "coverage:report": "nyc report --reporter=text-lcov > lcov.info", - "copy-typescript-definition": "cp -f src/currency.d.ts dist", - "copy-flow-definition": "cp -f src/currency.js.flow dist", - "lint": "eslint .", - "minify:docs": "html-minifier --input-dir ./docs/build --output-dir ./docs/build --file-ext html --collapse-whitespace --decode-entities --minify-css --minify-js", - "prepare": "npm run build", - "pretest:js": "npm run transpile:es", - "pretest:typescript": "npm run copy-typescript-definition", - "pretest:flow": "npm run copy-flow-definition", - "transpile:es": "rollup -c ./config/rollup.config.js", - "transpile:umd": "rollup -c ./config/rollup.umd.js", - "transpile": "npm-run-all transpile:*", - "test": "npm-run-all test:typescript test:flow test:js", - "test:js": "BABEL_ENV=test ava ./test/test.js", - "test:typescript": "tsc -p ./test --noEmit", - "test:flow": "flow" - }, - "repository": { - "type": "git", - "url": "git://github.com/scurker/currency.js.git" - }, - "files": [ - "dist" - ], - "typings": "./dist/currency.d.ts", - "keywords": [ - "currency", - "money", - "utilities", - "accounting", - "format", - "number", - "parse", - "precision", - "decimal" - ], - "author": "Jason Wilson", - "license": "MIT", - "bugs": { - "url": "https://github.com/scurker/currency.js/issues" - }, - "devDependencies": { - "@babel/cli": "^7.10.4", - "@babel/core": "^7.10.4", - "@babel/node": "^7.10.4", - "@babel/preset-env": "^7.10.4", - "@babel/preset-flow": "^7.10.4", - "@babel/register": "^7.5.5", - "@scurker/eslint-config": "^1.1.5", - "ava": "^2.2.0", - "cheerio": "^1.0.0-rc", - "eslint": "^7.4.0", - "flow-bin": "^0.151.0", - "google-closure-compiler-js": "^20200719.0.0", - "gzip-size": "^6.0.0", - "handlebars": "^4.7.6", - "highlight.js": "^10.0.0", - "html-minifier": "^4.0.0", - "matchdep": "^2.0.0", - "metalsmith": "^2.3.0", - "metalsmith-ignore": "^1.0.0", - "metalsmith-markdown": "^1.3.0", - "minimatch": "^3.0.4", - "npm-run-all": "^4.1.5", - "nyc": "^15.1.0", - "pretty-bytes": "^5.3.0", - "rollup": "^2.21.0", - "rollup-plugin-babel": "^4.4.0", - "sinon": "^10.0.0", - "typescript": "^4.0.2" - }, - "nyc": { - "include": [ - "dist/**/*.js" - ] - } -} diff --git a/node_modules/currency.js/readme.md b/node_modules/currency.js/readme.md deleted file mode 100644 index dd69ff4..0000000 --- a/node_modules/currency.js/readme.md +++ /dev/null @@ -1,179 +0,0 @@ -
- -![currency.js logo](https://user-images.githubusercontent.com/1062039/31397824-9dfa15f0-adac-11e7-9869-fb20746e90c1.png) - -# currency.js - -[![Build Status](https://github.com/scurker/currency.js/actions/workflows/ci.yml/badge.svg)](https://github.com/scurker/currency.js/actions/workflows/ci.yml) -[![Coverage Status](https://coveralls.io/repos/scurker/currency.js/badge.svg?branch=master&service=github)](https://coveralls.io/github/scurker/currency.js?branch=master) -[![npm](https://img.shields.io/npm/v/currency.js.svg?style=flat)](https://www.npmjs.com/package/currency.js) -[![gzip size](http://img.badgesize.io/https://unpkg.com/currency.js/dist/currency.min.js?compression=gzip)](https://unpkg.com/currency.js/dist/currency.min.js) - -
- -*currency.js* is a lightweight ~1kb javascript library for working with currency values. It was built to work around floating point issues in javascript. This [talk by Bartek Szopka](http://www.youtube.com/watch?v=MqHDDtVYJRI) explains in detail why javascript has floating point issues. - -currency.js works with values as integers behind the scenes, resolving some of the most basic precision problems. - -```javascript -2.51 + .01; // 2.5199999999999996 -currency(2.51).add(.01); // 2.52 - -2.52 - .01; // 2.5100000000000002 -currency(2.52).subtract(.01); // 2.51 -``` - -This should work for most *reasonable* values of currencies. As long as your currency values are less than 253 (in cents) or 90,071,992,547,409.91 you should be okay. - -### Features - -* *0 dependencies!* -* Immutable -* Flexible formatting options -* Handles *any* type of currency input, strings, numbers, or another currency instance - -### Installation - -With [npm](https://www.npmjs.com/): - -```sh -npm install --save currency.js -``` - -With [yarn](https://yarnpkg.com): - -```sh -yarn add currency.js -``` - -Via cdn: - -```html - -``` - -Need the latest cutting edge? All commits on master are tagged with `next` on npm: - -```sh -npm install --save currency.js@next -``` - -### Usage - -Currency will accept numbers, strings, or the currency object itself as values. - -```javascript -currency(123); // 123.00 -currency(1.23); // 1.23 -currency("1.23") // 1.23 -currency("$12.30") // 12.30 - -var value = currency("123.45"); -currency(value); // 123.45 -``` - -Currency accepts decimal values (i.e. `1.23`) with a default precision of 2, but can accept a minor currency unit (e.g. cents in a dollar). This will respect the precision option when parsing. - -```javascript -currency(123, { fromCents: true }); // 1.23 -currency('123', { fromCents: true }); // 1.23 -currency(123, { fromCents: true, precision: 0 }); // 123 -currency(123, { fromCents: true, precision: 3 }); // 0.123 -``` - -There's various arithmetic methods that help take the guesswork out of trying to resolve floating point problems. - -```javascript -currency(123.50).add(0.23); // 123.73 -currency(5.00).subtract(0.50); // 4.50 -currency(45.25).multiply(3); // 135.75 -currency(1.12).distribute(5); // [0.23, 0.23, 0.22, 0.22, 0.22] -``` - -There's even a built in formatter that will automatically place comma delimiters in the right place. - -```javascript -currency("2,573,693.75").add("100,275.50").format(); // "$2,673,969.25" -currency("1,237.72").subtract(300).format(); // "$937.72" -``` - -You can also change the format, localizing the decimal and/or delimiter to your locale. - -```javascript -var euro = value => currency(value, { symbol: "€", separator: ".", decimal: "," }); -euro("2.573.693,75").add("100.275,50").format(); // "€2.673.969,25" -euro("1.237,72").subtract(300).format(); // "€937,72" -``` - -### Options - -*currency.js* comes with its own set of default options conforming to USD. You can customize these according to your locale. - -`symbol` *default*: `$`
-Currency symbol included when calling `currency.format()`. - -`separator` *default*: `,`
-Separator dividing the number groups when calling `currency.format()`. - -`decimal` *default*: `.`
-Decimal used when calling `currency.format()`. - -`precision` *default*: `2`
-Number of decimal places to store as cents. - -`pattern` *default*: `!#`
-Allows you to customize the format pattern using `!` as replacement for the currency symbol and `#` as replacement for the currency amount. - -`negativePattern` *default*: `-!#`
-Allows you to customize the negative format pattern using `!` as replacement for the currency symbol and `#` as replacement for the currency amount. - -`format` *default* `null` -Allows you to customize the format of the currency when calling `currency.format()`. `format` passes in the `currency` object as well as the `options` object to the function and expects a string to be returned. Use this when the provided formatting options do not meet your needs. - -`errorOnInvalid` *default*: `false`
-If an invalid value such as `null` or `undefined` is passed in, will throw an error. - -`increment` *default*: `null`
-When implementing a currency that implements rounding, setting the increment value will allow you to set the closest increment to round the display value to. `currency(1.48, { increment: .05 }); // => 1.50` - -`useVedic` *default*: `false`
-Formats number groupings using the Indian Numbering System, i.e. `10,00,000.00` - -`fromCents` *default*: `false`
-Parse the amount value as a minor currency unit (e.g. cents in a dollar) instead of dollars. - -> View more examples and full documentation at [https://currency.js.org](https://currency.js.org). - -### Internationalization Examples - -```js -currency(1.23, { separator: " ", decimal: ",", symbol: "€" }); -``` - -If you need to work with multiple currency values, the easiest way is to setup factory functions with your required currency settings. - -```js -const USD = value => currency(value, { symbol: "$", precision: 2 }); -const JPY = value => currency(value, { symbol: "¥", precision: 0 }); -const GAS = value => currency(value, { precision: 3 }); - -USD(1234.56).format(); // "$1,234.56" -JPY(1234.56).format(); // "¥1,235" -GAS(1234.56).format(); // "$1,234.560" -``` - -## Add-ons - -* [babel-plugin-transform-currency-operators](https://github.com/scurker/babel-plugin-transform-currency-operators): An experimental babel plugin for transforming currency operations `currency(1.23) + 4.56` to `currency(1.23).add(4.56)`. - -## Other Libraries - -Maybe currency.js isn't the right fit for your needs. Check out some of these other fine libraries: - -* [accounting.js](https://github.com/openexchangerates/accounting.js) -* [dinero.js](https://github.com/sarahdayan/dinero.js) -* [walletjs](https://github.com/dleitee/walletjs) - -## License - -[MIT](/license) diff --git a/package.json b/package.json index 23d231e..ccbf052 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,18 @@ { - "name": "name", - "description": "description", - "authors": "author", - "version": "1.0.0", - "main": "pathToMain", - "dependencies": { - "currency.js": "^2.0.4" - } -} \ No newline at end of file + "name": "name", + "description": "description", + "authors": "author", + "version": "1.0.0", + "main": "pathToMain", + "dependencies": { + "currency.js": "^2.0.4", + "mathjs": "^14.0.0" + }, + "devDependencies": { + "@types/chrome": "^0.0.287", + "@types/bun": "latest" + }, + "scripts": { + "pack": "bun build --minify src/btc.js > dist.js && zip ext.zip dist.js manifest.json images/*" + } +} diff --git a/src/btc.ts b/src/btc.ts new file mode 100644 index 0000000..ccd9350 --- /dev/null +++ b/src/btc.ts @@ -0,0 +1,108 @@ +import currency from "currency.js"; +import { CURRENCIES, REGEX } from "model"; + +async function convertPrices() { + const currencyMap: Record = {}; + const elements = document.querySelectorAll("*"); + const existingTooltipElements: HTMLElement[] = []; + + for (const e of elements) { + const element = e as HTMLElement; + const matches = element.textContent?.match(REGEX); + if (!matches) continue; + if (element.dataset.processed === "true") continue; + + element.dataset.processed = "true"; + + const currencyCode = CURRENCIES[matches[1] as keyof typeof CURRENCIES].code; + const storageKey = `${currencyCode}-exchangeRate`; + + if (!currencyMap[currencyCode]) { + const storageData = await chrome.storage.local.get(storageKey); + if (!storageData || Object.keys(storageData).length === 0) { + await updateExchangeRateToLocalStorage(currencyCode); + } + const { [storageKey]: exchangeRate } = await chrome.storage.local.get( + storageKey + ); + currencyMap[currencyCode] = exchangeRate; + } + + const exchangeRate = currencyMap[currencyCode]; + const originalText = element.textContent!; + const price = currency(originalText, { + symbol: matches[1], + precision: 8, + decimal: ",", + }); + const bitcoinPrice = price.divide(exchangeRate); + const bitsPrice = currency(bitcoinPrice.multiply(1e6), { precision: 2 }); + + const tooltip = bitsPrice.format({ + symbol: "bits", + decimal: ".", + separator: " ", + pattern: "# !", + }); + + // Add tooltip + element.dataset.btc = tooltip; + + existingTooltipElements.push(element); + } + + await Promise.resolve(); + existingTooltipElements.forEach((e) => { + if (existingTooltipElements.some((it) => it.contains(e) && it !== e)) { + e.dataset.btc = ""; + } + }); +} + +async function updateExchangeRateToLocalStorage(currency: string = "USD") { + const { lastUpdate } = await chrome.storage.local.get( + `${currency}-exchangeRateTimestamp` + ); + + if (lastUpdate && new Date().getTime() - lastUpdate < 3600 * 1000) return; + + try { + const response = await fetch( + `https://api.coindesk.com/v1/bpi/currentprice/${currency}.json` + ); + const data = await response.json(); + if (!data?.bpi?.[currency]) + throw new Error(`Exchange rate data not found for ${currency}`); + await chrome.storage.local.set({ + [`${currency}-exchangeRate`]: data.bpi[currency].rate_float, + [`${currency}-exchangeRateTimestamp`]: Date.now(), + }); + } catch (error) { + console.error(error); + } +} + +// Create a style element +const style = document.createElement("style"); + +// Add CSS rules +style.textContent = ` +[data-btc]:hover::after { + display: block; + position: absolute; + content: attr(data-btc); + border: 1px solid black; + background: #eee; + padding: .25em; + z-index: 99; +} +`; + +// Append the style element to the document head +document.head.appendChild(style); + +const observer = new MutationObserver(convertPrices); +const config = { childList: true, subtree: true }; +observer.observe(document, config); + +setInterval(updateExchangeRateToLocalStorage, 300 * 1000); diff --git a/src/hpr.spec.ts b/src/hpr.spec.ts new file mode 100644 index 0000000..8cadb58 --- /dev/null +++ b/src/hpr.spec.ts @@ -0,0 +1,13 @@ +import { expect, test } from "bun:test"; +import { halvingPriceBand } from "hpr"; + +test("halvingPriceBand", () => { + expect(halvingPriceBand(54000, "2024-12-01")).toBeGreaterThanOrEqual(-0.25); + expect(halvingPriceBand(80000, "2024-12-01")).toBeLessThanOrEqual(0.21); + expect(halvingPriceBand(81000, "2024-12-01")).toBeGreaterThanOrEqual(0.21); + expect(halvingPriceBand(114000, "2024-12-01")).toBeLessThanOrEqual(0.57); + expect(halvingPriceBand(130000, "2024-12-01")).toBe("Yellow"); + expect(halvingPriceBand(200000, "2024-12-01")).toBe("Orange"); + expect(halvingPriceBand(250000, "2024-12-01")).toBe("Red"); + expect(halvingPriceBand(310000, "2024-12-01")).toBe("Black"); +}); diff --git a/src/hpr.ts b/src/hpr.ts new file mode 100644 index 0000000..7daf429 --- /dev/null +++ b/src/hpr.ts @@ -0,0 +1,72 @@ +export function getBandName(index: number) { + if (index < -0.5) return "White"; + if (-0.5 <= index && index <= 0.5) return "Blue"; + if (0.5 <= index && index <= 1.5) return "Green"; + if (1.5 <= index && index <= 2.5) return "Yellow"; + if (2.5 <= index && index <= 3.5) return "Orange"; + if (3.5 <= index && index <= 4.5) return "Red"; + return "Black"; +} + +// Helper function to fit a logarithmic regression model and return the parameters +function fitLogRegression( + xData: number[], + yData: number[] +): { a: number; b: number } { + const n = xData.length; + let sumX = 0, + sumY = 0, + sumXY = 0, + sumX2 = 0; + + for (let i = 0; i < n; i++) { + sumX += Math.log(xData[i]); + sumY += Math.log10(yData[i]); + sumXY += Math.log(xData[i]) * Math.log10(yData[i]); + sumX2 += Math.log(xData[i]) ** 2; + } + + // Solving for a and b + const a = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX ** 2); + const b = (sumY - a * sumX) / n; + + return { a, b }; +} + +export function halvingPriceBand(price: number, date: string): number { + const bands = ["Green", "Yellow", "Orange", "Red"]; + + // Halving dates and prices + const halvingDates = [ + new Date(2012, 10, 28), // November is month 10 (0-indexed) + new Date(2016, 6, 9), // July is month 6 (0-indexed) + new Date(2020, 4, 11), // May is month 4 (0-indexed) + ]; + const halvingPrices = [12.33, 651.94, 8591.65]; + + // Convert halving dates to days since Genesis Block (2009-01-03) + const genesisBlock = new Date(2009, 0, 3); + const xHalvingDates = halvingDates.map((date) => + Math.floor( + (date.getTime() - genesisBlock.getTime()) / (1000 * 60 * 60 * 24) + ) + ); + + // Fit the model parameters + const { a, b } = fitLogRegression(xHalvingDates, halvingPrices); + + // Parse input date and find xInput (days since Genesis Block) + const inputDate = new Date(date); + const xInput = Math.floor( + (inputDate.getTime() - genesisBlock.getTime()) / (1000 * 60 * 60 * 24) + ); + + // Calculate the HPR value using the fitted logarithmic model + const logPrice = a * Math.log(xInput) + b; + const hprPrice = Math.pow(10, logPrice); + + // Find the relative difference from the HPR value + const difference = price / hprPrice; + + return difference - 1; +} diff --git a/src/model.ts b/src/model.ts new file mode 100644 index 0000000..26c6430 --- /dev/null +++ b/src/model.ts @@ -0,0 +1,69 @@ +type Currency = { + code: string; + symbol: string; + decimal_digits: number; + rounding: number; + thousands_sep: string; + decimal_sep: string; + symbol_first: boolean; +}; + +// TODO: Find a currency json file +export const CURRENCIES = { + R$: { + code: "BRL", + symbol: "R$", + decimal_digits: 2, + rounding: 0, + thousands_sep: ".", + decimal_sep: ",", + symbol_first: true, + }, + "€": { + code: "EUR", + symbol: "€", + decimal_digits: 2, + rounding: 0, + thousands_sep: ".", + decimal_sep: ",", + symbol_first: true, + }, + "¥": { + code: "JPY", + symbol: "¥", + decimal_digits: 0, + rounding: 0, + thousands_sep: ",", + decimal_sep: ".", + symbol_first: true, + }, + "£": { + code: "GBP", + symbol: "£", + decimal_digits: 2, + rounding: 0, + thousands_sep: ".", + decimal_sep: ",", + symbol_first: true, + }, + $: { + code: "USD", + symbol: "$", + decimal_digits: 2, + rounding: 0, + thousands_sep: ",", + decimal_sep: ".", + symbol_first: true, + }, +}; + +const MONEY_TOKENS: string[] = Object.values(CURRENCIES) + .map((currency: Currency) => currency.symbol) + .concat(Object.values(CURRENCIES).map((currency: Currency) => currency.code)); + +export const REGEX = new RegExp( + `^(${MONEY_TOKENS.map((token) => token.replace("$", "\\$")).join( + "|" + )})\\s?((\\d+(\\.|,|')?)+\\d)$`, + "i" +); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f7cd4f0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "outDir": "dist", + "rootDir": "src", + "sourceMap": true, + "noImplicitAny": true, + "noEmit": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "baseUrl": "src" + }, + "include": ["src/**/*.ts"] +}