diff --git a/maubot.go b/app/bot.go similarity index 92% rename from maubot.go rename to app/bot.go index f59f45b..0315777 100644 --- a/maubot.go +++ b/app/bot.go @@ -14,15 +14,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package maubot +package app import ( "net/http" "os" + "maubot.xyz" "maubot.xyz/config" "maubot.xyz/database" - "maubot.xyz/interfaces" "maubot.xyz/matrix" log "maunium.net/go/maulogger" ) @@ -31,7 +31,7 @@ type Bot struct { Config *config.MainConfig Database *database.Database Clients map[string]*matrix.Client - PluginCreators map[string]*interfaces.PluginCreator + PluginCreators map[string]*maubot.PluginCreator Plugins map[string]*PluginWrapper Server *http.Server } @@ -41,7 +41,7 @@ func New(config *config.MainConfig) *Bot { Config: config, Clients: make(map[string]*matrix.Client), Plugins: make(map[string]*PluginWrapper), - PluginCreators: make(map[string]*interfaces.PluginCreator), + PluginCreators: make(map[string]*maubot.PluginCreator), } } diff --git a/http.go b/app/http.go similarity index 99% rename from http.go rename to app/http.go index fe96a47..baf784a 100644 --- a/http.go +++ b/app/http.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package maubot +package app import ( "context" diff --git a/app/matrix.go b/app/matrix.go new file mode 100644 index 0000000..8a9e631 --- /dev/null +++ b/app/matrix.go @@ -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 . + +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() + } +} diff --git a/app/pluginloader.go b/app/pluginloader.go new file mode 100644 index 0000000..aff9856 --- /dev/null +++ b/app/pluginloader.go @@ -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 . + +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 +} diff --git a/plugins.go b/app/plugins.go similarity index 99% rename from plugins.go rename to app/plugins.go index 42f6f82..d53e09b 100644 --- a/plugins.go +++ b/app/plugins.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package maubot +package app import ( "io/ioutil" diff --git a/cmd/maubot/main.go b/cmd/maubot/main.go index 3d8e739..fa76fb4 100644 --- a/cmd/maubot/main.go +++ b/cmd/maubot/main.go @@ -23,7 +23,7 @@ import ( "syscall" _ "github.com/mattn/go-sqlite3" - "maubot.xyz" + "maubot.xyz/app" "maubot.xyz/config" flag "maunium.net/go/mauflag" log "maunium.net/go/maulogger" @@ -55,7 +55,7 @@ func main() { cfg.Logging.Configure(log.DefaultLogger) log.Debugln("Logger configured") - bot := maubot.New(cfg) + bot := app.New(cfg) bot.Init() bot.Start() diff --git a/interfaces/interfaces.go b/interfaces/interfaces.go deleted file mode 100644 index c4ccea8..0000000 --- a/interfaces/interfaces.go +++ /dev/null @@ -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 -} diff --git a/matrix.go b/matrix.go index c65a545..2d9a12b 100644 --- a/matrix.go +++ b/matrix.go @@ -16,39 +16,109 @@ package maubot -import ( - "os" +type EventType string +type MessageType string - "maubot.xyz/matrix" - log "maunium.net/go/maulogger" +// State events +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() { - 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 - } +// Message events +const ( + EventRedaction EventType = "m.room.redaction" + EventMessage = "m.room.message" + EventSticker = "m.sticker" +) + +// Msgtypes +const ( + MsgText MessageType = "m.text" + 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() { - log.Debugln("Starting Matrix syncer") - for _, client := range bot.Clients { - if client.DB.Sync { - client.Sync() - } - } +type EventFuncs interface { + Reply(string) (string, error) + ReplyContent(Content) (string, error) + SendMessage(string) (string, error) + SendContent(Content) (string, error) + SendRawEvent(EventType, interface{}) (string, error) } -func (bot *Bot) stopClients() { - log.Debugln("Stopping Matrix syncers") - for _, client := range bot.Clients { - client.StopSync() - } +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 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"` } diff --git a/matrix/event.go b/matrix/event.go index f86b602..f418bee 100644 --- a/matrix/event.go +++ b/matrix/event.go @@ -17,7 +17,9 @@ package matrix import ( - "maubot.xyz/interfaces" + "encoding/json" + + "maubot.xyz" "maunium.net/go/gomatrix" ) @@ -26,43 +28,64 @@ type Event struct { 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 if evt.StateKey != nil { stateKey = *evt.StateKey } - return &interfaces.Event{ + mbEvent := &maubot.Event{ EventFuncs: evt, StateKey: stateKey, Sender: evt.Sender, - Type: evt.Type, + Type: maubot.EventType(evt.Type), Timestamp: evt.Timestamp, ID: evt.ID, RoomID: evt.RoomID, - Content: evt.Content, + Content: roundtripContent(evt.Content), Redacts: evt.Redacts, - Unsigned: interfaces.Unsigned{ - PrevContent: evt.Unsigned.PrevContent, + Unsigned: maubot.Unsigned{ + PrevContent: roundtripContent(evt.Unsigned.PrevContent), PrevSender: evt.Unsigned.PrevSender, ReplacesState: evt.Unsigned.ReplacesState, Age: evt.Unsigned.Age, }, } + RemoveReplyFallback(mbEvent) + return mbEvent } func (evt *Event) Reply(text string) (string, error) { - return evt.SendEvent( + return evt.SendRawEvent(maubot.EventMessage, SetReply( RenderMarkdown(text), evt.Event)) } -func (evt *Event) SendMessage(text string) (string, error) { - return evt.SendEvent(RenderMarkdown(text)) +func (evt *Event) ReplyContent(content maubot.Content) (string, error) { + return evt.SendRawEvent(maubot.EventMessage, SetReply(content, evt.Event)) } -func (evt *Event) SendEvent(content map[string]interface{}) (string, error) { - resp, err := evt.Client.SendMessageEvent(evt.RoomID, "m.room.message", content) +func (evt *Event) SendMessage(text string) (string, error) { + 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 { return "", err } diff --git a/matrix/htmlutil.go b/matrix/htmlutil.go index 565008f..9c235fd 100644 --- a/matrix/htmlutil.go +++ b/matrix/htmlutil.go @@ -19,9 +19,10 @@ package matrix import ( "strings" "gopkg.in/russross/blackfriday.v2" + "maubot.xyz" ) -func RenderMarkdown(text string) map[string]interface{} { +func RenderMarkdown(text string) maubot.Content { parser := blackfriday.New( blackfriday.WithExtensions(blackfriday.NoIntraEmphasis | blackfriday.Tables | @@ -43,10 +44,10 @@ func RenderMarkdown(text string) map[string]interface{} { renderer.RenderFooter(&buf, ast) htmlBody := buf.String() - return map[string]interface{}{ - "formatted_body": htmlBody, - "format": "org.matrix.custom.html", - "msgtype": "m.text", - "body": HTMLToText(htmlBody), + return maubot.Content{ + FormattedBody: htmlBody, + Format: maubot.FormatHTML, + MsgType: maubot.MsgText, + Body: HTMLToText(htmlBody), } } diff --git a/matrix/matrix.go b/matrix/matrix.go index 790f8c0..a276d37 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -17,14 +17,15 @@ package matrix import ( + "maubot.xyz" "maubot.xyz/database" - "maubot.xyz/interfaces" "maunium.net/go/gomatrix" log "maunium.net/go/maulogger" ) type Client struct { *gomatrix.Client + syncer *MaubotSyncer DB *database.MatrixClient } @@ -40,9 +41,10 @@ func NewClient(db *database.MatrixClient) (*Client, error) { 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 } @@ -50,19 +52,19 @@ func NewClient(db *database.MatrixClient) (*Client, error) { func (client *Client) ParseEvent(evt *gomatrix.Event) *Event { return &Event{ Client: client, - Event: evt, + Event: evt, } } -func (client *Client) AddEventHandler(evt string, handler interfaces.EventHandler) { - client.Syncer.(*MaubotSyncer).OnEventType(evt, handler) +func (client *Client) AddEventHandler(evt maubot.EventType, handler maubot.EventHandler) { + 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 { return true } - if membership, _ := evt.Content["membership"].(string); membership == "invite" { + if evt.Content.Membership == "invite" { client.JoinRoom(evt.RoomID) return false } diff --git a/matrix/replyutil.go b/matrix/replyutil.go index 3f4c2c0..6009dfc 100644 --- a/matrix/replyutil.go +++ b/matrix/replyutil.go @@ -17,11 +17,13 @@ package matrix import ( + "fmt" "regexp" "strings" - "fmt" - "maunium.net/go/gomatrix" + "golang.org/x/net/html" + "maubot.xyz" + "maunium.net/go/gomatrix" ) var HTMLReplyFallbackRegex = regexp.MustCompile(`^[\s\S]+?`) @@ -42,13 +44,13 @@ func TrimReplyFallbackText(text string) string { return strings.Join(lines, "\n") } -func RemoveReplyFallback(evt *gomatrix.Event) { - if format, ok := evt.Content["format"].(string); ok && format == "org.matrix.custom.html" { - htmlBody, _ := evt.Content["formatted_body"].(string) - evt.Content["formatted_body"] = TrimReplyFallbackHTML(htmlBody) +func RemoveReplyFallback(evt *maubot.Event) { + if len(evt.Content.RelatesTo.InReplyTo.EventID) > 0 { + if evt.Content.Format == maubot.FormatHTML { + 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 = `
@@ -86,22 +88,18 @@ func ReplyFallbackText(evt *gomatrix.Event) string { return fallbackText.String() } -func SetReply(content map[string]interface{}, inReplyTo *gomatrix.Event) map[string]interface{} { - content["m.relates_to"] = map[string]interface{}{ - "m.in_reply_to": map[string]interface{}{ - "event_id": inReplyTo.ID, - "room_id": inReplyTo.RoomID, - }, +func SetReply(content maubot.Content, inReplyTo *gomatrix.Event) maubot.Content { + content.RelatesTo.InReplyTo.EventID = inReplyTo.ID + content.RelatesTo.InReplyTo.RoomID = 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 } diff --git a/matrix/sync.go b/matrix/sync.go index 44c28f7..b8fd06c 100644 --- a/matrix/sync.go +++ b/matrix/sync.go @@ -6,14 +6,14 @@ import ( "runtime/debug" "time" - "maubot.xyz/interfaces" + "maubot.xyz" "maunium.net/go/gomatrix" ) type MaubotSyncer struct { Client *Client Store gomatrix.Storer - listeners map[string][]interfaces.EventHandler + listeners map[maubot.EventType][]maubot.EventHandler } // NewDefaultSyncer returns an instantiated DefaultSyncer @@ -21,7 +21,7 @@ func NewMaubotSyncer(client *Client, store gomatrix.Storer) *MaubotSyncer { return &MaubotSyncer{ Client: client, 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. // 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] if !exists { - s.listeners[eventType] = []interfaces.EventHandler{} + s.listeners[eventType] = []maubot.EventHandler{} } 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) { event := s.Client.ParseEvent(mxEvent) - listeners, exists := s.listeners[event.Type] + listeners, exists := s.listeners[maubot.EventType(event.Type)] if !exists { return } diff --git a/plugin.go b/plugin.go index e6af4c8..1fe0cd3 100644 --- a/plugin.go +++ b/plugin.go @@ -16,66 +16,16 @@ package maubot -import ( - "fmt" - "plugin" - - "maubot.xyz/database" - "maubot.xyz/interfaces" -) - -type PluginWrapper struct { - interfaces.Plugin - Creator *interfaces.PluginCreator - DB *database.Plugin +type Plugin interface { + Start() + Stop() } -func LoadPlugin(path string) (*interfaces.PluginCreator, error) { - rawPlugin, err := plugin.Open(path) - if err != nil { - return nil, fmt.Errorf("failed to open: %v", err) - } +type PluginCreatorFunc func(client MatrixClient) Plugin - pluginCreatorSymbol, err := rawPlugin.Lookup("Plugin") - if err == nil { - pluginCreator, ok := pluginCreatorSymbol.(*interfaces.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.(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 +type PluginCreator struct { + Create PluginCreatorFunc + Name string + Version string + Path string }