diff options
-rw-r--r-- | go.mod | 24 | ||||
-rw-r--r-- | go.sum | 50 | ||||
-rw-r--r-- | src/main.go | 236 |
3 files changed, 310 insertions, 0 deletions
diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f684195 --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module git.emile.space/remarvin + +go 1.22.6 + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect + github.com/rs/zerolog v1.33.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.mau.fi/util v0.8.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + maunium.net/go/mautrix v0.21.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..61aebd8 --- /dev/null +++ b/go.sum @@ -0,0 +1,50 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.mau.fi/util v0.8.1 h1:Ga43cz6esQBYqcjZ/onRoVnYWoUwjWbsxVeJg2jOTSo= +go.mau.fi/util v0.8.1/go.mod h1:T1u/rD2rzidVrBLyaUdPpZiJdP/rsyi+aTzn0D+Q6wc= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +maunium.net/go/mautrix v0.21.1 h1:Z+e448jtlY977iC1kokNJTH5kg2WmDpcQCqn+v9oZOA= +maunium.net/go/mautrix v0.21.1/go.mod h1:7F/S6XAdyc/6DW+Q7xyFXRSPb6IjfqMb1OMepQ8C8OE= diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..0540ed0 --- /dev/null +++ b/src/main.go @@ -0,0 +1,236 @@ +// Copyright (C) 2017 Tulir Asokan +// Copyright (C) 2018-2020 Luca Weiss +// Copyright (C) 2023 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This is the sourcecode for the "remarvin" bot, a quick and dirty marvin bot + +// TODO(emile): figure out how to get the whole crypto foo runnning, as it isn't working using the +// example code provided in the mautrix/go repo + +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "sync" + "time" + + "github.com/chzyer/readline" + // _ "github.com/mattn/go-sqlite3" + "github.com/rs/zerolog" + "go.mau.fi/util/exzerolog" + + "maunium.net/go/mautrix" + // "maunium.net/go/mautrix/crypto/cryptohelper" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +var homeserver = flag.String("homeserver", "", "Matrix homeserver") +var username = flag.String("username", "", "Matrix username localpart") +var accesstoken = flag.String("accesstoken", "", "Matrix accesstoken") + +// var password = flag.String("password", "", "Matrix password") +// var database = flag.String("database", "mautrix-example.db", "SQLite database path") + +var debug = flag.Bool("debug", false, "Enable debug logs") + +func main() { + flag.Parse() + if *username == "" || *homeserver == "" || *accesstoken == "" { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + os.Exit(1) + } + + // create a new client using the given username, homeserver and accesstoken + userID := id.NewUserID(*username, *homeserver) + client, err := mautrix.NewClient(*homeserver, userID, *accesstoken) + if err != nil { + panic(err) + } + + // there's a prompt for manual intervention + rl, err := readline.New("[no room]> ") + if err != nil { + panic(err) + } + defer rl.Close() + + // some fancy logging from the example + log := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { + w.Out = rl.Stdout() + w.TimeFormat = time.Stamp + })).With().Timestamp().Logger() + if !*debug { + log = log.Level(zerolog.InfoLevel) + } + exzerolog.SetupDefaults(&log) + client.Log = log + + // marvin always replies in the last room he was mentioned in + var lastRoomID id.RoomID + + syncer := client.Syncer.(*mautrix.DefaultSyncer) + syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) { + + // When marvin received an event, the room the event was sent from gets set here + // This is used for replying within that room + lastRoomID = evt.RoomID + rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID)) + log.Info(). + Str("sender", evt.Sender.String()). + Str("type", evt.Type.String()). + Str("id", evt.ID.String()). + Str("body", evt.Content.AsMessage().Body). + Msg("Received message") + + body := evt.Content.AsMessage().Body + + // filtering out the synced messages, remarvin only answers messages that are coming in + // after has has been started, otherwise you could spam `.5` and marvin would spam back + // which is annoying + // yes, we have an offset of like 6h + eventTime := evt.Timestamp + (6 * 60 * 60) + currentTime := time.Now().UnixMilli() + if eventTime <= currentTime { + log.Info().Msg("old msg, not responding") + return + } + + // don't want to reply to our own messages! + if evt.Sender.String() == client.UserID.String() { + log.Info().Msg("ourself, not responding") + return + } + + if body == ".5" { + line := `five questions huh? + We usually ask new people here 5 questions for the means of introduction. No personal data wanted. Are you in for that? + + Hi and welcome to milliways. + + For the means of introduction we ask new people 5 questions. + + 1. who are you + 2. how did you get here + 3. what can you do for milliways + 4. what can milliways do for you + 5. what are you good in which is not computers + + and bonus question: Do you come to 38c3? + ` + resp, err := client.SendText(context.TODO(), lastRoomID, line) + if err != nil { + log.Error().Err(err).Msg("Failed to send event") + } else { + log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent") + } + } + }) + + // auto join the room if invited + syncer.OnEventType(event.StateMember, func(ctx context.Context, evt *event.Event) { + if evt.GetStateKey() == client.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite { + _, err := client.JoinRoomByID(ctx, evt.RoomID) + if err == nil { + lastRoomID = evt.RoomID + rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID)) + log.Info(). + Str("room_id", evt.RoomID.String()). + Str("inviter", evt.Sender.String()). + Msg("Joined room after invite") + } else { + log.Error().Err(err). + Str("room_id", evt.RoomID.String()). + Str("inviter", evt.Sender.String()). + Msg("Failed to join room after invite") + } + } + + }) + + // The crypto stuff here isn't working + // Seems like it can't find the olm stuff on my system, due to this bot only running in the + // public matrix channel and it only sending some questions to all users, this shouldn't be all + // to much of a problem, yet I'd like to get this working at some point + + // -------------- + // cryptoHelper, err := cryptohelper.NewCryptoHelper(client, []byte("meow"), *database) + // if err != nil { + // panic(err) + // } + // -------------- + + // You can also store the user/device IDs and access token and put them in the client beforehand instead of using LoginAs. + //client.UserID = "..." + //client.DeviceID = "..." + //client.AccessToken = "..." + // You don't need to set a device ID in LoginAs because the crypto helper will set it for you if necessary. + + // -------------- + // cryptoHelper.LoginAs = &mautrix.ReqLogin{ + // Type: mautrix.AuthTypePassword, + // Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: *username}, + // Password: *password, + // } + // -------------- + + // If you want to use multiple clients with the same DB, you should set a distinct database account ID for each one. + //cryptoHelper.DBAccountID = "" + + // -------------- + // err = cryptoHelper.Init(context.TODO()) + // if err != nil { + // panic(err) + // } + // -------------- + + // Set the client crypto helper in order to automatically encrypt outgoing messages + // -------------- + // client.Crypto = cryptoHelper + // -------------- + + log.Info().Msg("Now running") + syncCtx, cancelSync := context.WithCancel(context.Background()) + var syncStopWait sync.WaitGroup + syncStopWait.Add(1) + + go func() { + err = client.SyncWithContext(syncCtx) + defer syncStopWait.Done() + if err != nil && !errors.Is(err, context.Canceled) { + panic(err) + } + }() + + for { + line, err := rl.Readline() + if err != nil { // io.EOF + break + } + if lastRoomID == "" { + log.Error().Msg("Wait for an incoming message before sending messages") + continue + } + resp, err := client.SendText(context.TODO(), lastRoomID, line) + if err != nil { + log.Error().Err(err).Msg("Failed to send event") + } else { + log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent") + } + } + cancelSync() + syncStopWait.Wait() + // err = cryptoHelper.Close() + // if err != nil { + // log.Error().Err(err).Msg("Error closing database") + // } +} |