summary refs log tree commit diff
path: root/src
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 /src
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/main.go236
1 files changed, 236 insertions, 0 deletions
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")
+	//  }
+}