194 lines
3.9 KiB
Go
194 lines
3.9 KiB
Go
|
// 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")
|
||
|
}
|