Event-driven lightweight chatbot framework that supports dependency injection and dynamically loading plugin jars at runtime.
- Project status: Archived, because the technology used is outdated
- Last update: Feb 26, 2018 - Bug fix and Add static ClassPath
- Last commit: Jun 23, 2021 Update README and Add example
- JDK 1.8 (higher version may work but not tested)
- Build system: Gradle >= 3.5-rc-2 (project development environment using 3.5-rc-2 but higher versions should also work)
- Dependency: google/guava 23.5 - Google core libraries for Java (use for list all top level classes)
- Dependency: rubenlagus/TelegramBots 3.4 - Java library to create bots using Telegram Bots API
- Dependencies in 'lib' folder: SpongePowered/Configurate 3.3 - Used to read configuration, serialize and deserialize object to hocon file
Only has few dependencies, dependency injection and event listeners are implemented by the framework itself.
The framework will automatically scan the plugin folder, find and load the jar file.
- Low-invasive: Dependency injection and event listeners are based on annotation
- Database is not required: No need to maintain a database to store data, the framework provides a default path for each plugin to store serialized objects
On Linux
git clon https://github.com/YukinaMochizuki/ShuviBot.git
cd ShuviBot
./gradlew jar
After the first run, a default config file will be generated
user@linux:~/path/ShuviBot$ mv build/libs/mochizuki_bot-1.0-SNAPSHOT.jar /your/other/path
user@linux:~/path/ShuviBot$ cd /your/other/path
user@linux:/your/other/path$ java -jar mochizuki_bot-1.0-SNAPSHOT.jar
Loading library...
System build 0.80226
[11:27:15][Main thread/INFO]:Starting L2 cache support system
[11:27:15][Main thread/INFO]:version Alpha 1.0
[11:27:15][Bot Main/INFO]:Instantiate Config Storage
[11:27:16][Bot Main/INFO]:The Configuration file Path is./config.conf
[11:27:16][Bot Main/INFO]:Default Config has be created, please setting it
user@linux:/your/other/path$ ls
config.conf mochizuki_bot-1.0-SNAPSHOT.jar plugin
user@linux:/your/other/path$ cat config.conf
Bot {
Global {
Logger-level=INFO
}
ServiceManager {
BasicIO {
type=Hocon
}
}
Telegram {
BotName=BotName
BotToken="123456789:ABCDEFGH"
ChatNumber=0
}
}
After setting the BotName and BotToken in config.conf, we need to continue setting ChatNumber, but don’t worry, this process is automatic.
user@linux:/your/other/path$ java -jar mochizuki_bot-1.0-SNAPSHOT.jar
Loading library...
System build 0.80226
[12:15:30][Main thread/INFO]:Starting L2 cache support system
[12:15:30][Main thread/INFO]:version Alpha 1.0
[12:15:30][Bot Main/INFO]:Instantiate Config Storage
[12:15:30][Bot Main/INFO]:The Configuration file Path is./config.conf
[12:15:31][Bot Main/INFO]:Set chat ID mode
[12:15:31][Bot Main/INFO]:Instantiate Developer Mode Telegram Bots API...OK
[12:15:33][Bot Main/INFO]:Exit for 1 Min later
When you see Exit for 1 Min later
, please send a message to your telegram bot. The framework will automatically detect the chat_id and save it to the config file, then framework can be used normally at the next startup.
user@linux:/your/other/path$ java -jar mochizuki_bot-1.0-SNAPSHOT.jar
Loading library...
System build 0.80226
[12:20:31][Main thread/INFO]:Starting L2 cache support system
[12:20:31][Main thread/INFO]:version Alpha 1.0
[12:20:31][Bot Main/INFO]:Instantiate Config Storage
[12:20:31][Bot Main/INFO]:The Configuration file Path is./config.conf
[12:20:32][Bot Main/INFO]:Set Logger levels to INFO
[12:20:32][Bot Main/INFO]:==============Debug==============
[12:20:32][Bot Main/INFO]:==============Debug==============
[12:20:32][Bot Main/INFO]:Instantiate Telegram Bots API...OK
[12:20:33][ServiceManager/INFO]:Instantiate Service Manager
[12:20:33][BasicIO/INFO]:Data Input/Output use Hocon mode
[12:20:33][BasicIO/INFO]:The Configuration file Path is./value.conf
[12:20:33][BasicIO/INFO]:Configuration file is not found, will create a new one
[12:20:33][ServiceManager/INFO]:Instantiate ConversationManager
[12:20:33][ServiceManager/INFO]:Instantiate Command Manager
[12:20:33][ServiceManager/INFO]:Instantiate Plugin Manager
[12:20:33][Plugin Manager/INFO]: Loading plugin is complete
[12:20:35][Bot Main/INFO]:Instantiate ServiceManager completed
[12:20:35][Bot Main/INFO]:Command type ready
enjoy it!
After adding plugin ann, it will be automatically recognized by the framework
ProjectL2.java
import org.mochizuki.bot.service.Annotation.Plugin;
@Plugin(id = "ProjectL2",
name = "ProjectL2",
version = "alpha1.0",
authors = "mochizuki")
public class ProjectL2 {
}
public class ProjectL2 {
@Inject
private ConversationManager conversationManager;
@Inject
private Logger logger;
@Inject
private ServiceInterface serviceInterface;
@Inject @ConfigDir(sharedRoot = true)
private Path path;
}
All system event are listed in this file
public class ProjectL2 {
... other code
@Listener(eventType = EventType.BotPreInitializationEvent)
public void onPreInitializationEvent(Event event){
}
}
Then we can try to add some commands...
public class ProjectL2 {
... other code
@Listener(eventType = EventType.BotPreInitializationEvent)
public void onPreInitializationEvent(Event event){
}
@Listener(eventType = EventType.BotInitializationEvent)
public void onInitializationEvent(Event event){
CommandManager commandManager = CommandManager.getCommandManager();
try {
commandManager.addCommand(this.getClass().getDeclaredMethod("ProjectL2_save"),this);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
private void ProjectL2_save(){
serviceInterface.displayMessage(logger,"save...");
}
}
Now we can send /ProjectL2_save
to the telegram bot to execute ProjectL2_save
method
In addition to sending commands, the framework also provides the function of forwarding all messages to the plugin. First we create a class implements SingleModeInterface
.
import org.mochizuki.bot.service.conversation.SingleModeInterface;
public class SingleMessage implements SingleModeInterface{
private ServiceInterface serviceInterface;
public SingleMessage(ServiceInterface serviceInterface){
this.serviceInterface = serviceInterface;
}
@Override
public void massageInput(String s) {
serviceInterface.displayMessage(null, s);
}
@Override
public String getID() {
return "GoInSingleMode";
}
}
Then register it when the BotPostInitializationEvent
is triggered.
public class ProjectL2 {
@Inject
private ServiceInterface serviceInterface;
@Inject
private ConversationManager conversationManager;
@Listener(eventType = EventType.BotPostInitializationEvent)
public void onPostInitializationEvent(Event event){
conversationManager.singleModeRegister(this, new SingleMessage(serviceInterface));
try {
CommandManager commandManager = CommandManager.getCommandManager();
commandManager.addCommand(this.getClass().getDeclaredMethod("ProjectL2_save"), this);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
private void changeToSingleMode(){
conversationManager.changeSingleMode("GoInSingleMode");
}
}
Now you can send messages directly to your plugin.
Full example plugin code is in ExamplePlugin folder.
import com.google.common.reflect.TypeToken;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
public class ProjectL2 {
@Inject @ConfigDir(sharedRoot = true)
private Path path;
private Path configPath;
private Path valuePath;
private YourClass yourObject
@Listener(eventType = EventType.BotPreInitializationEvent)
public void onPreInitializationEvent(Event event){
configPath = Paths.get(path.toString(),"config.conf");
valuePath = Paths.get(path.toString(),"value.conf");
try {
if (!Files.exists(path)) Files.createDirectory(path);
if(!Files.exists(valuePath)) Files.createFile(valuePath);
} catch (IOException e) {
e.printStackTrace();
}
this.valueLoader = HoconConfigurationLoader.builder().setPath(valuePath).build();
try {
this.configRootNode = configLoader.load();
this.valueRootNode = valueLoader.load();
yourObject = valueRootNode.getNode("ProjectL2","YourClass")
.getValue(TypeToken.of(YourClass.class)); //return null if not exist
} catch (IOException e) {
e.printStackTrace();
} catch (ObjectMappingException e){
e.printStackTrace();
}
}
@Listener(eventType = EventType.BotStoppingServerEvent)
public void onBotStopping(Event event){
try {
valueRootNode.getNode("ProjectL2","YourClass").
setValue(TypeToken.of(YourClass.class), yourObject);
} catch (ObjectMappingException e) {
e.printStackTrace();
}
try {
valueLoader.save(valueRootNode);
} catch(IOException e) {
e.printStackTrace();
}
}
}