-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(matrix): add matrix service * test(matrix): add some matrix tests * fix(matrix): simplify client using stateless APIs * fix(matrix): slim the public API and fix lint errors * test(matrix): add test case * test(matrix): add erroring test case
- Loading branch information
Showing
8 changed files
with
662 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Matrix | ||
|
||
## URL Format | ||
|
||
*matrix://__`user`__:__`password`__@__`host`__:__`port`__/[?rooms=__`!roomID1`__[,__`roomAlias2`__]][&disableTLS=yes]* | ||
|
||
## Authentication | ||
|
||
If no `user` is specified, the `password` is treated as the authentication token. This means that no matter what login | ||
flow your server uses, if you can manually retrieve a token, then Shoutrrr can use it. | ||
|
||
### Password Login Flow | ||
|
||
If a `user` and `password` is supplied, the `m.login.password` login flow is attempted if the server supports it. | ||
|
||
## Rooms | ||
|
||
If `rooms` are *not* specified, the service will send the message to all the rooms that the user has currently joined. | ||
|
||
Otherwise, the service will only send the message to the specified rooms. If the user is *not* in any of those rooms, | ||
but have been invited to it, it will automatically accept that invite. | ||
|
||
**Note**: The service will **not** join any rooms unless they are explicitly specified in `rooms`. If you need the user | ||
to join those rooms, you can send a notification with `rooms` explicitly set once. | ||
|
||
### Room Lookup | ||
|
||
Rooms specified in `rooms` will be treated as room IDs if the start with a `!` and used directly to identify rooms. If | ||
they have no such prefix (or use a *correctly escaped* `#`) they will instead be treated as aliases, and a directory | ||
lookup will be used to resolve their corresponding IDs. | ||
|
||
**Note**: Don't use unescaped `#` for the channel aliases as that will be treated as the `fragment` part of the URL. | ||
Either omit them or URL encode them, I.E. `rooms=%23alias:server` or `rooms=alias:server` | ||
|
||
### TLS | ||
|
||
If you do not have TLS enabled on the server you can disable it by providing `disableTLS=yes`. This will effectively | ||
use `http` intead of `https` for the API calls. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package matrix | ||
|
||
import ( | ||
"fmt" | ||
"github.com/containrrr/shoutrrr/pkg/format" | ||
"github.com/containrrr/shoutrrr/pkg/services/standard" | ||
t "github.com/containrrr/shoutrrr/pkg/types" | ||
"log" | ||
"net/url" | ||
) | ||
|
||
// Scheme is the identifying part of this service's configuration URL | ||
const Scheme = "matrix" | ||
|
||
// Service providing Matrix as a notification service | ||
type Service struct { | ||
standard.Standard | ||
config *Config | ||
client *client | ||
pkr format.PropKeyResolver | ||
} | ||
|
||
// Initialize loads ServiceConfig from configURL and sets logger for this Service | ||
func (s *Service) Initialize(configURL *url.URL, logger *log.Logger) error { | ||
s.SetLogger(logger) | ||
s.config = &Config{} | ||
|
||
s.pkr = format.NewPropKeyResolver(s.config) | ||
if err := s.config.setURL(&s.pkr, configURL); err != nil { | ||
return err | ||
} | ||
|
||
s.client = newClient(s.config.Host, s.config.DisableTLS, logger) | ||
if s.config.User != "" { | ||
return s.client.login(s.config.User, s.config.Password) | ||
} | ||
|
||
s.client.useToken(s.config.Password) | ||
return nil | ||
} | ||
|
||
// Send notification | ||
func (s *Service) Send(message string, params *t.Params) error { | ||
config := *s.config | ||
if err := s.pkr.UpdateConfigFromParams(&config, params); err != nil { | ||
return err | ||
} | ||
|
||
errors := s.client.sendMessage(message, s.config.Rooms) | ||
|
||
if len(errors) > 0 { | ||
for _, err := range errors { | ||
s.Logf("error sending message: %v", err) | ||
} | ||
return fmt.Errorf("%v error(s) sending message, with initial error: %v", len(errors), errors[0]) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package matrix | ||
|
||
type messageType string | ||
type flowType string | ||
type identifierType string | ||
|
||
const ( | ||
apiLogin = "/_matrix/client/r0/login" | ||
apiRoomJoin = "/_matrix/client/r0/join/%s" | ||
apiSendMessage = "/_matrix/client/r0/rooms/%s/send/m.room.message" | ||
apiJoinedRooms = "/_matrix/client/r0/joined_rooms" | ||
|
||
contentType = "application/json" | ||
|
||
accessTokenKey = "access_token" | ||
|
||
msgTypeText messageType = "m.text" | ||
flowLoginPassword flowType = "m.login.password" | ||
idTypeUser identifierType = "m.id.user" | ||
) | ||
|
||
type apiResLoginFlows struct { | ||
Flows []flow `json:"flows"` | ||
} | ||
|
||
type apiReqLogin struct { | ||
Type flowType `json:"type"` | ||
Identifier *identifier `json:"identifier"` | ||
Password string `json:"password,omitempty"` | ||
Token string `json:"token,omitempty"` | ||
} | ||
|
||
type apiResLogin struct { | ||
AccessToken string `json:"access_token"` | ||
HomeServer string `json:"home_server"` | ||
UserID string `json:"user_id"` | ||
DeviceID string `json:"device_id"` | ||
} | ||
|
||
type apiReqSend struct { | ||
MsgType messageType `json:"msgtype"` | ||
Body string `json:"body"` | ||
} | ||
|
||
type apiResRoom struct { | ||
RoomID string `json:"room_id"` | ||
} | ||
|
||
type apiResJoinedRooms struct { | ||
Rooms []string `json:"joined_rooms"` | ||
} | ||
|
||
type apiResEvent struct { | ||
EventID string `json:"event_id"` | ||
} | ||
|
||
type apiResError struct { | ||
Message string `json:"error"` | ||
Code string `json:"errcode"` | ||
} | ||
|
||
func (e *apiResError) Error() string { | ||
return e.Message | ||
} | ||
|
||
type flow struct { | ||
Type flowType `json:"type"` | ||
} | ||
|
||
type identifier struct { | ||
Type identifierType `json:"type"` | ||
User string `json:"user,omitempty"` | ||
} | ||
|
||
func newUserIdentifier(user string) (id *identifier) { | ||
return &identifier{ | ||
Type: idTypeUser, | ||
User: user, | ||
} | ||
} |
Oops, something went wrong.