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) { endpoint := fmt.Sprintf( "%s/_matrix/client/v3/rooms/%s/messages?dir=b&limit=%d", m.HomeserverURL, url.PathEscape(m.RoomID), m.Limit, ) 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 messagesResp struct { Chunk []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:"chunk"` } if err := json.NewDecoder(resp.Body).Decode(&messagesResp); err != nil { return nil, err } if len(messagesResp.Chunk) == 0 { return []MatrixMessage{}, nil } out := make([]MatrixMessage, 0, len(messagesResp.Chunk)) for i := len(messagesResp.Chunk) - 1; i >= 0; i-- { ev := messagesResp.Chunk[i] switch ev.Type { case "m.room.message": 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, }) case "m.room.encrypted": ts := time.UnixMilli(ev.OriginServerTS).Local().Format("02.01.2006 15:04") out = append(out, MatrixMessage{ Sender: ev.Sender, Body: "[Verschlüsselte Nachricht]", Timestamp: ts, }) } } return out, nil }