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)
}