diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 0e65cea..4251b72 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 2c288cf..e3d2267 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,8 +5,8 @@ plugins {
id 'io.gitlab.arturbosch.detekt' version '1.22.0'
id 'java'
id 'net.thauvin.erik.gradle.semver' version '1.0.4'
- id 'org.jetbrains.kotlin.jvm' version '1.7.21'
- id 'org.jetbrains.kotlin.kapt' version '1.7.21'
+ id 'org.jetbrains.kotlin.jvm' version '1.7.22'
+ id 'org.jetbrains.kotlin.kapt' version '1.7.22'
id 'org.jetbrains.kotlinx.kover' version '0.6.1'
id 'org.sonarqube' version '3.5.0.2730'
id 'pmd'
@@ -30,7 +30,7 @@ mainClassName = packageName + '.Mobibot'
ext.versions = [
log4j: '2.19.0',
- pmd : '6.51.0',
+ pmd : '6.52.0',
]
repositories {
@@ -52,7 +52,7 @@ dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-text:1.10.0'
implementation 'commons-codec:commons-codec:1.15'
- implementation 'commons-net:commons-net:3.8.0'
+ implementation 'commons-net:commons-net:3.9.0'
// Google
implementation 'com.google.code.gson:gson:2.10'
@@ -65,7 +65,7 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
// Logging
- implementation 'org.slf4j:slf4j-api:2.0.4'
+ implementation 'org.slf4j:slf4j-api:2.0.5'
implementation "org.apache.logging.log4j:log4j-api:$versions.log4j"
implementation "org.apache.logging.log4j:log4j-core:$versions.log4j"
implementation "org.apache.logging.log4j:log4j-slf4j2-impl:$versions.log4j"
diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index 49d6c04..370754e 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -11,6 +11,7 @@
LongParameterList:Comment.kt$Comment$( channel: String, cmd: String, entry: EntryLink, entryIndex: Int, commentIndex: Int, event: GenericMessageEvent )LongParameterList:EntryLink.kt$EntryLink$( // Link's comments val comments: MutableList<EntryComment> = mutableListOf(), // Tags/categories val tags: MutableList<SyndCategory> = mutableListOf(), // Channel var channel: String, // Creation date var date: Date = Calendar.getInstance().time, // Link's URL var link: String, // Author's login var login: String = "", // Author's nickname var nick: String, // Link's title var title: String )LongParameterList:Twitter.kt$Twitter.Companion$( consumerKey: String?, consumerSecret: String?, token: String?, tokenSecret: String?, handle: String?, message: String, isDm: Boolean )
+ MagicNumber:ChatGpt.kt$ChatGpt.Companion$200MagicNumber:Comment.kt$Comment$3MagicNumber:CryptoPrices.kt$CryptoPrices$10MagicNumber:CurrencyConverter.kt$CurrencyConverter$11
@@ -46,6 +47,7 @@
MaxLineLength:TwitterOAuth.kt$TwitterOAuth$*NestedBlockDepth:Addons.kt$Addons$fun add(command: AbstractCommand)NestedBlockDepth:Addons.kt$Addons$fun add(module: AbstractModule)
+ NestedBlockDepth:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?): StringNestedBlockDepth:Comment.kt$Comment$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$@JvmStatic fun convertCurrency(query: String): MessageNestedBlockDepth:EntryLink.kt$EntryLink$private fun setTags(tags: List<String?>)
@@ -72,6 +74,7 @@
SwallowedException:GoogleSearchTest.kt$GoogleSearchTest$e: ModuleExceptionSwallowedException:StockQuoteTest.kt$StockQuoteTest$e: ModuleExceptionSwallowedException:WolframAlphaTest.kt$WolframAlphaTest$e: ModuleException
+ ThrowsCount:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?): StringThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message>ThrowsCount:Joke.kt$Joke.Companion$@JvmStatic @Throws(ModuleException::class) fun randomJoke(): List<Message>ThrowsCount:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message>
diff --git a/config/pmd.xml b/config/pmd.xml
index 890a490..e23c3fa 100644
--- a/config/pmd.xml
+++ b/config/pmd.xml
@@ -72,7 +72,6 @@
-
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e9ae037..f398c33 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-rc-3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/properties/mobibot.properties b/properties/mobibot.properties
index facff63..981fced 100644
--- a/properties/mobibot.properties
+++ b/properties/mobibot.properties
@@ -69,3 +69,8 @@ tell-max-size=50
#
#wolfram-appid=
#wolfram-units=imperial
+
+#
+# ChatGPT/OpenAI API key from: https://beta.openai.com/account/api-keys
+#
+#chatgpt-api-key=
diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt b/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt
index 6dbe080..94c7a8c 100644
--- a/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt
+++ b/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt
@@ -68,6 +68,7 @@ import net.thauvin.erik.mobibot.commands.links.View
import net.thauvin.erik.mobibot.commands.seen.Seen
import net.thauvin.erik.mobibot.commands.tell.Tell
import net.thauvin.erik.mobibot.modules.Calc
+import net.thauvin.erik.mobibot.modules.ChatGpt
import net.thauvin.erik.mobibot.modules.CryptoPrices
import net.thauvin.erik.mobibot.modules.CurrencyConverter
import net.thauvin.erik.mobibot.modules.Dice
@@ -432,6 +433,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
// Load the modules
addons.add(Calc())
+ addons.add(ChatGpt())
addons.add(CryptoPrices())
addons.add(CurrencyConverter())
addons.add(Dice())
diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/modules/ChatGpt.kt b/src/main/kotlin/net/thauvin/erik/mobibot/modules/ChatGpt.kt
new file mode 100644
index 0000000..8593cb6
--- /dev/null
+++ b/src/main/kotlin/net/thauvin/erik/mobibot/modules/ChatGpt.kt
@@ -0,0 +1,153 @@
+/*
+ * ChatGpt.kt
+ *
+ * Copyright (c) 2004-2022, 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.Utils
+import net.thauvin.erik.mobibot.Utils.sendMessage
+import org.json.JSONException
+import org.json.JSONObject
+import org.json.JSONWriter
+import org.pircbotx.hooks.types.GenericMessageEvent
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.IOException
+import java.net.URI
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+
+class ChatGpt : ThreadedModule() {
+ private val logger: Logger = LoggerFactory.getLogger(ChatGpt::class.java)
+
+ override val name = "ChatGPT"
+
+ override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
+ if (args.isNotBlank()) {
+ try {
+ event.sendMessage(
+ chat(
+ args.trim(),
+ properties[CHATGPT_API_KEY]
+ )
+ )
+ } catch (e: ModuleException) {
+ if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
+ e.message?.let {
+ event.respond(it)
+ }
+ }
+ } else {
+ helpResponse(event)
+ }
+ }
+
+ companion object {
+ /**
+ * The ChatGPT API Key property.
+ */
+ const val CHATGPT_API_KEY = "chatgpt-api-key"
+
+
+ // ChatGPT command
+ private const val CHATGPT_CMD = "chatgpt"
+
+ // ChatGPT API URL
+ private const val API_URL = "https://api.openai.com/v1/completions"
+
+ @JvmStatic
+ @Throws(ModuleException::class)
+ fun chat(query: String, apiKey: String?): String {
+ if (!apiKey.isNullOrEmpty()) {
+ val prompt = JSONWriter.valueToString("Q:$query\nA:")
+ val request = HttpRequest.newBuilder()
+ .uri(URI.create(API_URL))
+ .header("Content-Type", "application/json")
+ .header("Authorization", "Bearer $apiKey")
+ .POST(
+ HttpRequest.BodyPublishers.ofString(
+ """{
+ "model": "text-davinci-003",
+ "prompt": $prompt,
+ "temperature": 0,
+ "max_tokens": 100,
+ "top_p": 1,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "stop": ["\n"]
+ }""".trimIndent()
+ )
+ )
+ .build()
+ try {
+ val response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString())
+ if (response.statusCode() == 200) {
+ try {
+ val jsonResponse = JSONObject(response.body())
+ println(response.body());
+ var choices = jsonResponse.getJSONArray("choices")
+ return choices.getJSONObject(0).getString("text").trim()
+ } catch (e: JSONException) {
+ throw ModuleException(
+ "chatgpt($query): JSON",
+ "A JSON error has occurred while conversing with ChatGPT.",
+ e
+ )
+ }
+ } else {
+ throw IOException("Status Code: " + response.statusCode())
+ }
+ } catch (e: IOException) {
+ throw ModuleException(
+ "chatgpt($query): IO",
+ "An IO error has occurred while conversing with GhatGPT.",
+ e
+ )
+ }
+ } else {
+ throw ModuleException("chatgpt($query)", "No ChatGPT API key specified.")
+ }
+ }
+ }
+
+ init {
+ commands.add(CHATGPT_CMD)
+ with(help) {
+ add("To get answers from ChatGPT:")
+ add(Utils.helpFormat("%c $CHATGPT_CMD "))
+ add("For example:")
+ add(Utils.helpFormat("%c $CHATGPT_CMD explain quantum computing in simple terms"))
+ add(Utils.helpFormat("%c $CHATGPT_CMD how do I make an HTTP request in Javascript?"))
+ }
+ initProperties(CHATGPT_API_KEY)
+ }
+}
diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/FeedReaderTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/FeedReaderTest.kt
index 4e75ef0..2fa58b6 100644
--- a/src/test/kotlin/net/thauvin/erik/mobibot/FeedReaderTest.kt
+++ b/src/test/kotlin/net/thauvin/erik/mobibot/FeedReaderTest.kt
@@ -60,12 +60,12 @@ class FeedReaderTest {
index(1).prop(Message::msg).contains("erik.thauvin.net")
}
- messages = readFeed("https://lorem-rss.herokuapp.com/feed?length=0")
+ messages = readFeed("https://www.mobitopia.org/mobibot/logs/2021-10-27.xml")
assertThat(messages, "messages").index(0).prop(Message::msg).contains("nothing")
- messages = readFeed("https://lorem-rss.herokuapp.com/feed?length=42", 42)
+ messages = readFeed("https://www.mobitopia.org/mobibot/logs/2005-10-11.xml", 42)
assertThat(messages, "messages").size().isEqualTo(84)
- assertThat(messages.last(), "messages.last").prop(Message::msg).contains("http://example.com/test/")
+ assertThat(messages.last(), "messages.last").prop(Message::msg).contains("techdigest.tv")
assertThat { readFeed("blah") }.isFailure().isInstanceOf(MalformedURLException::class.java)
diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/modules/ChatGptTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/modules/ChatGptTest.kt
new file mode 100644
index 0000000..f230831
--- /dev/null
+++ b/src/test/kotlin/net/thauvin/erik/mobibot/modules/ChatGptTest.kt
@@ -0,0 +1,58 @@
+/*
+ * ChatGptTest.kt
+ *
+ * Copyright (c) 2004-2022, 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 assertk.assertThat
+import assertk.assertions.contains
+import assertk.assertions.hasNoCause
+import assertk.assertions.isFailure
+import assertk.assertions.isInstanceOf
+import net.thauvin.erik.mobibot.LocalProperties
+import org.testng.annotations.Test
+
+class ChatGptTest : LocalProperties() {
+ @Test(groups = ["modules"])
+ fun testApiKey() {
+ assertThat { ChatGpt.chat("1 gallon to liter", "") }
+ .isFailure()
+ .isInstanceOf(ModuleException::class.java)
+ .hasNoCause()
+ }
+
+ @Test(groups = ["modules"])
+ fun testChat() {
+ val apiKey = getProperty(ChatGpt.CHATGPT_API_KEY)
+ assertThat(
+ ChatGpt.chat("how do I make an HTTP request in Javascript?", apiKey)
+ ).contains("XMLHttpRequest")
+ }
+}
diff --git a/version.properties b/version.properties
index c9d4380..ce4a4d7 100644
--- a/version.properties
+++ b/version.properties
@@ -1,9 +1,9 @@
#Generated by the Semver Plugin for Gradle
-#Mon Nov 21 10:09:13 PST 2022
-version.buildmeta=764
+#Sun Dec 04 16:10:05 PST 2022
+version.buildmeta=798
version.major=0
version.minor=8
version.patch=0
version.prerelease=rc
version.project=mobibot
-version.semver=0.8.0-rc+764
+version.semver=0.8.0-rc+798