Move interfaces to maubot package and other stuff to app/

This commit is contained in:
Tulir Asokan 2018-06-14 11:11:24 +03:00
parent ef8fffaff8
commit 3a27831112
14 changed files with 329 additions and 198 deletions

80
app/bot.go Normal file
View file

@ -0,0 +1,80 @@
// maubot - A plugin-based Matrix bot system written in Go.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package app
import (
"net/http"
"os"
"maubot.xyz"
"maubot.xyz/config"
"maubot.xyz/database"
"maubot.xyz/matrix"
log "maunium.net/go/maulogger"
)
type Bot struct {
Config *config.MainConfig
Database *database.Database
Clients map[string]*matrix.Client
PluginCreators map[string]*maubot.PluginCreator
Plugins map[string]*PluginWrapper
Server *http.Server
}
func New(config *config.MainConfig) *Bot {
return &Bot{
Config: config,
Clients: make(map[string]*matrix.Client),
Plugins: make(map[string]*PluginWrapper),
PluginCreators: make(map[string]*maubot.PluginCreator),
}
}
func (bot *Bot) Init() {
bot.initDatabase()
bot.initClients()
bot.initServer()
bot.loadPlugins()
bot.createPlugins()
log.Debugln("Init func exit")
}
func (bot *Bot) Start() {
go bot.startClients()
go bot.startServer()
bot.startPlugins()
log.Debugln("Start func exit")
}
func (bot *Bot) Stop() {
bot.stopPlugins()
bot.stopServer()
bot.stopClients()
log.Debugln("Stop func exit")
}
func (bot *Bot) initDatabase() {
log.Debugln("Initializing database")
bot.Database = &bot.Config.Database
err := bot.Database.Connect()
if err != nil {
log.Fatalln("Failed to connect to database:", err)
os.Exit(2)
}
bot.Database.CreateTables()
}

59
app/http.go Normal file
View file

@ -0,0 +1,59 @@
// maubot - A plugin-based Matrix bot system written in Go.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package app
import (
"context"
"net/http"
"os"
"time"
"github.com/gorilla/mux"
log "maunium.net/go/maulogger"
)
func (bot *Bot) initServer() {
log.Debugln("Initializing HTTP server")
r := mux.NewRouter()
http.Handle(bot.Config.Server.BasePath, r)
bot.Server = &http.Server{
Addr: bot.Config.Server.Listen,
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r,
}
}
func (bot *Bot) startServer() {
log.Debugf("Listening at http://%s%s\n", bot.Server.Addr, bot.Config.Server.BasePath)
if err := bot.Server.ListenAndServe(); err != nil {
log.Fatalln("HTTP server errored:", err)
bot.Server = nil
bot.Stop()
os.Exit(10)
}
}
func (bot *Bot) stopServer() {
if bot.Server != nil {
log.Debugln("Stopping HTTP server")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
bot.Server.Shutdown(ctx)
}
}

54
app/matrix.go Normal file
View file

@ -0,0 +1,54 @@
// maubot - A plugin-based Matrix bot system written in Go.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package app
import (
"os"
"maubot.xyz/matrix"
log "maunium.net/go/maulogger"
)
func (bot *Bot) initClients() {
log.Debugln("Initializing Matrix clients")
clients := bot.Database.MatrixClient.GetAll()
for _, client := range clients {
mxClient, err := matrix.NewClient(client)
if err != nil {
log.Fatalf("Failed to create client to %s as %s: %v\n", client.Homeserver, client.UserID, err)
os.Exit(3)
}
log.Debugln("Initialized user", client.UserID, "with homeserver", client.Homeserver)
bot.Clients[client.UserID] = mxClient
}
}
func (bot *Bot) startClients() {
log.Debugln("Starting Matrix syncer")
for _, client := range bot.Clients {
if client.DB.Sync {
client.Sync()
}
}
}
func (bot *Bot) stopClients() {
log.Debugln("Stopping Matrix syncers")
for _, client := range bot.Clients {
client.StopSync()
}
}

81
app/pluginloader.go Normal file
View file

@ -0,0 +1,81 @@
// maubot - A plugin-based Matrix bot system written in Go.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package app
import (
"fmt"
"plugin"
"maubot.xyz"
"maubot.xyz/database"
)
type PluginWrapper struct {
maubot.Plugin
Creator *maubot.PluginCreator
DB *database.Plugin
}
func LoadPlugin(path string) (*maubot.PluginCreator, error) {
rawPlugin, err := plugin.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open: %v", err)
}
pluginCreatorSymbol, err := rawPlugin.Lookup("Plugin")
if err == nil {
pluginCreator, ok := pluginCreatorSymbol.(*maubot.PluginCreator)
if ok {
pluginCreator.Path = path
return pluginCreator, nil
}
}
pluginCreatorFuncSymbol, err := rawPlugin.Lookup("Create")
if err != nil {
return nil, fmt.Errorf("symbol \"Create\" not found: %v", err)
}
pluginCreatorFunc, ok := pluginCreatorFuncSymbol.(maubot.PluginCreatorFunc)
if !ok {
return nil, fmt.Errorf("symbol \"Create\" does not implement maubot.PluginCreator")
}
nameSymbol, err := rawPlugin.Lookup("Name")
if err != nil {
return nil, fmt.Errorf("symbol \"Name\" not found: %v", err)
}
name, ok := nameSymbol.(string)
if !ok {
return nil, fmt.Errorf("symbol \"Name\" is not a string")
}
versionSymbol, err := rawPlugin.Lookup("Version")
if err != nil {
return nil, fmt.Errorf("symbol \"Version\" not found: %v", err)
}
version, ok := versionSymbol.(string)
if !ok {
return nil, fmt.Errorf("symbol \"Version\" is not a string")
}
return &maubot.PluginCreator{
Create: pluginCreatorFunc,
Name: name,
Version: version,
Path: path,
}, nil
}

112
app/plugins.go Normal file
View file

@ -0,0 +1,112 @@
// maubot - A plugin-based Matrix bot system written in Go.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package app
import (
"io/ioutil"
"os"
"path/filepath"
log "maunium.net/go/maulogger"
)
func (bot *Bot) loadPlugin(dir, fileName string) {
ext := fileName[len(fileName)-4:]
if ext != ".mbp" {
return
}
path := filepath.Join(dir, fileName)
pluginCreator, err := LoadPlugin(path)
if err != nil {
log.Fatalf("Failed to load plugin at %s: %v\n", path, err)
os.Exit(4)
}
_, exists := bot.PluginCreators[pluginCreator.Name]
if exists {
log.Debugf("Skipping plugin at %s: plugin with same name already loaded", path)
return
}
bot.PluginCreators[pluginCreator.Name] = pluginCreator
log.Debugf("Loaded plugin creator %s v%s\n", pluginCreator.Name, pluginCreator.Version)
}
func (bot *Bot) loadPlugins() {
for _, dir := range bot.Config.PluginDirs {
files, err := ioutil.ReadDir(dir)
if err != nil {
log.Fatalf("Failed to read plugin directory %s: %v\n", dir, err)
os.Exit(4)
}
for _, file := range files {
bot.loadPlugin(dir, file.Name())
}
}
}
func (bot *Bot) createPlugins() {
log.Debugln("Creating plugin instances")
plugins := bot.Database.Plugin.GetAll()
for _, plugin := range plugins {
if !plugin.Enabled {
log.Debugln("Skipping disabled plugin", plugin.ID)
continue
}
creator, ok := bot.PluginCreators[plugin.Type]
if !ok {
log.Errorln("Plugin creator", plugin.Type, "for", plugin.ID, "not found, disabling plugin...")
plugin.Enabled = false
plugin.Update()
continue
}
client, ok := bot.Clients[plugin.UserID]
if !ok {
log.Errorln("Client", plugin.UserID, "for", plugin.ID, "not found, disabling plugin...")
plugin.Enabled = false
plugin.Update()
continue
}
log.Debugf("Created plugin %s (type %s v%s)\n", plugin.ID, creator.Name, creator.Version)
bot.Plugins[plugin.ID] = &PluginWrapper{
Plugin: creator.Create(client),
Creator: creator,
DB: plugin,
}
}
}
func (bot *Bot) startPlugins() {
log.Debugln("Starting plugin instances...")
for _, plugin := range bot.Plugins {
log.Debugf("Starting plugin %s (type %s v%s)\n", plugin.DB.ID, plugin.Creator.Name, plugin.Creator.Version)
go plugin.Start()
}
}
func (bot *Bot) stopPlugins() {
log.Debugln("Stopping plugin instances...")
for _, plugin := range bot.Plugins {
log.Debugf("Stopping plugin %s (type %s v%s)\n", plugin.DB.ID, plugin.Creator.Name, plugin.Creator.Version)
plugin.Stop()
}
}