= TreeMap()
-
- /**
- * No API key error message.
- */
- const val ERROR_MESSAGE_NO_API_KEY = "No Exchange Rate API key specified."
-
+ private val LOGGER: Logger = LoggerFactory.getLogger(CurrencyConverter2::class.java)
/**
* Converts from a currency to another.
*/
@JvmStatic
- fun convertCurrency(apiKey: String?, query: String): Message {
- if (apiKey.isNullOrEmpty()) {
- throw ModuleException("${CURRENCY_CMD}($query)", ERROR_MESSAGE_NO_API_KEY)
- }
-
+ fun convertCurrency(query: String): Message {
val cmds = query.split(" ")
return if (cmds.size == 4) {
if (cmds[3] == cmds[1] || "0" == cmds[0]) {
PublicMessage("You're kidding, right?")
} else {
- val to = cmds[1].uppercase()
- val from = cmds[3].uppercase()
- if (SYMBOLS.contains(to) && SYMBOLS.contains(from)) {
+ val from = cmds[1].uppercase()
+ val to = cmds[3].uppercase()
+ if (CURRENCY_CODES.contains(to) && CURRENCY_CODES.contains(from)) {
try {
val amt = cmds[0].replace(",", "")
- val url = URL("https://v6.exchangerate-api.com/v6/$apiKey/pair/$to/$from/$amt")
+ val url = URL("https://api.frankfurter.dev/v1/latest?base=$from&symbols=$to")
val body = url.reader().body
- val json = JSONObject(body)
-
- if (json.getString("result") == "success") {
- val result = DECIMAL_FORMAT.format(json.getDouble("conversion_result"))
- PublicMessage(
- "${cmds[0]} ${SYMBOLS[to]} = $result ${SYMBOLS[from]}"
- )
- } else {
- ErrorMessage("Sorry, an error occurred while converting the currencies.")
+ if (LOGGER.isTraceEnabled) {
+ LOGGER.trace(body)
}
+ val json = JSONObject(body)
+ val rates = json.getJSONObject("rates")
+ val rate = rates.getDouble(to)
+ val result = DECIMAL_FORMAT.format(amt.toDouble() * rate)
+
+
+ PublicMessage(
+ "${cmds[0]} ${CURRENCY_CODES[from]} = $result ${CURRENCY_CODES[to]}"
+ )
+ } catch (nfe: NumberFormatException) {
+ if (LOGGER.isWarnEnabled) {
+ LOGGER.warn("IO error while converting currencies: ${nfe.message}", nfe)
+ }
+ ErrorMessage("Sorry, an error occurred while converting the currencies.")
} catch (ioe: IOException) {
if (LOGGER.isWarnEnabled) {
LOGGER.warn("IO error while converting currencies: ${ioe.message}", ioe)
@@ -122,7 +114,9 @@ class CurrencyConverter : AbstractModule() {
ErrorMessage("Sorry, an IO error occurred while converting the currencies.")
}
} else {
- ErrorMessage("Sounds like monopoly money to me!")
+ ErrorMessage(
+ "Sounds like monopoly money to me! Try looking up the supported currency codes."
+ )
}
}
} else {
@@ -131,44 +125,40 @@ class CurrencyConverter : AbstractModule() {
}
/**
- * Loads the currency ISO symbols.
+ * Loads the currency ISO codes.
*/
@JvmStatic
@Throws(ModuleException::class)
- fun loadSymbols(apiKey: String?) {
- if (!apiKey.isNullOrEmpty()) {
- try {
- val url = URL("https://v6.exchangerate-api.com/v6/$apiKey/codes")
- val json = JSONObject(url.reader().body)
- if (json.getString("result") == "success") {
- val codes = json.getJSONArray("supported_codes")
- for (i in 0 until codes.length()) {
- val code = codes.getJSONArray(i)
- SYMBOLS[code.getString(0)] = code.getString(1)
- }
- }
- } catch (e: IOException) {
- throw ModuleException(
- "loadCodes(): IOE",
- "An IO error has occurred while retrieving the currencies.",
- e
- )
+ fun loadCurrencyCodes() {
+ try {
+ val url = URL("https://api.frankfurter.dev/v1/currencies")
+ val body = url.reader().body
+ val json = JSONObject(body)
+ if (LOGGER.isTraceEnabled) {
+ LOGGER.trace(body)
}
+ json.keySet().forEach { key ->
+ CURRENCY_CODES[key] = json.getString(key)
+ }
+ } catch (e: IOException) {
+ throw ModuleException(
+ "loadCurrencyCodes(): IOE",
+ "An IO error has occurred while retrieving the currency codes.",
+ e
+ )
}
}
}
init {
commands.add(CURRENCY_CMD)
- initProperties(API_KEY_PROP)
- loadSymbols(properties[ChatGpt2.API_KEY_PROP])
}
// Reload currency codes
- private fun reload(apiKey: String?) {
- if (!apiKey.isNullOrEmpty() && SYMBOLS.isEmpty()) {
+ private fun reload() {
+ if (CURRENCY_CODES.isEmpty()) {
try {
- loadSymbols(apiKey)
+ loadCurrencyCodes()
} catch (e: ModuleException) {
if (LOGGER.isWarnEnabled) LOGGER.warn(e.debugMessage, e)
}
@@ -179,15 +169,15 @@ class CurrencyConverter : AbstractModule() {
* Converts the specified currencies.
*/
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
- reload(properties[API_KEY_PROP])
+ reload()
when {
- SYMBOLS.isEmpty() -> {
- event.respond(EMPTY_SYMBOLS_TABLE)
+ CURRENCY_CODES.isEmpty() -> {
+ event.respond(EMPTY_CODES_TABLE)
}
args.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ (to|in) [a-zA-Z]{3}+".toRegex()) -> {
try {
- val msg = convertCurrency(properties[API_KEY_PROP], args)
+ val msg = convertCurrency(args)
if (msg.isError) {
helpResponse(event)
} else {
@@ -201,7 +191,7 @@ class CurrencyConverter : AbstractModule() {
args.contains(CODES_KEYWORD) -> {
event.sendMessage("The supported currency codes are:")
- event.sendList(SYMBOLS.keys.toList(), 11, isIndent = true)
+ event.sendList(CURRENCY_CODES.keys.toList(), 11, isIndent = true)
}
else -> {
@@ -211,10 +201,10 @@ class CurrencyConverter : AbstractModule() {
}
override fun helpResponse(event: GenericMessageEvent): Boolean {
- reload(properties[API_KEY_PROP])
+ reload()
- if (SYMBOLS.isEmpty()) {
- event.sendMessage(EMPTY_SYMBOLS_TABLE)
+ if (CURRENCY_CODES.isEmpty()) {
+ event.sendMessage(EMPTY_CODES_TABLE)
} else {
val nick = event.bot().nick
event.sendMessage("To convert from one currency to another:")
diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverterTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverter2Test.kt
similarity index 72%
rename from src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverterTest.kt
rename to src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverter2Test.kt
index 7ee2b4d..e4756ca 100644
--- a/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverterTest.kt
+++ b/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverter2Test.kt
@@ -32,10 +32,12 @@ package net.thauvin.erik.mobibot.modules
import assertk.all
import assertk.assertThat
-import assertk.assertions.*
-import net.thauvin.erik.mobibot.LocalProperties
-import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.convertCurrency
-import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.loadSymbols
+import assertk.assertions.contains
+import assertk.assertions.isInstanceOf
+import assertk.assertions.matches
+import assertk.assertions.prop
+import net.thauvin.erik.mobibot.modules.CurrencyConverter2.Companion.convertCurrency
+import net.thauvin.erik.mobibot.modules.CurrencyConverter2.Companion.loadCurrencyCodes
import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.PublicMessage
@@ -46,10 +48,9 @@ import org.mockito.Mockito
import org.pircbotx.hooks.types.GenericMessageEvent
import kotlin.test.Test
-class CurrencyConverterTest : LocalProperties() {
+class CurrencyConverter2Test {
init {
- val apiKey = getProperty(CurrencyConverter.API_KEY_PROP)
- loadSymbols(apiKey)
+ loadCurrencyCodes()
}
@Nested
@@ -57,41 +58,37 @@ class CurrencyConverterTest : LocalProperties() {
inner class CommandResponseTests {
@Test
fun `USD to CAD`() {
- val currencyConverter = CurrencyConverter()
+ val currencyConverter = CurrencyConverter2()
val event = Mockito.mock(GenericMessageEvent::class.java)
val captor = ArgumentCaptor.forClass(String::class.java)
- currencyConverter.properties.put(
- CurrencyConverter.API_KEY_PROP, getProperty(CurrencyConverter.API_KEY_PROP)
- )
currencyConverter.commandResponse("channel", "currency", "1 USD to CAD", event)
Mockito.verify(event, Mockito.atLeastOnce()).respond(captor.capture())
- assertThat(captor.value).matches("1 United States Dollar = \\d+\\.\\d{2,3} Canadian Dollar".toRegex())
+ assertThat(captor.value)
+ .matches("1 United States Dollar = \\d+\\.\\d{2,3} Canadian Dollar".toRegex())
}
@Test
- fun `API Key is not specified`() {
- val currencyConverter = CurrencyConverter()
+ fun `USD to GBP`() {
+ val currencyConverter = CurrencyConverter2()
val event = Mockito.mock(GenericMessageEvent::class.java)
val captor = ArgumentCaptor.forClass(String::class.java)
- currencyConverter.commandResponse("channel", "currency", "1 USD to CAD", event)
+ currencyConverter.commandResponse("channel", "currency", "1 usd to gbp", event)
Mockito.verify(event, Mockito.atLeastOnce()).respond(captor.capture())
- assertThat(captor.value).isEqualTo(CurrencyConverter.ERROR_MESSAGE_NO_API_KEY)
+ assertThat(captor.value).matches("1 United States Dollar = \\d+\\.\\d{2,3} British Pound".toRegex())
}
}
@Nested
@DisplayName("Currency Converter Tests")
inner class CurrencyConverterTests {
- private val apiKey = getProperty(CurrencyConverter.API_KEY_PROP)
-
@Test
fun `Convert CAD to USD`() {
assertThat(
- convertCurrency(apiKey, "100,000.00 CAD to USD").msg,
+ convertCurrency("100,000.00 CAD to USD").msg,
"convertCurrency(100,000.00 GBP to USD)"
).matches("100,000.00 Canadian Dollar = \\d+\\.\\d{2,3} United States Dollar".toRegex())
}
@@ -99,7 +96,7 @@ class CurrencyConverterTest : LocalProperties() {
@Test
fun `Convert USD to EUR`() {
assertThat(
- convertCurrency(apiKey, "100 USD to EUR").msg,
+ convertCurrency("100 USD to EUR").msg,
"convertCurrency(100 USD to EUR)"
).matches("100 United States Dollar = \\d{2,3}\\.\\d{2,3} Euro".toRegex())
}
@@ -107,14 +104,23 @@ class CurrencyConverterTest : LocalProperties() {
@Test
fun `Convert USD to GBP`() {
assertThat(
- convertCurrency(apiKey, "1 USD to GBP").msg,
+ convertCurrency("1 USD to GBP").msg,
"convertCurrency(1 USD to BGP)"
- ).matches("1 United States Dollar = 0\\.\\d{2,3} Pound Sterling".toRegex())
+ ).matches("1 United States Dollar = 0\\.\\d{2,3} British Pound".toRegex())
+ }
+
+ @Test
+ fun `Convert USD to Invalid Currency`() {
+ assertThat(convertCurrency("100 USD to FOO"), "convertCurrency(100 USD to FOO)").all {
+ prop(Message::msg)
+ .contains("Sounds like monopoly money to me! Try looking up the supported currency codes.")
+ isInstanceOf(ErrorMessage::class.java)
+ }
}
@Test
fun `Convert USD to USD`() {
- assertThat(convertCurrency(apiKey, "100 USD to USD"), "convertCurrency(100 USD to USD)").all {
+ assertThat(convertCurrency("100 USD to USD"), "convertCurrency(100 USD to USD)").all {
prop(Message::msg).contains("You're kidding, right?")
isInstanceOf(PublicMessage::class.java)
}
@@ -122,7 +128,7 @@ class CurrencyConverterTest : LocalProperties() {
@Test
fun `Invalid Query should throw exception`() {
- assertThat(convertCurrency(apiKey, "100 USD"), "convertCurrency(100 USD)").all {
+ assertThat(convertCurrency("100 USD"), "convertCurrency(100 USD)").all {
prop(Message::msg).contains("Invalid query.")
isInstanceOf(ErrorMessage::class.java)
}
diff --git a/website/index.html b/website/index.html
index a2d52b3..a559804 100644
--- a/website/index.html
+++ b/website/index.html
@@ -75,7 +75,7 @@
mobibot: cryto btc
mobibot: cryto eth eur
- Converting between currencies
+ Converting between currencies using Frankfurter
mobibot: currency 17.54 USD to EUR
Performing Google searches
@@ -86,7 +86,7 @@
mobibot: chatgpt explain quantum computing in simple terms
mobibot: gemini what are all the colors in a rainbow?
- Displaying weather information
+ Displaying weather information from OpenWeatherMap
mobibot: weather san francisco
mobibot: weather 94123
mobibot: weather tokyo, jp