mirror-nourybot-matrix/cmd/bot/main.go
2024-02-04 12:17:32 +01:00

216 lines
5.9 KiB
Go

// 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/.
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/chzyer/readline"
"github.com/joho/godotenv"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
var debug = flag.Bool("debug", false, "Enable debug logs")
var env = flag.String("env", "dev", "Environment to run in (dev/prod)")
// var database = flag.String("database", "test.db", "SQLite database path")
type config struct {
homeserver string
username string
password string
database string
db_account_id string
}
type application struct {
MatrixClient *mautrix.Client
Log zerolog.Logger
}
func main() {
var cfg config
flag.Parse()
err := godotenv.Load()
if err != nil {
panic(err)
}
if *env == "prod" {
cfg.homeserver = os.Getenv("PROD_MATRIX_HOMESERVER")
cfg.username = os.Getenv("PROD_MATRIX_USERNAME")
cfg.password = os.Getenv("PROD_MATRIX_PASSWORD")
cfg.database = os.Getenv("PROD_SQLITE_DATABASE")
cfg.db_account_id = os.Getenv("PROD_DB_ACCOUNT_ID")
} else {
cfg.homeserver = os.Getenv("DEV_MATRIX_HOMESERVER")
cfg.username = os.Getenv("DEV_MATRIX_USERNAME")
cfg.password = os.Getenv("DEV_MATRIX_PASSWORD")
cfg.database = os.Getenv("DEV_SQLITE_DATABASE")
cfg.db_account_id = os.Getenv("DEV_DB_ACCOUNT_ID")
}
client, err := mautrix.NewClient(cfg.homeserver, "", "")
if err != nil {
panic(err)
}
rl, err := readline.New("[no room]> ")
if err != nil {
panic(err)
}
defer rl.Close()
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)
}
client.Log = log
app := &application{
MatrixClient: client,
Log: log,
}
var lastRoomID id.RoomID
syncer := app.MatrixClient.Syncer.(*mautrix.DefaultSyncer)
syncer.OnSync(app.MatrixClient.DontProcessOldEvents)
//app.MatrixClient.Syncer.(mautrix.ExtensibleSyncer).OnSync(app.MatrixClient.DontProcessOldEvents)
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
lastRoomID = evt.RoomID
rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID))
if evt.Content.AsMessage().Body[:1] == "!" {
app.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 xdddddddddddddddddddddddd message")
app.ParseCommand(evt)
return
} else {
app.ParseEvent(evt)
return
}
})
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
if evt.GetStateKey() == app.MatrixClient.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
_, err := app.MatrixClient.JoinRoomByID(context.TODO(), 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")
}
}
})
cryptoHelper, err := cryptohelper.NewCryptoHelper(app.MatrixClient, []byte("meow"), cfg.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: cfg.username},
Password: cfg.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 = cfg.db_account_id
err = cryptoHelper.Init()
if err != nil {
panic(err)
}
// Set the client crypto helper in order to automatically encrypt outgoing messages
app.MatrixClient.Crypto = cryptoHelper
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
os.Exit(1)
}()
log.Info().Msg("Now running")
syncCtx, cancelSync := context.WithCancel(context.Background())
var syncStopWait sync.WaitGroup
syncStopWait.Add(1)
go func() {
err = app.MatrixClient.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 := app.MatrixClient.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")
}
}
func (app *application) SendText(evt *event.Event, message string) {
room := evt.RoomID
resp, err := app.MatrixClient.SendText(context.TODO(), room, message)
if err != nil {
app.Log.Error().Err(err).Msg("Failed to send event")
} else {
app.Log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent")
}
}