Move interfaces to maubot package and other stuff to app/
This commit is contained in:
parent
ef8fffaff8
commit
3a27831112
14 changed files with 329 additions and 198 deletions
|
@ -14,15 +14,15 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package maubot
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"maubot.xyz"
|
||||||
"maubot.xyz/config"
|
"maubot.xyz/config"
|
||||||
"maubot.xyz/database"
|
"maubot.xyz/database"
|
||||||
"maubot.xyz/interfaces"
|
|
||||||
"maubot.xyz/matrix"
|
"maubot.xyz/matrix"
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
)
|
)
|
||||||
|
@ -31,7 +31,7 @@ type Bot struct {
|
||||||
Config *config.MainConfig
|
Config *config.MainConfig
|
||||||
Database *database.Database
|
Database *database.Database
|
||||||
Clients map[string]*matrix.Client
|
Clients map[string]*matrix.Client
|
||||||
PluginCreators map[string]*interfaces.PluginCreator
|
PluginCreators map[string]*maubot.PluginCreator
|
||||||
Plugins map[string]*PluginWrapper
|
Plugins map[string]*PluginWrapper
|
||||||
Server *http.Server
|
Server *http.Server
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func New(config *config.MainConfig) *Bot {
|
||||||
Config: config,
|
Config: config,
|
||||||
Clients: make(map[string]*matrix.Client),
|
Clients: make(map[string]*matrix.Client),
|
||||||
Plugins: make(map[string]*PluginWrapper),
|
Plugins: make(map[string]*PluginWrapper),
|
||||||
PluginCreators: make(map[string]*interfaces.PluginCreator),
|
PluginCreators: make(map[string]*maubot.PluginCreator),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package maubot
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
54
app/matrix.go
Normal file
54
app/matrix.go
Normal 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
81
app/pluginloader.go
Normal 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
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package maubot
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"maubot.xyz"
|
"maubot.xyz/app"
|
||||||
"maubot.xyz/config"
|
"maubot.xyz/config"
|
||||||
flag "maunium.net/go/mauflag"
|
flag "maunium.net/go/mauflag"
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
|
@ -55,7 +55,7 @@ func main() {
|
||||||
cfg.Logging.Configure(log.DefaultLogger)
|
cfg.Logging.Configure(log.DefaultLogger)
|
||||||
log.Debugln("Logger configured")
|
log.Debugln("Logger configured")
|
||||||
|
|
||||||
bot := maubot.New(cfg)
|
bot := app.New(cfg)
|
||||||
bot.Init()
|
bot.Init()
|
||||||
bot.Start()
|
bot.Start()
|
||||||
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package interfaces
|
|
||||||
|
|
||||||
type Plugin interface {
|
|
||||||
Start()
|
|
||||||
Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventHandler func(*Event) bool
|
|
||||||
|
|
||||||
type MatrixClient interface {
|
|
||||||
AddEventHandler(string, EventHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventFuncs interface {
|
|
||||||
Reply(text string) (string, error)
|
|
||||||
SendMessage(text string) (string, error)
|
|
||||||
SendEvent(content map[string]interface{}) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
EventFuncs
|
|
||||||
|
|
||||||
StateKey string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
|
|
||||||
Sender string `json:"sender"` // The user ID of the sender of the event
|
|
||||||
Type string `json:"type"` // The event type
|
|
||||||
Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
|
|
||||||
ID string `json:"event_id"` // The unique ID of this event
|
|
||||||
RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
|
|
||||||
Content map[string]interface{} `json:"content"` // The JSON content of the event.
|
|
||||||
Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
|
|
||||||
Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver.
|
|
||||||
}
|
|
||||||
|
|
||||||
type Unsigned struct {
|
|
||||||
PrevContent map[string]interface{} `json:"prev_content,omitempty"`
|
|
||||||
PrevSender string `json:"prev_sender,omitempty"`
|
|
||||||
ReplacesState string `json:"replaces_state,omitempty"`
|
|
||||||
Age int64 `json:"age"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PluginCreatorFunc func(client MatrixClient) Plugin
|
|
||||||
|
|
||||||
type PluginCreator struct {
|
|
||||||
Create PluginCreatorFunc
|
|
||||||
Name string
|
|
||||||
Version string
|
|
||||||
Path string
|
|
||||||
}
|
|
126
matrix.go
126
matrix.go
|
@ -16,39 +16,109 @@
|
||||||
|
|
||||||
package maubot
|
package maubot
|
||||||
|
|
||||||
import (
|
type EventType string
|
||||||
"os"
|
type MessageType string
|
||||||
|
|
||||||
"maubot.xyz/matrix"
|
// State events
|
||||||
log "maunium.net/go/maulogger"
|
const (
|
||||||
|
StateAliases EventType = "m.room.aliases"
|
||||||
|
StateCanonicalAlias = "m.room.canonical_alias"
|
||||||
|
StateCreate = "m.room.create"
|
||||||
|
StateJoinRules = "m.room.join_rules"
|
||||||
|
StateMember = "m.room.member"
|
||||||
|
StatePowerLevels = "m.room.power_levels"
|
||||||
|
StateRoomName = "m.room.name"
|
||||||
|
StateTopic = "m.room.topic"
|
||||||
|
StateRoomAvatar = "m.room.avatar"
|
||||||
|
StatePinnedEvents = "m.room.pinned_events"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bot *Bot) initClients() {
|
// Message events
|
||||||
log.Debugln("Initializing Matrix clients")
|
const (
|
||||||
clients := bot.Database.MatrixClient.GetAll()
|
EventRedaction EventType = "m.room.redaction"
|
||||||
for _, client := range clients {
|
EventMessage = "m.room.message"
|
||||||
mxClient, err := matrix.NewClient(client)
|
EventSticker = "m.sticker"
|
||||||
if err != nil {
|
)
|
||||||
log.Fatalf("Failed to create client to %s as %s: %v\n", client.Homeserver, client.UserID, err)
|
|
||||||
os.Exit(3)
|
// Msgtypes
|
||||||
}
|
const (
|
||||||
log.Debugln("Initialized user", client.UserID, "with homeserver", client.Homeserver)
|
MsgText MessageType = "m.text"
|
||||||
bot.Clients[client.UserID] = mxClient
|
MsgEmote = "m.emote"
|
||||||
}
|
MsgNotice = "m.notice"
|
||||||
|
MsgImage = "m.image"
|
||||||
|
MsgLocation = "m.location"
|
||||||
|
MsgVideo = "m.video"
|
||||||
|
MsgAudio = "m.audio"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FormatHTML = "org.matrix.custom.html"
|
||||||
|
|
||||||
|
type EventHandler func(*Event) bool
|
||||||
|
|
||||||
|
type MatrixClient interface {
|
||||||
|
AddEventHandler(EventType, EventHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *Bot) startClients() {
|
type EventFuncs interface {
|
||||||
log.Debugln("Starting Matrix syncer")
|
Reply(string) (string, error)
|
||||||
for _, client := range bot.Clients {
|
ReplyContent(Content) (string, error)
|
||||||
if client.DB.Sync {
|
SendMessage(string) (string, error)
|
||||||
client.Sync()
|
SendContent(Content) (string, error)
|
||||||
}
|
SendRawEvent(EventType, interface{}) (string, error)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *Bot) stopClients() {
|
type Event struct {
|
||||||
log.Debugln("Stopping Matrix syncers")
|
EventFuncs
|
||||||
for _, client := range bot.Clients {
|
|
||||||
client.StopSync()
|
StateKey string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
|
||||||
}
|
Sender string `json:"sender"` // The user ID of the sender of the event
|
||||||
|
Type EventType `json:"type"` // The event type
|
||||||
|
Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
|
||||||
|
ID string `json:"event_id"` // The unique ID of this event
|
||||||
|
RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
|
||||||
|
Content Content `json:"content"`
|
||||||
|
Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
|
||||||
|
Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver.
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unsigned struct {
|
||||||
|
PrevContent Content `json:"prev_content,omitempty"`
|
||||||
|
PrevSender string `json:"prev_sender,omitempty"`
|
||||||
|
ReplacesState string `json:"replaces_state,omitempty"`
|
||||||
|
Age int64 `json:"age"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Content struct {
|
||||||
|
Raw map[string]interface{} `json:"-"`
|
||||||
|
|
||||||
|
MsgType MessageType `json:"msgtype"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
FormattedBody string `json:"formatted_body"`
|
||||||
|
|
||||||
|
Info FileInfo `json:"info"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
Membership string `json:"membership"`
|
||||||
|
|
||||||
|
RelatesTo RelatesTo `json:"m.relates_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
MimeType string `json:"mimetype"`
|
||||||
|
ThumbnailInfo *FileInfo `json:"thumbnail_info"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url"`
|
||||||
|
Height int `json:"h"`
|
||||||
|
Width int `json:"w"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RelatesTo struct {
|
||||||
|
InReplyTo InReplyTo `json:"m.in_reply_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InReplyTo struct {
|
||||||
|
EventID string `json:"event_id"`
|
||||||
|
// Not required, just for future-proofing
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package matrix
|
package matrix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maubot.xyz/interfaces"
|
"encoding/json"
|
||||||
|
|
||||||
|
"maubot.xyz"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,43 +28,64 @@ type Event struct {
|
||||||
Client *Client
|
Client *Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Event) Interface() *interfaces.Event {
|
func roundtripContent(rawContent map[string]interface{}) (content maubot.Content) {
|
||||||
|
if len(rawContent) == 0 {
|
||||||
|
content.Raw = rawContent
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(&rawContent)
|
||||||
|
json.Unmarshal(data, &content)
|
||||||
|
content.Raw = rawContent
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt *Event) Interface() *maubot.Event {
|
||||||
var stateKey string
|
var stateKey string
|
||||||
if evt.StateKey != nil {
|
if evt.StateKey != nil {
|
||||||
stateKey = *evt.StateKey
|
stateKey = *evt.StateKey
|
||||||
}
|
}
|
||||||
return &interfaces.Event{
|
mbEvent := &maubot.Event{
|
||||||
EventFuncs: evt,
|
EventFuncs: evt,
|
||||||
StateKey: stateKey,
|
StateKey: stateKey,
|
||||||
Sender: evt.Sender,
|
Sender: evt.Sender,
|
||||||
Type: evt.Type,
|
Type: maubot.EventType(evt.Type),
|
||||||
Timestamp: evt.Timestamp,
|
Timestamp: evt.Timestamp,
|
||||||
ID: evt.ID,
|
ID: evt.ID,
|
||||||
RoomID: evt.RoomID,
|
RoomID: evt.RoomID,
|
||||||
Content: evt.Content,
|
Content: roundtripContent(evt.Content),
|
||||||
Redacts: evt.Redacts,
|
Redacts: evt.Redacts,
|
||||||
Unsigned: interfaces.Unsigned{
|
Unsigned: maubot.Unsigned{
|
||||||
PrevContent: evt.Unsigned.PrevContent,
|
PrevContent: roundtripContent(evt.Unsigned.PrevContent),
|
||||||
PrevSender: evt.Unsigned.PrevSender,
|
PrevSender: evt.Unsigned.PrevSender,
|
||||||
ReplacesState: evt.Unsigned.ReplacesState,
|
ReplacesState: evt.Unsigned.ReplacesState,
|
||||||
Age: evt.Unsigned.Age,
|
Age: evt.Unsigned.Age,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
RemoveReplyFallback(mbEvent)
|
||||||
|
return mbEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Event) Reply(text string) (string, error) {
|
func (evt *Event) Reply(text string) (string, error) {
|
||||||
return evt.SendEvent(
|
return evt.SendRawEvent(maubot.EventMessage,
|
||||||
SetReply(
|
SetReply(
|
||||||
RenderMarkdown(text),
|
RenderMarkdown(text),
|
||||||
evt.Event))
|
evt.Event))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Event) SendMessage(text string) (string, error) {
|
func (evt *Event) ReplyContent(content maubot.Content) (string, error) {
|
||||||
return evt.SendEvent(RenderMarkdown(text))
|
return evt.SendRawEvent(maubot.EventMessage, SetReply(content, evt.Event))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Event) SendEvent(content map[string]interface{}) (string, error) {
|
func (evt *Event) SendMessage(text string) (string, error) {
|
||||||
resp, err := evt.Client.SendMessageEvent(evt.RoomID, "m.room.message", content)
|
return evt.SendRawEvent(maubot.EventMessage, RenderMarkdown(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt *Event) SendContent(content maubot.Content) (string, error) {
|
||||||
|
return evt.SendRawEvent(maubot.EventMessage, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt *Event) SendRawEvent(evtType maubot.EventType, content interface{}) (string, error) {
|
||||||
|
resp, err := evt.Client.SendMessageEvent(evt.RoomID, string(evtType), content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,10 @@ package matrix
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"gopkg.in/russross/blackfriday.v2"
|
"gopkg.in/russross/blackfriday.v2"
|
||||||
|
"maubot.xyz"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RenderMarkdown(text string) map[string]interface{} {
|
func RenderMarkdown(text string) maubot.Content {
|
||||||
parser := blackfriday.New(
|
parser := blackfriday.New(
|
||||||
blackfriday.WithExtensions(blackfriday.NoIntraEmphasis |
|
blackfriday.WithExtensions(blackfriday.NoIntraEmphasis |
|
||||||
blackfriday.Tables |
|
blackfriday.Tables |
|
||||||
|
@ -43,10 +44,10 @@ func RenderMarkdown(text string) map[string]interface{} {
|
||||||
renderer.RenderFooter(&buf, ast)
|
renderer.RenderFooter(&buf, ast)
|
||||||
htmlBody := buf.String()
|
htmlBody := buf.String()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return maubot.Content{
|
||||||
"formatted_body": htmlBody,
|
FormattedBody: htmlBody,
|
||||||
"format": "org.matrix.custom.html",
|
Format: maubot.FormatHTML,
|
||||||
"msgtype": "m.text",
|
MsgType: maubot.MsgText,
|
||||||
"body": HTMLToText(htmlBody),
|
Body: HTMLToText(htmlBody),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,15 @@
|
||||||
package matrix
|
package matrix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maubot.xyz"
|
||||||
"maubot.xyz/database"
|
"maubot.xyz/database"
|
||||||
"maubot.xyz/interfaces"
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*gomatrix.Client
|
*gomatrix.Client
|
||||||
|
syncer *MaubotSyncer
|
||||||
|
|
||||||
DB *database.MatrixClient
|
DB *database.MatrixClient
|
||||||
}
|
}
|
||||||
|
@ -40,9 +41,10 @@ func NewClient(db *database.MatrixClient) (*Client, error) {
|
||||||
DB: db,
|
DB: db,
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Syncer = NewMaubotSyncer(client, client.Store)
|
client.syncer = NewMaubotSyncer(client, client.Store)
|
||||||
|
client.Client.Syncer = client.syncer
|
||||||
|
|
||||||
client.AddEventHandler(gomatrix.StateMember, client.onJoin)
|
client.AddEventHandler(maubot.StateMember, client.onJoin)
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
@ -50,19 +52,19 @@ func NewClient(db *database.MatrixClient) (*Client, error) {
|
||||||
func (client *Client) ParseEvent(evt *gomatrix.Event) *Event {
|
func (client *Client) ParseEvent(evt *gomatrix.Event) *Event {
|
||||||
return &Event{
|
return &Event{
|
||||||
Client: client,
|
Client: client,
|
||||||
Event: evt,
|
Event: evt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) AddEventHandler(evt string, handler interfaces.EventHandler) {
|
func (client *Client) AddEventHandler(evt maubot.EventType, handler maubot.EventHandler) {
|
||||||
client.Syncer.(*MaubotSyncer).OnEventType(evt, handler)
|
client.syncer.OnEventType(evt, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) onJoin(evt *interfaces.Event) bool {
|
func (client *Client) onJoin(evt *maubot.Event) bool {
|
||||||
if !client.DB.AutoJoinRooms || evt.StateKey != client.DB.UserID {
|
if !client.DB.AutoJoinRooms || evt.StateKey != client.DB.UserID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if membership, _ := evt.Content["membership"].(string); membership == "invite" {
|
if evt.Content.Membership == "invite" {
|
||||||
client.JoinRoom(evt.RoomID)
|
client.JoinRoom(evt.RoomID)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,13 @@
|
||||||
package matrix
|
package matrix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"fmt"
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
|
"maubot.xyz"
|
||||||
|
"maunium.net/go/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
|
var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
|
||||||
|
@ -42,13 +44,13 @@ func TrimReplyFallbackText(text string) string {
|
||||||
return strings.Join(lines, "\n")
|
return strings.Join(lines, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveReplyFallback(evt *gomatrix.Event) {
|
func RemoveReplyFallback(evt *maubot.Event) {
|
||||||
if format, ok := evt.Content["format"].(string); ok && format == "org.matrix.custom.html" {
|
if len(evt.Content.RelatesTo.InReplyTo.EventID) > 0 {
|
||||||
htmlBody, _ := evt.Content["formatted_body"].(string)
|
if evt.Content.Format == maubot.FormatHTML {
|
||||||
evt.Content["formatted_body"] = TrimReplyFallbackHTML(htmlBody)
|
evt.Content.FormattedBody = TrimReplyFallbackHTML(evt.Content.FormattedBody)
|
||||||
|
}
|
||||||
|
evt.Content.Body = TrimReplyFallbackText(evt.Content.Body)
|
||||||
}
|
}
|
||||||
plainBody, _ := evt.Content["body"].(string)
|
|
||||||
evt.Content["body"] = TrimReplyFallbackText(plainBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReplyFormat = `<mx-reply><blockquote>
|
const ReplyFormat = `<mx-reply><blockquote>
|
||||||
|
@ -86,22 +88,18 @@ func ReplyFallbackText(evt *gomatrix.Event) string {
|
||||||
return fallbackText.String()
|
return fallbackText.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetReply(content map[string]interface{}, inReplyTo *gomatrix.Event) map[string]interface{} {
|
func SetReply(content maubot.Content, inReplyTo *gomatrix.Event) maubot.Content {
|
||||||
content["m.relates_to"] = map[string]interface{}{
|
content.RelatesTo.InReplyTo.EventID = inReplyTo.ID
|
||||||
"m.in_reply_to": map[string]interface{}{
|
content.RelatesTo.InReplyTo.RoomID = inReplyTo.RoomID
|
||||||
"event_id": inReplyTo.ID,
|
|
||||||
"room_id": inReplyTo.RoomID,
|
if content.MsgType == maubot.MsgText || content.MsgType == maubot.MsgNotice {
|
||||||
},
|
if len(content.FormattedBody) == 0 || content.Format != maubot.FormatHTML {
|
||||||
|
content.FormattedBody = html.EscapeString(content.Body)
|
||||||
|
content.Format = maubot.FormatHTML
|
||||||
|
}
|
||||||
|
content.FormattedBody = ReplyFallbackHTML(inReplyTo) + content.FormattedBody
|
||||||
|
content.Body = ReplyFallbackText(inReplyTo) + content.Body
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := content["body"].(string)
|
|
||||||
content["body"] = ReplyFallbackText(inReplyTo) + body
|
|
||||||
|
|
||||||
htmlBody, ok := content["formatted_body"].(string)
|
|
||||||
if !ok {
|
|
||||||
htmlBody = html.EscapeString(body)
|
|
||||||
content["format"] = "org.matrix.custom.html"
|
|
||||||
}
|
|
||||||
content["formatted_body"] = ReplyFallbackHTML(inReplyTo) + htmlBody
|
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maubot.xyz/interfaces"
|
"maubot.xyz"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MaubotSyncer struct {
|
type MaubotSyncer struct {
|
||||||
Client *Client
|
Client *Client
|
||||||
Store gomatrix.Storer
|
Store gomatrix.Storer
|
||||||
listeners map[string][]interfaces.EventHandler
|
listeners map[maubot.EventType][]maubot.EventHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultSyncer returns an instantiated DefaultSyncer
|
// NewDefaultSyncer returns an instantiated DefaultSyncer
|
||||||
|
@ -21,7 +21,7 @@ func NewMaubotSyncer(client *Client, store gomatrix.Storer) *MaubotSyncer {
|
||||||
return &MaubotSyncer{
|
return &MaubotSyncer{
|
||||||
Client: client,
|
Client: client,
|
||||||
Store: store,
|
Store: store,
|
||||||
listeners: make(map[string][]interfaces.EventHandler),
|
listeners: make(map[maubot.EventType][]maubot.EventHandler),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +73,10 @@ func (s *MaubotSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (er
|
||||||
|
|
||||||
// OnEventType allows callers to be notified when there are new events for the given event type.
|
// OnEventType allows callers to be notified when there are new events for the given event type.
|
||||||
// There are no duplicate checks.
|
// There are no duplicate checks.
|
||||||
func (s *MaubotSyncer) OnEventType(eventType string, callback interfaces.EventHandler) {
|
func (s *MaubotSyncer) OnEventType(eventType maubot.EventType, callback maubot.EventHandler) {
|
||||||
_, exists := s.listeners[eventType]
|
_, exists := s.listeners[eventType]
|
||||||
if !exists {
|
if !exists {
|
||||||
s.listeners[eventType] = []interfaces.EventHandler{}
|
s.listeners[eventType] = []maubot.EventHandler{}
|
||||||
}
|
}
|
||||||
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ func (s *MaubotSyncer) getOrCreateRoom(roomID string) *gomatrix.Room {
|
||||||
|
|
||||||
func (s *MaubotSyncer) notifyListeners(mxEvent *gomatrix.Event) {
|
func (s *MaubotSyncer) notifyListeners(mxEvent *gomatrix.Event) {
|
||||||
event := s.Client.ParseEvent(mxEvent)
|
event := s.Client.ParseEvent(mxEvent)
|
||||||
listeners, exists := s.listeners[event.Type]
|
listeners, exists := s.listeners[maubot.EventType(event.Type)]
|
||||||
if !exists {
|
if !exists {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
68
plugin.go
68
plugin.go
|
@ -16,66 +16,16 @@
|
||||||
|
|
||||||
package maubot
|
package maubot
|
||||||
|
|
||||||
import (
|
type Plugin interface {
|
||||||
"fmt"
|
Start()
|
||||||
"plugin"
|
Stop()
|
||||||
|
|
||||||
"maubot.xyz/database"
|
|
||||||
"maubot.xyz/interfaces"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PluginWrapper struct {
|
|
||||||
interfaces.Plugin
|
|
||||||
Creator *interfaces.PluginCreator
|
|
||||||
DB *database.Plugin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPlugin(path string) (*interfaces.PluginCreator, error) {
|
type PluginCreatorFunc func(client MatrixClient) Plugin
|
||||||
rawPlugin, err := plugin.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginCreatorSymbol, err := rawPlugin.Lookup("Plugin")
|
type PluginCreator struct {
|
||||||
if err == nil {
|
Create PluginCreatorFunc
|
||||||
pluginCreator, ok := pluginCreatorSymbol.(*interfaces.PluginCreator)
|
Name string
|
||||||
if ok {
|
Version string
|
||||||
pluginCreator.Path = path
|
Path string
|
||||||
return pluginCreator, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginCreatorFuncSymbol, err := rawPlugin.Lookup("Create")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("symbol \"Create\" not found: %v", err)
|
|
||||||
}
|
|
||||||
pluginCreatorFunc, ok := pluginCreatorFuncSymbol.(interfaces.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 &interfaces.PluginCreator{
|
|
||||||
Create: pluginCreatorFunc,
|
|
||||||
Name: name,
|
|
||||||
Version: version,
|
|
||||||
Path: path,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue