From e9a45206fa7e7d6a61ef317c9e03e1b2233bded5 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 26 Jun 2019 18:10:07 +0200 Subject: [PATCH 01/13] Add Basic RCON Support Add Connector Can Send Message TODO: Error Catching Response from server --- .../service/rcon/RconConnector.scala | 80 +++++++++++++++++++ .../service/rcon/impl/RconOutputImpl.scala | 19 +++++ 2 files changed, 99 insertions(+) create mode 100644 src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala create mode 100644 src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala new file mode 100644 index 00000000..3e7bfc3c --- /dev/null +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala @@ -0,0 +1,80 @@ +package org.codeoverflow.chatoverflow.requirement.service.rcon + +import java.io.{InputStream, OutputStream} +import java.net.Socket +import java.nio.{ByteBuffer, ByteOrder} +import java.util.Random + +import org.codeoverflow.chatoverflow.WithLogger +import org.codeoverflow.chatoverflow.connector.Connector + +class RconConnector(override val sourceIdentifier: String) extends Connector(sourceIdentifier) with WithLogger { + override protected var requiredCredentialKeys: List[String] = List("password", "address") + override protected var optionalCredentialKeys: List[String] = List("port") + + private var socket: Socket = _ + private var outputStream: OutputStream = _ + private var inputStream: InputStream = _ + private var requestId: Int = 0 + + def sendCommand(command: String): String = { + logger debug s"Sending $command to RCON" + requestId += 1 + write(2, command.getBytes("ASCII")) + "" + } + + + /** + * Starts the connector, e.g. creates a connection with its platform. + */ + override def start(): Boolean = { + logger info s"Starting rcon connection to ${credentials.get.getValue("address").get}" + var port: Int = 25575 + if (credentials.get.exists("port")) { + port = credentials.get.getValue("port").get.toInt + if (port < 1 || port > 65535) { + return false + } + } + socket = new Socket(credentials.get.getValue("address").get, port) + outputStream = socket.getOutputStream + inputStream = socket.getInputStream + login() + true + } + + private def login(): Unit = { + requestId = new Random().nextInt(Integer.MAX_VALUE) + logger info "Logging RCON in..." + val password = credentials.get.getValue("password").get + write(3, password.getBytes("ASCII")) + logger debug "RCON Login sent" + } + + private def write(packageType: Int, payload: Array[Byte]): Boolean = { + val length = 4 + 4 + payload.length + 1 + 1 + var byteBuffer: ByteBuffer = ByteBuffer.allocate(length + 4) + byteBuffer.order(ByteOrder.LITTLE_ENDIAN) + + byteBuffer.putInt(length) + byteBuffer.putInt(requestId) + byteBuffer.putInt(packageType) + byteBuffer.put(payload) + byteBuffer.put(0x00.toByte) + byteBuffer.put(0x00.toByte) + + outputStream.write(byteBuffer.array()) + outputStream.flush() + true + } + + /** + * This stops the activity of the connector, e.g. by closing the platform connection. + */ + override def stop(): Boolean = { + logger info s"Stopped RCON connector to ${credentials.get.getValue("address")}!" + socket.close() + true + } +} diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala new file mode 100644 index 00000000..bbba2b6b --- /dev/null +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala @@ -0,0 +1,19 @@ +package org.codeoverflow.chatoverflow.requirement.service.rcon.impl + +import org.codeoverflow.chatoverflow.WithLogger +import org.codeoverflow.chatoverflow.api.io.output.RconOutput +import org.codeoverflow.chatoverflow.registry.Impl +import org.codeoverflow.chatoverflow.requirement.OutputImpl +import org.codeoverflow.chatoverflow.requirement.service.rcon.RconConnector + +@Impl(impl = classOf[RconOutput], connector = classOf[RconConnector]) +class RconOutputImpl extends OutputImpl[RconConnector] with RconOutput with WithLogger { + override def sendCommand(command: String): String = sourceConnector.get.sendCommand(command) + + /** + * Start the input, called after source connector did init + * + * @return true if starting the input was successful, false if some problems occurred + */ + override def start(): Boolean = true +} From 6dea5bc75abc76d541e3c6630b0b472bff6d97af Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 26 Jun 2019 21:21:19 +0200 Subject: [PATCH 02/13] Cache connection errors. Wait 5 secounds till login to cause less connection errors. --- .../service/rcon/RconConnector.scala | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala index 3e7bfc3c..fa0a25e4 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala @@ -1,7 +1,7 @@ package org.codeoverflow.chatoverflow.requirement.service.rcon import java.io.{InputStream, OutputStream} -import java.net.Socket +import java.net.{Socket, SocketException} import java.nio.{ByteBuffer, ByteOrder} import java.util.Random @@ -38,9 +38,11 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou } } socket = new Socket(credentials.get.getValue("address").get, port) + socket.setKeepAlive(true) outputStream = socket.getOutputStream inputStream = socket.getInputStream login() + Thread.sleep(5000) true } @@ -53,19 +55,30 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou } private def write(packageType: Int, payload: Array[Byte]): Boolean = { - val length = 4 + 4 + payload.length + 1 + 1 - var byteBuffer: ByteBuffer = ByteBuffer.allocate(length + 4) - byteBuffer.order(ByteOrder.LITTLE_ENDIAN) + try { + val length = 4 + 4 + payload.length + 1 + 1 + var byteBuffer: ByteBuffer = ByteBuffer.allocate(length + 4) + byteBuffer.order(ByteOrder.LITTLE_ENDIAN) - byteBuffer.putInt(length) - byteBuffer.putInt(requestId) - byteBuffer.putInt(packageType) - byteBuffer.put(payload) - byteBuffer.put(0x00.toByte) - byteBuffer.put(0x00.toByte) + byteBuffer.putInt(length) + byteBuffer.putInt(requestId) + byteBuffer.putInt(packageType) + byteBuffer.put(payload) + byteBuffer.put(0x00.toByte) + byteBuffer.put(0x00.toByte) - outputStream.write(byteBuffer.array()) - outputStream.flush() + outputStream.write(byteBuffer.array()) + outputStream.flush() + } catch { + case e: NullPointerException => { + logger error "There was and is no Connection to the RCON Server, please try restarting." + return false + } + case e: SocketException => { + logger error "Connection Error to RCON Server. This request will not be sended!" + return false + } + } true } From f51df0fe4b6e38f2dee15b09e3abad28eb511b5b Mon Sep 17 00:00:00 2001 From: Felix Date: Thu, 27 Jun 2019 19:57:46 +0200 Subject: [PATCH 03/13] Add reading of incomming RCON messages --- .../service/rcon/RconConnector.scala | 46 +++++++++++++++++-- .../service/rcon/impl/RconInputImpl.scala | 19 ++++++++ .../service/rcon/impl/RconOutputImpl.scala | 6 ++- 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala index fa0a25e4..a569fc56 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala @@ -1,6 +1,6 @@ package org.codeoverflow.chatoverflow.requirement.service.rcon -import java.io.{InputStream, OutputStream} +import java.io.{DataInputStream, InputStream, OutputStream} import java.net.{Socket, SocketException} import java.nio.{ByteBuffer, ByteOrder} import java.util.Random @@ -16,12 +16,19 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou private var outputStream: OutputStream = _ private var inputStream: InputStream = _ private var requestId: Int = 0 + private var loggedIn = false def sendCommand(command: String): String = { + if (!loggedIn) { + logger error "Could not execute RCON Command due to wrong password or no connection" + return null + } logger debug s"Sending $command to RCON" requestId += 1 - write(2, command.getBytes("ASCII")) - "" + if (write(2, command.getBytes("ASCII"))) { + return read() + } + null } @@ -50,8 +57,14 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou requestId = new Random().nextInt(Integer.MAX_VALUE) logger info "Logging RCON in..." val password = credentials.get.getValue("password").get - write(3, password.getBytes("ASCII")) - logger debug "RCON Login sent" + if (write(3, password.getBytes("ASCII"))) { + if (read() == null) { + logger error "Could not log in to RCON Server. Password is Wrong!" + } else { + logger debug "Login to RCON was successful" + loggedIn = true + } + } } private def write(packageType: Int, payload: Array[Byte]): Boolean = { @@ -82,6 +95,29 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou true } + private def read(): String = { + try { + val header: Array[Byte] = Array.ofDim[Byte](4*3) + inputStream.read(header) + val headerBuffer: ByteBuffer = ByteBuffer.wrap(header) + headerBuffer.order(ByteOrder.LITTLE_ENDIAN) + val length = headerBuffer.getInt() + val packageType = headerBuffer.getInt + val payload: Array[Byte] = Array.ofDim[Byte](length - 4 - 4 - 2) + val dataInputStream: DataInputStream = new DataInputStream(inputStream) + dataInputStream.readFully(payload) + dataInputStream.read(Array.ofDim[Byte](2)) + if (packageType == -1) { + return null + } + new String(payload, "ASCII") + } catch { + case e: NegativeArraySizeException => null; + } + } + + private[rcon] def isLoggedIn: Boolean = loggedIn + /** * This stops the activity of the connector, e.g. by closing the platform connection. */ diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala new file mode 100644 index 00000000..7c6031ce --- /dev/null +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala @@ -0,0 +1,19 @@ +package org.codeoverflow.chatoverflow.requirement.service.rcon.impl + +import org.codeoverflow.chatoverflow.WithLogger +import org.codeoverflow.chatoverflow.api.io.input.RconInput +import org.codeoverflow.chatoverflow.registry.Impl +import org.codeoverflow.chatoverflow.requirement.InputImpl +import org.codeoverflow.chatoverflow.requirement.service.rcon.RconConnector + +@Impl(impl = classOf[RconInput], connector = classOf[RconConnector]) +class RconInputImpl extends InputImpl[RconConnector] with RconInput with WithLogger { + override def getCommandOutput(command: String): String = sourceConnector.get.sendCommand(command) + + /** + * Start the input, called after source connector did init + * + * @return true if starting the input was successful, false if some problems occurred + */ + override def start(): Boolean = sourceConnector.get.isLoggedIn +} diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala index bbba2b6b..bd8b61cf 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala @@ -8,12 +8,14 @@ import org.codeoverflow.chatoverflow.requirement.service.rcon.RconConnector @Impl(impl = classOf[RconOutput], connector = classOf[RconConnector]) class RconOutputImpl extends OutputImpl[RconConnector] with RconOutput with WithLogger { - override def sendCommand(command: String): String = sourceConnector.get.sendCommand(command) + override def sendCommand(command: String): Boolean = { + sourceConnector.get.sendCommand(command) != null + } /** * Start the input, called after source connector did init * * @return true if starting the input was successful, false if some problems occurred */ - override def start(): Boolean = true + override def start(): Boolean = sourceConnector.get.isLoggedIn } From 2cc0fe81ee781e5c597cbfd9d65b2d6a15889f5b Mon Sep 17 00:00:00 2001 From: DragonCoder01 Date: Mon, 8 Jul 2019 17:35:38 +0200 Subject: [PATCH 04/13] Fixed some typos in the README.md file. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c3f5176f..46501964 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ What if you could **combine** the power of with your interactive chat in your livestream. What if you could easily react on events, e.g. - Automatically **share** your new subscribers on twitter -- Automatically **control** your studio's lighting colors trough chat messages +- Automatically **control** your studio's lighting colors through chat messages - Automatically **post** an user's cheer on your minecraft server - Automatically **upload** a youtube video with stream highlights when your stream stops -and so much more. We know, there is [IFTTT](https://ifttt.com/). But sometimes, building blocks are to generic and services not optimized for your streaming environment. +and so much more. We know, there is [IFTTT](https://ifttt.com/). But sometimes, building blocks are to generic and services aren't optimized for your streaming environment. The alternative: Develop everything by yourself and waste hundreds of hours with API-integration. We already solved this problem for you. This is **Chat Overflow**. @@ -22,7 +22,7 @@ The alternative: Develop everything by yourself and waste hundreds of hours with **Chat Overflow** is a plugin framework, which offers ready-to-use platform integrations for all* major streaming- and social-media-sites. -**Chat Overflow** enables you to to level up your stream with by writing simple, platform-independent plugins in java or scala**. +**Chat Overflow** enables you to to level up your stream by writing simple, platform-independent plugins in java or scala**. It's getting even better: The **Chat Overflow** license allows you to sell your custom plugins, creating new services for other streamers. @@ -39,22 +39,22 @@ And it's so easy. Here is all the code to get started with a simple twitch chat \* There are still missing platforms. This is a open-source project. You can [help](https://github.com/codeoverflow-org/chatoverflow/issues), too! -\** The API is written in java. So, every JVM-compatible language is possible. Java, Scala, Kotlin, ... +\** The API is written in java. So, every JVM-compatible language is possible. Java, Scala, Kotlin, etc. ### Installation / Releases Head over to [releases](https://github.com/codeoverflow-org/chatoverflow/releases). Just download the newest zip file, make sure that java is installed and launch the framework. -Note, that you'll have to develop your own plugins or search for plugins online (e.g. on our [Discord Server](https://discord.gg/p2HDsme)). **Chat Overflow** is only the framework. +Note that you'll have to develop your own plugins or search for plugins online (e.g. on our [Discord Server](https://discord.gg/p2HDsme)). **Chat Overflow** is only the framework. ### Development Start with the [Installation](https://github.com/codeoverflow-org/chatoverflow/wiki/Installation). Then learn more about the [CLI](https://github.com/codeoverflow-org/chatoverflow/wiki/Using-the-CLI). -Please see the wiki to learn how to code new [platform sources](https://github.com/codeoverflow-org/chatoverflow/wiki/Adding-a-new-platform-source) and new [plugins](https://github.com/codeoverflow-org/chatoverflow/wiki/Writing-a-plugin). +Please consult the wiki to learn how to code new [platform sources](https://github.com/codeoverflow-org/chatoverflow/wiki/Adding-a-new-platform-source) and new [plugins](https://github.com/codeoverflow-org/chatoverflow/wiki/Writing-a-plugin). -***Pre-Alpha note***: Please note, that the development workflow and the documentation will be updated soon. +***Pre-Alpha note***: Please note that the development workflow and the documentation will be updated soon. ### Discord From caf186b0b952b5707957f685f39a060d1b84282c Mon Sep 17 00:00:00 2001 From: Daniel Huber Date: Thu, 11 Jul 2019 18:27:54 +0200 Subject: [PATCH 05/13] Wait for authentication in twitch bot and report invalid credentials --- .../chat/TwitchChatConnectListener.scala | 25 ++++++++++++ .../twitch/chat/TwitchChatConnector.scala | 40 ++++++++++++++----- 2 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnectListener.scala diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnectListener.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnectListener.scala new file mode 100644 index 00000000..e3a74bfa --- /dev/null +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnectListener.scala @@ -0,0 +1,25 @@ +package org.codeoverflow.chatoverflow.requirement.service.twitch.chat + +import org.pircbotx.hooks.events.{ConnectAttemptFailedEvent, ConnectEvent, NoticeEvent} +import org.pircbotx.hooks.{Event, ListenerAdapter} + +/** + * Handles connection events for the TwitchChatConnector. + * Calls the callback function once the bot connected and reports connection errors. + * @param fn the callback which will be called once suitable event has been received. + * The first param informs whether the connection could be established successfully + * and the second param includes a error description if something has gone wrong. + */ +class TwitchChatConnectListener(fn: (Boolean, String) => Unit) extends ListenerAdapter { + override def onEvent(event: Event): Unit = { + event match { + case _: ConnectEvent => fn(true, "") + case e: ConnectAttemptFailedEvent => fn(false, "couldn't connect to irc chat server") + case e: NoticeEvent => + if (e.getNotice.contains("authentication failed")) { + fn(false, "authentication failed") + } + case _ => + } + } +} diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala index 3ee7107f..7a54e24f 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala @@ -15,10 +15,12 @@ import scala.collection.mutable.ListBuffer */ class TwitchChatConnector(override val sourceIdentifier: String) extends Connector(sourceIdentifier) with WithLogger { private val twitchChatListener = new TwitchChatListener + private val connectionListener = new TwitchChatConnectListener(onConnect) private val oauthKey = "oauth" override protected var requiredCredentialKeys: List[String] = List(oauthKey) override protected var optionalCredentialKeys: List[String] = List() private var bot: PircBotX = _ + private var status: Option[(Boolean, String)] = None private val channels = ListBuffer[String]() def addMessageEventListener(listener: MessageEvent => Unit): Unit = { @@ -63,6 +65,7 @@ class TwitchChatConnector(override val sourceIdentifier: String) extends Connect .setName(credentials.get.credentialsIdentifier) .setServerPassword(password.getOrElse("")) .addListener(twitchChatListener) + .addListener(connectionListener) .buildConfiguration() } else { logger error "No credentials set!" @@ -71,33 +74,47 @@ class TwitchChatConnector(override val sourceIdentifier: String) extends Connect } + /** + * Gets called by the TwitchChatConnectListener when the bot has connected. + * Saves the passed information into the status variable. + */ + private def onConnect(success: Boolean, msg: String): Unit = { + status.synchronized { + // tell the thread which starts the connector that the status has been reported + status.notify() + status = Some((success, msg)) + } + } + /** * Starts the connector, e.g. creates a connection with its platform. */ override def start(): Boolean = { bot = new PircBotX(getConfig) startBot() - true } - private def startBot(): Unit = { - - var errorCount = 0 - + private def startBot(): Boolean = { new Thread(() => { bot.startBot() }).start() - while (bot.getState != PircBotX.State.CONNECTED && errorCount < 30) { - logger info "Waiting while the bot is connecting..." - Thread.sleep(100) - errorCount += 1 + logger info "Waiting while the bot is connecting and logging in..." + status.synchronized { + status.wait(10000) + } + + if (status.isEmpty) { + logger error "Bot couldn't connect within timeout of 10 seconds." + return false } - if (errorCount >= 30) { - logger error "Fatal. Unable to start bot." + val (success, msg) = status.get + if (!success) { + logger error s"Bot couldn't connect. Reason: $msg." } + success } /** @@ -106,6 +123,7 @@ class TwitchChatConnector(override val sourceIdentifier: String) extends Connect override def stop(): Boolean = { bot.sendIRC().quitServer() bot.close() + status = None true } } From ab8031d85e055eaf98d07a3b1f493c1ac480feaa Mon Sep 17 00:00:00 2001 From: Daniel Huber Date: Thu, 11 Jul 2019 18:30:36 +0200 Subject: [PATCH 06/13] Clear joined channels of TwitchChatConnector on stop If the channels aren't cleared and the connector is restarted multiple times it won't join into any channels that the connector has joined before the restart because it still thinks it has joined them already --- .../requirement/service/twitch/chat/TwitchChatConnector.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala index 7a54e24f..ebd1fb06 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala @@ -124,6 +124,7 @@ class TwitchChatConnector(override val sourceIdentifier: String) extends Connect bot.sendIRC().quitServer() bot.close() status = None + channels.clear() true } } From ee0d583cff54d724f2f4d74e704828b608cce038 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 13 Jul 2019 15:06:03 +0200 Subject: [PATCH 07/13] Fix #89: Clear all event handlers when shutting down an event input --- .../requirement/impl/EventInputImpl.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/impl/EventInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/impl/EventInputImpl.scala index 695c5634..ac44bcec 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/impl/EventInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/impl/EventInputImpl.scala @@ -39,4 +39,15 @@ abstract class EventInputImpl[T <: Event, C <: Connector](implicit ctc: ClassTag handlers.filter(handler => handler.clazz == cts.runtimeClass) .foreach(handler => handler.consumer.asInstanceOf[Consumer[S]].accept(event)) } + + override def shutdown(): Boolean = { + if (sourceConnector.isDefined) { + val stopped = stop() + handlers.clear() + stopped & sourceConnector.get.shutdown() + } else { + logger warn "Source connector not set." + false + } + } } From 4de98d7d01eb8e4e9fd7ee41453a013bbf119fa6 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 13 Jul 2019 16:40:25 +0200 Subject: [PATCH 08/13] Fix #89: Remove an inputs listeners from the connector when shutting down --- .../discord/DiscordChatConnector.scala | 15 +++++++++++++++ .../service/discord/DiscordChatListener.scala | 10 ++++++++++ .../discord/impl/DiscordChatInputImpl.scala | 9 ++++++++- .../service/serial/SerialConnector.scala | 19 ++++++++++++++++--- .../serial/SerialPortInputListener.scala | 2 ++ .../service/serial/impl/SerialInputImpl.scala | 9 +++++++-- .../tipeeestream/TipeeestreamConnector.scala | 6 ++++++ .../tipeeestream/TipeeestreamListener.scala | 12 ++++++++++++ .../impl/TipeestreamEventInputImpl.scala | 7 ++++++- .../twitch/chat/TwitchChatConnector.scala | 8 ++++++++ .../twitch/chat/TwitchChatListener.scala | 8 ++++++++ .../chat/impl/TwitchChatInputImpl.scala | 6 +++++- 12 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatConnector.scala index 37cb8dbb..59aafbb2 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatConnector.scala @@ -47,6 +47,21 @@ class DiscordChatConnector(override val sourceIdentifier: String) extends Connec def addReactionDelEventListener(listener: MessageReactionRemoveEvent => Unit): Unit = discordChatListener.addReactionDelEventListener(listener) + def removeMessageReceivedListener(listener: MessageReceivedEvent => Unit): Unit = + discordChatListener.removeMessageReceivedListener(listener) + + def removeMessageUpdateListener(listener: MessageUpdateEvent => Unit): Unit = + discordChatListener.removeMessageUpdateEventListener(listener) + + def removeMessageDeleteListener(listener: MessageDeleteEvent => Unit): Unit = + discordChatListener.removeMessageDeleteEventListener(listener) + + def removeReactionAddEventListener(listener: MessageReactionAddEvent => Unit): Unit = + discordChatListener.removeReactionAddEventListener(listener) + + def removeReactionDelEventListener(listener: MessageReactionRemoveEvent => Unit): Unit = + discordChatListener.removeReactionDelEventListener(listener) + /** * Connects to discord */ diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatListener.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatListener.scala index fb94c5a2..7196b36d 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatListener.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/DiscordChatListener.scala @@ -32,6 +32,16 @@ class DiscordChatListener extends EventListener { def addReactionDelEventListener(listener: MessageReactionRemoveEvent => Unit): Unit = reactionDelEventListener += listener + def removeMessageReceivedListener(listener: MessageReceivedEvent => Unit): Unit = messageEventListener -= listener + + def removeMessageUpdateEventListener(listener: MessageUpdateEvent => Unit): Unit = messageUpdateEventListener -= listener + + def removeMessageDeleteEventListener(listener: MessageDeleteEvent => Unit): Unit = messageDeleteEventListener -= listener + + def removeReactionAddEventListener(listener: MessageReactionAddEvent => Unit): Unit = reactionAddEventListener -= listener + + def removeReactionDelEventListener(listener: MessageReactionRemoveEvent => Unit): Unit = reactionDelEventListener -= listener + override def onEvent(event: Event): Unit = { event match { case receivedEvent: MessageReceivedEvent => messageEventListener.foreach(listener => listener(receivedEvent)) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala index 9ce19b36..97156f6e 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala @@ -81,7 +81,14 @@ class DiscordChatInputImpl extends EventInputImpl[DiscordEvent, DiscordChatConne * * @return true if stopping was successful */ - override def stop(): Boolean = true + override def stop(): Boolean = { + sourceConnector.get.removeMessageReceivedListener(onMessage) + sourceConnector.get.removeMessageUpdateListener(onMessageUpdate) + sourceConnector.get.removeMessageDeleteListener(onMessageDelete) + sourceConnector.get.removeReactionAddEventListener(onReactionAdded) + sourceConnector.get.removeReactionDelEventListener(onReactionRemoved) + true + } /** * Listens for received messages, parses the data, adds them to the buffer and handles them over to the correct handler diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialConnector.scala index d570613b..70b75cda 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialConnector.scala @@ -2,10 +2,12 @@ package org.codeoverflow.chatoverflow.requirement.service.serial import java.io.{InputStream, PrintStream} -import com.fazecast.jSerialComm.{SerialPort, SerialPortInvalidPortException} +import com.fazecast.jSerialComm.{SerialPort, SerialPortEvent, SerialPortInvalidPortException} import org.codeoverflow.chatoverflow.WithLogger import org.codeoverflow.chatoverflow.connector.Connector +import scala.collection.mutable + /** * The serial connector allows to communicate with a device connected to the pcs serial port (like an Arduino) * @@ -19,6 +21,7 @@ class SerialConnector(override val sourceIdentifier: String) extends Connector(s private var serialPort: Option[SerialPort] = None private var out: Option[PrintStream] = None private var in: Option[InputStream] = None + private val inputListeners: mutable.Map[Array[Byte] => Unit, SerialPortEvent => Unit] = mutable.Map() /** * @throws java.lang.IllegalStateException if the serial port is not available yet @@ -49,11 +52,20 @@ class SerialConnector(override val sourceIdentifier: String) extends Connector(s @throws(classOf[IllegalStateException]) def addInputListener(listener: Array[Byte] => Unit): Unit = { if (serialPort.isEmpty) throw new IllegalStateException("Serial port is not available yet") - serialPortInputListener.addDataAvailableListener(_ => { + val l: SerialPortEvent => Unit = _ => { val buffer = new Array[Byte](serialPort.get.bytesAvailable()) serialPort.get.readBytes(buffer, buffer.length) listener(buffer) - }) + } + inputListeners += (listener -> l) + serialPortInputListener.addDataAvailableListener(l) + } + + def removeInputListener(listener: Array[Byte] => Unit): Unit = { + inputListeners remove listener match { + case Some(l) => serialPortInputListener.removeDataAvailableListener(l) + case _ => //listener not found, do nothing + } } /** @@ -93,6 +105,7 @@ class SerialConnector(override val sourceIdentifier: String) extends Connector(s * Closes the connection with the port */ override def stop(): Boolean = { + serialPort.foreach(_.closePort()) true } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialPortInputListener.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialPortInputListener.scala index ee41ec79..caa7c796 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialPortInputListener.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/SerialPortInputListener.scala @@ -17,4 +17,6 @@ class SerialPortInputListener extends SerialPortDataListener { } def addDataAvailableListener(listener: SerialPortEvent => Unit): Unit = listeners += listener + + def removeDataAvailableListener(listener: SerialPortEvent => Unit): Unit = listeners += listener } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala index 103b7529..73a79af5 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala @@ -13,10 +13,12 @@ import org.codeoverflow.chatoverflow.requirement.service.serial.SerialConnector class SerialInputImpl extends EventInputImpl[SerialEvent, SerialConnector] with SerialInput with WithLogger { override def start(): Boolean = { - sourceConnector.get.addInputListener(bytes => call(new SerialDataAvailableEvent(bytes))) + sourceConnector.get.addInputListener(onInput) true } + private def onInput(bytes: Array[Byte]): Unit = call(new SerialDataAvailableEvent(bytes)) + override def getInputStream: InputStream = sourceConnector.get.getInputStream /** @@ -24,5 +26,8 @@ class SerialInputImpl extends EventInputImpl[SerialEvent, SerialConnector] with * * @return true if stopping was successful */ - override def stop(): Boolean = true + override def stop(): Boolean = { + sourceConnector.get.removeInputListener(onInput) + true + } } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamConnector.scala index f8d160c0..655aa3f0 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamConnector.scala @@ -86,6 +86,12 @@ class TipeeestreamConnector(override val sourceIdentifier: String) extends Conne def addFollowEventListener(listener: JSONObject => Unit): Unit = tipeeeStreamListener.addFollowEventListener(listener) + def removeSubscriptionEventListener(listener: JSONObject => Unit): Unit = tipeeeStreamListener.removeSubscriptionEventListener(listener) + + def removeDonationEventListener(listener: JSONObject => Unit): Unit = tipeeeStreamListener.removeDonationEventListener(listener) + + def removeFollowEventListener(listener: JSONObject => Unit): Unit = tipeeeStreamListener.removeFollowEventListener(listener) + override def stop(): Boolean = { socket.foreach(_.close()) true diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamListener.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamListener.scala index 180f370d..92aef724 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamListener.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/TipeeestreamListener.scala @@ -23,6 +23,18 @@ class TipeeestreamListener { followEventListeners += listener } + def removeSubscriptionEventListener(listener: JSONObject => Unit): Unit = { + subscriptionEventListeners -= listener + } + + def removeDonationEventListener(listener: JSONObject => Unit): Unit = { + donationEventListeners -= listener + } + + def removeFollowEventListener(listener: JSONObject => Unit): Unit = { + followEventListeners -= listener + } + def onSocketEvent(objects : Array[AnyRef]) : Unit = { val json: JSONObject = objects(0).asInstanceOf[JSONObject] val event: JSONObject = json.getJSONObject("event") diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala index d0241b2a..eb14b5fa 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala @@ -83,5 +83,10 @@ class TipeestreamEventInputImpl extends EventInputImpl[TipeeestreamEvent, Tipeee } } - override def stop(): Boolean = true + override def stop(): Boolean = { + sourceConnector.get.removeFollowEventListener(onFollow) + sourceConnector.get.removeSubscriptionEventListener(onSubscription) + sourceConnector.get.removeDonationEventListener(onDonation) + true + } } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala index ebd1fb06..e925b08b 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatConnector.scala @@ -31,6 +31,14 @@ class TwitchChatConnector(override val sourceIdentifier: String) extends Connect twitchChatListener.addUnknownEventListener(listener) } + def removeMessageEventListener(listener: MessageEvent => Unit): Unit = { + twitchChatListener.removeMessageEventListener(listener) + } + + def removeUnknownEventListener(listener: UnknownEvent => Unit): Unit = { + twitchChatListener.removeUnknownEventListener(listener) + } + def joinChannel(channel: String): Unit = { bot.send().joinChannel(channel) channels += channel diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatListener.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatListener.scala index d4148403..c8037496 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatListener.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/TwitchChatListener.scala @@ -29,4 +29,12 @@ class TwitchChatListener extends ListenerAdapter { unknownEventListener += listener } + def removeMessageEventListener(listener: MessageEvent => Unit): Unit = { + messageEventListener -= listener + } + + def removeUnknownEventListener(listener: UnknownEvent => Unit): Unit = { + unknownEventListener -= listener + } + } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala index 35594adc..58b512e5 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala @@ -104,5 +104,9 @@ class TwitchChatInputImpl extends EventInputImpl[TwitchEvent, chat.TwitchChatCon * * @return true if stopping was successful */ - override def stop(): Boolean = true + override def stop(): Boolean = { + sourceConnector.get.removeMessageEventListener(onMessage) + sourceConnector.get.removeUnknownEventListener(onUnknown) + true + } } \ No newline at end of file From 30398851bda16a349ba93b380f9804c27d0424f8 Mon Sep 17 00:00:00 2001 From: Daniel Huber Date: Sat, 13 Jul 2019 19:17:15 +0200 Subject: [PATCH 09/13] Pass functions to register method from inputs instead of methods --- .../service/serial/impl/SerialInputImpl.scala | 6 ++++-- .../impl/TipeestreamEventInputImpl.scala | 16 ++++++++++------ .../twitch/chat/impl/TwitchChatInputImpl.scala | 13 ++++++++----- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala index 73a79af5..ad9d4f27 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/serial/impl/SerialInputImpl.scala @@ -12,8 +12,10 @@ import org.codeoverflow.chatoverflow.requirement.service.serial.SerialConnector @Impl(impl = classOf[SerialInput], connector = classOf[SerialConnector]) class SerialInputImpl extends EventInputImpl[SerialEvent, SerialConnector] with SerialInput with WithLogger { + private val onInputFn = onInput _ + override def start(): Boolean = { - sourceConnector.get.addInputListener(onInput) + sourceConnector.get.addInputListener(onInputFn) true } @@ -27,7 +29,7 @@ class SerialInputImpl extends EventInputImpl[SerialEvent, SerialConnector] with * @return true if stopping was successful */ override def stop(): Boolean = { - sourceConnector.get.removeInputListener(onInput) + sourceConnector.get.removeInputListener(onInputFn) true } } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala index eb14b5fa..e6b89887 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/tipeeestream/impl/TipeestreamEventInputImpl.scala @@ -19,10 +19,14 @@ class TipeestreamEventInputImpl extends EventInputImpl[TipeeestreamEvent, Tipeee private val DATE_FORMATTER = new DateTimeFormatterBuilder() .parseCaseInsensitive().append(DateTimeFormatter.ISO_LOCAL_DATE_TIME).appendOffset("+HHMM", "Z").toFormatter + private val onFollowFn = onFollow _ + private val onSubscriptionFn = onSubscription _ + private val onDonationFn = onDonation _ + override def start(): Boolean = { - sourceConnector.get.addFollowEventListener(onFollow) - sourceConnector.get.addSubscriptionEventListener(onSubscription) - sourceConnector.get.addDonationEventListener(onDonation) + sourceConnector.get.addFollowEventListener(onFollowFn) + sourceConnector.get.addSubscriptionEventListener(onSubscriptionFn) + sourceConnector.get.addDonationEventListener(onDonationFn) true } @@ -84,9 +88,9 @@ class TipeestreamEventInputImpl extends EventInputImpl[TipeeestreamEvent, Tipeee } override def stop(): Boolean = { - sourceConnector.get.removeFollowEventListener(onFollow) - sourceConnector.get.removeSubscriptionEventListener(onSubscription) - sourceConnector.get.removeDonationEventListener(onDonation) + sourceConnector.get.removeFollowEventListener(onFollowFn) + sourceConnector.get.removeSubscriptionEventListener(onSubscriptionFn) + sourceConnector.get.removeDonationEventListener(onDonationFn) true } } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala index 58b512e5..4271dcb8 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/twitch/chat/impl/TwitchChatInputImpl.scala @@ -9,7 +9,7 @@ import org.codeoverflow.chatoverflow.api.io.dto.chat.{ChatEmoticon, TextChannel} import org.codeoverflow.chatoverflow.api.io.event.chat.twitch.{TwitchChatMessageReceiveEvent, TwitchEvent, TwitchPrivateChatMessageReceiveEvent} import org.codeoverflow.chatoverflow.api.io.input.chat._ import org.codeoverflow.chatoverflow.registry.Impl -import org.codeoverflow.chatoverflow.requirement.impl.{EventInputImpl, InputImpl} +import org.codeoverflow.chatoverflow.requirement.impl.EventInputImpl import org.codeoverflow.chatoverflow.requirement.service.twitch.chat import org.codeoverflow.chatoverflow.requirement.service.twitch.chat.TwitchChatConnector import org.pircbotx.hooks.events.{MessageEvent, UnknownEvent} @@ -31,9 +31,12 @@ class TwitchChatInputImpl extends EventInputImpl[TwitchEvent, chat.TwitchChatCon private var currentChannel: Option[String] = None + private val onMessageFn = onMessage _ + private val onUnknownFn = onUnknown _ + override def start(): Boolean = { - sourceConnector.get.addMessageEventListener(onMessage) - sourceConnector.get.addUnknownEventListener(onUnknown) + sourceConnector.get.addMessageEventListener(onMessageFn) + sourceConnector.get.addUnknownEventListener(onUnknownFn) true } @@ -105,8 +108,8 @@ class TwitchChatInputImpl extends EventInputImpl[TwitchEvent, chat.TwitchChatCon * @return true if stopping was successful */ override def stop(): Boolean = { - sourceConnector.get.removeMessageEventListener(onMessage) - sourceConnector.get.removeUnknownEventListener(onUnknown) + sourceConnector.get.removeMessageEventListener(onMessageFn) + sourceConnector.get.removeUnknownEventListener(onUnknownFn) true } } \ No newline at end of file From 2b80cc383693b13484a4f78706d4b98a4d682f1b Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 13 Jul 2019 21:01:22 +0200 Subject: [PATCH 10/13] Also fix function passing for discord --- .../discord/impl/DiscordChatInputImpl.scala | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala index 97156f6e..efcdd05b 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/discord/impl/DiscordChatInputImpl.scala @@ -34,12 +34,18 @@ class DiscordChatInputImpl extends EventInputImpl[DiscordEvent, DiscordChatConne private val privateMessages = ListBuffer[DiscordChatMessage]() private var channelId: Option[String] = None + private val onMessageFn = onMessage _ + private val onMessageUpdateFn = onMessageUpdate _ + private val onMessageDeleteFn = onMessageDelete _ + private val onReactionAddedFn = onReactionAdded _ + private val onReactionRemovedFn = onReactionRemoved _ + override def start(): Boolean = { - sourceConnector.get.addMessageReceivedListener(onMessage) - sourceConnector.get.addMessageUpdateListener(onMessageUpdate) - sourceConnector.get.addMessageDeleteListener(onMessageDelete) - sourceConnector.get.addReactionAddEventListener(onReactionAdded) - sourceConnector.get.addReactionDelEventListener(onReactionRemoved) + sourceConnector.get.addMessageReceivedListener(onMessageFn) + sourceConnector.get.addMessageUpdateListener(onMessageUpdateFn) + sourceConnector.get.addMessageDeleteListener(onMessageDeleteFn) + sourceConnector.get.addReactionAddEventListener(onReactionAddedFn) + sourceConnector.get.addReactionDelEventListener(onReactionRemovedFn) true } @@ -82,11 +88,11 @@ class DiscordChatInputImpl extends EventInputImpl[DiscordEvent, DiscordChatConne * @return true if stopping was successful */ override def stop(): Boolean = { - sourceConnector.get.removeMessageReceivedListener(onMessage) - sourceConnector.get.removeMessageUpdateListener(onMessageUpdate) - sourceConnector.get.removeMessageDeleteListener(onMessageDelete) - sourceConnector.get.removeReactionAddEventListener(onReactionAdded) - sourceConnector.get.removeReactionDelEventListener(onReactionRemoved) + sourceConnector.get.removeMessageReceivedListener(onMessageFn) + sourceConnector.get.removeMessageUpdateListener(onMessageUpdateFn) + sourceConnector.get.removeMessageDeleteListener(onMessageDeleteFn) + sourceConnector.get.removeReactionAddEventListener(onReactionAddedFn) + sourceConnector.get.removeReactionDelEventListener(onReactionRemovedFn) true } From 71fc2c91ad465e1da3fd49de924fe01e59c684ac Mon Sep 17 00:00:00 2001 From: Jonas Date: Sun, 14 Jul 2019 13:11:00 +0200 Subject: [PATCH 11/13] Update version number to 0.2.1-prealpha --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index af0f91b9..307b300c 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ // --------------------------------------------------------------------------------------------------------------------- name := "ChatOverflow" -version := "0.2" +version := "0.2.1" mainClass := Some("org.codeoverflow.chatoverflow.Launcher") // One version for all sub projects. Use "retrieveManaged := true" to download and show all library dependencies. From 2315ecbcba2fcbe706cbf50b8b15ea70a5406aef Mon Sep 17 00:00:00 2001 From: Felix Date: Thu, 18 Jul 2019 16:50:00 +0200 Subject: [PATCH 12/13] Make RCON Connector Ready for new ChatOverflow version --- .../requirement/service/rcon/impl/RconInputImpl.scala | 4 +++- .../requirement/service/rcon/impl/RconOutputImpl.scala | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala index 7c6031ce..060cb725 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala @@ -3,7 +3,7 @@ package org.codeoverflow.chatoverflow.requirement.service.rcon.impl import org.codeoverflow.chatoverflow.WithLogger import org.codeoverflow.chatoverflow.api.io.input.RconInput import org.codeoverflow.chatoverflow.registry.Impl -import org.codeoverflow.chatoverflow.requirement.InputImpl +import org.codeoverflow.chatoverflow.requirement.impl.InputImpl import org.codeoverflow.chatoverflow.requirement.service.rcon.RconConnector @Impl(impl = classOf[RconInput], connector = classOf[RconConnector]) @@ -16,4 +16,6 @@ class RconInputImpl extends InputImpl[RconConnector] with RconInput with WithLog * @return true if starting the input was successful, false if some problems occurred */ override def start(): Boolean = sourceConnector.get.isLoggedIn + + override def stop(): Boolean = true } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala index bd8b61cf..a010b899 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala @@ -3,7 +3,7 @@ package org.codeoverflow.chatoverflow.requirement.service.rcon.impl import org.codeoverflow.chatoverflow.WithLogger import org.codeoverflow.chatoverflow.api.io.output.RconOutput import org.codeoverflow.chatoverflow.registry.Impl -import org.codeoverflow.chatoverflow.requirement.OutputImpl +import org.codeoverflow.chatoverflow.requirement.impl.OutputImpl import org.codeoverflow.chatoverflow.requirement.service.rcon.RconConnector @Impl(impl = classOf[RconOutput], connector = classOf[RconConnector]) @@ -18,4 +18,11 @@ class RconOutputImpl extends OutputImpl[RconConnector] with RconOutput with With * @return true if starting the input was successful, false if some problems occurred */ override def start(): Boolean = sourceConnector.get.isLoggedIn + + /** + * Stops the output, called before source connector will shutdown + * + * @return true if stopping was successful + */ + override def stop(): Boolean = true } From c509b1340d330387efaa6ce9f1053c20c3dd0e20 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 21 Jul 2019 12:37:42 +0200 Subject: [PATCH 13/13] Change requested implementations in RCON service. Only pass true to start if start and login are succesful But than always start Impl Also quit if port is no valid number --- .../service/rcon/RconConnector.scala | 51 +++++++++++-------- .../service/rcon/impl/RconInputImpl.scala | 2 +- .../service/rcon/impl/RconOutputImpl.scala | 2 +- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala index a569fc56..82615095 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/RconConnector.scala @@ -1,6 +1,6 @@ package org.codeoverflow.chatoverflow.requirement.service.rcon -import java.io.{DataInputStream, InputStream, OutputStream} +import java.io.{DataInputStream, IOException, InputStream, OutputStream} import java.net.{Socket, SocketException} import java.nio.{ByteBuffer, ByteOrder} import java.util.Random @@ -16,13 +16,8 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou private var outputStream: OutputStream = _ private var inputStream: InputStream = _ private var requestId: Int = 0 - private var loggedIn = false def sendCommand(command: String): String = { - if (!loggedIn) { - logger error "Could not execute RCON Command due to wrong password or no connection" - return null - } logger debug s"Sending $command to RCON" requestId += 1 if (write(2, command.getBytes("ASCII"))) { @@ -39,32 +34,50 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou logger info s"Starting rcon connection to ${credentials.get.getValue("address").get}" var port: Int = 25575 if (credentials.get.exists("port")) { - port = credentials.get.getValue("port").get.toInt + try{ + port = credentials.get.getValue("port").get.toInt + } catch { + case e: NumberFormatException => { + logger error "Please enter a valid port" + return false + } + } if (port < 1 || port > 65535) { + logger error "Please enter a valid port" + return false + } + } + try { + socket = new Socket(credentials.get.getValue("address").get, port) + socket.setKeepAlive(true) + outputStream = socket.getOutputStream + inputStream = socket.getInputStream + } catch { + case e: IOException => { + logger error "No Connection to RCON Server. Is it up?" return false } } - socket = new Socket(credentials.get.getValue("address").get, port) - socket.setKeepAlive(true) - outputStream = socket.getOutputStream - inputStream = socket.getInputStream - login() + val loggedIn = login() + // Sleeping here to allow the (minecraft) server to start its own rcon procedure. Otherwise it caused errors in my tests. Thread.sleep(5000) - true + loggedIn } - private def login(): Unit = { + private def login(): Boolean = { requestId = new Random().nextInt(Integer.MAX_VALUE) logger info "Logging RCON in..." val password = credentials.get.getValue("password").get if (write(3, password.getBytes("ASCII"))) { if (read() == null) { logger error "Could not log in to RCON Server. Password is Wrong!" + return false } else { logger debug "Login to RCON was successful" - loggedIn = true + return true } } + false } private def write(packageType: Int, payload: Array[Byte]): Boolean = { @@ -83,10 +96,6 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou outputStream.write(byteBuffer.array()) outputStream.flush() } catch { - case e: NullPointerException => { - logger error "There was and is no Connection to the RCON Server, please try restarting." - return false - } case e: SocketException => { logger error "Connection Error to RCON Server. This request will not be sended!" return false @@ -116,13 +125,11 @@ class RconConnector(override val sourceIdentifier: String) extends Connector(sou } } - private[rcon] def isLoggedIn: Boolean = loggedIn - /** * This stops the activity of the connector, e.g. by closing the platform connection. */ override def stop(): Boolean = { - logger info s"Stopped RCON connector to ${credentials.get.getValue("address")}!" + logger info s"Stopped RCON connector to ${credentials.get.getValue("address").get}!" socket.close() true } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala index 060cb725..e837b9a8 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconInputImpl.scala @@ -15,7 +15,7 @@ class RconInputImpl extends InputImpl[RconConnector] with RconInput with WithLog * * @return true if starting the input was successful, false if some problems occurred */ - override def start(): Boolean = sourceConnector.get.isLoggedIn + override def start(): Boolean = true override def stop(): Boolean = true } diff --git a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala index a010b899..087a19f9 100644 --- a/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala +++ b/src/main/scala/org/codeoverflow/chatoverflow/requirement/service/rcon/impl/RconOutputImpl.scala @@ -17,7 +17,7 @@ class RconOutputImpl extends OutputImpl[RconConnector] with RconOutput with With * * @return true if starting the input was successful, false if some problems occurred */ - override def start(): Boolean = sourceConnector.get.isLoggedIn + override def start(): Boolean = true /** * Stops the output, called before source connector will shutdown