Compare commits
No commits in common. "c437d4388e4ad32aab6376d9e133a9b9d20e753c" and "385f5dda97709256d06b1eee7afa327637f575d0" have entirely different histories.
c437d4388e
...
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=
|
164
fafomo.go
164
fafomo.go
|
@ -16,20 +16,21 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"log"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/klog"
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var backend string
|
||||||
|
|
||||||
var folks map[string]struct{}
|
var folks map[string]struct{}
|
||||||
|
|
||||||
type JSONTop struct {
|
type JSONTop struct {
|
||||||
|
@ -50,40 +51,31 @@ func ircquote(s string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func listFmt(list []string) string {
|
func listFmt(list []string) string {
|
||||||
var listQuoted []string
|
return "[ " + strings.Join(list, " ") + " ]"
|
||||||
for _, l := range list {
|
|
||||||
listQuoted = append(listQuoted, ircquote(l))
|
|
||||||
}
|
|
||||||
return "[ " + strings.Join(listQuoted, " ") + " ]"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(ctx context.Context) (string, error) {
|
func update(ctx context.Context) string {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", flagBackendURL, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", backend, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
panic(err)
|
||||||
}
|
}
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
panic(err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
var users JSONTop
|
var users JSONTop
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
json.NewDecoder(res.Body).Decode(&users)
|
||||||
return "", fmt.Errorf("unexpected API status code %d", res.StatusCode)
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(res.Body).Decode(&users); err != nil {
|
|
||||||
return "", fmt.Errorf("could not decode API response: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var left []string
|
var left []string
|
||||||
var arrived []string
|
var arrived []string
|
||||||
var unchanged []string
|
var unchanged []string
|
||||||
|
|
||||||
newFolks := make(map[string]struct{})
|
new_folks := make(map[string]struct{})
|
||||||
for _, user := range users.Users {
|
for _, user := range users.Users {
|
||||||
newFolks[user.Login] = struct{}{}
|
new_folks[user.Login] = struct{}{}
|
||||||
if _, ok := folks[user.Login]; !ok {
|
if _, ok := folks[user.Login]; !ok {
|
||||||
arrived = append(arrived, user.Login)
|
arrived = append(arrived, user.Login)
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,137 +83,111 @@ func update(ctx context.Context) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for user, _ := range folks {
|
for user, _ := range folks {
|
||||||
if _, ok := newFolks[user]; !ok {
|
if _, ok := new_folks[user]; !ok {
|
||||||
left = append(left, user)
|
left = append(left, user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
folks = newFolks
|
folks = new_folks
|
||||||
|
|
||||||
// no change
|
// no change
|
||||||
if len(arrived) == 0 && len(left) == 0 {
|
if len(arrived) == 0 && len(left) == 0 {
|
||||||
return "", nil
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts []string
|
message := ""
|
||||||
if len(arrived) > 0 {
|
if len(arrived) > 0 {
|
||||||
parts = append(parts, fmt.Sprintf("arrived: %s", listFmt(arrived)))
|
message += "arrived: " + listFmt(arrived) + "; "
|
||||||
}
|
}
|
||||||
if len(left) > 0 {
|
if len(left) > 0 {
|
||||||
parts = append(parts, fmt.Sprintf("left: %s", listFmt(left)))
|
message += "left: " + listFmt(left) + "; "
|
||||||
}
|
}
|
||||||
if len(unchanged) > 0 {
|
if len(unchanged) > 0 {
|
||||||
adjective := "also"
|
message += "also there: " + listFmt(unchanged) + "; "
|
||||||
if len(left) > 0 && len(arrived) == 0 {
|
|
||||||
adjective = "still"
|
|
||||||
}
|
|
||||||
parts = append(parts, fmt.Sprintf("%s there: %s", adjective, listFmt(unchanged)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(parts, "; "), nil
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
flagMatrixHomeserver = "matrix.org"
|
|
||||||
flagMatrixUsername = "@fafomo:matrix.org"
|
|
||||||
flagMatrixPassword string
|
|
||||||
flagMatrixRoom string
|
|
||||||
flagBackendURL string
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.StringVar(&flagMatrixHomeserver, "matrix_homeserver", flagMatrixHomeserver, "Address of Matrix homeserver")
|
homeserver := os.Getenv("FAFOMO_HOMESERVER")
|
||||||
flag.StringVar(&flagMatrixUsername, "matrix_username", flagMatrixUsername, "Matrix login username")
|
username := os.Getenv("FAFOMO_USERNAME")
|
||||||
flag.StringVar(&flagMatrixPassword, "matrix_password", flagMatrixPassword, "Matrix login password")
|
password := os.Getenv("FAFOMO_PASSWORD")
|
||||||
flag.StringVar(&flagMatrixRoom, "matrix_room", flagMatrixRoom, "Matrix room MXID")
|
room := os.Getenv("FAFOMO_ROOM")
|
||||||
flag.StringVar(&flagBackendURL, "backend_url", flagBackendURL, "Checkinator (backend) addresss")
|
backend = os.Getenv("FAFOMO_BACKEND")
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
for _, s := range []struct {
|
startCtx, _ := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
value string
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{flagMatrixHomeserver, "matrix_homeserver"},
|
|
||||||
{flagMatrixUsername, "matrix_username"},
|
|
||||||
{flagMatrixPassword, "matrix_password"},
|
|
||||||
{flagMatrixRoom, "matrix_room"},
|
|
||||||
{flagBackendURL, "backend_url"},
|
|
||||||
} {
|
|
||||||
if s.value == "" {
|
|
||||||
klog.Exitf("-%s must be set", s.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startCtx, startCtxC := context.WithTimeout(context.Background(), 20*time.Second)
|
update(startCtx)
|
||||||
defer startCtxC()
|
|
||||||
|
|
||||||
if _, err := update(startCtx); err != nil {
|
client, err := mautrix.NewClient(homeserver, id.UserID(username), "")
|
||||||
klog.Exitf("Initial update failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := mautrix.NewClient(flagMatrixHomeserver, id.UserID(flagMatrixUsername), "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Exitf("NewClient failed: %v", err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
klog.Infof("Logging in...")
|
log.Println("Logging in...")
|
||||||
|
|
||||||
login, err := client.Login(startCtx, &mautrix.ReqLogin{
|
login, err := client.Login(startCtx, &mautrix.ReqLogin{
|
||||||
Type: mautrix.AuthTypePassword,
|
Type: mautrix.AuthTypePassword,
|
||||||
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: flagMatrixUsername},
|
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: username},
|
||||||
Password: flagMatrixPassword,
|
Password: password,
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
klog.Exitf("Failed to log in: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client.AccessToken = login.AccessToken
|
client.AccessToken = login.AccessToken
|
||||||
|
|
||||||
syncer := client.Syncer.(*mautrix.DefaultSyncer)
|
syncer := client.Syncer.(*mautrix.DefaultSyncer)
|
||||||
syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) {
|
syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) {
|
||||||
klog.V(1).Infof("Sender: %s, type: %s, id: %s, body: %q", evt.Sender, evt.Type, evt.ID, evt.Content.AsMessage().Body)
|
// 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")
|
||||||
})
|
})
|
||||||
|
|
||||||
klog.Infof("Now running...")
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
ctx, ctxC := signal.NotifyContext(context.Background(), os.Interrupt)
|
log.Println("Now running...")
|
||||||
defer ctxC()
|
|
||||||
|
syncCtx, _ := context.WithCancel(context.Background())
|
||||||
|
syncCtx, stop := signal.NotifyContext(syncCtx, os.Interrupt)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
var syncStopWait sync.WaitGroup
|
||||||
|
syncStopWait.Add(1)
|
||||||
|
|
||||||
// Run Matrix sync process in the background.
|
|
||||||
syncDone := make(chan struct{})
|
|
||||||
go func() {
|
go func() {
|
||||||
err = client.SyncWithContext(ctx)
|
err = client.SyncWithContext(syncCtx)
|
||||||
defer close(syncDone)
|
defer syncStopWait.Done()
|
||||||
if err != nil && !errors.Is(err, ctx.Err()) {
|
if err != nil && !errors.Is(err, context.Canceled) {
|
||||||
klog.Exitf("Sync failed: %v", err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Update space members every minute.
|
|
||||||
ticker := time.NewTicker(60 * time.Second)
|
ticker := time.NewTicker(60 * time.Second)
|
||||||
process:
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
ctx, _ := context.WithTimeout(ctx, 5*time.Second)
|
ctx, _ := context.WithTimeout(syncCtx, 5*time.Second)
|
||||||
|
|
||||||
message, err := update(ctx)
|
message := update(ctx)
|
||||||
if err != nil {
|
if message != "" {
|
||||||
klog.Errorf("Update failed: %v", err)
|
log.Printf("Sent update: %s\n", message)
|
||||||
} else if message != "" {
|
_, err := client.SendText(ctx, id.RoomID(room), message)
|
||||||
klog.Infof("Sent update: %s\n", message)
|
|
||||||
_, err := client.SendText(ctx, id.RoomID(flagMatrixRoom), message)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Failed to send event: %v\n", err)
|
log.Printf("Failed to send event: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-syncCtx.Done():
|
||||||
break process
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
klog.Infof("Waiting for graceful sync before exiting...")
|
stop()
|
||||||
<-syncDone
|
syncStopWait.Wait()
|
||||||
klog.Infof("Done.")
|
|
||||||
|
log.Println("Exited")
|
||||||
}
|
}
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -2,10 +2,7 @@ module fa-fo.de/fafomo
|
||||||
|
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require maunium.net/go/mautrix v0.21.0
|
||||||
k8s.io/klog v1.0.0
|
|
||||||
maunium.net/go/mautrix v0.21.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -3,7 +3,6 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
@ -42,7 +41,5 @@ 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=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
|
||||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
|
||||||
maunium.net/go/mautrix v0.21.0 h1:Z6nVu+clkJgj6ANwFYQQ1BtYeVXZPZ9lRgwuFN57gOY=
|
maunium.net/go/mautrix v0.21.0 h1:Z6nVu+clkJgj6ANwFYQQ1BtYeVXZPZ9lRgwuFN57gOY=
|
||||||
maunium.net/go/mautrix v0.21.0/go.mod h1:qm9oDhcHxF/Xby5RUuONIGpXw1SXXqLZj/GgvMxJxu0=
|
maunium.net/go/mautrix v0.21.0/go.mod h1:qm9oDhcHxF/Xby5RUuONIGpXw1SXXqLZj/GgvMxJxu0=
|
||||||
|
|
Loading…
Reference in a new issue