Matrix-Chatfenster (read-only) im Dashboard integrieren

This commit is contained in:
Kai
2026-02-17 15:42:58 +01:00
parent 53d4052446
commit 234bd7e182
9 changed files with 207 additions and 2 deletions

115
cmd/server/matrix.go Normal file
View File

@@ -0,0 +1,115 @@
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
type MatrixClient struct {
HomeserverURL string
AccessToken string
RoomID string
Limit int
httpClient *http.Client
}
func newMatrixClientFromEnv() *MatrixClient {
hs := strings.TrimRight(readEnv("MATRIX_HOMESERVER_URL", ""), "/")
token := readEnv("MATRIX_ACCESS_TOKEN", "")
roomID := readEnv("MATRIX_ROOM_ID", "")
if hs == "" || token == "" || roomID == "" {
return nil
}
limit := mustInt(readEnv("MATRIX_LIMIT", "25"), 25)
if limit < 1 {
limit = 1
}
if limit > 100 {
limit = 100
}
return &MatrixClient{
HomeserverURL: hs,
AccessToken: token,
RoomID: roomID,
Limit: limit,
httpClient: &http.Client{
Timeout: 6 * time.Second,
},
}
}
func (m *MatrixClient) FetchRecentMessages(ctx context.Context) ([]MatrixMessage, error) {
filter := fmt.Sprintf(`{"room":{"rooms":["%s"],"timeline":{"limit":%d}}}`, m.RoomID, m.Limit)
endpoint := m.HomeserverURL + "/_matrix/client/v3/sync?timeout=0&filter=" + url.QueryEscape(filter)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+m.AccessToken)
resp, err := m.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, errors.New("matrix sync failed: " + strconv.Itoa(resp.StatusCode))
}
var syncResp struct {
Rooms struct {
Join map[string]struct {
Timeline struct {
Events []struct {
Type string `json:"type"`
Sender string `json:"sender"`
OriginServerTS int64 `json:"origin_server_ts"`
Content struct {
MsgType string `json:"msgtype"`
Body string `json:"body"`
} `json:"content"`
} `json:"events"`
} `json:"timeline"`
} `json:"join"`
} `json:"rooms"`
}
if err := json.NewDecoder(resp.Body).Decode(&syncResp); err != nil {
return nil, err
}
room, ok := syncResp.Rooms.Join[m.RoomID]
if !ok {
return []MatrixMessage{}, nil
}
out := make([]MatrixMessage, 0, len(room.Timeline.Events))
for _, ev := range room.Timeline.Events {
if ev.Type != "m.room.message" {
continue
}
if ev.Content.MsgType != "m.text" && ev.Content.MsgType != "m.notice" {
continue
}
body := strings.TrimSpace(ev.Content.Body)
if body == "" {
continue
}
ts := time.UnixMilli(ev.OriginServerTS).Local().Format("02.01.2006 15:04")
out = append(out, MatrixMessage{
Sender: ev.Sender,
Body: body,
Timestamp: ts,
})
}
return out, nil
}