| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- package matrixbot // import "miniflux.app/v2/internal/integration/matrixbot"
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
- "time"
- "miniflux.app/v2/internal/crypto"
- "miniflux.app/v2/internal/version"
- )
- const defaultClientTimeout = 10 * time.Second
- type Client struct {
- matrixBaseURL string
- }
- func NewClient(matrixBaseURL string) *Client {
- return &Client{matrixBaseURL: matrixBaseURL}
- }
- // Specs: https://spec.matrix.org/v1.8/client-server-api/#getwell-knownmatrixclient
- func (c *Client) DiscoverEndpoints() (*DiscoveryEndpointResponse, error) {
- endpointURL, err := url.JoinPath(c.matrixBaseURL, "/.well-known/matrix/client")
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to join base URL and path: %w", err)
- }
- request, err := http.NewRequest(http.MethodGet, endpointURL, nil)
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to create request: %v", err)
- }
- request.Header.Set("Accept", "application/json")
- request.Header.Set("User-Agent", "Miniflux/"+version.Version)
- httpClient := &http.Client{Timeout: defaultClientTimeout}
- response, err := httpClient.Do(request)
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to send request: %v", err)
- }
- defer response.Body.Close()
- if response.StatusCode >= 400 {
- return nil, fmt.Errorf("matrix: unexpected response from %s status code is %d", endpointURL, response.StatusCode)
- }
- var discoveryEndpointResponse DiscoveryEndpointResponse
- if err := json.NewDecoder(response.Body).Decode(&discoveryEndpointResponse); err != nil {
- return nil, fmt.Errorf("matrix: unable to decode discovery response: %w", err)
- }
- return &discoveryEndpointResponse, nil
- }
- // Specs https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3login
- func (c *Client) Login(homeServerURL, matrixUsername, matrixPassword string) (*LoginResponse, error) {
- endpointURL, err := url.JoinPath(homeServerURL, "/_matrix/client/v3/login")
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to join base URL and path: %w", err)
- }
- loginRequest := LoginRequest{
- Type: "m.login.password",
- Identifier: UserIdentifier{
- Type: "m.id.user",
- User: matrixUsername,
- },
- Password: matrixPassword,
- }
- requestBody, err := json.Marshal(loginRequest)
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to encode request body: %v", err)
- }
- request, err := http.NewRequest(http.MethodPost, endpointURL, bytes.NewReader(requestBody))
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to create request: %v", err)
- }
- request.Header.Set("Content-Type", "application/json")
- request.Header.Set("Accept", "application/json")
- request.Header.Set("User-Agent", "Miniflux/"+version.Version)
- httpClient := &http.Client{Timeout: defaultClientTimeout}
- response, err := httpClient.Do(request)
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to send request: %v", err)
- }
- defer response.Body.Close()
- if response.StatusCode >= 400 {
- return nil, fmt.Errorf("matrix: unexpected response from %s status code is %d", endpointURL, response.StatusCode)
- }
- var loginResponse LoginResponse
- if err := json.NewDecoder(response.Body).Decode(&loginResponse); err != nil {
- return nil, fmt.Errorf("matrix: unable to decode login response: %w", err)
- }
- return &loginResponse, nil
- }
- // Specs https://spec.matrix.org/v1.8/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
- func (c *Client) SendFormattedTextMessage(homeServerURL, accessToken, roomID, textMessage, formattedMessage string) (*RoomEventResponse, error) {
- txnID := crypto.GenerateRandomStringHex(10)
- endpointURL, err := url.JoinPath(homeServerURL, "/_matrix/client/v3/rooms/", roomID, "/send/m.room.message/", txnID)
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to join base URL and path: %w", err)
- }
- messageEvent := TextMessageEventRequest{
- MsgType: "m.text",
- Body: textMessage,
- Format: "org.matrix.custom.html",
- FormattedBody: formattedMessage,
- }
- requestBody, err := json.Marshal(messageEvent)
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to encode request body: %v", err)
- }
- request, err := http.NewRequest(http.MethodPut, endpointURL, bytes.NewReader(requestBody))
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to create request: %v", err)
- }
- request.Header.Set("Content-Type", "application/json")
- request.Header.Set("Accept", "application/json")
- request.Header.Set("User-Agent", "Miniflux/"+version.Version)
- request.Header.Set("Authorization", "Bearer "+accessToken)
- httpClient := &http.Client{Timeout: defaultClientTimeout}
- response, err := httpClient.Do(request)
- if err != nil {
- return nil, fmt.Errorf("matrix: unable to send request: %v", err)
- }
- defer response.Body.Close()
- if response.StatusCode >= 400 {
- return nil, fmt.Errorf("matrix: unexpected response from %s status code is %d", endpointURL, response.StatusCode)
- }
- var eventResponse RoomEventResponse
- if err := json.NewDecoder(response.Body).Decode(&eventResponse); err != nil {
- return nil, fmt.Errorf("matrix: unable to decode event response: %w", err)
- }
- return &eventResponse, nil
- }
- type HomeServerInformation struct {
- BaseURL string `json:"base_url"`
- }
- type IdentityServerInformation struct {
- BaseURL string `json:"base_url"`
- }
- type DiscoveryEndpointResponse struct {
- HomeServerInformation HomeServerInformation `json:"m.homeserver"`
- IdentityServerInformation IdentityServerInformation `json:"m.identity_server"`
- }
- type UserIdentifier struct {
- Type string `json:"type"`
- User string `json:"user"`
- }
- type LoginRequest struct {
- Type string `json:"type"`
- Identifier UserIdentifier `json:"identifier"`
- Password string `json:"password"`
- }
- type LoginResponse struct {
- UserID string `json:"user_id"`
- AccessToken string `json:"access_token"`
- DeviceID string `json:"device_id"`
- HomeServer string `json:"home_server"`
- }
- type TextMessageEventRequest struct {
- MsgType string `json:"msgtype"`
- Body string `json:"body"`
- Format string `json:"format"`
- FormattedBody string `json:"formatted_body"`
- }
- type RoomEventResponse struct {
- EventID string `json:"event_id"`
- }
|