I have created this extension to detect currency values in GBP, USD, EUR, and JPY and then convert them to the other 3 currencies automatically.
The extension runs fine, but it uses the fallback_rates not the rates from the API. What am I doing wrong here?
"use strict";
/**
* "Convert" extension for PopClip - Currency Conversion with live exchange rates
* Uses the ExchangeRate API to get the latest rates
* #popclip
* name: Currency Converter
* icon: circle
* entitlements: [network]
*/
Object.defineProperty(exports, "__esModule", { value: true });
// Configuration - Replace with your actual API key
const API_KEY = "MY API KEY";
const API_URL = `https://v6.exchangerate-api.com/v6/${API_KEY}/latest/USD`;
// Fallback rates in case API is unavailable
const FALLBACK_RATES = {
USD: 1,
EUR: 0.9518,
GBP: 0.7898,
JPY: 149.1802
};
// Global variables for rates
let currentRates = FALLBACK_RATES;
let lastFetchTime = 0;
const CACHE_DURATION = 3600000; // 1 hour in milliseconds
/**
* Formats the output number with thousand separators and no decimal points.
* @param {number} num - The number to format.
* @returns {string} - The formatted number as a string.
*/
function formatOutput(num) {
return Intl.NumberFormat(undefined, {
useGrouping: true,
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(num);
}
/**
* Determines the multiplier based on the suffix.
* @param {string} suffix - The suffix indicating the scale (e.g., K, million, 千).
* @returns {number} - The multiplier corresponding to the suffix.
*/
function getMultiplier(suffix) {
if (!suffix) return 1;
switch (suffix.toLowerCase()) {
case 'k':
case 'thousand':
case '千':
return 1000;
case 'm':
case 'million':
return 1000000;
case '万':
return 10000;
case 'b':
case 'billion':
return 1000000000;
case '億':
return 100000000;
default:
return 1;
}
}
/**
* Constructs a regular expression to match currency expressions.
* @param {string} partial - The currency symbol or code.
* @returns {RegExp} - The constructed regular expression.
*/
function makeRegex(partial) {
return new RegExp(
`^\\s*(?:` +
`(${partial})\\s*(\\d{1,3}(?:,\\d{3})*(?:\\.\\d+)?|\\d+(?:\\.\\d+)?)` +
`(?:\\s*([KkMmBb]|thousand|million|billion|千|万|億))?` +
`|` +
`(\\d{1,3}(?:,\\d{3})*(?:\\.\\d+)?|\\d+(?:\\.\\d+)?)` +
`(?:\\s*([KkMmBb]|thousand|million|billion|千|万|億))?\\s*(${partial})` +
`)\\s*$`,
'ui'
);
}
/**
* Calculate all cross-rates based on the current rates
* @returns {Object} - Object with all cross rates
*/
function calculateCrossRates() {
const rates = currentRates;
return {
// USD conversions
usdToEur: rates.EUR,
usdToGbp: rates.GBP,
usdToJpy: rates.JPY,
// EUR conversions
eurToUsd: 1 / rates.EUR,
eurToGbp: rates.GBP / rates.EUR,
eurToJpy: rates.JPY / rates.EUR,
// GBP conversions
gbpToUsd: 1 / rates.GBP,
gbpToEur: rates.EUR / rates.GBP,
gbpToJpy: rates.JPY / rates.GBP,
// JPY conversions
jpyToUsd: 1 / rates.JPY,
jpyToEur: rates.EUR / rates.JPY,
jpyToGbp: rates.GBP / rates.JPY
};
}
/**
* Get conversion definitions using the current rates
* @returns {Array} - Array of conversion definitions
*/
function getConversions() {
const rates = calculateCrossRates();
return [
// USD Conversions
{ regex: makeRegex('\\$|US\\$|USD'), outputUnit: 'EUR', factor: rates.usdToEur },
{ regex: makeRegex('\\$|US\\$|USD'), outputUnit: 'GBP', factor: rates.usdToGbp },
{ regex: makeRegex('\\$|US\\$|USD'), outputUnit: 'JPY', factor: rates.usdToJpy },
// EUR Conversions
{ regex: makeRegex('€|EUR'), outputUnit: 'USD', factor: rates.eurToUsd },
{ regex: makeRegex('€|EUR'), outputUnit: 'GBP', factor: rates.eurToGbp },
{ regex: makeRegex('€|EUR'), outputUnit: 'JPY', factor: rates.eurToJpy },
// GBP Conversions
{ regex: makeRegex('£|GBP'), outputUnit: 'USD', factor: rates.gbpToUsd },
{ regex: makeRegex('£|GBP'), outputUnit: 'EUR', factor: rates.gbpToEur },
{ regex: makeRegex('£|GBP'), outputUnit: 'JPY', factor: rates.gbpToJpy },
// JPY Conversions
{ regex: makeRegex('¥|JPY|円'), outputUnit: 'USD', factor: rates.jpyToUsd },
{ regex: makeRegex('¥|JPY|円'), outputUnit: 'EUR', factor: rates.jpyToEur },
{ regex: makeRegex('¥|JPY|円'), outputUnit: 'GBP', factor: rates.jpyToGbp }
];
}
/**
* Performs the currency conversion based on the input.
* @param {string} input - The input string containing the currency and amount.
* @returns {Array<string>} - An array of converted currency strings or an empty array if no match.
*/
function convert(input) {
const results = [];
const conversions = getConversions();
for (let { regex, outputUnit, factor } of conversions) {
const match = regex.exec(input);
if (match === null) {
continue;
}
// Determine if the symbol/code is before or after the number
let numberPart = '';
let suffix = '';
if (match[1]) { // Symbol/code before the number
numberPart = match[2];
suffix = match[3] || '';
} else if (match[6]) { // Symbol/code after the number
numberPart = match[4];
suffix = match[5] || '';
} else {
continue; // No valid number part found
}
if (!numberPart) {
continue; // No valid number part found
}
// Remove commas and parse as float to handle decimals
let amount = parseFloat(numberPart.replace(/,/g, ''));
if (isNaN(amount)) {
continue; // Invalid number, skip
}
// Apply multiplier based on suffix
let multiplier = getMultiplier(suffix);
let resultNumber = amount * multiplier * factor;
// Format the converted amount with thousand separators and no decimals
let convertedAmount = formatOutput(resultNumber);
// Determine the appropriate symbol for the output currency
let outputSymbol = '';
switch (outputUnit) {
case 'USD':
outputSymbol = '$';
break;
case 'EUR':
outputSymbol = '€';
break;
case 'GBP':
outputSymbol = '£';
break;
case 'JPY':
outputSymbol = '¥';
break;
default:
outputSymbol = '';
}
// Construct the converted string
const converted = `${outputSymbol}${convertedAmount}`;
// Add to results array if not already included
if (!results.includes(converted)) {
results.push(converted);
}
}
return results;
}
/**
* Updates the exchange rates in the background using axios
*/
function updateExchangeRates() {
const now = Date.now();
// Only update if the cache is expired
if (now - lastFetchTime < CACHE_DURATION) {
return;
}
// Update the timestamp immediately to prevent multiple calls
lastFetchTime = now;
// Use axios to fetch new rates
const axios = require("axios");
axios.get(API_URL)
.then(response => {
const data = response.data;
if (data.result === "success") {
// Update the rates
currentRates = {
USD: 1,
EUR: data.conversion_rates.EUR,
GBP: data.conversion_rates.GBP,
JPY: data.conversion_rates.JPY
};
console.log("Exchange rates updated:", currentRates);
} else {
console.error("API Error:", data["error-type"]);
}
})
.catch(error => {
console.error("Error fetching exchange rates:", error);
});
}
// Try to update rates when the extension is loaded
updateExchangeRates();
/**
* Defines the PopClip actions for currency conversion.
* Each conversion is returned as an individual, selectable action.
* @param {object} input - The input object containing the selected text.
* @returns {Array<object>|undefined} - An array of action objects or undefined if no conversion.
*/
const actions = (input) => {
// Try to update rates in the background, but don't wait for it
updateExchangeRates();
// Use current rates (either cached or fallback) for conversion
const results = convert(input.text);
if (results.length === 0)
return;
// Create an action for each conversion result
const actionObjects = results.map((result) => {
return {
title: result,
icon: null, // Allow title to show without an icon
run: function(context) {
context.output = result;
}
};
});
return actionObjects;
};
exports.actions = actions;