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

Commit 7181a78

Browse files
committed
2 parents f51df0f + 8fdba80 commit 7181a78

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+919
-494
lines changed

README.md

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,59 @@
22
<img src = "chatoverflow-logo.png"/>
33
</p>
44

5-
Imagine an [IFTTT](https://ifttt.com/), solely developed for livestreaming.
6-
Living in a event-triggered environment, chat overflow reacts on inputs like twitch chat, sub messages, bits, ...
5+
What if you could **combine** the power of
6+
- **Chat bots** like *nightbot*, *moobot* and *botler*
7+
- **Supporting services** like *StreamElements*, *Streamlabs*, *TipeeeStream*, *Loots* and
8+
- **Social Media** and Chat software, e.g. *Twitter*, *Discord*, *YouTube* etc.
79

8-
Looking for a new chat bot, stream overlay or custom lighting in your living room, whenever someone subscribes?
9-
Everything can be developed with the easy-to-use API from the chat overflow framework.
10+
with your interactive chat in your livestream. What if you could easily react on events, e.g.
1011

11-
One example: Chatbot. Starting with two lines of code:
12+
- Automatically **share** your new subscribers on twitter
13+
- Automatically **control** your studio's lighting colors through chat messages
14+
- Automatically **post** an user's cheer on your minecraft server
15+
- Automatically **upload** a youtube video with stream highlights when your stream stops
1216

13-
```
14-
twitchChat = require.input.twitchChat(...)
17+
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.
18+
19+
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**.
20+
21+
## The ChatOverflow Project
22+
23+
**Chat Overflow** is a plugin framework, which offers ready-to-use platform integrations for all* major streaming- and social-media-sites.
24+
25+
**Chat Overflow** enables you to to level up your stream by writing simple, platform-independent plugins in java or scala**.
26+
27+
It's getting even better: The **Chat Overflow** license allows you to sell your custom plugins, creating new services for other streamers.
28+
29+
And it's so easy. Here is all the code to get started with a simple twitch chat bot:
30+
31+
```java
32+
twitchChat = require.input.twitchChat()
1533
twitchChat.get.registerMessageHandler(msg => ...)
1634
```
1735

18-
Chat overflow fills the gap between simple but limited services like IFTTT and own from-scratch developed applications.
36+
**Chat Overflow** fills the gap between simple but limited services like IFTTT and own from-scratch developed applications.
1937

2038
*Current development state: **Pre-Alpha***
2139

22-
## Chat Overflow Framework
23-
The framework contains all information to connect to livestream inputs and outputs. Combined with the [API Project](https://github.com/codeoverflow-org/chatoverflow-api), plugins can be executed. They provide defined behavior, e.g. the [public plugins](https://github.com/codeoverflow-org/chatoverflow-plugins).
40+
\* There are still missing platforms. This is a open-source project. You can [help](https://github.com/codeoverflow-org/chatoverflow/issues), too!
41+
42+
\** The API is written in java. So, every JVM-compatible language is possible. Java, Scala, Kotlin, etc.
43+
44+
### Installation / Releases
45+
Head over to [releases](https://github.com/codeoverflow-org/chatoverflow/releases).
46+
47+
Just download the newest zip file, make sure that java is installed and launch the framework.
48+
49+
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.
2450

2551
### Development
2652

2753
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).
2854

29-
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).
30-
31-
### Usage
55+
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).
3256

33-
Chat Overflow releases are not yet available. ETA Q2 2019.
57+
***Pre-Alpha note***: Please note that the development workflow and the documentation will be updated soon.
3458

3559
### Discord
3660

@@ -40,4 +64,6 @@ The perfect place if you need help with the framework, found a bug or want to sh
4064

4165
### About Code Overflow
4266

43-
Code Overflow started as a coding-livestream project by three computer science students @ [KIT, Karlsruhe](http://www.kit.edu/) and is headed by [skate702](http://skate702.de).
67+
Code Overflow started as a coding-livestream project from 3 computer science students @ [KIT, Karlsruhe](http://www.kit.edu/).
68+
Now, it is a team of free open-source developers, sitting all over the world (really, lol).
69+
The project is headed by [skate702](http://skate702.de).

build.sbt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// ---------------------------------------------------------------------------------------------------------------------
44

55
name := "ChatOverflow"
6-
version := "0.2"
6+
version := "0.2.1"
77
mainClass := Some("org.codeoverflow.chatoverflow.Launcher")
88

99
// One version for all sub projects. Use "retrieveManaged := true" to download and show all library dependencies.
@@ -64,6 +64,8 @@ libraryDependencies += "net.dv8tion" % "JDA" % "3.8.3_463"
6464
//Serial Communication
6565
libraryDependencies += "com.fazecast" % "jSerialComm" % "[2.0.0,3.0.0)"
6666

67+
// Socket.io
68+
libraryDependencies += "io.socket" % "socket.io-client"% "1.0.0"
6769
// ---------------------------------------------------------------------------------------------------------------------
6870
// PLUGIN FRAMEWORK DEFINITIONS
6971
// ---------------------------------------------------------------------------------------------------------------------
@@ -84,7 +86,7 @@ lazy val deploy = TaskKey[Unit]("deploy", "Prepares the environment for deployme
8486
lazy val gui = TaskKey[Unit]("gui", "Installs GUI dependencies and builds it using npm.")
8587

8688
pluginBuildFileName := "plugins.sbt"
87-
pluginFolderNames := List("plugins-public")
89+
pluginFolderNames := List("plugins-public", "plugins-private")
8890
pluginTargetFolderNames := List("plugins", s"target/scala-$scalaMajorVersion/plugins")
8991
apiProjectPath := "api"
9092
guiProjectPath := "gui"

src/main/scala/ScalatraBootstrap.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.scalatra._
1010
* This class provides all runtime information for Scalatra. Servlets are mounted here.
1111
*/
1212
class ScalatraBootstrap extends LifeCycle {
13-
val apiVersion = "1.0"
13+
val apiVersion = "0.2"
1414
implicit val swagger: CodeOverflowSwagger = new CodeOverflowSwagger(apiVersion)
1515

1616
override def init(context: ServletContext) {

src/main/scala/org/codeoverflow/chatoverflow/Launcher.scala

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ object Launcher extends WithLogger {
1111

1212
var server: Option[Server] = None
1313
var pluginDataPath: String = "data"
14+
private var chatOverflow: Option[ChatOverflow] = None
1415

1516
/**
1617
* Software entry point.
@@ -24,6 +25,7 @@ object Launcher extends WithLogger {
2425
// The complete project visible trough one single instance
2526
val chatOverflow = new ChatOverflow(config.pluginFolderPath,
2627
config.configFolderPath, config.requirementPackage, config.pluginLogOutputOnConsole)
28+
this.chatOverflow = Some(chatOverflow)
2729

2830
// Initialize chat overflow
2931
chatOverflow.init()
@@ -68,7 +70,34 @@ object Launcher extends WithLogger {
6870
* Shuts down the framework.
6971
*/
7072
def exit(): Unit = {
71-
logger info "Shutting down Chat Overflow."
73+
logger debug "Shutting down Chat Overflow."
74+
75+
if (server.isDefined) {
76+
logger info "Stopping the server."
77+
server.get.stop()
78+
}
79+
80+
if (chatOverflow.isDefined) {
81+
logger info "Saving all settings."
82+
chatOverflow.get.save()
83+
84+
logger debug "Trying to stop all running instances..."
85+
chatOverflow.get.pluginInstanceRegistry.getAllPluginInstances.
86+
filter(_.isRunning).foreach(_.stopPlease())
87+
88+
for (i <- 1 to 5) {
89+
if (chatOverflow.get.pluginInstanceRegistry.getAllPluginInstances.
90+
exists(_.isRunning)) {
91+
logger info s"Check $i of 5. Still waiting on: ${
92+
chatOverflow.get.pluginInstanceRegistry.getAllPluginInstances.
93+
filter(_.isRunning).map(_.instanceName).mkString(", ")
94+
}"
95+
Thread.sleep(1000)
96+
}
97+
}
98+
}
99+
100+
logger info "Bye Bye. Stay minzig!"
72101
System.exit(0)
73102
}
74103
}

src/main/scala/org/codeoverflow/chatoverflow/configuration/CryptoUtil.scala

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package org.codeoverflow.chatoverflow.configuration
22

33
import java.nio.charset.StandardCharsets
4-
import java.security.{DigestException, MessageDigest, SecureRandom}
4+
import java.security.{DigestException, InvalidKeyException, MessageDigest, SecureRandom}
55
import java.util
66
import java.util.Base64
77

88
import javax.crypto.spec.{IvParameterSpec, PBEKeySpec, SecretKeySpec}
99
import javax.crypto.{Cipher, SecretKeyFactory}
10+
import org.codeoverflow.chatoverflow.WithLogger
1011

1112
/**
1213
* Provides methods to de- and encrypt using the AES-CBC algorithm.
@@ -15,7 +16,7 @@ import javax.crypto.{Cipher, SecretKeyFactory}
1516
* The SSL compliant / crypto-js compliant code is based on
1617
* https://stackoverflow.com/questions/41432896/cryptojs-aes-encryption-and-java-aes-decryption
1718
*/
18-
object CryptoUtil {
19+
object CryptoUtil extends WithLogger {
1920

2021
// Used for the run-unique auth key
2122
private val runSpecificRandom = generateIV
@@ -65,6 +66,9 @@ object CryptoUtil {
6566
Some(decrString.substring(5))
6667
}
6768
} catch {
69+
case e: InvalidKeyException => logger.error("Your environment does not work with AES256." +
70+
"Please update your java runtime version to at least: 1.8.0_161", e)
71+
None
6872
case _: Exception => None
6973
}
7074
}
@@ -143,6 +147,13 @@ object CryptoUtil {
143147
new String(Base64.getEncoder.encode(message), StandardCharsets.UTF_8)
144148
}
145149

150+
private def generateSalt: Array[Byte] = {
151+
val random = new SecureRandom()
152+
val salt = new Array[Byte](8)
153+
random.nextBytes(salt)
154+
salt
155+
}
156+
146157
/**
147158
* Encrypts the provided plaintext using AES.
148159
*
@@ -173,13 +184,6 @@ object CryptoUtil {
173184
ciphertext.toString
174185
}
175186

176-
private def generateSalt: Array[Byte] = {
177-
val random = new SecureRandom()
178-
val salt = new Array[Byte](8)
179-
random.nextBytes(salt)
180-
salt
181-
}
182-
183187
private def generateIV: Array[Byte] = {
184188
val random = new SecureRandom()
185189
val iv = new Array[Byte](16)

src/main/scala/org/codeoverflow/chatoverflow/connector/Connector.scala

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
2525
protected var optionalCredentialKeys: List[String]
2626
protected var running = false
2727

28+
private var instanceCount = 0
29+
2830
def getCredentials: Option[Credentials] = this.credentials
2931

3032
/**
@@ -76,6 +78,7 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
7678
// Check if running
7779
if (running) {
7880
logger info s"Unable to start $connectorSourceAndType. Already running!"
81+
changeInstanceCount(1)
7982
true
8083
} else {
8184

@@ -101,6 +104,7 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
101104

102105
if (start()) {
103106
logger info s"Started $connectorSourceAndType."
107+
changeInstanceCount(1)
104108
running = true
105109
true
106110
} else {
@@ -127,15 +131,29 @@ abstract class Connector(val sourceIdentifier: String) extends WithLogger {
127131
/**
128132
* Shuts down the connector by calling the stop method.
129133
*/
130-
def shutdown(): Unit = {
131-
if (stop()) {
132-
running = false
133-
logger info s"Stopped $connectorSourceAndType."
134+
def shutdown(): Boolean = {
135+
changeInstanceCount(-1)
136+
137+
if (instanceCount <= 0) {
138+
logger info "Instance count is zero. Connector is no longer needed and shut down. RIP."
139+
if (stop()) {
140+
running = false
141+
logger info s"Stopped $connectorSourceAndType."
142+
true
143+
} else {
144+
logger warn s"Unable to shutdown $connectorSourceAndType."
145+
false
146+
}
134147
} else {
135-
logger warn s"Unable to shutdown $connectorSourceAndType."
148+
true // Return true to keep transparency
136149
}
137150
}
138151

152+
private def changeInstanceCount(delta: Int): Unit = {
153+
instanceCount += delta
154+
logger info s"Instance count: $instanceCount"
155+
}
156+
139157
/**
140158
* Returns the unique type string of the implemented connector.
141159
*

src/main/scala/org/codeoverflow/chatoverflow/framework/manager/PluginManagerImpl.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package org.codeoverflow.chatoverflow.framework.manager
33
import java.util
44

55
import org.codeoverflow.chatoverflow.WithLogger
6-
import org.codeoverflow.chatoverflow.api.plugin.PluginManager
6+
import org.codeoverflow.chatoverflow.api.plugin.{PluginLogMessage, PluginManager}
77

88
import scala.collection.JavaConverters._
99
import scala.collection.mutable.ListBuffer
@@ -15,15 +15,15 @@ import scala.collection.mutable.ListBuffer
1515
*/
1616
class PluginManagerImpl(pluginInstanceName: String, logOutputOnConsole: Boolean) extends PluginManager with WithLogger {
1717

18-
private val logMessages = new ListBuffer[String]
18+
private val logMessages = new ListBuffer[PluginLogMessage]
1919

2020
/**
2121
* Prints a log message on the console and saves the message for later inspection.
2222
*
2323
* @param message the message to show
2424
*/
2525
override def log(message: String): Unit = {
26-
logMessages += message
26+
logMessages += new PluginLogMessage(message)
2727

2828
if (logOutputOnConsole) {
2929
logger info s"[$pluginInstanceName] $message"
@@ -35,5 +35,5 @@ class PluginManagerImpl(pluginInstanceName: String, logOutputOnConsole: Boolean)
3535
*
3636
* @return a list of log messages
3737
*/
38-
override def getLogMessages: util.List[String] = logMessages.toList.asJava
38+
override def getLogMessages: util.List[PluginLogMessage] = logMessages.toList.asJava
3939
}

src/main/scala/org/codeoverflow/chatoverflow/framework/manager/PluginManagerStub.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package org.codeoverflow.chatoverflow.framework.manager
22

33
import java.util
44

5-
import org.codeoverflow.chatoverflow.api.plugin.PluginManager
5+
import org.codeoverflow.chatoverflow.api.plugin.{PluginLogMessage, PluginManager}
66

77
/**
88
* This plugin manager stub does not provide any useful functionality and should only be used for
@@ -23,7 +23,7 @@ class PluginManagerStub extends PluginManager {
2323
*
2424
* @return a list of log messages
2525
*/
26-
override def getLogMessages: util.List[String] = {
27-
new util.ArrayList[String]()
26+
override def getLogMessages: util.List[PluginLogMessage] = {
27+
new util.ArrayList[PluginLogMessage]()
2828
}
2929
}

src/main/scala/org/codeoverflow/chatoverflow/instance/PluginInstance.scala

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,6 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
7373
new PluginManagerStub()
7474
}
7575

76-
/**
77-
* Returns, if the plugin instance is created. This should be always the case when a plugin instance
78-
* is added to the registry.
79-
*/
80-
private def isCreated: Boolean = plugin.isDefined
81-
8276
/**
8377
* Creates a new thread and tries to start the plugin.
8478
* Make sure to set the requirements first!
@@ -183,6 +177,20 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
183177
case e: AbstractMethodError => logger.error(s"Plugin '$instanceName' just crashed. Looks like a plugin version error.", e)
184178
case e: Exception => logger.error(s"Plugin '$instanceName' just had an exception. Might be a plugin implementation fault.", e)
185179
case e: Throwable => logger.error(s"Plugin '$instanceName' just crashed. We don't know whats going up here!", e)
180+
181+
} finally {
182+
183+
// If the plugin ended or crashed, try to shut down the connectors
184+
val inputRequirements = getRequirements.getInputRequirements.toArray
185+
inputRequirements.filter(_.asInstanceOf[Requirement[_]].isSet).foreach(requirement => {
186+
requirement.asInstanceOf[Requirement[Input]].get().shutdown()
187+
})
188+
189+
val outputRequirements = getRequirements.getOutputRequirements.toArray
190+
outputRequirements.filter(_.asInstanceOf[Requirement[_]].isSet).foreach(requirement => {
191+
requirement.asInstanceOf[Requirement[Output]].get().shutdown()
192+
})
193+
186194
}
187195
})
188196
instanceThread.start()
@@ -198,6 +206,12 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
198206
}
199207
}
200208

209+
/**
210+
* Returns, if the plugin instance is created. This should be always the case when a plugin instance
211+
* is added to the registry.
212+
*/
213+
private def isCreated: Boolean = plugin.isDefined
214+
201215
/**
202216
* Returns the requirements object of the specific plugin instance.
203217
*

0 commit comments

Comments
 (0)