Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Adding RCON Connector #68

Merged
merged 18 commits into from
Nov 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e9a4520
Add Basic RCON Support
strifel Jun 26, 2019
296d451
Merge branch 'master' of https://github.com/strifel/chatoverflow
strifel Jun 26, 2019
6dea5bc
Cache connection errors. Wait 5 secounds till login to cause less con…
strifel Jun 26, 2019
f51df0f
Add reading of incomming RCON messages
strifel Jun 27, 2019
2cc0fe8
Fixed some typos in the README.md file.
Jul 8, 2019
a6bac81
Merge pull request #77 from DragonCoder01/fixreadme
sebinside Jul 8, 2019
caf186b
Wait for authentication in twitch bot and report invalid credentials
hlxid Jul 11, 2019
ab8031d
Clear joined channels of TwitchChatConnector on stop
hlxid Jul 11, 2019
ee0d583
Fix #89: Clear all event handlers when shutting down an event input
J0B10 Jul 13, 2019
4de98d7
Fix #89: Remove an inputs listeners from the connector when shutting …
J0B10 Jul 13, 2019
3039885
Pass functions to register method from inputs instead of methods
hlxid Jul 13, 2019
70956ce
Merge pull request #90 from daniel0611/fix/89-handler-called-twice
J0B10 Jul 13, 2019
2b80cc3
Also fix function passing for discord
J0B10 Jul 13, 2019
71fc2c9
Update version number to 0.2.1-prealpha
J0B10 Jul 14, 2019
8fdba80
Merge branch 'hotfix/0.2.1-prealpha'
J0B10 Jul 14, 2019
7181a78
Merge branch 'master' of https://github.com/codeoverflow-org/chatover…
strifel Jul 15, 2019
2315ecb
Make RCON Connector Ready for new ChatOverflow version
strifel Jul 18, 2019
c509b13
Change requested implementations in RCON service.
strifel Jul 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ 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**.

## The ChatOverflow Project

**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.

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -81,7 +87,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(onMessageFn)
sourceConnector.get.removeMessageUpdateListener(onMessageUpdateFn)
sourceConnector.get.removeMessageDeleteListener(onMessageDeleteFn)
sourceConnector.get.removeReactionAddEventListener(onReactionAddedFn)
sourceConnector.get.removeReactionDelEventListener(onReactionRemovedFn)
true
}

/**
* Listens for received messages, parses the data, adds them to the buffer and handles them over to the correct handler
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package org.codeoverflow.chatoverflow.requirement.service.rcon

import java.io.{DataInputStream, IOException, InputStream, OutputStream}
import java.net.{Socket, SocketException}
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
if (write(2, command.getBytes("ASCII"))) {
return read()
}
null
}


/**
* 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")) {
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
}
}
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)
loggedIn
}

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"
return true
}
}
false
}

private def write(packageType: Int, payload: Array[Byte]): Boolean = {
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)

outputStream.write(byteBuffer.array())
outputStream.flush()
} catch {
case e: SocketException => {
logger error "Connection Error to RCON Server. This request will not be sended!"
return false
}
}
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;
}
}

/**
* 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").get}!"
socket.close()
true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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.impl.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 = true

override def stop(): Boolean = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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.impl.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): 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

/**
* Stops the output, called before source connector will shutdown
*
* @return true if stopping was successful
*/
override def stop(): Boolean = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*
Expand All @@ -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
Expand Down Expand Up @@ -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
}
}

/**
Expand Down Expand Up @@ -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
}
Expand Down
Loading