diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index f7cc151..9f157cf 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -59,7 +59,8 @@ NestedBlockDepth:Addons.kt$Addons$fun add(module: AbstractModule): Boolean NestedBlockDepth:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?, maxTokens: Int): String NestedBlockDepth:Comment.kt$Comment$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) - NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$@JvmStatic fun convertCurrency(query: String): Message + NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$@JvmStatic @Throws(ModuleException::class) fun loadSymbols(apiKey: String?) + NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$@JvmStatic fun convertCurrency(apiKey: String?, query: String): Message NestedBlockDepth:EntryLink.kt$EntryLink$private fun setTags(tags: List<String?>) NestedBlockDepth:FeedsManager.kt$FeedsManager.Companion$@JvmStatic @Throws(IOException::class, FeedException::class) fun loadFeed(entries: Entries, currentFile: String = currentXml): String NestedBlockDepth:FeedsManager.kt$FeedsManager.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml) diff --git a/properties/mobibot.properties b/properties/mobibot.properties index fa8ce50..24d9977 100644 --- a/properties/mobibot.properties +++ b/properties/mobibot.properties @@ -45,6 +45,12 @@ disabled-modules=mastodon # Automatically post links to Mastodon #mastodon-auto-post=true + +# +# Get Exchange Rate API key from: https://www.exchangerate-api.com/ +# +#exchangerate-api-key= + # # Create custom search engine at: https://programmablesearchengine.google.com/ # and get API key from: https://console.cloud.google.com/apis diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt b/src/main/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt index 0bf9d7a..284f9d2 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt @@ -44,6 +44,7 @@ import org.pircbotx.hooks.types.GenericMessageEvent import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.IOException +import java.math.BigDecimal import java.net.URL import java.util.* @@ -57,10 +58,10 @@ class CurrencyConverter : AbstractModule() { override val name = "CurrencyConverter" // Reload currency codes - private fun reload() { - if (SYMBOLS.isEmpty()) { + private fun reload(apiKey: String?) { + if (!apiKey.isNullOrEmpty() && SYMBOLS.isEmpty()) { try { - loadSymbols() + loadSymbols(apiKey) } catch (e: ModuleException) { if (logger.isWarnEnabled) logger.warn(e.debugMessage, e) } @@ -71,12 +72,12 @@ class CurrencyConverter : AbstractModule() { * Converts the specified currencies. */ override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) { - reload() + reload(properties[API_KEY_PROP]) if (SYMBOLS.isEmpty()) { event.respond(EMPTY_SYMBOLS_TABLE) } else if (args.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+".toRegex())) { - val msg = convertCurrency(args) + val msg = convertCurrency(properties[API_KEY_PROP], args) event.respond(msg.msg) if (msg.isError) { helpResponse(event) @@ -90,7 +91,7 @@ class CurrencyConverter : AbstractModule() { } override fun helpResponse(event: GenericMessageEvent): Boolean { - reload() + reload(properties[API_KEY_PROP]) if (SYMBOLS.isEmpty()) { event.sendMessage(EMPTY_SYMBOLS_TABLE) @@ -99,21 +100,26 @@ class CurrencyConverter : AbstractModule() { event.sendMessage("To convert from one currency to another:") event.sendMessage(helpFormat(helpCmdSyntax("%c $CURRENCY_CMD 100 USD to EUR", nick, isPrivateMsgEnabled))) event.sendMessage( - helpFormat( - helpCmdSyntax("%c $CURRENCY_CMD 50,000 GBP to BTC", nick, isPrivateMsgEnabled) - ) + helpFormat( + helpCmdSyntax("%c $CURRENCY_CMD 50,000 GBP to BTC", nick, isPrivateMsgEnabled) + ) ) event.sendMessage("To list the supported currency codes:") event.sendMessage( - helpFormat( - helpCmdSyntax("%c $CURRENCY_CMD $CODES_KEYWORD", nick, isPrivateMsgEnabled) - ) + helpFormat( + helpCmdSyntax("%c $CURRENCY_CMD $CODES_KEYWORD", nick, isPrivateMsgEnabled) + ) ) } return true } companion object { + /** + * The API Key property. + */ + const val API_KEY_PROP = "exchangerate-api-key" + // Currency command private const val CURRENCY_CMD = "currency" @@ -130,7 +136,11 @@ class CurrencyConverter : AbstractModule() { * Converts from a currency to another. */ @JvmStatic - fun convertCurrency(query: String): Message { + fun convertCurrency(apiKey: String?, query: String): Message { + if (apiKey.isNullOrEmpty()) { + throw ModuleException("${CURRENCY_CMD}($query)", "No Exchange Rate API key specified.") + } + val cmds = query.split(" ") return if (cmds.size == 4) { if (cmds[3] == cmds[1] || "0" == cmds[0]) { @@ -141,12 +151,14 @@ class CurrencyConverter : AbstractModule() { if (SYMBOLS.contains(to) && SYMBOLS.contains(from)) { try { val amt = cmds[0].replace(",", "") - val url = URL("https://api.exchangerate.host/convert?from=$to&to=$from&amount=$amt") - val json = JSONObject(url.reader().body) + val url = URL("https://v6.exchangerate-api.com/v6/$apiKey/pair/$to/$from/$amt") + val body = url.reader().body + val json = JSONObject(body) - if (json.getBoolean("success")) { + if (json.getString("result") == "success") { + val result = json.getDouble("conversion_result") PublicMessage( - "${cmds[0]} ${SYMBOLS[to]} = ${json.get("result")} ${SYMBOLS[from]}" + "${cmds[0]} ${SYMBOLS[to]} = $result ${SYMBOLS[from]}" ) } else { ErrorMessage("Sorry, an error occurred while converting the currencies.") @@ -158,7 +170,9 @@ class CurrencyConverter : AbstractModule() { ErrorMessage("Sounds like monopoly money to me!") } } - } else ErrorMessage("Invalid query. Let's try again.") + } else { + ErrorMessage("Invalid query. Let's try again.") + } } /** @@ -166,28 +180,32 @@ class CurrencyConverter : AbstractModule() { */ @JvmStatic @Throws(ModuleException::class) - fun loadSymbols() { - try { - val url = URL("https://api.exchangerate.host/symbols") - val json = JSONObject(url.reader().body) - if (json.getBoolean("success")) { - val symbols = json.getJSONObject("symbols") - for (key in symbols.keys()) { - SYMBOLS[key] = symbols.getJSONObject(key).getString("description") + 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( + } catch (e: IOException) { + throw ModuleException( "loadCodes(): IOE", "An IO error has occurred while retrieving the currencies.", e - ) + ) + } } } } init { commands.add(CURRENCY_CMD) - loadSymbols() + initProperties(API_KEY_PROP) + loadSymbols(properties[ChatGpt.API_KEY_PROP]) } } diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverterTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverterTest.kt index 10a2470..59ea2e7 100644 --- a/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverterTest.kt +++ b/src/test/kotlin/net/thauvin/erik/mobibot/modules/CurrencyConverterTest.kt @@ -36,6 +36,7 @@ import assertk.assertions.contains import assertk.assertions.isInstanceOf import assertk.assertions.matches import assertk.assertions.prop +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 net.thauvin.erik.mobibot.msg.ErrorMessage @@ -48,32 +49,35 @@ import org.testng.annotations.Test /** * The `CurrencyConvertTest` class. */ -class CurrencyConverterTest { +class CurrencyConverterTest: LocalProperties() { + @BeforeClass @Throws(ModuleException::class) fun before() { - loadSymbols() + val apiKey = getProperty(CurrencyConverter.API_KEY_PROP) + loadSymbols(apiKey) } @Test(groups = ["modules"]) fun testConvertCurrency() { + val apiKey = getProperty(CurrencyConverter.API_KEY_PROP) assertThat( - convertCurrency("100 USD to EUR").msg, + convertCurrency(apiKey,"100 USD to EUR").msg, "convertCurrency(100 USD to EUR)" ).matches("100 United States Dollar = \\d{2,3}\\.\\d+ Euro".toRegex()) assertThat( - convertCurrency("1 USD to BTC").msg, - "convertCurrency(1 USD to BTC)" - ).matches("1 United States Dollar = 0\\.\\d+ Bitcoin".toRegex()) + convertCurrency(apiKey,"1 USD to GBP").msg, + "convertCurrency(1 USD to BGP)" + ).matches("1 United States Dollar = 0\\.\\d+ Pound Sterling".toRegex()) assertThat( - convertCurrency("100,000.00 GBP to BTC").msg, - "convertCurrency(100,000.00 GBP to BTC)" - ).matches("100,000.00 British Pound Sterling = \\d{1,2}\\.\\d+ Bitcoin".toRegex()) - assertThat(convertCurrency("100 USD to USD"), "convertCurrency(100 USD to USD)").all { + convertCurrency(apiKey,"100,000.00 CAD to USD").msg, + "convertCurrency(100,000.00 GBP to USD)" + ).matches("100,000.00 Canadian Dollar = \\d+\\.\\d+ United States Dollar".toRegex()) + assertThat(convertCurrency(apiKey,"100 USD to USD"), "convertCurrency(100 USD to USD)").all { prop(Message::msg).contains("You're kidding, right?") isInstanceOf(PublicMessage::class.java) } - assertThat(convertCurrency("100 USD"), "convertCurrency(100 USD)").all { + assertThat(convertCurrency(apiKey,"100 USD"), "convertCurrency(100 USD)").all { prop(Message::msg).contains("Invalid query.") isInstanceOf(ErrorMessage::class.java) }