From c99210f624129c2b6abe6fdd5a4c920a8dd1e701 Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Tue, 4 May 2021 01:03:33 -0700 Subject: [PATCH] Added Bitcoin module. --- config/detekt/baseline.xml | 3 + .../net/thauvin/erik/mobibot/Mobibot.kt | 2 + .../thauvin/erik/mobibot/modules/Bitcoin.kt | 123 ++++++++++++++++++ .../erik/mobibot/modules/BitcoinTest.kt | 55 ++++++++ version.properties | 6 +- 5 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/net/thauvin/erik/mobibot/modules/Bitcoin.kt create mode 100644 src/test/kotlin/net/thauvin/erik/mobibot/modules/BitcoinTest.kt diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index afd1e14..a4065f6 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -15,6 +15,7 @@ LongParameterList:Mobibot.kt$Mobibot$( nick: String, list: List<String>, maxPerLine: Int, isPrivate: Boolean, isBold: Boolean = false, isIndent: Boolean = false ) LongParameterList:Twitter.kt$Twitter.Companion$( consumerKey: String?, consumerSecret: String?, token: String?, tokenSecret: String?, handle: String?, message: String, isDm: Boolean ) NestedBlockDepth:Addons.kt$Addons$ fun add(command: AbstractCommand, props: Properties) + NestedBlockDepth:Bitcoin.kt$Bitcoin$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) NestedBlockDepth:Comment.kt$Comment$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean ) NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter$override fun helpResponse(sender: String, isPrivate: Boolean): Boolean @@ -38,10 +39,12 @@ NestedBlockDepth:WorldTime.kt$WorldTime$override fun commandResponse( sender: String, cmd: String, args: String, isPrivate: Boolean ) ReturnCount:Addons.kt$Addons$ fun exec(sender: String, login: String, cmd: String, args: String, isOp: Boolean, isPrivate: Boolean): Boolean ReturnCount:Addons.kt$Addons$ fun help(sender: String, topic: String, isOp: Boolean, isPrivate: Boolean): Boolean + ThrowsCount:Bitcoin.kt$Bitcoin.Companion$ @JvmStatic @Throws(ModuleException::class) fun marketPrice(currency: String): List<Message> ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$ @JvmStatic @Throws(ModuleException::class) fun searchGoogle(query: String, apiKey: String?, cseKey: String?): List<Message> ThrowsCount:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message> ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject ThrowsCount:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message> + TooGenericExceptionCaught:Bitcoin.kt$Bitcoin.Companion$e: NullPointerException TooGenericExceptionCaught:FeedReader.kt$FeedReader$e: Exception TooGenericExceptionCaught:Mobibot.kt$Mobibot$e: Exception TooGenericExceptionCaught:Mobibot.kt$Mobibot$ex: Exception diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt b/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt index bf30f72..72f0e89 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt @@ -64,6 +64,7 @@ import net.thauvin.erik.mobibot.commands.links.View import net.thauvin.erik.mobibot.commands.tell.Tell import net.thauvin.erik.mobibot.entries.EntriesMgr import net.thauvin.erik.mobibot.entries.EntryLink +import net.thauvin.erik.mobibot.modules.Bitcoin import net.thauvin.erik.mobibot.modules.Calc import net.thauvin.erik.mobibot.modules.CurrencyConverter import net.thauvin.erik.mobibot.modules.Dice @@ -677,6 +678,7 @@ class Mobibot(nickname: String, channel: String, logsDirPath: String, p: Propert addons.add(View(this), p) // Load the modules + addons.add(Bitcoin(this), p) addons.add(Calc(this), p) addons.add(CurrencyConverter(this), p) addons.add(Dice(this), p) diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/modules/Bitcoin.kt b/src/main/kotlin/net/thauvin/erik/mobibot/modules/Bitcoin.kt new file mode 100644 index 0000000..0289bf2 --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/mobibot/modules/Bitcoin.kt @@ -0,0 +1,123 @@ +/* + * Bitcoin.kt + * + * Copyright (c) 2004-2021, Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.thauvin.erik.mobibot.modules + +import net.thauvin.erik.mobibot.Mobibot +import net.thauvin.erik.mobibot.Utils +import net.thauvin.erik.mobibot.msg.ErrorMessage +import net.thauvin.erik.mobibot.msg.Message +import net.thauvin.erik.mobibot.msg.NoticeMessage +import net.thauvin.erik.mobibot.msg.PublicMessage +import org.json.JSONException +import org.json.JSONObject +import java.io.IOException +import java.net.URL + +/** + * The Bitcoin module. + */ +class Bitcoin(bot: Mobibot) : ThreadedModule(bot) { + // Currencies + private val currencies = listOf( + "USD", "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "DKK", "EUR", "GBP", "HKD", "INR", "ISK", "JPY", "KRW", + "NZD", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD"); + + /** + * Returns the bitcoin market price from [Blockchain.info](https://blockchain.info/ticker). + */ + override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) { + with(bot) { + val arg = args.trim().uppercase() + @Suppress("MagicNumber") + if (!currencies.contains(arg)) { + helpResponse(sender, isPrivate) + send(sender, "The supported currencies are: ", isPrivate) + @Suppress("MagicNumber") + sendList(sender, currencies, 12, isPrivate, isIndent = true) + } else { + try { + val messages = marketPrice(arg) + for (msg in messages) { + send(sender, msg) + } + } catch (e: ModuleException) { + if (logger.isWarnEnabled) logger.warn(e.debugMessage, e) + send(e.message) + } + } + } + } + + companion object { + // Blockchain Ticker URL + private const val TICKER_URL = "https://blockchain.info/ticker" + + // Quote command + private const val BITCOIN_CMD = "bitcoin" + + /** + * Retrieves the bitcoin market price. + */ + @JvmStatic + @Throws(ModuleException::class) + fun marketPrice(currency: String): List { + val debugMessage = "marketPrice($currency)" + val messages = mutableListOf() + try { + val response = Utils.urlReader(URL("$TICKER_URL")) + val json = JSONObject(response) + val bpi = json.getJSONObject(currency.trim().uppercase()) + val symbol = bpi.getString("symbol"); + with(messages) { + add(PublicMessage("BTC: $symbol" + bpi.getBigDecimal("last") + " [$currency]")) + add(NoticeMessage(" 15m: $symbol" + bpi.getBigDecimal("15m"))) + add(NoticeMessage(" Buy: $symbol" + bpi.getBigDecimal("buy"))) + add(NoticeMessage(" Sell: $symbol" + bpi.getBigDecimal("sell"))) + } + return messages + } catch (e: IOException) { + throw ModuleException(debugMessage, "An IO error has occurred retrieving the bitcoin market price.", e) + } catch (e: NullPointerException) { + throw ModuleException(debugMessage, "An error has occurred retrieving the bitcoin market price.", e) + } catch (e: org.json.JSONException) { + throw ModuleException( + debugMessage, "A parsing error has occurred retriving the bitcoin market price.", e) + } + } + } + + init { + commands.add(BITCOIN_CMD) + help.add("To retrieve the bitcoin market price:") + help.add(Utils.helpFormat("%c $BITCOIN_CMD ")) + } +} diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/modules/BitcoinTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/modules/BitcoinTest.kt new file mode 100644 index 0000000..8453420 --- /dev/null +++ b/src/test/kotlin/net/thauvin/erik/mobibot/modules/BitcoinTest.kt @@ -0,0 +1,55 @@ +/* + * BitcoinTest.kt + * + * Copyright (c) 2004-2021, Erik C. Thauvin (erik@thauvin.net) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.thauvin.erik.mobibot.modules + +import net.thauvin.erik.mobibot.LocalProperties +import net.thauvin.erik.mobibot.modules.Bitcoin.Companion.marketPrice +import org.assertj.core.api.Assertions.assertThat +import org.testng.annotations.Test + +/** + * The `BitcoinTest` class. + */ +class BitcoinTest : LocalProperties() { + @Test + @Throws(ModuleException::class) + fun testMarketPrice() { + var messages = marketPrice("USD") + assertThat(messages).`as`("not empty").isNotEmpty + assertThat(messages[0].msg).`as`("btc & $").startsWith("BTC").contains("$") + assertThat(messages[1].msg).`as`("15m").contains("15m") + + messages = marketPrice("GBP") + assertThat(messages[0].msg).`as`("BPB btc & £").startsWith("BTC").contains("£") + assertThat(messages[1].msg).`as`("GBP 15m").contains("15m") + } +} diff --git a/version.properties b/version.properties index 9d6cb44..dcde182 100644 --- a/version.properties +++ b/version.properties @@ -1,9 +1,9 @@ #Generated by the Semver Plugin for Gradle -#Mon May 03 14:17:39 PDT 2021 -version.buildmeta=576 +#Tue May 04 01:01:14 PDT 2021 +version.buildmeta=606 version.major=0 version.minor=8 version.patch=0 version.prerelease=beta version.project=mobibot -version.semver=0.8.0-beta+576 +version.semver=0.8.0-beta+606