mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
Overhaul event content representation
This commit is contained in:
parent
104cc57eef
commit
f4fc99cddb
23 changed files with 1279 additions and 496 deletions
23
client.go
23
client.go
|
|
@ -358,9 +358,9 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
|
|||
}
|
||||
|
||||
// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
|
||||
func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) {
|
||||
func (cli *Client) CreateFilter(filter *Filter) (resp *RespCreateFilter, err error) {
|
||||
urlPath := cli.BuildURL("user", cli.UserID, "filter")
|
||||
_, err = cli.MakeRequest("POST", urlPath, &filter, &resp)
|
||||
_, err = cli.MakeRequest("POST", urlPath, filter, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -620,7 +620,7 @@ func (cli *Client) SendMassagedStateEvent(roomID id.RoomID, eventType event.Type
|
|||
// SendText sends an m.room.message event into the given room with a msgtype of m.text
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text
|
||||
func (cli *Client) SendText(roomID id.RoomID, text string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.Content{
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.MessageEventContent{
|
||||
MsgType: event.MsgText,
|
||||
Body: text,
|
||||
})
|
||||
|
|
@ -629,7 +629,7 @@ func (cli *Client) SendText(roomID id.RoomID, text string) (*RespSendEvent, erro
|
|||
// SendImage sends an m.room.message event into the given room with a msgtype of m.image
|
||||
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
|
||||
func (cli *Client) SendImage(roomID id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.Content{
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.MessageEventContent{
|
||||
MsgType: event.MsgImage,
|
||||
Body: body,
|
||||
URL: url.CUString(),
|
||||
|
|
@ -639,7 +639,7 @@ func (cli *Client) SendImage(roomID id.RoomID, body string, url id.ContentURI) (
|
|||
// SendVideo sends an m.room.message event into the given room with a msgtype of m.video
|
||||
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||
func (cli *Client) SendVideo(roomID id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.Content{
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.MessageEventContent{
|
||||
MsgType: event.MsgVideo,
|
||||
Body: body,
|
||||
URL: url.CUString(),
|
||||
|
|
@ -649,15 +649,15 @@ func (cli *Client) SendVideo(roomID id.RoomID, body string, url id.ContentURI) (
|
|||
// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice
|
||||
func (cli *Client) SendNotice(roomID id.RoomID, text string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.Content{
|
||||
return cli.SendMessageEvent(roomID, event.EventMessage, event.MessageEventContent{
|
||||
MsgType: event.MsgNotice,
|
||||
Body: text,
|
||||
})
|
||||
}
|
||||
|
||||
func (cli *Client) SendReaction(roomID id.RoomID, eventID id.EventID, reaction string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, event.EventReaction, event.Content{
|
||||
RelatesTo: &event.RelatesTo{
|
||||
return cli.SendMessageEvent(roomID, event.EventReaction, event.ReactionEventContent{
|
||||
RelatesTo: event.RelatesTo{
|
||||
EventID: eventID,
|
||||
Type: event.RelAnnotation,
|
||||
Key: reaction,
|
||||
|
|
@ -882,10 +882,15 @@ func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
|
|||
// pagination query parameters to paginate history in the room.
|
||||
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
|
||||
func (cli *Client) Messages(roomID id.RoomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) {
|
||||
filter := cli.Syncer.GetFilterJSON(cli.UserID)
|
||||
filterJSON, err := json.Marshal(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := map[string]string{
|
||||
"from": from,
|
||||
"dir": string(dir),
|
||||
"filter": string(cli.Syncer.GetFilterJSON(cli.UserID)),
|
||||
"filter": string(filterJSON),
|
||||
}
|
||||
if to != "" {
|
||||
query["to"] = to
|
||||
|
|
|
|||
51
event/accountdata.go
Normal file
51
event/accountdata.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// TagEventContent represents the content of a m.tag room account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-tag
|
||||
type TagEventContent struct {
|
||||
Tags Tags `json:"tags"`
|
||||
}
|
||||
|
||||
type Tags map[string]Tag
|
||||
|
||||
type Tag struct {
|
||||
Order json.Number `json:"order"`
|
||||
}
|
||||
|
||||
// DirectChatsEventContent represents the content of a m.direct account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-direct
|
||||
type DirectChatsEventContent map[id.UserID][]id.RoomID
|
||||
|
||||
// PushRulesEventContent represents the content of a m.push_rules account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-push-rules
|
||||
//type PushRulesEventContent struct {
|
||||
// Global *pushrules.PushRuleset `json:"global"`
|
||||
//}
|
||||
|
||||
// FullyReadEventContent represents the content of a m.fully_read account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-fully-read
|
||||
type FullyReadEventContent struct {
|
||||
EventID id.EventID `json:"event_id"`
|
||||
}
|
||||
|
||||
// IgnoredUserListEventContent represents the content of a m.ignored_user_list account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-ignored-user-list
|
||||
type IgnoredUserListEventContent struct {
|
||||
IgnoredUsers map[id.UserID]IgnoredUser `json:"ignored_users"`
|
||||
}
|
||||
|
||||
type IgnoredUser struct {
|
||||
// This is an empty object
|
||||
}
|
||||
302
event/content.go
Normal file
302
event/content.go
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
)
|
||||
|
||||
// TypeMap is a mapping from event type to the content struct type.
|
||||
// This is used by Content.ParseRaw() for creating the correct type of struct.
|
||||
var TypeMap = map[Type]reflect.Type{
|
||||
StateMember: reflect.TypeOf(MemberEventContent{}),
|
||||
StatePowerLevels: reflect.TypeOf(PowerLevelsEventContent{}),
|
||||
StateCanonicalAlias: reflect.TypeOf(CanonicalAliasEventContent{}),
|
||||
StateRoomName: reflect.TypeOf(RoomNameEventContent{}),
|
||||
StateRoomAvatar: reflect.TypeOf(RoomAvatarEventContent{}),
|
||||
StateTopic: reflect.TypeOf(TopicEventContent{}),
|
||||
StateTombstone: reflect.TypeOf(TombstoneEventContent{}),
|
||||
StateCreate: reflect.TypeOf(CreateEventContent{}),
|
||||
StateJoinRules: reflect.TypeOf(JoinRulesEventContent{}),
|
||||
StateHistoryVisibility: reflect.TypeOf(HistoryVisibilityEventContent{}),
|
||||
StateGuestAccess: reflect.TypeOf(GuestAccessEventContent{}),
|
||||
StatePinnedEvents: reflect.TypeOf(PinnedEventsEventContent{}),
|
||||
|
||||
EventMessage: reflect.TypeOf(MessageEventContent{}),
|
||||
EventSticker: reflect.TypeOf(MessageEventContent{}),
|
||||
EventEncrypted: reflect.TypeOf(EncryptedEventContent{}),
|
||||
EventRedaction: reflect.TypeOf(RedactionEventContent{}),
|
||||
EventReaction: reflect.TypeOf(ReactionEventContent{}),
|
||||
|
||||
AccountDataRoomTags: reflect.TypeOf(TagEventContent{}),
|
||||
AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}),
|
||||
AccountDataFullyRead: reflect.TypeOf(FullyReadEventContent{}),
|
||||
AccountDataIgnoredUserList: reflect.TypeOf(IgnoredUserListEventContent{}),
|
||||
//AccountDataPushRules: reflect.TypeOf(PushRulesEventContent{}),
|
||||
|
||||
EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}),
|
||||
EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}),
|
||||
EphemeralEventPresence: reflect.TypeOf(PresenceEventContent{}),
|
||||
|
||||
ToDeviceRoomKey: reflect.TypeOf(RoomKeyEventContent{}),
|
||||
ToDeviceForwardedRoomKey: reflect.TypeOf(ForwardedRoomKeyEventContent{}),
|
||||
ToDeviceRoomKeyRequest: reflect.TypeOf(RoomKeyRequestEventContent{}),
|
||||
}
|
||||
|
||||
// Content stores the content of a Matrix event.
|
||||
//
|
||||
// By default, the content is only parsed into a map[string]interface{}. However, you can call ParseRaw with the
|
||||
// correct event type to parse the content into a nicer struct, which you can then access from Parsed or via the
|
||||
// helper functions.
|
||||
type Content struct {
|
||||
VeryRaw json.RawMessage
|
||||
Raw map[string]interface{}
|
||||
Parsed interface{}
|
||||
}
|
||||
|
||||
func (content *Content) UnmarshalJSON(data []byte) error {
|
||||
content.VeryRaw = data
|
||||
err := json.Unmarshal(data, &content.Raw)
|
||||
return err
|
||||
}
|
||||
|
||||
func (content *Content) MarshalJSON() ([]byte, error) {
|
||||
if content.Raw == nil {
|
||||
if content.Parsed == nil {
|
||||
if content.VeryRaw == nil {
|
||||
return nil, errors.New("no content")
|
||||
}
|
||||
return content.VeryRaw, nil
|
||||
}
|
||||
return json.Marshal(content.Parsed)
|
||||
} else if content.Parsed != nil {
|
||||
content.MergeParsedToRaw()
|
||||
}
|
||||
return json.Marshal(content.Raw)
|
||||
}
|
||||
|
||||
func mergeMaps(into, from map[string]interface{}) {
|
||||
for key, newValue := range from {
|
||||
existingValue, ok := into[key]
|
||||
if !ok {
|
||||
into[key] = newValue
|
||||
continue
|
||||
}
|
||||
existingValueMap, okEx := existingValue.(map[string]interface{})
|
||||
newValueMap, okNew := newValue.(map[string]interface{})
|
||||
if okEx && okNew {
|
||||
mergeMaps(existingValueMap, newValueMap)
|
||||
} else {
|
||||
into[key] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (content *Content) ParseRaw(evtType Type) error {
|
||||
structType, ok := TypeMap[evtType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported content type %s", evtType.String())
|
||||
}
|
||||
content.Parsed = reflect.New(structType).Interface()
|
||||
return json.Unmarshal(content.VeryRaw, &content.Parsed)
|
||||
}
|
||||
|
||||
func (content *Content) MergeParsedToRaw() {
|
||||
s := structs.New(content.Parsed)
|
||||
s.TagName = "json"
|
||||
mergeMaps(content.Raw, s.Map())
|
||||
}
|
||||
|
||||
// Helper cast functions below
|
||||
|
||||
func (content *Content) AsMember() *MemberEventContent {
|
||||
casted, ok := content.Parsed.(*MemberEventContent)
|
||||
if !ok {
|
||||
return &MemberEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsPowerLevels() *PowerLevelsEventContent {
|
||||
casted, ok := content.Parsed.(*PowerLevelsEventContent)
|
||||
if !ok {
|
||||
return &PowerLevelsEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsCanonicalAlias() *CanonicalAliasEventContent {
|
||||
casted, ok := content.Parsed.(*CanonicalAliasEventContent)
|
||||
if !ok {
|
||||
return &CanonicalAliasEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsRoomName() *RoomNameEventContent {
|
||||
casted, ok := content.Parsed.(*RoomNameEventContent)
|
||||
if !ok {
|
||||
return &RoomNameEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsRoomAvatar() *RoomAvatarEventContent {
|
||||
casted, ok := content.Parsed.(*RoomAvatarEventContent)
|
||||
if !ok {
|
||||
return &RoomAvatarEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsTopic() *TopicEventContent {
|
||||
casted, ok := content.Parsed.(*TopicEventContent)
|
||||
if !ok {
|
||||
return &TopicEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsTombstone() *TombstoneEventContent {
|
||||
casted, ok := content.Parsed.(*TombstoneEventContent)
|
||||
if !ok {
|
||||
return &TombstoneEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsCreate() *CreateEventContent {
|
||||
casted, ok := content.Parsed.(*CreateEventContent)
|
||||
if !ok {
|
||||
return &CreateEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsJoinRules() *JoinRulesEventContent {
|
||||
casted, ok := content.Parsed.(*JoinRulesEventContent)
|
||||
if !ok {
|
||||
return &JoinRulesEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsHistoryVisibility() *HistoryVisibilityEventContent {
|
||||
casted, ok := content.Parsed.(*HistoryVisibilityEventContent)
|
||||
if !ok {
|
||||
return &HistoryVisibilityEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsGuestAccess() *GuestAccessEventContent {
|
||||
casted, ok := content.Parsed.(*GuestAccessEventContent)
|
||||
if !ok {
|
||||
return &GuestAccessEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsPinnedEvents() *PinnedEventsEventContent {
|
||||
casted, ok := content.Parsed.(*PinnedEventsEventContent)
|
||||
if !ok {
|
||||
return &PinnedEventsEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsMessage() *MessageEventContent {
|
||||
casted, ok := content.Parsed.(*MessageEventContent)
|
||||
if !ok {
|
||||
return &MessageEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsEncrypted() *EncryptedEventContent {
|
||||
casted, ok := content.Parsed.(*EncryptedEventContent)
|
||||
if !ok {
|
||||
return &EncryptedEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsRedaction() *RedactionEventContent {
|
||||
casted, ok := content.Parsed.(*RedactionEventContent)
|
||||
if !ok {
|
||||
return &RedactionEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsReaction() *ReactionEventContent {
|
||||
casted, ok := content.Parsed.(*ReactionEventContent)
|
||||
if !ok {
|
||||
return &ReactionEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsTag() *TagEventContent {
|
||||
casted, ok := content.Parsed.(*TagEventContent)
|
||||
if !ok {
|
||||
return &TagEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsDirectChats() *DirectChatsEventContent {
|
||||
casted, ok := content.Parsed.(*DirectChatsEventContent)
|
||||
if !ok {
|
||||
return &DirectChatsEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsFullyRead() *FullyReadEventContent {
|
||||
casted, ok := content.Parsed.(*FullyReadEventContent)
|
||||
if !ok {
|
||||
return &FullyReadEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsIgnoredUserList() *IgnoredUserListEventContent {
|
||||
casted, ok := content.Parsed.(*IgnoredUserListEventContent)
|
||||
if !ok {
|
||||
return &IgnoredUserListEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsTyping() *TypingEventContent {
|
||||
casted, ok := content.Parsed.(*TypingEventContent)
|
||||
if !ok {
|
||||
return &TypingEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsReceipt() *ReceiptEventContent {
|
||||
casted, ok := content.Parsed.(*ReceiptEventContent)
|
||||
if !ok {
|
||||
return &ReceiptEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsPresence() *PresenceEventContent {
|
||||
casted, ok := content.Parsed.(*PresenceEventContent)
|
||||
if !ok {
|
||||
return &PresenceEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsRoomKey() *RoomKeyEventContent {
|
||||
casted, ok := content.Parsed.(*RoomKeyEventContent)
|
||||
if !ok {
|
||||
return &RoomKeyEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsForwardedRoomKey() *ForwardedRoomKeyEventContent {
|
||||
casted, ok := content.Parsed.(*ForwardedRoomKeyEventContent)
|
||||
if !ok {
|
||||
return &ForwardedRoomKeyEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
func (content *Content) AsRoomKeyRequest() *RoomKeyRequestEventContent {
|
||||
casted, ok := content.Parsed.(*RoomKeyRequestEventContent)
|
||||
if !ok {
|
||||
return &RoomKeyRequestEventContent{}
|
||||
}
|
||||
return casted
|
||||
}
|
||||
108
event/encryption.go
Normal file
108
event/encryption.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// Algorithm is a Matrix message encryption algorithm.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#messaging-algorithm-names
|
||||
type Algorithm string
|
||||
|
||||
const (
|
||||
AlgorithmOlmV1 Algorithm = "m.olm.v1.curve25519-aes-sha2"
|
||||
AlgorithmMegolmV1 Algorithm = "m.megolm.v1.aes-sha2"
|
||||
)
|
||||
|
||||
var unpaddedBase64 = base64.StdEncoding.WithPadding(base64.NoPadding)
|
||||
|
||||
// UnpaddedBase64 is a byte array that implements the JSON Marshaler and Unmarshaler interfaces
|
||||
// to encode and decode the byte array as unpadded base64.
|
||||
type UnpaddedBase64 []byte
|
||||
|
||||
func (ub64 *UnpaddedBase64) UnmarshalJSON(data []byte) error {
|
||||
if data[0] != '"' || data[len(data)-1] != '"' {
|
||||
return errors.New("failed to decode data into bytes: input doesn't look like a JSON string")
|
||||
}
|
||||
*ub64 = make([]byte, unpaddedBase64.DecodedLen(len(data)-2))
|
||||
_, err := unpaddedBase64.Decode(*ub64, data[1:len(data)-1])
|
||||
return err
|
||||
}
|
||||
|
||||
func (ub64 *UnpaddedBase64) MarshalJSON() ([]byte, error) {
|
||||
data := make([]byte, unpaddedBase64.EncodedLen(len(*ub64))+2)
|
||||
data[0] = '"'
|
||||
data[len(data)-1] = '"'
|
||||
unpaddedBase64.Encode(data[1:len(data)-1], *ub64)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// EncryptionEventContent represents the content of a m.room.encryption state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-encryption
|
||||
type EncryptionEventContent struct {
|
||||
// The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'.
|
||||
Algorithm Algorithm `json:"algorithm"`
|
||||
// How long the session should be used before changing it. 604800000 (a week) is the recommended default.
|
||||
RotationPeriodMillis int64 `json:"rotation_period_ms,omitempty"`
|
||||
// How many messages should be sent before changing the session. 100 is the recommended default.
|
||||
RotationPeriodMessages int `json:"rotation_period_messages,omitempty"`
|
||||
}
|
||||
|
||||
// EncryptedEventContent represents the content of a m.room.encrypted message event.
|
||||
// This struct only supports the m.megolm.v1.aes-sha2 algorithm. The legacy m.olm.v1 algorithm is not supported.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-encrypted
|
||||
type EncryptedEventContent struct {
|
||||
Algorithm Algorithm `json:"algorithm"`
|
||||
SenderKey string `json:"sender_key"`
|
||||
DeviceID id.DeviceID `json:"device_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
Ciphertext UnpaddedBase64 `json:"ciphertext"`
|
||||
}
|
||||
|
||||
// RoomKeyEventContent represents the content of a m.room_key to_device event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-key
|
||||
type RoomKeyEventContent struct {
|
||||
Algorithm Algorithm `json:"algorithm"`
|
||||
RoomID id.RoomID `json:"room_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
SessionKey string `json:"session_key"`
|
||||
}
|
||||
|
||||
// ForwardedRoomKeyEventContent represents the content of a m.forwarded_room_key to_device event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-forwarded-room-key
|
||||
type ForwardedRoomKeyEventContent struct {
|
||||
RoomKeyEventContent
|
||||
SenderClaimedKey string `json:"sender_claimed_ed25519_key"`
|
||||
ForwardingKeyChain []string `json:"forwarding_curve25519_key_chain"`
|
||||
}
|
||||
|
||||
type KeyRequestAction string
|
||||
|
||||
const (
|
||||
KeyRequestActionRequest = "request"
|
||||
KeyRequestActionCancel = "request_cancellation"
|
||||
)
|
||||
|
||||
// RoomKeyRequestEventContent represents the content of a m.room_key_request to_device event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-key-request
|
||||
type RoomKeyRequestEventContent struct {
|
||||
Body RequestedKeyInfo `json:"body"`
|
||||
Action KeyRequestAction `json:"action"`
|
||||
RequestingDeviceID id.DeviceID `json:"requesting_device_id"`
|
||||
RequestID string `json:"request_id"`
|
||||
}
|
||||
|
||||
type RequestedKeyInfo struct {
|
||||
Algorithm Algorithm `json:"algorithm"`
|
||||
RoomID id.RoomID `json:"room_id"`
|
||||
SenderKey string `json:"sender_key"`
|
||||
SessionID string `json:"session_id"`
|
||||
}
|
||||
48
event/ephemeral.go
Normal file
48
event/ephemeral.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// TagEventContent represents the content of a m.typing ephemeral event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-typing
|
||||
type TypingEventContent struct {
|
||||
UserIDs []id.UserID `json:"user_ids"`
|
||||
}
|
||||
|
||||
// ReceiptEventContent represents the content of a m.receipt ephemeral event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-receipt
|
||||
type ReceiptEventContent map[id.EventID]Receipts
|
||||
|
||||
type Receipts struct {
|
||||
Read map[id.UserID]ReadReceipt `json:"m.read"`
|
||||
}
|
||||
|
||||
type ReadReceipt struct {
|
||||
Timestamp int64 `json:"ts"`
|
||||
}
|
||||
|
||||
type Presence string
|
||||
|
||||
const (
|
||||
PresenceOnline = "online"
|
||||
PresenceOffline = "offline"
|
||||
PresenceUnavailable = "unavailable"
|
||||
)
|
||||
|
||||
// PresenceEventContent represents the content of a m.presence ephemeral event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-presence
|
||||
type PresenceEventContent struct {
|
||||
Presence Presence `json:"presence"`
|
||||
Displayname string `json:"displayname,omitempty"`
|
||||
AvatarURL id.ContentURIString `json:"avatar_url,omitempty"`
|
||||
LastActiveAgo int64 `json:"last_active_ago,omitempty"`
|
||||
CurrentlyActive bool `json:"currently_active,omitempty"`
|
||||
StatusMessage string `json:"status_msg,omitempty"`
|
||||
}
|
||||
371
event/events.go
371
event/events.go
|
|
@ -7,35 +7,9 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/attachment"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type MessageType string
|
||||
|
||||
// Msgtypes
|
||||
const (
|
||||
MsgText MessageType = "m.text"
|
||||
MsgEmote = "m.emote"
|
||||
MsgNotice = "m.notice"
|
||||
MsgImage = "m.image"
|
||||
MsgLocation = "m.location"
|
||||
MsgVideo = "m.video"
|
||||
MsgAudio = "m.audio"
|
||||
MsgFile = "m.file"
|
||||
)
|
||||
|
||||
type Format string
|
||||
|
||||
// Message formats
|
||||
const (
|
||||
FormatHTML Format = "org.matrix.custom.html"
|
||||
)
|
||||
|
||||
// Event represents a single Matrix event.
|
||||
type Event struct {
|
||||
StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
|
||||
|
|
@ -72,348 +46,3 @@ type Unsigned struct {
|
|||
RedactedBecause *Event `json:"redacted_because,omitempty"`
|
||||
InviteRoomState []StrippedState `json:"invite_room_state"`
|
||||
}
|
||||
|
||||
type EncryptedFileInfo struct {
|
||||
attachment.EncryptedFile
|
||||
URL id.ContentURIString
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
VeryRaw json.RawMessage `json:"-"`
|
||||
Raw map[string]interface{} `json:"-"`
|
||||
|
||||
// m.room.message
|
||||
MsgType MessageType `json:"msgtype,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Format Format `json:"format,omitempty"`
|
||||
FormattedBody string `json:"formatted_body,omitempty"`
|
||||
// media url and info
|
||||
URL id.ContentURIString `json:"url,omitempty"`
|
||||
Info *FileInfo `json:"info,omitempty"`
|
||||
File *EncryptedFileInfo `json:"file,omitempty"`
|
||||
// edits and relations
|
||||
NewContent *Content `json:"m.new_content,omitempty"`
|
||||
RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
|
||||
|
||||
*PowerLevels
|
||||
|
||||
// m.room.member state
|
||||
Member
|
||||
// Membership key for easy access in m.room.member events
|
||||
Membership Membership `json:"membership,omitempty"`
|
||||
|
||||
// Encryption stuff
|
||||
Algorithm string `json:"algorithm,omitempty"`
|
||||
// These are for m.room.encrypted
|
||||
SenderKey string `json:"sender_key,omitempty"`
|
||||
DeviceID id.DeviceID `json:"device_id,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
Ciphertext string `json:"ciphertext,omitempty"`
|
||||
// These are for m.room_key and m.forwarded_room_key
|
||||
RoomID id.RoomID `json:"room_id"`
|
||||
SessionKey string `json:"session_key"`
|
||||
// These are only for m.forwarded_room_key
|
||||
SenderClaimedKey string `json:"sender_claimed_ed25519_key"`
|
||||
ForwardingKeyChain []string `json:"forwarding_curve25519_key_chain"`
|
||||
|
||||
// m.room.canonical_alias state
|
||||
Alias id.RoomAlias `json:"alias,omitempty"`
|
||||
AltAliases []string `json:"alt_aliases,omitempty"`
|
||||
// m.room.name state
|
||||
Name string `json:"name,omitempty"`
|
||||
// m.room.topic state
|
||||
Topic string `json:"topic,omitempty"`
|
||||
// m.room.tombstone state
|
||||
ReplacementRoom id.RoomID `json:"replacement_room,omitempty"`
|
||||
// m.tag account data
|
||||
RoomTags Tags `json:"tags,omitempty"`
|
||||
// m.typing ephemeral
|
||||
TypingUserIDs []id.UserID `json:"user_ids,omitempty"`
|
||||
}
|
||||
|
||||
type serializableContent Content
|
||||
|
||||
var DisableFancyEventParsing = false
|
||||
|
||||
func (content *Content) UnmarshalJSON(data []byte) error {
|
||||
content.VeryRaw = data
|
||||
if err := json.Unmarshal(data, &content.Raw); err != nil || DisableFancyEventParsing {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, (*serializableContent)(content))
|
||||
}
|
||||
|
||||
func (content *Content) MarshalJSON() ([]byte, error) {
|
||||
if DisableFancyEventParsing {
|
||||
return json.Marshal(content.Raw)
|
||||
}
|
||||
return json.Marshal((*serializableContent)(content))
|
||||
}
|
||||
|
||||
func (content *Content) GetRelatesTo() *RelatesTo {
|
||||
if content.RelatesTo == nil {
|
||||
content.RelatesTo = &RelatesTo{}
|
||||
}
|
||||
return content.RelatesTo
|
||||
}
|
||||
|
||||
func (content *Content) GetPowerLevels() *PowerLevels {
|
||||
if content.PowerLevels == nil {
|
||||
content.PowerLevels = &PowerLevels{}
|
||||
}
|
||||
return content.PowerLevels
|
||||
}
|
||||
|
||||
func (content *Content) GetInfo() *FileInfo {
|
||||
if content.Info == nil {
|
||||
content.Info = &FileInfo{}
|
||||
}
|
||||
return content.Info
|
||||
}
|
||||
|
||||
type Tags map[string]Tag
|
||||
|
||||
type Tag struct {
|
||||
Order json.Number `json:"order,omitempty"`
|
||||
}
|
||||
|
||||
// Membership is an enum specifying the membership state of a room member.
|
||||
type Membership string
|
||||
|
||||
func (ms Membership) IsInviteOrJoin() bool {
|
||||
return ms == MembershipJoin || ms == MembershipInvite
|
||||
}
|
||||
|
||||
func (ms Membership) IsLeaveOrBan() bool {
|
||||
return ms == MembershipLeave || ms == MembershipBan
|
||||
}
|
||||
|
||||
// The allowed membership states as specified in spec section 10.5.5.
|
||||
const (
|
||||
MembershipJoin Membership = "join"
|
||||
MembershipLeave Membership = "leave"
|
||||
MembershipInvite Membership = "invite"
|
||||
MembershipBan Membership = "ban"
|
||||
MembershipKnock Membership = "knock"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
Membership Membership `json:"membership,omitempty"`
|
||||
AvatarURL id.ContentURIString `json:"avatar_url,omitempty"`
|
||||
Displayname string `json:"displayname,omitempty"`
|
||||
IsDirect bool `json:"is_direct,omitempty"`
|
||||
ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type ThirdPartyInvite struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
Signed struct {
|
||||
Token string `json:"token"`
|
||||
Signatures json.RawMessage `json:"signatures"`
|
||||
MXID string `json:"mxid"`
|
||||
}
|
||||
}
|
||||
|
||||
type PowerLevels struct {
|
||||
usersLock sync.RWMutex `json:"-"`
|
||||
Users map[id.UserID]int `json:"users"`
|
||||
UsersDefault int `json:"users_default"`
|
||||
|
||||
eventsLock sync.RWMutex `json:"-"`
|
||||
Events map[string]int `json:"events"`
|
||||
EventsDefault int `json:"events_default"`
|
||||
|
||||
StateDefaultPtr *int `json:"state_default,omitempty"`
|
||||
|
||||
InvitePtr *int `json:"invite,omitempty"`
|
||||
KickPtr *int `json:"kick,omitempty"`
|
||||
BanPtr *int `json:"ban,omitempty"`
|
||||
RedactPtr *int `json:"redact,omitempty"`
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) Invite() int {
|
||||
if pl.InvitePtr != nil {
|
||||
return *pl.InvitePtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) Kick() int {
|
||||
if pl.KickPtr != nil {
|
||||
return *pl.KickPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) Ban() int {
|
||||
if pl.BanPtr != nil {
|
||||
return *pl.BanPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) Redact() int {
|
||||
if pl.RedactPtr != nil {
|
||||
return *pl.RedactPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) StateDefault() int {
|
||||
if pl.StateDefaultPtr != nil {
|
||||
return *pl.StateDefaultPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) GetUserLevel(userID id.UserID) int {
|
||||
pl.usersLock.RLock()
|
||||
defer pl.usersLock.RUnlock()
|
||||
level, ok := pl.Users[userID]
|
||||
if !ok {
|
||||
return pl.UsersDefault
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) SetUserLevel(userID id.UserID, level int) {
|
||||
pl.usersLock.Lock()
|
||||
defer pl.usersLock.Unlock()
|
||||
if level == pl.UsersDefault {
|
||||
delete(pl.Users, userID)
|
||||
} else {
|
||||
pl.Users[userID] = level
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) EnsureUserLevel(userID id.UserID, level int) bool {
|
||||
existingLevel := pl.GetUserLevel(userID)
|
||||
if existingLevel != level {
|
||||
pl.SetUserLevel(userID, level)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) GetEventLevel(eventType Type) int {
|
||||
pl.eventsLock.RLock()
|
||||
defer pl.eventsLock.RUnlock()
|
||||
level, ok := pl.Events[eventType.String()]
|
||||
if !ok {
|
||||
if eventType.IsState() {
|
||||
return pl.StateDefault()
|
||||
}
|
||||
return pl.EventsDefault
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) SetEventLevel(eventType Type, level int) {
|
||||
pl.eventsLock.Lock()
|
||||
defer pl.eventsLock.Unlock()
|
||||
if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
|
||||
delete(pl.Events, eventType.String())
|
||||
} else {
|
||||
pl.Events[eventType.String()] = level
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *PowerLevels) EnsureEventLevel(eventType Type, level int) bool {
|
||||
existingLevel := pl.GetEventLevel(eventType)
|
||||
if existingLevel != level {
|
||||
pl.SetEventLevel(eventType, level)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
MimeType string `json:"mimetype,omitempty"`
|
||||
ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
|
||||
ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"`
|
||||
ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"`
|
||||
Width int `json:"-"`
|
||||
Height int `json:"-"`
|
||||
Duration uint `json:"-"`
|
||||
Size int `json:"-"`
|
||||
}
|
||||
|
||||
type serializableFileInfo struct {
|
||||
MimeType string `json:"mimetype,omitempty"`
|
||||
ThumbnailInfo *serializableFileInfo `json:"thumbnail_info,omitempty"`
|
||||
ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"`
|
||||
|
||||
Width json.Number `json:"w,omitempty"`
|
||||
Height json.Number `json:"h,omitempty"`
|
||||
Duration json.Number `json:"duration,omitempty"`
|
||||
Size json.Number `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
func (sfi *serializableFileInfo) CopyFrom(fileInfo *FileInfo) *serializableFileInfo {
|
||||
if fileInfo == nil {
|
||||
return nil
|
||||
}
|
||||
*sfi = serializableFileInfo{
|
||||
MimeType: fileInfo.MimeType,
|
||||
ThumbnailURL: fileInfo.ThumbnailURL,
|
||||
ThumbnailInfo: (&serializableFileInfo{}).CopyFrom(fileInfo.ThumbnailInfo),
|
||||
}
|
||||
if fileInfo.Width > 0 {
|
||||
sfi.Width = json.Number(strconv.Itoa(fileInfo.Width))
|
||||
}
|
||||
if fileInfo.Height > 0 {
|
||||
sfi.Height = json.Number(strconv.Itoa(fileInfo.Height))
|
||||
}
|
||||
if fileInfo.Size > 0 {
|
||||
sfi.Size = json.Number(strconv.Itoa(fileInfo.Size))
|
||||
|
||||
}
|
||||
if fileInfo.Duration > 0 {
|
||||
sfi.Duration = json.Number(strconv.Itoa(int(fileInfo.Duration)))
|
||||
}
|
||||
return sfi
|
||||
}
|
||||
|
||||
func (sfi *serializableFileInfo) CopyTo(fileInfo *FileInfo) {
|
||||
*fileInfo = FileInfo{
|
||||
Width: int(numberToUint(sfi.Width)),
|
||||
Height: int(numberToUint(sfi.Height)),
|
||||
Size: int(numberToUint(sfi.Size)),
|
||||
Duration: numberToUint(sfi.Duration),
|
||||
MimeType: sfi.MimeType,
|
||||
ThumbnailURL: sfi.ThumbnailURL,
|
||||
}
|
||||
if sfi.ThumbnailInfo != nil {
|
||||
fileInfo.ThumbnailInfo = &FileInfo{}
|
||||
sfi.ThumbnailInfo.CopyTo(fileInfo.ThumbnailInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func (fileInfo *FileInfo) UnmarshalJSON(data []byte) error {
|
||||
sfi := &serializableFileInfo{}
|
||||
if err := json.Unmarshal(data, sfi); err != nil {
|
||||
return err
|
||||
}
|
||||
sfi.CopyTo(fileInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fileInfo *FileInfo) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((&serializableFileInfo{}).CopyFrom(fileInfo))
|
||||
}
|
||||
|
||||
func numberToUint(val json.Number) uint {
|
||||
f64, _ := val.Float64()
|
||||
if f64 > 0 {
|
||||
return uint(f64)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo {
|
||||
if fileInfo.ThumbnailInfo == nil {
|
||||
fileInfo.ThumbnailInfo = &FileInfo{}
|
||||
}
|
||||
return fileInfo.ThumbnailInfo
|
||||
}
|
||||
|
|
|
|||
53
event/member.go
Normal file
53
event/member.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// Membership is an enum specifying the membership state of a room member.
|
||||
type Membership string
|
||||
|
||||
func (ms Membership) IsInviteOrJoin() bool {
|
||||
return ms == MembershipJoin || ms == MembershipInvite
|
||||
}
|
||||
|
||||
func (ms Membership) IsLeaveOrBan() bool {
|
||||
return ms == MembershipLeave || ms == MembershipBan
|
||||
}
|
||||
|
||||
// The allowed membership states as specified in spec section 10.5.5.
|
||||
const (
|
||||
MembershipJoin Membership = "join"
|
||||
MembershipLeave Membership = "leave"
|
||||
MembershipInvite Membership = "invite"
|
||||
MembershipBan Membership = "ban"
|
||||
MembershipKnock Membership = "knock"
|
||||
)
|
||||
|
||||
// MemberEventContent represents the content of a m.room.member state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-member
|
||||
type MemberEventContent struct {
|
||||
Membership Membership `json:"membership"`
|
||||
AvatarURL id.ContentURIString `json:"avatar_url,omitempty"`
|
||||
Displayname string `json:"displayname,omitempty"`
|
||||
IsDirect bool `json:"is_direct,omitempty"`
|
||||
ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type ThirdPartyInvite struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
Signed struct {
|
||||
Token string `json:"token"`
|
||||
Signatures json.RawMessage `json:"signatures"`
|
||||
MXID string `json:"mxid"`
|
||||
}
|
||||
}
|
||||
197
event/message.go
Normal file
197
event/message.go
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"maunium.net/go/mautrix/crypto/attachment"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// MessageType is the sub-type of a m.room.message event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message-msgtypes
|
||||
type MessageType string
|
||||
|
||||
// Msgtypes
|
||||
const (
|
||||
MsgText MessageType = "m.text"
|
||||
MsgEmote MessageType = "m.emote"
|
||||
MsgNotice MessageType = "m.notice"
|
||||
MsgImage MessageType = "m.image"
|
||||
MsgLocation MessageType = "m.location"
|
||||
MsgVideo MessageType = "m.video"
|
||||
MsgAudio MessageType = "m.audio"
|
||||
MsgFile MessageType = "m.file"
|
||||
)
|
||||
|
||||
// Format specifies the format of the formatted_body in m.room.message events.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message-msgtypes
|
||||
type Format string
|
||||
|
||||
// Message formats
|
||||
const (
|
||||
FormatHTML Format = "org.matrix.custom.html"
|
||||
)
|
||||
|
||||
// RedactionEventContent represents the content of a m.room.redaction message event.
|
||||
//
|
||||
// The redacted event ID is still at the top level, but will move in a future room version.
|
||||
// See https://github.com/matrix-org/matrix-doc/pull/2244 and https://github.com/matrix-org/matrix-doc/pull/2174
|
||||
//
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-redaction
|
||||
type RedactionEventContent struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// ReactionEventContent represents the content of a m.reaction message event.
|
||||
// This is not yet in a spec release, see https://github.com/matrix-org/matrix-doc/pull/1849
|
||||
type ReactionEventContent struct {
|
||||
RelatesTo RelatesTo `json:"m.relates_to"`
|
||||
}
|
||||
|
||||
// MssageEventContent represents the content of a m.room.message event.
|
||||
//
|
||||
// It is also used to represent m.sticker events, as they are equivalent to m.room.message
|
||||
// with the exception of the msgtype field.
|
||||
//
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message
|
||||
type MessageEventContent struct {
|
||||
// Base m.room.message fields
|
||||
MsgType MessageType `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
|
||||
// Extra fields for text types
|
||||
Format Format `json:"format,omitempty"`
|
||||
FormattedBody string `json:"formatted_body,omitempty"`
|
||||
|
||||
// Extra fields for media types
|
||||
URL id.ContentURIString `json:"url,omitempty"`
|
||||
Info *FileInfo `json:"info,omitempty"`
|
||||
File *EncryptedFileInfo `json:"file,omitempty"`
|
||||
|
||||
// Edits and relations
|
||||
NewContent *MessageEventContent `json:"m.new_content,omitempty"`
|
||||
RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
|
||||
}
|
||||
|
||||
func (content *MessageEventContent) GetRelatesTo() *RelatesTo {
|
||||
if content.RelatesTo == nil {
|
||||
content.RelatesTo = &RelatesTo{}
|
||||
}
|
||||
return content.RelatesTo
|
||||
}
|
||||
|
||||
func (content *MessageEventContent) GetFile() *EncryptedFileInfo {
|
||||
if content.File == nil {
|
||||
content.File = &EncryptedFileInfo{}
|
||||
}
|
||||
return content.File
|
||||
}
|
||||
|
||||
func (content *MessageEventContent) GetInfo() *FileInfo {
|
||||
if content.Info == nil {
|
||||
content.Info = &FileInfo{}
|
||||
}
|
||||
return content.Info
|
||||
}
|
||||
|
||||
type EncryptedFileInfo struct {
|
||||
attachment.EncryptedFile
|
||||
URL id.ContentURIString
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
MimeType string `json:"mimetype,omitempty"`
|
||||
ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
|
||||
ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"`
|
||||
ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"`
|
||||
Width int `json:"-"`
|
||||
Height int `json:"-"`
|
||||
Duration int `json:"-"`
|
||||
Size int `json:"-"`
|
||||
}
|
||||
|
||||
type serializableFileInfo struct {
|
||||
MimeType string `json:"mimetype,omitempty"`
|
||||
ThumbnailInfo *serializableFileInfo `json:"thumbnail_info,omitempty"`
|
||||
ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"`
|
||||
|
||||
Width json.Number `json:"w,omitempty"`
|
||||
Height json.Number `json:"h,omitempty"`
|
||||
Duration json.Number `json:"duration,omitempty"`
|
||||
Size json.Number `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
func (sfi *serializableFileInfo) CopyFrom(fileInfo *FileInfo) *serializableFileInfo {
|
||||
if fileInfo == nil {
|
||||
return nil
|
||||
}
|
||||
*sfi = serializableFileInfo{
|
||||
MimeType: fileInfo.MimeType,
|
||||
ThumbnailURL: fileInfo.ThumbnailURL,
|
||||
ThumbnailInfo: (&serializableFileInfo{}).CopyFrom(fileInfo.ThumbnailInfo),
|
||||
}
|
||||
if fileInfo.Width > 0 {
|
||||
sfi.Width = json.Number(strconv.Itoa(fileInfo.Width))
|
||||
}
|
||||
if fileInfo.Height > 0 {
|
||||
sfi.Height = json.Number(strconv.Itoa(fileInfo.Height))
|
||||
}
|
||||
if fileInfo.Size > 0 {
|
||||
sfi.Size = json.Number(strconv.Itoa(fileInfo.Size))
|
||||
|
||||
}
|
||||
if fileInfo.Duration > 0 {
|
||||
sfi.Duration = json.Number(strconv.Itoa(int(fileInfo.Duration)))
|
||||
}
|
||||
return sfi
|
||||
}
|
||||
|
||||
func (sfi *serializableFileInfo) CopyTo(fileInfo *FileInfo) {
|
||||
*fileInfo = FileInfo{
|
||||
Width: numberToInt(sfi.Width),
|
||||
Height: numberToInt(sfi.Height),
|
||||
Size: numberToInt(sfi.Size),
|
||||
Duration: numberToInt(sfi.Duration),
|
||||
MimeType: sfi.MimeType,
|
||||
ThumbnailURL: sfi.ThumbnailURL,
|
||||
}
|
||||
if sfi.ThumbnailInfo != nil {
|
||||
fileInfo.ThumbnailInfo = &FileInfo{}
|
||||
sfi.ThumbnailInfo.CopyTo(fileInfo.ThumbnailInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func (fileInfo *FileInfo) UnmarshalJSON(data []byte) error {
|
||||
sfi := &serializableFileInfo{}
|
||||
if err := json.Unmarshal(data, sfi); err != nil {
|
||||
return err
|
||||
}
|
||||
sfi.CopyTo(fileInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fileInfo *FileInfo) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((&serializableFileInfo{}).CopyFrom(fileInfo))
|
||||
}
|
||||
|
||||
func numberToInt(val json.Number) int {
|
||||
f64, _ := val.Float64()
|
||||
if f64 > 0 {
|
||||
return int(f64)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo {
|
||||
if fileInfo.ThumbnailInfo == nil {
|
||||
fileInfo.ThumbnailInfo = &FileInfo{}
|
||||
}
|
||||
return fileInfo.ThumbnailInfo
|
||||
}
|
||||
133
event/message_test.go
Normal file
133
event/message_test.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
const invalidMessageEvent = `{
|
||||
"sender": "@tulir:maunium.net",
|
||||
"type": "m.room.message",
|
||||
"origin_server_ts": 1587252684192,
|
||||
"event_id": "$foo",
|
||||
"room_id": "!bar",
|
||||
"content": {
|
||||
"body": {
|
||||
"hmm": false
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
func TestMessageEventContent__ParseInvalid(t *testing.T) {
|
||||
var evt *event.Event
|
||||
err := json.Unmarshal([]byte(invalidMessageEvent), &evt)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, id.UserID("@tulir:maunium.net"), evt.Sender)
|
||||
assert.Equal(t, event.EventMessage, evt.Type)
|
||||
assert.Equal(t, int64(1587252684192), evt.Timestamp)
|
||||
assert.Equal(t, id.EventID("$foo"), evt.ID)
|
||||
assert.Equal(t, id.RoomID("!bar"), evt.RoomID)
|
||||
|
||||
err = evt.Content.ParseRaw(evt.Type)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
const messageEvent = `{
|
||||
"sender": "@tulir:maunium.net",
|
||||
"type": "m.room.message",
|
||||
"origin_server_ts": 1587252684192,
|
||||
"event_id": "$foo",
|
||||
"room_id": "!bar",
|
||||
"content": {
|
||||
"msgtype": "m.text",
|
||||
"body": "* **Hello**, World!",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "* <strong>Hello</strong>, World!",
|
||||
"m.new_content": {
|
||||
"msgtype": "m.text",
|
||||
"body": "**Hello**, World!",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<strong>Hello</strong>, World!"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
func TestMessageEventContent__ParseEdit(t *testing.T) {
|
||||
var evt *event.Event
|
||||
err := json.Unmarshal([]byte(messageEvent), &evt)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, id.UserID("@tulir:maunium.net"), evt.Sender)
|
||||
assert.Equal(t, event.EventMessage, evt.Type)
|
||||
assert.Equal(t, int64(1587252684192), evt.Timestamp)
|
||||
assert.Equal(t, id.EventID("$foo"), evt.ID)
|
||||
assert.Equal(t, id.RoomID("!bar"), evt.RoomID)
|
||||
|
||||
err = evt.Content.ParseRaw(evt.Type)
|
||||
assert.IsType(t, &event.MessageEventContent{}, evt.Content.Parsed)
|
||||
content := evt.Content.Parsed.(*event.MessageEventContent)
|
||||
assert.Equal(t, event.MsgText, content.MsgType)
|
||||
assert.Equal(t, event.MsgText, content.NewContent.MsgType)
|
||||
assert.Equal(t, "**Hello**, World!", content.NewContent.Body)
|
||||
assert.Equal(t, "<strong>Hello</strong>, World!", content.NewContent.FormattedBody)
|
||||
}
|
||||
|
||||
const imageMessageEvent = `{
|
||||
"sender": "@tulir:maunium.net",
|
||||
"type": "m.room.message",
|
||||
"origin_server_ts": 1587252684192,
|
||||
"event_id": "$foo",
|
||||
"room_id": "!bar",
|
||||
"content": {
|
||||
"msgtype": "m.image",
|
||||
"body": "image.png",
|
||||
"url": "mxc://example.com/image",
|
||||
"info": {
|
||||
"mimetype": "image/png",
|
||||
"w": 64,
|
||||
"h": 64,
|
||||
"size": 12345,
|
||||
"thumbnail_url": "mxc://example.com/image_thumb"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
func TestMessageEventContent__ParseMedia(t *testing.T) {
|
||||
var evt *event.Event
|
||||
err := json.Unmarshal([]byte(imageMessageEvent), &evt)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, id.UserID("@tulir:maunium.net"), evt.Sender)
|
||||
assert.Equal(t, event.EventMessage, evt.Type)
|
||||
assert.Equal(t, int64(1587252684192), evt.Timestamp)
|
||||
assert.Equal(t, id.EventID("$foo"), evt.ID)
|
||||
assert.Equal(t, id.RoomID("!bar"), evt.RoomID)
|
||||
|
||||
err = evt.Content.ParseRaw(evt.Type)
|
||||
assert.IsType(t, &event.MessageEventContent{}, evt.Content.Parsed)
|
||||
content := evt.Content.Parsed.(*event.MessageEventContent)
|
||||
fmt.Println(content)
|
||||
fmt.Println(content.Info)
|
||||
assert.Equal(t, event.MsgImage, content.MsgType)
|
||||
parsedURL, err := content.URL.Parse()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, id.ContentURI{Homeserver: "example.com", FileID: "image"}, parsedURL)
|
||||
assert.Nil(t, content.NewContent)
|
||||
assert.Equal(t, "image/png", content.GetInfo().MimeType)
|
||||
assert.EqualValues(t, 64, content.GetInfo().Width)
|
||||
assert.EqualValues(t, 64, content.GetInfo().Height)
|
||||
assert.EqualValues(t, 12345, content.GetInfo().Size)
|
||||
}
|
||||
128
event/powerlevels.go
Normal file
128
event/powerlevels.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// PowerLevelsEventContent represents the content of a m.room.power_levels state event content.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-power-levels
|
||||
type PowerLevelsEventContent struct {
|
||||
usersLock sync.RWMutex `json:"-"`
|
||||
Users map[id.UserID]int `json:"users"`
|
||||
UsersDefault int `json:"users_default"`
|
||||
|
||||
eventsLock sync.RWMutex `json:"-"`
|
||||
Events map[string]int `json:"events"`
|
||||
EventsDefault int `json:"events_default"`
|
||||
|
||||
StateDefaultPtr *int `json:"state_default,omitempty"`
|
||||
|
||||
InvitePtr *int `json:"invite,omitempty"`
|
||||
KickPtr *int `json:"kick,omitempty"`
|
||||
BanPtr *int `json:"ban,omitempty"`
|
||||
RedactPtr *int `json:"redact,omitempty"`
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) Invite() int {
|
||||
if pl.InvitePtr != nil {
|
||||
return *pl.InvitePtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) Kick() int {
|
||||
if pl.KickPtr != nil {
|
||||
return *pl.KickPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) Ban() int {
|
||||
if pl.BanPtr != nil {
|
||||
return *pl.BanPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) Redact() int {
|
||||
if pl.RedactPtr != nil {
|
||||
return *pl.RedactPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) StateDefault() int {
|
||||
if pl.StateDefaultPtr != nil {
|
||||
return *pl.StateDefaultPtr
|
||||
}
|
||||
return 50
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) GetUserLevel(userID id.UserID) int {
|
||||
pl.usersLock.RLock()
|
||||
defer pl.usersLock.RUnlock()
|
||||
level, ok := pl.Users[userID]
|
||||
if !ok {
|
||||
return pl.UsersDefault
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) SetUserLevel(userID id.UserID, level int) {
|
||||
pl.usersLock.Lock()
|
||||
defer pl.usersLock.Unlock()
|
||||
if level == pl.UsersDefault {
|
||||
delete(pl.Users, userID)
|
||||
} else {
|
||||
pl.Users[userID] = level
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) EnsureUserLevel(userID id.UserID, level int) bool {
|
||||
existingLevel := pl.GetUserLevel(userID)
|
||||
if existingLevel != level {
|
||||
pl.SetUserLevel(userID, level)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) GetEventLevel(eventType Type) int {
|
||||
pl.eventsLock.RLock()
|
||||
defer pl.eventsLock.RUnlock()
|
||||
level, ok := pl.Events[eventType.String()]
|
||||
if !ok {
|
||||
if eventType.IsState() {
|
||||
return pl.StateDefault()
|
||||
}
|
||||
return pl.EventsDefault
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) SetEventLevel(eventType Type, level int) {
|
||||
pl.eventsLock.Lock()
|
||||
defer pl.eventsLock.Unlock()
|
||||
if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
|
||||
delete(pl.Events, eventType.String())
|
||||
} else {
|
||||
pl.Events[eventType.String()] = level
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *PowerLevelsEventContent) EnsureEventLevel(eventType Type, level int) bool {
|
||||
existingLevel := pl.GetEventLevel(eventType)
|
||||
if existingLevel != level {
|
||||
pl.SetEventLevel(eventType, level)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ func TrimReplyFallbackText(text string) string {
|
|||
return strings.TrimSpace(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func (content *Content) RemoveReplyFallback() {
|
||||
func (content *MessageEventContent) RemoveReplyFallback() {
|
||||
if len(content.GetReplyTo()) > 0 {
|
||||
if content.Format == FormatHTML {
|
||||
content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody)
|
||||
|
|
@ -43,7 +43,7 @@ func (content *Content) RemoveReplyFallback() {
|
|||
}
|
||||
}
|
||||
|
||||
func (content *Content) GetReplyTo() id.EventID {
|
||||
func (content *MessageEventContent) GetReplyTo() id.EventID {
|
||||
if content.RelatesTo != nil && content.RelatesTo.Type == RelReference {
|
||||
return content.RelatesTo.EventID
|
||||
}
|
||||
|
|
@ -53,9 +53,13 @@ func (content *Content) GetReplyTo() id.EventID {
|
|||
const ReplyFormat = `<mx-reply><blockquote><a href="https://matrix.to/#/%s/%s">In reply to</a> <a href="https://matrix.to/#/%s">%s</a><br>%s</blockquote></mx-reply>`
|
||||
|
||||
func (evt *Event) GenerateReplyFallbackHTML() string {
|
||||
body := evt.Content.FormattedBody
|
||||
parsedContent, ok := evt.Content.Parsed.(MessageEventContent)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
body := parsedContent.FormattedBody
|
||||
if len(body) == 0 {
|
||||
body = html.EscapeString(evt.Content.Body)
|
||||
body = html.EscapeString(parsedContent.Body)
|
||||
}
|
||||
|
||||
senderDisplayName := evt.Sender
|
||||
|
|
@ -64,7 +68,11 @@ func (evt *Event) GenerateReplyFallbackHTML() string {
|
|||
}
|
||||
|
||||
func (evt *Event) GenerateReplyFallbackText() string {
|
||||
body := evt.Content.Body
|
||||
parsedContent, ok := evt.Content.Parsed.(MessageEventContent)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
body := parsedContent.Body
|
||||
lines := strings.Split(strings.TrimSpace(body), "\n")
|
||||
firstLine, lines := lines[0], lines[1:]
|
||||
|
||||
|
|
@ -79,7 +87,7 @@ func (evt *Event) GenerateReplyFallbackText() string {
|
|||
return fallbackText.String()
|
||||
}
|
||||
|
||||
func (content *Content) SetReply(inReplyTo *Event) {
|
||||
func (content *MessageEventContent) SetReply(inReplyTo *Event) {
|
||||
content.RelatesTo = &RelatesTo{
|
||||
EventID: inReplyTo.ID,
|
||||
Type: RelReference,
|
||||
|
|
|
|||
113
event/state.go
Normal file
113
event/state.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// CanonicalAliasEventContent represents the content of a m.room.canonical_alias state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-canonical-alias
|
||||
type CanonicalAliasEventContent struct {
|
||||
Alias id.RoomAlias `json:"alias"`
|
||||
|
||||
// This field isn't in a spec release yet.
|
||||
// MSC2432: https://github.com/matrix-org/matrix-doc/pull/2432
|
||||
AltAliases []string `json:"alt_aliases,omitempty"`
|
||||
}
|
||||
|
||||
// RoomNameEventContent represents the content of a m.room.name state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-name
|
||||
type RoomNameEventContent struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// RoomAvatarEventContent represents the content of a m.room.avatar state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-avatar
|
||||
type RoomAvatarEventContent struct {
|
||||
URL id.ContentURI `json:"url"`
|
||||
}
|
||||
|
||||
// TopicEventContent represents the content of a m.room.topic state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-topic
|
||||
type TopicEventContent struct {
|
||||
Topic string `json:"topic"`
|
||||
}
|
||||
|
||||
// TombstoneEventContent represents the content of a m.room.tombstone state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-tombstone
|
||||
type TombstoneEventContent struct {
|
||||
Body string `json:"body"`
|
||||
ReplacementRoom id.RoomID `json:"replacement_room"`
|
||||
}
|
||||
|
||||
// CreateEventContent represents the content of a m.room.create state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-create
|
||||
type CreateEventContent struct {
|
||||
Creator id.UserID `json:"creator"`
|
||||
Federate bool `json:"m.federate,omitempty"`
|
||||
RoomVersion string `json:"version,omitempty"`
|
||||
Predecessor struct {
|
||||
RoomID id.RoomID `json:"room_id"`
|
||||
EventID id.EventID `json:"event_id"`
|
||||
} `json:"predecessor"`
|
||||
}
|
||||
|
||||
// JoinRule specifies how open a room is to new members.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-join-rules
|
||||
type JoinRule string
|
||||
|
||||
const (
|
||||
JoinRulePublic JoinRule = "public"
|
||||
JoinRuleKnock JoinRule = "knock"
|
||||
JoinRuleInvite JoinRule = "invite"
|
||||
JoinRulePrivate JoinRule = "private"
|
||||
)
|
||||
|
||||
// JoinRulesEventContent represents the content of a m.room.join_rules state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-join-rules
|
||||
type JoinRulesEventContent struct {
|
||||
JoinRule JoinRule `json:"join_rule"`
|
||||
}
|
||||
|
||||
// PinnedEventsEventContent represents the content of a m.room.pinned_events state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-pinned-events
|
||||
type PinnedEventsEventContent struct {
|
||||
Pinned []id.EventID `json:"pinned"`
|
||||
}
|
||||
|
||||
// HistoryVisibility specifies who can see new messages.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-history-visibility
|
||||
type HistoryVisibility string
|
||||
|
||||
const (
|
||||
HistoryVisibilityInvited HistoryVisibility = "invited"
|
||||
HistoryVisibilityJoined HistoryVisibility = "joined"
|
||||
HistoryVisibilityShared HistoryVisibility = "shared"
|
||||
HistoryVisibilityWorldReadable HistoryVisibility = "world_readable"
|
||||
)
|
||||
|
||||
// HistoryVisibilityEventContent represents the content of a m.room.history_visibility state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-history-visibility
|
||||
type HistoryVisibilityEventContent struct {
|
||||
HistoryVisibility HistoryVisibility `json:"history_visibility"`
|
||||
}
|
||||
|
||||
// GuestAccess specifies whether or not guest accounts can join.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-guest-access
|
||||
type GuestAccess string
|
||||
|
||||
const (
|
||||
GuestAccessCanJoin GuestAccess = "can_join"
|
||||
GuestAccessForbidden GuestAccess = "forbidden"
|
||||
)
|
||||
|
||||
// GuestAccessEventContent represents the content of a m.room.guest_access state event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-guest-access
|
||||
type GuestAccessEventContent struct {
|
||||
GuestAccess GuestAccess `json:"guest_access"`
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ func (et *Type) GuessClass() EventTypeClass {
|
|||
return AccountDataEventType
|
||||
case EventRedaction.Type, EventMessage.Type, EventEncrypted.Type, EventReaction.Type, EventSticker.Type:
|
||||
return MessageEventType
|
||||
case ToDeviceNewDevice.Type, ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type:
|
||||
case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type:
|
||||
return ToDeviceEventType
|
||||
default:
|
||||
return UnknownEventType
|
||||
|
|
@ -97,18 +97,20 @@ func (et *Type) String() string {
|
|||
|
||||
// State events
|
||||
var (
|
||||
StateAliases = Type{"m.room.aliases", StateEventType}
|
||||
StateCanonicalAlias = Type{"m.room.canonical_alias", StateEventType}
|
||||
StateCreate = Type{"m.room.create", StateEventType}
|
||||
StateJoinRules = Type{"m.room.join_rules", StateEventType}
|
||||
StateMember = Type{"m.room.member", StateEventType}
|
||||
StatePowerLevels = Type{"m.room.power_levels", StateEventType}
|
||||
StateRoomName = Type{"m.room.name", StateEventType}
|
||||
StateTopic = Type{"m.room.topic", StateEventType}
|
||||
StateRoomAvatar = Type{"m.room.avatar", StateEventType}
|
||||
StatePinnedEvents = Type{"m.room.pinned_events", StateEventType}
|
||||
StateTombstone = Type{"m.room.tombstone", StateEventType}
|
||||
StateEncryption = Type{"m.room.encryption", StateEventType}
|
||||
StateAliases = Type{"m.room.aliases", StateEventType}
|
||||
StateCanonicalAlias = Type{"m.room.canonical_alias", StateEventType}
|
||||
StateCreate = Type{"m.room.create", StateEventType}
|
||||
StateJoinRules = Type{"m.room.join_rules", StateEventType}
|
||||
StateHistoryVisibility = Type{"m.room.history_visibility", StateEventType}
|
||||
StateGuestAccess = Type{"m.room.guest_access", StateEventType}
|
||||
StateMember = Type{"m.room.member", StateEventType}
|
||||
StatePowerLevels = Type{"m.room.power_levels", StateEventType}
|
||||
StateRoomName = Type{"m.room.name", StateEventType}
|
||||
StateTopic = Type{"m.room.topic", StateEventType}
|
||||
StateRoomAvatar = Type{"m.room.avatar", StateEventType}
|
||||
StatePinnedEvents = Type{"m.room.pinned_events", StateEventType}
|
||||
StateTombstone = Type{"m.room.tombstone", StateEventType}
|
||||
StateEncryption = Type{"m.room.encryption", StateEventType}
|
||||
)
|
||||
|
||||
// Message events
|
||||
|
|
@ -129,15 +131,16 @@ var (
|
|||
|
||||
// Account data events
|
||||
var (
|
||||
AccountDataDirectChats = Type{"m.direct", AccountDataEventType}
|
||||
AccountDataPushRules = Type{"m.push_rules", AccountDataEventType}
|
||||
AccountDataRoomTags = Type{"m.tag", AccountDataEventType}
|
||||
AccountDataDirectChats = Type{"m.direct", AccountDataEventType}
|
||||
AccountDataPushRules = Type{"m.push_rules", AccountDataEventType}
|
||||
AccountDataRoomTags = Type{"m.tag", AccountDataEventType}
|
||||
AccountDataFullyRead = Type{"m.fully_read", AccountDataEventType}
|
||||
AccountDataIgnoredUserList = Type{"m.ignored_user_list", AccountDataEventType}
|
||||
)
|
||||
|
||||
// Device-to-device events
|
||||
var (
|
||||
ToDeviceNewDevice = Type{"m.new_device", ToDeviceEventType}
|
||||
ToDeviceRoomKey = Type{"m.room_key", ToDeviceEventType}
|
||||
ToDeviceRoomKeyRequest = Type{"m.room_key_request", ToDeviceEventType}
|
||||
ToDeviceRoomKey = Type{"m.room_key", ToDeviceEventType}
|
||||
ToDeviceRoomKeyRequest = Type{"m.room_key_request", ToDeviceEventType}
|
||||
ToDeviceForwardedRoomKey = Type{"m.forwarded_room_key", ToDeviceEventType}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const (
|
|||
)
|
||||
|
||||
//Filter is used by clients to specify how the server should filter responses to e.g. sync requests
|
||||
//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering
|
||||
//Specified by: https://matrix.org/docs/spec/client_server/r0.6.0.html#filtering
|
||||
type Filter struct {
|
||||
AccountData FilterPart `json:"account_data,omitempty"`
|
||||
EventFields []string `json:"event_fields,omitempty"`
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ var bfhtml = blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
|
|||
var Renderer = blackfriday.WithRenderer(bfhtml)
|
||||
var NoHTMLRenderer = blackfriday.WithRenderer(&EscapingRenderer{bfhtml})
|
||||
|
||||
func RenderMarkdown(text string, allowMarkdown, allowHTML bool) event.Content {
|
||||
func RenderMarkdown(text string, allowMarkdown, allowHTML bool) event.MessageEventContent {
|
||||
htmlBody := text
|
||||
|
||||
if allowMarkdown {
|
||||
|
|
@ -58,7 +58,7 @@ func RenderMarkdown(text string, allowMarkdown, allowHTML bool) event.Content {
|
|||
text = HTMLToText(htmlBody)
|
||||
|
||||
if htmlBody != text {
|
||||
return event.Content{
|
||||
return event.MessageEventContent{
|
||||
FormattedBody: htmlBody,
|
||||
Format: event.FormatHTML,
|
||||
MsgType: event.MsgText,
|
||||
|
|
@ -67,7 +67,7 @@ func RenderMarkdown(text string, allowMarkdown, allowHTML bool) event.Content {
|
|||
}
|
||||
}
|
||||
|
||||
return event.Content{
|
||||
return event.MessageEventContent{
|
||||
MsgType: event.MsgText,
|
||||
Body: text,
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -3,6 +3,8 @@ module maunium.net/go/mautrix
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/russross/blackfriday/v2 v2.0.1
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
)
|
||||
|
|
|
|||
14
go.sum
14
go.sum
|
|
@ -1,4 +1,15 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
|
||||
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
|
@ -6,3 +17,6 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3ob
|
|||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@ type PushCondition struct {
|
|||
var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$")
|
||||
|
||||
// Match checks if this condition is fulfilled for the given event in the given room.
|
||||
func (cond *PushCondition) Match(room Room, event *event.Event) bool {
|
||||
func (cond *PushCondition) Match(room Room, evt *event.Event) bool {
|
||||
switch cond.Kind {
|
||||
case KindEventMatch:
|
||||
return cond.matchValue(room, event)
|
||||
return cond.matchValue(room, evt)
|
||||
case KindContainsDisplayName:
|
||||
return cond.matchDisplayName(room, event)
|
||||
return cond.matchDisplayName(room, evt)
|
||||
case KindRoomMemberCount:
|
||||
return cond.matchMemberCount(room)
|
||||
default:
|
||||
|
|
@ -62,7 +62,7 @@ func (cond *PushCondition) Match(room Room, event *event.Event) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (cond *PushCondition) matchValue(room Room, event *event.Event) bool {
|
||||
func (cond *PushCondition) matchValue(room Room, evt *event.Event) bool {
|
||||
index := strings.IndexRune(cond.Key, '.')
|
||||
key := cond.Key
|
||||
subkey := ""
|
||||
|
|
@ -78,31 +78,35 @@ func (cond *PushCondition) matchValue(room Room, event *event.Event) bool {
|
|||
|
||||
switch key {
|
||||
case "type":
|
||||
return pattern.MatchString(event.Type.String())
|
||||
return pattern.MatchString(evt.Type.String())
|
||||
case "sender":
|
||||
return pattern.MatchString(string(event.Sender))
|
||||
return pattern.MatchString(string(evt.Sender))
|
||||
case "room_id":
|
||||
return pattern.MatchString(string(event.RoomID))
|
||||
return pattern.MatchString(string(evt.RoomID))
|
||||
case "state_key":
|
||||
if event.StateKey == nil {
|
||||
if evt.StateKey == nil {
|
||||
return cond.Pattern == ""
|
||||
}
|
||||
return pattern.MatchString(*event.StateKey)
|
||||
return pattern.MatchString(*evt.StateKey)
|
||||
case "content":
|
||||
val, _ := event.Content.Raw[subkey].(string)
|
||||
val, _ := evt.Content.Raw[subkey].(string)
|
||||
return pattern.MatchString(val)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (cond *PushCondition) matchDisplayName(room Room, event *event.Event) bool {
|
||||
func (cond *PushCondition) matchDisplayName(room Room, evt *event.Event) bool {
|
||||
displayname := room.GetOwnDisplayname()
|
||||
if len(displayname) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
msg := event.Content.Body
|
||||
msg, ok := evt.Content.Raw["body"].(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
isAcceptable := func(r uint8) bool {
|
||||
return unicode.IsSpace(rune(r)) || unicode.IsPunct(rune(r))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func init() {
|
|||
}
|
||||
|
||||
type PushRuleCollection interface {
|
||||
GetActions(room Room, event *event.Event) PushActionArray
|
||||
GetActions(room Room, evt *event.Event) PushActionArray
|
||||
}
|
||||
|
||||
type PushRuleArray []*PushRule
|
||||
|
|
@ -32,9 +32,9 @@ func (rules PushRuleArray) SetType(typ PushRuleType) PushRuleArray {
|
|||
return rules
|
||||
}
|
||||
|
||||
func (rules PushRuleArray) GetActions(room Room, event *event.Event) PushActionArray {
|
||||
func (rules PushRuleArray) GetActions(room Room, evt *event.Event) PushActionArray {
|
||||
for _, rule := range rules {
|
||||
if !rule.Match(room, event) {
|
||||
if !rule.Match(room, evt) {
|
||||
continue
|
||||
}
|
||||
return rule.Actions
|
||||
|
|
@ -59,16 +59,16 @@ func (rules PushRuleArray) SetTypeAndMap(typ PushRuleType) PushRuleMap {
|
|||
return data
|
||||
}
|
||||
|
||||
func (ruleMap PushRuleMap) GetActions(room Room, event *event.Event) PushActionArray {
|
||||
func (ruleMap PushRuleMap) GetActions(room Room, evt *event.Event) PushActionArray {
|
||||
var rule *PushRule
|
||||
var found bool
|
||||
switch ruleMap.Type {
|
||||
case RoomRule:
|
||||
rule, found = ruleMap.Map[string(event.RoomID)]
|
||||
rule, found = ruleMap.Map[string(evt.RoomID)]
|
||||
case SenderRule:
|
||||
rule, found = ruleMap.Map[string(event.Sender)]
|
||||
rule, found = ruleMap.Map[string(evt.Sender)]
|
||||
}
|
||||
if found && rule.Match(room, event) {
|
||||
if found && rule.Match(room, evt) {
|
||||
return rule.Actions
|
||||
}
|
||||
return nil
|
||||
|
|
@ -114,37 +114,41 @@ type PushRule struct {
|
|||
Pattern string `json:"pattern,omitempty"`
|
||||
}
|
||||
|
||||
func (rule *PushRule) Match(room Room, event *event.Event) bool {
|
||||
func (rule *PushRule) Match(room Room, evt *event.Event) bool {
|
||||
if !rule.Enabled {
|
||||
return false
|
||||
}
|
||||
switch rule.Type {
|
||||
case OverrideRule, UnderrideRule:
|
||||
return rule.matchConditions(room, event)
|
||||
return rule.matchConditions(room, evt)
|
||||
case ContentRule:
|
||||
return rule.matchPattern(room, event)
|
||||
return rule.matchPattern(room, evt)
|
||||
case RoomRule:
|
||||
return id.RoomID(rule.RuleID) == event.RoomID
|
||||
return id.RoomID(rule.RuleID) == evt.RoomID
|
||||
case SenderRule:
|
||||
return id.UserID(rule.RuleID) == event.Sender
|
||||
return id.UserID(rule.RuleID) == evt.Sender
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (rule *PushRule) matchConditions(room Room, event *event.Event) bool {
|
||||
func (rule *PushRule) matchConditions(room Room, evt *event.Event) bool {
|
||||
for _, cond := range rule.Conditions {
|
||||
if !cond.Match(room, event) {
|
||||
if !cond.Match(room, evt) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (rule *PushRule) matchPattern(room Room, event *event.Event) bool {
|
||||
func (rule *PushRule) matchPattern(room Room, evt *event.Event) bool {
|
||||
pattern, err := glob.Compile(rule.Pattern)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return pattern.MatchString(event.Content.Body)
|
||||
msg, ok := evt.Content.Raw["body"].(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return pattern.MatchString(msg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ var DefaultPushActions = PushActionArray{&PushAction{Action: ActionDontNotify}}
|
|||
// GetActions matches the given event against all of the push rule
|
||||
// collections in this push ruleset in the order of priority as
|
||||
// specified in spec section 11.12.1.4.
|
||||
func (rs *PushRuleset) GetActions(room Room, event *event.Event) (match PushActionArray) {
|
||||
func (rs *PushRuleset) GetActions(room Room, evt *event.Event) (match PushActionArray) {
|
||||
// Add push rule collections to array in priority order
|
||||
arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride}
|
||||
// Loop until one of the push rule collections matches the room/event combo.
|
||||
|
|
@ -78,7 +78,7 @@ func (rs *PushRuleset) GetActions(room Room, event *event.Event) (match PushActi
|
|||
if pra == nil {
|
||||
continue
|
||||
}
|
||||
if match = pra.GetActions(room, event); match != nil {
|
||||
if match = pra.GetActions(room, evt); match != nil {
|
||||
// Match found, return it.
|
||||
return
|
||||
}
|
||||
|
|
|
|||
22
responses.go
22
responses.go
|
|
@ -1,8 +1,6 @@
|
|||
package mautrix
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
|
@ -159,13 +157,13 @@ type RespSync struct {
|
|||
NextBatch string `json:"next_batch"`
|
||||
|
||||
AccountData struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"account_data"`
|
||||
Presence struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"presence"`
|
||||
ToDevice struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"to_device"`
|
||||
|
||||
DeviceLists struct {
|
||||
|
|
@ -178,10 +176,10 @@ type RespSync struct {
|
|||
Leave map[id.RoomID]struct {
|
||||
Summary LazyLoadSummary `json:"summary"`
|
||||
State struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"state"`
|
||||
Timeline struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
Limited bool `json:"limited"`
|
||||
PrevBatch string `json:"prev_batch"`
|
||||
} `json:"timeline"`
|
||||
|
|
@ -189,24 +187,24 @@ type RespSync struct {
|
|||
Join map[id.RoomID]struct {
|
||||
Summary LazyLoadSummary `json:"summary"`
|
||||
State struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"state"`
|
||||
Timeline struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
Limited bool `json:"limited"`
|
||||
PrevBatch string `json:"prev_batch"`
|
||||
} `json:"timeline"`
|
||||
Ephemeral struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"ephemeral"`
|
||||
AccountData struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"account_data"`
|
||||
} `json:"join"`
|
||||
Invite map[id.RoomID]struct {
|
||||
Summary LazyLoadSummary `json:"summary"`
|
||||
State struct {
|
||||
Events []json.RawMessage `json:"events"`
|
||||
Events []*event.Event `json:"events"`
|
||||
} `json:"invite_state"`
|
||||
} `json:"invite"`
|
||||
} `json:"rooms"`
|
||||
|
|
|
|||
5
room.go
5
room.go
|
|
@ -34,7 +34,10 @@ func (room Room) GetMembershipState(userID id.UserID) event.Membership {
|
|||
state := event.MembershipLeave
|
||||
evt := room.GetStateEvent(event.StateMember, string(userID))
|
||||
if evt != nil {
|
||||
state = evt.Content.Membership
|
||||
membership, ok := evt.Content.Raw["membership"].(string)
|
||||
if ok {
|
||||
state = event.Membership(membership)
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
|
|
|||
68
sync.go
68
sync.go
|
|
@ -1,9 +1,7 @@
|
|||
package mautrix
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
|
|
@ -20,7 +18,7 @@ type Syncer interface {
|
|||
// OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently.
|
||||
OnFailedSync(res *RespSync, err error) (time.Duration, error)
|
||||
// GetFilterJSON for the given user ID. NOT the filter ID.
|
||||
GetFilterJSON(userID id.UserID) json.RawMessage
|
||||
GetFilterJSON(userID id.UserID) *Filter
|
||||
}
|
||||
|
||||
// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively
|
||||
|
|
@ -44,17 +42,6 @@ func NewDefaultSyncer(userID id.UserID, store Storer) *DefaultSyncer {
|
|||
}
|
||||
}
|
||||
|
||||
func parseEvent(roomID id.RoomID, data json.RawMessage) *event.Event {
|
||||
event := &event.Event{}
|
||||
err := json.Unmarshal(data, event)
|
||||
if err != nil {
|
||||
// TODO add separate handler for these
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Failed to unmarshal event: %v\n%s\n", err, string(data))
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of
|
||||
// unrepeating events. Returns a fatal error if a listener panics.
|
||||
func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) {
|
||||
|
|
@ -70,38 +57,26 @@ func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error)
|
|||
|
||||
for roomID, roomData := range res.Rooms.Join {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, data := range roomData.State.Events {
|
||||
event := parseEvent(roomID, data)
|
||||
if event != nil {
|
||||
room.UpdateState(event)
|
||||
s.notifyListeners(event)
|
||||
}
|
||||
for _, evt := range roomData.State.Events {
|
||||
room.UpdateState(evt)
|
||||
s.notifyListeners(evt)
|
||||
}
|
||||
for _, data := range roomData.Timeline.Events {
|
||||
event := parseEvent(roomID, data)
|
||||
if event != nil {
|
||||
s.notifyListeners(event)
|
||||
}
|
||||
for _, evt := range roomData.Timeline.Events {
|
||||
s.notifyListeners(evt)
|
||||
}
|
||||
}
|
||||
for roomID, roomData := range res.Rooms.Invite {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, data := range roomData.State.Events {
|
||||
event := parseEvent(roomID, data)
|
||||
if event != nil {
|
||||
room.UpdateState(event)
|
||||
s.notifyListeners(event)
|
||||
}
|
||||
for _, evt := range roomData.State.Events {
|
||||
room.UpdateState(evt)
|
||||
s.notifyListeners(evt)
|
||||
}
|
||||
}
|
||||
for roomID, roomData := range res.Rooms.Leave {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, data := range roomData.Timeline.Events {
|
||||
event := parseEvent(roomID, data)
|
||||
if event.StateKey != nil {
|
||||
room.UpdateState(event)
|
||||
s.notifyListeners(event)
|
||||
}
|
||||
for _, evt := range roomData.Timeline.Events {
|
||||
room.UpdateState(evt)
|
||||
s.notifyListeners(evt)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
@ -132,11 +107,10 @@ func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool
|
|||
// TODO: We probably want to process messages from after the last join event in the timeline.
|
||||
for roomID, roomData := range resp.Rooms.Join {
|
||||
for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
|
||||
evtData := roomData.Timeline.Events[i]
|
||||
// TODO this is horribly inefficient since it's also parsed in ProcessResponse
|
||||
e := parseEvent(roomID, evtData)
|
||||
if e != nil && e.Type == event.StateMember && e.GetStateKey() == string(s.UserID) {
|
||||
if e.Content.Membership == "join" {
|
||||
evt := roomData.Timeline.Events[i]
|
||||
if evt.Type == event.StateMember && evt.GetStateKey() == string(s.UserID) {
|
||||
membership, _ := evt.Content.Raw["membership"].(string)
|
||||
if membership == "join" {
|
||||
_, ok := resp.Rooms.Join[roomID]
|
||||
if !ok {
|
||||
continue
|
||||
|
|
@ -177,6 +151,12 @@ func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, e
|
|||
}
|
||||
|
||||
// GetFilterJSON returns a filter with a timeline limit of 50.
|
||||
func (s *DefaultSyncer) GetFilterJSON(userID id.UserID) json.RawMessage {
|
||||
return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
|
||||
func (s *DefaultSyncer) GetFilterJSON(userID id.UserID) *Filter {
|
||||
return &Filter{
|
||||
Room: RoomFilter{
|
||||
Timeline: FilterPart{
|
||||
Limit: 50,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue