Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion federationclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (ac *FederationClient) MakeJoin(
if len(roomVersions) > 0 {
var vqs []string
for _, v := range roomVersions {
vqs = append(vqs, fmt.Sprintf("ver=%s", v))
vqs = append(vqs, fmt.Sprintf("ver=%s", url.QueryEscape(string(v))))
}
versionQueryString = "?" + strings.Join(vqs, "&")
}
Expand Down Expand Up @@ -323,6 +323,31 @@ func (ac *FederationClient) LookupMissingEvents(
return
}

// Peek starts a peek on a remote server
func (ac *FederationClient) Peek(
ctx context.Context, s ServerName, roomID, peekID string,
roomVersions []RoomVersion,
) (res RespPeek, err error) {
versionQueryString := ""
if len(roomVersions) > 0 {
var vqs []string
for _, v := range roomVersions {
vqs = append(vqs, fmt.Sprintf("ver=%s", url.QueryEscape(string(v))))
}
versionQueryString = "?" + strings.Join(vqs, "&")
}
path := federationPathPrefixV1 + "/peek/" +
url.PathEscape(roomID) + "/" +
url.PathEscape(peekID) + versionQueryString
req := NewFederationRequest("PUT", s, path)
var empty struct{}
if err = req.SetContent(empty); err != nil {
return
}
err = ac.doRequest(ctx, req, &res)
return
}

// LookupRoomAlias looks up a room alias hosted on the remote server.
// The domain part of the roomAlias must match the name of the server it is
// being looked up on.
Expand Down
82 changes: 82 additions & 0 deletions federationtypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ type RespState struct {
AuthEvents []Event `json:"auth_chain"`
}

// A RespPeek is the content of a response to GET /_matrix/federation/v1/peek/{roomID}/{peekID}
type RespPeek struct {
// How often should we renew the peek?
RenewalInterval int64 `json:"renewal_interval"`
// A list of events giving the state of the room at the point of the request
StateEvents []Event `json:"state"`
// A list of events needed to authenticate the state events.
AuthEvents []Event `json:"auth_chain"`
// The room version that we're trying to peek.
RoomVersion RoomVersion `json:"room_version"`
// The ID of the event whose state snapshot this is - i.e. the
// most recent forward extremity in the room.
LatestEvent Event `json:"latest_event"`
}

// MissingEvents represents a request for missing events.
// https://matrix.org/docs/spec/server_server/r0.1.3#post-matrix-federation-v1-get-missing-events-roomid
type MissingEvents struct {
Expand Down Expand Up @@ -256,6 +271,58 @@ func (r *RespMissingEvents) UnmarshalJSON(data []byte) error {
return nil
}

// MarshalJSON implements json.Marshaller
func (r RespPeek) MarshalJSON() ([]byte, error) {
if len(r.StateEvents) == 0 {
r.StateEvents = []Event{}
}
if len(r.AuthEvents) == 0 {
r.AuthEvents = []Event{}
}
return json.Marshal(r)
}

// UnmarshalJSON implements json.Unmarshaller
func (r *RespPeek) UnmarshalJSON(data []byte) error {
r.AuthEvents = []Event{}
r.StateEvents = []Event{}
var intermediate struct {
RoomVersion RoomVersion `json:"room_version"`
StateEvents []json.RawMessage `json:"state"`
AuthEvents []json.RawMessage `json:"auth_chain"`
RenewalInterval int64 `json:"renewal_interval"`
LatestEvent json.RawMessage `json:"latest_event"`
}
if err := json.Unmarshal(data, &intermediate); err != nil {
return fmt.Errorf("RespPeek UnmarshalJSON(intermediate): %w", err)
}
r.RoomVersion = intermediate.RoomVersion
r.RenewalInterval = intermediate.RenewalInterval
if _, err := r.RoomVersion.EventFormat(); err != nil {
return fmt.Errorf("RespPeek UnmarshalJSON(roomversion): %w", err)
}
for _, raw := range intermediate.AuthEvents {
event, err := NewEventFromUntrustedJSON([]byte(raw), r.RoomVersion)
if err != nil {
return fmt.Errorf("RespPeek UnmarshalJSON(AuthEvent): %w", err)
}
r.AuthEvents = append(r.AuthEvents, event)
}
for _, raw := range intermediate.StateEvents {
event, err := NewEventFromUntrustedJSON([]byte(raw), r.RoomVersion)
if err != nil {
return fmt.Errorf("RespPeek UnmarshalJSON(StateEvent): %w", err)
}
r.StateEvents = append(r.StateEvents, event)
}
latestEvent, err := NewEventFromUntrustedJSON([]byte(intermediate.LatestEvent), r.RoomVersion)
if err != nil {
return fmt.Errorf("RespPeek UnmarshalJSON(StateEvent): %w", err)
}
r.LatestEvent = latestEvent
return nil
}

// MarshalJSON implements json.Marshaller
func (r RespState) MarshalJSON() ([]byte, error) {
if len(r.StateEvents) == 0 {
Expand Down Expand Up @@ -533,6 +600,21 @@ func (r *RespSendJoin) UnmarshalJSON(data []byte) error {
return nil
}

// ToRespState returns a new RespState with the same data from the given RespPeek
func (r RespPeek) ToRespState() RespState {
if len(r.StateEvents) == 0 {
r.StateEvents = []Event{}
}
if len(r.AuthEvents) == 0 {
r.AuthEvents = []Event{}
}
return RespState{
roomVersion: r.RoomVersion,
StateEvents: r.StateEvents,
AuthEvents: r.AuthEvents,
}
}

type respSendJoinFields struct {
StateEvents []Event `json:"state"`
AuthEvents []Event `json:"auth_chain"`
Expand Down