Currency cleanup

This commit is contained in:
Erik C. Thauvin 2022-07-11 13:50:33 -07:00
parent 78a0df8a8e
commit 55f279c9b1
5 changed files with 88 additions and 84 deletions

View file

@ -54,19 +54,20 @@ class CryptoPrices : ThreadedModule() {
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) { override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
synchronized(this) { synchronized(this) {
if (pubDate != today()) { if (pubDate != today()) {
CODES.clear() CURRENCIES.clear()
} }
} }
super.commandResponse(channel, cmd, args, event) super.commandResponse(channel, cmd, args, event)
} }
/** /**
* Returns the cryptocurrency market price from [Coinbase](https://developers.coinbase.com/api/v2#get-spot-price). * Returns the cryptocurrency market price from
* [Coinbase](https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-spot-price).
*/ */
override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) { override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
if (CODES.isEmpty()) { if (CURRENCIES.isEmpty()) {
try { try {
loadCodes() loadCurrencies()
} catch (e: ModuleException) { } catch (e: ModuleException) {
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e) if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
} }
@ -74,8 +75,8 @@ class CryptoPrices : ThreadedModule() {
val debugMessage = "crypto($cmd $args)" val debugMessage = "crypto($cmd $args)"
if (args == CURRENCY_CODES_KEYWORD) { if (args == CURRENCY_CODES_KEYWORD) {
event.sendMessage("The supported currency codes are:") event.sendMessage("The supported currencies are:")
event.sendList(ArrayList(CODES.keys), 10, isIndent = true) event.sendList(ArrayList(CURRENCIES.keys), 10, isIndent = true)
} else if (args.matches("\\w+( [a-zA-Z]{3}+)?".toRegex())) { } else if (args.matches("\\w+( [a-zA-Z]{3}+)?".toRegex())) {
try { try {
val price = currentPrice(args.split(' ')) val price = currentPrice(args.split(' '))
@ -84,7 +85,7 @@ class CryptoPrices : ThreadedModule() {
} catch (ignore: IllegalArgumentException) { } catch (ignore: IllegalArgumentException) {
price.amount price.amount
} }
event.respond("${price.base} current price is $amount [${CODES[price.currency]}]") event.respond("${price.base} current price is $amount [${CURRENCIES[price.currency]}]")
} catch (e: CryptoException) { } catch (e: CryptoException) {
if (logger.isWarnEnabled) logger.warn("$debugMessage => ${e.statusCode}", e) if (logger.isWarnEnabled) logger.warn("$debugMessage => ${e.statusCode}", e)
e.message?.let { e.message?.let {
@ -104,13 +105,13 @@ class CryptoPrices : ThreadedModule() {
// Crypto command // Crypto command
private const val CRYPTO_CMD = "crypto" private const val CRYPTO_CMD = "crypto"
// Currency codes // Fiat Currencies
private val CODES: MutableMap<String, String> = mutableMapOf() private val CURRENCIES: MutableMap<String, String> = mutableMapOf()
// Currency codes keyword // Currency codes keyword
private const val CURRENCY_CODES_KEYWORD = "codes" private const val CURRENCY_CODES_KEYWORD = "codes"
// Last currency table retrieval date // Last currencies retrieval date
private var pubDate = "" private var pubDate = ""
/** /**
@ -128,31 +129,32 @@ class CryptoPrices : ThreadedModule() {
* For testing purposes. * For testing purposes.
*/ */
fun getCurrencyName(code: String): String? { fun getCurrencyName(code: String): String? {
return CODES[code] return CURRENCIES[code]
} }
/** /**
* Loads the country ISO codes. * Loads the Fiat currencies..
*/ */
@JvmStatic @JvmStatic
@Throws(ModuleException::class) @Throws(ModuleException::class)
fun loadCodes() { fun loadCurrencies() {
if (CODES.isEmpty()) { try {
try { val json = JSONObject(CryptoPrice.apiCall(listOf("currencies")))
val json = JSONObject(CryptoPrice.apiCall(listOf("currencies"))) val data = json.getJSONArray("data")
val data = json.getJSONArray("data") if (CURRENCIES.isNotEmpty()) {
for (i in 0 until data.length()) { CURRENCIES.clear()
val d = data.getJSONObject(i)
CODES[d.getString("id")] = d.getString("name")
}
pubDate = today()
} catch (e: CryptoException) {
throw ModuleException(
"loadCodes(): CE",
"An error has occurred while retrieving the currency table.",
e
)
} }
for (i in 0 until data.length()) {
val d = data.getJSONObject(i)
CURRENCIES[d.getString("id")] = d.getString("name")
}
pubDate = today()
} catch (e: CryptoException) {
throw ModuleException(
"loadCurrencies(): CE",
"An error has occurred while retrieving the currencies table.",
e
)
} }
} }
} }
@ -166,7 +168,7 @@ class CryptoPrices : ThreadedModule() {
add(helpFormat("%c $CRYPTO_CMD BTC")) add(helpFormat("%c $CRYPTO_CMD BTC"))
add(helpFormat("%c $CRYPTO_CMD ETH EUR")) add(helpFormat("%c $CRYPTO_CMD ETH EUR"))
add(helpFormat("%c $CRYPTO_CMD ETH2 GPB")) add(helpFormat("%c $CRYPTO_CMD ETH2 GPB"))
add("To list the supported currency codes:") add("To list the supported currencies:")
add(helpFormat("%c $CRYPTO_CMD $CURRENCY_CODES_KEYWORD")) add(helpFormat("%c $CRYPTO_CMD $CURRENCY_CODES_KEYWORD"))
} }
} }

View file

@ -47,6 +47,7 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
import java.util.*
/** /**
@ -60,50 +61,50 @@ class CurrencyConverter : ThreadedModule() {
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) { override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
synchronized(this) { synchronized(this) {
if (pubDate != today()) { if (pubDate != today()) {
CODES.clear() SYMBOLS.clear()
} }
} }
super.commandResponse(channel, cmd, args, event) super.commandResponse(channel, cmd, args, event)
} }
// Reload currency codes
private fun reload() {
if (SYMBOLS.isEmpty()) {
try {
loadSymbols()
} catch (e: ModuleException) {
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
}
}
}
/** /**
* Converts the specified currencies. * Converts the specified currencies.
*/ */
override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) { override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
if (CODES.isEmpty()) { reload()
try {
loadCodes()
} catch (e: ModuleException) {
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
}
}
if (CODES.isEmpty()) { if (SYMBOLS.isEmpty()) {
event.respond(EMPTY_CODES_TABLE) event.respond(EMPTY_SYMBOLS_TABLE)
} else if (args.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+".toRegex())) { } else if (args.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+".toRegex())) {
val msg = convertCurrency(args) val msg = convertCurrency(args)
event.respond(msg.msg) event.respond(msg.msg)
if (msg.isError) { if (msg.isError) {
helpResponse(event) helpResponse(event)
} }
} else if (args.contains(CURRENCY_CODES_KEYWORD)) { } else if (args.contains(CODES_KEYWORD)) {
event.sendMessage("The supported currency codes are:") event.sendMessage("The supported currency codes are:")
event.sendList(ArrayList(CODES.keys.sorted()), 11, isIndent = true) event.sendList(SYMBOLS.keys.toList(), 11, isIndent = true)
} else { } else {
helpResponse(event) helpResponse(event)
} }
} }
override fun helpResponse(event: GenericMessageEvent): Boolean { override fun helpResponse(event: GenericMessageEvent): Boolean {
if (CODES.isEmpty()) { reload()
try {
loadCodes() if (SYMBOLS.isEmpty()) {
} catch (e: ModuleException) { event.sendMessage(EMPTY_SYMBOLS_TABLE)
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
}
}
if (CODES.isEmpty()) {
event.sendMessage(EMPTY_CODES_TABLE)
} else { } else {
val nick = event.bot().nick val nick = event.bot().nick
event.sendMessage("To convert from one currency to another:") event.sendMessage("To convert from one currency to another:")
@ -116,7 +117,7 @@ class CurrencyConverter : ThreadedModule() {
event.sendMessage("To list the supported currency codes:") event.sendMessage("To list the supported currency codes:")
event.sendMessage( event.sendMessage(
helpFormat( helpFormat(
helpCmdSyntax("%c $CURRENCY_CMD $CURRENCY_CODES_KEYWORD", nick, isPrivateMsgEnabled) helpCmdSyntax("%c $CURRENCY_CMD $CODES_KEYWORD", nick, isPrivateMsgEnabled)
) )
) )
} }
@ -128,15 +129,15 @@ class CurrencyConverter : ThreadedModule() {
private const val CURRENCY_CMD = "currency" private const val CURRENCY_CMD = "currency"
// Currency codes keyword // Currency codes keyword
private const val CURRENCY_CODES_KEYWORD = "codes" private const val CODES_KEYWORD = "codes"
// Empty code table. // Empty symbols table.
private const val EMPTY_CODES_TABLE = "Sorry, but the currency table is empty." private const val EMPTY_SYMBOLS_TABLE = "Sorry, but the currency table is empty."
// Currency codes // Currency symbols
private val CODES: MutableMap<String, String> = mutableMapOf() private val SYMBOLS: TreeMap<String, String> = TreeMap()
// Last currency table retrieval date // Last currency retrieval date
private var pubDate = "" private var pubDate = ""
/** /**
@ -151,7 +152,7 @@ class CurrencyConverter : ThreadedModule() {
} else { } else {
val to = cmds[1].uppercase() val to = cmds[1].uppercase()
val from = cmds[3].uppercase() val from = cmds[3].uppercase()
if (CODES.contains(to) && CODES.contains(from)) { if (SYMBOLS.contains(to) && SYMBOLS.contains(from)) {
try { try {
val amt = cmds[0].replace(",", "") val amt = cmds[0].replace(",", "")
val url = URL("https://api.exchangerate.host/convert?from=$to&to=$from&amount=$amt") val url = URL("https://api.exchangerate.host/convert?from=$to&to=$from&amount=$amt")
@ -159,7 +160,7 @@ class CurrencyConverter : ThreadedModule() {
if (json.getBoolean("success")) { if (json.getBoolean("success")) {
PublicMessage( PublicMessage(
"${cmds[0]} ${CODES[to]} = ${json.getDouble("result")} ${CODES[from]}" "${cmds[0]} ${SYMBOLS[to]} = ${json.getDouble("result")} ${SYMBOLS[from]}"
) )
} else { } else {
ErrorMessage("Sorry, an error occurred while converting the currencies.") ErrorMessage("Sorry, an error occurred while converting the currencies.")
@ -175,29 +176,30 @@ class CurrencyConverter : ThreadedModule() {
} }
/** /**
* Loads the country ISO codes. * Loads the currency ISO symbols.
*/ */
@JvmStatic @JvmStatic
@Throws(ModuleException::class) @Throws(ModuleException::class)
fun loadCodes() { fun loadSymbols() {
if (CODES.isEmpty()) { try {
try { val url = URL("https://api.exchangerate.host/symbols")
val url = URL("https://api.exchangerate.host/symbols") val json = JSONObject(url.reader())
val json = JSONObject(url.reader()) if (json.getBoolean("success")) {
if (json.getBoolean("success")) { val symbols = json.getJSONObject("symbols")
val symbols = json.getJSONObject("symbols") if (SYMBOLS.isNotEmpty()) {
for (key in symbols.keys()) { SYMBOLS.clear()
CODES[key] = symbols.getJSONObject(key).getString("description")
}
pubDate = today()
} }
} catch (e: IOException) { for (key in symbols.keys()) {
throw ModuleException( SYMBOLS[key] = symbols.getJSONObject(key).getString("description")
"loadCodes(): IOE", }
"An IO error has occurred while retrieving the currency table.", pubDate = today()
e
)
} }
} catch (e: IOException) {
throw ModuleException(
"loadCodes(): IOE",
"An IO error has occurred while retrieving the currencies.",
e
)
} }
} }
} }

View file

@ -39,7 +39,7 @@ import assertk.assertions.prop
import net.thauvin.erik.crypto.CryptoPrice import net.thauvin.erik.crypto.CryptoPrice
import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.currentPrice import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.currentPrice
import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.getCurrencyName import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.getCurrencyName
import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.loadCodes import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.loadCurrencies
import org.testng.annotations.BeforeClass import org.testng.annotations.BeforeClass
import org.testng.annotations.Test import org.testng.annotations.Test
@ -50,7 +50,7 @@ class CryptoPricesTest {
@BeforeClass @BeforeClass
@Throws(ModuleException::class) @Throws(ModuleException::class)
fun before() { fun before() {
loadCodes() loadCurrencies()
} }
@Test @Test

View file

@ -38,7 +38,7 @@ import assertk.assertions.isInstanceOf
import assertk.assertions.matches import assertk.assertions.matches
import assertk.assertions.prop import assertk.assertions.prop
import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.convertCurrency import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.convertCurrency
import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.loadCodes import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.loadSymbols
import net.thauvin.erik.mobibot.msg.ErrorMessage import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.PublicMessage import net.thauvin.erik.mobibot.msg.PublicMessage
@ -53,7 +53,7 @@ class CurrencyConverterTest {
@BeforeClass @BeforeClass
@Throws(ModuleException::class) @Throws(ModuleException::class)
fun before() { fun before() {
loadCodes() loadSymbols()
} }
@Test @Test

View file

@ -1,9 +1,9 @@
#Generated by the Semver Plugin for Gradle #Generated by the Semver Plugin for Gradle
#Mon Jul 11 07:36:52 PDT 2022 #Mon Jul 11 13:05:47 PDT 2022
version.buildmeta=324 version.buildmeta=329
version.major=0 version.major=0
version.minor=8 version.minor=8
version.patch=0 version.patch=0
version.prerelease=rc version.prerelease=rc
version.project=mobibot version.project=mobibot
version.semver=0.8.0-rc+324 version.semver=0.8.0-rc+329