initial commit of fafomo, the FAFO Matrix Bot
This commit is contained in:
commit
385f5dda97
5
dotenv.template
Normal file
5
dotenv.template
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
FAFOMO_HOMESERVER=matrix.org
|
||||||
|
FAFOMO_USERNAME=@fafomo:matrix.org
|
||||||
|
FAFOMO_PASSWORD=
|
||||||
|
FAFOMO_ROOM=
|
||||||
|
FAFOMO_BACKEND=
|
193
fafomo.go
Normal file
193
fafomo.go
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
// Copyright (C) 2024 Leah Neukirchen
|
||||||
|
//
|
||||||
|
// based on mautrix-go example code:
|
||||||
|
//
|
||||||
|
// 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"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
var backend string
|
||||||
|
|
||||||
|
var folks map[string]struct{}
|
||||||
|
|
||||||
|
type JSONTop struct {
|
||||||
|
Users []JSONUser `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONUser struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ircquote(s string) string {
|
||||||
|
if len(s) < 1 {
|
||||||
|
return s // bad luck, V
|
||||||
|
}
|
||||||
|
|
||||||
|
ZWJ := "\u200d"
|
||||||
|
return s[0:1] + ZWJ + s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func listFmt(list []string) string {
|
||||||
|
return "[ " + strings.Join(list, " ") + " ]"
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(ctx context.Context) string {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", backend, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var users JSONTop
|
||||||
|
|
||||||
|
json.NewDecoder(res.Body).Decode(&users)
|
||||||
|
|
||||||
|
var left []string
|
||||||
|
var arrived []string
|
||||||
|
var unchanged []string
|
||||||
|
|
||||||
|
new_folks := make(map[string]struct{})
|
||||||
|
for _, user := range users.Users {
|
||||||
|
new_folks[user.Login] = struct{}{}
|
||||||
|
if _, ok := folks[user.Login]; !ok {
|
||||||
|
arrived = append(arrived, user.Login)
|
||||||
|
} else {
|
||||||
|
unchanged = append(unchanged, user.Login)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for user, _ := range folks {
|
||||||
|
if _, ok := new_folks[user]; !ok {
|
||||||
|
left = append(left, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
folks = new_folks
|
||||||
|
|
||||||
|
// no change
|
||||||
|
if len(arrived) == 0 && len(left) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
message := ""
|
||||||
|
if len(arrived) > 0 {
|
||||||
|
message += "arrived: " + listFmt(arrived) + "; "
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
message += "left: " + listFmt(left) + "; "
|
||||||
|
}
|
||||||
|
if len(unchanged) > 0 {
|
||||||
|
message += "also there: " + listFmt(unchanged) + "; "
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
homeserver := os.Getenv("FAFOMO_HOMESERVER")
|
||||||
|
username := os.Getenv("FAFOMO_USERNAME")
|
||||||
|
password := os.Getenv("FAFOMO_PASSWORD")
|
||||||
|
room := os.Getenv("FAFOMO_ROOM")
|
||||||
|
backend = os.Getenv("FAFOMO_BACKEND")
|
||||||
|
|
||||||
|
startCtx, _ := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
|
|
||||||
|
update(startCtx)
|
||||||
|
|
||||||
|
client, err := mautrix.NewClient(homeserver, id.UserID(username), "")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Logging in...")
|
||||||
|
|
||||||
|
login, err := client.Login(startCtx, &mautrix.ReqLogin{
|
||||||
|
Type: mautrix.AuthTypePassword,
|
||||||
|
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: username},
|
||||||
|
Password: password,
|
||||||
|
})
|
||||||
|
|
||||||
|
client.AccessToken = login.AccessToken
|
||||||
|
|
||||||
|
syncer := client.Syncer.(*mautrix.DefaultSyncer)
|
||||||
|
syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) {
|
||||||
|
// 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")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Now running...")
|
||||||
|
|
||||||
|
syncCtx, _ := context.WithCancel(context.Background())
|
||||||
|
syncCtx, stop := signal.NotifyContext(syncCtx, os.Interrupt)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(60 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
ctx, _ := context.WithTimeout(syncCtx, 5*time.Second)
|
||||||
|
|
||||||
|
message := update(ctx)
|
||||||
|
if message != "" {
|
||||||
|
log.Printf("Sent update: %s\n", message)
|
||||||
|
_, err := client.SendText(ctx, id.RoomID(room), message)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to send event: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-syncCtx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop()
|
||||||
|
syncStopWait.Wait()
|
||||||
|
|
||||||
|
log.Println("Exited")
|
||||||
|
}
|
21
go.mod
Normal file
21
go.mod
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
module fa-fo.de/fafomo
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
require maunium.net/go/mautrix v0.21.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/rs/zerolog v1.33.0 // indirect
|
||||||
|
github.com/tidwall/gjson v1.17.3 // 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.0 // indirect
|
||||||
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||||
|
golang.org/x/net v0.29.0 // indirect
|
||||||
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
|
)
|
45
go.sum
Normal file
45
go.sum
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
|
||||||
|
github.com/tidwall/gjson v1.17.3/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.0 h1:MiSny8jgQq4XtCLAT64gDJhZVhqiDeMVIEBDFVw+M0g=
|
||||||
|
go.mau.fi/util v0.8.0/go.mod h1:1Ixb8HWoVbl3rT6nAX6nV4iMkzn7KU/KXwE0Rn5RmsQ=
|
||||||
|
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||||
|
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||||
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||||
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||||
|
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||||
|
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||||
|
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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||||
|
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
maunium.net/go/mautrix v0.21.0 h1:Z6nVu+clkJgj6ANwFYQQ1BtYeVXZPZ9lRgwuFN57gOY=
|
||||||
|
maunium.net/go/mautrix v0.21.0/go.mod h1:qm9oDhcHxF/Xby5RUuONIGpXw1SXXqLZj/GgvMxJxu0=
|
Loading…
Reference in a new issue