Matrix-Chatfenster (read-only) im Dashboard integrieren
This commit is contained in:
@@ -69,6 +69,16 @@ func (a *App) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
Calendar: buildCalendar(plans, crops, products, stepMap, customTasks, doneMap, (settings.CurrentCycle*12)+(settings.CurrentMonth-1), settings.CurrentDay, settings.DaysPerMonth, 14),
|
||||
PlanningCount: len(plans),
|
||||
}
|
||||
if a.matrix != nil {
|
||||
data.MatrixEnabled = true
|
||||
data.MatrixRoomID = a.matrix.RoomID
|
||||
msgs, err := a.matrix.FetchRecentMessages(r.Context())
|
||||
if err != nil {
|
||||
data.MatrixError = err.Error()
|
||||
} else {
|
||||
data.MatrixMessages = msgs
|
||||
}
|
||||
}
|
||||
data.TodayGroups = groupTasksByField(data.TodayTasks)
|
||||
a.renderTemplate(w, "templates/dashboard.html", data)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ var monthNames = []string{
|
||||
}
|
||||
|
||||
type App struct {
|
||||
db *sql.DB
|
||||
db *sql.DB
|
||||
matrix *MatrixClient
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -38,7 +39,10 @@ func main() {
|
||||
log.Fatalf("schema setup failed: %v", err)
|
||||
}
|
||||
|
||||
app := &App{db: db}
|
||||
app := &App{
|
||||
db: db,
|
||||
matrix: newMatrixClientFromEnv(),
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/", app.handleDashboard)
|
||||
|
||||
115
cmd/server/matrix.go
Normal file
115
cmd/server/matrix.go
Normal 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
|
||||
}
|
||||
@@ -149,6 +149,10 @@ type DashboardPage struct {
|
||||
TodayGroups []FieldTaskGroup
|
||||
Calendar []CalendarMonth
|
||||
PlanningCount int
|
||||
MatrixEnabled bool
|
||||
MatrixRoomID string
|
||||
MatrixError string
|
||||
MatrixMessages []MatrixMessage
|
||||
}
|
||||
|
||||
type FieldsPage struct {
|
||||
@@ -181,3 +185,9 @@ type GeneralPage struct {
|
||||
Settings Settings
|
||||
Months []MonthOption
|
||||
}
|
||||
|
||||
type MatrixMessage struct {
|
||||
Sender string
|
||||
Body string
|
||||
Timestamp string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user