Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
zingmars committed Oct 6, 2015
1 parent 92a52d2 commit 4d12905
Show file tree
Hide file tree
Showing 29 changed files with 2,560 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#IntelliJ IDEA gitignore list
.idea/*
*.ipr
*.iws
out/*
.idea_modules/*
atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
logs/*
*.cfg
module_otg-bot.xml
otg-bot.properties
otg-bot.xml
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(29.09.2015) v0.0.0 - Initial commit
(06.10.2015) v1.0.0 - Initial commit to github
12 changes: 12 additions & 0 deletions OTG-Bot.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="KotlinJavaRuntime (2)" level="project" />
</component>
</module>
4 changes: 4 additions & 0 deletions PluginSettings.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#Cbox Bot settings file. Please don't touch unless you know specifically what to change, else you might cause a crash.
#Tue Oct 06 10:28:36 UTC 2015
LastSeen=
StreamerList=zingmars\:0\\0
100 changes: 100 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Cbox.ws Bot in Kotlin
====

# This is a very overengineered "simple" bot that...

1) Connects to a specified cbox.ws chat box and reads/logs messages

2) Allows moderator-only commands and that kind of stuff

3) Provides a CLI (trough, say a telnet client) access that allows quick reconfiguring and reloading if needed (admittedly)

4) Has a plugin API that lets you inject any compiled kotlin class (technically at any time too) and run it, as long as it inherits the base plugin class and functions

5) Does it in a multi-threaded fashion

And probably much more...

-----
# Why?

When looking around the webs I found this language, Kotlin. Now, I have a project coming up that involves building an Android app, and not being on good terms with Java myself, I decided to build something simple to understand it's basics (this is why you'll find stuff like classloader, socket server etc. in this project). I've also wanted to build something that connects to a chat box that a community I frequent uses; I've tried making a desktop client before before, but I didn't finish it out of laziness. It should however in theory work with any cbox.ws based chatbox, although I haven't tested it, so I can't vouch for it.

-----
# Setup guide (using IntelliJ IDEA):

1) Open File->Settings->Plugins, look for Kotlin, download and install it

2) Open project's folder using the Open function

3) On the Project view open the src directory and open the app.kt file

4) There should now be a bar asking your to set up Project SDK. If you haven't already, point it to your jdk's directory.

5) I have included kotlin's runtimes with the project, so you should now be able to make and run this project.

------
# TL;DR - Files

/src:

/BotPlugins - contains plugins for the bot

/Containers - some cross communication model classes

app.kt - main entry point for this application. Loads and manages every other class

The rest should be obvious - ZipUtil handles file zipping (for log file archiving), CLI handles command line input, Logger Logs, HTTP handles HTTP connections etc.

------
# Plugins

Just throw them into /src/BotPlugins. All plugins must follow this base example:

```
package BotPlugins
import Containers.PluginBufferItem
public class PluginName : BasePlugin()
{
override fun pubInit() :Boolean
{
return true
}
override fun connector(buffer : PluginBufferItem) :Boolean
{
return true
}
override fun stop()
{
}
}
```

There are couple of notes though:

1. Plugins are using a really weird ClassLoader implementation, so it it's a bit buggy. The reason for this is that I wanted to see if dynamic class loading is possible with Java, and although it is, I don't really think it's worth the hassle I went through.

2. Plugin name MUST be the same as the filename. If it's not, it won't be loaded. The files in BotPlugins folder can be empty however, but they need to have a compiled version.

3. Yes, it must be a class.

4. pubInit() is the class that's executed once it has been given proper context (namely - logger, a settings file, threadmanager etc). More info on that is available as a comment on BasePlugin.kt

5. connector() is called for every message

6. stop() is called when the plugin is turned off. This is for when you have threads or something like that running.

7. Even though you can use pretty much the whole logger class, it would be saner to use plugin related log commands. The boring one's for when the loglevel is over 2 and it's there to avoid spamming the log with pointless stuff.

8. Yes, you can reload your class while it's running (using CLI, or if you make it to - through an user command), but you'll need to replace the compiled version in the jar file. It works when run from an IDE, not so much when you have a portable jar file.

------
# TODO?

A full refactor to make the code consistent would be nice. Plenty of TODO's scattered through the code as well. Even so, this is project is, for all intents and purposes, finished. Feel free to fork and do whatever.

------
# License

Please view LICENSE (BSD 2-clause). TL;DR - Do what you want, just include the original license and don't blame me when something breaks
Binary file added kotlin-reflect.jar
Binary file not shown.
Binary file added kotlin-runtime-sources.jar
Binary file not shown.
Binary file added kotlin-runtime.jar
Binary file not shown.
25 changes: 25 additions & 0 deletions settings.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#OTG Bot settings file. Please don't touch unless you know specifically what to change, else you might cause a crash.
#Sat Oct 03 18:15:13 MSK 2015
daemonPort=9970
boxtag=0
fileLog=true
server=0
chatLogFile=chat.txt
logChat=true
logFile=log.txt
username=zingmars
boxid=0
logFolder=logs
password=0
daemonEnabled=true
isOriginal=False
maxLogs=100
consoleLog=true
avatar=
refreshRate=4000
logLevel=1
enablePlugins=true
pluginLogFile=plugins.txt
pluginDirectory=src/BotPlugins
archiveLogs=true
logRotate=true
48 changes: 48 additions & 0 deletions src/BasePlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Base plugin class to be inherited from
* Created by zingmars on 04.10.2015.
*/
package BotPlugins
import Containers.PluginBufferItem
import Settings
import Logger
import Plugins
import ThreadController

open public class BasePlugin() {
public var settings :Settings? = null
public var logger :Logger? = null
public var handher :Plugins? = null
public var pluginName :String? = null
public var controller :ThreadController? = null

init {
// This is the base class for all CBot plugins
// To make a plugin just extend this class and put it in src/BotPlugins/ (or whatever is defined in your settings file) directory
// Note - your filename must match your class name and it must be inside the BotPlugins package
// To initiate just override pubInit() (you can override this initializer too, but you won't have access to any variables at that point), and to do your logic just override connector.
// Note that your connector override will need to have the PluginBufferItem input for it to receive any messages.
// To send data back just add an element to any of the buffers available in ThreadController, (i.e. BoxBuffer will output anything send to it)
// Note that pubInit and connector both return a boolean value that indicates whether or not the plugin was successful
}
//non-overridable classes
final public fun initiate(settings :Settings, logger: Logger, pluginsHandler :Plugins, controller :ThreadController,pluginName :String) :Boolean
{
this.settings = settings
this.logger = logger
this.handher = pluginsHandler
this.pluginName = pluginName
this.controller = controller
if(this.pubInit()) this.logger?.LogPlugin(pluginName, "Started!")
else {
this.stop()
this.logger?.LogPlugin(pluginName, "failed to load!")
return false
}
return true
}
//overridable classes
open public fun pubInit() :Boolean { return true } //Initializer
open public fun connector(buffer :PluginBufferItem) :Boolean { return true } //Receives data from Plugin controller
open public fun stop() {} //This is run when the plugin is unloaded
}
96 changes: 96 additions & 0 deletions src/BotPlugins/AdminCommands.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Checks chat for user written messages and responds to specific queries
* Created by zingmars on 04.10.2015.
*/
package BotPlugins
import Containers.PluginBufferItem
import java.util.*

public class AdminCommands : BasePlugin()
{
private var DB : HashMap<String, String> = HashMap()
override fun pubInit() :Boolean
{
try {
if(handher?.isAdmin() == false) {
throw Exception("User does not have mod rights to the given box. Exiting.")
}

settings?.checkSetting("IPDB", true)
var savedDB = settings?.GetSetting("LastSeen").toString()

if(savedDB != "") {
var data = savedDB.split(",")
for(user in data) {
var IP = savedDB.split(":")[0]
var Usernames = savedDB.split(":")[1]
DB.put(IP, Usernames)
}
}
return true
} catch (e: Exception) {
logger?.LogPlugin(pluginName.toString(), "Error: " + e.toString())
return false
}
}
override fun connector(buffer : PluginBufferItem) :Boolean
{
var message = buffer.message.split(" ")
var changed :Boolean
//TODO: Rewrite, this is a horrible 2AM energy drink powered way to do this.
if(DB.containsKey(buffer.extradata)) {
//Remove from the old entry
var keys = DB.keySet().iterator()
while(keys.hasNext())
{
var key = keys.next()
var userlist = DB.get(key)
if(userlist != null) {
var replaceableString = buffer.userName
if(userlist.indexOf(";"+buffer.userName) > 0) replaceableString = ";" + replaceableString
if (userlist.contains(buffer.userName)) {
userlist.replace(replaceableString, "")
}
}
}
//Add to DB
var entry = DB.get(buffer.extradata)
if (entry != null && !entry.contains(buffer.userName)) DB.set(buffer.userName, ";"+buffer.extradata)
changed = true
} else {
logger?.LogPlugin(this.pluginName.toString(), "New user encountered: " + buffer.userName)
DB.put(buffer.userName, buffer.extradata)
changed = true
}

if(changed) {
var data = ""
var keys = DB.keySet().iterator()
while(keys.hasNext()) {
var key = keys.next()
var user = DB.get(key)
data += key + ":" + user
}
settings?.SetSetting("IPDB", data)
}

if(message[0].toLowerCase() == "@alias") {
var keys = DB.keySet().iterator()
while(keys.hasNext())
{
var key = keys.next()
var userlist = DB.get(key)
if(userlist != null) {
if(userlist.contains(message[1])) {
controller?.AddToBoxBuffer("User aliases: " + userlist)
break
} else {
controller?.AddToBoxBuffer("No data")
logger?.LogPlugin(this.pluginName.toString(), "Error: Could not find data for " + message[1])
}
}
}
}
return true
}
}
49 changes: 49 additions & 0 deletions src/BotPlugins/DeadboxCheck.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Stores last cbox activity and posts about it if records are broken
* Created by zingmars on 04.10.2015.
*/
package BotPlugins
import Containers.PluginBufferItem

public class DeadboxCheck : BasePlugin()
{
private var RecordUsername = ""
private var RecordTime = 0L
private var lastMessage = 0L
override fun pubInit() :Boolean
{
try {
settings?.checkSetting("DeadBox", true)
var savedData = settings?.GetSetting("DeadBox").toString()

if(savedData != "") {
var data = savedData.split(",")
RecordUsername = data[0]
RecordTime = data[1].toLong()
}
return true
} catch (e: Exception) {
logger?.LogPlugin(this.pluginName.toString(), "Error: " + e.toString())
return false
}
}
override fun connector(buffer : PluginBufferItem) :Boolean
{
var timeSinceLast = buffer.time.toLong() - lastMessage
if(lastMessage != 0L && timeSinceLast >= 3600L) {
if(timeSinceLast > RecordTime) {
RecordTime = timeSinceLast
RecordUsername = buffer.userName
controller?.AddToBoxBuffer("Congratz " + buffer.userName + "! You just revived the box and set a new record doing so! This deadbox lasted " + (timeSinceLast.toDouble()/60).toString() + " minutes.")
saveData()
} else {
controller?.AddToBoxBuffer("Congratz " + buffer.userName + "! You just revived the box. This deadbox lasted " + (timeSinceLast.toDouble()/60).toString() + " minutes. The longest recorded deadbox was " + (RecordTime.toDouble()/60).toString() + " minutes long and it was broken by " + RecordUsername)
}
}
return true
}
private fun saveData()
{
settings?.SetSetting("DeadBox", RecordUsername+","+RecordTime.toString())
}
}
Loading

0 comments on commit 4d12905

Please sign in to comment.