Separate plugin interface to reduce file size

This commit is contained in:
Tulir Asokan 2018-06-14 10:29:37 +03:00
parent d572522a96
commit ef8fffaff8
9 changed files with 254 additions and 31 deletions

View file

@ -17,6 +17,7 @@
package matrix
import (
"maubot.xyz/interfaces"
"maunium.net/go/gomatrix"
)
@ -25,6 +26,30 @@ type Event struct {
Client *Client
}
func (evt *Event) Interface() *interfaces.Event {
var stateKey string
if evt.StateKey != nil {
stateKey = *evt.StateKey
}
return &interfaces.Event{
EventFuncs: evt,
StateKey: stateKey,
Sender: evt.Sender,
Type: evt.Type,
Timestamp: evt.Timestamp,
ID: evt.ID,
RoomID: evt.RoomID,
Content: evt.Content,
Redacts: evt.Redacts,
Unsigned: interfaces.Unsigned{
PrevContent: evt.Unsigned.PrevContent,
PrevSender: evt.Unsigned.PrevSender,
ReplacesState: evt.Unsigned.ReplacesState,
Age: evt.Unsigned.Age,
},
}
}
func (evt *Event) Reply(text string) (string, error) {
return evt.SendEvent(
SetReply(

View file

@ -18,6 +18,7 @@ package matrix
import (
"maubot.xyz/database"
"maubot.xyz/interfaces"
"maunium.net/go/gomatrix"
log "maunium.net/go/maulogger"
)
@ -39,22 +40,33 @@ func NewClient(db *database.MatrixClient) (*Client, error) {
DB: db,
}
client.Syncer = NewMaubotSyncer(client, client.Store)
client.AddEventHandler(gomatrix.StateMember, client.onJoin)
return client, nil
}
func (client *Client) AddEventHandler(evt string, handler gomatrix.OnEventListener) {
client.Syncer.(*gomatrix.DefaultSyncer).OnEventType(evt, handler)
func (client *Client) ParseEvent(evt *gomatrix.Event) *Event {
return &Event{
Client: client,
Event: evt,
}
}
func (client *Client) onJoin(evt *gomatrix.Event) {
if !client.DB.AutoJoinRooms || evt.StateKey == nil || *evt.StateKey != client.DB.UserID {
return
func (client *Client) AddEventHandler(evt string, handler interfaces.EventHandler) {
client.Syncer.(*MaubotSyncer).OnEventType(evt, handler)
}
func (client *Client) onJoin(evt *interfaces.Event) bool {
if !client.DB.AutoJoinRooms || evt.StateKey != client.DB.UserID {
return true
}
if membership, _ := evt.Content["membership"].(string); membership == "invite" {
client.JoinRoom(evt.RoomID)
return false
}
return true
}
func (client *Client) JoinRoom(roomID string) {

152
matrix/sync.go Normal file
View file

@ -0,0 +1,152 @@
package matrix
import (
"encoding/json"
"fmt"
"runtime/debug"
"time"
"maubot.xyz/interfaces"
"maunium.net/go/gomatrix"
)
type MaubotSyncer struct {
Client *Client
Store gomatrix.Storer
listeners map[string][]interfaces.EventHandler
}
// NewDefaultSyncer returns an instantiated DefaultSyncer
func NewMaubotSyncer(client *Client, store gomatrix.Storer) *MaubotSyncer {
return &MaubotSyncer{
Client: client,
Store: store,
listeners: make(map[string][]interfaces.EventHandler),
}
}
// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of
// unrepeating events. Returns a fatal error if a listener panics.
func (s *MaubotSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (err error) {
if !s.shouldProcessResponse(res, since) {
return
}
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.Client.UserID, since, r, debug.Stack())
}
}()
for roomID, roomData := range res.Rooms.Join {
room := s.getOrCreateRoom(roomID)
for _, event := range roomData.State.Events {
event.RoomID = roomID
room.UpdateState(event)
s.notifyListeners(event)
}
for _, event := range roomData.Timeline.Events {
event.RoomID = roomID
s.notifyListeners(event)
}
}
for roomID, roomData := range res.Rooms.Invite {
room := s.getOrCreateRoom(roomID)
for _, event := range roomData.State.Events {
event.RoomID = roomID
room.UpdateState(event)
s.notifyListeners(event)
}
}
for roomID, roomData := range res.Rooms.Leave {
room := s.getOrCreateRoom(roomID)
for _, event := range roomData.Timeline.Events {
if event.StateKey != nil {
event.RoomID = roomID
room.UpdateState(event)
s.notifyListeners(event)
}
}
}
return
}
// OnEventType allows callers to be notified when there are new events for the given event type.
// There are no duplicate checks.
func (s *MaubotSyncer) OnEventType(eventType string, callback interfaces.EventHandler) {
_, exists := s.listeners[eventType]
if !exists {
s.listeners[eventType] = []interfaces.EventHandler{}
}
s.listeners[eventType] = append(s.listeners[eventType], callback)
}
// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
// stuff that shouldn't be processed.
func (s *MaubotSyncer) shouldProcessResponse(resp *gomatrix.RespSync, since string) bool {
if since == "" {
return false
}
// This is a horrible hack because /sync will return the most recent messages for a room
// as soon as you /join it. We do NOT want to process those events in that particular room
// because they may have already been processed (if you toggle the bot in/out of the room).
//
// Work around this by inspecting each room's timeline and seeing if an m.room.member event for us
// exists and is "join" and then discard processing that room entirely if so.
// TODO: We probably want to process messages from after the last join event in the timeline.
for roomID, roomData := range resp.Rooms.Join {
for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
e := roomData.Timeline.Events[i]
if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.Client.UserID {
m := e.Content["membership"]
mship, ok := m.(string)
if !ok {
continue
}
if mship == "join" {
_, ok := resp.Rooms.Join[roomID]
if !ok {
continue
}
delete(resp.Rooms.Join, roomID) // don't re-process messages
delete(resp.Rooms.Invite, roomID) // don't re-process invites
break
}
}
}
}
return true
}
// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
func (s *MaubotSyncer) getOrCreateRoom(roomID string) *gomatrix.Room {
room := s.Store.LoadRoom(roomID)
if room == nil {
room = gomatrix.NewRoom(roomID)
s.Store.SaveRoom(room)
}
return room
}
func (s *MaubotSyncer) notifyListeners(mxEvent *gomatrix.Event) {
event := s.Client.ParseEvent(mxEvent)
listeners, exists := s.listeners[event.Type]
if !exists {
return
}
for _, fn := range listeners {
if !fn(event.Interface()) {
break
}
}
}
// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
func (s *MaubotSyncer) OnFailedSync(res *gomatrix.RespSync, err error) (time.Duration, error) {
return 10 * time.Second, nil
}
// GetFilterJSON returns a filter with a timeline limit of 50.
func (s *MaubotSyncer) GetFilterJSON(userID string) json.RawMessage {
return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
}