summary refs log tree commit diff
diff options
context:
space:
mode:
authorEmile <git@emile.space>2024-10-25 15:30:20 +0200
committerEmile <git@emile.space>2024-10-25 15:30:20 +0200
commita5ad511de32778b4e609584d0fc027192646a1a1 (patch)
tree6a6f71be21d250179d4375e5df518382d935de28
initial commit
-rw-r--r--go.mod24
-rw-r--r--go.sum50
-rw-r--r--src/main.go236
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")
+	//  }
+}