package main import ( "context" "net/http" "os" "os/signal" "strings" "sync" "syscall" "time" "github.com/matrix-org/gomatrix" "github.com/sirupsen/logrus" "git.hacknology.de/projekte/leakybot/internal/config" "git.hacknology.de/projekte/leakybot/internal/httputil" "git.hacknology.de/projekte/leakybot/internal/power" "git.hacknology.de/projekte/leakybot/internal/ruler" ) const ( idSeparator = "--" ) var ( signals = []os.Signal{syscall.SIGINT, syscall.SIGTERM} log = &logrus.Logger{ Out: os.Stderr, Formatter: &logrus.TextFormatter{ DisableTimestamp: true, }, Hooks: make(logrus.LevelHooks), Level: logrus.InfoLevel, ExitFunc: os.Exit, ReportCaller: false, } ) func formatID(user, room string) string { return strings.Join([]string{user, room}, idSeparator) } func splitID(id string) (user, room string) { tokens := strings.SplitN(id, idSeparator, 2) return tokens[0], tokens[1] } func main() { cfg, err := config.Get(os.Args[0], os.Args[1:], os.Getenv) if err != nil { log.Fatalf("Error getting config: %s", err) } log.SetLevel(cfg.LogLevel) ctx, cancel := context.WithCancel(context.Background()) defer cancel() wg := &sync.WaitGroup{} log.Infof("Homeserver: %s User: %s", cfg.Account.HomeServer, cfg.Account.Username) client, err := gomatrix.NewClient(cfg.Account.HomeServer, cfg.Account.Username, cfg.Account.AccessToken) if err != nil { log.Fatalf("Error creating client: %s", err) } client.Client = &http.Client{ Timeout: 60 * time.Second, Transport: httputil.ContextTransport(ctx, http.DefaultTransport), } rooms, err := client.JoinedRooms() if err != nil { log.Fatalf("Error retrieving rooms: %s", err) } for _, r := range rooms.JoinedRooms { log.Debugf("Found room: %s", r) } rulerFuncs := ruler.Funcs{ Warn: func(id string) error { user, room := splitID(id) return power.ModifyLevel(log, client, room, user, -1) }, Unwarn: func(id string) error { user, room := splitID(id) return power.RemoveLevel(log, client, room, user) }, Ban: func(id string) error { user, room := splitID(id) _, err := client.BanUser(room, &gomatrix.ReqBanUser{ Reason: "spam", UserID: user, }) return err }, Unban: func(string) error { return nil }, } r, err := ruler.New(log, cfg.Rules, time.Now, rulerFuncs) if err != nil { log.Fatalf("Error creating ruler: %s", err) } eventCallback := func(evt *gomatrix.Event) { id := formatID(evt.Sender, evt.RoomID) timeStamp := time.Unix(evt.Timestamp/1000, 0) if err := r.PushEvent(timeStamp, id); err != nil { log.Errorf("Error pushing event to ruler: %s", err) return } // Using MarkRead from the client library results in an error ("content not JSON") // TODO revert to using MarkRead once fixed in library urlPath := client.BuildURL("rooms", evt.RoomID, "receipt", "m.read", evt.ID) if err := client.MakeRequest(http.MethodPost, urlPath, struct{}{}, nil); err != nil { log.WithFields(logrus.Fields{ "event": evt.ID, "room": evt.RoomID, }).WithError(err).Error("Can not mark event as read.") } } syncer := client.Syncer.(*gomatrix.DefaultSyncer) syncer.OnEventType("m.room.encrypted", eventCallback) syncer.OnEventType("m.room.message", eventCallback) sigCh := make(chan os.Signal) signal.Notify(sigCh, signals...) go func() { sig := <-sigCh signal.Reset(signals...) log.Debugf("Got signal %q. Terminating...", sig) cancel() }() r.Start(ctx, wg) log.Debug("Starting synchronization loop...") for { if ctx.Err() != nil { break } if err := client.Sync(); err != nil { log.Errorf("error during sync: %s", err) } } }