Reworked the URL reader to include the code and body text when an error is returned.
This commit is contained in:
parent
2a2ab39b5f
commit
951fdaa5f7
13 changed files with 84 additions and 40 deletions
4
.github/workflows/gradle.yml
vendored
4
.github/workflows/gradle.yml
vendored
|
@ -49,8 +49,6 @@ jobs:
|
|||
env:
|
||||
CI_NAME: "GitHub CI"
|
||||
ALPHAVANTAGE_API_KEY: ${{ secrets.ALPHAVANTAGE_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
GOOGLE_CSE_CX: ${{ secrets.GOOGLE_CSE_CX }}
|
||||
OWM_API_KEY: ${{ secrets.OWM_API_KEY }}
|
||||
PINBOARD_API_TOKEN: ${{ secrets.PINBOARD_API_TOKEN }}
|
||||
TWITTER_CONSUMERKEY: ${{ secrets.TWITTER_CONSUMERKEY }}
|
||||
|
@ -58,8 +56,6 @@ jobs:
|
|||
TWITTER_HANDLE: ${{ secrets.TWITTER_HANDLE }}
|
||||
TWITTER_TOKEN: ${{ secrets.TWITTER_TOKEN }}
|
||||
TWITTER_TOKENSECRET: ${{ secrets.TWITTER_TOKENSECRET }}
|
||||
WOLFRAM_API_KEY: ${{ secrets.WOLFRAM_API_KEY }}
|
||||
WOLFRAM_UNITS: ${{ secrets.WOLFRAM_UNITS }}
|
||||
run: ./gradlew build check --stacktrace
|
||||
|
||||
- name: SonarCloud
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
<ID>MagicNumber:Twitter.kt$Twitter$60L</ID>
|
||||
<ID>MagicNumber:TwitterOAuth.kt$TwitterOAuth$401</ID>
|
||||
<ID>MagicNumber:Users.kt$Users$8</ID>
|
||||
<ID>MagicNumber:Utils.kt$Utils$200</ID>
|
||||
<ID>MagicNumber:Utils.kt$Utils$399</ID>
|
||||
<ID>MagicNumber:Weather2.kt$Weather2.Companion$1.60934</ID>
|
||||
<ID>MagicNumber:Weather2.kt$Weather2.Companion$32</ID>
|
||||
<ID>MagicNumber:Weather2.kt$Weather2.Companion$404</ID>
|
||||
|
@ -65,12 +67,12 @@
|
|||
<ID>ReturnCount:ExceptionSanitizer.kt$ExceptionSanitizer$fun ModuleException.sanitize(vararg sanitize: String): ModuleException</ID>
|
||||
<ID>SwallowedException:GoogleSearchTest.kt$GoogleSearchTest$e: ModuleException</ID>
|
||||
<ID>SwallowedException:StockQuoteTest.kt$StockQuoteTest$e: ModuleException</ID>
|
||||
<ID>SwallowedException:WolframAlpha.kt$WolframAlpha.Companion$ioe: IOException</ID>
|
||||
<ID>SwallowedException:WolframAlphaTest.kt$WolframAlphaTest$e: ModuleException</ID>
|
||||
<ID>ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message></ID>
|
||||
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message></ID>
|
||||
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject</ID>
|
||||
<ID>ThrowsCount:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message></ID>
|
||||
<ID>ThrowsCount:WolframAlpha.kt$WolframAlpha.Companion$@JvmStatic @Throws(ModuleException::class) fun queryWolfram(query: String, units: String = IMPERIAL, appId: String?): String</ID>
|
||||
<ID>TooGenericExceptionCaught:StockQuote.kt$StockQuote.Companion$e: NullPointerException</ID>
|
||||
<ID>TooGenericExceptionCaught:Weather2.kt$Weather2.Companion$e: NullPointerException</ID>
|
||||
<ID>TooManyFunctions:Mobibot.kt$Mobibot : ListenerAdapter</ID>
|
||||
|
|
|
@ -65,7 +65,7 @@ tell-max-size=50
|
|||
#alphavantage-api-key=
|
||||
|
||||
#
|
||||
# Get Wolfram Alpa API key from: https://developer.wolframalpha.com/portal/
|
||||
# Get Wolfram Alpa AppID from: https://developer.wolframalpha.com/portal/
|
||||
#
|
||||
#wolfram-api-key=
|
||||
#wolfram-appid=
|
||||
#wolfram-units=imperial
|
||||
|
|
|
@ -41,11 +41,10 @@ import org.pircbotx.hooks.types.GenericMessageEvent
|
|||
import org.slf4j.Logger
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
@ -56,7 +55,6 @@ import java.time.ZoneId
|
|||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Date
|
||||
import java.util.Properties
|
||||
import java.util.stream.Collectors
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.fileSize
|
||||
|
||||
|
@ -186,6 +184,12 @@ object Utils {
|
|||
return event.bot().userChannelDao.getChannel(channel).isOp(event.user)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a HTTP status code indicates a successful response.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Int.isHttpSuccess() = this in 200..399
|
||||
|
||||
/**
|
||||
* Returns the last item of a list of strings or empty if none.
|
||||
*/
|
||||
|
@ -402,8 +406,21 @@ object Utils {
|
|||
*/
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun URL.reader(): String {
|
||||
BufferedReader(InputStreamReader(this.openStream(), StandardCharsets.UTF_8))
|
||||
.use { reader -> return reader.lines().collect(Collectors.joining(System.lineSeparator())) }
|
||||
fun URL.reader(): UrlReaderResponse {
|
||||
val connection = this.openConnection() as HttpURLConnection
|
||||
connection.setRequestProperty(
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"
|
||||
)
|
||||
return if (connection.responseCode.isHttpSuccess()) {
|
||||
UrlReaderResponse(connection.responseCode, connection.inputStream.bufferedReader().readText())
|
||||
} else {
|
||||
UrlReaderResponse(connection.responseCode, connection.errorStream.bufferedReader().readText())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the [URL.reader] response code and body text.
|
||||
*/
|
||||
data class UrlReaderResponse(val responseCode: Int, val body: String)
|
||||
}
|
||||
|
|
|
@ -80,11 +80,11 @@ class CryptoPrices : ThreadedModule() {
|
|||
} catch (e: CryptoException) {
|
||||
if (logger.isWarnEnabled) logger.warn("$debugMessage => ${e.statusCode}", e)
|
||||
e.message?.let {
|
||||
event.sendMessage(it)
|
||||
event.respond(it)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
if (logger.isErrorEnabled) logger.error(debugMessage, e)
|
||||
event.sendMessage("An IO error has occurred while retrieving the cryptocurrency market price.")
|
||||
event.respond("An IO error has occurred while retrieving the cryptocurrency market price.")
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
|
|
|
@ -143,7 +143,7 @@ class CurrencyConverter : ThreadedModule() {
|
|||
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())
|
||||
val json = JSONObject(url.reader().body)
|
||||
|
||||
if (json.getBoolean("success")) {
|
||||
PublicMessage(
|
||||
|
@ -170,7 +170,7 @@ class CurrencyConverter : ThreadedModule() {
|
|||
fun loadSymbols() {
|
||||
try {
|
||||
val url = URL("https://api.exchangerate.host/symbols")
|
||||
val json = JSONObject(url.reader())
|
||||
val json = JSONObject(url.reader().body)
|
||||
if (json.getBoolean("success")) {
|
||||
val symbols = json.getJSONObject("symbols")
|
||||
for (key in symbols.keys()) {
|
||||
|
|
|
@ -76,7 +76,7 @@ class GoogleSearch : ThreadedModule() {
|
|||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.sendMessage(it)
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -118,7 +118,7 @@ class GoogleSearch : ThreadedModule() {
|
|||
"https://www.googleapis.com/customsearch/v1?key=$apiKey&cx=$cseKey" +
|
||||
""aUser=${quotaUser}&q=${query.encodeUrl()}&filter=1&num=5&alt=json"
|
||||
)
|
||||
val json = JSONObject(url.reader())
|
||||
val json = JSONObject(url.reader().body)
|
||||
if (json.has("items")) {
|
||||
val ja = json.getJSONArray("items")
|
||||
for (i in 0 until ja.length()) {
|
||||
|
@ -126,6 +126,10 @@ class GoogleSearch : ThreadedModule() {
|
|||
results.add(NoticeMessage(j.getString("title").unescapeXml()))
|
||||
results.add(NoticeMessage(helpFormat(j.getString("link"), false), Colors.DARK_GREEN))
|
||||
}
|
||||
} else if (json.has("error")) {
|
||||
val error = json.getJSONObject("error")
|
||||
val message = error.getString("message")
|
||||
throw ModuleException("searchGoogle($query): ${error.getInt("code")} : $message", message)
|
||||
} else {
|
||||
results.add(ErrorMessage("No results found.", Colors.RED))
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class StockQuote : ThreadedModule() {
|
|||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.sendMessage(it)
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -147,7 +147,7 @@ class StockQuote : ThreadedModule() {
|
|||
response = URL(
|
||||
"${ALPHAVANTAGE_URL}SYMBOL_SEARCH&keywords=" + symbol.encodeUrl() + "&apikey="
|
||||
+ apiKey.encodeUrl()
|
||||
).reader()
|
||||
).reader().body
|
||||
var json = getJsonResponse(response, debugMessage)
|
||||
val symbols = json.getJSONArray("bestMatches")
|
||||
if (symbols.isEmpty) {
|
||||
|
@ -160,7 +160,7 @@ class StockQuote : ThreadedModule() {
|
|||
"${ALPHAVANTAGE_URL}GLOBAL_QUOTE&symbol="
|
||||
+ symbolInfo.getString("1. symbol").encodeUrl() + "&apikey="
|
||||
+ apiKey.encodeUrl()
|
||||
).reader()
|
||||
).reader().body
|
||||
json = getJsonResponse(response, debugMessage)
|
||||
val quote = json.getJSONObject("Global Quote")
|
||||
if (quote.isEmpty) {
|
||||
|
|
|
@ -34,6 +34,7 @@ package net.thauvin.erik.mobibot.modules
|
|||
|
||||
import net.thauvin.erik.mobibot.Utils
|
||||
import net.thauvin.erik.mobibot.Utils.encodeUrl
|
||||
import net.thauvin.erik.mobibot.Utils.isHttpSuccess
|
||||
import net.thauvin.erik.mobibot.Utils.reader
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
@ -68,13 +69,13 @@ class WolframAlpha : ThreadedModule() {
|
|||
} else {
|
||||
getUnits(properties[WOLFRAM_UNITS_PROP])
|
||||
},
|
||||
apiKey = properties[WOLFRAM_API_KEY_PROP]
|
||||
appId = properties[WOLFRAM_APPID_KEY]
|
||||
)
|
||||
)
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.sendMessage(it)
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -86,7 +87,7 @@ class WolframAlpha : ThreadedModule() {
|
|||
/**
|
||||
* The Wolfram Alpha API Key property.
|
||||
*/
|
||||
const val WOLFRAM_API_KEY_PROP = "wolfram-api-key"
|
||||
const val WOLFRAM_APPID_KEY = "wolfram-appid"
|
||||
|
||||
/**
|
||||
* The Wolfram units properties
|
||||
|
@ -103,14 +104,23 @@ class WolframAlpha : ThreadedModule() {
|
|||
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun queryWolfram(query: String, units: String = IMPERIAL, apiKey: String?): String {
|
||||
if (!apiKey.isNullOrEmpty()) {
|
||||
fun queryWolfram(query: String, units: String = IMPERIAL, appId: String?): String {
|
||||
if (!appId.isNullOrEmpty()) {
|
||||
try {
|
||||
return URL("${API_URL}${apiKey}&units=${units}&i=" + query.encodeUrl()).reader()
|
||||
val urlReader = URL("${API_URL}${appId}&units=${units}&i=" + query.encodeUrl()).reader()
|
||||
if (urlReader.responseCode.isHttpSuccess()) {
|
||||
return urlReader.body
|
||||
} else {
|
||||
throw ModuleException(
|
||||
"wolfram($query): ${urlReader.responseCode} : ${urlReader.body} ",
|
||||
urlReader.body.ifEmpty {
|
||||
"Looks like Wolfram Alpha isn't able to answer that. (${urlReader.responseCode})"
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (ioe: IOException) {
|
||||
throw ModuleException(
|
||||
"wolfram($query): IOE",
|
||||
"Looks like Wolfram Alpha isn't able to answer that.",
|
||||
"wolfram($query): IOE", "An IO Error occurred while querying Wolfram Alpha.", ioe
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -128,6 +138,6 @@ class WolframAlpha : ThreadedModule() {
|
|||
add(Utils.helpFormat("%c $WOLFRAM_CMD days until christmas"))
|
||||
add(Utils.helpFormat("%c $WOLFRAM_CMD distance earth moon units=metric"))
|
||||
}
|
||||
initProperties(WOLFRAM_API_KEY_PROP, WOLFRAM_UNITS_PROP)
|
||||
initProperties(WOLFRAM_APPID_KEY, WOLFRAM_UNITS_PROP)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ class PinboardTest : LocalProperties() {
|
|||
|
||||
private fun validatePin(apiToken: String, url: String, vararg matches: String): Boolean {
|
||||
val response =
|
||||
URL("https://api.pinboard.in/v1/posts/get?auth_token=${apiToken}&tag=test&" + url.encodeUrl()).reader()
|
||||
URL("https://api.pinboard.in/v1/posts/get?auth_token=${apiToken}&tag=test&" + url.encodeUrl()).reader().body
|
||||
|
||||
matches.forEach {
|
||||
if (!response.contains(it)) {
|
||||
|
|
|
@ -267,7 +267,7 @@ class UtilsTest {
|
|||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun testUrlReader() {
|
||||
assertThat(URL("https://postman-echo.com/status/200").reader(), "urlReader()")
|
||||
assertThat(URL("https://postman-echo.com/status/200").reader().body, "urlReader()")
|
||||
.isEqualTo("{\"status\":200}")
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ package net.thauvin.erik.mobibot.modules
|
|||
import assertk.all
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.contains
|
||||
import assertk.assertions.hasMessage
|
||||
import assertk.assertions.hasNoCause
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isFailure
|
||||
|
@ -63,6 +64,11 @@ class GoogleSearchTest : LocalProperties() {
|
|||
|
||||
assertThat { searchGoogle("test", "apiKey", "") }.isFailure()
|
||||
.isInstanceOf(ModuleException::class.java).hasNoCause()
|
||||
|
||||
assertThat { searchGoogle("test", "apiKey", "cssKey") }
|
||||
.isFailure()
|
||||
.isInstanceOf(ModuleException::class.java)
|
||||
.hasMessage("API key not valid. Please pass a valid API key.")
|
||||
}
|
||||
|
||||
@Test(groups = ["no-ci", "modules"])
|
||||
|
|
|
@ -34,6 +34,7 @@ package net.thauvin.erik.mobibot.modules
|
|||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.contains
|
||||
import assertk.assertions.hasMessage
|
||||
import assertk.assertions.isFailure
|
||||
import assertk.assertions.isInstanceOf
|
||||
import net.thauvin.erik.mobibot.ExceptionSanitizer.sanitize
|
||||
|
@ -41,21 +42,29 @@ import net.thauvin.erik.mobibot.LocalProperties
|
|||
import org.testng.annotations.Test
|
||||
|
||||
class WolframAlphaTest : LocalProperties() {
|
||||
@Test(groups = ["modules"])
|
||||
@Test(groups=["modules"])
|
||||
fun testAppId() {
|
||||
assertThat { WolframAlpha.queryWolfram("1 gallon to liter", appId = "DEMO") }
|
||||
.isFailure()
|
||||
.isInstanceOf(ModuleException::class.java)
|
||||
.hasMessage("Error 1: Invalid appid")
|
||||
|
||||
assertThat { WolframAlpha.queryWolfram("1 gallon to liter", appId = "") }
|
||||
.isFailure()
|
||||
.isInstanceOf(ModuleException::class.java)
|
||||
}
|
||||
|
||||
@Test(groups = ["modules", "no-ci"])
|
||||
@Throws(ModuleException::class)
|
||||
fun queryWolframTest() {
|
||||
val apiKey = getProperty(WolframAlpha.WOLFRAM_API_KEY_PROP)
|
||||
val apiKey = getProperty(WolframAlpha.WOLFRAM_APPID_KEY)
|
||||
try {
|
||||
assertThat(WolframAlpha.queryWolfram("SFO to SEA", apiKey = apiKey), "SFO to SEA").contains("miles")
|
||||
assertThat(WolframAlpha.queryWolfram("SFO to SEA", appId = apiKey), "SFO to SEA").contains("miles")
|
||||
|
||||
assertThat(
|
||||
WolframAlpha.queryWolfram("SFO to LAX", WolframAlpha.METRIC, apiKey),
|
||||
"SFO to LA"
|
||||
).contains("kilometers")
|
||||
|
||||
assertThat { WolframAlpha.queryWolfram("1 gallon to liter", apiKey = "") }
|
||||
.isFailure()
|
||||
.isInstanceOf(ModuleException::class.java)
|
||||
} catch (e: ModuleException) {
|
||||
// Avoid displaying api key in CI logs
|
||||
if ("true" == System.getenv("CI")) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue