matrix-gptzoo/bot/matrix.go

180 lines
4.6 KiB
Go
Raw Normal View History

2023-05-17 19:51:45 +02:00
package bot
2023-05-16 20:07:05 +02:00
import (
"fmt"
"time"
"github.com/chzyer/readline"
"github.com/rs/zerolog"
2023-05-18 09:59:01 +02:00
"github.com/sashabaranov/go-openai"
2023-05-16 20:07:05 +02:00
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id"
)
type Config struct {
Homeserver string
UserID string
UserAccessKey string
UserPassword string
DBPath string
Pickle string
2023-05-17 19:51:45 +02:00
OpenAIKey string
2023-05-16 20:07:05 +02:00
}
type Matrix struct {
2023-05-17 19:51:45 +02:00
config Config
readline *readline.Instance
client *mautrix.Client
cryptoHelper *cryptohelper.CryptoHelper
conversations Conversations
gptClient *GPT
2023-05-16 20:07:05 +02:00
}
func New(cfg Config) *Matrix {
return &Matrix{
config: cfg,
}
}
func (m *Matrix) Init() error {
client, err := mautrix.NewClient(m.config.Homeserver, id.UserID(m.config.UserID), m.config.UserAccessKey)
if err != nil {
return err
}
var oei mautrix.OldEventIgnorer
oei.Register(client.Syncer.(mautrix.ExtensibleSyncer))
m.client = client
m.client.Log = zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.TimeFormat = time.Stamp
2023-05-18 09:29:23 +02:00
})).With().Timestamp().Logger().Level(zerolog.InfoLevel)
2023-05-16 20:07:05 +02:00
m.cryptoHelper, err = cryptohelper.NewCryptoHelper(client, []byte(m.config.Pickle), m.config.DBPath)
if err != nil {
return err
}
m.cryptoHelper.LoginAs = &mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: m.config.UserID},
Password: m.config.UserPassword,
}
if err := m.cryptoHelper.Init(); err != nil {
return err
}
m.client.Crypto = m.cryptoHelper
2023-05-17 19:51:45 +02:00
m.gptClient = NewGPT(m.config.OpenAIKey)
m.conversations = make(Conversations, 0)
2023-05-16 20:07:05 +02:00
return nil
}
func (m *Matrix) Run() error {
if err := m.client.Sync(); err != nil {
return err
}
return nil
}
func (m *Matrix) Close() error {
if err := m.client.Sync(); err != nil {
return err
}
if err := m.cryptoHelper.Close(); err != nil {
return err
}
return nil
}
func (m *Matrix) AddEventHandler(eventType event.Type, handler mautrix.EventHandler) {
syncer := m.client.Syncer.(*mautrix.DefaultSyncer)
syncer.OnEventType(eventType, handler)
}
func (m *Matrix) InviteHandler() (event.Type, mautrix.EventHandler) {
return event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
if evt.GetStateKey() == m.client.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
_, err := m.client.JoinRoomByID(evt.RoomID)
if err == nil {
m.client.Log.Info().
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Joined room after invite")
} else {
m.client.Log.Error().Err(err).
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Failed to join room after invite")
}
}
}
}
2023-05-23 15:40:16 +02:00
func (m *Matrix) ResponseHandler() (event.Type, mautrix.EventHandler) {
2023-05-16 20:07:05 +02:00
return event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
2023-05-18 09:29:23 +02:00
content := evt.Content.AsMessage()
2023-05-18 10:38:40 +02:00
eventID := evt.ID
2023-05-16 20:07:05 +02:00
m.client.Log.Info().
2023-05-18 09:29:23 +02:00
Str("content", content.Body).
2023-05-18 10:38:40 +02:00
Msg("received message")
conv := m.conversations.FindByEventID(eventID)
if conv != nil {
m.client.Log.Info().
Str("event_id", eventID.String()).
Msg("already known message, ignoring")
return
}
2023-05-18 09:59:01 +02:00
2023-05-18 10:38:40 +02:00
parentID := id.EventID("")
if relatesTo := content.GetRelatesTo(); relatesTo != nil {
parentID = relatesTo.GetReplyTo()
}
if parentID != "" {
2023-05-23 15:40:16 +02:00
m.client.Log.Info().Msg("parent found, looking for conversation")
2023-05-18 10:38:40 +02:00
conv = m.conversations.FindByEventID(parentID)
}
if conv != nil {
conv.Add(Message{
EventID: eventID,
ParentID: parentID,
Role: openai.ChatMessageRoleUser,
Content: content.Body,
})
m.client.Log.Info().Msg("found parent, appending message to conversation")
} else {
2023-05-23 15:40:16 +02:00
conv = NewConversation(eventID, content.Body)
2023-05-18 10:38:40 +02:00
m.conversations = append(m.conversations, conv)
m.client.Log.Info().Msg("no parent found, starting new conversation")
}
2023-05-18 09:59:01 +02:00
2023-05-18 10:38:40 +02:00
if evt.Sender != id.UserID(m.config.UserID) {
2023-05-18 09:59:01 +02:00
// get reply from GPT
reply, err := m.gptClient.Complete(conv)
2023-05-16 20:07:05 +02:00
if err != nil {
2023-05-18 09:29:23 +02:00
m.client.Log.Error().Err(err).Msg("OpenAI API returned with ")
2023-05-16 20:07:05 +02:00
return
}
2023-05-18 09:59:01 +02:00
formattedReply := format.RenderMarkdown(reply, true, false)
formattedReply.RelatesTo = &event.RelatesTo{
2023-05-18 09:29:23 +02:00
InReplyTo: &event.InReplyTo{
EventID: eventID,
},
}
2023-05-18 10:38:40 +02:00
if _, err := m.client.SendMessageEvent(evt.RoomID, event.EventMessage, &formattedReply); err != nil {
2023-05-18 09:29:23 +02:00
m.client.Log.Err(err).Msg("failed to send message")
return
}
2023-05-18 09:59:01 +02:00
m.client.Log.Info().Str("message", fmt.Sprintf("%+v", formattedReply.Body)).Msg("Sent reply")
2023-05-16 20:07:05 +02:00
}
}
}